@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
package/src/commands/harness.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
} from "../harness-control-plane/storage";
|
|
28
28
|
import {
|
|
29
29
|
DEFAULT_RETRY_BUDGET,
|
|
30
|
+
type EventEnvelope,
|
|
30
31
|
type GitDelta,
|
|
31
32
|
type Harness as HarnessKind,
|
|
32
33
|
type Observation,
|
|
@@ -76,27 +77,191 @@ function gitDeltaFor(workspace: string): { gitDelta: GitDelta; branch: string |
|
|
|
76
77
|
return { gitDelta: "unknown", branch, deleted: false };
|
|
77
78
|
}
|
|
78
79
|
}
|
|
80
|
+
interface HarnessPreflight {
|
|
81
|
+
ok: boolean;
|
|
82
|
+
blockers: string[];
|
|
83
|
+
workspace: string;
|
|
84
|
+
actualBranch: string | null;
|
|
85
|
+
declaredBranch: string | null;
|
|
86
|
+
normalizedIssueOrPr: string | null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizeIssueOrPr(value: unknown): string | null {
|
|
90
|
+
if (value === undefined || value === null) return null;
|
|
91
|
+
if (typeof value === "number") {
|
|
92
|
+
if (Number.isSafeInteger(value) && value > 0) return String(value);
|
|
93
|
+
throw new Error(`invalid_issue_or_pr:${value}`);
|
|
94
|
+
}
|
|
95
|
+
if (typeof value !== "string") throw new Error("invalid_issue_or_pr:not-string-or-number");
|
|
96
|
+
const trimmed = value.trim();
|
|
97
|
+
if (!trimmed) return null;
|
|
98
|
+
const patterns = [
|
|
99
|
+
/^#?(\d+)$/i,
|
|
100
|
+
/^(?:pr|pull|issue)[-_#]?(\d+)$/i,
|
|
101
|
+
/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+#(\d+)$/,
|
|
102
|
+
/^(?:https?:\/\/github\.com\/)?[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\/(?:pull|issues)\/(\d+)\/?$/i,
|
|
103
|
+
];
|
|
104
|
+
for (const pattern of patterns) {
|
|
105
|
+
const match = trimmed.match(pattern);
|
|
106
|
+
if (match?.[1]) return match[1];
|
|
107
|
+
}
|
|
108
|
+
throw new Error(`invalid_issue_or_pr:${trimmed}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function gitOutput(workspace: string, args: string[]): string | null {
|
|
112
|
+
try {
|
|
113
|
+
return execFileSync("git", args, {
|
|
114
|
+
cwd: workspace,
|
|
115
|
+
encoding: "utf8",
|
|
116
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
117
|
+
}).trim();
|
|
118
|
+
} catch {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function buildPreflight(input: Record<string, unknown>): HarnessPreflight {
|
|
124
|
+
const workspace = typeof input.workspace === "string" ? input.workspace : process.cwd();
|
|
125
|
+
const declaredBranch = typeof input.branch === "string" && input.branch.trim() ? input.branch.trim() : null;
|
|
126
|
+
const blockers: string[] = [];
|
|
127
|
+
const gitRoot = gitOutput(workspace, ["rev-parse", "--show-toplevel"]);
|
|
128
|
+
const actualBranch = gitRoot ? gitOutput(workspace, ["rev-parse", "--abbrev-ref", "HEAD"]) : null;
|
|
129
|
+
let normalizedIssueOrPr: string | null = null;
|
|
130
|
+
|
|
131
|
+
if (!gitRoot) blockers.push("workspace-not-git-repo");
|
|
132
|
+
if (gitRoot && actualBranch === "HEAD") blockers.push("detached-head");
|
|
133
|
+
if (declaredBranch && actualBranch && actualBranch !== "HEAD" && declaredBranch !== actualBranch) {
|
|
134
|
+
blockers.push("branch-mismatch");
|
|
135
|
+
}
|
|
136
|
+
try {
|
|
137
|
+
normalizedIssueOrPr = normalizeIssueOrPr(input.issueOrPr ?? input.pr ?? input.issue);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
blockers.push(error instanceof Error ? error.message : String(error));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
ok: blockers.length === 0,
|
|
144
|
+
blockers,
|
|
145
|
+
workspace,
|
|
146
|
+
actualBranch: actualBranch === "HEAD" ? null : actualBranch,
|
|
147
|
+
declaredBranch,
|
|
148
|
+
normalizedIssueOrPr,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
79
151
|
|
|
80
|
-
|
|
152
|
+
function startFatalPreflightBlockers(input: Record<string, unknown>, preflight: HarnessPreflight): string[] {
|
|
153
|
+
const strict = input.strictPreflight === true || typeof input.branch === "string";
|
|
154
|
+
return preflight.blockers.filter(blocker => {
|
|
155
|
+
if (blocker === "branch-mismatch") return true;
|
|
156
|
+
if (blocker.startsWith("invalid_issue_or_pr:")) return true;
|
|
157
|
+
if (strict && (blocker === "workspace-not-git-repo" || blocker === "detached-head")) return true;
|
|
158
|
+
return false;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Fallback liveness after owner routing failed: no reachable owner handled this CLI call. */
|
|
81
163
|
function ownerLiveFor(_state: SessionState): boolean {
|
|
82
164
|
return false;
|
|
83
165
|
}
|
|
84
166
|
|
|
85
|
-
function
|
|
167
|
+
function pushUnique(out: string[], value: unknown): void {
|
|
168
|
+
if (typeof value === "string" && !out.includes(value)) out.push(value);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
interface CompletedTerminalEvent {
|
|
172
|
+
cursor: number;
|
|
173
|
+
createdAt: string;
|
|
174
|
+
kind: string;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function completedTerminalEvent(events: EventEnvelope[]): CompletedTerminalEvent | null {
|
|
178
|
+
for (const event of [...events].reverse()) {
|
|
179
|
+
const signal = (event.evidence as { signal?: unknown } | undefined)?.signal;
|
|
180
|
+
if (event.kind === "rpc_agent_completed" || signal === "completed") {
|
|
181
|
+
return { cursor: event.cursor, createdAt: event.createdAt, kind: event.kind };
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function buildObservation(
|
|
188
|
+
root: string,
|
|
189
|
+
state: SessionState,
|
|
190
|
+
ownerLive: boolean,
|
|
191
|
+
): Promise<{
|
|
192
|
+
observation: Observation;
|
|
193
|
+
completedTerminalEvent: CompletedTerminalEvent | null;
|
|
194
|
+
}> {
|
|
86
195
|
const workspace = state.handle.workspace;
|
|
87
196
|
const { gitDelta, branch, deleted } = gitDeltaFor(workspace);
|
|
197
|
+
const events = await readEvents(root, state.sessionId, 0);
|
|
198
|
+
const observedSignals = ["SessionStart"];
|
|
199
|
+
for (const event of events.slice(-200)) {
|
|
200
|
+
pushUnique(observedSignals, (event.evidence as { signal?: unknown } | undefined)?.signal);
|
|
201
|
+
}
|
|
202
|
+
const terminalEvent = completedTerminalEvent(events);
|
|
203
|
+
const lastEventAt = events.at(-1)?.createdAt;
|
|
88
204
|
return {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
205
|
+
observation: {
|
|
206
|
+
lifecycle: state.lifecycle,
|
|
207
|
+
ownerLive,
|
|
208
|
+
cwd: workspace,
|
|
209
|
+
branch: branch ?? state.handle.branch,
|
|
210
|
+
gitDelta,
|
|
211
|
+
lastActivityAt: lastEventAt ?? state.updatedAt,
|
|
212
|
+
observedSignals,
|
|
213
|
+
risk: deleted ? "deleted-worktree" : !ownerLive && gitDelta === "dirty" ? "vanished-dirty" : "normal",
|
|
214
|
+
},
|
|
215
|
+
completedTerminalEvent: terminalEvent,
|
|
97
216
|
};
|
|
98
217
|
}
|
|
99
218
|
|
|
219
|
+
function isOwnerLivenessBlocker(blocker: string): boolean {
|
|
220
|
+
return blocker === "detached-owner-not-live" || blocker.startsWith("owner-vanished:");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function reconcileCompletedOwnerExited(
|
|
224
|
+
root: string,
|
|
225
|
+
state: SessionState,
|
|
226
|
+
observation: Observation,
|
|
227
|
+
completedTerminal: CompletedTerminalEvent | null,
|
|
228
|
+
): Promise<SessionState> {
|
|
229
|
+
if (!completedTerminal || observation.ownerLive || observation.gitDelta !== "clean") return state;
|
|
230
|
+
if (state.lifecycle === "completed" || state.lifecycle === "retired") return state;
|
|
231
|
+
state.lifecycle = "completed";
|
|
232
|
+
state.blockers = state.blockers.filter(blocker => !isOwnerLivenessBlocker(blocker));
|
|
233
|
+
state.updatedAt = nowIso();
|
|
234
|
+
await writeSessionState(root, state);
|
|
235
|
+
return state;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function needsVanishedOwnerBlock(
|
|
239
|
+
state: SessionState,
|
|
240
|
+
observation: Observation,
|
|
241
|
+
completedTerminal: CompletedTerminalEvent | null,
|
|
242
|
+
): boolean {
|
|
243
|
+
if (observation.ownerLive || state.lifecycle !== "observing") return false;
|
|
244
|
+
if (completedTerminal || observation.observedSignals.includes("completed")) return false;
|
|
245
|
+
return observation.observedSignals.some(
|
|
246
|
+
signal => signal === "prompt-accepted" || signal === "tool-call" || signal === "streaming",
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function markVanishedOwnerBlocked(
|
|
251
|
+
root: string,
|
|
252
|
+
state: SessionState,
|
|
253
|
+
observation: Observation,
|
|
254
|
+
completedTerminal: CompletedTerminalEvent | null,
|
|
255
|
+
): Promise<SessionState> {
|
|
256
|
+
if (!needsVanishedOwnerBlock(state, observation, completedTerminal)) return state;
|
|
257
|
+
const blocker = `owner-vanished:${observation.gitDelta}`;
|
|
258
|
+
state.lifecycle = "blocked";
|
|
259
|
+
state.blockers = state.blockers.includes(blocker) ? state.blockers : [...state.blockers, blocker];
|
|
260
|
+
state.updatedAt = nowIso();
|
|
261
|
+
await writeSessionState(root, state);
|
|
262
|
+
return state;
|
|
263
|
+
}
|
|
264
|
+
|
|
100
265
|
function resolveRetryBudget(input: Record<string, unknown>): RetryBudget {
|
|
101
266
|
const supplied = input.retryBudget;
|
|
102
267
|
if (supplied && typeof supplied === "object" && !Array.isArray(supplied)) {
|
|
@@ -139,7 +304,7 @@ export default class Harness extends Command {
|
|
|
139
304
|
|
|
140
305
|
static args = {
|
|
141
306
|
verb: Args.string({
|
|
142
|
-
description: "start|submit|observe|classify|recover|validate|finalize|retire|events|monitor|operate",
|
|
307
|
+
description: "start|preflight|submit|observe|classify|recover|validate|finalize|retire|events|monitor|operate",
|
|
143
308
|
required: true,
|
|
144
309
|
}),
|
|
145
310
|
};
|
|
@@ -168,6 +333,8 @@ export default class Harness extends Command {
|
|
|
168
333
|
switch (verb) {
|
|
169
334
|
case "start":
|
|
170
335
|
return await this.#start(root, input);
|
|
336
|
+
case "preflight":
|
|
337
|
+
return this.#preflight(input);
|
|
171
338
|
case "observe":
|
|
172
339
|
return await this.#observe(root, input, flags.session);
|
|
173
340
|
case "classify":
|
|
@@ -196,6 +363,20 @@ export default class Harness extends Command {
|
|
|
196
363
|
}
|
|
197
364
|
}
|
|
198
365
|
|
|
366
|
+
#preflight(input: Record<string, unknown>): void {
|
|
367
|
+
const preflight = buildPreflight(input);
|
|
368
|
+
writeJson({
|
|
369
|
+
ok: preflight.ok,
|
|
370
|
+
evidence: {
|
|
371
|
+
preflight,
|
|
372
|
+
guidance: preflight.ok
|
|
373
|
+
? "workspace metadata is normalized"
|
|
374
|
+
: "fix blockers before gjc harness start; branch must match the actual checkout and issueOrPr must be numeric or a recognized PR/issue form",
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
if (!preflight.ok) process.exitCode = 1;
|
|
378
|
+
}
|
|
379
|
+
|
|
199
380
|
async #finalizeVerb(root: string, input: Record<string, unknown>, flagSession: string | undefined): Promise<void> {
|
|
200
381
|
const sessionId = requireSessionId(input, flagSession);
|
|
201
382
|
if (await this.#tryOwnerRoute(root, sessionId, "finalize", { ...input, sessionId })) return;
|
|
@@ -214,6 +395,7 @@ export default class Harness extends Command {
|
|
|
214
395
|
): Promise<void> {
|
|
215
396
|
const sessionId = flagSession ?? (typeof input.sessionId === "string" ? input.sessionId : undefined);
|
|
216
397
|
if (sessionId && (await this.#tryOwnerRoute(root, sessionId, verb, { ...input, sessionId }))) return;
|
|
398
|
+
if (verb === "recover" && sessionId) return this.#recoverWithoutOwner(root, sessionId, input);
|
|
217
399
|
return this.#pending(root, verb, input, flagSession);
|
|
218
400
|
}
|
|
219
401
|
|
|
@@ -343,6 +525,21 @@ export default class Harness extends Command {
|
|
|
343
525
|
process.exitCode = 1;
|
|
344
526
|
return;
|
|
345
527
|
}
|
|
528
|
+
const preflight = buildPreflight(input);
|
|
529
|
+
const fatalBlockers = startFatalPreflightBlockers(input, preflight);
|
|
530
|
+
if (fatalBlockers.length > 0) {
|
|
531
|
+
writeJson({
|
|
532
|
+
ok: false,
|
|
533
|
+
error: "harness_preflight_failed",
|
|
534
|
+
evidence: {
|
|
535
|
+
preflight: { ...preflight, blockers: fatalBlockers, ok: false },
|
|
536
|
+
guidance:
|
|
537
|
+
"fix blockers before start; run gjc harness preflight with the same input for branch and issue/PR diagnostics",
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
process.exitCode = 1;
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
346
543
|
const workspace = typeof input.workspace === "string" ? input.workspace : process.cwd();
|
|
347
544
|
const sessionId = typeof input.sessionId === "string" ? input.sessionId : generateSessionId();
|
|
348
545
|
const eventsPath = `${root}/sessions/${sessionId}/events.jsonl`;
|
|
@@ -353,9 +550,9 @@ export default class Harness extends Command {
|
|
|
353
550
|
harness,
|
|
354
551
|
repo: typeof input.repo === "string" ? input.repo : null,
|
|
355
552
|
workspace,
|
|
356
|
-
branch:
|
|
553
|
+
branch: preflight.declaredBranch ?? preflight.actualBranch,
|
|
357
554
|
base: typeof input.base === "string" ? input.base : null,
|
|
358
|
-
issueOrPr:
|
|
555
|
+
issueOrPr: preflight.normalizedIssueOrPr,
|
|
359
556
|
processHandle: { kind: "runtime-owner", ownerId: null, pid: null },
|
|
360
557
|
rpcHandle: { kind: "rpc-subprocess", pid: null, sessionDir: `${root}/sessions/${sessionId}/gjc-session` },
|
|
361
558
|
ownerHandle: { leasePath, endpoint: null, heartbeatAt: null },
|
|
@@ -407,6 +604,25 @@ export default class Harness extends Command {
|
|
|
407
604
|
await writeSessionState(root, state);
|
|
408
605
|
}
|
|
409
606
|
}
|
|
607
|
+
if (ownerBlockerReason) {
|
|
608
|
+
const resolved = await resolveOwner(root, sessionId);
|
|
609
|
+
if (resolved.live && resolved.socketPath) {
|
|
610
|
+
ownerLive = true;
|
|
611
|
+
ownerBlockerReason = null;
|
|
612
|
+
handle.processHandle = {
|
|
613
|
+
kind: "runtime-owner",
|
|
614
|
+
ownerId: resolved.lease?.ownerId ?? null,
|
|
615
|
+
pid: resolved.lease?.pid ?? null,
|
|
616
|
+
};
|
|
617
|
+
handle.ownerHandle = {
|
|
618
|
+
leasePath,
|
|
619
|
+
endpoint: resolved.socketPath,
|
|
620
|
+
heartbeatAt: resolved.lease?.heartbeatAt ?? null,
|
|
621
|
+
};
|
|
622
|
+
state.handle = handle;
|
|
623
|
+
await writeSessionState(root, state);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
410
626
|
if (ownerBlockerReason) {
|
|
411
627
|
state.lifecycle = "blocked";
|
|
412
628
|
state.blockers = [...state.blockers, ownerBlockerReason];
|
|
@@ -421,6 +637,7 @@ export default class Harness extends Command {
|
|
|
421
637
|
{
|
|
422
638
|
handle,
|
|
423
639
|
ownerRuntime,
|
|
640
|
+
preflight,
|
|
424
641
|
...(ownerFallbackReason ? { ownerFallbackReason } : {}),
|
|
425
642
|
...(ownerBlockerReason ? { reason: ownerBlockerReason } : {}),
|
|
426
643
|
},
|
|
@@ -453,10 +670,24 @@ export default class Harness extends Command {
|
|
|
453
670
|
async #observe(root: string, input: Record<string, unknown>, flagSession: string | undefined): Promise<void> {
|
|
454
671
|
const sessionId = requireSessionId(input, flagSession);
|
|
455
672
|
if (await this.#tryOwnerRoute(root, sessionId, "observe", { ...input, sessionId })) return;
|
|
456
|
-
|
|
673
|
+
let state = await loadState(root, sessionId);
|
|
457
674
|
const ownerLive = ownerLiveFor(state);
|
|
458
|
-
const observation = buildObservation(state, ownerLive);
|
|
459
|
-
|
|
675
|
+
const { observation, completedTerminalEvent } = await buildObservation(root, state, ownerLive);
|
|
676
|
+
state = await reconcileCompletedOwnerExited(root, state, observation, completedTerminalEvent);
|
|
677
|
+
const vanishedOwnerBlock = needsVanishedOwnerBlock(state, observation, completedTerminalEvent);
|
|
678
|
+
state = await markVanishedOwnerBlocked(root, state, observation, completedTerminalEvent);
|
|
679
|
+
writeJson(
|
|
680
|
+
buildResponse(state, ownerLive, {
|
|
681
|
+
observation: { ...observation, lifecycle: state.lifecycle },
|
|
682
|
+
readOnly: !ownerLive,
|
|
683
|
+
...(vanishedOwnerBlock
|
|
684
|
+
? { ownerVanished: true, blockerReason: `owner-vanished:${observation.gitDelta}` }
|
|
685
|
+
: {}),
|
|
686
|
+
...(completedTerminalEvent && !ownerLive
|
|
687
|
+
? { completedOwnerExited: true, terminalResult: completedTerminalEvent }
|
|
688
|
+
: {}),
|
|
689
|
+
}),
|
|
690
|
+
);
|
|
460
691
|
}
|
|
461
692
|
|
|
462
693
|
async #classify(root: string, input: Record<string, unknown>, flagSession: string | undefined): Promise<void> {
|
|
@@ -466,7 +697,16 @@ export default class Harness extends Command {
|
|
|
466
697
|
const sessionId = flagSession ?? (typeof input.sessionId === "string" ? input.sessionId : undefined);
|
|
467
698
|
if (sessionId) {
|
|
468
699
|
stateView = await loadState(root, sessionId);
|
|
469
|
-
if (!observation)
|
|
700
|
+
if (!observation) {
|
|
701
|
+
const built = await buildObservation(root, stateView, ownerLiveFor(stateView));
|
|
702
|
+
observation = built.observation;
|
|
703
|
+
stateView = await markVanishedOwnerBlocked(
|
|
704
|
+
root,
|
|
705
|
+
stateView,
|
|
706
|
+
built.observation,
|
|
707
|
+
built.completedTerminalEvent,
|
|
708
|
+
);
|
|
709
|
+
}
|
|
470
710
|
}
|
|
471
711
|
if (!observation) throw new Error("classify_requires_observation_or_session");
|
|
472
712
|
const full: Observation = {
|
|
@@ -481,7 +721,12 @@ export default class Harness extends Command {
|
|
|
481
721
|
};
|
|
482
722
|
const decision = classifyRecovery({ observation: full, retryBudget: budget });
|
|
483
723
|
if (stateView) {
|
|
484
|
-
writeJson(
|
|
724
|
+
writeJson(
|
|
725
|
+
buildResponse(stateView, ownerLiveFor(stateView), {
|
|
726
|
+
decision,
|
|
727
|
+
observation: { ...full, lifecycle: stateView.lifecycle },
|
|
728
|
+
}),
|
|
729
|
+
);
|
|
485
730
|
return;
|
|
486
731
|
}
|
|
487
732
|
// Pure classify without a session: synthesize a minimal state view.
|
|
@@ -531,7 +776,7 @@ export default class Harness extends Command {
|
|
|
531
776
|
const sessionId = requireSessionId(input, flagSession);
|
|
532
777
|
if (await this.#tryOwnerRoute(root, sessionId, "retire", { ...input, sessionId })) return;
|
|
533
778
|
const state = await loadState(root, sessionId);
|
|
534
|
-
const observation = buildObservation(state, ownerLiveFor(state));
|
|
779
|
+
const { observation } = await buildObservation(root, state, ownerLiveFor(state));
|
|
535
780
|
if (observation.gitDelta === "dirty" || observation.gitDelta === "unknown") {
|
|
536
781
|
writeJson(
|
|
537
782
|
buildResponse(
|
|
@@ -554,6 +799,31 @@ export default class Harness extends Command {
|
|
|
554
799
|
writeJson(buildResponse(state, false, { retired: true }));
|
|
555
800
|
}
|
|
556
801
|
|
|
802
|
+
async #recoverWithoutOwner(root: string, sessionId: string, input: Record<string, unknown>): Promise<void> {
|
|
803
|
+
const budget = resolveRetryBudget(input);
|
|
804
|
+
let state = await loadState(root, sessionId);
|
|
805
|
+
const { observation, completedTerminalEvent } = await buildObservation(root, state, false);
|
|
806
|
+
state = await markVanishedOwnerBlocked(root, state, observation, completedTerminalEvent);
|
|
807
|
+
const decision = classifyRecovery({
|
|
808
|
+
observation: { ...observation, lifecycle: state.lifecycle },
|
|
809
|
+
retryBudget: budget,
|
|
810
|
+
});
|
|
811
|
+
writeJson(
|
|
812
|
+
buildResponse(
|
|
813
|
+
state,
|
|
814
|
+
false,
|
|
815
|
+
{
|
|
816
|
+
pending: false,
|
|
817
|
+
reason: "owner-not-live",
|
|
818
|
+
decision,
|
|
819
|
+
observation: { ...observation, lifecycle: state.lifecycle },
|
|
820
|
+
},
|
|
821
|
+
false,
|
|
822
|
+
),
|
|
823
|
+
);
|
|
824
|
+
process.exitCode = 1;
|
|
825
|
+
}
|
|
826
|
+
|
|
557
827
|
async #pending(
|
|
558
828
|
root: string,
|
|
559
829
|
verb: string,
|
package/src/commands/launch.ts
CHANGED
|
@@ -52,8 +52,8 @@ export default class Index extends Command {
|
|
|
52
52
|
description: "Allow starting in ~ without auto-switching to a temp dir",
|
|
53
53
|
}),
|
|
54
54
|
mode: Flags.string({
|
|
55
|
-
description: "Output mode: text (default), json, rpc,
|
|
56
|
-
options: ["text", "json", "rpc", "acp", "rpc-ui"],
|
|
55
|
+
description: "Output mode: text (default), json, rpc, acp, rpc-ui, or bridge",
|
|
56
|
+
options: ["text", "json", "rpc", "acp", "rpc-ui", "bridge"],
|
|
57
57
|
}),
|
|
58
58
|
print: Flags.boolean({
|
|
59
59
|
char: "p",
|
package/src/commands/state.ts
CHANGED
|
@@ -9,9 +9,10 @@ export default class State extends Command {
|
|
|
9
9
|
'$ gjc state write --input \'{"state":{"interview_id":"abc"}}\' --mode deep-interview --json',
|
|
10
10
|
"$ gjc state clear --mode deep-interview",
|
|
11
11
|
"$ gjc state deep-interview read --json",
|
|
12
|
-
'$ gjc state ralplan write --input \'{"phase":"
|
|
12
|
+
'$ gjc state ralplan write --input \'{"phase":"planner","active":true}\' --json',
|
|
13
13
|
"$ gjc state team contract",
|
|
14
14
|
"$ gjc state deep-interview handoff --to ralplan --json",
|
|
15
|
+
"$ gjc state doctor --skill ralplan --json",
|
|
15
16
|
];
|
|
16
17
|
|
|
17
18
|
async run(): Promise<void> {
|
package/src/commands/team.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Args, Command, Flags } from "@gajae-code/utils/cli";
|
|
2
|
+
import { renderCliWriteReceipt } from "../gjc-runtime/cli-write-receipt";
|
|
2
3
|
import { renderTeamStatusMarkdown } from "../gjc-runtime/state-renderer";
|
|
3
4
|
import {
|
|
4
5
|
buildTeamHudSummary,
|
|
@@ -45,6 +46,23 @@ function formatTaskCounts(counts: Record<string, number>): string {
|
|
|
45
46
|
.join(" ");
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
function snapshotWriteReceipt(snapshot: GjcTeamSnapshot): Record<string, unknown> {
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
team_name: snapshot.team_name,
|
|
53
|
+
phase: snapshot.phase,
|
|
54
|
+
state_dir: snapshot.state_dir,
|
|
55
|
+
tmux_session: snapshot.tmux_session,
|
|
56
|
+
tmux_target: snapshot.tmux_target,
|
|
57
|
+
worker_count: snapshot.workers.length,
|
|
58
|
+
task_counts: snapshot.task_counts,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function writeReceipt(value: Record<string, unknown>): void {
|
|
63
|
+
process.stdout.write(renderCliWriteReceipt(value));
|
|
64
|
+
}
|
|
65
|
+
|
|
48
66
|
function parseInputFlag(argv: string[]): Record<string, unknown> {
|
|
49
67
|
const index = argv.indexOf("--input");
|
|
50
68
|
if (index < 0) return {};
|
|
@@ -124,7 +142,7 @@ export default class Team extends Command {
|
|
|
124
142
|
const snapshot = await monitorGjcTeamSnapshot(teamName);
|
|
125
143
|
await syncTeamHud(snapshot);
|
|
126
144
|
if (json) {
|
|
127
|
-
|
|
145
|
+
writeReceipt(snapshotWriteReceipt(snapshot));
|
|
128
146
|
return;
|
|
129
147
|
}
|
|
130
148
|
writeText([
|
|
@@ -141,7 +159,7 @@ export default class Team extends Command {
|
|
|
141
159
|
const snapshot = await shutdownGjcTeam(teamName);
|
|
142
160
|
await syncTeamHud(snapshot);
|
|
143
161
|
if (json) {
|
|
144
|
-
|
|
162
|
+
writeReceipt(snapshotWriteReceipt(snapshot));
|
|
145
163
|
return;
|
|
146
164
|
}
|
|
147
165
|
writeText([`team: ${snapshot.team_name}`, `phase: ${snapshot.phase}`, `state: ${snapshot.state_dir}`]);
|
|
@@ -174,7 +192,7 @@ export default class Team extends Command {
|
|
|
174
192
|
// API operations without a resolvable snapshot leave HUD state unchanged.
|
|
175
193
|
}
|
|
176
194
|
}
|
|
177
|
-
|
|
195
|
+
writeReceipt(result as Record<string, unknown>);
|
|
178
196
|
return;
|
|
179
197
|
}
|
|
180
198
|
|
|
@@ -183,7 +201,7 @@ export default class Team extends Command {
|
|
|
183
201
|
const snapshot = await startGjcTeam({ ...options, dryRun });
|
|
184
202
|
await syncTeamHud(snapshot);
|
|
185
203
|
if (json) {
|
|
186
|
-
|
|
204
|
+
writeReceipt(snapshotWriteReceipt(snapshot));
|
|
187
205
|
return;
|
|
188
206
|
}
|
|
189
207
|
writeText([
|
|
@@ -38,6 +38,7 @@ interface AppKeybindings {
|
|
|
38
38
|
"app.session.fork": true;
|
|
39
39
|
"app.session.resume": true;
|
|
40
40
|
"app.session.observe": true;
|
|
41
|
+
"app.jobs.open": true;
|
|
41
42
|
"app.session.togglePath": true;
|
|
42
43
|
"app.session.toggleSort": true;
|
|
43
44
|
"app.session.rename": true;
|
|
@@ -149,6 +150,11 @@ export const KEYBINDINGS = {
|
|
|
149
150
|
defaultKeys: "ctrl+s",
|
|
150
151
|
description: "Observe subagent sessions",
|
|
151
152
|
},
|
|
153
|
+
|
|
154
|
+
"app.jobs.open": {
|
|
155
|
+
defaultKeys: "alt+j",
|
|
156
|
+
description: "Open monitor/cron jobs overlay",
|
|
157
|
+
},
|
|
152
158
|
"app.session.togglePath": {
|
|
153
159
|
defaultKeys: "ctrl+p",
|
|
154
160
|
description: "Toggle session path display",
|
|
@@ -74,6 +74,7 @@ export type StatusLineSegmentId =
|
|
|
74
74
|
| "git"
|
|
75
75
|
| "pr"
|
|
76
76
|
| "subagents"
|
|
77
|
+
| "jobs"
|
|
77
78
|
| "token_in"
|
|
78
79
|
| "token_out"
|
|
79
80
|
| "token_total"
|
|
@@ -2352,11 +2353,12 @@ export const SETTINGS_SCHEMA = {
|
|
|
2352
2353
|
|
|
2353
2354
|
"task.maxConcurrency": {
|
|
2354
2355
|
type: "number",
|
|
2355
|
-
default:
|
|
2356
|
+
default: 8,
|
|
2356
2357
|
ui: {
|
|
2357
2358
|
tab: "tasks",
|
|
2358
2359
|
label: "Max Concurrent Tasks",
|
|
2359
|
-
description:
|
|
2360
|
+
description:
|
|
2361
|
+
"Safer concurrent limit for subagents; higher fan-out still requires an explicit plan above 4 tasks.",
|
|
2360
2362
|
options: [
|
|
2361
2363
|
{ value: "0", label: "Unlimited" },
|
|
2362
2364
|
{ value: "1", label: "1 task" },
|
|
@@ -2408,7 +2410,8 @@ export const SETTINGS_SCHEMA = {
|
|
|
2408
2410
|
ui: {
|
|
2409
2411
|
tab: "tasks",
|
|
2410
2412
|
label: "Fork Context Max Tokens",
|
|
2411
|
-
description:
|
|
2413
|
+
description:
|
|
2414
|
+
"Approximate token cap for explicit full fork-context seeds. 0 uses 15% of the target model context window, with a 15k fallback when the window is unknown.",
|
|
2412
2415
|
},
|
|
2413
2416
|
},
|
|
2414
2417
|
|
package/src/dap/client.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logger, ptree } from "@gajae-code/utils";
|
|
2
|
+
import { formatCrashDiagnosticNotice, writeCrashReport } from "../debug/crash-diagnostics";
|
|
2
3
|
import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
|
|
3
4
|
import { ToolAbortError } from "../tools/tool-errors";
|
|
4
5
|
import type {
|
|
@@ -532,15 +533,28 @@ export class DapClient {
|
|
|
532
533
|
}
|
|
533
534
|
}
|
|
534
535
|
|
|
535
|
-
#handleProcessExit(): void {
|
|
536
|
+
async #handleProcessExit(): Promise<void> {
|
|
536
537
|
if (this.#disposed) return;
|
|
537
538
|
this.#disposed = true;
|
|
538
539
|
const stderr = this.proc.peekStderr().trim();
|
|
539
540
|
const exitCode = this.proc.exitCode;
|
|
541
|
+
const crashNotice = formatCrashDiagnosticNotice(
|
|
542
|
+
await writeCrashReport(
|
|
543
|
+
{
|
|
544
|
+
kind: "dap",
|
|
545
|
+
command: [this.adapter.resolvedCommand, ...this.adapter.args],
|
|
546
|
+
exitCode,
|
|
547
|
+
stderr,
|
|
548
|
+
protocol: this.adapter.connectMode ?? "stdio",
|
|
549
|
+
},
|
|
550
|
+
{ cwd: this.cwd },
|
|
551
|
+
),
|
|
552
|
+
);
|
|
553
|
+
const diagnosticSuffix = crashNotice ? `\n${crashNotice}` : "";
|
|
540
554
|
const error = new Error(
|
|
541
555
|
stderr
|
|
542
|
-
? `DAP adapter exited (code ${exitCode}): ${stderr}`
|
|
543
|
-
: `DAP adapter exited unexpectedly (code ${exitCode})`,
|
|
556
|
+
? `DAP adapter exited (code ${exitCode}): ${stderr}${diagnosticSuffix}`
|
|
557
|
+
: `DAP adapter exited unexpectedly (code ${exitCode})${diagnosticSuffix}`,
|
|
544
558
|
);
|
|
545
559
|
this.#rejectPendingRequests(error);
|
|
546
560
|
}
|