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,11 +1,13 @@
1
+ import fsp from 'node:fs/promises';
1
2
  import path from 'node:path';
2
- import { codexSchemaPath, runCodexExecResumeWithOutputSchema, structuredOutputBlocker } from '../codex-exec-output-schema.js';
3
+ import { codexSchemaPath, runCodexExecResumeWithOutputSchema, structuredOutputBlocker, validateStructuredOutput } from '../codex-exec-output-schema.js';
3
4
  import { readJson } from '../fsx.js';
4
- import { runOpenAIStructuredOutput } from '../structured-output-adapter.js';
5
- import { resolveCodexLbImagegenTarget } from '../imagegen/codex-lb-imagegen-target.js';
5
+ import { strictJsonSchemaFormat } from '../structured-output-adapter.js';
6
+ import { parseResponsesSsePayload } from '../responses-stream.js';
7
+ import { DESKTOP_BRIDGE_IMAGEGEN_RECOVERY_GUIDANCE, resolveDesktopBridgeImagegenTarget } from '../imagegen/desktop-bridge-imagegen-target.js';
6
8
  import { generatedImageMetadata } from './imagegen-adapter.js';
7
9
  import { buildIssueLedgerFromGeneratedCallouts } from './callout-extraction.js';
8
- export async function extractRealCallouts(input) {
10
+ export async function extractRealCallouts(input, opts = {}) {
9
11
  const schemaPath = await codexSchemaPath('image-ux-issue-ledger');
10
12
  const jsonSchema = await readJson(schemaPath);
11
13
  const prompt = input.prompt || buildRealCalloutExtractionPrompt(input);
@@ -74,16 +76,39 @@ export async function extractRealCallouts(input) {
74
76
  });
75
77
  }
76
78
  else {
77
- const codexLb = await resolveCodexLbImagegenTarget({}).catch(() => null);
78
- const useCodexLb = codexLb?.selected === true && codexLb.blocker === null;
79
- providerResult = await runOpenAIStructuredOutput({
80
- prompt,
81
- schemaName: 'image_ux_issue_ledger',
82
- jsonSchema,
83
- imagePath: path.resolve(input.root, input.generatedImagePath),
84
- ...(useCodexLb ? { baseUrl: codexLb.base_url, apiKey: codexLb.api_key } : {}),
85
- ...(useCodexLb && codexLb.model ? { model: codexLb.model } : {})
86
- });
79
+ const suppliedTarget = opts.desktopBridgeTarget
80
+ ? fixtureDesktopBridgeTarget(opts.desktopBridgeTarget)
81
+ : null;
82
+ const target = suppliedTarget || await resolveDesktopBridgeImagegenTarget({
83
+ explicitModel: input.model
84
+ || process.env.OPENAI_STRUCTURED_OUTPUT_MODEL
85
+ || process.env.SKS_IMAGEGEN_RESPONSES_MODEL
86
+ || null,
87
+ ...(opts.home ? { home: opts.home } : {}),
88
+ ...(opts.env ? { env: opts.env } : {}),
89
+ ...(opts.desktopBridgeStatus !== undefined ? { desktopBridgeStatus: opts.desktopBridgeStatus } : {}),
90
+ ...(opts.desktopBridgeStatusImpl ? { desktopBridgeStatusImpl: opts.desktopBridgeStatusImpl } : {})
91
+ }).catch(() => null);
92
+ if (!target || target.blocker || !target.endpoint || !target.model) {
93
+ const reason = target?.blocker || 'desktop_bridge_status_unavailable';
94
+ providerResult = {
95
+ ok: false,
96
+ status: 'blocked',
97
+ provider: 'desktop_bridge_structured_extractor',
98
+ parsed_json: null,
99
+ blocker: structuredOutputBlocker(reason, DESKTOP_BRIDGE_IMAGEGEN_RECOVERY_GUIDANCE),
100
+ fixture: target?.status_source === 'injected_fixture'
101
+ };
102
+ }
103
+ else {
104
+ providerResult = await runDesktopBridgeStructuredOutput({
105
+ target,
106
+ prompt,
107
+ schemaName: 'image_ux_issue_ledger',
108
+ jsonSchema,
109
+ imagePath: path.resolve(input.root, input.generatedImagePath)
110
+ });
111
+ }
87
112
  }
