@gajae-code/coding-agent 0.18.11 → 0.18.12

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 (270) hide show
  1. package/CHANGELOG.md +293 -0
  2. package/dist/types/async/job-manager.d.ts +7 -3
  3. package/dist/types/async/result-formatting.d.ts +2 -0
  4. package/dist/types/autoresearch/git.d.ts +1 -1
  5. package/dist/types/capability/fs.d.ts +2 -2
  6. package/dist/types/cli/accounts-cli.d.ts +2 -2
  7. package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
  8. package/dist/types/cli/install-activation.d.ts +10 -4
  9. package/dist/types/cli/public-command-errors.d.ts +1 -0
  10. package/dist/types/cli/update-cli.d.ts +14 -1
  11. package/dist/types/cli-ordinary.d.ts +2 -2
  12. package/dist/types/commands/accounts.d.ts +2 -1
  13. package/dist/types/commands/migrate.d.ts +1 -1
  14. package/dist/types/commands/sdk.d.ts +5 -0
  15. package/dist/types/commit/changelog/index.d.ts +1 -0
  16. package/dist/types/config/autorouting-tier-map.d.ts +30 -0
  17. package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
  18. package/dist/types/config/config-hot-reload.d.ts +39 -0
  19. package/dist/types/config/file-lock.d.ts +8 -1
  20. package/dist/types/config/model-discovery-manager.d.ts +2 -0
  21. package/dist/types/config/model-profile-activation.d.ts +62 -6
  22. package/dist/types/config/model-registry.d.ts +56 -5
  23. package/dist/types/config/model-resolver.d.ts +2 -0
  24. package/dist/types/config/models-config-schema.d.ts +10 -0
  25. package/dist/types/config/settings-schema.d.ts +46 -3
  26. package/dist/types/config/settings.d.ts +33 -1
  27. package/dist/types/dap/config.d.ts +8 -4
  28. package/dist/types/debug/system-info.d.ts +6 -1
  29. package/dist/types/discovery/mcp-compat.d.ts +3 -1
  30. package/dist/types/edit/modes/patch.d.ts +5 -0
  31. package/dist/types/edit/modes/replace.d.ts +3 -0
  32. package/dist/types/exec/bash-executor.d.ts +12 -0
  33. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
  34. package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
  35. package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
  36. package/dist/types/migrate/adapters/codex.d.ts +2 -1
  37. package/dist/types/migrate/adapters/index.d.ts +10 -1
  38. package/dist/types/migrate/executor.d.ts +3 -1
  39. package/dist/types/migrate/types.d.ts +15 -3
  40. package/dist/types/modes/components/settings-selector.d.ts +6 -0
  41. package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
  42. package/dist/types/modes/theme/theme.d.ts +1 -0
  43. package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
  44. package/dist/types/runtime-mcp/manager.d.ts +7 -0
  45. package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
  46. package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
  47. package/dist/types/sdk/broker/broker-exit.d.ts +2 -2
  48. package/dist/types/sdk/broker/broker.d.ts +7 -1
  49. package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
  50. package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
  51. package/dist/types/sdk/broker/ensure.d.ts +136 -7
  52. package/dist/types/sdk/broker/hop.d.ts +36 -0
  53. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
  54. package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
  55. package/dist/types/sdk/broker/session-index.d.ts +4 -1
  56. package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
  57. package/dist/types/sdk/bus/index.d.ts +23 -0
  58. package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
  59. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -1
  60. package/dist/types/sdk/cli/session-cli.d.ts +0 -1
  61. package/dist/types/sdk/host/session-runtime.d.ts +32 -0
  62. package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
  63. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
  64. package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
  65. package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
  66. package/dist/types/sdk/session-list.d.ts +1 -1
  67. package/dist/types/session/agent-session.d.ts +58 -2
  68. package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
  69. package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
  70. package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
  71. package/dist/types/session/messages.d.ts +5 -1
  72. package/dist/types/session/session-manager.d.ts +13 -3
  73. package/dist/types/session/startup-auth-config.d.ts +24 -2
  74. package/dist/types/session/streaming-output.d.ts +11 -0
  75. package/dist/types/session/terminal-abort.d.ts +3 -1
  76. package/dist/types/session-import/service.d.ts +1 -1
  77. package/dist/types/skill-state/workflow-mutation-guard.d.ts +7 -3
  78. package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
  79. package/dist/types/task/executor.d.ts +19 -2
  80. package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
  81. package/dist/types/tools/atomic-file-write.d.ts +9 -7
  82. package/dist/types/tools/bash-allowed-prefixes.d.ts +11 -0
  83. package/dist/types/tools/descriptors.d.ts +2 -0
  84. package/dist/types/tools/fetch.d.ts +1 -1
  85. package/dist/types/tools/index.d.ts +2 -0
  86. package/dist/types/tools/python.d.ts +1 -1
  87. package/dist/types/tools/report-issue.d.ts +41 -0
  88. package/dist/types/tools/subagent.d.ts +23 -0
  89. package/dist/types/web/scrapers/types.d.ts +8 -1
  90. package/dist/types/web/search/providers/insane.d.ts +7 -1
  91. package/dist/types/web/search/providers/searxng.d.ts +5 -0
  92. package/package.json +8 -8
  93. package/scripts/build-binary.ts +2 -1
  94. package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
  95. package/scripts/generate-docs-index.ts +8 -1
  96. package/scripts/generate-sdk-operation-inventory.ts +22 -4
  97. package/src/async/job-manager.ts +356 -56
  98. package/src/async/result-formatting.ts +40 -0
  99. package/src/autoresearch/git.ts +9 -2
  100. package/src/autoresearch/prompts/prompt-setup.md +1 -1
  101. package/src/capability/fs.ts +27 -19
  102. package/src/capability/index.ts +2 -2
  103. package/src/cli/accounts-cli.ts +176 -8
  104. package/src/cli/auth-broker-cli.ts +21 -11
  105. package/src/cli/doctor/install-repairs.ts +35 -5
  106. package/src/cli/fast-help.ts +1 -1
  107. package/src/cli/install-activation.ts +47 -4
  108. package/src/cli/migrate-cli.ts +7 -5
  109. package/src/cli/public-command-errors.ts +4 -0
  110. package/src/cli/root-flags.ts +2 -2
  111. package/src/cli/stats-cli.ts +9 -5
  112. package/src/cli/update-cli.ts +224 -7
  113. package/src/cli-ordinary.ts +7 -2
  114. package/src/commands/accounts.ts +10 -5
  115. package/src/commands/migrate.ts +3 -3
  116. package/src/commands/sdk.ts +199 -42
  117. package/src/commands/ultragoal.ts +16 -8
  118. package/src/commit/changelog/index.ts +39 -1
  119. package/src/commit/message.ts +10 -3
  120. package/src/config/autorouting-tier-map.ts +30 -0
  121. package/src/config/config-hot-reload-trust.ts +148 -0
  122. package/src/config/config-hot-reload.ts +734 -0
  123. package/src/config/file-lock.ts +216 -56
  124. package/src/config/model-bindings-applier.ts +85 -47
  125. package/src/config/model-discovery-manager.ts +10 -0
  126. package/src/config/model-profile-activation.ts +716 -113
  127. package/src/config/model-registry.ts +1334 -158
  128. package/src/config/model-resolver.ts +3 -1
  129. package/src/config/models-config-schema.ts +6 -0
  130. package/src/config/settings-schema.ts +60 -3
  131. package/src/config/settings.ts +458 -24
  132. package/src/coordinator-mcp/policy.ts +26 -1
  133. package/src/crash/sanitize.ts +4 -0
  134. package/src/customization/import.ts +3 -1
  135. package/src/dap/config.ts +31 -11
  136. package/src/dap/defaults.json +1 -1
  137. package/src/debug/crash-diagnostics.ts +2 -1
  138. package/src/debug/report-bundle.ts +19 -0
  139. package/src/debug/system-info.ts +8 -3
  140. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
  141. package/src/discovery/agents-md.ts +1 -1
  142. package/src/discovery/helpers.ts +1 -2
  143. package/src/discovery/mcp-compat.ts +120 -17
  144. package/src/edit/apply-patch/index.ts +12 -1
  145. package/src/edit/index.ts +13 -17
  146. package/src/edit/modes/patch.ts +187 -8
  147. package/src/edit/modes/replace.ts +136 -0
  148. package/src/eval/js/context-manager.ts +29 -3
  149. package/src/eval/js/worker-entry.ts +51 -18
  150. package/src/eval/py/executor.ts +14 -4
  151. package/src/eval/py/runner.py +144 -5
  152. package/src/exec/bash-executor.ts +81 -4
  153. package/src/exec/bash-shell-guardian.ts +4 -1
  154. package/src/exec/bash-shell-supervisor.ts +8 -4
  155. package/src/exec/isolated-shell.ts +5 -1
  156. package/src/export/html/index.ts +46 -6
  157. package/src/extensibility/custom-tools/wrapper.ts +3 -1
  158. package/src/extensibility/extensions/loader.ts +8 -1
  159. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
  160. package/src/gjc-runtime/gc-runtime.ts +33 -6
  161. package/src/gjc-runtime/launch-tmux.ts +7 -2
  162. package/src/gjc-runtime/launch-worktree.test.ts +148 -0
  163. package/src/gjc-runtime/launch-worktree.ts +101 -14
  164. package/src/gjc-runtime/managed-owner-admission.ts +46 -2
  165. package/src/gjc-runtime/session-resolution.ts +38 -6
  166. package/src/gjc-runtime/session-state-lock.ts +59 -14
  167. package/src/gjc-runtime/state-runtime.ts +23 -19
  168. package/src/hooks/native-skill-hook.ts +14 -11
  169. package/src/internal-urls/docs-index.generated.ts +14 -13
  170. package/src/internal-urls/local-protocol.ts +142 -8
  171. package/src/lsp/index.ts +29 -0
  172. package/src/lsp/workspace-path.ts +23 -0
  173. package/src/migrate/action-planner.ts +57 -0
  174. package/src/migrate/adapters/claude-code.ts +23 -4
  175. package/src/migrate/adapters/codex.ts +22 -4
  176. package/src/migrate/adapters/index.ts +22 -4
  177. package/src/migrate/adapters/opencode.ts +6 -1
  178. package/src/migrate/executor.ts +84 -7
  179. package/src/migrate/report.ts +19 -2
  180. package/src/migrate/types.ts +14 -4
  181. package/src/modes/DESIGN.md +51 -0
  182. package/src/modes/acp/permission-mode.ts +18 -1
  183. package/src/modes/components/assistant-message.ts +55 -1
  184. package/src/modes/components/settings-selector.ts +103 -37
  185. package/src/modes/controllers/command-controller.ts +38 -2
  186. package/src/modes/controllers/plan-mode-controller.ts +17 -7
  187. package/src/modes/controllers/selector-controller.ts +62 -6
  188. package/src/modes/interactive-mode.ts +68 -0
  189. package/src/modes/theme/theme.ts +15 -0
  190. package/src/prompts/tools/debug.md +1 -1
  191. package/src/prompts/tools/read.md +1 -1
  192. package/src/prompts/tools/report-issue.md +13 -0
  193. package/src/prompts/tools/task.md +1 -1
  194. package/src/runtime-credential-selector.ts +2 -2
  195. package/src/runtime-mcp/content-limits.ts +7 -0
  196. package/src/runtime-mcp/manager-pool.test.ts +5 -4
  197. package/src/runtime-mcp/manager.ts +32 -0
  198. package/src/runtime-mcp/oauth-flow.ts +32 -6
  199. package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
  200. package/src/runtime-mcp/render.ts +16 -1
  201. package/src/runtime-mcp/tool-bridge.ts +126 -17
  202. package/src/sdk/broker/broker-exit.ts +8 -5
  203. package/src/sdk/broker/broker.ts +342 -82
  204. package/src/sdk/broker/daemon-entry.ts +338 -116
  205. package/src/sdk/broker/doctor-restart.ts +2 -1
  206. package/src/sdk/broker/ensure.ts +1143 -265
  207. package/src/sdk/broker/hop.ts +274 -0
  208. package/src/sdk/broker/lifecycle-ledger.ts +35 -11
  209. package/src/sdk/broker/lock-host-identity.ts +29 -0
  210. package/src/sdk/broker/session-index.ts +62 -12
  211. package/src/sdk/bus/chat-command-policy.ts +16 -3
  212. package/src/sdk/bus/index.ts +52 -21
  213. package/src/sdk/bus/reconciliation-store.ts +201 -8
  214. package/src/sdk/bus/telegram-daemon-contract.ts +3 -1
  215. package/src/sdk/cli/session-cli.ts +21 -6
  216. package/src/sdk/host/session-runtime.test.ts +118 -23
  217. package/src/sdk/host/session-runtime.ts +352 -47
  218. package/src/sdk/lifecycle/service.ts +1 -0
  219. package/src/sdk/prompt-deadline-flush.ts +78 -5
  220. package/src/sdk/prompt-deadline-manager.ts +68 -14
  221. package/src/sdk/protocol/adapter-validation.ts +53 -11
  222. package/src/sdk/protocol/operation-inventory.generated.json +101 -2
  223. package/src/sdk/reconciliation-extensions.ts +1 -0
  224. package/src/sdk/session-list.ts +1 -0
  225. package/src/sdk/session.ts +114 -61
  226. package/src/session/agent-session.ts +1043 -81
  227. package/src/session/internal/managed-session-scope.ts +126 -39
  228. package/src/session/internal/managed-session-storage.ts +14 -11
  229. package/src/session/internal/native-publish-outcome.ts +5 -1
  230. package/src/session/messages.ts +47 -6
  231. package/src/session/session-manager.ts +236 -14
  232. package/src/session/startup-auth-config.ts +116 -8
  233. package/src/session/streaming-output.ts +262 -4
  234. package/src/session/terminal-abort.ts +14 -2
  235. package/src/session-import/service.ts +1 -0
  236. package/src/skill-state/workflow-mutation-guard.ts +491 -32
  237. package/src/slash-commands/builtin-registry.ts +15 -3
  238. package/src/slash-commands/helpers/credential-switch.ts +10 -5
  239. package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
  240. package/src/task/executor.ts +103 -12
  241. package/src/task/index.ts +389 -175
  242. package/src/tools/ast-edit-write-path.ts +64 -0
  243. package/src/tools/ast-edit.ts +18 -1
  244. package/src/tools/atomic-file-write.ts +46 -15
  245. package/src/tools/auto-generated-guard.ts +3 -0
  246. package/src/tools/bash-allowed-prefixes.ts +153 -5
  247. package/src/tools/bash-interactive.ts +14 -2
  248. package/src/tools/bash.ts +14 -0
  249. package/src/tools/debug.ts +7 -6
  250. package/src/tools/descriptors.test.ts +44 -0
  251. package/src/tools/descriptors.ts +34 -2
  252. package/src/tools/fetch.ts +3 -2
  253. package/src/tools/index.ts +2 -0
  254. package/src/tools/output-meta.ts +23 -2
  255. package/src/tools/python.ts +1 -1
  256. package/src/tools/read.ts +17 -35
  257. package/src/tools/report-issue.ts +184 -0
  258. package/src/tools/subagent-render.ts +114 -12
  259. package/src/tools/subagent.ts +108 -1
  260. package/src/tools/todo-write.ts +41 -3
  261. package/src/tools/tool-catalog.generated.ts +31 -3
  262. package/src/tools/write.ts +35 -3
  263. package/src/utils/commit-message-generator.ts +9 -2
  264. package/src/utils/file-mentions.ts +29 -1
  265. package/src/utils/title-generator.ts +7 -2
  266. package/src/web/scrapers/index.ts +3 -1
  267. package/src/web/scrapers/types.ts +8 -0
  268. package/src/web/scrapers/youtube.ts +3 -2
  269. package/src/web/search/providers/insane.ts +91 -28
  270. package/src/web/search/providers/searxng.ts +62 -10
