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
@@ -236,23 +236,23 @@ export async function compileContextGraph(input) {
236
236
  const root = path.resolve(input.root);
237
237
  if (input.useCompileLock === false)
238
238
  return compileLocked(input, root, startedAt);
239
- try {
240
- return await withTriWikiStateLock(root, async () => {
241
- const outcome = await withContextGraphCompileLock(root, () => compileLocked(input, root, startedAt));
242
- if (outcome.acquired)
243
- return outcome.value;
244
- await appendContextGraphEvent(root, {
245
- type: 'compile.lock_contended',
246
- at: input.observedAt ?? new Date().toISOString(),
247
- reason: 'lock_held'
248
- });
249
- return failure('lock_held', ['lock_held'], Date.now() - startedAt);
250
- });
251
- }
252
- catch (error) {
253
- if (!String(error).includes('file_lock_timeout:'))
254
- throw error;
255
- return failure('lock_held', ['triwiki_state_lock_held'], Date.now() - startedAt);
256
- }
239
+ const outcome = await withContextGraphCompileLock(root, async () => {
240
+ try {
241
+ return await withTriWikiStateLock(root, () => compileLocked(input, root, startedAt));
242
+ }
243
+ catch (error) {
244
+ if (!String(error).includes('file_lock_timeout:'))
245
+ throw error;
246
+ return failure('lock_held', ['triwiki_state_lock_held'], Date.now() - startedAt);
247
+ }
248
+ });
249
+ if (outcome.acquired)
250
+ return outcome.value;
251
+ await appendContextGraphEvent(root, {
252
+ type: 'compile.lock_contended',
253
+ at: input.observedAt ?? new Date().toISOString(),
254
+ reason: 'lock_held'
255
+ });
256
+ return failure('lock_held', ['lock_held'], Date.now() - startedAt);
257
257
  }
258
258
  export { DEFAULT_CONTEXT_GRAPH_LIMITS } from './extract.js';
