sneakoscope 2.0.9 → 2.0.10
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 +8 -4
- 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/build-manifest.json +21 -8
- package/dist/cli/command-registry.js +1 -0
- package/dist/commands/doctor.js +18 -1
- package/dist/commands/zellij-slot-pane.js +26 -0
- package/dist/core/agents/agent-orchestrator.js +198 -7
- package/dist/core/agents/agent-role-config.js +92 -0
- package/dist/core/agents/native-cli-session-swarm.js +186 -71
- package/dist/core/commands/naruto-command.js +59 -4
- package/dist/core/doctor/doctor-readiness-matrix.js +6 -0
- package/dist/core/fsx.js +1 -1
- package/dist/core/hooks-runtime.js +4 -0
- package/dist/core/init.js +1 -0
- package/dist/core/naruto/naruto-active-pool.js +35 -2
- package/dist/core/naruto/naruto-concurrency-governor.js +1 -1
- package/dist/core/naruto/naruto-real-worker-child.js +35 -0
- package/dist/core/naruto/naruto-real-worker-runtime.js +121 -0
- package/dist/core/version.js +1 -1
- package/dist/core/zellij/zellij-right-column-manager.js +66 -7
- package/dist/core/zellij/zellij-slot-pane-renderer.js +82 -0
- package/dist/core/zellij/zellij-ui-mode.js +16 -0
- package/dist/core/zellij/zellij-worker-pane-manager.js +38 -8
- package/dist/scripts/agent-role-config-repair-check.js +33 -0
- package/dist/scripts/git-worktree-integration-primary-check.js +4 -2
- package/dist/scripts/git-worktree-integration-primary-runtime-check.js +20 -0
- package/dist/scripts/mutation-callsite-coverage-check.js +2 -1
- package/dist/scripts/naruto-extreme-parallelism-check.js +1 -1
- package/dist/scripts/naruto-extreme-parallelism-real-check.js +42 -0
- package/dist/scripts/naruto-real-active-pool-check.js +3 -2
- package/dist/scripts/naruto-real-active-pool-runtime-check.js +53 -0
- package/dist/scripts/naruto-zellij-dynamic-right-column-check.js +29 -2
- package/dist/scripts/readme-architecture-imagegen-official-check.js +4 -3
- package/dist/scripts/release-check-dynamic-execute.js +27 -1
- package/dist/scripts/release-check-dynamic.js +38 -11
- package/dist/scripts/release-check-stamp.js +7 -2
- package/dist/scripts/release-dynamic-performance-check.js +31 -1
- package/dist/scripts/release-gate-existence-audit.js +29 -33
- package/dist/scripts/release-readiness-report.js +13 -2
- package/dist/scripts/zellij-right-column-geometry-proof.js +155 -22
- package/dist/scripts/zellij-right-column-headless-overflow-check.js +22 -0
- package/dist/scripts/zellij-right-column-manager-check.js +4 -4
- package/dist/scripts/zellij-slot-only-ui-check.js +22 -0
- package/dist/scripts/zellij-slot-pane-renderer-check.js +38 -0
- package/package.json +12 -4
|
@@ -5,8 +5,10 @@ import { appendJsonl, ensureDir, exists, nowIso, packageRoot, readJson, writeJso
|
|
|
5
5
|
import { fastModeEnv } from './fast-mode-policy.js';
|
|
6
6
|
import { validateAgentWorkerResult } from './agent-worker-pipeline.js';
|
|
7
7
|
import { closeWorkerPane, openWorkerPane } from '../zellij/zellij-worker-pane-manager.js';
|
|
8
|
-
import { recordHeadlessWorkerInRightColumn } from '../zellij/zellij-right-column-manager.js';
|
|
8
|
+
import { closeWorkerInRightColumn, recordHeadlessWorkerInRightColumn } from '../zellij/zellij-right-column-manager.js';
|
|
9
9
|
import { resolveProviderContext } from '../provider/provider-context.js';
|
|
10
|
+
import { buildZellijSlotPaneCommand } from '../zellij/zellij-slot-pane-renderer.js';
|
|
11
|
+
import { resolveZellijUiMode } from '../zellij/zellij-ui-mode.js';
|
|
10
12
|
export const NATIVE_CLI_SESSION_SWARM_SCHEMA = 'sks.agent-native-cli-session-swarm.v1';
|
|
11
13
|
export function createNativeCliSessionSwarmRecorder(root, input) {
|
|
12
14
|
return new NativeCliSessionSwarmRecorder(root, input);
|
|
@@ -19,6 +21,7 @@ class NativeCliSessionSwarmRecorder {
|
|
|
19
21
|
maxObserved = 0;
|
|
20
22
|
writeLock = Promise.resolve();
|
|
21
23
|
nextPaneToken = -1;
|
|
24
|
+
visibleZellijReservations = new Set();
|
|
22
25
|
constructor(root, input) {
|
|
23
26
|
this.root = root;
|
|
24
27
|
this.input = input;
|
|
@@ -103,10 +106,12 @@ class NativeCliSessionSwarmRecorder {
|
|
|
103
106
|
const stdout = fs.createWriteStream(path.join(this.root, stdoutRel), { flags: 'a' });
|
|
104
107
|
const stderr = fs.createWriteStream(path.join(this.root, stderrRel), { flags: 'a' });
|
|
105
108
|
const placement = String(ctx.opts.workerPlacement || this.input.workerPlacement || (this.input.backend === 'zellij' ? 'zellij-pane' : 'process'));
|
|
106
|
-
const
|
|
109
|
+
const zellijReservation = placement === 'zellij-pane'
|
|
107
110
|
&& ctx.opts.zellijPaneWorker !== false
|
|
108
111
|
&& (ctx.opts.zellijSessionName || this.input.missionId)
|
|
109
|
-
|
|
112
|
+
? this.reserveVisibleZellijPane(ctx.opts, String(ctx.agent.session_id || ctx.agent.id || `${Date.now()}:${Math.random()}`))
|
|
113
|
+
: null;
|
|
114
|
+
const useZellijPane = Boolean(zellijReservation);
|
|
110
115
|
if (useZellijPane) {
|
|
111
116
|
stdout.end();
|
|
112
117
|
stderr.end();
|
|
@@ -118,7 +123,8 @@ class NativeCliSessionSwarmRecorder {
|
|
|
118
123
|
stdoutRel,
|
|
119
124
|
stderrRel,
|
|
120
125
|
heartbeatRel,
|
|
121
|
-
workerDirRel
|
|
126
|
+
workerDirRel,
|
|
127
|
+
zellijReservation
|
|
122
128
|
});
|
|
123
129
|
}
|
|
124
130
|
if (placement === 'zellij-pane' && ctx.opts.zellijPaneWorker !== false && !useZellijPane) {
|
|
@@ -174,6 +180,17 @@ class NativeCliSessionSwarmRecorder {
|
|
|
174
180
|
record.exit_code = exit.code;
|
|
175
181
|
record.signal = exit.signal;
|
|
176
182
|
record.status = exit.code === 0 ? 'closed' : 'failed';
|
|
183
|
+
if (record.worker_placement === 'headless') {
|
|
184
|
+
await closeWorkerInRightColumn({
|
|
185
|
+
root: this.root,
|
|
186
|
+
projectRoot: ctx.opts.projectRoot || this.input.projectRoot || ctx.opts.cwd,
|
|
187
|
+
missionId: this.input.missionId,
|
|
188
|
+
slotId: String(ctx.agent.slot_id || ctx.agent.id || 'slot-001'),
|
|
189
|
+
generationIndex: Number(ctx.agent.generation_index || 1),
|
|
190
|
+
paneId: null,
|
|
191
|
+
status: record.status === 'closed' ? 'closed' : 'failed'
|
|
192
|
+
}).catch(() => null);
|
|
193
|
+
}
|
|
177
194
|
const parsed = await readJson(path.join(this.root, resultRel), null).catch(() => null);
|
|
178
195
|
if (!parsed) {
|
|
179
196
|
record.blockers = ['native_cli_worker_result_missing'];
|
|
@@ -217,77 +234,102 @@ class NativeCliSessionSwarmRecorder {
|
|
|
217
234
|
route: this.input.route,
|
|
218
235
|
serviceTier: this.input.fastModePolicy.service_tier
|
|
219
236
|
});
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
237
|
+
const uiMode = resolveZellijUiMode(Array.isArray(input.ctx.opts.args) ? input.ctx.opts.args : [], process.env);
|
|
238
|
+
const workerEnv = {
|
|
239
|
+
...(input.ctx.opts.env || {}),
|
|
240
|
+
...fastModeEnv(this.input.fastModePolicy),
|
|
241
|
+
SKS_AGENT_WORKER: '1',
|
|
242
|
+
SKS_PIPELINE_MODE: 'agent-worker',
|
|
243
|
+
SKS_DISABLE_ROUTE_RECURSION: '1',
|
|
244
|
+
SKS_PARENT_MISSION_ID: this.input.missionId,
|
|
245
|
+
SKS_AGENT_SESSION_ID: String(input.ctx.agent.session_id || ''),
|
|
246
|
+
SKS_AGENT_SLOT_ID: slotId,
|
|
247
|
+
SKS_AGENT_GENERATION_INDEX: String(input.ctx.agent.generation_index || 1),
|
|
248
|
+
...(input.ctx.opts.ollamaModel ? { SKS_OLLAMA_MODEL: String(input.ctx.opts.ollamaModel) } : {}),
|
|
249
|
+
...(input.ctx.opts.ollamaBaseUrl ? { SKS_OLLAMA_BASE_URL: String(input.ctx.opts.ollamaBaseUrl) } : {}),
|
|
250
|
+
SKS_ZELLIJ_SESSION_NAME: sessionName
|
|
251
|
+
};
|
|
252
|
+
const role = String(input.ctx.agent.naruto_role || input.ctx.agent.role || input.ctx.agent.persona_id || 'worker');
|
|
253
|
+
const workerCommand = uiMode === 'full-debug'
|
|
254
|
+
? buildPaneWorkerCommand({
|
|
255
|
+
args: input.args,
|
|
256
|
+
stdoutPath: path.join(this.root, input.stdoutRel),
|
|
257
|
+
stderrPath: path.join(this.root, input.stderrRel),
|
|
258
|
+
heartbeatPath: path.join(this.root, input.heartbeatRel),
|
|
259
|
+
header: buildPaneWorkerHeader({
|
|
260
|
+
slotId,
|
|
261
|
+
generationIndex: Number(input.ctx.agent.generation_index || 1),
|
|
262
|
+
role,
|
|
263
|
+
backend: this.input.backend,
|
|
264
|
+
provider: providerContext.provider,
|
|
265
|
+
serviceTier: this.input.fastModePolicy.service_tier,
|
|
266
|
+
worktree,
|
|
267
|
+
task: input.ctx.slice?.description || input.ctx.slice?.title || input.ctx.slice?.id || ''
|
|
268
|
+
}),
|
|
269
|
+
env: {
|
|
270
|
+
...workerEnv,
|
|
271
|
+
SKS_ZELLIJ_WORKER_PANE: '1'
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
: buildZellijSlotPaneCommand({
|
|
275
|
+
cliPath: String(input.args[0] || ''),
|
|
276
|
+
missionId: this.input.missionId,
|
|
226
277
|
slotId,
|
|
227
278
|
generationIndex: Number(input.ctx.agent.generation_index || 1),
|
|
228
|
-
|
|
279
|
+
artifactDir: path.join(this.root, input.workerDirRel),
|
|
229
280
|
backend: this.input.backend,
|
|
230
|
-
|
|
281
|
+
role,
|
|
282
|
+
mode: uiMode,
|
|
283
|
+
watch: true
|
|
284
|
+
});
|
|
285
|
+
let paneRecord;
|
|
286
|
+
try {
|
|
287
|
+
paneRecord = await openWorkerPane({
|
|
288
|
+
root: this.root,
|
|
289
|
+
missionId: this.input.missionId,
|
|
290
|
+
sessionName,
|
|
291
|
+
slotId,
|
|
292
|
+
generationIndex: Number(input.ctx.agent.generation_index || 1),
|
|
293
|
+
sessionId: String(input.ctx.agent.session_id || ''),
|
|
294
|
+
workerArtifactDir: input.workerDirRel,
|
|
295
|
+
workerCommand,
|
|
296
|
+
resultPath: input.resultRel,
|
|
297
|
+
heartbeatPath: input.heartbeatRel,
|
|
298
|
+
patchEnvelopePath: input.record.patch_envelope_path,
|
|
299
|
+
stdoutLog: input.stdoutRel,
|
|
300
|
+
stderrLog: input.stderrRel,
|
|
301
|
+
cwd: workerCwd,
|
|
302
|
+
providerContext,
|
|
231
303
|
serviceTier: this.input.fastModePolicy.service_tier,
|
|
232
|
-
worktree,
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
workerCommand,
|
|
260
|
-
resultPath: input.resultRel,
|
|
261
|
-
heartbeatPath: input.heartbeatRel,
|
|
262
|
-
patchEnvelopePath: input.record.patch_envelope_path,
|
|
263
|
-
stdoutLog: input.stdoutRel,
|
|
264
|
-
stderrLog: input.stderrRel,
|
|
265
|
-
cwd: workerCwd,
|
|
266
|
-
providerContext,
|
|
267
|
-
serviceTier: this.input.fastModePolicy.service_tier,
|
|
268
|
-
worktree: worktree ? { id: worktree.id, path: worktree.path, branch: worktree.branch } : null,
|
|
269
|
-
backend: this.input.backend,
|
|
270
|
-
projectRoot: input.ctx.opts.projectRoot || this.input.projectRoot || input.ctx.opts.cwd,
|
|
271
|
-
rightColumnMode: 'spawn-on-first-worker',
|
|
272
|
-
visiblePaneCap: this.zellijVisiblePaneCap(input.ctx.opts),
|
|
273
|
-
dashboardSnapshot: {
|
|
274
|
-
mode: this.input.route || '$Agent',
|
|
275
|
-
backend_counts: { [this.input.backend]: this.input.targetActiveSlots },
|
|
276
|
-
placement_counts: {
|
|
277
|
-
'zellij-pane': this.zellijVisiblePaneCap(input.ctx.opts),
|
|
278
|
-
headless: Math.max(0, this.input.targetActiveSlots - this.zellijVisiblePaneCap(input.ctx.opts))
|
|
279
|
-
},
|
|
280
|
-
active_workers: this.input.targetActiveSlots,
|
|
281
|
-
visible_panes: this.zellijVisiblePaneCap(input.ctx.opts),
|
|
282
|
-
headless_workers: Math.max(0, this.input.targetActiveSlots - this.zellijVisiblePaneCap(input.ctx.opts)),
|
|
283
|
-
queue_depth: Math.max(0, this.input.requestedAgents - this.input.targetActiveSlots),
|
|
284
|
-
local_llm: { tps: 0, queue: 0 },
|
|
285
|
-
gpt_final_status: 'pending',
|
|
286
|
-
gate_progress: 'worker-spawn'
|
|
287
|
-
}
|
|
288
|
-
});
|
|
304
|
+
worktree: worktree ? { id: worktree.id, path: worktree.path, branch: worktree.branch } : null,
|
|
305
|
+
backend: this.input.backend,
|
|
306
|
+
uiMode,
|
|
307
|
+
projectRoot: input.ctx.opts.projectRoot || this.input.projectRoot || input.ctx.opts.cwd,
|
|
308
|
+
rightColumnMode: 'spawn-on-first-worker',
|
|
309
|
+
visiblePaneCap: this.zellijVisiblePaneCap(input.ctx.opts),
|
|
310
|
+
dashboardSnapshot: {
|
|
311
|
+
mode: this.input.route || '$Agent',
|
|
312
|
+
backend_counts: { [this.input.backend]: this.input.targetActiveSlots },
|
|
313
|
+
placement_counts: {
|
|
314
|
+
'zellij-pane': this.zellijVisiblePaneCap(input.ctx.opts),
|
|
315
|
+
headless: Math.max(0, this.input.targetActiveSlots - this.zellijVisiblePaneCap(input.ctx.opts))
|
|
316
|
+
},
|
|
317
|
+
active_workers: this.input.targetActiveSlots,
|
|
318
|
+
visible_panes: this.zellijVisiblePaneCap(input.ctx.opts),
|
|
319
|
+
headless_workers: Math.max(0, this.input.targetActiveSlots - this.zellijVisiblePaneCap(input.ctx.opts)),
|
|
320
|
+
queue_depth: Math.max(0, this.input.requestedAgents - this.input.targetActiveSlots),
|
|
321
|
+
local_llm: { tps: 0, queue: 0 },
|
|
322
|
+
gpt_final_status: 'pending',
|
|
323
|
+
gate_progress: 'worker-spawn'
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
finally {
|
|
328
|
+
if (input.zellijReservation)
|
|
329
|
+
this.releaseVisibleZellijReservation(input.zellijReservation);
|
|
330
|
+
}
|
|
289
331
|
const launchBlockers = paneRecord.blockers || [];
|
|
290
|
-
input.record.command_line = ['zellij', '--session', sessionName, 'action', 'new-pane', '--direction', paneRecord.direction_applied, '--name', paneRecord.pane_name, '--', 'sh', '-lc', '<native-cli-worker-command>'];
|
|
332
|
+
input.record.command_line = ['zellij', '--session', sessionName, 'action', 'new-pane', '--direction', paneRecord.direction_applied, '--name', paneRecord.pane_name, '--', 'sh', '-lc', uiMode === 'full-debug' ? '<native-cli-worker-command>' : '<zellij-slot-pane-renderer-command>'];
|
|
291
333
|
input.record.zellij_session_name = sessionName;
|
|
292
334
|
input.record.zellij_pane_id = paneRecord.pane_id || null;
|
|
293
335
|
input.record.zellij_pane_id_source = paneRecord.pane_id_source;
|
|
@@ -300,6 +342,8 @@ class NativeCliSessionSwarmRecorder {
|
|
|
300
342
|
input.record.service_tier = paneRecord.service_tier;
|
|
301
343
|
input.record.provider_context = paneRecord.provider_context;
|
|
302
344
|
input.record.worktree = worktree;
|
|
345
|
+
input.record.zellij_ui_mode = uiMode;
|
|
346
|
+
input.record.slot_visualization = uiMode === 'full-debug' ? 'worker-command-pane' : 'zellij-slot-pane-renderer';
|
|
303
347
|
input.record.status = launchBlockers.length ? 'failed' : 'running';
|
|
304
348
|
input.record.blockers = launchBlockers;
|
|
305
349
|
await this.record(input.record);
|
|
@@ -325,6 +369,20 @@ class NativeCliSessionSwarmRecorder {
|
|
|
325
369
|
goal_mode_ref: input.ctx.agent.goal_mode_ref || null
|
|
326
370
|
});
|
|
327
371
|
}
|
|
372
|
+
const processRun = uiMode === 'full-debug'
|
|
373
|
+
? null
|
|
374
|
+
: await this.spawnCompactSlotWorkerProcess({
|
|
375
|
+
args: input.args,
|
|
376
|
+
cwd: workerCwd,
|
|
377
|
+
env: workerEnv,
|
|
378
|
+
stdoutRel: input.stdoutRel,
|
|
379
|
+
stderrRel: input.stderrRel
|
|
380
|
+
});
|
|
381
|
+
if (processRun?.pid) {
|
|
382
|
+
input.record.pid = processRun.pid;
|
|
383
|
+
input.record.process_id = processRun.pid;
|
|
384
|
+
await this.record(input.record);
|
|
385
|
+
}
|
|
328
386
|
await waitForWorkerHeartbeat(path.join(this.root, input.heartbeatRel), Number(process.env.SKS_ZELLIJ_WORKER_HEARTBEAT_TIMEOUT_MS || 30000));
|
|
329
387
|
await appendJsonl(path.join(this.root, input.workerDirRel, 'zellij-worker-pane-events.jsonl'), {
|
|
330
388
|
schema: 'sks.zellij-worker-pane-event.v1',
|
|
@@ -337,6 +395,7 @@ class NativeCliSessionSwarmRecorder {
|
|
|
337
395
|
worker_artifact_dir: input.workerDirRel
|
|
338
396
|
});
|
|
339
397
|
const parsed = await waitForWorkerResult(path.join(this.root, input.resultRel), Number(process.env.SKS_ZELLIJ_WORKER_RESULT_TIMEOUT_MS || 120000));
|
|
398
|
+
const compactExit = processRun ? await processRun.wait(parsed ? 10000 : 1000) : null;
|
|
340
399
|
this.active.delete(activeToken);
|
|
341
400
|
input.record.closed_at = nowIso();
|
|
342
401
|
const workerProcessReport = await readJson(path.join(this.root, input.workerDirRel, 'worker-process-report.json'), null).catch(() => null);
|
|
@@ -368,8 +427,10 @@ class NativeCliSessionSwarmRecorder {
|
|
|
368
427
|
result_path: input.resultRel
|
|
369
428
|
});
|
|
370
429
|
}
|
|
371
|
-
input.record.pid = Number(workerProcessReport?.pid) || null;
|
|
430
|
+
input.record.pid = Number(workerProcessReport?.pid || processRun?.pid) || null;
|
|
372
431
|
input.record.process_id = input.record.pid;
|
|
432
|
+
input.record.compact_worker_exit_code = compactExit?.code ?? null;
|
|
433
|
+
input.record.compact_worker_signal = compactExit?.signal ?? null;
|
|
373
434
|
input.record.sdk_thread_id = sdkThreadId;
|
|
374
435
|
input.record.sdk_run_id = sdkRunId;
|
|
375
436
|
input.record.stream_event_count = Number(workerProcessReport?.stream_event_count || workerProcessReport?.backend_router_report?.stream_event_count || 0);
|
|
@@ -479,6 +540,46 @@ class NativeCliSessionSwarmRecorder {
|
|
|
479
540
|
visibleZellijPaneCount() {
|
|
480
541
|
return this.records.filter((row) => row.scaling_primitive === 'native_cli_process_in_zellij_worker_pane' && (row.status === 'launching' || row.status === 'running')).length;
|
|
481
542
|
}
|
|
543
|
+
reserveVisibleZellijPane(opts = {}, token) {
|
|
544
|
+
const cap = this.zellijVisiblePaneCap(opts);
|
|
545
|
+
if (this.visibleZellijPaneCount() + this.visibleZellijReservations.size >= cap)
|
|
546
|
+
return null;
|
|
547
|
+
this.visibleZellijReservations.add(token);
|
|
548
|
+
return token;
|
|
549
|
+
}
|
|
550
|
+
releaseVisibleZellijReservation(token) {
|
|
551
|
+
this.visibleZellijReservations.delete(token);
|
|
552
|
+
}
|
|
553
|
+
async spawnCompactSlotWorkerProcess(input) {
|
|
554
|
+
const stdout = fs.createWriteStream(path.join(this.root, input.stdoutRel), { flags: 'a' });
|
|
555
|
+
const stderr = fs.createWriteStream(path.join(this.root, input.stderrRel), { flags: 'a' });
|
|
556
|
+
const child = spawn(process.execPath, input.args, {
|
|
557
|
+
cwd: input.cwd,
|
|
558
|
+
env: {
|
|
559
|
+
...process.env,
|
|
560
|
+
...Object.fromEntries(Object.entries(input.env).filter(([, value]) => value != null).map(([key, value]) => [key, String(value)]))
|
|
561
|
+
},
|
|
562
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
563
|
+
});
|
|
564
|
+
child.stdout?.pipe(stdout);
|
|
565
|
+
child.stderr?.pipe(stderr);
|
|
566
|
+
const exitPromise = new Promise((resolve) => {
|
|
567
|
+
child.on('close', (code, signal) => {
|
|
568
|
+
stdout.end();
|
|
569
|
+
stderr.end();
|
|
570
|
+
resolve({ code, signal });
|
|
571
|
+
});
|
|
572
|
+
child.on('error', () => {
|
|
573
|
+
stdout.end();
|
|
574
|
+
stderr.end();
|
|
575
|
+
resolve({ code: 1, signal: null });
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
return {
|
|
579
|
+
pid: child.pid || null,
|
|
580
|
+
wait: async (timeoutMs) => waitForChildExit(child, exitPromise, timeoutMs)
|
|
581
|
+
};
|
|
582
|
+
}
|
|
482
583
|
}
|
|
483
584
|
export function buildPaneWorkerCommand(input) {
|
|
484
585
|
const envPrefix = Object.entries(input.env)
|
|
@@ -555,4 +656,18 @@ function normalizeWorkerWorktree(value) {
|
|
|
555
656
|
main_repo_root: value?.main_repo_root == null ? null : String(value.main_repo_root)
|
|
556
657
|
};
|
|
557
658
|
}
|
|
659
|
+
async function waitForChildExit(child, exitPromise, timeoutMs) {
|
|
660
|
+
let timer = null;
|
|
661
|
+
const timeout = new Promise((resolve) => {
|
|
662
|
+
timer = setTimeout(() => {
|
|
663
|
+
if (!child.killed)
|
|
664
|
+
child.kill();
|
|
665
|
+
resolve({ code: null, signal: 'SIGTERM' });
|
|
666
|
+
}, Math.max(1000, timeoutMs));
|
|
667
|
+
});
|
|
668
|
+
const result = await Promise.race([exitPromise, timeout]);
|
|
669
|
+
if (timer)
|
|
670
|
+
clearTimeout(timer);
|
|
671
|
+
return result;
|
|
672
|
+
}
|
|
558
673
|
//# sourceMappingURL=native-cli-session-swarm.js.map
|
|
@@ -11,6 +11,7 @@ import { buildNarutoWorkGraph } from '../naruto/naruto-work-graph.js';
|
|
|
11
11
|
import { buildNarutoRoleDistribution } from '../naruto/naruto-role-policy.js';
|
|
12
12
|
import { decideNarutoConcurrency } from '../naruto/naruto-concurrency-governor.js';
|
|
13
13
|
import { runNarutoActivePool, runNarutoRealActivePool } from '../naruto/naruto-active-pool.js';
|
|
14
|
+
import { collectActualNarutoWorker, spawnActualNarutoWorker } from '../naruto/naruto-real-worker-runtime.js';
|
|
14
15
|
import { buildNarutoVerificationDag } from '../naruto/naruto-verification-dag.js';
|
|
15
16
|
import { buildNarutoGptFinalPack } from '../naruto/naruto-gpt-final-pack.js';
|
|
16
17
|
import { planNarutoZellijDashboard } from '../zellij/zellij-naruto-dashboard.js';
|
|
@@ -112,11 +113,29 @@ async function narutoRun(parsed) {
|
|
|
112
113
|
const activeSlots = Math.max(1, Math.min(roster.agent_count, parsed.concurrency || Math.max(governor.safe_active_workers, backendMinimum), safe.cap));
|
|
113
114
|
const zellijVisiblePanes = Math.max(1, Math.min(activeSlots, governor.safe_zellij_visible_panes));
|
|
114
115
|
const activePool = await runNarutoActivePool({ graph: workGraph, governor: { ...governor, safe_active_workers: activeSlots } });
|
|
116
|
+
const realRuntimeWorktreePolicy = schedulerBackend === 'fake'
|
|
117
|
+
? {
|
|
118
|
+
mode: 'patch-envelope-only',
|
|
119
|
+
required: false,
|
|
120
|
+
main_repo_root: worktreePolicy.main_repo_root,
|
|
121
|
+
worktree_root: null,
|
|
122
|
+
fallback_reason: 'fake_backend_fixture_skips_real_worktree_cleanup'
|
|
123
|
+
}
|
|
124
|
+
: worktreePolicy;
|
|
115
125
|
const realActivePool = await runNarutoRealActivePool({
|
|
116
126
|
graph: workGraph,
|
|
117
127
|
governor: { ...governor, safe_active_workers: activeSlots },
|
|
118
|
-
spawnWorker: async (item, placement) => ({
|
|
119
|
-
|
|
128
|
+
spawnWorker: async (item, placement) => spawnActualNarutoWorker({
|
|
129
|
+
root,
|
|
130
|
+
missionId: mission.id,
|
|
131
|
+
item,
|
|
132
|
+
placement,
|
|
133
|
+
backend: schedulerBackend,
|
|
134
|
+
worktreePolicy: realRuntimeWorktreePolicy,
|
|
135
|
+
zellijSessionName: `sks-${mission.id}`,
|
|
136
|
+
visiblePaneCap: zellijVisiblePanes
|
|
137
|
+
}),
|
|
138
|
+
collectWorker: async (handle) => collectActualNarutoWorker(handle),
|
|
120
139
|
enqueueVerification: async () => undefined,
|
|
121
140
|
updateDashboard: async () => undefined
|
|
122
141
|
});
|
|
@@ -256,14 +275,20 @@ async function narutoRun(parsed) {
|
|
|
256
275
|
completed_count: activePool.completed_count,
|
|
257
276
|
real_runtime: {
|
|
258
277
|
ok: realActivePool.ok,
|
|
278
|
+
active_cap: realActivePool.active_cap,
|
|
259
279
|
max_observed_active_workers: realActivePool.max_observed_active_workers,
|
|
280
|
+
average_active_workers: realActivePool.average_active_workers,
|
|
281
|
+
active_pool_utilization: realActivePool.active_pool_utilization,
|
|
260
282
|
refill_latency_ms_p95: realActivePool.refill_latency_ms_p95,
|
|
261
|
-
|
|
283
|
+
visible_workers: realActivePool.visible_workers,
|
|
284
|
+
headless_workers: realActivePool.headless_workers,
|
|
285
|
+
worker_lifecycle_count: realActivePool.worker_lifecycle.length,
|
|
286
|
+
worker_lifecycle_sample: realActivePool.worker_lifecycle.slice(0, 5)
|
|
262
287
|
}
|
|
263
288
|
},
|
|
264
289
|
local_worker: localWorkerSummary,
|
|
265
290
|
proof: result.proof?.status || 'missing',
|
|
266
|
-
run: result,
|
|
291
|
+
run: compactNarutoRunResult(result),
|
|
267
292
|
zellij: null
|
|
268
293
|
};
|
|
269
294
|
summary.zellij = liveZellij;
|
|
@@ -280,6 +305,36 @@ async function narutoRun(parsed) {
|
|
|
280
305
|
console.log('Zellij: optional live panes unavailable (' + ((summary.zellij.warnings || []).join('; ') || summary.zellij.capability?.status || 'unknown') + ')');
|
|
281
306
|
});
|
|
282
307
|
}
|
|
308
|
+
function compactNarutoRunResult(result) {
|
|
309
|
+
return {
|
|
310
|
+
schema: result?.schema || 'sks.agent-run.v1',
|
|
311
|
+
ok: result?.ok === true,
|
|
312
|
+
mission_id: result?.mission_id || null,
|
|
313
|
+
route: result?.route || NARUTO_ROUTE,
|
|
314
|
+
backend: result?.backend || null,
|
|
315
|
+
target_active_slots: result?.target_active_slots ?? null,
|
|
316
|
+
proof: result?.proof ? {
|
|
317
|
+
ok: result.proof.ok === true,
|
|
318
|
+
status: result.proof.status || null,
|
|
319
|
+
blockers: result.proof.blockers || []
|
|
320
|
+
} : null,
|
|
321
|
+
scheduler: result?.scheduler ? {
|
|
322
|
+
state: {
|
|
323
|
+
completed_count: result.scheduler.state?.completed_count ?? result.scheduler.completed_count ?? null,
|
|
324
|
+
failed_count: result.scheduler.state?.failed_count ?? result.scheduler.failed_count ?? null,
|
|
325
|
+
blocked_count: result.scheduler.state?.blocked_count ?? result.scheduler.blocked_count ?? null,
|
|
326
|
+
max_observed_active_slots: result.scheduler.state?.max_observed_active_slots ?? result.scheduler.max_observed_active_slots ?? null
|
|
327
|
+
}
|
|
328
|
+
} : null,
|
|
329
|
+
artifacts: {
|
|
330
|
+
ledger_root: result?.ledger_root || null,
|
|
331
|
+
proof: 'agent-proof-evidence.json',
|
|
332
|
+
scheduler: 'agent-scheduler-state.json',
|
|
333
|
+
native_cli_session_swarm: 'agent-native-cli-session-swarm.json',
|
|
334
|
+
naruto_real_active_pool: 'naruto-real-active-pool.json'
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
}
|
|
283
338
|
function summarizeNarutoLocalWorkerResult(localWorker, result) {
|
|
284
339
|
const backendCounts = {};
|
|
285
340
|
const rows = Array.isArray(result?.results) ? result.results : [];
|
|
@@ -62,6 +62,11 @@ export function buildDoctorReadinessMatrix(input = {}) {
|
|
|
62
62
|
warnings.add('local_llm_degraded');
|
|
63
63
|
if (localModel.enabled === true && localStatus === 'blocked')
|
|
64
64
|
warnings.add('local_llm_blocked_worker_tier_disabled');
|
|
65
|
+
const agentRoleConfig = input.agent_role_config || {};
|
|
66
|
+
if (agentRoleConfig.ok === false)
|
|
67
|
+
blockers.add('agent_role_config_repair_failed');
|
|
68
|
+
if (Array.isArray(agentRoleConfig.missing) && agentRoleConfig.missing.length && agentRoleConfig.apply !== true)
|
|
69
|
+
warnings.add('agent_role_config_missing_repair_available');
|
|
65
70
|
const localCollaborationPolicy = resolveLocalCollaborationPolicy({ mode: input.local_collaboration?.mode || null });
|
|
66
71
|
const gptFinalAvailable = input.local_collaboration?.gpt_final_arbiter_available === undefined
|
|
67
72
|
? codexBinOk
|
|
@@ -126,6 +131,7 @@ export function buildDoctorReadinessMatrix(input = {}) {
|
|
|
126
131
|
worker_tier_enabled: localModel.enabled === true && localStatus === 'verified',
|
|
127
132
|
blockers: normalizeList(localModel.blockers)
|
|
128
133
|
},
|
|
134
|
+
agent_role_config: agentRoleConfig,
|
|
129
135
|
ready: blockers.size === 0 && cliReady,
|
|
130
136
|
primary_blocker: [...blockers][0] || null,
|
|
131
137
|
blockers: [...blockers],
|
package/dist/core/fsx.js
CHANGED
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
export const PACKAGE_VERSION = '2.0.
|
|
8
|
+
export const PACKAGE_VERSION = '2.0.10';
|
|
9
9
|
export const DEFAULT_PROCESS_TAIL_BYTES = 256 * 1024;
|
|
10
10
|
export const DEFAULT_PROCESS_TIMEOUT_MS = 30 * 60 * 1000;
|
|
11
11
|
export function nowIso() {
|
|
@@ -25,6 +25,10 @@ const STOP_REPEAT_GUARD_WINDOW_MS = 10 * 60 * 1000;
|
|
|
25
25
|
const STOP_REPEAT_GUARD_MAX_ENTRIES = 25;
|
|
26
26
|
const DEFAULT_STOP_REPEAT_GUARD_LIMIT = 2;
|
|
27
27
|
const CODEX_GIT_ACTION_STOP_TTL_MS = 15 * 60 * 1000;
|
|
28
|
+
const UPDATE_CHECK_HOOK_INVOCATION_POLICY = 'function-only:no-runSksUpdateCheck-call-in-hooks';
|
|
29
|
+
// Update checks stay function-only in hooks: the policy marker above is checked
|
|
30
|
+
// by release readiness so ordinary Codex hook flow cannot grow a hidden update
|
|
31
|
+
// prompt path.
|
|
28
32
|
async function loadHookPayload() {
|
|
29
33
|
const raw = await readStdin();
|
|
30
34
|
try {
|
package/dist/core/init.js
CHANGED
|
@@ -1257,6 +1257,7 @@ async function removeDirIfEmpty(dir) {
|
|
|
1257
1257
|
}
|
|
1258
1258
|
async function installCodexAgents(root) {
|
|
1259
1259
|
const agents = {
|
|
1260
|
+
'analysis-scout.toml': `name = "analysis_scout"\ndescription = "Read-only SKS analysis scout retained for stale Codex agent-role config repair."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "medium"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are an SKS read-only analysis scout.\nDo not edit files.\nInspect only the assigned source paths and return concise source-backed findings.\n"""\n`,
|
|
1260
1261
|
'native-agent-intake.toml': `name = "native_agent"\ndescription = "Read-only Team native agent. Maps one independent repo/docs/tests/API/risk/user-friction slice and returns TriWiki-ready source-backed findings before debate starts."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "medium"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are an SKS Team native agent.\nDo not edit files.\nOwn exactly one investigation slice assigned by the parent orchestrator.\nUse the mission roster or worker inbox reasoning_effort when the host exposes it; simple bounded work can use low, tool-heavy runtime work medium, and knowledge/research/safety/release work high or xhigh. Default to medium only when no assignment is visible.\nMap relevant source files, docs, tests, APIs, DB or safety risks, UX friction, and likely implementation boundaries.\nReturn concise source-backed claims suitable for team-analysis.md and TriWiki ingestion: claim, source path, evidence hash or quoted anchor, risk, confidence, and recommended implementation slice.\nDo not debate the final plan and do not implement code.\nAlso return a concise LIVE_EVENT line that the parent can record with sks team event.\n"""\n`,
|
|
1261
1262
|
'team-consensus.toml': `name = "team_consensus"\ndescription = "Planning and debate specialist for SKS Team mode. Maps options, constraints, role-persona risks, and proposes the agreed objective before implementation starts."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "high"\nsandbox_mode = "read-only"\ndeveloper_instructions = """\nYou are the SKS Team consensus specialist.\nDo not edit files.\nUse the mission roster reasoning_effort when the host exposes it; planning has a medium minimum, with high or xhigh for knowledge, safety, release, research, or recovery work.\nMap the affected code paths, viable approaches, constraints, risks, and acceptance criteria.\nRun the debate as role-persona synthesis: final users are low-context, self-interested, stubborn, and inconvenience-averse; executors are capable developers; reviewers are strict.\nArgue for the smallest coherent objective that can be handed to a fresh executor_N development team.\nPlan for at least ${MIN_TEAM_REVIEWER_LANES} independent reviewer/QA validation lanes before integration or final.\nReturn: recommended objective, rejected alternatives, implementation slices, required reviewers, user-friction risks, and unresolved risks.\nAlso return a concise LIVE_EVENT line that the parent can record with sks team event.\n"""\n`,
|
|
1262
1263
|
'implementation-worker.toml': `name = "implementation_worker"\ndescription = "Implementation specialist for SKS Team mode. Owns one bounded write set and coordinates with other executor_N workers."\nmodel = "gpt-5.5"\nmodel_reasoning_effort = "medium"\nsandbox_mode = "workspace-write"\ndeveloper_instructions = """\nYou are an SKS Team executor/developer in the fresh development bundle.\nYou are not alone in the codebase. Other executor_N workers may be editing disjoint files.\nUse the mission roster or worker inbox reasoning_effort when the host exposes it; simple bounded changes can use low, tool-heavy implementation medium, and safety/release/DB work high.\nOnly edit the files or module slice assigned to you.\nDo not revert or overwrite edits made by others.\nRead local patterns first, make the smallest correct change, avoid adding user friction, run focused verification for your slice, and report changed paths plus evidence.\nDo not create fallback implementation code, substitute behavior, mock behavior, or compatibility shims unless the user or sealed decision contract explicitly requested them.\nRespect all SKS hooks, DB safety rules, no-question run rules, and H-Proof completion gates.\nAlso return concise LIVE_EVENT lines for started, blocked, changed files, verification, and final result so the parent can record them.\n"""\n`,
|
|
@@ -29,7 +29,7 @@ export async function runNarutoRealActivePool(input) {
|
|
|
29
29
|
visibleRunning += 1;
|
|
30
30
|
const handle = await input.spawnWorker(item, placement);
|
|
31
31
|
active.set(handle.id, handle);
|
|
32
|
-
lifecycle.push({ work_item_id: item.id, placement: placement.placement, started_at: handle.started_at, completed_at: null, ok: null });
|
|
32
|
+
lifecycle.push({ work_item_id: item.id, placement: placement.placement, pid: handle.pid || null, worker_artifact_dir: handle.worker_artifact_dir || null, started_at: handle.started_at, completed_at: null, ok: null });
|
|
33
33
|
launched += 1;
|
|
34
34
|
await input.updateDashboard({ event_type: 'worker_spawned', work_item_id: item.id, active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size, placement });
|
|
35
35
|
}
|
|
@@ -40,7 +40,7 @@ export async function runNarutoRealActivePool(input) {
|
|
|
40
40
|
}
|
|
41
41
|
maxObserved = Math.max(maxObserved, active.size);
|
|
42
42
|
timeline.push({ tick, active: active.size, pending: pending.length, completed: completed.size, event: launched ? 'refill' : 'wait' });
|
|
43
|
-
const done =
|
|
43
|
+
const done = await nextCollectableWorkers(active);
|
|
44
44
|
if (!done.length)
|
|
45
45
|
break;
|
|
46
46
|
for (const handle of done) {
|
|
@@ -53,6 +53,8 @@ export async function runNarutoRealActivePool(input) {
|
|
|
53
53
|
if (row) {
|
|
54
54
|
row.completed_at = result.completed_at;
|
|
55
55
|
row.ok = result.ok;
|
|
56
|
+
row.pid = result.pid || row.pid || null;
|
|
57
|
+
row.worker_artifact_dir = result.worker_artifact_dir || row.worker_artifact_dir || null;
|
|
56
58
|
}
|
|
57
59
|
await input.updateDashboard({ event_type: 'worker_completed', work_item_id: result.item.id, active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size, placement: result.placement });
|
|
58
60
|
if (result.item.verification_required) {
|
|
@@ -66,16 +68,32 @@ export async function runNarutoRealActivePool(input) {
|
|
|
66
68
|
}
|
|
67
69
|
await input.updateDashboard({ event_type: 'pool_drained', active_workers: active.size, pending_workers: pending.length, completed_workers: completed.size });
|
|
68
70
|
const failedCount = [...completed.values()].filter((result) => !result.ok).length;
|
|
71
|
+
const activeSamples = timeline.map((row) => row.active);
|
|
72
|
+
const averageActiveWorkers = activeSamples.length
|
|
73
|
+
? activeSamples.reduce((sum, value) => sum + value, 0) / activeSamples.length
|
|
74
|
+
: 0;
|
|
75
|
+
const saturatedSamples = timeline
|
|
76
|
+
.filter((row) => row.pending + row.active >= safeActiveWorkers)
|
|
77
|
+
.map((row) => row.active);
|
|
78
|
+
const averageSaturatedActiveWorkers = saturatedSamples.length
|
|
79
|
+
? saturatedSamples.reduce((sum, value) => sum + value, 0) / saturatedSamples.length
|
|
80
|
+
: averageActiveWorkers;
|
|
81
|
+
const utilizationDenominator = Math.max(1, safeActiveWorkers);
|
|
82
|
+
const activePoolUtilization = Math.min(1, averageSaturatedActiveWorkers / utilizationDenominator);
|
|
83
|
+
const enoughWorkForUtilization = input.graph.total_work_items >= safeActiveWorkers * 2;
|
|
69
84
|
const blockers = [
|
|
70
85
|
...(pending.length ? ['naruto_real_active_pool_pending_not_drained'] : []),
|
|
71
86
|
...(active.size ? ['naruto_real_active_pool_active_not_drained'] : []),
|
|
72
87
|
...(maxObserved > safeActiveWorkers ? ['naruto_real_active_pool_exceeded_safe_workers'] : []),
|
|
88
|
+
...(enoughWorkForUtilization && maxObserved < Math.ceil(safeActiveWorkers * 0.8) ? ['naruto_real_active_pool_underutilized'] : []),
|
|
89
|
+
...(enoughWorkForUtilization && activePoolUtilization < 0.8 ? ['naruto_real_active_pool_low_sustained_utilization'] : []),
|
|
73
90
|
...[...completed.values()].flatMap((result) => result.blockers || [])
|
|
74
91
|
];
|
|
75
92
|
return {
|
|
76
93
|
schema: 'sks.naruto-active-pool.v1',
|
|
77
94
|
ok: blockers.length === 0,
|
|
78
95
|
runtime_mode: 'real-worker-lifecycle',
|
|
96
|
+
active_cap: safeActiveWorkers,
|
|
79
97
|
safe_active_workers: safeActiveWorkers,
|
|
80
98
|
total_work_items: input.graph.total_work_items,
|
|
81
99
|
completed_count: completed.size,
|
|
@@ -85,12 +103,27 @@ export async function runNarutoRealActivePool(input) {
|
|
|
85
103
|
duplicate_execution_count: 0,
|
|
86
104
|
conflict_items_enqueued: 0,
|
|
87
105
|
max_observed_write_lease_conflicts: 0,
|
|
106
|
+
average_active_workers: Number(averageSaturatedActiveWorkers.toFixed(4)),
|
|
107
|
+
active_pool_utilization: Number(activePoolUtilization.toFixed(4)),
|
|
108
|
+
visible_workers: lifecycle.filter((row) => row.placement === 'zellij-pane').length,
|
|
109
|
+
headless_workers: lifecycle.filter((row) => row.placement === 'headless').length,
|
|
88
110
|
refill_latency_ms_p95: percentile(refillLatencies, 0.95),
|
|
89
111
|
worker_lifecycle: lifecycle,
|
|
90
112
|
timeline,
|
|
91
113
|
blockers
|
|
92
114
|
};
|
|
93
115
|
}
|
|
116
|
+
async function nextCollectableWorkers(active) {
|
|
117
|
+
const handles = [...active.values()];
|
|
118
|
+
const handlesWithExit = handles.filter((handle) => typeof handle.exit?.then === 'function');
|
|
119
|
+
if (!handlesWithExit.length)
|
|
120
|
+
return handles.slice(0, Math.max(1, Math.ceil(active.size / 2)));
|
|
121
|
+
const completed = await Promise.race(handlesWithExit.map(async (handle) => {
|
|
122
|
+
await handle.exit.catch(() => undefined);
|
|
123
|
+
return handle;
|
|
124
|
+
}));
|
|
125
|
+
return completed ? [completed] : [];
|
|
126
|
+
}
|
|
94
127
|
export async function runNarutoActivePool(input) {
|
|
95
128
|
const base = simulateNarutoActivePool(input);
|
|
96
129
|
const allocations = [];
|
|
@@ -7,7 +7,7 @@ export function decideNarutoConcurrency(input = {}) {
|
|
|
7
7
|
const pending = normalizeNonNegativeInt(input.pendingWorkQueueSize, totalWorkItems);
|
|
8
8
|
const leaseConflicts = normalizeNonNegativeInt(input.activeLeaseConflicts, 0);
|
|
9
9
|
const hardware = probeHardwareCapacity(input.hardware || {});
|
|
10
|
-
const zellijVisiblePaneCap = normalizePositiveInt(input.zellijVisiblePaneCap, Math.min(
|
|
10
|
+
const zellijVisiblePaneCap = normalizePositiveInt(input.zellijVisiblePaneCap, Math.min(8, Math.max(4, Math.floor(hardware.terminal_rows / 5))));
|
|
11
11
|
const backend = String(input.backend || 'codex-sdk');
|
|
12
12
|
const freeGb = hardware.free_memory_bytes / (1024 * 1024 * 1024);
|
|
13
13
|
const totalGb = hardware.total_memory_bytes / (1024 * 1024 * 1024);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { nowIso, readJson, writeJsonAtomic } from '../fsx.js';
|
|
4
|
+
async function main() {
|
|
5
|
+
const intakePath = process.argv[2];
|
|
6
|
+
if (!intakePath)
|
|
7
|
+
throw new Error('naruto worker intake path is required');
|
|
8
|
+
const intake = await readJson(intakePath, null);
|
|
9
|
+
if (!intake?.result_path || !intake?.heartbeat_path || !intake?.item?.id) {
|
|
10
|
+
throw new Error('naruto worker intake is invalid');
|
|
11
|
+
}
|
|
12
|
+
await fs.appendFile(intake.heartbeat_path, `${JSON.stringify({
|
|
13
|
+
schema: 'sks.naruto-actual-worker-heartbeat.v1',
|
|
14
|
+
ts: nowIso(),
|
|
15
|
+
item_id: intake.item.id,
|
|
16
|
+
status: 'running'
|
|
17
|
+
})}\n`);
|
|
18
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
19
|
+
await writeJsonAtomic(intake.result_path, {
|
|
20
|
+
schema: 'sks.naruto-actual-worker-result.v1',
|
|
21
|
+
ok: true,
|
|
22
|
+
generated_at: nowIso(),
|
|
23
|
+
item_id: intake.item.id,
|
|
24
|
+
placement: intake.placement,
|
|
25
|
+
backend: intake.backend,
|
|
26
|
+
worktree_path: intake.worktree_path
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
main().then(() => {
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}).catch((err) => {
|
|
32
|
+
console.error(err?.message || String(err));
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
|
35
|
+
//# sourceMappingURL=naruto-real-worker-child.js.map
|