@@ -29,6 +29,7 @@ import asyncio
29
29
  import ast
30
30
  import base64
31
31
  import builtins
32
+ import codecs
32
33
  import inspect
33
34
  import io
34
35
  import json
@@ -49,9 +50,14 @@ from typing import Any, Callable
49
50
  # Frame writer
50
51
  # ---------------------------------------------------------------------------
51
52
 
52
- _RAW_STDOUT = sys.__stdout__
53
53
  _RAW_STDERR = sys.__stderr__
54
54
  _OUT_LOCK = threading.Lock()
55
+ _PROTO_FD: int | None = None
56
+ _CAP_LOCK = threading.Lock()
57
+ _FLUSH_EVENT = threading.Event()
58
+ _FLUSH_TOKEN: bytes | None = None
59
+ _CAPTURED = b""
60
+ _CAPTURE_DECODER = codecs.getincrementaldecoder("utf-8")(errors="replace")
55
61
 
56
62
 
57
63
  def _json_default(o: Any) -> Any:
@@ -61,13 +67,141 @@ def _json_default(o: Any) -> Any:
61
67
  return f"<unrepr {type(o).__name__}>"
62
68
 
63
69
 
70
+ def _write_all(fd: int, data: bytes) -> None:
71
+ view = memoryview(data)
72
+ while len(view):
73
+ written = os.write(fd, view)
74
+ if written <= 0:
75
+ raise OSError(0, "protocol write returned no bytes")
76
+ view = view[written:]
77
+
78
+
64
79
  def _emit(frame: dict) -> None:
