@gajae-code/coding-agent 0.2.5 → 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 +28 -0
- package/dist/types/async/job-manager.d.ts +91 -2
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/commands/harness.d.ts +37 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +10 -4
- package/dist/types/config/settings.d.ts +2 -0
- 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 +6 -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/extensibility/custom-tools/types.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +6 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -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-graph.d.ts +4 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +33 -0
- package/dist/types/gjc-runtime/state-renderer.d.ts +65 -0
- package/dist/types/gjc-runtime/state-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
- package/dist/types/gjc-runtime/state-validation.d.ts +6 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +147 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
- package/dist/types/gjc-runtime/workflow-manifest.d.ts +54 -0
- package/dist/types/harness-control-plane/classifier.d.ts +13 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +31 -0
- package/dist/types/harness-control-plane/finalize.d.ts +47 -0
- package/dist/types/harness-control-plane/frame-mapper.d.ts +29 -0
- package/dist/types/harness-control-plane/operate.d.ts +35 -0
- package/dist/types/harness-control-plane/owner.d.ts +46 -0
- package/dist/types/harness-control-plane/preserve.d.ts +19 -0
- package/dist/types/harness-control-plane/receipts.d.ts +88 -0
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +66 -0
- package/dist/types/harness-control-plane/seams.d.ts +21 -0
- package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +19 -0
- package/dist/types/harness-control-plane/storage.d.ts +53 -0
- package/dist/types/harness-control-plane/types.d.ts +162 -0
- package/dist/types/hooks/skill-keywords.d.ts +2 -1
- package/dist/types/hooks/skill-state.d.ts +23 -29
- 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/hook-selector.d.ts +1 -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 +2 -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 +4 -0
- package/dist/types/session/agent-session.d.ts +19 -1
- package/dist/types/skill-state/active-state.d.ts +2 -0
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
- package/dist/types/skill-state/workflow-state-contract.d.ts +25 -2
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/task/executor.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 +198 -14
- 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 +26 -1
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +334 -6
- package/src/cli/args.ts +9 -2
- package/src/cli/auth-broker-cli.ts +1 -0
- package/src/cli/config-cli.ts +10 -2
- package/src/cli.ts +2 -0
- package/src/commands/deep-interview.ts +1 -0
- package/src/commands/harness.ts +862 -0
- package/src/commands/launch.ts +2 -2
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +54 -39
- package/src/config/keybindings.ts +6 -0
- package/src/config/settings-schema.ts +13 -3
- package/src/config/settings.ts +5 -0
- 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 +372 -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/team/SKILL.md +47 -21
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +106 -13
- 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/extensibility/custom-tools/types.ts +1 -0
- package/src/extensibility/extensions/types.ts +6 -0
- package/src/extensibility/shared-events.ts +1 -0
- package/src/gjc-runtime/cli-write-receipt.ts +31 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +98 -42
- package/src/gjc-runtime/goal-mode-request.ts +11 -3
- package/src/gjc-runtime/ralplan-runtime.ts +235 -43
- package/src/gjc-runtime/state-graph.ts +86 -0
- package/src/gjc-runtime/state-migrations.ts +179 -0
- package/src/gjc-runtime/state-renderer.ts +345 -0
- package/src/gjc-runtime/state-runtime.ts +1155 -46
- package/src/gjc-runtime/state-schema.ts +192 -0
- package/src/gjc-runtime/state-validation.ts +49 -0
- package/src/gjc-runtime/state-writer.ts +749 -0
- package/src/gjc-runtime/team-runtime.ts +1255 -189
- package/src/gjc-runtime/ultragoal-runtime.ts +460 -43
- package/src/gjc-runtime/workflow-command-ref.ts +239 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +1601 -0
- package/src/gjc-runtime/workflow-manifest.ts +427 -0
- package/src/harness-control-plane/classifier.ts +128 -0
- package/src/harness-control-plane/control-endpoint.ts +148 -0
- package/src/harness-control-plane/finalize.ts +222 -0
- package/src/harness-control-plane/frame-mapper.ts +286 -0
- package/src/harness-control-plane/operate.ts +225 -0
- package/src/harness-control-plane/owner.ts +600 -0
- package/src/harness-control-plane/preserve.ts +102 -0
- package/src/harness-control-plane/receipts.ts +216 -0
- package/src/harness-control-plane/rpc-adapter.ts +276 -0
- package/src/harness-control-plane/seams.ts +39 -0
- package/src/harness-control-plane/session-lease.ts +388 -0
- package/src/harness-control-plane/state-machine.ts +98 -0
- package/src/harness-control-plane/storage.ts +257 -0
- package/src/harness-control-plane/types.ts +214 -0
- package/src/hooks/skill-keywords.ts +4 -2
- package/src/hooks/skill-state.ts +197 -64
- 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/assistant-message.ts +5 -1
- package/src/modes/components/custom-editor.ts +101 -0
- package/src/modes/components/hook-selector.ts +133 -20
- 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/event-controller.ts +71 -6
- package/src/modes/controllers/extension-ui-controller.ts +43 -1
- package/src/modes/controllers/input-controller.ts +105 -9
- package/src/modes/controllers/selector-controller.ts +31 -1
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +28 -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/agents/executor.md +13 -0
- package/src/prompts/tools/subagent.md +39 -4
- package/src/prompts/tools/task-summary.md +3 -9
- package/src/prompts/tools/task.md +5 -1
- package/src/sdk.ts +8 -0
- package/src/session/agent-session.ts +445 -71
- package/src/session/session-manager.ts +13 -1
- package/src/skill-state/active-state.ts +58 -65
- package/src/skill-state/deep-interview-mutation-guard.ts +114 -17
- package/src/skill-state/initial-phase.ts +2 -0
- package/src/skill-state/workflow-state-contract.ts +33 -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 +79 -13
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +376 -74
- package/src/task/output-manager.ts +5 -4
- package/src/task/receipt.ts +297 -0
- package/src/task/render.ts +54 -134
- package/src/task/spawn-gate.ts +132 -0
- package/src/task/types.ts +104 -10
- package/src/tools/ask.ts +88 -27
- 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 +423 -79
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type ModeChangeEntry,
|
|
9
9
|
type SessionEntry,
|
|
10
10
|
} from "../session/session-manager";
|
|
11
|
+
import { removeFileAudited, writeJsonAtomic } from "./state-writer";
|
|
11
12
|
|
|
12
13
|
export const GJC_SESSION_FILE_ENV = "GJC_SESSION_FILE";
|
|
13
14
|
export const GJC_SESSION_ID_ENV = "GJC_SESSION_ID";
|
|
@@ -88,8 +89,10 @@ export async function writePendingGoalModeRequest(input: {
|
|
|
88
89
|
goalsPath: input.goalsPath,
|
|
89
90
|
};
|
|
90
91
|
const filePath = requestPath(input.cwd);
|
|
91
|
-
await
|
|
92
|
-
|
|
92
|
+
await writeJsonAtomic(filePath, request, {
|
|
93
|
+
cwd: input.cwd,
|
|
94
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime" },
|
|
95
|
+
});
|
|
93
96
|
return request;
|
|
94
97
|
}
|
|
95
98
|
|
|
@@ -153,6 +156,8 @@ export async function writeCurrentSessionGoalModeState(input: {
|
|
|
153
156
|
mode: "goal",
|
|
154
157
|
data: { goal: state.goal },
|
|
155
158
|
};
|
|
159
|
+
// The session transcript file lives outside `.gjc/` (GJC_SESSION_FILE), so it is not a
|
|
160
|
+
// sanctioned-writer target; append directly.
|
|
156
161
|
await fs.appendFile(sessionFile, `${JSON.stringify(entry)}\n`);
|
|
157
162
|
return { status: "updated", goal: state.goal, sessionFile };
|
|
158
163
|
}
|
|
@@ -176,7 +181,10 @@ export async function consumePendingGoalModeRequest(cwd: string): Promise<Pendin
|
|
|
176
181
|
) {
|
|
177
182
|
return null;
|
|
178
183
|
}
|
|
179
|
-
await
|
|
184
|
+
await removeFileAudited(filePath, {
|
|
185
|
+
cwd,
|
|
186
|
+
audit: { category: "prune", verb: "remove", owner: "gjc-runtime" },
|
|
187
|
+
}).catch(error => {
|
|
180
188
|
if (!isEnoent(error)) throw error;
|
|
181
189
|
});
|
|
182
190
|
return { ...candidate, objective: candidate.objective.trim() } as PendingGoalModeRequest;
|
|
@@ -3,7 +3,11 @@ import * as fs from "node:fs/promises";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { syncSkillActiveState } from "../skill-state/active-state";
|
|
5
5
|
import { buildRalplanHudSummary } from "../skill-state/workflow-hud";
|
|
6
|
+
import { WORKFLOW_STATE_VERSION } from "../skill-state/workflow-state-contract";
|
|
7
|
+
import { renderCliWriteReceipt } from "./cli-write-receipt";
|
|
6
8
|
import { isRestrictedRoleAgentBash } from "./restricted-role-agent-bash";
|
|
9
|
+
import { migrateWorkflowState } from "./state-migrations";
|
|
10
|
+
import { appendJsonl, readExistingStateForMutation, writeArtifact, writeWorkflowEnvelopeAtomic } from "./state-writer";
|
|
7
11
|
|
|
8
12
|
/**
|
|
9
13
|
* Native implementation of `gjc ralplan`.
|
|
@@ -38,6 +42,17 @@ const KNOWN_CRITIC_KINDS = new Set(["openai-code"]);
|
|
|
38
42
|
|
|
39
43
|
const PATH_COMPONENT_RE = /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$/;
|
|
40
44
|
|
|
45
|
+
const SUBAGENT_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/;
|
|
46
|
+
|
|
47
|
+
const KNOWN_FALLBACK_REASONS = new Set([
|
|
48
|
+
"context_unavailable",
|
|
49
|
+
"not_found",
|
|
50
|
+
"no_runner",
|
|
51
|
+
"resume_failed",
|
|
52
|
+
"process_restart",
|
|
53
|
+
"missing_record",
|
|
54
|
+
]);
|
|
55
|
+
|
|
41
56
|
class RalplanCommandError extends Error {
|
|
42
57
|
constructor(
|
|
43
58
|
public readonly exitStatus: number,
|
|
@@ -56,6 +71,12 @@ const VALUE_FLAGS = new Set([
|
|
|
56
71
|
"--session-id",
|
|
57
72
|
"--architect",
|
|
58
73
|
"--critic",
|
|
74
|
+
"--planner-id",
|
|
75
|
+
"--planner-resumable",
|
|
76
|
+
"--fallback-reason",
|
|
77
|
+
"--fallback-attempted-id",
|
|
78
|
+
"--fallback-stage-n",
|
|
79
|
+
"--fallback-receipt-path",
|
|
59
80
|
]);
|
|
60
81
|
|
|
61
82
|
function flagValue(args: readonly string[], flag: string): string | undefined {
|
|
@@ -144,37 +165,190 @@ function ralplanStatePath(cwd: string, sessionId: string | undefined): string {
|
|
|
144
165
|
}
|
|
145
166
|
|
|
146
167
|
async function readActiveRunId(cwd: string, sessionId: string | undefined): Promise<string | undefined> {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return undefined;
|
|
168
|
+
const statePath = ralplanStatePath(cwd, sessionId);
|
|
169
|
+
const existingRead = await readExistingStateForMutation(statePath);
|
|
170
|
+
if (existingRead.kind === "absent") return undefined;
|
|
171
|
+
if (existingRead.kind === "corrupt") {
|
|
172
|
+
throw new RalplanCommandError(
|
|
173
|
+
2,
|
|
174
|
+
`existing ralplan state is corrupt or tampered (${existingRead.error}); refusing to overwrite ${statePath}`,
|
|
175
|
+
);
|
|
156
176
|
}
|
|
177
|
+
const candidate = typeof existingRead.value.run_id === "string" ? existingRead.value.run_id.trim() : "";
|
|
178
|
+
if (!candidate) return undefined;
|
|
179
|
+
assertSafePathComponent(candidate, "run-id");
|
|
180
|
+
return candidate;
|
|
157
181
|
}
|
|
158
182
|
|
|
159
183
|
async function persistActiveRunId(cwd: string, sessionId: string | undefined, runId: string): Promise<void> {
|
|
160
184
|
const statePath = ralplanStatePath(cwd, sessionId);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}
|
|
168
|
-
} catch {
|
|
169
|
-
// fresh receipt; fall through to create
|
|
185
|
+
const existingRead = await readExistingStateForMutation(statePath);
|
|
186
|
+
if (existingRead.kind === "corrupt") {
|
|
187
|
+
throw new RalplanCommandError(
|
|
188
|
+
2,
|
|
189
|
+
`existing ralplan state is corrupt or tampered (${existingRead.error}); refusing to overwrite ${statePath}`,
|
|
190
|
+
);
|
|
170
191
|
}
|
|
171
|
-
|
|
192
|
+
let existing: Record<string, unknown> = existingRead.kind === "valid" ? existingRead.value : {};
|
|
193
|
+
|
|
194
|
+
if (existing.run_id === runId && existing.version === WORKFLOW_STATE_VERSION) return;
|
|
172
195
|
existing.run_id = runId;
|
|
173
196
|
if (typeof existing.skill !== "string") existing.skill = "ralplan";
|
|
174
197
|
if (typeof existing.active !== "boolean") existing.active = true;
|
|
198
|
+
if (typeof existing.current_phase !== "string") existing.current_phase = "planner";
|
|
199
|
+
existing = migrateWorkflowState(existing, "ralplan").state;
|
|
175
200
|
existing.updated_at = new Date().toISOString();
|
|
176
|
-
await
|
|
177
|
-
|
|
201
|
+
await writeWorkflowEnvelopeAtomic(statePath, existing, {
|
|
202
|
+
cwd,
|
|
203
|
+
receipt: { cwd, skill: "ralplan", owner: "gjc-runtime", command: "gjc ralplan persist-run-id", sessionId },
|
|
204
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* --------------------------- planner run-state --------------------------- */
|
|
209
|
+
|
|
210
|
+
interface PlannerStateUpdate {
|
|
211
|
+
subagentId?: string;
|
|
212
|
+
resumable?: boolean;
|
|
213
|
+
fallbackReason?: string;
|
|
214
|
+
fallbackAttemptedId?: string;
|
|
215
|
+
fallbackStageN?: number;
|
|
216
|
+
fallbackReceiptPath?: string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function parseBooleanFlag(raw: string, flag: string): boolean {
|
|
220
|
+
if (raw === "true") return true;
|
|
221
|
+
if (raw === "false") return false;
|
|
222
|
+
throw new RalplanCommandError(2, `invalid ${flag}: ${raw}. Expected "true" or "false".`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function assertSubagentId(value: string, label: string): void {
|
|
226
|
+
if (!SUBAGENT_ID_RE.test(value)) {
|
|
227
|
+
throw new RalplanCommandError(2, `invalid ${label}: ${value}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function plannerFlagValue(args: readonly string[], flag: string): string | undefined {
|
|
232
|
+
const value = flagValue(args, flag);
|
|
233
|
+
if (value === undefined && hasFlag(args, flag)) {
|
|
234
|
+
throw new RalplanCommandError(2, `missing value for ${flag}.`);
|
|
235
|
+
}
|
|
236
|
+
return value;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Parse the optional persisted-Planner metadata flags that may ride alongside a
|
|
241
|
+
* `--write`. Returns `undefined` when none are present so existing writes are
|
|
242
|
+
* unaffected. Throws `RalplanCommandError` on any malformed value. This records
|
|
243
|
+
* a same-session audit/routing hint, not a durable subagent registry.
|
|
244
|
+
*/
|
|
245
|
+
function parsePlannerStateArgs(args: readonly string[]): PlannerStateUpdate | undefined {
|
|
246
|
+
const subagentId = plannerFlagValue(args, "--planner-id");
|
|
247
|
+
const resumableRaw = plannerFlagValue(args, "--planner-resumable");
|
|
248
|
+
const fallbackReason = plannerFlagValue(args, "--fallback-reason");
|
|
249
|
+
const fallbackAttemptedId = plannerFlagValue(args, "--fallback-attempted-id");
|
|
250
|
+
const fallbackStageNRaw = plannerFlagValue(args, "--fallback-stage-n");
|
|
251
|
+
const fallbackReceiptPath = plannerFlagValue(args, "--fallback-receipt-path");
|
|
252
|
+
|
|
253
|
+
const anyPresent = [
|
|
254
|
+
subagentId,
|
|
255
|
+
resumableRaw,
|
|
256
|
+
fallbackReason,
|
|
257
|
+
fallbackAttemptedId,
|
|
258
|
+
fallbackStageNRaw,
|
|
259
|
+
fallbackReceiptPath,
|
|
260
|
+
].some(value => value !== undefined);
|
|
261
|
+
if (!anyPresent) return undefined;
|
|
262
|
+
|
|
263
|
+
const update: PlannerStateUpdate = {};
|
|
264
|
+
|
|
265
|
+
if (subagentId !== undefined) {
|
|
266
|
+
assertSubagentId(subagentId, "--planner-id");
|
|
267
|
+
update.subagentId = subagentId;
|
|
268
|
+
}
|
|
269
|
+
if (resumableRaw !== undefined) {
|
|
270
|
+
update.resumable = parseBooleanFlag(resumableRaw, "--planner-resumable");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const anyFallback = [fallbackReason, fallbackAttemptedId, fallbackStageNRaw, fallbackReceiptPath].some(
|
|
274
|
+
value => value !== undefined,
|
|
275
|
+
);
|
|
276
|
+
if (anyFallback) {
|
|
277
|
+
if (!fallbackReason) {
|
|
278
|
+
throw new RalplanCommandError(2, "--fallback-reason is required when recording planner fallback metadata.");
|
|
279
|
+
}
|
|
280
|
+
if (!KNOWN_FALLBACK_REASONS.has(fallbackReason)) {
|
|
281
|
+
throw new RalplanCommandError(
|
|
282
|
+
2,
|
|
283
|
+
`invalid --fallback-reason: ${fallbackReason}. Expected one of: ${[...KNOWN_FALLBACK_REASONS].join(", ")}.`,
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
update.fallbackReason = fallbackReason;
|
|
287
|
+
if (fallbackAttemptedId === undefined) {
|
|
288
|
+
throw new RalplanCommandError(
|
|
289
|
+
2,
|
|
290
|
+
"--fallback-attempted-id is required when recording planner fallback metadata.",
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
assertSubagentId(fallbackAttemptedId, "--fallback-attempted-id");
|
|
294
|
+
update.fallbackAttemptedId = fallbackAttemptedId;
|
|
295
|
+
if (fallbackStageNRaw === undefined) {
|
|
296
|
+
throw new RalplanCommandError(2, "--fallback-stage-n is required when recording planner fallback metadata.");
|
|
297
|
+
}
|
|
298
|
+
update.fallbackStageN = parseStageN(fallbackStageNRaw);
|
|
299
|
+
if (fallbackReceiptPath !== undefined) {
|
|
300
|
+
if (fallbackReceiptPath.trim() === "") {
|
|
301
|
+
throw new RalplanCommandError(2, "--fallback-receipt-path must not be empty.");
|
|
302
|
+
}
|
|
303
|
+
update.fallbackReceiptPath = fallbackReceiptPath;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return update;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Snake-case projection of a PlannerStateUpdate for state JSON + receipts. Omitted fields stay absent — an unknown `planner_resumable` is encoded by omission, never literal null. */
|
|
311
|
+
function plannerStatePayload(update: PlannerStateUpdate): Record<string, unknown> {
|
|
312
|
+
const payload: Record<string, unknown> = {};
|
|
313
|
+
if (update.subagentId !== undefined) payload.planner_subagent_id = update.subagentId;
|
|
314
|
+
if (update.resumable !== undefined) payload.planner_resumable = update.resumable;
|
|
315
|
+
if (update.fallbackReason !== undefined) payload.planner_fallback_reason = update.fallbackReason;
|
|
316
|
+
if (update.fallbackAttemptedId !== undefined) payload.planner_fallback_attempted_id = update.fallbackAttemptedId;
|
|
317
|
+
if (update.fallbackStageN !== undefined) payload.planner_fallback_stage_n = update.fallbackStageN;
|
|
318
|
+
if (update.fallbackReceiptPath !== undefined) payload.planner_fallback_receipt_path = update.fallbackReceiptPath;
|
|
319
|
+
return payload;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Merge persisted-Planner metadata into the ralplan run-state JSON. Same-session
|
|
324
|
+
* audit/routing hint only — it records what the caller has already proven and is
|
|
325
|
+
* NOT a durable cross-process subagent registry.
|
|
326
|
+
*/
|
|
327
|
+
async function applyPlannerStateUpdate(
|
|
328
|
+
cwd: string,
|
|
329
|
+
sessionId: string | undefined,
|
|
330
|
+
update: PlannerStateUpdate,
|
|
331
|
+
): Promise<void> {
|
|
332
|
+
const statePath = ralplanStatePath(cwd, sessionId);
|
|
333
|
+
const existingRead = await readExistingStateForMutation(statePath);
|
|
334
|
+
if (existingRead.kind === "corrupt") {
|
|
335
|
+
throw new RalplanCommandError(
|
|
336
|
+
2,
|
|
337
|
+
`existing ralplan state is corrupt or tampered (${existingRead.error}); refusing to overwrite ${statePath}`,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
let existing: Record<string, unknown> = existingRead.kind === "valid" ? existingRead.value : {};
|
|
341
|
+
Object.assign(existing, plannerStatePayload(update));
|
|
342
|
+
if (typeof existing.skill !== "string") existing.skill = "ralplan";
|
|
343
|
+
if (typeof existing.active !== "boolean") existing.active = true;
|
|
344
|
+
if (typeof existing.current_phase !== "string") existing.current_phase = "planner";
|
|
345
|
+
existing = migrateWorkflowState(existing, "ralplan").state;
|
|
346
|
+
existing.updated_at = new Date().toISOString();
|
|
347
|
+
await writeWorkflowEnvelopeAtomic(statePath, existing, {
|
|
348
|
+
cwd,
|
|
349
|
+
receipt: { cwd, skill: "ralplan", owner: "gjc-runtime", command: "gjc ralplan planner-state", sessionId },
|
|
350
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
351
|
+
});
|
|
178
352
|
}
|
|
179
353
|
|
|
180
354
|
async function resolveArtifactArgs(args: readonly string[], cwd: string): Promise<ResolvedArtifactArgs> {
|
|
@@ -220,27 +394,36 @@ interface PersistedArtifact {
|
|
|
220
394
|
|
|
221
395
|
async function persistArtifact(resolved: ResolvedArtifactArgs, cwd: string): Promise<PersistedArtifact> {
|
|
222
396
|
const runDir = path.join(cwd, ".gjc", "plans", "ralplan", resolved.runId);
|
|
223
|
-
|
|
397
|
+
|
|
224
398
|
const fileName = `stage-${pad2(resolved.stageN)}-${resolved.stage}.md`;
|
|
225
399
|
const filePath = path.join(runDir, fileName);
|
|
226
400
|
const content = resolved.artifact.endsWith("\n") ? resolved.artifact : `${resolved.artifact}\n`;
|
|
227
|
-
await
|
|
401
|
+
await writeArtifact(filePath, content, {
|
|
402
|
+
cwd,
|
|
403
|
+
audit: { category: "artifact", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
404
|
+
});
|
|
228
405
|
|
|
229
406
|
const sha256 = createHash("sha256").update(content).digest("hex");
|
|
230
407
|
const createdAt = new Date().toISOString();
|
|
231
|
-
const
|
|
408
|
+
const indexEntry = {
|
|
232
409
|
stage: resolved.stage,
|
|
233
410
|
stage_n: resolved.stageN,
|
|
234
411
|
path: filePath,
|
|
235
412
|
created_at: createdAt,
|
|
236
413
|
sha256,
|
|
237
|
-
}
|
|
238
|
-
await
|
|
414
|
+
};
|
|
415
|
+
await appendJsonl(path.join(runDir, "index.jsonl"), indexEntry, {
|
|
416
|
+
cwd,
|
|
417
|
+
audit: { category: "ledger", verb: "append", owner: "gjc-runtime", skill: "ralplan" },
|
|
418
|
+
});
|
|
239
419
|
|
|
240
420
|
let pendingApprovalPath: string | undefined;
|
|
241
421
|
if (resolved.stage === "final") {
|
|
242
422
|
pendingApprovalPath = path.join(runDir, "pending-approval.md");
|
|
243
|
-
await
|
|
423
|
+
await writeArtifact(pendingApprovalPath, content, {
|
|
424
|
+
cwd,
|
|
425
|
+
audit: { category: "artifact", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
426
|
+
});
|
|
244
427
|
}
|
|
245
428
|
|
|
246
429
|
return {
|
|
@@ -284,8 +467,12 @@ async function syncRalplanHud(options: {
|
|
|
284
467
|
}
|
|
285
468
|
|
|
286
469
|
async function handleArtifactWrite(args: readonly string[], cwd: string): Promise<RalplanCommandResult> {
|
|
470
|
+
const plannerState = parsePlannerStateArgs(args);
|
|
287
471
|
const resolved = await resolveArtifactArgs(args, cwd);
|
|
288
472
|
const persisted = await persistArtifact(resolved, cwd);
|
|
473
|
+
if (plannerState) {
|
|
474
|
+
await applyPlannerStateUpdate(cwd, resolved.sessionId, plannerState);
|
|
475
|
+
}
|
|
289
476
|
await syncRalplanHud({
|
|
290
477
|
cwd,
|
|
291
478
|
sessionId: resolved.sessionId,
|
|
@@ -303,6 +490,7 @@ async function handleArtifactWrite(args: readonly string[], cwd: string): Promis
|
|
|
303
490
|
created_at: persisted.createdAt,
|
|
304
491
|
};
|
|
305
492
|
if (persisted.pendingApprovalPath) payload.pending_approval_path = persisted.pendingApprovalPath;
|
|
493
|
+
if (plannerState) payload.planner_state = plannerStatePayload(plannerState);
|
|
306
494
|
const stdout = resolved.json
|
|
307
495
|
? `${JSON.stringify(payload, null, 2)}\n`
|
|
308
496
|
: `Persisted ralplan ${persisted.stage} stage ${persisted.stageN} at ${persisted.path}.\n`;
|
|
@@ -382,7 +570,7 @@ async function seedRalplanState(
|
|
|
382
570
|
const stateDir = resolved.sessionId
|
|
383
571
|
? path.join(cwd, ".gjc", "state", "sessions", encodeSessionSegment(resolved.sessionId))
|
|
384
572
|
: path.join(cwd, ".gjc", "state");
|
|
385
|
-
|
|
573
|
+
|
|
386
574
|
const statePath = path.join(stateDir, "ralplan-state.json");
|
|
387
575
|
// Reuse an existing run id when present so a re-invocation of `gjc ralplan "task"` doesn't
|
|
388
576
|
// orphan in-progress artifacts under a fresh run id.
|
|
@@ -394,6 +582,7 @@ async function seedRalplanState(
|
|
|
394
582
|
active: true,
|
|
395
583
|
current_phase: "planner",
|
|
396
584
|
skill: "ralplan",
|
|
585
|
+
version: WORKFLOW_STATE_VERSION,
|
|
397
586
|
mode: resolved.deliberate ? "deliberate" : "short",
|
|
398
587
|
interactive: resolved.interactive,
|
|
399
588
|
task: resolved.task,
|
|
@@ -403,7 +592,17 @@ async function seedRalplanState(
|
|
|
403
592
|
if (resolved.architectKind) payload.architect_kind = resolved.architectKind;
|
|
404
593
|
if (resolved.criticKind) payload.critic_kind = resolved.criticKind;
|
|
405
594
|
if (resolved.sessionId) payload.session_id = resolved.sessionId;
|
|
406
|
-
await
|
|
595
|
+
await writeWorkflowEnvelopeAtomic(statePath, payload, {
|
|
596
|
+
cwd,
|
|
597
|
+
receipt: {
|
|
598
|
+
cwd,
|
|
599
|
+
skill: "ralplan",
|
|
600
|
+
owner: "gjc-runtime",
|
|
601
|
+
command: "gjc ralplan seed",
|
|
602
|
+
sessionId: resolved.sessionId,
|
|
603
|
+
},
|
|
604
|
+
audit: { category: "state", verb: "write", owner: "gjc-runtime", skill: "ralplan" },
|
|
605
|
+
});
|
|
407
606
|
return { statePath, runId };
|
|
408
607
|
}
|
|
409
608
|
|
|
@@ -426,26 +625,19 @@ async function handleConsensusHandoff(args: readonly string[], cwd: string): Pro
|
|
|
426
625
|
const summary = {
|
|
427
626
|
skill: "ralplan",
|
|
428
627
|
mode,
|
|
429
|
-
interactive: resolved.interactive,
|
|
430
|
-
architect: resolved.architectKind ?? "default",
|
|
431
|
-
critic: resolved.criticKind ?? "default",
|
|
432
|
-
task: resolved.task,
|
|
433
628
|
state_path: statePath,
|
|
434
629
|
run_id: runId,
|
|
435
|
-
handoff: "
|
|
630
|
+
handoff: "/skill:ralplan",
|
|
436
631
|
};
|
|
437
632
|
const stdout = resolved.json
|
|
438
|
-
?
|
|
633
|
+
? renderCliWriteReceipt({ ok: true, ...summary })
|
|
439
634
|
: [
|
|
440
|
-
`
|
|
441
|
-
`
|
|
442
|
-
resolved.architectKind
|
|
443
|
-
|
|
444
|
-
"Run `/skill:ralplan` inside the GJC agent to execute the consensus loop.",
|
|
635
|
+
`ralplan seed run_id=${runId}`,
|
|
636
|
+
`state_path=${statePath}`,
|
|
637
|
+
`mode=${mode} interactive=${resolved.interactive} architect=${resolved.architectKind ?? "default"} critic=${resolved.criticKind ?? "default"}`,
|
|
638
|
+
"handoff=/skill:ralplan",
|
|
445
639
|
"",
|
|
446
|
-
]
|
|
447
|
-
.filter((line): line is string => Boolean(line))
|
|
448
|
-
.join("\n");
|
|
640
|
+
].join("\n");
|
|
449
641
|
return { status: 0, stdout };
|
|
450
642
|
}
|
|
451
643
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { CanonicalGjcWorkflowSkill } from "../skill-state/active-state";
|
|
2
|
+
import { CANONICAL_GJC_WORKFLOW_SKILLS } from "../skill-state/active-state";
|
|
3
|
+
import { getSkillManifest } from "./workflow-manifest";
|
|
4
|
+
|
|
5
|
+
export type StateGraphSkill = CanonicalGjcWorkflowSkill | "all";
|
|
6
|
+
export type StateGraphFormat = "ascii" | "mermaid" | "dot";
|
|
7
|
+
|
|
8
|
+
function assertGraphFormat(format: string): asserts format is StateGraphFormat {
|
|
9
|
+
if (format !== "ascii" && format !== "mermaid" && format !== "dot") {
|
|
10
|
+
throw new Error(`Invalid graph format: ${format}. Expected one of: ascii, mermaid, dot.`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function skillsFor(skill: StateGraphSkill): CanonicalGjcWorkflowSkill[] {
|
|
15
|
+
return skill === "all" ? [...CANONICAL_GJC_WORKFLOW_SKILLS] : [skill];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function renderAscii(skill: StateGraphSkill): string {
|
|
19
|
+
const chunks = skillsFor(skill).map(item => {
|
|
20
|
+
const manifest = getSkillManifest(item);
|
|
21
|
+
const states = manifest.states
|
|
22
|
+
.map(state => {
|
|
23
|
+
const markers = [state.initial ? "initial" : undefined, state.terminal ? "terminal" : undefined]
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.join(", ");
|
|
26
|
+
return ` - ${state.id}${markers ? ` (${markers})` : ""}`;
|
|
27
|
+
})
|
|
28
|
+
.join("\n");
|
|
29
|
+
const transitions = manifest.transitions
|
|
30
|
+
.map(transition => ` - ${transition.from} -> ${transition.to} [${transition.verb}]`)
|
|
31
|
+
.join("\n");
|
|
32
|
+
return `${manifest.skill} (${manifest.graphLabel})\nstates:\n${states}\ntransitions:\n${transitions}`;
|
|
33
|
+
});
|
|
34
|
+
return `${chunks.join("\n\n")}\n`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderMermaid(skill: StateGraphSkill): string {
|
|
38
|
+
const lines = ["stateDiagram-v2"];
|
|
39
|
+
for (const item of skillsFor(skill)) {
|
|
40
|
+
const manifest = getSkillManifest(item);
|
|
41
|
+
lines.push(` state "${manifest.graphLabel}" as ${item} {`);
|
|
42
|
+
lines.push(` [*] --> ${manifest.initialState}`);
|
|
43
|
+
for (const transition of manifest.transitions) {
|
|
44
|
+
lines.push(` ${transition.from} --> ${transition.to}: ${transition.verb}`);
|
|
45
|
+
}
|
|
46
|
+
for (const terminal of manifest.terminalStates) {
|
|
47
|
+
lines.push(` ${terminal} --> [*]`);
|
|
48
|
+
}
|
|
49
|
+
lines.push(" }");
|
|
50
|
+
}
|
|
51
|
+
return `${lines.join("\n")}\n`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function dotId(skill: CanonicalGjcWorkflowSkill, state: string): string {
|
|
55
|
+
return `"${skill}:${state}"`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function renderDot(skill: StateGraphSkill): string {
|
|
59
|
+
const lines = ["digraph gjc_state {", " rankdir=LR;"];
|
|
60
|
+
for (const item of skillsFor(skill)) {
|
|
61
|
+
const manifest = getSkillManifest(item);
|
|
62
|
+
lines.push(` subgraph "cluster_${item}" {`);
|
|
63
|
+
lines.push(` label="${manifest.graphLabel}";`);
|
|
64
|
+
for (const state of manifest.states) {
|
|
65
|
+
const shape = state.terminal ? "doublecircle" : "circle";
|
|
66
|
+
lines.push(` ${dotId(item, state.id)} [label="${state.id}", shape=${shape}];`);
|
|
67
|
+
}
|
|
68
|
+
lines.push(` "${item}:__start" [label="", shape=point];`);
|
|
69
|
+
lines.push(` "${item}:__start" -> ${dotId(item, manifest.initialState)};`);
|
|
70
|
+
for (const transition of manifest.transitions) {
|
|
71
|
+
lines.push(
|
|
72
|
+
` ${dotId(item, transition.from)} -> ${dotId(item, transition.to)} [label="${transition.verb}"];`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
lines.push(" }");
|
|
76
|
+
}
|
|
77
|
+
lines.push("}");
|
|
78
|
+
return `${lines.join("\n")}\n`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function renderStateGraph(skill: StateGraphSkill, format: string = "ascii"): string {
|
|
82
|
+
assertGraphFormat(format);
|
|
83
|
+
if (format === "mermaid") return renderMermaid(skill);
|
|
84
|
+
if (format === "dot") return renderDot(skill);
|
|
85
|
+
return renderAscii(skill);
|
|
86
|
+
}
|