@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
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session-scoped storage for the harness control plane.
|
|
3
|
+
*
|
|
4
|
+
* Layout (under the harness state root, default `<cwd>/.gjc/state/harness`):
|
|
5
|
+
* sessions/<encoded-id>/state.json lifecycle + handle (atomic)
|
|
6
|
+
* sessions/<encoded-id>/lease.json owner lease (M3)
|
|
7
|
+
* sessions/<encoded-id>/events.jsonl owner-only severity envelopes
|
|
8
|
+
* sessions/<encoded-id>/receipts.jsonl append-only receipt index
|
|
9
|
+
* sessions/<encoded-id>/receipts/<family>/<receiptId>.json immutable receipts
|
|
10
|
+
* sessions/<encoded-id>/artifacts/... diff/validation artifacts
|
|
11
|
+
* sessions/<encoded-id>/gjc-session/ underlying gajae-code --session-dir
|
|
12
|
+
*
|
|
13
|
+
* Receipt files are immutable: re-writing an existing receipt id fails closed.
|
|
14
|
+
* JSON writes are atomic (temp + rename).
|
|
15
|
+
*/
|
|
16
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
17
|
+
import * as fsSync from "node:fs";
|
|
18
|
+
import * as fs from "node:fs/promises";
|
|
19
|
+
import * as os from "node:os";
|
|
20
|
+
import * as path from "node:path";
|
|
21
|
+
import type { EventEnvelope, ReceiptFamily, SessionState } from "./types";
|
|
22
|
+
|
|
23
|
+
const SESSION_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
24
|
+
export const MAX_UNIX_SOCKET_PATH_BYTES = 100;
|
|
25
|
+
|
|
26
|
+
interface SocketPathMetadata {
|
|
27
|
+
root: string;
|
|
28
|
+
sessionId: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function socketBase(env: NodeJS.ProcessEnv, allowOverride: boolean): { base: string; fromOverride: boolean } {
|
|
32
|
+
const override = env.GJC_HARNESS_SOCKET_DIR?.trim();
|
|
33
|
+
if (allowOverride && override) return { base: path.resolve(override), fromOverride: true };
|
|
34
|
+
return { base: path.join(os.tmpdir(), `gjch${process.getuid?.() ?? "u"}`), fromOverride: false };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function socketPathForBase(root: string, sessionId: string, base: string): string {
|
|
38
|
+
const digest = createHash("sha256").update(`${root}\0${sessionId}`).digest("hex");
|
|
39
|
+
fsSync.mkdirSync(base, { recursive: true });
|
|
40
|
+
for (const len of [16, 24, 32, 48, 64]) {
|
|
41
|
+
const stem = `c-${digest.slice(0, len)}`;
|
|
42
|
+
const metadataPath = path.join(base, `${stem}.json`);
|
|
43
|
+
const metadata: SocketPathMetadata = { root, sessionId };
|
|
44
|
+
try {
|
|
45
|
+
const existing = JSON.parse(fsSync.readFileSync(metadataPath, "utf8")) as SocketPathMetadata;
|
|
46
|
+
if (existing.root === root && existing.sessionId === sessionId) return path.join(base, `${stem}.sock`);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
49
|
+
fsSync.writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`, "utf8");
|
|
50
|
+
return path.join(base, `${stem}.sock`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
throw new StorageError(`socket_path_collision:${sessionId}`, "socket_path_collision");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function controlSocketPath(root: string, sessionId: string, env: NodeJS.ProcessEnv = process.env): string {
|
|
57
|
+
assertSafeSessionId(sessionId);
|
|
58
|
+
let { base, fromOverride } = socketBase(env, true);
|
|
59
|
+
let finalPath = socketPathForBase(root, sessionId, base);
|
|
60
|
+
if (Buffer.byteLength(finalPath) > MAX_UNIX_SOCKET_PATH_BYTES && fromOverride) {
|
|
61
|
+
base = socketBase(env, false).base;
|
|
62
|
+
finalPath = socketPathForBase(root, sessionId, base);
|
|
63
|
+
}
|
|
64
|
+
const finalBytes = Buffer.byteLength(finalPath);
|
|
65
|
+
if (finalBytes > MAX_UNIX_SOCKET_PATH_BYTES) {
|
|
66
|
+
throw new StorageError(`socket_path_too_long:${finalBytes}`, "socket_path_too_long");
|
|
67
|
+
}
|
|
68
|
+
return finalPath;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class StorageError extends Error {
|
|
72
|
+
constructor(
|
|
73
|
+
message: string,
|
|
74
|
+
readonly code: string,
|
|
75
|
+
) {
|
|
76
|
+
super(message);
|
|
77
|
+
this.name = "StorageError";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Resolve the harness state root from explicit value, env, or cwd default. */
|
|
82
|
+
export function resolveHarnessRoot(opts?: { root?: string; cwd?: string; env?: NodeJS.ProcessEnv }): string {
|
|
83
|
+
const env = opts?.env ?? process.env;
|
|
84
|
+
if (opts?.root) return path.resolve(opts.root);
|
|
85
|
+
const fromEnv = env.GJC_HARNESS_STATE_ROOT;
|
|
86
|
+
if (fromEnv?.trim()) return path.resolve(fromEnv.trim());
|
|
87
|
+
return path.join(opts?.cwd ?? process.cwd(), ".gjc", "state", "harness");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function assertSafeSessionId(id: string): void {
|
|
91
|
+
if (!SESSION_ID_RE.test(id)) {
|
|
92
|
+
throw new StorageError(`unsafe_session_id:${id}`, "unsafe_session_id");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function generateSessionId(prefix = "h"): string {
|
|
97
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "").replace("T", "-").slice(0, 15);
|
|
98
|
+
const rand = randomBytes(4).toString("hex");
|
|
99
|
+
return `${prefix}-${ts}-${rand}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface SessionPaths {
|
|
103
|
+
dir: string;
|
|
104
|
+
state: string;
|
|
105
|
+
lease: string;
|
|
106
|
+
events: string;
|
|
107
|
+
receiptsIndex: string;
|
|
108
|
+
receiptsDir: string;
|
|
109
|
+
artifactsDir: string;
|
|
110
|
+
controlSock: string;
|
|
111
|
+
controlFifo: string;
|
|
112
|
+
gjcSessionDir: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function sessionPaths(root: string, sessionId: string): SessionPaths {
|
|
116
|
+
assertSafeSessionId(sessionId);
|
|
117
|
+
const dir = path.join(root, "sessions", sessionId);
|
|
118
|
+
return {
|
|
119
|
+
dir,
|
|
120
|
+
state: path.join(dir, "state.json"),
|
|
121
|
+
lease: path.join(dir, "lease.json"),
|
|
122
|
+
events: path.join(dir, "events.jsonl"),
|
|
123
|
+
receiptsIndex: path.join(dir, "receipts.jsonl"),
|
|
124
|
+
receiptsDir: path.join(dir, "receipts"),
|
|
125
|
+
artifactsDir: path.join(dir, "artifacts"),
|
|
126
|
+
controlSock: path.join(dir, "control.sock"),
|
|
127
|
+
controlFifo: path.join(dir, "control.fifo"),
|
|
128
|
+
gjcSessionDir: path.join(dir, "gjc-session"),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function writeJsonAtomic(file: string, value: unknown): Promise<void> {
|
|
133
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
134
|
+
const tmp = `${file}.tmp-${randomBytes(4).toString("hex")}`;
|
|
135
|
+
await fs.writeFile(tmp, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
136
|
+
await fs.rename(tmp, file);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function readJson<T>(file: string): Promise<T | null> {
|
|
140
|
+
try {
|
|
141
|
+
const raw = await fs.readFile(file, "utf8");
|
|
142
|
+
return JSON.parse(raw) as T;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export async function readSessionState(root: string, sessionId: string): Promise<SessionState | null> {
|
|
150
|
+
return readJson<SessionState>(sessionPaths(root, sessionId).state);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function writeSessionState(root: string, state: SessionState): Promise<void> {
|
|
154
|
+
const paths = sessionPaths(root, state.sessionId);
|
|
155
|
+
await fs.mkdir(paths.dir, { recursive: true });
|
|
156
|
+
await writeJsonAtomic(paths.state, state);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export async function sessionExists(root: string, sessionId: string): Promise<boolean> {
|
|
160
|
+
return (await readSessionState(root, sessionId)) !== null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Append a single severity envelope to events.jsonl. Single-writer discipline is the owner's job (M3). */
|
|
164
|
+
export async function appendEvent(root: string, sessionId: string, envelope: EventEnvelope): Promise<void> {
|
|
165
|
+
const paths = sessionPaths(root, sessionId);
|
|
166
|
+
await fs.mkdir(paths.dir, { recursive: true });
|
|
167
|
+
await fs.appendFile(paths.events, `${JSON.stringify(envelope)}\n`, "utf8");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Read events from cursor (exclusive). Tail-only: never mutates the log. */
|
|
171
|
+
export async function readEvents(root: string, sessionId: string, fromCursor = 0): Promise<EventEnvelope[]> {
|
|
172
|
+
const paths = sessionPaths(root, sessionId);
|
|
173
|
+
let raw: string;
|
|
174
|
+
try {
|
|
175
|
+
raw = await fs.readFile(paths.events, "utf8");
|
|
176
|
+
} catch (error) {
|
|
177
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
178
|
+
throw error;
|
|
179
|
+
}
|
|
180
|
+
const out: EventEnvelope[] = [];
|
|
181
|
+
for (const line of raw.split("\n")) {
|
|
182
|
+
const trimmed = line.trim();
|
|
183
|
+
if (!trimmed) continue;
|
|
184
|
+
const env = JSON.parse(trimmed) as EventEnvelope;
|
|
185
|
+
if (env.cursor > fromCursor) out.push(env);
|
|
186
|
+
}
|
|
187
|
+
return out;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface ReceiptIndexEntry {
|
|
191
|
+
receiptId: string;
|
|
192
|
+
family: ReceiptFamily;
|
|
193
|
+
valid: boolean;
|
|
194
|
+
createdAt: string;
|
|
195
|
+
path: string;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Persist a receipt immutably. Fails closed if the receipt id already exists,
|
|
200
|
+
* then appends an index entry to receipts.jsonl.
|
|
201
|
+
*/
|
|
202
|
+
export async function writeReceiptImmutable(
|
|
203
|
+
root: string,
|
|
204
|
+
sessionId: string,
|
|
205
|
+
family: ReceiptFamily,
|
|
206
|
+
receiptId: string,
|
|
207
|
+
value: { receiptId: string; family: ReceiptFamily; valid: boolean; createdAt: string },
|
|
208
|
+
): Promise<ReceiptIndexEntry> {
|
|
209
|
+
assertSafeSessionId(sessionId);
|
|
210
|
+
if (!SESSION_ID_RE.test(receiptId)) {
|
|
211
|
+
throw new StorageError(`unsafe_receipt_id:${receiptId}`, "unsafe_receipt_id");
|
|
212
|
+
}
|
|
213
|
+
const paths = sessionPaths(root, sessionId);
|
|
214
|
+
const familyDir = path.join(paths.receiptsDir, family);
|
|
215
|
+
const file = path.join(familyDir, `${receiptId}.json`);
|
|
216
|
+
await fs.mkdir(familyDir, { recursive: true });
|
|
217
|
+
try {
|
|
218
|
+
await fs.writeFile(file, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", flag: "wx" });
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if ((error as NodeJS.ErrnoException).code === "EEXIST") {
|
|
221
|
+
throw new StorageError(`receipt_immutable_conflict:${family}/${receiptId}`, "receipt_immutable_conflict");
|
|
222
|
+
}
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
const entry: ReceiptIndexEntry = {
|
|
226
|
+
receiptId,
|
|
227
|
+
family,
|
|
228
|
+
valid: value.valid,
|
|
229
|
+
createdAt: value.createdAt,
|
|
230
|
+
path: file,
|
|
231
|
+
};
|
|
232
|
+
await fs.appendFile(paths.receiptsIndex, `${JSON.stringify(entry)}\n`, "utf8");
|
|
233
|
+
return entry;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export async function readReceiptIndex(
|
|
237
|
+
root: string,
|
|
238
|
+
sessionId: string,
|
|
239
|
+
family?: ReceiptFamily,
|
|
240
|
+
): Promise<ReceiptIndexEntry[]> {
|
|
241
|
+
const paths = sessionPaths(root, sessionId);
|
|
242
|
+
let raw: string;
|
|
243
|
+
try {
|
|
244
|
+
raw = await fs.readFile(paths.receiptsIndex, "utf8");
|
|
245
|
+
} catch (error) {
|
|
246
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
|
|
247
|
+
throw error;
|
|
248
|
+
}
|
|
249
|
+
const out: ReceiptIndexEntry[] = [];
|
|
250
|
+
for (const line of raw.split("\n")) {
|
|
251
|
+
const trimmed = line.trim();
|
|
252
|
+
if (!trimmed) continue;
|
|
253
|
+
const entry = JSON.parse(trimmed) as ReceiptIndexEntry;
|
|
254
|
+
if (!family || entry.family === family) out.push(entry);
|
|
255
|
+
}
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for the gajae-code-native coding-harness operations control plane (v1).
|
|
3
|
+
*
|
|
4
|
+
* See the approved consensus plan at
|
|
5
|
+
* `.gjc/plans/ralplan/2026-06-02-0853-3e33/stage-02-revision.md` and the spec at
|
|
6
|
+
* `.gjc/specs/deep-interview-harness-control-plane.md`.
|
|
7
|
+
*
|
|
8
|
+
* v1 implements the gajae-code adapter only. omx/codex/remote/auth are deferred seams.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Harnesses the control plane can operate. v1 implements `gajae-code` only. */
|
|
12
|
+
export type Harness = "gajae-code" | "codex" | "omx";
|
|
13
|
+
|
|
14
|
+
/** Lifecycle states of an operated session. */
|
|
15
|
+
export type HarnessLifecycle =
|
|
16
|
+
| "new"
|
|
17
|
+
| "started"
|
|
18
|
+
| "submitted"
|
|
19
|
+
| "observing"
|
|
20
|
+
| "recovering"
|
|
21
|
+
| "validating"
|
|
22
|
+
| "finalizing"
|
|
23
|
+
| "completed"
|
|
24
|
+
| "blocked"
|
|
25
|
+
| "retired";
|
|
26
|
+
|
|
27
|
+
/** Event severities emitted by the owner. */
|
|
28
|
+
export type Severity = "info" | "warn" | "critical";
|
|
29
|
+
|
|
30
|
+
/** Bounded git delta classification surfaced by `observe`. */
|
|
31
|
+
export type GitDelta = "clean" | "dirty" | "zero-delta" | "unknown";
|
|
32
|
+
|
|
33
|
+
/** Risk classification surfaced by `observe`. */
|
|
34
|
+
export type RiskKind = "normal" | "prompt-not-accepted" | "deleted-worktree" | "vanished-dirty";
|
|
35
|
+
|
|
36
|
+
/** Deterministic recovery classifications. */
|
|
37
|
+
export type RecoveryClassification =
|
|
38
|
+
| "continue"
|
|
39
|
+
| "send-enter"
|
|
40
|
+
| "reinject-prompt"
|
|
41
|
+
| "restart-clean"
|
|
42
|
+
| "restart-preserve-delta"
|
|
43
|
+
| "fallback-codex-exec"
|
|
44
|
+
| "human-check";
|
|
45
|
+
|
|
46
|
+
/** Receipt families persisted under the session storage dir. */
|
|
47
|
+
export type ReceiptFamily = "vanish" | "prompt-acceptance" | "validation" | "completion";
|
|
48
|
+
|
|
49
|
+
/** The CLI verbs / primitives exposed by `gjc harness <verb>`. */
|
|
50
|
+
export type HarnessVerb =
|
|
51
|
+
| "start"
|
|
52
|
+
| "submit"
|
|
53
|
+
| "observe"
|
|
54
|
+
| "classify"
|
|
55
|
+
| "recover"
|
|
56
|
+
| "validate"
|
|
57
|
+
| "finalize"
|
|
58
|
+
| "retire"
|
|
59
|
+
| "events"
|
|
60
|
+
| "monitor"
|
|
61
|
+
| "operate";
|
|
62
|
+
|
|
63
|
+
/** Submission transports. */
|
|
64
|
+
export type SubmitMode = "paste-buffer" | "stdin" | "file";
|
|
65
|
+
|
|
66
|
+
/** A single entry in the forcing-function `nextAllowedActions` list. */
|
|
67
|
+
export interface NextAllowedAction {
|
|
68
|
+
verb: HarnessVerb;
|
|
69
|
+
available: boolean;
|
|
70
|
+
/** Present when `available` is false; explains why the verb is currently disallowed. */
|
|
71
|
+
reason?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Compact, model-facing view of session state included in every response. */
|
|
75
|
+
export interface SessionStateView {
|
|
76
|
+
sessionId: string;
|
|
77
|
+
lifecycle: HarnessLifecycle;
|
|
78
|
+
harness: Harness;
|
|
79
|
+
ownerLive: boolean;
|
|
80
|
+
blockers: string[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The universal contract: EVERY primitive response carries `{state, evidence, nextAllowedActions}`.
|
|
85
|
+
* `ok` is a transport-level convenience; semantic blocking is expressed via state + nextAllowedActions.
|
|
86
|
+
*/
|
|
87
|
+
export interface PrimitiveResponse<E = Record<string, unknown>> {
|
|
88
|
+
ok: boolean;
|
|
89
|
+
state: SessionStateView;
|
|
90
|
+
evidence: E;
|
|
91
|
+
nextAllowedActions: NextAllowedAction[];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Re-grabbable session handle returned by `start` / `operate`. */
|
|
95
|
+
export interface SessionHandle {
|
|
96
|
+
sessionId: string;
|
|
97
|
+
harness: Harness;
|
|
98
|
+
repo: string | null;
|
|
99
|
+
workspace: string;
|
|
100
|
+
branch: string | null;
|
|
101
|
+
base: string | null;
|
|
102
|
+
issueOrPr: string | null;
|
|
103
|
+
processHandle: { kind: "runtime-owner"; ownerId: string | null; pid: number | null };
|
|
104
|
+
rpcHandle: { kind: "rpc-subprocess"; pid: number | null; sessionDir: string };
|
|
105
|
+
ownerHandle: { leasePath: string; endpoint: string | null; heartbeatAt: string | null };
|
|
106
|
+
routerHandle: { kind: "default-in-owner"; policy: string; eventsPath: string };
|
|
107
|
+
viewportHandle: { kind: "event-monitor"; tmuxSessionName: string | null; viewOnly: true };
|
|
108
|
+
startedAt: string;
|
|
109
|
+
updatedAt: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Persisted per-session record (state.json). */
|
|
113
|
+
export interface SessionState {
|
|
114
|
+
schemaVersion: number;
|
|
115
|
+
sessionId: string;
|
|
116
|
+
lifecycle: HarnessLifecycle;
|
|
117
|
+
harness: Harness;
|
|
118
|
+
handle: SessionHandle;
|
|
119
|
+
/** Per-classification retry counters consumed by the recovery policy. */
|
|
120
|
+
retries: Record<string, number>;
|
|
121
|
+
blockers: string[];
|
|
122
|
+
createdAt: string;
|
|
123
|
+
updatedAt: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Bounded observed-signal vocabulary surfaced by `observe` (the owner only ever emits these). */
|
|
127
|
+
export type ObservedSignal =
|
|
128
|
+
| "SessionStart"
|
|
129
|
+
| "prompt-accepted"
|
|
130
|
+
| "tool-call"
|
|
131
|
+
| "test-running"
|
|
132
|
+
| "commit-created"
|
|
133
|
+
| "completed"
|
|
134
|
+
| "error"
|
|
135
|
+
| "streaming"
|
|
136
|
+
| "idle";
|
|
137
|
+
|
|
138
|
+
export const OBSERVED_SIGNALS: readonly ObservedSignal[] = [
|
|
139
|
+
"SessionStart",
|
|
140
|
+
"prompt-accepted",
|
|
141
|
+
"tool-call",
|
|
142
|
+
"test-running",
|
|
143
|
+
"commit-created",
|
|
144
|
+
"completed",
|
|
145
|
+
"error",
|
|
146
|
+
"streaming",
|
|
147
|
+
"idle",
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
/** Bounded observation surfaced by `observe` — never a raw pane/transcript dump. */
|
|
151
|
+
export interface Observation {
|
|
152
|
+
lifecycle: HarnessLifecycle;
|
|
153
|
+
ownerLive: boolean;
|
|
154
|
+
cwd: string;
|
|
155
|
+
branch: string | null;
|
|
156
|
+
gitDelta: GitDelta;
|
|
157
|
+
lastActivityAt: string | null;
|
|
158
|
+
observedSignals: string[];
|
|
159
|
+
risk: RiskKind;
|
|
160
|
+
/** RPC subprocess liveness, distinct from owner-process/lease liveness. Optional for back-compat. */
|
|
161
|
+
rpcLive?: boolean;
|
|
162
|
+
/** ISO timestamp of the most recent RPC frame the owner observed, if any. */
|
|
163
|
+
rpcLastFrameAt?: string | null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Input to the deterministic recovery classifier. */
|
|
167
|
+
export interface ClassifyInput {
|
|
168
|
+
observation: Observation;
|
|
169
|
+
/** Remaining retry budget per classification family. */
|
|
170
|
+
retryBudget: RetryBudget;
|
|
171
|
+
/** Whether an accepted prompt was in flight when the owner/RPC was last seen. */
|
|
172
|
+
acceptedPromptActive?: boolean;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Default and supplied retry budgets. */
|
|
176
|
+
export interface RetryBudget {
|
|
177
|
+
reinjectPrompt: number;
|
|
178
|
+
zeroDeltaVanish: number;
|
|
179
|
+
dirtyVanishPreserve: number;
|
|
180
|
+
validationRepair: number;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Result of the deterministic recovery classifier. */
|
|
184
|
+
export interface RecoveryDecision {
|
|
185
|
+
classification: RecoveryClassification;
|
|
186
|
+
reason: string;
|
|
187
|
+
severity: Severity;
|
|
188
|
+
/** Whether executing the recommended action requires a live owner. */
|
|
189
|
+
ownerRequired: boolean;
|
|
190
|
+
/** Receipt family that MUST be valid before the action may proceed (e.g. `vanish`). */
|
|
191
|
+
requiredReceiptFamily: ReceiptFamily | null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Severity-tagged event envelope written exclusively by the owner. */
|
|
195
|
+
export interface EventEnvelope<E = Record<string, unknown>> {
|
|
196
|
+
eventId: string;
|
|
197
|
+
cursor: number;
|
|
198
|
+
createdAt: string;
|
|
199
|
+
severity: Severity;
|
|
200
|
+
kind: string;
|
|
201
|
+
state: SessionStateView;
|
|
202
|
+
evidence: E;
|
|
203
|
+
nextAllowedActions: NextAllowedAction[];
|
|
204
|
+
writer: { ownerId: string; leaseEpoch: number };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export const SESSION_SCHEMA_VERSION = 1 as const;
|
|
208
|
+
|
|
209
|
+
export const DEFAULT_RETRY_BUDGET: RetryBudget = {
|
|
210
|
+
reinjectPrompt: 2,
|
|
211
|
+
zeroDeltaVanish: 1,
|
|
212
|
+
dirtyVanishPreserve: 1,
|
|
213
|
+
validationRepair: 2,
|
|
214
|
+
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { CANONICAL_GJC_WORKFLOW_SKILLS, type CanonicalGjcWorkflowSkill } from "../skill-state/active-state";
|
|
2
|
+
|
|
1
3
|
export interface SkillKeywordDefinition {
|
|
2
4
|
keyword: string;
|
|
3
5
|
skill: GjcWorkflowSkill;
|
|
@@ -5,9 +7,9 @@ export interface SkillKeywordDefinition {
|
|
|
5
7
|
guidance: string;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
|
-
export const GJC_WORKFLOW_SKILLS =
|
|
10
|
+
export const GJC_WORKFLOW_SKILLS = CANONICAL_GJC_WORKFLOW_SKILLS;
|
|
9
11
|
|
|
10
|
-
export type GjcWorkflowSkill =
|
|
12
|
+
export type GjcWorkflowSkill = CanonicalGjcWorkflowSkill;
|
|
11
13
|
|
|
12
14
|
export const GJC_SKILL_KEYWORD_DEFINITIONS: readonly SkillKeywordDefinition[] = [
|
|
13
15
|
{
|