You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 5060ac4
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: docs/ARCHITECTURE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -131,7 +131,7 @@ A worker deny-on-ask registers a harness-owned denied-call envelope (`src/permis
131
131
- Bootstrap consumes the shared session assembly (`src/session/assemble-runtime.ts`, layered over `src/session/runtime-assembly.ts`) instead of hand-wiring a second copy. Intentional deltas vs TUI:
132
132
- No workflow controller (`isWorkflowActive` is always false)
133
133
- Non-interactive permission gate by default
134
-
-`ask_operator` is unmounted when stdin/stdout are not TTYs (no cancel stub on the wire); TTY exec still prompts on stdin
134
+
-`ask_operator` is unmounted when stdin is not a TTY (no cancel stub on the wire); TTY-stdin exec still prompts on stdin even with stdout piped
135
135
- MCP connect is awaited before workflow resume and first inference, abort-capped at 15s so a hung handshake cannot block the run. A rejected batch does not disarm that abort while sibling dials are still in flight. A 1s log fires if connect is still in progress; remaining dials keep running until settle or abort. The TUI still fire-and-forgets connect (no rewrite). `tool_search` does not treat an empty catalog as a definitive miss while servers are still connecting.
* Whether exec may prompt the operator. Permission and operator prompts read
1415
+
* stdin and write stderr, so a piped stdout must not disable them: stdin TTY
1416
+
* alone means an operator can answer. Fully headless (stdin not a TTY) fails
1417
+
* closed — denials name the action and the remedy on stderr instead of
1418
+
* prompting. stdoutTTY rides along so the call site stays explicit that a
1419
+
* piped stdout is a supported prompting configuration, not an oversight.
1420
+
*/
1421
+
exportfunctionresolveExecInteractive(stdio: {
1422
+
stdinTTY: boolean|undefined;
1423
+
stdoutTTY: boolean|undefined;
1424
+
}): boolean{
1425
+
returnstdio.stdinTTY===true;
1426
+
}
1427
+
1410
1428
asyncfunctionpromptOperator(
1411
1429
question: string,
1412
1430
options: string[],
@@ -1432,11 +1450,22 @@ async function promptOperator(
1432
1450
}
1433
1451
}
1434
1452
1435
-
asyncfunctionpromptPermission(
1453
+
exportasyncfunctionpromptPermission(
1436
1454
request: PermissionRequest,
1437
1455
interactive: boolean,
1438
1456
): Promise<ApprovalOutcome>{
1439
-
if(!interactive)return{allow: false};
1457
+
if(!interactive){
1458
+
// Fail closed and say so on stderr (never stdout: piped stdout may feed
1459
+
// JSON consumers — see printResumeHint). A headless ask has no operator
1460
+
// to answer, so the denial names the action and the bypass instead of
1461
+
// vanishing into a generic block. Same sentence as the gate's
1462
+
// non-interactive deny reasons (src/permission/gate.ts) by intent; the
1463
+
// gate stays canonical for model-facing text, this seam for the operator.
1464
+
stderr.write(
1465
+
`Permission denied: ${request.action} requires operator approval, which is unavailable in a non-interactive run. Re-run with --dangerously-skip-permissions to bypass, or narrow the action.\n`,
1466
+
);
1467
+
return{allow: false};
1468
+
}
1440
1469
// Same freeze-while-deciding contract as the TUI gate: the tool wall-clock
1441
1470
// budget must not run down while the operator reads the prompt.
0 commit comments