@aragon-agent/cli 0.6.38 → 0.6.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +263 -0
- package/README.md +544 -11
- package/dist/agent/controller.d.ts +245 -10
- package/dist/agent/controller.js +367 -22
- package/dist/agent/model-profile-settings.js +12 -1
- package/dist/agent/plan-prompt.d.ts +1 -3
- package/dist/agent/plan-prompt.js +13 -7
- package/dist/agent/reducer.d.ts +70 -2
- package/dist/agent/reducer.js +59 -6
- package/dist/agent/skill-triage-prompt.d.ts +42 -0
- package/dist/agent/skill-triage-prompt.js +74 -0
- package/dist/agent/system-prompt.d.ts +28 -2
- package/dist/agent/system-prompt.js +12 -4
- package/dist/cli.js +106 -32
- package/dist/commands/builtins.js +24 -3
- package/dist/commands/keys-command.d.ts +23 -0
- package/dist/commands/keys-command.js +38 -0
- package/dist/commands/registry.d.ts +39 -0
- package/dist/commands/skills.js +86 -1
- package/dist/commands/subagent-command.d.ts +25 -0
- package/dist/commands/subagent-command.js +242 -0
- package/dist/commands/task-command.d.ts +39 -0
- package/dist/commands/task-command.js +102 -0
- package/dist/compaction/command.js +16 -3
- package/dist/compaction/compactor.d.ts +25 -0
- package/dist/compaction/compactor.js +111 -10
- package/dist/compaction/digest.d.ts +24 -1
- package/dist/compaction/digest.js +139 -17
- package/dist/compaction/limits.d.ts +21 -0
- package/dist/compaction/limits.js +21 -0
- package/dist/compaction/reason-text.d.ts +36 -0
- package/dist/compaction/reason-text.js +78 -0
- package/dist/compaction/types.d.ts +82 -14
- package/dist/compaction/wiring.d.ts +13 -8
- package/dist/compaction/wiring.js +1 -0
- package/dist/config/cli-commands.d.ts +23 -0
- package/dist/config/cli-commands.js +36 -0
- package/dist/config/env.js +17 -4
- package/dist/config/load.js +55 -3
- package/dist/config/schema.d.ts +125 -10
- package/dist/config/schema.js +138 -14
- package/dist/config/store.js +23 -3
- package/dist/diagnostics/doctor.d.ts +9 -0
- package/dist/diagnostics/doctor.js +34 -0
- package/dist/diagnostics/info.d.ts +71 -0
- package/dist/diagnostics/info.js +91 -5
- package/dist/exec/bridge/caller-tools.d.ts +96 -0
- package/dist/exec/bridge/caller-tools.js +213 -0
- package/dist/exec/bridge/decisions.d.ts +78 -0
- package/dist/exec/bridge/decisions.js +116 -0
- package/dist/exec/bridge/human-answers.d.ts +38 -0
- package/dist/exec/bridge/human-answers.js +164 -0
- package/dist/exec/bridge/limits.d.ts +68 -0
- package/dist/exec/bridge/limits.js +68 -0
- package/dist/exec/bridge/ndjson.d.ts +17 -0
- package/dist/exec/bridge/ndjson.js +83 -0
- package/dist/exec/bridge/pending.d.ts +70 -0
- package/dist/exec/bridge/pending.js +132 -0
- package/dist/exec/bridge/protocol.d.ts +346 -0
- package/dist/exec/bridge/protocol.js +256 -0
- package/dist/exec/bridge/server.d.ts +142 -0
- package/dist/exec/bridge/server.js +502 -0
- package/dist/exec/bridge/source.d.ts +58 -0
- package/dist/exec/bridge/source.js +145 -0
- package/dist/exec/bridge/writer.d.ts +26 -0
- package/dist/exec/bridge/writer.js +156 -0
- package/dist/exec/cli-commands.js +30 -2
- package/dist/exec/emitter.d.ts +16 -2
- package/dist/exec/emitter.js +26 -6
- package/dist/exec/events.d.ts +465 -10
- package/dist/exec/events.js +47 -1
- package/dist/exec/hooks/dispatch.d.ts +76 -0
- package/dist/exec/hooks/dispatch.js +169 -0
- package/dist/exec/hooks/index.d.ts +83 -0
- package/dist/exec/hooks/index.js +205 -0
- package/dist/exec/hooks/limits.d.ts +56 -0
- package/dist/exec/hooks/limits.js +49 -0
- package/dist/exec/hooks/registry.d.ts +49 -0
- package/dist/exec/hooks/registry.js +91 -0
- package/dist/exec/hooks/tool-wrapper.d.ts +94 -0
- package/dist/exec/hooks/tool-wrapper.js +154 -0
- package/dist/exec/hooks/turn.d.ts +61 -0
- package/dist/exec/hooks/turn.js +59 -0
- package/dist/exec/hooks/validate.d.ts +85 -0
- package/dist/exec/hooks/validate.js +310 -0
- package/dist/exec/index.d.ts +130 -4
- package/dist/exec/index.js +501 -10
- package/dist/exec/options.d.ts +124 -2
- package/dist/exec/options.js +413 -6
- package/dist/exec/permission.d.ts +9 -1
- package/dist/exec/permission.js +7 -1
- package/dist/exec/request/limits.d.ts +51 -0
- package/dist/exec/request/limits.js +49 -0
- package/dist/exec/request/scope.d.ts +106 -0
- package/dist/exec/request/scope.js +149 -0
- package/dist/exec/result/limits.d.ts +74 -0
- package/dist/exec/result/limits.js +72 -0
- package/dist/exec/result/prompt.d.ts +28 -0
- package/dist/exec/result/prompt.js +74 -0
- package/dist/exec/result/schema.d.ts +99 -0
- package/dist/exec/result/schema.js +464 -0
- package/dist/exec/result/sink.d.ts +56 -0
- package/dist/exec/result/sink.js +50 -0
- package/dist/exec/result/tool.d.ts +37 -0
- package/dist/exec/result/tool.js +118 -0
- package/dist/exec/runner.d.ts +395 -1
- package/dist/exec/runner.js +860 -8
- package/dist/exec/threads/aggregate.d.ts +34 -0
- package/dist/exec/threads/aggregate.js +30 -0
- package/dist/exec/threads/capacity.d.ts +23 -0
- package/dist/exec/threads/capacity.js +38 -0
- package/dist/exec/threads/dispatch-source.d.ts +60 -0
- package/dist/exec/threads/dispatch-source.js +106 -0
- package/dist/exec/threads/execute-threads.d.ts +53 -0
- package/dist/exec/threads/execute-threads.js +234 -0
- package/dist/exec/threads/gate.d.ts +42 -0
- package/dist/exec/threads/gate.js +81 -0
- package/dist/exec/threads/identity.d.ts +36 -0
- package/dist/exec/threads/identity.js +67 -0
- package/dist/exec/threads/interrupt.d.ts +29 -0
- package/dist/exec/threads/interrupt.js +79 -0
- package/dist/exec/threads/limits.d.ts +58 -0
- package/dist/exec/threads/limits.js +35 -0
- package/dist/exec/threads/outcome.d.ts +47 -0
- package/dist/exec/threads/outcome.js +26 -0
- package/dist/exec/threads/registry.d.ts +81 -0
- package/dist/exec/threads/registry.js +427 -0
- package/dist/exec/threads/runtime.d.ts +107 -0
- package/dist/exec/threads/runtime.js +523 -0
- package/dist/exec/threads/thread-source.d.ts +20 -0
- package/dist/exec/threads/thread-source.js +134 -0
- package/dist/fast/limits.d.ts +54 -0
- package/dist/fast/limits.js +54 -0
- package/dist/fast/summary-call.d.ts +72 -0
- package/dist/fast/summary-call.js +133 -0
- package/dist/fast/summary-prompt.d.ts +106 -0
- package/dist/fast/summary-prompt.js +320 -0
- package/dist/fast/task-summary-types.d.ts +66 -0
- package/dist/fast/task-summary-types.js +17 -0
- package/dist/logging/logger.d.ts +1 -1
- package/dist/proc/limits.d.ts +7 -0
- package/dist/proc/limits.js +7 -0
- package/dist/proc/log-mirror.d.ts +21 -4
- package/dist/proc/log-mirror.js +29 -7
- package/dist/proc/output-streams.d.ts +50 -0
- package/dist/proc/output-streams.js +54 -0
- package/dist/skills/service.d.ts +41 -0
- package/dist/skills/service.js +62 -0
- package/dist/skills/triage.d.ts +194 -0
- package/dist/skills/triage.js +393 -0
- package/dist/team/forced-dispatch.d.ts +95 -0
- package/dist/team/forced-dispatch.js +208 -0
- package/dist/team/limits.d.ts +21 -1
- package/dist/team/limits.js +21 -1
- package/dist/team/normalize.d.ts +15 -0
- package/dist/team/normalize.js +21 -4
- package/dist/team/prompt.d.ts +6 -3
- package/dist/team/prompt.js +9 -5
- package/dist/team/runtime.d.ts +9 -0
- package/dist/team/runtime.js +8 -0
- package/dist/team/subagent.d.ts +15 -0
- package/dist/team/subagent.js +45 -1
- package/dist/team/task-tool.js +11 -7
- package/dist/tools/bash-tool.d.ts +10 -0
- package/dist/tools/bash-tool.js +71 -6
- package/dist/tools/human-input.d.ts +35 -2
- package/dist/tools/human-input.js +38 -0
- package/dist/tools/index.d.ts +93 -1
- package/dist/tools/index.js +112 -3
- package/dist/tools/plan-tools.d.ts +20 -12
- package/dist/tools/plan-tools.js +24 -17
- package/dist/tools/tool-gate.d.ts +100 -0
- package/dist/tools/tool-gate.js +87 -0
- package/dist/ui/App.js +238 -12
- package/dist/ui/Header.d.ts +28 -16
- package/dist/ui/Header.js +21 -19
- package/dist/ui/StatusBar.d.ts +1 -0
- package/dist/ui/StatusBar.js +1 -0
- package/dist/ui/Transcript.js +1 -1
- package/dist/ui/entries/CompactionCard.d.ts +11 -1
- package/dist/ui/entries/CompactionCard.js +65 -4
- package/dist/ui/interaction-hints.js +6 -0
- package/dist/ui/layout/OverlayFrame.d.ts +18 -0
- package/dist/ui/layout/OverlayFrame.js +19 -4
- package/dist/ui/layout/header-hints.d.ts +55 -62
- package/dist/ui/layout/header-hints.js +59 -166
- package/dist/ui/layout/header-left.d.ts +115 -0
- package/dist/ui/layout/header-left.js +206 -0
- package/dist/ui/layout/shortcut-panel-layout.d.ts +191 -0
- package/dist/ui/layout/shortcut-panel-layout.js +321 -0
- package/dist/ui/layout/status-detail-layout.js +5 -1
- package/dist/ui/layout/status-layout.d.ts +10 -0
- package/dist/ui/layout/status-layout.js +18 -1
- package/dist/ui/layout/task-summary-cell.d.ts +61 -0
- package/dist/ui/layout/task-summary-cell.js +65 -0
- package/dist/ui/overlays/HelpOverlay.d.ts +12 -0
- package/dist/ui/overlays/HelpOverlay.js +29 -2
- package/dist/ui/overlays/SettingsScreen.js +29 -5
- package/dist/ui/overlays/ShortcutPanel.d.ts +39 -0
- package/dist/ui/overlays/ShortcutPanel.js +106 -0
- package/dist/ui/overlays/TeamFanoutPage.d.ts +35 -0
- package/dist/ui/overlays/TeamFanoutPage.js +78 -0
- package/dist/ui/overlays/model-profile-state.d.ts +19 -1
- package/dist/ui/overlays/model-profile-state.js +12 -0
- package/dist/ui/overlays/team-fanout-state.d.ts +73 -0
- package/dist/ui/overlays/team-fanout-state.js +97 -0
- package/dist/ui/shortcuts.d.ts +81 -0
- package/dist/ui/shortcuts.js +221 -0
- package/package.json +2 -2
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `submit_result`: the model's only way to hand an answer over as data
|
|
3
|
+
* (headless-request-scope-and-typed-result section 2.3.2 / 2.3.5).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY - `src/exec/**` is inside the glyph scanner's scope.
|
|
6
|
+
*
|
|
7
|
+
* WHY A TOOL AND NOT A PROVIDER FEATURE. `response_format` / forced `tool_choice`
|
|
8
|
+
* would need three adapter changes inside Core, whose public surface
|
|
9
|
+
* (`public-api.test.ts`) and host-coupling ban (`no-host-coupling.test.ts`) are both
|
|
10
|
+
* frozen. A tool costs one extra round trip and buys three things Core cannot give
|
|
11
|
+
* here: the caller's schema travels to the model as a REAL JSON Schema (every
|
|
12
|
+
* adapter serializes `parameters` verbatim), a violation comes back as a readable
|
|
13
|
+
* field-level message the model can act on inside the same prompt, and the whole
|
|
14
|
+
* mechanism is provable without a network.
|
|
15
|
+
*
|
|
16
|
+
* NOTHING IN HERE KNOWS WHAT A REQUEST OR A PROCESS IS. It validates and it writes
|
|
17
|
+
* one box. Ending the request is the runner's job (`checkResultComplete`), which is
|
|
18
|
+
* what keeps "accepted" and "finished" from becoming two different opinions.
|
|
19
|
+
*/
|
|
20
|
+
import { type AgentTool } from '@aragon-agent/core';
|
|
21
|
+
import { type ResultSchemaSpec } from './schema.js';
|
|
22
|
+
import type { ResultSink } from './sink.js';
|
|
23
|
+
/** The one tool name this feature registers. */
|
|
24
|
+
export declare const SUBMIT_RESULT_TOOL = "submit_result";
|
|
25
|
+
export interface SubmitResultToolOptions {
|
|
26
|
+
spec: ResultSchemaSpec;
|
|
27
|
+
sink: ResultSink;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The tool the model sees.
|
|
31
|
+
*
|
|
32
|
+
* THE SCHEMA IS INLINED INTO `parameters.properties.result`, NOT WRITTEN INTO THE
|
|
33
|
+
* DESCRIPTION, and that is not a style choice: `buildSystemPrompt` renders a tool as
|
|
34
|
+
* `description.split('\n')[0]` - one line - so a schema in the description never
|
|
35
|
+
* reaches the model at all, while `parameters` is serialized verbatim.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createSubmitResultTool(options: SubmitResultToolOptions): AgentTool;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `submit_result`: the model's only way to hand an answer over as data
|
|
3
|
+
* (headless-request-scope-and-typed-result section 2.3.2 / 2.3.5).
|
|
4
|
+
*
|
|
5
|
+
* ASCII ONLY - `src/exec/**` is inside the glyph scanner's scope.
|
|
6
|
+
*
|
|
7
|
+
* WHY A TOOL AND NOT A PROVIDER FEATURE. `response_format` / forced `tool_choice`
|
|
8
|
+
* would need three adapter changes inside Core, whose public surface
|
|
9
|
+
* (`public-api.test.ts`) and host-coupling ban (`no-host-coupling.test.ts`) are both
|
|
10
|
+
* frozen. A tool costs one extra round trip and buys three things Core cannot give
|
|
11
|
+
* here: the caller's schema travels to the model as a REAL JSON Schema (every
|
|
12
|
+
* adapter serializes `parameters` verbatim), a violation comes back as a readable
|
|
13
|
+
* field-level message the model can act on inside the same prompt, and the whole
|
|
14
|
+
* mechanism is provable without a network.
|
|
15
|
+
*
|
|
16
|
+
* NOTHING IN HERE KNOWS WHAT A REQUEST OR A PROCESS IS. It validates and it writes
|
|
17
|
+
* one box. Ending the request is the runner's job (`checkResultComplete`), which is
|
|
18
|
+
* what keeps "accepted" and "finished" from becoming two different opinions.
|
|
19
|
+
*/
|
|
20
|
+
import { errorResult, textResult, } from '@aragon-agent/core';
|
|
21
|
+
import { RESULT_LIMITS } from './limits.js';
|
|
22
|
+
import { canonicalizeResult, checkResultValue, coerceStringifiedObject, formatResultViolations, resultValueBytes, } from './schema.js';
|
|
23
|
+
/** The one tool name this feature registers. */
|
|
24
|
+
export const SUBMIT_RESULT_TOOL = 'submit_result';
|
|
25
|
+
/**
|
|
26
|
+
* The tool the model sees.
|
|
27
|
+
*
|
|
28
|
+
* THE SCHEMA IS INLINED INTO `parameters.properties.result`, NOT WRITTEN INTO THE
|
|
29
|
+
* DESCRIPTION, and that is not a style choice: `buildSystemPrompt` renders a tool as
|
|
30
|
+
* `description.split('\n')[0]` - one line - so a schema in the description never
|
|
31
|
+
* reaches the model at all, while `parameters` is serialized verbatim.
|
|
32
|
+
*/
|
|
33
|
+
export function createSubmitResultTool(options) {
|
|
34
|
+
const { spec, sink } = options;
|
|
35
|
+
return {
|
|
36
|
+
name: SUBMIT_RESULT_TOOL,
|
|
37
|
+
label: 'Submit result',
|
|
38
|
+
description: 'Submit the FINAL answer as an object that conforms to the required schema. ' +
|
|
39
|
+
'Call this exactly once, when you are done. Do not also print the answer as prose. ' +
|
|
40
|
+
'If it returns a validation error, fix the fields it names and call it again.',
|
|
41
|
+
// The wrapper is raw JSON from the caller's file; the index signature on
|
|
42
|
+
// `JSONSchema7` is what lets it through without a second schema type of our own.
|
|
43
|
+
parameters: spec.wrapper,
|
|
44
|
+
async execute(id, params) {
|
|
45
|
+
const attempts = sink.noteAttempt();
|
|
46
|
+
const submitted = coerceStringifiedObject(spec.schema, params.result);
|
|
47
|
+
// STEP 1 - THE WRAPPER'S OWN REQUIREMENT, IN THE CALLER'S VOCABULARY.
|
|
48
|
+
// "the key was absent" and "the value was undefined" are the same thing after
|
|
49
|
+
// JSON decoding, and the field name is what the model has to be told.
|
|
50
|
+
if (submitted === undefined)
|
|
51
|
+
return fail(sink, ['result: required']);
|
|
52
|
+
// STEP 1b - THE WRAPPER'S `additionalProperties: false`, ENFORCED HERE AND NOT
|
|
53
|
+
// ONLY BY Core (section 2.3.2, point 3). Core runs that keyword ONLY when `ajv`
|
|
54
|
+
// is importable; its fallback never reads the keyword at all, so on a machine
|
|
55
|
+
// without `ajv` a wrapper rule left to Core is a comment. A stray top-level key
|
|
56
|
+
// is refused rather than ignored: "it was silently dropped" and "it was part of
|
|
57
|
+
// the contract" are indistinguishable from the caller's side of the pipe.
|
|
58
|
+
const extraKeys = wrapperExtraKeys(params);
|
|
59
|
+
if (extraKeys.length > 0)
|
|
60
|
+
return fail(sink, extraKeys.map(describeExtraKey));
|
|
61
|
+
// STEP 2/3 - THE BELT (section 2.3.4). Runs on EVERY machine, including the one
|
|
62
|
+
// with no `ajv`, which is why it implements the keywords Core's fallback skips.
|
|
63
|
+
const violations = checkResultValue(spec.schema, submitted);
|
|
64
|
+
if (violations.length > 0)
|
|
65
|
+
return fail(sink, violations);
|
|
66
|
+
// STEP 4/5 - CANONICAL, THEN SIZED. Canonical first, so the number checked is
|
|
67
|
+
// the size of what a consumer will read back rather than of what was typed.
|
|
68
|
+
const canonical = canonicalizeResult(submitted);
|
|
69
|
+
const bytes = resultValueBytes(canonical);
|
|
70
|
+
if (bytes > RESULT_LIMITS.maxResultValueBytes) {
|
|
71
|
+
return fail(sink, [
|
|
72
|
+
`result: ${bytes} bytes is above the ${RESULT_LIMITS.maxResultValueBytes}-byte limit`,
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
// STEP 6 - SINGLE ASSIGNMENT. A second accepted call is a no-op by design, and
|
|
76
|
+
// the value a caller is entitled to is the FIRST one.
|
|
77
|
+
if (!sink.accepted())
|
|
78
|
+
sink.accept(canonical, { attempts, callId: id });
|
|
79
|
+
sink.noteViolations([]);
|
|
80
|
+
// STEP 7 - A SUCCESS RESULT EVEN ON THE SECOND CALL (I-S3). An error here would
|
|
81
|
+
// tell the model its answer was not taken, and it would keep trying.
|
|
82
|
+
return textResult('Result accepted. Do not call submit_result again; stop here.');
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Record the rejection where the repair loop can quote it, and answer the model.
|
|
88
|
+
*
|
|
89
|
+
* `errorResult` RATHER THAN THROWING: the engine pushes whatever a tool returns into
|
|
90
|
+
* history, and an error RESULT is what keeps the `tool_use` from hanging into the
|
|
91
|
+
* next provider request. A thrown error would be converted by the executor into
|
|
92
|
+
* wording that says nothing about which fields were wrong.
|
|
93
|
+
*/
|
|
94
|
+
function fail(sink, violations) {
|
|
95
|
+
sink.noteViolations(violations);
|
|
96
|
+
const shown = formatResultViolations(violations);
|
|
97
|
+
return errorResult(`submit_result was not accepted (attempt ${sink.attempts()}):\n${shown}\n` +
|
|
98
|
+
'Fix the fields named above and call submit_result again with the whole object.');
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The top-level keys the model sent that the wrapper does not accept.
|
|
102
|
+
*
|
|
103
|
+
* `result` IS THE WHOLE CONTRACT (section 2.3.2): the wrapper is
|
|
104
|
+
* `{ required: ['result'], additionalProperties: false }` and nothing else, so every
|
|
105
|
+
* other key is a stray. Returns `[]` for a non-object payload, which the missing-result
|
|
106
|
+
* check above has already answered with a better message.
|
|
107
|
+
*/
|
|
108
|
+
function wrapperExtraKeys(params) {
|
|
109
|
+
if (typeof params !== 'object' || params === null)
|
|
110
|
+
return [];
|
|
111
|
+
return Object.keys(params)
|
|
112
|
+
.filter((key) => key !== 'result')
|
|
113
|
+
.slice(0, RESULT_LIMITS.maxViolations);
|
|
114
|
+
}
|
|
115
|
+
/** One stray key, in the same `path: what is wrong` shape as every other violation. */
|
|
116
|
+
function describeExtraKey(key) {
|
|
117
|
+
return `${key}: not allowed (this tool takes only "result")`;
|
|
118
|
+
}
|
package/dist/exec/runner.d.ts
CHANGED
|
@@ -19,8 +19,11 @@ import type { HeadlessController } from '../agent/headless.js';
|
|
|
19
19
|
import { type FollowThroughMode } from '../todo/follow-through.js';
|
|
20
20
|
import type { ExecEmitter } from './emitter.js';
|
|
21
21
|
import type { FastSnapshot } from '../fast/types.js';
|
|
22
|
-
import type { ExecStopReason } from './events.js';
|
|
22
|
+
import type { ExecCost, ExecStopReason } from './events.js';
|
|
23
23
|
import type { ProcEvent } from '../proc/types.js';
|
|
24
|
+
import type { BridgeServer } from './bridge/server.js';
|
|
25
|
+
import type { BridgePromptSource } from './bridge/source.js';
|
|
26
|
+
import type { ResultSink } from './result/sink.js';
|
|
24
27
|
/**
|
|
25
28
|
* The controller surface the runner needs.
|
|
26
29
|
*
|
|
@@ -54,11 +57,47 @@ export interface ExecRunnerController extends HeadlessController {
|
|
|
54
57
|
* literal, and a host with no supervisor has nothing to report.
|
|
55
58
|
*/
|
|
56
59
|
subscribeProc?(listener: (event: ProcEvent) => void): () => void;
|
|
60
|
+
/**
|
|
61
|
+
* Whether a turn is in flight, and the enqueue path for `steer`.
|
|
62
|
+
*
|
|
63
|
+
* OPTIONAL, FOR THE SAME REASON THE SIX ABOVE ARE: this interface is minimal so
|
|
64
|
+
* a test can satisfy it with an object literal. `steer` over the bridge is their
|
|
65
|
+
* only consumer, and a stub without them answers `not_running`, which is the
|
|
66
|
+
* honest reading of "this controller cannot accept guidance".
|
|
67
|
+
*/
|
|
68
|
+
isRunning?(): boolean;
|
|
69
|
+
queueUserMessage?(text: string): string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* One caller message plus the metadata a bridge turn needs.
|
|
73
|
+
*
|
|
74
|
+
* `requestId` is the caller's own frame id, echoed onto `user`, `turn_state`,
|
|
75
|
+
* `assistant`, `tool_call` and `tool_result` so a wrapper can attribute events to
|
|
76
|
+
* the request that caused them. `queued` says whether this message had to wait
|
|
77
|
+
* behind another one - see `bridge/source.ts`, where it is captured at ARRIVAL,
|
|
78
|
+
* the only moment the question has a meaningful answer.
|
|
79
|
+
*/
|
|
80
|
+
export interface ExecPrompt {
|
|
81
|
+
text: string;
|
|
82
|
+
requestId?: string;
|
|
83
|
+
queued?: boolean;
|
|
57
84
|
}
|
|
58
85
|
/** Where the caller's messages come from. One turn, or an NDJSON stdin stream. */
|
|
59
86
|
export interface ExecPromptSource {
|
|
60
87
|
/** The next caller message, or `null` when the caller is done. */
|
|
61
88
|
next(): Promise<string | null>;
|
|
89
|
+
/**
|
|
90
|
+
* METADATA FOR THE MESSAGE `next()` JUST RETURNED, read once and synchronously.
|
|
91
|
+
*
|
|
92
|
+
* A SIDE CHANNEL RATHER THAN A WIDER RETURN TYPE, and that is a deliberate
|
|
93
|
+
* departure from the design (`§2.4.6` proposed `next(): Promise<ExecPrompt | null>`).
|
|
94
|
+
* Widening `next()` would have rewritten `stdin-stream.ts`'s contract and every
|
|
95
|
+
* caller of it - including `exec-stdin-stream.test.ts`, which AC-3 requires to
|
|
96
|
+
* pass VERBATIM as the proof that the v1 input format did not move. Keeping the
|
|
97
|
+
* return type and adding an optional reader gives the bridge what it needs while
|
|
98
|
+
* leaving that file byte-identical, and it costs exactly one call site: `run()`.
|
|
99
|
+
*/
|
|
100
|
+
lastPrompt?(): ExecPrompt | undefined;
|
|
62
101
|
}
|
|
63
102
|
export declare function singlePrompt(text: string): ExecPromptSource;
|
|
64
103
|
/** Replaceable so a unit test can assert the mechanism, not just the outcome. */
|
|
@@ -71,6 +110,35 @@ export interface ExecRunnerOptions {
|
|
|
71
110
|
sessionId: string;
|
|
72
111
|
maxTurns?: number;
|
|
73
112
|
maxDurationMs?: number;
|
|
113
|
+
/**
|
|
114
|
+
* The structured-result contract for this run (headless-request-scope-and-typed-
|
|
115
|
+
* result section 2.4 / 2.3.5). All of these are ABSENT unless `--result-schema`
|
|
116
|
+
* was given, which is what keeps every other run byte-identical (I-S2).
|
|
117
|
+
*/
|
|
118
|
+
resultSink?: ResultSink;
|
|
119
|
+
/** The schema's file name, echoed into `request_end.structured` and into errors. */
|
|
120
|
+
resultSchemaName?: string;
|
|
121
|
+
/** `--result-repair-turns`; defaults to `RESULT_LIMITS.defaultRepairTurns`. */
|
|
122
|
+
resultRepairTurns?: number;
|
|
123
|
+
/** `--on-result-unsatisfied`; `error` is the default and the only one that exits. */
|
|
124
|
+
onResultUnsatisfied?: 'error' | 'continue';
|
|
125
|
+
/**
|
|
126
|
+
* The per-request budgets (bridge only). ABSENT MEANS NO CEILING, deliberately:
|
|
127
|
+
* a default ceiling would cut off a legitimate long request with nothing saying
|
|
128
|
+
* why, and section 9.7 keeps "no default" as the decision.
|
|
129
|
+
*/
|
|
130
|
+
requestMaxTurns?: number;
|
|
131
|
+
requestMaxDurationMs?: number;
|
|
132
|
+
/**
|
|
133
|
+
* How to price a usage reading.
|
|
134
|
+
*
|
|
135
|
+
* A CALLBACK RATHER THAN AN IMPORT: the cost authority (`buildCost`) lives in
|
|
136
|
+
* `exec/index.ts` because it needs the controller's price table, and importing it
|
|
137
|
+
* here would be a cycle. The runner passes its own cumulative usage and compaction
|
|
138
|
+
* billing, so a request's cost is the DIFFERENCE between two readings of the same
|
|
139
|
+
* function - which is exactly what makes it equal to running that request alone.
|
|
140
|
+
*/
|
|
141
|
+
cost?: (usage: TokenUsage, compaction?: ExecCompactionBilling) => ExecCost;
|
|
74
142
|
partialMessages?: boolean;
|
|
75
143
|
includeThinking?: boolean;
|
|
76
144
|
followThrough?: FollowThroughMode;
|
|
@@ -80,6 +148,79 @@ export interface ExecRunnerOptions {
|
|
|
80
148
|
signals?: ExecSignalPort;
|
|
81
149
|
/** Turns already spent by earlier invocations of a resumed session. */
|
|
82
150
|
priorTurns?: number;
|
|
151
|
+
/**
|
|
152
|
+
* WHICH THREAD THIS RUNNER SERVES (headless-threads-and-lifecycle-hooks section
|
|
153
|
+
* 2.3.10, I-T10). Absent in every non-thread run, and its absence is what keeps that
|
|
154
|
+
* stream byte-identical: a field that is not set is not serialized.
|
|
155
|
+
*/
|
|
156
|
+
threadId?: string;
|
|
157
|
+
/**
|
|
158
|
+
* Called once per request, in `run()`'s `finally`, AFTER `settleRequest`.
|
|
159
|
+
*
|
|
160
|
+
* THE ADMISSION GATE'S RELEASE POINT, and the reason it is not inside the runner: the
|
|
161
|
+
* gate must free the slot exactly when the request's account has been written, and the
|
|
162
|
+
* runner is the only object that knows that moment. Keeping it a callback means the
|
|
163
|
+
* runner learns nothing about threads.
|
|
164
|
+
*/
|
|
165
|
+
onRequestSettled?: (requestSeq: number) => void;
|
|
166
|
+
/**
|
|
167
|
+
* `turn_start` (section 2.4.6). Awaited at the top of every follow-through iteration,
|
|
168
|
+
* AFTER the budget check and BEFORE `prompt()`.
|
|
169
|
+
*/
|
|
170
|
+
beforeTurn?: BeforeTurnHook;
|
|
171
|
+
/** `prompt_pre` (section 2.4.7). Runs between `scope.begin()` and the ack. */
|
|
172
|
+
hookPort?: ExecRunnerHookPort;
|
|
173
|
+
/**
|
|
174
|
+
* DEFAULT `true`, AND THREAD MODE PASSES `false` (section 2.3.8a, review P0-1).
|
|
175
|
+
*
|
|
176
|
+
* `installTerminator()` writes into `logging/install.ts`'s SINGLE process-wide slot, so
|
|
177
|
+
* two runners that both install it means the last one wins: Ctrl-C would abort one
|
|
178
|
+
* controller and leave the rest running. Making it an OPTION rather than a convention is
|
|
179
|
+
* what turns "remember not to install twice" into something the type system carries.
|
|
180
|
+
*/
|
|
181
|
+
installSignalTerminator?: boolean;
|
|
182
|
+
}
|
|
183
|
+
/** One `turn_start` answer, already validated by the hook runtime. */
|
|
184
|
+
export interface BeforeTurnResult {
|
|
185
|
+
/** Text to queue through steering. */
|
|
186
|
+
injected?: string;
|
|
187
|
+
/** The hook that injected, so a stranded injection can name itself. */
|
|
188
|
+
hookId?: string;
|
|
189
|
+
/** `injection_stranded`, `hook_timeout`, ... */
|
|
190
|
+
code?: string;
|
|
191
|
+
/** `--on-hook-timeout error`: this request must end as an error. */
|
|
192
|
+
isError?: boolean;
|
|
193
|
+
}
|
|
194
|
+
export type BeforeTurnHook = (info: {
|
|
195
|
+
turn: number;
|
|
196
|
+
source: 'caller' | 'todo_continue' | 'result_repair';
|
|
197
|
+
/**
|
|
198
|
+
* The request this turn belongs to. ALWAYS KNOWN HERE - `scope.begin()` has run - and
|
|
199
|
+
* carried on the `hook_request` so a caller can correlate a `turn_start` round trip
|
|
200
|
+
* with the request it belongs to; a synthesized `turn_<n>` would collide across two
|
|
201
|
+
* requests inside one thread.
|
|
202
|
+
*/
|
|
203
|
+
requestId: string;
|
|
204
|
+
}) => Promise<BeforeTurnResult | void>;
|
|
205
|
+
/** What `prompt_pre` decided about one caller message. */
|
|
206
|
+
export interface PromptPreDecision {
|
|
207
|
+
action: 'allow' | 'rewrite' | 'deny';
|
|
208
|
+
/** The text the model will actually receive, on `rewrite`. */
|
|
209
|
+
text?: string;
|
|
210
|
+
reason?: string;
|
|
211
|
+
message?: string;
|
|
212
|
+
/** A stable code for the `ack` / `request_end.error` when denied. */
|
|
213
|
+
code?: string;
|
|
214
|
+
/** Whether the denial makes the REQUEST a failure (a hook deny does). */
|
|
215
|
+
isError?: boolean;
|
|
216
|
+
}
|
|
217
|
+
export interface ExecRunnerHookPort {
|
|
218
|
+
/** Never rejects: every failure arrives as a decision. */
|
|
219
|
+
promptPre(input: {
|
|
220
|
+
text: string;
|
|
221
|
+
queued: boolean;
|
|
222
|
+
requestId: string;
|
|
223
|
+
}): Promise<PromptPreDecision>;
|
|
83
224
|
}
|
|
84
225
|
export interface ExecCompactionBilling {
|
|
85
226
|
usage: TokenUsage;
|
|
@@ -91,6 +232,20 @@ export interface ExecRunStats {
|
|
|
91
232
|
turns: number;
|
|
92
233
|
usage: TokenUsage;
|
|
93
234
|
lastAssistantText: string;
|
|
235
|
+
/**
|
|
236
|
+
* The LAST accepted structured result of this process, or absent.
|
|
237
|
+
*
|
|
238
|
+
* PROCESS-LEVEL ON PURPOSE, while the SINK is per request (I-S8): `result` is one
|
|
239
|
+
* event for the whole run and must not report "nothing was accepted" because the
|
|
240
|
+
* final request happened to be a follow-up that submitted nothing. A per-request
|
|
241
|
+
* reader (`request_end.structured`) uses the sink, not this.
|
|
242
|
+
*/
|
|
243
|
+
lastStructured?: {
|
|
244
|
+
value: unknown;
|
|
245
|
+
attempts: number;
|
|
246
|
+
};
|
|
247
|
+
/** True when a schema was given and nothing was ever accepted (I-S5). */
|
|
248
|
+
resultUnsatisfied: boolean;
|
|
94
249
|
errored: boolean;
|
|
95
250
|
stopReason: ExecStopReason;
|
|
96
251
|
errorCode: string | null;
|
|
@@ -105,6 +260,11 @@ export declare class ExecRunner {
|
|
|
105
260
|
private readonly now;
|
|
106
261
|
private readonly progress;
|
|
107
262
|
private controller;
|
|
263
|
+
/** Set by `attachBridge`; absent in every non-bridge run (I-1). */
|
|
264
|
+
private bridge;
|
|
265
|
+
private bridgeSource;
|
|
266
|
+
/** The caller's id for the turn currently running, if there is one. */
|
|
267
|
+
private requestId;
|
|
108
268
|
private unsubscribers;
|
|
109
269
|
/** Per-job progress throttle state: the last text emitted, and when. */
|
|
110
270
|
private readonly jobProgressAt;
|
|
@@ -121,11 +281,48 @@ export declare class ExecRunner {
|
|
|
121
281
|
private errorMessage;
|
|
122
282
|
private signalNumber;
|
|
123
283
|
private thinkingBuffer;
|
|
284
|
+
/**
|
|
285
|
+
* An injection that has been queued but whose acceptance has not been observed.
|
|
286
|
+
*
|
|
287
|
+
* ITS ONLY JOB IS TO MAKE I-H12 TRUE: a `turn_start` injection queued for a request that
|
|
288
|
+
* then fails to start must be REPORTED, not silently left in the steering queue where the
|
|
289
|
+
* next request's loop top would push it into history.
|
|
290
|
+
*/
|
|
291
|
+
private pendingInjection;
|
|
292
|
+
/**
|
|
293
|
+
* How many `steering_accepted` receipts have arrived.
|
|
294
|
+
*
|
|
295
|
+
* IT EXISTS TO CLOSE A ONE-TURN RACE: a stub (or a very fast engine) can confirm the
|
|
296
|
+
* injection INSIDE `queueUserMessage`, in which case setting the pending marker afterwards
|
|
297
|
+
* would leave a stale one and report a healthy injection as stranded.
|
|
298
|
+
*/
|
|
299
|
+
private steeringReceipts;
|
|
124
300
|
private budgetTimer;
|
|
125
301
|
private timerStarted;
|
|
126
302
|
private detached;
|
|
127
303
|
private lastSignalAt;
|
|
304
|
+
/**
|
|
305
|
+
* The one owner of every request-level fact (section 2.4.3).
|
|
306
|
+
*
|
|
307
|
+
* CREATED UNCONDITIONALLY, even outside bridge mode, and that is a deliberate
|
|
308
|
+
* departure from the design's "only in bridge mode" (implementation note): the
|
|
309
|
+
* acceptance latch and the per-request reset have to exist for a single-request run
|
|
310
|
+
* too, or `checkResultComplete` would need a second latch of its own - and two
|
|
311
|
+
* latches for one fact is how "the second request never stops" happens. It emits
|
|
312
|
+
* nothing and starts no timer unless a request budget was configured, so a
|
|
313
|
+
* non-bridge run is byte-identical.
|
|
314
|
+
*/
|
|
315
|
+
private readonly requestScope;
|
|
316
|
+
/** True between the request's `ack` and its `request_end`. */
|
|
317
|
+
private requestInFlight;
|
|
318
|
+
/** Repair continuations used by the request in flight. */
|
|
319
|
+
private repairsUsed;
|
|
320
|
+
private resultUnsatisfied;
|
|
321
|
+
/** The last value any request accepted, for the process-level `result` event. */
|
|
322
|
+
private lastStructured;
|
|
128
323
|
constructor(options: ExecRunnerOptions);
|
|
324
|
+
/** `buildCost` when the composition root supplied it; zero-and-unknown otherwise. */
|
|
325
|
+
private costOf;
|
|
129
326
|
/**
|
|
130
327
|
* Subscribe to all four streams.
|
|
131
328
|
*
|
|
@@ -136,6 +333,70 @@ export declare class ExecRunner {
|
|
|
136
333
|
* multiple subscribers.
|
|
137
334
|
*/
|
|
138
335
|
attach(controller: ExecRunnerController): void;
|
|
336
|
+
/**
|
|
337
|
+
* Wire the protocol channel. Called AFTER `attach(controller)`, never before:
|
|
338
|
+
* the turn ports below reach the controller.
|
|
339
|
+
*
|
|
340
|
+
* THREE LINES OF STATE AND ONE INSTALLATION, so that everything bridge-shaped in
|
|
341
|
+
* this file is reachable from one place. `steer` and `interrupt` belong here
|
|
342
|
+
* rather than in the server because they need the RUNNER's idea of what is in
|
|
343
|
+
* flight - and routing them through the server keeps the source's frame router
|
|
344
|
+
* a pure demultiplexer.
|
|
345
|
+
*/
|
|
346
|
+
attachBridge(server: BridgeServer, source?: BridgePromptSource): void;
|
|
347
|
+
/**
|
|
348
|
+
* `steer` over the wire.
|
|
349
|
+
*
|
|
350
|
+
* THE CHECK AND THE CALL ARE SEPARATE, AND BOTH ARE NEEDED: `queueUserMessage()`
|
|
351
|
+
* throws when the agent is not running, and "it was running when I looked" is
|
|
352
|
+
* not the same claim as "it accepted the message". Both failures map to the same
|
|
353
|
+
* receipt, because a caller cannot act on the difference and a lie ("accepted")
|
|
354
|
+
* would make it wait for text that never enters the conversation.
|
|
355
|
+
*/
|
|
356
|
+
private onSteerFrame;
|
|
357
|
+
/**
|
|
358
|
+
* `interrupt` over the wire, and the three things it can mean.
|
|
359
|
+
*
|
|
360
|
+
* A NAMED QUEUED TURN IS DELETED, NOT ABORTED: the user removed a message that
|
|
361
|
+
* had not started, and aborting the run in flight would cancel somebody else's
|
|
362
|
+
* work. An unnamed (or in-flight) request is the ordinary signal path, which
|
|
363
|
+
* already settles, persists and reports - the runner's own `requestInterrupt`.
|
|
364
|
+
*/
|
|
365
|
+
private onInterruptFrame;
|
|
366
|
+
/**
|
|
367
|
+
* `cancel_request` over the wire: stop ONE request, never the process.
|
|
368
|
+
*
|
|
369
|
+
* THREE OUTCOMES, and the third one is the important one (section 2.4.7):
|
|
370
|
+
*
|
|
371
|
+
* QUEUED the source deletes it and THIS method writes its `request_end`,
|
|
372
|
+
* because `run()`'s loop never saw that prompt and never will.
|
|
373
|
+
* The `ack` echoes the requestId, since a queued prompt has no
|
|
374
|
+
* `ack` of its own before it is served.
|
|
375
|
+
* IN FLIGHT cancel the scope and abort. The request's own `finally` writes the
|
|
376
|
+
* `request_end`, so there is still exactly one writer per fact.
|
|
377
|
+
* UNKNOWN `ack{accepted:false}` and a non-fatal error. A FINISHED REQUEST IS
|
|
378
|
+
* `unknown`, NEVER "in flight" - aborting a healthy run because a
|
|
379
|
+
* caller cancelled something it already got an answer for is the
|
|
380
|
+
* exact defect the source's `cancelQueued` comment records.
|
|
381
|
+
*/
|
|
382
|
+
cancelRequest(requestId: string | undefined, frameId: string | undefined): void;
|
|
383
|
+
/**
|
|
384
|
+
* The refusal, written where the caller can act on it.
|
|
385
|
+
*
|
|
386
|
+
* TWO FRAMES, DELIBERATELY: the `ack` says "not accepted" and the non-fatal `error`
|
|
387
|
+
* says why. A caller that only watches `ack` is not left guessing, and one that only
|
|
388
|
+
* watches `error` is not left without a receipt.
|
|
389
|
+
*/
|
|
390
|
+
private reportUnknownRequest;
|
|
391
|
+
/**
|
|
392
|
+
* The `request_end` for a request that was cancelled BEFORE it was served.
|
|
393
|
+
*
|
|
394
|
+
* `requestSeq` IS ABSENT, NOT ZERO: the number is minted when a prompt is served,
|
|
395
|
+
* this one never was, and inventing one would collide with a later real request.
|
|
396
|
+
* Everything else is honestly zero, including `cost` - which is `known: true` with
|
|
397
|
+
* `amount: 0` because nothing was spent and that is a fact, not a missing price.
|
|
398
|
+
*/
|
|
399
|
+
private writeCancelledRequestEnd;
|
|
139
400
|
/**
|
|
140
401
|
* Map a supervisor event onto the `job` channel.
|
|
141
402
|
*
|
|
@@ -174,12 +435,123 @@ export declare class ExecRunner {
|
|
|
174
435
|
* the two faces cannot drift about what "unfinished" means.
|
|
175
436
|
*/
|
|
176
437
|
run(source: ExecPromptSource): Promise<void>;
|
|
438
|
+
/**
|
|
439
|
+
* Run `prompt_pre` for the request that just began, and settle it if a hook refuses.
|
|
440
|
+
*
|
|
441
|
+
* RETURNS THE TEXT TO SEND, or `null` when the request has already been settled and the
|
|
442
|
+
* caller's loop must continue with the next one. A caller hook's denial is a FAILED
|
|
443
|
+
* request - the caller did not get what it asked for - which is why `isError` is true
|
|
444
|
+
* here and false for a caller's own `cancel_request`.
|
|
445
|
+
*/
|
|
446
|
+
private applyPromptPre;
|
|
447
|
+
/**
|
|
448
|
+
* The `request_end` for a request a hook refused.
|
|
449
|
+
*
|
|
450
|
+
* ONE DELIBERATE DIFFERENCE FROM `writeCancelledRequestEnd`: `requestSeq` IS PRESENT.
|
|
451
|
+
* That method serves a prompt that was withdrawn while queued, before `begin()` ever ran,
|
|
452
|
+
* so it has no number to give; this one HAS begun, and inventing a denial without the
|
|
453
|
+
* number would make "which request was refused" a guessing game.
|
|
454
|
+
*/
|
|
455
|
+
private writeDeniedRequestEnd;
|
|
456
|
+
/**
|
|
457
|
+
* Record a hook-caused request failure (section 2.4.8's `error` policy).
|
|
458
|
+
*
|
|
459
|
+
* ALL THREE FIELDS, because three different readers need them: `errorCode` and
|
|
460
|
+
* `errorMessage` become `request_end.error`, and `requestHadError` is what makes
|
|
461
|
+
* `requestError()` return anything at all - setting the first two alone leaves the frame
|
|
462
|
+
* looking clean, which is exactly the failure `--on-hook-timeout error` exists to prevent.
|
|
463
|
+
*/
|
|
464
|
+
private markHookError;
|
|
465
|
+
/**
|
|
466
|
+
* Report an injection that was queued for a request that never accepted it (I-H12).
|
|
467
|
+
*
|
|
468
|
+
* TWO FRAMES, because there are two facts: the hook round trip ended as `aborted` with
|
|
469
|
+
* `injection_stranded` (visible in the hook channel), and this request's `error.code`
|
|
470
|
+
* carries the same string so a caller reading only `request_end` learns it too.
|
|
471
|
+
*/
|
|
472
|
+
private reportStrandedInjection;
|
|
473
|
+
/**
|
|
474
|
+
* Write this request's `request_end` - the FIRST of the two writers (I-R9).
|
|
475
|
+
*
|
|
476
|
+
* The second is `cancelRequest`, for a prompt that was cancelled while queued and
|
|
477
|
+
* therefore never reached this method at all. Both go through
|
|
478
|
+
* `RequestScope.remember()`, which is what makes "exactly one per request" true
|
|
479
|
+
* instead of merely intended.
|
|
480
|
+
*/
|
|
481
|
+
private settleRequest;
|
|
482
|
+
/**
|
|
483
|
+
* What `RequestScope.end()` needs: the clock, the failure, and - when the PROCESS
|
|
484
|
+
* stopped underneath this request - the process reason (section 2.4.2).
|
|
485
|
+
*
|
|
486
|
+
* SEPARATE FROM `requestError()` BECAUSE THE TWO ANSWER DIFFERENT QUESTIONS: the
|
|
487
|
+
* error says "you did not get what you asked for", the reason says "why this request
|
|
488
|
+
* ended". A process `interrupt` sets the second and not the first, which is exactly
|
|
489
|
+
* the case a caller reading only `stopReason` has to see as `cancelled` rather than
|
|
490
|
+
* as a finished answer.
|
|
491
|
+
*/
|
|
492
|
+
private settlementInput;
|
|
493
|
+
/**
|
|
494
|
+
* The process-level stop, translated into the REQUEST vocabulary (section 2.4.2).
|
|
495
|
+
*
|
|
496
|
+
* `undefined` for `end_turn` and for everything a request already speaks for itself.
|
|
497
|
+
* Only the three cases where the RUN ended underneath a live request are mapped, and
|
|
498
|
+
* the mapping is one-way on purpose: `ExecStopReason` and `ExecRequestStopReason` are
|
|
499
|
+
* different unions over different questions and must stay different.
|
|
500
|
+
*/
|
|
501
|
+
private processStopForRequest;
|
|
502
|
+
/** The request-level error, or `null`. One place so `result` and `request_end` agree. */
|
|
503
|
+
private requestError;
|
|
177
504
|
private onCompactionUsage;
|
|
178
505
|
private requestSeq;
|
|
179
506
|
private requestHadError;
|
|
180
507
|
private isRequestCancelled;
|
|
181
508
|
/** One caller message plus every auto-continuation it produces. */
|
|
182
509
|
private runFollowThroughChain;
|
|
510
|
+
/**
|
|
511
|
+
* What the structured-result contract says about the request that just ended.
|
|
512
|
+
*
|
|
513
|
+
* FOUR ANSWERS, and `none` is the important one: with no `--result-schema` this
|
|
514
|
+
* method reads nothing and the caller's behaviour is exactly what it was before the
|
|
515
|
+
* feature existed (I-S2).
|
|
516
|
+
*/
|
|
517
|
+
private resultContractOutcome;
|
|
518
|
+
/**
|
|
519
|
+
* Record that this request ended without the answer the caller asked for.
|
|
520
|
+
*
|
|
521
|
+
* TWO FACTS, TWO FIELDS, NEVER ONE NAME FOR BOTH (P1-9): `error.code` says the
|
|
522
|
+
* contract failed, and the process-level `stopReason` moves to `error` ONLY when
|
|
523
|
+
* `--on-result-unsatisfied error` (the default) and nothing else already decided why
|
|
524
|
+
* the request stopped.
|
|
525
|
+
*/
|
|
526
|
+
private markResultUnsatisfied;
|
|
527
|
+
/**
|
|
528
|
+
* Stop the request for a budget reason, and record the contract failure it implies.
|
|
529
|
+
*
|
|
530
|
+
* THE CANCELLATION GUARD IS THE POINT. A caller that withdrew this request has not
|
|
531
|
+
* failed to deliver a result, so `cancel_request` must not leave a
|
|
532
|
+
* `result_schema_unsatisfied` behind - "the work was called off" and "the answer
|
|
533
|
+
* never arrived" are different facts and only the second is a contract failure
|
|
534
|
+
* (section 2.3.7). One helper rather than three copies, because the copy that
|
|
535
|
+
* forgets this line is a defect nobody notices for a release.
|
|
536
|
+
*/
|
|
537
|
+
private haltRequestOnBudget;
|
|
538
|
+
/**
|
|
539
|
+
* The request-level budgets, at a boundary the engine can survive.
|
|
540
|
+
*
|
|
541
|
+
* RETURNS TRUE WHEN IT STOPPED THE REQUEST. The process is never touched: calling
|
|
542
|
+
* `controller.abort()` ends the model's current loop, and the next `prompt()` starts
|
|
543
|
+
* a fresh one - which is the whole of I-R1.
|
|
544
|
+
*/
|
|
545
|
+
private stopRequestOnBudget;
|
|
546
|
+
/**
|
|
547
|
+
* "ACCEPTED MEANS FINISHED", enforced at the one event that follows a tool result
|
|
548
|
+
* (section 2.3.6).
|
|
549
|
+
*
|
|
550
|
+
* `turn_end` fires BEFORE `tool_execution_start`, so by the time this runs the turn
|
|
551
|
+
* has already been counted and `agent_end` cannot mistake the abort for a run that
|
|
552
|
+
* produced nothing.
|
|
553
|
+
*/
|
|
554
|
+
private checkResultComplete;
|
|
183
555
|
/**
|
|
184
556
|
* The wall clock starts at the FIRST `prompt()`, not at process start: the
|
|
185
557
|
* caller is bounding the run, and config resolution plus a skills scan are not
|
|
@@ -258,7 +630,29 @@ export declare class ExecRunner {
|
|
|
258
630
|
*/
|
|
259
631
|
requestInterrupt(): void;
|
|
260
632
|
private onSignal;
|
|
633
|
+
/**
|
|
634
|
+
* THE ONE STAMPING POINT (section 2.3.10, I-T10; review P1-1).
|
|
635
|
+
*
|
|
636
|
+
* NOT `correlation()`, which returns only `{requestId}` and is spread into a handful of
|
|
637
|
+
* events: the thread id has to be on EVERY line this runner produces, including the ones
|
|
638
|
+
* that carry no request (a `todo` snapshot, a `job` update). Stamping here and in
|
|
639
|
+
* `writeToBridge` covers the whole file - and AC-54 turns the claim into a single
|
|
640
|
+
* universal assertion ("every line with a sessionId has a threadId") rather than a
|
|
641
|
+
* per-event checklist that a new event can quietly escape.
|
|
642
|
+
*/
|
|
261
643
|
private emit;
|
|
644
|
+
/**
|
|
645
|
+
* The same stamp for the frames that bypass `emit()`.
|
|
646
|
+
*
|
|
647
|
+
* SIX SITES USE THIS AND THEY ARE ALL ACKS AND CONTROL FRAMES: steer ack, interrupt ack,
|
|
648
|
+
* the cancelled `request_end`, the request ack, the settled `request_end` and the
|
|
649
|
+
* `steering` receipt. A seventh added later that writes to `bridge` directly would lose
|
|
650
|
+
* the stamp - which is why the method exists rather than six copies of a conditional
|
|
651
|
+
* spread.
|
|
652
|
+
*/
|
|
653
|
+
private writeToBridge;
|
|
654
|
+
/** The correlation fields every attributable event carries in bridge mode. */
|
|
655
|
+
private correlation;
|
|
262
656
|
/** Caller-facing prose. STDERR ALWAYS - stdout carries nothing but the schema. */
|
|
263
657
|
private note;
|
|
264
658
|
private onAgentEvent;
|