sneakoscope 8.1.2 → 8.2.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 (261) hide show
  1. package/README.md +96 -58
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/dist/bin/fast-inline.js +5 -51
  5. package/dist/cli/command-manifest-lite.js +2 -1
  6. package/dist/cli/command-registry.js +2 -1
  7. package/dist/cli/global-mode-router.js +1 -1
  8. package/dist/cli/install-helpers-codex-lb-config.js +112 -914
  9. package/dist/cli/install-helpers-codex-lb-shared.js +3 -44
  10. package/dist/cli/install-helpers-install-support.js +4 -20
  11. package/dist/cli/install-helpers.js +36 -2716
  12. package/dist/cli/router.js +9 -9
  13. package/dist/commands/bridge-command-output.js +111 -0
  14. package/dist/commands/bridge.js +320 -0
  15. package/dist/commands/codex-app.js +39 -123
  16. package/dist/commands/doctor-helpers.js +380 -0
  17. package/dist/commands/doctor-profile.js +3 -0
  18. package/dist/commands/doctor.js +114 -873
  19. package/dist/commands/mad-sks.js +0 -4
  20. package/dist/config/skills-manifest.json +1 -1
  21. package/dist/core/agents/agent-effort-policy.js +11 -21
  22. package/dist/core/agents/agent-recursion-guard.js +26 -0
  23. package/dist/core/agents/agent-runner-codex-exec.js +7 -9
  24. package/dist/core/architecture-hardening/contracts/contracts.js +225 -161
  25. package/dist/core/architecture-hardening/state/state-service.js +49 -13
  26. package/dist/core/auto-review.js +15 -9
  27. package/dist/core/codex/codex-config-guard.js +8 -0
  28. package/dist/core/codex-adapter.js +8 -10
  29. package/dist/core/codex-app/codex-app-fast-ui-repair.js +7 -29
  30. package/dist/core/codex-app/codex-app-launcher.js +10 -12
  31. package/dist/core/codex-app/codex-app-restart.js +10 -12
  32. package/dist/core/codex-app/menubar/constants.js +2 -5
  33. package/dist/core/codex-app/menubar/global-install.js +3 -2
  34. package/dist/core/codex-app/menubar/installer/result.js +23 -0
  35. package/dist/core/codex-app/menubar/installer/runtime.js +28 -0
  36. package/dist/core/codex-app/menubar/installer.js +5 -96
  37. package/dist/core/codex-app/menubar/migration.js +1 -42
  38. package/dist/core/codex-app/openrouter-provider.js +0 -78
  39. package/dist/core/codex-app.js +97 -360
  40. package/dist/core/codex-control/codex-app-server-runtime-env.js +8 -28
  41. package/dist/core/codex-control/codex-current-image-path-real-probe.js +2 -3
  42. package/dist/core/codex-control/codex-current-web-search-probe.js +2 -3
  43. package/dist/core/codex-control/codex-sdk-adapter.js +5 -48
  44. package/dist/core/codex-control/codex-task-runner.js +16 -60
  45. package/dist/core/codex-control/desktop-bridge-launch-guard.js +227 -0
  46. package/dist/core/codex-exec-output-schema.js +11 -13
  47. package/dist/core/codex-lb/bridge-contracts.js +2 -0
  48. package/dist/core/codex-lb/bridge-runtime-validation/capability.js +104 -0
  49. package/dist/core/codex-lb/bridge-runtime-validation/catalog.js +52 -0
  50. package/dist/core/codex-lb/bridge-runtime-validation/shared.js +109 -0
  51. package/dist/core/codex-lb/bridge-runtime-validation/status.js +250 -0
  52. package/dist/core/codex-lb/bridge-runtime-validation.js +106 -0
  53. package/dist/core/codex-lb/capability-runner.js +505 -201
  54. package/dist/core/codex-lb/codex-lb-env.js +179 -12
  55. package/dist/core/codex-lb/combined-catalog/builder.js +86 -0
  56. package/dist/core/codex-lb/combined-catalog/contracts.js +5 -0
  57. package/dist/core/codex-lb/combined-catalog/normalize.js +170 -0
  58. package/dist/core/codex-lb/combined-catalog/persistence.js +207 -0
  59. package/dist/core/codex-lb/combined-catalog/shared.js +12 -0
  60. package/dist/core/codex-lb/combined-catalog/storage.js +171 -0
  61. package/dist/core/codex-lb/combined-catalog.js +12 -0
  62. package/dist/core/codex-lb/desktop-bridge/client.js +4 -1
  63. package/dist/core/codex-lb/desktop-bridge/header-policy.js +48 -63
  64. package/dist/core/codex-lb/desktop-bridge/http-forward.js +115 -83
  65. package/dist/core/codex-lb/desktop-bridge/launchd.js +20 -13
  66. package/dist/core/codex-lb/desktop-bridge/location-rewrite.js +4 -1
  67. package/dist/core/codex-lb/desktop-bridge/security.js +381 -267
  68. package/dist/core/codex-lb/desktop-bridge/server.js +148 -19
  69. package/dist/core/codex-lb/desktop-bridge/state.js +56 -70
  70. package/dist/core/codex-lb/desktop-bridge/types.js +6 -1
  71. package/dist/core/codex-lb/desktop-bridge/websocket-forward.js +281 -106
  72. package/dist/core/codex-lb/desktop-bridge-migration/historical-config.js +245 -0
  73. package/dist/core/codex-lb/desktop-bridge-migration/historical-toml.js +56 -0
  74. package/dist/core/codex-lb/desktop-bridge-migration/metadata.js +73 -0
  75. package/dist/core/codex-lb/desktop-bridge-migration/receipt.js +52 -0
  76. package/dist/core/codex-lb/desktop-bridge-migration/retired-runtime-cleanup.js +106 -0
  77. package/dist/core/codex-lb/desktop-bridge-migration/types.js +1 -0
  78. package/dist/core/codex-lb/desktop-bridge-migration.js +238 -0
  79. package/dist/core/codex-lb/desktop-controller-v3/catalog.js +257 -0
  80. package/dist/core/codex-lb/desktop-controller-v3/diagnostics.js +62 -0
  81. package/dist/core/codex-lb/desktop-controller-v3/lifecycle-commands.js +203 -0
  82. package/dist/core/codex-lb/desktop-controller-v3/live-probes.js +293 -0
  83. package/dist/core/codex-lb/desktop-controller-v3/preflight-probes.js +144 -0
  84. package/dist/core/codex-lb/desktop-controller-v3/provider-commands.js +182 -0
  85. package/dist/core/codex-lb/desktop-controller-v3/shared.js +279 -0
  86. package/dist/core/codex-lb/desktop-controller-v3/status.js +269 -0
  87. package/dist/core/codex-lb/desktop-controller-v3/types.js +1 -0
  88. package/dist/core/codex-lb/desktop-controller-v3/verification.js +89 -0
  89. package/dist/core/codex-lb/desktop-controller-v3.js +67 -0
  90. package/dist/core/codex-lb/desktop-controller.js +2 -1730
  91. package/dist/core/codex-lb/desktop-service.js +462 -627
  92. package/dist/core/codex-lb/migration-receipt.js +560 -118
  93. package/dist/core/codex-lb/probes/auxiliary-surfaces-probe.js +37 -53
  94. package/dist/core/codex-lb/probes/bridge-probe.js +125 -64
  95. package/dist/core/codex-lb/probes/catalog-probe.js +36 -136
  96. package/dist/core/codex-lb/probes/computer-use-probe.js +63 -39
  97. package/dist/core/codex-lb/probes/image-generation-probe.js +98 -41
  98. package/dist/core/codex-lb/probes/probe-evidence.js +32 -33
  99. package/dist/core/codex-lb/probes/voice-realtime-probe.js +69 -43
  100. package/dist/core/codex-lb/provider-credentials/configuration.js +127 -0
  101. package/dist/core/codex-lb/provider-credentials/locks.js +11 -0
  102. package/dist/core/codex-lb/provider-credentials/private-snapshot.js +28 -0
  103. package/dist/core/codex-lb/provider-credentials/removal.js +33 -0
  104. package/dist/core/codex-lb/provider-credentials/resolution.js +59 -0
  105. package/dist/core/codex-lb/provider-credentials/runtime.js +55 -0
  106. package/dist/core/codex-lb/provider-credentials/types.js +1 -0
  107. package/dist/core/codex-lb/provider-credentials/validation-store.js +137 -0
  108. package/dist/core/codex-lb/provider-credentials.js +5 -0
  109. package/dist/core/codex-lb/provider-registry.js +323 -0
  110. package/dist/core/codex-lb/provider-route-policy.js +118 -0
  111. package/dist/core/codex-lb/request-route-resolver.js +172 -0
  112. package/dist/core/codex-lb/route-index.js +148 -0
  113. package/dist/core/codex-lb/trusted-deep-evidence.js +158 -286
  114. package/dist/core/commands/basic-cli.js +1 -1
  115. package/dist/core/commands/check-command.js +3 -10
  116. package/dist/core/commands/gate-result-contract.js +10 -14
  117. package/dist/core/commands/intent-normalization/normalizer.js +1 -1
  118. package/dist/core/commands/mad-sks-command.js +2 -6
  119. package/dist/core/commands/release-command.js +11 -1
  120. package/dist/core/commands/telegram-command.js +46 -6
  121. package/dist/core/doctor/doctor-readiness-matrix.js +9 -7
  122. package/dist/core/doctor/skill-legacy-surface.js +5 -5
  123. package/dist/core/evidence/v2/evidence-key.js +30 -3
  124. package/dist/core/feature-fixtures.js +8 -1
  125. package/dist/core/feature-registry.js +3 -2
  126. package/dist/core/hooks-runtime/stop-finalization.js +11 -1
  127. package/dist/core/hooks-runtime/subagent-context.js +15 -1
  128. package/dist/core/hooks-runtime/subagent-skill-availability-contract.js +1 -1
  129. package/dist/core/hooks-runtime/tool-output-quarantine.js +1 -1
  130. package/dist/core/hooks-runtime.js +73 -87
  131. package/dist/core/image-ux-review/imagegen-adapter.js +108 -94
  132. package/dist/core/image-ux-review/real-callout-extractor.js +144 -14
  133. package/dist/core/imagegen/desktop-bridge-imagegen-target.js +77 -0
  134. package/dist/core/imagegen/imagegen-capability.js +43 -151
  135. package/dist/core/init/skills/filesystem.js +89 -0
  136. package/dist/core/init/skills/inventory.js +97 -0
  137. package/dist/core/init/skills/manifest.js +151 -0
  138. package/dist/core/init/skills.js +5 -325
  139. package/dist/core/init.js +0 -63
  140. package/dist/core/install/installed-package-smoke.js +1 -1
  141. package/dist/core/managed-assets/managed-assets-manifest.js +0 -1
  142. package/dist/core/policy/latest-version-guidance.js +15 -2
  143. package/dist/core/preflight/parallel-preflight-engine.js +27 -51
  144. package/dist/core/provider/provider-badge.js +9 -4
  145. package/dist/core/provider/provider-context.js +57 -107
  146. package/dist/core/providers/openrouter/openrouter-account.js +3 -1
  147. package/dist/core/providers/openrouter/openrouter-secret-store.js +91 -22
  148. package/dist/core/release/desktop-bridge-release-evidence.js +457 -0
  149. package/dist/core/release/gate-manifest.js +0 -1
  150. package/dist/core/release/main-push-guard.js +13 -0
  151. package/dist/core/release/main-push-receipt.js +11 -0
  152. package/dist/core/release/physical-release-gates.js +664 -61
  153. package/dist/core/release/publish-preflight.js +3 -3
  154. package/dist/core/release/release-gate-batch-runner.js +19 -6
  155. package/dist/core/release/release-gate-dag.js +33 -6
  156. package/dist/core/release/release-gate-node.js +1 -3
  157. package/dist/core/release/release-gate-output-contract.js +55 -0
  158. package/dist/core/release/release-pack-contract.js +1 -1
  159. package/dist/core/release/release-pack-receipt.js +209 -8
  160. package/dist/core/release/release-real-contract.js +1 -1
  161. package/dist/core/release/stage-publish.js +106 -28
  162. package/dist/core/retention/temp-lease.js +204 -0
  163. package/dist/core/retention.js +18 -195
  164. package/dist/core/routes.js +1 -2
  165. package/dist/core/safety/intent-contract/intent-contract.js +3 -3
  166. package/dist/core/subagents/naruto-host-credentials.js +6 -1
  167. package/dist/core/subagents/official-subagent-config.js +27 -9
  168. package/dist/core/subagents/official-subagent-runner.js +14 -31
  169. package/dist/core/subagents/wave-lifecycle.js +36 -8
  170. package/dist/core/subagents/wave-parent-guidance.js +2 -1
  171. package/dist/core/telegram/doctor.js +2 -2
  172. package/dist/core/telegram/liveness.js +10 -4
  173. package/dist/core/triwiki/context-graph/compiler/index.js +18 -18
  174. package/dist/core/update/update-migration-state/fast-mode-config.js +172 -0
  175. package/dist/core/update/update-migration-state/session-state-split.js +247 -0
  176. package/dist/core/update/update-migration-state/simple-stages.js +46 -0
  177. package/dist/core/update/update-migration-state.js +4 -481
  178. package/dist/core/version.js +1 -1
  179. package/dist/native/sks-menubar/Sources/OperationCoordinator.swift +28 -4
  180. package/dist/native/sks-menubar/Sources/OperationModels.swift +112 -2
  181. package/dist/native/sks-menubar/Sources/ProviderRouteExplanation.swift +21 -0
  182. package/dist/native/sks-menubar/Sources/ProvidersBridgeCatalog.swift +140 -0
  183. package/dist/native/sks-menubar/Sources/ProvidersOpenRouter.swift +107 -470
  184. package/dist/native/sks-menubar/Sources/ProvidersReliability.swift +27 -197
  185. package/dist/native/sks-menubar/Sources/ProvidersRoutingTruth.swift +335 -269
  186. package/dist/native/sks-menubar/Sources/ProvidersViewController.swift +233 -437
  187. package/dist/native/sks-menubar/Sources/RemoteCodingViewController.swift +16 -7
  188. package/dist/native/sks-menubar/Sources/SecureProcessEnvelope.swift +39 -9
  189. package/dist/native/sks-menubar/Sources/TelegramSupport.swift +56 -0
  190. package/dist/scripts/canonical-test-runner.js +7 -1
  191. package/dist/scripts/check-runtime-schemas.js +7 -2
  192. package/dist/scripts/codex-sdk-real-smoke-check.js +3 -1
  193. package/dist/scripts/desktop-bridge-capabilities-check.js +123 -0
  194. package/dist/scripts/{codex-lb-desktop-bridge-latency-check.js → desktop-bridge-latency-check.js} +61 -16
  195. package/dist/scripts/desktop-bridge-real-evidence-check.js +64 -0
  196. package/dist/scripts/{codex-lb-desktop-bridge-check.js → desktop-bridge-transport-check.js} +92 -155
  197. package/dist/scripts/desktop-bridge-transport-client.js +116 -0
  198. package/dist/scripts/desktop-bridge-unification-check.js +276 -0
  199. package/dist/scripts/doctor-fixes-codex-app-fast-ui-check.js +12 -8
  200. package/dist/scripts/gate-policy-audit-check.js +52 -2
  201. package/dist/scripts/provider-badge-context-check.js +55 -12
  202. package/dist/scripts/publish-preflight.js +1 -2
  203. package/dist/scripts/release-affected-selector-check.js +1 -2
  204. package/dist/scripts/release-aggressive-resource-governor-check.js +1 -1
  205. package/dist/scripts/release-cache-glob-hashing-check.js +1 -0
  206. package/dist/scripts/release-cache-version-neutral-fixture-check.js +1 -0
  207. package/dist/scripts/release-full-parallelism-blackbox.js +1 -1
  208. package/dist/scripts/release-gate-batch-runner-check.js +9 -0
  209. package/dist/scripts/release-gate-dag-runner-check.js +6 -0
  210. package/dist/scripts/release-gate-dag-runner.js +4 -2
  211. package/dist/scripts/release-metadata-check.js +16 -3
  212. package/dist/scripts/release-pack-receipt.js +29 -6
  213. package/dist/scripts/release-physical-gates-check.js +39 -0
  214. package/dist/scripts/release-real-check.js +7 -5
  215. package/package.json +6 -8
  216. package/release-gates.v2.json +156 -157
  217. package/runtime-required-scripts.json +11 -11
  218. package/schemas/codex/desktop-bridge-status-v3.schema.json +286 -0
  219. package/schemas/codex/desktop-capabilities-v3.schema.json +198 -0
  220. package/schemas/release/release-gate-node.schema.json +2 -3
  221. package/dist/cli/codex-lb-setup-warning-output.js +0 -17
  222. package/dist/cli/install-helpers-codex-lb-chain.js +0 -529
  223. package/dist/commands/codex-lb.js +0 -1232
  224. package/dist/core/architecture-hardening/migration/migration.js +0 -111
  225. package/dist/core/codex-app/auxiliary-oauth-policy.js +0 -36
  226. package/dist/core/codex-app/catalog-compat/catalog-service.js +0 -104
  227. package/dist/core/codex-app/child-policy/child-policy.js +0 -35
  228. package/dist/core/codex-app/desktop-routing-snapshot.js +0 -233
  229. package/dist/core/codex-app/glm-model-profile.js +0 -1
  230. package/dist/core/codex-app/glm-profile-installer.js +0 -191
  231. package/dist/core/codex-app/glm-profile-schema.js +0 -42
  232. package/dist/core/codex-app/multi-provider-router.js +0 -391
  233. package/dist/core/codex-app/openrouter-activate.js +0 -405
  234. package/dist/core/codex-app/openrouter-model-catalog.js +0 -217
  235. package/dist/core/codex-app/provider-child-policy.js +0 -64
  236. package/dist/core/codex-app/provider-mode.js +0 -91
  237. package/dist/core/codex-app/provider-session-policy.js +0 -85
  238. package/dist/core/codex-app/session-policy/session-pinning.js +0 -68
  239. package/dist/core/codex-control/codex-lb-launch-recovery.js +0 -319
  240. package/dist/core/codex-lb/codex-lb-setup.js +0 -293
  241. package/dist/core/codex-lb/codex-lb-tool-catalog.js +0 -456
  242. package/dist/core/codex-lb/codex-lb-tool-output-recovery.js +0 -290
  243. package/dist/core/codex-lb/desktop-center-credentials.js +0 -1083
  244. package/dist/core/codex-lb/desktop-mode.js +0 -32
  245. package/dist/core/codex-lb/desktop-real-evidence.js +0 -168
  246. package/dist/core/codex-lb/legacy-migration.js +0 -576
  247. package/dist/core/codex-lb/native-openai-transport/transport.js +0 -68
  248. package/dist/core/codex-lb/provider-routing/provider-router.js +0 -58
  249. package/dist/core/codex-lb/routing-truth.js +0 -327
  250. package/dist/core/imagegen/codex-lb-imagegen-target.js +0 -92
  251. package/dist/native/sks-menubar/Sources/ProvidersConnectTest.swift +0 -118
  252. package/dist/native/sks-menubar/Sources/ProvidersFastMode.swift +0 -17
  253. package/dist/native/sks-menubar/Sources/ProvidersMultiProvider.swift +0 -331
  254. package/dist/native/sks-menubar/Sources/ProvidersRoleModels.swift +0 -260
  255. package/dist/scripts/codex-lb-catalog-passthrough-check.js +0 -120
  256. package/dist/scripts/codex-lb-desktop-auth-invariant-check.js +0 -169
  257. package/dist/scripts/codex-lb-desktop-capabilities-check.js +0 -287
  258. package/dist/scripts/codex-lb-desktop-real-evidence-check.js +0 -123
  259. package/dist/scripts/codex-lb-fast-mode-truth-check.js +0 -114
  260. package/dist/scripts/codex-lb-fast-ui-preservation-check.js +0 -152
  261. package/dist/scripts/codex-lb-gpt56-fast-profile-check.js +0 -231
