@stage-labs/metro 0.1.0-beta.278 → 0.1.0-beta.279
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/package.json +1 -1
- package/runtime/marketplace/plugin/.claude-plugin/plugin.json +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +1 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +43 -6
- package/runtime/node_modules/@metro-labs/whatsapp/src/client-history.ts +1 -1
- package/runtime/runtime.json +1 -1
- package/runtime/sdk-runner/runner.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metro",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.279",
|
|
4
4
|
"description": "Metro for Claude Code, installed by the metro daemon on this machine: every connector the agent holds as its own MCP server relayed through the daemon, the orchestrator-only guard on the main thread, and the standing rules loaded at each session start. No vendor credential ever sits in a config file.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Bonustrack",
|
|
@@ -141,6 +141,7 @@ installBearerSessions(agentsDir(), localOwner);
|
|
|
141
141
|
});
|
|
142
142
|
log.info({ agent: await ensureLocalAgent() }, 'local daemon: agent');
|
|
143
143
|
await materializeFrom(fileSource);
|
|
144
|
+
ensureStationDeps();
|
|
144
145
|
forgetOrphans(knownAccounts());
|
|
145
146
|
syncGmailHost();
|
|
146
147
|
supervisor.start();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
4
5
|
import { log } from '@metro-labs/core/log';
|
|
5
6
|
import { isRecord } from '@metro-labs/core/is-record';
|
|
6
7
|
import type { StationName } from '@metro-labs/core/station-names';
|
|
@@ -16,6 +17,7 @@ export interface RuntimeManifest {
|
|
|
16
17
|
|
|
17
18
|
export interface RuntimeStore {
|
|
18
19
|
dir: string;
|
|
20
|
+
sources: string;
|
|
19
21
|
manifest: RuntimeManifest;
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -24,6 +26,15 @@ const ranges = (raw: unknown): Record<string, string> =>
|
|
|
24
26
|
? Object.fromEntries(Object.entries(raw).filter((e): e is [string, string] => typeof e[1] === 'string'))
|
|
25
27
|
: {};
|
|
26
28
|
|
|
29
|
+
function patchesOf(raw: unknown): Record<string, string> {
|
|
30
|
+
if (!isRecord(raw)) throw new Error('Invalid runtime patch map');
|
|
31
|
+
return Object.fromEntries(Object.entries(raw).map(([name, path]) => {
|
|
32
|
+
if (typeof path !== 'string' || !/^patches\/[a-f0-9]{64}\.patch$/.test(path))
|
|
33
|
+
throw new Error(`Invalid runtime patch path for ${name}`);
|
|
34
|
+
return [name, path];
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
|
|
27
38
|
export function readManifest(path: string): RuntimeManifest {
|
|
28
39
|
const raw: unknown = JSON.parse(readFileSync(path, 'utf8'));
|
|
29
40
|
if (!isRecord(raw)) throw new Error(`${path} is not a runtime manifest`);
|
|
@@ -31,7 +42,7 @@ export function readManifest(path: string): RuntimeManifest {
|
|
|
31
42
|
return {
|
|
32
43
|
core: ranges(raw.core),
|
|
33
44
|
stations: Object.fromEntries(Object.entries(stations).map(([name, deps]) => [name, ranges(deps)])),
|
|
34
|
-
...(raw.patchedDependencies === undefined ? {} : { patchedDependencies:
|
|
45
|
+
...(raw.patchedDependencies === undefined ? {} : { patchedDependencies: patchesOf(raw.patchedDependencies) }),
|
|
35
46
|
};
|
|
36
47
|
}
|
|
37
48
|
|
|
@@ -39,7 +50,7 @@ export function runtimeStore(): RuntimeStore | null {
|
|
|
39
50
|
const dir = process.env.METRO_RUNTIME_STORE?.trim() ?? '';
|
|
40
51
|
const manifest = process.env.METRO_RUNTIME_MANIFEST?.trim() ?? '';
|
|
41
52
|
if (dir === '' || manifest === '') return null;
|
|
42
|
-
return { dir, manifest: readManifest(manifest) };
|
|
53
|
+
return { dir, sources: dirname(manifest), manifest: readManifest(manifest) };
|
|
43
54
|
}
|
|
44
55
|
|
|
45
56
|
export function dependenciesFor(manifest: RuntimeManifest, stations: Iterable<string>): Record<string, string> {
|
|
@@ -72,9 +83,33 @@ function current(dir: string): string | null {
|
|
|
72
83
|
}
|
|
73
84
|
}
|
|
74
85
|
|
|
86
|
+
function copyPatches(store: RuntimeStore): void {
|
|
87
|
+
const paths = Object.values(patchesOf(store.manifest.patchedDependencies ?? {}));
|
|
88
|
+
if (paths.length === 0) return;
|
|
89
|
+
const destination = join(store.dir, 'patches');
|
|
90
|
+
mkdirSync(destination, { recursive: true });
|
|
91
|
+
if (!lstatSync(join(store.sources, 'patches')).isDirectory() || !lstatSync(destination).isDirectory())
|
|
92
|
+
throw new Error('Invalid runtime patch directory');
|
|
93
|
+
for (const path of new Set(paths)) {
|
|
94
|
+
const source = join(store.sources, path);
|
|
95
|
+
const target = join(store.dir, path);
|
|
96
|
+
if (!lstatSync(source).isFile() || lstatSync(target, { throwIfNoEntry: false })?.isFile() === false)
|
|
97
|
+
throw new Error(`Invalid runtime patch file: ${path}`);
|
|
98
|
+
const hash = createHash('sha256').update(readFileSync(source)).digest('hex');
|
|
99
|
+
if (path !== `patches/${hash}.patch`) throw new Error(`Invalid runtime patch contents: ${path}`);
|
|
100
|
+
copyFileSync(source, target);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
75
104
|
export function installRuntime(store: RuntimeStore, stations: Iterable<string>): boolean {
|
|
76
105
|
const wanted = packageText(dependenciesFor(store.manifest, stations), store.manifest.patchedDependencies ?? {});
|
|
77
106
|
const marker = join(store.dir, 'node_modules', '.metro-installed');
|
|
107
|
+
try {
|
|
108
|
+
copyPatches(store);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
rmSync(marker, { force: true });
|
|
111
|
+
throw err;
|
|
112
|
+
}
|
|
78
113
|
if (existsSync(marker) && current(store.dir) === wanted) return false;
|
|
79
114
|
rmSync(marker, { force: true });
|
|
80
115
|
mkdirSync(store.dir, { recursive: true });
|
|
@@ -91,8 +126,10 @@ export function installRuntime(store: RuntimeStore, stations: Iterable<string>):
|
|
|
91
126
|
return true;
|
|
92
127
|
}
|
|
93
128
|
|
|
94
|
-
export function ensureStationDeps(station
|
|
129
|
+
export function ensureStationDeps(station?: StationName): void {
|
|
95
130
|
const store = runtimeStore();
|
|
96
131
|
if (store === null) return;
|
|
97
|
-
|
|
132
|
+
const stations = knownAccounts().map((a) => a.station);
|
|
133
|
+
if (station !== undefined) stations.push(station);
|
|
134
|
+
installRuntime(store, stations);
|
|
98
135
|
}
|
|
@@ -3,7 +3,7 @@ import { TrainError } from '@metro-labs/core/train-error';
|
|
|
3
3
|
import { supportsHistoryDeletion } from './app-state.js';
|
|
4
4
|
import { createHistory, type History } from './history.js';
|
|
5
5
|
|
|
6
|
-
const UNAVAILABLE = 'WhatsApp local history is unavailable because this runtime lacks the deletion patch.
|
|
6
|
+
const UNAVAILABLE = 'WhatsApp local history is unavailable because this runtime lacks the deletion patch. Ask the owner to update and restart Metro to load the patched runtime. Prior history was invalidated to avoid stale deleted messages.';
|
|
7
7
|
const ignore = (): void => undefined;
|
|
8
8
|
|
|
9
9
|
export function createClientHistory(accountId: string, supported = supportsHistoryDeletion()): History {
|
package/runtime/runtime.json
CHANGED