@gajae-code/coding-agent 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/types/async/job-manager.d.ts +7 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/commands/deep-interview.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +4 -4
- package/dist/types/debug/crash-diagnostics.d.ts +45 -0
- package/dist/types/debug/runtime-gauges.d.ts +6 -0
- package/dist/types/deep-interview/render-middleware.d.ts +1 -0
- package/dist/types/eval/py/executor.d.ts +2 -0
- package/dist/types/eval/py/kernel.d.ts +2 -0
- package/dist/types/exec/bash-executor.d.ts +10 -0
- package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
- package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
- package/dist/types/gjc-runtime/state-migrations.d.ts +9 -0
- package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +10 -0
- package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +3 -2
- package/dist/types/hooks/skill-state.d.ts +21 -0
- package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
- package/dist/types/internal-urls/types.d.ts +4 -0
- package/dist/types/lsp/index.d.ts +10 -10
- package/dist/types/modes/bridge/auth.d.ts +12 -0
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
- package/dist/types/modes/bridge/event-stream.d.ts +8 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
- package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
- package/dist/types/modes/components/status-line/types.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +8 -0
- package/dist/types/modes/index.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +1 -0
- package/dist/types/modes/jobs-observer.d.ts +57 -0
- package/dist/types/modes/rpc/host-tools.d.ts +1 -16
- package/dist/types/modes/rpc/host-uris.d.ts +1 -38
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
- package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +11 -1
- package/dist/types/skill-state/workflow-state-contract.d.ts +1 -2
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/task/id.d.ts +7 -0
- package/dist/types/task/index.d.ts +5 -0
- package/dist/types/task/receipt.d.ts +85 -0
- package/dist/types/task/spawn-gate.d.ts +38 -0
- package/dist/types/task/types.d.ts +143 -11
- package/dist/types/tools/cron.d.ts +6 -0
- package/dist/types/tools/index.d.ts +2 -0
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/subagent.d.ts +15 -0
- package/package.json +7 -7
- package/scripts/build-binary.ts +7 -0
- package/src/async/job-manager.ts +36 -0
- package/src/cli/args.ts +9 -2
- package/src/commands/deep-interview.ts +1 -0
- package/src/commands/harness.ts +289 -19
- package/src/commands/launch.ts +2 -2
- package/src/commands/state.ts +2 -1
- package/src/commands/team.ts +22 -4
- package/src/config/keybindings.ts +6 -0
- package/src/config/settings-schema.ts +6 -3
- package/src/dap/client.ts +17 -3
- package/src/debug/crash-diagnostics.ts +223 -0
- package/src/debug/runtime-gauges.ts +20 -0
- package/src/deep-interview/render-middleware.ts +6 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +28 -2
- package/src/eval/py/executor.ts +21 -1
- package/src/eval/py/kernel.ts +15 -0
- package/src/exec/bash-executor.ts +41 -0
- package/src/gjc-runtime/cli-write-receipt.ts +31 -0
- package/src/gjc-runtime/deep-interview-runtime.ts +69 -32
- package/src/gjc-runtime/ralplan-runtime.ts +213 -36
- package/src/gjc-runtime/state-migrations.ts +54 -7
- package/src/gjc-runtime/state-runtime.ts +461 -64
- package/src/gjc-runtime/state-schema.ts +192 -0
- package/src/gjc-runtime/state-writer.ts +32 -1
- package/src/gjc-runtime/team-runtime.ts +177 -105
- package/src/gjc-runtime/ultragoal-runtime.ts +114 -26
- package/src/gjc-runtime/workflow-command-ref.ts +239 -0
- package/src/gjc-runtime/workflow-manifest.generated.json +108 -4
- package/src/gjc-runtime/workflow-manifest.ts +3 -1
- package/src/harness-control-plane/control-endpoint.ts +19 -8
- package/src/harness-control-plane/owner.ts +57 -10
- package/src/harness-control-plane/state-machine.ts +2 -1
- package/src/hooks/skill-state.ts +176 -26
- package/src/internal-urls/agent-protocol.ts +68 -21
- package/src/internal-urls/artifact-protocol.ts +12 -17
- package/src/internal-urls/docs-index.generated.ts +3 -2
- package/src/internal-urls/registry-helpers.ts +19 -16
- package/src/internal-urls/types.ts +4 -0
- package/src/lsp/client.ts +18 -2
- package/src/main.ts +21 -5
- package/src/modes/bridge/auth.ts +41 -0
- package/src/modes/bridge/bridge-client-bridge.ts +47 -0
- package/src/modes/bridge/bridge-mode.ts +520 -0
- package/src/modes/bridge/bridge-ui-context.ts +200 -0
- package/src/modes/bridge/event-stream.ts +70 -0
- package/src/modes/components/custom-editor.ts +101 -0
- package/src/modes/components/hook-selector.ts +61 -18
- package/src/modes/components/jobs-overlay-model.ts +109 -0
- package/src/modes/components/jobs-overlay.ts +172 -0
- package/src/modes/components/status-line/presets.ts +7 -5
- package/src/modes/components/status-line/segments.ts +25 -0
- package/src/modes/components/status-line/types.ts +2 -0
- package/src/modes/components/status-line.ts +9 -1
- package/src/modes/controllers/extension-ui-controller.ts +39 -3
- package/src/modes/controllers/input-controller.ts +97 -9
- package/src/modes/controllers/selector-controller.ts +29 -0
- package/src/modes/index.ts +1 -0
- package/src/modes/interactive-mode.ts +27 -0
- package/src/modes/jobs-observer.ts +204 -0
- package/src/modes/rpc/host-tools.ts +1 -186
- package/src/modes/rpc/host-uris.ts +1 -235
- package/src/modes/rpc/rpc-client.ts +25 -10
- package/src/modes/rpc/rpc-mode.ts +12 -381
- package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
- package/src/modes/shared/agent-wire/command-validation.ts +131 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
- package/src/modes/shared/agent-wire/handshake.ts +117 -0
- package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
- package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
- package/src/modes/shared/agent-wire/protocol.ts +96 -0
- package/src/modes/shared/agent-wire/responses.ts +17 -0
- package/src/modes/shared/agent-wire/scopes.ts +89 -0
- package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
- package/src/modes/shared/agent-wire/ui-result.ts +48 -0
- package/src/modes/types.ts +1 -0
- package/src/prompts/tools/subagent.md +12 -7
- package/src/prompts/tools/task-summary.md +3 -9
- package/src/prompts/tools/task.md +5 -1
- package/src/sdk.ts +4 -0
- package/src/session/agent-session.ts +214 -38
- package/src/skill-state/deep-interview-mutation-guard.ts +23 -4
- package/src/skill-state/workflow-state-contract.ts +7 -4
- package/src/skill-state/workflow-state-version.ts +3 -0
- package/src/slash-commands/builtin-registry.ts +8 -0
- package/src/task/executor.ts +29 -5
- package/src/task/id.ts +33 -0
- package/src/task/index.ts +257 -67
- package/src/task/output-manager.ts +5 -4
- package/src/task/receipt.ts +297 -0
- package/src/task/render.ts +48 -131
- package/src/task/spawn-gate.ts +132 -0
- package/src/task/types.ts +48 -7
- package/src/tools/ask.ts +73 -33
- package/src/tools/ast-edit.ts +1 -0
- package/src/tools/ast-grep.ts +1 -0
- package/src/tools/bash.ts +1 -1
- package/src/tools/cron.ts +48 -0
- package/src/tools/find.ts +4 -1
- package/src/tools/index.ts +2 -0
- package/src/tools/path-utils.ts +3 -2
- package/src/tools/read.ts +1 -0
- package/src/tools/search.ts +1 -0
- package/src/tools/skill.ts +6 -1
- package/src/tools/subagent.ts +237 -84
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical zod schemas for GJC workflow state (Workstream A, v4).
|
|
3
|
+
*
|
|
4
|
+
* Schemas are **lenient/additive** (`.passthrough()`): unknown keys are
|
|
5
|
+
* preserved, non-anchored fields are optional. This upholds the binding v2
|
|
6
|
+
* read contract — reads never reject evolving/old state. The strict
|
|
7
|
+
* `RequiredOnWriteEnvelopeSchema` is the WRITE-side gate (fail-closed), anchored
|
|
8
|
+
* to exactly what the sanctioned writer emits.
|
|
9
|
+
*
|
|
10
|
+
* These schemas describe the persisted `WorkflowStateReceipt`/envelope; they are
|
|
11
|
+
* a distinct concept from the `CliWriteReceipt` stdout presentation type.
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from "node:fs/promises";
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { WORKFLOW_STATE_VERSION } from "../skill-state/workflow-state-version";
|
|
16
|
+
|
|
17
|
+
const CANONICAL_GJC_WORKFLOW_SKILLS = ["deep-interview", "ralplan", "ultragoal", "team"] as const;
|
|
18
|
+
const skillEnum = z.enum([...CANONICAL_GJC_WORKFLOW_SKILLS]);
|
|
19
|
+
const ownerEnum = z.enum(["gjc-state-cli", "gjc-runtime", "gjc-hook"]);
|
|
20
|
+
const receiptStatusEnum = z.enum(["fresh", "stale"]);
|
|
21
|
+
|
|
22
|
+
export const WorkflowStateContentChecksumSchema = z
|
|
23
|
+
.object({
|
|
24
|
+
algorithm: z.literal("sha256"),
|
|
25
|
+
value: z.string(),
|
|
26
|
+
covered_path: z.string(),
|
|
27
|
+
computed_at: z.string(),
|
|
28
|
+
})
|
|
29
|
+
.passthrough();
|
|
30
|
+
|
|
31
|
+
/** Lenient receipt schema for reads (mirrors WorkflowStateReceipt). */
|
|
32
|
+
export const WorkflowStateReceiptSchema = z
|
|
33
|
+
.object({
|
|
34
|
+
version: z.number(),
|
|
35
|
+
skill: skillEnum,
|
|
36
|
+
owner: ownerEnum,
|
|
37
|
+
command: z.string(),
|
|
38
|
+
state_path: z.string(),
|
|
39
|
+
storage_path: z.string(),
|
|
40
|
+
mutated_at: z.string(),
|
|
41
|
+
fresh_until: z.string(),
|
|
42
|
+
status: receiptStatusEnum,
|
|
43
|
+
mutation_id: z.string(),
|
|
44
|
+
verb: z.string().optional(),
|
|
45
|
+
from_phase: z.string().optional(),
|
|
46
|
+
to_phase: z.string().optional(),
|
|
47
|
+
forced: z.boolean().optional(),
|
|
48
|
+
paths: z.array(z.string()).optional(),
|
|
49
|
+
content_sha256: WorkflowStateContentChecksumSchema.optional(),
|
|
50
|
+
})
|
|
51
|
+
.passthrough();
|
|
52
|
+
|
|
53
|
+
/** Lenient envelope schema for reads. Every non-structural field optional. */
|
|
54
|
+
export const WorkflowStateEnvelopeSchema = z
|
|
55
|
+
.object({
|
|
56
|
+
skill: z.string().optional(),
|
|
57
|
+
active: z.boolean().optional(),
|
|
58
|
+
current_phase: z.string().optional(),
|
|
59
|
+
version: z.number().optional(),
|
|
60
|
+
updated_at: z.string().optional(),
|
|
61
|
+
session_id: z.string().optional(),
|
|
62
|
+
receipt: WorkflowStateReceiptSchema.optional(),
|
|
63
|
+
})
|
|
64
|
+
.passthrough();
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Strict receipt required on WRITE (post checksum-stamping). Anchored to the
|
|
68
|
+
* fields the sanctioned writer emits — `content_sha256` is REQUIRED here.
|
|
69
|
+
*/
|
|
70
|
+
export const RequiredWorkflowStateReceiptSchema = z
|
|
71
|
+
.object({
|
|
72
|
+
version: z.number(),
|
|
73
|
+
skill: skillEnum,
|
|
74
|
+
owner: ownerEnum,
|
|
75
|
+
command: z.string(),
|
|
76
|
+
state_path: z.string(),
|
|
77
|
+
storage_path: z.string(),
|
|
78
|
+
mutated_at: z.string(),
|
|
79
|
+
fresh_until: z.string(),
|
|
80
|
+
status: receiptStatusEnum,
|
|
81
|
+
mutation_id: z.string(),
|
|
82
|
+
content_sha256: WorkflowStateContentChecksumSchema,
|
|
83
|
+
})
|
|
84
|
+
.passthrough();
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Write-side fail-closed gate: the serialized on-disk envelope must satisfy
|
|
88
|
+
* this after checksum stamping. Anchored to current sanctioned-writer output.
|
|
89
|
+
*/
|
|
90
|
+
export const RequiredOnWriteEnvelopeSchema = z
|
|
91
|
+
.object({
|
|
92
|
+
skill: skillEnum,
|
|
93
|
+
version: z.literal(WORKFLOW_STATE_VERSION),
|
|
94
|
+
updated_at: z.string(),
|
|
95
|
+
current_phase: z.string(),
|
|
96
|
+
active: z.boolean(),
|
|
97
|
+
receipt: RequiredWorkflowStateReceiptSchema,
|
|
98
|
+
})
|
|
99
|
+
.passthrough();
|
|
100
|
+
|
|
101
|
+
/** Per-skill mode state consumed by hooks / the mutation guard. */
|
|
102
|
+
export const ModeStateSchema = z
|
|
103
|
+
.object({
|
|
104
|
+
active: z.boolean().optional(),
|
|
105
|
+
current_phase: z.string().optional(),
|
|
106
|
+
skill: z.string().optional(),
|
|
107
|
+
session_id: z.string().optional(),
|
|
108
|
+
thread_id: z.string().optional(),
|
|
109
|
+
cwd: z.string().optional(),
|
|
110
|
+
updated_at: z.string().optional(),
|
|
111
|
+
handoff_from: z.string().optional(),
|
|
112
|
+
handoff_to: z.string().optional(),
|
|
113
|
+
handoff_at: z.string().optional(),
|
|
114
|
+
})
|
|
115
|
+
.passthrough();
|
|
116
|
+
|
|
117
|
+
export const SkillActiveEntrySchema = z
|
|
118
|
+
.object({
|
|
119
|
+
skill: z.string(),
|
|
120
|
+
phase: z.string().optional(),
|
|
121
|
+
active: z.boolean().optional(),
|
|
122
|
+
activated_at: z.string().optional(),
|
|
123
|
+
updated_at: z.string().optional(),
|
|
124
|
+
session_id: z.string().optional(),
|
|
125
|
+
thread_id: z.string().optional(),
|
|
126
|
+
turn_id: z.string().optional(),
|
|
127
|
+
stale: z.boolean().optional(),
|
|
128
|
+
handoff_from: z.string().optional(),
|
|
129
|
+
handoff_to: z.string().optional(),
|
|
130
|
+
handoff_at: z.string().optional(),
|
|
131
|
+
receipt: WorkflowStateReceiptSchema.optional(),
|
|
132
|
+
})
|
|
133
|
+
.passthrough();
|
|
134
|
+
|
|
135
|
+
export const SkillActiveStateSchema = z
|
|
136
|
+
.object({
|
|
137
|
+
version: z.number().optional(),
|
|
138
|
+
active: z.boolean().optional(),
|
|
139
|
+
skill: z.string().optional(),
|
|
140
|
+
keyword: z.string().optional(),
|
|
141
|
+
phase: z.string().optional(),
|
|
142
|
+
activated_at: z.string().optional(),
|
|
143
|
+
updated_at: z.string().optional(),
|
|
144
|
+
source: z.string().optional(),
|
|
145
|
+
session_id: z.string().optional(),
|
|
146
|
+
thread_id: z.string().optional(),
|
|
147
|
+
turn_id: z.string().optional(),
|
|
148
|
+
active_skills: z.array(SkillActiveEntrySchema).optional(),
|
|
149
|
+
})
|
|
150
|
+
.passthrough();
|
|
151
|
+
|
|
152
|
+
export type WorkflowStateEnvelope = z.infer<typeof WorkflowStateEnvelopeSchema>;
|
|
153
|
+
export type RequiredOnWriteEnvelope = z.infer<typeof RequiredOnWriteEnvelopeSchema>;
|
|
154
|
+
export type ModeStateParsed = z.infer<typeof ModeStateSchema>;
|
|
155
|
+
export type SkillActiveStateParsed = z.infer<typeof SkillActiveStateSchema>;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Validated read result.
|
|
159
|
+
* - `null` → file absent (ENOENT); callers treat as no state.
|
|
160
|
+
* - `{ok:true}` → parsed + schema-valid.
|
|
161
|
+
* - `{ok:false}` → present but unparseable or schema-invalid. Callers fail
|
|
162
|
+
* OPEN (normalize/log), never crash — preserving v2 reads.
|
|
163
|
+
*/
|
|
164
|
+
export type ReadGjcJsonResult<T> = { ok: true; value: T; raw: unknown } | { ok: false; error: string; raw: unknown };
|
|
165
|
+
|
|
166
|
+
function isEnoent(error: unknown): boolean {
|
|
167
|
+
return Boolean(error) && (error as NodeJS.ErrnoException).code === "ENOENT";
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Parse + schema-validate a `.gjc` JSON file at the read boundary.
|
|
172
|
+
* Returns `null` when the file is absent. Fail-open: an invalid file yields
|
|
173
|
+
* `{ ok: false }` with the raw value attached so the caller can normalize/log.
|
|
174
|
+
*/
|
|
175
|
+
export async function readGjcJson<T>(filePath: string, schema: z.ZodType<T>): Promise<ReadGjcJsonResult<T> | null> {
|
|
176
|
+
let text: string;
|
|
177
|
+
try {
|
|
178
|
+
text = await fs.readFile(filePath, "utf-8");
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (isEnoent(error)) return null;
|
|
181
|
+
return { ok: false, error: `read error: ${(error as Error).message}`, raw: undefined };
|
|
182
|
+
}
|
|
183
|
+
let raw: unknown;
|
|
184
|
+
try {
|
|
185
|
+
raw = JSON.parse(text);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
return { ok: false, error: `invalid JSON: ${(error as Error).message}`, raw: text };
|
|
188
|
+
}
|
|
189
|
+
const parsed = schema.safeParse(raw);
|
|
190
|
+
if (parsed.success) return { ok: true, value: parsed.data, raw };
|
|
191
|
+
return { ok: false, error: parsed.error.message, raw };
|
|
192
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
type WorkflowStateMutationOwner,
|
|
10
10
|
type WorkflowStateReceipt,
|
|
11
11
|
} from "../skill-state/workflow-state-contract";
|
|
12
|
+
import { RequiredOnWriteEnvelopeSchema } from "./state-schema";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Sole sanctioned project `.gjc/**` writer module (gate G1).
|
|
@@ -131,6 +132,27 @@ export class AlreadyExistsError extends Error {
|
|
|
131
132
|
}
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
export type StrictMutationReadResult =
|
|
136
|
+
| { kind: "absent" }
|
|
137
|
+
| { kind: "corrupt"; error: string }
|
|
138
|
+
| { kind: "valid"; value: Record<string, unknown> };
|
|
139
|
+
|
|
140
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
141
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function readExistingStateForMutation(filePath: string): Promise<StrictMutationReadResult> {
|
|
145
|
+
try {
|
|
146
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
147
|
+
const parsed = JSON.parse(raw);
|
|
148
|
+
if (isPlainObject(parsed)) return { kind: "valid", value: parsed };
|
|
149
|
+
return { kind: "corrupt", error: "state file must contain a JSON object" };
|
|
150
|
+
} catch (error) {
|
|
151
|
+
const err = error as NodeJS.ErrnoException;
|
|
152
|
+
if (err.code === "ENOENT") return { kind: "absent" };
|
|
153
|
+
return { kind: "corrupt", error: err.message };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
134
156
|
function isErrno(error: unknown, code: string): boolean {
|
|
135
157
|
return typeof error === "object" && error !== null && "code" in error && (error as { code?: unknown }).code === code;
|
|
136
158
|
}
|
|
@@ -357,7 +379,16 @@ export async function writeWorkflowEnvelopeAtomic(
|
|
|
357
379
|
): Promise<string> {
|
|
358
380
|
const filePath = resolveGjcTarget(targetPath, cwdForOptions(options));
|
|
359
381
|
const withReceipt = withWorkflowReceipt(value, buildReceipt(options));
|
|
360
|
-
|
|
382
|
+
const stamped = stampWorkflowEnvelopeChecksum(withReceipt, filePath);
|
|
383
|
+
const parsed = RequiredOnWriteEnvelopeSchema.safeParse(stamped);
|
|
384
|
+
if (!parsed.success) {
|
|
385
|
+
throw new Error(
|
|
386
|
+
`Refusing to write invalid workflow state envelope to ${filePath}: ${parsed.error.issues
|
|
387
|
+
.map(issue => `${issue.path.join(".") || "<root>"}: ${issue.message}`)
|
|
388
|
+
.join("; ")}`,
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
await atomicWrite(filePath, jsonText(stamped));
|
|
361
392
|
await maybeAudit(filePath, options);
|
|
362
393
|
return filePath;
|
|
363
394
|
}
|
|
@@ -3,6 +3,8 @@ import * as fs from "node:fs/promises";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import type { WorkflowHudSummary } from "../skill-state/active-state";
|
|
5
5
|
import { buildTeamHudSummary as buildWorkflowTeamHudSummary } from "../skill-state/workflow-hud";
|
|
6
|
+
import { WORKFLOW_STATE_VERSION } from "../skill-state/workflow-state-contract";
|
|
7
|
+
|
|
6
8
|
import { applyGjcTmuxProfile } from "./launch-tmux";
|
|
7
9
|
import {
|
|
8
10
|
AlreadyExistsError,
|
|
@@ -13,6 +15,7 @@ import {
|
|
|
13
15
|
removeFileAudited,
|
|
14
16
|
writeJsonAtomic,
|
|
15
17
|
writeReport,
|
|
18
|
+
writeWorkflowEnvelopeAtomic,
|
|
16
19
|
} from "./state-writer";
|
|
17
20
|
import { GJC_TMUX_PROFILE_OPTION, GJC_TMUX_PROFILE_VALUE } from "./tmux-common";
|
|
18
21
|
|
|
@@ -283,6 +286,55 @@ export interface GjcTeamMailboxMessage {
|
|
|
283
286
|
idempotency_key?: string;
|
|
284
287
|
}
|
|
285
288
|
|
|
289
|
+
function taskReceiptFields(teamName: string, task: GjcTeamTask): Record<string, unknown> {
|
|
290
|
+
return {
|
|
291
|
+
team_name: teamName,
|
|
292
|
+
task_id: task.id,
|
|
293
|
+
status: task.status,
|
|
294
|
+
owner: task.owner,
|
|
295
|
+
worker_id: task.claim?.owner ?? task.owner ?? task.assignee,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function mailboxMessageReceiptFields(teamName: string, message: GjcTeamMailboxMessage): Record<string, unknown> {
|
|
300
|
+
return {
|
|
301
|
+
team_name: teamName,
|
|
302
|
+
message_id: message.message_id,
|
|
303
|
+
from_worker: message.from_worker,
|
|
304
|
+
to_worker: message.to_worker,
|
|
305
|
+
delivered: Boolean(message.delivered_at),
|
|
306
|
+
notified: Boolean(message.notified_at),
|
|
307
|
+
delivered_at: message.delivered_at,
|
|
308
|
+
notified_at: message.notified_at,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function notificationReceiptFields(notification: GjcTeamNotification): Record<string, unknown> {
|
|
313
|
+
return {
|
|
314
|
+
team_name: notification.team_name,
|
|
315
|
+
notification_id: notification.id,
|
|
316
|
+
recipient: notification.recipient,
|
|
317
|
+
source_type: notification.source.type,
|
|
318
|
+
source_id: notification.source.id,
|
|
319
|
+
delivery_state: notification.delivery_state,
|
|
320
|
+
pane_attempt_result: notification.pane_attempt_result,
|
|
321
|
+
pane_attempt_reason: notification.pane_attempt_reason,
|
|
322
|
+
replay_count: notification.replay_count,
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function notificationSummaryReceipt(
|
|
327
|
+
teamName: string,
|
|
328
|
+
result: { notifications: GjcTeamNotification[]; summary: GjcTeamNotificationSummary },
|
|
329
|
+
): Record<string, unknown> {
|
|
330
|
+
return {
|
|
331
|
+
team_name: teamName,
|
|
332
|
+
notification_ids: result.notifications.map(notification => notification.id),
|
|
333
|
+
delivery_states: result.notifications.map(notification => notification.delivery_state),
|
|
334
|
+
summary: result.summary,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
286
338
|
interface FsError {
|
|
287
339
|
code?: string;
|
|
288
340
|
}
|
|
@@ -950,11 +1002,11 @@ function teamModeStatePath(): string {
|
|
|
950
1002
|
export async function persistGjcTeamModeStateSummary(snapshot: GjcTeamSnapshot, cwd = process.cwd()): Promise<void> {
|
|
951
1003
|
const active = snapshot.phase !== "complete" && snapshot.phase !== "cancelled";
|
|
952
1004
|
const updatedAt = now();
|
|
953
|
-
await
|
|
1005
|
+
await writeWorkflowEnvelopeAtomic(
|
|
954
1006
|
teamModeStatePath(),
|
|
955
1007
|
{
|
|
956
1008
|
skill: "team",
|
|
957
|
-
version:
|
|
1009
|
+
version: WORKFLOW_STATE_VERSION,
|
|
958
1010
|
active,
|
|
959
1011
|
current_phase: snapshot.phase,
|
|
960
1012
|
team_name: snapshot.team_name,
|
|
@@ -3746,121 +3798,142 @@ export async function executeGjcTeamApiOperation(
|
|
|
3746
3798
|
return { tasks: await listGjcTeamTasks(teamName, cwd, env) };
|
|
3747
3799
|
case "read-task":
|
|
3748
3800
|
return { task: await readGjcTeamTask(teamName, String(input.task_id ?? input.taskId), cwd, env) };
|
|
3749
|
-
case "create-task":
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
case "update-task":
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3801
|
+
case "create-task": {
|
|
3802
|
+
const task = await createGjcTeamTask(
|
|
3803
|
+
teamName,
|
|
3804
|
+
String(input.subject ?? "Task"),
|
|
3805
|
+
String(input.description ?? ""),
|
|
3806
|
+
cwd,
|
|
3807
|
+
env,
|
|
3808
|
+
taskMetadataFromInput(input, true),
|
|
3809
|
+
);
|
|
3810
|
+
return { ok: true, ...taskReceiptFields(teamName, task) };
|
|
3811
|
+
}
|
|
3812
|
+
case "update-task": {
|
|
3813
|
+
const task = await updateGjcTeamTask(
|
|
3814
|
+
teamName,
|
|
3815
|
+
String(input.task_id ?? input.taskId),
|
|
3816
|
+
{
|
|
3817
|
+
subject: typeof input.subject === "string" ? input.subject : undefined,
|
|
3818
|
+
description: typeof input.description === "string" ? input.description : undefined,
|
|
3819
|
+
...taskMetadataFromInput(input),
|
|
3820
|
+
},
|
|
3821
|
+
cwd,
|
|
3822
|
+
env,
|
|
3823
|
+
);
|
|
3824
|
+
return { ok: true, ...taskReceiptFields(teamName, task) };
|
|
3825
|
+
}
|
|
3774
3826
|
case "claim-task": {
|
|
3775
3827
|
const requestedTaskId = input.task_id ?? input.taskId;
|
|
3776
|
-
|
|
3828
|
+
const result = await claimGjcTeamTask(
|
|
3777
3829
|
teamName,
|
|
3778
3830
|
worker,
|
|
3779
3831
|
cwd,
|
|
3780
3832
|
env,
|
|
3781
3833
|
typeof requestedTaskId === "string" ? requestedTaskId : undefined,
|
|
3782
3834
|
);
|
|
3835
|
+
return {
|
|
3836
|
+
ok: result.ok,
|
|
3837
|
+
reason: result.reason,
|
|
3838
|
+
team_name: teamName,
|
|
3839
|
+
worker_id: result.worker_id ?? worker,
|
|
3840
|
+
...(result.task ? taskReceiptFields(teamName, result.task) : {}),
|
|
3841
|
+
claim_token: result.claim_token,
|
|
3842
|
+
};
|
|
3783
3843
|
}
|
|
3784
3844
|
case "transition-task":
|
|
3785
|
-
case "transition-task-status":
|
|
3845
|
+
case "transition-task-status": {
|
|
3846
|
+
const task = await transitionGjcTeamTaskStatus(
|
|
3847
|
+
teamName,
|
|
3848
|
+
String(input.task_id ?? input.taskId),
|
|
3849
|
+
parseGjcTeamTaskStatus(input.to ?? input.status),
|
|
3850
|
+
cwd,
|
|
3851
|
+
env,
|
|
3852
|
+
typeof input.claim_token === "string" ? input.claim_token : undefined,
|
|
3853
|
+
explicitWorker,
|
|
3854
|
+
input.completion_evidence ?? input.completionEvidence,
|
|
3855
|
+
);
|
|
3786
3856
|
return {
|
|
3787
3857
|
ok: true,
|
|
3788
|
-
task
|
|
3789
|
-
|
|
3790
|
-
String(input.task_id ?? input.taskId),
|
|
3791
|
-
parseGjcTeamTaskStatus(input.to ?? input.status),
|
|
3792
|
-
cwd,
|
|
3793
|
-
env,
|
|
3794
|
-
typeof input.claim_token === "string" ? input.claim_token : undefined,
|
|
3795
|
-
explicitWorker,
|
|
3796
|
-
input.completion_evidence ?? input.completionEvidence,
|
|
3797
|
-
),
|
|
3858
|
+
...taskReceiptFields(teamName, task),
|
|
3859
|
+
worker_id: explicitWorker ?? task.owner ?? task.assignee,
|
|
3798
3860
|
};
|
|
3799
|
-
|
|
3861
|
+
}
|
|
3862
|
+
case "release-task-claim": {
|
|
3863
|
+
const task = await releaseGjcTeamTaskClaim(
|
|
3864
|
+
teamName,
|
|
3865
|
+
String(input.task_id),
|
|
3866
|
+
String(input.claim_token),
|
|
3867
|
+
worker,
|
|
3868
|
+
cwd,
|
|
3869
|
+
env,
|
|
3870
|
+
);
|
|
3871
|
+
return { ok: true, ...taskReceiptFields(teamName, task), worker_id: worker };
|
|
3872
|
+
}
|
|
3873
|
+
case "send-message": {
|
|
3874
|
+
const message = await sendGjcTeamMessage(
|
|
3875
|
+
teamName,
|
|
3876
|
+
String(input.from_worker),
|
|
3877
|
+
String(input.to_worker),
|
|
3878
|
+
String(input.body),
|
|
3879
|
+
cwd,
|
|
3880
|
+
env,
|
|
3881
|
+
typeof input.idempotency_key === "string" ? input.idempotency_key : undefined,
|
|
3882
|
+
);
|
|
3883
|
+
return { ok: true, ...mailboxMessageReceiptFields(teamName, message) };
|
|
3884
|
+
}
|
|
3885
|
+
case "broadcast": {
|
|
3886
|
+
const messages = await broadcastGjcTeamMessage(
|
|
3887
|
+
teamName,
|
|
3888
|
+
String(input.from_worker),
|
|
3889
|
+
String(input.body),
|
|
3890
|
+
cwd,
|
|
3891
|
+
env,
|
|
3892
|
+
typeof input.idempotency_key === "string" ? input.idempotency_key : undefined,
|
|
3893
|
+
);
|
|
3800
3894
|
return {
|
|
3801
3895
|
ok: true,
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
),
|
|
3810
|
-
};
|
|
3811
|
-
case "send-message":
|
|
3812
|
-
return {
|
|
3813
|
-
message: await sendGjcTeamMessage(
|
|
3814
|
-
teamName,
|
|
3815
|
-
String(input.from_worker),
|
|
3816
|
-
String(input.to_worker),
|
|
3817
|
-
String(input.body),
|
|
3818
|
-
cwd,
|
|
3819
|
-
env,
|
|
3820
|
-
typeof input.idempotency_key === "string" ? input.idempotency_key : undefined,
|
|
3821
|
-
),
|
|
3822
|
-
};
|
|
3823
|
-
case "broadcast":
|
|
3824
|
-
return {
|
|
3825
|
-
messages: await broadcastGjcTeamMessage(
|
|
3826
|
-
teamName,
|
|
3827
|
-
String(input.from_worker),
|
|
3828
|
-
String(input.body),
|
|
3829
|
-
cwd,
|
|
3830
|
-
env,
|
|
3831
|
-
typeof input.idempotency_key === "string" ? input.idempotency_key : undefined,
|
|
3832
|
-
),
|
|
3896
|
+
team_name: teamName,
|
|
3897
|
+
message_ids: messages.map(message => message.message_id),
|
|
3898
|
+
delivery_states: messages.map(message => ({
|
|
3899
|
+
message_id: message.message_id,
|
|
3900
|
+
to_worker: message.to_worker,
|
|
3901
|
+
delivered: Boolean(message.delivered_at),
|
|
3902
|
+
notified: Boolean(message.notified_at),
|
|
3903
|
+
})),
|
|
3833
3904
|
};
|
|
3905
|
+
}
|
|
3834
3906
|
case "mailbox-list":
|
|
3835
3907
|
return { messages: await listGjcTeamMailbox(teamName, worker, cwd, env) };
|
|
3836
|
-
case "mailbox-mark-delivered":
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
case "mailbox-mark-notified":
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
3853
|
-
|
|
3854
|
-
|
|
3855
|
-
|
|
3856
|
-
|
|
3857
|
-
|
|
3908
|
+
case "mailbox-mark-delivered": {
|
|
3909
|
+
const message = await markGjcTeamMailboxMessage(
|
|
3910
|
+
teamName,
|
|
3911
|
+
worker,
|
|
3912
|
+
String(input.message_id),
|
|
3913
|
+
"delivered_at",
|
|
3914
|
+
cwd,
|
|
3915
|
+
env,
|
|
3916
|
+
);
|
|
3917
|
+
return { ok: true, ...mailboxMessageReceiptFields(teamName, message) };
|
|
3918
|
+
}
|
|
3919
|
+
case "mailbox-mark-notified": {
|
|
3920
|
+
const message = await markGjcTeamMailboxMessage(
|
|
3921
|
+
teamName,
|
|
3922
|
+
worker,
|
|
3923
|
+
String(input.message_id),
|
|
3924
|
+
"notified_at",
|
|
3925
|
+
cwd,
|
|
3926
|
+
env,
|
|
3927
|
+
);
|
|
3928
|
+
return { ok: true, ...mailboxMessageReceiptFields(teamName, message) };
|
|
3929
|
+
}
|
|
3858
3930
|
case "notification-list": {
|
|
3859
3931
|
const dir = await findTeamDir(teamName, cwd, env);
|
|
3860
3932
|
const config = await readConfig(dir);
|
|
3861
3933
|
await reconcileTeamNotifications(dir, config);
|
|
3862
3934
|
const notifications = await listNotificationRecords(dir);
|
|
3863
|
-
|
|
3935
|
+
const result = { notifications, summary: summarizeNotifications(notifications) };
|
|
3936
|
+
return notificationSummaryReceipt(teamName, result);
|
|
3864
3937
|
}
|
|
3865
3938
|
case "notification-read":
|
|
3866
3939
|
return {
|
|
@@ -3870,20 +3943,19 @@ export async function executeGjcTeamApiOperation(
|
|
|
3870
3943
|
),
|
|
3871
3944
|
};
|
|
3872
3945
|
case "notification-replay":
|
|
3873
|
-
return replayGjcTeamNotifications(teamName, cwd, env);
|
|
3946
|
+
return notificationSummaryReceipt(teamName, await replayGjcTeamNotifications(teamName, cwd, env));
|
|
3874
3947
|
case "notification-mark-pane-attempt": {
|
|
3875
3948
|
const dir = await findTeamDir(teamName, cwd, env);
|
|
3876
3949
|
const notification = await readNotificationRecord(dir, String(input.notification_id));
|
|
3877
|
-
|
|
3878
|
-
notification
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
};
|
|
3950
|
+
const updated = await writeNotificationRecord(dir, {
|
|
3951
|
+
...notification,
|
|
3952
|
+
delivery_state: parsePaneAttemptResult(String(input.result ?? "failed")),
|
|
3953
|
+
pane_attempt_result: parsePaneAttemptResult(String(input.result ?? "failed")),
|
|
3954
|
+
pane_attempt_reason: String(input.reason ?? "manual_api"),
|
|
3955
|
+
pane_attempt_at: now(),
|
|
3956
|
+
updated_at: now(),
|
|
3957
|
+
});
|
|
3958
|
+
return { ok: true, ...notificationReceiptFields(updated) };
|
|
3887
3959
|
}
|
|
3888
3960
|
case "worker-startup-ack":
|
|
3889
3961
|
return writeGjcWorkerStartupAck(teamName, worker, cwd, env, input);
|