65
80
  """Serialize a frame and write it to the host as a single NDJSON line."""
66
- line = json.dumps(frame, ensure_ascii=False, default=_json_default)
81
+ payload = (json.dumps(frame, ensure_ascii=False, default=_json_default) + "\n").encode("utf-8")
82
+ fd = _PROTO_FD if _PROTO_FD is not None else 1
67
83
  with _OUT_LOCK:
68
- _RAW_STDOUT.write(line)
69
- _RAW_STDOUT.write("\n")
70
- _RAW_STDOUT.flush()
84
+ _write_all(fd, payload)
85
+
86
+
87
+ def _token_suffix_len(buf: bytes, token: bytes) -> int:
88
+ max_n = min(len(buf), len(token) - 1)
89
+ for size in range(max_n, 0, -1):
90
+ if buf.endswith(token[:size]):
91
+ return size
92
+ return 0
93
+
94
+
95
+ def _take_captured_locked() -> tuple[list[bytes], bool]:
96
+ """Split captured fd-1 bytes. A flush token is a delimiter, not output."""
97
+ global _CAPTURED, _FLUSH_TOKEN
98
+ pieces: list[bytes] = []
99
+ flushed = False
100
+ while True:
101
+ token = _FLUSH_TOKEN
102
+ if token is None:
103
+ if _CAPTURED:
104
+ pieces.append(_CAPTURED)
105
+ _CAPTURED = b""
106
+ return pieces, flushed
107
+ idx = _CAPTURED.find(token)
108
+ if idx < 0:
109
+ keep = _token_suffix_len(_CAPTURED, token)
110
+ if len(_CAPTURED) > keep:
111
+ pieces.append(_CAPTURED[: len(_CAPTURED) - keep])
112
+ _CAPTURED = _CAPTURED[-keep:] if keep else b""
113
+ return pieces, flushed
114
+ if idx:
115
+ pieces.append(_CAPTURED[:idx])
116
+ _CAPTURED = _CAPTURED[idx + len(token) :]
117
+ _FLUSH_TOKEN = None
118
+ flushed = True
119
+
120
+
121
+ def _publish_captured(pieces: list[bytes], flushed: bool) -> None:
122
+ global _CAPTURE_DECODER
123
+ for piece in pieces:
124
+ text = _CAPTURE_DECODER.decode(piece)
125
+ if text:
126
+ _forward_captured_stdout(text)
127
+ if not flushed:
128
+ return
129
+ tail = _CAPTURE_DECODER.decode(b"", final=True)
130
+ if tail:
131
+ _forward_captured_stdout(tail)
132
+ _CAPTURE_DECODER = codecs.getincrementaldecoder("utf-8")(errors="replace")
133
+
134
+
135
+ def _forward_captured_stdout(text: str) -> None:
136
+ rid = _STATE.current_id
137
+ if not text:
138
+ return
139
+ if rid is None:
140
+ _RAW_STDERR.write(text)
141
+ _RAW_STDERR.flush()
142
+ return
143
+ _emit({"type": "stdout", "id": rid, "data": text})
144
+
145
+
146
+ def _pump_captured_stdout(read_fd: int) -> None:
147
+ global _CAPTURED
148
+ while True:
149
+ try:
150
+ chunk = os.read(read_fd, 65536)
151
+ except OSError:
152
+ return
153
+ if not chunk:
154
+ return
155
+ with _CAP_LOCK:
156
+ _CAPTURED += chunk
157
+ pieces, flushed = _take_captured_locked()
158
+ _publish_captured(pieces, flushed)
159
+ if flushed:
160
+ _FLUSH_EVENT.set()
161
+
162
+
163
+ def _capture_user_stdout() -> None:
164
+ """Move the host pipe off fd 1 so raw user writes are cell output."""
165
+ global _PROTO_FD
166
+ if _PROTO_FD is not None:
167
+ return
168
+ try:
169
+ sys.__stdout__.flush()
170
+ except Exception:
171
+ pass
172
+ proto = os.dup(1)
173
+ os.set_inheritable(proto, False)
174
+ _PROTO_FD = proto
175
+ read_fd, write_fd = os.pipe()
176
+ os.set_inheritable(read_fd, False)
177
+ os.dup2(write_fd, 1, inheritable=True)
178
+ os.close(write_fd)
179
+ threading.Thread(
180
+ target=_pump_captured_stdout,
181
+ args=(read_fd,),
182
+ name="gjc-stdout-capture",
183
+ daemon=True,
184
+ ).start()
185
+
186
+
187
+ def _flush_captured_stdout() -> None:
188
+ if _PROTO_FD is None:
189
+ return
190
+ token = b"\0gjcfd1:" + os.urandom(8).hex().encode("ascii") + b"\0"
191
+ with _CAP_LOCK:
192
+ global _FLUSH_TOKEN
193
+ _FLUSH_EVENT.clear()
194
+ _FLUSH_TOKEN = token
195
+ _write_all(1, token)
196
+ if _FLUSH_EVENT.wait(5):
197
+ return
198
+ with _CAP_LOCK:
199
+ if _FLUSH_TOKEN == token:
200
+ _FLUSH_TOKEN = None
201
+ pieces, flushed = _take_captured_locked()
202
+ else:
203
+ pieces, flushed = [], False
204
+ _publish_captured(pieces, flushed)
71
205
 
