@letta-ai/letta-code 0.34.5 → 0.34.6
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/dist/gateway-core.js +5 -5
- package/dist/gateway-core.js.map +3 -3
- package/dist/mcp-client.js +2 -2
- package/dist/mcp-client.js.map +1 -1
- package/dist/mcp-oauth.js +2 -2
- package/dist/mcp-oauth.js.map +1 -1
- package/dist/types/agent/acting-user.d.ts +10 -0
- package/dist/types/agent/acting-user.d.ts.map +1 -1
- package/dist/types/agent/approval-execution.d.ts +5 -1
- package/dist/types/agent/approval-execution.d.ts.map +1 -1
- package/dist/types/agent/message.d.ts +3 -1
- package/dist/types/agent/message.d.ts.map +1 -1
- package/dist/types/agent/subagents/manager.d.ts +1 -1
- package/dist/types/agent/subagents/manager.d.ts.map +1 -1
- package/dist/types/agent/subagents/subagent-launcher.d.ts.map +1 -1
- package/dist/types/agent/subagents/subagent-stream.d.ts.map +1 -1
- package/dist/types/backend/api/agent-message.d.ts +1 -1
- package/dist/types/backend/api/agent-message.d.ts.map +1 -1
- package/dist/types/backend/api/conversation-enqueue.d.ts +4 -2
- package/dist/types/backend/api/conversation-enqueue.d.ts.map +1 -1
- package/dist/types/channels/gateway-core.d.ts +1 -1
- package/dist/types/channels/gateway-core.d.ts.map +1 -1
- package/dist/types/headless-listener-launch.d.ts.map +1 -1
- package/dist/types/queue/queue-runtime.d.ts +11 -2
- package/dist/types/queue/queue-runtime.d.ts.map +1 -1
- package/dist/types/runtime-context.d.ts +4 -0
- package/dist/types/runtime-context.d.ts.map +1 -1
- package/dist/types/tools/external-tool-background-eligibility.d.ts +1 -0
- package/dist/types/tools/external-tool-background-eligibility.d.ts.map +1 -1
- package/dist/types/tools/external-tool-background.d.ts +1 -0
- package/dist/types/tools/external-tool-background.d.ts.map +1 -1
- package/dist/types/tools/impl/claude-stream-session.d.ts +1 -0
- package/dist/types/tools/impl/claude-stream-session.d.ts.map +1 -1
- package/dist/types/tools/impl/codex-app-server.d.ts +1 -0
- package/dist/types/tools/impl/codex-app-server.d.ts.map +1 -1
- package/dist/types/tools/impl/external-coding-agent-task.d.ts +4 -2
- package/dist/types/tools/impl/external-coding-agent-task.d.ts.map +1 -1
- package/dist/types/tools/impl/external-coding-agent.d.ts +1 -1
- package/dist/types/tools/impl/external-coding-agent.d.ts.map +1 -1
- package/dist/types/tools/impl/memory-task-lifecycle.d.ts +1 -1
- package/dist/types/tools/impl/memory-task-lifecycle.d.ts.map +1 -1
- package/dist/types/tools/impl/monitor.d.ts +1 -1
- package/dist/types/tools/impl/monitor.d.ts.map +1 -1
- package/dist/types/tools/impl/native-session-capture.d.ts +16 -3
- package/dist/types/tools/impl/native-session-capture.d.ts.map +1 -1
- package/dist/types/tools/impl/process_manager.d.ts +2 -2
- package/dist/types/tools/impl/process_manager.d.ts.map +1 -1
- package/dist/types/tools/impl/send-agent-message.d.ts.map +1 -1
- package/dist/types/tools/impl/shell-env.d.ts.map +1 -1
- package/dist/types/tools/impl/task.d.ts +2 -1
- package/dist/types/tools/impl/task.d.ts.map +1 -1
- package/dist/types/tools/toolset.d.ts +1 -0
- package/dist/types/tools/toolset.d.ts.map +1 -1
- package/dist/types/types/protocol_v2.d.ts +5 -4
- package/dist/types/types/protocol_v2.d.ts.map +1 -1
- package/dist/types/types/turn-finished-protocol.d.ts +13 -0
- package/dist/types/types/turn-finished-protocol.d.ts.map +1 -0
- package/dist/types/utils/message-queue-bridge.d.ts +2 -1
- package/dist/types/utils/message-queue-bridge.d.ts.map +1 -1
- package/dist/types/utils/task-notifications.d.ts +1 -1
- package/dist/types/utils/task-notifications.d.ts.map +1 -1
- package/dist/types/websocket/listener/connection.d.ts +6 -2
- package/dist/types/websocket/listener/connection.d.ts.map +1 -1
- package/dist/types/websocket/listener/constants.d.ts +1 -0
- package/dist/types/websocket/listener/constants.d.ts.map +1 -1
- package/dist/types/websocket/listener/conversation-runtime.d.ts +31 -0
- package/dist/types/websocket/listener/conversation-runtime.d.ts.map +1 -1
- package/dist/types/websocket/listener/durable-file-lock.d.ts +46 -0
- package/dist/types/websocket/listener/durable-file-lock.d.ts.map +1 -0
- package/dist/types/websocket/listener/failed-teleport-cleanup.d.ts +16 -0
- package/dist/types/websocket/listener/failed-teleport-cleanup.d.ts.map +1 -0
- package/dist/types/websocket/listener/inbound-queue.d.ts +3 -1
- package/dist/types/websocket/listener/inbound-queue.d.ts.map +1 -1
- package/dist/types/websocket/listener/input-disposition-capacity.d.ts +8 -0
- package/dist/types/websocket/listener/input-disposition-capacity.d.ts.map +1 -0
- package/dist/types/websocket/listener/input-disposition-expiry.d.ts +3 -0
- package/dist/types/websocket/listener/input-disposition-expiry.d.ts.map +1 -0
- package/dist/types/websocket/listener/input-disposition-path.d.ts +2 -0
- package/dist/types/websocket/listener/input-disposition-path.d.ts.map +1 -0
- package/dist/types/websocket/listener/input-disposition-retention.d.ts +9 -0
- package/dist/types/websocket/listener/input-disposition-retention.d.ts.map +1 -0
- package/dist/types/websocket/listener/input-disposition-validation.d.ts +2 -0
- package/dist/types/websocket/listener/input-disposition-validation.d.ts.map +1 -0
- package/dist/types/websocket/listener/input-disposition.d.ts +68 -0
- package/dist/types/websocket/listener/input-disposition.d.ts.map +1 -0
- package/dist/types/websocket/listener/input-terminal-journal.d.ts +60 -0
- package/dist/types/websocket/listener/input-terminal-journal.d.ts.map +1 -0
- package/dist/types/websocket/listener/interrupted-turn-input-ownership.d.ts +27 -0
- package/dist/types/websocket/listener/interrupted-turn-input-ownership.d.ts.map +1 -0
- package/dist/types/websocket/listener/interrupted-turn-record.d.ts +132 -0
- package/dist/types/websocket/listener/interrupted-turn-record.d.ts.map +1 -0
- package/dist/types/websocket/listener/interrupted-turn-schema.d.ts +5 -0
- package/dist/types/websocket/listener/interrupted-turn-schema.d.ts.map +1 -0
- package/dist/types/websocket/listener/interrupted-turn-storage.d.ts +5 -0
- package/dist/types/websocket/listener/interrupted-turn-storage.d.ts.map +1 -0
- package/dist/types/websocket/listener/interrupted-turn-types.d.ts +59 -0
- package/dist/types/websocket/listener/interrupted-turn-types.d.ts.map +1 -0
- package/dist/types/websocket/listener/legacy-authority-quarantine.d.ts +13 -0
- package/dist/types/websocket/listener/legacy-authority-quarantine.d.ts.map +1 -0
- package/dist/types/websocket/listener/lifecycle-message.d.ts +7 -0
- package/dist/types/websocket/listener/lifecycle-message.d.ts.map +1 -0
- package/dist/types/websocket/listener/outbound-delivery.d.ts +21 -0
- package/dist/types/websocket/listener/outbound-delivery.d.ts.map +1 -0
- package/dist/types/websocket/listener/outbound-wire.d.ts +11 -1
- package/dist/types/websocket/listener/outbound-wire.d.ts.map +1 -1
- package/dist/types/websocket/listener/protocol-outbound.d.ts +8 -10
- package/dist/types/websocket/listener/protocol-outbound.d.ts.map +1 -1
- package/dist/types/websocket/listener/protocol-validation.d.ts +7 -0
- package/dist/types/websocket/listener/protocol-validation.d.ts.map +1 -0
- package/dist/types/websocket/listener/queue.d.ts.map +1 -1
- package/dist/types/websocket/listener/recorded-tool-results.d.ts +5 -0
- package/dist/types/websocket/listener/recorded-tool-results.d.ts.map +1 -0
- package/dist/types/websocket/listener/recovery-authority-guard.d.ts +24 -0
- package/dist/types/websocket/listener/recovery-authority-guard.d.ts.map +1 -0
- package/dist/types/websocket/listener/recovery-lineage-sidecar.d.ts +57 -0
- package/dist/types/websocket/listener/recovery-lineage-sidecar.d.ts.map +1 -0
- package/dist/types/websocket/listener/remote-settings-lock.d.ts.map +1 -1
- package/dist/types/websocket/listener/remote-settings.d.ts.map +1 -1
- package/dist/types/websocket/listener/runtime.d.ts +3 -0
- package/dist/types/websocket/listener/runtime.d.ts.map +1 -1
- package/dist/types/websocket/listener/scope.d.ts.map +1 -1
- package/dist/types/websocket/listener/startup-frame-buffer.d.ts +26 -0
- package/dist/types/websocket/listener/startup-frame-buffer.d.ts.map +1 -0
- package/dist/types/websocket/listener/startup-ingress.d.ts +44 -0
- package/dist/types/websocket/listener/startup-ingress.d.ts.map +1 -0
- package/dist/types/websocket/listener/teleport-protocol-inbound.d.ts +5 -0
- package/dist/types/websocket/listener/teleport-protocol-inbound.d.ts.map +1 -0
- package/dist/types/websocket/listener/turn-correlation.d.ts +8 -0
- package/dist/types/websocket/listener/turn-correlation.d.ts.map +1 -1
- package/dist/types/websocket/listener/turn-finished-replay.d.ts +90 -0
- package/dist/types/websocket/listener/turn-finished-replay.d.ts.map +1 -0
- package/dist/types/websocket/listener/turn-lifecycle.d.ts +9 -1
- package/dist/types/websocket/listener/turn-lifecycle.d.ts.map +1 -1
- package/dist/types/websocket/listener/types.d.ts +252 -6
- package/dist/types/websocket/listener/types.d.ts.map +1 -1
- package/letta.js +13157 -4646
- package/npm-shrinkwrap.json +5763 -0
- package/package.json +11 -4
- package/scripts/check.js +1 -0
- package/scripts/runtime-graph.test.ts +112 -0
- package/scripts/runtime-graph.ts +366 -0
- package/scripts/source-file-size-baseline.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@letta-ai/letta-code",
|
|
3
|
-
"version": "0.34.
|
|
3
|
+
"version": "0.34.6",
|
|
4
4
|
"lettaStartupLogProtocol": 1,
|
|
5
5
|
"description": "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
|
|
6
6
|
"type": "module",
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"README.md",
|
|
14
14
|
"letta.js",
|
|
15
15
|
"image-resize-worker.js",
|
|
16
|
+
"npm-shrinkwrap.json",
|
|
16
17
|
"assets/tutor-profile.png",
|
|
17
18
|
"scripts",
|
|
18
19
|
"skills",
|
|
@@ -209,14 +210,17 @@
|
|
|
209
210
|
"check:test-coverage": "node scripts/check-test-coverage.cjs",
|
|
210
211
|
"check:skill-frontmatter": "node scripts/check-skill-frontmatter.js",
|
|
211
212
|
"check:bundled-skill-scripts": "node scripts/check-bundled-skill-scripts.js",
|
|
213
|
+
"runtime-graph:generate": "bun run scripts/runtime-graph.ts generate",
|
|
214
|
+
"runtime-graph:verify": "bun run scripts/runtime-graph.ts verify",
|
|
212
215
|
"check": "bun run scripts/check.js",
|
|
213
216
|
"dev": "node scripts/dev.cjs",
|
|
214
217
|
"build": "node scripts/postinstall-patches.js && bun run build.js",
|
|
215
218
|
"test:update-chain:manual": "bun run src/test-utils/update-chain-smoke.ts --mode manual",
|
|
216
219
|
"test:update-chain:startup": "bun run src/test-utils/update-chain-smoke.ts --mode startup",
|
|
217
|
-
"prepublishOnly": "bun run build",
|
|
220
|
+
"prepublishOnly": "bun run runtime-graph:verify && bun run build",
|
|
218
221
|
"postinstall": "node scripts/postinstall-patches.js || echo letta: vendor patches skipped && node -e \"try{require('fs').chmodSync(require('path').join(require.resolve('node-pty/package.json'),'../prebuilds/darwin-arm64/spawn-helper'),0o755)}catch(e){}\" || true",
|
|
219
|
-
"mod-learning:memory-citations": "bun scripts/mod-learning/learn-mod.ts --env docs/examples/mods/learning/memory-citations.env.json"
|
|
222
|
+
"mod-learning:memory-citations": "bun scripts/mod-learning/learn-mod.ts --env docs/examples/mods/learning/memory-citations.env.json",
|
|
223
|
+
"test:built-entrypoints": "LETTA_TEST_CLI_BUNDLE=\"$PWD/letta.js\" LETTA_TEST_BUILT_ARTIFACT=1 bun test src/cli/subcommands/lifecycle-output.test.ts src/websocket/app-server-dream.test.ts --timeout 60000"
|
|
220
224
|
},
|
|
221
225
|
"lint-staged": {
|
|
222
226
|
"*.{ts,tsx,js,jsx,json}": [
|
|
@@ -268,5 +272,8 @@
|
|
|
268
272
|
"./dist/types/types/protocol.d.ts"
|
|
269
273
|
]
|
|
270
274
|
}
|
|
271
|
-
}
|
|
275
|
+
},
|
|
276
|
+
"lettaLifecycleProtocol": 1,
|
|
277
|
+
"lettaRuntimeGraphProtocol": 1,
|
|
278
|
+
"lettaRuntimeGraphSha512": "sha512-XLLoGDBuKF5JywIzS03nqOmcuSbwY1oGBwkINW2htdSGxlMnDVGIKq6HZLoE6hDMudNN3mzVIBUhdIYIlj2zzw=="
|
|
272
279
|
}
|
package/scripts/check.js
CHANGED
|
@@ -23,6 +23,7 @@ const checks = [
|
|
|
23
23
|
{ name: "test coverage", script: ["check:test-coverage"] },
|
|
24
24
|
{ name: "skill frontmatter", script: ["check:skill-frontmatter"] },
|
|
25
25
|
{ name: "bundled skill scripts", script: ["check:bundled-skill-scripts"] },
|
|
26
|
+
{ name: "runtime dependency graph", script: ["runtime-graph:verify"] },
|
|
26
27
|
{ name: "biome", script: ["lint"] },
|
|
27
28
|
{ name: "typescript", script: ["typecheck"] },
|
|
28
29
|
];
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import {
|
|
4
|
+
canonicalRuntimeGraph,
|
|
5
|
+
runtimeGraphDigest,
|
|
6
|
+
verifyRuntimeGraph,
|
|
7
|
+
} from "./runtime-graph";
|
|
8
|
+
|
|
9
|
+
const integrity = `sha512-${Buffer.alloc(64, 7).toString("base64")}`;
|
|
10
|
+
|
|
11
|
+
function dependencyLock() {
|
|
12
|
+
return {
|
|
13
|
+
name: "@letta-ai/letta-code",
|
|
14
|
+
version: "1.2.3",
|
|
15
|
+
lockfileVersion: 3,
|
|
16
|
+
packages: {
|
|
17
|
+
"": {
|
|
18
|
+
name: "@letta-ai/letta-code",
|
|
19
|
+
version: "1.2.3",
|
|
20
|
+
dependencies: { dependency: "^4.0.0" },
|
|
21
|
+
optionalDependencies: {},
|
|
22
|
+
},
|
|
23
|
+
"node_modules/dependency": {
|
|
24
|
+
version: "4.5.6",
|
|
25
|
+
resolved:
|
|
26
|
+
"https://registry.npmjs.org/dependency/-/dependency-4.5.6.tgz",
|
|
27
|
+
integrity,
|
|
28
|
+
libc: ["musl", "glibc"],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe("runtime dependency graph protocol", () => {
|
|
35
|
+
test("matches the daemon's canonical graph digest", () => {
|
|
36
|
+
const lock = dependencyLock();
|
|
37
|
+
const canonical = [
|
|
38
|
+
{
|
|
39
|
+
location: "node_modules/dependency",
|
|
40
|
+
version: "4.5.6",
|
|
41
|
+
resolved:
|
|
42
|
+
"https://registry.npmjs.org/dependency/-/dependency-4.5.6.tgz",
|
|
43
|
+
integrity,
|
|
44
|
+
optional: false,
|
|
45
|
+
os: [],
|
|
46
|
+
cpu: [],
|
|
47
|
+
libc: ["glibc", "musl"],
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
const expected = `sha512-${createHash("sha512")
|
|
51
|
+
.update(JSON.stringify(canonical))
|
|
52
|
+
.digest("base64")}`;
|
|
53
|
+
|
|
54
|
+
expect(canonicalRuntimeGraph(lock)).toEqual(canonical);
|
|
55
|
+
expect(runtimeGraphDigest(lock)).toBe(expected);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("includes libc compatibility in the canonical digest", () => {
|
|
59
|
+
const multiLibcLock = dependencyLock();
|
|
60
|
+
const muslOnlyLock = dependencyLock();
|
|
61
|
+
muslOnlyLock.packages["node_modules/dependency"].libc = ["musl"];
|
|
62
|
+
|
|
63
|
+
expect(runtimeGraphDigest(multiLibcLock)).not.toBe(
|
|
64
|
+
runtimeGraphDigest(muslOnlyLock),
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("verifies the release manifest declaration and shrinkwrap root", () => {
|
|
69
|
+
const lock = dependencyLock();
|
|
70
|
+
const digest = runtimeGraphDigest(lock);
|
|
71
|
+
|
|
72
|
+
expect(
|
|
73
|
+
verifyRuntimeGraph(
|
|
74
|
+
{
|
|
75
|
+
name: "@letta-ai/letta-code",
|
|
76
|
+
version: "1.2.3",
|
|
77
|
+
dependencies: { dependency: "^4.0.0" },
|
|
78
|
+
optionalDependencies: {},
|
|
79
|
+
lettaRuntimeGraphProtocol: 1,
|
|
80
|
+
lettaRuntimeGraphSha512: digest,
|
|
81
|
+
},
|
|
82
|
+
lock,
|
|
83
|
+
),
|
|
84
|
+
).toBe(digest);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("rejects mutable or non-canonical dependency artifacts", () => {
|
|
88
|
+
const lock = dependencyLock();
|
|
89
|
+
lock.packages["node_modules/dependency"].resolved =
|
|
90
|
+
"https://registry.example.test/dependency.tgz";
|
|
91
|
+
|
|
92
|
+
expect(() => runtimeGraphDigest(lock)).toThrow(/not canonical/);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("rejects a stale release declaration", () => {
|
|
96
|
+
const lock = dependencyLock();
|
|
97
|
+
|
|
98
|
+
expect(() =>
|
|
99
|
+
verifyRuntimeGraph(
|
|
100
|
+
{
|
|
101
|
+
name: "@letta-ai/letta-code",
|
|
102
|
+
version: "1.2.3",
|
|
103
|
+
dependencies: { dependency: "^4.0.0" },
|
|
104
|
+
optionalDependencies: {},
|
|
105
|
+
lettaRuntimeGraphProtocol: 1,
|
|
106
|
+
lettaRuntimeGraphSha512: `sha512-${Buffer.alloc(64).toString("base64")}`,
|
|
107
|
+
},
|
|
108
|
+
lock,
|
|
109
|
+
),
|
|
110
|
+
).toThrow(/declaration is stale/);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
export const RUNTIME_GRAPH_PROTOCOL = 1;
|
|
8
|
+
export const RUNTIME_GRAPH_NPM_VERSION = "12.2.0";
|
|
9
|
+
export const RUNTIME_GRAPH_BUN_VERSION = "1.3.14";
|
|
10
|
+
const PACKAGE_PATH = new URL("../package.json", import.meta.url);
|
|
11
|
+
const SHRINKWRAP_PATH = new URL("../npm-shrinkwrap.json", import.meta.url);
|
|
12
|
+
const REGISTRY_HOST = "registry.npmjs.org";
|
|
13
|
+
const SHA512_PATTERN = /^sha512-([A-Za-z0-9+/]+={0,2})$/;
|
|
14
|
+
|
|
15
|
+
type PackageManifest = {
|
|
16
|
+
name: string;
|
|
17
|
+
version: string;
|
|
18
|
+
dependencies?: Record<string, string>;
|
|
19
|
+
optionalDependencies?: Record<string, string>;
|
|
20
|
+
engines?: Record<string, string>;
|
|
21
|
+
lettaRuntimeGraphProtocol?: number;
|
|
22
|
+
lettaRuntimeGraphSha512?: string;
|
|
23
|
+
[key: string]: unknown;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type LockEntry = {
|
|
27
|
+
version?: string;
|
|
28
|
+
resolved?: string;
|
|
29
|
+
integrity?: string;
|
|
30
|
+
optional?: boolean;
|
|
31
|
+
os?: string[];
|
|
32
|
+
cpu?: string[];
|
|
33
|
+
libc?: string[];
|
|
34
|
+
link?: boolean;
|
|
35
|
+
dependencies?: Record<string, string>;
|
|
36
|
+
optionalDependencies?: Record<string, string>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
type DependencyLock = {
|
|
40
|
+
name?: string;
|
|
41
|
+
version?: string;
|
|
42
|
+
lockfileVersion?: number;
|
|
43
|
+
packages?: Record<string, LockEntry>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type CanonicalGraphEntry = {
|
|
47
|
+
location: string;
|
|
48
|
+
version: string;
|
|
49
|
+
resolved: string;
|
|
50
|
+
integrity: string;
|
|
51
|
+
optional: boolean;
|
|
52
|
+
os: string[];
|
|
53
|
+
cpu: string[];
|
|
54
|
+
libc: string[];
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
function hasSha512Integrity(value: unknown): value is string {
|
|
58
|
+
if (typeof value !== "string") return false;
|
|
59
|
+
const match = SHA512_PATTERN.exec(value);
|
|
60
|
+
return Boolean(match && Buffer.from(match[1], "base64").byteLength === 64);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function canonicalRuntimeGraph(
|
|
64
|
+
lock: DependencyLock,
|
|
65
|
+
): CanonicalGraphEntry[] {
|
|
66
|
+
if (lock.lockfileVersion !== 3 || !lock.packages) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"npm-shrinkwrap.json must contain a lockfileVersion 3 package graph",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const graph: CanonicalGraphEntry[] = [];
|
|
73
|
+
for (const [location, entry] of Object.entries(lock.packages)) {
|
|
74
|
+
if (!location) continue;
|
|
75
|
+
if (entry.link || typeof entry.version !== "string") {
|
|
76
|
+
throw new Error(`Dependency graph entry ${location} is not immutable`);
|
|
77
|
+
}
|
|
78
|
+
if (typeof entry.resolved !== "string") {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Dependency graph entry ${location} has no resolved artifact`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
const resolved = new URL(entry.resolved);
|
|
84
|
+
if (
|
|
85
|
+
resolved.protocol !== "https:" ||
|
|
86
|
+
resolved.hostname !== REGISTRY_HOST ||
|
|
87
|
+
resolved.username ||
|
|
88
|
+
resolved.password ||
|
|
89
|
+
resolved.port ||
|
|
90
|
+
resolved.search ||
|
|
91
|
+
resolved.hash
|
|
92
|
+
) {
|
|
93
|
+
throw new Error(`Dependency graph entry ${location} is not canonical`);
|
|
94
|
+
}
|
|
95
|
+
if (!hasSha512Integrity(entry.integrity)) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Dependency graph entry ${location} lacks SHA-512 integrity`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
graph.push({
|
|
101
|
+
location,
|
|
102
|
+
version: entry.version,
|
|
103
|
+
resolved: entry.resolved,
|
|
104
|
+
integrity: entry.integrity,
|
|
105
|
+
optional: entry.optional === true,
|
|
106
|
+
os: Array.isArray(entry.os) ? entry.os : [],
|
|
107
|
+
cpu: Array.isArray(entry.cpu) ? entry.cpu : [],
|
|
108
|
+
libc: Array.isArray(entry.libc) ? [...entry.libc].sort() : [],
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
graph.sort((left, right) => left.location.localeCompare(right.location));
|
|
113
|
+
return graph;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function runtimeGraphDigest(lock: DependencyLock): string {
|
|
117
|
+
const graph = canonicalRuntimeGraph(lock);
|
|
118
|
+
return `sha512-${createHash("sha512").update(JSON.stringify(graph)).digest("base64")}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function productionManifest(manifest: PackageManifest): PackageManifest {
|
|
122
|
+
return {
|
|
123
|
+
name: manifest.name,
|
|
124
|
+
version: manifest.version,
|
|
125
|
+
dependencies: manifest.dependencies ?? {},
|
|
126
|
+
optionalDependencies: manifest.optionalDependencies ?? {},
|
|
127
|
+
engines: manifest.engines ?? {},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function sameRecord(
|
|
132
|
+
left: Record<string, string> | undefined,
|
|
133
|
+
right: Record<string, string> | undefined,
|
|
134
|
+
): boolean {
|
|
135
|
+
const entries = (value: Record<string, string> | undefined) =>
|
|
136
|
+
Object.entries(value ?? {}).sort(([leftKey], [rightKey]) =>
|
|
137
|
+
leftKey.localeCompare(rightKey),
|
|
138
|
+
);
|
|
139
|
+
return JSON.stringify(entries(left)) === JSON.stringify(entries(right));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function verifyRuntimeGraph(
|
|
143
|
+
manifest: PackageManifest,
|
|
144
|
+
lock: DependencyLock,
|
|
145
|
+
): string {
|
|
146
|
+
const root = lock.packages?.[""];
|
|
147
|
+
if (
|
|
148
|
+
lock.name !== manifest.name ||
|
|
149
|
+
lock.version !== manifest.version ||
|
|
150
|
+
root?.name !== manifest.name ||
|
|
151
|
+
root?.version !== manifest.version ||
|
|
152
|
+
!sameRecord(root.dependencies, manifest.dependencies) ||
|
|
153
|
+
!sameRecord(root.optionalDependencies, manifest.optionalDependencies)
|
|
154
|
+
) {
|
|
155
|
+
throw new Error("npm-shrinkwrap.json root does not match package.json");
|
|
156
|
+
}
|
|
157
|
+
const digest = runtimeGraphDigest(lock);
|
|
158
|
+
if (
|
|
159
|
+
manifest.lettaRuntimeGraphProtocol !== RUNTIME_GRAPH_PROTOCOL ||
|
|
160
|
+
manifest.lettaRuntimeGraphSha512 !== digest
|
|
161
|
+
) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`package.json runtime graph declaration is stale (expected ${digest})`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return digest;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function readJson<T>(url: URL): Promise<T> {
|
|
170
|
+
return JSON.parse(await readFile(url, "utf8")) as T;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function runNpm(args: string[], cwd: string): Promise<string> {
|
|
174
|
+
return new Promise<string>((resolve, reject) => {
|
|
175
|
+
const child = spawn(
|
|
176
|
+
process.platform === "win32" ? "npm.cmd" : "npm",
|
|
177
|
+
args,
|
|
178
|
+
{
|
|
179
|
+
cwd,
|
|
180
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
181
|
+
env: {
|
|
182
|
+
...process.env,
|
|
183
|
+
npm_config_registry: "https://registry.npmjs.org",
|
|
184
|
+
npm_config_ignore_scripts: "true",
|
|
185
|
+
npm_config_audit: "false",
|
|
186
|
+
npm_config_fund: "false",
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
);
|
|
190
|
+
let stdout = "";
|
|
191
|
+
child.stdout.on("data", (chunk) => {
|
|
192
|
+
stdout += String(chunk);
|
|
193
|
+
});
|
|
194
|
+
child.once("error", reject);
|
|
195
|
+
child.once("exit", (code, signal) => {
|
|
196
|
+
if (code === 0) resolve(stdout.trim());
|
|
197
|
+
else
|
|
198
|
+
reject(
|
|
199
|
+
new Error(`npm exited with ${signal ?? code ?? "unknown status"}`),
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function runBun(args: string[], cwd: string): Promise<string> {
|
|
206
|
+
return new Promise<string>((resolve, reject) => {
|
|
207
|
+
const child = spawn(
|
|
208
|
+
process.platform === "win32" ? "bun.exe" : "bun",
|
|
209
|
+
args,
|
|
210
|
+
{
|
|
211
|
+
cwd,
|
|
212
|
+
stdio: ["ignore", "pipe", "inherit"],
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
let stdout = "";
|
|
216
|
+
child.stdout.on("data", (chunk) => {
|
|
217
|
+
stdout += String(chunk);
|
|
218
|
+
});
|
|
219
|
+
child.once("error", reject);
|
|
220
|
+
child.once("exit", (code, signal) => {
|
|
221
|
+
if (code === 0) resolve(stdout.trim());
|
|
222
|
+
else
|
|
223
|
+
reject(
|
|
224
|
+
new Error(`bun exited with ${signal ?? code ?? "unknown status"}`),
|
|
225
|
+
);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function assertExactToolchain(cwd: string): Promise<void> {
|
|
231
|
+
const [npmVersion, bunVersion] = await Promise.all([
|
|
232
|
+
runNpm(["--version"], cwd),
|
|
233
|
+
runBun(["--version"], cwd),
|
|
234
|
+
]);
|
|
235
|
+
if (npmVersion !== RUNTIME_GRAPH_NPM_VERSION) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`runtime graph generation requires npm ${RUNTIME_GRAPH_NPM_VERSION}, found ${npmVersion}`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
if (bunVersion !== RUNTIME_GRAPH_BUN_VERSION) {
|
|
241
|
+
throw new Error(
|
|
242
|
+
`runtime graph generation requires Bun ${RUNTIME_GRAPH_BUN_VERSION}, found ${bunVersion}`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async function generateRuntimeGraph(): Promise<string> {
|
|
248
|
+
const manifest = await readJson<PackageManifest>(PACKAGE_PATH);
|
|
249
|
+
const staging = await mkdtemp(join(tmpdir(), "letta-runtime-graph-"));
|
|
250
|
+
const runtimeRoot = join(staging, "runtime");
|
|
251
|
+
const installerRoot = join(staging, "installer");
|
|
252
|
+
try {
|
|
253
|
+
await assertExactToolchain(staging);
|
|
254
|
+
await Promise.all([
|
|
255
|
+
mkdir(runtimeRoot, { recursive: true }),
|
|
256
|
+
mkdir(installerRoot, { recursive: true }),
|
|
257
|
+
]);
|
|
258
|
+
await writeFile(
|
|
259
|
+
join(runtimeRoot, "package.json"),
|
|
260
|
+
`${JSON.stringify(productionManifest(manifest), null, 2)}\n`,
|
|
261
|
+
);
|
|
262
|
+
try {
|
|
263
|
+
const existing = await readFile(SHRINKWRAP_PATH);
|
|
264
|
+
await writeFile(join(runtimeRoot, "npm-shrinkwrap.json"), existing);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const packOutput = await runBun(
|
|
270
|
+
[
|
|
271
|
+
"pm",
|
|
272
|
+
"pack",
|
|
273
|
+
"--quiet",
|
|
274
|
+
"--ignore-scripts",
|
|
275
|
+
"--destination",
|
|
276
|
+
installerRoot,
|
|
277
|
+
],
|
|
278
|
+
runtimeRoot,
|
|
279
|
+
);
|
|
280
|
+
const packedPath = packOutput
|
|
281
|
+
.split(/\r?\n/)
|
|
282
|
+
.map((line) => line.trim())
|
|
283
|
+
.find((line) => line.endsWith(".tgz"));
|
|
284
|
+
const tarball = packedPath?.split(/[\\/]/).pop();
|
|
285
|
+
if (!tarball)
|
|
286
|
+
throw new Error("bun pm pack did not produce a runtime tarball");
|
|
287
|
+
await writeFile(
|
|
288
|
+
join(installerRoot, "package.json"),
|
|
289
|
+
`${JSON.stringify(
|
|
290
|
+
{
|
|
291
|
+
private: true,
|
|
292
|
+
dependencies: {
|
|
293
|
+
[manifest.name]: `file:${tarball}`,
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
null,
|
|
297
|
+
2,
|
|
298
|
+
)}\n`,
|
|
299
|
+
);
|
|
300
|
+
await runNpm(
|
|
301
|
+
[
|
|
302
|
+
"install",
|
|
303
|
+
"--package-lock-only",
|
|
304
|
+
"--ignore-scripts",
|
|
305
|
+
"--no-audit",
|
|
306
|
+
"--no-fund",
|
|
307
|
+
],
|
|
308
|
+
installerRoot,
|
|
309
|
+
);
|
|
310
|
+
const outerLock = JSON.parse(
|
|
311
|
+
await readFile(join(installerRoot, "package-lock.json"), "utf8"),
|
|
312
|
+
) as DependencyLock;
|
|
313
|
+
const runtimeLocation = `node_modules/${manifest.name}`;
|
|
314
|
+
const packages = { ...outerLock.packages };
|
|
315
|
+
delete packages[""];
|
|
316
|
+
delete packages[runtimeLocation];
|
|
317
|
+
packages[""] = {
|
|
318
|
+
name: manifest.name,
|
|
319
|
+
version: manifest.version,
|
|
320
|
+
dependencies: manifest.dependencies ?? {},
|
|
321
|
+
optionalDependencies: manifest.optionalDependencies ?? {},
|
|
322
|
+
};
|
|
323
|
+
const lock: DependencyLock = {
|
|
324
|
+
name: manifest.name,
|
|
325
|
+
version: manifest.version,
|
|
326
|
+
lockfileVersion: 3,
|
|
327
|
+
packages,
|
|
328
|
+
};
|
|
329
|
+
const digest = runtimeGraphDigest(lock);
|
|
330
|
+
manifest.lettaRuntimeGraphProtocol = RUNTIME_GRAPH_PROTOCOL;
|
|
331
|
+
manifest.lettaRuntimeGraphSha512 = digest;
|
|
332
|
+
await writeFile(SHRINKWRAP_PATH, `${JSON.stringify(lock, null, 2)}\n`);
|
|
333
|
+
await writeFile(PACKAGE_PATH, `${JSON.stringify(manifest, null, 2)}\n`);
|
|
334
|
+
return digest;
|
|
335
|
+
} finally {
|
|
336
|
+
await rm(staging, { recursive: true, force: true });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function verifyFiles(): Promise<string> {
|
|
341
|
+
const [manifest, lock] = await Promise.all([
|
|
342
|
+
readJson<PackageManifest>(PACKAGE_PATH),
|
|
343
|
+
readJson<DependencyLock>(SHRINKWRAP_PATH),
|
|
344
|
+
]);
|
|
345
|
+
return verifyRuntimeGraph(manifest, lock);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function main(): Promise<void> {
|
|
349
|
+
const command = process.argv[2] ?? "verify";
|
|
350
|
+
const digest =
|
|
351
|
+
command === "generate"
|
|
352
|
+
? await generateRuntimeGraph()
|
|
353
|
+
: command === "verify"
|
|
354
|
+
? await verifyFiles()
|
|
355
|
+
: (() => {
|
|
356
|
+
throw new Error(`Unknown runtime graph command: ${command}`);
|
|
357
|
+
})();
|
|
358
|
+
console.log(`Runtime dependency graph verified: ${digest}`);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (import.meta.main) {
|
|
362
|
+
main().catch((error) => {
|
|
363
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
364
|
+
process.exitCode = 1;
|
|
365
|
+
});
|
|
366
|
+
}
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"src/websocket/listen-client-protocol.test.ts": 5707,
|
|
41
41
|
"src/websocket/listener/commands/memory.ts": 1114,
|
|
42
42
|
"src/websocket/listener/file-commands.ts": 1053,
|
|
43
|
-
"src/websocket/listener/protocol-inbound.ts":
|
|
43
|
+
"src/websocket/listener/protocol-inbound.ts": 2061
|
|
44
44
|
}
|