@gajae-code/coding-agent 0.2.5 → 0.3.0

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 (112) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/types/async/job-manager.d.ts +84 -2
  3. package/dist/types/commands/harness.d.ts +37 -0
  4. package/dist/types/config/settings-schema.d.ts +6 -0
  5. package/dist/types/config/settings.d.ts +2 -0
  6. package/dist/types/deep-interview/render-middleware.d.ts +5 -0
  7. package/dist/types/extensibility/custom-tools/types.d.ts +1 -0
  8. package/dist/types/extensibility/extensions/types.d.ts +6 -0
  9. package/dist/types/extensibility/shared-events.d.ts +1 -0
  10. package/dist/types/gjc-runtime/state-graph.d.ts +4 -0
  11. package/dist/types/gjc-runtime/state-migrations.d.ts +24 -0
  12. package/dist/types/gjc-runtime/state-renderer.d.ts +65 -0
  13. package/dist/types/gjc-runtime/state-runtime.d.ts +2 -0
  14. package/dist/types/gjc-runtime/state-validation.d.ts +6 -0
  15. package/dist/types/gjc-runtime/state-writer.d.ts +137 -0
  16. package/dist/types/gjc-runtime/team-runtime.d.ts +81 -7
  17. package/dist/types/gjc-runtime/workflow-manifest.d.ts +54 -0
  18. package/dist/types/harness-control-plane/classifier.d.ts +13 -0
  19. package/dist/types/harness-control-plane/control-endpoint.d.ts +30 -0
  20. package/dist/types/harness-control-plane/finalize.d.ts +47 -0
  21. package/dist/types/harness-control-plane/frame-mapper.d.ts +29 -0
  22. package/dist/types/harness-control-plane/operate.d.ts +35 -0
  23. package/dist/types/harness-control-plane/owner.d.ts +46 -0
  24. package/dist/types/harness-control-plane/preserve.d.ts +19 -0
  25. package/dist/types/harness-control-plane/receipts.d.ts +88 -0
  26. package/dist/types/harness-control-plane/rpc-adapter.d.ts +66 -0
  27. package/dist/types/harness-control-plane/seams.d.ts +21 -0
  28. package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
  29. package/dist/types/harness-control-plane/state-machine.d.ts +19 -0
  30. package/dist/types/harness-control-plane/storage.d.ts +53 -0
  31. package/dist/types/harness-control-plane/types.d.ts +162 -0
  32. package/dist/types/hooks/skill-keywords.d.ts +2 -1
  33. package/dist/types/hooks/skill-state.d.ts +2 -29
  34. package/dist/types/modes/components/hook-selector.d.ts +1 -0
  35. package/dist/types/modes/interactive-mode.d.ts +1 -0
  36. package/dist/types/modes/types.d.ts +1 -0
  37. package/dist/types/sdk.d.ts +2 -0
  38. package/dist/types/session/agent-session.d.ts +8 -0
  39. package/dist/types/skill-state/active-state.d.ts +2 -0
  40. package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +1 -1
  41. package/dist/types/skill-state/workflow-state-contract.d.ts +24 -0
  42. package/dist/types/task/executor.d.ts +3 -0
  43. package/dist/types/task/types.d.ts +55 -3
  44. package/dist/types/tools/subagent.d.ts +11 -1
  45. package/package.json +7 -7
  46. package/src/async/job-manager.ts +298 -6
  47. package/src/cli/auth-broker-cli.ts +1 -0
  48. package/src/cli/config-cli.ts +10 -2
  49. package/src/cli.ts +2 -0
  50. package/src/commands/harness.ts +592 -0
  51. package/src/commands/team.ts +36 -39
  52. package/src/config/settings-schema.ts +7 -0
  53. package/src/config/settings.ts +5 -0
  54. package/src/deep-interview/render-middleware.ts +366 -0
  55. package/src/defaults/gjc/skills/team/SKILL.md +47 -21
  56. package/src/defaults/gjc/skills/ultragoal/SKILL.md +78 -11
  57. package/src/extensibility/custom-tools/types.ts +1 -0
  58. package/src/extensibility/extensions/types.ts +6 -0
  59. package/src/extensibility/shared-events.ts +1 -0
  60. package/src/gjc-runtime/deep-interview-runtime.ts +40 -21
  61. package/src/gjc-runtime/goal-mode-request.ts +11 -3
  62. package/src/gjc-runtime/ralplan-runtime.ts +25 -10
  63. package/src/gjc-runtime/state-graph.ts +86 -0
  64. package/src/gjc-runtime/state-migrations.ts +132 -0
  65. package/src/gjc-runtime/state-renderer.ts +345 -0
  66. package/src/gjc-runtime/state-runtime.ts +733 -21
  67. package/src/gjc-runtime/state-validation.ts +49 -0
  68. package/src/gjc-runtime/state-writer.ts +718 -0
  69. package/src/gjc-runtime/team-runtime.ts +1083 -89
  70. package/src/gjc-runtime/ultragoal-runtime.ts +348 -19
  71. package/src/gjc-runtime/workflow-manifest.generated.json +1497 -0
  72. package/src/gjc-runtime/workflow-manifest.ts +425 -0
  73. package/src/harness-control-plane/classifier.ts +128 -0
  74. package/src/harness-control-plane/control-endpoint.ts +137 -0
  75. package/src/harness-control-plane/finalize.ts +222 -0
  76. package/src/harness-control-plane/frame-mapper.ts +286 -0
  77. package/src/harness-control-plane/operate.ts +225 -0
  78. package/src/harness-control-plane/owner.ts +553 -0
  79. package/src/harness-control-plane/preserve.ts +102 -0
  80. package/src/harness-control-plane/receipts.ts +216 -0
  81. package/src/harness-control-plane/rpc-adapter.ts +276 -0
  82. package/src/harness-control-plane/seams.ts +39 -0
  83. package/src/harness-control-plane/session-lease.ts +388 -0
  84. package/src/harness-control-plane/state-machine.ts +97 -0
  85. package/src/harness-control-plane/storage.ts +257 -0
  86. package/src/harness-control-plane/types.ts +214 -0
  87. package/src/hooks/skill-keywords.ts +4 -2
  88. package/src/hooks/skill-state.ts +24 -41
  89. package/src/internal-urls/docs-index.generated.ts +1 -1
  90. package/src/modes/components/assistant-message.ts +5 -1
  91. package/src/modes/components/hook-selector.ts +72 -2
  92. package/src/modes/controllers/event-controller.ts +71 -6
  93. package/src/modes/controllers/extension-ui-controller.ts +6 -0
  94. package/src/modes/controllers/input-controller.ts +9 -1
  95. package/src/modes/controllers/selector-controller.ts +2 -1
  96. package/src/modes/interactive-mode.ts +1 -0
  97. package/src/modes/types.ts +1 -0
  98. package/src/prompts/agents/executor.md +13 -0
  99. package/src/prompts/tools/subagent.md +33 -3
  100. package/src/sdk.ts +4 -0
  101. package/src/session/agent-session.ts +231 -33
  102. package/src/session/session-manager.ts +13 -1
  103. package/src/skill-state/active-state.ts +58 -65
  104. package/src/skill-state/deep-interview-mutation-guard.ts +91 -13
  105. package/src/skill-state/initial-phase.ts +2 -0
  106. package/src/skill-state/workflow-state-contract.ts +26 -0
  107. package/src/task/executor.ts +50 -8
  108. package/src/task/index.ts +120 -8
  109. package/src/task/render.ts +6 -3
  110. package/src/task/types.ts +56 -3
  111. package/src/tools/ask.ts +28 -7
  112. package/src/tools/subagent.ts +255 -64
