@gajae-code/coding-agent 0.3.0 → 0.3.1
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 +18 -0
- package/dist/types/async/job-manager.d.ts +7 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +4 -4
- package/dist/types/debug/crash-diagnostics.d.ts +45 -0
- package/dist/types/debug/runtime-gauges.d.ts +6 -0
- package/dist/types/deep-interview/render-middleware.d.ts +1 -0
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/eval/py/kernel.d.ts +2 -0
- package/dist/types/exec/bash-executor.d.ts +10 -0
- package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +9 -0
- package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +10 -0
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +3 -2
- package/dist/types/hooks/skill-state.d.ts +21 -0
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
- package/dist/types/internal-urls/types.d.ts +4 -0
- package/dist/types/lsp/index.d.ts +10 -10
- package/dist/types/modes/bridge/auth.d.ts +12 -0
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
- package/dist/types/modes/bridge/event-stream.d.ts +8 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
- package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
- package/dist/types/modes/index.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/jobs-observer.d.ts +57 -0
- package/dist/types/modes/rpc/host-tools.d.ts +1 -16
- package/dist/types/modes/rpc/host-uris.d.ts +1 -38
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
- package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +11 -1
- package/dist/types/skill-state/workflow-state-contract.d.ts +1 -2
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/task/id.d.ts +7 -0
- package/dist/types/task/index.d.ts +5 -0
- package/dist/types/task/receipt.d.ts +85 -0
- package/dist/types/task/spawn-gate.d.ts +38 -0
- package/dist/types/task/types.d.ts +143 -11
- package/dist/types/tools/cron.d.ts +6 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/subagent.d.ts +15 -0
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +36 -0
- package/src/cli/args.ts +9 -2
- package/src/commands/deep-interview.ts +1 -0
- package/src/commands/harness.ts +289 -19
- package/src/commands/launch.ts +2 -2
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +22 -4
- package/src/config/keybindings.ts +6 -0
- package/src/config/settings-schema.ts +6 -3
- package/src/dap/client.ts +17 -3
- package/src/debug/crash-diagnostics.ts +223 -0
- package/src/debug/runtime-gauges.ts +20 -0
- package/src/deep-interview/render-middleware.ts +6 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +28 -2
- package/src/eval/py/executor.ts +21 -1
- package/src/eval/py/kernel.ts +15 -0
- package/src/exec/bash-executor.ts +41 -0
- package/src/gjc-runtime/cli-write-receipt.ts +31 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +69 -32
- package/src/gjc-runtime/ralplan-runtime.ts +213 -36
- package/src/gjc-runtime/state-migrations.ts +54 -7
- package/src/gjc-runtime/state-runtime.ts +461 -64
- package/src/gjc-runtime/state-schema.ts +192 -0
- package/src/gjc-runtime/state-writer.ts +32 -1
- package/src/gjc-runtime/team-runtime.ts +177 -105
- package/src/gjc-runtime/ultragoal-runtime.ts +114 -26
- package/src/gjc-runtime/workflow-command-ref.ts +239 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +108 -4
- package/src/gjc-runtime/workflow-manifest.ts +3 -1
- package/src/harness-control-plane/control-endpoint.ts +19 -8
- package/src/harness-control-plane/owner.ts +57 -10
- package/src/harness-control-plane/state-machine.ts +2 -1
- package/src/hooks/skill-state.ts +176 -26
- package/src/internal-urls/agent-protocol.ts +68 -21
- package/src/internal-urls/artifact-protocol.ts +12 -17
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/registry-helpers.ts +19 -16
- package/src/internal-urls/types.ts +4 -0
- package/src/lsp/client.ts +18 -2
- package/src/main.ts +21 -5
- package/src/modes/bridge/auth.ts +41 -0
- package/src/modes/bridge/bridge-client-bridge.ts +47 -0
- package/src/modes/bridge/bridge-mode.ts +520 -0
- package/src/modes/bridge/bridge-ui-context.ts +200 -0
- package/src/modes/bridge/event-stream.ts +70 -0
- package/src/modes/components/custom-editor.ts +101 -0
- package/src/modes/components/hook-selector.ts +61 -18
- package/src/modes/components/jobs-overlay-model.ts +109 -0
- package/src/modes/components/jobs-overlay.ts +172 -0
- package/src/modes/components/status-line/presets.ts +7 -5
- package/src/modes/components/status-line/segments.ts +25 -0
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/status-line.ts +9 -1
- package/src/modes/controllers/extension-ui-controller.ts +39 -3
- package/src/modes/controllers/input-controller.ts +97 -9
- package/src/modes/controllers/selector-controller.ts +29 -0
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +27 -0
- package/src/modes/jobs-observer.ts +204 -0
- package/src/modes/rpc/host-tools.ts +1 -186
- package/src/modes/rpc/host-uris.ts +1 -235
- package/src/modes/rpc/rpc-client.ts +25 -10
- package/src/modes/rpc/rpc-mode.ts +12 -381
- package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
- package/src/modes/shared/agent-wire/command-validation.ts +131 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
- package/src/modes/shared/agent-wire/handshake.ts +117 -0
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
- package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
- package/src/modes/shared/agent-wire/protocol.ts +96 -0
- package/src/modes/shared/agent-wire/responses.ts +17 -0
- package/src/modes/shared/agent-wire/scopes.ts +89 -0
- package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
- package/src/modes/shared/agent-wire/ui-result.ts +48 -0
- package/src/modes/types.ts +1 -0
- package/src/prompts/tools/subagent.md +12 -7
- package/src/prompts/tools/task-summary.md +3 -9
- package/src/prompts/tools/task.md +5 -1
- package/src/sdk.ts +4 -0
- package/src/session/agent-session.ts +214 -38
- package/src/skill-state/deep-interview-mutation-guard.ts +23 -4
- package/src/skill-state/workflow-state-contract.ts +7 -4
- package/src/skill-state/workflow-state-version.ts +3 -0
- package/src/slash-commands/builtin-registry.ts +8 -0
- package/src/task/executor.ts +29 -5
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +257 -67
- package/src/task/output-manager.ts +5 -4
- package/src/task/receipt.ts +297 -0
- package/src/task/render.ts +48 -131
- package/src/task/spawn-gate.ts +132 -0
- package/src/task/types.ts +48 -7
- package/src/tools/ask.ts +73 -33
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash.ts +1 -1
- package/src/tools/cron.ts +48 -0
- package/src/tools/find.ts +4 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +1 -0
- package/src/tools/search.ts +1 -0
- package/src/tools/skill.ts +6 -1
- package/src/tools/subagent.ts +237 -84
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import type { SingleResult, TaskToolDetails } from "./types";
|
|
2
|
+
|
|
3
|
+
export interface TaskRoi {
|
|
4
|
+
tokens: number;
|
|
5
|
+
contextTokens?: number;
|
|
6
|
+
clonedTokens?: number;
|
|
7
|
+
costTotal?: number;
|
|
8
|
+
outputBytes?: number;
|
|
9
|
+
outputLines?: number;
|
|
10
|
+
producedChanges: boolean;
|
|
11
|
+
materialContribution: boolean;
|
|
12
|
+
lowRoi: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface TaskResultReceipt {
|
|
15
|
+
index: number;
|
|
16
|
+
id: string;
|
|
17
|
+
agent: string;
|
|
18
|
+
agentSource: SingleResult["agentSource"];
|
|
19
|
+
task: string;
|
|
20
|
+
assignment?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
status: "completed" | "failed" | "aborted" | "merge_failed" | "paused";
|
|
23
|
+
exitCode: number;
|
|
24
|
+
aborted?: boolean;
|
|
25
|
+
paused?: boolean;
|
|
26
|
+
truncated: boolean;
|
|
27
|
+
durationMs: number;
|
|
28
|
+
tokens: number;
|
|
29
|
+
contextTokens?: number;
|
|
30
|
+
contextWindow?: number;
|
|
31
|
+
modelOverride?: string | string[];
|
|
32
|
+
usage?: SingleResult["usage"];
|
|
33
|
+
cost?: number;
|
|
34
|
+
branchName?: string;
|
|
35
|
+
retryFailure?: { attempt: number; errorSummary: string };
|
|
36
|
+
errorSummary?: string;
|
|
37
|
+
abortSummary?: string;
|
|
38
|
+
preview: string;
|
|
39
|
+
previewTruncated: boolean;
|
|
40
|
+
outputRef?: { uri: string; sizeBytes: number; lineCount: number; sha256?: string };
|
|
41
|
+
outputUnavailable?: boolean;
|
|
42
|
+
review?: {
|
|
43
|
+
overallCorrectness?: string;
|
|
44
|
+
findingCount: number;
|
|
45
|
+
findings?: Array<{ severity?: string; summary: string }>;
|
|
46
|
+
};
|
|
47
|
+
extractedToolCounts?: Record<string, number>;
|
|
48
|
+
forkContext?: SingleResult["forkContext"];
|
|
49
|
+
roi?: TaskRoi;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const BANNED_RAW_TASK_KEYS = new Set([
|
|
53
|
+
"output",
|
|
54
|
+
"stderr",
|
|
55
|
+
"extractedToolData",
|
|
56
|
+
"resultText",
|
|
57
|
+
"errorText",
|
|
58
|
+
"artifactPayload",
|
|
59
|
+
"rawResult",
|
|
60
|
+
"rawResults",
|
|
61
|
+
"rawNestedResults",
|
|
62
|
+
"fullOutput",
|
|
63
|
+
"full_result",
|
|
64
|
+
"toolOutput",
|
|
65
|
+
"toolResultRaw",
|
|
66
|
+
"stdout",
|
|
67
|
+
"rawOutput",
|
|
68
|
+
"recentOutput",
|
|
69
|
+
"currentToolArgs",
|
|
70
|
+
"inflightTaskDetails",
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
function truncateText(value: string | undefined, maxChars: number): string | undefined {
|
|
74
|
+
if (!value) return undefined;
|
|
75
|
+
return value.length > maxChars ? value.slice(0, maxChars) : value;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function buildSafeSynopsis(raw: SingleResult, outputRef: TaskResultReceipt["outputRef"]): string {
|
|
79
|
+
const status = getStatus(raw);
|
|
80
|
+
if (raw.retryFailure) {
|
|
81
|
+
return `Task ${status}; retry stopped after attempt ${raw.retryFailure.attempt}.`;
|
|
82
|
+
}
|
|
83
|
+
if (raw.abortReason) {
|
|
84
|
+
return `Task ${status}; abort reason recorded.`;
|
|
85
|
+
}
|
|
86
|
+
if (raw.error) {
|
|
87
|
+
return `Task ${status}; error recorded.`;
|
|
88
|
+
}
|
|
89
|
+
if (outputRef) {
|
|
90
|
+
return `Task ${status}; output stored in ${outputRef.uri} (${outputRef.lineCount} lines, ${outputRef.sizeBytes} bytes).`;
|
|
91
|
+
}
|
|
92
|
+
return `Task ${status}; output artifact unavailable.`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function getStatus(raw: SingleResult): TaskResultReceipt["status"] {
|
|
96
|
+
if (raw.paused) return "paused";
|
|
97
|
+
if (raw.aborted) return "aborted";
|
|
98
|
+
if (raw.exitCode === 0 && raw.error) return "merge_failed";
|
|
99
|
+
if (raw.exitCode !== 0 || raw.error) return "failed";
|
|
100
|
+
return "completed";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function buildReview(raw: SingleResult): TaskResultReceipt["review"] | undefined {
|
|
104
|
+
const data = raw.extractedToolData;
|
|
105
|
+
if (!data) return undefined;
|
|
106
|
+
const yields = Array.isArray(data.yield) ? data.yield : [];
|
|
107
|
+
const reviewYield = yields
|
|
108
|
+
.map(item => (item && typeof item === "object" ? (item as { data?: unknown }).data : undefined))
|
|
109
|
+
.findLast(item => item && typeof item === "object" && "overall_correctness" in item) as
|
|
110
|
+
| { overall_correctness?: unknown }
|
|
111
|
+
| undefined;
|
|
112
|
+
const rawFindings = Array.isArray(data.report_finding) ? data.report_finding : [];
|
|
113
|
+
const findings = rawFindings.slice(0, 20).map(item => {
|
|
114
|
+
const value = item && typeof item === "object" ? (item as Record<string, unknown>) : {};
|
|
115
|
+
const severity =
|
|
116
|
+
typeof value.severity === "string"
|
|
117
|
+
? value.severity
|
|
118
|
+
: typeof value.priority === "string"
|
|
119
|
+
? value.priority
|
|
120
|
+
: undefined;
|
|
121
|
+
const summaryValue = value.summary ?? value.title ?? value.message ?? value.body ?? "finding";
|
|
122
|
+
return { severity, summary: truncateText(String(summaryValue), 200) ?? "finding" };
|
|
123
|
+
});
|
|
124
|
+
if (!reviewYield && findings.length === 0) return undefined;
|
|
125
|
+
return {
|
|
126
|
+
overallCorrectness:
|
|
127
|
+
typeof reviewYield?.overall_correctness === "string" ? reviewYield.overall_correctness : undefined,
|
|
128
|
+
findingCount: rawFindings.length,
|
|
129
|
+
findings: findings.length > 0 ? findings : undefined,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function hasReviewFindings(raw: SingleResult): boolean {
|
|
134
|
+
const findings = raw.extractedToolData?.report_finding;
|
|
135
|
+
return Array.isArray(findings) && findings.length > 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function hasNonEmptyPreview(raw: SingleResult): boolean {
|
|
139
|
+
return Boolean((raw.output.trim() || raw.stderr.trim()).trim());
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Heuristic task ROI signal built only from receipt-safe accounting fields.
|
|
144
|
+
* Advisory only: these flags never change task success/failure semantics.
|
|
145
|
+
*/
|
|
146
|
+
export function buildTaskRoi(raw: SingleResult): TaskRoi {
|
|
147
|
+
const outputBytes = raw.outputMeta?.byteSize ?? (raw.outputMeta ? Buffer.byteLength(raw.output, "utf8") : undefined);
|
|
148
|
+
const outputLines = raw.outputMeta?.lineCount;
|
|
149
|
+
const producedChanges =
|
|
150
|
+
raw.producedChanges ??
|
|
151
|
+
Boolean(raw.branchName || (Array.isArray(raw.nestedPatches) && raw.nestedPatches.length > 0));
|
|
152
|
+
const status = getStatus(raw);
|
|
153
|
+
const terminal = status !== "paused" && !raw.aborted;
|
|
154
|
+
const materialContribution = Boolean(
|
|
155
|
+
producedChanges ||
|
|
156
|
+
(outputBytes !== undefined && outputBytes > 0) ||
|
|
157
|
+
hasReviewFindings(raw) ||
|
|
158
|
+
(status === "completed" && raw.tokens > 0 && hasNonEmptyPreview(raw)),
|
|
159
|
+
);
|
|
160
|
+
const lowRoi = terminal && raw.tokens > 0 && !materialContribution;
|
|
161
|
+
return {
|
|
162
|
+
tokens: raw.tokens,
|
|
163
|
+
contextTokens: raw.contextTokens,
|
|
164
|
+
clonedTokens: raw.forkContext?.clonedTokens,
|
|
165
|
+
costTotal: raw.usage?.cost.total,
|
|
166
|
+
outputBytes,
|
|
167
|
+
outputLines,
|
|
168
|
+
producedChanges,
|
|
169
|
+
materialContribution,
|
|
170
|
+
lowRoi,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function buildTaskRoiSummary(receipts: readonly TaskResultReceipt[]): TaskToolDetails["roiSummary"] {
|
|
175
|
+
const totalCostTotal = receipts.reduce((total, receipt) => total + (receipt.roi?.costTotal ?? 0), 0);
|
|
176
|
+
const totalClonedTokens = receipts.reduce((total, receipt) => total + (receipt.roi?.clonedTokens ?? 0), 0);
|
|
177
|
+
return {
|
|
178
|
+
childCount: receipts.length,
|
|
179
|
+
totalTokens: receipts.reduce((total, receipt) => total + (receipt.roi?.tokens ?? receipt.tokens), 0),
|
|
180
|
+
totalCostTotal: totalCostTotal > 0 ? totalCostTotal : undefined,
|
|
181
|
+
totalClonedTokens: totalClonedTokens > 0 ? totalClonedTokens : undefined,
|
|
182
|
+
lowRoiChildIds: receipts.filter(receipt => receipt.roi?.lowRoi).map(receipt => receipt.id),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function buildTaskReceipt(raw: SingleResult): TaskResultReceipt {
|
|
187
|
+
const outputRef = raw.outputMeta
|
|
188
|
+
? {
|
|
189
|
+
uri: `agent://${raw.id}`,
|
|
190
|
+
sizeBytes: raw.outputMeta.byteSize ?? Buffer.byteLength(raw.output, "utf8"),
|
|
191
|
+
lineCount: raw.outputMeta.lineCount,
|
|
192
|
+
sha256: raw.outputMeta.sha256,
|
|
193
|
+
}
|
|
194
|
+
: undefined;
|
|
195
|
+
const preview = buildSafeSynopsis(raw, outputRef);
|
|
196
|
+
const extractedToolCounts = raw.extractedToolData
|
|
197
|
+
? Object.fromEntries(
|
|
198
|
+
Object.entries(raw.extractedToolData).map(([tool, values]) => [
|
|
199
|
+
tool,
|
|
200
|
+
Array.isArray(values) ? values.length : 0,
|
|
201
|
+
]),
|
|
202
|
+
)
|
|
203
|
+
: undefined;
|
|
204
|
+
return {
|
|
205
|
+
index: raw.index,
|
|
206
|
+
id: raw.id,
|
|
207
|
+
agent: raw.agent,
|
|
208
|
+
agentSource: raw.agentSource,
|
|
209
|
+
task: raw.task,
|
|
210
|
+
assignment: raw.assignment,
|
|
211
|
+
description: raw.description,
|
|
212
|
+
status: getStatus(raw),
|
|
213
|
+
exitCode: raw.exitCode,
|
|
214
|
+
aborted: raw.aborted,
|
|
215
|
+
paused: raw.paused,
|
|
216
|
+
truncated: raw.truncated,
|
|
217
|
+
durationMs: raw.durationMs,
|
|
218
|
+
tokens: raw.tokens,
|
|
219
|
+
contextTokens: raw.contextTokens,
|
|
220
|
+
contextWindow: raw.contextWindow,
|
|
221
|
+
modelOverride: raw.modelOverride,
|
|
222
|
+
usage: raw.usage,
|
|
223
|
+
cost: raw.usage?.cost.total,
|
|
224
|
+
branchName: raw.branchName,
|
|
225
|
+
retryFailure: raw.retryFailure
|
|
226
|
+
? { attempt: raw.retryFailure.attempt, errorSummary: "Retry failure recorded." }
|
|
227
|
+
: undefined,
|
|
228
|
+
errorSummary: raw.error ? "Error recorded." : undefined,
|
|
229
|
+
abortSummary: raw.abortReason ? "Abort reason recorded." : undefined,
|
|
230
|
+
preview,
|
|
231
|
+
previewTruncated: false,
|
|
232
|
+
outputRef,
|
|
233
|
+
outputUnavailable: outputRef ? undefined : true,
|
|
234
|
+
review: buildReview(raw),
|
|
235
|
+
extractedToolCounts,
|
|
236
|
+
forkContext: raw.forkContext,
|
|
237
|
+
roi: buildTaskRoi(raw),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Raw, pre-sanitization task details: the internal shape produced during task
|
|
243
|
+
* execution, where `results` are full `SingleResult` objects. The public
|
|
244
|
+
* `TaskToolDetails` exposes only receipts.
|
|
245
|
+
*/
|
|
246
|
+
export interface RawTaskToolDetails {
|
|
247
|
+
projectAgentsDir: string | null;
|
|
248
|
+
results: SingleResult[];
|
|
249
|
+
totalDurationMs: number;
|
|
250
|
+
usage?: TaskToolDetails["usage"];
|
|
251
|
+
async?: TaskToolDetails["async"];
|
|
252
|
+
forkContextClonedTokens?: number;
|
|
253
|
+
roiSummary?: TaskToolDetails["roiSummary"];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Central converter from raw task details to receipt-only public details. */
|
|
257
|
+
export function sanitizeTaskToolDetails(raw: RawTaskToolDetails): TaskToolDetails {
|
|
258
|
+
return {
|
|
259
|
+
projectAgentsDir: raw.projectAgentsDir,
|
|
260
|
+
results: raw.results.map(buildTaskReceipt),
|
|
261
|
+
totalDurationMs: raw.totalDurationMs,
|
|
262
|
+
usage: raw.usage,
|
|
263
|
+
forkContextClonedTokens: raw.forkContextClonedTokens,
|
|
264
|
+
roiSummary: raw.roiSummary ?? buildTaskRoiSummary(raw.results.map(buildTaskReceipt)),
|
|
265
|
+
async: raw.async,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function findRawTaskLeakKeys(value: unknown): string[] {
|
|
270
|
+
const found = new Set<string>();
|
|
271
|
+
const seen = new WeakSet<object>();
|
|
272
|
+
const visit = (current: unknown) => {
|
|
273
|
+
if (!current || typeof current !== "object") return;
|
|
274
|
+
if (seen.has(current)) return;
|
|
275
|
+
seen.add(current);
|
|
276
|
+
if (Array.isArray(current)) {
|
|
277
|
+
for (const item of current) visit(item);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
for (const [key, child] of Object.entries(current)) {
|
|
281
|
+
// Banned keys only leak when they carry text or structure. A numeric
|
|
282
|
+
// value (e.g. the `output` token count on a canonical `Usage` record,
|
|
283
|
+
// whose shape is `input/output/cacheRead/cacheWrite/totalTokens`) is safe.
|
|
284
|
+
if (BANNED_RAW_TASK_KEYS.has(key) && typeof child !== "number") found.add(key);
|
|
285
|
+
visit(child);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
visit(value);
|
|
289
|
+
return [...found].sort();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function assertNoRawTaskFields(value: unknown, surface: string): void {
|
|
293
|
+
const keys = findRawTaskLeakKeys(value);
|
|
294
|
+
if (keys.length > 0) {
|
|
295
|
+
throw new Error(`${surface} contains raw task fields: ${keys.join(", ")}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
package/src/task/render.ts
CHANGED
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import type { Component } from "@gajae-code/tui";
|
|
9
|
-
import {
|
|
9
|
+
import { Text } from "@gajae-code/tui";
|
|
10
10
|
import { formatNumber } from "@gajae-code/utils";
|
|
11
11
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
12
12
|
import type { Theme } from "../modes/theme/theme";
|
|
13
13
|
import {
|
|
14
14
|
formatBadge,
|
|
15
|
+
formatBytes,
|
|
15
16
|
formatDuration,
|
|
16
17
|
formatMoreItems,
|
|
17
18
|
formatStatusIcon,
|
|
@@ -27,8 +28,9 @@ import {
|
|
|
27
28
|
type SubmitReviewDetails,
|
|
28
29
|
} from "../tools/review";
|
|
29
30
|
import { Ellipsis, Hasher, type RenderCache, renderStatusLine } from "../tui";
|
|
31
|
+
import type { TaskResultReceipt } from "./receipt";
|
|
30
32
|
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
31
|
-
import type { AgentProgress,
|
|
33
|
+
import type { AgentProgress, TaskParams, TaskToolDetails } from "./types";
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* Get status icon for agent state.
|
|
@@ -139,21 +141,6 @@ function formatTaskId(id: string): string {
|
|
|
139
141
|
return `${indices} ${labels}`;
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
const MISSING_YIELD_WARNING_PREFIX = "SYSTEM WARNING: Subagent exited without calling yield tool";
|
|
143
|
-
|
|
144
|
-
function extractMissingYieldWarning(output: string): { warning?: string; rest: string } {
|
|
145
|
-
const lines = output.split("\n");
|
|
146
|
-
const firstLine = lines[0]?.trim() ?? "";
|
|
147
|
-
if (!firstLine.startsWith(MISSING_YIELD_WARNING_PREFIX)) {
|
|
148
|
-
return { rest: output };
|
|
149
|
-
}
|
|
150
|
-
const rest = lines
|
|
151
|
-
.slice(1)
|
|
152
|
-
.join("\n")
|
|
153
|
-
.replace(/^\s*\n+/, "");
|
|
154
|
-
return { warning: firstLine, rest };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
144
|
function buildTreePrefix(ancestors: boolean[], theme: Theme): string {
|
|
158
145
|
return ancestors.map(hasNext => (hasNext ? `${theme.tree.vertical} ` : " ")).join("");
|
|
159
146
|
}
|
|
@@ -804,35 +791,24 @@ function renderFindings(
|
|
|
804
791
|
/**
|
|
805
792
|
* Render final result for a single agent.
|
|
806
793
|
*/
|
|
807
|
-
function renderAgentResult(result:
|
|
794
|
+
function renderAgentResult(result: TaskResultReceipt, isLast: boolean, expanded: boolean, theme: Theme): string[] {
|
|
808
795
|
const lines: string[] = [];
|
|
809
796
|
const prefix = isLast ? theme.fg("dim", theme.tree.last) : theme.fg("dim", theme.tree.branch);
|
|
810
797
|
const continuePrefix = isLast ? " " : `${theme.fg("dim", theme.tree.vertical)} `;
|
|
811
798
|
|
|
812
|
-
const
|
|
813
|
-
const
|
|
814
|
-
const
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
: success
|
|
822
|
-
? theme.status.success
|
|
799
|
+
const success = result.status === "completed";
|
|
800
|
+
const mergeFailed = result.status === "merge_failed";
|
|
801
|
+
const aborted = result.status === "aborted";
|
|
802
|
+
const icon = success
|
|
803
|
+
? theme.status.success
|
|
804
|
+
: aborted
|
|
805
|
+
? theme.status.aborted
|
|
806
|
+
: mergeFailed
|
|
807
|
+
? theme.status.warning
|
|
823
808
|
: theme.status.error;
|
|
824
|
-
const iconColor =
|
|
825
|
-
const statusText =
|
|
826
|
-
? "aborted"
|
|
827
|
-
: needsWarning
|
|
828
|
-
? "warning"
|
|
829
|
-
: success
|
|
830
|
-
? "done"
|
|
831
|
-
: mergeFailed
|
|
832
|
-
? "merge failed"
|
|
833
|
-
: "failed";
|
|
809
|
+
const iconColor = success ? "success" : mergeFailed ? "warning" : "error";
|
|
810
|
+
const statusText = mergeFailed ? "merge failed" : success ? "done" : result.status;
|
|
834
811
|
|
|
835
|
-
// Main status line: id: description [status] · stats · ⟨agent⟩
|
|
836
812
|
const description = result.description?.trim();
|
|
837
813
|
const displayId = formatTaskId(result.id);
|
|
838
814
|
const titlePart = description ? `${theme.bold(displayId)}: ${description}` : displayId;
|
|
@@ -847,121 +823,62 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
847
823
|
tokens: result.tokens,
|
|
848
824
|
contextTokens: result.contextTokens,
|
|
849
825
|
contextWindow: result.contextWindow,
|
|
850
|
-
cost: result.usage?.cost.total ?? 0,
|
|
826
|
+
cost: result.cost ?? result.usage?.cost.total ?? 0,
|
|
851
827
|
},
|
|
852
828
|
theme,
|
|
853
829
|
);
|
|
854
830
|
statusLine += `${theme.sep.dot}${theme.fg("dim", formatDuration(result.durationMs))}`;
|
|
855
|
-
|
|
856
|
-
if (result.truncated) {
|
|
831
|
+
if (result.truncated || result.previewTruncated) {
|
|
857
832
|
statusLine += ` ${theme.fg("warning", "[truncated]")}`;
|
|
858
833
|
}
|
|
859
|
-
|
|
860
834
|
lines.push(statusLine);
|
|
861
835
|
|
|
862
836
|
lines.push(...renderTaskSection(result.assignment ?? result.task, continuePrefix, expanded, theme));
|
|
863
837
|
|
|
864
|
-
if (
|
|
865
|
-
|
|
866
|
-
`${continuePrefix}${theme.fg("
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
const reviewData = completeData
|
|
875
|
-
?.map(c => c.data as SubmitReviewDetails)
|
|
876
|
-
.filter(d => d && typeof d === "object" && "overall_correctness" in d);
|
|
877
|
-
const submitReviewData = reviewData && reviewData.length > 0 ? reviewData : undefined;
|
|
878
|
-
|
|
879
|
-
if (submitReviewData && submitReviewData.length > 0) {
|
|
880
|
-
// Use combined review renderer
|
|
881
|
-
const summary = submitReviewData[submitReviewData.length - 1];
|
|
882
|
-
const findings = reportFindingData;
|
|
883
|
-
lines.push(...renderReviewResult(summary, findings, continuePrefix, expanded, theme));
|
|
884
|
-
return lines;
|
|
885
|
-
}
|
|
886
|
-
if (reportFindingData.length > 0) {
|
|
887
|
-
const hasCompleteData = completeData && completeData.length > 0;
|
|
888
|
-
const message = hasCompleteData
|
|
889
|
-
? "Review verdict missing expected fields"
|
|
890
|
-
: "Review incomplete (yield not called)";
|
|
891
|
-
lines.push(`${continuePrefix}${theme.fg("warning", theme.status.warning)} ${theme.fg("dim", message)}`);
|
|
892
|
-
lines.push(`${continuePrefix}${formatFindingSummary(reportFindingData, theme)}`);
|
|
893
|
-
lines.push(...renderFindings(reportFindingData, continuePrefix, expanded, theme));
|
|
894
|
-
return lines;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
// Check for extracted tool data with custom renderers (skip review tools)
|
|
898
|
-
let hasCustomRendering = false;
|
|
899
|
-
const deferredToolLines: string[] = [];
|
|
900
|
-
if (result.extractedToolData) {
|
|
901
|
-
for (const [toolName, dataArray] of Object.entries(result.extractedToolData)) {
|
|
902
|
-
// Skip review tools - handled above
|
|
903
|
-
if (toolName === "yield" || toolName === "report_finding") continue;
|
|
904
|
-
|
|
905
|
-
const handler = subprocessToolRegistry.getHandler(toolName);
|
|
906
|
-
if (handler?.renderFinal && (dataArray as unknown[]).length > 0) {
|
|
907
|
-
const isTaskTool = toolName === "task";
|
|
908
|
-
const component = handler.renderFinal(dataArray as unknown[], theme, expanded);
|
|
909
|
-
const target = isTaskTool ? deferredToolLines : lines;
|
|
910
|
-
if (!isTaskTool) {
|
|
911
|
-
hasCustomRendering = true;
|
|
912
|
-
target.push(`${continuePrefix}${theme.fg("dim", `Tool: ${toolName}`)}`);
|
|
913
|
-
}
|
|
914
|
-
if (component instanceof Text) {
|
|
915
|
-
// Prefix each line with continuePrefix
|
|
916
|
-
const text = component.getText();
|
|
917
|
-
for (const line of text.split("\n")) {
|
|
918
|
-
target.push(`${continuePrefix}${line}`);
|
|
919
|
-
}
|
|
920
|
-
} else if (component instanceof Container) {
|
|
921
|
-
// For containers, render each child
|
|
922
|
-
for (const child of (component as Container).children) {
|
|
923
|
-
if (child instanceof Text) {
|
|
924
|
-
target.push(`${continuePrefix}${child.getText()}`);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
838
|
+
if (result.review) {
|
|
839
|
+
if (result.review.overallCorrectness) {
|
|
840
|
+
lines.push(`${continuePrefix}${theme.fg("dim", `Review: ${result.review.overallCorrectness}`)}`);
|
|
841
|
+
}
|
|
842
|
+
if (result.review.findingCount > 0) {
|
|
843
|
+
lines.push(`${continuePrefix}${theme.fg("dim", `${result.review.findingCount} findings`)}`);
|
|
844
|
+
if (expanded && result.review.findings) {
|
|
845
|
+
for (const finding of result.review.findings) {
|
|
846
|
+
const severity = finding.severity ? `${finding.severity}: ` : "";
|
|
847
|
+
lines.push(`${continuePrefix}${theme.fg("dim", `- ${severity}${finding.summary}`)}`);
|
|
927
848
|
}
|
|
928
849
|
}
|
|
929
850
|
}
|
|
851
|
+
} else {
|
|
852
|
+
lines.push(...renderOutputSection(result.preview, continuePrefix, expanded, theme, 3, 12));
|
|
853
|
+
}
|
|
854
|
+
if (result.roi?.lowRoi) {
|
|
855
|
+
lines.push(`${continuePrefix}${theme.fg("warning", "low ROI: produced no material contribution")}`);
|
|
930
856
|
}
|
|
931
857
|
|
|
932
|
-
if (
|
|
858
|
+
if (result.outputRef) {
|
|
933
859
|
lines.push(
|
|
934
|
-
`${continuePrefix}${theme.fg(
|
|
860
|
+
`${continuePrefix}${theme.fg(
|
|
935
861
|
"dim",
|
|
936
|
-
|
|
862
|
+
`Output: ${result.outputRef.uri} (${formatBytes(result.outputRef.sizeBytes)}, ${result.outputRef.lineCount} lines)`,
|
|
937
863
|
)}`,
|
|
938
864
|
);
|
|
865
|
+
} else if (result.outputUnavailable) {
|
|
866
|
+
lines.push(`${continuePrefix}${theme.fg("dim", "Output artifact unavailable")}`);
|
|
939
867
|
}
|
|
940
868
|
|
|
941
|
-
|
|
942
|
-
|
|
869
|
+
if (result.branchName && success) {
|
|
870
|
+
lines.push(`${continuePrefix}${theme.fg("dim", `Branch: ${result.branchName}`)}`);
|
|
871
|
+
}
|
|
872
|
+
if (result.abortSummary) {
|
|
943
873
|
lines.push(
|
|
944
|
-
|
|
874
|
+
`${continuePrefix}${theme.fg("error", theme.status.aborted)} ${theme.fg("dim", truncateToWidth(replaceTabs(result.abortSummary), 80))}`,
|
|
945
875
|
);
|
|
946
876
|
}
|
|
947
|
-
|
|
948
|
-
if (deferredToolLines.length > 0) {
|
|
949
|
-
lines.push(...deferredToolLines);
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
if (result.patchPath && !aborted && result.exitCode === 0) {
|
|
953
|
-
lines.push(`${continuePrefix}${theme.fg("dim", `Patch: ${result.patchPath}`)}`);
|
|
954
|
-
} else if (result.branchName && !aborted && result.exitCode === 0) {
|
|
955
|
-
lines.push(`${continuePrefix}${theme.fg("dim", `Branch: ${result.branchName}`)}`);
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
// Error message
|
|
959
|
-
if (result.error && (!success || mergeFailed) && (!aborted || result.error !== result.abortReason)) {
|
|
877
|
+
if (result.errorSummary && (!success || mergeFailed)) {
|
|
960
878
|
lines.push(
|
|
961
|
-
`${continuePrefix}${theme.fg(mergeFailed ? "warning" : "error", truncateToWidth(replaceTabs(result.
|
|
879
|
+
`${continuePrefix}${theme.fg(mergeFailed ? "warning" : "error", truncateToWidth(replaceTabs(result.errorSummary), 70))}`,
|
|
962
880
|
);
|
|
963
881
|
}
|
|
964
|
-
|
|
965
882
|
return lines;
|
|
966
883
|
}
|
|
967
884
|
|
|
@@ -1009,9 +926,9 @@ export function renderResult(
|
|
|
1009
926
|
lines.push(...renderAgentResult(res, isLast, expanded, theme));
|
|
1010
927
|
});
|
|
1011
928
|
|
|
1012
|
-
const abortedCount = details.results.filter(r => r.aborted).length;
|
|
1013
|
-
const mergeFailedCount = details.results.filter(r =>
|
|
1014
|
-
const successCount = details.results.filter(r =>
|
|
929
|
+
const abortedCount = details.results.filter(r => r.status === "aborted").length;
|
|
930
|
+
const mergeFailedCount = details.results.filter(r => r.status === "merge_failed").length;
|
|
931
|
+
const successCount = details.results.filter(r => r.status === "completed").length;
|
|
1015
932
|
const failCount = details.results.length - successCount - mergeFailedCount - abortedCount;
|
|
1016
933
|
let summary = `${theme.fg("dim", "Total:")} `;
|
|
1017
934
|
if (abortedCount > 0) {
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/** The hard, locked batch threshold enforced by the runtime gate. */
|
|
2
|
+
export const DEFAULT_SPAWN_THRESHOLD = 4;
|
|
3
|
+
|
|
4
|
+
/** The justification a large batch or reviewer-spawned explorer must supply to pass the hard gate. */
|
|
5
|
+
export interface SpawnPlanReceipt {
|
|
6
|
+
whyParallel: string;
|
|
7
|
+
whyNotLocal: string;
|
|
8
|
+
independence: string;
|
|
9
|
+
expectedReceiptShape: string;
|
|
10
|
+
maxInlineTokens: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SpawnGateRequest {
|
|
14
|
+
/** Number of children the batch wants to spawn. */
|
|
15
|
+
childCount: number;
|
|
16
|
+
/** The spawn-plan receipt, when provided. */
|
|
17
|
+
plan?: SpawnPlanReceipt;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ReviewerExploreGateRequest {
|
|
21
|
+
/** Agent type/name doing the spawning, when known. */
|
|
22
|
+
spawningAgentType?: string | null;
|
|
23
|
+
/** Target agent type/name requested by the task call. */
|
|
24
|
+
targetAgent: string;
|
|
25
|
+
/** The spawn-plan receipt, when provided. */
|
|
26
|
+
plan?: SpawnPlanReceipt;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type SpawnGateOutcome = "allowed" | "rejected";
|
|
30
|
+
|
|
31
|
+
export interface SpawnGateDecision {
|
|
32
|
+
outcome: SpawnGateOutcome;
|
|
33
|
+
/** Human-readable reason, suitable for a blocked-result message. */
|
|
34
|
+
reason: string;
|
|
35
|
+
/** Whether a plan was required for this request. */
|
|
36
|
+
planRequired: boolean;
|
|
37
|
+
/** Missing plan field names when rejected for an incomplete plan. */
|
|
38
|
+
missingFields: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const REQUIRED_STRING_FIELDS = ["whyParallel", "whyNotLocal", "independence", "expectedReceiptShape"] as const;
|
|
42
|
+
|
|
43
|
+
export function findMissingPlanFields(plan: SpawnPlanReceipt | undefined): string[] {
|
|
44
|
+
if (plan === undefined) {
|
|
45
|
+
return [...REQUIRED_STRING_FIELDS, "maxInlineTokens"];
|
|
46
|
+
}
|
|
47
|
+
const missing: string[] = [];
|
|
48
|
+
for (const field of REQUIRED_STRING_FIELDS) {
|
|
49
|
+
const value = plan[field];
|
|
50
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
51
|
+
missing.push(field);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (
|
|
55
|
+
typeof plan.maxInlineTokens !== "number" ||
|
|
56
|
+
!Number.isFinite(plan.maxInlineTokens) ||
|
|
57
|
+
plan.maxInlineTokens <= 0
|
|
58
|
+
) {
|
|
59
|
+
missing.push("maxInlineTokens");
|
|
60
|
+
}
|
|
61
|
+
return missing;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function decide(childCount: number, threshold: number, plan: SpawnPlanReceipt | undefined): SpawnGateDecision {
|
|
65
|
+
if (!Number.isInteger(childCount) || childCount < 0) {
|
|
66
|
+
throw new RangeError("childCount must be a non-negative integer");
|
|
67
|
+
}
|
|
68
|
+
if (!Number.isInteger(threshold) || threshold < 1) {
|
|
69
|
+
throw new RangeError("threshold must be a positive integer");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const planRequired = childCount > threshold;
|
|
73
|
+
if (!planRequired) {
|
|
74
|
+
return {
|
|
75
|
+
outcome: "allowed",
|
|
76
|
+
reason: `batch of ${childCount} is at or below threshold ${threshold}`,
|
|
77
|
+
planRequired: false,
|
|
78
|
+
missingFields: [],
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const missingFields = findMissingPlanFields(plan);
|
|
83
|
+
if (missingFields.length > 0) {
|
|
84
|
+
return {
|
|
85
|
+
outcome: "rejected",
|
|
86
|
+
reason: `batch of ${childCount} exceeds threshold ${threshold} and the spawn-plan receipt is ${
|
|
87
|
+
plan === undefined ? "missing" : `incomplete (${missingFields.join(", ")})`
|
|
88
|
+
}`,
|
|
89
|
+
planRequired: true,
|
|
90
|
+
missingFields,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
outcome: "allowed",
|
|
96
|
+
reason: `batch of ${childCount} exceeds threshold ${threshold} and a complete spawn-plan receipt was provided`,
|
|
97
|
+
planRequired: true,
|
|
98
|
+
missingFields: [],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function evaluateSpawnGate(request: SpawnGateRequest): SpawnGateDecision {
|
|
103
|
+
return decide(request.childCount, DEFAULT_SPAWN_THRESHOLD, request.plan);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function evaluateReviewerExploreGate(request: ReviewerExploreGateRequest): SpawnGateDecision {
|
|
107
|
+
if (request.spawningAgentType !== "reviewer" || request.targetAgent !== "explore") {
|
|
108
|
+
return {
|
|
109
|
+
outcome: "allowed",
|
|
110
|
+
reason: "reviewer->explore gate does not apply",
|
|
111
|
+
planRequired: false,
|
|
112
|
+
missingFields: [],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const missingFields = findMissingPlanFields(request.plan);
|
|
117
|
+
if (missingFields.length > 0) {
|
|
118
|
+
return {
|
|
119
|
+
outcome: "rejected",
|
|
120
|
+
reason: `reviewer->explore spawn requires a complete spawn-plan receipt (${missingFields.join(", ")})`,
|
|
121
|
+
planRequired: true,
|
|
122
|
+
missingFields,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
outcome: "allowed",
|
|
128
|
+
reason: "reviewer->explore spawn has a complete spawn-plan receipt",
|
|
129
|
+
planRequired: true,
|
|
130
|
+
missingFields: [],
|
|
131
|
+
};
|
|
132
|
+
}
|