@gajae-code/coding-agent 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +1 -1
  3. package/dist/types/async/job-manager.d.ts +7 -0
  4. package/dist/types/cli/args.d.ts +3 -1
  5. package/dist/types/commands/deep-interview.d.ts +3 -0
  6. package/dist/types/commands/launch.d.ts +6 -0
  7. package/dist/types/config/keybindings.d.ts +5 -0
  8. package/dist/types/config/model-profile-activation.d.ts +30 -0
  9. package/dist/types/config/model-profiles.d.ts +19 -0
  10. package/dist/types/config/model-registry.d.ts +8 -0
  11. package/dist/types/config/model-resolver.d.ts +1 -1
  12. package/dist/types/config/models-config-schema.d.ts +47 -0
  13. package/dist/types/config/settings-schema.d.ts +14 -4
  14. package/dist/types/debug/crash-diagnostics.d.ts +45 -0
  15. package/dist/types/debug/runtime-gauges.d.ts +6 -0
  16. package/dist/types/deep-interview/render-middleware.d.ts +1 -0
  17. package/dist/types/eval/py/executor.d.ts +2 -0
  18. package/dist/types/eval/py/kernel.d.ts +2 -0
  19. package/dist/types/exec/bash-executor.d.ts +10 -0
  20. package/dist/types/gjc-runtime/cli-write-receipt.d.ts +24 -0
  21. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +1 -0
  22. package/dist/types/gjc-runtime/state-migrations.d.ts +9 -0
  23. package/dist/types/gjc-runtime/state-schema.d.ts +317 -0
  24. package/dist/types/gjc-runtime/state-writer.d.ts +10 -0
  25. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +2 -1
  26. package/dist/types/gjc-runtime/workflow-command-ref.d.ts +43 -0
  27. package/dist/types/harness-control-plane/control-endpoint.d.ts +3 -2
  28. package/dist/types/hooks/skill-state.d.ts +21 -0
  29. package/dist/types/internal-urls/agent-protocol.d.ts +2 -2
  30. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  31. package/dist/types/internal-urls/registry-helpers.d.ts +8 -7
  32. package/dist/types/internal-urls/types.d.ts +4 -0
  33. package/dist/types/lsp/index.d.ts +10 -10
  34. package/dist/types/main.d.ts +10 -1
  35. package/dist/types/modes/bridge/auth.d.ts +12 -0
  36. package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
  37. package/dist/types/modes/bridge/bridge-mode.d.ts +44 -0
  38. package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
  39. package/dist/types/modes/bridge/event-stream.d.ts +8 -0
  40. package/dist/types/modes/components/custom-editor.d.ts +6 -0
  41. package/dist/types/modes/components/custom-provider-wizard.d.ts +10 -0
  42. package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
  43. package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
  44. package/dist/types/modes/components/model-selector.d.ts +6 -1
  45. package/dist/types/modes/components/provider-onboarding-selector.d.ts +1 -1
  46. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  47. package/dist/types/modes/components/status-line.d.ts +2 -0
  48. package/dist/types/modes/controllers/input-controller.d.ts +1 -0
  49. package/dist/types/modes/controllers/selector-controller.d.ts +9 -0
  50. package/dist/types/modes/index.d.ts +1 -0
  51. package/dist/types/modes/interactive-mode.d.ts +1 -0
  52. package/dist/types/modes/jobs-observer.d.ts +57 -0
  53. package/dist/types/modes/rpc/host-tools.d.ts +1 -16
  54. package/dist/types/modes/rpc/host-uris.d.ts +1 -38
  55. package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +20 -0
  56. package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
  57. package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +24 -0
  58. package/dist/types/modes/shared/agent-wire/handshake.d.ts +46 -0
  59. package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
  60. package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
  61. package/dist/types/modes/shared/agent-wire/protocol.d.ts +44 -0
  62. package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
  63. package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
  64. package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
  65. package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
  66. package/dist/types/modes/types.d.ts +2 -0
  67. package/dist/types/sdk.d.ts +3 -1
  68. package/dist/types/session/agent-session.d.ts +11 -1
  69. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -2
  70. package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
  71. package/dist/types/task/executor.d.ts +1 -0
  72. package/dist/types/task/id.d.ts +7 -0
  73. package/dist/types/task/index.d.ts +5 -0
  74. package/dist/types/task/receipt.d.ts +85 -0
  75. package/dist/types/task/spawn-gate.d.ts +38 -0
  76. package/dist/types/task/types.d.ts +143 -11
  77. package/dist/types/tools/cron.d.ts +6 -0
  78. package/dist/types/tools/hindsight-recall.d.ts +0 -2
  79. package/dist/types/tools/hindsight-reflect.d.ts +0 -2
  80. package/dist/types/tools/hindsight-retain.d.ts +0 -2
  81. package/dist/types/tools/index.d.ts +6 -4
  82. package/dist/types/tools/path-utils.d.ts +1 -0
  83. package/dist/types/tools/subagent.d.ts +15 -0
  84. package/package.json +7 -7
  85. package/scripts/build-binary.ts +7 -0
  86. package/src/async/job-manager.ts +36 -0
  87. package/src/cli/args.ts +19 -2
  88. package/src/commands/deep-interview.ts +1 -0
  89. package/src/commands/harness.ts +289 -19
  90. package/src/commands/launch.ts +10 -2
  91. package/src/commands/state.ts +2 -1
  92. package/src/commands/team.ts +22 -4
  93. package/src/config/keybindings.ts +6 -0
  94. package/src/config/model-profile-activation.ts +157 -0
  95. package/src/config/model-profiles.ts +155 -0
  96. package/src/config/model-registry.ts +19 -0
  97. package/src/config/model-resolver.ts +3 -2
  98. package/src/config/models-config-schema.ts +36 -0
  99. package/src/config/settings-schema.ts +16 -3
  100. package/src/dap/client.ts +17 -3
  101. package/src/debug/crash-diagnostics.ts +223 -0
  102. package/src/debug/runtime-gauges.ts +20 -0
  103. package/src/deep-interview/render-middleware.ts +6 -0
  104. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
  105. package/src/defaults/gjc/skills/ralplan/SKILL.md +31 -2
  106. package/src/defaults/gjc/skills/ultragoal/SKILL.md +39 -3
  107. package/src/defaults/gjc/skills/ultragoal/ai-slop-cleaner.md +61 -0
  108. package/src/defaults/gjc-defaults.ts +7 -0
  109. package/src/eval/py/executor.ts +21 -1
  110. package/src/eval/py/kernel.ts +15 -0
  111. package/src/exec/bash-executor.ts +41 -0
  112. package/src/gjc-runtime/cli-write-receipt.ts +31 -0
  113. package/src/gjc-runtime/deep-interview-runtime.ts +69 -32
  114. package/src/gjc-runtime/ralplan-runtime.ts +213 -36
  115. package/src/gjc-runtime/state-migrations.ts +54 -7
  116. package/src/gjc-runtime/state-runtime.ts +461 -64
  117. package/src/gjc-runtime/state-schema.ts +192 -0
  118. package/src/gjc-runtime/state-writer.ts +32 -1
  119. package/src/gjc-runtime/team-runtime.ts +177 -105
  120. package/src/gjc-runtime/ultragoal-runtime.ts +231 -38
  121. package/src/gjc-runtime/workflow-command-ref.ts +239 -0
  122. package/src/gjc-runtime/workflow-manifest.generated.json +108 -4
  123. package/src/gjc-runtime/workflow-manifest.ts +3 -1
  124. package/src/harness-control-plane/control-endpoint.ts +19 -8
  125. package/src/harness-control-plane/owner.ts +57 -10
  126. package/src/harness-control-plane/state-machine.ts +2 -1
  127. package/src/hooks/skill-state.ts +176 -26
  128. package/src/internal-urls/agent-protocol.ts +68 -21
  129. package/src/internal-urls/artifact-protocol.ts +12 -17
  130. package/src/internal-urls/docs-index.generated.ts +8 -10
  131. package/src/internal-urls/registry-helpers.ts +19 -16
  132. package/src/internal-urls/types.ts +4 -0
  133. package/src/lsp/client.ts +18 -2
  134. package/src/main.ts +88 -6
  135. package/src/modes/bridge/auth.ts +41 -0
  136. package/src/modes/bridge/bridge-client-bridge.ts +47 -0
  137. package/src/modes/bridge/bridge-mode.ts +520 -0
  138. package/src/modes/bridge/bridge-ui-context.ts +200 -0
  139. package/src/modes/bridge/event-stream.ts +70 -0
  140. package/src/modes/components/custom-editor.ts +101 -0
  141. package/src/modes/components/custom-provider-wizard.ts +318 -0
  142. package/src/modes/components/hook-selector.ts +61 -18
  143. package/src/modes/components/jobs-overlay-model.ts +109 -0
  144. package/src/modes/components/jobs-overlay.ts +172 -0
  145. package/src/modes/components/model-selector.ts +108 -18
  146. package/src/modes/components/provider-onboarding-selector.ts +6 -1
  147. package/src/modes/components/status-line/presets.ts +7 -5
  148. package/src/modes/components/status-line/segments.ts +25 -0
  149. package/src/modes/components/status-line/types.ts +2 -0
  150. package/src/modes/components/status-line.ts +9 -1
  151. package/src/modes/controllers/extension-ui-controller.ts +39 -3
  152. package/src/modes/controllers/input-controller.ts +97 -9
  153. package/src/modes/controllers/selector-controller.ts +86 -1
  154. package/src/modes/index.ts +1 -0
  155. package/src/modes/interactive-mode.ts +27 -0
  156. package/src/modes/jobs-observer.ts +204 -0
  157. package/src/modes/rpc/host-tools.ts +1 -186
  158. package/src/modes/rpc/host-uris.ts +1 -235
  159. package/src/modes/rpc/rpc-client.ts +25 -10
  160. package/src/modes/rpc/rpc-mode.ts +12 -381
  161. package/src/modes/shared/agent-wire/command-dispatch.ts +341 -0
  162. package/src/modes/shared/agent-wire/command-validation.ts +131 -0
  163. package/src/modes/shared/agent-wire/event-envelope.ts +108 -0
  164. package/src/modes/shared/agent-wire/handshake.ts +117 -0
  165. package/src/modes/shared/agent-wire/host-tool-bridge.ts +194 -0
  166. package/src/modes/shared/agent-wire/host-uri-bridge.ts +236 -0
  167. package/src/modes/shared/agent-wire/protocol.ts +96 -0
  168. package/src/modes/shared/agent-wire/responses.ts +17 -0
  169. package/src/modes/shared/agent-wire/scopes.ts +89 -0
  170. package/src/modes/shared/agent-wire/ui-request-broker.ts +150 -0
  171. package/src/modes/shared/agent-wire/ui-result.ts +48 -0
  172. package/src/modes/types.ts +2 -0
  173. package/src/prompts/memories/consolidation.md +1 -1
  174. package/src/prompts/memories/read-path.md +6 -7
  175. package/src/prompts/memories/unavailable.md +2 -2
  176. package/src/prompts/tools/bash.md +1 -1
  177. package/src/prompts/tools/irc.md +1 -1
  178. package/src/prompts/tools/read.md +2 -2
  179. package/src/prompts/tools/recall.md +1 -0
  180. package/src/prompts/tools/reflect.md +1 -0
  181. package/src/prompts/tools/retain.md +1 -0
  182. package/src/prompts/tools/subagent.md +12 -7
  183. package/src/prompts/tools/task-summary.md +3 -9
  184. package/src/prompts/tools/task.md +5 -1
  185. package/src/sdk.ts +5 -1
  186. package/src/session/agent-session.ts +214 -38
  187. package/src/skill-state/deep-interview-mutation-guard.ts +23 -4
  188. package/src/skill-state/workflow-state-contract.ts +7 -4
  189. package/src/skill-state/workflow-state-version.ts +3 -0
  190. package/src/slash-commands/builtin-registry.ts +9 -1
  191. package/src/task/executor.ts +31 -5
  192. package/src/task/id.ts +33 -0
  193. package/src/task/index.ts +259 -67
  194. package/src/task/output-manager.ts +5 -4
  195. package/src/task/receipt.ts +297 -0
  196. package/src/task/render.ts +48 -131
  197. package/src/task/spawn-gate.ts +132 -0
  198. package/src/task/types.ts +48 -7
  199. package/src/tools/ask.ts +73 -33
  200. package/src/tools/ast-edit.ts +1 -0
  201. package/src/tools/ast-grep.ts +1 -0
  202. package/src/tools/bash.ts +1 -1
  203. package/src/tools/cron.ts +48 -0
  204. package/src/tools/find.ts +4 -1
  205. package/src/tools/hindsight-recall.ts +0 -2
  206. package/src/tools/hindsight-reflect.ts +0 -2
  207. package/src/tools/hindsight-retain.ts +0 -2
  208. package/src/tools/index.ts +6 -18
  209. package/src/tools/path-utils.ts +3 -2
  210. package/src/tools/read.ts +4 -3
  211. package/src/tools/search.ts +1 -0
  212. package/src/tools/skill.ts +6 -1
  213. 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