@@ -1,9 +1,9 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import os from 'node:os';
4
- import { randomUUID } from 'node:crypto';
5
- import { exists, readJson, writeJsonAtomic, ensureDir, dirSize, fileSize, formatBytes, rmrf, nowIso, appendJsonlBounded, listFilesRecursive, managedSksTmpRoot, sha256, SKS_TEMP_LEASE_FILE } from './fsx.js';
4
+ import { exists, readJson, writeJsonAtomic, ensureDir, dirSize, fileSize, formatBytes, rmrf, nowIso, appendJsonlBounded, listFilesRecursive, managedSksTmpRoot, sha256 } from './fsx.js';
6
5
  import { FROM_CHAT_IMG_TEMP_TRIWIKI_ARTIFACT, FROM_CHAT_IMG_TEMP_TRIWIKI_SESSIONS } from './routes.js';
6
+ import { activeTempEnvironmentKey, currentProcessOwns, inspectTempPath, liveTempLease, removeDeadCanonicalTestLease, sharedTempEntryMatchesProject } from './retention/temp-lease.js';
7
7
  export const DEFAULT_RETENTION_POLICY = Object.freeze({
8
8
  schema_version: 1,
9
9
  max_missions: 30,
@@ -107,8 +107,6 @@ const FULL_RETENTION_SCAN_MAX_FILES = 1_000_000;
107
107
  const FULL_RETENTION_SCAN_MAX_DEPTH = 80;
108
108
  const MISSION_COMPACTION_SCAN_MAX_FILES = 100_000;
109
109
  const MISSION_COMPACTION_SCAN_MAX_DEPTH = 20;
110
- const TEMP_RETENTION_SCAN_MAX_ENTRIES = 100_000;
111
- const TEMP_RETENTION_SCAN_MAX_DEPTH = 20;
112
110
  export async function ensureRetentionPolicy(root) {
113
111
  const p = path.join(root, '.sneakoscope', 'policy.json');
114
112
  if (!(await exists(p)))
@@ -414,197 +412,6 @@ async function safeMissionDirectory(root, id) {
414
412
  return null;
415
413
  return candidate;
416
414
  }
417
- async function inspectTempPath(target) {
418
- const rootStat = await fs.lstat(target).catch(() => null);
419
- if (!rootStat || rootStat.isSymbolicLink()) {
420
- return {
421
- complete: false,
422
- latestMtimeMs: 0,
423
- bytes: 0,
424
- blockers: [rootStat ? 'temp_path_is_symlink' : 'temp_path_missing_or_unreadable']
425
- };
426
- }
427
- let latestMtimeMs = rootStat.mtimeMs;
428
- let bytes = rootStat.isFile() ? rootStat.size : 0;
429
- let entryCount = 0;
430
- const blockers = [];
431
- const stack = rootStat.isDirectory()
432
- ? [{ dir: target, depth: 0 }]
433
- : [];
434
- while (stack.length) {
435
- const current = stack.pop();
436
- let entries;
437
- try {
438
- entries = await fs.readdir(current.dir, { withFileTypes: true });
439
- }
440
- catch {
441
- blockers.push('temp_path_readdir_failed');
442
- continue;
443
- }
444
- for (const entry of entries) {
445
- entryCount += 1;
446
- if (entryCount > TEMP_RETENTION_SCAN_MAX_ENTRIES) {
447
- blockers.push(`temp_path_max_entries_exceeded:${TEMP_RETENTION_SCAN_MAX_ENTRIES}`);
448
- return { complete: false, latestMtimeMs, bytes, blockers: [...new Set(blockers)] };
449
- }
450
- const child = path.join(current.dir, entry.name);
451
- const stat = await fs.lstat(child).catch(() => null);
452
- if (!stat) {
453
- blockers.push('temp_path_stat_failed');
454
- continue;
455
- }
456
- latestMtimeMs = Math.max(latestMtimeMs, stat.mtimeMs);
457
- if (stat.isSymbolicLink())
458
- continue;
459
- if (stat.isDirectory()) {
460
- if (current.depth >= TEMP_RETENTION_SCAN_MAX_DEPTH) {
461
- blockers.push(`temp_path_max_depth_exceeded:${TEMP_RETENTION_SCAN_MAX_DEPTH}`);
462
- }
463
- else {
464
- stack.push({ dir: child, depth: current.depth + 1 });
465
- }
466
- continue;
467
- }
468
- if (stat.isFile())
469
- bytes += stat.size;
470
- }
471
- }
472
- return {
473
- complete: blockers.length === 0,
474
- latestMtimeMs,
475
- bytes,
476
- blockers: [...new Set(blockers)]
477
- };
478
- }
479
- function currentProcessOwns(stat) {
480
- if (typeof process.getuid !== 'function')
481
- return true;
482
- return stat.uid === process.getuid();
483
- }
484
- function sharedTempEntryMatchesProject(entryName, projectHash) {
485
- return entryName === `sks-${projectHash}` || entryName.startsWith(`sks-${projectHash}-`);
486
- }
487
- function activeTempEnvironmentKey(target) {
488
- const resolvedTarget = path.resolve(target);
489
- for (const key of ['SKS_TMP_DIR', 'TMPDIR', 'TMP', 'TEMP']) {
490
- const raw = process.env[key];
491
- if (!raw)
492
- continue;
493
- const activePath = path.resolve(raw);
494
- if (isWithin(resolvedTarget, activePath))
495
- return key;
496
- }
497
- return null;
498
- }
499
- async function liveTempLease(target) {
500
- const leasePath = path.join(target, SKS_TEMP_LEASE_FILE);
501
- const stat = await fs.lstat(leasePath).catch(() => null);
502
- if (!stat?.isFile() || stat.isSymbolicLink() || !currentProcessOwns(stat))
503
- return null;
504
- const lease = await readJson(leasePath, null).catch(() => null);
505
- const pid = Number(lease?.pid);
506
- if (lease?.schema !== 'sks.temp-lease.v1' || !processIdAlive(pid))
507
- return null;
508
- return {
509
- path: leasePath,
510
- pid,
511
- kind: lease?.kind ? String(lease.kind) : null
512
- };
513
- }
514
- async function removeDeadCanonicalTestLease(base, target, targetStat, dryRun, actions) {
515
- if (!targetStat.isDirectory() || targetStat.isSymbolicLink() || !currentProcessOwns(targetStat))
516
- return false;
517
- const [realBase, realTarget] = await Promise.all([
518
- fs.realpath(base).catch(() => null),
519
- fs.realpath(target).catch(() => null)
520
- ]);
521
- if (!realBase || !realTarget || path.dirname(realTarget) !== realBase || !isWithin(realBase, realTarget))
522
- return false;
523
- const leasePath = path.join(target, SKS_TEMP_LEASE_FILE);
524
- const leaseLstat = await fs.lstat(leasePath).catch(() => null);
525
- if (!leaseLstat?.isFile() || leaseLstat.isSymbolicLink() || !currentProcessOwns(leaseLstat) || leaseLstat.size > 4096)
526
- return false;
527
- const leaseHandle = await fs.open(leasePath, 'r').catch(() => null);
528
- if (!leaseHandle)
529
- return false;
530
- let lease = null;
531
- try {
532
- const leaseStat = await leaseHandle.stat();
533
- if (!leaseStat.isFile()
534
- || !currentProcessOwns(leaseStat)
535
- || leaseStat.dev !== leaseLstat.dev
536
- || leaseStat.ino !== leaseLstat.ino)
537
- return false;
538
- lease = JSON.parse(await leaseHandle.readFile({ encoding: 'utf8' }));
539
- }
540
- catch {
541
- return false;
542
- }
543
- finally {
544
- await leaseHandle.close().catch(() => undefined);
545
- }
546
- const leaseKeys = lease && typeof lease === 'object' && !Array.isArray(lease)
547
- ? Object.keys(lease).sort()
548
- : [];
549
- const pid = Number(lease?.pid);
550
- if (lease?.schema !== 'sks.temp-lease.v1'
551
- || lease?.kind !== 'canonical-test-runner'
552
- || !Number.isSafeInteger(pid)
553
- || pid <= 0
554
- || typeof lease?.created_at !== 'string'
555
- || !Number.isFinite(Date.parse(lease.created_at))
556
- || leaseKeys.join(',') !== 'created_at,kind,pid,schema'
557
- || processIdAlive(pid))
558
- return false;
559
- const currentTargetStat = await fs.lstat(target).catch(() => null);
560
- if (!currentTargetStat?.isDirectory()
561
- || currentTargetStat.isSymbolicLink()
562
- || !currentProcessOwns(currentTargetStat)
563
- || currentTargetStat.dev !== targetStat.dev
564
- || currentTargetStat.ino !== targetStat.ino)
565
- return false;
566
- const action = {
567
- action: 'remove_sks_temp',
568
- path: target,
569
- reason: 'dead_canonical_test_lease',
570
- lease_path: leasePath,
571
- owner_pid: pid,
572
- lease_kind: lease.kind
573
- };
574
- if (dryRun) {
575
- actions.push(action);
576
- return true;
577
- }
578
- const quarantine = path.join(realBase, `.sks-retention-quarantine-${process.pid}-${randomUUID()}`);
579
- try {
580
- await fs.rename(target, quarantine);
581
- }
582
- catch {
583
- return false;
584
- }
585
- const quarantinedStat = await fs.lstat(quarantine).catch(() => null);
586
- if (!quarantinedStat
587
- || quarantinedStat.dev !== targetStat.dev
588
- || quarantinedStat.ino !== targetStat.ino) {
589
- if (!(await exists(target)))
590
- await fs.rename(quarantine, target).catch(() => undefined);
591
- return false;
592
- }
593
- await rmrf(quarantine);
594
- actions.push(action);
595
- return true;
596
- }
597
- function processIdAlive(pid) {
598
- if (!Number.isSafeInteger(pid) || pid <= 0)
599
- return false;
600
- try {
601
- process.kill(pid, 0);
602
- return true;
603
- }
604
- catch (error) {
605
- return error?.code === 'EPERM';
606
- }
607
- }
608
415
  async function pruneTmp(root, policy, dryRun, actions) {
609
416
  const tmp = path.join(root, '.sneakoscope', 'tmp');
610
417
  if (!(await exists(tmp)))
@@ -1251,6 +1058,8 @@ export async function sweepSksTempDirs(root, opts = {}) {
1251
1058
  const maxAgeMs = Math.max(0, Number(opts.maxAgeHours ?? 6)) * 60 * 60 * 1000;
1252
1059
  const realRoot = await fs.realpath(path.resolve(root)).catch(() => path.resolve(root));
1253
1060
  const projectHash = sha256(realRoot).slice(0, 12);
1061
+ const activeTempRoot = path.resolve(os.tmpdir());
1062
+ const activeTempRootLease = await liveTempLease(activeTempRoot);
1254
1063
  const roots = [
1255
1064
  { base: path.join(os.tmpdir(), 'sks-gate'), external: true, shared: true },
1256
1065
  { base: managedSksTmpRoot(), external: true, shared: false, canonicalLeaseCleanup: true },
@@ -1264,6 +1073,20 @@ export async function sweepSksTempDirs(root, opts = {}) {
1264
1073
  const base = rootSpec.base;
1265
1074
  if (!(await exists(base)))
1266
1075
  continue;
1076
+ if (rootSpec.external
1077
+ && activeTempRootLease
1078
+ && activeTempRootLease.kind === 'canonical-test-runner'
1079
+ && isWithin(activeTempRoot, base)) {
1080
+ actions.push({
1081
+ action: 'skip_sks_temp_sweep',
1082
+ path: base,
1083
+ reason: 'active_parent_temp_lease',
1084
+ lease_path: activeTempRootLease.path,
1085
+ owner_pid: activeTempRootLease.pid,
1086
+ lease_kind: activeTempRootLease.kind
1087
+ });
1088
+ continue;
1089
+ }
1267
1090
  if (!(await safeRetentionBase(root, base, { external: rootSpec.external }))) {
1268
1091
  actions.push({ action: 'skip_unsafe_retention_root', path: base, reason: 'symlink_or_outside_managed_temp' });
1269
1092
  continue;
@@ -661,10 +661,9 @@ export const COMMAND_CATALOG = [
661
661
  { name: 'uninstall', usage: 'sks uninstall [--dry-run] [--yes] [--keep-config] [--keep-data] [--purge-projects] [--json]', description: 'Remove SKS global skills, hooks, menu bar, state, temp files, and optional project residue while preserving user-owned content by default.' },
662
662
  { name: 'deps', usage: 'sks deps check [--json] [--yes]', description: 'Check Node/npm and Codex CLI readiness; pass --yes to repair missing Codex CLI tooling when supported.' },
663
663
  { name: 'codex', usage: 'sks codex compatibility|version|update-status [--refresh]|update|doctor|schema|current [--json]', description: 'Check Codex CLI compatibility/version/update status, run the official `codex update`, and inspect current manifest, capability, and hook-schema evidence.' },
664
- { name: 'codex-app', usage: 'sks codex-app [check|set-openrouter-key --api-key-stdin|use-openrouter --model <id>|openrouter-status|product-design|chrome-extension|pat status|remote-control]', description: 'Check Codex App install, OpenRouter provider activation, codex-lb key-entry guidance, Product Design plugin readiness, Codex Chrome Extension web verification readiness, PAT-safe status, first-party MCP/plugin readiness, and Codex CLI remote-control availability.' },
664
+ { name: 'codex-app', usage: 'sks codex-app [check|status|restart|product-design|chrome-extension|pat status|remote-control]', description: 'Check Codex App integration, Desktop Bridge readiness, Product Design plugin readiness, Codex Chrome Extension web verification readiness, PAT-safe status, first-party MCP/plugin readiness, and Codex CLI remote-control availability. Provider routing is managed only by sks bridge.' },
665
665
  { name: 'codex-native', usage: 'sks codex-native status|feature-broker|invocation-plan|init-deep [--json]', description: 'Inspect Codex Native feature broker readiness, invocation routing, pattern evidence, and managed memory setup.' },
666
666
  { name: 'hooks', usage: 'sks hooks explain|status|trust-report|replay|codex-validate|warning-check ... [--json]', description: 'Explain Codex hook events, validate current vendored event output schemas, replay fixtures, and enforce warning-zero SKS hook policies.' },
667
- { name: 'codex-lb', usage: 'sks codex-lb status|connect-test|health|metrics|doctor|circuit|repair|setup ...', description: 'Configure, run a one-request connection proof, health-check, repair, and record circuit evidence for codex-lb provider auth without confusing ChatGPT OAuth and proxy keys.' },
668
667
  { name: 'remote', usage: 'sks remote readiness|machines|worker ... [--json]', description: 'Inspect official Codex Remote readiness and the allowlisted proof-aware SSH stdio worker surface.' },
669
668
  { name: 'mad-sks', usage: 'sks mad-sks plan|run|apply|sql|apply-migration|status|close|rollback-apply ... | sks --mad [--high]', description: 'Open or inspect MAD-SKS scoped permission workflows, merged SQL-plane execution, and the native Codex permission launcher.' },
670
669
  { name: 'auto-review', usage: 'sks auto-review status|enable|start [--high] | sks --Auto-review --high', description: 'Enable Codex automatic approval review and launch the native Codex session with the auto-review profile.' },
@@ -18,7 +18,7 @@ export function buildIntentContract(input) {
18
18
  canonical_command: normalizeCommand(input.canonicalCommand),
19
19
  target_hashes: [...new Set(input.targetHashes)].sort(),
20
20
  policy_version: safeIdentifier(input.policyVersion, 'intent_policy_version_invalid'),
21
- mode_snapshot: input.modeSnapshot,
21
+ runtime_snapshot: input.runtimeSnapshot,
22
22
  evidence_state: input.evidenceState,
23
23
  retry_budget: Math.max(0, Math.min(2, input.retryBudget ?? 0)),
24
24
  risk,
@@ -34,8 +34,8 @@ export function decideIntentReplay(previous, current) {
34
34
  const reasons = [];
35
35
  if (previous.policy_version !== current.policy_version)
36
36
  reasons.push('policy_version_changed');
37
- if (previous.mode_snapshot !== current.mode_snapshot)
38
- reasons.push('mode_snapshot_changed');
37
+ if (previous.runtime_snapshot !== current.runtime_snapshot)
38
+ reasons.push('runtime_snapshot_changed');
39
39
  if (JSON.stringify(previous.target_hashes) !== JSON.stringify(current.target_hashes))
40
40
  reasons.push('target_hash_changed');
41
41
  if (previous.effect !== current.effect || previous.canonical_command !== current.canonical_command)
@@ -1,6 +1,7 @@
1
1
  import { LUNA_SUBAGENT_MODEL, SOL_SUBAGENT_MODEL, TERRA_SUBAGENT_MODEL } from './model-policy.js';
2
2
  export const NARUTO_AUTH_MODES = ['managed', 'host'];
3
3
  const IDENTIFIER_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
4
+ const RETIRED_DIRECT_MANAGED_PROVIDERS = new Set(['codex-lb', 'openrouter']);
4
5
  export const NARUTO_EFFORT_TIERS = ['minimal', 'low', 'medium', 'high', 'max'];
5
6
  function readFlagValue(args, name) {
6
7
  const inline = args.find((arg) => arg.startsWith(`${name}=`));
@@ -55,6 +56,9 @@ export function resolveNarutoCredentialPolicy(input) {
55
56
  else if (authMode === 'managed') {
56
57
  blockers.push('naruto_model_provider_requires_host_auth_mode');
57
58
  }
59
+ else if (RETIRED_DIRECT_MANAGED_PROVIDERS.has(providerRaw.value)) {
60
+ blockers.push('desktop_bridge_direct_provider_selection_retired');
61
+ }
58
62
  else {
59
63
  modelProvider = providerRaw.value;
60
64
  sources.modelProvider = providerRaw.source;
@@ -149,8 +153,9 @@ function validateGpt56EffortPair(scope, model, effort, blockers) {
149
153
  }
150
154
  export function narutoCredentialConfigArgs(policy) {
151
155
  const out = [];
152
- if (policy.modelProvider)
156
+ if (policy.modelProvider && !RETIRED_DIRECT_MANAGED_PROVIDERS.has(policy.modelProvider)) {
153
157
  out.push('-c', `model_provider="${policy.modelProvider}"`);
158
+ }
154
159
  if (policy.forcedLoginMethod)
155
160
  out.push('-c', `forced_login_method="${policy.forcedLoginMethod}"`);
156
161
  return out;
@@ -124,9 +124,14 @@ export async function readOfficialSubagentConfig(root, opts = {}) {
124
124
  const defaultSubagentModel = resolveLayeredValue(projectLayer.agents.default_subagent_model, globalLayer.agents.default_subagent_model, DEFAULT_OFFICIAL_SUBAGENT_MODEL, nonEmptyString);
125
125
  const defaultSubagentReasoningEffort = resolveLayeredValue(projectLayer.agents.default_subagent_reasoning_effort, globalLayer.agents.default_subagent_reasoning_effort, DEFAULT_OFFICIAL_SUBAGENT_REASONING_EFFORT, nonEmptyString);
126
126
  const multiAgentV2 = resolveMultiAgentV2Layer(projectLayer.features.multi_agent_v2, globalLayer.features.multi_agent_v2, effectiveMaxThreads);
127
+ const effectiveMultiAgentV2 = {
128
+ ...multiAgentV2.value,
129
+ maxConcurrentThreadsPerSession: Math.min(multiAgentV2.value.maxConcurrentThreadsPerSession, HARD_NARUTO_MAX_THREADS + 1)
130
+ };
127
131
  const depthCoerced = maxDepth.value > 1;
128
132
  const warnings = [
129
133
  ...(depthCoerced ? [`official_subagent_max_depth_coerced_to_one:${maxDepth.value}:${maxDepth.source}`] : []),
134
+ ...capacityNormalizationWarnings(maxThreads, multiAgentV2),
130
135
  ...(projectLayer.legacyWarnings),
131
136
  ...(globalLayer.legacyWarnings)
132
137
  ];
@@ -138,7 +143,7 @@ export async function readOfficialSubagentConfig(root, opts = {}) {
138
143
  interruptMessage: interruptMessage.value,
139
144
  defaultSubagentModel: defaultSubagentModel.value,
140
145
  defaultSubagentReasoningEffort: defaultSubagentReasoningEffort.value,
141
- multiAgentV2: multiAgentV2.value,
146
+ multiAgentV2: effectiveMultiAgentV2,
142
147
  sources: {
143
148
  enabled: enabled.source,
144
149
  maxThreads: maxThreads.source,
@@ -159,11 +164,15 @@ export function officialSubagentConfigWarnings(text = '', inheritedText = '') {
159
164
  const inherited = configLayer(inheritedText, 'global');
160
165
  if (project.blockers.length || inherited.blockers.length)
161
166
  return [];
167
+ const maxThreads = resolveLayeredValue(readAgentsMaxThreadsFromRecord(project.agents), readAgentsMaxThreadsFromRecord(inherited.agents), DEFAULT_OFFICIAL_SUBAGENT_MAX_THREADS, positiveInteger);
168
+ const effectiveMaxThreads = Math.min(maxThreads.value, HARD_NARUTO_MAX_THREADS);
169
+ const multiAgentV2 = resolveMultiAgentV2Layer(project.features.multi_agent_v2, inherited.features.multi_agent_v2, effectiveMaxThreads);
162
170
  const maxDepth = resolveLayeredValue(project.agents.max_depth, inherited.agents.max_depth, DEFAULT_OFFICIAL_SUBAGENT_MAX_DEPTH, positiveInteger);
163
171
  return [
164
172
  ...(maxDepth.value > 1
165
173
  ? [`official_subagent_max_depth_coerced_to_one:${maxDepth.value}:${maxDepth.source}`]
166
174
  : []),
175
+ ...capacityNormalizationWarnings(maxThreads, multiAgentV2),
167
176
  ...project.legacyWarnings,
168
177
  ...inherited.legacyWarnings
169
178
  ];
@@ -364,16 +373,8 @@ function configLayer(text, label) {
364
373
  ...(configuredMaxThreads !== undefined && positiveInteger(configuredMaxThreads) === null
365
374
  ? [`${label}_official_subagent_max_threads_invalid`]
366
375
  : []),
367
- ...(positiveInteger(configuredMaxThreads) !== null
368
- && Number(configuredMaxThreads) > HARD_NARUTO_MAX_THREADS
369
- ? [`${label}_official_subagent_max_threads_exceeds_hard_cap:${configuredMaxThreads}:${HARD_NARUTO_MAX_THREADS}`]
370
- : []),
371
376
  ...(configuredV2Threads !== undefined && positiveInteger(configuredV2Threads) === null
372
377
  ? [`${label}_official_subagent_multi_agent_v2_max_threads_invalid`]
373
- : []),
374
- ...(positiveInteger(configuredV2Threads) !== null
375
- && Number(configuredV2Threads) > HARD_NARUTO_MAX_THREADS + 1
376
- ? [`${label}_official_subagent_multi_agent_v2_max_threads_exceeds_hard_cap:${configuredV2Threads}:${HARD_NARUTO_MAX_THREADS + 1}`]
377
378
  : [])
378
379
  ],
379
380
  legacyWarnings
@@ -393,6 +394,23 @@ function resolveMultiAgentV2Layer(projectValue, globalValue, maxThreads) {
393
394
  return { value: global, source: 'global' };
394
395
  return { value: fallback, source: 'default' };
395
396
  }
397
+ function capacityNormalizationWarnings(maxThreads, multiAgentV2) {
398
+ return [
399
+ ...(maxThreads.value > HARD_NARUTO_MAX_THREADS
400
+ ? [
401
+ `official_subagent_max_threads_normalized:configured=${maxThreads.value}`
402
+ + `:effective=${HARD_NARUTO_MAX_THREADS}:source=${maxThreads.source}`
403
+ ]
404
+ : []),
405
+ ...(multiAgentV2.value.maxConcurrentThreadsPerSession > HARD_NARUTO_MAX_THREADS + 1
406
+ ? [
407
+ 'official_subagent_multi_agent_v2_max_threads_normalized'
408
+ + `:configured=${multiAgentV2.value.maxConcurrentThreadsPerSession}`
409
+ + `:effective=${HARD_NARUTO_MAX_THREADS + 1}:source=${multiAgentV2.source}`
410
+ ]
411
+ : [])
412
+ ];
413
+ }
396
414
  function normalizeMultiAgentV2(value, fallback) {
397
415
  if (value === true) {
398
416
  return { ...fallback, enabled: true };
@@ -3,7 +3,7 @@ import os from 'node:os';
3
3
  import path from 'node:path';
4
4
  import { randomId, runProcess, writeJsonAtomic } from '../fsx.js';
5
5
  import { DEFAULT_SUBAGENT_EFFORT, DEFAULT_SUBAGENT_MODEL, NARUTO_PARENT_EFFORT, NARUTO_PARENT_MODEL } from './model-policy.js';
6
- import { inspectCodexLbCliLaunchRecovery } from '../codex-control/codex-lb-launch-recovery.js';
6
+ import { inspectDesktopBridgeCliLaunchGuard, stripRetiredDirectProviderEnv } from '../codex-control/desktop-bridge-launch-guard.js';
7
7
  import { prepareCodexAppServerRuntimeEnv } from '../codex-control/codex-app-server-runtime-env.js';
8
8
  import { probeNarutoCodexCapability } from '../codex-compat/codex-capability-matrix.js';
9
9
  import { resolveOfficialCodexPackageRuntime } from '../codex-runtime/resolve-codex-runtime.js';
@@ -359,27 +359,12 @@ export async function runOfficialSubagentWorkflow(input) {
359
359
  hostCapabilityLaunchNonce = randomId(32);
360
360
  hostCapabilityPendingDir = officialSubagentMissionDir(canonicalRoot, input.missionId);
361
361
  }
362
- const sourceEnv = { ...process.env, ...(input.env || {}) };
363
- const runtimeEnv = input.credentialPolicy?.authMode === 'managed'
362
+ const sourceEnv = stripRetiredDirectProviderEnv({ ...process.env, ...(input.env || {}) });
363
+ const preparedRuntimeEnv = input.credentialPolicy?.authMode === 'managed'
364
364
  ? sourceEnv
365
365
  : await (input.prepareCodexRuntimeEnvImpl || prepareCodexAppServerRuntimeEnv)({ env: sourceEnv });
366
- const codexLbCliSelected = Boolean(runtimeEnv.CODEX_LB_API_KEY && runtimeEnv.CODEX_LB_BASE_URL);
367
- const resolvedCredentialPolicy = input.credentialPolicy ?? (codexLbCliSelected
368
- ? resolveNarutoCredentialPolicy({
369
- args: ['--auth-mode=host', '--model-provider=codex-lb', '--provider-env-key=CODEX_LB_API_KEY'],
370
- env: runtimeEnv,
371
- defaultParentModel: NARUTO_PARENT_MODEL,
372
- defaultParentEffort: NARUTO_PARENT_EFFORT,
373
- defaultSubagentModel: DEFAULT_SUBAGENT_MODEL,
374
- defaultSubagentEffort: DEFAULT_SUBAGENT_EFFORT
375
- })
376
- : defaultNarutoCredentialPolicy());
377
- const credentialPolicy = codexLbCliSelected
378
- && resolvedCredentialPolicy.authMode === 'host'
379
- && resolvedCredentialPolicy.modelProvider === null
380
- && resolvedCredentialPolicy.providerEnvKey === null
381
- ? { ...resolvedCredentialPolicy, providerEnvKey: 'CODEX_LB_API_KEY' }
382
- : resolvedCredentialPolicy;
366
+ const runtimeEnv = stripRetiredDirectProviderEnv(preparedRuntimeEnv);
367
+ const credentialPolicy = input.credentialPolicy ?? defaultNarutoCredentialPolicy();
383
368
  const inheritedSecretValues = knownInheritedSecretValues(runtimeEnv);
384
369
  const childEnv = buildOfficialSubagentChildEnv({
385
370
  env: runtimeEnv,
@@ -492,27 +477,25 @@ export async function runOfficialSubagentWorkflow(input) {
492
477
  hostCapabilityConfigArgs: hostCapabilityCodexConfigArgs(hostCapabilityRuntime),
493
478
  credentialPolicy
494
479
  });
495
- const toolOutputRecovery = await inspectCodexLbCliLaunchRecovery({
480
+ const launchGuard = await inspectDesktopBridgeCliLaunchGuard({
496
481
  root: input.root,
497
482
  env: childEnv,
498
- cliArgs: args.slice(0, -1),
499
- ...(input.recoveryFetch ? { fetchImpl: input.recoveryFetch } : {}),
500
- ...(input.recoveryTimeoutMs === undefined ? {} : { timeoutMs: input.recoveryTimeoutMs })
483
+ cliArgs: args.slice(0, -1)
501
484
  });
502
- if (!toolOutputRecovery.ok) {
485
+ if (!launchGuard.ok) {
503
486
  return {
504
487
  ...base,
505
488
  ok: false,
506
- status: 'tool_output_recovery_blocked',
489
+ status: 'desktop_bridge_launch_guard_blocked',
507
490
  prepared: false,
508
491
  codex_exit_code: null,
509
492
  parent_summary: null,
510
493
  parent_summary_file: null,
511
494
  host_capability_runtime: hostCapabilityRuntime,
512
495
  host_capability_evidence: hostCapabilityCollector.finish(),
513
- tool_output_recovery: toolOutputRecovery,
514
- blockers: toolOutputRecovery.blockers,
515
- operator_actions: toolOutputRecovery.operator_actions,
496
+ desktop_bridge_launch_guard: launchGuard,
497
+ blockers: launchGuard.blockers,
498
+ operator_actions: launchGuard.operator_actions,
516
499
  completion_evidence: false
517
500
  };
518
501
  }
@@ -536,7 +519,7 @@ export async function runOfficialSubagentWorkflow(input) {
536
519
  parent_summary_file: null,
537
520
  host_capability_runtime: hostCapabilityRuntime,
538
521
  host_capability_evidence: hostCapabilityCollector.finish(),
539
- tool_output_recovery: toolOutputRecovery,
522
+ desktop_bridge_launch_guard: launchGuard,
540
523
  blockers: ['host_capability_pending_runtime_write_failed'],
541
524
  completion_evidence: false
542
525
  };
@@ -611,7 +594,7 @@ export async function runOfficialSubagentWorkflow(input) {
611
594
  oauth_callback_port_diagnostic: oauthCallbackPortDiagnostic,
612
595
  host_capability_runtime: hostCapabilityRuntime,
613
596
  host_capability_evidence: hostCapabilityEvidence,
614
- tool_output_recovery: toolOutputRecovery,
597
+ desktop_bridge_launch_guard: launchGuard,
615
598
  process: {
616
599
  pid: processResult.pid || null,
617
600
  timed_out: processResult.timedOut,
@@ -46,13 +46,16 @@ export function subagentCountContractBlockers(plan, observedStarts = 0) {
46
46
  const waveCapacity = positiveCount(lifecycle?.wave_capacity)
47
47
  || positiveCount(plan?.first_wave)
48
48
  || positiveCount(plan?.capacity_controller?.selected_capacity);
49
- const observedWavePeak = Array.isArray(lifecycle?.waves)
49
+ const legacyObservedWavePeak = Array.isArray(lifecycle?.waves)
50
50
  ? lifecycle.waves.reduce((peak, wave) => {
51
51
  if (!wave || typeof wave !== 'object' || !Array.isArray(wave.thread_ids))
52
52
  return peak;
53
53
  return Math.max(peak, new Set(wave.thread_ids.map(String).filter(Boolean)).size);
54
54
  }, 0)
55
55
  : 0;
56
+ const observedWavePeak = lifecycle?.peak_open_threads === undefined
57
+ ? legacyObservedWavePeak
58
+ : normalizeCount(lifecycle.peak_open_threads);
56
59
  const waveCapacityExceeded = waveCapacity > 0 && observedWavePeak > waveCapacity;
57
60
  return [
58
61
  ...(exactTargetRejected ? ['subagent_target_change_rejected'] : []),
@@ -112,6 +115,7 @@ export function createSubagentWaveLifecycle(input) {
112
115
  cumulative_completed: 0,
113
116
  cumulative_failed: 0,
114
117
  cumulative_settled: 0,
118
+ peak_open_threads: 0,
115
119
  open_threads: 0,
116
120
  remaining_to_start: targetSubagents,
117
121
  post_wave_rescan_required: false,
@@ -152,12 +156,16 @@ export async function refreshSubagentWaveLifecycle(artifactDir, input = {}) {
152
156
  const targetSubagents = countPolicy === 'exact' && existing
153
157
  ? requestedTargetSubagents
154
158
  : Math.min(automaticSubagentTargetCap(plan), Math.max(requestedTargetSubagents, normalizeCount(existing?.target_subagents), startedCount));
159
+ const waveCapacity = positiveCount(plan.first_wave)
160
+ || positiveCount(plan.capacity_controller?.selected_capacity)
161
+ || positiveCount(existing?.wave_capacity)
162
+ || positiveCount(plan.max_threads)
163
+ || targetSubagents;
155
164
  const next = projectLifecycle(existing || createSubagentWaveLifecycle({
156
165
  workflowRunId,
157
166
  targetSubagents,
158
167
  countPolicy,
159
- waveCapacity: positiveCount(plan.first_wave)
160
- || positiveCount(plan.capacity_controller?.selected_capacity)
168
+ waveCapacity
161
169
  }), {
162
170
  workflowRunId,
163
171
  requestedTargetSubagents,
@@ -165,8 +173,7 @@ export async function refreshSubagentWaveLifecycle(artifactDir, input = {}) {
165
173
  countPolicy,
166
174
  events,
167
175
  evidence: input.evidence || null,
168
- waveCapacity: positiveCount(plan.first_wave)
169
- || positiveCount(plan.capacity_controller?.selected_capacity),
176
+ waveCapacity,
170
177
  lastEvent: input.event?.event_name || null,
171
178
  targetChangeRejected: existing?.target_change_rejected === true || (countPolicy === 'exact'
172
179
  && Boolean(existing)
@@ -230,14 +237,13 @@ function projectLifecycle(previous, input) {
230
237
  const cumulativeSettled = [...stops].filter((threadId) => starts.includes(threadId)).length;
231
238
  const completed = Math.max(0, cumulativeSettled - failed.size);
232
239
  const openThreads = Math.max(0, starts.length - cumulativeSettled);
240
+ const peakOpenThreads = maxConcurrentOpenThreads(input.events);
233
241
  const remainingToStart = Math.max(0, input.targetSubagents - starts.length);
234
242
  const lastWave = waves.at(-1);
235
243
  const postWaveRescanRequired = Boolean(lastWave?.status === 'settled'
236
244
  && openThreads === 0
237
245
  && remainingToStart > 0);
238
- const recoveredCapacity = lastWave?.status === 'settled'
239
- ? lastWave.thread_ids.length
240
- : 0;
246
+ const recoveredCapacity = Math.min(cumulativeSettled, Math.max(0, input.waveCapacity - openThreads));
241
247
  const parentGuidance = buildWaveParentGuidance({
242
248
  remaining_to_start: remainingToStart,
243
249
  open_threads: openThreads,
@@ -260,6 +266,7 @@ function projectLifecycle(previous, input) {
260
266
  cumulative_completed: completed,
261
267
  cumulative_failed: failed.size,
262
268
  cumulative_settled: cumulativeSettled,
269
+ peak_open_threads: peakOpenThreads,
263
270
  open_threads: openThreads,
264
271
  remaining_to_start: remainingToStart,
265
272
  post_wave_rescan_required: postWaveRescanRequired,
@@ -283,6 +290,27 @@ function normalizeLifecycle(value, workflowRunId) {
283
290
  function uniqueThreadIds(events) {
284
291
  return [...new Set(events.map((event) => event.thread_id || '').filter(Boolean))];
285
292
  }
293
+ function maxConcurrentOpenThreads(events) {
294
+ const started = new Set();
295
+ const open = new Set();
296
+ let peak = 0;
297
+ for (const event of events) {
298
+ const threadId = event.thread_id;
299
+ if (!threadId)
300
+ continue;
301
+ if (event.event_name === 'SubagentStart') {
302
+ if (started.has(threadId))
303
+ continue;
304
+ started.add(threadId);
305
+ open.add(threadId);
306
+ peak = Math.max(peak, open.size);
307
+ continue;
308
+ }
309
+ if (started.has(threadId))
310
+ open.delete(threadId);
311
+ }
312
+ return peak;
313
+ }
286
314
  function latestEventTime(events, name, threadIds) {
287
315
  const allowed = new Set(threadIds);
288
316
  return events
@@ -19,7 +19,8 @@ export function buildWaveParentGuidance(lifecycle) {
19
19
  }
20
20
  if (rescan || remaining > 0) {
21
21
  actions.push(REFRESH_LIFECYCLE_ACTION);
22
- const nextWaveLimit = Math.min(remaining, waveCapacity);
22
+ const reusableSlots = Math.max(0, waveCapacity - openThreads);
23
+ const nextWaveLimit = Math.min(remaining, reusableSlots);
23
24
  if (nextWaveLimit > 0) {
24
25
  actions.push(`${SPAWN_NEXT_WAVE_ACTION}:${nextWaveLimit}`);
25
26
  }
@@ -123,8 +123,8 @@ async function probeTelegramReceipt(receiptPath, now) {
123
123
  token_configured: receipt.token_configured,
124
124
  token_source: receipt.token_source ?? (receipt.token_configured ? 'unknown' : 'none'),
125
125
  bot_identity_valid: receipt.bot_identity_valid,
126
- getme_checked_at: receipt.getme_checked_at,
127
- getme_latency_ms: receipt.getme_latency_ms,
126
+ getme_checked_at: receipt.getme_checked_at ?? null,
127
+ getme_latency_ms: receipt.getme_latency_ms ?? null,
128
128
  getme_check_kind: 'receipt',
129
129
  paired_chat_count: receipt.paired_chat_count,
130
130
  audit_healthy: receipt.audit_healthy !== false,
@@ -89,8 +89,9 @@ function isTelegramLivenessReceipt(value) {
89
89
  && (row.token_source === undefined || ['env', 'user_secret_file', 'none', 'unknown'].includes(String(row.token_source)))
90
90
  && optionalPositiveSafeInteger(row.bot_id)
91
91
  && typeof row.bot_identity_valid === 'boolean'
92
- && nullableIso(row.getme_checked_at)
93
- && (row.getme_latency_ms === null || (Number.isFinite(row.getme_latency_ms) && Number(row.getme_latency_ms) >= 0))
92
+ && optionalNullableIso(row.getme_checked_at)
93
+ && (row.getme_latency_ms === undefined || row.getme_latency_ms === null
94
+ || (Number.isFinite(row.getme_latency_ms) && Number(row.getme_latency_ms) >= 0))
94
95
  && Number.isSafeInteger(row.paired_chat_count) && Number(row.paired_chat_count) >= 0
95
96
  && typeof row.started_at === 'string' && Number.isFinite(Date.parse(row.started_at))
96
97
  && typeof row.heartbeat_at === 'string' && Number.isFinite(Date.parse(row.heartbeat_at))
@@ -110,9 +111,14 @@ function isPollSnapshot(value) {
110
111
  && typeof row.running === 'boolean'
111
112
  && Number.isSafeInteger(row.offset) && Number(row.offset) >= 0
112
113
  && Number.isSafeInteger(row.consecutive_failures) && Number(row.consecutive_failures) >= 0
113
- && nullableIso(row.last_poll_at) && nullableIso(row.last_success_at) && nullableIso(row.last_update_at)
114
- && (row.last_error === null || typeof row.last_error === 'string');
114
+ && optionalNullableIso(row.last_poll_at)
115
+ && optionalNullableIso(row.last_success_at)
116
+ && optionalNullableIso(row.last_update_at)
117
+ && (row.last_error === undefined || row.last_error === null || typeof row.last_error === 'string');
115
118
  }
116
119
  function nullableIso(value) {
117
120
  return value === null || (typeof value === 'string' && Number.isFinite(Date.parse(value)));
118
121
  }
122
+ function optionalNullableIso(value) {
123
+ return value === undefined || nullableIso(value);
124
+ }