@@ -0,0 +1,172 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import { writeCodexConfigGuarded } from '../../codex/codex-config-guard.js';
4
+ import { readText } from '../../fsx.js';
5
+ import { escapeRegExp } from '../../text/regex.js';
6
+ export async function runConfigFastModeNormalizeStage() {
7
+ const { reconcileRetiredSksConfigText } = await import('../../auto-review.js');
8
+ const configPath = path.join(os.homedir(), '.codex', 'config.toml');
9
+ const text = await readText(configPath, null);
10
+ if (typeof text !== 'string') {
11
+ return { ok: true, status: 'ok', actions: ['codex_config_absent'], blockers: [], warnings: [] };
12
+ }
13
+ const normalized = normalizeLegacyFastModeConfigForUpdate(text);
14
+ const retired = reconcileRetiredSksConfigText(normalized.text);
15
+ const nextText = ensureTrailingNewline(retired.text);
16
+ const actions = [
17
+ ...normalized.actions,
18
+ ...(retired.detected_count > 0 ? ['stripped_retired_sks_config_profiles_and_policies'] : [])
19
+ ];
20
+ if (nextText === ensureTrailingNewline(text)) {
21
+ return {
22
+ ok: true,
23
+ status: 'ok',
24
+ actions: actions.length ? actions : ['fastmode_config_current'],
25
+ blockers: [],
26
+ warnings: [],
27
+ detail: {
28
+ config_path: configPath,
29
+ default_profile: normalized.defaultProfile,
30
+ retired_config_detected_count: retired.detected_count
31
+ }
32
+ };
33
+ }
34
+ const guardResult = await writeCodexConfigGuarded({
35
+ configPath,
36
+ before: text,
37
+ mutate: () => nextText,
38
+ cause: 'project-update-fastmode-normalize',
39
+ backupTag: 'project-update-fastmode-normalize',
40
+ preserveFastUiKeys: true
41
+ });
42
+ if (!guardResult.ok) {
43
+ return {
44
+ ok: false,
45
+ status: 'failed',
46
+ actions: ['normalize_fastmode_config_blocked'],
47
+ blockers: [`codex_config_guard:${guardResult.status}`],
48
+ warnings: [],
49
+ detail: {
50
+ config_path: configPath,
51
+ default_profile: normalized.defaultProfile,
52
+ retired_config_detected_count: retired.detected_count,
53
+ guard: guardResult
54
+ }
55
+ };
56
+ }
57
+ return {
58
+ ok: true,
59
+ status: 'ok',
60
+ actions: actions.length ? actions : ['fastmode_config_current'],
61
+ blockers: [],
62
+ warnings: [],
63
+ detail: {
64
+ config_path: configPath,
65
+ default_profile: normalized.defaultProfile,
66
+ retired_config_detected_count: retired.detected_count,
67
+ guard: guardResult
68
+ }
69
+ };
70
+ }
71
+ function insertTopLevelTomlKey(text, line) {
72
+ const raw = String(text || '').trimEnd();
73
+ const firstTable = raw.search(/^\s*\[/m);
74
+ if (firstTable < 0)
75
+ return `${line}\n${raw}`.trim() + '\n';
76
+ return `${raw.slice(0, firstTable).trimEnd()}\n${line}\n\n${raw.slice(firstTable).trimStart()}`.trim() + '\n';
77
+ }
78
+ function normalizeLegacyFastModeConfigForUpdate(text) {
79
+ let next = String(text || '');
80
+ const actions = [];
81
+ const misplaced = tomlTableString(next, 'user.fast_mode', 'default_profile');
82
+ const topLevel = topLevelTomlString(next, 'default_profile');
83
+ const legacyFastDefault = misplaced === 'sks-fast-high' || topLevel === 'sks-fast-high';
84
+ const before = next;
85
+ next = removeTopLevelTomlKeyLocal(next, 'default_profile');
86
+ next = removeTomlTableLocal(next, 'user.fast_mode');
87
+ next = removeTomlTableLocal(next, 'profiles.sks-fast-high');
88
+ next = removeTomlTableKeyLocal(next, 'notice', 'fast_default_opt_out');
89
+ if (next !== before)
90
+ actions.push('stripped_removed_fastmode_config_schema_keys');
91
+ if (legacyFastDefault && !topLevelTomlString(next, 'service_tier')) {
92
+ next = insertTopLevelTomlKey(next, 'service_tier = "fast"');
93
+ actions.push('migrated_legacy_fast_default_to_service_tier');
94
+ }
95
+ return { text: ensureTrailingNewline(next), actions, defaultProfile: misplaced || topLevel };
96
+ }
97
+ function removeTopLevelTomlKeyLocal(text, key) {
98
+ const lines = String(text || '').split('\n');
99
+ const firstTable = lines.findIndex((line) => /^\s*\[.+\]\s*$/.test(line));
100
+ const end = firstTable === -1 ? lines.length : firstTable;
101
+ const keyPattern = new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`);
102
+ return lines
103
+ .filter((line, index) => index >= end || !keyPattern.test(line))
104
+ .join('\n')
105
+ .replace(/^\n+/, '')
106
+ .replace(/\n{3,}/g, '\n\n');
107
+ }
108
+ function removeTomlTableLocal(text, table) {
109
+ const lines = String(text || '').trimEnd().split('\n');
110
+ const header = `[${table}]`;
111
+ const start = lines.findIndex((line) => line.trim() === header);
112
+ if (start === -1)
113
+ return String(text || '');
114
+ let end = lines.length;
115
+ for (let index = start + 1; index < lines.length; index += 1) {
116
+ const line = lines[index];
117
+ if (line !== undefined && /^\s*\[.+\]\s*$/.test(line)) {
118
+ end = index;
119
+ break;
120
+ }
121
+ }
122
+ return lines
123
+ .filter((_, index) => index < start || index >= end)
124
+ .join('\n')
125
+ .replace(/^\n+/, '')
126
+ .replace(/\n{3,}/g, '\n\n');
127
+ }
128
+ function tomlTableString(text, table, key) {
129
+ const block = tomlTableBlock(text, table);
130
+ const match = block?.match(new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]*)"\\s*$`, 'm'));
131
+ return match?.[1] || null;
132
+ }
133
+ function topLevelTomlString(text, key) {
134
+ const source = String(text || '');
135
+ const firstTable = source.search(/^\s*\[/m);
136
+ const top = firstTable < 0 ? source : source.slice(0, firstTable);
137
+ const match = top.match(new RegExp(`^\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]*)"\\s*$`, 'm'));
138
+ return match?.[1] || null;
139
+ }
140
+ function tomlTableBlock(text, table) {
141
+ const lines = String(text || '').split(/\r?\n/);
142
+ const start = lines.findIndex((line) => tableHeaderMatches(line, table));
143
+ if (start < 0)
144
+ return null;
145
+ let end = lines.length;
146
+ for (let index = start + 1; index < lines.length; index += 1) {
147
+ if (/^\s*\[[^\]]+\]\s*$/.test(lines[index] || '')) {
148
+ end = index;
149
+ break;
150
+ }
151
+ }
152
+ return lines.slice(start, end).join('\n');
153
+ }
154
+ function removeTomlTableKeyLocal(text, table, key) {
155
+ const lines = String(text || '').split(/\r?\n/);
156
+ let inTable = false;
157
+ const out = [];
158
+ for (const line of lines) {
159
+ if (/^\s*\[[^\]]+\]\s*$/.test(line))
160
+ inTable = tableHeaderMatches(line, table);
161
+ if (inTable && new RegExp(`^\\s*${escapeRegExp(key)}\\s*=`).test(line))
162
+ continue;
163
+ out.push(line);
164
+ }
165
+ return out.join('\n');
166
+ }
167
+ function tableHeaderMatches(line, table) {
168
+ return new RegExp(`^\\s*\\[${escapeRegExp(table)}\\]\\s*$`).test(line || '');
169
+ }
170
+ function ensureTrailingNewline(text) {
171
+ return `${String(text || '').trim()}\n`;
172
+ }
@@ -0,0 +1,247 @@
1
+ import { constants as fsConstants } from 'node:fs';
2
+ import fsp from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { nowIso, sha256, writeJsonAtomic } from '../../fsx.js';
5
+ import { ensureConfinedDirectory, inspectConfinedPath, moveConfinedPath, uniqueConfinedPath } from '../../managed-path-safety.js';
6
+ import { sessionStateKey } from '../../mission.js';
7
+ const LEGACY_CURRENT_STATE_MAX_BYTES = 1024 * 1024;
8
+ export async function runSessionStateSplitStage(root) {
9
+ const legacyCurrent = path.join(root, '.sneakoscope', 'current.json');
10
+ const stateCurrent = path.join(root, '.sneakoscope', 'state', 'current.json');
11
+ const sessionsDir = path.join(root, '.sneakoscope', 'state', 'sessions');
12
+ await ensureConfinedDirectory(root, sessionsDir);
13
+ const actions = [];
14
+ const legacyRead = await readBoundedUpdateState(root, legacyCurrent, { requireLegacyShape: true });
15
+ if (legacyRead.blocker) {
16
+ return {
17
+ ok: false,
18
+ status: 'failed',
19
+ actions: ['legacy_current_state_preserved'],
20
+ blockers: [legacyRead.blocker],
21
+ warnings: [],
22
+ detail: { legacy_present: legacyRead.present }
23
+ };
24
+ }
25
+ const currentRead = await readBoundedUpdateState(root, stateCurrent);
26
+ if (currentRead.blocker) {
27
+ return {
28
+ ok: false,
29
+ status: 'failed',
30
+ actions: ['current_state_preserved'],
31
+ blockers: [currentRead.blocker],
32
+ warnings: [],
33
+ detail: { legacy_present: legacyRead.present }
34
+ };
35
+ }
36
+ let current = currentRead.value;
37
+ if (!current && legacyRead.value) {
38
+ current = legacyRead.value;
39
+ await writeJsonAtomic(stateCurrent, current);
40
+ const verified = await readBoundedUpdateState(root, stateCurrent);
41
+ if (verified.blocker || !verified.value || updateStateDigest(verified.value) !== legacyRead.sha256) {
42
+ return {
43
+ ok: false,
44
+ status: 'failed',
45
+ actions: ['copy_legacy_current_json_failed_verification'],
46
+ blockers: [verified.blocker || 'legacy_current_state_copy_verification_failed'],
47
+ warnings: [],
48
+ detail: { legacy_present: true }
49
+ };
50
+ }
51
+ actions.push('copied_legacy_current_json_to_state_current');
52
+ }
53
+ const missionId = typeof current?.mission_id === 'string'
54
+ ? current.mission_id
55
+ : typeof current?.mission === 'string'
56
+ ? current.mission
57
+ : null;
58
+ const rawSessionKey = typeof current?._session_key === 'string' && current._session_key.trim()
59
+ ? current._session_key
60
+ : null;
61
+ const canonicalSessionKey = rawSessionKey ? sessionStateKey(rawSessionKey) : null;
62
+ if (canonicalSessionKey) {
63
+ const sessionPath = path.join(sessionsDir, `${canonicalSessionKey}.json`);
64
+ const sessionRead = await readBoundedUpdateState(root, sessionPath);
65
+ if (sessionRead.blocker) {
66
+ return {
67
+ ok: false,
68
+ status: 'failed',
69
+ actions,
70
+ blockers: [sessionRead.blocker],
71
+ warnings: [],
72
+ detail: { legacy_present: legacyRead.present, mission_id: missionId, session_key: canonicalSessionKey }
73
+ };
74
+ }
75
+ if (!sessionRead.present) {
76
+ await writeJsonAtomic(sessionPath, {
77
+ ...current,
78
+ _session_key: canonicalSessionKey,
79
+ migrated_from: path.relative(root, stateCurrent),
80
+ migrated_at: nowIso()
81
+ });
82
+ const verifiedSession = await readBoundedUpdateState(root, sessionPath);
83
+ if (verifiedSession.blocker
84
+ || verifiedSession.value?._session_key !== canonicalSessionKey
85
+ || verifiedSession.value?.mission_id !== current?.mission_id) {
86
+ return {
87
+ ok: false,
88
+ status: 'failed',
89
+ actions,
90
+ blockers: [verifiedSession.blocker || 'legacy_session_state_copy_verification_failed'],
91
+ warnings: [],
92
+ detail: { legacy_present: legacyRead.present, mission_id: missionId, session_key: canonicalSessionKey }
93
+ };
94
+ }
95
+ actions.push('wrote_state_session_alias');
96
+ }
97
+ }
98
+ let quarantinePath = null;
99
+ if (legacyRead.present) {
100
+ const quarantineBase = path.join(root, '.sneakoscope', 'quarantine', 'update-legacy-state', `current-${legacyRead.sha256?.slice(0, 12) || 'unknown'}.json`);
101
+ quarantinePath = await uniqueConfinedPath(root, quarantineBase);
102
+ await moveConfinedPath(root, legacyCurrent, quarantinePath);
103
+ const quarantined = await readBoundedUpdateState(root, quarantinePath, { requireLegacyShape: true });
104
+ const legacyAfter = await inspectConfinedPath(root, legacyCurrent);
105
+ if (legacyAfter.exists
106
+ || quarantined.blocker
107
+ || !quarantined.value
108
+ || quarantined.sha256 !== legacyRead.sha256) {
109
+ const sourceAfter = await inspectConfinedPath(root, legacyCurrent);
110
+ if (!sourceAfter.exists && (await inspectConfinedPath(root, quarantinePath)).exists) {
111
+ await moveConfinedPath(root, quarantinePath, legacyCurrent).catch(() => undefined);
112
+ }
113
+ return {
114
+ ok: false,
115
+ status: 'failed',
116
+ actions: [...actions, 'legacy_current_state_quarantine_rolled_back'],
117
+ blockers: [quarantined.blocker || 'legacy_current_state_quarantine_verification_failed'],
118
+ warnings: [],
119
+ detail: { legacy_present: true, mission_id: missionId }
120
+ };
121
+ }
122
+ actions.push('quarantined_legacy_current_json');
123
+ }
124
+ if (!actions.length)
125
+ actions.push('session_state_current');
126
+ return {
127
+ ok: true,
128
+ status: 'ok',
129
+ actions,
130
+ blockers: [],
131
+ warnings: [],
132
+ detail: {
133
+ legacy_present: legacyRead.present,
134
+ mission_id: missionId,
135
+ session_key: canonicalSessionKey,
136
+ legacy_quarantine_path: quarantinePath ? path.relative(root, quarantinePath) : null
137
+ }
138
+ };
139
+ }
140
+ async function readBoundedUpdateState(root, file, opts = {}) {
141
+ let inspected;
142
+ try {
143
+ inspected = await inspectConfinedPath(root, file);
144
+ }
145
+ catch (error) {
146
+ return {
147
+ present: true,
148
+ value: null,
149
+ sha256: null,
150
+ blocker: `update_state_path_unsafe:${error?.code || error?.message || String(error)}`
151
+ };
152
+ }
153
+ if (!inspected.exists)
154
+ return { present: false, value: null, sha256: null, blocker: null };
155
+ if (inspected.leafSymlink) {
156
+ return { present: true, value: null, sha256: null, blocker: 'update_state_symlink_refused' };
157
+ }
158
+ if (!inspected.stat?.isFile()) {
159
+ return { present: true, value: null, sha256: null, blocker: 'update_state_non_regular_refused' };
160
+ }
161
+ if (inspected.stat.size > LEGACY_CURRENT_STATE_MAX_BYTES) {
162
+ return { present: true, value: null, sha256: null, blocker: 'update_state_size_limit_exceeded' };
163
+ }
164
+ let text;
165
+ let handle = null;
166
+ try {
167
+ const noFollow = process.platform === 'win32' ? 0 : fsConstants.O_NOFOLLOW;
168
+ handle = await fsp.open(file, fsConstants.O_RDONLY | noFollow);
169
+ const before = await handle.stat();
170
+ if (!before.isFile()) {
171
+ return { present: true, value: null, sha256: null, blocker: 'update_state_non_regular_refused' };
172
+ }
173
+ if (before.size > LEGACY_CURRENT_STATE_MAX_BYTES) {
174
+ return { present: true, value: null, sha256: null, blocker: 'update_state_size_limit_exceeded' };
175
+ }
176
+ const buffer = Buffer.alloc(LEGACY_CURRENT_STATE_MAX_BYTES + 1);
177
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
178
+ const after = await handle.stat();
179
+ const pathAfter = await inspectConfinedPath(root, file);
180
+ if (bytesRead > LEGACY_CURRENT_STATE_MAX_BYTES
181
+ || bytesRead !== before.size
182
+ || after.size !== before.size
183
+ || after.dev !== before.dev
184
+ || after.ino !== before.ino
185
+ || !pathAfter.exists
186
+ || pathAfter.leafSymlink
187
+ || pathAfter.stat?.dev !== after.dev
188
+ || pathAfter.stat?.ino !== after.ino) {
189
+ return { present: true, value: null, sha256: null, blocker: 'update_state_changed_during_safe_read' };
190
+ }
191
+ text = buffer.subarray(0, bytesRead).toString('utf8');
192
+ }
193
+ catch (error) {
194
+ return {
195
+ present: true,
196
+ value: null,
197
+ sha256: null,
198
+ blocker: `update_state_read_failed:${error?.code || error?.message || String(error)}`
199
+ };
200
+ }
201
+ finally {
202
+ await handle?.close().catch(() => undefined);
203
+ }
204
+ let value;
205
+ try {
206
+ value = JSON.parse(text);
207
+ }
208
+ catch {
209
+ return { present: true, value: null, sha256: null, blocker: 'update_state_json_invalid' };
210
+ }
211
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
212
+ return { present: true, value: null, sha256: null, blocker: 'update_state_object_required' };
213
+ }
214
+ const record = value;
215
+ if (opts.requireLegacyShape && !isRecognizedLegacyCurrentState(record)) {
216
+ return { present: true, value: null, sha256: null, blocker: 'legacy_current_state_ownership_unproven' };
217
+ }
218
+ return {
219
+ present: true,
220
+ value: record,
221
+ sha256: updateStateDigest(record),
222
+ blocker: null
223
+ };
224
+ }
225
+ function isRecognizedLegacyCurrentState(value) {
226
+ const mission = typeof value.mission_id === 'string'
227
+ ? value.mission_id.trim()
228
+ : typeof value.mission === 'string'
229
+ ? value.mission.trim()
230
+ : '';
231
+ if (/^M-[A-Za-z0-9][A-Za-z0-9._-]{2,127}$/.test(mission))
232
+ return true;
233
+ const mode = typeof value.mode === 'string' ? value.mode.trim() : '';
234
+ const phase = typeof value.phase === 'string' ? value.phase.trim() : '';
235
+ if (mode === 'IDLE' && phase === 'IDLE')
236
+ return true;
237
+ if (!mode || !phase)
238
+ return false;
239
+ const sessionKey = typeof value._session_key === 'string' && value._session_key.trim().length > 0;
240
+ const routeCommand = typeof value.route_command === 'string' && value.route_command.trim().startsWith('$');
241
+ const managedStopGate = ['stop_gate', 'stop_gate_abs_path']
242
+ .some((key) => typeof value[key] === 'string' && String(value[key]).trim().length > 0);
243
+ return sessionKey || routeCommand || managedStopGate;
244
+ }
245
+ function updateStateDigest(value) {
246
+ return sha256(JSON.stringify(value));
247
+ }
@@ -0,0 +1,46 @@
1
+ import { codexHookTrustDoctor } from '../../codex-hooks/codex-hook-trust-doctor.js';
2
+ export async function runOtherHarnessCleanupStage(root) {
3
+ const { scanHarnessConflicts } = await import('../../harness-conflicts.js');
4
+ const scan = await scanHarnessConflicts(root);
5
+ if (scan.hard_block) {
6
+ return {
7
+ ok: false,
8
+ status: 'failed',
9
+ actions: ['other_harness_conflict_detected'],
10
+ blockers: scan.hard.map((row) => `other_harness_conflict:${row.path}`),
11
+ warnings: [],
12
+ detail: {
13
+ cleaned_count: 0,
14
+ remaining_count: scan.hard.length,
15
+ error_count: 0,
16
+ cleanup_prompt_command: 'sks conflicts cleanup --yes'
17
+ }
18
+ };
19
+ }
20
+ return {
21
+ ok: true,
22
+ status: 'ok',
23
+ actions: ['other_harness_conflict_check_clean'],
24
+ blockers: [],
25
+ warnings: [],
26
+ detail: {
27
+ cleaned_count: 0,
28
+ remaining_count: 0,
29
+ error_count: 0
30
+ }
31
+ };
32
+ }
33
+ export async function runHookTrustRefreshStage(root) {
34
+ const result = await codexHookTrustDoctor(root, { fix: true, managed: true, actual: true });
35
+ const blockers = result.ok === false
36
+ ? (result.blockers || ['hook_trust_refresh_failed'])
37
+ : [];
38
+ return {
39
+ ok: blockers.length === 0,
40
+ status: blockers.length ? 'failed' : 'ok',
41
+ actions: ['refreshed_hook_trust'],
42
+ blockers,
43
+ warnings: result.warnings || [],
44
+ detail: { entries: result.current_hash_count ?? null }
45
+ };
46
+ }