@gajae-code/coding-agent 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/CHANGELOG.md +82 -1
  2. package/dist/types/async/job-manager.d.ts +17 -3
  3. package/dist/types/config/settings-schema.d.ts +54 -0
  4. package/dist/types/exa/mcp-client.d.ts +10 -1
  5. package/dist/types/exec/bash-executor.d.ts +45 -12
  6. package/dist/types/extensibility/extensions/types.d.ts +16 -2
  7. package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
  8. package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
  9. package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
  10. package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
  11. package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
  12. package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
  13. package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
  14. package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
  15. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +18 -0
  16. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  17. package/dist/types/gjc-runtime/team-runtime.d.ts +25 -0
  18. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
  19. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +10 -0
  20. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
  21. package/dist/types/modes/acp/acp-agent.d.ts +2 -19
  22. package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
  23. package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
  24. package/dist/types/modes/components/session-observer-overlay.d.ts +18 -0
  25. package/dist/types/modes/components/settings-selector.d.ts +9 -0
  26. package/dist/types/modes/components/status-line/gh.d.ts +5 -0
  27. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
  28. package/dist/types/reminders/star-reminder.d.ts +3 -13
  29. package/dist/types/runtime-mcp/manager.d.ts +1 -0
  30. package/dist/types/sdk/acp/adapter.d.ts +3 -1
  31. package/dist/types/sdk/broker/transport.d.ts +2 -0
  32. package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
  33. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
  34. package/dist/types/sdk/bus/config.d.ts +6 -2
  35. package/dist/types/sdk/bus/index.d.ts +29 -0
  36. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +8 -1
  37. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  38. package/dist/types/sdk/bus/reconciliation-store.d.ts +7 -3
  39. package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
  40. package/dist/types/sdk/bus/rich-render.d.ts +2 -0
  41. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
  42. package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
  43. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
  44. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
  45. package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
  46. package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
  47. package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
  48. package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
  49. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  50. package/dist/types/sdk/prompt-status.d.ts +23 -3
  51. package/dist/types/sdk/session.d.ts +6 -0
  52. package/dist/types/session/agent-session.d.ts +38 -2
  53. package/dist/types/session/artifacts.d.ts +8 -0
  54. package/dist/types/session/blob-store.d.ts +57 -4
  55. package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
  56. package/dist/types/session/session-manager.d.ts +23 -6
  57. package/dist/types/session/session-storage.d.ts +4 -0
  58. package/dist/types/session/streaming-output.d.ts +36 -7
  59. package/dist/types/setup/credential-import.d.ts +12 -0
  60. package/dist/types/skill-state/active-state.d.ts +12 -0
  61. package/dist/types/skill-state/workflow-hud.d.ts +6 -0
  62. package/dist/types/task/executor.d.ts +3 -2
  63. package/dist/types/task/index.d.ts +3 -1
  64. package/dist/types/task/provider-retry-status.d.ts +8 -0
  65. package/dist/types/task/receipt.d.ts +7 -1
  66. package/dist/types/task/types.d.ts +23 -0
  67. package/dist/types/tools/ask-answer-registry.d.ts +2 -2
  68. package/dist/types/tools/bash-interactive.d.ts +4 -1
  69. package/dist/types/tools/bash.d.ts +3 -1
  70. package/dist/types/tools/image-gen.d.ts +4 -0
  71. package/dist/types/tools/index.d.ts +4 -0
  72. package/dist/types/tools/output-meta.d.ts +23 -2
  73. package/dist/types/tools/path-utils.d.ts +1 -0
  74. package/dist/types/tools/read.d.ts +0 -6
  75. package/dist/types/tools/review.d.ts +5 -7
  76. package/dist/types/tools/subagent.d.ts +2 -0
  77. package/dist/types/utils/gh.d.ts +13 -0
  78. package/dist/types/utils/image-resize.d.ts +1 -0
  79. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  80. package/dist/types/web/search/providers/searxng.d.ts +7 -0
  81. package/dist/types/web/search/providers/xai.d.ts +2 -0
  82. package/package.json +10 -8
  83. package/scripts/acp-conformance-agent.ts +218 -0
  84. package/scripts/capture-gjc-bundle-settings.ts +306 -0
  85. package/scripts/generate-sdk-operation-inventory.ts +3 -0
  86. package/scripts/resident-memory-bench.ts +1341 -0
  87. package/scripts/run-acp-conformance.ts +285 -0
  88. package/src/async/job-manager.ts +78 -20
  89. package/src/cli/fast-help.ts +1 -1
  90. package/src/cli/plugin-cli.ts +176 -18
  91. package/src/commands/team.ts +19 -1
  92. package/src/config/file-lock.ts +11 -2
  93. package/src/config/settings-schema.ts +51 -0
  94. package/src/coordinator-mcp/question-gate-codec.ts +1 -1
  95. package/src/coordinator-mcp/server.ts +5 -12
  96. package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -0
  97. package/src/defaults/gjc/skills/ralplan/SKILL.md +19 -16
  98. package/src/defaults/gjc/skills/team/SKILL.md +2 -0
  99. package/src/defaults/gjc/skills/ultragoal/SKILL.md +37 -24
  100. package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
  101. package/src/defaults/gjc-defaults.ts +4 -4
  102. package/src/exa/mcp-client.ts +12 -3
  103. package/src/exec/bash-executor.ts +144 -27
  104. package/src/extensibility/extensions/runner.ts +15 -0
  105. package/src/extensibility/extensions/types.ts +18 -1
  106. package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
  107. package/src/extensibility/gjc-plugins/index.ts +19 -2
  108. package/src/extensibility/gjc-plugins/installer.ts +189 -50
  109. package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
  110. package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
  111. package/src/extensibility/gjc-plugins/observability.ts +3 -3
  112. package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
  113. package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
  114. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
  115. package/src/extensibility/gjc-plugins/schema.ts +84 -12
  116. package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
  117. package/src/extensibility/gjc-plugins/types.ts +141 -0
  118. package/src/extensibility/gjc-plugins/validation.ts +9 -0
  119. package/src/gjc-runtime/launch-tmux.ts +25 -6
  120. package/src/gjc-runtime/managed-owner-admission.ts +1 -1
  121. package/src/gjc-runtime/ralplan-runtime.ts +411 -17
  122. package/src/gjc-runtime/team-launch.ts +8 -0
  123. package/src/gjc-runtime/team-runtime.ts +140 -32
  124. package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
  125. package/src/gjc-runtime/tmux-owner-isolation.ts +10 -0
  126. package/src/gjc-runtime/tmux-sessions.ts +58 -13
  127. package/src/gjc-runtime/ultragoal-runtime.ts +401 -611
  128. package/src/gjc-runtime/workflow-manifest.generated.json +8 -99
  129. package/src/gjc-runtime/workflow-manifest.ts +4 -68
  130. package/src/internal-urls/docs-index.generated.ts +15 -15
  131. package/src/modes/DESIGN.md +51 -0
  132. package/src/modes/acp/acp-agent.ts +298 -186
  133. package/src/modes/components/gjc-bundle-settings.ts +508 -0
  134. package/src/modes/components/notifications-settings-editor.ts +12 -1
  135. package/src/modes/components/oauth-selector.ts +68 -30
  136. package/src/modes/components/session-observer-overlay.ts +305 -86
  137. package/src/modes/components/settings-selector.ts +56 -5
  138. package/src/modes/components/status-line/gh.ts +16 -0
  139. package/src/modes/components/tool-status-header.ts +3 -14
  140. package/src/modes/components/transcript-viewer-overlay.ts +99 -24
  141. package/src/modes/controllers/command-controller.ts +4 -0
  142. package/src/modes/controllers/extension-ui-controller.ts +7 -0
  143. package/src/modes/controllers/selector-controller.ts +45 -0
  144. package/src/modes/runtime-init.ts +3 -0
  145. package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
  146. package/src/prompts/system/eager-todo.md +8 -4
  147. package/src/prompts/system/system-prompt.md +11 -4
  148. package/src/prompts/tools/bash.md +5 -5
  149. package/src/prompts/tools/search-tool-bm25.md +5 -0
  150. package/src/prompts/tools/task.md +1 -1
  151. package/src/reminders/star-reminder.ts +4 -42
  152. package/src/runtime-mcp/manager.ts +19 -4
  153. package/src/sdk/acp/adapter.ts +22 -6
  154. package/src/sdk/broker/transport.ts +35 -3
  155. package/src/sdk/bus/attachment-registry.ts +5 -1
  156. package/src/sdk/bus/chat-daemon-cli.ts +4 -39
  157. package/src/sdk/bus/chat-daemon-control.ts +6 -3
  158. package/src/sdk/bus/config.ts +18 -2
  159. package/src/sdk/bus/index.ts +661 -126
  160. package/src/sdk/bus/kind-aware-reconciliation.ts +148 -73
  161. package/src/sdk/bus/lifecycle-commands.ts +4 -1
  162. package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
  163. package/src/sdk/bus/reconciliation-store.ts +95 -16
  164. package/src/sdk/bus/rich-draft.ts +1 -1
  165. package/src/sdk/bus/rich-render.ts +2 -2
  166. package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
  167. package/src/sdk/bus/telegram-cli.ts +33 -8
  168. package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
  169. package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
  170. package/src/sdk/bus/telegram-daemon.ts +916 -23
  171. package/src/sdk/bus/telegram-reference.ts +40 -3
  172. package/src/sdk/bus/threaded-render.ts +48 -3
  173. package/src/sdk/bus/topic-registry.ts +20 -0
  174. package/src/sdk/host/query/revision-store.ts +36 -2
  175. package/src/sdk/host/reverse-leases.ts +44 -7
  176. package/src/sdk/prompt-status.ts +16 -3
  177. package/src/sdk/protocol/operation-inventory.generated.json +22 -0
  178. package/src/sdk/session.ts +88 -9
  179. package/src/session/agent-session.ts +822 -172
  180. package/src/session/artifacts.ts +22 -0
  181. package/src/session/auth-broker-config.ts +10 -3
  182. package/src/session/blob-store.ts +915 -24
  183. package/src/session/internal/managed-session-scope.ts +44 -6
  184. package/src/session/internal/managed-session-storage.ts +48 -6
  185. package/src/session/session-manager.ts +1614 -435
  186. package/src/session/session-storage.ts +9 -1
  187. package/src/session/streaming-output.ts +238 -68
  188. package/src/setup/credential-import.ts +75 -13
  189. package/src/skill-state/active-state.ts +32 -0
  190. package/src/skill-state/workflow-hud.ts +17 -0
  191. package/src/skill-state/workflow-mutation-guard.ts +496 -2
  192. package/src/task/executor.ts +122 -39
  193. package/src/task/index.ts +23 -14
  194. package/src/task/provider-retry-status.ts +16 -0
  195. package/src/task/receipt.ts +23 -7
  196. package/src/task/render.ts +6 -2
  197. package/src/task/types.ts +108 -0
  198. package/src/tools/ask-answer-registry.ts +12 -6
  199. package/src/tools/ask.ts +86 -55
  200. package/src/tools/ast-edit.ts +1 -0
  201. package/src/tools/ast-grep.ts +1 -0
  202. package/src/tools/bash-interactive.ts +8 -3
  203. package/src/tools/bash.ts +503 -60
  204. package/src/tools/find.ts +1 -0
  205. package/src/tools/image-gen.ts +29 -5
  206. package/src/tools/index.ts +4 -0
  207. package/src/tools/output-meta.ts +108 -14
  208. package/src/tools/path-utils.ts +3 -2
  209. package/src/tools/read.ts +18 -0
  210. package/src/tools/review.ts +5 -7
  211. package/src/tools/search.ts +1 -0
  212. package/src/tools/subagent-render.ts +6 -2
  213. package/src/tools/subagent.ts +6 -0
  214. package/src/tools/telegram-send.ts +6 -3
  215. package/src/utils/gh.ts +39 -0
  216. package/src/utils/image-resize.ts +3 -0
  217. package/src/web/search/providers/anthropic.ts +4 -4
  218. package/src/web/search/providers/kimi.ts +15 -5
  219. package/src/web/search/providers/searxng.ts +47 -4
  220. package/src/web/search/providers/xai.ts +7 -2
  221. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
  222. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
  223. package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
