@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
@@ -78,7 +78,8 @@
78
78
  "write",
79
79
  "clear",
80
80
  "contract",
81
- "handoff"
81
+ "handoff",
82
+ "doctor"
82
83
  ],
83
84
  "enumValues": [
84
85
  "deep-interview",
@@ -96,6 +97,7 @@
96
97
  "clear",
97
98
  "contract",
98
99
  "handoff",
100
+ "doctor",
99
101
  "kickoff",
100
102
  "write-spec",
101
103
  "write-artifact"
@@ -151,6 +153,26 @@
151
153
  "name": "force",
152
154
  "type": "boolean"
153
155
  },
156
+ {
157
+ "appliesToVerbs": [
158
+ "doctor"
159
+ ],
160
+ "enumValues": [
161
+ "deep-interview",
162
+ "ralplan",
163
+ "ultragoal",
164
+ "team"
165
+ ],
166
+ "name": "skill",
167
+ "type": "enum"
168
+ },
169
+ {
170
+ "appliesToVerbs": [
171
+ "doctor"
172
+ ],
173
+ "name": "json",
174
+ "type": "boolean"
175
+ },
154
176
  {
155
177
  "appliesToVerbs": [
156
178
  "kickoff"
@@ -267,6 +289,10 @@
267
289
  "name": "handoff",
268
290
  "surface": "state-action"
269
291
  },
292
+ {
293
+ "name": "doctor",
294
+ "surface": "state-action"
295
+ },
270
296
  {
271
297
  "name": "kickoff",
272
298
  "surface": "command-flag"
@@ -426,7 +452,8 @@
426
452
  "write",
427
453
  "clear",
428
454
  "contract",
429
- "handoff"
455
+ "handoff",
456
+ "doctor"
430
457
  ],
431
458
  "enumValues": [
432
459
  "deep-interview",
@@ -444,6 +471,7 @@
444
471
  "clear",
445
472
  "contract",
446
473
  "handoff",
474
+ "doctor",
447
475
  "kickoff",
448
476
  "write-spec",
449
477
  "write-artifact"
@@ -499,6 +527,26 @@
499
527
  "name": "force",
500
528
  "type": "boolean"
501
529
  },
530
+ {
531
+ "appliesToVerbs": [
532
+ "doctor"
533
+ ],
534
+ "enumValues": [
535
+ "deep-interview",
536
+ "ralplan",
537
+ "ultragoal",
538
+ "team"
539
+ ],
540
+ "name": "skill",
541
+ "type": "enum"
542
+ },
543
+ {
544
+ "appliesToVerbs": [
545
+ "doctor"
546
+ ],
547
+ "name": "json",
548
+ "type": "boolean"
549
+ },
502
550
  {
503
551
  "appliesToVerbs": [
504
552
  "kickoff"
@@ -604,6 +652,10 @@
604
652
  "name": "handoff",
605
653
  "surface": "state-action"
606
654
  },
655
+ {
656
+ "name": "doctor",
657
+ "surface": "state-action"
658
+ },
607
659
  {
608
660
  "name": "kickoff",
609
661
  "surface": "command-flag"
@@ -783,7 +835,8 @@
783
835
  "write",
784
836
  "clear",
785
837
  "contract",
786
- "handoff"
838
+ "handoff",
839
+ "doctor"
787
840
  ],
788
841
  "enumValues": [
789
842
  "deep-interview",
@@ -801,6 +854,7 @@
801
854
  "clear",
802
855
  "contract",
803
856
  "handoff",
857
+ "doctor",
804
858
  "kickoff",
805
859
  "write-spec",
806
860
  "write-artifact"
@@ -856,6 +910,26 @@
856
910
  "name": "force",
857
911
  "type": "boolean"
858
912
  },
913
+ {
914
+ "appliesToVerbs": [
915
+ "doctor"
916
+ ],
917
+ "enumValues": [
918
+ "deep-interview",
919
+ "ralplan",
920
+ "ultragoal",
921
+ "team"
922
+ ],
923
+ "name": "skill",
924
+ "type": "enum"
925
+ },
926
+ {
927
+ "appliesToVerbs": [
928
+ "doctor"
929
+ ],
930
+ "name": "json",
931
+ "type": "boolean"
932
+ },
859
933
  {
860
934
  "appliesToVerbs": [
861
935
  "start"
@@ -1010,6 +1084,10 @@
1010
1084
  "name": "handoff",
1011
1085
  "surface": "state-action"
1012
1086
  },
1087
+ {
1088
+ "name": "doctor",
1089
+ "surface": "state-action"
1090
+ },
1013
1091
  {
1014
1092
  "name": "start",
1015
1093
  "surface": "command-positional"
@@ -1196,7 +1274,8 @@
1196
1274
  "write",
1197
1275
  "clear",
1198
1276
  "contract",
1199
- "handoff"
1277
+ "handoff",
1278
+ "doctor"
1200
1279
  ],
1201
1280
  "enumValues": [
1202
1281
  "deep-interview",
@@ -1214,6 +1293,7 @@
1214
1293
  "clear",
1215
1294
  "contract",
1216
1295
  "handoff",
1296
+ "doctor",
1217
1297
  "kickoff",
1218
1298
  "write-spec",
1219
1299
  "write-artifact"
@@ -1269,6 +1349,26 @@
1269
1349
  "name": "force",
1270
1350
  "type": "boolean"
1271
1351
  },
1352
+ {
1353
+ "appliesToVerbs": [
1354
+ "doctor"
1355
+ ],
1356
+ "enumValues": [
1357
+ "deep-interview",
1358
+ "ralplan",
1359
+ "ultragoal",
1360
+ "team"
1361
+ ],
1362
+ "name": "skill",
1363
+ "type": "enum"
1364
+ },
1365
+ {
1366
+ "appliesToVerbs": [
1367
+ "doctor"
1368
+ ],
1369
+ "name": "json",
1370
+ "type": "boolean"
1371
+ },
1272
1372
  {
1273
1373
  "appliesToVerbs": [
1274
1374
  "create-goals"
@@ -1444,6 +1544,10 @@
1444
1544
  "name": "handoff",
1445
1545
  "surface": "state-action"
1446
1546
  },
1547
+ {
1548
+ "name": "doctor",
1549
+ "surface": "state-action"
1550
+ },
1447
1551
  {
1448
1552
  "name": "status",
1449
1553
  "surface": "command-positional"
@@ -64,7 +64,7 @@ const AGENTS_RETENTION: RetentionPolicy = { category: "agents" };
64
64
  const PRUNE_RETENTION: RetentionPolicy = { category: "prune/delete", maxAgeDays: 30 };
65
65
  const FORCE_RETENTION: RetentionPolicy = { category: "force", maxAgeDays: 90 };
66
66
 
67
- const STATE_VERBS = ["read", "write", "clear", "contract", "handoff"] as const;
67
+ const STATE_VERBS = ["read", "write", "clear", "contract", "handoff", "doctor"] as const;
68
68
  const PLANNED_ADMIN_VERBS = ["graph", "prune", "migrate", "force-overwrite"] as const;
69
69
 
70
70
  const COMMON_TYPED_ARGS: TypedArgSpec[] = [
@@ -82,6 +82,8 @@ const COMMON_TYPED_ARGS: TypedArgSpec[] = [
82
82
  },
83
83
  { name: "replace", type: "boolean", appliesToVerbs: ["write"] },
84
84
  { name: "force", type: "boolean", appliesToVerbs: ["write", "clear", "handoff"] },
85
+ { name: "skill", type: "enum", enumValues: [...CANONICAL_GJC_WORKFLOW_SKILLS], appliesToVerbs: ["doctor"] },
86
+ { name: "json", type: "boolean", appliesToVerbs: ["doctor"] },
85
87
  ];
86
88
 
87
89
  function verb(name: string, surface: WorkflowVerb["surface"]): WorkflowVerb {
@@ -88,13 +88,16 @@ export class ControlServer {
88
88
  }
89
89
 
90
90
  export class EndpointUnreachableError extends Error {
91
- constructor(readonly socketPath: string) {
92
- super(`endpoint_unreachable:${socketPath}`);
91
+ constructor(
92
+ readonly socketPath: string,
93
+ readonly reason = "unreachable",
94
+ ) {
95
+ super(`endpoint_${reason}:${socketPath}`);
93
96
  this.name = "EndpointUnreachableError";
94
97
  }
95
98
  }
96
99
 
97
- /** Call the owner's control endpoint. Rejects with {@link EndpointUnreachableError} when no owner listens. */
100
+ /** Call the owner's control endpoint. Rejects with {@link EndpointUnreachableError} when no owner listens or responds. */
98
101
  export function callEndpoint(socketPath: string, req: EndpointRequest, timeoutMs = 5_000): Promise<unknown> {
99
102
  return new Promise((resolve, reject) => {
100
103
  const socket = net.connect(socketPath);
@@ -107,7 +110,10 @@ export function callEndpoint(socketPath: string, req: EndpointRequest, timeoutMs
107
110
  socket.destroy();
108
111
  fn();
109
112
  };
110
- const timer = setTimeout(() => done(() => reject(new Error(`endpoint_timeout:${socketPath}`))), timeoutMs);
113
+ const timer = setTimeout(
114
+ () => done(() => reject(new EndpointUnreachableError(socketPath, "timeout"))),
115
+ timeoutMs,
116
+ );
111
117
  socket.setEncoding("utf8");
112
118
  socket.on("connect", () => socket.write(frame(req)));
113
119
  socket.on("data", (chunk: string) => {
@@ -118,16 +124,21 @@ export function callEndpoint(socketPath: string, req: EndpointRequest, timeoutMs
118
124
  done(() => {
119
125
  try {
120
126
  resolve(JSON.parse(line));
121
- } catch (error) {
122
- reject(error instanceof Error ? error : new Error(String(error)));
127
+ } catch {
128
+ reject(new EndpointUnreachableError(socketPath, "bad_frame"));
123
129
  }
124
130
  });
125
131
  }
126
132
  });
127
133
  socket.on("error", (error: NodeJS.ErrnoException) => {
128
134
  done(() => {
129
- if (error.code === "ENOENT" || error.code === "ECONNREFUSED") {
130
- reject(new EndpointUnreachableError(socketPath));
135
+ if (
136
+ error.code === "ENOENT" ||
137
+ error.code === "ECONNREFUSED" ||
138
+ error.code === "ECONNRESET" ||
139
+ error.code === "EPIPE"
140
+ ) {
141
+ reject(new EndpointUnreachableError(socketPath, error.code.toLowerCase()));
131
142
  } else {
132
143
  reject(error);
133
144
  }
@@ -52,6 +52,31 @@ import {
52
52
  import type { EventEnvelope, GitDelta, Observation, PrimitiveResponse, SessionState, Severity } from "./types";
53
53
  import { DEFAULT_RETRY_BUDGET, OBSERVED_SIGNALS } from "./types";
54
54
 
55
+ function isStartupLivenessBlocker(blocker: string): boolean {
56
+ return blocker === "detached-owner-not-live";
57
+ }
58
+
59
+ function isOwnerVanishedBlocker(blocker: string): boolean {
60
+ return blocker.startsWith("owner-vanished:");
61
+ }
62
+
63
+ function reconcileLiveOwnerState(state: SessionState): { state: SessionState; reconciled: boolean } {
64
+ const blockers = state.blockers.filter(blocker => !isStartupLivenessBlocker(blocker));
65
+ const hadLivenessBlocker = blockers.length !== state.blockers.length;
66
+ const lifecycle =
67
+ hadLivenessBlocker && state.lifecycle === "blocked" && blockers.length === 0 ? "observing" : state.lifecycle;
68
+ if (!hadLivenessBlocker && lifecycle === state.lifecycle) return { state, reconciled: false };
69
+ return {
70
+ state: {
71
+ ...state,
72
+ lifecycle,
73
+ blockers,
74
+ updatedAt: new Date().toISOString(),
75
+ },
76
+ reconciled: true,
77
+ };
78
+ }
79
+
55
80
  export interface OwnerOptions {
56
81
  root: string;
57
82
  sessionId: string;
@@ -139,6 +164,11 @@ export class RuntimeOwner {
139
164
  async #loadState(): Promise<SessionState> {
140
165
  const state = await readSessionState(this.#opts.root, this.#opts.sessionId);
141
166
  if (!state) throw new Error(`session_not_found:${this.#opts.sessionId}`);
167
+ const reconciled = reconcileLiveOwnerState(state);
168
+ if (reconciled.reconciled) {
169
+ await writeSessionState(this.#opts.root, reconciled.state);
170
+ return reconciled.state;
171
+ }
142
172
  return state;
143
173
  }
144
174
 
@@ -369,17 +399,22 @@ export class RuntimeOwner {
369
399
 
370
400
  async #recover(): Promise<PrimitiveResponse> {
371
401
  const obs = await this.#observeGit();
372
- const decision = classifyRecovery({ observation: obs, retryBudget: { ...DEFAULT_RETRY_BUDGET } });
402
+ const state = await this.#loadState();
403
+ const recoveringPriorVanish = state.blockers.some(isOwnerVanishedBlocker);
404
+ const recoveryObservation: Observation = recoveringPriorVanish
405
+ ? { ...obs, ownerLive: false, risk: obs.gitDelta === "dirty" ? "vanished-dirty" : obs.risk }
406
+ : obs;
407
+ const decision = classifyRecovery({ observation: recoveryObservation, retryBudget: { ...DEFAULT_RETRY_BUDGET } });
373
408
  let vanishReceiptId: string | null = null;
374
409
  if (requiresVanishBeforeAction(decision.classification)) {
375
- const dirty = obs.gitDelta === "dirty" || obs.gitDelta === "unknown";
376
- const p = dirty ? preserveDirtyWorktree(obs.cwd) : null;
410
+ const dirty = recoveryObservation.gitDelta === "dirty" || recoveryObservation.gitDelta === "unknown";
411
+ const p = dirty ? preserveDirtyWorktree(recoveryObservation.cwd) : null;
377
412
  const evidence: VanishEvidence = {
378
413
  classification: decision.classification,
379
- gitDelta: obs.gitDelta,
414
+ gitDelta: recoveryObservation.gitDelta,
380
415
  gitStatusPorcelain: p
381
416
  ? `tracked:${p.trackedDiffSha256};untracked:${p.untrackedManifest.length}`
382
- : obs.observedSignals.join(","),
417
+ : recoveryObservation.observedSignals.join(","),
383
418
  untrackedManifest: p?.untrackedManifest ?? [],
384
419
  preservation: p?.stashRef ? "stash" : "snapshot",
385
420
  stashRef: p?.stashRef ?? null,
@@ -391,15 +426,25 @@ export class RuntimeOwner {
391
426
  sessionId: this.#opts.sessionId,
392
427
  family: "vanish",
393
428
  source: "owner",
394
- subject: { workspace: obs.cwd, branch: obs.branch, head: null, commit: null },
429
+ subject: {
430
+ workspace: recoveryObservation.cwd,
431
+ branch: recoveryObservation.branch,
432
+ head: null,
433
+ commit: null,
434
+ },
395
435
  evidence,
396
436
  });
397
437
  await writeReceiptImmutable(this.#opts.root, this.#opts.sessionId, "vanish", receipt.receiptId, receipt);
398
438
  vanishReceiptId = receipt.receiptId;
399
439
  }
400
- const state = await this.#loadState();
440
+ if (vanishReceiptId) {
441
+ state.blockers = state.blockers.filter(blocker => !isOwnerVanishedBlocker(blocker));
442
+ state.lifecycle = state.blockers.length === 0 ? "observing" : state.lifecycle;
443
+ state.updatedAt = new Date(this.#opts.clock ? this.#opts.clock() : Date.now()).toISOString();
444
+ await writeSessionState(this.#opts.root, state);
445
+ }
401
446
  await this.#emit(decision.severity, "recover_classified", { classification: decision.classification });
402
- return this.#response(state, { decision, observation: obs, vanishReceiptId });
447
+ return this.#response(state, { decision, observation: recoveryObservation, vanishReceiptId });
403
448
  }
404
449
 
405
450
  async #operate(input: Record<string, unknown>): Promise<PrimitiveResponse> {
@@ -475,12 +520,14 @@ export class RuntimeOwner {
475
520
 
476
521
  async #submit(input: Record<string, unknown>): Promise<PrimitiveResponse> {
477
522
  const prompt = typeof input.prompt === "string" ? input.prompt : "";
523
+ const state = await this.#loadState();
478
524
  if (!prompt) {
479
- const state = await this.#loadState();
480
525
  return this.#response(state, { accepted: false, reason: "empty-prompt" }, false);
481
526
  }
527
+ if (state.lifecycle === "blocked") {
528
+ return this.#response(state, { accepted: false, reason: "lifecycle-blocked" }, false);
529
+ }
482
530
  const result = await singleFlightAccept(this.#opts.rpc, prompt, this.#opts.acceptanceTimeoutMs);
483
- const state = await this.#loadState();
484
531
  if (result.accepted) {
485
532
  state.lifecycle = "observing";
486
533
  state.updatedAt = new Date(this.#opts.clock ? this.#opts.clock() : Date.now()).toISOString();
@@ -57,8 +57,9 @@ export function nextAllowedActions(lifecycle: HarnessLifecycle, ownerLive: boole
57
57
  // `start` creates a new session; never re-applicable to an existing record.
58
58
  add("start", false, "session-already-exists");
59
59
 
60
- // `submit` is owner-routed: it requires a live owner and a non-terminal lifecycle.
60
+ // `submit` is owner-routed: it requires a live owner and a non-blocked, non-terminal lifecycle.
61
61
  if (terminal) add("submit", false, `lifecycle-terminal:${lifecycle}`);
62
+ else if (lifecycle === "blocked") add("submit", false, "lifecycle-blocked");
62
63
  else if (!ownerLive) add("submit", false, "owner-not-live");
63
64
  else add("submit", true);
64
65