sneakoscope 3.1.0 → 3.1.2

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 (84) hide show
  1. package/README.md +1 -1
  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/cli/install-helpers.js +6 -7
  8. package/dist/commands/zellij-slot-column-anchor.js +3 -1
  9. package/dist/commands/zellij-slot-pane.js +19 -2
  10. package/dist/core/agents/agent-janitor.js +10 -1
  11. package/dist/core/agents/agent-orchestrator.js +8 -2
  12. package/dist/core/agents/agent-proof-evidence.js +20 -0
  13. package/dist/core/agents/agent-runner-ollama.js +11 -4
  14. package/dist/core/agents/fast-mode-policy.js +7 -5
  15. package/dist/core/agents/intelligent-work-graph.js +93 -14
  16. package/dist/core/agents/native-cli-session-swarm.js +115 -9
  17. package/dist/core/agents/no-subagent-scaling-policy.js +10 -1
  18. package/dist/core/agents/official-subagent-helper-policy.js +62 -0
  19. package/dist/core/codex-app.js +0 -2
  20. package/dist/core/codex-control/codex-task-runner.js +9 -0
  21. package/dist/core/commands/fast-mode-command.js +1 -1
  22. package/dist/core/commands/loop-command.js +86 -13
  23. package/dist/core/commands/naruto-command.js +34 -21
  24. package/dist/core/commands/team-command.js +1 -0
  25. package/dist/core/commands/wiki-command.js +35 -1
  26. package/dist/core/fsx.js +1 -1
  27. package/dist/core/init.js +1 -2
  28. package/dist/core/locks/file-lock.js +88 -0
  29. package/dist/core/loops/loop-artifacts.js +54 -2
  30. package/dist/core/loops/loop-checkpoint.js +22 -0
  31. package/dist/core/loops/loop-concurrency-budget.js +55 -0
  32. package/dist/core/loops/loop-final-arbiter-contract.js +28 -0
  33. package/dist/core/loops/loop-finalizer.js +55 -7
  34. package/dist/core/loops/loop-fixture-policy.js +58 -0
  35. package/dist/core/loops/loop-gate-registry.js +96 -0
  36. package/dist/core/loops/loop-gate-runner.js +206 -17
  37. package/dist/core/loops/loop-gpt-final-arbiter.js +81 -0
  38. package/dist/core/loops/loop-integration-merge.js +80 -0
  39. package/dist/core/loops/loop-interrupt-registry.js +118 -0
  40. package/dist/core/loops/loop-lease.js +35 -20
  41. package/dist/core/loops/loop-merge-strategy.js +105 -0
  42. package/dist/core/loops/loop-mutation-ledger.js +103 -0
  43. package/dist/core/loops/loop-planner.js +36 -5
  44. package/dist/core/loops/loop-runtime-control.js +27 -0
  45. package/dist/core/loops/loop-runtime.js +254 -96
  46. package/dist/core/loops/loop-scheduler.js +14 -5
  47. package/dist/core/loops/loop-side-effect-scanner.js +91 -0
  48. package/dist/core/loops/loop-worker-prompts.js +43 -0
  49. package/dist/core/loops/loop-worker-runtime.js +281 -0
  50. package/dist/core/loops/loop-worktree-runtime.js +92 -0
  51. package/dist/core/naruto/naruto-finalizer.js +7 -2
  52. package/dist/core/naruto/naruto-loop-mesh.js +10 -1
  53. package/dist/core/proof/auto-finalize.js +3 -2
  54. package/dist/core/proof/proof-schema.js +6 -0
  55. package/dist/core/proof/proof-writer.js +5 -2
  56. package/dist/core/proof/root-cause-policy.js +70 -0
  57. package/dist/core/proof/route-adapter.js +18 -1
  58. package/dist/core/proof/route-finalizer.js +71 -6
  59. package/dist/core/proof/route-proof-gate.js +4 -0
  60. package/dist/core/release/release-gate-batch-runner.js +56 -10
  61. package/dist/core/release/release-gate-cache-v2.js +18 -3
  62. package/dist/core/release/release-gate-dag.js +121 -18
  63. package/dist/core/release/release-gate-node.js +2 -1
  64. package/dist/core/release/release-gate-resource-governor.js +27 -6
  65. package/dist/core/skills/core-skill-meta-update.js +24 -0
  66. package/dist/core/skills/core-skill-reflection.js +94 -0
  67. package/dist/core/skills/core-skill-trainer.js +103 -0
  68. package/dist/core/trust-kernel/completion-contract.js +4 -0
  69. package/dist/core/trust-kernel/route-contract.js +4 -1
  70. package/dist/core/version.js +1 -1
  71. package/dist/core/zellij/zellij-right-column-manager.js +13 -2
  72. package/dist/core/zellij/zellij-slot-column-anchor.js +40 -3
  73. package/dist/core/zellij/zellij-slot-pane-renderer.js +36 -11
  74. package/dist/core/zellij/zellij-slot-telemetry.js +96 -44
  75. package/dist/core/zellij/zellij-worker-pane-manager.js +42 -4
  76. package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +14 -2
  77. package/dist/scripts/loop-directive-check-lib.js +225 -2
  78. package/dist/scripts/loop-hardening-check-lib.js +289 -0
  79. package/dist/scripts/loop-worker-fixture-child.js +53 -0
  80. package/dist/scripts/naruto-real-local-gpt-final-smoke.js +10 -1
  81. package/dist/scripts/prepublish-release-check-or-fast.js +38 -10
  82. package/dist/scripts/release-check-stamp.js +29 -4
  83. package/dist/scripts/release-gate-existence-audit.js +1 -0
  84. package/package.json +32 -2
@@ -12,6 +12,7 @@ import { resolveZellijWorkerPaneUiMode } from '../zellij/zellij-ui-mode.js';
12
12
  import { appendZellijSlotTelemetry } from '../zellij/zellij-slot-telemetry.js';
13
13
  import { appendParallelRuntimeEvent } from './parallel-runtime-proof.js';
14
14
  import { appendAgentMessage } from './agent-message-bus.js';
15
+ import { markLoopWorkerInterrupted, registerLoopActiveWorker } from '../loops/loop-interrupt-registry.js';
15
16
  export const NATIVE_CLI_SESSION_SWARM_SCHEMA = 'sks.agent-native-cli-session-swarm.v1';