88
113
  if (!providerResult.ok || !providerResult.parsed_json) {
89
114
  return {
@@ -95,6 +120,7 @@ export async function extractRealCallouts(input) {
95
120
  generated_image_sha256: generated.sha256,
96
121
  parsed_json_present: false,
97
122
  validation_status: 'blocked',
123
+ ...(providerResult.fixture ? { fake_adapter: true, source: 'mock_fixture' } : {}),
98
124
  issue_ledger: buildIssueLedgerFromGeneratedCallouts({
99
125
  schema: 'sks.image-ux-generated-review-ledger.v2',
100
126
  generated_review_images: [{ ...generated, callout_extraction_status: 'pending', callouts: [] }],
@@ -123,9 +149,113 @@ export async function extractRealCallouts(input) {
123
149
  parsed_json_present: true,
124
150
  validation_status: ledger.validation.ok ? 'valid' : 'blocked',
125
151
  issue_ledger: ledger,
152
+ ...(providerResult.fixture ? { fake_adapter: true, source: 'mock_fixture' } : {}),
126
153
  blocker: ledger.issues.length ? null : structuredOutputBlocker('callout_extraction_schema_failed', 'Generated image did not yield visible callouts.')
127
154
  };
128
155
  }
156
+ async function runDesktopBridgeStructuredOutput(request) {
157
+ const imageBytes = await fsp.readFile(request.imagePath);
158
+ const imageUrl = `data:${mimeForPath(request.imagePath)};base64,${imageBytes.toString('base64')}`;
159
+ const fixture = request.target.status_source === 'injected_fixture'
160
+ || request.target.live_evidence_allowed !== true;
161
+ try {
162
+ const response = await fetch(request.target.endpoint, {
163
+ method: 'POST',
164
+ headers: {
165
+ 'content-type': 'application/json',
166
+ 'x-sks-model': request.target.model
167
+ },
168
+ body: JSON.stringify({
169
+ model: request.target.model,
170
+ input: [{
171
+ role: 'user',
172
+ content: [
173
+ { type: 'input_text', text: request.prompt },
174
+ { type: 'input_image', image_url: imageUrl }
175
+ ]
176
+ }],
177
+ text: { format: strictJsonSchemaFormat(request.schemaName, request.jsonSchema) }
178
+ })
179
+ });
180
+ const text = await response.text();
181
+ if (!response.ok) {
182
+ return bridgeStructuredOutputBlocked('desktop_bridge_structured_output_api_error', text, fixture);
183
+ }
184
+ const payload = parseStructuredOutputBody(text);
185
+ if (!payload)
186
+ return bridgeStructuredOutputBlocked('json_parse_failed', 'Desktop Bridge returned no readable JSON.', fixture);
187
+ const parsed = payload.output_parsed || parseStructuredOutputPayload(payload);
188
+ if (!parsed || typeof parsed !== 'object') {
189
+ return bridgeStructuredOutputBlocked('json_parse_failed', 'Desktop Bridge output did not contain parsed JSON.', fixture);
190
+ }
191
+ const validation = validateStructuredOutput(parsed, strictJsonSchemaFormat(request.schemaName, request.jsonSchema).schema);
192
+ return {
193
+ ok: validation.ok,
194
+ status: validation.ok ? 'parsed' : 'blocked',
195
+ provider: fixture ? 'desktop_bridge_fixture_structured_extractor' : 'desktop_bridge_structured_extractor',
196
+ parsed_json: validation.ok ? parsed : null,
197
+ blocker: validation.ok
198
+ ? null
199
+ : structuredOutputBlocker('schema_validation_failed', validation.issues.join(', ')),
200
+ fixture
201
+ };
202
+ }
203
+ catch (error) {
204
+ return bridgeStructuredOutputBlocked('desktop_bridge_structured_output_api_error', error instanceof Error ? error.message : String(error), fixture);
205
+ }
206
+ }
207
+ function bridgeStructuredOutputBlocked(reason, detail, fixture) {
208
+ return {
209
+ ok: false,
210
+ status: 'blocked',
211
+ provider: fixture ? 'desktop_bridge_fixture_structured_extractor' : 'desktop_bridge_structured_extractor',
212
+ parsed_json: null,
213
+ blocker: structuredOutputBlocker(reason, String(detail || '').slice(0, 2000)),
214
+ fixture
215
+ };
216
+ }
217
+ function fixtureDesktopBridgeTarget(value) {
218
+ if (!value || typeof value !== 'object')
219
+ return null;
220
+ return {
221
+ ...value,
222
+ status_source: 'injected_fixture',
223
+ live_evidence_allowed: false
224
+ };
225
+ }
226
+ function parseStructuredOutputBody(text) {
227
+ try {
228
+ return JSON.parse(text);
229
+ }
230
+ catch {
231
+ return parseResponsesSsePayload(text);
232
+ }
233
+ }
234
+ function parseStructuredOutputPayload(payload) {
235
+ const output = Array.isArray(payload.output) ? payload.output : [];
236
+ for (const item of output) {
237
+ const content = Array.isArray(item?.content) ? item.content : [];
238
+ for (const part of content) {
239
+ if (part?.parsed)
240
+ return part.parsed;
241
+ if (typeof part?.text === 'string') {
242
+ try {
243
+ return JSON.parse(part.text);
244
+ }
245
+ catch { }
246
+ }
247
+ }
248
+ }
249
+ return null;
250
+ }
251
+ function mimeForPath(file) {
252
+ const ext = path.extname(file).toLowerCase();
253
+ if (ext === '.jpg' || ext === '.jpeg')
254
+ return 'image/jpeg';
255
+ if (ext === '.webp')
256
+ return 'image/webp';
257
+ return 'image/png';
258
+ }
129
259
  export function buildRealCalloutExtractionPrompt(input) {
130
260
  const source = input.sourceScreenshot || {};
131
261
  return [
@@ -0,0 +1,77 @@
1
+ import { desktopBridgeStatusV3 } from '../codex-lb/desktop-controller-v3.js';
2
+ export const DESKTOP_BRIDGE_IMAGEGEN_TARGET_SCHEMA = 'sks.desktop-bridge-imagegen-target.v1';
3
+ export const DESKTOP_BRIDGE_IMAGEGEN_RECOVERY_GUIDANCE = 'Run `sks bridge status --json` to inspect Desktop Bridge provider readiness.';
4
+ export async function resolveDesktopBridgeImagegenTarget(opts = {}) {
5
+ const env = opts.env || process.env;
6
+ const injected = opts.desktopBridgeStatus !== undefined || opts.desktopBridgeStatusImpl !== undefined;
7
+ const status = opts.desktopBridgeStatus !== undefined
8
+ ? opts.desktopBridgeStatus
9
+ : await (opts.desktopBridgeStatusImpl || desktopBridgeStatusV3)({
10
+ ...(opts.home ? { home: opts.home } : {}),
11
+ env
12
+ }).catch(() => null);
13
+ const model = String(opts.explicitModel ?? env.SKS_IMAGEGEN_RESPONSES_MODEL ?? '').trim() || null;
14
+ const selected = status?.schema === 'sks.desktop-bridge-status.v3'
15
+ && status.management?.managed === true;
16
+ const loopbackOrigin = verifiedLoopbackOrigin(status);
17
+ const policy = status?.routing?.policy || null;
18
+ const route = model && policy?.fallback === 'none'
19
+ ? policy.model_routes?.[model] || null
20
+ : null;
21
+ const provider = route ? status?.providers?.[route.provider_id] || null : null;
22
+ const providerImagegen = provider?.capabilities?.capabilities?.image_generation || null;
23
+ const blocker = !status || status.schema !== 'sks.desktop-bridge-status.v3'
24
+ ? 'desktop_bridge_status_unavailable'
25
+ : !selected
26
+ ? 'desktop_bridge_not_managed'
27
+ : status.service?.running !== true || status.service?.state !== 'ready'
28
+ ? 'desktop_bridge_not_running'
29
+ : !loopbackOrigin
30
+ ? 'desktop_bridge_loopback_unverified'
31
+ : status.readiness?.bridge_ready !== true || status.catalog_sync?.state !== 'verified'
32
+ ? status.readiness?.blockers?.[0] || status.catalog_sync?.blockers?.[0] || 'desktop_bridge_state_unverified'
33
+ : !model
34
+ ? 'desktop_bridge_imagegen_model_missing'
35
+ : !policy || policy.fallback !== 'none' || !route
36
+ ? 'catalog_model_route_missing'
37
+ : provider?.enabled !== true
38
+ ? 'bridge_route_provider_disabled'
39
+ : provider.credential?.state !== 'ready'
40
+ ? provider.credential?.blockers?.[0] || 'bridge_route_provider_credential_unverified'
41
+ : providerImagegen?.state !== 'verified'
42
+ ? providerImagegen?.blockers?.[0] || 'bridge_route_imagegen_capability_unverified'
43
+ : null;
44
+ const bridgeVerified = blocker === null;
45
+ return {
46
+ schema: DESKTOP_BRIDGE_IMAGEGEN_TARGET_SCHEMA,
47
+ selected,
48
+ bridge_verified: bridgeVerified,
49
+ endpoint: bridgeVerified ? `${loopbackOrigin}/backend-api/codex/responses` : null,
50
+ model,
51
+ model_source: model ? 'explicit' : null,
52
+ route: route || null,
53
+ provider_id: route?.provider_id || null,
54
+ status_source: injected ? 'injected_fixture' : 'runtime',
55
+ live_evidence_allowed: bridgeVerified && !injected,
56
+ blocker,
57
+ setup_guidance: DESKTOP_BRIDGE_IMAGEGEN_RECOVERY_GUIDANCE
58
+ };
59
+ }
60
+ function verifiedLoopbackOrigin(status) {
61
+ const raw = String(status?.service?.loopback_origin || '').trim();
62
+ if (!raw)
63
+ return null;
64
+ try {
65
+ const parsed = new URL(raw);
66
+ const loopback = parsed.hostname === '127.0.0.1'
67
+ || parsed.hostname === '::1'
68
+ || parsed.hostname === '[::1]';
69
+ if (parsed.protocol !== 'http:' || !loopback || parsed.username || parsed.password
70
+ || parsed.pathname !== '/' || parsed.search || parsed.hash || !parsed.port)
71
+ return null;
72
+ return parsed.origin;
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
@@ -1,11 +1,7 @@
1
- import os from 'node:os';
2
- import path from 'node:path';
3
- import { loadCodexLbEnv, parseShellEnvValue } from '../codex-lb/codex-lb-env.js';
4
- import { CODEX_LB_ROUTING_TRUTH_RECEIPT, codexLbRoutingTruthIsActive, readCodexLbRoutingTruthReceipt } from '../codex-lb/routing-truth.js';
5
- import { nowIso, readText, runProcess, which } from '../fsx.js';
1
+ import { desktopBridgeStatusV3 } from '../codex-lb/desktop-controller-v3.js';
2
+ import { nowIso, runProcess, which } from '../fsx.js';
6
3
  import { redactSecrets, redactString } from '../secret-redaction.js';
7
4
  import { evaluateImagegenAuthReadiness } from './imagegen-auth-readiness.js';
8
- import { escapeRegExp } from '../text/regex.js';
9
5
  export async function detectImagegenCapability(opts = {}) {
10
6
  const codexBin = opts.codexBin || await which('codex').catch(() => null);
11
7
  const codexApp = await detectCodexAppImagegen(codexBin, opts);
@@ -97,130 +93,57 @@ export async function detectImagegenCapability(opts = {}) {
97
93
  };
98
94
  }
99
95
  async function detectCodexLbImagegenAuth(opts = {}, env = process.env) {
100
- const home = opts.home || env.HOME || process.env.HOME || os.homedir();
101
- const codexHome = opts.codexHome || env.CODEX_HOME || path.join(home, '.codex');
102
- const configPath = opts.configPath || path.join(codexHome, 'config.toml');
103
- const configText = typeof opts.configText === 'string'
104
- ? opts.configText
105
- : await readText(configPath, '').catch(() => '');
106
- const block = tomlTableBlock(configText, 'model_providers.codex-lb');
107
- const selected = opts.codexLbSelected === true || topLevelTomlString(configText, 'model_provider') === 'codex-lb';
108
- const providerConfigured = Boolean(block);
109
- const requiresOpenAiAuth = tomlBoolean(block, 'requires_openai_auth');
110
- const bearerEnvKey = tomlString(block, 'env_key');
111
- const gatewayHeaderEnvKey = tomlEnvHttpHeader(block, 'X-Codex-LB-API-Key');
112
- const envKey = bearerEnvKey || gatewayHeaderEnvKey;
113
- const cliContract = providerConfigured
114
- && tomlString(block, 'name') === 'codex-lb'
115
- && tomlString(block, 'wire_api') === 'responses'
116
- && bearerEnvKey === 'CODEX_LB_API_KEY'
117
- && !gatewayHeaderEnvKey
118
- && requiresOpenAiAuth === false;
119
- const baseUrl = tomlString(block, 'base_url') || String(env.CODEX_LB_BASE_URL || '').trim();
120
- const envPath = opts.codexLbEnvPath || path.join(codexHome, 'sks-codex-lb.env');
121
- const injectedEnvText = typeof opts.codexLbEnvText === 'string' ? opts.codexLbEnvText : null;
122
- const keyFromInjectedFile = injectedEnvText !== null && envKey ? parseShellEnvValue(injectedEnvText, envKey) : '';
123
- const loaded = injectedEnvText === null
124
- ? await loadCodexLbEnv({
125
- home,
126
- processEnv: {},
127
- envPath,
128
- metadataPath: path.join(codexHome, 'sks-codex-lb.json')
129
- }).catch(() => null)
130
- : null;
131
- const apiKeyPresent = Boolean(opts.codexLbApiKey || keyFromInjectedFile || loaded?.secret_api_key);
132
- const apiKeySource = opts.codexLbApiKey
133
- ? 'option'
134
- : keyFromInjectedFile ? 'env-file' : loaded?.api_key?.source || null;
135
- const blocker = codexLbAuthBlocker({
136
- selected,
137
- providerConfigured,
138
- requiresOpenAiAuth,
139
- envKey,
140
- baseUrl,
141
- apiKeyPresent,
142
- cliContract
143
- });
144
- const routingTruthReceiptPath = opts.codexLbRoutingTruthReceiptPath
145
- || path.join(codexHome, CODEX_LB_ROUTING_TRUTH_RECEIPT);
146
- const routingTruth = await readCodexLbRoutingTruthReceipt({
147
- receiptPath: routingTruthReceiptPath
96
+ const status = opts.desktopBridgeStatus || await (opts.desktopBridgeStatusImpl || desktopBridgeStatusV3)({
97
+ home: opts.home || env.HOME,
98
+ env
148
99
  }).catch(() => null);
149
- const configuredHost = publicUrlHost(baseUrl);
150
- const routingAuthTransport = 'authorization-bearer';
151
- const routingTruthContextMatches = routingTruth?.selected === selected
152
- && routingTruth?.configured_host === configuredHost
153
- && routingTruth?.auth_transport === routingAuthTransport;
154
- const routingActive = routingTruthContextMatches
155
- && codexLbRoutingTruthIsActive(routingTruth);
100
+ const provider = status?.providers?.['codex-lb'] || null;
101
+ const policy = status?.routing?.policy || null;
102
+ const modelRoutes = Object.values(policy?.model_routes || {});
103
+ const selected = provider?.enabled === true && (policy?.default_provider_id === 'codex-lb'
104
+ || modelRoutes.some((route) => route?.provider_id === 'codex-lb'));
105
+ const credentialReady = provider?.credential?.state === 'ready';
106
+ const endpointConfigured = provider?.endpoint?.configured === true;
107
+ const capabilityEvidence = provider?.capabilities?.capabilities?.image_generation || null;
108
+ const capabilityReady = capabilityEvidence?.state === 'verified';
109
+ const blocker = !status
110
+ ? 'desktop_bridge_status_unavailable'
111
+ : !provider
112
+ ? 'codex_lb_provider_status_missing'
113
+ : !provider.enabled
114
+ ? 'codex_lb_provider_disabled'
115
+ : !credentialReady
116
+ ? provider.credential?.blockers?.[0] || `codex_lb_credential_${provider.credential?.state || 'unknown'}`
117
+ : !endpointConfigured
118
+ ? 'codex_lb_endpoint_missing'
119
+ : !selected
120
+ ? 'codex_lb_route_inactive'
121
+ : !capabilityReady
122
+ ? capabilityEvidence?.blockers?.[0] || `codex_lb_imagegen_capability_${capabilityEvidence?.state || 'unverified'}`
123
+ : null;
156
124
  return {
157
125
  available: blocker === null,
158
126
  selected,
159
- cli_contract: cliContract,
160
- provider_configured: providerConfigured,
161
- requires_openai_auth: requiresOpenAiAuth,
162
- openai_auth_disabled: requiresOpenAiAuth === false,
163
- env_key: envKey || null,
164
- base_url: baseUrl || null,
165
- env_path: envPath,
127
+ cli_contract: provider?.endpoint?.auth_transport === 'authorization-bearer',
128
+ provider_configured: Boolean(provider),
129
+ requires_openai_auth: null,
130
+ openai_auth_disabled: null,
131
+ env_key: null,
132
+ base_url: provider?.endpoint?.origin_redacted || null,
133
+ env_path: null,
166
134
  api_key: {
167
- present: apiKeyPresent,
168
- source: apiKeySource,
135
+ present: credentialReady,
136
+ source: provider?.credential?.source || null,
169
137
  redacted: true
170
138
  },
171
- routing_active: routingActive,
172
- routing_truth: routingTruth,
173
- routing_truth_receipt_path: routingTruthReceiptPath,
174
- routing_blocker: codexLbRoutingBlocker({
175
- selected,
176
- configuredHost,
177
- routingTruth,
178
- expectedAuthTransport: routingAuthTransport,
179
- routingTruthContextMatches,
180
- routingActive
181
- }),
139
+ routing_active: selected,
140
+ routing_blocker: selected ? null : 'codex_lb_route_inactive',
141
+ capability_evidence: capabilityEvidence,
142
+ provider_capability_summary: provider?.capabilities || null,
143
+ bridge_status_checked_at: status?.checked_at || null,
182
144
  blocker
183
145
  };
184
146
  }
185
- function codexLbRoutingBlocker(state) {
186
- if (!state.selected)
187
- return 'codex_lb_not_selected';
188
- if (!state.routingTruth)
189
- return 'codex_lb_routing_truth_receipt_missing';
190
- if (state.routingTruth.selected !== true)
191
- return 'codex_lb_routing_truth_selection_mismatch';
192
- if (state.routingTruth.configured_host !== state.configuredHost)
193
- return 'codex_lb_routing_truth_host_mismatch';
194
- if (state.routingTruth.auth_transport !== state.expectedAuthTransport)
195
- return 'codex_lb_routing_truth_auth_transport_mismatch';
196
- if (!state.routingTruthContextMatches || !state.routingActive) {
197
- return state.routingTruth.blockers?.[0] || 'codex_lb_routing_truth_unverified';
198
- }
199
- return null;
200
- }
201
- function codexLbAuthBlocker(state) {
202
- if (!state.selected && !state.cliContract)
203
- return 'codex_lb_not_selected';
204
- if (!state.providerConfigured)
205
- return 'codex_lb_provider_missing';
206
- if (state.cliContract) {
207
- if (!state.apiKeyPresent)
208
- return 'codex_lb_api_key_missing';
209
- if (!state.baseUrl)
210
- return 'codex_lb_base_url_missing';
211
- return null;
212
- }
213
- if (state.requiresOpenAiAuth !== true) {
214
- return state.requiresOpenAiAuth === false ? 'codex_lb_legacy_openai_auth_disabled' : 'codex_lb_requires_openai_auth_missing';
215
- }
216
- if (state.envKey !== 'CODEX_LB_API_KEY')
217
- return 'codex_lb_env_key_missing_or_unsupported';
218
- if (!state.baseUrl)
219
- return 'codex_lb_base_url_missing';
220
- if (!state.apiKeyPresent)
221
- return 'codex_lb_api_key_missing';
222
- return null;
223
- }
224
147
  async function detectCodexAppImagegen(codexBin, opts = {}) {
225
148
  if (opts.codexAppAvailable === true || process.env.SKS_CODEX_APP_IMAGEGEN_AVAILABLE === '1') {
226
149
  return { available: true, detector: 'env_or_option', blocker: null, raw: null };
@@ -323,34 +246,3 @@ function boolish(value) {
323
246
  return false;
324
247
  return null;
325
248
  }
326
- function publicUrlHost(value) {
327
- try {
328
- return typeof value === 'string' && value ? new URL(value).host : null;
329
- }
330
- catch {
331
- return null;
332
- }
333
- }
334
- function topLevelTomlString(text = '', key = '') {
335
- const topLevel = String(text || '').split(/\n\s*\[/)[0] || '';
336
- return tomlString(topLevel, key);
337
- }
338
- function tomlTableBlock(text = '', table = '') {
339
- const re = new RegExp(`(^|\\n)\\[${escapeRegExp(table)}\\]([\\s\\S]*?)(?=\\n\\[[^\\]]+\\]|\\s*$)`);
340
- return String(text || '').match(re)?.[2] || '';
341
- }
342
- function tomlString(text = '', key = '') {
343
- const re = new RegExp(`(^|\\n)\\s*${escapeRegExp(key)}\\s*=\\s*"([^"]*)"\\s*(?:#.*)?(?=\\n|$)`);
344
- return String(text || '').match(re)?.[2] || '';
345
- }
346
- function tomlEnvHttpHeader(text = '', header = '') {
347
- const inline = String(text || '').match(/(?:^|\n)\s*env_http_headers\s*=\s*\{([^}]*)\}/)?.[1] || '';
348
- return inline.match(new RegExp(`"${escapeRegExp(header)}"\\s*=\\s*"([^"]+)"`))?.[1] || '';
349
- }
350
- function tomlBoolean(text = '', key = '') {
351
- const re = new RegExp(`(^|\\n)\\s*${escapeRegExp(key)}\\s*=\\s*(true|false)\\s*(?:#.*)?(?=\\n|$)`, 'i');
352
- const raw = String(text || '').match(re)?.[2];
353
- if (!raw)
354
- return null;
355
- return raw.toLowerCase() === 'true';
356
- }
@@ -0,0 +1,89 @@
1
+ import fsp from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { inspectConfinedPath } from '../../managed-path-safety.js';
4
+ import { sha256 } from '../../fsx.js';
5
+ import { SKS_SKILL_MANIFEST_FILE, canonicalSkillNameFromValue } from './inventory.js';
6
+ export async function listSkillDirs(targetDir, opts = {}) {
7
+ const boundary = rootFromSkillsDir(targetDir);
8
+ let rootInspection;
9
+ try {
10
+ rootInspection = await inspectConfinedPath(boundary, targetDir);
11
+ }
12
+ catch (error) {
13
+ if (nodeErrorCode(error) === 'ENOENT')
14
+ return [];
15
+ throw error;
16
+ }
17
+ if (!rootInspection.exists)
18
+ return [];
19
+ if (rootInspection.leafSymlink || !rootInspection.stat?.isDirectory()) {
20
+ throw new Error(`skill_target_not_safe_directory:${targetDir}`);
21
+ }
22
+ const rows = await fsp.readdir(targetDir, { withFileTypes: true });
23
+ const out = [];
24
+ for (const row of rows) {
25
+ const dir = path.join(targetDir, row.name);
26
+ const directoryCanonical = canonicalSkillNameFromValue(row.name);
27
+ if (row.isSymbolicLink() || !row.isDirectory()) {
28
+ if (opts.includeUnsafeEntries) {
29
+ out.push({
30
+ name: row.name,
31
+ dir,
32
+ skillMdPath: null,
33
+ text: '',
34
+ canonical: directoryCanonical,
35
+ declaredCanonical: '',
36
+ directoryCanonical,
37
+ hash: '',
38
+ unsafeEntry: row.isSymbolicLink() ? 'symlink' : 'non-directory'
39
+ });
40
+ }
41
+ continue;
42
+ }
43
+ const skillMdPath = path.join(dir, 'SKILL.md');
44
+ const inspected = await inspectConfinedPath(boundary, skillMdPath);
45
+ if (!inspected.exists || inspected.leafSymlink || !inspected.stat?.isFile()) {
46
+ if (opts.includeUnsafeEntries) {
47
+ out.push({
48
+ name: row.name,
49
+ dir,
50
+ skillMdPath,
51
+ text: '',
52
+ canonical: directoryCanonical,
53
+ declaredCanonical: '',
54
+ directoryCanonical,
55
+ hash: '',
56
+ unsafeEntry: inspected.leafSymlink ? 'skill-file-symlink' : 'missing-or-non-file-skill'
57
+ });
58
+ }
59
+ continue;
60
+ }
61
+ const text = await fsp.readFile(skillMdPath, 'utf8');
62
+ const displayName = /^name:\s*(.+)\s*$/m.exec(text)?.[1] || row.name;
63
+ const declaredCanonical = canonicalSkillNameFromValue(displayName);
64
+ out.push({
65
+ name: row.name,
66
+ dir,
67
+ skillMdPath,
68
+ text,
69
+ canonical: declaredCanonical,
70
+ declaredCanonical,
71
+ directoryCanonical,
72
+ hash: sha256(text)
73
+ });
74
+ }
75
+ return out;
76
+ }
77
+ export function rootFromSkillsDir(targetDir) {
78
+ const normalized = path.resolve(targetDir);
79
+ if (path.basename(normalized) === 'skills' && path.basename(path.dirname(normalized)) === '.agents') {
80
+ return path.dirname(path.dirname(normalized));
81
+ }
82
+ return path.dirname(path.dirname(normalized));
83
+ }
84
+ export async function pruneProjectGeneratedManifest(targetDir) {
85
+ await fsp.rm(path.join(targetDir, SKS_SKILL_MANIFEST_FILE), { force: true }).catch(() => undefined);
86
+ }
87
+ function nodeErrorCode(error) {
88
+ return String(error?.code || '');
89
+ }