@@ -21,6 +21,8 @@ import {
21
21
  MANAGED_OWNER_TRANSCRIPT_PATH_ENV,
22
22
  } from "../../gjc-runtime/managed-owner-admission";
23
23
  import {
24
+ MANAGED_OWNER_CHILD_TOKEN_ENV,
25
+ MANAGED_OWNER_COMMAND_ENV,
24
26
  MANAGED_OWNER_INCARNATION_ENV,
25
27
  MANAGED_OWNER_RUN_ID_ENV,
26
28
  MANAGED_OWNER_SUPERVISOR_ARG,
@@ -56,6 +58,7 @@ import {
56
58
  type GjcTmuxSessionStatus,
57
59
  listGjcTmuxSessions,
58
60
  } from "../../gjc-runtime/tmux-sessions";
61
+ import { processIncarnation } from "../broker/process-incarnation";
59
62
  import type {
60
63
  LifecycleErrorReason,
61
64
  ResumeCandidate,
@@ -77,6 +80,29 @@ import {
77
80
  } from "./lifecycle-orchestrator";
78
81
  import { listRecentSessions } from "./recent-activity";
79
82
 
83
+ const directLifecycleManagedOwnerEnvUnsets = [
84
+ GJC_TMUX_OWNER_GENERATION_ENV,
85
+ GJC_TMUX_OWNER_STATE_DIR_ENV,
86
+ GJC_TMUX_OWNER_SERVER_KEY_ENV,
87
+ MANAGED_OWNER_COMMAND_ENV,
88
+ MANAGED_OWNER_RUN_ID_ENV,
89
+ MANAGED_OWNER_INCARNATION_ENV,
90
+ MANAGED_OWNER_CHILD_TOKEN_ENV,
91
+ MANAGED_OWNER_PREDECESSOR_TOKEN_ENV,
92
+ MANAGED_OWNER_PREDECESSOR_GENERATION_ENV,
93
+ MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV,
94
+ MANAGED_OWNER_PREDECESSOR_INCARNATION_ENV,
95
+ MANAGED_OWNER_TRANSCRIPT_PATH_ENV,
96
+ ] as const;
97
+ const directLifecycleStabilizationMs = 250;
98
+
99
+ function directLifecycleEnvArguments(env: Record<string, string>): string {
100
+ return [
101
+ ...directLifecycleManagedOwnerEnvUnsets.flatMap(name => ["-u", shellQuote(name)]),
102
+ ...Object.entries(env).map(([key, value]) => `${key}=${shellQuote(value)}`),
103
+ ].join(" ");
104
+ }
105
+
80
106
  type NativeControlServerConstructor = new (
81
107
  token: string,
82
108
  ownerId: string,
@@ -857,74 +883,343 @@ async function completeLifecycleSpawnTransaction(input: {
857
883
  throw error;
858
884
  }
859
885
  }
886
+ function directTmuxReceipt(
887
+ stdout: string,
888
+ sessionName: string,
889
+ ): { nativeSessionId: string; sessionName: string } | undefined {
890
+ const line = stdout.endsWith("\n") ? stdout.slice(0, -1) : stdout;
891
+ const [nativeSessionId, receivedName, ...rest] = line.split("\t");
892
+ return rest.length === 0 && /^\$\d+$/.test(nativeSessionId) && receivedName === sessionName
893
+ ? { nativeSessionId, sessionName: receivedName }
894
+ : undefined;
895
+ }
896
+
897
+ type DirectTmuxBinding =
898
+ | { state: "bound"; serverPid: number; panePid: number; paneDead: boolean; paneDeadStatus: string }
899
+ | { state: "absent" }
900
+ | { state: "unverifiable" };
901
+
902
+ function isKnownNoSessionDiagnostic(stderr: string): boolean {
903
+ const diagnostic = stderr.trim().toLowerCase();
904
+ return (
905
+ diagnostic.length > 0 &&
906
+ diagnostic.length <= 512 &&
907
+ /no server running|can't find session|no such session|unknown session/.test(diagnostic)
908
+ );
909
+ }
910
+
911
+ function directTmuxBinding(
912
+ tmux: string,
913
+ env: NodeJS.ProcessEnv,
914
+ cwd: string,
915
+ nativeSessionId: string,
916
+ sessionName: string,
917
+ ): DirectTmuxBinding {
918
+ const result = Bun.spawnSync(
919
+ [
920
+ tmux,
921
+ "display-message",
922
+ "-p",
923
+ "-t",
924
+ nativeSessionId,
925
+ "#{pid}\t#{session_id}\t#{session_name}\t#{pane_pid}\t#{pane_dead}\t#{pane_dead_status}",
926
+ ],
927
+ { stdout: "pipe", stderr: "pipe", env, cwd },
928
+ );
929
+ if (result.exitCode !== 0)
930
+ return isKnownNoSessionDiagnostic(result.stderr.toString()) ? { state: "absent" } : { state: "unverifiable" };
931
+ const output = result.stdout.toString();
932
+ const line = output.endsWith("\n") ? output.slice(0, -1) : output;
933
+ const [pid, receivedId, receivedName, panePid, paneDead, paneDeadStatus, ...rest] = line.split("\t");
934
+ return rest.length === 0 &&
935
+ /^\d+$/.test(pid) &&
936
+ Number(pid) > 0 &&
937
+ receivedId === nativeSessionId &&
938
+ receivedName === sessionName &&
939
+ /^\d+$/.test(panePid) &&
940
+ Number(panePid) > 0 &&
941
+ (paneDead === "0" || paneDead === "1")
942
+ ? {
943
+ state: "bound",
944
+ serverPid: Number(pid),
945
+ panePid: Number(panePid),
946
+ paneDead: paneDead === "1",
947
+ paneDeadStatus,
948
+ }
949
+ : { state: "unverifiable" };
950
+ }
951
+
952
+ function directTmuxIdentityIsCurrent(input: {
953
+ tmux: string;
954
+ env: NodeJS.ProcessEnv;
955
+ cwd: string;
956
+ nativeSessionId: string;
957
+ sessionName: string;
958
+ serverPid: number;
959
+ serverIncarnation: string;
960
+ readProcessIncarnation: (pid: number) => string | undefined;
961
+ }): boolean {
962
+ const binding = directTmuxBinding(input.tmux, input.env, input.cwd, input.nativeSessionId, input.sessionName);
963
+ return (
964
+ binding.state === "bound" &&
965
+ binding.serverPid === input.serverPid &&
966
+ input.readProcessIncarnation(input.serverPid) === input.serverIncarnation
967
+ );
968
+ }
969
+ function directTmuxPaneLivenessIsCurrent(input: {
970
+ tmux: string;
971
+ env: NodeJS.ProcessEnv;
972
+ cwd: string;
973
+ nativeSessionId: string;
974
+ sessionName: string;
975
+ serverPid: number;
976
+ serverIncarnation: string;
977
+ panePid: number;
978
+ paneIncarnation: string;
979
+ readProcessIncarnation: (pid: number) => string | undefined;
980
+ }): boolean {
981
+ const binding = directTmuxBinding(input.tmux, input.env, input.cwd, input.nativeSessionId, input.sessionName);
982
+ return (
983
+ binding.state === "bound" &&
984
+ binding.serverPid === input.serverPid &&
985
+ input.readProcessIncarnation(input.serverPid) === input.serverIncarnation &&
986
+ !binding.paneDead &&
987
+ binding.panePid === input.panePid &&
988
+ input.readProcessIncarnation(input.panePid) === input.paneIncarnation
989
+ );
990
+ }
991
+
992
+ function directTmuxGuardedMutation(input: {
993
+ tmux: string;
994
+ env: NodeJS.ProcessEnv;
995
+ cwd: string;
996
+ nativeSessionId: string;
997
+ sessionName: string;
998
+ serverPid: number;
999
+ serverIncarnation: string;
1000
+ readProcessIncarnation: (pid: number) => string | undefined;
1001
+ commands: string;
1002
+ success: string;
1003
+ }): boolean {
1004
+ if (!directTmuxIdentityIsCurrent(input)) return false;
1005
+ const result = Bun.spawnSync(
1006
+ [
1007
+ input.tmux,
1008
+ "if-shell",
1009
+ "-t",
1010
+ input.nativeSessionId,
1011
+ "-F",
1012
+ lifecycleMetadataPredicate(input.serverPid, input.nativeSessionId, input.sessionName),
1013
+ `${input.commands} ; display-message -p ${input.success}`,
1014
+ "display-message -p __gjc_lifecycle_metadata_refused__",
1015
+ ],
1016
+ { stdout: "pipe", stderr: "pipe", env: input.env, cwd: input.cwd },
1017
+ );
1018
+ return result.exitCode === 0 && result.stdout.toString().trim() === input.success;
1019
+ }
1020
+
1021
+ function cleanupDirectLifecycleAttempt(input: {
1022
+ tmux: string;
1023
+ env: NodeJS.ProcessEnv;
1024
+ cwd: string;
1025
+ nativeSessionId: string;
1026
+ sessionName: string;
1027
+ serverPid: number;
1028
+ serverIncarnation: string;
1029
+ readProcessIncarnation: (pid: number) => string | undefined;
1030
+ }): void {
1031
+ if (
1032
+ !directTmuxGuardedMutation({
1033
+ ...input,
1034
+ commands: `kill-session -t '${input.nativeSessionId}'`,
1035
+ success: "__gjc_lifecycle_cleanup_ok__",
1036
+ })
1037
+ )
1038
+ throw cleanupUncertain();
1039
+ if (directTmuxBinding(input.tmux, input.env, input.cwd, input.nativeSessionId, input.sessionName).state !== "absent")
1040
+ throw cleanupUncertain();
1041
+ }
1042
+
1043
+ async function completeNonLinuxLifecycleSpawn(input: {
1044
+ tmux: string;
1045
+ env: NodeJS.ProcessEnv;
1046
+ sessionId: string;
1047
+ cwd: string;
1048
+ sessionName: string;
1049
+ sessionStateFile: string;
1050
+ command: string;
1051
+ readProcessIncarnation: (pid: number) => string | undefined;
1052
+ }): Promise<void> {
1053
+ let attempt:
1054
+ | {
1055
+ nativeSessionId: string;
1056
+ sessionName: string;
1057
+ serverPid: number;
1058
+ serverIncarnation: string;
1059
+ panePid?: number;
1060
+ paneIncarnation?: string;
1061
+ }
1062
+ | undefined;
1063
+ let primaryError: unknown;
1064
+ try {
1065
+ const created = Bun.spawnSync(
1066
+ [
1067
+ input.tmux,
1068
+ "new-session",
1069
+ "-d",
1070
+ "-P",
1071
+ "-F",
1072
+ "#{session_id}\t#{session_name}",
1073
+ "-s",
1074
+ input.sessionName,
1075
+ "sh",
1076
+ "-c",
1077
+ input.command,
1078
+ ],
1079
+ { stdout: "pipe", stderr: "pipe", env: input.env, cwd: input.cwd },
1080
+ );
1081
+ const receipt = directTmuxReceipt(created.stdout.toString(), input.sessionName);
1082
+ if (!receipt) throw new Error("gjc_lifecycle_tmux_launch_failed");
1083
+ const binding = directTmuxBinding(input.tmux, input.env, input.cwd, receipt.nativeSessionId, receipt.sessionName);
1084
+ const serverIncarnation = binding.state === "bound" && input.readProcessIncarnation(binding.serverPid);
1085
+ const paneIncarnation =
1086
+ binding.state === "bound" && !binding.paneDead && input.readProcessIncarnation(binding.panePid);
1087
+ if (binding.state !== "bound" || !serverIncarnation) throw new Error("gjc_lifecycle_tmux_launch_failed");
1088
+ attempt = { ...receipt, serverPid: binding.serverPid, serverIncarnation };
1089
+ if (!binding.paneDead && paneIncarnation) {
1090
+ attempt.panePid = binding.panePid;
1091
+ attempt.paneIncarnation = paneIncarnation;
1092
+ }
1093
+ if (created.exitCode !== 0 || !directTmuxIdentityIsCurrent({ ...input, ...attempt }))
1094
+ throw new Error("gjc_lifecycle_tmux_launch_failed");
1095
+ const commands = buildGjcTmuxProfileCommands(attempt.nativeSessionId, input.env, {
1096
+ sessionId: input.sessionId,
1097
+ sessionStateFile: input.sessionStateFile,
1098
+ project: input.cwd,
1099
+ })
1100
+ .map(command => command.args.map(tmuxCommandArgument).join(" "))
1101
+ .join(" ; ");
1102
+ if (
1103
+ !directTmuxGuardedMutation({ ...input, ...attempt, commands, success: "__gjc_lifecycle_metadata_ok__" }) ||
1104
+ !directTmuxIdentityIsCurrent({ ...input, ...attempt })
1105
+ )
1106
+ throw new Error("gjc_lifecycle_metadata_write_failed");
1107
+ await Bun.sleep(directLifecycleStabilizationMs);
1108
+ if (
1109
+ attempt.panePid === undefined ||
1110
+ attempt.paneIncarnation === undefined ||
1111
+ !directTmuxPaneLivenessIsCurrent({
1112
+ ...input,
1113
+ ...attempt,
1114
+ panePid: attempt.panePid,
1115
+ paneIncarnation: attempt.paneIncarnation,
1116
+ })
1117
+ )
1118
+ throw new Error("gjc_lifecycle_tmux_launch_liveness_failed");
1119
+ } catch (error) {
1120
+ primaryError = error;
1121
+ }
1122
+ if (primaryError === undefined) return;
1123
+ let cleanupFailure: unknown;
1124
+ if (attempt) {
1125
+ try {
1126
+ cleanupDirectLifecycleAttempt({ ...input, ...attempt });
1127
+ } catch (error) {
1128
+ cleanupFailure = error;
1129
+ }
1130
+ } else cleanupFailure = cleanupUncertain();
1131
+ if (cleanupFailure !== undefined)
1132
+ throw new AggregateError([primaryError, cleanupFailure], "gjc_lifecycle_cleanup_uncertain");
1133
+ throw primaryError;
1134
+ }
860
1135
  /** Real daemon-safe tmux launcher: canonical owner-isolation plan + GJC tags. */
861
1136
  export function daemonSpawnCreate(
862
1137
  env: NodeJS.ProcessEnv = process.env,
863
- opts: { ownerIsolationProbe?: OwnerIsolationProbe } = {},
1138
+ opts: {
1139
+ ownerIsolationProbe?: OwnerIsolationProbe;
1140
+ platform?: NodeJS.Platform;
1141
+ processIncarnation?: (pid: number) => string | undefined;
1142
+ } = {},
864
1143
  ) {
865
1144
  return async (
866
1145
  frame: SessionCreateFrame,
867
1146
  ids: { lifecycleRequestId: string; intendedSessionId: string; startupPromptRef?: string },
868
1147
  ): Promise<CreateEffectResult> => {
869
- const tmuxBinary = resolveGjcTmuxBinary({ env });
1148
+ const tmuxBinary = resolveGjcTmuxBinary({ env, platform: opts.platform });
870
1149
  if (tmuxBinary.isPsmux) throw new Error("gjc_lifecycle_psmux_unsupported");
871
1150
  const tmux = tmuxBinary.command;
872
1151
  const name = tmuxSessionNameFor(ids.intendedSessionId);
873
1152
  const { cwd, args } = buildCreateArgv(frame, ids);
874
1153
  const sessionStateFile = lifecycleRuntimeStateFile(cwd, ids.intendedSessionId, name);
875
- const stateDir = path.dirname(sessionStateFile);
876
- const previousBaseline = await captureOwnerGenerationBaseline(stateDir, ids.intendedSessionId);
877
- const predecessor = resolveManagedOwnerPredecessorSync(stateDir, ids.intendedSessionId, previousBaseline);
878
- const generation = crypto.randomUUID();
879
- const runId = crypto.randomUUID();
880
- const incarnation = crypto.randomUUID();
881
- // Detached: no interactive TTY needed (daemon-safe). These values contain
882
- // only opaque ids and paths needed by the resident sidecar to publish its
883
- // exact-owner terminal verdict.
884
- const childEnv: Record<string, string> = {
1154
+ const commonChildEnv: Record<string, string> = {
885
1155
  GJC_TMUX_LAUNCHED: "1",
886
1156
  GJC_NOTIFICATIONS: "1",
887
1157
  GJC_SESSION_ID: ids.intendedSessionId,
888
1158
  GJC_LIFECYCLE_REQUEST_ID: ids.lifecycleRequestId,
889
1159
  [GJC_COORDINATOR_SESSION_ID_ENV]: ids.intendedSessionId,
890
1160
  [GJC_COORDINATOR_SESSION_STATE_FILE_ENV]: sessionStateFile,
891
- [GJC_TMUX_OWNER_GENERATION_ENV]: generation,
892
- [GJC_TMUX_OWNER_STATE_DIR_ENV]: stateDir,
893
- [GJC_TMUX_OWNER_SERVER_KEY_ENV]: "default",
894
- GJC_MANAGED_OWNER_COMMAND_JSON: JSON.stringify(["gjc", ...args]),
895
- [MANAGED_OWNER_RUN_ID_ENV]: runId,
896
- [MANAGED_OWNER_INCARNATION_ENV]: incarnation,
897
- ...(predecessor
898
- ? {
899
- [MANAGED_OWNER_PREDECESSOR_TOKEN_ENV]: predecessor.predecessorToken,
900
- [MANAGED_OWNER_PREDECESSOR_GENERATION_ENV]: predecessor.generation,
901
- [MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV]: predecessor.runId,
902
- [MANAGED_OWNER_PREDECESSOR_INCARNATION_ENV]: predecessor.incarnation,
903
- [MANAGED_OWNER_TRANSCRIPT_PATH_ENV]: env.GJC_SESSION_FILE ?? "",
904
- }
905
- : {}),
906
1161
  };
907
- if (ids.startupPromptRef) childEnv.GJC_STARTUP_PROMPT_REF = ids.startupPromptRef;
908
- const envPairs = Object.entries(childEnv)
909
- .map(([key, value]) => `${key}=${shellQuote(value)}`)
910
- .join(" ");
911
- const command = `cd ${shellQuote(cwd)} && exec env ${envPairs} gjc ${shellQuote(MANAGED_OWNER_SUPERVISOR_ARG)}`;
912
- await completeLifecycleSpawnTransaction({
913
- tmux,
914
- env,
915
- sessionId: ids.intendedSessionId,
916
- generation,
917
- stateDir,
918
- cwd,
919
- sessionName: name,
920
- sessionStateFile,
921
- argv: [tmux, "new-session", "-d", "-P", "-F", "#{session_id}", "-s", name, "sh", "-c", command],
922
- ownerIsolationProbe: opts.ownerIsolationProbe,
923
- prepareSpawn: () => {
924
- if (frame.target.kind === "plain_dir") fs.mkdirSync(cwd, { recursive: true });
925
- },
926
- previousBaseline,
927
- });
1162
+ if (ids.startupPromptRef) commonChildEnv.GJC_STARTUP_PROMPT_REF = ids.startupPromptRef;
1163
+ if ((opts.platform ?? process.platform) === "linux") {
1164
+ const stateDir = path.dirname(sessionStateFile);
1165
+ const previousBaseline = await captureOwnerGenerationBaseline(stateDir, ids.intendedSessionId);
1166
+ const predecessor = resolveManagedOwnerPredecessorSync(stateDir, ids.intendedSessionId, previousBaseline);
1167
+ const generation = crypto.randomUUID();
1168
+ const runId = crypto.randomUUID();
1169
+ const incarnation = crypto.randomUUID();
1170
+ const managedChildEnv: Record<string, string> = {
1171
+ ...commonChildEnv,
1172
+ [GJC_TMUX_OWNER_GENERATION_ENV]: generation,
1173
+ [GJC_TMUX_OWNER_STATE_DIR_ENV]: stateDir,
1174
+ [GJC_TMUX_OWNER_SERVER_KEY_ENV]: "default",
1175
+ [MANAGED_OWNER_COMMAND_ENV]: JSON.stringify(["gjc", ...args]),
1176
+ [MANAGED_OWNER_RUN_ID_ENV]: runId,
1177
+ [MANAGED_OWNER_INCARNATION_ENV]: incarnation,
1178
+ ...(predecessor
1179
+ ? {
1180
+ [MANAGED_OWNER_PREDECESSOR_TOKEN_ENV]: predecessor.predecessorToken,
1181
+ [MANAGED_OWNER_PREDECESSOR_GENERATION_ENV]: predecessor.generation,
1182
+ [MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV]: predecessor.runId,
1183
+ [MANAGED_OWNER_PREDECESSOR_INCARNATION_ENV]: predecessor.incarnation,
1184
+ [MANAGED_OWNER_TRANSCRIPT_PATH_ENV]: env.GJC_SESSION_FILE ?? "",
1185
+ }
1186
+ : {}),
1187
+ };
1188
+ const managedEnvPairs = Object.entries(managedChildEnv)
1189
+ .map(([key, value]) => `${key}=${shellQuote(value)}`)
1190
+ .join(" ");
1191
+ const command = `cd ${shellQuote(cwd)} && exec env ${managedEnvPairs} gjc ${shellQuote(MANAGED_OWNER_SUPERVISOR_ARG)}`;
1192
+ await completeLifecycleSpawnTransaction({
1193
+ tmux,
1194
+ env,
1195
+ sessionId: ids.intendedSessionId,
1196
+ generation,
1197
+ stateDir,
1198
+ cwd,
1199
+ sessionName: name,
1200
+ sessionStateFile,
1201
+ argv: [tmux, "new-session", "-d", "-P", "-F", "#{session_id}", "-s", name, "sh", "-c", command],
1202
+ ownerIsolationProbe: opts.ownerIsolationProbe,
1203
+ prepareSpawn: () => {
1204
+ if (frame.target.kind === "plain_dir") fs.mkdirSync(cwd, { recursive: true });
1205
+ },
1206
+ previousBaseline,
1207
+ });
1208
+ } else {
1209
+ if (frame.target.kind === "plain_dir") fs.mkdirSync(cwd, { recursive: true });
1210
+ const directEnvPairs = directLifecycleEnvArguments(commonChildEnv);
1211
+ const directCommand = `cd ${shellQuote(cwd)} && exec env ${directEnvPairs} gjc ${args.map(shellQuote).join(" ")}`;
1212
+ await completeNonLinuxLifecycleSpawn({
1213
+ tmux,
1214
+ env,
1215
+ sessionId: ids.intendedSessionId,
1216
+ cwd,
1217
+ sessionName: name,
1218
+ sessionStateFile,
1219
+ command: directCommand,
1220
+ readProcessIncarnation: opts.processIncarnation ?? processIncarnation,
1221
+ });
1222
+ }
928
1223
 
929
1224
  return {
930
1225
  sessionId: ids.intendedSessionId,
@@ -1018,13 +1313,15 @@ export function daemonResumeSession(
1018
1313
  sessionsRoot?: string;
1019
1314
  listSessions?: (env: NodeJS.ProcessEnv) => GjcTmuxSessionStatus[];
1020
1315
  ownerIsolationProbe?: OwnerIsolationProbe;
1316
+ platform?: NodeJS.Platform;
1317
+ processIncarnation?: (pid: number) => string | undefined;
1021
1318
  } = {},
1022
1319
  ) {
1023
1320
  return async (target: {
1024
1321
  sessionIdOrPrefix: string;
1025
1322
  path?: string;
1026
1323
  }): Promise<ResumeEffectResult | { ambiguous: ResumeCandidate[] } | { notFound: true }> => {
1027
- const tmuxBinary = resolveGjcTmuxBinary({ env });
1324
+ const tmuxBinary = resolveGjcTmuxBinary({ env, platform: opts.platform });
1028
1325
  if (tmuxBinary.isPsmux) throw new Error("gjc_lifecycle_psmux_unsupported");
1029
1326
  const live = (opts.listSessions?.(env) ?? listGjcTmuxSessions(env)).filter(
1030
1327
  s => s.sessionId === target.sessionIdOrPrefix || s.sessionId?.startsWith(target.sessionIdOrPrefix),
@@ -1085,50 +1382,68 @@ export function daemonResumeSession(
1085
1382
  const tmux = tmuxBinary.command;
1086
1383
  const name = tmuxSessionNameFor(resumeId);
1087
1384
  const sessionStateFile = lifecycleRuntimeStateFile(resolvedResumeCwd, resumeId, name);
1088
- const stateDir = path.dirname(sessionStateFile);
1089
- const previousBaseline = await captureOwnerGenerationBaseline(stateDir, resumeId);
1090
- const predecessor = resolveManagedOwnerPredecessorSync(stateDir, resumeId, previousBaseline);
1091
- const generation = crypto.randomUUID();
1092
- const runId = crypto.randomUUID();
1093
- const incarnation = crypto.randomUUID();
1094
- const childEnv: Record<string, string> = {
1385
+ const commonChildEnv: Record<string, string> = {
1095
1386
  GJC_TMUX_LAUNCHED: "1",
1096
1387
  GJC_NOTIFICATIONS: "1",
1097
1388
  [GJC_COORDINATOR_SESSION_ID_ENV]: resumeId,
1098
1389
  [GJC_COORDINATOR_SESSION_STATE_FILE_ENV]: sessionStateFile,
1099
- [GJC_TMUX_OWNER_GENERATION_ENV]: generation,
1100
- [GJC_TMUX_OWNER_STATE_DIR_ENV]: stateDir,
1101
- [GJC_TMUX_OWNER_SERVER_KEY_ENV]: "default",
1102
- GJC_MANAGED_OWNER_COMMAND_JSON: JSON.stringify(["gjc", "--resume", resumeId]),
1103
- [MANAGED_OWNER_RUN_ID_ENV]: runId,
1104
- [MANAGED_OWNER_INCARNATION_ENV]: incarnation,
1105
- ...(predecessor
1106
- ? {
1107
- [MANAGED_OWNER_PREDECESSOR_TOKEN_ENV]: predecessor.predecessorToken,
1108
- [MANAGED_OWNER_PREDECESSOR_GENERATION_ENV]: predecessor.generation,
1109
- [MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV]: predecessor.runId,
1110
- [MANAGED_OWNER_PREDECESSOR_INCARNATION_ENV]: predecessor.incarnation,
1111
- [MANAGED_OWNER_TRANSCRIPT_PATH_ENV]: env.GJC_SESSION_FILE ?? "",
1112
- }
1113
- : {}),
1114
1390
  };
1115
- const envPairs = Object.entries(childEnv)
1116
- .map(([key, value]) => `${key}=${shellQuote(value)}`)
1117
- .join(" ");
1118
- const command = `cd ${shellQuote(resolvedResumeCwd)} && exec env ${envPairs} gjc ${shellQuote(MANAGED_OWNER_SUPERVISOR_ARG)}`;
1119
- await completeLifecycleSpawnTransaction({
1120
- tmux,
1121
- env,
1122
- sessionId: resumeId,
1123
- generation,
1124
- stateDir,
1125
- cwd: resolvedResumeCwd,
1126
- sessionName: name,
1127
- sessionStateFile,
1128
- argv: [tmux, "new-session", "-d", "-P", "-F", "#{session_id}", "-s", name, "sh", "-c", command],
1129
- ownerIsolationProbe: opts.ownerIsolationProbe,
1130
- previousBaseline,
1131
- });
1391
+ if ((opts.platform ?? process.platform) === "linux") {
1392
+ const stateDir = path.dirname(sessionStateFile);
1393
+ const previousBaseline = await captureOwnerGenerationBaseline(stateDir, resumeId);
1394
+ const predecessor = resolveManagedOwnerPredecessorSync(stateDir, resumeId, previousBaseline);
1395
+ const generation = crypto.randomUUID();
1396
+ const runId = crypto.randomUUID();
1397
+ const incarnation = crypto.randomUUID();
1398
+ const managedChildEnv: Record<string, string> = {
1399
+ ...commonChildEnv,
1400
+ [GJC_TMUX_OWNER_GENERATION_ENV]: generation,
1401
+ [GJC_TMUX_OWNER_STATE_DIR_ENV]: stateDir,
1402
+ [GJC_TMUX_OWNER_SERVER_KEY_ENV]: "default",
1403
+ [MANAGED_OWNER_COMMAND_ENV]: JSON.stringify(["gjc", "--resume", resumeId]),
1404
+ [MANAGED_OWNER_RUN_ID_ENV]: runId,
1405
+ [MANAGED_OWNER_INCARNATION_ENV]: incarnation,
1406
+ ...(predecessor
1407
+ ? {
1408
+ [MANAGED_OWNER_PREDECESSOR_TOKEN_ENV]: predecessor.predecessorToken,
1409
+ [MANAGED_OWNER_PREDECESSOR_GENERATION_ENV]: predecessor.generation,
1410
+ [MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV]: predecessor.runId,
1411
+ [MANAGED_OWNER_PREDECESSOR_INCARNATION_ENV]: predecessor.incarnation,
1412
+ [MANAGED_OWNER_TRANSCRIPT_PATH_ENV]: env.GJC_SESSION_FILE ?? "",
1413
+ }
1414
+ : {}),
1415
+ };
1416
+ const managedEnvPairs = Object.entries(managedChildEnv)
1417
+ .map(([key, value]) => `${key}=${shellQuote(value)}`)
1418
+ .join(" ");
1419
+ const command = `cd ${shellQuote(resolvedResumeCwd)} && exec env ${managedEnvPairs} gjc ${shellQuote(MANAGED_OWNER_SUPERVISOR_ARG)}`;
1420
+ await completeLifecycleSpawnTransaction({
1421
+ tmux,
1422
+ env,
1423
+ sessionId: resumeId,
1424
+ generation,
1425
+ stateDir,
1426
+ cwd: resolvedResumeCwd,
1427
+ sessionName: name,
1428
+ sessionStateFile,
1429
+ argv: [tmux, "new-session", "-d", "-P", "-F", "#{session_id}", "-s", name, "sh", "-c", command],
1430
+ ownerIsolationProbe: opts.ownerIsolationProbe,
1431
+ previousBaseline,
1432
+ });
1433
+ } else {
1434
+ const directEnvPairs = directLifecycleEnvArguments(commonChildEnv);
1435
+ const directCommand = `cd ${shellQuote(resolvedResumeCwd)} && exec env ${directEnvPairs} gjc ${shellQuote("--resume")} ${shellQuote(resumeId)}`;
1436
+ await completeNonLinuxLifecycleSpawn({
1437
+ tmux,
1438
+ env,
1439
+ sessionId: resumeId,
1440
+ cwd: resolvedResumeCwd,
1441
+ sessionName: name,
1442
+ sessionStateFile,
1443
+ command: directCommand,
1444
+ readProcessIncarnation: opts.processIncarnation ?? processIncarnation,
1445
+ });
1446
+ }
1132
1447
 
1133
1448
  return {
1134
1449
  sessionId: resumeId,