@@ -0,0 +1,425 @@
1
+ /**
2
+ * TypeScript is the authoritative source of truth for GJC workflow manifests.
3
+ * Any JSON manifest projection is derived from this module and must never be
4
+ * hand-edited.
5
+ */
6
+
7
+ import type { CanonicalGjcWorkflowSkill } from "../skill-state/active-state";
8
+ import { CANONICAL_GJC_WORKFLOW_SKILLS } from "../skill-state/active-state";
9
+ import { initialPhaseForSkill } from "../skill-state/initial-phase";
10
+
11
+ export interface WorkflowState {
12
+ id: string;
13
+ initial?: boolean;
14
+ terminal?: boolean;
15
+ }
16
+
17
+ export interface WorkflowTransition {
18
+ from: string;
19
+ to: string;
20
+ verb: string;
21
+ }
22
+
23
+ export interface WorkflowVerb {
24
+ name: string;
25
+ planned?: boolean;
26
+ /** Invocation surface that exposes this verb in the real CLI parser. */
27
+ surface?: "state-action" | "command-positional" | "command-flag";
28
+ }
29
+
30
+ export interface TypedArgSpec {
31
+ name: string;
32
+ type: "string" | "number" | "boolean" | "enum" | "object";
33
+ enumValues?: string[];
34
+ required?: boolean;
35
+ appliesToVerbs?: string[];
36
+ planned?: boolean;
37
+ }
38
+
39
+ export interface RetentionPolicy {
40
+ category: string;
41
+ keep?: number;
42
+ maxAgeDays?: number;
43
+ }
44
+
45
+ export interface SkillManifest {
46
+ skill: CanonicalGjcWorkflowSkill;
47
+ states: WorkflowState[];
48
+ initialState: string;
49
+ terminalStates: string[];
50
+ transitions: WorkflowTransition[];
51
+ verbs: WorkflowVerb[];
52
+ typedArgs: TypedArgSpec[];
53
+ retention: RetentionPolicy[];
54
+ hudFields: string[];
55
+ graphLabel: string;
56
+ }
57
+
58
+ const STATE_RETENTION: RetentionPolicy = { category: "state", keep: 1 };
59
+ const ARTIFACT_RETENTION: RetentionPolicy = { category: "artifact" };
60
+ const LEDGER_RETENTION: RetentionPolicy = { category: "ledger" };
61
+ const LOG_RETENTION: RetentionPolicy = { category: "log", maxAgeDays: 30 };
62
+ const REPORT_RETENTION: RetentionPolicy = { category: "report", maxAgeDays: 30 };
63
+ const AGENTS_RETENTION: RetentionPolicy = { category: "agents" };
64
+ const PRUNE_RETENTION: RetentionPolicy = { category: "prune/delete", maxAgeDays: 30 };
65
+ const FORCE_RETENTION: RetentionPolicy = { category: "force", maxAgeDays: 90 };
66
+
67
+ const STATE_VERBS = ["read", "write", "clear", "contract", "handoff"] as const;
68
+ const PLANNED_ADMIN_VERBS = ["graph", "prune", "migrate", "force-overwrite"] as const;
69
+
70
+ const COMMON_TYPED_ARGS: TypedArgSpec[] = [
71
+ { name: "input", type: "string", appliesToVerbs: ["write", "api"] },
72
+ { name: "mode", type: "enum", enumValues: [...CANONICAL_GJC_WORKFLOW_SKILLS], appliesToVerbs: [...STATE_VERBS] },
73
+ { name: "session-id", type: "string", appliesToVerbs: [...STATE_VERBS, "kickoff", "write-spec", "write-artifact"] },
74
+ { name: "thread-id", type: "string", appliesToVerbs: ["write", "clear", "handoff"] },
75
+ { name: "turn-id", type: "string", appliesToVerbs: ["write", "clear", "handoff"] },
76
+ {
77
+ name: "to",
78
+ type: "enum",
79
+ enumValues: [...CANONICAL_GJC_WORKFLOW_SKILLS],
80
+ required: true,
81
+ appliesToVerbs: ["handoff"],
82
+ },
83
+ { name: "replace", type: "boolean", appliesToVerbs: ["write"] },
84
+ { name: "force", type: "boolean", appliesToVerbs: ["write", "clear", "handoff"] },
85
+ ];
86
+
87
+ function verb(name: string, surface: WorkflowVerb["surface"]): WorkflowVerb {
88
+ return { name, surface };
89
+ }
90
+
91
+ function stateVerbs(): WorkflowVerb[] {
92
+ return STATE_VERBS.map(name => verb(name, "state-action"));
93
+ }
94
+
95
+ function positionalVerbs(names: readonly string[]): WorkflowVerb[] {
96
+ return names.map(name => verb(name, "command-positional"));
97
+ }
98
+
99
+ function flagVerbs(names: readonly string[]): WorkflowVerb[] {
100
+ return names.map(name => verb(name, "command-flag"));
101
+ }
102
+
103
+ function plannedVerbs(names: readonly string[]): WorkflowVerb[] {
104
+ return names.map(name => ({ ...verb(name, "state-action"), planned: true }));
105
+ }
106
+
107
+ function state(id: string, initialState: string, terminalStates: readonly string[]): WorkflowState {
108
+ const entry: WorkflowState = { id };
109
+ if (id === initialState) entry.initial = true;
110
+ if (terminalStates.includes(id)) entry.terminal = true;
111
+ return entry;
112
+ }
113
+
114
+ function manifest(input: {
115
+ skill: CanonicalGjcWorkflowSkill;
116
+ states: string[];
117
+ terminalStates: string[];
118
+ transitions: WorkflowTransition[];
119
+ verbs: WorkflowVerb[];
120
+ typedArgs?: TypedArgSpec[];
121
+ retention: RetentionPolicy[];
122
+ hudFields: string[];
123
+ graphLabel: string;
124
+ initialState?: string;
125
+ }): SkillManifest {
126
+ const staleInitialState = initialPhaseForSkill(input.skill);
127
+ const initialState = input.initialState ?? staleInitialState;
128
+ return {
129
+ skill: input.skill,
130
+ states: input.states.map(item => state(item, initialState, input.terminalStates)),
131
+ initialState,
132
+ terminalStates: input.terminalStates,
133
+ transitions: input.transitions,
134
+ verbs: input.verbs,
135
+ typedArgs: [...COMMON_TYPED_ARGS, ...(input.typedArgs ?? [])],
136
+ retention: input.retention,
137
+ hudFields: input.hudFields,
138
+ graphLabel: input.graphLabel,
139
+ };
140
+ }
141
+
142
+ export const WORKFLOW_MANIFEST: Record<CanonicalGjcWorkflowSkill, SkillManifest> = {
143
+ "deep-interview": manifest({
144
+ skill: "deep-interview",
145
+ states: ["interviewing", "handoff", "complete"],
146
+ terminalStates: ["handoff", "complete"],
147
+ transitions: [
148
+ { from: "interviewing", to: "handoff", verb: "write-spec" },
149
+ { from: "handoff", to: "complete", verb: "clear" },
150
+ { from: "interviewing", to: "complete", verb: "clear" },
151
+ ],
152
+ verbs: [...stateVerbs(), ...flagVerbs(["kickoff", "write-spec"]), ...plannedVerbs(PLANNED_ADMIN_VERBS)],
153
+ typedArgs: [
154
+ { name: "quick", type: "boolean", appliesToVerbs: ["kickoff"] },
155
+ { name: "standard", type: "boolean", appliesToVerbs: ["kickoff"] },
156
+ { name: "deep", type: "boolean", appliesToVerbs: ["kickoff"] },
157
+ { name: "threshold", type: "number", appliesToVerbs: ["kickoff"] },
158
+ { name: "threshold-source", type: "string", appliesToVerbs: ["kickoff"] },
159
+ { name: "stage", type: "enum", enumValues: ["final"], appliesToVerbs: ["write-spec"] },
160
+ { name: "slug", type: "string", appliesToVerbs: ["write-spec"] },
161
+ { name: "spec", type: "string", required: true, appliesToVerbs: ["write-spec"] },
162
+ { name: "handoff", type: "enum", enumValues: ["ralplan"], appliesToVerbs: ["write-spec"] },
163
+ { name: "deliberate", type: "boolean", appliesToVerbs: ["write-spec"] },
164
+ { name: "json", type: "boolean", appliesToVerbs: ["write-spec"] },
165
+ { name: "args", type: "string", planned: true },
166
+ { name: "metadata-json", type: "string", planned: true },
167
+ ],
168
+ retention: [STATE_RETENTION, ARTIFACT_RETENTION, PRUNE_RETENTION, FORCE_RETENTION],
169
+ hudFields: ["current_phase", "ambiguity_score", "threshold", "spec_slug", "spec_path", "topology"],
170
+ graphLabel: "Deep Interview",
171
+ }),
172
+ ralplan: manifest({
173
+ skill: "ralplan",
174
+ states: ["planner", "architect", "critic", "revision", "adr", "final", "handoff"],
175
+ terminalStates: ["final", "handoff"],
176
+ transitions: [
177
+ { from: "planner", to: "architect", verb: "write-artifact" },
178
+ { from: "architect", to: "critic", verb: "write-artifact" },
179
+ { from: "critic", to: "revision", verb: "write-artifact" },
180
+ { from: "revision", to: "adr", verb: "write-artifact" },
181
+ { from: "adr", to: "final", verb: "write-artifact" },
182
+ { from: "planner", to: "handoff", verb: "handoff" },
183
+ { from: "architect", to: "handoff", verb: "handoff" },
184
+ { from: "critic", to: "handoff", verb: "handoff" },
185
+ { from: "revision", to: "handoff", verb: "handoff" },
186
+ { from: "adr", to: "handoff", verb: "handoff" },
187
+ ],
188
+ verbs: [...stateVerbs(), ...flagVerbs(["kickoff", "write-artifact"]), ...plannedVerbs(PLANNED_ADMIN_VERBS)],
189
+ typedArgs: [
190
+ { name: "interactive", type: "boolean", appliesToVerbs: ["kickoff"] },
191
+ { name: "deliberate", type: "boolean", appliesToVerbs: ["kickoff"] },
192
+ { name: "architect", type: "string", appliesToVerbs: ["kickoff"] },
193
+ { name: "critic", type: "string", appliesToVerbs: ["kickoff"] },
194
+ { name: "json", type: "boolean", appliesToVerbs: ["kickoff", "write-artifact"] },
195
+ {
196
+ name: "stage",
197
+ type: "enum",
198
+ enumValues: ["planner", "architect", "critic", "revision", "adr", "final"],
199
+ appliesToVerbs: ["write-artifact"],
200
+ },
201
+ { name: "stage_n", type: "number", appliesToVerbs: ["write-artifact"] },
202
+ { name: "artifact", type: "string", required: true, appliesToVerbs: ["write-artifact"] },
203
+ { name: "run-id", type: "string", appliesToVerbs: ["write-artifact"] },
204
+ { name: "args", type: "string", planned: true },
205
+ { name: "metadata-json", type: "string", planned: true },
206
+ ],
207
+ retention: [STATE_RETENTION, ARTIFACT_RETENTION, LEDGER_RETENTION, PRUNE_RETENTION, FORCE_RETENTION],
208
+ hudFields: ["current_phase", "mode", "run_id", "stage", "stage_n", "plan_path"],
209
+ graphLabel: "Ralplan",
210
+ }),
211
+ ultragoal: manifest({
212
+ skill: "ultragoal",
213
+ states: ["goal-planning", "pending", "active", "blocked", "failed", "complete", "handoff"],
214
+ terminalStates: ["failed", "complete", "handoff"],
215
+ transitions: [
216
+ { from: "goal-planning", to: "pending", verb: "create-goals" },
217
+ { from: "pending", to: "active", verb: "complete-goals" },
218
+ { from: "active", to: "blocked", verb: "checkpoint" },
219
+ { from: "active", to: "failed", verb: "checkpoint" },
220
+ { from: "active", to: "complete", verb: "checkpoint" },
221
+ { from: "blocked", to: "active", verb: "checkpoint" },
222
+ { from: "failed", to: "active", verb: "complete-goals" },
223
+ { from: "goal-planning", to: "handoff", verb: "handoff" },
224
+ { from: "pending", to: "handoff", verb: "handoff" },
225
+ { from: "active", to: "handoff", verb: "handoff" },
226
+ { from: "blocked", to: "handoff", verb: "handoff" },
227
+ ],
228
+ verbs: [
229
+ ...stateVerbs(),
230
+ ...positionalVerbs([
231
+ "status",
232
+ "create",
233
+ "create-goals",
234
+ "complete-goals",
235
+ "checkpoint",
236
+ "record-review-blockers",
237
+ "steer",
238
+ ]),
239
+ ...plannedVerbs(PLANNED_ADMIN_VERBS),
240
+ ],
241
+ typedArgs: [
242
+ { name: "brief", type: "string", appliesToVerbs: ["create-goals"] },
243
+ { name: "brief-file", type: "string", appliesToVerbs: ["create-goals"] },
244
+ { name: "from-stdin", type: "boolean", appliesToVerbs: ["create-goals"] },
245
+ {
246
+ name: "gjc-goal-mode",
247
+ type: "enum",
248
+ enumValues: ["aggregate", "per-story"],
249
+ appliesToVerbs: ["create-goals"],
250
+ },
251
+ { name: "retry-failed", type: "boolean", appliesToVerbs: ["complete-goals"] },
252
+ { name: "goal-id", type: "string", required: true, appliesToVerbs: ["checkpoint", "record-review-blockers"] },
253
+ {
254
+ name: "status",
255
+ type: "enum",
256
+ enumValues: ["pending", "active", "complete", "failed", "blocked", "review_blocked", "superseded"],
257
+ required: true,
258
+ appliesToVerbs: ["checkpoint"],
259
+ },
260
+ {
261
+ name: "evidence",
262
+ type: "string",
263
+ required: true,
264
+ appliesToVerbs: ["checkpoint", "record-review-blockers", "steer"],
265
+ },
266
+ { name: "gjc-goal-json", type: "string", appliesToVerbs: ["checkpoint", "record-review-blockers"] },
267
+ { name: "quality-gate-json", type: "string", appliesToVerbs: ["checkpoint"] },
268
+ { name: "kind", type: "enum", enumValues: ["add_subgoal"], appliesToVerbs: ["steer"] },
269
+ { name: "title", type: "string", appliesToVerbs: ["record-review-blockers", "steer"] },
270
+ { name: "objective", type: "string", appliesToVerbs: ["record-review-blockers", "steer"] },
271
+ { name: "rationale", type: "string", appliesToVerbs: ["steer"] },
272
+ {
273
+ name: "json",
274
+ type: "boolean",
275
+ appliesToVerbs: [
276
+ "status",
277
+ "create-goals",
278
+ "complete-goals",
279
+ "checkpoint",
280
+ "record-review-blockers",
281
+ "steer",
282
+ ],
283
+ },
284
+ { name: "directive-json", type: "string", appliesToVerbs: ["steer"], planned: true },
285
+ { name: "args", type: "string", planned: true },
286
+ { name: "metadata-json", type: "string", planned: true },
287
+ ],
288
+ retention: [STATE_RETENTION, ARTIFACT_RETENTION, LEDGER_RETENTION, PRUNE_RETENTION, FORCE_RETENTION],
289
+ hudFields: ["current_phase", "active_goal_id", "status", "counts", "ledger_path", "brief_path"],
290
+ graphLabel: "Ultragoal",
291
+ }),
292
+ team: manifest({
293
+ skill: "team",
294
+ states: ["starting", "running", "awaiting_integration", "complete", "failed", "cancelled", "handoff"],
295
+ terminalStates: ["complete", "failed", "cancelled", "handoff"],
296
+ transitions: [
297
+ { from: "starting", to: "running", verb: "start" },
298
+ { from: "starting", to: "failed", verb: "start" },
299
+ { from: "running", to: "awaiting_integration", verb: "api" },
300
+ { from: "running", to: "complete", verb: "shutdown" },
301
+ { from: "running", to: "failed", verb: "shutdown" },
302
+ { from: "running", to: "cancelled", verb: "shutdown" },
303
+ { from: "awaiting_integration", to: "running", verb: "resume" },
304
+ { from: "awaiting_integration", to: "complete", verb: "shutdown" },
305
+ { from: "starting", to: "handoff", verb: "handoff" },
306
+ { from: "running", to: "handoff", verb: "handoff" },
307
+ { from: "awaiting_integration", to: "handoff", verb: "handoff" },
308
+ ],
309
+ verbs: [
310
+ ...stateVerbs(),
311
+ ...positionalVerbs(["start", "list", "status", "monitor", "resume", "shutdown", "api"]),
312
+ ...plannedVerbs(PLANNED_ADMIN_VERBS),
313
+ ],
314
+ typedArgs: [
315
+ { name: "dry-run", type: "boolean", appliesToVerbs: ["start"] },
316
+ { name: "worktree", type: "string", appliesToVerbs: ["start"] },
317
+ { name: "w", type: "string", appliesToVerbs: ["start"] },
318
+ { name: "input", type: "string", required: true, appliesToVerbs: ["api"] },
319
+ {
320
+ name: "operation",
321
+ type: "enum",
322
+ enumValues: [
323
+ "send-message",
324
+ "broadcast",
325
+ "mailbox-list",
326
+ "mailbox-mark-delivered",
327
+ "mailbox-mark-notified",
328
+ "notification-list",
329
+ "notification-read",
330
+ "notification-replay",
331
+ "notification-mark-pane-attempt",
332
+ "worker-startup-ack",
333
+ "create-task",
334
+ "read-task",
335
+ "list-tasks",
336
+ "update-task",
337
+ "claim-task",
338
+ "transition-task-status",
339
+ "transition-task",
340
+ "release-task-claim",
341
+ "read-config",
342
+ "read-manifest",
343
+ "read-worker-status",
344
+ "read-worker-heartbeat",
345
+ "update-worker-heartbeat",
346
+ "write-worker-inbox",
347
+ "write-worker-identity",
348
+ "append-event",
349
+ "read-events",
350
+ "await-event",
351
+ "write-shutdown-request",
352
+ "read-shutdown-ack",
353
+ "read-monitor-snapshot",
354
+ "write-monitor-snapshot",
355
+ "read-task-approval",
356
+ "write-task-approval",
357
+ ],
358
+ required: true,
359
+ appliesToVerbs: ["api"],
360
+ },
361
+ { name: "worker-id", type: "string", appliesToVerbs: ["api"] },
362
+ { name: "task-id", type: "string", appliesToVerbs: ["api"] },
363
+ { name: "claim-token", type: "string", appliesToVerbs: ["api"] },
364
+ {
365
+ name: "status",
366
+ type: "enum",
367
+ enumValues: ["pending", "blocked", "in_progress", "completed", "failed"],
368
+ appliesToVerbs: ["api"],
369
+ },
370
+ { name: "completion_evidence", type: "object", appliesToVerbs: ["api"] },
371
+ { name: "completionEvidence", type: "object", appliesToVerbs: ["api"] },
372
+ { name: "args", type: "string", planned: true },
373
+ { name: "metadata-json", type: "string", planned: true },
374
+ ],
375
+ retention: [
376
+ STATE_RETENTION,
377
+ ARTIFACT_RETENTION,
378
+ LEDGER_RETENTION,
379
+ LOG_RETENTION,
380
+ REPORT_RETENTION,
381
+ AGENTS_RETENTION,
382
+ PRUNE_RETENTION,
383
+ FORCE_RETENTION,
384
+ ],
385
+ hudFields: ["current_phase", "team_name", "workers", "task_counts", "phase", "integration"],
386
+ graphLabel: "Team",
387
+ }),
388
+ };
389
+
390
+ export function getSkillManifest(skill: CanonicalGjcWorkflowSkill): SkillManifest {
391
+ return WORKFLOW_MANIFEST[skill];
392
+ }
393
+
394
+ export function isKnownWorkflowState(skill: CanonicalGjcWorkflowSkill, state: string): boolean {
395
+ return WORKFLOW_MANIFEST[skill].states.some(entry => entry.id === state);
396
+ }
397
+
398
+ export function isValidTransition(skill: CanonicalGjcWorkflowSkill, from: string, to: string): boolean {
399
+ if (from === to) return true;
400
+ return WORKFLOW_MANIFEST[skill].transitions.some(transition => transition.from === from && transition.to === to);
401
+ }
402
+
403
+ export function listVerbs(skill: CanonicalGjcWorkflowSkill): string[] {
404
+ return WORKFLOW_MANIFEST[skill].verbs.map(verb => verb.name);
405
+ }
406
+
407
+ export function typedArgsFor(skill: CanonicalGjcWorkflowSkill, verb: string): TypedArgSpec[] {
408
+ return WORKFLOW_MANIFEST[skill].typedArgs.filter(
409
+ arg => arg.appliesToVerbs === undefined || arg.appliesToVerbs.includes(verb),
410
+ );
411
+ }
412
+
413
+ function stableSort(value: unknown): unknown {
414
+ if (Array.isArray(value)) return value.map(item => stableSort(item));
415
+ if (value === null || typeof value !== "object") return value;
416
+ return Object.fromEntries(
417
+ Object.entries(value)
418
+ .sort(([left], [right]) => left.localeCompare(right))
419
+ .map(([key, item]) => [key, stableSort(item)]),
420
+ );
421
+ }
422
+
423
+ export function serializeManifestProjection(): string {
424
+ return `${JSON.stringify(stableSort(WORKFLOW_MANIFEST), null, 2)}\n`;
425
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Deterministic recovery classifier (pure).
3
+ *
4
+ * Maps a bounded {@link Observation} + remaining retry budget to exactly one
5
+ * {@link RecoveryDecision}. Encodes the plan's hard data-loss invariants:
6
+ * - dirty deltas are NEVER `restart-clean`; they map to `restart-preserve-delta`.
7
+ * - unknown deltas are NEVER destructive; they map to `human-check`.
8
+ * - a deleted/mismatched worktree maps to `human-check` (never recreate over unknown data).
9
+ * `send-enter` is intentionally never emitted: it is unsupported for the gajae-code
10
+ * RPC adapter in v1 (no blind key injection).
11
+ */
12
+ import type { ClassifyInput, RecoveryDecision } from "./types";
13
+
14
+ export function classifyRecovery(input: ClassifyInput): RecoveryDecision {
15
+ const { observation: o, retryBudget: budget } = input;
16
+
17
+ // Deleted worktree / path mismatch — never recreate over unknown data.
18
+ if (o.risk === "deleted-worktree") {
19
+ return {
20
+ classification: "human-check",
21
+ reason: "deleted-worktree-or-path-mismatch",
22
+ severity: "critical",
23
+ ownerRequired: false,
24
+ requiredReceiptFamily: "vanish",
25
+ };
26
+ }
27
+
28
+ if (o.ownerLive) {
29
+ if (o.risk === "prompt-not-accepted") {
30
+ if (budget.reinjectPrompt > 0) {
31
+ return {
32
+ classification: "reinject-prompt",
33
+ reason: "prompt-ack-without-agent-start",
34
+ severity: "warn",
35
+ ownerRequired: true,
36
+ requiredReceiptFamily: "prompt-acceptance",
37
+ };
38
+ }
39
+ return {
40
+ classification: "human-check",
41
+ reason: "prompt-not-accepted-budget-exhausted",
42
+ severity: "critical",
43
+ ownerRequired: false,
44
+ requiredReceiptFamily: null,
45
+ };
46
+ }
47
+ if (o.observedSignals.includes("validation-failed")) {
48
+ if (budget.validationRepair > 0) {
49
+ return {
50
+ classification: "continue",
51
+ reason: "validation-failed-repair-budget-remains",
52
+ severity: "warn",
53
+ ownerRequired: true,
54
+ requiredReceiptFamily: "validation",
55
+ };
56
+ }
57
+ return {
58
+ classification: "human-check",
59
+ reason: "validation-failed-budget-exhausted",
60
+ severity: "critical",
61
+ ownerRequired: false,
62
+ requiredReceiptFamily: "validation",
63
+ };
64
+ }
65
+ return {
66
+ classification: "continue",
67
+ reason: "owner-live-active",
68
+ severity: "info",
69
+ ownerRequired: true,
70
+ requiredReceiptFamily: null,
71
+ };
72
+ }
73
+
74
+ // Owner / RPC vanished — branch on git delta. Every branch requires a `vanish` receipt.
75
+ switch (o.gitDelta) {
76
+ case "dirty":
77
+ if (budget.dirtyVanishPreserve > 0) {
78
+ return {
79
+ classification: "restart-preserve-delta",
80
+ reason: "owner-vanished-dirty-delta",
81
+ severity: "critical",
82
+ ownerRequired: true,
83
+ requiredReceiptFamily: "vanish",
84
+ };
85
+ }
86
+ return {
87
+ classification: "fallback-codex-exec",
88
+ reason: "dirty-vanish-preserve-budget-exhausted",
89
+ severity: "critical",
90
+ ownerRequired: true,
91
+ requiredReceiptFamily: "vanish",
92
+ };
93
+ case "zero-delta":
94
+ if (budget.zeroDeltaVanish > 0) {
95
+ return {
96
+ classification: "restart-clean",
97
+ reason: "owner-vanished-zero-delta",
98
+ severity: "warn",
99
+ ownerRequired: true,
100
+ requiredReceiptFamily: "vanish",
101
+ };
102
+ }
103
+ return {
104
+ classification: "fallback-codex-exec",
105
+ reason: "zero-delta-vanish-budget-exhausted",
106
+ severity: "critical",
107
+ ownerRequired: true,
108
+ requiredReceiptFamily: "vanish",
109
+ };
110
+ case "clean":
111
+ return {
112
+ classification: "restart-clean",
113
+ reason: "owner-vanished-clean",
114
+ severity: "warn",
115
+ ownerRequired: true,
116
+ requiredReceiptFamily: "vanish",
117
+ };
118
+ default:
119
+ // unknown delta — critical, never destructive.
120
+ return {
121
+ classification: "human-check",
122
+ reason: "owner-vanished-unknown-delta",
123
+ severity: "critical",
124
+ ownerRequired: false,
125
+ requiredReceiptFamily: "vanish",
126
+ };
127
+ }
128
+ }
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Per-session control endpoint — a Unix domain socket served by the RuntimeOwner so
3
+ * stateless `gjc harness` CLI calls can route owner-routed primitives (submit, observe,
4
+ * recover, retire) to the live owner. One JSON request line in, one JSON response line out.
5
+ *
6
+ * The owner is the only listener; clients connect per call. When no socket is reachable
7
+ * the caller falls back to the no-owner behavior (read-only observe, owner-not-live submit).
8
+ *
9
+ * FIFO fallback (for platforms/paths where AF_UNIX is unavailable or path-length limited)
10
+ * is a documented seam tracked as an ADR follow-up.
11
+ */
12
+
13
+ import * as fs from "node:fs/promises";
14
+ import * as net from "node:net";
15
+ import * as path from "node:path";
16
+ import { MAX_UNIX_SOCKET_PATH_BYTES } from "./storage";
17
+
18
+ export interface EndpointRequest {
19
+ verb: string;
20
+ input: Record<string, unknown>;
21
+ }
22
+
23
+ export type EndpointHandler = (req: EndpointRequest) => Promise<unknown>;
24
+
25
+ function frame(value: unknown): string {
26
+ return `${JSON.stringify(value)}\n`;
27
+ }
28
+
29
+ export class ControlServer {
30
+ #server: net.Server | null = null;
31
+ constructor(
32
+ readonly socketPath: string,
33
+ private readonly handler: EndpointHandler,
34
+ ) {}
35
+
36
+ async listen(): Promise<void> {
37
+ await fs.mkdir(path.dirname(this.socketPath), { recursive: true });
38
+ if (Buffer.byteLength(this.socketPath) > MAX_UNIX_SOCKET_PATH_BYTES) {
39
+ throw new Error(`socket_path_too_long:${this.socketPath}`);
40
+ }
41
+ await fs.rm(this.socketPath, { force: true });
42
+ await new Promise<void>((resolve, reject) => {
43
+ const server = net.createServer(socket => this.#onConnection(socket));
44
+ server.once("error", reject);
45
+ server.listen(this.socketPath, () => {
46
+ server.removeListener("error", reject);
47
+ this.#server = server;
48
+ resolve();
49
+ });
50
+ });
51
+ }
52
+
53
+ #onConnection(socket: net.Socket): void {
54
+ socket.setEncoding("utf8");
55
+ let buffer = "";
56
+ let handled = false;
57
+ socket.on("data", (chunk: string) => {
58
+ if (handled) return;
59
+ buffer += chunk;
60
+ const idx = buffer.indexOf("\n");
61
+ if (idx < 0) return;
62
+ handled = true;
63
+ const line = buffer.slice(0, idx).trim();
64
+ void this.#dispatch(line)
65
+ .then(response => {
66
+ socket.end(frame(response));
67
+ })
68
+ .catch((error: unknown) => {
69
+ socket.end(frame({ ok: false, error: error instanceof Error ? error.message : String(error) }));
70
+ });
71
+ });
72
+ }
73
+
74
+ async #dispatch(line: string): Promise<unknown> {
75
+ const req = JSON.parse(line) as EndpointRequest;
76
+ if (!req || typeof req.verb !== "string") throw new Error("bad_request");
77
+ return this.handler({ verb: req.verb, input: req.input ?? {} });
78
+ }
79
+
80
+ async close(): Promise<void> {
81
+ const server = this.#server;
82
+ this.#server = null;
83
+ if (server) {
84
+ await new Promise<void>(resolve => server.close(() => resolve()));
85
+ }
86
+ await fs.rm(this.socketPath, { force: true });
87
+ }
88
+ }
89
+
90
+ export class EndpointUnreachableError extends Error {
91
+ constructor(readonly socketPath: string) {
92
+ super(`endpoint_unreachable:${socketPath}`);
93
+ this.name = "EndpointUnreachableError";
94
+ }
95
+ }
96
+
97
+ /** Call the owner's control endpoint. Rejects with {@link EndpointUnreachableError} when no owner listens. */
98
+ export function callEndpoint(socketPath: string, req: EndpointRequest, timeoutMs = 5_000): Promise<unknown> {
99
+ return new Promise((resolve, reject) => {
100
+ const socket = net.connect(socketPath);
101
+ let buffer = "";
102
+ let settled = false;
103
+ const done = (fn: () => void): void => {
104
+ if (settled) return;
105
+ settled = true;
106
+ clearTimeout(timer);
107
+ socket.destroy();
108
+ fn();
109
+ };
110
+ const timer = setTimeout(() => done(() => reject(new Error(`endpoint_timeout:${socketPath}`))), timeoutMs);
111
+ socket.setEncoding("utf8");
112
+ socket.on("connect", () => socket.write(frame(req)));
113
+ socket.on("data", (chunk: string) => {
114
+ buffer += chunk;
115
+ const idx = buffer.indexOf("\n");
116
+ if (idx >= 0) {
117
+ const line = buffer.slice(0, idx).trim();
118
+ done(() => {
119
+ try {
120
+ resolve(JSON.parse(line));
121
+ } catch (error) {
122
+ reject(error instanceof Error ? error : new Error(String(error)));
123
+ }
124
+ });
125
+ }
126
+ });
127
+ socket.on("error", (error: NodeJS.ErrnoException) => {
128
+ done(() => {
129
+ if (error.code === "ENOENT" || error.code === "ECONNREFUSED") {
130
+ reject(new EndpointUnreachableError(socketPath));
131
+ } else {
132
+ reject(error);
133
+ }
134
+ });
135
+ });
136
+ });
137
+ }