@gajae-code/coding-agent 0.11.3 → 0.11.5

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 +47 -0
  2. package/dist/types/cli/notify-cli.d.ts +5 -1
  3. package/dist/types/cli/worktree-cli.d.ts +8 -25
  4. package/dist/types/commands/gc.d.ts +4 -0
  5. package/dist/types/commands/worktree.d.ts +7 -32
  6. package/dist/types/config/settings-schema.d.ts +20 -0
  7. package/dist/types/export/html/template.generated.d.ts +1 -1
  8. package/dist/types/gjc-runtime/deep-interview-state.d.ts +28 -0
  9. package/dist/types/gjc-runtime/gc-runtime.d.ts +9 -0
  10. package/dist/types/gjc-runtime/launch-tmux.d.ts +3 -0
  11. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +42 -0
  12. package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +41 -0
  13. package/dist/types/gjc-runtime/state-argv.d.ts +38 -0
  14. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +9 -0
  15. package/dist/types/gjc-runtime/ultragoal-owner-loss-recovery.d.ts +94 -0
  16. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
  17. package/dist/types/harness-control-plane/owner.d.ts +3 -1
  18. package/dist/types/internal-urls/local-protocol.d.ts +18 -0
  19. package/dist/types/main.d.ts +2 -2
  20. package/dist/types/modes/components/ansi-display-validator.d.ts +2 -0
  21. package/dist/types/modes/components/custom-editor.d.ts +8 -3
  22. package/dist/types/modes/components/notifications-settings-editor.d.ts +4 -0
  23. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -1
  24. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  25. package/dist/types/modes/components/tool-transcript-format.d.ts +77 -0
  26. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +3 -0
  27. package/dist/types/modes/controllers/goal-mode-controller.d.ts +8 -1
  28. package/dist/types/modes/controllers/input-controller.d.ts +8 -2
  29. package/dist/types/modes/controllers/selector-controller.d.ts +15 -0
  30. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +3 -0
  31. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +16 -0
  32. package/dist/types/plan-mode/approved-plan.d.ts +1 -0
  33. package/dist/types/runtime-mcp/manager.d.ts +2 -0
  34. package/dist/types/runtime-mcp/oauth-discovery.d.ts +12 -1
  35. package/dist/types/sdk/broker/broker.d.ts +1 -0
  36. package/dist/types/sdk/broker/discovery.d.ts +10 -0
  37. package/dist/types/sdk/broker/ensure.d.ts +24 -1
  38. package/dist/types/sdk/broker/internal.d.ts +3 -0
  39. package/dist/types/sdk/broker/session-index.d.ts +12 -0
  40. package/dist/types/sdk/broker/state-version.d.ts +2 -1
  41. package/dist/types/sdk/bus/chat-daemon-cli.d.ts +3 -1
  42. package/dist/types/sdk/bus/chat-daemon-control.d.ts +39 -4
  43. package/dist/types/sdk/bus/config-commands.d.ts +8 -0
  44. package/dist/types/sdk/bus/config.d.ts +22 -0
  45. package/dist/types/sdk/bus/index.d.ts +162 -2
  46. package/dist/types/sdk/bus/notification-service.d.ts +71 -8
  47. package/dist/types/sdk/bus/session-control.d.ts +11 -8
  48. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +5 -0
  49. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -3
  50. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +23 -4
  51. package/dist/types/sdk/bus/telegram-daemon.d.ts +214 -20
  52. package/dist/types/sdk/session.d.ts +16 -1
  53. package/dist/types/secrets/index.d.ts +4 -2
  54. package/dist/types/secrets/obfuscator.d.ts +1 -1
  55. package/dist/types/session/agent-session.d.ts +31 -2
  56. package/dist/types/session/artifacts.d.ts +30 -8
  57. package/dist/types/session/internal/managed-session-scope.d.ts +7 -0
  58. package/dist/types/session/internal/managed-session-storage.d.ts +63 -6
  59. package/dist/types/session/session-manager.d.ts +88 -18
  60. package/dist/types/session/session-storage.d.ts +27 -0
  61. package/dist/types/session/streaming-output.d.ts +4 -0
  62. package/dist/types/task/executor.d.ts +9 -0
  63. package/dist/types/tool-discovery/tool-index.d.ts +2 -0
  64. package/dist/types/tools/ask.d.ts +87 -12
  65. package/dist/types/tools/bash-skill-urls.d.ts +2 -2
  66. package/dist/types/tools/browser/screenshot-format.d.ts +8 -0
  67. package/dist/types/tools/browser/tab-protocol.d.ts +11 -0
  68. package/dist/types/tools/browser/tab-supervisor.d.ts +1 -1
  69. package/dist/types/tools/browser/tab-worker-path-resolver.d.ts +2 -0
  70. package/dist/types/tools/browser/tab-worker-smoke.d.ts +16 -0
  71. package/dist/types/tools/index.d.ts +4 -0
  72. package/dist/types/tools/path-utils.d.ts +9 -14
  73. package/dist/types/tools/render-utils.d.ts +1 -8
  74. package/dist/types/tools/resource-gc.d.ts +14 -0
  75. package/dist/types/utils/image-loading.d.ts +23 -0
  76. package/dist/types/utils/image-resize.d.ts +2 -0
  77. package/dist/types/utils/pasted-image-loading.d.ts +39 -0
  78. package/dist/types/utils/pasted-image-path.d.ts +21 -19
  79. package/dist/types/web/scrapers/html-to-markdown.d.ts +5 -0
  80. package/dist/types/web/scrapers/types.d.ts +1 -5
  81. package/package.json +25 -9
  82. package/scripts/generate-sdk-operation-inventory.ts +2 -0
  83. package/scripts/run-threat-model-check.ts +60 -0
  84. package/src/cli/notify-cli.ts +40 -18
  85. package/src/cli/worktree-cli.ts +9 -287
  86. package/src/cli.ts +10 -9
  87. package/src/commands/gc.ts +11 -1
  88. package/src/commands/sdk.ts +10 -4
  89. package/src/commands/worktree.ts +10 -53
  90. package/src/config/model-profiles.ts +15 -15
  91. package/src/config/settings-schema.ts +17 -0
  92. package/src/daemon/runtime.ts +2 -2
  93. package/src/defaults/gjc/skills/deep-interview/SKILL.md +70 -49
  94. package/src/defaults/gjc/skills/ralplan/SKILL.md +0 -6
  95. package/src/defaults/gjc/skills/team/SKILL.md +7 -9
  96. package/src/defaults/gjc/skills/ultragoal/SKILL.md +0 -2
  97. package/src/eval/js/shared/runtime.ts +1 -1
  98. package/src/eval/py/index.ts +2 -2
  99. package/src/export/html/template.generated.ts +1 -1
  100. package/src/extensibility/plugins/marketplace/manager.ts +94 -10
  101. package/src/gjc-runtime/deep-interview-recorder.ts +7 -0
  102. package/src/gjc-runtime/deep-interview-runtime.ts +10 -0
  103. package/src/gjc-runtime/deep-interview-state.ts +127 -3
  104. package/src/gjc-runtime/gc-render.ts +19 -1
  105. package/src/gjc-runtime/gc-runtime.ts +68 -3
  106. package/src/gjc-runtime/launch-tmux.ts +61 -6
  107. package/src/gjc-runtime/managed-owner-admission.ts +352 -0
  108. package/src/gjc-runtime/managed-owner-supervisor.ts +162 -0
  109. package/src/gjc-runtime/state-argv.ts +292 -0
  110. package/src/gjc-runtime/state-runtime.ts +80 -222
  111. package/src/gjc-runtime/team-runtime.ts +1 -1
  112. package/src/gjc-runtime/tmux-owner-isolation.ts +117 -0
  113. package/src/gjc-runtime/ultragoal-owner-loss-recovery.ts +328 -0
  114. package/src/gjc-runtime/ultragoal-runtime.ts +14 -0
  115. package/src/harness-control-plane/owner.ts +59 -3
  116. package/src/hindsight/client.ts +65 -2
  117. package/src/internal-urls/agent-protocol.ts +76 -5
  118. package/src/internal-urls/docs-index.generated.ts +7 -6
  119. package/src/internal-urls/local-protocol.ts +416 -18
  120. package/src/lsp/index.ts +4 -88
  121. package/src/main.ts +37 -13
  122. package/src/modes/acp/acp-agent.ts +8 -0
  123. package/src/modes/components/ansi-display-validator.ts +54 -0
  124. package/src/modes/components/custom-editor.ts +103 -32
  125. package/src/modes/components/custom-provider-wizard.ts +26 -1
  126. package/src/modes/components/notifications-settings-editor.ts +26 -2
  127. package/src/modes/components/session-observer-overlay.ts +40 -37
  128. package/src/modes/components/settings-selector.ts +3 -1
  129. package/src/modes/components/tool-transcript-format.ts +393 -0
  130. package/src/modes/components/transcript-viewer-overlay.ts +63 -1
  131. package/src/modes/controllers/goal-mode-controller.ts +20 -1
  132. package/src/modes/controllers/input-controller.ts +98 -37
  133. package/src/modes/controllers/plan-mode-controller.ts +2 -0
  134. package/src/modes/controllers/runtime-mcp-command-controller.ts +3 -0
  135. package/src/modes/controllers/selector-controller.ts +71 -9
  136. package/src/modes/interactive-mode.ts +14 -2
  137. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -1
  138. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +17 -1
  139. package/src/modes/shared/agent-wire/workflow-gate-schema.ts +4 -2
  140. package/src/modes/shared/agent-wire/workflow-gate-types.ts +120 -3
  141. package/src/plan-mode/approved-plan.ts +3 -1
  142. package/src/prompts/agents/architect.md +1 -0
  143. package/src/prompts/agents/critic.md +1 -0
  144. package/src/prompts/system/custom-system-prompt.md +1 -1
  145. package/src/prompts/system/system-prompt.md +2 -1
  146. package/src/prompts/tools/lsp.md +3 -3
  147. package/src/runtime-mcp/manager.ts +16 -1
  148. package/src/runtime-mcp/oauth-discovery.ts +116 -26
  149. package/src/sdk/broker/broker.ts +139 -32
  150. package/src/sdk/broker/discovery.ts +125 -0
  151. package/src/sdk/broker/ensure.ts +103 -19
  152. package/src/sdk/broker/internal.ts +10 -0
  153. package/src/sdk/broker/session-index.ts +309 -38
  154. package/src/sdk/broker/state-version.ts +5 -2
  155. package/src/sdk/bus/chat-daemon-cli.ts +72 -10
  156. package/src/sdk/bus/chat-daemon-control.ts +526 -130
  157. package/src/sdk/bus/chat-daemon-runtime.ts +1 -0
  158. package/src/sdk/bus/config-commands.ts +22 -0
  159. package/src/sdk/bus/config.ts +37 -0
  160. package/src/sdk/bus/index.ts +384 -92
  161. package/src/sdk/bus/lifecycle-control-runtime.ts +53 -3
  162. package/src/sdk/bus/notification-service.ts +284 -62
  163. package/src/sdk/bus/recent-activity.ts +14 -5
  164. package/src/sdk/bus/session-control.ts +150 -44
  165. package/src/sdk/bus/telegram-daemon-cli.ts +20 -14
  166. package/src/sdk/bus/telegram-daemon-contract.ts +6 -3
  167. package/src/sdk/bus/telegram-daemon-control.ts +294 -76
  168. package/src/sdk/bus/telegram-daemon.ts +1842 -281
  169. package/src/sdk/protocol/operation-inventory.generated.json +11 -0
  170. package/src/sdk/session.ts +155 -27
  171. package/src/secrets/index.ts +53 -10
  172. package/src/secrets/obfuscator.ts +21 -18
  173. package/src/secrets/regex.ts +2 -0
  174. package/src/session/agent-session.ts +495 -253
  175. package/src/session/artifacts.ts +188 -23
  176. package/src/session/context-estimation.ts +7 -2
  177. package/src/session/internal/managed-session-scope.ts +215 -39
  178. package/src/session/internal/managed-session-storage.ts +1152 -46
  179. package/src/session/session-manager.ts +1055 -148
  180. package/src/session/session-storage.ts +109 -11
  181. package/src/session/streaming-output.ts +6 -0
  182. package/src/task/executor.ts +71 -26
  183. package/src/task/index.ts +63 -20
  184. package/src/tool-discovery/tool-index.ts +18 -0
  185. package/src/tools/ask.ts +329 -57
  186. package/src/tools/bash-allowed-prefixes.ts +79 -29
  187. package/src/tools/bash-skill-urls.ts +14 -3
  188. package/src/tools/bash.ts +2 -2
  189. package/src/tools/browser/launch.ts +3 -1
  190. package/src/tools/browser/readable.ts +1 -1
  191. package/src/tools/browser/screenshot-format.ts +34 -0
  192. package/src/tools/browser/tab-protocol.ts +3 -0
  193. package/src/tools/browser/tab-supervisor.ts +125 -122
  194. package/src/tools/browser/tab-worker-entry.ts +33 -14
  195. package/src/tools/browser/tab-worker-path-resolver.ts +67 -0
  196. package/src/tools/browser/tab-worker-smoke.ts +87 -0
  197. package/src/tools/browser/tab-worker.ts +7 -5
  198. package/src/tools/index.ts +4 -0
  199. package/src/tools/path-utils.ts +42 -35
  200. package/src/tools/plan-mode-guard.ts +1 -0
  201. package/src/tools/read.ts +7 -1
  202. package/src/tools/render-utils.ts +1 -27
  203. package/src/tools/resource-gc.ts +123 -34
  204. package/src/tools/sqlite-reader.ts +3 -2
  205. package/src/tools/subagent.ts +2 -0
  206. package/src/utils/image-loading.ts +85 -34
  207. package/src/utils/image-resize.ts +16 -9
  208. package/src/utils/pasted-image-loading.ts +514 -0
  209. package/src/utils/pasted-image-path.ts +223 -98
  210. package/src/web/scrapers/html-to-markdown.ts +68 -0
  211. package/src/web/scrapers/readthedocs.ts +1 -1
  212. package/src/web/scrapers/types.ts +1 -68
  213. package/src/web/search/providers/openai-compatible.ts +76 -23