- await atomicWrite(filePath, jsonText(stampWorkflowEnvelopeChecksum(withReceipt, filePath)));
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 writeJsonAtomic(
1005
+ await writeWorkflowEnvelopeAtomic(
954
1006
  teamModeStatePath(),
955
1007
  {
956
1008
  skill: "team",
957
- version: 1,
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
- return {
3751
- task: await createGjcTeamTask(
3752
- teamName,
3753
- String(input.subject ?? "Task"),
3754
- String(input.description ?? ""),
3755
- cwd,
3756
- env,
3757
- taskMetadataFromInput(input, true),
3758
- ),
3759
- };
3760
- case "update-task":
3761
- return {
3762
- task: await updateGjcTeamTask(
3763
- teamName,
3764
- String(input.task_id ?? input.taskId),
3765
- {
3766
- subject: typeof input.subject === "string" ? input.subject : undefined,
3767
- description: typeof input.description === "string" ? input.description : undefined,
3768
- ...taskMetadataFromInput(input),
3769
- },
3770
- cwd,
3771
- env,
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
- return claimGjcTeamTask(
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: await transitionGjcTeamTaskStatus(
3789
- teamName,
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
- case "release-task-claim":
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
- task: await releaseGjcTeamTaskClaim(
3803
- teamName,
3804
- String(input.task_id),
3805
- String(input.claim_token),
3806
- worker,
3807
- cwd,
3808
- env,
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
- return {
3838
- message: await markGjcTeamMailboxMessage(
3839
- teamName,
3840
- worker,
3841
- String(input.message_id),
3842
- "delivered_at",
3843
- cwd,
3844
- env,
3845
- ),
3846
- };
3847
- case "mailbox-mark-notified":
3848
- return {
3849
- message: await markGjcTeamMailboxMessage(
3850
- teamName,
3851
- worker,
3852
- String(input.message_id),
3853
- "notified_at",
3854
- cwd,
3855
- env,
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
- return { notifications, summary: summarizeNotifications(notifications) };
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
- return {
3878
- notification: await writeNotificationRecord(dir, {
3879
- ...notification,
3880
- delivery_state: parsePaneAttemptResult(String(input.result ?? "failed")),
3881
- pane_attempt_result: parsePaneAttemptResult(String(input.result ?? "failed")),
3882
- pane_attempt_reason: String(input.reason ?? "manual_api"),
3883
- pane_attempt_at: now(),
3884
- updated_at: now(),
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);