sneakoscope 7.3.0 → 7.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (283) hide show
  1. package/README.md +129 -10
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/cli/codex-app-command.js +1 -1
  5. package/dist/cli/command-manifest-lite.js +4 -1
  6. package/dist/cli/command-registry.js +2 -0
  7. package/dist/cli/install-helpers-codex-lb-chain.js +42 -0
  8. package/dist/cli/install-helpers-codex-lb-config.js +114 -9
  9. package/dist/cli/install-helpers-install-support.js +6 -16
  10. package/dist/cli/install-helpers.js +788 -600
  11. package/dist/cli/router.js +30 -2
  12. package/dist/commands/codex-app.js +37 -1
  13. package/dist/commands/codex-lb.js +491 -300
  14. package/dist/config/skills-manifest.json +57 -71
  15. package/dist/core/align/align-route.js +127 -0
  16. package/dist/core/codex-app/codex-app-fast-ui-repair.js +8 -4
  17. package/dist/core/codex-app/codex-app-restart.js +83 -0
  18. package/dist/core/codex-app/codex-app-ui-state-snapshot.js +8 -4
  19. package/dist/core/codex-app/desktop-routing-snapshot.js +1 -1
  20. package/dist/core/codex-app/menubar/constants.js +5 -0
  21. package/dist/core/codex-app/menubar/generation-transaction-journal.js +1 -3
  22. package/dist/core/codex-app/menubar/installer.js +18 -0
  23. package/dist/core/codex-app/multi-provider-router.js +22 -10
  24. package/dist/core/codex-app/openrouter-activate.js +26 -1
  25. package/dist/core/codex-app.js +118 -26
  26. package/dist/core/codex-control/codex-app-server-runtime-env.js +86 -0
  27. package/dist/core/codex-control/codex-app-server-v2-client.js +78 -8
  28. package/dist/core/codex-control/codex-task-runner.js +2 -2
  29. package/dist/core/codex-lb/capability-runner.js +204 -0
  30. package/dist/core/codex-lb/capability-types.js +1 -0
  31. package/dist/core/codex-lb/codex-lb-env.js +9 -13
  32. package/dist/core/codex-lb/codex-lb-setup.js +45 -8
  33. package/dist/core/codex-lb/codex-lb-tool-catalog.js +52 -74
  34. package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +1 -1
  35. package/dist/core/codex-lb/desktop-auth-invariant.js +185 -0
  36. package/dist/core/codex-lb/desktop-bridge/client.js +34 -0
  37. package/dist/core/codex-lb/desktop-bridge/header-policy.js +106 -0
  38. package/dist/core/codex-lb/desktop-bridge/http-forward.js +101 -0
  39. package/dist/core/codex-lb/desktop-bridge/index.js +10 -0
  40. package/dist/core/codex-lb/desktop-bridge/launchd.js +81 -0
  41. package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +27 -0
  42. package/dist/core/codex-lb/desktop-bridge/security.js +274 -0
  43. package/dist/core/codex-lb/desktop-bridge/server.js +179 -0
  44. package/dist/core/codex-lb/desktop-bridge/state.js +119 -0
  45. package/dist/core/codex-lb/desktop-bridge/types.js +16 -0
  46. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +151 -0
  47. package/dist/core/codex-lb/desktop-center-credentials.js +189 -0
  48. package/dist/core/codex-lb/desktop-controller.js +1410 -0
  49. package/dist/core/codex-lb/desktop-mode.js +32 -0
  50. package/dist/core/codex-lb/desktop-real-evidence.js +168 -0
  51. package/dist/core/codex-lb/desktop-service.js +613 -0
  52. package/dist/core/codex-lb/legacy-migration.js +575 -0
  53. package/dist/core/codex-lb/migration-receipt.js +246 -0
  54. package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +60 -0
  55. package/dist/core/codex-lb/probes/bridge-probe.js +75 -0
  56. package/dist/core/codex-lb/probes/catalog-probe.js +128 -0
  57. package/dist/core/codex-lb/probes/computer-use-probe.js +50 -0
  58. package/dist/core/codex-lb/probes/image-generation-probe.js +47 -0
  59. package/dist/core/codex-lb/probes/probe-evidence.js +39 -0
  60. package/dist/core/codex-lb/probes/voice-realtime-probe.js +44 -0
  61. package/dist/core/codex-lb/trusted-deep-evidence.js +403 -0
  62. package/dist/core/codex-native/core-skill-manifest.js +22 -6
  63. package/dist/core/codex-runtime/codex-desktop-config-policy.js +2 -1
  64. package/dist/core/commands/align-command.js +220 -0
  65. package/dist/core/commands/image-ux-review-command.js +280 -68
  66. package/dist/core/commands/naruto-command.js +257 -8
  67. package/dist/core/commands/ppt-command.js +2 -2
  68. package/dist/core/commands/telegram-command.js +402 -34
  69. package/dist/core/commands/triwiki-command.js +11 -1
  70. package/dist/core/commands/triwiki-graph-command.js +160 -0
  71. package/dist/core/commands/wiki-command.js +100 -20
  72. package/dist/core/doctor/skill-legacy-surface.js +1 -3
  73. package/dist/core/errors/message.js +3 -0
  74. package/dist/core/evidence/evidence-freshness.js +14 -0
  75. package/dist/core/feature-fixtures.js +9 -1
  76. package/dist/core/feature-registry.js +5 -3
  77. package/dist/core/fsx.js +7 -3
  78. package/dist/core/hooks-runtime/codex-commit-hooks-selftest.js +82 -0
  79. package/dist/core/hooks-runtime/context-graph-freshness-preflight.js +113 -0
  80. package/dist/core/hooks-runtime/hook-context.js +15 -2
  81. package/dist/core/hooks-runtime/managed-skill-generation-heal.js +46 -0
  82. package/dist/core/hooks-runtime/naruto-terminal-finalization.js +1 -0
  83. package/dist/core/hooks-runtime/official-subagent-lifecycle.js +1 -1
  84. package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
  85. package/dist/core/hooks-runtime.js +33 -89
  86. package/dist/core/image-ux-review/callout-extraction.js +6 -0
  87. package/dist/core/image-ux-review/imagegen-adapter.js +64 -23
  88. package/dist/core/image-ux-review/real-callout-extractor.js +1 -0
  89. package/dist/core/image-ux-review.js +230 -12
  90. package/dist/core/imagegen/codex-lb-imagegen-target.js +21 -7
  91. package/dist/core/imagegen/imagegen-capability.js +10 -4
  92. package/dist/core/imagegen/require-imagegen.js +29 -5
  93. package/dist/core/init/skills.js +25 -13
  94. package/dist/core/init.js +1 -1
  95. package/dist/core/install/installed-package-smoke.js +157 -4
  96. package/dist/core/managed-assets/managed-assets-manifest.js +2 -1
  97. package/dist/core/managed-path-safety.js +8 -10
  98. package/dist/core/mcp-config/backup.js +2 -4
  99. package/dist/core/mcp-config/config-reader.js +2 -4
  100. package/dist/core/mcp-config/scope.js +3 -5
  101. package/dist/core/naruto/context-graph-advisor-scope.js +242 -0
  102. package/dist/core/naruto/context-graph-advisor.js +0 -0
  103. package/dist/core/perf/release-latency-slo.js +12 -6
  104. package/dist/core/pipeline-internals/runtime-core.js +78 -0
  105. package/dist/core/pipeline-internals/runtime-gates.js +5 -2
  106. package/dist/core/policy/latest-version-guidance.js +113 -0
  107. package/dist/core/ppt.js +8 -8
  108. package/dist/core/preflight/parallel-preflight-engine.js +1 -1
  109. package/dist/core/proof/auto-finalize.js +98 -6
  110. package/dist/core/proof/route-proof-gate.js +12 -1
  111. package/dist/core/proof/route-proof-policy.js +8 -8
  112. package/dist/core/release/gate-manifest.js +32 -1
  113. package/dist/core/release/npm-stage-contract.js +21 -0
  114. package/dist/core/release/package-size-budget.js +2 -2
  115. package/dist/core/release/release-gate-contract.js +5 -0
  116. package/dist/core/release/release-proof-io.js +1 -3
  117. package/dist/core/release/release-real-contract.js +5 -4
  118. package/dist/core/release/release-stamp-proof.js +1 -3
  119. package/dist/core/release/stage-publish.js +27 -0
  120. package/dist/core/remote/audit-idempotency.js +50 -18
  121. package/dist/core/remote/codex-public-activity.js +283 -0
  122. package/dist/core/remote/index.js +1 -0
  123. package/dist/core/remote/ssh-worker-client.js +4 -1
  124. package/dist/core/remote/worker.js +206 -27
  125. package/dist/core/research/research-adversarial-review.js +1 -3
  126. package/dist/core/research/research-claim-synthesizer.js +1 -3
  127. package/dist/core/research/research-falsification-runner.js +1 -3
  128. package/dist/core/research/research-source-evidence.js +1 -3
  129. package/dist/core/responses-stream.js +65 -19
  130. package/dist/core/routes/constants.js +1 -1
  131. package/dist/core/routes/design-policy.js +7 -2
  132. package/dist/core/routes/dollar-manifest-lite.js +2 -4
  133. package/dist/core/routes/evidence.js +1 -1
  134. package/dist/core/routes.js +30 -10
  135. package/dist/core/safety/command-contract/contracts.js +2 -0
  136. package/dist/core/safety/command-contract/types.js +1 -1
  137. package/dist/core/search/context-graph-seeds.js +205 -0
  138. package/dist/core/search/context.js +0 -0
  139. package/dist/core/search/provider.js +2 -2
  140. package/dist/core/secret-redaction.js +6 -0
  141. package/dist/core/subagents/naruto-help-contract.js +2 -1
  142. package/dist/core/subagents/naruto-host-credentials.js +161 -0
  143. package/dist/core/subagents/official-subagent-preparation.js +4 -1
  144. package/dist/core/subagents/official-subagent-prompt.js +44 -2
  145. package/dist/core/subagents/official-subagent-runner.js +26 -8
  146. package/dist/core/subagents/triwiki-attention.js +135 -123
  147. package/dist/core/telegram/bot-api.js +85 -10
  148. package/dist/core/telegram/center-snapshot.js +718 -0
  149. package/dist/core/telegram/config.js +158 -31
  150. package/dist/core/telegram/hub.js +288 -33
  151. package/dist/core/telegram/index.js +2 -0
  152. package/dist/core/telegram/ingress-spool.js +419 -0
  153. package/dist/core/telegram/ledgers.js +188 -29
  154. package/dist/core/telegram/messages.js +26 -6
  155. package/dist/core/telegram/owner-lock.js +10 -7
  156. package/dist/core/telegram/runtime-projection.js +38 -3
  157. package/dist/core/telegram/runtime.js +298 -63
  158. package/dist/core/telegram/service.js +137 -8
  159. package/dist/core/telegram/setup.js +291 -73
  160. package/dist/core/text/strings.js +3 -0
  161. package/dist/core/triwiki/code-pack.js +12 -120
  162. package/dist/core/triwiki/context-graph/compiler/cache-key.js +277 -0
  163. package/dist/core/triwiki/context-graph/compiler/extract.js +104 -0
  164. package/dist/core/triwiki/context-graph/compiler/freshness.js +108 -0
  165. package/dist/core/triwiki/context-graph/compiler/index.js +237 -0
  166. package/dist/core/triwiki/context-graph/compiler/merge.js +0 -0
  167. package/dist/core/triwiki/context-graph/compiler/serialize.js +115 -0
  168. package/dist/core/triwiki/context-graph/contracts.js +236 -0
  169. package/dist/core/triwiki/context-graph/extractors/code/builders.js +150 -0
  170. package/dist/core/triwiki/context-graph/extractors/code/declarations.js +161 -0
  171. package/dist/core/triwiki/context-graph/extractors/code/index.js +265 -0
  172. package/dist/core/triwiki/context-graph/extractors/code/inventory.js +237 -0
  173. package/dist/core/triwiki/context-graph/extractors/code/module-graph.js +252 -0
  174. package/dist/core/triwiki/context-graph/extractors/code/modules.js +79 -0
  175. package/dist/core/triwiki/context-graph/extractors/code/references.js +184 -0
  176. package/dist/core/triwiki/context-graph/extractors/code/selection.js +100 -0
  177. package/dist/core/triwiki/context-graph/extractors/code/ts-config.js +161 -0
  178. package/dist/core/triwiki/context-graph/extractors/code/types.js +18 -0
  179. package/dist/core/triwiki/context-graph/extractors/evidence/claims.js +289 -0
  180. package/dist/core/triwiki/context-graph/extractors/evidence/index.js +54 -0
  181. package/dist/core/triwiki/context-graph/extractors/evidence/proof-index.js +213 -0
  182. package/dist/core/triwiki/context-graph/extractors/evidence/proofs.js +266 -0
  183. package/dist/core/triwiki/context-graph/extractors/evidence/redaction.js +177 -0
  184. package/dist/core/triwiki/context-graph/extractors/evidence/shared.js +0 -0
  185. package/dist/core/triwiki/context-graph/extractors/evidence/sources.js +154 -0
  186. package/dist/core/triwiki/context-graph/extractors/index.js +7 -0
  187. package/dist/core/triwiki/context-graph/extractors/topology/commands.js +207 -0
  188. package/dist/core/triwiki/context-graph/extractors/topology/gate-edges.js +201 -0
  189. package/dist/core/triwiki/context-graph/extractors/topology/gates.js +182 -0
  190. package/dist/core/triwiki/context-graph/extractors/topology/globs.js +124 -0
  191. package/dist/core/triwiki/context-graph/extractors/topology/index.js +92 -0
  192. package/dist/core/triwiki/context-graph/extractors/topology/routes.js +135 -0
  193. package/dist/core/triwiki/context-graph/extractors/topology/shared.js +215 -0
  194. package/dist/core/triwiki/context-graph/graph-index.js +143 -0
  195. package/dist/core/triwiki/context-graph/ids.js +0 -0
  196. package/dist/core/triwiki/context-graph/lint/index.js +0 -0
  197. package/dist/core/triwiki/context-graph/lint/rules.js +232 -0
  198. package/dist/core/triwiki/context-graph/lint/warnings.js +93 -0
  199. package/dist/core/triwiki/context-graph/paths.js +116 -0
  200. package/dist/core/triwiki/context-graph/profiles.js +78 -0
  201. package/dist/core/triwiki/context-graph/projections/anchors.js +91 -0
  202. package/dist/core/triwiki/context-graph/projections/attention.js +58 -0
  203. package/dist/core/triwiki/context-graph/projections/code-pack-workspace.js +62 -0
  204. package/dist/core/triwiki/context-graph/projections/code-pack.js +144 -0
  205. package/dist/core/triwiki/context-graph/projections/index.js +7 -0
  206. package/dist/core/triwiki/context-graph/projections/module-view.js +140 -0
  207. package/dist/core/triwiki/context-graph/projections/node-summary.js +118 -0
  208. package/dist/core/triwiki/context-graph/projections/pack-contract.js +6 -0
  209. package/dist/core/triwiki/context-graph/query/explain.js +0 -0
  210. package/dist/core/triwiki/context-graph/query/index.js +178 -0
  211. package/dist/core/triwiki/context-graph/query/load.js +116 -0
  212. package/dist/core/triwiki/context-graph/query/pack.js +117 -0
  213. package/dist/core/triwiki/context-graph/query/rank.js +142 -0
  214. package/dist/core/triwiki/context-graph/query/ranking-config.js +64 -0
  215. package/dist/core/triwiki/context-graph/query/seeds.js +241 -0
  216. package/dist/core/triwiki/context-graph/query/snapshot-cache.js +72 -0
  217. package/dist/core/triwiki/context-graph/query/traverse.js +154 -0
  218. package/dist/core/triwiki/context-graph/query-types.js +29 -0
  219. package/dist/core/triwiki/context-graph/store/compile-lock.js +21 -0
  220. package/dist/core/triwiki/context-graph/store/event-log.js +48 -0
  221. package/dist/core/triwiki/context-graph/store/fragment-cache.js +85 -0
  222. package/dist/core/triwiki/context-graph/store/graph-status.js +85 -0
  223. package/dist/core/triwiki/context-graph/store/snapshot-store.js +155 -0
  224. package/dist/core/triwiki/triwiki-proof-bank-index-repair.js +115 -0
  225. package/dist/core/triwiki/triwiki-proof-bank-index-store.js +290 -0
  226. package/dist/core/triwiki/triwiki-proof-bank-index.js +126 -0
  227. package/dist/core/triwiki/triwiki-proof-bank.js +7 -1
  228. package/dist/core/verification/context-graph-affected.js +271 -0
  229. package/dist/core/version.js +1 -1
  230. package/dist/native/sks-menubar/Sources/AlertFactory.swift +11 -3
  231. package/dist/native/sks-menubar/Sources/AppDelegate.swift +9 -0
  232. package/dist/native/sks-menubar/Sources/ControlCenterWindowController.swift +25 -3
  233. package/dist/native/sks-menubar/Sources/ControlKit.swift +19 -8
  234. package/dist/native/sks-menubar/Sources/DiagnosticsViewController.swift +1 -2
  235. package/dist/native/sks-menubar/Sources/MCPServersViewController.swift +45 -17
  236. package/dist/native/sks-menubar/Sources/OverviewSummary.swift +162 -0
  237. package/dist/native/sks-menubar/Sources/OverviewViewController.swift +136 -190
  238. package/dist/native/sks-menubar/Sources/ProcessClient.swift +107 -11
  239. package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +5 -1
  240. package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +16 -4
  241. package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +4 -1
  242. package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +131 -0
  243. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +288 -341
  244. package/dist/native/sks-menubar/Sources/RemoteTelegramCenterState.swift +447 -0
  245. package/dist/native/sks-menubar/Sources/RemoteTelegramRendering.swift +484 -0
  246. package/dist/native/sks-menubar/Sources/RemoteTelegramViewController.swift +364 -115
  247. package/dist/native/sks-menubar/Sources/SettingsViewController.swift +73 -12
  248. package/dist/native/sks-menubar/Sources/SidebarItem.swift +3 -3
  249. package/dist/native/sks-menubar/Sources/StatusItemController.swift +13 -13
  250. package/dist/native/sks-menubar/Sources/TelegramHubSupervisor.swift +469 -0
  251. package/dist/native/sks-menubar/Sources/UpdatesViewController.swift +1 -6
  252. package/dist/native/sks-menubar/Tests/AppRuntimeTestSupport.swift +12 -0
  253. package/dist/native/sks-menubar/Tests/NativeTelegramRegressionRunner.swift +55 -0
  254. package/dist/native/sks-menubar/Tests/ProcessClientTests.swift +33 -0
  255. package/dist/native/sks-menubar/Tests/ProvidersViewControllerTests.swift +142 -0
  256. package/dist/native/sks-menubar/Tests/RemoteTelegramCenterStateTests.swift +56 -0
  257. package/dist/native/sks-menubar/Tests/TelegramHubSupervisorTests.swift +55 -0
  258. package/dist/scripts/codex-lb-catalog-passthrough-check.js +120 -0
  259. package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +169 -0
  260. package/dist/scripts/codex-lb-desktop-bridge-check.js +394 -0
  261. package/dist/scripts/codex-lb-desktop-bridge-latency-check.js +196 -0
  262. package/dist/scripts/codex-lb-desktop-capabilities-check.js +287 -0
  263. package/dist/scripts/codex-lb-desktop-real-evidence-check.js +123 -0
  264. package/dist/scripts/codex-lb-fast-mode-truth-check.js +10 -3
  265. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +81 -40
  266. package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +99 -37
  267. package/dist/scripts/context-graph-check.js +191 -0
  268. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +1 -1
  269. package/dist/scripts/gate-policy-audit-check.js +1 -1
  270. package/dist/scripts/helper-dedup-check.js +0 -2
  271. package/dist/scripts/installed-package-smoke-check.js +4 -1
  272. package/dist/scripts/latest-version-guidance-check.js +74 -0
  273. package/dist/scripts/release-metadata-check.js +21 -4
  274. package/dist/scripts/release-real-check.js +7 -0
  275. package/dist/scripts/ux-review-imagegen-blackbox-check.js +25 -1
  276. package/dist/scripts/ux-review-run-wires-imagegen-check.js +9 -1
  277. package/package.json +8 -3
  278. package/release-gates.v2.json +176 -1
  279. package/runtime-required-scripts.json +30 -0
  280. package/schemas/triwiki/context-graph.schema.json +119 -0
  281. package/dist/cli/install-helpers-codex-lb-selftest-chain.js +0 -112
  282. package/dist/cli/install-helpers-codex-lb-selftest.js +0 -481
  283. package/dist/core/triwiki/code-index-scanner.js +0 -309