16
17
  export function createNativeCliSessionSwarmRecorder(root, input) {
17
18
  return new NativeCliSessionSwarmRecorder(root, input);
@@ -176,6 +177,13 @@ class NativeCliSessionSwarmRecorder {
176
177
  });
177
178
  record.pid = child.pid || null;
178
179
  record.process_id = child.pid || null;
180
+ const loopHandle = await registerLoopWorkerHandle({
181
+ root: ctx.opts.projectRoot || this.input.projectRoot || ctx.opts.cwd || packageRoot(),
182
+ env: ctx.opts.env || {},
183
+ agentId: String(ctx.agent.id || ctx.agent.session_id || 'agent'),
184
+ sessionId: ctx.agent.session_id || null,
185
+ pid: child.pid || null
186
+ });
179
187
  record.status = 'running';
180
188
  await appendParallelRuntimeEvent(this.root, this.input.missionId, {
181
189
  event_type: 'worker_process_spawned',
@@ -211,6 +219,9 @@ class NativeCliSessionSwarmRecorder {
211
219
  record.exit_code = exit.code;
212
220
  record.signal = exit.signal;
213
221
  record.status = exit.code === 0 ? 'closed' : 'failed';
222
+ if (loopHandle) {
223
+ await markLoopWorkerInterrupted(ctx.opts.projectRoot || this.input.projectRoot || ctx.opts.cwd || packageRoot(), loopHandle.mission_id, loopHandle.worker_id, record.status === 'closed' ? 'completed' : 'failed').catch(() => undefined);
224
+ }
214
225
  if (record.worker_placement === 'headless') {
215
226
  await closeWorkerInRightColumn({
216
227
  root: this.root,
@@ -343,6 +354,13 @@ class NativeCliSessionSwarmRecorder {
343
354
  stdoutRel: input.stdoutRel,
344
355
  stderrRel: input.stderrRel
345
356
  });
357
+ let loopHandle = await registerLoopWorkerHandle({
358
+ root: input.ctx.opts.projectRoot || this.input.projectRoot || input.ctx.opts.cwd || packageRoot(),
359
+ env: input.ctx.opts.env || {},
360
+ agentId: String(input.ctx.agent.id || input.ctx.agent.session_id || 'agent'),
361
+ sessionId: input.ctx.agent.session_id || null,
362
+ pid: processRun?.pid || null
363
+ });
346
364
  if (processRun?.pid) {
347
365
  input.record.pid = processRun.pid;
348
366
  input.record.process_id = processRun.pid;
@@ -379,6 +397,7 @@ class NativeCliSessionSwarmRecorder {
379
397
  serviceTier: this.input.fastModePolicy.service_tier,
380
398
  worktree: worktree ? { id: worktree.id, path: worktree.path, branch: worktree.branch } : null,
381
399
  backend: this.input.backend,
400
+ taskTitle: String(input.ctx.slice?.title || input.ctx.slice?.description || input.ctx.slice?.id || '') || null,
382
401
  uiMode,
383
402
  projectRoot: input.ctx.opts.projectRoot || this.input.projectRoot || input.ctx.opts.cwd,
384
403
  rightColumnMode: 'spawn-on-first-worker',
@@ -484,7 +503,23 @@ class NativeCliSessionSwarmRecorder {
484
503
  session_id: input.ctx.agent.session_id,
485
504
  worker_artifact_dir: input.workerDirRel
486
505
  });
487
- const parsed = await waitForWorkerResult(path.join(this.root, input.resultRel), Number(process.env.SKS_ZELLIJ_WORKER_RESULT_TIMEOUT_MS || 120000));
506
+ const parsed = await this.waitForWorkerResultWithActivity({
507
+ resultPath: path.join(this.root, input.resultRel),
508
+ activityPaths: [
509
+ path.join(this.root, input.heartbeatRel),
510
+ path.join(this.root, input.stdoutRel),
511
+ path.join(this.root, input.stderrRel),
512
+ path.join(this.root, input.workerDirRel, 'codex-sdk-events.jsonl'),
513
+ path.join(this.root, input.workerDirRel, 'python-codex-sdk-events.jsonl'),
514
+ path.join(this.root, input.workerDirRel, 'local-llm-events.jsonl')
515
+ ],
516
+ stdoutPath: path.join(this.root, input.stdoutRel),
517
+ ctx: input.ctx,
518
+ heartbeatRel: input.heartbeatRel,
519
+ resultRel: input.resultRel,
520
+ stdoutRel: input.stdoutRel,
521
+ stderrRel: input.stderrRel
522
+ });
488
523
  const compactExit = processRun ? await processRun.wait(parsed ? 10000 : 1000) : null;
489
524
  this.active.delete(activeToken);
490
525
  input.record.closed_at = nowIso();
@@ -525,6 +560,15 @@ class NativeCliSessionSwarmRecorder {
525
560
  }
526
561
  input.record.pid = Number(workerProcessReport?.pid || processRun?.pid) || null;
527
562
  input.record.process_id = input.record.pid;
563
+ if (!loopHandle && input.record.pid) {
564
+ loopHandle = await registerLoopWorkerHandle({
565
+ root: input.ctx.opts.projectRoot || this.input.projectRoot || input.ctx.opts.cwd || packageRoot(),
566
+ env: input.ctx.opts.env || {},
567
+ agentId: String(input.ctx.agent.id || input.ctx.agent.session_id || 'agent'),
568
+ sessionId: input.ctx.agent.session_id || null,
569
+ pid: input.record.pid
570
+ });
571
+ }
528
572
  input.record.compact_worker_exit_code = compactExit?.code ?? null;
529
573
  input.record.compact_worker_signal = compactExit?.signal ?? null;
530
574
  input.record.sdk_thread_id = sdkThreadId;
@@ -533,6 +577,9 @@ class NativeCliSessionSwarmRecorder {
533
577
  input.record.structured_output_valid = workerProcessReport?.structured_output_valid === true || workerProcessReport?.backend_router_report?.structured_output_valid === true;
534
578
  input.record.exit_code = parsed ? (parsed.status === 'done' ? 0 : 1) : 1;
535
579
  input.record.status = parsed?.status === 'done' ? 'closed' : 'failed';
580
+ if (loopHandle) {
581
+ await markLoopWorkerInterrupted(input.ctx.opts.projectRoot || this.input.projectRoot || input.ctx.opts.cwd || packageRoot(), loopHandle.mission_id, loopHandle.worker_id, input.record.status === 'closed' ? 'completed' : 'failed').catch(() => undefined);
582
+ }
536
583
  const heartbeatOk = await hasHeartbeat(path.join(this.root, input.heartbeatRel));
537
584
  input.record.blockers = [
538
585
  ...(parsed ? parsed.blockers || [] : ['zellij_worker_result_timeout']),
@@ -593,6 +640,45 @@ class NativeCliSessionSwarmRecorder {
593
640
  artifacts: [...new Set([...(Array.isArray(parsed.artifacts) ? parsed.artifacts : []), input.stdoutRel, input.stderrRel, path.join(input.workerDirRel, 'zellij-worker-pane.json')])]
594
641
  });
595
642
  }
643
+ // Root-cause-2 fix: a fixed 2-minute wall-clock result timeout killed live codex-sdk
644
+ // workers (real runs exceed 2 min), marking false zellij_worker_result_timeout failures and
645
+ // freezing the UI while the worker kept running. Replace it with an activity-aware wait: keep
646
+ // waiting as long as ANY worker artifact (heartbeat, stdout/stderr, sdk event jsonl) was touched
647
+ // recently. Only give up after SKS_ZELLIJ_WORKER_INACTIVITY_TIMEOUT_MS of silence (default 5min)
648
+ // OR an absolute cap SKS_ZELLIJ_WORKER_RESULT_TIMEOUT_MS (default 1h; 0 = no cap). While waiting,
649
+ // emit a heartbeat telemetry event every ~10s so the SLOTS snapshot updated_at stays fresh from
650
+ // the orchestrator side too.
651
+ async waitForWorkerResultWithActivity(input) {
652
+ const inactivityTimeoutMs = Math.max(1000, Number(process.env.SKS_ZELLIJ_WORKER_INACTIVITY_TIMEOUT_MS || 300000));
653
+ const absoluteCapRaw = Number(process.env.SKS_ZELLIJ_WORKER_RESULT_TIMEOUT_MS ?? 3600000);
654
+ const absoluteCapMs = Number.isFinite(absoluteCapRaw) ? absoluteCapRaw : 3600000;
655
+ const start = Date.now();
656
+ let lastActivityMs = start;
657
+ let lastHeartbeatEmit = 0;
658
+ for (;;) {
659
+ const result = await readJson(input.resultPath, null).catch(() => null);
660
+ if (result)
661
+ return result;
662
+ const now = Date.now();
663
+ const newestActivity = await newestMtimeMs(input.activityPaths);
664
+ if (newestActivity != null && newestActivity > lastActivityMs)
665
+ lastActivityMs = newestActivity;
666
+ if (absoluteCapMs > 0 && now - start >= absoluteCapMs)
667
+ return null;
668
+ if (now - lastActivityMs >= inactivityTimeoutMs)
669
+ return null;
670
+ if (now - lastHeartbeatEmit >= 10000) {
671
+ lastHeartbeatEmit = now;
672
+ await this.telemetry(input.ctx, {
673
+ eventType: 'heartbeat',
674
+ status: 'running',
675
+ artifacts: [input.heartbeatRel, input.resultRel, input.stdoutRel, input.stderrRel],
676
+ logTail: await tailFile(input.stdoutPath, 600)
677
+ });
678
+ }
679
+ await new Promise((resolve) => setTimeout(resolve, 250));
680
+ }
681
+ }
596
682
  async finalize() {
597
683
  await this.persist();
598
684
  return this.summary();
@@ -777,15 +863,19 @@ function buildPaneWorkerHeader(input) {
777
863
  'status: running'
778
864
  ].join('\n');
779
865
  }
780
- async function waitForWorkerResult(file, timeoutMs) {
781
- const deadline = Date.now() + Math.max(1000, timeoutMs);
782
- while (Date.now() < deadline) {
783
- const result = await readJson(file, null).catch(() => null);
784
- if (result)
785
- return result;
786
- await new Promise((resolve) => setTimeout(resolve, 250));
866
+ async function newestMtimeMs(files) {
867
+ let newest = null;
868
+ for (const file of files) {
869
+ try {
870
+ const mtime = (await fs.promises.stat(file)).mtimeMs;
871
+ if (newest == null || mtime > newest)
872
+ newest = mtime;
873
+ }
874
+ catch {
875
+ // missing file: no activity signal from it
876
+ }
787
877
  }
788
- return null;
878
+ return newest;
789
879
  }
790
880
  async function waitForWorkerHeartbeat(file, timeoutMs) {
791
881
  const deadline = Date.now() + Math.max(1000, timeoutMs);
@@ -855,6 +945,22 @@ function normalizeParallelPlacement(value) {
855
945
  return text;
856
946
  return 'unknown';
857
947
  }
948
+ async function registerLoopWorkerHandle(input) {
949
+ const missionId = String(input.env.SKS_MISSION_ID || input.env.SKS_PARENT_MISSION_ID || '').trim();
950
+ const loopId = String(input.env.SKS_LOOP_ID || '').trim();
951
+ const phase = String(input.env.SKS_LOOP_PHASE || '').trim();
952
+ if (!missionId || !loopId || (phase !== 'maker' && phase !== 'checker'))
953
+ return null;
954
+ return registerLoopActiveWorker(input.root, {
955
+ mission_id: missionId,
956
+ loop_id: loopId,
957
+ phase,
958
+ worker_id: input.agentId,
959
+ session_id: input.sessionId,
960
+ pid: input.pid,
961
+ interrupt_supported: Boolean(input.pid || input.sessionId)
962
+ }).catch(() => null);
963
+ }
858
964
  async function tailFile(file, max) {
859
965
  try {
860
966
  const text = await fs.promises.readFile(file, 'utf8');
@@ -3,6 +3,7 @@ import { nowIso, readJson, readText, writeJsonAtomic } from '../fsx.js';
3
3
  export const NO_SUBAGENT_SCALING_POLICY_SCHEMA = 'sks.no-subagent-scaling-policy.v1';
4
4
  export async function writeNoSubagentScalingPolicy(root, input = {}) {
5
5
  const nativeProof = input.nativeProof || await readJson(path.join(root, 'native-cli-session-proof.json'), null);
6
+ const officialSubagentHelperPolicy = input.officialSubagentHelperPolicy || await readJson(path.join(root, 'official-subagent-helper-policy.json'), null);
6
7
  const swarm = await readJson(path.join(root, 'agent-native-cli-session-swarm.json'), null);
7
8
  const events = await readText(path.join(root, 'agent-events.jsonl'), '');
8
9
  const subagentEventCount = String(events).split(/\n/).filter((line) => /Subagent(Start|Stop)|subagent/i.test(line)).length;
@@ -11,7 +12,8 @@ export async function writeNoSubagentScalingPolicy(root, input = {}) {
11
12
  const blockers = [
12
13
  ...(allowedScalingPrimitives.has(String(swarm?.scaling_primitive || '')) ? [] : ['main_scaling_primitive_not_native_cli_process']),
13
14
  ...(nativeProcessCount > 0 ? [] : ['native_cli_worker_process_proof_missing']),
14
- ...(nativeProof?.worker_proof_is_only_subagent_events === true ? ['worker_proof_only_subagent_events'] : [])
15
+ ...(nativeProof?.worker_proof_is_only_subagent_events === true ? ['worker_proof_only_subagent_events'] : []),
16
+ ...(officialSubagentHelperPolicy?.ok === false ? officialSubagentHelperPolicy.blockers || ['official_subagent_helper_policy_not_ok'] : [])
15
17
  ];
16
18
  const report = {
17
19
  schema: NO_SUBAGENT_SCALING_POLICY_SCHEMA,
@@ -21,6 +23,13 @@ export async function writeNoSubagentScalingPolicy(root, input = {}) {
21
23
  subagent_events_counted_as_worker_sessions: false,
22
24
  scout_events_counted_as_worker_sessions: false,
23
25
  worker_internal_scout_usage_allowed_as_helper_only: true,
26
+ official_codex_subagent_helper_lane_allowed: officialSubagentHelperPolicy?.ok !== false,
27
+ official_codex_subagent_helper_policy: officialSubagentHelperPolicy ? 'official-subagent-helper-policy.json' : null,
28
+ official_helper_lane_worker_capacity_credit: 0,
29
+ official_helper_lane_observed_subagent_event_count: Number(officialSubagentHelperPolicy?.observed_subagent_event_count || 0),
30
+ official_helper_lane_events_counted_as_worker_sessions: false,
31
+ codex_builtin_capability_lane_allowed: officialSubagentHelperPolicy?.official_codex_subagent_helper_lane_enabled === true,
32
+ codex_builtin_imagegen_helper_allowed: officialSubagentHelperPolicy?.codex_builtin_imagegen_helper_allowed === true,
24
33
  native_worker_process_count: nativeProcessCount,
25
34
  subagent_event_count: subagentEventCount,
26
35
  worker_process_proof: 'native-cli-session-proof.json',
@@ -0,0 +1,62 @@
1
+ import path from 'node:path';
2
+ import { nowIso, readJson, readText, writeJsonAtomic } from '../fsx.js';
3
+ export const OFFICIAL_SUBAGENT_HELPER_POLICY_SCHEMA = 'sks.official-subagent-helper-policy.v1';
4
+ export async function writeOfficialSubagentHelperPolicy(root, input = {}) {
5
+ const nativeProof = input.nativeProof || await readJson(path.join(root, 'native-cli-session-proof.json'), null);
6
+ const events = await readText(path.join(root, 'agent-events.jsonl'), '');
7
+ const subagentEventCount = String(events).split(/\n/).filter((line) => /Subagent(Start|Stop)|subagent/i.test(line)).length;
8
+ const nativeProcessCount = Number(nativeProof?.spawned_worker_process_count || 0);
9
+ const workerCapacityCredit = 0;
10
+ const subagentEventsCountedAsWorkerSessions = false;
11
+ const builtInAgentsAllowed = ['default', 'worker', 'explorer'];
12
+ const codexAppCapabilitiesAllowed = [
13
+ 'image_generation',
14
+ 'computer_use_for_native_app_targets',
15
+ 'in_app_browser_for_allowed_web_targets',
16
+ 'web_search',
17
+ 'mcp_tools',
18
+ 'product_design_plugin'
19
+ ];
20
+ const requiredOutputProofForGeneratedImages = ['path', 'sha256', 'bytes', 'dimensions', 'model_or_surface'];
21
+ const blockers = [
22
+ ...(workerCapacityCredit === 0 ? [] : ['official_helper_worker_capacity_credit_not_zero']),
23
+ ...(subagentEventsCountedAsWorkerSessions === false ? [] : ['official_helper_subagent_events_counted_as_worker_sessions']),
24
+ ...(codexAppCapabilitiesAllowed.includes('image_generation') ? [] : ['official_helper_image_generation_capability_missing']),
25
+ ...(requiredOutputProofForGeneratedImages.includes('sha256') && requiredOutputProofForGeneratedImages.includes('dimensions')
26
+ ? []
27
+ : ['official_helper_image_output_proof_incomplete'])
28
+ ];
29
+ const report = {
30
+ schema: OFFICIAL_SUBAGENT_HELPER_POLICY_SCHEMA,
31
+ generated_at: nowIso(),
32
+ ok: blockers.length === 0,
33
+ official_codex_subagent_helper_lane_enabled: true,
34
+ official_codex_subagents_allowed: true,
35
+ helper_lane_role: 'capability_helper_only',
36
+ helper_lane_may_run_in_parallel_with_native_workers: true,
37
+ worker_capacity_source: 'native-cli-session-proof.json',
38
+ worker_capacity_credit: workerCapacityCredit,
39
+ subagent_events_counted_as_worker_sessions: subagentEventsCountedAsWorkerSessions,
40
+ native_worker_process_count: nativeProcessCount,
41
+ observed_subagent_event_count: subagentEventCount,
42
+ built_in_agents_allowed: builtInAgentsAllowed,
43
+ codex_app_capabilities_allowed: codexAppCapabilitiesAllowed,
44
+ codex_builtin_imagegen_helper_allowed: true,
45
+ preferred_image_generation_surface: 'Codex App $imagegen/gpt-image-2',
46
+ codex_app_builtin_evidence_class: 'codex_app_builtin',
47
+ api_fallback_evidence_class: 'api_fallback',
48
+ provider_surface_evidence_required: true,
49
+ imagegen_api_fallback_counts_as_codex_app_evidence: false,
50
+ required_output_proof_for_generated_images: requiredOutputProofForGeneratedImages,
51
+ proof_rule: 'Official Codex subagents and app tools may assist native workers, but never increase requested_agents, target_active_slots, spawned_worker_process_count, or max_observed_worker_process_count.',
52
+ limitations: [
53
+ 'helper_subagent_events_do_not_prove_worker_capacity',
54
+ 'codex_app_capability_detection_is_not_generated_output_proof',
55
+ 'image_generation_claims_require_real_raster_output_evidence'
56
+ ],
57
+ blockers
58
+ };
59
+ await writeJsonAtomic(path.join(root, 'official-subagent-helper-policy.json'), report);
60
+ return report;
61
+ }
62
+ //# sourceMappingURL=official-subagent-helper-policy.js.map
@@ -809,8 +809,6 @@ async function codexFastModeConfigStatus(opts = {}) {
809
809
  blockers.push('user.fast_mode.visible_missing');
810
810
  if (!/(^|\n)\s*enabled\s*=\s*true\s*(?:#.*)?(?=\n|$)/.test(fastMode))
811
811
  blockers.push('user.fast_mode.enabled_missing');
812
- if (!/(^|\n)\s*default_profile\s*=\s*"sks-fast-high"\s*(?:#.*)?(?=\n|$)/.test(fastMode))
813
- blockers.push('user.fast_mode.default_profile_missing');
814
812
  return {
815
813
  ok: blockers.length === 0,
816
814
  checked: true,
@@ -290,6 +290,15 @@ async function runLocalControlTask(root, task, schema, routerDecision) {
290
290
  ...(validation.ok ? [] : ['local_llm_structured_output_invalid', ...validation.issues.map((issue) => `schema:${issue}`)])
291
291
  ];
292
292
  const workerResult = normalizeWorkerResult(structuredOutput, task, finalBlockers, validation.ok, 'local-llm');
293
+ // Stamp the local-llm request id as backend proof on model-authored patch
294
+ // envelopes; without it agent-patch-schema rejects every local-llm patch
295
+ // with model_authored_backend_proof_missing (the model cannot know the id).
296
+ if (Array.isArray(workerResult.patch_envelopes)) {
297
+ workerResult.patch_envelopes = workerResult.patch_envelopes.map((envelope) => ({
298
+ ...envelope,
299
+ backend_ollama_request_id: envelope?.backend_ollama_request_id || adapterResult.requestId
300
+ }));
301
+ }
293
302
  const workerResultPath = path.join(root, 'local-llm-worker-result.json');
294
303
  await writeJsonAtomic(workerResultPath, workerResult);
295
304
  const patchEnvelopePath = Array.isArray(workerResult.patch_envelopes) && workerResult.patch_envelopes.length
@@ -62,7 +62,7 @@ export async function fastModeCommand(args = []) {
62
62
  else if (action === 'clear')
63
63
  console.log(`Cleared: ${removed ? 'yes' : 'already default'}`);
64
64
  else if (!preference)
65
- console.log('Preference: implicit fast');
65
+ console.log('Preference: implicit standard');
66
66
  console.log('Dollar: $Fast-On | $Fast-Off | $Fast-Mode');
67
67
  return result;
68
68
  }
@@ -1,12 +1,15 @@
1
1
  import path from 'node:path';
2
2
  import { printJson } from '../../cli/output.js';
3
3
  import { createMission, findLatestMission, loadMission, setCurrent } from '../mission.js';
4
- import { readJson, sksRoot, writeJsonAtomic } from '../fsx.js';
5
- import { loopGraphProofPath, loopPlanPath, loopRoot } from '../loops/loop-artifacts.js';
4
+ import { readJson, sksRoot } from '../fsx.js';
5
+ import { loopActiveWorkerHandlesPath, loopIntegrationMergePath, loopLatestCheckpointPath, loopPlanPath, loopProofPath, loopRoot, loopSideEffectReportPath } from '../loops/loop-artifacts.js';
6
+ import { finalizeLoopGraph } from '../loops/loop-finalizer.js';
6
7
  import { readLoopGraphProof } from '../loops/loop-observability.js';
7
8
  import { planLoopsFromRequest } from '../loops/loop-planner.js';
8
9
  import { renderLoopProofSummary } from '../loops/loop-proof-summary.js';
9
- import { runLoopPlan } from '../loops/loop-runtime.js';
10
+ import { runLoopNode, runLoopPlan } from '../loops/loop-runtime.js';
11
+ import { scheduleLoopGraph } from '../loops/loop-scheduler.js';
12
+ import { writeLoopKillRequest } from '../loops/loop-runtime-control.js';
10
13
  import { flag, promptOf, readFlagValue } from './command-utils.js';
11
14
  export async function loopCommand(subcommand = 'help', args = []) {
12
15
  const action = subcommand || 'help';
@@ -21,7 +24,7 @@ export async function loopCommand(subcommand = 'help', args = []) {
21
24
  if (action === 'kill')
22
25
  return loopKill(args);
23
26
  if (action === 'resume')
24
- return loopRun(args);
27
+ return loopResume(args);
25
28
  if (action === 'graph')
26
29
  return loopGraph(args);
27
30
  console.log(`SKS Loop
@@ -32,7 +35,7 @@ Usage:
32
35
  sks loop status latest [--json]
33
36
  sks loop proof latest [--json]
34
37
  sks loop kill <loop-id|all>
35
- sks loop resume latest
38
+ sks loop resume latest [--rerun-completed]
36
39
  sks loop graph latest
37
40
  `);
38
41
  }
@@ -78,13 +81,33 @@ async function loopStatus(args) {
78
81
  const plan = await readJson(loopPlanPath(root, missionId), null);
79
82
  const proof = await readLoopGraphProof(root, missionId);
80
83
  const states = await Promise.all((plan?.graph.nodes || []).map((node) => readJson(path.join(loopRoot(root, missionId), node.loop_id, 'loop-state.json'), null)));
81
- const result = { schema: 'sks.loop-status-command.v1', mission_id: missionId, plan_ok: Boolean(plan && plan.blockers.length === 0), graph: proof, states };
84
+ const proofs = await Promise.all((plan?.graph.nodes || []).map((node) => readJson(loopProofPath(root, missionId, node.loop_id), null)));
85
+ const checkpoints = await Promise.all((plan?.graph.nodes || []).map((node) => readJson(loopLatestCheckpointPath(root, missionId, node.loop_id), null)));
86
+ const activeWorkerHandles = await readJsonl(loopActiveWorkerHandlesPath(root, missionId));
87
+ const merge = await readJson(loopIntegrationMergePath(root, missionId), null);
88
+ const sideEffects = await readJson(loopSideEffectReportPath(root, missionId), null);
89
+ const result = { schema: 'sks.loop-status-command.v1', mission_id: missionId, plan_ok: Boolean(plan && plan.blockers.length === 0), graph: proof, states, proofs, checkpoints, active_worker_handles: activeWorkerHandles, merge, side_effects: sideEffects };
82
90
  if (flag(args, '--json'))
83
91
  return printJson(result);
84
92
  console.log(`Loop status: ${missionId}`);
85
93
  for (const state of states.filter(Boolean)) {
86
- console.log(` ${String(state.loop_id).padEnd(18)} ${String(state.status).padEnd(10)} iter ${state.iteration} owner ${(Array.isArray(state.acting_on?.files) ? state.acting_on.files.join(', ') : '-')}`);
94
+ const loopId = String(state.loop_id);
95
+ const nodeProof = proofs.find((row) => row?.loop_id === loopId);
96
+ const checkpoint = checkpoints.find((row) => row?.loop_id === loopId);
97
+ const backend = nodeProof?.maker_result?.backend || 'blocked';
98
+ const gates = nodeProof?.gate_result ? `${nodeProof.gate_result.passed_gates.length}/${nodeProof.gate_result.selected_gates.length}` : '-';
99
+ const worktree = nodeProof?.worktree?.id || state.acting_on?.worktree_id || '-';
100
+ const resumable = checkpoint?.resumable ? `resumable:${checkpoint.phase}` : 'resumable:-';
101
+ const active = activeWorkerHandles.filter((row) => row.loop_id === loopId && row.status === 'running').length;
102
+ const mergeStatus = merge?.merge_attempts?.[loopId]?.selected_strategy || '-';
103
+ console.log(` ${loopId.padEnd(18)} ${String(state.status).padEnd(10)} backend ${String(backend).padEnd(24)} workers ${String(active).padEnd(2)} gates ${gates.padEnd(5)} worktree ${String(worktree).padEnd(18)} merge ${String(mergeStatus).padEnd(14)} ${resumable}`);
87
104
  }
105
+ if (merge)
106
+ console.log(`Merge: ${merge.ok ? 'passed' : 'blocked'} strategy=${JSON.stringify(merge.strategy_summary || {})}`);
107
+ if (sideEffects)
108
+ console.log(`Side effects: ${sideEffects.ok ? 'passed' : 'blocked'} blockers=${sideEffects.blockers?.length || 0}`);
109
+ if (proof?.gpt_final_arbiter)
110
+ console.log(`Final arbiter: ${proof.gpt_final_arbiter.verdict || 'unknown'} handled_by=${proof.gpt_final_arbiter.handled_by || 'loop-finalizer'}`);
88
111
  }
89
112
  async function loopProof(args) {
90
113
  const root = await sksRoot();
@@ -97,6 +120,12 @@ async function loopProof(args) {
97
120
  if (flag(args, '--json'))
98
121
  return printJson(proof);
99
122
  console.log(renderLoopProofSummary(proof));
123
+ if (proof.integration_merge)
124
+ console.log(`Merge: ${proof.integration_merge.ok ? 'passed' : 'blocked'} ${JSON.stringify(proof.integration_merge.strategy_summary || {})}`);
125
+ if (proof.side_effect_report)
126
+ console.log(`Side effects: ${proof.side_effect_report.ok ? 'passed' : 'blocked'} ${proof.side_effect_report.blockers?.join(', ') || 'none'}`);
127
+ if (proof.gpt_final_arbiter)
128
+ console.log(`Final arbiter: ${proof.gpt_final_arbiter.verdict || 'unknown'} contract=${proof.gpt_final_arbiter.gate_contract_path || 'missing'}`);
100
129
  }
101
130
  async function loopGraph(args) {
102
131
  const root = await sksRoot();
@@ -112,14 +141,43 @@ async function loopKill(args) {
112
141
  const target = args[0];
113
142
  if (!missionId || !target)
114
143
  throw new Error('Usage: sks loop kill <loop-id|all>');
115
- await writeJsonAtomic(path.join(loopRoot(root, missionId), 'kill-request.json'), {
116
- schema: 'sks.loop-kill-request.v1',
117
- mission_id: missionId,
118
- target,
119
- requested_at: new Date().toISOString()
120
- });
144
+ await writeLoopKillRequest(root, missionId, target);
121
145
  console.log(`Loop kill requested: ${target}`);
122
146
  }
147
+ async function loopResume(args) {
148
+ const root = await sksRoot();
149
+ const missionId = await resolveLoopMission(root, args[0]);
150
+ if (!missionId)
151
+ throw new Error('Usage: sks loop resume <mission-id|latest> [--rerun-completed]');
152
+ const plan = await readJson(loopPlanPath(root, missionId));
153
+ const rerunCompleted = flag(args, '--rerun-completed');
154
+ const existingProofs = await Promise.all(plan.graph.nodes.map((node) => readJson(loopProofPath(root, missionId, node.loop_id), null)));
155
+ const completed = new Set(existingProofs.filter((proof) => proof !== null && proof.status === 'completed').map((proof) => proof.loop_id));
156
+ const runnable = rerunCompleted ? plan.graph.nodes : plan.graph.nodes.filter((node) => !completed.has(node.loop_id));
157
+ const schedule = scheduleLoopGraph(runnable, normalizeParallelism(readFlagValue(args, '--parallelism', 'balanced')));
158
+ const started = Date.now();
159
+ const resumedProofs = [];
160
+ for (const batch of schedule.batches) {
161
+ const batchProofs = await Promise.all(batch.map((node) => runLoopNode({ root, plan, node })));
162
+ resumedProofs.push(...batchProofs);
163
+ }
164
+ const mergedProofs = [
165
+ ...existingProofs.filter((proof) => proof !== null && proof.status === 'completed' && !rerunCompleted),
166
+ ...resumedProofs
167
+ ];
168
+ const graphProof = await finalizeLoopGraph({
169
+ root,
170
+ plan,
171
+ proofs: mergedProofs,
172
+ maxActiveLoops: schedule.max_active_loops,
173
+ maxActiveWorkers: Math.max(1, mergedProofs.reduce((sum, proof) => sum + proof.maker_result.worker_count + proof.checker_result.worker_count, 0)),
174
+ wallMs: Math.max(1, Date.now() - started)
175
+ });
176
+ await setCurrent(root, { mission_id: missionId, mode: 'LOOP', route: 'Loop', route_command: '$Loop', phase: graphProof.ok ? 'LOOP_COMPLETED' : 'LOOP_BLOCKED', stop_gate: 'loop-graph-proof.json' });
177
+ if (flag(args, '--json'))
178
+ return printJson({ schema: 'sks.loop-resume-command.v1', ok: graphProof.ok, mission_id: missionId, resumed_loops: resumedProofs.map((proof) => proof.loop_id), skipped_completed: [...completed], graph_proof: graphProof });
179
+ console.log(renderLoopProofSummary(graphProof));
180
+ }
123
181
  async function resolveLoopMission(root, arg) {
124
182
  if (arg && arg !== 'latest')
125
183
  return arg;
@@ -132,4 +190,19 @@ async function resolveLoopMission(root, arg) {
132
190
  function normalizeParallelism(value) {
133
191
  return value === 'safe' || value === 'extreme' ? value : 'balanced';
134
192
  }
193
+ async function readJsonl(file) {
194
+ const text = await import('node:fs/promises').then((fs) => fs.readFile(file, 'utf8')).catch(() => '');
195
+ return String(text).split(/\r?\n/)
196
+ .map((line) => line.trim())
197
+ .filter(Boolean)
198
+ .map((line) => {
199
+ try {
200
+ return JSON.parse(line);
201
+ }
202
+ catch {
203
+ return null;
204
+ }
205
+ })
206
+ .filter(Boolean);
207
+ }
135
208
  //# sourceMappingURL=loop-command.js.map
@@ -5,6 +5,7 @@ import { runNativeAgentOrchestrator } from '../agents/agent-orchestrator.js';
5
5
  import { classifyOllamaWorkerSlice } from '../agents/agent-runner-ollama.js';
6
6
  import { buildNarutoCloneRoster, systemSafeNarutoConcurrency } from '../agents/agent-roster.js';
7
7
  import { DEFAULT_NARUTO_CLONES, MAX_NARUTO_AGENT_COUNT } from '../agents/agent-schema.js';
8
+ import { normalizeServiceTier } from '../agents/fast-mode-policy.js';
8
9
  import { resolveOllamaWorkerConfig } from '../agents/ollama-worker-config.js';
9
10
  import { attachZellijSessionInteractive, launchZellijLayout } from '../zellij/zellij-launcher.js';
10
11
  import { maybePromptZellijUpdateForLaunch } from '../zellij/zellij-update.js';
@@ -380,11 +381,13 @@ async function narutoRun(parsed) {
380
381
  workerPlacement: parsed.json || parsed.noOpenZellij ? 'process' : 'zellij-pane',
381
382
  zellijPaneWorker: true,
382
383
  zellijVisiblePaneCap: zellijVisiblePanes,
383
- // Shadow clones ALWAYS run in fast service tier — never honor --no-fast/standard.
384
- fastMode: true,
385
- serviceTier: 'fast',
386
- noFast: false,
384
+ ...(parsed.fastMode === undefined ? {} : { fastMode: parsed.fastMode }),
385
+ ...(parsed.serviceTier === undefined ? {} : { serviceTier: parsed.serviceTier }),
386
+ noFast: parsed.noFast,
387
387
  writeMode: writeCapable ? parsed.writeMode || 'parallel' : 'off',
388
+ applyPatches: parsed.applyPatches,
389
+ dryRunPatches: parsed.dryRunPatches,
390
+ maxWriteAgents: parsed.maxWriteAgents,
388
391
  gitWorktreePolicy: worktreePolicy,
389
392
  narutoWorkGraph: workGraph,
390
393
  narutoAllocationPolicy: allocationPolicy,
@@ -418,18 +421,6 @@ async function narutoRun(parsed) {
418
421
  blockers: [...(result.proof?.blockers || []), ...(parallelRuntimeOk ? [] : ['naruto_parallel_runtime_proof_below_gate'])],
419
422
  updated_at: nowIso()
420
423
  });
421
- await setCurrent(root, {
422
- mission_id: mission.id,
423
- route: 'Naruto',
424
- route_command: '$Naruto',
425
- mode: 'NARUTO',
426
- phase: result.ok === true ? 'NARUTO_COMPLETE_OR_REVIEW' : 'NARUTO_BLOCKED',
427
- native_sessions_verified: nativeProofOk,
428
- subagents_verified: nativeProofOk,
429
- naruto_gate_file: 'naruto-gate.json',
430
- stop_gate: 'naruto-gate.json',
431
- prompt: parsed.prompt
432
- });
433
424
  const clones = result.roster?.agent_count ?? roster.agent_count;
434
425
  const localWorkerSummary = summarizeNarutoLocalWorkerResult(localWorker, result);
435
426
  // Finalizer policy: when local LLM workers contributed patches, the GPT
@@ -437,16 +428,29 @@ async function narutoRun(parsed) {
437
428
  const finalizer = evaluateNarutoFinalizer({
438
429
  localParticipated: Number(localWorkerSummary?.selected_worker_count || 0) > 0,
439
430
  gptFinalStatus: result.proof?.gpt_final_status || null,
440
- applyPatches: writeCapable
431
+ applyPatches: parsed.applyPatches
441
432
  });
442
433
  await writeJsonAtomic(path.join(mission.dir, 'naruto-finalizer.json'), {
443
434
  ...finalizer,
444
435
  generated_at: nowIso(),
445
436
  mission_id: mission.id
446
437
  });
438
+ const summaryOk = result.ok === true && (parsed.applyPatches === true ? finalizer.ok === true : finalizer.run_ok === true);
439
+ await setCurrent(root, {
440
+ mission_id: mission.id,
441
+ route: 'Naruto',
442
+ route_command: '$Naruto',
443
+ mode: 'NARUTO',
444
+ phase: summaryOk ? 'NARUTO_COMPLETE_OR_REVIEW' : 'NARUTO_BLOCKED',
445
+ native_sessions_verified: nativeProofOk,
446
+ subagents_verified: nativeProofOk,
447
+ naruto_gate_file: 'naruto-gate.json',
448
+ stop_gate: 'naruto-gate.json',
449
+ prompt: parsed.prompt
450
+ });
447
451
  const summary = {
448
452
  schema: NARUTO_RESULT_SCHEMA,
449
- ok: result.ok === true,
453
+ ok: summaryOk,
450
454
  mode: 'NARUTO',
451
455
  jutsu: 'kage_bunshin_no_jutsu',
452
456
  mission_id: result.mission_id,
@@ -502,6 +506,7 @@ async function narutoRun(parsed) {
502
506
  headless_workers: parallelRuntime.headless_workers,
503
507
  passed: parallelRuntime.passed
504
508
  } : null,
509
+ parallel_write_policy: result.parallel_write_policy || null,
505
510
  local_worker: localWorkerSummary,
506
511
  finalizer,
507
512
  proof: result.proof?.status || 'missing',
@@ -538,6 +543,7 @@ function compactNarutoRunResult(result) {
538
543
  mission_id: result?.mission_id || null,
539
544
  route: result?.route || NARUTO_ROUTE,
540
545
  backend: result?.backend || null,
546
+ parallel_write_policy: result?.parallel_write_policy || null,
541
547
  target_active_slots: result?.target_active_slots ?? null,
542
548
  proof: result?.proof ? {
543
549
  ok: result.proof.ok === true,
@@ -754,7 +760,7 @@ async function narutoHelp(parsed) {
754
760
  mode: 'NARUTO',
755
761
  description: 'Shadow Clone Swarm: fan out up to ' + MAX_NARUTO_AGENT_COUNT + ' parallel clone sessions.',
756
762
  usage: [
757
- 'sks naruto run "<task>" [--clones N] [--backend codex-sdk|fake|ollama] [--local-model|--no-ollama] [--work-items N] [--real] [--readonly] [--json]',
763
+ 'sks naruto run "<task>" [--clones N] [--backend codex-sdk|fake|ollama] [--local-model|--no-ollama] [--work-items N] [--write-mode parallel|serial|off] [--apply-patches] [--dry-run-patches] [--real] [--readonly] [--json]',
758
764
  'sks naruto status [--mission <id>] [--json]',
759
765
  'sks naruto proof latest [--messages 20] [--json]'
760
766
  ],
@@ -788,6 +794,13 @@ function parseNarutoArgs(args = []) {
788
794
  const readonly = hasFlag(args, '--readonly') || hasFlag(args, '--read-only');
789
795
  const writeModeRaw = String(readOption(args, '--write-mode', hasFlag(args, '--parallel-write') ? 'parallel' : '') || '');
790
796
  const writeMode = (['proof-safe', 'parallel', 'serial', 'off'].includes(writeModeRaw) ? writeModeRaw : null);
797
+ const applyPatches = hasFlag(args, '--apply-patches');
798
+ const dryRunPatches = hasFlag(args, '--dry-run-patches') || hasFlag(args, '--dry-run-patch');
799
+ const maxWriteAgents = Math.max(0, Math.floor(Number(readOption(args, '--max-write-agents', '0')) || 0));
800
+ const explicitServiceTier = String(readOption(args, '--service-tier', '') || '');
801
+ const serviceTier = normalizeServiceTier(explicitServiceTier, null) || undefined;
802
+ const fastMode = hasFlag(args, '--no-fast') || serviceTier === 'standard' ? false : hasFlag(args, '--fast') ? true : undefined;
803
+ const noFast = hasFlag(args, '--no-fast');
791
804
  const positionalMission = action === 'dashboard' || action === 'workers' || action === 'status' || action === 'proof'
792
805
  ? positionalArgs(rest, new Set()).find((arg) => /^latest$|^M-/.test(arg))
793
806
  : null;
@@ -799,9 +812,9 @@ function parseNarutoArgs(args = []) {
799
812
  const smoke = hasFlag(args, '--smoke');
800
813
  const parallelism = normalizeParallelism(readOption(args, '--parallelism', 'extreme'));
801
814
  const messages = normalizeMessages(readOption(args, '--messages', '8'));
802
- const valueFlags = new Set(['--clones', '--agents', '--work-items', '--concurrency', '--target-active-slots', '--backend', '--write-mode', '--mission', '--mission-id', '--ollama-model', '--local-model-model', '--ollama-base-url', '--local-model-base-url', '--parallelism', '--messages']);
815
+ const valueFlags = new Set(['--clones', '--agents', '--work-items', '--concurrency', '--target-active-slots', '--backend', '--write-mode', '--max-write-agents', '--service-tier', '--mission', '--mission-id', '--ollama-model', '--local-model-model', '--ollama-base-url', '--local-model-base-url', '--parallelism', '--messages']);
803
816
  const prompt = positionalArgs(rest, valueFlags).join(' ').trim() || 'Naruto shadow clone swarm run';
804
- return { action, prompt, clones, workItems, concurrency, backend, backendExplicit, mock, real, readonly, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, writeMode, json, missionId, noOpenZellij, attach, smoke, parallelism, messages };
817
+ return { action, prompt, clones, workItems, concurrency, backend, backendExplicit, mock, real, readonly, ollamaEnabled: useOllama && !noOllama, noOllama, ollamaModel, ollamaBaseUrl, writeMode, applyPatches, dryRunPatches, maxWriteAgents, fastMode, serviceTier, noFast, json, missionId, noOpenZellij, attach, smoke, parallelism, messages };
805
818
  }
806
819
  function normalizeParallelism(value) {
807
820
  const text = String(value || 'extreme').toLowerCase();
@@ -25,6 +25,7 @@ async function redirectTeamCreateToNaruto(args = []) {
25
25
  redirected_to: 'sks naruto run',
26
26
  route_command: '$Naruto',
27
27
  deprecated_route: '$Team',
28
+ parallel_write_policy: result?.parallel_write_policy || result?.run?.parallel_write_policy || null,
28
29
  created_at: nowIso(),
29
30
  args: list
30
31
  });