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.
- package/README.md +1 -1
- package/crates/sks-core/Cargo.lock +1 -1
- package/crates/sks-core/Cargo.toml +1 -1
- package/crates/sks-core/src/main.rs +1 -1
- package/dist/.sks-build-stamp.json +4 -4
- package/dist/bin/sks.js +1 -1
- package/dist/cli/install-helpers.js +6 -7
- package/dist/commands/zellij-slot-column-anchor.js +3 -1
- package/dist/commands/zellij-slot-pane.js +19 -2
- package/dist/core/agents/agent-janitor.js +10 -1
- package/dist/core/agents/agent-orchestrator.js +8 -2
- package/dist/core/agents/agent-proof-evidence.js +20 -0
- package/dist/core/agents/agent-runner-ollama.js +11 -4
- package/dist/core/agents/fast-mode-policy.js +7 -5
- package/dist/core/agents/intelligent-work-graph.js +93 -14
- package/dist/core/agents/native-cli-session-swarm.js +115 -9
- package/dist/core/agents/no-subagent-scaling-policy.js +10 -1
- package/dist/core/agents/official-subagent-helper-policy.js +62 -0
- package/dist/core/codex-app.js +0 -2
- package/dist/core/codex-control/codex-task-runner.js +9 -0
- package/dist/core/commands/fast-mode-command.js +1 -1
- package/dist/core/commands/loop-command.js +86 -13
- package/dist/core/commands/naruto-command.js +34 -21
- package/dist/core/commands/team-command.js +1 -0
- package/dist/core/commands/wiki-command.js +35 -1
- package/dist/core/fsx.js +1 -1
- package/dist/core/init.js +1 -2
- package/dist/core/locks/file-lock.js +88 -0
- package/dist/core/loops/loop-artifacts.js +54 -2
- package/dist/core/loops/loop-checkpoint.js +22 -0
- package/dist/core/loops/loop-concurrency-budget.js +55 -0
- package/dist/core/loops/loop-final-arbiter-contract.js +28 -0
- package/dist/core/loops/loop-finalizer.js +55 -7
- package/dist/core/loops/loop-fixture-policy.js +58 -0
- package/dist/core/loops/loop-gate-registry.js +96 -0
- package/dist/core/loops/loop-gate-runner.js +206 -17
- package/dist/core/loops/loop-gpt-final-arbiter.js +81 -0
- package/dist/core/loops/loop-integration-merge.js +80 -0
- package/dist/core/loops/loop-interrupt-registry.js +118 -0
- package/dist/core/loops/loop-lease.js +35 -20
- package/dist/core/loops/loop-merge-strategy.js +105 -0
- package/dist/core/loops/loop-mutation-ledger.js +103 -0
- package/dist/core/loops/loop-planner.js +36 -5
- package/dist/core/loops/loop-runtime-control.js +27 -0
- package/dist/core/loops/loop-runtime.js +254 -96
- package/dist/core/loops/loop-scheduler.js +14 -5
- package/dist/core/loops/loop-side-effect-scanner.js +91 -0
- package/dist/core/loops/loop-worker-prompts.js +43 -0
- package/dist/core/loops/loop-worker-runtime.js +281 -0
- package/dist/core/loops/loop-worktree-runtime.js +92 -0
- package/dist/core/naruto/naruto-finalizer.js +7 -2
- package/dist/core/naruto/naruto-loop-mesh.js +10 -1
- package/dist/core/proof/auto-finalize.js +3 -2
- package/dist/core/proof/proof-schema.js +6 -0
- package/dist/core/proof/proof-writer.js +5 -2
- package/dist/core/proof/root-cause-policy.js +70 -0
- package/dist/core/proof/route-adapter.js +18 -1
- package/dist/core/proof/route-finalizer.js +71 -6
- package/dist/core/proof/route-proof-gate.js +4 -0
- package/dist/core/release/release-gate-batch-runner.js +56 -10
- package/dist/core/release/release-gate-cache-v2.js +18 -3
- package/dist/core/release/release-gate-dag.js +121 -18
- package/dist/core/release/release-gate-node.js +2 -1
- package/dist/core/release/release-gate-resource-governor.js +27 -6
- package/dist/core/skills/core-skill-meta-update.js +24 -0
- package/dist/core/skills/core-skill-reflection.js +94 -0
- package/dist/core/skills/core-skill-trainer.js +103 -0
- package/dist/core/trust-kernel/completion-contract.js +4 -0
- package/dist/core/trust-kernel/route-contract.js +4 -1
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-right-column-manager.js +13 -2
- package/dist/core/zellij/zellij-slot-column-anchor.js +40 -3
- package/dist/core/zellij/zellij-slot-pane-renderer.js +36 -11
- package/dist/core/zellij/zellij-slot-telemetry.js +96 -44
- package/dist/core/zellij/zellij-worker-pane-manager.js +42 -4
- package/dist/scripts/lib/native-cli-session-swarm-check-lib.js +14 -2
- package/dist/scripts/loop-directive-check-lib.js +225 -2
- package/dist/scripts/loop-hardening-check-lib.js +289 -0
- package/dist/scripts/loop-worker-fixture-child.js +53 -0
- package/dist/scripts/naruto-real-local-gpt-final-smoke.js +10 -1
- package/dist/scripts/prepublish-release-check-or-fast.js +38 -10
- package/dist/scripts/release-check-stamp.js +29 -4
- package/dist/scripts/release-gate-existence-audit.js +1 -0
- 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
|
|
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
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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
|
|
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
|
package/dist/core/codex-app.js
CHANGED
|
@@ -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
|
|
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
|
|
5
|
-
import {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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:
|
|
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:
|
|
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
|
});
|