deadbyte-mcp 0.15.50 → 0.15.52
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 +47 -17
- package/PROVENANCE.json +29 -11
- package/README.txt +18 -18
- package/RELEASE.json +392 -368
- package/SBOM.cdx.json +9 -4
- package/TEST-MATRIX.json +32 -2
- package/docs/R132-MANIFEST-PORTABILITY.md +23 -0
- package/docs/R135-AUTONOMOUS-SCOPE.md +21 -0
- package/docs/R136-SCOPED-OBSERVER.md +21 -0
- package/docs/R137-DESKTOP-COMMANDER-FOUNDATION.md +31 -0
- package/docs/R138-COMMANDER-WORKFLOW.md +21 -0
- package/docs/R139-COMMANDER-ERGONOMICS.md +17 -0
- package/docs/THIRD-PARTY-DESKTOP-COMMANDER-MIT.md +27 -0
- package/package.json +1 -1
- package/scripts/npm-prepublish-contract-r74.mjs +8 -2
- package/scripts/release-manifest.mjs +15 -1
- package/src/autonomous-scope-r135.mjs +91 -0
- package/src/commander-config-recovery-r137.mjs +67 -0
- package/src/commander-extended-runtime.mjs +22 -5
- package/src/commander-external-process-r48.mjs +25 -3
- package/src/commander-large-text-r139.mjs +66 -0
- package/src/commander-wire-adapter-r47.mjs +63 -12
- package/src/release-slot.mjs +13 -3
- package/src/scoped-observer-launcher-r136.mjs +77 -0
- package/src/scoped-observer-worker-r136.mjs +65 -0
- package/src/version.mjs +1 -1
- package/test/commander-wire-contract-r47.test.mjs +8 -5
- package/test/r130-release-security-contract.test.mjs +7 -7
- package/test/r131-installed-manifest-boundary.test.mjs +65 -0
- package/test/r132-release-identity.test.mjs +44 -0
- package/test/r135-autonomous-scope.test.mjs +103 -0
- package/test/r136-scoped-command-rights.test.mjs +39 -0
- package/test/r136-scoped-observer.test.mjs +89 -0
- package/test/r137-commander-config-recovery.test.mjs +111 -0
- package/test/r137-commander-three-version-contract.test.mjs +56 -0
- package/test/r138-commander-process-pagination.test.mjs +42 -0
- package/test/r138-commander-session-list.test.mjs +24 -0
- package/test/r138-commander-stream-resume.test.mjs +50 -0
- package/test/r139-large-text-edit.test.mjs +51 -0
- package/test/r139-large-text-read.test.mjs +46 -0
- package/test/r139-process-start-preview.test.mjs +50 -0
- package/test/r139-search-partial.test.mjs +42 -0
- package/test/r140-large-text-integrity.test.mjs +68 -0
- package/test/r141-commander-tail-utf8.test.mjs +55 -0
- package/test/r141-real-windows-disk.test.mjs +71 -0
- package/test/r142-native-machinefs-authority.test.mjs +127 -0
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r74-prepublish-contract.test.mjs +6 -4
package/SBOM.cdx.json
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bomFormat": "CycloneDX",
|
|
3
3
|
"specVersion": "1.6",
|
|
4
|
-
"serialNumber": "urn:cdx:deadbyte-mcp:
|
|
4
|
+
"serialNumber": "urn:cdx:deadbyte-mcp:r143:0.15.52",
|
|
5
5
|
"version": 1,
|
|
6
6
|
"metadata": {
|
|
7
7
|
"component": {
|
|
8
8
|
"type": "application",
|
|
9
9
|
"name": "deadbyte-mcp",
|
|
10
|
-
"version": "0.15.
|
|
10
|
+
"version": "0.15.52"
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"components": [
|
|
14
14
|
{
|
|
15
15
|
"type": "library",
|
|
16
16
|
"name": "@modelcontextprotocol/client",
|
|
17
|
-
"version": "2.1
|
|
17
|
+
"version": "2.3.1"
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
"type": "library",
|
|
21
21
|
"name": "@modelcontextprotocol/server",
|
|
22
|
-
"version": "2.1
|
|
22
|
+
"version": "2.3.1"
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
"type": "library",
|
|
@@ -60,6 +60,11 @@
|
|
|
60
60
|
}
|
|
61
61
|
],
|
|
62
62
|
"evidence_status": "CANDIDATE_BINARY_PIN_ONLY_NOT_DEPLOYED"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"type": "library",
|
|
66
|
+
"name": "@modelcontextprotocol/core",
|
|
67
|
+
"version": "2.3.1"
|
|
63
68
|
}
|
|
64
69
|
]
|
|
65
70
|
}
|
package/TEST-MATRIX.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "deadbyte.test-matrix.v1",
|
|
3
|
-
"version": "0.15.
|
|
4
|
-
"train": "
|
|
3
|
+
"version": "0.15.52",
|
|
4
|
+
"train": "R143",
|
|
5
5
|
"primary_platform": "win32-x64",
|
|
6
6
|
"reference": {
|
|
7
7
|
"package": "@wonderwhy-er/desktop-commander",
|
|
@@ -10,6 +10,21 @@
|
|
|
10
10
|
"schemas_sha256": "f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140"
|
|
11
11
|
},
|
|
12
12
|
"required": [
|
|
13
|
+
{
|
|
14
|
+
"id": "r143-protected-native-full",
|
|
15
|
+
"command": "node --test --test-concurrency=4 test/*.test.mjs on exact R143 protected installed tree",
|
|
16
|
+
"expected": "Zero failures, 49 classified skips preserved, installed manifest PASS"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "r143-package-verify-only",
|
|
20
|
+
"command": "node scripts/npm-release-r48.mjs --verify-only",
|
|
21
|
+
"expected": "Exact-version doctor, manifest verification, npm package and source lineage PASS"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "r143-publication-reconciliation",
|
|
25
|
+
"command": "Publish only after prepublish gates PASS, then inspect npm registry and npx outside repo",
|
|
26
|
+
"expected": "Published version 0.15.52 matches preserved package identity; production not activated"
|
|
27
|
+
},
|
|
13
28
|
{
|
|
14
29
|
"id": "r71-full-regression",
|
|
15
30
|
"command": "npm test",
|
|
@@ -34,6 +49,21 @@
|
|
|
34
49
|
"id": "r71-owner-grant",
|
|
35
50
|
"command": "Protected owner authentication and project scope",
|
|
36
51
|
"expected": "BLOCK unless independently owner enrolled and scope verified"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "r132-protected-full",
|
|
55
|
+
"command": "node --test --test-concurrency=4 test/*.test.mjs on exact R132 protected fixture",
|
|
56
|
+
"expected": "Zero failures, historical skips recorded, manifest verify-installed pass"
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
"id": "r132-independent-rollback",
|
|
60
|
+
"command": "isolated activation -> rollback -> restore smoke on scratch state root",
|
|
61
|
+
"expected": "No production mutation; runtime predecessor preserved"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": "r132-artifact-parity",
|
|
65
|
+
"command": "node scripts/npm-release-r48.mjs --verify-only + fresh npm audit",
|
|
66
|
+
"expected": "Verified exact 0.15.51 tarball, SDK 2.3.1 and 0 vulnerabilities"
|
|
37
67
|
}
|
|
38
68
|
],
|
|
39
69
|
"observed_pre_release_evidence": {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# R132 / deadbyte-mcp@0.15.51 — manifest portability
|
|
2
|
+
|
|
3
|
+
Status: ISOLATED RELEASE CANDIDATE (no npm publish or production activation).
|
|
4
|
+
This release train follows published 0.15.50 and the independently tested R131 developer successor.
|
|
5
|
+
|
|
6
|
+
## Defect reproduced
|
|
7
|
+
npm v12.1.0 built a package manifest on the source checkout that included `node_modules/which/CHANGELOG.md`. After installing into a separate protected Windows filesystem, a fresh `npm pack --dry-run` omitted that file even though its SHA-256 and bytes remained unchanged. Consequently the old development-package comparison failed in an installed environment (R130: 1,699 pass, 1 failure, 49 skipped). This is NOT evidence of a runtime executable/hash mutation.
|
|
8
|
+
|
|
9
|
+
## Surgical fix (R131)
|
|
10
|
+
- Keep `release-manifest.mjs verify` unchanged for authors creating an npm tarball: full npm-pack list equality, no path exceptions.
|
|
11
|
+
- Introduce `release-manifest.mjs verify-installed` for protected installations: independently verify SHA-256 of EVERY manifested file and reject extraneous regular files unless their path is exactly `.npmignore` or `package-lock.json`. No directory-level exclusions; symlinks, unexpected runtime files and mutated bytes still fail.
|
|
12
|
+
- `npm-prepublish-contract-r74.mjs` source/installed mode is explicit; defaults to strict source verification. On protected Windows the test selects installed mode via `DEADBYTE_TEST_PROTECTED_MANIFEST=installed`. It still tests pinned native hashes, predecessor separation and private file leakage.
|
|
13
|
+
- R131 protected Windows full native 1,705 PASS, 0 FAIL, 49 SKIP (1,754 tests); source manifest 2,371 files PASS.
|
|
14
|
+
- R132 adds honest 0.15.51 release metadata, current 2.3.1 client/server/core SBOM, and new source-predecessor identity tests. R132 exact-byte gates must run independently; R131 results are not R132 results.
|
|
15
|
+
|
|
16
|
+
## Immutable lineage
|
|
17
|
+
- npm published 0.15.50 tarball SHA-256 `133b9f4adedee14a3e4f2fd1c9433f7ebf102742a73398161b238fd68eb42d84`.
|
|
18
|
+
- npm 0.15.50 source manifest SHA-256 `98dd37c71adae980856adc17b9c59d58d167a01d46c61106055956d0f306fede`.
|
|
19
|
+
- R131 predecessor candidate source manifest SHA-256 `3e5edb9987aa7c9004dda3164a278da70a60495c0c128e2602426075a5222c10`.
|
|
20
|
+
- Production runtime predecessor remains v0.15.45; its pointer must not change during isolated gates.
|
|
21
|
+
|
|
22
|
+
## Remaining release gates
|
|
23
|
+
Full Windows native regression on exact R132 protected fixture; isolated activation/rollback/restore tests with scratch state; npm pack manifest and independent doctor; SBOM parity and npm audit; no production activation, grant changes or npm publish until the respective gates and explicit actions.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# R135 — Autonomous scope isolation foundation
|
|
2
|
+
|
|
3
|
+
Status: DEVELOPMENT / OFFLINE PROJECTION ONLY. Do not publish, deploy, arm any grant, or change the production pointer based on this candidate.
|
|
4
|
+
|
|
5
|
+
## Observed 0.15.45 production conflict
|
|
6
|
+
The manual Coding policy SHA-256 `63f2153517c2e1f3febc5e78ef7e9f766fac3483178ae098021746d5be6ba246` includes `local_c` (C:\\) and `local_d` (D:\\) with read/write/exec. The Autonomous policy names only `core`. The R70 controller rejects **any** broad Coding root at Autonomous metadata admission, even when Autonomous selects a project. This is deliberate defense against an Autonomous tool invoking the already-armed broad Coding capability. Removing only the controller assertion would be a security regression.
|
|
7
|
+
|
|
8
|
+
## Isolated projection implementation
|
|
9
|
+
`src/autonomous-scope-r135.mjs` exports `deriveIsolatedAutonomousScope`, a pure synchronous *policy preview* function. It takes trusted parsed manual Coding/Autonomous policies, one project root ID, and a separate existing project-scoped state root. It returns two **in-memory** JSON policy byte buffers and their SHA-256 digests, without touching manual or production policy files.
|
|
10
|
+
|
|
11
|
+
The projected Coding policy retains only the nominated real project directory and commands referenced by the selected Autonomous profiles. The projected Autonomous policy is rebound to the SHA-256 of those exact projected Coding bytes. Both leases and evidence roots are redirected to the separate isolated state. The function refuses whole-drive roots, missing / redirected project roots, commands missing from Coding, profiles targeting other roots, overlapping existing manual lease storage, and preexisting child grant files.
|
|
12
|
+
|
|
13
|
+
The previous R70 admission check is kept unchanged. `assertAutonomousBootstrapRoots` passes for the projected pair and still rejects the original broad manual Coding policy. The owner approval signature gate and all grant issuance/epoch controls are unchanged.
|
|
14
|
+
|
|
15
|
+
## Security boundary not yet implemented
|
|
16
|
+
An offline policy projection is **not** enforcement. A shared process with access to the broad manual Coding lease could still inherit broad authority. Do not install these policy bytes inside the existing production controller or arm Autonomous against its Coding policy. Before deployment, implement and test an independently hosted project-scoped runtime with its own distinct Coding grant, owner enrollment/signature, trust root, disjoint lifecycle and authority epoch, and proof of privilege noninheritance even if the manual runtime has broad roots. These policy bytes alone do not demonstrate such isolation.
|
|
17
|
+
|
|
18
|
+
## Evidence and gates
|
|
19
|
+
The R135 source is an isolated copy of R132B, still internally labeled version 0.15.51 and **must not republish**. R135 test files explicitly cover fail-closed scope projection, manual policy preservation, no inherited grants, and compatibility with the existing R70 whole-drive rejection. The independent production preflight R134 remains BLOCKED for live activation; unresolved historical transaction evidence remains immutable. The R135 candidate is not a 0.15.51 release artifact.
|
|
20
|
+
|
|
21
|
+
Live signed owner approval and actual production activation/rollback are separate decisions. No modifications to the existing 0.15.45 runtime, grants, policies or active-release pointer are part of R135.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# R136 — Disarmed Scoped Observer Process / Coding Command-Root Closure
|
|
2
|
+
|
|
3
|
+
Status: **DEV ONLY — NOT A PRIVILEGE SANDBOX, NOT READY FOR LIVE AUTONOMOUS EXECUTION**.
|
|
4
|
+
|
|
5
|
+
## Predecessor and regression
|
|
6
|
+
Source predecessor is R135 (development candidate), copied separately without changing the published `deadbyte-mcp@0.15.51` package or active production `0.15.45`. R135's projected Coding commands retained broad `root_ids` from parent manual Coding. This created a latent unsafe future integration path even though the R135 projection had only one root and no execution.
|
|
7
|
+
|
|
8
|
+
R136 changes `deriveIsolatedAutonomousScope` to require that each selected command already allow the selected scoped root and then replace its `root_ids` with that root alone. The original manual Coding policy is byte-preserved. Tests were observed RED and then GREEN for both command closure and missing scope.
|
|
9
|
+
|
|
10
|
+
## Child process
|
|
11
|
+
`src/scoped-observer-launcher-r136.mjs` creates a separate Node child process running `src/scoped-observer-worker-r136.mjs` with an explicit tiny environment allowlist (Windows directory and temporary paths only). All `NODE_OPTIONS`, `DEADBYTE_*`, and other ambient user secrets are omitted. The worker verifies exact policy SHA-256 against its launch pins, checks that both policy files live under its declared isolated state, both grants are absent, Coding/Autonomous reference a single project root, and every Coding command has only the selected root. It exposes only a read-only `status` IPC response, explicit denials for privileged operations, and `shutdown`. IPC identity keys cannot be user overwritten. Exit/disposal is verified.
|
|
12
|
+
|
|
13
|
+
This worker does **not** invoke `coding-plane` commands, issue grants, or access manual authority epochs. It does not attach to production or register tools with the live MCP server.
|
|
14
|
+
|
|
15
|
+
## Security limitations and next gate
|
|
16
|
+
A separate process with sanitized environment is NOT an OS-level security boundary. It still runs under the same Windows account and therefore potentially retains filesystem rights to broad manual Coding resources. The worker has no privileged action handlers, but adding any executor to it would require independent owner-signed grant enrollment, restricted Windows token / explicit filesystem ACL isolation, disjoint trust store and authority epoch, stronger IPC mutual authentication, process lifecycle and crash/recovery tests, and a proof that broad grants cannot be read or reused. Passing R136 does not authorize such execution.
|
|
17
|
+
|
|
18
|
+
Historical 13 rollback/update records remain unchanged. R70 rejection of broad Coding roots remains unchanged and must continue passing. `0.15.51` is immutable and any deployable code requires a new version and full protected Windows release gate. No production grants, policies, pointer, running processes or settings are changed by this development patch.
|
|
19
|
+
|
|
20
|
+
## Verification
|
|
21
|
+
Tests: `test/r136-scoped-command-rights.test.mjs`, `test/r136-scoped-observer.test.mjs`, all prior R135 scope tests and R70 security tests. Windows real production policy fixture read only: `D:\Projects\deadbyte-mcp\testing\r136-scoped-runtime-dev\R136-LIVE-POLICY-PROBE.json`. It shows process PID, independent scope hash pins, action denials, environment scrubbing, no leases, original production SHA preservation. This is not an activation/rollback test.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# R137 — Desktop Commander reference foundation (three pinned versions)
|
|
2
|
+
|
|
3
|
+
Status: **isolated development, unsealed and unpublished**.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
Use locally preserved Desktop Commander builds as engineering/compatibility baselines while keeping DEADBYTE MCP's compact native authority and runtime model. Do NOT embed or invoke three vendor servers, replace the existing MCP core, add network telemetry, or relax authority/grant checks. This is the small-interface strategy: preserve 26 known useful Commander tools and one reliable config file.
|
|
7
|
+
|
|
8
|
+
Local reference snapshots:
|
|
9
|
+
- `D:\\Projects\\deadbyte-mcp\\references\\desktop-commander v0.2.52`: package 0.2.52, MIT (package metadata). Tool schema SHA-256 `f342345e34e48769dabaefe1b932f09d41e2e207b422f7794a15ee50ffa8f140`, 27 toolArgSchemas including hidden `track_ui_event`, 26 public.
|
|
10
|
+
- `D:\\Projects\\deadbyte-mcp\\references\\desktop-commander v0.3.0`: package 0.3.0, MIT (package metadata). Tool schema SHA-256 `7d4c62e2099d757d8fba68011cfe25d3088426f56c0b77d5b8fa3edb4ec49c2c`, 26 schemas incl hidden, 25 public; `get_prompts` absent. Has `config-recovery.js`, SHA-256 `f091cc7f0f62413cf9debb461f3777ee2e1d4c194501daa7ad2c4570d5ccc542`.
|
|
11
|
+
- `D:\\Projects\\deadbyte-mcp\\references\\desktop-commander-0.3.1-alpha.2\\static`: package 0.3.1-alpha.2, MIT (actual LICENSE), 27 schemas incl hidden (26 public); schema SHA-256 `6a290497b29f9dc5f43d66008ea54fcf0c302edea5612af519ef52ab3816d29e`. Original tarball SHA-256 `faaf3f34f114589c0630ce4eaa7114ecc628ce2030822dfe67feac0e17fedc4b`. LICENSE SHA-256 `0fac4e4dfbd64a2a616b9ae2e14c4a3a5635d2a5fc9c43177fb834059afa58e3`.
|
|
12
|
+
|
|
13
|
+
## What was actually implemented
|
|
14
|
+
`src/commander-config-recovery-r137.mjs` implements a narrow recovery inspired by the 0.3.x feature but with stricter authority separation:
|
|
15
|
+
- Recover only two fully parsed and bounded top-level display limits: `fileReadLineLimit` (1..1000) and `fileWriteLineLimit` (1..50).
|
|
16
|
+
- Never salvage `allowedDirectories`, `blockedCommands`, `defaultShell`, `telemetryEnabled`, or any privileged or unknown setting from damaged bytes.
|
|
17
|
+
- Save a distinct byte-exact `commander-config.json.corrupt.<timestamp>.<uuid>` backup before repairing; verify the original SHA on readback; replace with a new temporary file then atomic rename. Never repair oversized files silently.
|
|
18
|
+
- Serialize parallel first reads to avoid duplicated recoveries; retain normal hash-preconditioned `setConfigValue` behavior.
|
|
19
|
+
- Return recovery metadata as structured result of `get_config` only if recovery actually occurred.
|
|
20
|
+
|
|
21
|
+
`test/r137-commander-three-version-contract.test.mjs` checks the pinned version-specific schemas and DEADBYTE normalization for each existing public name. `track_ui_event` remains deliberately hidden; no vendor telemetry is enabled. The original exhaustive R48 schema differential against 0.2.52 is preserved.
|
|
22
|
+
|
|
23
|
+
This design is NOT a new filesystem or command authorization mechanism. Native DEADBYTE policy / sessions / hash-pinned mutations continue to govern actions, independent of the recovered compatibility settings.
|
|
24
|
+
|
|
25
|
+
## License notice and provenance
|
|
26
|
+
Desktop Commander copyright Eduard Ruzga / contributors; MIT License. The 0.3.1-alpha.2 package contains the license text in `static/LICENSE`; the other two locally retained package.json files state MIT but do not include standalone LICENSE files. The full MIT license text/notice for the reference design is included at `docs/THIRD-PARTY-DESKTOP-COMMANDER-MIT.md`. No dist scripts, vendor node_modules or telemetry SDK copied into DEADBYTE. No new runtime dependencies introduced.
|
|
27
|
+
|
|
28
|
+
## Gates and operational boundary
|
|
29
|
+
R137 source is an isolated copy of R136; its package.json still says `0.15.51`, a **published immutable version that must not be republished**. Full native regression should run from an independently copied protected Windows fixture with User RX ACLs, not mutable D:\\ source, as R89 native lock enforces installation trust.
|
|
30
|
+
|
|
31
|
+
No production mutation; current active runtime stays 0.15.45, npm published latest stays 0.15.51, and R135/R136 autonomous process experiments are not activated. Further release requires a new version, full protected regression, owner-reviewed policy and independent activation/rollback gates.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# R138 — Commander process, output, session workflow
|
|
2
|
+
|
|
3
|
+
Status: DEVELOPMENT successor, not published, not deployed, no authority changes.
|
|
4
|
+
|
|
5
|
+
The native DEADBYTE MCP runtime remains the sole executor; reference Desktop Commander 0.2.52/0.3.0/0.3.1-alpha.2 tool schemas, not vendor servers. This train focuses on making its existing Commander bridge practical without adding Autonomous privilege layers.
|
|
6
|
+
|
|
7
|
+
## Process list integrity
|
|
8
|
+
`src/commander-external-process-r48.mjs` now validates every observation page, including the SHA-256 snapshot identity, timestamp, max page size and cursor. It detects repeated cursors before re-querying, denies duplicate PIDs within a snapshot, denies changing timestamps or snapshot IDs, and bounds page count. A corrupt observation is rejected before the returned PID can be used for an external kill. The existing identity-bound external termination path is unchanged.
|
|
9
|
+
|
|
10
|
+
## Output pagination and sessions
|
|
11
|
+
`src/commander-wire-adapter-r47.mjs` now attaches byte-level resume and truncation metadata to the Commander-compatible `read_process_output` structured response. Incremental offset=0 reads return `next_byte_offset` and `more_unread`; historical offset reads return `next_byte_offset:null` because they do not consume the incremental cursor. `earliest_offset` and `truncated` report retention loss explicitly. The public tool input schemas remain exactly as before.
|
|
12
|
+
|
|
13
|
+
`list_sessions` now displays child PID, profile, root, execution state and known exit outcome on one line; empty inventories are readable. Structured session data is untouched. No PID kill authority, raw command execution permissions or standing grants are expanded.
|
|
14
|
+
|
|
15
|
+
## Tests and provenance
|
|
16
|
+
TDD initial RED reproduced repeated cursor, duplicate PID, inconsistent snapshot timestamps, invalid cursor/identity, missing resume metadata and unreadable session list. The modified tests subsequently passed alongside the R48 Commander wire/managed/external-process regressions. The reference schema parity test for three pinned Desktop Commander versions remains present from R137.
|
|
17
|
+
|
|
18
|
+
The predecessor R137 source manifest SHA-256 is `7fd24ceda2f9369293c79f8da2a12616af822fa4c1224833336e7271a662a643`. Active production pointer SHA-256 is `df926f14fca1c14478342dce5ed41aa1c4b67fb12ae733114f03c8c720ea61e9` (runtime `0.15.45`) and must remain unchanged. R138 still uses package version `0.15.51` for development and must not republish it. Any release requires a distinct version and independent full protected Windows gate.
|
|
19
|
+
|
|
20
|
+
## Limits
|
|
21
|
+
The Commander bridge has existing file editing/reading/search and process operations, not every Desktop Commander UX feature. R138 proves the named improvements, not full remote parity, production activation, all historical 49 skipped tests, or OS privilege isolation.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# R139 — Practical Commander: large text, partial search, startup preview
|
|
2
|
+
|
|
3
|
+
Status: DEVELOPMENT ONLY / not a new npm release / not production activation.
|
|
4
|
+
|
|
5
|
+
Predecessor is R138 (2390 file manifest SHA-256 `8a60c3408dfc18f7593ff6a029a194ee2b503bcc2eed16c2b9ac020d490e863a`). Isolated source `D:\\Projects\\deadbyte-mcp\\development\\deadbyte-mcp-r139-commander-ergonomics-dev`. Runtime remains `0.15.45`; published npm latest remains `0.15.51`.
|
|
6
|
+
|
|
7
|
+
## Four improvements
|
|
8
|
+
- **Large text reading:** `read_file` for text exceeding the former native 4 MiB compatibility-read cap now uses `src/commander-large-text-r139.mjs` to scan at most 64 MiB with 256 KiB native pages (or the stricter policy cap). It verifies an identical native full-file SHA-256 across pages and rejects malformed cursors, file changes, oversized lines, invalid UTF-8, and oversized files. Only requested line ranges are retained in memory; the full-file scan is O(file size) and may require several native reads. Regular small files and document-specific readers remain unchanged.
|
|
9
|
+
- **Large text edit:** plain-text `edit_block` can modify a uniquely matched string in a UTF-8 file up to 16 MiB (or tighter native policy limit) using the *existing* native hash-preconditioned atomic write. Count of replacements must match the caller. Binary/NUL and invalid UTF-8 targets are rejected; a UTF-8 BOM is preserved. Not an unbounded text writer; DOCX/XLSX native authoring paths are unchanged.
|
|
10
|
+
- **Search results sooner:** `get_more_search_results` now returns discovered matches during an unfinished multi-page search, with `isComplete:false` and `hasMoreResults:true`; it does not disguise discovered matches as zero until all pages finish. Search runtime's existing signed cursor, abort policy and limits are unchanged.
|
|
11
|
+
- **Initial output preview:** `start_process` performs one nonwaiting (`waitMs:0`) 16 KiB maximum read after the process has started and returns any *already available* UTF-8 data in `structuredContent.initial_output` and text. It does not advance the independent process output cursor, so `read_process_output` will still show those bytes again; consumers should deduplicate if mixing both outputs. A failed/binary output probe does **not** disguise a successfully started process as a failed start.
|
|
12
|
+
|
|
13
|
+
## Security / compatibility
|
|
14
|
+
No command policies, grants, project roots, owner authority, token permissions, npm dependencies, or public tool input schemas are broadened. Existing Desktop Commander 0.2.52, 0.3.0 and 0.3.1-alpha.2 public-schema tests stay in the suite. Most large-file failures remain fail-closed rather than attempting partial destructive edits. A changed file is not rewritten, and no unrestricted subprocess or external vendor server is introduced.
|
|
15
|
+
|
|
16
|
+
## Evidence
|
|
17
|
+
Observed TDD RED -> GREEN for each feature using `test/r139-*.test.mjs` and existing R47/R48 tests. The new tests include large 5 MiB read/edit, SHA change during paging, binary edit refusal, exact replacement, UTF-8 BOM preservation, oversized edit refusal, partial search and immediate process-output preview. This candidate retains package version `0.15.51` and must not republish that immutable version. Production activation requires a separate decision and live/rollback gates.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Desktop Commander reference — MIT notice
|
|
2
|
+
|
|
3
|
+
The reference design and compatibility tests draw on the Desktop Commander MCP project, by Eduard Ruzga and Desktop Commander Contributors. Source: https://github.com/wonderwhy-er/DesktopCommanderMCP. Exact reference snapshots are described in R137-DESKTOP-COMMANDER-FOUNDATION.md.
|
|
4
|
+
|
|
5
|
+
The following text is the license notice found in the local 0.3.1-alpha.2 distribution. Include it if copying or redistributing covered Desktop Commander software.
|
|
6
|
+
|
|
7
|
+
MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2024-2025 Eduard Ruzga and Desktop Commander Contributors
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deadbyte-mcp",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.52",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Authenticated MCP agentic runtime with signed skills, persistent model backends, identity-bound remote devices, durable job supervision, immutable release slots, and separately revocable machine authority.",
|
|
@@ -7,7 +7,8 @@ import {fileURLToPath} from 'node:url';
|
|
|
7
7
|
const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
8
8
|
const sha256Bytes = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
9
9
|
|
|
10
|
-
export async function verifyR74PrepublishContract({rootPath = root} = {}) {
|
|
10
|
+
export async function verifyR74PrepublishContract({rootPath = root, manifestVerification = 'source'} = {}) {
|
|
11
|
+
if (!['source','installed'].includes(manifestVerification)) throw new Error('Unsupported prepublish manifest verification mode');
|
|
11
12
|
const result = {
|
|
12
13
|
schema: 'deadbyte.r74-prepublish-contract.v1',
|
|
13
14
|
valid: false,
|
|
@@ -63,7 +64,11 @@ export async function verifyR74PrepublishContract({rootPath = root} = {}) {
|
|
|
63
64
|
result.identities.manifest_sha256 = sha256Bytes(manifestBytes);
|
|
64
65
|
result.checks.manifest_exists = true;
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
// Authoring/release packaging retains strict npm-pack manifest equality.
|
|
68
|
+
// Isolated protected installations instead validate every pinned byte and
|
|
69
|
+
// reject unlisted files except two fixed non-runtime packaging metadata files.
|
|
70
|
+
const manifestCommand = manifestVerification === 'installed' ? 'verify-installed' : 'verify';
|
|
71
|
+
const manifestVerifyResult = spawnSync(process.execPath, [path.join(rootPath, 'scripts', 'release-manifest.mjs'), manifestCommand], {
|
|
67
72
|
cwd: rootPath,
|
|
68
73
|
encoding: 'utf8',
|
|
69
74
|
timeout: 60000,
|
|
@@ -73,6 +78,7 @@ export async function verifyR74PrepublishContract({rootPath = root} = {}) {
|
|
|
73
78
|
throw new Error(`Prepublish contract failed: working tree manifest verification failed: ${manifestVerifyResult.stderr || manifestVerifyResult.stdout}`);
|
|
74
79
|
}
|
|
75
80
|
result.checks.manifest_verified = true;
|
|
81
|
+
result.checks.manifest_verification_mode = manifestVerification;
|
|
76
82
|
|
|
77
83
|
// 4. Packaged files boundary (no leaks of private/testing trees)
|
|
78
84
|
const npmCli = process.env.npm_execpath || path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npm-cli.js');
|
|
@@ -175,6 +175,19 @@ async function verifyTree() {
|
|
|
175
175
|
console.log(`MANIFEST_VERIFY_TREE_OK files=${entries.size} sha256=${sha256(actual)}`);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// Installed-tree verification deliberately avoids regenerating the packlist
|
|
179
|
+
// (npm pack has environment-sensitive inclusion behavior after installation).
|
|
180
|
+
// Every manifested byte is still hash-checked; only precisely named
|
|
181
|
+
// packaging metadata, excluded from npm payload, may appear additionally.
|
|
182
|
+
async function verifyInstalled() {
|
|
183
|
+
const actual = await readFile(manifestPath);
|
|
184
|
+
const entries = parseSlotManifest(actual.toString('utf8'));
|
|
185
|
+
await verifyManifestTreeExact(root, entries, {
|
|
186
|
+
allowedExtraPaths:['.npmignore','package-lock.json']
|
|
187
|
+
});
|
|
188
|
+
console.log(`MANIFEST_VERIFY_INSTALLED_OK files=${entries.size} sha256=${sha256(actual)}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
178
191
|
const invokedPath = process.argv[1] ? path.resolve(process.argv[1]) : '';
|
|
179
192
|
if (invokedPath === fileURLToPath(import.meta.url)) {
|
|
180
193
|
const command = process.argv[2] ?? 'verify';
|
|
@@ -182,7 +195,8 @@ if (invokedPath === fileURLToPath(import.meta.url)) {
|
|
|
182
195
|
if (command === 'write') await writeManifest();
|
|
183
196
|
else if (command === 'verify') await verifyManifest();
|
|
184
197
|
else if (command === 'verify-tree') await verifyTree();
|
|
185
|
-
else
|
|
198
|
+
else if (command === 'verify-installed') await verifyInstalled();
|
|
199
|
+
else throw new Error(`usage: node scripts/release-manifest.mjs [write|verify|verify-tree|verify-installed]`);
|
|
186
200
|
} catch (error) {
|
|
187
201
|
console.error(`MANIFEST_${command.toUpperCase()}_FAIL ${error?.stack ?? error}`);
|
|
188
202
|
process.exitCode = 1;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import {existsSync,lstatSync,realpathSync} from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
// R135 is an offline POLICY PROJECTION only. It cannot mint a lease or grant,
|
|
6
|
+
// modify an active generation, or make a broad manual Coding grant safe to
|
|
7
|
+
// inherit in the same process. Execution requires a separate proven boundary.
|
|
8
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
9
|
+
const clone=value=>structuredClone(value);
|
|
10
|
+
function realProjectRoot(input){
|
|
11
|
+
if(typeof input!=='string'||!path.isAbsolute(input))throw new Error('real project directory required');
|
|
12
|
+
const candidate=path.resolve(input),disk=path.parse(candidate).root;
|
|
13
|
+
const eq=(a,b)=>process.platform==='win32'?a.toLowerCase()===b.toLowerCase():a===b;
|
|
14
|
+
if(eq(candidate,disk))throw new Error('project-scoped root required');
|
|
15
|
+
try{
|
|
16
|
+
const info=lstatSync(candidate);
|
|
17
|
+
if(!info.isDirectory()||info.isSymbolicLink()||!eq(realpathSync(candidate),candidate)){
|
|
18
|
+
throw new Error('path redirected');
|
|
19
|
+
}
|
|
20
|
+
let cursor=candidate;
|
|
21
|
+
for(;;){
|
|
22
|
+
if(lstatSync(cursor).isSymbolicLink())throw new Error('linked ancestor');
|
|
23
|
+
const parent=path.dirname(cursor);
|
|
24
|
+
if(parent===cursor)break;
|
|
25
|
+
cursor=parent;
|
|
26
|
+
}
|
|
27
|
+
}catch{throw new Error('real project directory required')}
|
|
28
|
+
return candidate;
|
|
29
|
+
}
|
|
30
|
+
export function deriveIsolatedAutonomousScope({manualCodingPolicy,autonomousPolicy,scopeRootId,isolatedStateRoot}={}){
|
|
31
|
+
if(manualCodingPolicy?.schema!=='deadbyte.coding-policy.v1'||manualCodingPolicy.enabled!==true)
|
|
32
|
+
throw new Error('manual Coding policy schema invalid');
|
|
33
|
+
if(autonomousPolicy?.schema!=='deadbyte.autonomous-policy.v1'||autonomousPolicy.enabled!==true)
|
|
34
|
+
throw new Error('Autonomous policy schema invalid');
|
|
35
|
+
if(!/^[A-Za-z0-9._-]{1,64}$/.test(scopeRootId??''))throw new Error('invalid project scope');
|
|
36
|
+
if(!path.isAbsolute(isolatedStateRoot??''))throw new Error('isolated state root must be absolute');
|
|
37
|
+
const scopedRoot=manualCodingPolicy.roots?.[scopeRootId];
|
|
38
|
+
if(!scopedRoot)throw new Error('approved project root missing from Coding policy');
|
|
39
|
+
if(!Array.isArray(autonomousPolicy.root_ids)||autonomousPolicy.root_ids.length!==1||
|
|
40
|
+
autonomousPolicy.root_ids[0]!==scopeRootId)throw new Error('Autonomous root outside approved scope');
|
|
41
|
+
const safeRoot=realProjectRoot(scopedRoot.path);
|
|
42
|
+
const isolated=realProjectRoot(isolatedStateRoot);
|
|
43
|
+
const eq=(a,b)=>process.platform==='win32'?a.toLowerCase()===b.toLowerCase():a===b;
|
|
44
|
+
const manualLease=manualCodingPolicy.lease_file,autonLease=autonomousPolicy.lease_file;
|
|
45
|
+
for(const lease of [manualLease,autonLease]){
|
|
46
|
+
if(typeof lease!=='string'||!path.isAbsolute(lease))throw new Error('manual policy lease invalid');
|
|
47
|
+
const rel=path.relative(isolated,path.resolve(lease));
|
|
48
|
+
if(rel===''||(!path.isAbsolute(rel)&&rel!=='..'&&!rel.startsWith('..'+path.sep))){
|
|
49
|
+
throw new Error('isolated state root overlaps manual Coding lease');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Fail closed for any profiles referencing another root or missing command.
|
|
53
|
+
const selectedCommands={};
|
|
54
|
+
for(const [id,profile] of Object.entries(autonomousPolicy.profiles??{})){
|
|
55
|
+
if(profile?.root_id!==scopeRootId)throw new Error('Autonomous profile '+id+' root outside approved scope');
|
|
56
|
+
const command=profile.command_id;
|
|
57
|
+
if(typeof command!=='string'||!Object.hasOwn(manualCodingPolicy.commands??{},command)){
|
|
58
|
+
throw new Error('Autonomous command missing from Coding policy');
|
|
59
|
+
}
|
|
60
|
+
const selected=clone(manualCodingPolicy.commands[command]);
|
|
61
|
+
if(!Array.isArray(selected.root_ids)||!selected.root_ids.includes(scopeRootId)){
|
|
62
|
+
throw new Error('Autonomous command '+command+' outside approved scope');
|
|
63
|
+
}
|
|
64
|
+
selected.root_ids=[scopeRootId];
|
|
65
|
+
selectedCommands[command]=selected;
|
|
66
|
+
}
|
|
67
|
+
if(autonomousPolicy.planner?.backend==='codex_app_server'&&
|
|
68
|
+
autonomousPolicy.planner.root_id!==scopeRootId)throw new Error('planner root outside approved scope');
|
|
69
|
+
const codingPolicy=clone(manualCodingPolicy);
|
|
70
|
+
codingPolicy.roots={[scopeRootId]:{...clone(scopedRoot),path:safeRoot}};
|
|
71
|
+
codingPolicy.commands=selectedCommands;
|
|
72
|
+
codingPolicy.lease_file=path.join(isolated,'runtime','coding-grant.json');
|
|
73
|
+
codingPolicy.evidence_dir=path.join(isolated,'evidence','coding');
|
|
74
|
+
if(existsSync(codingPolicy.lease_file)||existsSync(path.join(isolated,'runtime','autonomous-grant.json'))){
|
|
75
|
+
throw new Error('isolated grant file already present');
|
|
76
|
+
}
|
|
77
|
+
const codingBytes=Buffer.from(JSON.stringify(codingPolicy,null,2)+'\n','utf8');
|
|
78
|
+
const nextAutonomous=clone(autonomousPolicy);
|
|
79
|
+
nextAutonomous.root_ids=[scopeRootId];
|
|
80
|
+
nextAutonomous.coding_policy_sha256=sha(codingBytes);
|
|
81
|
+
nextAutonomous.lease_file=path.join(isolated,'runtime','autonomous-grant.json');
|
|
82
|
+
nextAutonomous.evidence_dir=path.join(isolated,'evidence','autonomous');
|
|
83
|
+
const autonomousBytes=Buffer.from(JSON.stringify(nextAutonomous,null,2)+'\n','utf8');
|
|
84
|
+
return Object.freeze({
|
|
85
|
+
scope_root_id:scopeRootId,isolated_state_root:isolated,
|
|
86
|
+
codingBytes,autonomousBytes,
|
|
87
|
+
codingPolicy,autonomousPolicy:nextAutonomous,
|
|
88
|
+
coding_sha256:sha(codingBytes),autonomous_sha256:sha(autonomousBytes),
|
|
89
|
+
status:'OFFLINE_UNAUTHORIZED_PROJECTION'
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {createHash,randomUUID} from 'node:crypto';
|
|
2
|
+
import {readFile,writeFile,rename} from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
// Reference design: Desktop Commander 0.3.0 MIT config-recovery.
|
|
6
|
+
// DEADBYTE independently restricts salvage to NON-AUTHORITY display limits;
|
|
7
|
+
// privileged configuration never flows into this recovery path.
|
|
8
|
+
const MAX_BYTES=1024*1024;
|
|
9
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
10
|
+
const isObject=v=>v!==null&&typeof v==='object'&&!Array.isArray(v);
|
|
11
|
+
|
|
12
|
+
export function salvageSafeCommanderLimits(raw){
|
|
13
|
+
if(typeof raw!=='string'||Buffer.byteLength(raw,'utf8')>MAX_BYTES)return {};
|
|
14
|
+
let str=raw.charCodeAt(0)===0xfeff?raw.slice(1):raw;
|
|
15
|
+
if(!str.trimStart().startsWith('{'))return {};
|
|
16
|
+
const base=str.indexOf('{'),ends=[];
|
|
17
|
+
let depth=0,quoted=false,escaped=false;
|
|
18
|
+
for(let i=base;i<str.length;i++){
|
|
19
|
+
const c=str[i];
|
|
20
|
+
if(quoted){if(escaped)escaped=false;else if(c==='\\')escaped=true;else if(c==='"')quoted=false;continue;}
|
|
21
|
+
if(c==='"'){quoted=true;continue;}
|
|
22
|
+
if(c==='{'||c==='['){depth++;continue;}
|
|
23
|
+
if(c==='}'||c===']'){depth--;if(depth===0){ends.push({index:i+1,complete:true});break;}if(depth<0)break;continue;}
|
|
24
|
+
if(c===','&&depth===1)ends.push({index:i,complete:false});
|
|
25
|
+
}
|
|
26
|
+
let obj={};
|
|
27
|
+
for(const item of ends.reverse()){
|
|
28
|
+
try{
|
|
29
|
+
const v=JSON.parse(str.slice(base,item.index)+(item.complete?'':'}'));
|
|
30
|
+
if(isObject(v)){obj=v;break;}
|
|
31
|
+
}catch{}
|
|
32
|
+
}
|
|
33
|
+
const safe={};
|
|
34
|
+
for(const [key,max] of [['fileReadLineLimit',1000],['fileWriteLineLimit',50]]){
|
|
35
|
+
const value=obj[key];
|
|
36
|
+
if(Number.isSafeInteger(value)&&value>=1&&value<=max)safe[key]=value;
|
|
37
|
+
}
|
|
38
|
+
return safe;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function recoverCorruptCommanderConfig(file,bytes,defaults){
|
|
42
|
+
if(!Buffer.isBuffer(bytes)||bytes.length>MAX_BYTES)throw new Error('Commander config corrupt or oversized; manual inspection required');
|
|
43
|
+
if(!isObject(defaults))throw new Error('recovery defaults invalid');
|
|
44
|
+
const safe=salvageSafeCommanderLimits(bytes.toString('utf8'));
|
|
45
|
+
const next={...structuredClone(defaults),...safe};
|
|
46
|
+
// The only trusted recovered settings are bounded display limits.
|
|
47
|
+
next.telemetryEnabled=false;
|
|
48
|
+
next.allowedDirectories=[];
|
|
49
|
+
next.blockedCommands=structuredClone(defaults.blockedCommands);
|
|
50
|
+
const current=await readFile(file);
|
|
51
|
+
if(sha(current)!==sha(bytes))throw new Error('Commander config changed during recovery; stop');
|
|
52
|
+
const backup=path.basename(file)+'.corrupt.'+Date.now()+'.'+randomUUID();
|
|
53
|
+
const backupPath=path.join(path.dirname(file),backup);
|
|
54
|
+
await writeFile(backupPath,bytes,{flag:'wx',mode:0o600});
|
|
55
|
+
const tmp=file+'.recover-'+randomUUID();
|
|
56
|
+
try{
|
|
57
|
+
await writeFile(tmp,JSON.stringify(next)+'\n',{flag:'wx',mode:0o600});
|
|
58
|
+
const verify=await readFile(file);
|
|
59
|
+
if(sha(verify)!==sha(bytes))throw new Error('Commander config changed during recovery; stop');
|
|
60
|
+
await rename(tmp,file);
|
|
61
|
+
}catch(error){
|
|
62
|
+
const {rm}=await import('node:fs/promises');
|
|
63
|
+
await rm(tmp,{force:true}).catch(()=>{});
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
return {config:next,recovery:{status:'recovered',backup_filename:backup,corrupt_sha256:sha(bytes),salvaged_keys:Object.keys(safe)}};
|
|
67
|
+
}
|
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { canonicalJson } from './canonical-json.mjs';
|
|
5
5
|
import { createDeadbytePdf } from './commander-pdf-r48.mjs';
|
|
6
6
|
import { DEADBYTE_VERSION } from './version.mjs';
|
|
7
|
+
import {recoverCorruptCommanderConfig} from './commander-config-recovery-r137.mjs';
|
|
7
8
|
|
|
8
9
|
export const COMMANDER_EXTENDED_SCHEMA='deadbyte.commander-extended.v1';
|
|
9
10
|
const MAX_VALUE=16384,MAX_FEEDBACK=4096,MAX_PREVIEW=65536;
|
|
@@ -44,17 +45,33 @@ function makePdf(text){
|
|
|
44
45
|
export function createCommanderExtendedRuntime({stateRoot,history=null,prompts=DEFAULT_PROMPTS,resources=[]}={}){
|
|
45
46
|
if(typeof stateRoot!=='string'||!stateRoot)throw new Error('extended runtime stateRoot required');
|
|
46
47
|
const configPath=path.join(stateRoot,'commander-config.json'),feedbackDir=path.join(stateRoot,'feedback');
|
|
47
|
-
let configCache=null;
|
|
48
|
+
let configCache=null,recovery=null,loadPromise=null;
|
|
48
49
|
async function loadConfig(){
|
|
49
50
|
if(configCache)return configCache;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
if(!loadPromise)loadPromise=(async()=>{
|
|
52
|
+
let bytes;
|
|
53
|
+
try{bytes=await readFile(configPath);}
|
|
54
|
+
catch(e){
|
|
55
|
+
if(e?.code!=='ENOENT')throw e;
|
|
56
|
+
configCache=structuredClone(COMPAT_CONFIG_DEFAULTS);return configCache;
|
|
57
|
+
}
|
|
58
|
+
try{
|
|
59
|
+
const parsed=JSON.parse(bytes.toString('utf8'));
|
|
60
|
+
if(!parsed||typeof parsed!=='object'||Array.isArray(parsed))throw new SyntaxError('config must be an object');
|
|
61
|
+
configCache={...structuredClone(COMPAT_CONFIG_DEFAULTS),...parsed};
|
|
62
|
+
}catch(e){
|
|
63
|
+
if(!(e instanceof SyntaxError))throw e;
|
|
64
|
+
const result=await recoverCorruptCommanderConfig(configPath,bytes,COMPAT_CONFIG_DEFAULTS);
|
|
65
|
+
configCache=result.config;recovery=result.recovery;
|
|
66
|
+
}
|
|
67
|
+
return configCache;
|
|
68
|
+
})();
|
|
69
|
+
try{return await loadPromise;}finally{loadPromise=null;}
|
|
53
70
|
}
|
|
54
71
|
async function getConfig({key=null}={}){
|
|
55
72
|
const cfg=await loadConfig();
|
|
56
73
|
if(key!==null&&!keyOk(key))throw new Error('config key invalid');
|
|
57
|
-
return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',config:key===null?structuredClone(cfg):{[key]:cfg[key]},config_sha256:sha256(cfg)};
|
|
74
|
+
return {schema:COMMANDER_EXTENDED_SCHEMA,status:'ok',config:key===null?structuredClone(cfg):{[key]:cfg[key]},config_sha256:sha256(cfg),...(recovery?{recovery:structuredClone(recovery)}:{})};
|
|
58
75
|
}
|
|
59
76
|
async function setConfigValue({key,value,expected_sha256}){
|
|
60
77
|
if(!keyOk(key))throw new Error('config key invalid');
|
|
@@ -61,17 +61,39 @@ export function createCommanderExternalProcessRuntime({observationRuntime,hostPo
|
|
|
61
61
|
let cursor=null,first=null;
|
|
62
62
|
const processes=[];
|
|
63
63
|
const receiptIds=[];
|
|
64
|
+
const cursorSet=new Set(),pidSet=new Set();
|
|
64
65
|
do{
|
|
65
66
|
const page=await observationRuntime.processList({limit:PAGE_SIZE,includeExternal:true,cursor});
|
|
66
67
|
if(page?.status!=='ok'||!Array.isArray(page.processes)) throw new Error('external process observation failed');
|
|
68
|
+
if(typeof page.snapshot_id!=='string'||!/^[0-9a-f]{64}$/.test(page.snapshot_id))
|
|
69
|
+
throw new Error('external process observation snapshot identity invalid');
|
|
70
|
+
if(typeof page.observed_at_utc!=='string'||!ISO_UTC.test(page.observed_at_utc)||
|
|
71
|
+
!Number.isFinite(Date.parse(page.observed_at_utc)))
|
|
72
|
+
throw new Error('external process observation timestamp invalid');
|
|
67
73
|
if(!first) first=page;
|
|
68
|
-
else
|
|
74
|
+
else{
|
|
75
|
+
if(page.snapshot_id!==first.snapshot_id) throw new Error('external process observation snapshot changed during pagination');
|
|
76
|
+
if(page.observed_at_utc!==first.observed_at_utc)
|
|
77
|
+
throw new Error('external process observation time changed during pagination');
|
|
78
|
+
}
|
|
79
|
+
if(page.processes.length>PAGE_SIZE)throw new Error('external process observation page exceeds bounded limit');
|
|
69
80
|
for(const item of page.processes){
|
|
70
81
|
if(processes.length>=MAX_PROCESSES) throw new Error('external process observation exceeds bounded process cap');
|
|
71
|
-
|
|
82
|
+
const identity=normalizeIdentity(item);
|
|
83
|
+
if(pidSet.has(identity.pid))throw new Error('external process observation duplicate PID');
|
|
84
|
+
pidSet.add(identity.pid);
|
|
85
|
+
processes.push(identity);
|
|
72
86
|
}
|
|
73
87
|
if(typeof page.receipt_id==='string') receiptIds.push(page.receipt_id);
|
|
74
|
-
|
|
88
|
+
const next=page.next_cursor??null;
|
|
89
|
+
if(next!==null){
|
|
90
|
+
if(typeof next!=='string'||next.length<1||next.length>8192)throw new Error('external process observation cursor invalid');
|
|
91
|
+
if(cursorSet.has(next))throw new Error('external process pagination cursor repeated');
|
|
92
|
+
cursorSet.add(next);
|
|
93
|
+
if(cursorSet.size>Math.ceil(MAX_PROCESSES/PAGE_SIZE)+1)
|
|
94
|
+
throw new Error('external process pagination exceeded page limit');
|
|
95
|
+
}
|
|
96
|
+
cursor=next;
|
|
75
97
|
}while(cursor!==null);
|
|
76
98
|
return {
|
|
77
99
|
status:'ok',
|