@stage-labs/metro 0.1.0-beta.270 → 0.1.0-beta.271
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/claude/api.ts +6 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +22 -5
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +5 -2
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +10 -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.271",
|
|
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",
|
|
@@ -26,6 +26,7 @@ import { setupAnswer, type SetupApiDeps } from './setup-api.js';
|
|
|
26
26
|
import {
|
|
27
27
|
autostartEnabled,
|
|
28
28
|
ensureSession,
|
|
29
|
+
sessionSnapshot,
|
|
29
30
|
sessionStatus,
|
|
30
31
|
setAutostart,
|
|
31
32
|
startSession,
|
|
@@ -270,6 +271,11 @@ export function handleClaudeRequest(
|
|
|
270
271
|
apiSession(req)
|
|
271
272
|
.then(async (session) => {
|
|
272
273
|
if (!session) throw new ApiError('unauthorized', 401);
|
|
274
|
+
if (path === `${PREFIX}/session/snapshot`) {
|
|
275
|
+
if (req.method !== 'GET') throw new ApiError('method not allowed', 405);
|
|
276
|
+
sendJson(req, res, 200, sessionSnapshot(deps.session));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
273
279
|
if (req.method !== 'GET' && ADMIN_ONLY.test(path)) requireAdmin(session);
|
|
274
280
|
if (await streamed(req, res, path, search, (deps.dir ?? claudeDir)())) return;
|
|
275
281
|
sendJson(req, res, 200, await routed(req, path, search, deps, session));
|
|
@@ -61,6 +61,13 @@ export interface SessionStatus {
|
|
|
61
61
|
lastError: string | null;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
export interface SessionSnapshot {
|
|
65
|
+
running: boolean;
|
|
66
|
+
runner: HarnessRunner | null;
|
|
67
|
+
activity: RunnerActivity | null;
|
|
68
|
+
lastStartedAt: string | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
64
71
|
interface Memory {
|
|
65
72
|
lastStartedAt: number | null;
|
|
66
73
|
lastError: string | null;
|
|
@@ -267,17 +274,27 @@ export function stopSession(deps: SessionDeps = {}): SessionStatus {
|
|
|
267
274
|
export const sessionLive = (deps: SessionDeps = {}): boolean =>
|
|
268
275
|
sessionRunning(deps.tmux ?? 'tmux') || sdkAlive(readAgentActivity(sessionHome(deps)));
|
|
269
276
|
|
|
270
|
-
export function
|
|
277
|
+
export function sessionSnapshot(deps: SessionDeps = {}): SessionSnapshot {
|
|
271
278
|
const activity = readAgentActivity(sessionHome(deps));
|
|
272
279
|
const sdk = sdkAlive(activity);
|
|
280
|
+
const tmux = sessionRunning(deps.tmux ?? 'tmux');
|
|
281
|
+
const runner = sdk ? 'sdk' : tmux ? readState(deps.agents ?? agentsDir()).runner : null;
|
|
273
282
|
return {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
runner: sdk ? 'sdk' : sessionRunner(deps),
|
|
283
|
+
running: tmux || sdk,
|
|
284
|
+
runner: isHarnessRunner(runner) ? runner : null,
|
|
277
285
|
activity,
|
|
286
|
+
lastStartedAt: memory.lastStartedAt === null ? null : new Date(memory.lastStartedAt).toISOString(),
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export function sessionStatus(deps: SessionDeps = {}): SessionStatus {
|
|
291
|
+
const snapshot = sessionSnapshot(deps);
|
|
292
|
+
return {
|
|
293
|
+
name: SESSION_NAME,
|
|
294
|
+
...snapshot,
|
|
295
|
+
runner: snapshot.runner ?? sessionRunner(deps),
|
|
278
296
|
autostart: autostartEnabled(deps.agents ?? agentsDir()),
|
|
279
297
|
blocked: sessionBlocked(deps),
|
|
280
|
-
lastStartedAt: memory.lastStartedAt === null ? null : new Date(memory.lastStartedAt).toISOString(),
|
|
281
298
|
lastError: memory.lastError,
|
|
282
299
|
};
|
|
283
300
|
}
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
connectionOf,
|
|
15
15
|
parseModelConfig,
|
|
16
16
|
readModelConfig,
|
|
17
|
+
readModelSnapshot,
|
|
17
18
|
removeConnection,
|
|
18
19
|
setFallbacks,
|
|
19
20
|
setRoute,
|
|
@@ -28,6 +29,7 @@ import { refreshUsage } from './usage-refresh.js';
|
|
|
28
29
|
import { handleOpenRouterSignIn } from './openrouter-signin.js';
|
|
29
30
|
|
|
30
31
|
const PATH = '/api/model';
|
|
32
|
+
const SNAPSHOT = '/api/model/snapshot';
|
|
31
33
|
const CONNECTIONS = '/api/model/connections';
|
|
32
34
|
const FALLBACKS = '/api/model/fallbacks';
|
|
33
35
|
const CODEX = '/api/model/codex/';
|
|
@@ -201,12 +203,13 @@ const TABLES: [string, Record<string, Route>][] = [
|
|
|
201
203
|
[BEDROCK, BEDROCK_ROUTES],
|
|
202
204
|
[GEMINI, GEMINI_ROUTES],
|
|
203
205
|
];
|
|
204
|
-
const EXACT = new Set([PATH, FALLBACKS, BUNDLE, RESTORE, CONNECTIONS]);
|
|
206
|
+
const EXACT = new Set([PATH, SNAPSHOT, FALLBACKS, BUNDLE, RESTORE, CONNECTIONS]);
|
|
205
207
|
const PREFIXES = [`${CONNECTIONS}/`, CODEX, ...TABLES.map(([prefix]) => prefix)];
|
|
206
208
|
|
|
207
209
|
const mine = (path: string): boolean => EXACT.has(path) || PREFIXES.some((prefix) => path.startsWith(prefix));
|
|
208
210
|
|
|
209
211
|
function routeFor(path: string, method: string | undefined): Route | number {
|
|
212
|
+
if (path === SNAPSHOT) return method === 'GET' ? { method: 'GET', run: (_req, _deps, store) => Promise.resolve().then(() => settingsBody(store.read())).catch(asApiError) } : 405;
|
|
210
213
|
if (path === PATH || path === FALLBACKS) return settingsRoute(path, method);
|
|
211
214
|
if (path === BUNDLE || path === RESTORE) return bundleRoute(path, method);
|
|
212
215
|
if (path === CONNECTIONS || path.startsWith(`${CONNECTIONS}/`)) return connectionsRoute(path, method);
|
|
@@ -232,7 +235,7 @@ export function handleModelRequest(req: IncomingMessage, res: ServerResponse, de
|
|
|
232
235
|
return true;
|
|
233
236
|
}
|
|
234
237
|
const store: Store = {
|
|
235
|
-
read: deps.read ?? readModelConfig,
|
|
238
|
+
read: deps.read ?? (path === SNAPSHOT ? readModelSnapshot : readModelConfig),
|
|
236
239
|
write: (cfg) => {
|
|
237
240
|
checkRunnerRoute(store.read(), cfg, deps);
|
|
238
241
|
(deps.write ?? writeModelConfig)(cfg);
|
|
@@ -158,13 +158,22 @@ export function parseModelConfig(raw: unknown): ModelConfig {
|
|
|
158
158
|
const needsIds = (raw: unknown): boolean =>
|
|
159
159
|
isRecord(raw) && Array.isArray(raw.connections) && raw.connections.some((c: unknown) => isRecord(c) && text(c.id) === '');
|
|
160
160
|
|
|
161
|
+
const readStoredModel = (dir: string): unknown =>
|
|
162
|
+
readJson<unknown>(join(dir, MODEL_FILE), null, { warn: 'model-config: model.json is unreadable, so every request goes to Anthropic until it is fixed' });
|
|
163
|
+
|
|
161
164
|
export function readModelConfig(dir = agentsDir()): ModelConfig {
|
|
162
|
-
const raw =
|
|
165
|
+
const raw = readStoredModel(dir);
|
|
163
166
|
const cfg = parseModelConfig(raw);
|
|
164
167
|
if (needsIds(raw)) writeModelConfig(cfg, dir);
|
|
165
168
|
return cfg;
|
|
166
169
|
}
|
|
167
170
|
|
|
171
|
+
export function readModelSnapshot(dir = agentsDir()): ModelConfig {
|
|
172
|
+
const raw = readStoredModel(dir);
|
|
173
|
+
if (needsIds(raw)) throw new ModelConfigError('model snapshot is unavailable until every connection has a saved id');
|
|
174
|
+
return parseModelConfig(raw);
|
|
175
|
+
}
|
|
176
|
+
|
|
168
177
|
export function writeModelConfig(cfg: ModelConfig, dir = agentsDir()): void {
|
|
169
178
|
writeSecure(join(dir, MODEL_FILE), JSON.stringify(cfg, null, 2));
|
|
170
179
|
}
|
package/runtime/runtime.json
CHANGED