@@ -0,0 +1,283 @@
1
+ import path from 'node:path';
2
+ import { asRecordOrNull as asRecord } from '../json/records.js';
3
+ import { redactString } from '../secret-redaction.js';
4
+ import { redactRemoteValue } from './audit-idempotency.js';
5
+ const PUBLIC_DETAIL_BYTES = 2 * 1024;
6
+ export function publicCodexActivityFromItem(value, projectRoot) {
7
+ const item = asRecord(value);
8
+ if (!item)
9
+ return null;
10
+ const type = String(item.type || '');
11
+ const status = publicStatus(item.status);
12
+ const duration = publicInteger(item.durationMs);
13
+ if (type === 'commandExecution') {
14
+ return {
15
+ kind: 'command',
16
+ status,
17
+ title: 'Command execution',
18
+ detail: publicCommandSummary(item.command),
19
+ duration_ms: duration,
20
+ exit_code: publicInteger(item.exitCode)
21
+ };
22
+ }
23
+ if (type === 'fileChange') {
24
+ const changes = Array.isArray(item.changes) ? item.changes.map(asRecord).filter(Boolean) : [];
25
+ const paths = changes
26
+ .map((change) => publicProjectPath(change.path, projectRoot))
27
+ .filter((entry) => Boolean(entry))
28
+ .slice(0, 12);
29
+ const omitted = Math.max(0, changes.length - paths.length);
30
+ return {
31
+ kind: 'file_change',
32
+ status,
33
+ title: `${changes.length} file change${changes.length === 1 ? '' : 's'}`,
34
+ detail: paths.length
35
+ ? `${paths.join(', ')}${omitted ? ` · +${omitted} more` : ''}`
36
+ : null,
37
+ duration_ms: null,
38
+ exit_code: null
39
+ };
40
+ }
41
+ if (type === 'mcpToolCall') {
42
+ const server = publicCodexText(item.server, 120);
43
+ const tool = publicCodexText(item.tool, 160);
44
+ return {
45
+ kind: 'mcp_tool',
46
+ status,
47
+ title: [server, tool].filter(Boolean).join('.') || 'MCP tool',
48
+ detail: asRecord(item.error) ? 'Tool reported an error.' : null,
49
+ duration_ms: duration,
50
+ exit_code: null
51
+ };
52
+ }
53
+ if (type === 'dynamicToolCall') {
54
+ const namespace = publicCodexText(item.namespace, 120);
55
+ const tool = publicCodexText(item.tool, 160);
56
+ return {
57
+ kind: 'dynamic_tool',
58
+ status,
59
+ title: [namespace, tool].filter(Boolean).join('.') || 'Dynamic tool',
60
+ detail: item.success === false ? 'Tool reported failure.' : null,
61
+ duration_ms: duration,
62
+ exit_code: null
63
+ };
64
+ }
65
+ if (type === 'collabAgentToolCall') {
66
+ const receivers = Array.isArray(item.receiverThreadIds) ? item.receiverThreadIds.length : 0;
67
+ return {
68
+ kind: 'subagent_tool',
69
+ status,
70
+ title: `Subagent ${publicCodexText(item.tool, 120) || 'activity'}`,
71
+ detail: receivers ? `${receivers} target${receivers === 1 ? '' : 's'}` : null,
72
+ duration_ms: null,
73
+ exit_code: null
74
+ };
75
+ }
76
+ if (type === 'subAgentActivity') {
77
+ return {
78
+ kind: 'subagent',
79
+ status: publicStatus(item.kind),
80
+ title: `Subagent ${publicCodexText(item.kind, 80) || 'activity'}`,
81
+ detail: null,
82
+ duration_ms: null,
83
+ exit_code: null
84
+ };
85
+ }
86
+ if (type === 'webSearch') {
87
+ const action = asRecord(item.action);
88
+ const queryCount = Array.isArray(action?.queries)
89
+ ? action.queries.length
90
+ : item.query || action?.query
91
+ ? 1
92
+ : 0;
93
+ return {
94
+ kind: 'web_search',
95
+ status,
96
+ title: 'Web search',
97
+ detail: queryCount > 0 ? `${queryCount} bounded search request${queryCount === 1 ? '' : 's'}` : null,
98
+ duration_ms: null,
99
+ exit_code: null
100
+ };
101
+ }
102
+ if (type === 'plan') {
103
+ return {
104
+ kind: 'plan',
105
+ status,
106
+ title: 'Plan updated',
107
+ detail: null,
108
+ duration_ms: null,
109
+ exit_code: null
110
+ };
111
+ }
112
+ if (type === 'contextCompaction') {
113
+ return {
114
+ kind: 'compaction',
115
+ status: 'completed',
116
+ title: 'Context compacted',
117
+ detail: null,
118
+ duration_ms: null,
119
+ exit_code: null
120
+ };
121
+ }
122
+ if (type === 'imageGeneration') {
123
+ return {
124
+ kind: 'image_generation',
125
+ status,
126
+ title: 'Image generation',
127
+ detail: null,
128
+ duration_ms: duration,
129
+ exit_code: null
130
+ };
131
+ }
132
+ if (type === 'imageView') {
133
+ return {
134
+ kind: 'image_view',
135
+ status,
136
+ title: 'Image inspected',
137
+ detail: null,
138
+ duration_ms: null,
139
+ exit_code: null
140
+ };
141
+ }
142
+ if (type === 'sleep') {
143
+ return {
144
+ kind: 'wait',
145
+ status,
146
+ title: 'Waiting',
147
+ detail: null,
148
+ duration_ms: duration,
149
+ exit_code: null
150
+ };
151
+ }
152
+ if (type === 'enteredReviewMode' || type === 'exitedReviewMode') {
153
+ return {
154
+ kind: 'review',
155
+ status,
156
+ title: type === 'enteredReviewMode' ? 'Review started' : 'Review completed',
157
+ detail: null,
158
+ duration_ms: null,
159
+ exit_code: null
160
+ };
161
+ }
162
+ return null;
163
+ }
164
+ export function publicCodexJournalEvent(value, expectedThreadId, projectRoot) {
165
+ const event = asRecord(value);
166
+ const method = String(event?.method || '');
167
+ if (!['turn/started', 'turn/completed', 'item/started', 'item/completed'].includes(method))
168
+ return null;
169
+ const params = asRecord(event?.params);
170
+ const threadId = publicIdentifier(params?.threadId, 200);
171
+ if (!threadId || !expectedThreadId || threadId !== expectedThreadId)
172
+ return null;
173
+ const turn = asRecord(params?.turn);
174
+ const turnId = publicIdentifier(params?.turnId ?? turn?.id, 200);
175
+ if (!turnId)
176
+ return null;
177
+ if (method === 'turn/started' || method === 'turn/completed') {
178
+ const lifecycle = method.endsWith('started') ? 'started' : 'completed';
179
+ const status = publicStatus(turn?.status || lifecycle);
180
+ return {
181
+ type: lifecycle === 'started' ? 'codex.turn.started' : 'codex.turn.completed',
182
+ dedupe_key: `turn:${turnId}:${lifecycle}`,
183
+ turn_id: turnId,
184
+ item_id: null,
185
+ summary: {
186
+ thread_id: threadId,
187
+ turn_id: turnId,
188
+ kind: 'turn',
189
+ status,
190
+ title: lifecycle === 'started' ? 'Codex turn started' : `Codex turn ${status}`,
191
+ detail: null,
192
+ duration_ms: publicInteger(turn?.durationMs),
193
+ exit_code: null
194
+ }
195
+ };
196
+ }
197
+ const item = asRecord(params?.item);
198
+ const itemId = publicIdentifier(item?.id, 240);
199
+ const activity = publicCodexActivityFromItem(item, projectRoot);
200
+ if (!itemId || !activity)
201
+ return null;
202
+ const lifecycle = method.endsWith('started') ? 'started' : 'completed';
203
+ return {
204
+ type: lifecycle === 'started' ? 'codex.item.started' : 'codex.item.completed',
205
+ dedupe_key: `item:${itemId}:${lifecycle}`,
206
+ turn_id: turnId,
207
+ item_id: itemId,
208
+ summary: {
209
+ thread_id: threadId,
210
+ turn_id: turnId,
211
+ item_id: itemId,
212
+ item_type: String(item?.type || 'unknown').slice(0, 80),
213
+ ...activity
214
+ }
215
+ };
216
+ }
217
+ export function publicCodexText(value, maxBytes = PUBLIC_DETAIL_BYTES) {
218
+ if (typeof value !== 'string')
219
+ return null;
220
+ let text = redactString(String(redactRemoteValue(value)))
221
+ .replace(/\bgh[pousr]_[A-Za-z0-9_]{20,}\b/g, '[REDACTED]')
222
+ .replace(/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, '[REDACTED]')
223
+ .replace(/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, '[REDACTED]')
224
+ .replace(/\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/gi, 'Bearer [REDACTED]')
225
+ .replace(/((?:api[_-]?key|token|secret|password|passwd|authorization)\s*[:=]\s*)[^\s"',}]+/gi, '$1[REDACTED]')
226
+ .replace(/([?&](?:api[_-]?key|token|secret|password)=)[^&#\s]+/gi, '$1[REDACTED]')
227
+ .replace(/(?:\/Users|\/home|\/private|\/Volumes|\/etc|\/root)\/[^\s"'`]+(?:\/[^\s"'`]*)?/g, '[path-redacted]')
228
+ .replace(/\b[A-Za-z]:\\(?:[^\\\s"'`]+\\)*[^\\\s"'`]*/g, '[path-redacted]')
229
+ .trim();
230
+ if (!text)
231
+ return null;
232
+ if (Buffer.byteLength(text, 'utf8') <= maxBytes)
233
+ return text;
234
+ const suffix = '…';
235
+ const budget = Math.max(0, maxBytes - Buffer.byteLength(suffix, 'utf8'));
236
+ text = Buffer.from(text, 'utf8').subarray(0, budget).toString('utf8').replace(/\uFFFD+$/g, '');
237
+ return `${text}${suffix}`;
238
+ }
239
+ function publicProjectPath(value, projectRoot) {
240
+ if (typeof value !== 'string' || !value.trim())
241
+ return null;
242
+ const absolute = path.isAbsolute(value) ? path.resolve(value) : path.resolve(projectRoot, value);
243
+ const relative = path.relative(path.resolve(projectRoot), absolute);
244
+ if (!relative || relative === '.')
245
+ return path.basename(projectRoot);
246
+ if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
247
+ return '[outside-project]';
248
+ return publicCodexText(relative.split(path.sep).join('/'), 480);
249
+ }
250
+ function publicCommandSummary(value) {
251
+ if (typeof value !== 'string')
252
+ return null;
253
+ const tokens = value.trim().split(/\s+/).filter(Boolean);
254
+ while (tokens[0] && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[0]))
255
+ tokens.shift();
256
+ if (tokens[0] && path.basename(tokens[0]) === 'env') {
257
+ tokens.shift();
258
+ while (tokens[0] && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[0]))
259
+ tokens.shift();
260
+ }
261
+ const executable = tokens[0]
262
+ ? path.basename(tokens[0]).replace(/[^A-Za-z0-9._+-]/g, '').slice(0, 80)
263
+ : '';
264
+ if (!executable)
265
+ return null;
266
+ const safeSubcommands = new Set([
267
+ 'build', 'check', 'diff', 'install', 'lint', 'list', 'run', 'status',
268
+ 'test', 'typecheck', 'verify', '--check', '--version', '-v'
269
+ ]);
270
+ const subcommand = tokens.slice(1, 3).find((token) => safeSubcommands.has(token));
271
+ return `Executable: ${executable}${subcommand ? ` · ${subcommand}` : ''}`;
272
+ }
273
+ function publicStatus(value) {
274
+ const text = typeof value === 'string' ? value.trim() : '';
275
+ return text && /^[A-Za-z][A-Za-z0-9._-]{0,63}$/.test(text) ? text : 'unknown';
276
+ }
277
+ function publicIdentifier(value, maxLength) {
278
+ const text = typeof value === 'string' ? value.trim() : '';
279
+ return text && text.length <= maxLength && /^[A-Za-z0-9][A-Za-z0-9._:/-]*$/.test(text) ? text : null;
280
+ }
281
+ function publicInteger(value) {
282
+ return typeof value === 'number' && Number.isSafeInteger(value) ? value : null;
283
+ }
@@ -7,6 +7,7 @@ export * from './owner-proof.js';
7
7
  export * from './protocol.js';
8
8
  export * from './session-snapshot.js';
9
9
  export * from './session-binding.js';
10
+ export * from './codex-public-activity.js';
10
11
  export * from './worker.js';
11
12
  export * from './ssh-worker-client.js';
12
13
  export * from './local-worker-client.js';
@@ -22,6 +22,7 @@ export class RemoteSshWorkerClient {
22
22
  projectId;
23
23
  sshCommand;
24
24
  requestTimeoutMs;
25
+ commandRequestTimeoutMs;
25
26
  maxResponseLineBytes;
26
27
  maxConnectionOutputBytes;
27
28
  reconnectAttempts;
@@ -46,6 +47,8 @@ export class RemoteSshWorkerClient {
46
47
  this.projectId = options.projectId;
47
48
  this.sshCommand = options.sshCommand ?? 'ssh';
48
49
  this.requestTimeoutMs = Math.max(1_000, options.requestTimeoutMs ?? 30_000);
50
+ this.commandRequestTimeoutMs = Math.max(1_000, options.commandRequestTimeoutMs
51
+ ?? (options.requestTimeoutMs === undefined ? 31 * 60_000 : options.requestTimeoutMs));
49
52
  this.maxResponseLineBytes = Math.max(4_096, options.maxResponseLineBytes ?? 512 * 1024);
50
53
  this.maxConnectionOutputBytes = Math.max(this.maxResponseLineBytes, options.maxConnectionOutputBytes ?? 8 * 1024 * 1024);
51
54
  this.reconnectAttempts = Math.max(1, Math.min(10, options.reconnectAttempts ?? 4));
@@ -167,7 +170,7 @@ export class RemoteSshWorkerClient {
167
170
  const delivery = pending?.dispatched ? 'unknown' : 'not_dispatched';
168
171
  reject(new RemoteSshClientError(delivery === 'unknown' ? 'delivery_unknown' : 'disconnected_before_dispatch', delivery, delivery === 'not_dispatched' || request.type !== 'command'));
169
172
  this.abortConnection('ssh_request_timeout');
170
- }, this.requestTimeoutMs);
173
+ }, request.type === 'command' ? this.commandRequestTimeoutMs : this.requestTimeoutMs);
171
174
  timer.unref?.();
172
175
  const pending = { request, resolve, reject, timer, dispatched: false };
173
176
  this.pending.set(request.id, pending);
@@ -1,7 +1,8 @@
1
1
  import crypto from 'node:crypto';
2
2
  import fsp from 'node:fs/promises';
3
3
  import path from 'node:path';
4
- import { createCodexAppServerV2Client } from '../codex-control/codex-app-server-v2-client.js';
4
+ import { CodexAppServerRequestError, createCodexAppServerV2Client } from '../codex-control/codex-app-server-v2-client.js';
5
+ import { CodexAppServerRuntimeEnvError, prepareCodexAppServerRuntimeEnv } from '../codex-control/codex-app-server-runtime-env.js';
5
6
  import { RemoteAuditLog, RemoteCommandLedger, RemoteEventJournal, remoteRuntimePaths } from './audit-idempotency.js';
6
7
  import { cancelOwnedSession, parseRemoteCancelPayload, RemoteOwnerProofError, RemoteOwnerProofStore } from './owner-proof.js';
7
8
  import { RemoteProtocolError, workerErrorResponse, workerSuccessResponse } from './protocol.js';
@@ -9,6 +10,12 @@ import { RemoteCodexSessionBindingStore, readRemoteCodexBindingSnapshot, remoteC
9
10
  import { listRemoteSessionRows, readRemoteSessionView, readRemoteSessionSnapshot, remoteSessionGeneration, remoteSessionRecord } from './session-snapshot.js';
10
11
  import { REMOTE_COMMAND_RECEIPT_SCHEMA } from './types.js';
11
12
  import { asRecordOrNull as asRecord } from '../json/records.js';
13
+ import { publicCodexJournalEvent } from './codex-public-activity.js';
14
+ const MAX_PUBLIC_CODEX_JOURNAL_EVENTS_PER_TURN = 256;
15
+ const PUBLIC_CODEX_JOURNAL_FLUSH_BATCH_SIZE = 8;
16
+ const PUBLIC_CODEX_JOURNAL_FLUSH_INTERVAL_MS = 400;
17
+ const PUBLIC_CODEX_JOURNAL_FLUSH_DEADLINE_MS = 750;
18
+ const TELEGRAM_CODEX_DEVELOPER_INSTRUCTIONS = 'This is a dedicated private Telegram coding thread. Work only inside the configured project workspace, never expose secrets, and return a concise user-facing final response for each turn.';
12
19
  export class RemoteWorkerExecutionError extends Error {
13
20
  code;
14
21
  delivery;
@@ -41,7 +48,10 @@ export class RemoteWorker {
41
48
  const paths = remoteRuntimePaths(this.root);
42
49
  this.commands = options.commandLedger ?? new RemoteCommandLedger(paths.commands, { lockPath: paths.commandLock });
43
50
  this.audit = options.audit ?? new RemoteAuditLog(paths.audit);
44
- this.events = options.events ?? new RemoteEventJournal(paths.events, { lockPath: paths.eventLock });
51
+ this.events = options.events ?? new RemoteEventJournal(paths.events, {
52
+ lockPath: paths.eventLock,
53
+ healthPath: paths.eventHealth
54
+ });
45
55
  this.owners = options.owners ?? new RemoteOwnerProofStore(paths.owners);
46
56
  this.bindings = options.bindings ?? new RemoteCodexSessionBindingStore(remoteCodexSessionBindingsPath(this.root));
47
57
  this.codexClientFactory = options.codexClientFactory ?? defaultCodexClientFactory;
@@ -295,7 +305,9 @@ export class RemoteWorker {
295
305
  if (!owner.codex_thread_id || !owner.active_turn_id)
296
306
  throw new RemoteWorkerExecutionError('codex_turn_binding_missing');
297
307
  const client = await this.codexClientFactory(this.root).catch((err) => {
298
- void err;
308
+ if (err instanceof CodexAppServerRuntimeEnvError) {
309
+ throw new RemoteWorkerExecutionError(err.code, 'not_dispatched', true);
310
+ }
299
311
  throw new RemoteWorkerExecutionError('codex_app_server_unavailable', 'not_dispatched', true);
300
312
  });
301
313
  try {
@@ -306,7 +318,7 @@ export class RemoteWorker {
306
318
  threadId: owner.codex_thread_id,
307
319
  clientUserMessageId: crypto.randomUUID(),
308
320
  expectedTurnId: owner.active_turn_id,
309
- input: [{ type: 'text', text, text_elements: [] }]
321
+ input: [{ type: 'text', text }]
310
322
  });
311
323
  }
312
324
  catch (err) {
@@ -397,14 +409,96 @@ export class RemoteWorker {
397
409
  }
398
410
  }
399
411
  async runDedicatedCodexTurn(binding, text) {
400
- const client = await this.codexClientFactory(this.root).catch(() => {
412
+ const client = await this.codexClientFactory(this.root).catch((err) => {
413
+ if (err instanceof CodexAppServerRuntimeEnvError) {
414
+ throw new RemoteWorkerExecutionError(err.code, 'not_dispatched', true);
415
+ }
401
416
  throw new RemoteWorkerExecutionError('codex_app_server_unavailable', 'not_dispatched', true);
402
417
  });
403
418
  const threadWasPersisted = Boolean(binding.codex_thread_id);
404
419
  let threadId = binding.codex_thread_id;
405
420
  let turnId = null;
421
+ let disposeEvents = null;
422
+ let journalEventCount = 0;
423
+ let journalTruncationRecorded = false;
424
+ const journalEvents = [];
425
+ let journalFlushTimer = null;
426
+ let journalFlushFailure = null;
427
+ let journalFlushChain = Promise.resolve();
428
+ const flushJournalEvents = () => {
429
+ if (journalFlushTimer) {
430
+ clearTimeout(journalFlushTimer);
431
+ journalFlushTimer = null;
432
+ }
433
+ if (journalEvents.length === 0 || journalFlushFailure)
434
+ return journalFlushChain;
435
+ const batch = journalEvents.splice(0, journalEvents.length);
436
+ journalFlushChain = journalFlushChain.then(async () => {
437
+ if (journalFlushFailure)
438
+ return;
439
+ const outcome = await settleWithin(this.events.appendMany(batch), PUBLIC_CODEX_JOURNAL_FLUSH_DEADLINE_MS);
440
+ if (outcome !== 'completed')
441
+ journalFlushFailure = outcome;
442
+ });
443
+ return journalFlushChain;
444
+ };
445
+ const scheduleJournalFlush = () => {
446
+ if (journalFlushFailure || journalFlushTimer)
447
+ return;
448
+ if (journalEvents.length >= PUBLIC_CODEX_JOURNAL_FLUSH_BATCH_SIZE) {
449
+ void flushJournalEvents();
450
+ return;
451
+ }
452
+ journalFlushTimer = setTimeout(() => {
453
+ journalFlushTimer = null;
454
+ void flushJournalEvents();
455
+ }, PUBLIC_CODEX_JOURNAL_FLUSH_INTERVAL_MS);
456
+ journalFlushTimer.unref?.();
457
+ };
406
458
  try {
407
- await client.initialize();
459
+ await client.initialize().catch((error) => {
460
+ throw codexStageExecutionError('initialize', error);
461
+ });
462
+ if (client.onEvent) {
463
+ disposeEvents = client.onEvent((event) => {
464
+ const projected = publicCodexJournalEvent(event, threadId, this.root);
465
+ if (!projected)
466
+ return;
467
+ if (journalEventCount >= MAX_PUBLIC_CODEX_JOURNAL_EVENTS_PER_TURN) {
468
+ if (!journalTruncationRecorded) {
469
+ journalTruncationRecorded = true;
470
+ journalEvents.push({
471
+ type: 'codex.activity.truncated',
472
+ session_id: binding.session_id,
473
+ command_id: null,
474
+ summary: {
475
+ thread_id: threadId,
476
+ turn_id: projected.turn_id,
477
+ kind: 'activity',
478
+ status: 'truncated',
479
+ title: 'Live activity journal reached its per-turn limit',
480
+ detail: `Historical thread projection remains available after completion; live journal limit is ${MAX_PUBLIC_CODEX_JOURNAL_EVENTS_PER_TURN} events.`,
481
+ duration_ms: null,
482
+ exit_code: null
483
+ }
484
+ });
485
+ scheduleJournalFlush();
486
+ }
487
+ return;
488
+ }
489
+ journalEventCount += 1;
490
+ journalEvents.push({
491
+ type: projected.type,
492
+ session_id: binding.session_id,
493
+ command_id: null,
494
+ summary: {
495
+ dedupe_key: projected.dedupe_key,
496
+ ...projected.summary
497
+ }
498
+ });
499
+ scheduleJournalFlush();
500
+ });
501
+ }
408
502
  if (!client.resumeThread || !client.startTurn || !client.readThread || !client.waitForTurnCompletion) {
409
503
  throw new RemoteWorkerExecutionError('codex_app_server_session_api_unavailable', 'not_dispatched', false);
410
504
  }
@@ -414,7 +508,8 @@ export class RemoteWorker {
414
508
  threadId,
415
509
  cwd: this.root,
416
510
  approvalPolicy: 'never',
417
- sandbox: 'workspace-write'
511
+ sandbox: 'workspace-write',
512
+ developerInstructions: TELEGRAM_CODEX_DEVELOPER_INSTRUCTIONS
418
513
  }));
419
514
  const resumedThread = asRecord(resumed?.thread);
420
515
  if (String(resumedThread?.id ?? '') !== threadId)
@@ -436,6 +531,9 @@ export class RemoteWorker {
436
531
  throw new RemoteWorkerExecutionError(missingRollout ? 'codex_thread_history_missing' : 'codex_thread_resume_failed', 'not_dispatched', !missingRollout);
437
532
  }
438
533
  if (!missingRollout) {
534
+ if (isCodexAppServerContractError(err)) {
535
+ throw new RemoteWorkerExecutionError('codex_thread_resume_contract_invalid', 'not_dispatched', false);
536
+ }
439
537
  throw new RemoteWorkerExecutionError('codex_thread_resume_failed', 'not_dispatched', true);
440
538
  }
441
539
  threadId = null;
@@ -449,14 +547,15 @@ export class RemoteWorker {
449
547
  cwd: this.root,
450
548
  approvalPolicy: 'never',
451
549
  sandbox: 'workspace-write',
452
- threadSource: 'sks-telegram',
453
- baseInstructions: 'This is a dedicated private Telegram coding thread. Work only inside the configured project workspace, never expose secrets, and return a concise user-facing final response for each turn.'
454
- }).catch(() => {
455
- throw new RemoteWorkerExecutionError('codex_thread_start_failed', 'not_dispatched', true);
550
+ serviceName: 'sneakoscope_telegram',
551
+ developerInstructions: TELEGRAM_CODEX_DEVELOPER_INSTRUCTIONS
552
+ }).catch((error) => {
553
+ throw codexStageExecutionError('thread_start', error);
456
554
  }));
457
555
  threadId = stringValue(asRecord(started?.thread)?.id);
458
- if (!threadId)
459
- throw new Error('codex_thread_start_missing_id');
556
+ if (!threadId) {
557
+ throw new RemoteWorkerExecutionError('codex_thread_start_missing_id', 'unknown', false);
558
+ }
460
559
  }
461
560
  const started = asRecord(await client.startTurn({
462
561
  threadId,
@@ -468,20 +567,29 @@ export class RemoteWorker {
468
567
  networkAccess: false
469
568
  },
470
569
  clientUserMessageId: crypto.randomUUID(),
471
- input: [{ type: 'text', text, text_elements: [] }]
570
+ input: [{ type: 'text', text }]
571
+ }).catch((error) => {
572
+ throw codexStageExecutionError('turn_start', error);
472
573
  }));
473
574
  turnId = stringValue(asRecord(started?.turn)?.id);
474
- if (!turnId)
475
- throw new Error('codex_turn_id_missing');
476
- await client.waitForTurnCompletion(threadId, turnId, 30 * 60_000);
477
- const read = asRecord(await client.readThread(threadId, true));
575
+ if (!turnId) {
576
+ throw new RemoteWorkerExecutionError('codex_turn_start_missing_id', 'unknown', false);
577
+ }
578
+ await client.waitForTurnCompletion(threadId, turnId, 30 * 60_000).catch(() => {
579
+ throw new RemoteWorkerExecutionError('codex_turn_completion_unknown', 'acknowledged', false);
580
+ });
581
+ const read = asRecord(await client.readThread(threadId, true).catch(() => {
582
+ throw new RemoteWorkerExecutionError('codex_turn_result_unavailable', 'acknowledged', false);
583
+ }));
478
584
  const thread = asRecord(read?.thread);
479
- if (String(thread?.id ?? '') !== threadId)
480
- throw new Error('codex_thread_read_mismatch');
585
+ if (String(thread?.id ?? '') !== threadId) {
586
+ throw new RemoteWorkerExecutionError('codex_thread_read_mismatch', 'acknowledged', false);
587
+ }
481
588
  const turns = Array.isArray(thread?.turns) ? thread.turns.map(asRecord).filter(Boolean) : [];
482
589
  const turn = turns.find((candidate) => String(candidate.id ?? '') === turnId);
483
- if (!turn)
484
- throw new Error('codex_completed_turn_missing');
590
+ if (!turn) {
591
+ throw new RemoteWorkerExecutionError('codex_completed_turn_missing', 'acknowledged', false);
592
+ }
485
593
  const turnStatus = String(turn.status ?? '');
486
594
  if (turnStatus !== 'completed') {
487
595
  if (threadWasPersisted) {
@@ -501,9 +609,12 @@ export class RemoteWorker {
501
609
  .map((item) => item.text)
502
610
  .filter(Boolean)
503
611
  .at(-1);
504
- if (!finalMessage)
505
- throw new Error('codex_final_agent_message_missing');
506
- await this.bindings.recordTurn(binding.session_id, threadId, turnId, 'completed');
612
+ if (!finalMessage) {
613
+ throw new RemoteWorkerExecutionError('codex_final_response_missing', 'acknowledged', false);
614
+ }
615
+ await this.bindings.recordTurn(binding.session_id, threadId, turnId, 'completed').catch(() => {
616
+ throw new RemoteWorkerExecutionError('codex_session_binding_persist_failed', 'acknowledged', false);
617
+ });
507
618
  return {
508
619
  accepted: true,
509
620
  control: 'codex_app_server_v2',
@@ -519,15 +630,52 @@ export class RemoteWorker {
519
630
  if (threadWasPersisted && turnId && threadId) {
520
631
  await this.bindings.recordTurn(binding.session_id, threadId, turnId, 'failed').catch(() => undefined);
521
632
  }
522
- throw new RemoteWorkerExecutionError(turnId ? 'codex_turn_failed' : 'input_delivery_unknown', turnId ? 'acknowledged' : 'unknown', false);
633
+ throw new RemoteWorkerExecutionError(turnId ? 'codex_turn_failed' : 'codex_pre_dispatch_failed', turnId ? 'acknowledged' : 'not_dispatched', false);
523
634
  }
524
635
  finally {
636
+ disposeEvents?.();
637
+ if (journalFlushTimer) {
638
+ clearTimeout(journalFlushTimer);
639
+ journalFlushTimer = null;
640
+ }
641
+ const finalFlush = await settleWithin(flushJournalEvents(), PUBLIC_CODEX_JOURNAL_FLUSH_DEADLINE_MS * 2);
642
+ const flushFailure = journalFlushFailure
643
+ ?? (finalFlush === 'completed' ? null : finalFlush);
644
+ if (flushFailure) {
645
+ await settleWithin(this.audit.append({
646
+ event: 'codex_public_activity_journal_failed',
647
+ session_id: binding.session_id,
648
+ turn_id: turnId,
649
+ error_code: flushFailure === 'timed_out'
650
+ ? 'remote_event_journal_flush_timed_out'
651
+ : 'remote_event_journal_write_failed'
652
+ }), PUBLIC_CODEX_JOURNAL_FLUSH_DEADLINE_MS);
653
+ }
525
654
  await client.close().catch(() => undefined);
526
655
  }
527
656
  }
528
657
  }
658
+ async function settleWithin(promise, timeoutMs) {
659
+ return await new Promise((resolve) => {
660
+ let settled = false;
661
+ const finish = (outcome) => {
662
+ if (settled)
663
+ return;
664
+ settled = true;
665
+ clearTimeout(timer);
666
+ resolve(outcome);
667
+ };
668
+ const timer = setTimeout(() => finish('timed_out'), timeoutMs);
669
+ promise.then(() => finish('completed'), () => finish('failed'));
670
+ });
671
+ }
529
672
  async function defaultCodexClientFactory(cwd) {
530
- const created = await createCodexAppServerV2Client({ cwd, requestedBy: 'remote-worker' });
673
+ const env = await prepareCodexAppServerRuntimeEnv();
674
+ const created = await createCodexAppServerV2Client({
675
+ cwd,
676
+ env,
677
+ requestedBy: 'remote-worker'
678
+ });
531
679
  return created.client;
532
680
  }
533
681
  function normalizeExecutionError(err) {
@@ -555,3 +703,34 @@ function isMissingCodexRolloutError(error) {
555
703
  const message = error instanceof Error ? error.message : String(error);
556
704
  return /no rollout found for thread id|thread(?:\/resume)?:? (?:thread )?(?:not found|not loaded)/i.test(message);
557
705
  }
706
+ function codexStageExecutionError(stage, error) {
707
+ const contractInvalid = isCodexAppServerContractError(error);
708
+ if (stage === 'initialize') {
709
+ return new RemoteWorkerExecutionError(contractInvalid
710
+ ? 'codex_app_server_initialize_contract_invalid'
711
+ : 'codex_app_server_initialize_failed', 'not_dispatched', !contractInvalid);
712
+ }
713
+ if (stage === 'thread_start') {
714
+ if (!isCodexAppServerMethodRejection(error, 'thread/start')) {
715
+ return new RemoteWorkerExecutionError('codex_thread_start_delivery_unknown', 'unknown', false);
716
+ }
717
+ return new RemoteWorkerExecutionError(contractInvalid ? 'codex_thread_start_contract_invalid' : 'codex_thread_start_failed', 'not_dispatched', false);
718
+ }
719
+ if (isCodexAppServerMethodRejection(error, 'turn/start')) {
720
+ return new RemoteWorkerExecutionError(contractInvalid ? 'codex_turn_start_contract_invalid' : 'codex_turn_start_rejected', 'not_dispatched', false);
721
+ }
722
+ return new RemoteWorkerExecutionError('codex_turn_start_delivery_unknown', 'unknown', false);
723
+ }
724
+ function isCodexAppServerContractError(error) {
725
+ if (error instanceof CodexAppServerRequestError && error.kind !== 'rpc_rejection')
726
+ return false;
727
+ const message = error instanceof Error ? error.message : String(error);
728
+ return /invalid request|unknown variant|unknown field|invalid type|requires experimentalapi capability|method not found/i.test(message);
729
+ }
730
+ function isCodexAppServerMethodRejection(error, method) {
731
+ if (error instanceof CodexAppServerRequestError) {
732
+ return error.kind === 'rpc_rejection' && error.method === method;
733
+ }
734
+ const message = error instanceof Error ? error.message : String(error);
735
+ return message.trimStart().startsWith(`${method}:`);
736
+ }
@@ -1,6 +1,7 @@
1
1
  import fsp from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { nowIso, randomId, readJson, readText, sha256, writeJsonAtomic, writeTextAtomic } from '../fsx.js';
4
+ import { uniqueValues as unique } from '../text/strings.js';
4
5
  import { buildOfficialSubagentPrompt } from '../subagents/official-subagent-prompt.js';
5
6
  import { codexAppSessionKey, detectCodexAppSession, runOfficialSubagentWorkflow } from '../subagents/official-subagent-runner.js';
6
7
  import { readOfficialSubagentConfig } from '../subagents/official-subagent-config.js';
@@ -1065,6 +1066,3 @@ function duplicates(values) {
1065
1066
  }
1066
1067
  return [...duplicates];
1067
1068
  }
1068
- function unique(values) {
1069
- return [...new Set(values)];
1070
- }