@@ -29,9 +29,13 @@ import {
29
29
  import { renderCliWriteReceipt } from "./cli-write-receipt";
30
30
  import { applyAmbiguityFloorToEnvelope } from "./deep-interview-ambiguity";
31
31
  import {
32
+ assertDeepInterviewEnvelopeInputLimits,
33
+ assertDeepInterviewInputWithinLimit,
32
34
  assertDeepInterviewIntentManifest,
33
35
  assertDeepInterviewIntentReview,
36
+ assertDeepInterviewStructuredResponseWithinLimit,
34
37
  type DeepInterviewIntentManifest,
38
+ MAX_DEEP_INTERVIEW_STRUCTURED_RESPONSE_LENGTH,
35
39
  mergeDeepInterviewEnvelope,
36
40
  normalizeDeepInterviewEnvelope,
37
41
  } from "./deep-interview-state";
@@ -42,6 +46,7 @@ import {
42
46
  SessionResolutionError,
43
47
  writeSessionActivityMarker,
44
48
  } from "./session-resolution";
49
+ import { classifyStateArgv, firstStateFlagValue, type StateAction, type StateArgvClassification } from "./state-argv";
45
50
  import { renderStateGraph, type StateGraphFormat } from "./state-graph";
46
51
  import { migrateAndPersistLegacyState, migrateWorkflowState } from "./state-migrations";
47
52
  import {
@@ -72,7 +77,7 @@ import {
72
77
  writeGuardedWorkflowEnvelopeAtomic,
73
78
  } from "./state-writer";
74
79
  import { assertSafePathComponent, CommandError, flagValue, hasFlag, isPlainObject } from "./workflow-cli-common";
75
- import { getSkillManifest, isKnownWorkflowState, isValidTransition, typedArgsFor } from "./workflow-manifest";
80
+ import { getSkillManifest, isKnownWorkflowState, isValidTransition } from "./workflow-manifest";
76
81
 
77
82
  /**
78
83
  * Native implementation of the `gjc state read|write|clear` command surface.
@@ -99,136 +104,9 @@ class StateCommandError extends CommandError {
99
104
  }
100
105
 
101
106
  const GRAPH_FORMATS = new Set(["ascii", "mermaid", "dot"]);
102
- const FLAGS_WITH_VALUES = new Set([
103
- "--input",
104
- "--mode",
105
- "--session-id",
106
- "--thread-id",
107
- "--turn-id",
108
- "--to",
109
- "--skill",
110
- "--format",
111
- "--older-than",
112
- "--status",
113
- "--fields",
114
- "--since",
115
- "--limit",
116
- ]);
117
- const ACTION_NAMES = new Set([
118
- "read",
119
- "write",
120
- "clear",
121
- "contract",
122
- "handoff",
123
- "graph",
124
- "prune",
125
- "gc",
126
- "migrate",
127
- "status",
128
- "doctor",
129
- ]);
130
- const BOOLEAN_FLAGS = new Set([
131
- "--json",
132
- "--replace",
133
- "--hard",
134
- "--dry-run",
135
- "--migrate",
136
- "--compact",
137
- "--history",
138
- "--force",
139
- ]);
140
- const VERB_SPECIFIC_FLAGS = new Set([
141
- "--skill",
142
- "--format",
143
- "--older-than",
144
- "--status",
145
- "--fields",
146
- "--since",
147
- "--limit",
148
- "--history",
149
- ]);
150
-
151
- function flagName(arg: string): string | undefined {
152
- if (!arg.startsWith("--")) return undefined;
153
- const equalsIndex = arg.indexOf("=");
154
- return equalsIndex >= 0 ? arg.slice(0, equalsIndex) : arg;
155
- }
156
-
157
- function manifestFlagNames(action: ParsedInvocation["action"], positionalSkill: string | undefined): Set<string> {
158
- const names = new Set<string>();
159
- const skills =
160
- positionalSkill && KNOWN_MODES.includes(positionalSkill)
161
- ? [positionalSkill as CanonicalGjcWorkflowSkill]
162
- : CANONICAL_GJC_WORKFLOW_SKILLS;
163
- for (const skill of skills) {
164
- for (const arg of typedArgsFor(skill, action)) names.add(`--${arg.name}`);
165
- }
166
- return names;
167
- }
168
-
169
- function assertKnownFlags(args: readonly string[], parsed: ParsedInvocation): void {
170
- const manifestFlags = manifestFlagNames(parsed.action, parsed.positionalSkill);
171
- for (const arg of args) {
172
- const flag = flagName(arg);
173
- if (!flag) continue;
174
- if (
175
- FLAGS_WITH_VALUES.has(flag) ||
176
- BOOLEAN_FLAGS.has(flag) ||
177
- VERB_SPECIFIC_FLAGS.has(flag) ||
178
- manifestFlags.has(flag)
179
- ) {
180
- continue;
181
- }
182
- throw new StateCommandError(2, `unknown gjc state flag: ${flag}`);
183
- }
184
- }
185
-
186
- interface ParsedInvocation {
187
- action:
188
- | "read"
189
- | "write"
190
- | "clear"
191
- | "contract"
192
- | "handoff"
193
- | "graph"
194
- | "prune"
195
- | "gc"
196
- | "migrate"
197
- | "status"
198
- | "doctor";
199
- positionalSkill?: string;
200
- }
201
-
202
- function parsePositionalArgs(args: readonly string[]): ParsedInvocation {
203
- let skipNext = false;
204
- const positional: string[] = [];
205
- for (const arg of args) {
206
- if (skipNext) {
207
- skipNext = false;
208
- continue;
209
- }
210
- if (FLAGS_WITH_VALUES.has(arg)) {
211
- skipNext = true;
212
- continue;
213
- }
214
- if (!arg.startsWith("-")) positional.push(arg);
215
- }
216
- // Documented argv shapes:
217
- // gjc state read|write|clear|contract ...
218
- // gjc state <skill> read|write|contract ...
219
- const first = positional[0];
220
- const second = positional[1];
221
- if (first && ACTION_NAMES.has(first)) {
222
- return { action: first as ParsedInvocation["action"], positionalSkill: second };
223
- }
224
- if (first && second && ACTION_NAMES.has(second)) {
225
- return { action: second as ParsedInvocation["action"], positionalSkill: first };
226
- }
227
- // `gjc state <skill>` alone defaults to read for that skill.
228
- if (first && !second) {
229
- return { action: "read", positionalSkill: first };
230
- }
231
- return { action: "read" };
107
+ function assertKnownFlags(classification: StateArgvClassification): void {
108
+ const [unknownFlag] = classification.unknownFlags;
109
+ if (unknownFlag) throw new StateCommandError(2, `unknown gjc state flag: ${unknownFlag}`);
232
110
  }
233
111
 
234
112
  function isKnownMode(mode: string): mode is CanonicalGjcWorkflowSkill {
@@ -279,19 +157,16 @@ interface ResolvedSelectors {
279
157
  // `clear` resolves like a read (explicit -> payload -> env -> latest-activity marker)
280
158
  // per the spec: read/status/clear may fall back to the most-recent session. Commands
281
159
  // that create or mutate new state roots still require an explicit/env session id.
282
- const WRITE_SESSION_ACTIONS = new Set<ParsedInvocation["action"]>(["write", "handoff", "prune", "migrate"]);
160
+ const WRITE_SESSION_ACTIONS = new Set<StateAction>(["write", "handoff", "prune", "migrate"]);
283
161
 
284
- async function resolveSelectors(
285
- args: readonly string[],
286
- cwd: string,
287
- positionalSkill: string | undefined,
288
- action: ParsedInvocation["action"],
289
- ): Promise<ResolvedSelectors> {
290
- const payload = await readInputJson(flagValue(args, "--input"), cwd);
162
+ async function resolveSelectors(args: readonly string[], cwd: string, action: StateAction): Promise<ResolvedSelectors> {
163
+ const classification = classifyStateArgv(args);
164
+ const payload = await readInputJson(firstStateFlagValue(classification, "--input"), cwd);
291
165
 
166
+ const [modeCandidate, positionalCandidate] = classification.runtimeSelectorCandidates;
292
167
  const candidates: Array<string | undefined> = [
293
- flagValue(args, "--mode")?.trim() || undefined,
294
- positionalSkill?.trim() || undefined,
168
+ modeCandidate?.value,
169
+ positionalCandidate?.value,
295
170
  typeof payload?.mode === "string" ? (payload.mode as string).trim() || undefined : undefined,
296
171
  typeof payload?.skill === "string" ? (payload.skill as string).trim() || undefined : undefined,
297
172
  ];
@@ -1121,6 +996,7 @@ async function reconcileWorkflowSkillStateUnlocked(
1121
996
  ): Promise<{ stateFile: string }> {
1122
997
  const { cwd, mode, threadId, turnId, active, payload } = options;
1123
998
  const filePath = modeStateFile(cwd, mode, sessionId);
999
+ if (mode === "deep-interview") assertDeepInterviewStructuredResponseWithinLimit(payload);
1124
1000
  const existingRead = await readExistingStateForMutation(filePath);
1125
1001
  const existingPayload = existingRead.kind === "valid" ? existingRead.value : {};
1126
1002
  const nowIsoStr = nowIso();
@@ -1157,6 +1033,7 @@ async function reconcileWorkflowSkillStateUnlocked(
1157
1033
  unknown
1158
1034
  >)
1159
1035
  : mergeWithNullDelete(existingPayload, payload);
1036
+ if (mode === "deep-interview") assertDeepInterviewEnvelopeInputLimits(merged);
1160
1037
  merged.skill = mode;
1161
1038
  merged.current_phase = trimmedPhase;
1162
1039
  merged.active = active;
@@ -1232,12 +1109,8 @@ export async function readWorkflowStateJson(
1232
1109
  return (await readJsonFile(modeStateFile(cwd, skill, session.gjcSessionId))) ?? {};
1233
1110
  }
1234
1111
 
1235
- async function handleRead(
1236
- args: readonly string[],
1237
- cwd: string,
1238
- positionalSkill: string | undefined,
1239
- ): Promise<StateCommandResult> {
1240
- const selectors = await resolveSelectors(args, cwd, positionalSkill, "read");
1112
+ async function handleRead(args: readonly string[], cwd: string): Promise<StateCommandResult> {
1113
+ const selectors = await resolveSelectors(args, cwd, "read");
1241
1114
  const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.gjcSessionId));
1242
1115
  const fields = parseFieldsFlag(args);
1243
1116
  if (mode) {
@@ -1276,12 +1149,8 @@ async function handleRead(
1276
1149
  return { status: 0, stdout: `${JSON.stringify(existing ?? {}, null, 2)}\n` };
1277
1150
  }
1278
1151
 
1279
- async function handleStatus(
1280
- args: readonly string[],
1281
- cwd: string,
1282
- positionalSkill: string | undefined,
1283
- ): Promise<StateCommandResult> {
1284
- const selectors = await resolveSelectors(args, cwd, positionalSkill, "read");
1152
+ async function handleStatus(args: readonly string[], cwd: string): Promise<StateCommandResult> {
1153
+ const selectors = await resolveSelectors(args, cwd, "read");
1285
1154
  const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.gjcSessionId));
1286
1155
  if (!mode) {
1287
1156
  throw new StateCommandError(
@@ -1303,12 +1172,8 @@ async function handleStatus(
1303
1172
  };
1304
1173
  }
1305
1174
 
1306
- async function handleWrite(
1307
- args: readonly string[],
1308
- cwd: string,
1309
- positionalSkill: string | undefined,
1310
- ): Promise<StateCommandResult> {
1311
- const selectors = await resolveSelectors(args, cwd, positionalSkill, "write");
1175
+ async function handleWrite(args: readonly string[], cwd: string): Promise<StateCommandResult> {
1176
+ const selectors = await resolveSelectors(args, cwd, "write");
1312
1177
  const { gjcSessionId: sessionId, threadId, turnId, payload } = selectors;
1313
1178
  if (!payload) throw new StateCommandError(2, "gjc state write requires --input '<json>'");
1314
1179
  const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, sessionId));
@@ -1318,6 +1183,13 @@ async function handleWrite(
1318
1183
  "gjc state write requires --mode <skill>, positional <skill>, input.skill, or an active workflow in the current session active state",
1319
1184
  );
1320
1185
 
1186
+ if (mode === "deep-interview") {
1187
+ try {
1188
+ assertDeepInterviewStructuredResponseWithinLimit(payload);
1189
+ } catch (error) {
1190
+ throw new StateCommandError(2, error instanceof Error ? error.message : String(error));
1191
+ }
1192
+ }
1321
1193
  const filePath = modeStateFile(cwd, mode, sessionId);
1322
1194
  const forced = hasFlag(args, "--force");
1323
1195
  return await withWorkflowStateLock(
@@ -1360,6 +1232,11 @@ async function handleWrite(
1360
1232
  merged = applyAmbiguityFloorToEnvelope(
1361
1233
  mergeDeepInterviewEnvelope(existingPayload, payload, { replace: hasFlag(args, "--replace") }),
1362
1234
  ).envelope;
1235
+ try {
1236
+ assertDeepInterviewEnvelopeInputLimits(merged);
1237
+ } catch (error) {
1238
+ throw new StateCommandError(2, error instanceof Error ? error.message : String(error));
1239
+ }
1363
1240
  } else if (hasFlag(args, "--replace")) {
1364
1241
  merged = { ...payload };
1365
1242
  } else {
@@ -1465,12 +1342,8 @@ async function handleWrite(
1465
1342
  );
1466
1343
  }
1467
1344
 
1468
- async function handleClear(
1469
- args: readonly string[],
1470
- cwd: string,
1471
- positionalSkill: string | undefined,
1472
- ): Promise<StateCommandResult> {
1473
- const selectors = await resolveSelectors(args, cwd, positionalSkill, "clear");
1345
+ async function handleClear(args: readonly string[], cwd: string): Promise<StateCommandResult> {
1346
+ const selectors = await resolveSelectors(args, cwd, "clear");
1474
1347
  const { gjcSessionId: sessionId, threadId, turnId } = selectors;
1475
1348
  const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, sessionId));
1476
1349
  if (!mode)
@@ -1564,6 +1437,25 @@ async function handleClear(
1564
1437
  const DEEP_INTERVIEW_INTENT_ID_RE = /(?:artifact|surface|integration|constraint):[a-z0-9][a-z0-9._/-]{0,127}/g;
1565
1438
 
1566
1439
  async function assertDeepInterviewHandoffReady(state: Record<string, unknown>): Promise<void> {
1440
+ const specPath = typeof state.spec_path === "string" ? state.spec_path : undefined;
1441
+ const expectedSha = typeof state.spec_sha256 === "string" ? state.spec_sha256 : undefined;
1442
+ let content: string | undefined;
1443
+ if (specPath) {
1444
+ try {
1445
+ content = await fs.readFile(specPath, "utf-8");
1446
+ } catch (error) {
1447
+ throw new StateCommandError(
1448
+ 2,
1449
+ `deep-interview handoff cannot read persisted spec: ${error instanceof Error ? error.message : String(error)}`,
1450
+ );
1451
+ }
1452
+ const boundedContent = content.endsWith("\n") ? content.slice(0, -1) : content;
1453
+ assertDeepInterviewInputWithinLimit(
1454
+ boundedContent,
1455
+ MAX_DEEP_INTERVIEW_STRUCTURED_RESPONSE_LENGTH,
1456
+ "persisted deep-interview spec",
1457
+ );
1458
+ }
1567
1459
  const envelope = normalizeDeepInterviewEnvelope(state);
1568
1460
  const inner = envelope.state;
1569
1461
  if (!inner) return;
@@ -1573,19 +1465,8 @@ async function assertDeepInterviewHandoffReady(state: Record<string, unknown>):
1573
1465
  return;
1574
1466
  }
1575
1467
  assertDeepInterviewIntentManifest(inner.intent_contract);
1576
- const specPath = typeof state.spec_path === "string" ? state.spec_path : undefined;
1577
- const expectedSha = typeof state.spec_sha256 === "string" ? state.spec_sha256 : undefined;
1578
- if (!specPath || !expectedSha)
1468
+ if (!specPath || !expectedSha || content === undefined)
1579
1469
  throw new StateCommandError(2, "deep-interview handoff requires a persisted intent-validated spec");
1580
- let content: string;
1581
- try {
1582
- content = await fs.readFile(specPath, "utf-8");
1583
- } catch (error) {
1584
- throw new StateCommandError(
1585
- 2,
1586
- `deep-interview handoff cannot read persisted spec: ${error instanceof Error ? error.message : String(error)}`,
1587
- );
1588
- }
1589
1470
  if (createHash("sha256").update(content).digest("hex") !== expectedSha)
1590
1471
  throw new StateCommandError(2, "deep-interview handoff spec hash mismatch");
1591
1472
  const observedIds = [...new Set(content.match(DEEP_INTERVIEW_INTENT_ID_RE) ?? [])].sort();
@@ -1631,12 +1512,8 @@ async function assertDeepInterviewHandoffReady(state: Record<string, unknown>):
1631
1512
  * the phase remains in `skill-active-state.json` until a chain call (or
1632
1513
  * explicit `clear`) demotes it.
1633
1514
  */
1634
- async function handleHandoffUnlocked(
1635
- args: readonly string[],
1636
- cwd: string,
1637
- positionalSkill: string | undefined,
1638
- ): Promise<StateCommandResult> {
1639
- const selectors = await resolveSelectors(args, cwd, positionalSkill, "handoff");
1515
+ async function handleHandoffUnlocked(args: readonly string[], cwd: string): Promise<StateCommandResult> {
1516
+ const selectors = await resolveSelectors(args, cwd, "handoff");
1640
1517
  const { gjcSessionId: sessionId, threadId, turnId } = selectors;
1641
1518
  const caller = selectors.mode ?? (await inferModeFromActiveState(cwd, sessionId));
1642
1519
  if (!caller) {
@@ -1944,12 +1821,8 @@ async function handleHandoffUnlocked(
1944
1821
  };
1945
1822
  }
1946
1823
 
1947
- async function handleHandoff(
1948
- args: readonly string[],
1949
- cwd: string,
1950
- positionalSkill: string | undefined,
1951
- ): Promise<StateCommandResult> {
1952
- const selectors = await resolveSelectors(args, cwd, positionalSkill, "handoff");
1824
+ async function handleHandoff(args: readonly string[], cwd: string): Promise<StateCommandResult> {
1825
+ const selectors = await resolveSelectors(args, cwd, "handoff");
1953
1826
  // Serialize concurrent handoffs on a dedicated sentinel lock, NOT on the
1954
1827
  // derived `skill-active-state.json` cache. The inner transaction
1955
1828
  // (applyHandoffToActiveState / syncSkillActiveState -> rebuildActiveSnapshot)
@@ -1959,15 +1832,11 @@ async function handleHandoff(
1959
1832
  // `{ cwd }` so the sentinel resolves against the handoff cwd rather than
1960
1833
  // `process.cwd()`.
1961
1834
  const handoffLock = path.join(sessionStateDir(cwd, selectors.gjcSessionId), "handoff");
1962
- return withWorkflowStateLock(handoffLock, async () => handleHandoffUnlocked(args, cwd, positionalSkill), { cwd });
1835
+ return withWorkflowStateLock(handoffLock, async () => handleHandoffUnlocked(args, cwd), { cwd });
1963
1836
  }
1964
1837
 
1965
- async function handleContract(
1966
- args: readonly string[],
1967
- cwd: string,
1968
- positionalSkill: string | undefined,
1969
- ): Promise<StateCommandResult> {
1970
- const { mode } = await resolveSelectors(args, cwd, positionalSkill, "read");
1838
+ async function handleContract(args: readonly string[], cwd: string): Promise<StateCommandResult> {
1839
+ const { mode } = await resolveSelectors(args, cwd, "read");
1971
1840
  if (!mode) {
1972
1841
  throw new StateCommandError(2, "gjc state contract requires --mode <skill>, positional <skill>, or input.skill");
1973
1842
  }
@@ -2195,12 +2064,8 @@ async function handleGraph(
2195
2064
  };
2196
2065
  }
2197
2066
 
2198
- async function handlePrune(
2199
- args: readonly string[],
2200
- cwd: string,
2201
- positionalSkill: string | undefined,
2202
- ): Promise<StateCommandResult> {
2203
- const selectors = await resolveSelectors(args, cwd, positionalSkill, "prune");
2067
+ async function handlePrune(args: readonly string[], cwd: string): Promise<StateCommandResult> {
2068
+ const selectors = await resolveSelectors(args, cwd, "prune");
2204
2069
  const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.gjcSessionId));
2205
2070
  if (!mode) {
2206
2071
  throw new StateCommandError(
@@ -2265,12 +2130,8 @@ async function handleGc(
2265
2130
  return { status: 0, stdout: `${JSON.stringify(summary, null, 2)}\n` };
2266
2131
  }
2267
2132
 
2268
- async function handleMigrate(
2269
- args: readonly string[],
2270
- cwd: string,
2271
- positionalSkill: string | undefined,
2272
- ): Promise<StateCommandResult> {
2273
- const selectors = await resolveSelectors(args, cwd, positionalSkill, "migrate");
2133
+ async function handleMigrate(args: readonly string[], cwd: string): Promise<StateCommandResult> {
2134
+ const selectors = await resolveSelectors(args, cwd, "migrate");
2274
2135
  const mode = selectors.mode ?? (await inferModeFromActiveState(cwd, selectors.gjcSessionId));
2275
2136
  if (!mode) {
2276
2137
  throw new StateCommandError(
@@ -2301,34 +2162,31 @@ async function handleMigrate(
2301
2162
 
2302
2163
  export async function runNativeStateCommand(args: string[], cwd = process.cwd()): Promise<StateCommandResult> {
2303
2164
  try {
2304
- const parsed = parsePositionalArgs(args);
2305
- assertKnownFlags(args, parsed);
2306
- switch (parsed.action) {
2165
+ const parsed = classifyStateArgv(args);
2166
+ assertKnownFlags(parsed);
2167
+ switch (parsed.effectiveAction) {
2307
2168
  case "read":
2308
- if (hasFlag(args, "--migrate")) return await handleMigrate(args, cwd, parsed.positionalSkill);
2309
- return await handleRead(args, cwd, parsed.positionalSkill);
2169
+ return await handleRead(args, cwd);
2310
2170
  case "write":
2311
- return await handleWrite(args, cwd, parsed.positionalSkill);
2171
+ return await handleWrite(args, cwd);
2312
2172
  case "clear":
2313
- return await handleClear(args, cwd, parsed.positionalSkill);
2173
+ return await handleClear(args, cwd);
2314
2174
  case "contract":
2315
- return await handleContract(args, cwd, parsed.positionalSkill);
2175
+ return await handleContract(args, cwd);
2316
2176
  case "status":
2317
- return await handleStatus(args, cwd, parsed.positionalSkill);
2177
+ return await handleStatus(args, cwd);
2318
2178
  case "doctor":
2319
2179
  return await handleDoctor(args, cwd, parsed.positionalSkill);
2320
2180
  case "handoff":
2321
- return await handleHandoff(args, cwd, parsed.positionalSkill);
2181
+ return await handleHandoff(args, cwd);
2322
2182
  case "graph":
2323
2183
  return await handleGraph(args, cwd, parsed.positionalSkill);
2324
2184
  case "prune":
2325
- return await handlePrune(args, cwd, parsed.positionalSkill);
2185
+ return await handlePrune(args, cwd);
2326
2186
  case "gc":
2327
2187
  return await handleGc(args, cwd, parsed.positionalSkill);
2328
2188
  case "migrate":
2329
- return await handleMigrate(args, cwd, parsed.positionalSkill);
2330
- default:
2331
- return { status: 2, stderr: `Unknown gjc state command: ${parsed.action}\n` };
2189
+ return await handleMigrate(args, cwd);
2332
2190
  }
2333
2191
  } catch (error) {
2334
2192
  if (error instanceof CommandError) return { status: error.exitStatus, stderr: `${error.message}\n` };
@@ -1885,7 +1885,7 @@ async function startTmuxSession(
1885
1885
  for (const worker of config.workers) {
1886
1886
  const splitDirection: string = worker.index === 1 ? "-h" : "-v";
1887
1887
  const splitTarget: string =
1888
- worker.index === 1 ? config.leader.pane_id : (rightStackRootPaneId ?? config.leader.pane_id);
1888
+ worker.index === 1 ? config.tmux_target : (rightStackRootPaneId ?? config.tmux_target);
1889
1889
  const workerCommand = buildWorkerCommand(config, worker);
1890
1890
  const workerCwd = worker.worktree_path ?? config.leader.cwd;
1891
1891
  const useSendKeysFallback = shouldDispatchWorkerWithSendKeys(config.tmux_command);
@@ -11,6 +11,7 @@ import * as fsSync from "node:fs";
11
11
  import * as fs from "node:fs/promises";
12
12
 
13
13
  import * as path from "node:path";
14
+ import { openRecoveryFsRoot } from "@gajae-code/natives";
14
15
  import { isCompiledBinary } from "@gajae-code/utils/env";
15
16
  import { parseLinuxProcStartTime } from "./linux-proc";
16
17
 
@@ -1046,6 +1047,122 @@ function sameOwnerGenerationBaseline(left: OwnerGenerationBaseline, right: Owner
1046
1047
  );
1047
1048
  }
1048
1049
 
1050
+ export interface ManagedOwnerPredecessorEvidence {
1051
+ generation: string;
1052
+ sessionId: string;
1053
+ runId: string;
1054
+ incarnation: string;
1055
+ predecessorToken: string;
1056
+ }
1057
+
1058
+ function exactManagedOwnerJson(authority: ReturnType<typeof openRecoveryFsRoot>, name: string): unknown {
1059
+ const first = authority.read(name, 64 * 1024);
1060
+ if (!first.ok || !first.data) throw new Error("managed_owner_replacement_evidence_unavailable");
1061
+ const second = authority.read(name, 64 * 1024);
1062
+ if (
1063
+ !second.ok ||
1064
+ !second.data ||
1065
+ Buffer.compare(Buffer.from(first.data), Buffer.from(second.data)) !== 0 ||
1066
+ JSON.stringify(first.identity) !== JSON.stringify(second.identity)
1067
+ )
1068
+ throw new Error("managed_owner_replacement_evidence_changed");
1069
+ const content = Buffer.from(first.data).toString("utf8");
1070
+ if (!content.endsWith("\n") || content.indexOf("\n") !== content.length - 1)
1071
+ throw new Error("managed_owner_replacement_evidence_untrusted");
1072
+ return JSON.parse(content);
1073
+ }
1074
+
1075
+ /** Resolve one SIGABRT predecessor from the retained exact prior-generation root. */
1076
+ export function resolveManagedOwnerPredecessorSync(
1077
+ stateDir: string,
1078
+ sessionId: string,
1079
+ baseline: OwnerGenerationBaseline,
1080
+ ): ManagedOwnerPredecessorEvidence | undefined {
1081
+ if (baseline.state === "absent") return undefined;
1082
+ const root = lifecyclePaths(stateDir, sessionId, baseline.generation).root;
1083
+ let entries: string[];
1084
+ try {
1085
+ entries = fsSync.readdirSync(root);
1086
+ } catch {
1087
+ throw new Error("managed_owner_replacement_evidence_unavailable");
1088
+ }
1089
+ const bindings = new Set<string>();
1090
+ const receipts = new Set<string>();
1091
+ for (const entry of entries) {
1092
+ const binding = /^child-([^/]+)\.binding\.json$/.exec(entry);
1093
+ const receipt = /^sigabrt-([^/]+)\.receipt\.json$/.exec(entry);
1094
+ if (entry.startsWith("child-") && !binding) throw new Error("managed_owner_replacement_evidence_untrusted");
1095
+ if (entry.startsWith("sigabrt-") && !receipt) throw new Error("managed_owner_replacement_evidence_untrusted");
1096
+ if (binding) bindings.add(binding[1]!);
1097
+ if (receipt) receipts.add(receipt[1]!);
1098
+ }
1099
+ if (bindings.size === 0 && receipts.size === 0) return undefined;
1100
+ const tokens = [...bindings].filter(token => receipts.has(token));
1101
+ if (tokens.length !== 1 || receipts.size !== 1) throw new Error("managed_owner_replacement_evidence_ambiguous");
1102
+ const predecessorToken = tokens[0]!;
1103
+ if (!/^[A-Za-z0-9._-]+$/.test(predecessorToken)) throw new Error("managed_owner_replacement_evidence_untrusted");
1104
+ const authority = openRecoveryFsRoot(root);
1105
+ try {
1106
+ const binding = exactManagedOwnerJson(authority, `child-${predecessorToken}.binding.json`) as Record<
1107
+ string,
1108
+ unknown
1109
+ >;
1110
+ const receipt = exactManagedOwnerJson(authority, `sigabrt-${predecessorToken}.receipt.json`) as Record<
1111
+ string,
1112
+ unknown
1113
+ >;
1114
+ const command = binding.command;
1115
+ const runId = binding.run_id;
1116
+ const incarnation = binding.endpoint_incarnation;
1117
+ const commandDigest =
1118
+ Array.isArray(command) && command.length > 0 && command.every(value => typeof value === "string" && value)
1119
+ ? crypto.createHash("sha256").update(JSON.stringify(command)).digest("hex")
1120
+ : "";
1121
+ const valid =
1122
+ binding.schema_version === 2 &&
1123
+ binding.generation === baseline.generation &&
1124
+ binding.session_id === sessionId &&
1125
+ typeof runId === "string" &&
1126
+ runId.length > 0 &&
1127
+ typeof incarnation === "string" &&
1128
+ incarnation.length > 0 &&
1129
+ binding.child_token === predecessorToken &&
1130
+ binding.command_sha256 === commandDigest &&
1131
+ typeof binding.supervisor_pid === "number" &&
1132
+ Number.isSafeInteger(binding.supervisor_pid) &&
1133
+ binding.supervisor_pid > 0 &&
1134
+ typeof binding.supervisor_start_time === "string" &&
1135
+ typeof binding.created_at === "string" &&
1136
+ receipt.schema_version === 2 &&
1137
+ receipt.generation === binding.generation &&
1138
+ receipt.session_id === binding.session_id &&
1139
+ receipt.run_id === runId &&
1140
+ receipt.endpoint_incarnation === incarnation &&
1141
+ receipt.child_token === binding.child_token &&
1142
+ receipt.command_sha256 === binding.command_sha256 &&
1143
+ receipt.supervisor_pid === binding.supervisor_pid &&
1144
+ receipt.supervisor_start_time === binding.supervisor_start_time &&
1145
+ typeof receipt.child_pid === "number" &&
1146
+ Number.isSafeInteger(receipt.child_pid) &&
1147
+ receipt.child_pid > 0 &&
1148
+ typeof receipt.child_start_time === "string" &&
1149
+ typeof receipt.received_at === "string" &&
1150
+ (receipt.exit_code === null || Number.isSafeInteger(receipt.exit_code)) &&
1151
+ receipt.signal === "SIGABRT" &&
1152
+ receipt.signal_number === 6;
1153
+ if (!valid) throw new Error("managed_owner_replacement_evidence_untrusted");
1154
+ return {
1155
+ generation: baseline.generation,
1156
+ sessionId,
1157
+ runId,
1158
+ incarnation,
1159
+ predecessorToken,
1160
+ };
1161
+ } finally {
1162
+ authority.close();
1163
+ }
1164
+ }
1165
+
1049
1166
  export async function captureOwnerGenerationBaseline(
1050
1167
  stateDir: string,
1051
1168
  sessionId: string,