sneakoscope 2.0.5 → 2.0.6

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 (35) hide show
  1. package/README.md +7 -4
  2. package/crates/sks-core/Cargo.lock +1 -1
  3. package/crates/sks-core/Cargo.toml +1 -1
  4. package/crates/sks-core/src/main.rs +1 -1
  5. package/dist/.sks-build-stamp.json +4 -4
  6. package/dist/bin/sks.js +1 -1
  7. package/dist/build-manifest.json +13 -8
  8. package/dist/cli/install-helpers.js +23 -0
  9. package/dist/commands/codex-app.js +25 -3
  10. package/dist/commands/doctor.js +19 -4
  11. package/dist/commands/mad-sks.js +2 -2
  12. package/dist/core/agents/agent-orchestrator.js +22 -3
  13. package/dist/core/agents/agent-proof-evidence.js +24 -2
  14. package/dist/core/agents/agent-worker-pipeline.js +9 -1
  15. package/dist/core/agents/native-worker-backend-router.js +19 -1
  16. package/dist/core/codex-app.js +124 -2
  17. package/dist/core/commands/naruto-command.js +9 -4
  18. package/dist/core/fsx.js +1 -1
  19. package/dist/core/hooks-runtime.js +2 -233
  20. package/dist/core/init.js +8 -8
  21. package/dist/core/naruto/naruto-active-pool.js +20 -4
  22. package/dist/core/pipeline-internals/runtime-core.js +1 -1
  23. package/dist/core/ppt.js +31 -8
  24. package/dist/core/product-design-app-server.js +410 -0
  25. package/dist/core/product-design-plugin.js +139 -0
  26. package/dist/core/routes.js +8 -8
  27. package/dist/core/version.js +1 -1
  28. package/dist/scripts/naruto-active-pool-check.js +13 -1
  29. package/dist/scripts/naruto-readonly-routing-check.js +116 -0
  30. package/dist/scripts/naruto-shadow-clone-swarm-check.js +7 -0
  31. package/dist/scripts/product-design-auto-install-check.js +119 -0
  32. package/dist/scripts/product-design-plugin-routing-check.js +101 -0
  33. package/dist/scripts/release-parallel-check.js +15 -1
  34. package/dist/scripts/release-provenance-check.js +21 -0
  35. package/package.json +5 -2