72
206
 
73
207
  # ---------------------------------------------------------------------------
@@ -902,6 +1036,10 @@ def _handle_request(req: dict) -> None:
902
1036
  _flush_matplotlib_figures()
903
1037
  except Exception:
904
1038
  pass
1039
+ try:
1040
+ _flush_captured_stdout()
1041
+ except Exception:
1042
+ pass
905
1043
 
906
1044
  _emit({
907
1045
  "type": "done",
@@ -930,6 +1068,7 @@ def _emit_error(rid: str, exc: BaseException) -> None:
930
1068
 
931
1069
 
932
1070
  def main() -> None:
1071
+ _capture_user_stdout()
933
1072
  sys.stdout = _StreamProxy("stdout")
934
1073
  sys.stderr = _StreamProxy("stderr")
935
1074
  _install_idle_sigint()
@@ -12,6 +12,7 @@ import type {
12
12
  ShellRunResult,
13
13
  } from "@gajae-code/natives";
14
14
  import { postmortem } from "@gajae-code/utils";
15
+ import { projectDeclaredSpawnHookNames, stripProjectSpawnHooks } from "@gajae-code/utils/shell-config";
15
16
  import { Settings, type ShellMinimizerSettings } from "../config/settings";
16
17
  import { formatCrashDiagnosticNotice, writeCrashReport } from "../debug/crash-diagnostics";
17
18
  import {
@@ -93,6 +94,20 @@ export function setShellFactoryForTests(factory: ShellFactory | undefined): void
93
94
  shellFactoryForTests = factory;
94
95
  }
95
96
 
97
+ function projectHookUnsetEnv(
98
+ unsetEnv: string[] | undefined,
99
+ env: Record<string, string> | undefined,
100
+ sessionEnv: Record<string, string>,
101
+ ): string[] | undefined {
102
+ // Keys present in `sessionEnv` (e.g. the trusted HOME replacement) already
103
+ // override the ambient import, so only shadow the ones it does not carry.
104
+ const hooks = projectDeclaredSpawnHookNames().filter(
105
+ name => !Object.hasOwn(env ?? {}, name) && !Object.hasOwn(sessionEnv, name),
106
+ );
107
+ if (hooks.length === 0) return unsetEnv;
108
+ return [...(unsetEnv ?? []), ...hooks.filter(name => !unsetEnv?.includes(name))];
109
+ }
110
+
96
111
  async function createShell(
97
112
  options?: ShellOptions,
98
113
  onTerminal?: (shell: Shell) => void,
@@ -414,13 +429,67 @@ async function resolveShellCwd(cwd: string | undefined): Promise<string | undefi
414
429
  }
415
430
  }
416
431
 
432
+ function presentMinimizerString(value: unknown): string | undefined {
433
+ return typeof value === "string" && value.length > 0 ? value : undefined;
434
+ }
435
+
436
+ type ShellMinimizerSource = {
437
+ getGroup(prefix: "shellMinimizer"): ShellMinimizerSettings;
438
+ getGlobal?(path: "shellMinimizer.settingsPath" | "shellMinimizer.settingsHash"): unknown;
439
+ getOverride?(path: "shellMinimizer.settingsPath"): unknown;
440
+ };
441
+
442
+ /**
443
+ * A repo `.gjc/settings.json` must not point the minimizer at a TOML file.
444
+ * The native gate loads that file only when `settingsHash` matches, and the
445
+ * pin is the user/global value. A user path or a runtime override is still
446
+ * passed through. A project path is passed only together with that pin.
447
+ */
448
+ export function resolveTrustedShellMinimizer(settings: ShellMinimizerSource): ShellMinimizerSettings {
449
+ const group = settings.getGroup("shellMinimizer");
450
+ const globalPath =
451
+ typeof settings.getGlobal === "function"
452
+ ? presentMinimizerString(settings.getGlobal("shellMinimizer.settingsPath"))
453
+ : undefined;
454
+ const globalHash =
455
+ typeof settings.getGlobal === "function"
456
+ ? presentMinimizerString(settings.getGlobal("shellMinimizer.settingsHash"))
457
+ : undefined;
458
+ const overridePath =
459
+ typeof settings.getOverride === "function"
460
+ ? presentMinimizerString(settings.getOverride("shellMinimizer.settingsPath"))
461
+ : undefined;
462
+ const effectivePath = presentMinimizerString(group.settingsPath);
463
+
464
+ let settingsPath: string | undefined;
465
+ if (overridePath && effectivePath === overridePath) {
466
+ settingsPath = overridePath;
467
+ } else if (globalPath) {
468
+ settingsPath = globalPath;
469
+ } else if (effectivePath && globalHash) {
470
+ settingsPath = effectivePath;
471
+ }
472
+
473
+ // A runtime override of a different path is operator input, not the file the
474
+ // user hash pins. Every other admitted path is checked with that pin.
475
+ const unpinnedOverride = overridePath !== undefined && settingsPath === overridePath && settingsPath !== globalPath;
476
+ return {
477
+ ...group,
478
+ settingsPath,
479
+ settingsHash: globalHash && settingsPath && !unpinnedOverride ? globalHash : undefined,
480
+ };
481
+ }
482
+
417
483
  /** Translate `ShellMinimizerSettings` into native `MinimizerOptions`, or `undefined` when disabled. */
418
484
  export function buildMinimizerOptions(group: ShellMinimizerSettings): MinimizerOptions | undefined {
419
485
  if (!group.enabled) return undefined;
420
486
  const maxCaptureBytes = Math.min(4 * 1024 * 1024, Math.max(1024, Math.trunc(group.maxCaptureBytes)));
487
+ const settingsPath = group.settingsPath || undefined;
488
+ const settingsHash = settingsPath && group.settingsHash ? group.settingsHash : undefined;
421
489
  return {
422
490
  enabled: true,
423
- settingsPath: group.settingsPath || undefined,
491
+ settingsPath,
492
+ ...(settingsHash ? { settingsHash } : {}),
424
493
  only: group.only.length > 0 ? group.only : undefined,
425
494
  except: group.except.length > 0 ? group.except : undefined,
426
495
  maxCaptureBytes,
@@ -429,12 +498,16 @@ export function buildMinimizerOptions(group: ShellMinimizerSettings): MinimizerO
429
498
 
430
499
  export async function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
431
500
  const settings = options?.settings ?? (await Settings.init());
432
- const { shell, env: shellEnv, prefix } = settings.getShellConfig();
501
+ const { shell, env: configEnv, prefix } = settings.getShellConfig();
502
+ // The shell config env is shared with other consumers (Python kernels keep
503
+ // their own documented allowlist). Only the bash tool's shell must not see
504
+ // project hook variables such as BASH_ENV or GIT_CONFIG_*.
505
+ const shellEnv = stripProjectSpawnHooks({ ...configEnv });
433
506
  const configuredPrefix = options?.ignoreShellPrefix ? undefined : prefix;
434
507
  const snapshotPath =
435
508
  !options?.disableShellSnapshot && shell.includes("bash") ? await getOrCreateSnapshot(shell, shellEnv) : null;
436
509
 
437
- const minimizer = buildMinimizerOptions(settings.getGroup("shellMinimizer"));
510
+ const minimizer = buildMinimizerOptions(resolveTrustedShellMinimizer(settings));
438
511
 
439
512
  const commandCwd = await resolveShellCwd(options?.cwd);
440
513
  const commandEnv = options?.env ? { ...NON_INTERACTIVE_ENV, ...options.env } : NON_INTERACTIVE_ENV;
@@ -580,7 +653,11 @@ export async function executeBash(command: string, options?: BashExecutorOptions
580
653
  command: finalCommand,
581
654
  cwd: commandCwd,
582
655
  env: commandEnv,
583
- unsetEnv: options?.unsetEnv,
656
+ // The native shell imports the host process environment before
657
+ // `sessionEnv`, and where it runs in-process (Windows) that environment
658
+ // holds the autoloaded project `.env`. Shadow project hook variables per
659
+ // run unless this call sets them explicitly.
660
+ unsetEnv: projectHookUnsetEnv(options?.unsetEnv, options?.env, shellEnv),
584
661
  timeoutMs: executionTimeoutMs,
585
662
  signal: runAbortController.signal,
586
663
  };
@@ -8,6 +8,7 @@ import * as path from "node:path";
8
8
  import * as readline from "node:readline";
9
9
  import type { Process as NativeProcess } from "@gajae-code/natives";
10
10
  import { isCompiledBinary } from "@gajae-code/utils/env";
11
+ import { projectSafeSpawnEnv } from "@gajae-code/utils/shell-config";
11
12
  import { BASH_SHELL_SUPERVISOR_ARG, type BashShellWorkerRequest } from "./bash-shell-worker-protocol";
12
13
 
13
14
  type NativeProcessBindings = { Process: typeof NativeProcess };
@@ -401,7 +402,9 @@ export async function runBashShellGuardian(): Promise<void> {
401
402
  const [executable, ...args] = supervisorArgv();
402
403
  const supervisor = childProcess.spawn(executable!, args, {
403
404
  detached: process.platform !== "win32",
404
- env: process.env,
405
+ // This process reloaded the project `.env` on startup; keep project hook
406
+ // variables out of the supervisor -> runtime -> native shell chain.
407
+ env: projectSafeSpawnEnv(),
405
408
  stdio: ["pipe", "inherit", "inherit"],
406
409
  windowsHide: true,
407
410
  });
@@ -8,6 +8,7 @@ import * as path from "node:path";
8
8
  import * as readline from "node:readline";
9
9
  import type { Process as NativeProcess } from "@gajae-code/natives";
10
10
  import { isCompiledBinary } from "@gajae-code/utils/env";
11
+ import { projectSafeSpawnEnv } from "@gajae-code/utils/shell-config";
11
12
  import { parseLinuxProcParentPid, parseLinuxProcStartTime, parseLinuxProcState } from "../gjc-runtime/linux-proc";
12
13
  import { processIncarnation } from "../sdk/broker/process-incarnation";
13
14
  import { fatalCatchableSignals, signalExitCode } from "./bash-shell-signals";
@@ -39,9 +40,10 @@ function darwinUniqueProcessId(pid: number, incarnation: string): string | undef
39
40
  }
40
41
 
41
42
  function runtimeArgv(): string[] {
42
- return isCompiledBinary()
43
- ? [process.execPath, BASH_SHELL_RUNTIME_ARG]
44
- : [process.execPath, path.join(import.meta.dir, "bash-shell-runtime-entry.ts")];
43
+ if (isCompiledBinary()) return [process.execPath, BASH_SHELL_RUNTIME_ARG];
44
+ // The supervisor already sanitized this environment. Do not let the source
45
+ // runtime reload a cwd `.env` that may have changed since the snapshot.
46
+ return [process.execPath, "--no-env-file", path.join(import.meta.dir, "bash-shell-runtime-entry.ts")];
45
47
  }
46
48
 
47
49
  function enableLinuxChildSubreaper(): void {
@@ -264,7 +266,9 @@ export async function runBashShellSupervisor(): Promise<void> {
264
266
  const worker = childProcess.spawn(command!, args, {
265
267
  cwd: process.cwd(),
266
268
  detached: false,
267
- env: { ...process.env, GJC_ISOLATED_SHELL_RUNTIME: "1" },
269
+ // This process reloaded the project `.env` on startup; the runtime's native
270
+ // shell imports its environment wholesale, so drop project hook variables.
271
+ env: { ...projectSafeSpawnEnv(), GJC_ISOLATED_SHELL_RUNTIME: "1" },
268
272
  stdio: ["pipe", "pipe", "pipe"],
269
273
  windowsHide: true,
270
274
  });
@@ -5,6 +5,7 @@ import * as path from "node:path";
5
5
  import * as readline from "node:readline";
6
6
  import type { ShellRunOptions } from "@gajae-code/natives";
7
7
  import { isCompiledBinary } from "@gajae-code/utils/env";
8
+ import { projectSafeSpawnEnv } from "@gajae-code/utils/shell-config";
8
9
  import {
9
10
  BASH_SHELL_WORKER_ARG,
10
11
  type BashShellWorkerRequest,
@@ -84,7 +85,10 @@ export class IsolatedShell {
84
85
  this.#child = childProcess.spawn(command!, args, {
85
86
  cwd: process.cwd(),
86
87
  detached: process.platform !== "win32",
87
- env: process.env,
88
+ // The worker chain (guardian -> supervisor -> runtime) inherits this env and
89
+ // the native shell imports it wholesale, so project hook variables must be
90
+ // gone here, not only from the per-session overlay.
91
+ env: projectSafeSpawnEnv(),
88
92
  stdio: ["pipe", "pipe", "pipe"],
89
93
  windowsHide: true,
90
94
  });
@@ -2,7 +2,7 @@ import * as syncFs from "node:fs";
2
2
  import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
4
  import type { AgentState } from "@gajae-code/agent-core";
5
- import { APP_NAME, isEnoent } from "@gajae-code/utils";
5
+ import { APP_NAME, isEnoent, redactCrashSecrets } from "@gajae-code/utils";
6
6
  import { getResolvedThemeColors, getThemeExportColors } from "../../modes/theme/theme";
7
7
  import { SessionManager } from "../../session/session-manager";
8
8
  // Pre-generated template (created by scripts/generate-template.ts at publish time)
@@ -226,18 +226,18 @@ async function writeSessionHtml(
226
226
  };
227
227
  try {
228
228
  sink.write(themedTemplate.slice(0, markerOffset));
229
- writeEncoded(`{"header":${JSON.stringify(sm.getHeader())},"entries":[`);
229
+ writeEncoded(`{"header":${sessionEmbedJson(sm.getHeader())},"entries":[`);
230
230
  let first = true;
231
231
  sm.visitEntriesForExport(entry => {
232
- writeEncoded(`${first ? "" : ","}${JSON.stringify(entry)}`);
232
+ writeEncoded(`${first ? "" : ","}${sessionEmbedJson(entry)}`);
233
233
  first = false;
234
234
  });
235
- writeEncoded(`],"leafId":${JSON.stringify(sm.getLeafId())}`);
235
+ writeEncoded(`],"leafId":${sessionEmbedJson(sm.getLeafId())}`);
236
236
  if (state?.systemPrompt !== undefined)
237
- writeEncoded(`,"systemPrompt":${JSON.stringify(state.systemPrompt.join("\n\n"))}`);
237
+ writeEncoded(`,"systemPrompt":${sessionEmbedJson(state.systemPrompt.join("\n\n"))}`);
238
238
  if (state?.tools !== undefined)
239
239
  writeEncoded(
240
- `,"tools":${JSON.stringify(state.tools.map(tool => ({ name: tool.name, description: tool.description })))}`,
240
+ `,"tools":${sessionEmbedJson(state.tools.map(tool => ({ name: tool.name, description: tool.description })))}`,
241
241
  );
242
242
  writeEncoded("}");
243
243
  const final = encoder.finish();
@@ -253,6 +253,46 @@ async function writeSessionHtml(
253
253
  }
254
254
  }
255
255
 
256
+ /**
257
+ * Own `__proto__` stays a data property; a normal object would turn that assignment into the prototype.
258
+ * Each field is redacted together with its key. A detached leaf such as `abcdefgh1234` under `password`
259
+ * would otherwise miss the key-aware rule. If that serialized field does not parse, the value is
260
+ * redacted on its own so a quotation mark inside a labeled secret cannot break the export.
261
+ */
262
+ function redactJsonValue(value: unknown): unknown {
263
+ if (typeof value === "string") return redactCrashSecrets(value);
264
+ if (Array.isArray(value)) return value.map(item => redactJsonValue(item));
265
+ if (value === null || typeof value !== "object") return value;
266
+ const source = value as Record<string, unknown>;
267
+ const redacted = Object.create(null) as Record<string, unknown>;
268
+ for (const key of Object.keys(source)) redacted[key] = redactKeyedValue(key, source[key]);
269
+ return redacted;
270
+ }
271
+
272
+ const CREDENTIAL_JSON_KEY =
273
+ /^(?:api[_-]?key|apikey|access[_-]?token|refresh[_-]?token|id[_-]?token|session[_-]?id|session[_-]?token|client[_-]?secret|secret[_-]?key|aws[_-]?secret[_-]?access[_-]?key|secret[_-]?access[_-]?key|password|passwd|authorization)$/i;
274
+
275
+ function redactKeyedValue(key: string, value: unknown): unknown {
276
+ // A recognized key is authoritative for every JSON type, including a short
277
+ // string, a number, an array, or a nested object.
278
+ if (CREDENTIAL_JSON_KEY.test(key)) return "«redacted»";
279
+ if (typeof value !== "string") return redactJsonValue(value);
280
+ const encoded = JSON.stringify({ [key]: value });
281
+ try {
282
+ const parsed = JSON.parse(redactCrashSecrets(encoded)) as Record<string, unknown>;
283
+ if (parsed !== null && typeof parsed === "object" && Object.hasOwn(parsed, key)) return parsed[key];
284
+ } catch {
285
+ // A quote inside a credential field can make the redacted JSON invalid.
286
+ // Mask that field instead of exporting the raw value without its key.
287
+ if (CREDENTIAL_JSON_KEY.test(key)) return "«redacted»";
288
+ }
289
+ return redactCrashSecrets(value);
290
+ }
291
+
292
+ function sessionEmbedJson(value: unknown): string {
293
+ return JSON.stringify(redactJsonValue(JSON.parse(JSON.stringify(value))));
294
+ }
295
+
256
296
  /** Export session to HTML using SessionManager and AgentState. */
257
297
  export async function exportSessionToHtml(
258
298
  sm: SessionManager,
@@ -21,9 +21,11 @@ export class CustomToolAdapter<TParams extends TSchema = TSchema, TDetails = any
21
21
  private tool: CustomTool<TParams, TDetails>,
22
22
  private getContext: () => CustomToolContext,
23
23
  ) {
24
- applyToolProxy(tool, this);
24
+ // Own these before proxying: with useDefineForClassFields off, the uninitialized fields do not
25
+ // exist yet, so applyToolProxy would install getter-only accessors that reject assignment.
25
26
  this.strict = tool.strict;
26
27
  this.concurrency = tool.concurrency;
28
+ applyToolProxy(tool, this);
27
29
  }
28
30
 
29
31
  execute(
@@ -791,7 +791,14 @@ export async function discoverAndLoadExtensions(
791
791
  ...(options.profileAuthority === undefined ? {} : { profileAuthority: options.profileAuthority }),
792
792
  ...(options.settings === undefined ? {} : { settings: options.settings }),
793
793
  });
794
- for (const ext of discovered.items) {
794
+ // Filter project modules before first-wins name deduplication. `items` has
795
+ // already deduplicated, so a project module could otherwise shadow the
796
+ // same-named user module before being rejected here.
797
+ const seenDiscoveredNames = new Set<string>();
798
+ for (const ext of discovered.all) {
799
+ if (ext._source.provider === "native" && ext.level === "project") continue;
800
+ if (seenDiscoveredNames.has(ext.name)) continue;
801
+ seenDiscoveredNames.add(ext.name);
795
802
  if (ext._source.provider !== "native") continue;
796
803
  addPath(ext.path);
797
804
  }
@@ -10,7 +10,7 @@ import * as https from "node:https";
10
10
  import * as net from "node:net";
11
11
  import * as os from "node:os";
12
12
  import * as path from "node:path";
13
- import { isEnoent, logger } from "@gajae-code/utils";
13
+ import { isEnoent, logger, redactCrashSecrets } from "@gajae-code/utils";
14
14
  import * as git from "../../../utils/git";
15
15
  import { isPrivateOrSpecialAddress, validatePublicHttpUrl } from "../../../web/insane/url-guard";
16
16
 
@@ -461,7 +461,7 @@ async function cloneAndReadCatalog(url: string, cacheDir: string): Promise<Fetch
461
461
  const tmpDir = path.join(cacheDir, `.tmp-clone-${Date.now()}`);
462
462
  await fs.mkdir(cacheDir, { recursive: true });
463
463
 
464
- logger.debug(`[marketplace] cloning ${url} → ${tmpDir}`);
464
+ logger.debug(redactCrashSecrets(`[marketplace] cloning ${url} → ${tmpDir}`));
465
465
  await git.clone(url, tmpDir);
466
466
 
467
467
  const catalogPath = path.join(tmpDir, CATALOG_RELATIVE_PATH);
@@ -18,7 +18,15 @@ import * as fsp from "node:fs/promises";
18
18
  import * as os from "node:os";
19
19
  import * as path from "node:path";
20
20
  import { exactRemoveDirectoryTree, exactUnlink, snapshotDirectoryTree } from "@gajae-code/natives";
21
- import { getAgentDir, getBlobsDir, getSessionsDir, isEnoent, VERSION } from "@gajae-code/utils";
21
+ import {
22
+ getAgentDir,
23
+ getBlobsDir,
24
+ getSessionsDir,
25
+ isEnoent,
26
+ loadedProjectEnvSnapshot,
27
+ VERSION,
28
+ } from "@gajae-code/utils";
29
+ import { type ProjectEnvSnapshot, projectEnvSnapshot } from "@gajae-code/utils/env-file";
22
30
  import { getDefault } from "../config/settings-schema";
23
31
  import { listHarnessRootRegistriesForGc } from "../harness-control-plane/storage";
24
32
  import { SessionIndex } from "../sdk/broker/session-index";
@@ -54,6 +62,7 @@ import {
54
62
  } from "./empty-delete-gc";
55
63
  import { buildGcReportText } from "./gc-render";
56
64
  import { collectSessionScopeUsage, type GcSessionScopeUsage, shouldReportSessionScope } from "./gc-session-scope";
65
+ import { trustedCoordinatorEnv } from "./sidecar-bootstrap-env";
57
66
 
58
67
  export type GcStore = "harness_leases" | "file_locks" | "tmux_sessions" | "registry_entries" | "local_roots";
59
68
 
@@ -456,8 +465,21 @@ export function computeExitCode(report: GcReport): number {
456
465
  return 0;
457
466
  }
458
467
 
459
- function resolveGcAgentDir(env: NodeJS.ProcessEnv): string {
460
- return env.GJC_CODING_AGENT_DIR?.trim() || env.PI_CODING_AGENT_DIR?.trim() || getAgentDir();
468
+ /**
469
+ * Operator override only. Same project-dotenv rule as `getAgentDir()`: a value
470
+ * the checkout declares is ignored, and an operator value it does not declare
471
+ * is kept. A repository `.env` must not choose the tree `gjc gc` reclaims.
472
+ *
473
+ * `snapshot` is the dotenv provenance from when that environment was loaded.
474
+ * `process.env` uses the module-load snapshot. A separate env map uses the
475
+ * snapshot taken with it before awaited GC work. Neither path rereads files later.
476
+ */
477
+ function resolveGcAgentDir(env: NodeJS.ProcessEnv, snapshot: ProjectEnvSnapshot): string {
478
+ return (
479
+ trustedCoordinatorEnv("GJC_CODING_AGENT_DIR", env, snapshot) ||
480
+ trustedCoordinatorEnv("PI_CODING_AGENT_DIR", env, snapshot) ||
481
+ getAgentDir()
482
+ );
461
483
  }
462
484
 
463
485
  /**
@@ -572,6 +594,10 @@ export async function runGjcGcCommand(
572
594
  adapters?: GcStoreAdapter[],
573
595
  diskPolicy?: Partial<GcDiskPolicy>,
574
596
  ): Promise<GcRunResult> {
597
+ // process.env was loaded before this module. Reusing that snapshot keeps a
598
+ // project value rejected after its declaration is removed or changed.
599
+ // A caller-supplied env is not that load: bind its cwd files once, here.
600
+ const loadedProjectEnv = env === process.env ? loadedProjectEnvSnapshot() : projectEnvSnapshot(cwd);
575
601
  let parsed: ParsedGcArgs;
576
602
  try {
577
603
  parsed = parseGcArgs(argv);
@@ -656,17 +682,18 @@ export async function runGjcGcCommand(
656
682
  const ordinaryPrune = parsed.prune && (emptyDeleteReceipts?.errors.length ?? 0) === 0;
657
683
  const report = await collectGcReport(resolvedAdapters, ctx, ordinaryPrune);
658
684
  report.operation = parsed.repairSessionIndex ? "repair_session_index" : parsed.prune ? "prune" : "dry_run";
659
- report.session_index = await collectSessionIndexHealth(parsed.repairSessionIndex, resolveGcAgentDir(env));
685
+ const agentDir = resolveGcAgentDir(env, loadedProjectEnv);
686
+ report.session_index = await collectSessionIndexHealth(parsed.repairSessionIndex, agentDir);
660
687
  if (parsed.disk) {
661
688
  report.disk = await collectGcDiskReport({
662
- agentDir: resolveGcAgentDir(env),
689
+ agentDir,
663
690
  env,
664
691
  policy: resolveGcDiskPolicy(diskPolicy),
665
692
  prune: ordinaryPrune,
666
693
  });
667
694
  }
668
695
  if (emptyDeleteReceipts) report.empty_delete_receipts = emptyDeleteReceipts;
669
- const scopeUsage = await collectGcSessionScope(cwd, resolveGcAgentDir(env));
696
+ const scopeUsage = await collectGcSessionScope(cwd, agentDir);
670
697
  if (scopeUsage && shouldReportSessionScope(scopeUsage)) report.session_scope = scopeUsage;
671
698
  const sessionIndexFailed =
672
699
  report.session_index?.status === "corrupt" ||
@@ -13,6 +13,7 @@ import {
13
13
  MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV,
14
14
  MANAGED_OWNER_PREDECESSOR_TOKEN_ENV,
15
15
  MANAGED_OWNER_TRANSCRIPT_PATH_ENV,
16
+ trustedManagedOwnerLaunchSessionFile,
16
17
  } from "./managed-owner-admission";
17
18
  import {
18
19
  MANAGED_OWNER_INCARNATION_ENV,
@@ -1249,8 +1250,12 @@ function prepareManagedOwnerLifecycle(plan: TmuxLaunchPlan, context: TmuxLaunchC
1249
1250
  [MANAGED_OWNER_PREDECESSOR_GENERATION_ENV]: replacement.generation,
1250
1251
  [MANAGED_OWNER_PREDECESSOR_RUN_ID_ENV]: replacement.runId,
1251
1252
  [MANAGED_OWNER_PREDECESSOR_INCARNATION_ENV]: replacement.incarnation,
1252
- [MANAGED_OWNER_TRANSCRIPT_PATH_ENV]:
1253
- context.env?.GJC_SESSION_FILE ?? process.env.GJC_SESSION_FILE ?? "",
1253
+ [MANAGED_OWNER_TRANSCRIPT_PATH_ENV]: trustedManagedOwnerLaunchSessionFile(
1254
+ {
1255
+ GJC_SESSION_FILE: context.env?.GJC_SESSION_FILE ?? process.env.GJC_SESSION_FILE,
1256
+ },
1257
+ plan.cwd,
1258
+ ),
1254
1259
  }
1255
1260
  : {}),
1256
1261
  },