deadbyte-mcp 0.15.27 → 0.15.29
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/MANIFEST.SHA256 +41 -25
- package/PROVENANCE.json +16 -10
- package/README.txt +32 -14
- package/RELEASE.json +55 -15
- package/SBOM.cdx.json +3 -3
- package/TEST-MATRIX.json +147 -23
- package/bin/deadbyte-process-host.exe +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/controller/deadbyte-controller.ps1 +4 -1
- package/controller/deadbyte-process-policy.json +1 -1
- package/docs/R51-PROVENANCE.json +18 -0
- package/docs/R51-RELEASE.json +115 -0
- package/docs/R51-TEST-MATRIX.json +166 -0
- package/docs/R52-ACTIVATION-FORENSICS.md +85 -0
- package/native/deadbyte-process-host.cpp +5 -3
- package/package.json +95 -93
- package/scripts/deploy-r48.mjs +1 -1
- package/scripts/gate-windows-r51.ps1 +58 -0
- package/scripts/gate-windows-r52.ps1 +58 -0
- package/scripts/windows-gate-evidence-r51.mjs +84 -0
- package/scripts/windows-gate-evidence-r52.mjs +94 -0
- package/src/coding-plane.mjs +190 -28
- package/src/coding-task-runtime.mjs +10 -4
- package/src/deadbyte-cli.mjs +88 -28
- package/src/remote-live-log.mjs +17 -5
- package/src/remote-session-guardian.mjs +1 -1
- package/src/runtime-coherence.mjs +50 -0
- package/src/runtime-update.mjs +8 -2
- package/src/version.mjs +1 -1
- package/test/coding-runtime.test.mjs +104 -1
- package/test/helpers/coding-disconnect-server.mjs +11 -0
- package/test/production-docs.test.mjs +5 -3
- package/test/r48-release-contract.test.mjs +7 -30
- package/test/r49-windows-gate.test.mjs +17 -11
- package/test/r51-live-log-polling.test.mjs +28 -0
- package/test/r51-release-contract.test.mjs +58 -0
- package/test/r51-runtime-coherence.test.mjs +59 -0
- package/test/r52-host-revocation.test.mjs +65 -0
- package/test/r52-offline-predecessor-recovery.test.mjs +77 -0
- package/test/r52-release-contract.test.mjs +58 -0
- package/test/remote-live-log-r37.test.mjs +14 -0
- package/test/remote-session-cli.test.mjs +24 -24
package/src/coding-plane.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import { loadPrivateSigningKey, loadPublicVerifyKey } from './trust.mjs';
|
|
|
11
11
|
import { createCodingSearch } from './coding-search.mjs';
|
|
12
12
|
import { createSearchCursorCodec } from './search-cursor.mjs';
|
|
13
13
|
import { atomicMutation } from './atomic-mutation.mjs';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
14
15
|
|
|
15
16
|
export const CODING_POLICY_SCHEMA = 'deadbyte.coding-policy.v1';
|
|
16
17
|
export const CODING_LEASE_SCHEMA = 'deadbyte.coding-lease.v1';
|
|
@@ -368,6 +369,93 @@ function requestSha(operation, args) {
|
|
|
368
369
|
export function createCodingRuntime({ policy, signingKeyPath, verifyKeyPath }) {
|
|
369
370
|
if (!policy) throw new Error('coding runtime requires policy');
|
|
370
371
|
if (!signingKeyPath || !verifyKeyPath) throw new Error('coding runtime requires trust key paths');
|
|
372
|
+
const commandFlights = new Map();
|
|
373
|
+
const commandResults = new Map();
|
|
374
|
+
const COMMAND_RESULT_TTL_MS = 5 * 60 * 1000;
|
|
375
|
+
const COMMAND_RESULT_CACHE_MAX = 16;
|
|
376
|
+
const COMMAND_FLIGHT_SCHEMA = 'deadbyte.coding-command-flight.v1';
|
|
377
|
+
const COMMAND_RESULT_SCHEMA = 'deadbyte.coding-command-result.v1';
|
|
378
|
+
const commandFlightDir = path.join(policy.evidence_dir, 'command-flights');
|
|
379
|
+
function pruneCommandResults(now = Date.now()) {
|
|
380
|
+
for (const [key, entry] of commandResults) if (now - entry.completed_at_ms > COMMAND_RESULT_TTL_MS) commandResults.delete(key);
|
|
381
|
+
while (commandResults.size > COMMAND_RESULT_CACHE_MAX) commandResults.delete(commandResults.keys().next().value);
|
|
382
|
+
}
|
|
383
|
+
function commandFlightPaths(executionKey) {
|
|
384
|
+
const executionKeySha256 = sha256Bytes(Buffer.from(executionKey, 'utf8'));
|
|
385
|
+
return {
|
|
386
|
+
executionKeySha256,
|
|
387
|
+
claimPath: path.join(commandFlightDir, `${executionKeySha256}.claim.json`),
|
|
388
|
+
resultPath: path.join(commandFlightDir, `${executionKeySha256}.result.json`)
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
async function readDurableCommandResult(resultPath, executionKeySha256, request) {
|
|
392
|
+
let raw;
|
|
393
|
+
try { raw = JSON.parse(decodeUtf8Strict(await readFile(resultPath))); }
|
|
394
|
+
catch (error) {
|
|
395
|
+
if (error?.code === 'ENOENT') return null;
|
|
396
|
+
throw new Error(`coding command durable result invalid: ${String(error?.message ?? error)}`);
|
|
397
|
+
}
|
|
398
|
+
if (raw?.schema !== COMMAND_RESULT_SCHEMA || raw.execution_key_sha256 !== executionKeySha256 || raw.request_sha256 !== request ||
|
|
399
|
+
!Number.isInteger(raw.completed_at_ms) || !raw.result || typeof raw.result !== 'object' || Array.isArray(raw.result)) {
|
|
400
|
+
throw new Error('coding command durable result identity invalid');
|
|
401
|
+
}
|
|
402
|
+
if (Date.now() - raw.completed_at_ms > COMMAND_RESULT_TTL_MS) {
|
|
403
|
+
await rm(resultPath, { force: true });
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
const value = raw.result;
|
|
407
|
+
if (value.request_sha256 !== request || !RECEIPT_ID_RE.test(value.receipt_id ?? '') || !SHA256_RE.test(value.receipt_sha256 ?? '')) {
|
|
408
|
+
throw new Error('coding command durable result receipt identity invalid');
|
|
409
|
+
}
|
|
410
|
+
const verified = await verifyCodingReceiptFile(policy, verifyKeyPath, value.receipt_id);
|
|
411
|
+
if (!verified.ok || verified.request_sha256 !== request || verified.receipt_sha256 !== value.receipt_sha256) {
|
|
412
|
+
throw new Error(`coding command durable result receipt verification failed: ${verified.reason ?? 'identity mismatch'}`);
|
|
413
|
+
}
|
|
414
|
+
return { ...value };
|
|
415
|
+
}
|
|
416
|
+
async function writeDurableCommandResult(resultPath, executionKeySha256, request, value) {
|
|
417
|
+
const body = Buffer.from(`${JSON.stringify({ schema:COMMAND_RESULT_SCHEMA, execution_key_sha256:executionKeySha256,
|
|
418
|
+
request_sha256:request, completed_at_ms:Date.now(), result:value })}\n`, 'utf8');
|
|
419
|
+
try {
|
|
420
|
+
const handle = await open(resultPath, 'wx', 0o600);
|
|
421
|
+
try { await handle.writeFile(body); await handle.sync(); }
|
|
422
|
+
finally { await handle.close(); }
|
|
423
|
+
} catch (error) {
|
|
424
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
425
|
+
const prior = await readDurableCommandResult(resultPath, executionKeySha256, request);
|
|
426
|
+
if (!prior || prior.receipt_id !== value.receipt_id || prior.receipt_sha256 !== value.receipt_sha256) {
|
|
427
|
+
throw new Error('coding command durable result conflict');
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async function acquireDurableCommandFlight(executionKey, request, waitMs) {
|
|
432
|
+
await mkdir(commandFlightDir, { recursive: true });
|
|
433
|
+
const paths = commandFlightPaths(executionKey);
|
|
434
|
+
const cached = await readDurableCommandResult(paths.resultPath, paths.executionKeySha256, request);
|
|
435
|
+
if (cached) return { ...paths, owned:false, cached };
|
|
436
|
+
try {
|
|
437
|
+
const handle = await open(paths.claimPath, 'wx', 0o600);
|
|
438
|
+
try {
|
|
439
|
+
await handle.writeFile(Buffer.from(`${JSON.stringify({ schema:COMMAND_FLIGHT_SCHEMA, execution_key_sha256:paths.executionKeySha256,
|
|
440
|
+
request_sha256:request, owner_pid:process.pid, started_at_ms:Date.now() })}\n`, 'utf8'));
|
|
441
|
+
await handle.sync();
|
|
442
|
+
} finally { await handle.close(); }
|
|
443
|
+
return { ...paths, owned:true, cached:null };
|
|
444
|
+
} catch (error) {
|
|
445
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
446
|
+
}
|
|
447
|
+
const deadline = Date.now() + waitMs;
|
|
448
|
+
for (;;) {
|
|
449
|
+
const result = await readDurableCommandResult(paths.resultPath, paths.executionKeySha256, request);
|
|
450
|
+
if (result) return { ...paths, owned:false, cached:result };
|
|
451
|
+
let claimExists = true;
|
|
452
|
+
try { await stat(paths.claimPath); }
|
|
453
|
+
catch (error) { if (error?.code === 'ENOENT') claimExists = false; else throw error; }
|
|
454
|
+
if (!claimExists) return acquireDurableCommandFlight(executionKey, request, Math.max(100, deadline - Date.now()));
|
|
455
|
+
if (Date.now() >= deadline) throw new Error('identical coding command is still running in another runtime instance');
|
|
456
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
457
|
+
}
|
|
458
|
+
}
|
|
371
459
|
|
|
372
460
|
async function sessionStatus() {
|
|
373
461
|
const status = await codingSessionStatus(policy);
|
|
@@ -504,7 +592,7 @@ export function createCodingRuntime({ policy, signingKeyPath, verifyKeyPath }) {
|
|
|
504
592
|
receipt_sha256: signed.receipt.receipt_sha256, request_sha256: request };
|
|
505
593
|
}
|
|
506
594
|
async function runCommandOnce({ rootId, commandId, scriptPath, scriptSha256, args = [], cwd = '.', timeoutMs = null,
|
|
507
|
-
executionDeadlineUtc = null, preExecutionCheck = null }) {
|
|
595
|
+
executionDeadlineUtc = null, preExecutionCheck = null, cancellationCheck = null }) {
|
|
508
596
|
const lease = await assertCodingArmed(policy, rootId);
|
|
509
597
|
if (!ID_RE.test(commandId ?? '')) throw new Error('invalid coding command id');
|
|
510
598
|
const command = policy.commands[commandId];
|
|
@@ -546,28 +634,58 @@ export function createCodingRuntime({ policy, signingKeyPath, verifyKeyPath }) {
|
|
|
546
634
|
if (deadlineBound < 100) throw new Error('coding execution deadline expires too soon to execute command');
|
|
547
635
|
const effectiveTimeout = Math.min(requestedTimeout, command.timeout_ms, grantBound, deadlineBound);
|
|
548
636
|
const argv = [...command.prefix_args, script.resolved, ...args];
|
|
637
|
+
// The internal Windows host owns a kill-on-close Job Object. Its identity
|
|
638
|
+
// is package-pinned, never selected by a caller or granted public PID input.
|
|
639
|
+
let executable=command.executable, launchArgs=argv;
|
|
640
|
+
if(process.platform==='win32'){
|
|
641
|
+
const host=fileURLToPath(new URL('../bin/deadbyte-process-host.exe',import.meta.url));
|
|
642
|
+
const hostPolicy=JSON.parse(await readFile(new URL('../controller/deadbyte-process-policy.json',import.meta.url),'utf8'));
|
|
643
|
+
if(await sha256File(host)!==hostPolicy.native_host?.sha256) throw new Error('coding process host hash changed');
|
|
644
|
+
executable=host;
|
|
645
|
+
launchArgs=[command.executable,cwdResolved,randomBytes(32).toString('hex'),'--deadbyte-transport=pipe-quiet',...argv];
|
|
646
|
+
}
|
|
647
|
+
if(cancellationCheck!==null&&typeof cancellationCheck!=='function') throw new Error('cancellation check must be internal function');
|
|
648
|
+
if(cancellationCheck&&await cancellationCheck()) throw new Error('coding command cancelled before dispatch');
|
|
649
|
+
await recheckCodingAuthority(policy,activeLease,rootId);
|
|
549
650
|
const startMs = performance.now();
|
|
550
|
-
const child = spawn(
|
|
651
|
+
const child = spawn(executable, launchArgs, {
|
|
551
652
|
cwd: cwdResolved, env: codingEnvironment(), shell: false, windowsHide: true,
|
|
552
|
-
detached:
|
|
653
|
+
detached: process.platform!=='win32', stdio: ['ignore', 'pipe', 'pipe']
|
|
553
654
|
});
|
|
655
|
+
const killOwned=()=>{
|
|
656
|
+
if(child.exitCode!==null||child.signalCode!==null)return;
|
|
657
|
+
if(process.platform==='win32') child.kill('SIGKILL');
|
|
658
|
+
else {try{process.kill(-child.pid,'SIGKILL');}catch(error){if(error.code!=='ESRCH')throw error;}}
|
|
659
|
+
};
|
|
554
660
|
const stdoutState = { chunks: [], bytes: 0, exceeded: null };
|
|
555
661
|
const stderrState = { chunks: [], bytes: 0, exceeded: null };
|
|
556
|
-
collectBounded(child.stdout, command.max_output_bytes, stdoutState, 'stdout',
|
|
557
|
-
collectBounded(child.stderr, command.max_output_bytes, stderrState, 'stderr',
|
|
662
|
+
collectBounded(child.stdout, command.max_output_bytes, stdoutState, 'stdout', killOwned);
|
|
663
|
+
collectBounded(child.stderr, command.max_output_bytes, stderrState, 'stderr', killOwned);
|
|
558
664
|
let timedOut = false;
|
|
559
665
|
let timer;
|
|
666
|
+
let cancelTimer,checking=false,cancellationError=null,finished=false;
|
|
667
|
+
if(cancellationCheck){
|
|
668
|
+
cancelTimer=setInterval(async()=>{
|
|
669
|
+
if(checking||finished)return;
|
|
670
|
+
checking=true;
|
|
671
|
+
try{if(await cancellationCheck())killOwned();}
|
|
672
|
+
catch(error){cancellationError=error;killOwned();}
|
|
673
|
+
finally{checking=false;}
|
|
674
|
+
},50);
|
|
675
|
+
cancelTimer.unref?.();
|
|
676
|
+
}
|
|
560
677
|
const timerDelay = deadlineAtMs === null
|
|
561
678
|
? effectiveTimeout
|
|
562
679
|
: Math.max(1, Math.min(effectiveTimeout, Math.floor(deadlineAtMs - Date.now())));
|
|
563
680
|
const completion = new Promise((resolve, reject) => {
|
|
564
681
|
child.once('error', reject);
|
|
565
682
|
child.once('close', (code, signal) => resolve({ code, signal }));
|
|
566
|
-
timer = setTimeout(() => { timedOut = true;
|
|
683
|
+
timer = setTimeout(() => { timedOut = true; killOwned(); }, timerDelay);
|
|
567
684
|
timer.unref?.();
|
|
568
685
|
});
|
|
569
686
|
let result;
|
|
570
|
-
try { result = await completion; } finally { if (timer) clearTimeout(timer); }
|
|
687
|
+
try { result = await completion; } finally { finished=true;if (timer) clearTimeout(timer);if(cancelTimer)clearInterval(cancelTimer); }
|
|
688
|
+
if(cancellationError)throw cancellationError;
|
|
571
689
|
if (stdoutState.exceeded || stderrState.exceeded) throw new Error(stdoutState.exceeded ?? stderrState.exceeded);
|
|
572
690
|
const stdoutBytes = Buffer.concat(stdoutState.chunks);
|
|
573
691
|
const stderrBytes = Buffer.concat(stderrState.chunks);
|
|
@@ -582,28 +700,72 @@ export function createCodingRuntime({ policy, signingKeyPath, verifyKeyPath }) {
|
|
|
582
700
|
}
|
|
583
701
|
|
|
584
702
|
async function commandRun({ rootId, commandId, scriptPath, scriptSha256, args = [], cwd = '.', timeoutMs = null,
|
|
585
|
-
executionDeadlineUtc = null, preExecutionCheck = null, receiptId = null, requestSha256 = null }) {
|
|
703
|
+
executionDeadlineUtc = null, preExecutionCheck = null, cancellationCheck = null, receiptId = null, requestSha256 = null }) {
|
|
586
704
|
const request = requestSha256 ?? requestSha('coding_command_run', {
|
|
587
705
|
root_id: rootId, command_id: commandId, script_path: scriptPath, script_sha256: scriptSha256,
|
|
588
706
|
args, cwd, timeout_ms: timeoutMs
|
|
589
707
|
});
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
708
|
+
const executionKey = receiptId === null ? request : `${request}:${receiptId}`;
|
|
709
|
+
pruneCommandResults();
|
|
710
|
+
const cached = commandResults.get(executionKey);
|
|
711
|
+
if (cached) {
|
|
712
|
+
await assertCodingArmed(policy, rootId);
|
|
713
|
+
return { ...cached.result };
|
|
714
|
+
}
|
|
715
|
+
const inFlight = commandFlights.get(executionKey);
|
|
716
|
+
if (inFlight) {
|
|
717
|
+
await assertCodingArmed(policy, rootId);
|
|
718
|
+
return { ...(await inFlight) };
|
|
719
|
+
}
|
|
720
|
+
if (!ID_RE.test(commandId ?? '')) throw new Error('invalid coding command id');
|
|
721
|
+
const command = policy.commands[commandId];
|
|
722
|
+
if (!command) throw new Error(`coding command '${commandId}' is not allowed`);
|
|
723
|
+
if (!command.root_ids.includes(rootId)) throw new Error(`coding command '${commandId}' is not allowed for root '${rootId}'`);
|
|
724
|
+
if (!Array.isArray(args) || args.length > 64 || args.some(item => typeof item !== 'string' || item.length > 2048 || item.includes('\0'))) {
|
|
725
|
+
throw new Error('coding command args must be <=64 strings of <=2048 characters');
|
|
726
|
+
}
|
|
727
|
+
if (!SHA256_RE.test(scriptSha256 ?? '')) throw new Error('script_sha256 must be lowercase SHA-256');
|
|
728
|
+
const requestedTimeout = timeoutMs === null ? command.timeout_ms : finiteInteger(timeoutMs, 'timeout_ms', 100, command.timeout_ms, command.timeout_ms);
|
|
729
|
+
const durable = await acquireDurableCommandFlight(executionKey, request, Math.min(900_000, requestedTimeout + 10_000));
|
|
730
|
+
if (durable.cached) {
|
|
731
|
+
await assertCodingArmed(policy, rootId);
|
|
732
|
+
commandResults.set(executionKey, { completed_at_ms: Date.now(), result: durable.cached });
|
|
733
|
+
pruneCommandResults();
|
|
734
|
+
return { ...durable.cached };
|
|
735
|
+
}
|
|
736
|
+
const execution = (async () => {
|
|
737
|
+
const started = new Date().toISOString();
|
|
738
|
+
const result = await runCommandOnce({ rootId, commandId, scriptPath, scriptSha256, args, cwd, timeoutMs, executionDeadlineUtc, preExecutionCheck, cancellationCheck });
|
|
739
|
+
const signed = await signCodingReceipt(policy, signingKeyPath, result.lease, {
|
|
740
|
+
operation: 'coding_command_run', request_sha256: request, root_id: rootId, command_id: commandId,
|
|
741
|
+
script_path: scriptPath, executable_sha256: result.executable_sha256, script_sha256: result.script_sha256,
|
|
742
|
+
started_at_utc: started, completed_at_utc: new Date().toISOString(), exit_code: result.exit_code,
|
|
743
|
+
signal: result.signal, timed_out: result.timed_out, elapsed_ms: result.elapsed_ms,
|
|
744
|
+
stdout_sha256: result.stdout_sha256, stderr_sha256: result.stderr_sha256
|
|
745
|
+
}, receiptId);
|
|
746
|
+
const value = {
|
|
747
|
+
status: result.timed_out || result.exit_code !== 0 ? 'observed_failure' : 'passed',
|
|
748
|
+
root_id: rootId, command_id: commandId, executable_sha256: result.executable_sha256,
|
|
749
|
+
script_sha256: result.script_sha256, exit_code: result.exit_code, signal: result.signal,
|
|
750
|
+
timed_out: result.timed_out, timeout_ms: result.timeout_ms, elapsed_ms: result.elapsed_ms,
|
|
751
|
+
stdout: result.stdout, stderr: result.stderr,
|
|
752
|
+
receipt_id: signed.receipt.receipt_id, receipt_sha256: signed.receipt.receipt_sha256, request_sha256: request
|
|
753
|
+
};
|
|
754
|
+
await writeDurableCommandResult(durable.resultPath, durable.executionKeySha256, request, value);
|
|
755
|
+
return value;
|
|
756
|
+
})();
|
|
757
|
+
commandFlights.set(executionKey, execution);
|
|
758
|
+
let completedDurably = false;
|
|
759
|
+
try {
|
|
760
|
+
const value = await execution;
|
|
761
|
+
completedDurably = true;
|
|
762
|
+
commandResults.set(executionKey, { completed_at_ms: Date.now(), result: value });
|
|
763
|
+
pruneCommandResults();
|
|
764
|
+
return { ...value };
|
|
765
|
+
} finally {
|
|
766
|
+
if (completedDurably && durable.owned) await rm(durable.claimPath, { force: true });
|
|
767
|
+
if (commandFlights.get(executionKey) === execution) commandFlights.delete(executionKey);
|
|
768
|
+
}
|
|
607
769
|
}
|
|
608
770
|
|
|
609
771
|
function percentile(samples, fraction) {
|
|
@@ -620,7 +782,7 @@ export function createCodingRuntime({ policy, signingKeyPath, verifyKeyPath }) {
|
|
|
620
782
|
|
|
621
783
|
async function benchmarkRun({ rootId, commandId, scriptPath, scriptSha256, args = [], cwd = '.', timeoutMs = null,
|
|
622
784
|
executionDeadlineUtc = null, preExecutionCheck = null, warmups = 1, iterations = 5, baselineMedianMs = null, maxRegressionPct = 5,
|
|
623
|
-
receiptId = null, requestSha256 = null }) {
|
|
785
|
+
receiptId = null, requestSha256 = null, cancellationCheck = null }) {
|
|
624
786
|
if (!Number.isInteger(warmups) || warmups < 0 || warmups > 3) throw new Error('warmups must be in [0, 3]');
|
|
625
787
|
if (!Number.isInteger(iterations) || iterations < 1 || iterations > 20) throw new Error('iterations must be in [1, 20]');
|
|
626
788
|
if (baselineMedianMs !== null && (!Number.isFinite(baselineMedianMs) || baselineMedianMs <= 0)) throw new Error('baseline_median_ms must be positive or null');
|
|
@@ -631,13 +793,13 @@ export function createCodingRuntime({ policy, signingKeyPath, verifyKeyPath }) {
|
|
|
631
793
|
max_regression_pct: maxRegressionPct
|
|
632
794
|
});
|
|
633
795
|
for (let i = 0; i < warmups; i += 1) {
|
|
634
|
-
const warm = await runCommandOnce({ rootId, commandId, scriptPath, scriptSha256, args, cwd, timeoutMs, executionDeadlineUtc, preExecutionCheck });
|
|
796
|
+
const warm = await runCommandOnce({ rootId, commandId, scriptPath, scriptSha256, args, cwd, timeoutMs, executionDeadlineUtc, preExecutionCheck, cancellationCheck });
|
|
635
797
|
if (warm.timed_out || warm.exit_code !== 0) throw new Error(`benchmark warmup failed exit=${String(warm.exit_code)} timed_out=${warm.timed_out}`);
|
|
636
798
|
}
|
|
637
799
|
const samples = [];
|
|
638
800
|
let last = null;
|
|
639
801
|
for (let i = 0; i < iterations; i += 1) {
|
|
640
|
-
last = await runCommandOnce({ rootId, commandId, scriptPath, scriptSha256, args, cwd, timeoutMs, executionDeadlineUtc, preExecutionCheck });
|
|
802
|
+
last = await runCommandOnce({ rootId, commandId, scriptPath, scriptSha256, args, cwd, timeoutMs, executionDeadlineUtc, preExecutionCheck, cancellationCheck });
|
|
641
803
|
if (last.timed_out || last.exit_code !== 0) throw new Error(`benchmark iteration ${i + 1} failed exit=${String(last.exit_code)} timed_out=${last.timed_out}`);
|
|
642
804
|
samples.push(last.elapsed_ms);
|
|
643
805
|
}
|
|
@@ -474,7 +474,7 @@ async function reconcileInterrupted(ctx, plan, events, derived, codingRuntime, s
|
|
|
474
474
|
return deriveState(plan, events);
|
|
475
475
|
}
|
|
476
476
|
|
|
477
|
-
async function executeStep(codingRuntime, plan, step, attempt) {
|
|
477
|
+
async function executeStep(codingRuntime, plan, step, attempt, cancellationCheck = null) {
|
|
478
478
|
const receiptId = receiptIdFor(plan.task_id, step.step_id, attempt, plan.plan_sha256);
|
|
479
479
|
const requestSha256 = expectedRequest(codingRuntime, step);
|
|
480
480
|
if (step.kind === 'write') {
|
|
@@ -486,12 +486,12 @@ async function executeStep(codingRuntime, plan, step, attempt) {
|
|
|
486
486
|
} else if (step.kind === 'command') {
|
|
487
487
|
await codingRuntime.commandRun({ rootId: step.root_id, commandId: step.command_id, scriptPath: step.script_path,
|
|
488
488
|
scriptSha256: step.script_sha256, args: step.args, cwd: step.cwd, timeoutMs: step.timeout_ms,
|
|
489
|
-
receiptId, requestSha256 });
|
|
489
|
+
receiptId, requestSha256, cancellationCheck });
|
|
490
490
|
} else if (step.kind === 'benchmark') {
|
|
491
491
|
await codingRuntime.benchmarkRun({ rootId: step.root_id, commandId: step.command_id, scriptPath: step.script_path,
|
|
492
492
|
scriptSha256: step.script_sha256, args: step.args, cwd: step.cwd, timeoutMs: step.timeout_ms,
|
|
493
493
|
warmups: step.warmups, iterations: step.iterations, baselineMedianMs: step.baseline_median_ms,
|
|
494
|
-
maxRegressionPct: step.max_regression_pct, receiptId, requestSha256 });
|
|
494
|
+
maxRegressionPct: step.max_regression_pct, receiptId, requestSha256, cancellationCheck });
|
|
495
495
|
} else {
|
|
496
496
|
throw new Error(`coding step '${step.step_id}' is not executable`);
|
|
497
497
|
}
|
|
@@ -670,7 +670,10 @@ export function createCodingTaskRuntime({ codingRuntime, signingKeyPath, verifyK
|
|
|
670
670
|
try {
|
|
671
671
|
const remainingMs = timeoutMs === null ? null : Math.max(100, Math.floor(deadlineState(deadline).remaining_ms));
|
|
672
672
|
const boundedStep = remainingMs === null ? next : { ...next, timeout_ms: next.timeout_ms === null || next.timeout_ms === undefined ? remainingMs : Math.min(next.timeout_ms, remainingMs) };
|
|
673
|
-
const executed = await executeStep(codingRuntime, plan, boundedStep, attempt);
|
|
673
|
+
const executed = await executeStep(codingRuntime, plan, boundedStep, attempt,()=>readCancel(ctx,verifyKeyPath));
|
|
674
|
+
if(await consumeCancel(ctx,journal,signingKeyPath,verifyKeyPath)){
|
|
675
|
+
derived=deriveState(plan,journal);return {status:'cancelled',task_id:taskId,...derived};
|
|
676
|
+
}
|
|
674
677
|
if (!executed.outcome.valid) {
|
|
675
678
|
await appendEvent(ctx, journal, signingKeyPath, 'task_paused', {
|
|
676
679
|
stepId: next.step_id, payload: { reason: 'post_execute_receipt_mismatch', detail: executed.outcome.reason }
|
|
@@ -700,6 +703,9 @@ export function createCodingTaskRuntime({ codingRuntime, signingKeyPath, verifyK
|
|
|
700
703
|
}
|
|
701
704
|
derived = deriveState(plan, journal);
|
|
702
705
|
} catch (error) {
|
|
706
|
+
if(await consumeCancel(ctx,journal,signingKeyPath,verifyKeyPath)){
|
|
707
|
+
derived=deriveState(plan,journal);return {status:'cancelled',task_id:taskId,...derived};
|
|
708
|
+
}
|
|
703
709
|
if (deadlineExpired()) {
|
|
704
710
|
derived = await failDeadline(ctx, journal, plan, next.step_id);
|
|
705
711
|
return { status: 'failed', task_id: taskId, ...derived, deadline };
|
package/src/deadbyte-cli.mjs
CHANGED
|
@@ -13,8 +13,9 @@ import { openRemoteResultJournal, parseRemoteResultCursor } from './remote-resul
|
|
|
13
13
|
import { DEADBYTE_VERSION } from './version.mjs';
|
|
14
14
|
import { parseActivePointer } from './release-slot.mjs';
|
|
15
15
|
import { printDoctor } from './doctor-r45.mjs';
|
|
16
|
-
import { createLiveTimelineFilter, createLiveTimelineTracker, createLiveTimelineWriter, formatCompactAuditEvent, formatRemoteLifecycle, formatRemoteToolCall, formatRuntimeDiagnosticLine } from './remote-live-log.mjs';
|
|
16
|
+
import { createLiveTimelineFilter, createLiveTimelineTracker, createLiveTimelineWriter, formatCompactAuditEvent, formatRemoteLifecycle, formatRemoteToolCall, formatRuntimeDiagnosticLine, selectLiveLogNames } from './remote-live-log.mjs';
|
|
17
17
|
import { prepareRemoteConsole, remoteDisplayText } from './remote-console.mjs';
|
|
18
|
+
import { ensureRuntimeCoherence } from './runtime-coherence.mjs';
|
|
18
19
|
|
|
19
20
|
const REMOTE_COMMANDS=new Set([
|
|
20
21
|
'menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest',
|
|
@@ -29,12 +30,17 @@ const SESSION_STATUS=[
|
|
|
29
30
|
['processstatus',/^Process plane\s*:\s*ARMED\b/m],
|
|
30
31
|
['codingstatus',/^Coding plane\s*:\s*ARMED\b/m],
|
|
31
32
|
['autonomousstatus',/^Autonomous\s*:\s*ARMED\b/m],
|
|
33
|
+
['desktopstatus',/^Desktop plane\s*:\s*DISARMED\b/m],
|
|
32
34
|
['hoststatus',/^Host plane\s*:\s*DISARMED\b/m]
|
|
33
35
|
];
|
|
34
|
-
const SESSION_CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park'];
|
|
36
|
+
const SESSION_CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park'];
|
|
35
37
|
const SUPERVISOR_COMMANDS=Object.freeze({
|
|
36
38
|
start:'supervisorstart',stop:'supervisorstop',status:'supervisorstatus',recover:'supervisorrecover'
|
|
37
39
|
});
|
|
40
|
+
const CURRENT_PACKAGE_ROOT=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
|
|
41
|
+
const CURRENT_DEPLOY_SCRIPT=path.join(CURRENT_PACKAGE_ROOT,'scripts','deploy-r48.mjs');
|
|
42
|
+
const REMOTE_UPDATE_TIMEOUT_MS=900000;
|
|
43
|
+
const LIVE_LOG_DISCOVERY_INTERVAL_MS=1000;
|
|
38
44
|
|
|
39
45
|
function stateRoot(){return path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));}
|
|
40
46
|
async function requireRegular(file,label){const info=await lstat(file);if(!info.isFile()||info.isSymbolicLink()) throw new Error(`${label} must be a regular non-symlink file`);return file;}
|
|
@@ -94,6 +100,43 @@ async function reportVersionCoherence(root,{displayMode='ascii'}={}){
|
|
|
94
100
|
writeRemote(process.stderr,`${remoteTag('warn',displayMode)} VERSION SKEW: CLI package ${DEADBYTE_VERSION}; active runtime ${active.version} (${active.release_id})\n`,displayMode);
|
|
95
101
|
return active;
|
|
96
102
|
}
|
|
103
|
+
async function synchronizeForegroundRuntime(ctx,{displayMode='unicode'}={}){
|
|
104
|
+
const packageManifestSha256=createHash('sha256').update(await readFile(path.join(CURRENT_PACKAGE_ROOT,'MANIFEST.SHA256'))).digest('hex');
|
|
105
|
+
return ensureRuntimeCoherence({
|
|
106
|
+
cliVersion:DEADBYTE_VERSION,
|
|
107
|
+
packageManifestSha256,
|
|
108
|
+
readActive:()=>readActiveRuntimeBinding(ctx.root),
|
|
109
|
+
disarmPrivileged:async()=>{
|
|
110
|
+
for(const command of ['disarmhost','disarmdesktop']){
|
|
111
|
+
const result=invokeController(ctx,command,{capture:true,allowFailure:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
|
|
112
|
+
if(result.status!==0) throw new Error(`runtime coherence '${command}' failed exit=${result.status}`);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
activateCurrentPackage:async()=>{
|
|
116
|
+
const result=spawnSync(process.execPath,[CURRENT_DEPLOY_SCRIPT],{
|
|
117
|
+
cwd:CURRENT_PACKAGE_ROOT,
|
|
118
|
+
env:{...process.env,DEADBYTE_STATE_ROOT:ctx.root},
|
|
119
|
+
encoding:'utf8',
|
|
120
|
+
windowsHide:true,
|
|
121
|
+
shell:false,
|
|
122
|
+
timeout:REMOTE_UPDATE_TIMEOUT_MS,
|
|
123
|
+
maxBuffer:16*1024*1024
|
|
124
|
+
});
|
|
125
|
+
if(result.error){
|
|
126
|
+
if(result.error?.code==='ETIMEDOUT') throw new Error(`runtime coherence activation timed out after ${REMOTE_UPDATE_TIMEOUT_MS}ms`);
|
|
127
|
+
throw result.error;
|
|
128
|
+
}
|
|
129
|
+
if(result.status!==0){
|
|
130
|
+
const detail=boundedSingleLine(result.stderr||result.stdout||'no deployment output',2048);
|
|
131
|
+
throw new Error(`runtime coherence activation failed exit=${String(result.status)}: ${detail}`);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
onEvent:event=>{
|
|
135
|
+
if(event.phase==='upgrade_start') writeRemote(process.stdout,`[UPDATE] Runtime ${event.from_version} -> ${event.to_version}; activating current package...\n`,displayMode);
|
|
136
|
+
else if(event.phase==='upgrade_complete') writeRemote(process.stdout,`[UPDATE] Runtime synchronized: ${event.release_id||event.to_version}\n`,displayMode);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
97
140
|
async function readControllerStartFailure(root,command,{maxChars=2048}={}){
|
|
98
141
|
const file=path.join(root,'runtime','controller-start-failure.json');
|
|
99
142
|
let raw;
|
|
@@ -158,32 +201,34 @@ function controllerRuntimeDegraded(text){
|
|
|
158
201
|
return /^DEADBYTE MCP\s*=\s*DEGRADED\s*$/m.test(String(text||''));
|
|
159
202
|
}
|
|
160
203
|
async function listLogFiles(root){
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
204
|
+
const auditDir=path.join(root,'audit');
|
|
205
|
+
const runtimeDir=path.join(root,'runtime','logs');
|
|
206
|
+
let auditNames=[],runtimeNames=[];
|
|
207
|
+
try{auditNames=await readdir(auditDir);}catch(error){if(error?.code!=='ENOENT') throw error;}
|
|
208
|
+
try{runtimeNames=await readdir(runtimeDir);}catch(error){if(error?.code!=='ENOENT') throw error;}
|
|
209
|
+
const selected=selectLiveLogNames({auditNames,runtimeNames});
|
|
210
|
+
return [
|
|
211
|
+
...selected.audit.map(name=>path.join(auditDir,name)),
|
|
212
|
+
...selected.runtime.map(name=>path.join(runtimeDir,name))
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
function parseAuditLogLine(file,line){
|
|
216
|
+
if(!/^segment-\d{8}\.jsonl$/.test(path.basename(file))||!line) return null;
|
|
217
|
+
try{return JSON.parse(line);}catch{return null;}
|
|
172
218
|
}
|
|
173
|
-
function formatLogLine(file,line,{activityTracker=null,sequence=null,verbose=false,displayMode='unicode'}={}){
|
|
219
|
+
function formatLogLine(file,line,{auditEvent=null,activityTracker=null,sequence=null,verbose=false,displayMode='unicode'}={}){
|
|
174
220
|
if(!line) return null;
|
|
175
221
|
if(/^segment-\d{8}\.jsonl$/.test(path.basename(file))){
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
}catch{return null;}
|
|
222
|
+
const event=auditEvent??parseAuditLogLine(file,line);
|
|
223
|
+
if(!event) return null;
|
|
224
|
+
return activityTracker?activityTracker.observe(event,{sequence}):formatCompactAuditEvent(event);
|
|
180
225
|
}
|
|
181
226
|
return formatRuntimeDiagnosticLine(file,line,{verbose,displayMode});
|
|
182
227
|
}
|
|
183
228
|
function remoteResultJournalPath(root,sessionId){return path.join(root,'runtime','remote-results',`${sessionId}.json`);}
|
|
184
|
-
function auditTimelineRecord(file,line){
|
|
185
|
-
|
|
186
|
-
|
|
229
|
+
function auditTimelineRecord(file,line,auditEvent=null){
|
|
230
|
+
const event=auditEvent??parseAuditLogLine(file,line);
|
|
231
|
+
if(!event) return null;
|
|
187
232
|
if(typeof event?.request_id!=='string'||!event.request_id||typeof event.tool!=='string'||!event.tool) return null;
|
|
188
233
|
const stamp=[event.ts,event.created_at_utc,event.observed_at_utc].find(value=>typeof value==='string'&&Number.isFinite(Date.parse(value)));
|
|
189
234
|
const observed=stamp?new Date(Date.parse(stamp)).toISOString():new Date().toISOString();
|
|
@@ -239,7 +284,8 @@ async function readAppend(file,offset){
|
|
|
239
284
|
}
|
|
240
285
|
async function snapshotRemoteLogOffsets(root){
|
|
241
286
|
const offsets=new Map();
|
|
242
|
-
|
|
287
|
+
const files=await listLogFiles(root);
|
|
288
|
+
for(const file of files){
|
|
243
289
|
try{offsets.set(file,(await stat(file)).size);}catch{}
|
|
244
290
|
}
|
|
245
291
|
return offsets;
|
|
@@ -264,20 +310,28 @@ async function tailRemoteLogs(root,signal,{journal=null,offsets=null,activityTra
|
|
|
264
310
|
? '[LOG] Verbose live timeline. Full sanitized request detail enabled; Ctrl+C disconnects and revokes authority.\n'
|
|
265
311
|
: '[LOG] Human live timeline. Transport IN/OUT hidden; full replay/audit detail remains on disk. Ctrl+C disconnects and revokes authority.\n');
|
|
266
312
|
const activeOffsets=offsets??await snapshotRemoteLogOffsets(root);
|
|
313
|
+
let liveFiles=[...activeOffsets.keys()];
|
|
314
|
+
let nextDiscoveryAt=0;
|
|
267
315
|
while(!signal.aborted){
|
|
268
|
-
|
|
316
|
+
const now=Date.now();
|
|
317
|
+
if(now>=nextDiscoveryAt){
|
|
318
|
+
liveFiles=await listLogFiles(root);
|
|
319
|
+
nextDiscoveryAt=now+LIVE_LOG_DISCOVERY_INTERVAL_MS;
|
|
320
|
+
}
|
|
321
|
+
for(const file of liveFiles){
|
|
269
322
|
const prior=activeOffsets.has(file)?activeOffsets.get(file):0;
|
|
270
323
|
const next=await readAppend(file,prior);
|
|
271
324
|
activeOffsets.set(file,next.offset);
|
|
272
325
|
for(const line of next.text.split(/\r?\n/)){
|
|
273
|
-
const
|
|
326
|
+
const auditEvent=parseAuditLogLine(file,line);
|
|
327
|
+
const timeline=auditTimelineRecord(file,line,auditEvent);
|
|
274
328
|
let recorded=null;
|
|
275
329
|
if(timeline&&journal) recorded=timeline.event_type==='tool.call'
|
|
276
330
|
? await journal.recordCall(timeline)
|
|
277
331
|
: await journal.recordTerminal(timeline);
|
|
278
332
|
if(recorded?.duplicate) continue;
|
|
279
333
|
const accepted=timelineFilter&&timeline?timelineFilter.accept(timelineFilterEvent(timeline)):true;
|
|
280
|
-
const formatted=formatLogLine(file,line,{activityTracker,sequence:recorded?.event?.seq??null,verbose,displayMode});
|
|
334
|
+
const formatted=formatLogLine(file,line,{auditEvent,activityTracker,sequence:recorded?.event?.seq??null,verbose,displayMode});
|
|
281
335
|
if(!formatted||!accepted) continue;
|
|
282
336
|
const output=Array.isArray(formatted)?formatted:[formatted];
|
|
283
337
|
for(const item of output) if(item){
|
|
@@ -381,11 +435,17 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
381
435
|
const display=prepareRemoteConsole();
|
|
382
436
|
const displayMode=display.mode;
|
|
383
437
|
const ctx=await remoteContext();
|
|
384
|
-
await reportVersionCoherence(ctx.root,{displayMode});
|
|
385
438
|
if(displayMode==='ascii') writeRemote(process.stdout,`[DISPLAY] ASCII fallback: ${display.reason}\n`,displayMode);
|
|
386
439
|
if(resumeCursor!==null) await replayRemoteResults(ctx.root,resumeCursor,{displayMode});
|
|
387
440
|
await recoverRemoteSession(ctx,{displayMode});
|
|
388
441
|
const marker=await claimRemoteSession(ctx);
|
|
442
|
+
try{
|
|
443
|
+
await synchronizeForegroundRuntime(ctx,{displayMode});
|
|
444
|
+
}catch(error){
|
|
445
|
+
await cleanupRemoteSession(ctx,{displayMode});
|
|
446
|
+
await releaseRemoteSession(ctx,marker,{displayMode});
|
|
447
|
+
throw error;
|
|
448
|
+
}
|
|
389
449
|
const resultJournal=await openRemoteResultJournal({
|
|
390
450
|
filePath:remoteResultJournalPath(ctx.root,marker.result_session_id),sessionId:marker.result_session_id,maxEntries:2048
|
|
391
451
|
});
|
|
@@ -442,13 +502,13 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
442
502
|
process.stdout.write('[OK] ONLINE\n');
|
|
443
503
|
process.stdout.write(`${formatRemoteLifecycle('ready',{displayMode})}\n`);
|
|
444
504
|
if(url) writeRemote(process.stdout,`URL: ${url}\n`,displayMode);
|
|
445
|
-
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Verifying process, coding, autonomous; host must stay DISARMED...\n`,displayMode);
|
|
505
|
+
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} Verifying process, coding, autonomous; desktop + host must stay DISARMED...\n`,displayMode);
|
|
446
506
|
for(const [command,pattern] of SESSION_STATUS){
|
|
447
507
|
const result=invokeController(ctx,command,{capture:true,echoCaptured:false});
|
|
448
508
|
if(!pattern.test(result.stdout)) throw new Error(`remote session authority verification failed: ${command} state mismatch`);
|
|
449
509
|
}
|
|
450
510
|
ready=true;
|
|
451
|
-
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} ARMED: process, coding, autonomous | host DISARMED\n`,displayMode);
|
|
511
|
+
writeRemote(process.stdout,`${remoteTag('auth',displayMode)} ARMED: process, coding, autonomous | desktop, host DISARMED\n`,displayMode);
|
|
452
512
|
await logTask;
|
|
453
513
|
}finally{
|
|
454
514
|
const wasAborted=aborter.signal.aborted;
|
package/src/remote-live-log.mjs
CHANGED
|
@@ -53,6 +53,13 @@ const TOOL_ICONS_ASCII=Object.freeze({read:'R',write:'W',edit:'E',run:'>',verify
|
|
|
53
53
|
const START_VISIBLE_PATTERNS=[/(?:^|_)file_write$/,/text_replace$/,/symbol_replace$/,/native_diff_(?:apply|preview)$/,
|
|
54
54
|
/(?:command|benchmark)_run$/,/task_run$/,/goal_run$/,/goal_submit$/,/goal_approve$/,/process_session_(?:start|action|terminate)$/,/^desktop_action$/];
|
|
55
55
|
|
|
56
|
+
export function selectLiveLogNames({auditNames=[],runtimeNames=[]}={}){
|
|
57
|
+
if(!Array.isArray(auditNames)||!Array.isArray(runtimeNames)) throw new Error('live log names must be arrays');
|
|
58
|
+
const audit=auditNames.filter(name=>typeof name==='string'&&/^segment-\d{8}\.jsonl$/.test(name)).sort().slice(-2);
|
|
59
|
+
const runtime=runtimeNames.filter(name=>typeof name==='string'&&/\.log$/i.test(name)).sort();
|
|
60
|
+
return Object.freeze({audit:Object.freeze(audit),runtime:Object.freeze(runtime)});
|
|
61
|
+
}
|
|
62
|
+
|
|
56
63
|
export function truncateUtf8(value,maxBytes,{ellipsis='…'}={}){
|
|
57
64
|
if(typeof value!=='string'||!Number.isInteger(maxBytes)||maxBytes<1) return '';
|
|
58
65
|
const bytes=new TextEncoder().encode(value);
|
|
@@ -312,18 +319,19 @@ export function createLiveTimelineWriter({stream=process.stdout,displayMode='uni
|
|
|
312
319
|
return Object.freeze({write,flush,pending(){return active?{...active}:null;}});
|
|
313
320
|
}
|
|
314
321
|
|
|
315
|
-
export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,heartbeatBurstLimit=10,heartbeatWindowMs=3000,displayMode='unicode',rootPaths={},trace=false}={}){
|
|
322
|
+
export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,heartbeatBurstLimit=10,heartbeatWindowMs=3000,displayMode='unicode',rootPaths={},trace=false,slowCallMs=1000}={}){
|
|
316
323
|
if(typeof clock!=='function') throw new Error('live timeline clock must be a function');
|
|
317
324
|
if(!REMOTE_DISPLAY_MODES.includes(displayMode)) throw new Error('remote display mode invalid');
|
|
318
325
|
if(!Number.isInteger(heartbeatBurstLimit)||heartbeatBurstLimit<2||heartbeatBurstLimit>100) throw new Error('heartbeatBurstLimit invalid');
|
|
319
326
|
if(!Number.isInteger(heartbeatWindowMs)||heartbeatWindowMs<250||heartbeatWindowMs>60000) throw new Error('heartbeatWindowMs invalid');
|
|
320
327
|
if(!rootPaths||typeof rootPaths!=='object'||Array.isArray(rootPaths)) throw new Error('live timeline rootPaths invalid');
|
|
321
328
|
if(typeof trace!=='boolean') throw new Error('live timeline trace invalid');
|
|
329
|
+
if(!Number.isInteger(slowCallMs)||slowCallMs<100||slowCallMs>600000) throw new Error('live timeline slowCallMs invalid');
|
|
322
330
|
const pending=new Map();
|
|
323
331
|
const completed=new Set();
|
|
324
332
|
const toolCounts=new Map();
|
|
325
333
|
const durationSamples=[];
|
|
326
|
-
let resultCount=0,okCount=0,errorCount=0,unknownCount=0,mutationCount=0,totalDurationMs=0,slowest=null,burst=null,foldedHeartbeatLines=0;
|
|
334
|
+
let resultCount=0,okCount=0,errorCount=0,unknownCount=0,mutationCount=0,totalDurationMs=0,slowest=null,burst=null,foldedHeartbeatLines=0,peakInFlight=0;
|
|
327
335
|
|
|
328
336
|
function eventPrefix(event,sequence,{displayTool=null,icon=null}={}){
|
|
329
337
|
const seq=Number.isSafeInteger(sequence)&&sequence>0?'#'+sequence+' ':'';
|
|
@@ -336,9 +344,11 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,he
|
|
|
336
344
|
const error=event.outcome?.is_error===true;
|
|
337
345
|
const unknown=event.outcome?.side_effect_state==='unknown';
|
|
338
346
|
const displayTool=span?.display_tool||event.tool;
|
|
339
|
-
const
|
|
340
|
-
|
|
347
|
+
const rawDuration=Number(event.outcome?.duration_ms);
|
|
348
|
+
const duration=Number.isFinite(rawDuration)
|
|
349
|
+
?formatDuration(rawDuration)
|
|
341
350
|
:span?formatDuration(Math.max(0,clock()-span.started_at_ms)):'';
|
|
351
|
+
const slow=Number.isFinite(rawDuration)&&rawDuration>=slowCallMs?' [SLOW]':'';
|
|
342
352
|
const summary=span?.summary||'';
|
|
343
353
|
const rawReason=typeof event.outcome?.reason==='string'&&event.outcome.reason?event.outcome.reason:
|
|
344
354
|
(unknown&&typeof event.outcome?.recovery?.reason==='string'?event.outcome.recovery.reason:'');
|
|
@@ -353,7 +363,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,he
|
|
|
353
363
|
const prefixIcon=unknown?(displayMode==='ascii'?'WARN':'⚠️'):null;
|
|
354
364
|
const evidence=receipt?' '+(displayMode==='ascii'?'RECEIPT':'🧾 receipt')+(displayMode==='ascii'?'=':'=')+receipt:'';
|
|
355
365
|
const reasonSep=displayMode==='ascii'?' - ':' — ';
|
|
356
|
-
return remoteDisplayText(eventPrefix(event,sequence,{displayTool,icon:prefixIcon})+traceSuffix(span?.trace_meta)+(summary?' '+summary:'')+(duration?' '+duration:'')+' '+marker+evidence+(reason?reasonSep+reason:''),displayMode);
|
|
366
|
+
return remoteDisplayText(eventPrefix(event,sequence,{displayTool,icon:prefixIcon})+traceSuffix(span?.trace_meta)+(summary?' '+summary:'')+(duration?' '+duration:'')+slow+' '+marker+evidence+(reason?reasonSep+reason:''),displayMode);
|
|
357
367
|
}
|
|
358
368
|
function burstLine(value){
|
|
359
369
|
if(!value) return null;
|
|
@@ -396,6 +406,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,he
|
|
|
396
406
|
const summary=summarizeLiveToolArgs(displayTool,args,{rootPaths});
|
|
397
407
|
const traceMeta=traceIdentity(event,args);
|
|
398
408
|
pending.set(requestId,{request_id:requestId,tool,display_tool:displayTool,summary,trace_meta:traceMeta,started_at_ms:clock()});
|
|
409
|
+
peakInFlight=Math.max(peakInFlight,pending.size);
|
|
399
410
|
if(verbose) return formatRemoteToolCall({...event,tool:displayTool},{clock,displayMode})+traceSuffix(traceMeta);
|
|
400
411
|
if(visibleStart(displayTool)) return eventPrefix(event,sequence,{displayTool})+traceSuffix(traceMeta)+(summary?' '+summary:'')+' …';
|
|
401
412
|
return null;
|
|
@@ -453,6 +464,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,he
|
|
|
453
464
|
' | tool time '+(formatDuration(totalDurationMs)||'0ms')+
|
|
454
465
|
(p50!==null?' | p50 '+formatDuration(p50)+' | p95 '+formatDuration(p95):'')+
|
|
455
466
|
(slowest?' | slowest '+slowest.tool+' '+formatDuration(slowest.duration_ms):'')+
|
|
467
|
+
' | peak in-flight '+peakInFlight+
|
|
456
468
|
(foldedHeartbeatLines?' | folded '+foldedHeartbeatLines+' heartbeat lines':'')+
|
|
457
469
|
(top?' | top: '+top:''),displayMode);
|
|
458
470
|
}
|
|
@@ -5,7 +5,7 @@ import os from 'node:os';
|
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
7
7
|
|
|
8
|
-
const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmhost','park'];
|
|
8
|
+
const CLEANUP=['disarmautonomous','disarmcoding','disarmprocess','disarmdesktop','disarmhost','park'];
|
|
9
9
|
|
|
10
10
|
function parseArgs(argv){
|
|
11
11
|
const out={};
|