package/README.md CHANGED
@@ -16,10 +16,13 @@ Set up this agent project with Sneakoscope Codex. Use [[mandarange/Sneakoscope-C
16
16
 
17
17
  ## Current Release
18
18
 
19
- SKS **2.0.5** is a P0 Codex App Fast UI and MAD Zellij worker-pane closure patch on top of the 2.0 execution layer. `sks --mad` now relies on launch-time Fast/high overrides instead of user-level Codex config rewrites, safe Fast UI repair runs through `sks doctor --fix`, provider badges read env/auth/config.toml consistently, and interactive MAD worker panes attach to the real Zellij session as scheduler slots spawn.
19
+ SKS **2.0.6** wires Codex App Product Design into the design pipeline and hardens Naruto read-only routing on top of the 2.0 execution layer. Design routes can now discover, install, verify, and prefer the remote `product-design@openai-curated-remote` plugin, while read-only native worker runs keep write mode off and avoid treating pre-existing dirty files as generated patches.
20
20
 
21
21
  What changed:
22
22
 
23
+ - Product Design plugin readiness now checks both local and remote Codex App catalogs, auto-installs the remote plugin when needed, and records the installed/enabled skill surface.
24
+ - UI/design/PPT runtime routes prefer Product Design for research, ideation, audit, design QA, prototype, URL-to-code, image-to-code, share, and user-context steps.
25
+ - Naruto read-only runs force write mode off, propagate no-patch reasons through worker proof, and skip changed-file lease checks when no write-capable patch envelope exists.
23
26
  - `codex-sdk` is the default native agent backend for Team, QA, Research, Naruto, MAD-SKS, and direct agent runs, with every runtime task entering through `runCodexTask`.
24
27
  - Codex App UI snapshot, preservation, clobber guard, and doctor repair checks protect host-owned Fast UI/profile settings around `sks --mad`.
25
28
  - Provider context resolves `openai`, `codex-lb`, and `codex-app` with badge/fallback surfaces while avoiding private Codex App UI mutation.
@@ -78,7 +81,7 @@ Broader release checks still live behind `npm run release:check`. Detailed relea
78
81
  sks xai docs
79
82
  ```
80
83
 
81
- - **Quieter update prompts.** The "update available" choice is shown once per conversation and then stays quiet for a short window (default 8 min, `SKS_UPDATE_OFFER_THROTTLE_MS`) instead of repeating on every prompt. The check compares npm latest against source metadata, PATH `sks --version`, and the global npm package, so a completed `npm i -g sneakoscope` clears stale pending/accepted offers.
84
+ - **CLI-only SKS update notices.** Codex App hooks no longer stop normal work to ask for an SKS update. CLI launch surfaces such as `sks --mad` print a non-blocking latest-version notice, `sks update-check` / `sks update check` show the explicit status, and `sks doctor --fix` runs the guarded global SKS update path before repair.
82
85
 
83
86
  ## Retention And Cleanup
84
87
 
@@ -359,7 +362,7 @@ sks --mad --allow-package-install --allow-service-control --allow-network --yes
359
362
 
360
363
  This syncs existing codex-lb provider auth, creates/uses the `sks-mad-high` xhigh maintenance profile, opens the MAD-SKS permission gate for that Zellij run, starts a same-mission read-only native agent swarm, and launches a Codex CLI layout whose right-side lanes read that MAD ledger. Bare `sks --mad` grants target-project file and shell scope only; add explicit `--allow-*` flags for packages, services, network, Computer Use, browser use, generated assets, file permissions, DB writes, or other high-risk scopes. MAD-SKS is not a DB-only unlock: it is explicit user authorization to widen approved target-project scopes. Catastrophic database wipe/all-row/project-management safeguards remain active, and the pipeline contract still forbids unrequested fallback implementation code.
361
364
 
362
- Before launching, SKS checks npm for a newer `sneakoscope`; answer `y` to update or `n` to continue. Use `--yes` to approve missing dependency installs automatically. Tune MAD swarm startup with `--mad-agents <n>`, `--mad-swarm-work-items <n>`, and `--mad-swarm-backend <backend>`; `--no-mad-swarm` keeps only the cockpit UI if you need a temporary fallback.
365
+ Before launching, SKS checks npm for a newer `sneakoscope` and prints a non-blocking update notice when one is available; use `sks update now` or `sks doctor --fix` when you want SKS to update itself. Use `--yes` to approve missing dependency installs automatically. Tune MAD swarm startup with `--mad-agents <n>`, `--mad-swarm-work-items <n>`, and `--mad-swarm-backend <backend>`; `--no-mad-swarm` keeps only the cockpit UI if you need a temporary fallback.
363
366
 
364
367
  ### Team Missions
365
368
 
@@ -659,7 +662,7 @@ npm ls -g sneakoscope --depth=0
659
662
  sks doctor --fix
660
663
  ```
661
664
 
662
- Update prompts compare npm latest against the effective installed version from source metadata, PATH `sks --version`, and global npm package metadata. `sks update now` installs through npm global mode instead of mutating the current project's dependencies. If a global update succeeded but an old shim remains earlier on PATH, `sks doctor --fix` can remove duplicate global installs and refresh the managed setup.
665
+ CLI update checks compare npm latest against the effective installed version from source metadata, PATH `sks --version`, and global npm package metadata. Codex App hooks do not force update choices during ordinary work. `sks update now` installs through npm global mode instead of mutating the current project's dependencies, and `sks doctor --fix` runs that guarded global update path before setup/config repair. If a global update succeeded but an old shim remains earlier on PATH, `sks doctor --fix` can remove duplicate global installs and refresh the managed setup.
663
666
 
664
667
  ### Zellij is missing
665
668
 
@@ -76,7 +76,7 @@ dependencies = [
76
76
 
77
77
  [[package]]
78
78
  name = "sks-core"
79
- version = "2.0.5"
79
+ version = "2.0.6"
80
80
  dependencies = [
81
81
  "serde_json",
82
82
  ]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "sks-core"
3
- version = "2.0.5"
3
+ version = "2.0.6"
4
4
  edition = "2021"
5
5
 
6
6
  [dependencies]
@@ -4,7 +4,7 @@ use std::io::{self, Read, Seek, SeekFrom};
4
4
  fn main() {
5
5
  let mut args = std::env::args().skip(1);
6
6
  match args.next().as_deref() {
7
- Some("--version") => println!("sks-rs 2.0.5"),
7
+ Some("--version") => println!("sks-rs 2.0.6"),
8
8
  Some("compact-info") => {
9
9
  let mut input = String::new();
10
10
  let _ = io::stdin().read_to_string(&mut input);
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schema": "sks.dist-build-stamp.v1",
3
3
  "package_name": "sneakoscope",
4
- "package_version": "2.0.5",
5
- "source_digest": "696706cb9d175c4200c967130fbc5e680f4b6c905ec6e7fad5b1671827bcb4cb",
6
- "source_file_count": 1948,
7
- "built_at_source_time": 1780613485190
4
+ "package_version": "2.0.6",
5
+ "source_digest": "3947e39c47a565506e9ffd37568ae889bc7943f6dc342b5c357976b840770f94",
6
+ "source_file_count": 1953,
7
+ "built_at_source_time": 1780662014263
8
8
  }
package/dist/bin/sks.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- const FAST_PACKAGE_VERSION = '2.0.5';
2
+ const FAST_PACKAGE_VERSION = '2.0.6';
3
3
  const args = process.argv.slice(2);
4
4
  try {
5
5
  if (args[0] === '--agent' && args[1] === 'worker') {
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "schema": "sks.dist-build.v2",
3
- "version": "2.0.5",
4
- "package_version": "2.0.5",
3
+ "version": "2.0.6",
4
+ "package_version": "2.0.6",
5
5
  "typescript": true,
6
6
  "mjs_runtime_files": 0,
7
- "compiled_file_count": 1014,
8
- "compiled_js_count": 1014,
7
+ "compiled_file_count": 1019,
8
+ "compiled_js_count": 1019,
9
9
  "compiled_dts_count": 0,
10
- "source_digest": "696706cb9d175c4200c967130fbc5e680f4b6c905ec6e7fad5b1671827bcb4cb",
11
- "source_file_count": 1948,
12
- "source_files_hash": "50906865545a9076d7b2736ee379a428160a8869749b85ee79c4ddab8eab4b12",
13
- "source_list_hash": "50906865545a9076d7b2736ee379a428160a8869749b85ee79c4ddab8eab4b12",
10
+ "source_digest": "3947e39c47a565506e9ffd37568ae889bc7943f6dc342b5c357976b840770f94",
11
+ "source_file_count": 1953,
12
+ "source_files_hash": "5d6f0e4186fc37279e1cf6de0c6b3c7230123c4a588966f8fda2a9324eb75192",
13
+ "source_list_hash": "5d6f0e4186fc37279e1cf6de0c6b3c7230123c4a588966f8fda2a9324eb75192",
14
14
  "src_mjs_runtime_files": 0,
15
15
  "dist_stamp_schema": "sks.dist-build-stamp.v1",
16
16
  "files": [
@@ -473,6 +473,8 @@
473
473
  "core/ppt-review/slide-issue-extraction.js",
474
474
  "core/ppt.js",
475
475
  "core/preflight/parallel-preflight-engine.js",
476
+ "core/product-design-app-server.js",
477
+ "core/product-design-plugin.js",
476
478
  "core/prompt-context-builder.js",
477
479
  "core/prompt/prompt-placeholder-guard.js",
478
480
  "core/proof-field.js",
@@ -886,6 +888,7 @@
886
888
  "scripts/naruto-concurrency-governor-check.js",
887
889
  "scripts/naruto-gpt-final-pack-check.js",
888
890
  "scripts/naruto-parallel-patch-apply-check.js",
891
+ "scripts/naruto-readonly-routing-check.js",
889
892
  "scripts/naruto-real-local-gpt-final-smoke.js",
890
893
  "scripts/naruto-role-distribution-check.js",
891
894
  "scripts/naruto-shadow-clone-swarm-check.js",
@@ -916,6 +919,8 @@
916
919
  "scripts/prepublish-fast-check.js",
917
920
  "scripts/prepublish-release-check-or-fast.js",
918
921
  "scripts/priority-full-closure-check.js",
922
+ "scripts/product-design-auto-install-check.js",
923
+ "scripts/product-design-plugin-routing-check.js",
919
924
  "scripts/prompt-placeholder-guard-check.js",
920
925
  "scripts/provider-badge-context-check.js",
921
926
  "scripts/provider-context-config-toml-check.js",
@@ -13,6 +13,7 @@ import { context7ConfigToml, DOLLAR_SKILL_NAMES, GETDESIGN_REFERENCE, hasContext
13
13
  import { checkZellijCapability } from '../core/zellij/zellij-capability.js';
14
14
  import { reconcileCodexAppUpgradeProcesses } from '../core/codex-app.js';
15
15
  import { recordCodexLbHealthEvent } from '../core/codex-lb-circuit.js';
16
+ import { runSksUpdateCheck, runSksUpdateNow } from '../core/update-check.js';
16
17
  import { loadCodexLbEnv, writeCodexLbKeychain, codexLbMetadataPath } from '../core/codex-lb/codex-lb-env.js';
17
18
  import { buildCodexLbSetupPlan, codexLbPersistenceSummary, installCodexLbShellProfileSnippet, selectedCodexLbPersistenceModes } from '../core/codex-lb/codex-lb-setup.js';
18
19
  const DEFAULT_CODEX_APP_PLUGINS = [
@@ -2231,6 +2232,28 @@ export async function maybePromptCodexUpdateForLaunch(args = [], opts = {}) {
2231
2232
  return { status: 'skipped_by_user', latest: latest.version || null, current, command, bin: codex.bin || null };
2232
2233
  return installCodexLatest(command, latest.version, current);
2233
2234
  }
2235
+ export async function maybePromptSksUpdateForLaunch(args = [], opts = {}) {
2236
+ if (hasFlag(args, '--json') || hasFlag(args, '--skip-sks-update') || process.env.SKS_SKIP_SKS_UPDATE === '1')
2237
+ return { status: 'skipped' };
2238
+ const label = opts.label || 'SKS launch';
2239
+ const check = await runSksUpdateCheck({ timeoutMs: 5000 }).catch((err) => ({
2240
+ ok: false,
2241
+ update_available: false,
2242
+ latest: null,
2243
+ current: PACKAGE_VERSION,
2244
+ command: null,
2245
+ error: err?.message || String(err)
2246
+ }));
2247
+ if (!check.update_available) {
2248
+ return { status: check.error ? 'unavailable' : 'current', latest: check.latest || null, current: check.current || PACKAGE_VERSION, error: check.error || null };
2249
+ }
2250
+ const command = check.command || `sks update now --version ${check.latest}`;
2251
+ if (shouldAutoApproveInstall(args)) {
2252
+ return runSksUpdateNow({ version: check.latest, timeoutMs: 10 * 60 * 1000, maxOutputBytes: 128 * 1024 });
2253
+ }
2254
+ console.log(`SKS update available before ${label}: ${check.current} -> ${check.latest}. Run when ready: ${command}`);
2255
+ return { status: 'available', latest: check.latest || null, current: check.current || PACKAGE_VERSION, command };
2256
+ }
2234
2257
  export function shouldAutoApproveInstall(args = [], env = process.env) {
2235
2258
  if (hasFlag(args, '--from-postinstall') && env.SKS_POSTINSTALL_AUTO_INSTALL_CLI_TOOLS !== '1')
2236
2259
  return false;
@@ -1,11 +1,31 @@
1
1
  import { flag } from '../cli/args.js';
2
2
  import { printJson } from '../cli/output.js';
3
- import { codexAccessTokenStatus, codexAppIntegrationStatus, codexChromeExtensionStatus, formatCodexAppStatus } from '../core/codex-app.js';
3
+ import { codexAccessTokenStatus, codexAppIntegrationStatus, codexChromeExtensionStatus, codexProductDesignPluginStatus, formatCodexAppStatus, formatCodexProductDesignPluginStatus } from '../core/codex-app.js';
4
4
  import { codexAppRemoteControlCommand } from '../cli/codex-app-command.js';
5
5
  export async function run(_command, args = []) {
6
6
  const action = args[0] || 'check';
7
7
  if (action === 'remote-control' || action === 'remote')
8
8
  return codexAppRemoteControlCommand(args.slice(1));
9
+ if (action === 'product-design' || action === 'design-product' || action === 'ensure-product-design') {
10
+ const checkOnly = flag(args, '--check-only') || flag(args, '--no-install');
11
+ const status = await codexProductDesignPluginStatus({
12
+ autoInstallProductDesign: !checkOnly && (action === 'product-design'
13
+ || action === 'design-product'
14
+ || action === 'ensure-product-design'
15
+ || flag(args, '--install')
16
+ || flag(args, '--auto-install'))
17
+ });
18
+ if (flag(args, '--json')) {
19
+ printJson(status);
20
+ if (!status.ok)
21
+ process.exitCode = 1;
22
+ return;
23
+ }
24
+ console.log(formatCodexProductDesignPluginStatus(status));
25
+ if (!status.ok)
26
+ process.exitCode = 1;
27
+ return;
28
+ }
9
29
  if (action === 'chrome-extension' || action === 'chrome') {
10
30
  const status = await codexChromeExtensionStatus();
11
31
  if (flag(args, '--json')) {
@@ -32,7 +52,9 @@ export async function run(_command, args = []) {
32
52
  return;
33
53
  }
34
54
  if (action === 'check' || action === 'status') {
35
- const status = await codexAppIntegrationStatus();
55
+ const status = await codexAppIntegrationStatus({
56
+ autoInstallProductDesign: flag(args, '--install-product-design') || flag(args, '--auto-install-product-design')
57
+ });
36
58
  if (flag(args, '--json')) {
37
59
  printJson(status);
38
60
  if (!status.ok)
@@ -44,7 +66,7 @@ export async function run(_command, args = []) {
44
66
  process.exitCode = 1;
45
67
  return;
46
68
  }
47
- console.error('Usage: sks codex-app check|status|chrome-extension|pat status|remote-control [--json]');
69
+ console.error('Usage: sks codex-app check|status|product-design [--check-only]|ensure-product-design|chrome-extension|pat status|remote-control [--json]');
48
70
  process.exitCode = 1;
49
71
  }
50
72
  //# sourceMappingURL=codex-app.js.map
@@ -18,10 +18,23 @@ import { appendMigrationEvents, hashConfigText } from '../core/migration/migrati
18
18
  import { repairCodexAppFastUi } from '../core/codex-app/codex-app-fast-ui-repair.js';
19
19
  import { resolveProviderContext } from '../core/provider/provider-context.js';
20
20
  import { readLocalModelConfig } from '../core/agents/ollama-worker-config.js';
21
+ import { runSksUpdateNow } from '../core/update-check.js';
21
22
  export async function run(_command, args = []) {
23
+ const doctorFix = flag(args, '--fix');
22
24
  let setupRepair = null;
25
+ const sksUpdate = doctorFix
26
+ ? await runSksUpdateNow({
27
+ timeoutMs: 10 * 60 * 1000,
28
+ maxOutputBytes: 128 * 1024
29
+ }).catch((err) => ({
30
+ schema: 'sks.update-now.v1',
31
+ ok: false,
32
+ status: 'failed',
33
+ error: err?.message || String(err)
34
+ }))
35
+ : null;
23
36
  let migrationPreFix = null;
24
- if (flag(args, '--fix')) {
37
+ if (doctorFix) {
25
38
  // Snapshot config content before ANY mutation so the migration journal can
26
39
  // record real before/after hashes for the whole --fix transaction.
27
40
  migrationPreFix = await captureCodexConfigSnapshot();
@@ -97,7 +110,6 @@ export async function run(_command, args = []) {
97
110
  model_provider: null
98
111
  }
99
112
  }));
100
- const doctorFix = flag(args, '--fix');
101
113
  const explicitCodexAppUiRepair = flag(args, '--repair-codex-app-ui') || flag(args, '--yes');
102
114
  const codexAppUiPlan = await repairCodexAppFastUi(root, {
103
115
  apply: false,
@@ -165,7 +177,7 @@ export async function run(_command, args = []) {
165
177
  const zellijReadiness = buildZellijReadiness(root, zellij, ready);
166
178
  const result = {
167
179
  schema: 'sks.doctor-status.v1',
168
- ok: ready.ready,
180
+ ok: ready.ready && (!sksUpdate || sksUpdate.ok !== false),
169
181
  root,
170
182
  node: { ok: Number(process.versions.node.split('.')[0]) >= 20, version: process.version },
171
183
  codex,
@@ -189,7 +201,7 @@ export async function run(_command, args = []) {
189
201
  ready,
190
202
  sneakoscope: { ok: await exists(`${root}/.sneakoscope`) },
191
203
  package: { bytes: pkgBytes, human: formatBytes(pkgBytes) },
192
- repair: { setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup }
204
+ repair: { sks_update: sksUpdate, setup: setupRepair, codex_config: configRepair, migration_journal: migrationJournal, global_sks_installs: globalSksInstallCleanup }
193
205
  };
194
206
  if (flag(args, '--json')) {
195
207
  printJson(result);
@@ -265,6 +277,9 @@ export async function run(_command, args = []) {
265
277
  if (migrationJournal?.journal_path) {
266
278
  console.log(`Migration journal: ${migrationJournal.journal_path} (${migrationJournal.event_count} events, ${migrationJournal.mutations_without_rollback} without rollback)`);
267
279
  }
280
+ if (sksUpdate) {
281
+ console.log(`SKS update: ${sksUpdate.status}${sksUpdate.latest ? ` latest ${sksUpdate.latest}` : ''}${sksUpdate.error ? ` (${sksUpdate.error})` : ''}`);
282
+ }
268
283
  if (globalSksInstallCleanup) {
269
284
  console.log(`Global SKS installs: kept ${globalSksInstallCleanup.kept?.length ?? 0}, removed ${globalSksInstallCleanup.removed?.filter((entry) => entry.ok).length ?? 0}, source repo exempt ${globalSksInstallCleanup.candidates?.filter((entry) => entry.source_repo_exempt).length ?? 0}`);
270
285
  }
@@ -1,9 +1,9 @@
1
1
  import { madHighCommand } from '../core/commands/mad-sks-command.js';
2
- import { ensureMadLaunchDependencies, formatMadLaunchDependencyAction, maybePromptCodexUpdateForLaunch, maybePromptCodexLbSetupForLaunch } from '../cli/install-helpers.js';
2
+ import { ensureMadLaunchDependencies, formatMadLaunchDependencyAction, maybePromptCodexUpdateForLaunch, maybePromptCodexLbSetupForLaunch, maybePromptSksUpdateForLaunch } from '../cli/install-helpers.js';
3
3
  import { PACKAGE_VERSION } from '../core/fsx.js';
4
4
  export async function run(_command, args = []) {
5
5
  return madHighCommand(['--mad-sks', ...args], {
6
- maybePromptSksUpdateForLaunch: async () => ({ status: 'skipped' }),
6
+ maybePromptSksUpdateForLaunch,
7
7
  maybePromptCodexUpdateForLaunch,
8
8
  ensureMadLaunchDependencies,
9
9
  printDepsInstallAction: (action) => console.error(formatMadLaunchDependencyAction(action)),
@@ -117,6 +117,7 @@ export async function runNativeAgentOrchestrator(opts = {}) {
117
117
  const strategyCompiled = compileStrategy({
118
118
  prompt,
119
119
  route,
120
+ ...(writeCapable ? {} : { writeTargets: [] }),
120
121
  agentCount: roster.agent_count,
121
122
  visualRequired
122
123
  });
@@ -235,12 +236,13 @@ export async function runNativeAgentOrchestrator(opts = {}) {
235
236
  rate_limit_delay_ms: backend === 'codex-sdk' ? 250 : 0,
236
237
  resource_pressure_warnings: roster.agent_count > roster.concurrency ? ['agents_exceed_concurrency_batches'] : []
237
238
  });
239
+ const effectiveWriteMode = writeCapable ? opts.writeMode || 'off' : 'off';
238
240
  const parallelWritePolicy = {
239
241
  schema: 'sks.agent-parallel-write-policy.v1',
240
242
  generated_at: nowIso(),
241
243
  route,
242
244
  route_command: routeCommand,
243
- write_mode: opts.writeMode || 'off',
245
+ write_mode: effectiveWriteMode,
244
246
  apply_patches: opts.applyPatches === true,
245
247
  dry_run_patches: opts.dryRunPatches === true,
246
248
  max_write_agents: Number(opts.maxWriteAgents || 0),
@@ -779,7 +781,7 @@ async function runAgentPatchSwarmRuntime(root, ledgerRoot, input) {
779
781
  await queueStore.persistSnapshot();
780
782
  const journalSummary = await queueStore.journal.writeSummary();
781
783
  const proof = buildAgentPatchProof({ ...proofInput, transactionJournal: journalSummary });
782
- const zeroPatchBlockers = input.writeCapable && input.parallelWritePolicy?.write_mode === 'parallel' && pendingEntries.length === 0 && input.parallelWritePolicy?.dry_run_patches !== true
784
+ const zeroPatchBlockers = input.writeCapable && input.parallelWritePolicy?.write_mode === 'parallel' && pendingEntries.length === 0 && input.dryRun !== true && input.parallelWritePolicy?.dry_run_patches !== true
783
785
  ? ['write_mode_parallel_zero_patch_envelopes']
784
786
  : [];
785
787
  const blockers = [
@@ -870,6 +872,8 @@ function normalizeVisualLaneCount(value, fallback, maxAgentCount) {
870
872
  return Math.max(1, Math.min(maxAgentCount, Math.floor(raw)));
871
873
  }
872
874
  function isWriteCapableRun(opts) {
875
+ if (opts.readonly === true)
876
+ return false;
873
877
  return opts.applyPatches === true || opts.dryRunPatches === true || (opts.writeMode !== undefined && opts.writeMode !== 'off');
874
878
  }
875
879
  function defaultRouteCommand(route) {
@@ -991,6 +995,7 @@ async function runAgentByBackend(backend, agent, slice, opts) {
991
995
  ...sdkWorkerResult,
992
996
  backend: 'codex-sdk',
993
997
  patch_envelopes: patchEnvelopes,
998
+ ...(patchEnvelopes.length ? {} : { no_patch_reason: buildDirectNoPatchReason(slice, opts) }),
994
999
  codex_child_report: sdkReport,
995
1000
  codex_sdk_thread: sdkReport,
996
1001
  model_authored_patch_envelopes: patchEnvelopes.length > 0,
@@ -1027,10 +1032,24 @@ function buildDirectSdkWorkerPrompt(slice) {
1027
1032
  '',
1028
1033
  write.length
1029
1034
  ? `Write-capable slice. Return JSON matching ${CODEX_AGENT_WORKER_RESULT_SCHEMA_ID}; include patch_envelopes for write_paths=${JSON.stringify(write)}. Each patch envelope must include schema, source "model_authored", agent_id, session_id, slot_id, generation_index, task_slice_id, lease_id, allowed_paths, operations, and rationale. Each operation must include op, path, search, replace, content, and diff; use empty strings for operation fields that do not apply.`
1030
- : `Read-only slice. Return JSON matching ${CODEX_AGENT_WORKER_RESULT_SCHEMA_ID}.`,
1035
+ : `Read-only slice. Return JSON matching ${CODEX_AGENT_WORKER_RESULT_SCHEMA_ID}; do not report pre-existing repository dirtiness as changed_files.`,
1031
1036
  'Required JSON fields: status, summary, findings, changed_files, patch_envelopes, verification, rollback_notes, blockers.'
1032
1037
  ].join('\n');
1033
1038
  }
1039
+ function buildDirectNoPatchReason(slice, opts) {
1040
+ const writePathCount = sdkWritePaths(slice, opts).length;
1041
+ return {
1042
+ schema: 'sks.native-cli-worker-no-patch-reason.v1',
1043
+ generated_at: nowIso(),
1044
+ ok: writePathCount === 0,
1045
+ reason: writePathCount ? 'write_capable_task_without_backend_patch_envelope' : 'read_only_or_no_write_paths',
1046
+ route_justification: writePathCount ? 'backend returned no patch envelopes for a write-capable task' : 'task has no write paths',
1047
+ read_only_or_noop_evidence: writePathCount === 0,
1048
+ task_slice_id: slice?.id || null,
1049
+ backend: 'codex-sdk',
1050
+ blockers: writePathCount ? ['write_capable_no_patch_envelope'] : []
1051
+ };
1052
+ }
1034
1053
  function sdkWritePaths(slice, opts) {
1035
1054
  return [
1036
1055
  ...(Array.isArray(slice?.write_paths) ? slice.write_paths : []),
@@ -119,6 +119,14 @@ export async function writeAgentProofEvidence(root, input) {
119
119
  return !changed || Boolean(row.rollback_digest);
120
120
  }) : true;
121
121
  const parallelPatchApplyVerified = patchSwarm ? Array.isArray(patchProof?.wall_clock_parallel_evidence) && patchProof.wall_clock_parallel_evidence.length > 0 || Number(patchSwarm?.parallel_apply_count || 0) > 1 : false;
122
+ const readOnlyNoWriteLeaseMode = isReadOnlyNoWriteLeaseMode({
123
+ results: input.results || [],
124
+ leases: input.partition?.leases || [],
125
+ parallelWritePolicy,
126
+ taskGraph,
127
+ narutoWorkGraph
128
+ });
129
+ const changedFileLeaseBlockers = readOnlyNoWriteLeaseMode ? [] : agentChangedFileLeaseViolations(input.results || [], input.partition?.leases || []);
122
130
  const blockers = [
123
131
  ...(lifecycle.ok ? [] : ['agent_lifecycle_not_all_closed']),
124
132
  ...(lifecycle.ok ? [] : lifecycle.open_sessions.map((id) => 'session_open:' + id)),
@@ -195,7 +203,7 @@ export async function writeAgentProofEvidence(root, input) {
195
203
  ...(isNarutoRoute && !narutoVerificationDag ? ['naruto_verification_dag_missing'] : []),
196
204
  ...(isNarutoRoute && !narutoGptFinalPack ? ['naruto_gpt_final_pack_missing'] : []),
197
205
  ...(isNarutoRoute && !narutoZellijDashboard ? ['naruto_zellij_dashboard_missing'] : []),
198
- ...agentChangedFileLeaseViolations(input.results || [], input.partition?.leases || [])
206
+ ...changedFileLeaseBlockers
199
207
  ];
200
208
  const evidence = {
201
209
  schema: AGENT_PROOF_EVIDENCE_SCHEMA,
@@ -369,7 +377,7 @@ export async function writeAgentProofEvidence(root, input) {
369
377
  triwiki_use_first_count: Number(input.triwikiContext?.use_first?.length || 0),
370
378
  triwiki_hydrate_first_count: Number(input.triwikiContext?.hydrate_first?.length || 0),
371
379
  triwiki_claim_count: Number(input.triwikiContext?.claim_count || 0),
372
- changed_files_lease_checked: true,
380
+ changed_files_lease_checked: !readOnlyNoWriteLeaseMode,
373
381
  dependency_collision_risk: input.partition?.no_overlap_proof?.dependency_collision_risk || [],
374
382
  blockers
375
383
  };
@@ -448,6 +456,20 @@ function agentChangedFileLeaseViolations(results, leases) {
448
456
  }
449
457
  return violations;
450
458
  }
459
+ function isReadOnlyNoWriteLeaseMode(input) {
460
+ const writeLeaseCount = input.leases.filter((lease) => lease.kind === 'write').length;
461
+ if (writeLeaseCount > 0)
462
+ return false;
463
+ const resultWriteSignals = input.results.some((result) => (Array.isArray(result?.writes) && result.writes.length > 0)
464
+ || (Array.isArray(result?.patch_envelopes) && result.patch_envelopes.length > 0));
465
+ if (resultWriteSignals)
466
+ return false;
467
+ const policyReadonly = input.parallelWritePolicy?.readonly === true;
468
+ const policyWriteOff = String(input.parallelWritePolicy?.write_mode || 'off') === 'off';
469
+ const narutoReadOnly = input.narutoWorkGraph?.readonly === true || Number(input.narutoWorkGraph?.write_allowed_count || 0) === 0;
470
+ const taskGraphNoWrites = Number(input.taskGraph?.write_allowed_count || 0) === 0;
471
+ return policyReadonly || policyWriteOff || narutoReadOnly || taskGraphNoWrites;
472
+ }
451
473
  function pathWithin(file, leasePath) {
452
474
  const left = String(file || '').replace(/\\/g, '/').replace(/^\.\//, '');
453
475
  const right = String(leasePath || '').replace(/\\/g, '/').replace(/^\.\//, '').replace(/\/+$/, '');
@@ -67,7 +67,8 @@ export function validateAgentWorkerResult(result) {
67
67
  normalized.blockers.push(...patchEnvelopeValidation.blockers.map((issue) => 'patch_envelope_invalid:' + issue));
68
68
  normalized.verification = { status: 'failed', checks: [...normalized.verification.checks, 'agent-patch-envelope-schema'] };
69
69
  }
70
- if (patchEnvelopeValidation.envelopes.length === 0 && (normalized.changed_files.length > 0 || normalized.writes.length > 0)) {
70
+ const readOnlyOrNoopWithoutPatch = acceptsNoPatchReadOnlyOrNoop(result?.no_patch_reason);
71
+ if (patchEnvelopeValidation.envelopes.length === 0 && (normalized.writes.length > 0 || (!readOnlyOrNoopWithoutPatch && normalized.changed_files.length > 0))) {
71
72
  normalized.status = 'blocked';
72
73
  normalized.blockers.push('no_patch_generated');
73
74
  normalized.verification = { status: 'failed', checks: [...normalized.verification.checks, 'agent-patch-envelope-required-for-write'] };
@@ -132,4 +133,11 @@ function normalizeVerification(value) {
132
133
  checks: Array.isArray(value?.checks) ? value.checks : []
133
134
  };
134
135
  }
136
+ function acceptsNoPatchReadOnlyOrNoop(value) {
137
+ if (!value || typeof value !== 'object')
138
+ return false;
139
+ return value.ok === true
140
+ && value.read_only_or_noop_evidence === true
141
+ && String(value.reason || '') === 'read_only_or_no_write_paths';
142
+ }
135
143
  //# sourceMappingURL=agent-worker-pipeline.js.map
@@ -57,6 +57,7 @@ export async function runNativeWorkerBackendRouter(input) {
57
57
  result = validateAgentWorkerResult({
58
58
  ...processRun,
59
59
  patch_envelopes: patchEnvelopes,
60
+ ...(patchEnvelopes.length ? {} : { no_patch_reason: buildNoPatchReason(input, backend) }),
60
61
  artifacts: [...new Set([...(processRun.artifacts || []), ...(patchEnvelopes.length ? [input.patchRel] : [])])],
61
62
  process_child_report: processReport,
62
63
  model_authored_patch_envelopes: false,
@@ -81,6 +82,7 @@ export async function runNativeWorkerBackendRouter(input) {
81
82
  ...ollamaRun,
82
83
  backend: 'ollama',
83
84
  patch_envelopes: patchEnvelopes,
85
+ ...(patchEnvelopes.length ? {} : { no_patch_reason: buildNoPatchReason(input, backend) }),
84
86
  model_authored_patch_envelopes: patchEnvelopes.length > 0,
85
87
  fixture_patch_envelopes: false,
86
88
  verification: { status: ollamaRun.status === 'done' ? 'passed' : 'failed', checks: [...(ollamaRun.verification?.checks || []), 'native-worker-backend-router', 'ollama-api-generate'] }
@@ -147,6 +149,7 @@ export async function runNativeWorkerBackendRouter(input) {
147
149
  ...sdkWorkerResult,
148
150
  backend: sdkTask.backend === 'local-llm' ? 'local-llm' : 'codex-sdk',
149
151
  patch_envelopes: patchEnvelopes,
152
+ ...(patchEnvelopes.length ? {} : { no_patch_reason: buildNoPatchReason(input, sdkTask.backend || backend) }),
150
153
  codex_child_report: sdkReport,
151
154
  codex_sdk_thread: sdkReport,
152
155
  model_authored_patch_envelopes: patchEnvelopes.length > 0,
@@ -189,6 +192,7 @@ export async function runNativeWorkerBackendRouter(input) {
189
192
  result = validateAgentWorkerResult({
190
193
  ...zellijRun,
191
194
  patch_envelopes: patchEnvelopes,
195
+ ...(patchEnvelopes.length ? {} : { no_patch_reason: buildNoPatchReason(input, backend) }),
192
196
  zellij_child_report: zellijReport,
193
197
  model_authored_patch_envelopes: false,
194
198
  fixture_patch_envelopes: false,
@@ -279,10 +283,24 @@ function buildWorkerPrompt(slice) {
279
283
  '',
280
284
  write.length
281
285
  ? `Write-capable slice. Return JSON matching ${CODEX_AGENT_WORKER_RESULT_SCHEMA_ID}; include patch_envelopes for write_paths=${JSON.stringify(write)}.`
282
- : `Read-only slice. Return JSON matching ${CODEX_AGENT_WORKER_RESULT_SCHEMA_ID}.`,
286
+ : `Read-only slice. Return JSON matching ${CODEX_AGENT_WORKER_RESULT_SCHEMA_ID}; do not report pre-existing repository dirtiness as changed_files.`,
283
287
  'Required JSON fields: status, summary, findings, changed_files, patch_envelopes, verification, rollback_notes, blockers.'
284
288
  ].join('\n');
285
289
  }
290
+ function buildNoPatchReason(input, backend) {
291
+ const writePathCount = writePaths(input.slice, input.intake).length;
292
+ return {
293
+ schema: 'sks.native-cli-worker-no-patch-reason.v1',
294
+ generated_at: nowIso(),
295
+ ok: writePathCount === 0,
296
+ reason: writePathCount ? 'write_capable_task_without_backend_patch_envelope' : 'read_only_or_no_write_paths',
297
+ route_justification: writePathCount ? 'backend returned no patch envelopes for a write-capable task' : 'task has no write paths',
298
+ read_only_or_noop_evidence: writePathCount === 0,
299
+ task_slice_id: input.slice?.id || null,
300
+ backend,
301
+ blockers: writePathCount && backend !== 'fake' ? ['write_capable_no_patch_envelope'] : []
302
+ };
303
+ }
286
304
  function hasWriteLease(slice, intake) {
287
305
  return writePaths(slice, intake).length > 0;
288
306
  }