@gajae-code/coding-agent 0.3.0 → 0.3.2
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 +32 -0
- package/README.md +1 -1
- package/dist/types/async/job-manager.d.ts +7 -0
- package/dist/types/cli/args.d.ts +3 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/model-profile-activation.d.ts +30 -0
- package/dist/types/config/model-profiles.d.ts +19 -0
- package/dist/types/config/model-registry.d.ts +8 -0
- package/dist/types/config/model-resolver.d.ts +1 -1
- package/dist/types/config/models-config-schema.d.ts +47 -0
- package/dist/types/config/settings-schema.d.ts +14 -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/ultragoal-runtime.d.ts +2 -1
- 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/main.d.ts +10 -1
- 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/custom-provider-wizard.d.ts +10 -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/model-selector.d.ts +6 -1
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
- 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 +9 -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 +2 -0
- package/dist/types/sdk.d.ts +3 -1
- 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/executor.d.ts +1 -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/hindsight-recall.d.ts +0 -2
- package/dist/types/tools/hindsight-reflect.d.ts +0 -2
- package/dist/types/tools/hindsight-retain.d.ts +0 -2
- package/dist/types/tools/index.d.ts +6 -4
- 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 +19 -2
- package/src/commands/deep-interview.ts +1 -0
- package/src/commands/harness.ts +289 -19
- package/src/commands/launch.ts +10 -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/model-profile-activation.ts +157 -0
- package/src/config/model-profiles.ts +155 -0
- package/src/config/model-registry.ts +19 -0
- package/src/config/model-resolver.ts +3 -2
- package/src/config/models-config-schema.ts +36 -0
- package/src/config/settings-schema.ts +16 -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 +39 -3
- package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +61 -0
- package/src/defaults/gjc-defaults.ts +7 -0
- 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 +231 -38
- 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 +8 -10
- 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 +88 -6
- 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/custom-provider-wizard.ts +318 -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/model-selector.ts +108 -18
- package/src/modes/components/provider-onboarding-selector.ts +6 -1
- 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 +86 -1
- 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 +2 -0
- package/src/prompts/memories/consolidation.md +1 -1
- package/src/prompts/memories/read-path.md +6 -7
- package/src/prompts/memories/unavailable.md +2 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/recall.md +1 -0
- package/src/prompts/tools/reflect.md +1 -0
- package/src/prompts/tools/retain.md +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 +5 -1
- 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 +9 -1
- package/src/task/executor.ts +31 -5
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +259 -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/hindsight-recall.ts +0 -2
- package/src/tools/hindsight-reflect.ts +0 -2
- package/src/tools/hindsight-retain.ts +0 -2
- package/src/tools/index.ts +6 -18
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +4 -3
- package/src/tools/search.ts +1 -0
- package/src/tools/skill.ts +6 -1
- package/src/tools/subagent.ts +237 -84
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as os from "node:os";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
|
|
5
|
+
const CRASH_DIAGNOSTICS_ENV = "GJC_CRASH_DIAGNOSTICS";
|
|
6
|
+
const CRASH_DIAGNOSTICS_DIR_ENV = "GJC_CRASH_DIAGNOSTICS_DIR";
|
|
7
|
+
const STDERR_PREVIEW_BYTES = 4096;
|
|
8
|
+
const DIRECTORY_MODE = 0o700;
|
|
9
|
+
const REPORT_FILE_MODE = 0o600;
|
|
10
|
+
|
|
11
|
+
export type CrashProcessKind = "bash" | "python" | "lsp" | "dap" | "mcp" | "browser" | "worker" | "native" | "unknown";
|
|
12
|
+
|
|
13
|
+
export type CrashClass =
|
|
14
|
+
| "clean_exit"
|
|
15
|
+
| "non_zero_exit"
|
|
16
|
+
| "signal_exit"
|
|
17
|
+
| "timeout"
|
|
18
|
+
| "cancelled"
|
|
19
|
+
| "spawn_error"
|
|
20
|
+
| "protocol_exit"
|
|
21
|
+
| "native_panic"
|
|
22
|
+
| "unknown";
|
|
23
|
+
|
|
24
|
+
export interface CrashClassificationInput {
|
|
25
|
+
kind: CrashProcessKind;
|
|
26
|
+
command?: string[];
|
|
27
|
+
exitCode?: number | null;
|
|
28
|
+
signal?: string | null;
|
|
29
|
+
cancelled?: boolean;
|
|
30
|
+
timedOut?: boolean;
|
|
31
|
+
spawnError?: unknown;
|
|
32
|
+
stderr?: string;
|
|
33
|
+
protocol?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CrashClassification {
|
|
37
|
+
kind: CrashProcessKind;
|
|
38
|
+
class: CrashClass;
|
|
39
|
+
crashed: boolean;
|
|
40
|
+
exitCode: number | null;
|
|
41
|
+
signal: string | null;
|
|
42
|
+
command?: string[];
|
|
43
|
+
protocol?: string;
|
|
44
|
+
reason: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface CrashReport extends CrashClassification {
|
|
48
|
+
schemaVersion: 1;
|
|
49
|
+
createdAt: string;
|
|
50
|
+
pid: number;
|
|
51
|
+
cwd: string;
|
|
52
|
+
stderrPreview?: string;
|
|
53
|
+
spawnError?: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface CrashReportWriteResult {
|
|
57
|
+
report: CrashReport;
|
|
58
|
+
path: string | null;
|
|
59
|
+
enabled: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function crashDiagnosticsEnabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
63
|
+
const value = env[CRASH_DIAGNOSTICS_ENV];
|
|
64
|
+
return value === "1" || value === "true" || value === "yes";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function getCrashDiagnosticsDirectory(env: NodeJS.ProcessEnv = process.env): string {
|
|
68
|
+
return env[CRASH_DIAGNOSTICS_DIR_ENV] ?? path.join(os.tmpdir(), "gjc-crash-diagnostics");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function classifyProcessCrash(input: CrashClassificationInput): CrashClassification {
|
|
72
|
+
const exitCode = input.exitCode ?? null;
|
|
73
|
+
const signal = input.signal ?? null;
|
|
74
|
+
const command = input.command;
|
|
75
|
+
const protocol = input.protocol;
|
|
76
|
+
|
|
77
|
+
if (input.timedOut) {
|
|
78
|
+
return {
|
|
79
|
+
kind: input.kind,
|
|
80
|
+
class: "timeout",
|
|
81
|
+
crashed: true,
|
|
82
|
+
exitCode,
|
|
83
|
+
signal,
|
|
84
|
+
command,
|
|
85
|
+
protocol,
|
|
86
|
+
reason: "process timed out",
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (input.cancelled) {
|
|
90
|
+
return {
|
|
91
|
+
kind: input.kind,
|
|
92
|
+
class: "cancelled",
|
|
93
|
+
crashed: false,
|
|
94
|
+
exitCode,
|
|
95
|
+
signal,
|
|
96
|
+
command,
|
|
97
|
+
protocol,
|
|
98
|
+
reason: "process was cancelled",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (input.spawnError !== undefined) {
|
|
102
|
+
return {
|
|
103
|
+
kind: input.kind,
|
|
104
|
+
class: "spawn_error",
|
|
105
|
+
crashed: true,
|
|
106
|
+
exitCode,
|
|
107
|
+
signal,
|
|
108
|
+
command,
|
|
109
|
+
protocol,
|
|
110
|
+
reason: stringifyError(input.spawnError),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (signal) {
|
|
114
|
+
return {
|
|
115
|
+
kind: input.kind,
|
|
116
|
+
class: "signal_exit",
|
|
117
|
+
crashed: true,
|
|
118
|
+
exitCode,
|
|
119
|
+
signal,
|
|
120
|
+
command,
|
|
121
|
+
protocol,
|
|
122
|
+
reason: `process exited after signal ${signal}`,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (exitCode === 0) {
|
|
126
|
+
return {
|
|
127
|
+
kind: input.kind,
|
|
128
|
+
class: "clean_exit",
|
|
129
|
+
crashed: false,
|
|
130
|
+
exitCode,
|
|
131
|
+
signal,
|
|
132
|
+
command,
|
|
133
|
+
protocol,
|
|
134
|
+
reason: "process exited cleanly",
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (exitCode !== null) {
|
|
138
|
+
return {
|
|
139
|
+
kind: input.kind,
|
|
140
|
+
class: "non_zero_exit",
|
|
141
|
+
crashed: true,
|
|
142
|
+
exitCode,
|
|
143
|
+
signal,
|
|
144
|
+
command,
|
|
145
|
+
protocol,
|
|
146
|
+
reason: `process exited with code ${exitCode}`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
kind: input.kind,
|
|
151
|
+
class: "protocol_exit",
|
|
152
|
+
crashed: true,
|
|
153
|
+
exitCode,
|
|
154
|
+
signal,
|
|
155
|
+
command,
|
|
156
|
+
protocol,
|
|
157
|
+
reason: "process exited before protocol completion",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function writeCrashReport(
|
|
162
|
+
input: CrashClassificationInput,
|
|
163
|
+
options: { cwd?: string; env?: NodeJS.ProcessEnv; now?: Date } = {},
|
|
164
|
+
): Promise<CrashReportWriteResult> {
|
|
165
|
+
const classification = classifyProcessCrash(input);
|
|
166
|
+
const report: CrashReport = {
|
|
167
|
+
schemaVersion: 1,
|
|
168
|
+
createdAt: (options.now ?? new Date()).toISOString(),
|
|
169
|
+
pid: process.pid,
|
|
170
|
+
cwd: options.cwd ?? process.cwd(),
|
|
171
|
+
...classification,
|
|
172
|
+
stderrPreview: input.stderr ? trimStartBytes(input.stderr, STDERR_PREVIEW_BYTES) : undefined,
|
|
173
|
+
spawnError: input.spawnError === undefined ? undefined : stringifyError(input.spawnError),
|
|
174
|
+
};
|
|
175
|
+
const enabled = crashDiagnosticsEnabled(options.env);
|
|
176
|
+
|
|
177
|
+
if (!classification.crashed || !enabled) {
|
|
178
|
+
return { report, path: null, enabled };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const dir = getCrashDiagnosticsDirectory(options.env);
|
|
183
|
+
await ensurePrivateDiagnosticsDirectory(dir);
|
|
184
|
+
const filename = `${report.createdAt.replace(/[:.]/g, "-")}-${report.kind}-${report.class}-${process.pid}.json`;
|
|
185
|
+
const reportPath = path.join(dir, filename);
|
|
186
|
+
await writePrivateCrashReport(reportPath, `${JSON.stringify(report, null, 2)}\n`);
|
|
187
|
+
return { report, path: reportPath, enabled };
|
|
188
|
+
} catch {
|
|
189
|
+
return { report, path: null, enabled };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function formatCrashDiagnosticNotice(result: CrashReportWriteResult): string | null {
|
|
194
|
+
if (!result.report.crashed || !result.enabled) return null;
|
|
195
|
+
const location = result.path ? ` report=${result.path}` : "";
|
|
196
|
+
return `[crash:${result.report.kind}:${result.report.class}] ${result.report.reason}${location}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function ensurePrivateDiagnosticsDirectory(dir: string): Promise<void> {
|
|
200
|
+
await fs.mkdir(dir, { recursive: true, mode: DIRECTORY_MODE });
|
|
201
|
+
await fs.chmod(dir, DIRECTORY_MODE);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function writePrivateCrashReport(reportPath: string, contents: string): Promise<void> {
|
|
205
|
+
const file = await fs.open(reportPath, "wx", REPORT_FILE_MODE);
|
|
206
|
+
try {
|
|
207
|
+
await file.writeFile(contents);
|
|
208
|
+
} finally {
|
|
209
|
+
await file.close();
|
|
210
|
+
}
|
|
211
|
+
await fs.chmod(reportPath, REPORT_FILE_MODE);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function stringifyError(error: unknown): string {
|
|
215
|
+
if (error instanceof Error) return error.message;
|
|
216
|
+
return String(error);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function trimStartBytes(value: string, maxBytes: number): string {
|
|
220
|
+
const bytes = Buffer.from(value);
|
|
221
|
+
if (bytes.byteLength <= maxBytes) return value;
|
|
222
|
+
return Buffer.from(bytes.subarray(bytes.byteLength - maxBytes)).toString("utf8");
|
|
223
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime resource owner gauges (Stage 2 observability).
|
|
3
|
+
*
|
|
4
|
+
* Reports counts of long-lived runtime owners as plain data so memory/CPU leak
|
|
5
|
+
* work can see whether owner maps grow without bound across a session. This is
|
|
6
|
+
* framework-agnostic on purpose: it does not depend on the TUI metrics surface,
|
|
7
|
+
* so any consumer (debug report, a metrics bridge, a test) can sample it.
|
|
8
|
+
*/
|
|
9
|
+
import { getShellSessionCount } from "../exec/bash-executor";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Current runtime owner counts, keyed by `<owner>.<resource>`. Extend as more
|
|
13
|
+
* owners (Python kernels, LSP clients, browser tabs, async jobs, streaming
|
|
14
|
+
* queues) expose count getters.
|
|
15
|
+
*/
|
|
16
|
+
export function getRuntimeResourceCounts(): Record<string, number> {
|
|
17
|
+
return {
|
|
18
|
+
"bash.shellSessions": getShellSessionCount(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -330,6 +330,12 @@ export function renderDeepInterviewAskQuestion(question: string, uiTheme: Theme)
|
|
|
330
330
|
return renderModel(model, uiTheme);
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
+
export function isDeepInterviewAskQuestion(question: string): boolean {
|
|
334
|
+
if (parseTopologyQuestion(question) ?? parseRoundQuestion(question)) return true;
|
|
335
|
+
const normalized = normalizeText(question);
|
|
336
|
+
return /(?:^|\n)\s*Round\s+\d+\s*\|.*?\bAmbiguity\b/i.test(normalized);
|
|
337
|
+
}
|
|
338
|
+
|
|
333
339
|
export function formatDeepInterviewSelectorPrompt(question: string): string | null {
|
|
334
340
|
const model = parseTopologyQuestion(question) ?? parseRoundQuestion(question);
|
|
335
341
|
if (!model) return null;
|
|
@@ -125,7 +125,7 @@ Deep Interview threshold: <resolvedThresholdPercent> (source: <resolvedThreshold
|
|
|
125
125
|
```json
|
|
126
126
|
{
|
|
127
127
|
"active": true,
|
|
128
|
-
"current_phase": "
|
|
128
|
+
"current_phase": "interviewing",
|
|
129
129
|
"state": {
|
|
130
130
|
"interview_id": "<uuid>",
|
|
131
131
|
"type": "greenfield|brownfield",
|
|
@@ -49,10 +49,12 @@ Restricted read-only role agents (`planner`, `architect`, and `critic`) must pas
|
|
|
49
49
|
|
|
50
50
|
After a role agent persists a stage artifact, its model-facing response to the caller SHOULD be receipt-only: return the `gjc ralplan --write --json` receipt (`run_id`, `path`, `stage`, `stage_n`, `sha256`, `created_at`) plus the minimal verdict/status fields the caller needs for routing, and do **not** paste the full persisted markdown back into the parent conversation. Downstream reviewers should receive the artifact path/receipt and read the persisted file themselves when they actually need the body. This preserves the audit trail while preventing Planner/Architect/Critic verdict bodies from being duplicated into the main-agent context.
|
|
51
51
|
|
|
52
|
+
RECEIPT-ONLY guideline: role agents (`planner`, `architect`, and `critic`) persist durable outputs via `gjc ralplan --write` and return ONLY the receipt fields (`run_id`, `path`, `sha256`) plus verdict/status routing fields; include `stage` and `stage_n` when available, and never return the full persisted body.
|
|
53
|
+
|
|
52
54
|
This skill runs GJC planning in consensus mode for the provided arguments.
|
|
53
55
|
|
|
54
56
|
The consensus workflow:
|
|
55
|
-
1. **Planner** creates initial plan and a compact **RALPLAN-DR summary** before review,
|
|
57
|
+
1. **Planner** creates the initial plan and a compact **RALPLAN-DR summary** before review. Launch the Planner ONCE per run as a detached, resumable subagent (await it before the Architect) and record its returned subagent id as the run's persisted Planner id; persist the stage with `gjc ralplan --write --stage planner --stage_n 1 --artifact "..." --planner-id <id> --planner-resumable <true|false>` (see **Persisted Planner** below):
|
|
56
58
|
- After persistence, return only the receipt/path plus compact planning status; do not paste the full plan markdown back to the caller unless explicitly requested.
|
|
57
59
|
- Principles (3-5)
|
|
58
60
|
- Decision Drivers (top 3)
|
|
@@ -66,7 +68,7 @@ The consensus workflow:
|
|
|
66
68
|
- The Critic agent/subagent must persist its evaluation with `gjc ralplan --write --stage critic --stage_n <N> --artifact "..." --json`, then return the receipt/path plus compact verdict/status (`OKAY`/`ITERATE`/`REJECT`) instead of pasting the full evaluation body.
|
|
67
69
|
5. **Re-review loop** (max 5 iterations): Any non-`APPROVE` Critic verdict (`ITERATE` or `REJECT`) MUST run the same full closed loop:
|
|
68
70
|
a. Collect Architect + Critic feedback
|
|
69
|
-
b. Revise the plan with Planner
|
|
71
|
+
b. Revise the plan by resuming the SAME persisted Planner subagent with consolidated Architect + Critic feedback (see **Persisted Planner** below); fall back to a fresh Planner spawn only per the fallback routing table
|
|
70
72
|
c. Return to Architect review
|
|
71
73
|
- Persist each Planner revision with `gjc ralplan --write --stage revision --stage_n <N> --artifact "..." --json` before re-review, then pass the receipt/path forward instead of duplicating the full revision markdown in the parent conversation.
|
|
72
74
|
d. Return to Critic evaluation
|
|
@@ -88,6 +90,33 @@ The consensus workflow:
|
|
|
88
90
|
|
|
89
91
|
Follow the Plan skill's full documentation for consensus mode details.
|
|
90
92
|
|
|
93
|
+
### Persisted Planner (consensus loop)
|
|
94
|
+
|
|
95
|
+
The Planner is a **same-session persisted subagent**: launched detached once, awaited before the Architect, then **resumed** with consolidated Architect + Critic feedback on every re-review pass instead of being re-spawned. The Architect and Critic stay **fresh, independent spawns each pass** so their verdicts remain reproducible from their pass artifacts alone. Do NOT modify the subagent control surface; this orchestration uses the existing `subagent` resume/steer controls only.
|
|
96
|
+
|
|
97
|
+
**Persistence boundary:** this is same-parent, active-session continuity only. Resumability depends on the in-memory subagent record (and a persistent parent session — an in-memory parent yields `resumable:false`), not just a session file. The `.gjc` run-state record is an audit/routing hint, NOT a durable cross-process subagent registry. After a process restart, a missing record, or any unavailable/failed resume, use the fresh Planner fallback.
|
|
98
|
+
|
|
99
|
+
**Resume routing table** (per re-review pass, when resuming the persisted Planner id):
|
|
100
|
+
|
|
101
|
+
| Resume outcome | Action |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `running` | `steer`/inject the consolidated feedback to the same id, then await — do NOT fresh-spawn |
|
|
104
|
+
| `queued` | retain/update the queued message or await the same id — do NOT fresh-spawn just because it is queued |
|
|
105
|
+
| `context_unavailable`, `not_found`, `no_runner`, `resume_failed` | fresh Planner spawn for that pass; record the fallback metadata |
|
|
106
|
+
| terminal (`completed`/`failed`/`cancelled`) + revision message | resume the same id when context is available; otherwise use the fresh fallback above |
|
|
107
|
+
|
|
108
|
+
**Recording persisted-Planner metadata** (audit/routing only — never claim `subagent list` proves resumability, since the snapshot does not expose `resumable`). Ride these optional flags on the normal `--write` for the planner/revision stage of the pass:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
gjc ralplan --write --stage revision --stage_n <N> --artifact "..." \
|
|
112
|
+
--planner-id <id> --planner-resumable <true|false> \
|
|
113
|
+
--fallback-reason <context_unavailable|not_found|no_runner|resume_failed|process_restart|missing_record> \
|
|
114
|
+
--fallback-attempted-id <id> --fallback-stage-n <N> \
|
|
115
|
+
--fallback-receipt-path <fresh-planner-stage-artifact-path> --json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Set `--planner-resumable true` only when the parent session is provably persistent; set/record `false` after an observed `context_unavailable`; otherwise omit it (unknown). Fallback flags are recorded only when a fresh-spawn fallback actually occurs: a fallback record requires `--fallback-reason` **together with** `--fallback-attempted-id` and `--fallback-stage-n` (the failed id and the pass it failed on), while `--fallback-receipt-path` (the fresh Planner's stage artifact) is optional.
|
|
119
|
+
|
|
91
120
|
## Pre-Execution Gate
|
|
92
121
|
|
|
93
122
|
### Why the Gate Exists
|
|
@@ -50,12 +50,38 @@ Use `goal({"op":"get"})` snapshots inside Ultragoal for ledger reconciliation. T
|
|
|
50
50
|
|
|
51
51
|
## Create goals
|
|
52
52
|
|
|
53
|
-
1.
|
|
53
|
+
1. Decide on the brief. To produce **multiple** stories, separate them with a reserved `@goal:` delimiter line; the title follows on the same line and the objective is everything beneath it until the next delimiter:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
Shared brief constraints / context go here (optional preamble).
|
|
57
|
+
|
|
58
|
+
@goal: Parse the intake CSVs
|
|
59
|
+
Ingest reviewer CSVs from the watch dir, validate headers, and reject
|
|
60
|
+
malformed rows with a per-row reason. Objectives can span multiple lines
|
|
61
|
+
and contain `code`, "quotes", or commands — no escaping needed.
|
|
62
|
+
|
|
63
|
+
@goal: Normalize records
|
|
64
|
+
Map raw rows onto the canonical schema and dedupe by record id.
|
|
65
|
+
|
|
66
|
+
@goal: Export the audit report
|
|
67
|
+
Emit an audit-ready report covering every accepted and rejected row.
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Delimiter contract:
|
|
71
|
+
- A `@goal` line is a story boundary **only** when it starts at column 0 (no leading whitespace) and the character right after `@goal` is `:`, whitespace (space or tab), or end-of-line. So `@goal: Title`, `@goal Title`, and a bare `@goal` line all open a story.
|
|
72
|
+
- `@goalish`, `@goals:`, `@goal-foo`, `@goal.foo`, `@goal/foo`, and any indented or mid-line `@goal` are ordinary objective text, not delimiters. To keep a literal `@goal` line inside an objective, indent it.
|
|
73
|
+
- A title-only block (no body) uses the title as its objective. An empty title borrows the first body line as the title. A block with **neither** title nor body is rejected — `create-goals` errors instead of writing a placeholder goal.
|
|
74
|
+
- **Preamble** (any text before the first `@goal` delimiter) is global context/constraints only; it is retained in the brief but is **not** turned into a goal. Every executable story needs its own `@goal` block.
|
|
75
|
+
- With **no** `@goal` delimiter anywhere, the whole brief becomes a single goal `G001` (unchanged legacy behavior).
|
|
76
|
+
|
|
77
|
+
Stories become `G001`, `G002`, … in order.
|
|
78
|
+
|
|
79
|
+
2. Run one of:
|
|
54
80
|
- `gjc ultragoal create-goals --brief "<brief>"`
|
|
55
81
|
- `gjc ultragoal create-goals --brief-file <path>`
|
|
56
82
|
- `cat <brief> | gjc ultragoal create-goals --from-stdin`
|
|
57
83
|
- `gjc ultragoal create-goals --gjc-goal-mode per-story --brief "<brief>"` only when one GJC goal context per story is explicitly preferred
|
|
58
|
-
|
|
84
|
+
3. Inspect `.gjc/ultragoal/goals.json` and refine if needed.
|
|
59
85
|
|
|
60
86
|
## Complete goals
|
|
61
87
|
|
|
@@ -135,12 +161,22 @@ gjc ultragoal checkpoint --goal-id <id> --status complete --evidence "<team evid
|
|
|
135
161
|
|
|
136
162
|
Workers do not own ultragoal goal state, do not create worker ultragoal ledgers, and do not checkpoint Ultragoal. Workers must not run `gjc ultragoal checkpoint`; checkpoint authority stays with the leader after worker tasks are terminal. Team launch remains explicit; Ultragoal does not auto-launch Team and performs no hidden goal mutation.
|
|
137
163
|
|
|
164
|
+
## Internal Ultragoal sub-skill fragments
|
|
165
|
+
|
|
166
|
+
The completion-gate cleanup sweep is driven by `ai-slop-cleaner`, an internal Ultragoal sub-skill bundled as a `kind: "skill-fragment"` prompt with parent skill `ultragoal` (installed at `skill-fragments/ultragoal/ai-slop-cleaner.md`). It is analogous to deep-interview's auto-research fragment: loaded on demand for one specific hook, never a user-facing skill.
|
|
167
|
+
|
|
168
|
+
- It is not slash-command discoverable, has no public skill-listing entry, and is never resolvable through `skill://`.
|
|
169
|
+
- It is a read-only detector+reporter over the active story's changed files only: it never edits code, writes files, mutates `.gjc/`, checkpoints, calls goal tools, or spawns workflows.
|
|
170
|
+
- It classifies every finding as blocking or advisory across the full taxonomy (fallback-like masking vs. grounded, duplication, dead code, needless abstraction, boundary violations, UI/design slop, missing tests).
|
|
171
|
+
- The leader and a leader-spawned `executor` own all fixes; the cleaner reruns until zero blocking findings remain. Advisory findings live in the gate report only.
|
|
172
|
+
- Recursion guard: it must not spawn nested `ralplan`/`team`/`deep-interview`/`ultragoal`; broad or architectural findings are handed back to the leader as review blockers.
|
|
173
|
+
|
|
138
174
|
## Mandatory completion cleanup and review gate
|
|
139
175
|
|
|
140
176
|
An ultragoal story cannot be checkpointed `complete` until the active agent has run the quality gate. The gate is plan-first, contract-driven, and surface-based:
|
|
141
177
|
|
|
142
178
|
1. Run targeted implementation verification for the story.
|
|
143
|
-
2. Run
|
|
179
|
+
2. Run the internal ai-slop-cleaner skill fragment as the final cleanup sweep on the story's changed files only, before verification and red-team so only clean code is reviewed. It is a read-only detector that emits an `AI SLOP CLEANUP REPORT`; if there are no relevant edits it still runs and records a passed/no-op report. Every BLOCKING cleaner finding is a completion blocker: the leader spawns an `executor` to fix blocking findings only, then reruns the cleaner until blocking findings are zero. Advisory findings are included in the gate report only and are not written to the Ultragoal ledger. Carry the report through the existing `qualityGate.iteration.evidence` field; do not add a new top-level quality-gate key.
|
|
144
180
|
3. Rerun verification after the cleaner pass.
|
|
145
181
|
4. Delegate an `architect` review covering all three lanes:
|
|
146
182
|
- architecture-side: system boundaries, layering, data/control flow, operational risks.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Ultragoal AI Slop Cleaner Fragment
|
|
2
|
+
|
|
3
|
+
You are the AI slop cleaner for the Ultragoal completion gate. This is an internal Ultragoal sub-skill, loaded on demand as a `kind: "skill-fragment"` prompt with parent skill `ultragoal`. It is never user-facing: not slash-command discoverable, no public skill listing entry, and never resolvable through `skill://`.
|
|
4
|
+
|
|
5
|
+
You are a **read-only detector and reporter**. You never edit code, write files, run formatters, mutate `.gjc/` state, checkpoint, call goal tools, or spawn workflows. You detect slop in the active Ultragoal story's changed files, classify each finding, and emit a report. The Ultragoal leader spawns an `executor` to fix BLOCKING findings; you do not fix anything yourself.
|
|
6
|
+
|
|
7
|
+
## Scope
|
|
8
|
+
|
|
9
|
+
- Inspect ONLY the active Ultragoal story's changed-files list. No broad rewrites, no inspection outside that scope, no new dependencies.
|
|
10
|
+
- Allow only narrow supporting reads needed to understand the contracts of changed files; if you need broader context, report that need to the leader instead of expanding scope.
|
|
11
|
+
- If there are no relevant edits, emit a passed/no-op report (`Gate Result: PASS`, `Changed Files Reviewed` listing the files as "no relevant edits").
|
|
12
|
+
- Recursion guard: you are already inside an Ultragoal workflow. Do NOT spawn nested `ralplan`, `team`, `deep-interview`, or `ultragoal` workflows. Broad, ambiguous, cross-layer, or architectural findings are handed to the leader as review blockers, not resolved here.
|
|
13
|
+
|
|
14
|
+
## Taxonomy
|
|
15
|
+
|
|
16
|
+
Classify every finding against the full taxonomy:
|
|
17
|
+
|
|
18
|
+
1. **Fallback-like code** — classify each as **masking fallback slop** or **grounded compatibility/fail-safe fallback**.
|
|
19
|
+
- Masking signals (blocking): swallowed errors, silent defaults, bypassed validation/tests, untested alternate execution paths, primary-contract suppression.
|
|
20
|
+
- Grounded signals (advisory): scoped to an external/version/fail-safe boundary, documented rationale, preserved failure evidence, and regression tests covering both primary and fallback behavior.
|
|
21
|
+
2. **Duplication** — repeated logic, copy-paste branches, redundant helpers.
|
|
22
|
+
3. **Dead code** — unused code, unreachable branches, stale flags, debug leftovers.
|
|
23
|
+
4. **Needless abstraction** — pass-through wrappers, speculative indirection, single-use helper layers.
|
|
24
|
+
5. **Boundary violations** — hidden coupling, leaky responsibilities, wrong-layer imports or side effects.
|
|
25
|
+
6. **UI/design slop** — context-sensitive signals, not absolute bans; preserve intentional brand/design-system/accessibility/product rationale. Signals: small Korean body copy (challenge 11-12px; Korean body text generally needs 14px+ unless a dense accessible system supports smaller), gratuitous shadows/depth, repetitive eyebrow+title+description scaffolding and filler/emoji badges, default blue/purple palettes (e.g. #3B82F6) without rationale, over-perfect uniform 3/4-column grids, and extreme "AI demo" gradients.
|
|
26
|
+
7. **Missing tests** — behavior not locked, weak regression coverage, missing edge/failure-mode cases.
|
|
27
|
+
|
|
28
|
+
## Blocking vs advisory
|
|
29
|
+
|
|
30
|
+
- **Blocking** if it can mask failures, violate accepted contracts, weaken boundaries, leave changed behavior untested, create maintenance traps, or make later verification unsafe.
|
|
31
|
+
- **Advisory** if it is nice-to-have, stylistic/contextual, or outside safe story scope.
|
|
32
|
+
- Advisory findings stay in the gate report only; they are NOT written to the Ultragoal ledger.
|
|
33
|
+
|
|
34
|
+
## Report
|
|
35
|
+
|
|
36
|
+
Emit exactly this text block with these mandated labels:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
AI SLOP CLEANUP REPORT
|
|
40
|
+
======================
|
|
41
|
+
|
|
42
|
+
Scope: [changed files inspected]
|
|
43
|
+
Mode: read-only detector/report; no edits performed
|
|
44
|
+
Blocking Findings: [none, or numbered findings with file, category, evidence, required executor fix]
|
|
45
|
+
Advisory Findings: [none, or numbered findings with file, category, evidence, why advisory]
|
|
46
|
+
Fallback Findings: [none, or finding -> masking fallback slop / grounded compatibility/fail-safe fallback -> blocking/advisory]
|
|
47
|
+
UI/Design Findings: [none/N/A, or signal -> blocking/advisory -> rationale]
|
|
48
|
+
Missing Test Findings: [none, or gap -> blocking/advisory -> required coverage]
|
|
49
|
+
Recursion Guard: [confirmed no nested ralplan/team/deep-interview/ultragoal spawned; broad findings handed to leader]
|
|
50
|
+
Changed Files Reviewed:
|
|
51
|
+
- [path] - [reviewed / no relevant edits]
|
|
52
|
+
|
|
53
|
+
Gate Result: PASS | BLOCKED
|
|
54
|
+
Leader Action:
|
|
55
|
+
- PASS: continue to verification, architect review, and executor red-team QA.
|
|
56
|
+
- BLOCKED: spawn executor to fix BLOCKING findings only, then rerun this sweep until Blocking Findings is none.
|
|
57
|
+
Remaining Risks:
|
|
58
|
+
- [none, or advisory/deferred risks]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Port the oh-my-codex taxonomy and report shape, not its editing workflow. Do not instruct yourself to execute cleanup passes — detect and report only.
|
|
@@ -7,6 +7,7 @@ import autoResearchGreenfieldFragment from "./gjc/skills/deep-interview/auto-res
|
|
|
7
7
|
import deepInterviewSkill from "./gjc/skills/deep-interview/SKILL.md" with { type: "text" };
|
|
8
8
|
import ralplanSkill from "./gjc/skills/ralplan/SKILL.md" with { type: "text" };
|
|
9
9
|
import teamSkill from "./gjc/skills/team/SKILL.md" with { type: "text" };
|
|
10
|
+
import aiSlopCleanerFragment from "./gjc/skills/ultragoal/ai-slop-cleaner.md" with { type: "text" };
|
|
10
11
|
import ultragoalSkill from "./gjc/skills/ultragoal/SKILL.md" with { type: "text" };
|
|
11
12
|
|
|
12
13
|
export const DEFAULT_GJC_DEFINITION_NAMES = ["deep-interview", "ralplan", "team", "ultragoal"] as const;
|
|
@@ -92,6 +93,12 @@ const DEFAULT_GJC_DEFINITIONS: readonly DefaultGjcDefinition[] = [
|
|
|
92
93
|
relativePath: "skill-fragments/deep-interview/auto-answer-uncertain.md",
|
|
93
94
|
content: autoAnswerUncertainFragment,
|
|
94
95
|
},
|
|
96
|
+
{
|
|
97
|
+
kind: "skill-fragment",
|
|
98
|
+
parentSkillName: "ultragoal",
|
|
99
|
+
relativePath: "skill-fragments/ultragoal/ai-slop-cleaner.md",
|
|
100
|
+
content: aiSlopCleanerFragment,
|
|
101
|
+
},
|
|
95
102
|
];
|
|
96
103
|
|
|
97
104
|
export function getDefaultGjcDefinitions(): readonly DefaultGjcDefinition[] {
|
package/src/eval/py/executor.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getProjectDir, logger } from "@gajae-code/utils";
|
|
2
2
|
import { Settings } from "../../config/settings";
|
|
3
|
+
import { formatCrashDiagnosticNotice, writeCrashReport } from "../../debug/crash-diagnostics";
|
|
3
4
|
import { OutputSink } from "../../session/streaming-output";
|
|
4
5
|
import type { ToolSession } from "../../tools";
|
|
5
6
|
import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../../tools/output-meta";
|
|
@@ -62,6 +63,8 @@ export interface PythonExecutorOptions {
|
|
|
62
63
|
|
|
63
64
|
export interface PythonKernelExecutor {
|
|
64
65
|
execute: (code: string, options?: KernelExecuteOptions) => Promise<KernelExecuteResult>;
|
|
66
|
+
getExitCode?: () => number | null;
|
|
67
|
+
peekStderr?: () => string;
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
export interface PythonResult {
|
|
@@ -446,12 +449,29 @@ async function executeWithKernel(
|
|
|
446
449
|
const annotation = result.timedOut
|
|
447
450
|
? formatKernelTimeoutAnnotation(executionTimeoutMs, result.kernelKilled ?? false)
|
|
448
451
|
: undefined;
|
|
452
|
+
let crashNotice: string | null = null;
|
|
453
|
+
if (result.kernelKilled) {
|
|
454
|
+
crashNotice = formatCrashDiagnosticNotice(
|
|
455
|
+
await writeCrashReport(
|
|
456
|
+
{
|
|
457
|
+
kind: "python",
|
|
458
|
+
exitCode: kernel.getExitCode?.(),
|
|
459
|
+
cancelled: false,
|
|
460
|
+
timedOut: result.timedOut,
|
|
461
|
+
stderr: kernel.peekStderr?.(),
|
|
462
|
+
protocol: "eval.py.kernel",
|
|
463
|
+
},
|
|
464
|
+
{ cwd: options?.cwd },
|
|
465
|
+
),
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
const notice = [annotation, crashNotice].filter(text => text).join("; ") || undefined;
|
|
449
469
|
return {
|
|
450
470
|
exitCode: undefined,
|
|
451
471
|
cancelled: true,
|
|
452
472
|
displayOutputs,
|
|
453
473
|
stdinRequested: result.stdinRequested,
|
|
454
|
-
...(await sink.dump(
|
|
474
|
+
...(await sink.dump(notice)),
|
|
455
475
|
};
|
|
456
476
|
}
|
|
457
477
|
|
package/src/eval/py/kernel.ts
CHANGED
|
@@ -183,6 +183,8 @@ export class PythonKernel {
|
|
|
183
183
|
#disposed = false;
|
|
184
184
|
#shutdownConfirmed = false;
|
|
185
185
|
#exitedPromise: Promise<number> | null = null;
|
|
186
|
+
#exitCode: number | null = null;
|
|
187
|
+
#stderrTail = "";
|
|
186
188
|
#pending = new Map<string, PendingExecution>();
|
|
187
189
|
#readBuffer = "";
|
|
188
190
|
|
|
@@ -230,6 +232,7 @@ export class PythonKernel {
|
|
|
230
232
|
kernel.#stdin = proc.stdin;
|
|
231
233
|
kernel.#exitedPromise = proc.exited;
|
|
232
234
|
void kernel.#exitedPromise.then(code => {
|
|
235
|
+
kernel.#exitCode = code;
|
|
233
236
|
kernel.#alive = false;
|
|
234
237
|
kernel.#abortPendingExecutions(`Python kernel exited with code ${code}`, { kernelKilled: true });
|
|
235
238
|
});
|
|
@@ -255,6 +258,14 @@ export class PythonKernel {
|
|
|
255
258
|
return this.#alive && !this.#disposed;
|
|
256
259
|
}
|
|
257
260
|
|
|
261
|
+
getExitCode(): number | null {
|
|
262
|
+
return this.#exitCode;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
peekStderr(): string {
|
|
266
|
+
return this.#stderrTail;
|
|
267
|
+
}
|
|
268
|
+
|
|
258
269
|
async execute(code: string, options?: KernelExecuteOptions): Promise<KernelExecuteResult> {
|
|
259
270
|
if (!this.isAlive()) {
|
|
260
271
|
throw new Error("Python kernel is not running");
|
|
@@ -493,6 +504,10 @@ export class PythonKernel {
|
|
|
493
504
|
const { done, value } = await reader.read();
|
|
494
505
|
if (done) break;
|
|
495
506
|
const text = decoder.decode(value);
|
|
507
|
+
this.#stderrTail += text;
|
|
508
|
+
if (this.#stderrTail.length > 4096) {
|
|
509
|
+
this.#stderrTail = this.#stderrTail.slice(-4096);
|
|
510
|
+
}
|
|
496
511
|
if (text.trim()) {
|
|
497
512
|
logger.warn("Python runner stderr", { text });
|
|
498
513
|
}
|
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as fs from "node:fs/promises";
|
|
7
7
|
import { executeShell, type MinimizerOptions, Shell } from "@gajae-code/natives";
|
|
8
|
+
import { postmortem } from "@gajae-code/utils";
|
|
8
9
|
import { Settings, type ShellMinimizerSettings } from "../config/settings";
|
|
10
|
+
import { formatCrashDiagnosticNotice, writeCrashReport } from "../debug/crash-diagnostics";
|
|
9
11
|
import { OutputSink } from "../session/streaming-output";
|
|
10
12
|
import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../tools/output-meta";
|
|
11
13
|
import { getOrCreateSnapshot } from "../utils/shell-snapshot";
|
|
@@ -58,6 +60,30 @@ export interface BashResult {
|
|
|
58
60
|
const shellSessions = new Map<string, Shell>();
|
|
59
61
|
const brokenShellSessions = new Set<string>();
|
|
60
62
|
|
|
63
|
+
/** Number of persistent shell sessions currently retained (owner gauge). */
|
|
64
|
+
export function getShellSessionCount(): number {
|
|
65
|
+
return shellSessions.size;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Dispose all persistent shell sessions: abort in-flight work and drop the
|
|
70
|
+
* strong references so the native shells can be finalized. Healthy persistent
|
|
71
|
+
* sessions are otherwise retained for the whole process lifetime (MEM-7). This
|
|
72
|
+
* is registered as a postmortem cleanup so shutdown/signals release native
|
|
73
|
+
* shell resources, and is also callable directly (e.g. on owner teardown).
|
|
74
|
+
*/
|
|
75
|
+
export async function disposeAllShellSessions(): Promise<void> {
|
|
76
|
+
// Snapshot and drop strong references up front so concurrent callers cannot
|
|
77
|
+
// reuse a session that is being torn down, then await every native abort so
|
|
78
|
+
// shutdown/signal cleanup does not return before resources are released.
|
|
79
|
+
const sessions = [...shellSessions.values()];
|
|
80
|
+
shellSessions.clear();
|
|
81
|
+
brokenShellSessions.clear();
|
|
82
|
+
await Promise.allSettled(sessions.map(session => session.abort()));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
postmortem.register("bash-executor:shell-sessions", () => disposeAllShellSessions());
|
|
86
|
+
|
|
61
87
|
async function resolveShellCwd(cwd: string | undefined): Promise<string | undefined> {
|
|
62
88
|
if (!cwd) return undefined;
|
|
63
89
|
|
|
@@ -280,6 +306,21 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
280
306
|
}
|
|
281
307
|
}
|
|
282
308
|
|
|
309
|
+
const crashReport = await writeCrashReport(
|
|
310
|
+
{
|
|
311
|
+
kind: "bash",
|
|
312
|
+
command: [shell, "-lc", finalCommand],
|
|
313
|
+
exitCode: winner.result.exitCode,
|
|
314
|
+
stderr: undefined,
|
|
315
|
+
},
|
|
316
|
+
{ cwd: commandCwd },
|
|
317
|
+
);
|
|
318
|
+
const crashNotice = formatCrashDiagnosticNotice(crashReport);
|
|
319
|
+
if (crashNotice) {
|
|
320
|
+
const separator = "\n";
|
|
321
|
+
sink.push(`${separator}${crashNotice}\n`);
|
|
322
|
+
}
|
|
323
|
+
|
|
283
324
|
// Normal completion
|
|
284
325
|
return {
|
|
285
326
|
exitCode: winner.result.exitCode,
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI write/mutation receipt shaping (Workstream B, v4).
|
|
3
|
+
*
|
|
4
|
+
* `CliWriteReceipt` is the compact **stdout presentation** returned by a GJC
|
|
5
|
+
* mutation command. It carries only routing/audit fields a caller needs; it
|
|
6
|
+
* NEVER echoes the persisted body (full `state` envelope, ultragoal `plan`,
|
|
7
|
+
* team task body, ralplan `task`, etc.) — echoing those back is a redundant
|
|
8
|
+
* token leak because the caller already has the content it just wrote.
|
|
9
|
+
*
|
|
10
|
+
* This is intentionally a *separate* concept from the persisted
|
|
11
|
+
* `WorkflowStateReceipt` (the on-disk envelope `receipt` integrity field).
|
|
12
|
+
* Do NOT use `CliWriteReceipt` as a persistence schema or validate persisted
|
|
13
|
+
* envelopes against it.
|
|
14
|
+
*/
|
|
15
|
+
export interface CliWriteReceipt {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
[field: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Serialize a write/mutation receipt to compact stdout JSON.
|
|
22
|
+
* `undefined` fields are dropped so optional routing fields stay absent
|
|
23
|
+
* rather than serialized as `null`.
|
|
24
|
+
*/
|
|
25
|
+
export function renderCliWriteReceipt(receipt: Record<string, unknown>): string {
|
|
26
|
+
const out: Record<string, unknown> = {};
|
|
27
|
+
for (const key of Object.keys(receipt)) {
|
|
28
|
+
if (receipt[key] !== undefined) out[key] = receipt[key];
|
|
29
|
+
}
|
|
30
|
+
return `${JSON.stringify(out)}\n`;
|
|
31
|
+
}
|