deadbyte-mcp 0.15.44 → 0.15.46
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 +86 -138
- package/PROVENANCE.json +35 -18
- package/README.txt +22 -21
- package/RELEASE.json +369 -334
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +27 -17
- package/bridge/package.json +1 -1
- package/controller/deadbyte-controller.ps1 +1871 -1769
- package/controller/deadbyte-desktop-policy.json +6 -6
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R68-COMPAT-HOST-EXEC-CANDIDATE.md +63 -0
- package/docs/R69-LIVE-OUTCOME-R70-POLICY-TRANSFER.md +136 -0
- package/docs/R70-CODING-DIGEST-SUPERVISOR-CHECKPOINT-20261013.md +42 -0
- package/docs/R70-FINITE-GRANT-OWNER-BOUNDARY-20261013.md +80 -0
- package/docs/R70-OWNER-BOOTSTRAP-CHECKPOINT-20261013.md +60 -0
- package/docs/R70-REFERENCE-MECHANISMS.json +95 -0
- package/docs/R70-TRUST-GUARD-EXPIRY-CHECKPOINT-20261013.md +47 -0
- package/package.json +2 -2
- package/scripts/benchmark-l0-r71.mjs +76 -0
- package/scripts/gate-windows-r71.ps1 +64 -0
- package/scripts/gate-windows-r72.ps1 +76 -0
- package/scripts/npm-release-r48.mjs +4 -0
- package/scripts/npm-release-readiness-r71.mjs +21 -0
- package/scripts/npm-release-readiness-r72.mjs +76 -0
- package/scripts/verify-r70-full-regression.ps1 +72 -0
- package/src/authority-continuity-r65.mjs +28 -8
- package/src/autonomous-mcp-tools.mjs +1 -1
- package/src/autonomous-output-contracts.mjs +4 -1
- package/src/autonomous-policy.mjs +54 -7
- package/src/capability-ledger.mjs +1 -1
- package/src/capability-resolver.mjs +2 -1
- package/src/commander-compat-runtime.mjs +30 -2
- package/src/deadbyte-cli.mjs +722 -719
- package/src/launcher-isolation.mjs +58 -0
- package/src/native-diff-v2.mjs +18 -5
- package/src/native-diff.mjs +21 -5
- package/src/r70-owner-approval.mjs +78 -0
- package/src/remote-authority.mjs +75 -75
- package/src/remote-live-log.mjs +48 -14
- package/src/remote-mcp-machine-smoke-client.mjs +84 -84
- package/src/remote-result-journal.mjs +1 -1
- package/src/runtime-update.mjs +232 -24
- package/src/staging-input-isolation.mjs +79 -0
- package/src/tool-audit.mjs +2 -0
- package/src/version.mjs +1 -1
- package/test/autonomous-local-supervisor.test.mjs +2 -1
- package/test/autonomous-output-contract.test.mjs +17 -1
- package/test/autonomous-supervisor-integration.test.mjs +2 -1
- package/test/capability-resolver.test.mjs +17 -0
- package/test/codex-autonomous-r44.test.mjs +1 -1
- package/test/commander-compat-runtime.test.mjs +42 -0
- package/test/controller-policy-preflight-r59.test.mjs +1 -0
- package/test/deferred-slot-operation-behavior.test.mjs +2 -2
- package/test/native-diff-topology-r36.test.mjs +35 -2
- package/test/native-diff.test.mjs +83 -3
- package/test/r58-cleanup-retention.test.mjs +3 -3
- package/test/r58-concurrent-recovery.test.mjs +3 -3
- package/test/r58-stale-owner.test.mjs +1 -1
- package/test/r59-tool-surface-compat.test.mjs +9 -5
- package/test/r60-authority-coherence.test.mjs +10 -2
- package/test/r65-authority-runtime-epoch.test.mjs +9 -1
- package/test/r67-isolated-slot-rollback-restore.test.mjs +2 -1
- package/test/r67-npm-publish-gate.test.mjs +4 -2
- package/test/r67-release-contract.test.mjs +8 -7
- package/test/r69-live-outcome.test.mjs +137 -0
- package/test/r70-autonomous-grant-expiry.test.mjs +89 -0
- package/test/r70-autonomous-policy-bootstrap.test.mjs +166 -0
- package/test/r70-controller-autonomous-root.test.mjs +104 -0
- package/test/r70-controller-grant-subset.test.mjs +28 -0
- package/test/r70-controller-owner-issue-boundary.test.mjs +80 -0
- package/test/r70-controller-policy-snapshot.test.mjs +66 -0
- package/test/r70-owner-approval.test.mjs +67 -0
- package/test/r71-full-verifier-artifact.test.mjs +28 -0
- package/test/r71-isolated-slot-rollback-restore.test.mjs +62 -0
- package/test/r71-native-diff-mcp-wire.test.mjs +75 -0
- package/test/r71-publish-fence.test.mjs +38 -0
- package/test/r71-release-identity.test.mjs +33 -0
- package/test/r72-launcher-isolation.test.mjs +99 -0
- package/test/r72-publication-fence.test.mjs +56 -0
- package/test/r72-publication-output-integrity.test.mjs +110 -0
- package/test/r72-remote-safe-contract.test.mjs +101 -0
- package/test/r72-staging-input-isolation.test.mjs +87 -0
- package/test/r72-staging-workspace-isolation.test.mjs +114 -0
- package/test/remote-result-journal.test.mjs +12 -0
- package/test/remote-session-cli.test.mjs +112 -65
- package/test/standing-grants.test.mjs +2 -2
- package/test/tool-audit.test.mjs +14 -0
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-desktop-input-r40.obj +0 -0
- package/bin/deadbyte-desktop-observer-r40.obj +0 -0
- package/bin/deadbyte-desktop-uia-r40.obj +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/bin/deadbyte-native-inspect-r67.obj +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/conpty-input-observer.obj +0 -0
- package/miniconpty.obj +0 -0
- package/proof/CONTAINMENT-BUILD.txt +0 -163
- package/proof/MCP-SMOKE-STATUS.txt +0 -9
- package/proof/NODE-SYNTAX.txt +0 -4
- package/proof/PROOF.txt +0 -6
- package/proof/R34-TASK8-SUPERVISOR-CHECKPOINT.json +0 -29
- package/proof/R34-TASK9-MCP-SURFACE-CHECKPOINT.json +0 -28
- package/proof/SECRET-SCAN.txt +0 -4
- package/proof/SOURCE-GATES.txt +0 -16
- package/proof/STATUS.txt +0 -11
- package/proof/TARGET-V0313-CONTAINMENT-PASS.sha256 +0 -1
- package/proof/TARGET-V0313-CONTAINMENT-PASS.txt +0 -215
- package/proof/TARGET-V040-CONTAINED-IPC-FAIL.sha256 +0 -1
- package/proof/TARGET-V040-CONTAINED-IPC-FAIL.txt +0 -299
- package/proof/TARGET-V041-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
- package/proof/TARGET-V041-CONTAINED-CREATEPROCESS-203.txt +0 -311
- package/proof/TARGET-V042-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
- package/proof/TARGET-V042-CONTAINED-CREATEPROCESS-203.txt +0 -313
- package/proof/TARGET-V043-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
- package/proof/TARGET-V043-CONTAINED-CREATEPROCESS-203.txt +0 -308
- package/proof/TARGET-V044-CONTAINED-PASS.sha256 +0 -1
- package/proof/TARGET-V044-CONTAINED-PASS.txt +0 -432
- package/proof/TARGET-V045-DUAL-OPERATION-PASS.sha256 +0 -1
- package/proof/TARGET-V045-DUAL-OPERATION-PASS.txt +0 -481
- package/proof/TARGET-V046-FIXED-EXECUTABLE-REGISTRY-PASS.sha256 +0 -1
- package/proof/TARGET-V046-FIXED-EXECUTABLE-REGISTRY-PASS.txt +0 -610
- package/proof/TARGET-V047-RUNTIME-MANIFEST-PASS.sha256 +0 -1
- package/proof/TARGET-V047-RUNTIME-MANIFEST-PASS.txt +0 -642
- package/proof/TARGET-V050-BATCH-GATE-PASS.sha256 +0 -1
- package/proof/TARGET-V050-BATCH-GATE-PASS.txt +0 -713
- package/proof/TESTS.txt +0 -686
- package/proof/TRUST-EPHEMERAL.txt +0 -4
- package/proof/V03-FREEZE.txt +0 -27
- package/proof/V041-IPC-HOTFIX.txt +0 -16
- package/proof/V042-ENV-HOTFIX.txt +0 -15
- package/proof/V043-DRIVE-CWD-HOTFIX.txt +0 -14
- package/proof/V044-LOCALAPPDATA-HOTFIX.txt +0 -18
- package/proof/V045-OPERATION-REGISTRY.txt +0 -7
- package/proof/V046-WORKER-REGISTRY.txt +0 -12
- package/proof/V047-WORKER-MANIFEST.txt +0 -18
- package/proof/V050-BATCH-TRUST-CLOSURE.txt +0 -21
- package/proof/V060-ACTIVE-NETWORK-AUTHORITY.txt +0 -29
- package/proof/V070-NODE-SYNTAX.txt +0 -36
- package/proof/V070-NODE-TESTS.txt +0 -752
- package/proof/V070-PACK-DRY-RUN.txt +0 -126
- package/proof/V070-SOURCE-GATE.txt +0 -30
- package/proof/V070-STATIC-SCAN.txt +0 -4
- package/proof/V070-WINDOWS-GATE-PENDING.txt +0 -25
- package/proof/V071-NODE-SYNTAX.txt +0 -1
- package/proof/V071-NODE-TESTS.txt +0 -796
- package/proof/V071-PACK-DRY-RUN.json +0 -656
- package/proof/V071-SOURCE-GATE.txt +0 -16
- package/proof/V071-WINDOWS-GATE-PENDING.txt +0 -24
- package/proof/V073-NODE-SYNTAX.txt +0 -4
- package/proof/V073-SOURCE-GATE.txt +0 -21
- package/proof/V073-STATIC-SCAN.txt +0 -10
- package/proof/V073-WINDOWS-GATE-PENDING.txt +0 -5
- package/proof/VERIFY.txt +0 -7
- package/proof/WINDOWS-CONTAINED-STATUS.txt +0 -5
- package/proof/WINDOWS-CONTAINMENT-STATUS.txt +0 -3
- package/proof/commander-closure-20260927/CONTINUATION.json +0 -43
- package/proof/commander-closure-20260927/RESULT.json +0 -30
- package/proof/commander-closure-20260927/SEARCH-CONTINUATION.json +0 -30
- package/proof/commander-closure-20260927/XLS-REFERENCE-PROBE.json +0 -13
- package/proof/commander-closure-20260928/DIFFERENTIAL-CHECKPOINT.json +0 -122
- package/proof/r59-full-regression-current.json +0 -13
- package/proof/r59-full-regression-current.stderr.log +0 -0
- package/proof/r59-full-regression-current.stdout.log +0 -1268
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** R72: state-root-owned launcher routing. No staging caller may write the user-wide Downloads launcher. */
|
|
2
|
+
import {lstatSync} from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const normalize=file=>process.platform==='win32'?path.resolve(file).toLowerCase():path.resolve(file);
|
|
7
|
+
function contains(parent,child){
|
|
8
|
+
const rel=path.relative(parent,child);
|
|
9
|
+
return rel===''||(!path.isAbsolute(rel)&&rel!=='..'&&!rel.startsWith(`..${path.sep}`));
|
|
10
|
+
}
|
|
11
|
+
function assertNoReparseParts(file){
|
|
12
|
+
let cursor=path.resolve(file);
|
|
13
|
+
while(true){
|
|
14
|
+
try{
|
|
15
|
+
const info=lstatSync(cursor);
|
|
16
|
+
if(info.isSymbolicLink())throw new Error(`staging launcher boundary is a reparse/symlink: ${cursor}`);
|
|
17
|
+
}catch(error){if(error?.code!=='ENOENT')throw error;}
|
|
18
|
+
const parent=path.dirname(cursor);
|
|
19
|
+
if(parent===cursor)break;
|
|
20
|
+
cursor=parent;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function canonicalLauncherPaths(stateRoot,{home=os.homedir()}={}){
|
|
24
|
+
if(typeof stateRoot!=='string'||!path.isAbsolute(stateRoot)||stateRoot.includes('\0'))throw new Error('launcher state root must be absolute');
|
|
25
|
+
const state=path.resolve(stateRoot),productionState=path.resolve(home,'.deadbyte-mcp');
|
|
26
|
+
const production=normalize(state)===normalize(productionState);
|
|
27
|
+
if(!production){
|
|
28
|
+
if(contains(normalize(productionState),normalize(state))||contains(normalize(state),normalize(productionState)))
|
|
29
|
+
throw new Error('staging state overlaps production state root');
|
|
30
|
+
assertNoReparseParts(state);
|
|
31
|
+
assertNoReparseParts(path.join(state,'bin'));
|
|
32
|
+
assertNoReparseParts(path.join(state,'launchers'));
|
|
33
|
+
}
|
|
34
|
+
const top=production?path.join(home,'Downloads','DEADBYTE-MCP.cmd'):path.join(state,'launchers','DEADBYTE-MCP.cmd');
|
|
35
|
+
if(!production)assertNoReparseParts(top);
|
|
36
|
+
return Object.freeze({state,production,top,ps1:path.join(state,'bin','DEADBYTE-MCP.ps1'),cmd:path.join(state,'bin','DEADBYTE-MCP.cmd')});
|
|
37
|
+
}
|
|
38
|
+
export function assertCanonicalLaunchers(stateRoot,launchers){
|
|
39
|
+
const expected=canonicalLauncherPaths(stateRoot);
|
|
40
|
+
if(!launchers||typeof launchers!=='object')throw new Error('launcher paths absent');
|
|
41
|
+
for(const field of ['top','ps1','cmd']){
|
|
42
|
+
if(typeof launchers[field]!=='string'||!path.isAbsolute(launchers[field])||
|
|
43
|
+
normalize(launchers[field])!==normalize(expected[field]))
|
|
44
|
+
throw new Error(`launcher ${field} is not canonical for selected state root`);
|
|
45
|
+
}
|
|
46
|
+
return expected;
|
|
47
|
+
}
|
|
48
|
+
/** Relative to bin/ or launchers/, so the entry point always selects its own state root. */
|
|
49
|
+
export function renderStateBoundLauncherCmd(){
|
|
50
|
+
return [
|
|
51
|
+
'@echo off',
|
|
52
|
+
'setlocal EnableExtensions DisableDelayedExpansion',
|
|
53
|
+
'set "DEADBYTE_STATE_ROOT=%~dp0.."',
|
|
54
|
+
'powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File "%DEADBYTE_STATE_ROOT%\\bin\\DEADBYTE-MCP.ps1" %*',
|
|
55
|
+
'exit /b %ERRORLEVEL%',
|
|
56
|
+
''
|
|
57
|
+
].join('\r\n');
|
|
58
|
+
}
|
package/src/native-diff-v2.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { canonicalJson } from './canonical-json.mjs';
|
|
5
5
|
import { validatePortableRelativePath } from './path-policy.mjs';
|
|
6
6
|
import { loadPrivateSigningKey, loadPublicVerifyKey } from './trust.mjs';
|
|
7
|
-
import { assertAutonomousArmed, recheckAutonomousAuthority } from './autonomous-policy.mjs';
|
|
7
|
+
import { assertAutonomousArmed, autonomousSessionStatus, recheckAutonomousAuthority } from './autonomous-policy.mjs';
|
|
8
8
|
|
|
9
9
|
export const NATIVE_DIFF_V2_SCHEMA='deadbyte.native-diff.v2';
|
|
10
10
|
export const NATIVE_DIFF_V2_PREIMAGE_SCHEMA='deadbyte.native-diff-preimage.v2';
|
|
@@ -107,9 +107,16 @@ export function createNativeDiffV2Runtime({policy,codingRuntime,machineFsRuntime
|
|
|
107
107
|
if(!machineFsRuntime?.state||!machineFsRuntime?.removeEmptyDir) throw new Error('native diff V2 requires machine fs topology runtime');
|
|
108
108
|
if(!signingKeyPath||!verifyKeyPath) throw new Error('native diff V2 requires trust keys');
|
|
109
109
|
|
|
110
|
-
async function materializeOperations(rootId,rawOperations){
|
|
111
|
-
if(
|
|
112
|
-
|
|
110
|
+
async function materializeOperations(rootId,rawOperations,{codingOnly=false}={}){
|
|
111
|
+
if(codingOnly){
|
|
112
|
+
const lease=await codingRuntime.sessionStatus();
|
|
113
|
+
if(!lease.armed) throw new Error(`coding plane is disarmed: ${lease.reason}`);
|
|
114
|
+
if(!lease.root_ids.includes(rootId)||codingRuntime.policy.roots[rootId]?.read!==true)
|
|
115
|
+
throw new Error(`native diff preview root '${rootId}' is not allowed by Coding lease`);
|
|
116
|
+
} else {
|
|
117
|
+
if(!policy.root_ids.includes(rootId)) throw new Error(`native diff root '${rootId}' is not allowed`);
|
|
118
|
+
await assertAutonomousArmed(policy,rootId);
|
|
119
|
+
}
|
|
113
120
|
const {normalized,payloadBytes}=normalizeRawOperations(rawOperations,policy);
|
|
114
121
|
const operations=[];
|
|
115
122
|
for(const op of normalized){
|
|
@@ -168,7 +175,13 @@ export function createNativeDiffV2Runtime({policy,codingRuntime,machineFsRuntime
|
|
|
168
175
|
async function preview({rootId,operations:rawOperations,title='',applicationScope=null}){
|
|
169
176
|
if(typeof title!=='string'||title.length>160) throw new Error('native diff title must be <=160 characters');
|
|
170
177
|
if(applicationScope!==null&&!SHA256_RE.test(applicationScope)) throw new Error('native diff application_scope must be SHA-256 or null');
|
|
171
|
-
const
|
|
178
|
+
const autonomous=policy.root_ids.includes(rootId)?await autonomousSessionStatus(policy):null;
|
|
179
|
+
const codingOnly=autonomous?.armed!==true||!autonomous.root_ids?.includes(rootId);
|
|
180
|
+
const {operations,payloadBytes}=await materializeOperations(rootId,rawOperations,{codingOnly});
|
|
181
|
+
if(codingOnly) return {status:'preview_only',schema:NATIVE_DIFF_V2_SCHEMA,root_id:rootId,
|
|
182
|
+
operation_count:operations.length,payload_bytes:payloadBytes,
|
|
183
|
+
paths:operations.map(op=>op.path??op.source_path),
|
|
184
|
+
operations:operations.map(({content,old_string,new_string,...op})=>op)};
|
|
172
185
|
const identity={schema:NATIVE_DIFF_V2_SCHEMA,policy_sha256:policy.policy_sha256,coding_policy_sha256:policy.coding_policy_sha256,root_id:rootId,title,operations,...(applicationScope===null?{}:{application_scope:applicationScope})};
|
|
173
186
|
const diffId=sha256Object(identity); const ctx=diffContext(policy,diffId); const signing=await loadPrivateSigningKey(signingKeyPath);
|
|
174
187
|
const envelope=signEnvelope(signing,DIFF_DOMAIN,{...identity,diff_id:diffId,created_at_utc:new Date().toISOString()});
|
package/src/native-diff.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { canonicalJson } from './canonical-json.mjs';
|
|
5
5
|
import { validatePortableRelativePath } from './path-policy.mjs';
|
|
6
6
|
import { loadPrivateSigningKey, loadPublicVerifyKey } from './trust.mjs';
|
|
7
|
-
import { assertAutonomousArmed, recheckAutonomousAuthority } from './autonomous-policy.mjs';
|
|
7
|
+
import { assertAutonomousArmed, autonomousSessionStatus, recheckAutonomousAuthority } from './autonomous-policy.mjs';
|
|
8
8
|
import { createNativeDiffV2Runtime, NATIVE_DIFF_V2_SCHEMA } from './native-diff-v2.mjs';
|
|
9
9
|
|
|
10
10
|
export const NATIVE_DIFF_SCHEMA = 'deadbyte.native-diff.v1';
|
|
@@ -102,9 +102,19 @@ function createNativeDiffRuntimeV1({ policy, codingRuntime, signingKeyPath, veri
|
|
|
102
102
|
if (!policy || !codingRuntime?.policy) throw new Error('native diff runtime requires autonomous and coding policy');
|
|
103
103
|
if (!signingKeyPath || !verifyKeyPath) throw new Error('native diff runtime requires trust keys');
|
|
104
104
|
|
|
105
|
-
async function materializeOperations(rootId, rawOperations) {
|
|
106
|
-
if (
|
|
107
|
-
|
|
105
|
+
async function materializeOperations(rootId, rawOperations, {codingOnly=false} = {}) {
|
|
106
|
+
if (codingOnly) {
|
|
107
|
+
// Preview authorization delegates ONLY to the existing, locally armed
|
|
108
|
+
// Coding read lease. No Autonomous grant, signer, or target mutation.
|
|
109
|
+
const lease=await codingRuntime.sessionStatus();
|
|
110
|
+
if (!lease.armed) throw new Error(`coding plane is disarmed: ${lease.reason}`);
|
|
111
|
+
if (!lease.root_ids.includes(rootId) || codingRuntime.policy.roots[rootId]?.read !== true) {
|
|
112
|
+
throw new Error(`native diff preview root '${rootId}' is not allowed by Coding lease`);
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
if (!policy.root_ids.includes(rootId)) throw new Error(`native diff root '${rootId}' is not allowed`);
|
|
116
|
+
await assertAutonomousArmed(policy, rootId);
|
|
117
|
+
}
|
|
108
118
|
const { normalized, payloadBytes } = normalizeRawOperations(rawOperations, policy);
|
|
109
119
|
const operations = [];
|
|
110
120
|
for (const op of normalized) {
|
|
@@ -132,7 +142,13 @@ function createNativeDiffRuntimeV1({ policy, codingRuntime, signingKeyPath, veri
|
|
|
132
142
|
async function preview({ rootId, operations:rawOperations, title = '', applicationScope = null }) {
|
|
133
143
|
if (typeof title !== 'string' || title.length > 160) throw new Error('native diff title must be <=160 characters');
|
|
134
144
|
if (applicationScope !== null && !SHA256_RE.test(applicationScope)) throw new Error('native diff application_scope must be SHA-256 or null');
|
|
135
|
-
const
|
|
145
|
+
const autonomous=policy.root_ids.includes(rootId)?await autonomousSessionStatus(policy):null;
|
|
146
|
+
const codingOnly=autonomous?.armed!==true||!autonomous.root_ids?.includes(rootId);
|
|
147
|
+
const { operations, payloadBytes } = await materializeOperations(rootId, rawOperations, {codingOnly});
|
|
148
|
+
if (codingOnly) return {status:'preview_only',schema:NATIVE_DIFF_SCHEMA,root_id:rootId,
|
|
149
|
+
operation_count:operations.length,payload_bytes:payloadBytes,
|
|
150
|
+
paths:operations.map(op=>op.path),
|
|
151
|
+
operations:operations.map(({content,old_string,new_string,...op})=>op)};
|
|
136
152
|
const identity = { schema:NATIVE_DIFF_SCHEMA, policy_sha256:policy.policy_sha256,
|
|
137
153
|
coding_policy_sha256:policy.coding_policy_sha256, root_id:rootId, title, operations,
|
|
138
154
|
...(applicationScope === null ? {} : { application_scope:applicationScope }) };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {createHash,createPublicKey,verify as verifySignature} from 'node:crypto';
|
|
2
|
+
import {lstat,realpath} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import {canonicalJson} from './canonical-json.mjs';
|
|
5
|
+
|
|
6
|
+
const SUBJECT_SCHEMA='deadbyte.project-authority-subject.r70.v1';
|
|
7
|
+
const APPROVAL_SCHEMA='deadbyte.project-owner-approval.r70.v1';
|
|
8
|
+
const SCOPE='coding_autonomous_native_diff';
|
|
9
|
+
const DOMAIN=Buffer.from('DEADBYTE-R70-PROJECT-OWNER-APPROVAL\0','utf8');
|
|
10
|
+
const SHA=/^[0-9a-f]{64}$/;
|
|
11
|
+
const ID=/^[A-Za-z0-9._-]{1,64}$/;
|
|
12
|
+
const SUBJECT_FIELDS=['schema','scope','root_id','project_root','coding_policy_sha256',
|
|
13
|
+
'autonomous_policy_sha256','reviewed_artifact_sha256','issued_at_utc','expires_at_utc'];
|
|
14
|
+
const APPROVAL_FIELDS=['schema','subject_sha256','owner_key_id','subject','signature_base64'];
|
|
15
|
+
const EXPECTED_FIELDS=['rootId','projectRoot','codingPolicySha256','autonomousPolicySha256','reviewedArtifactSha256'];
|
|
16
|
+
const hash=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
17
|
+
const samePath=(a,b)=>process.platform==='win32'?a.toLowerCase()===b.toLowerCase():a===b;
|
|
18
|
+
|
|
19
|
+
function exact(value,fields,label){
|
|
20
|
+
if(!value||typeof value!=='object'||Array.isArray(value))throw new Error(`${label} invalid`);
|
|
21
|
+
const keys=Object.keys(value);
|
|
22
|
+
for(const key of keys)if(!fields.includes(key))throw new Error(`${label} unknown field '${key}'`);
|
|
23
|
+
for(const key of fields)if(!Object.hasOwn(value,key))throw new Error(`${label} missing field '${key}'`);
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
function digest(value,label){if(typeof value!=='string'||!SHA.test(value))throw new Error(`${label} invalid SHA-256`);return value;}
|
|
27
|
+
function instant(value,label){
|
|
28
|
+
if(typeof value!=='string'||!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(value))throw new Error(`${label} invalid UTC`);
|
|
29
|
+
const parsed=Date.parse(value);
|
|
30
|
+
if(!Number.isFinite(parsed)||new Date(parsed).toISOString()!==value)throw new Error(`${label} invalid UTC`);
|
|
31
|
+
return parsed;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function verifyProjectOwnerApproval({approval,ownerPublicKey,expected,now=new Date()}={}){
|
|
35
|
+
exact(expected,EXPECTED_FIELDS,'owner approval expected subject');
|
|
36
|
+
exact(approval,APPROVAL_FIELDS,'owner approval');
|
|
37
|
+
exact(approval.subject,SUBJECT_FIELDS,'owner approval subject');
|
|
38
|
+
const subject=approval.subject;
|
|
39
|
+
if(approval.schema!==APPROVAL_SCHEMA||subject.schema!==SUBJECT_SCHEMA||subject.scope!==SCOPE)throw new Error('owner approval scope/schema mismatch');
|
|
40
|
+
if(typeof subject.root_id!=='string'||!ID.test(subject.root_id)||subject.root_id!==expected.rootId)throw new Error('owner approval root identity mismatch');
|
|
41
|
+
if(typeof subject.project_root!=='string'||!path.isAbsolute(subject.project_root)||
|
|
42
|
+
typeof expected.projectRoot!=='string'||!path.isAbsolute(expected.projectRoot))throw new Error('owner approval root path invalid');
|
|
43
|
+
let real;
|
|
44
|
+
try{
|
|
45
|
+
const entry=await lstat(expected.projectRoot);
|
|
46
|
+
if(!entry.isDirectory()||entry.isSymbolicLink())throw new Error('not a real directory');
|
|
47
|
+
real=await realpath(expected.projectRoot);
|
|
48
|
+
}catch{throw new Error('owner approval project root is not a real directory');}
|
|
49
|
+
if(!samePath(path.resolve(real),path.resolve(expected.projectRoot))||
|
|
50
|
+
!samePath(path.resolve(real),path.resolve(subject.project_root)))throw new Error('owner approval real root mismatch');
|
|
51
|
+
if(digest(subject.coding_policy_sha256,'coding policy')!==digest(expected.codingPolicySha256,'expected coding policy')||
|
|
52
|
+
digest(subject.autonomous_policy_sha256,'autonomous policy')!==digest(expected.autonomousPolicySha256,'expected autonomous policy')){
|
|
53
|
+
throw new Error('owner approval policy digest mismatch');
|
|
54
|
+
}
|
|
55
|
+
if(digest(subject.reviewed_artifact_sha256,'reviewed artifact')!==digest(expected.reviewedArtifactSha256,'expected reviewed artifact')){
|
|
56
|
+
throw new Error('owner approval reviewed artifact mismatch');
|
|
57
|
+
}
|
|
58
|
+
const issued=instant(subject.issued_at_utc,'owner approval issued time');
|
|
59
|
+
const expires=instant(subject.expires_at_utc,'owner approval expiry');
|
|
60
|
+
const observed=now instanceof Date?now.getTime():NaN;
|
|
61
|
+
if(!Number.isFinite(observed)||expires<=issued||expires-issued>3_600_000)throw new Error('owner approval lifetime invalid');
|
|
62
|
+
if(observed<issued||observed>=expires)throw new Error('owner approval expired or not yet valid');
|
|
63
|
+
const subjectSha=hash(Buffer.from(canonicalJson(subject),'utf8'));
|
|
64
|
+
if(digest(approval.subject_sha256,'owner approval subject')!==subjectSha)throw new Error('owner approval subject hash mismatch');
|
|
65
|
+
let key;
|
|
66
|
+
try{key=ownerPublicKey?.type==='public'?ownerPublicKey:createPublicKey(ownerPublicKey);}catch{throw new Error('owner approval public key invalid');}
|
|
67
|
+
if(key.asymmetricKeyType!=='ed25519')throw new Error('owner approval public key must be Ed25519');
|
|
68
|
+
const keyId=hash(key.export({type:'spki',format:'der'}));
|
|
69
|
+
if(digest(approval.owner_key_id,'owner approval key id')!==keyId)throw new Error('owner approval key identity mismatch');
|
|
70
|
+
if(typeof approval.signature_base64!=='string'||!/^[A-Za-z0-9+/]{86}==$/.test(approval.signature_base64))throw new Error('owner approval signature encoding invalid');
|
|
71
|
+
const signature=Buffer.from(approval.signature_base64,'base64');
|
|
72
|
+
const signed={schema:APPROVAL_SCHEMA,subject_sha256:subjectSha,owner_key_id:keyId};
|
|
73
|
+
if(signature.length!==64||!verifySignature(null,Buffer.concat([DOMAIN,Buffer.from(canonicalJson(signed),'utf8')]),key,signature)){
|
|
74
|
+
throw new Error('owner approval signature invalid');
|
|
75
|
+
}
|
|
76
|
+
return Object.freeze({approved:true,subject_sha256:subjectSha,owner_key_id:keyId,
|
|
77
|
+
project_root:real,root_id:subject.root_id,scope:SCOPE,expires_at_utc:subject.expires_at_utc});
|
|
78
|
+
}
|
package/src/remote-authority.mjs
CHANGED
|
@@ -1,75 +1,75 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
|
|
3
|
-
export const AUTHORITY_PLANES=Object.freeze(['process','coding','autonomous','desktop','host']);
|
|
4
|
-
|
|
5
|
-
export function authorityVector(mode='default'){
|
|
6
|
-
if(!['default','desktop','host','full'].includes(mode)) throw new Error('remote authority mode invalid');
|
|
7
|
-
return Object.freeze({
|
|
8
|
-
process:
|
|
9
|
-
desktop:mode==='desktop'||mode==='full',
|
|
10
|
-
host:mode==='host'||mode==='full'
|
|
11
|
-
});
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function authorityRestorePlan(snapshot){
|
|
15
|
-
const disarm=[],arm=[];
|
|
16
|
-
for(const plane of AUTHORITY_PLANES){
|
|
17
|
-
const entry=snapshot?.authority?.[plane];
|
|
18
|
-
if(!entry||typeof entry.armed!=='boolean') throw new Error(`authority ${plane} observation invalid`);
|
|
19
|
-
(entry.armed?arm:disarm).push(`${entry.armed?'arm':'disarm'}${plane}`);
|
|
20
|
-
}
|
|
21
|
-
return Object.freeze([...disarm,...arm]);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function parseAuthorityObservation(raw){
|
|
25
|
-
let value;
|
|
26
|
-
try{value=typeof raw==='string'?JSON.parse(raw.replace(/^\uFEFF/,'')):raw;}
|
|
27
|
-
catch{throw new Error('controller observation JSON invalid');}
|
|
28
|
-
if(value?.schema!=='deadbyte.controller-observation.v1'||!value.authority||typeof value.authority!=='object'||Array.isArray(value.authority))
|
|
29
|
-
throw new Error('controller observation schema invalid');
|
|
30
|
-
const authority={};
|
|
31
|
-
for(const plane of AUTHORITY_PLANES){
|
|
32
|
-
const entry=value.authority[plane];
|
|
33
|
-
if(!entry||typeof entry!=='object'||typeof entry.armed!=='boolean'||typeof entry.configured!=='boolean'||typeof entry.reason!=='string')
|
|
34
|
-
throw new Error(`controller authority ${plane} observation invalid`);
|
|
35
|
-
const hasEpochSeq=Object.hasOwn(entry,'epoch_seq'),hasEpochSha=Object.hasOwn(entry,'epoch_sha256');
|
|
36
|
-
if(hasEpochSeq!==hasEpochSha)throw new Error(`controller authority ${plane} epoch observation incomplete`);
|
|
37
|
-
let epochSeq=null,epochSha256=null;
|
|
38
|
-
if(hasEpochSeq){
|
|
39
|
-
if(!Number.isSafeInteger(entry.epoch_seq)||entry.epoch_seq<1||typeof entry.epoch_sha256!=='string'||!/^[0-9a-f]{64}$/.test(entry.epoch_sha256))
|
|
40
|
-
throw new Error(`controller authority ${plane} epoch observation invalid`);
|
|
41
|
-
epochSeq=entry.epoch_seq;epochSha256=entry.epoch_sha256;
|
|
42
|
-
}
|
|
43
|
-
authority[plane]=Object.freeze({configured:entry.configured,armed:entry.armed,reason:entry.reason.slice(0,128),
|
|
44
|
-
epoch_seq:epochSeq,epoch_sha256:epochSha256});
|
|
45
|
-
}
|
|
46
|
-
return Object.freeze({...value,authority:Object.freeze(authority)});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function assertAuthorityVector(snapshot,expected){
|
|
50
|
-
for(const plane of AUTHORITY_PLANES){
|
|
51
|
-
const entry=snapshot?.authority?.[plane];
|
|
52
|
-
if(!entry||typeof expected?.[plane]!=='boolean') throw new Error(`authority ${plane} observation or expectation invalid`);
|
|
53
|
-
if(entry.armed!==expected[plane]) throw new Error(`${plane} authority expected ${expected[plane]?'ARMED':'DISARMED'}; observed ${entry.armed?'ARMED':'DISARMED'} (${entry.reason})`);
|
|
54
|
-
if(expected[plane]&&!entry.configured) throw new Error(`${plane} authority unconfigured`);
|
|
55
|
-
}
|
|
56
|
-
return snapshot;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function authorityBanner(snapshot){
|
|
60
|
-
return AUTHORITY_PLANES.map(plane=>`${plane} ${snapshot.authority[plane].armed?'ARMED':'DISARMED'}`).join(' | ');
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function assertRuntimeReadyObservation(snapshot,{coreRoot=null}={}){
|
|
64
|
-
if(snapshot?.state!=='ONLINE') throw new Error(`runtime state not ONLINE: ${String(snapshot?.state)}`);
|
|
65
|
-
for(const name of ['bridge','core','cloud']){
|
|
66
|
-
if(snapshot[name]?.alive!==true||snapshot[name]?.reason!=='verified') throw new Error(`${name} runtime identity unverified`);
|
|
67
|
-
}
|
|
68
|
-
if(snapshot.local_ready!==true) throw new Error('local MCP readiness unverified');
|
|
69
|
-
if(snapshot.supervisor?.running!==true) throw new Error('autonomous supervisor not running');
|
|
70
|
-
if(coreRoot!==null){
|
|
71
|
-
if(typeof snapshot.core_root!=='string'||path.win32.resolve(snapshot.core_root).toLowerCase()!==path.win32.resolve(coreRoot).toLowerCase())
|
|
72
|
-
throw new Error('core root does not match active release');
|
|
73
|
-
}
|
|
74
|
-
return snapshot;
|
|
75
|
-
}
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
|
|
3
|
+
export const AUTHORITY_PLANES=Object.freeze(['process','coding','autonomous','desktop','host']);
|
|
4
|
+
|
|
5
|
+
export function authorityVector(mode='default'){
|
|
6
|
+
if(!['default','operator','desktop','host','full'].includes(mode)) throw new Error('remote authority mode invalid');
|
|
7
|
+
return Object.freeze({
|
|
8
|
+
process:mode!=='default',coding:mode!=='default',autonomous:mode!=='default',
|
|
9
|
+
desktop:mode==='desktop'||mode==='full',
|
|
10
|
+
host:mode==='host'||mode==='full'
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function authorityRestorePlan(snapshot){
|
|
15
|
+
const disarm=[],arm=[];
|
|
16
|
+
for(const plane of AUTHORITY_PLANES){
|
|
17
|
+
const entry=snapshot?.authority?.[plane];
|
|
18
|
+
if(!entry||typeof entry.armed!=='boolean') throw new Error(`authority ${plane} observation invalid`);
|
|
19
|
+
(entry.armed?arm:disarm).push(`${entry.armed?'arm':'disarm'}${plane}`);
|
|
20
|
+
}
|
|
21
|
+
return Object.freeze([...disarm,...arm]);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function parseAuthorityObservation(raw){
|
|
25
|
+
let value;
|
|
26
|
+
try{value=typeof raw==='string'?JSON.parse(raw.replace(/^\uFEFF/,'')):raw;}
|
|
27
|
+
catch{throw new Error('controller observation JSON invalid');}
|
|
28
|
+
if(value?.schema!=='deadbyte.controller-observation.v1'||!value.authority||typeof value.authority!=='object'||Array.isArray(value.authority))
|
|
29
|
+
throw new Error('controller observation schema invalid');
|
|
30
|
+
const authority={};
|
|
31
|
+
for(const plane of AUTHORITY_PLANES){
|
|
32
|
+
const entry=value.authority[plane];
|
|
33
|
+
if(!entry||typeof entry!=='object'||typeof entry.armed!=='boolean'||typeof entry.configured!=='boolean'||typeof entry.reason!=='string')
|
|
34
|
+
throw new Error(`controller authority ${plane} observation invalid`);
|
|
35
|
+
const hasEpochSeq=Object.hasOwn(entry,'epoch_seq'),hasEpochSha=Object.hasOwn(entry,'epoch_sha256');
|
|
36
|
+
if(hasEpochSeq!==hasEpochSha)throw new Error(`controller authority ${plane} epoch observation incomplete`);
|
|
37
|
+
let epochSeq=null,epochSha256=null;
|
|
38
|
+
if(hasEpochSeq){
|
|
39
|
+
if(!Number.isSafeInteger(entry.epoch_seq)||entry.epoch_seq<1||typeof entry.epoch_sha256!=='string'||!/^[0-9a-f]{64}$/.test(entry.epoch_sha256))
|
|
40
|
+
throw new Error(`controller authority ${plane} epoch observation invalid`);
|
|
41
|
+
epochSeq=entry.epoch_seq;epochSha256=entry.epoch_sha256;
|
|
42
|
+
}
|
|
43
|
+
authority[plane]=Object.freeze({configured:entry.configured,armed:entry.armed,reason:entry.reason.slice(0,128),
|
|
44
|
+
epoch_seq:epochSeq,epoch_sha256:epochSha256});
|
|
45
|
+
}
|
|
46
|
+
return Object.freeze({...value,authority:Object.freeze(authority)});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function assertAuthorityVector(snapshot,expected){
|
|
50
|
+
for(const plane of AUTHORITY_PLANES){
|
|
51
|
+
const entry=snapshot?.authority?.[plane];
|
|
52
|
+
if(!entry||typeof expected?.[plane]!=='boolean') throw new Error(`authority ${plane} observation or expectation invalid`);
|
|
53
|
+
if(entry.armed!==expected[plane]) throw new Error(`${plane} authority expected ${expected[plane]?'ARMED':'DISARMED'}; observed ${entry.armed?'ARMED':'DISARMED'} (${entry.reason})`);
|
|
54
|
+
if(expected[plane]&&!entry.configured) throw new Error(`${plane} authority unconfigured`);
|
|
55
|
+
}
|
|
56
|
+
return snapshot;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function authorityBanner(snapshot){
|
|
60
|
+
return AUTHORITY_PLANES.map(plane=>`${plane} ${snapshot.authority[plane].armed?'ARMED':'DISARMED'}`).join(' | ');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function assertRuntimeReadyObservation(snapshot,{coreRoot=null}={}){
|
|
64
|
+
if(snapshot?.state!=='ONLINE') throw new Error(`runtime state not ONLINE: ${String(snapshot?.state)}`);
|
|
65
|
+
for(const name of ['bridge','core','cloud']){
|
|
66
|
+
if(snapshot[name]?.alive!==true||snapshot[name]?.reason!=='verified') throw new Error(`${name} runtime identity unverified`);
|
|
67
|
+
}
|
|
68
|
+
if(snapshot.local_ready!==true) throw new Error('local MCP readiness unverified');
|
|
69
|
+
if(snapshot.supervisor?.running!==true) throw new Error('autonomous supervisor not running');
|
|
70
|
+
if(coreRoot!==null){
|
|
71
|
+
if(typeof snapshot.core_root!=='string'||path.win32.resolve(snapshot.core_root).toLowerCase()!==path.win32.resolve(coreRoot).toLowerCase())
|
|
72
|
+
throw new Error('core root does not match active release');
|
|
73
|
+
}
|
|
74
|
+
return snapshot;
|
|
75
|
+
}
|
package/src/remote-live-log.mjs
CHANGED
|
@@ -183,6 +183,7 @@ export function formatRemoteToolCall(event,{clock=()=>Date.now(),displayMode='un
|
|
|
183
183
|
function formatDuration(ms){
|
|
184
184
|
const value=Number(ms);
|
|
185
185
|
if(!Number.isFinite(value)||value<0) return '';
|
|
186
|
+
if(value<1) return value===0?'0ms':`${Number(value.toPrecision(3))}ms`;
|
|
186
187
|
if(value<1000) return `${Math.round(value)}ms`;
|
|
187
188
|
return `${(value/1000).toFixed(2)}s`;
|
|
188
189
|
}
|
|
@@ -191,7 +192,8 @@ export function formatCompactAuditEvent(event){
|
|
|
191
192
|
if(!event||typeof event!=='object'||Array.isArray(event)||typeof event.tool!=='string'||!/^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(event.tool)) return null;
|
|
192
193
|
if(event.event_type==='tool.request') return `▶ RUN ${event.tool}${summarizeArgs(sanitizeAuditArgs(event.args??{}))}`;
|
|
193
194
|
if(event.event_type==='tool.result'){
|
|
194
|
-
const
|
|
195
|
+
const outcome=outcomeClass(event);
|
|
196
|
+
const marker=outcome==='unknown'?'⚠ UNKNOWN':outcome==='error'?'❌ ERR':outcome==='pending'?'⏳ PENDING':'✅ OK';
|
|
195
197
|
const duration=formatDuration(event.outcome?.duration_ms);
|
|
196
198
|
return `${marker} ${event.tool}${duration?` ${duration}`:''}`;
|
|
197
199
|
}
|
|
@@ -251,9 +253,16 @@ function traceText(meta,displayMode='unicode'){
|
|
|
251
253
|
(meta.parent_span_id?' parent='+shortTraceValue(meta.parent_span_id,displayMode):'');
|
|
252
254
|
}
|
|
253
255
|
function outcomeClass(event){
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
256
|
+
const outcome=event?.outcome;
|
|
257
|
+
if(outcome?.side_effect_state==='unknown') return 'unknown';
|
|
258
|
+
const status=outcome?.status;
|
|
259
|
+
if(status==='timeout'||status==='timed_out') return 'unknown';
|
|
260
|
+
if(outcome?.is_error===true||
|
|
261
|
+
['failed','denied','rejected','blocked','observed_failure','cancelled','canceled','unavailable','unsupported'].includes(status)) return 'error';
|
|
262
|
+
if(['pending','running','input_required','paused','processing','accepted'].includes(status)) return 'pending';
|
|
263
|
+
return 'ok';
|
|
264
|
+
}
|
|
265
|
+
export function classifyRemoteAuditOutcome(event){return outcomeClass(event);}
|
|
257
266
|
function parseFilterExpression(expression){
|
|
258
267
|
if(expression===null||expression===undefined||expression==='') return Object.freeze({});
|
|
259
268
|
if(typeof expression!=='string'||expression.length>512) throw new Error('timeline filter invalid');
|
|
@@ -264,7 +273,7 @@ function parseFilterExpression(expression){
|
|
|
264
273
|
const key=part.slice(0,at).trim();const value=part.slice(at+1).trim();
|
|
265
274
|
if(!['tool','category','status','trace'].includes(key)) throw new Error(`timeline filter key '${key}' unsupported`);
|
|
266
275
|
if(Object.hasOwn(out,key)) throw new Error(`timeline filter key '${key}' repeated`);
|
|
267
|
-
if(key==='status'&&!['ok','error','unknown'].includes(value)) throw new Error('timeline filter status invalid');
|
|
276
|
+
if(key==='status'&&!['ok','error','unknown','pending'].includes(value)) throw new Error('timeline filter status invalid');
|
|
268
277
|
if(key==='category'&&!Object.hasOwn(TOOL_ICONS,value)) throw new Error('timeline filter category invalid');
|
|
269
278
|
out[key]=value;
|
|
270
279
|
}
|
|
@@ -287,7 +296,7 @@ export function createLiveTimelineFilter(expression=''){
|
|
|
287
296
|
if(!requestId||!rawTool) return false;
|
|
288
297
|
if(event.event_type==='tool.request'){
|
|
289
298
|
const args=event.args??{};const tool=displayToolName(rawTool,args);const trace=traceIdentity(event,args);
|
|
290
|
-
const meta={tool,category:categoryForTool(tool),trace_id:trace.trace_id};
|
|
299
|
+
const meta={tool,raw_tool:rawTool,category:categoryForTool(tool),trace_id:trace.trace_id};
|
|
291
300
|
if(pending.has(requestId))pending.delete(requestId);
|
|
292
301
|
if(pending.size>=MAX_FILTER_IDENTITIES){pending.delete(pending.keys().next().value);evicted++;}
|
|
293
302
|
pending.set(requestId,meta);
|
|
@@ -296,7 +305,8 @@ export function createLiveTimelineFilter(expression=''){
|
|
|
296
305
|
if(event.event_type==='tool.result'){
|
|
297
306
|
const meta=pending.get(requestId)??{tool:rawTool,category:categoryForTool(rawTool),trace_id:traceIdentity(event).trace_id};
|
|
298
307
|
pending.delete(requestId);
|
|
299
|
-
|
|
308
|
+
const status=meta.raw_tool&&meta.raw_tool!==rawTool?'unknown':outcomeClass(event);
|
|
309
|
+
return baseMatch(meta)&&(!spec.status||spec.status===status);
|
|
300
310
|
}
|
|
301
311
|
return false;
|
|
302
312
|
},
|
|
@@ -312,10 +322,20 @@ export function createLiveTimelineWriter({stream=process.stdout,displayMode='uni
|
|
|
312
322
|
function flush(){
|
|
313
323
|
if(active){stream.write('\n');active=null;}
|
|
314
324
|
}
|
|
325
|
+
// Inspect terminal width for every emission: resize must not erase a wrapped prior line.
|
|
326
|
+
// Unknown Unicode cell width falls back to append-only output, never truncation.
|
|
327
|
+
function fitsOneRow(line){
|
|
328
|
+
const columns=stream.columns;
|
|
329
|
+
return /^[\x20-\x7e]*$/.test(line)&&(!Number.isInteger(columns)||line.length<columns);
|
|
330
|
+
}
|
|
315
331
|
function write(line,{event_type=null,request_id=null}={}){
|
|
316
332
|
if(typeof line!=='string'||!line) return;
|
|
317
333
|
const tty=stream.isTTY===true&&rewrite;
|
|
318
|
-
if(!tty
|
|
334
|
+
if(!tty||!fitsOneRow(line)){
|
|
335
|
+
if(active) flush();
|
|
336
|
+
stream.write(line+'\n');
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
319
339
|
if(active&&event_type==='tool.result'&&request_id&&request_id===active.request_id){
|
|
320
340
|
stream.write('\r\x1b[2K'+line+'\n');active=null;return;
|
|
321
341
|
}
|
|
@@ -340,7 +360,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
340
360
|
const MAX_PRESENTATION_IDENTITIES=4096;
|
|
341
361
|
const toolCounts=new Map();
|
|
342
362
|
const durationSamples=[];
|
|
343
|
-
let resultCount=0,okCount=0,errorCount=0,unknownCount=0,mutationCount=0,totalDurationMs=0,slowest=null,burst=null,foldedHeartbeatLines=0,peakInFlight=0;
|
|
363
|
+
let resultCount=0,okCount=0,errorCount=0,unknownCount=0,pendingCount=0,mutationCount=0,totalDurationMs=0,durationObservedCount=0,slowest=null,burst=null,foldedHeartbeatLines=0,peakInFlight=0;
|
|
344
364
|
let lastCapability=null,suppressedCapability=0,suppressedCapabilityReported=false;
|
|
345
365
|
let lastSourceSequence=0,pendingOverflow=0;
|
|
346
366
|
|
|
@@ -352,8 +372,10 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
352
372
|
}
|
|
353
373
|
function traceSuffix(meta){return trace&&meta?' '+traceText(meta,displayMode):'';}
|
|
354
374
|
function resultLine(event,span,sequence){
|
|
355
|
-
const
|
|
356
|
-
const
|
|
375
|
+
const outcome=outcomeClass(event);
|
|
376
|
+
const error=outcome==='error';
|
|
377
|
+
const unknown=outcome==='unknown';
|
|
378
|
+
const pendingResult=outcome==='pending';
|
|
357
379
|
const displayTool=span?.display_tool||event.tool;
|
|
358
380
|
const rawDuration=Number(event.outcome?.duration_ms);
|
|
359
381
|
const duration=Number.isFinite(rawDuration)
|
|
@@ -371,6 +393,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
371
393
|
?shortValue(event.outcome.receipt_sha256s[0],8):'';
|
|
372
394
|
const marker=unknown?'FAILED_AFTER_UNKNOWN_EFFECT':
|
|
373
395
|
error?(displayMode==='ascii'?'ERR':'✗'):
|
|
396
|
+
pendingResult?'PENDING':
|
|
374
397
|
['write','edit','run'].includes(category)?(displayMode==='ascii'?'SUCCEEDED':'✅ SUCCEEDED'):
|
|
375
398
|
(displayMode==='ascii'?'OK':'✓');
|
|
376
399
|
const rawStatus=safeAtom(event.outcome?.status);
|
|
@@ -402,11 +425,12 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
402
425
|
const countKey=toolCounts.has(displayTool)||toolCounts.size<256?displayTool:'[other]';
|
|
403
426
|
toolCounts.set(countKey,(toolCounts.get(countKey)??0)+1);
|
|
404
427
|
const outcome=outcomeClass(event);
|
|
405
|
-
if(outcome==='unknown') unknownCount++;else if(outcome==='error') errorCount++;else okCount++;
|
|
428
|
+
if(outcome==='unknown') unknownCount++;else if(outcome==='error') errorCount++;else if(outcome==='pending') pendingCount++;else okCount++;
|
|
406
429
|
if(['write','edit','run'].includes(categoryForTool(displayTool))) mutationCount++;
|
|
407
430
|
const duration=Number(event.outcome?.duration_ms);
|
|
408
431
|
if(Number.isFinite(duration)&&duration>=0){
|
|
409
432
|
totalDurationMs+=duration;
|
|
433
|
+
durationObservedCount++;
|
|
410
434
|
if(durationSamples.length<2048) durationSamples.push(duration);
|
|
411
435
|
if(!slowest||duration>slowest.duration_ms) slowest={tool:displayTool,duration_ms:duration};
|
|
412
436
|
}
|
|
@@ -444,6 +468,15 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
444
468
|
pending.delete(requestId);
|
|
445
469
|
completed.add(requestId);
|
|
446
470
|
if(completed.size>MAX_PRESENTATION_IDENTITIES)completed.delete(completed.values().next().value);
|
|
471
|
+
if(span&&span.tool!==tool){
|
|
472
|
+
const mismatchEvent={...event,outcome:{status:'correlation_mismatch',is_error:true,
|
|
473
|
+
side_effect_state:'unknown',duration_ms:event.outcome?.duration_ms,
|
|
474
|
+
reason:'CORRELATION_MISMATCH: terminal tool differs from START; inspect signed audit before retry'}};
|
|
475
|
+
addStats(mismatchEvent,span);
|
|
476
|
+
const prior=flushBurst();
|
|
477
|
+
const line=resultLine(mismatchEvent,span,sequence);
|
|
478
|
+
return prior?[prior,line]:line;
|
|
479
|
+
}
|
|
447
480
|
addStats(event,span);
|
|
448
481
|
const error=event.outcome?.is_error===true;
|
|
449
482
|
const nowMs=eventMs(event,clock);
|
|
@@ -502,10 +535,11 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
|
|
|
502
535
|
const sorted=[...durationSamples].sort((a,b)=>a-b);
|
|
503
536
|
const p50=sorted.length?(sorted.length%2?sorted[(sorted.length-1)/2]:(sorted[sorted.length/2-1]+sorted[sorted.length/2])/2):null;
|
|
504
537
|
const p95=sorted.length?sorted[Math.max(0,Math.ceil(sorted.length*0.95)-1)]:null;
|
|
538
|
+
const p99=sorted.length?sorted[Math.max(0,Math.ceil(sorted.length*0.99)-1)]:null;
|
|
505
539
|
return remoteDisplayText((displayMode==='ascii'?'[SUMMARY]':'📊 [SUMMARY]')+' '+resultCount+' calls | '+okCount+' ok | '+
|
|
506
|
-
errorCount+' error'+(errorCount===1?'':'s')+' | '+unknownCount+' unknown | '+mutationCount+' mutation'+(mutationCount===1?'':'s')+
|
|
540
|
+
errorCount+' error'+(errorCount===1?'':'s')+' | '+unknownCount+' unknown'+(pendingCount?' | '+pendingCount+' pending':'')+' | '+mutationCount+' mutation'+(mutationCount===1?'':'s')+
|
|
507
541
|
' | tool time '+(formatDuration(totalDurationMs)||'0ms')+
|
|
508
|
-
(p50!==null?' | p50 '+formatDuration(p50)+' | p95 '+formatDuration(p95):'')+
|
|
542
|
+
(p50!==null?' | p50 '+formatDuration(p50)+' | p95 '+formatDuration(p95)+' | p99 '+formatDuration(p99)+' | sample '+durationSamples.length+'/'+durationObservedCount:'')+
|
|
509
543
|
(slowest?' | slowest '+slowest.tool+' '+formatDuration(slowest.duration_ms):'')+
|
|
510
544
|
' | peak in-flight '+peakInFlight+
|
|
511
545
|
(foldedHeartbeatLines?' | folded '+foldedHeartbeatLines+' heartbeat lines':'')+
|