@spencerboucher/switchyard 0.7.0 → 0.8.0
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/README.md +1 -1
- package/dist/bridge/switchyard-terminal.js +96 -1
- package/dist/switchyard.mjs +463 -126
- package/package.json +1 -1
- package/web-dist/assets/{AutomationView-BIW_pO0M.js → AutomationView-GCthHkym.js} +2 -2
- package/web-dist/assets/{AutomationView-BIW_pO0M.js.map → AutomationView-GCthHkym.js.map} +1 -1
- package/web-dist/assets/{Badge-BiDp7NKI.js → Badge-CEMI3YyI.js} +2 -2
- package/web-dist/assets/{Badge-BiDp7NKI.js.map → Badge-CEMI3YyI.js.map} +1 -1
- package/web-dist/assets/{InteractionCard-CWxyDv81.js → InteractionCard-DaLqafmA.js} +2 -2
- package/web-dist/assets/{InteractionCard-CWxyDv81.js.map → InteractionCard-DaLqafmA.js.map} +1 -1
- package/web-dist/assets/{Menu-BDt1bxwz.js → Menu-BcKiEc6X.js} +2 -2
- package/web-dist/assets/{Menu-BDt1bxwz.js.map → Menu-BcKiEc6X.js.map} +1 -1
- package/web-dist/assets/{SegmentedControl-D-2BCZzd.js → SegmentedControl-BZPzNU08.js} +2 -2
- package/web-dist/assets/{SegmentedControl-D-2BCZzd.js.map → SegmentedControl-BZPzNU08.js.map} +1 -1
- package/web-dist/assets/{Tabs-ByiPklAw.js → Tabs-COH5EMql.js} +2 -2
- package/web-dist/assets/{Tabs-ByiPklAw.js.map → Tabs-COH5EMql.js.map} +1 -1
- package/web-dist/assets/{TextArea-BYr88SnV.js → TextArea-CeMEPZn4.js} +2 -2
- package/web-dist/assets/{TextArea-BYr88SnV.js.map → TextArea-CeMEPZn4.js.map} +1 -1
- package/web-dist/assets/{attention-Bya8XHKI.js → attention-BAdea2kA.js} +2 -2
- package/web-dist/assets/{attention-Bya8XHKI.js.map → attention-BAdea2kA.js.map} +1 -1
- package/web-dist/assets/{automations-DiXKRcnE.js → automations-DSFUzB0w.js} +2 -2
- package/web-dist/assets/{automations-DiXKRcnE.js.map → automations-DSFUzB0w.js.map} +1 -1
- package/web-dist/assets/{board-BrLaJwLJ.js → board-eQ3H8yCp.js} +2 -2
- package/web-dist/assets/{board-BrLaJwLJ.js.map → board-eQ3H8yCp.js.map} +1 -1
- package/web-dist/assets/code-D7JmYMKe.js +13 -0
- package/web-dist/assets/code-D7JmYMKe.js.map +1 -0
- package/web-dist/assets/{describe-DnvH6_fl.js → describe-C1YimBXJ.js} +2 -2
- package/web-dist/assets/{describe-DnvH6_fl.js.map → describe-C1YimBXJ.js.map} +1 -1
- package/web-dist/assets/dist-B_RpNOcK.js +56 -0
- package/web-dist/assets/dist-B_RpNOcK.js.map +1 -0
- package/web-dist/assets/{enroll-DUDpbJoB.js → enroll-1OCy5r50.js} +2 -2
- package/web-dist/assets/{enroll-DUDpbJoB.js.map → enroll-1OCy5r50.js.map} +1 -1
- package/web-dist/assets/{index-vIzH1m8u.js → index-BhpEXLav.js} +4 -4
- package/web-dist/assets/{index-vIzH1m8u.js.map → index-BhpEXLav.js.map} +1 -1
- package/web-dist/assets/index-DUeOwDZ6.css +2 -0
- package/web-dist/assets/{logic-xb-QF00E.js → logic-CBvFOyIo.js} +2 -2
- package/web-dist/assets/{logic-xb-QF00E.js.map → logic-CBvFOyIo.js.map} +1 -1
- package/web-dist/assets/{more-BP5x45cs.js → more-RjVyxPaz.js} +2 -2
- package/web-dist/assets/{more-BP5x45cs.js.map → more-RjVyxPaz.js.map} +1 -1
- package/web-dist/assets/{now--XjJC37b.js → now-CDjeFSLD.js} +2 -2
- package/web-dist/assets/{now--XjJC37b.js.map → now-CDjeFSLD.js.map} +1 -1
- package/web-dist/assets/{settings-D8T1esWq.js → settings-tH4UuIfJ.js} +2 -2
- package/web-dist/assets/{settings-D8T1esWq.js.map → settings-tH4UuIfJ.js.map} +1 -1
- package/web-dist/assets/{shared-CZ7fcu_B.js → shared-CRX6KSBm.js} +2 -2
- package/web-dist/assets/{shared-CZ7fcu_B.js.map → shared-CRX6KSBm.js.map} +1 -1
- package/web-dist/assets/states-DXozulGZ.js +2 -0
- package/web-dist/assets/states-DXozulGZ.js.map +1 -0
- package/web-dist/assets/{terminal-login-BJxV78au.js → terminal-login-CW2rE50W.js} +2 -2
- package/web-dist/assets/{terminal-login-BJxV78au.js.map → terminal-login-CW2rE50W.js.map} +1 -1
- package/web-dist/assets/{useNow-jxdoOC_1.js → useNow-C_B7JgMM.js} +2 -2
- package/web-dist/assets/{useNow-jxdoOC_1.js.map → useNow-C_B7JgMM.js.map} +1 -1
- package/web-dist/index.html +4 -4
- package/web-dist/sw.js +1 -1
- package/web-dist/assets/code-DKLO8bzz.js +0 -11
- package/web-dist/assets/code-DKLO8bzz.js.map +0 -1
- package/web-dist/assets/dist-BE_V9dwJ.js +0 -56
- package/web-dist/assets/dist-BE_V9dwJ.js.map +0 -1
- package/web-dist/assets/index-DvRcMGEe.css +0 -2
- package/web-dist/assets/states--geW2Ntl.js +0 -2
- package/web-dist/assets/states--geW2Ntl.js.map +0 -1
package/dist/switchyard.mjs
CHANGED
|
@@ -380,7 +380,8 @@ var SessionModel = z3.object({
|
|
|
380
380
|
var SlashCommand = z3.object({
|
|
381
381
|
name: z3.string().min(1),
|
|
382
382
|
description: z3.string().nullable(),
|
|
383
|
-
source: z3.enum(["extension", "prompt", "skill"])
|
|
383
|
+
source: z3.enum(["extension", "prompt", "skill"]),
|
|
384
|
+
argumentHint: z3.string().nullable().optional()
|
|
384
385
|
});
|
|
385
386
|
var ContextUsage = z3.object({
|
|
386
387
|
/** Estimated tokens in context; null right after compaction until the next reply. */
|
|
@@ -921,6 +922,51 @@ var SetThinkingRequest = z4.object({ level: ThinkingLevel });
|
|
|
921
922
|
var CompactRequest = z4.object({ instructions: z4.string().max(4e3).optional() });
|
|
922
923
|
var SessionModelsResult = z4.object({ models: z4.array(SessionModel) });
|
|
923
924
|
var SessionCommandsResult = z4.object({ commands: z4.array(SlashCommand) });
|
|
925
|
+
var CompleteRequest = z4.object({ text: z4.string().min(1).max(500) });
|
|
926
|
+
var CompletionItem = z4.object({
|
|
927
|
+
value: z4.string(),
|
|
928
|
+
label: z4.string(),
|
|
929
|
+
description: z4.string().nullable()
|
|
930
|
+
});
|
|
931
|
+
var SessionCompleteResult = z4.object({
|
|
932
|
+
items: z4.array(CompletionItem).max(50),
|
|
933
|
+
prefix: z4.string()
|
|
934
|
+
});
|
|
935
|
+
var TreeRow = z4.object({
|
|
936
|
+
entryId: z4.string(),
|
|
937
|
+
parentId: z4.string().nullable(),
|
|
938
|
+
depth: z4.number().int(),
|
|
939
|
+
role: z4.enum(["user", "assistant"]),
|
|
940
|
+
text: z4.string(),
|
|
941
|
+
activeBranch: z4.boolean(),
|
|
942
|
+
leaf: z4.boolean(),
|
|
943
|
+
label: z4.string().nullable(),
|
|
944
|
+
at: z4.string().nullable()
|
|
945
|
+
});
|
|
946
|
+
var SessionTreeResult = z4.object({ rows: z4.array(TreeRow) });
|
|
947
|
+
var BranchRequest = z4.object({
|
|
948
|
+
entryId: z4.string().nullable(),
|
|
949
|
+
fork: z4.boolean().default(false)
|
|
950
|
+
});
|
|
951
|
+
var HostBranchResult = z4.object({
|
|
952
|
+
sessionFile: z4.string().nullable(),
|
|
953
|
+
prefill: z4.string().nullable()
|
|
954
|
+
});
|
|
955
|
+
var BranchResult = z4.object({
|
|
956
|
+
session: Session.nullable(),
|
|
957
|
+
prefill: z4.string().nullable()
|
|
958
|
+
});
|
|
959
|
+
var SessionExportResult = z4.object({ fileName: z4.string(), content: z4.string() });
|
|
960
|
+
var CommandViewAction = z4.object({
|
|
961
|
+
label: z4.string().min(1).max(80),
|
|
962
|
+
command: z4.string().startsWith("/").max(500),
|
|
963
|
+
tone: z4.enum(["default", "primary", "danger"]).optional()
|
|
964
|
+
});
|
|
965
|
+
var CommandView = z4.object({
|
|
966
|
+
title: z4.string().min(1).max(200),
|
|
967
|
+
body: UINodeSchema,
|
|
968
|
+
actions: z4.array(CommandViewAction).max(30).default([])
|
|
969
|
+
});
|
|
924
970
|
var ModelStateResult = z4.object({
|
|
925
971
|
model: SessionModel.nullable(),
|
|
926
972
|
thinking: ThinkingLevel.nullable()
|
|
@@ -1321,6 +1367,20 @@ var HostCommand = z8.discriminatedUnion("type", [
|
|
|
1321
1367
|
z8.object({ type: z8.literal("session.snapshot"), sessionId: z8.string() }),
|
|
1322
1368
|
z8.object({ type: z8.literal("session.models"), sessionId: z8.string() }),
|
|
1323
1369
|
z8.object({ type: z8.literal("session.commands"), sessionId: z8.string() }),
|
|
1370
|
+
z8.object({
|
|
1371
|
+
type: z8.literal("session.complete"),
|
|
1372
|
+
sessionId: z8.string(),
|
|
1373
|
+
text: z8.string().max(500)
|
|
1374
|
+
}),
|
|
1375
|
+
z8.object({ type: z8.literal("session.tree"), sessionId: z8.string() }),
|
|
1376
|
+
z8.object({
|
|
1377
|
+
type: z8.literal("session.branch"),
|
|
1378
|
+
sessionId: z8.string(),
|
|
1379
|
+
entryId: z8.string().nullable(),
|
|
1380
|
+
fork: z8.boolean()
|
|
1381
|
+
}),
|
|
1382
|
+
z8.object({ type: z8.literal("session.reload"), sessionId: z8.string() }),
|
|
1383
|
+
z8.object({ type: z8.literal("session.export"), sessionId: z8.string() }),
|
|
1324
1384
|
z8.object({
|
|
1325
1385
|
type: z8.literal("session.setModel"),
|
|
1326
1386
|
sessionId: z8.string(),
|
|
@@ -2505,7 +2565,7 @@ function trimUrl(url) {
|
|
|
2505
2565
|
|
|
2506
2566
|
// packages/cli/dist/commands/up.js
|
|
2507
2567
|
import { spawn as spawn4 } from "node:child_process";
|
|
2508
|
-
import { existsSync as
|
|
2568
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
2509
2569
|
import { hostname as hostname2 } from "node:os";
|
|
2510
2570
|
|
|
2511
2571
|
// packages/coordinator/dist/attention/router.js
|
|
@@ -9361,49 +9421,20 @@ function registerSessionRoutes(app, ctx) {
|
|
|
9361
9421
|
where.push("archived_at IS NULL");
|
|
9362
9422
|
return ctx.store.sessions.list(where.join(" AND ") || void 0, params, "last_activity_at DESC");
|
|
9363
9423
|
});
|
|
9364
|
-
|
|
9365
|
-
const
|
|
9366
|
-
|
|
9367
|
-
const list = normalizeImportable(await ctx.hostHub.request(q.hostId, { type: "session.importable" }));
|
|
9368
|
-
return list.map((s) => ({
|
|
9369
|
-
...s,
|
|
9370
|
-
importedSessionId: ctx.store.sessionByNativeFile(q.hostId, s.sessionFile)?.id ?? null
|
|
9371
|
-
}));
|
|
9372
|
-
});
|
|
9373
|
-
app.post("/api/sessions/import", async (req, reply) => {
|
|
9374
|
-
const body = parse(ImportSessionRequest, req.body);
|
|
9375
|
-
requireConnected(ctx, body.hostId);
|
|
9376
|
-
const already = ctx.store.sessionByNativeFile(body.hostId, body.sessionFile);
|
|
9377
|
-
if (already) {
|
|
9378
|
-
throw new SwitchyardError("conflict", "This Pi session was already imported", {
|
|
9379
|
-
sessionId: already.id
|
|
9380
|
-
});
|
|
9381
|
-
}
|
|
9382
|
-
const list = normalizeImportable(await ctx.hostHub.request(body.hostId, { type: "session.importable" }));
|
|
9383
|
-
const native = list.find((s) => s.sessionFile === body.sessionFile);
|
|
9384
|
-
if (!native)
|
|
9385
|
-
throw new SwitchyardError("not_found", "Pi session file not found on host");
|
|
9386
|
-
if (!native.inactive) {
|
|
9387
|
-
throw new SwitchyardError("conflict", "This Pi session is still active; close it in Pi before importing");
|
|
9388
|
-
}
|
|
9389
|
-
const rawItems = await ctx.hostHub.request(body.hostId, {
|
|
9390
|
-
type: "session.readNative",
|
|
9391
|
-
sessionFile: body.sessionFile
|
|
9392
|
-
});
|
|
9393
|
-
const ws = ctx.store.workspaceByPath(body.hostId, native.cwd);
|
|
9394
|
-
const projectId = body.projectId ?? ws?.projectId ?? null;
|
|
9424
|
+
function importNative(native, hostId, projectIdIn, items) {
|
|
9425
|
+
const ws = ctx.store.workspaceByPath(hostId, native.cwd);
|
|
9426
|
+
const projectId = projectIdIn ?? ws?.projectId ?? null;
|
|
9395
9427
|
if (projectId)
|
|
9396
9428
|
ctx.store.projects.require(projectId);
|
|
9397
9429
|
const project = projectId ? ctx.store.projects.get(projectId) : null;
|
|
9398
|
-
const host = ctx.store.hosts.require(
|
|
9430
|
+
const host = ctx.store.hosts.require(hostId);
|
|
9399
9431
|
const now = ctx.store.nowIso();
|
|
9400
9432
|
const id = newId("ses");
|
|
9401
|
-
const items = Array.isArray(rawItems) ? rawItems : rawItems?.items ?? [];
|
|
9402
9433
|
const session = {
|
|
9403
9434
|
id,
|
|
9404
9435
|
title: native.name ?? native.cwd.split("/").pop() ?? "Imported session",
|
|
9405
9436
|
subtitle: null,
|
|
9406
|
-
hostId
|
|
9437
|
+
hostId,
|
|
9407
9438
|
workspaceId: ws && ws.projectId === projectId ? ws.id : null,
|
|
9408
9439
|
projectId,
|
|
9409
9440
|
cwd: native.cwd,
|
|
@@ -9437,6 +9468,39 @@ function registerSessionRoutes(app, ctx) {
|
|
|
9437
9468
|
ctx.store.upsertItem(item.data);
|
|
9438
9469
|
});
|
|
9439
9470
|
});
|
|
9471
|
+
return session;
|
|
9472
|
+
}
|
|
9473
|
+
app.get("/api/sessions/importable", async (req) => {
|
|
9474
|
+
const q = parse(ImportableQuery, req.query, "query");
|
|
9475
|
+
requireConnected(ctx, q.hostId);
|
|
9476
|
+
const list = normalizeImportable(await ctx.hostHub.request(q.hostId, { type: "session.importable" }));
|
|
9477
|
+
return list.map((s) => ({
|
|
9478
|
+
...s,
|
|
9479
|
+
importedSessionId: ctx.store.sessionByNativeFile(q.hostId, s.sessionFile)?.id ?? null
|
|
9480
|
+
}));
|
|
9481
|
+
});
|
|
9482
|
+
app.post("/api/sessions/import", async (req, reply) => {
|
|
9483
|
+
const body = parse(ImportSessionRequest, req.body);
|
|
9484
|
+
requireConnected(ctx, body.hostId);
|
|
9485
|
+
const already = ctx.store.sessionByNativeFile(body.hostId, body.sessionFile);
|
|
9486
|
+
if (already) {
|
|
9487
|
+
throw new SwitchyardError("conflict", "This Pi session was already imported", {
|
|
9488
|
+
sessionId: already.id
|
|
9489
|
+
});
|
|
9490
|
+
}
|
|
9491
|
+
const list = normalizeImportable(await ctx.hostHub.request(body.hostId, { type: "session.importable" }));
|
|
9492
|
+
const native = list.find((s) => s.sessionFile === body.sessionFile);
|
|
9493
|
+
if (!native)
|
|
9494
|
+
throw new SwitchyardError("not_found", "Pi session file not found on host");
|
|
9495
|
+
if (!native.inactive) {
|
|
9496
|
+
throw new SwitchyardError("conflict", "This Pi session is still active; close it in Pi before importing");
|
|
9497
|
+
}
|
|
9498
|
+
const rawItems = await ctx.hostHub.request(body.hostId, {
|
|
9499
|
+
type: "session.readNative",
|
|
9500
|
+
sessionFile: body.sessionFile
|
|
9501
|
+
});
|
|
9502
|
+
const items = Array.isArray(rawItems) ? rawItems : rawItems?.items ?? [];
|
|
9503
|
+
const session = importNative(native, body.hostId, body.projectId ?? null, items);
|
|
9440
9504
|
return reply.code(201).send(session);
|
|
9441
9505
|
});
|
|
9442
9506
|
app.get("/api/sessions/:id", async (req) => get(idOf(req)));
|
|
@@ -9458,9 +9522,15 @@ function registerSessionRoutes(app, ctx) {
|
|
|
9458
9522
|
return ctx.hostHub.request(s.hostId, { type: "session.snapshot", sessionId: s.id });
|
|
9459
9523
|
});
|
|
9460
9524
|
app.post("/api/sessions/:id/prompt", async (req) => {
|
|
9461
|
-
|
|
9525
|
+
let s = get(idOf(req));
|
|
9462
9526
|
const body = parse(PromptRequest, req.body);
|
|
9463
|
-
|
|
9527
|
+
if (!s.readOnly && !s.archivedAt && s.nativeHandle && !LIVE_RUNTIMES2.includes(s.runtime) && body.mode === "prompt") {
|
|
9528
|
+
await resumeSession(s);
|
|
9529
|
+
s = get(s.id);
|
|
9530
|
+
requireConnected(ctx, s.hostId);
|
|
9531
|
+
} else {
|
|
9532
|
+
requireLiveSession(ctx, s);
|
|
9533
|
+
}
|
|
9464
9534
|
const run = ctx.store.runForSession(s);
|
|
9465
9535
|
const result = await ctx.hostHub.request(s.hostId, {
|
|
9466
9536
|
type: "session.prompt",
|
|
@@ -9471,6 +9541,9 @@ function registerSessionRoutes(app, ctx) {
|
|
|
9471
9541
|
origin: "human",
|
|
9472
9542
|
controlRevision: run?.controlRevision ?? 1
|
|
9473
9543
|
});
|
|
9544
|
+
const view = result?.view;
|
|
9545
|
+
if (view)
|
|
9546
|
+
CommandView.parse(view);
|
|
9474
9547
|
return {
|
|
9475
9548
|
ok: true,
|
|
9476
9549
|
clientMessageId: body.clientMessageId ?? null,
|
|
@@ -9488,6 +9561,54 @@ function registerSessionRoutes(app, ctx) {
|
|
|
9488
9561
|
requireSessionControls(ctx, s);
|
|
9489
9562
|
return SessionCommandsResult.parse(await ctx.hostHub.request(s.hostId, { type: "session.commands", sessionId: s.id }));
|
|
9490
9563
|
});
|
|
9564
|
+
app.post("/api/sessions/:id/complete", async (req) => {
|
|
9565
|
+
const s = get(idOf(req));
|
|
9566
|
+
const body = parse(CompleteRequest, req.body);
|
|
9567
|
+
requireSessionControls(ctx, s);
|
|
9568
|
+
return SessionCompleteResult.parse(await ctx.hostHub.request(s.hostId, {
|
|
9569
|
+
type: "session.complete",
|
|
9570
|
+
sessionId: s.id,
|
|
9571
|
+
text: body.text
|
|
9572
|
+
}));
|
|
9573
|
+
});
|
|
9574
|
+
app.get("/api/sessions/:id/tree", async (req) => {
|
|
9575
|
+
const s = get(idOf(req));
|
|
9576
|
+
requireSessionControls(ctx, s);
|
|
9577
|
+
return SessionTreeResult.parse(await ctx.hostHub.request(s.hostId, { type: "session.tree", sessionId: s.id }));
|
|
9578
|
+
});
|
|
9579
|
+
app.post("/api/sessions/:id/branch", async (req, reply) => {
|
|
9580
|
+
const s = get(idOf(req));
|
|
9581
|
+
const body = parse(BranchRequest, req.body);
|
|
9582
|
+
requireSessionControls(ctx, s);
|
|
9583
|
+
const r = HostBranchResult.parse(await ctx.hostHub.request(s.hostId, {
|
|
9584
|
+
type: "session.branch",
|
|
9585
|
+
sessionId: s.id,
|
|
9586
|
+
entryId: body.entryId,
|
|
9587
|
+
fork: body.fork
|
|
9588
|
+
}));
|
|
9589
|
+
if (!r.sessionFile)
|
|
9590
|
+
return { session: null, prefill: r.prefill };
|
|
9591
|
+
const native = await ctx.hostHub.request(s.hostId, { type: "session.readNative", sessionFile: r.sessionFile });
|
|
9592
|
+
const session = importNative({
|
|
9593
|
+
sessionId: native.sessionId,
|
|
9594
|
+
sessionFile: r.sessionFile,
|
|
9595
|
+
cwd: native.cwd,
|
|
9596
|
+
name: `${native.name ?? s.title} (branch)`,
|
|
9597
|
+
mtime: ctx.store.nowIso()
|
|
9598
|
+
}, s.hostId, s.projectId, native.items ?? []);
|
|
9599
|
+
return reply.code(201).send({ session, prefill: r.prefill });
|
|
9600
|
+
});
|
|
9601
|
+
app.post("/api/sessions/:id/reload", async (req) => {
|
|
9602
|
+
const s = get(idOf(req));
|
|
9603
|
+
requireSessionControls(ctx, s);
|
|
9604
|
+
await ctx.hostHub.request(s.hostId, { type: "session.reload", sessionId: s.id });
|
|
9605
|
+
return { ok: true };
|
|
9606
|
+
});
|
|
9607
|
+
app.get("/api/sessions/:id/export", async (req) => {
|
|
9608
|
+
const s = get(idOf(req));
|
|
9609
|
+
requireSessionControls(ctx, s);
|
|
9610
|
+
return SessionExportResult.parse(await ctx.hostHub.request(s.hostId, { type: "session.export", sessionId: s.id }));
|
|
9611
|
+
});
|
|
9491
9612
|
app.post("/api/sessions/:id/model", async (req) => {
|
|
9492
9613
|
const s = get(idOf(req));
|
|
9493
9614
|
const body = parse(SetModelRequest, req.body);
|
|
@@ -9543,8 +9664,7 @@ function registerSessionRoutes(app, ctx) {
|
|
|
9543
9664
|
});
|
|
9544
9665
|
app.post("/api/sessions/:id/takeover", async (req) => takeover(ctx, idOf(req)));
|
|
9545
9666
|
app.post("/api/sessions/:id/return-to-automation", async (req) => returnToAutomation(ctx, idOf(req)));
|
|
9546
|
-
|
|
9547
|
-
const s = get(idOf(req));
|
|
9667
|
+
async function resumeSession(s) {
|
|
9548
9668
|
if (!s.nativeHandle) {
|
|
9549
9669
|
throw new SwitchyardError("native_session_missing", "This session has no native Pi session to resume");
|
|
9550
9670
|
}
|
|
@@ -9577,6 +9697,10 @@ function registerSessionRoutes(app, ctx) {
|
|
|
9577
9697
|
});
|
|
9578
9698
|
throw err;
|
|
9579
9699
|
}
|
|
9700
|
+
}
|
|
9701
|
+
app.post("/api/sessions/:id/resume", async (req) => {
|
|
9702
|
+
const s = get(idOf(req));
|
|
9703
|
+
await resumeSession(s);
|
|
9580
9704
|
return get(s.id);
|
|
9581
9705
|
});
|
|
9582
9706
|
app.post("/api/sessions/:id/rename", async (req) => {
|
|
@@ -12807,7 +12931,7 @@ function toErrorBody(err) {
|
|
|
12807
12931
|
function queueKey(command) {
|
|
12808
12932
|
if (command.type === "checks.run")
|
|
12809
12933
|
return `checks:${command.sessionId}`;
|
|
12810
|
-
if (command.type === "session.answer" || command.type === "session.abort" || command.type === "session.setControl" || command.type === "session.adapterAction" || command.type === "session.snapshot" || command.type === "session.models" || command.type === "session.commands") {
|
|
12934
|
+
if (command.type === "session.answer" || command.type === "session.abort" || command.type === "session.setControl" || command.type === "session.adapterAction" || command.type === "session.snapshot" || command.type === "session.models" || command.type === "session.commands" || command.type === "session.complete" || command.type === "session.tree" || command.type === "session.export" || command.type === "session.reload") {
|
|
12811
12935
|
return `control:${command.sessionId}`;
|
|
12812
12936
|
}
|
|
12813
12937
|
if ("sessionId" in command && typeof command.sessionId === "string") {
|
|
@@ -12966,6 +13090,16 @@ var CommandRouter = class {
|
|
|
12966
13090
|
return sup.models(c.sessionId);
|
|
12967
13091
|
case "session.commands":
|
|
12968
13092
|
return sup.commands(c.sessionId);
|
|
13093
|
+
case "session.complete":
|
|
13094
|
+
return sup.complete(c.sessionId, c.text);
|
|
13095
|
+
case "session.tree":
|
|
13096
|
+
return sup.tree(c.sessionId);
|
|
13097
|
+
case "session.branch":
|
|
13098
|
+
return sup.branch(c.sessionId, c.entryId, c.fork);
|
|
13099
|
+
case "session.reload":
|
|
13100
|
+
return sup.reload(c.sessionId);
|
|
13101
|
+
case "session.export":
|
|
13102
|
+
return sup.exportSession(c.sessionId);
|
|
12969
13103
|
case "session.setModel":
|
|
12970
13104
|
return sup.setModel(c.sessionId, c.provider, c.modelId);
|
|
12971
13105
|
case "session.setThinking":
|
|
@@ -14568,18 +14702,121 @@ function writeJsonl(stream, value) {
|
|
|
14568
14702
|
});
|
|
14569
14703
|
}
|
|
14570
14704
|
|
|
14705
|
+
// packages/pi-runtime/dist/pi-session-manager.js
|
|
14706
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
14707
|
+
import { dirname as dirname8, join as join14 } from "node:path";
|
|
14708
|
+
import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
14709
|
+
var managerPromise = null;
|
|
14710
|
+
function loadPiSessionManager(piBin = "pi") {
|
|
14711
|
+
managerPromise ??= (async () => {
|
|
14712
|
+
const root = resolvePiPackageRoot(piBin);
|
|
14713
|
+
if (!root)
|
|
14714
|
+
return null;
|
|
14715
|
+
try {
|
|
14716
|
+
const mod = await import(pathToFileURL3(join14(root, "dist", "index.js")).href);
|
|
14717
|
+
return mod.SessionManager ?? null;
|
|
14718
|
+
} catch {
|
|
14719
|
+
return null;
|
|
14720
|
+
}
|
|
14721
|
+
})();
|
|
14722
|
+
return managerPromise;
|
|
14723
|
+
}
|
|
14724
|
+
function messageText(message) {
|
|
14725
|
+
const c = message?.content;
|
|
14726
|
+
if (typeof c === "string")
|
|
14727
|
+
return c;
|
|
14728
|
+
if (!Array.isArray(c))
|
|
14729
|
+
return "";
|
|
14730
|
+
return c.filter((p) => p?.type === "text" && typeof p.text === "string").map((p) => p.text).join(" ");
|
|
14731
|
+
}
|
|
14732
|
+
var MAX_ROWS = 2e3;
|
|
14733
|
+
function treeRows(sm) {
|
|
14734
|
+
const active = new Set((sm.getBranch() ?? []).map((e) => e.id));
|
|
14735
|
+
const leafId = sm.getLeafId?.() ?? null;
|
|
14736
|
+
const rows = [];
|
|
14737
|
+
const walk = (nodes, depth, parentId) => {
|
|
14738
|
+
for (const node of nodes) {
|
|
14739
|
+
if (rows.length >= MAX_ROWS)
|
|
14740
|
+
return;
|
|
14741
|
+
const entry = node.entry;
|
|
14742
|
+
const role = entry?.message?.role;
|
|
14743
|
+
const shown = entry?.type === "message" && (role === "user" || role === "assistant");
|
|
14744
|
+
if (shown) {
|
|
14745
|
+
const raw = messageText(entry.message).replace(/\s+/g, " ").trim();
|
|
14746
|
+
const text = raw ? raw.slice(0, 160) : role === "assistant" ? "(tool calls)" : "";
|
|
14747
|
+
rows.push({
|
|
14748
|
+
entryId: entry.id,
|
|
14749
|
+
parentId,
|
|
14750
|
+
depth,
|
|
14751
|
+
role,
|
|
14752
|
+
text,
|
|
14753
|
+
activeBranch: active.has(entry.id),
|
|
14754
|
+
leaf: entry.id === leafId,
|
|
14755
|
+
label: node.label ?? null,
|
|
14756
|
+
at: typeof entry.timestamp === "string" ? entry.timestamp : null
|
|
14757
|
+
});
|
|
14758
|
+
walk(node.children ?? [], depth + 1, entry.id);
|
|
14759
|
+
} else {
|
|
14760
|
+
walk(node.children ?? [], depth, parentId);
|
|
14761
|
+
}
|
|
14762
|
+
}
|
|
14763
|
+
};
|
|
14764
|
+
walk(sm.getTree() ?? [], 0, null);
|
|
14765
|
+
if (!rows.some((r) => r.leaf)) {
|
|
14766
|
+
const last = [...rows].reverse().find((r) => r.activeBranch);
|
|
14767
|
+
if (last)
|
|
14768
|
+
last.leaf = true;
|
|
14769
|
+
}
|
|
14770
|
+
return rows;
|
|
14771
|
+
}
|
|
14772
|
+
async function branchSession(sessionFile, entryId, fork, piBin, load = loadPiSessionManager, checkFile = true) {
|
|
14773
|
+
const SM = await load(piBin);
|
|
14774
|
+
if (!SM) {
|
|
14775
|
+
throw new SwitchyardError("invalid_request", "Pi's SessionManager could not be loaded on this host");
|
|
14776
|
+
}
|
|
14777
|
+
const sm = SM.open(sessionFile, dirname8(sessionFile));
|
|
14778
|
+
let target;
|
|
14779
|
+
let prefill = null;
|
|
14780
|
+
if (fork) {
|
|
14781
|
+
const e = entryId ? sm.getEntry(entryId) : void 0;
|
|
14782
|
+
if (!e || e.type !== "message" || e.message?.role !== "user") {
|
|
14783
|
+
throw new SwitchyardError("invalid_request", "Pick one of your messages to fork from");
|
|
14784
|
+
}
|
|
14785
|
+
prefill = messageText(e.message);
|
|
14786
|
+
target = e.parentId ?? null;
|
|
14787
|
+
if (!target)
|
|
14788
|
+
return { sessionFile: null, prefill };
|
|
14789
|
+
} else {
|
|
14790
|
+
target = entryId ?? sm.getLeafId();
|
|
14791
|
+
if (!target) {
|
|
14792
|
+
throw new SwitchyardError("invalid_request", "This session has nothing to branch yet");
|
|
14793
|
+
}
|
|
14794
|
+
}
|
|
14795
|
+
const hasMessages = (sm.getBranch(target) ?? []).some((e) => e?.type === "message" && (e.message?.role === "user" || e.message?.role === "assistant"));
|
|
14796
|
+
if (!hasMessages) {
|
|
14797
|
+
if (fork)
|
|
14798
|
+
return { sessionFile: null, prefill };
|
|
14799
|
+
throw new SwitchyardError("invalid_request", "There is no conversation before that point");
|
|
14800
|
+
}
|
|
14801
|
+
const file = sm.createBranchedSession(target);
|
|
14802
|
+
if (!file || checkFile && !existsSync9(file)) {
|
|
14803
|
+
throw new SwitchyardError("invalid_request", "Pi did not write a branched session");
|
|
14804
|
+
}
|
|
14805
|
+
return { sessionFile: file, prefill };
|
|
14806
|
+
}
|
|
14807
|
+
|
|
14571
14808
|
// packages/pi-runtime/dist/rpc-process.js
|
|
14572
14809
|
import { spawn as spawn2 } from "node:child_process";
|
|
14573
14810
|
import { randomUUID } from "node:crypto";
|
|
14574
14811
|
import { EventEmitter as EventEmitter3 } from "node:events";
|
|
14575
|
-
import { chmodSync as chmodSync4, existsSync as
|
|
14812
|
+
import { chmodSync as chmodSync4, existsSync as existsSync10, mkdirSync as mkdirSync6, rmSync as rmSync2 } from "node:fs";
|
|
14576
14813
|
import { createServer } from "node:net";
|
|
14577
14814
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
14578
|
-
import { join as
|
|
14815
|
+
import { join as join15 } from "node:path";
|
|
14579
14816
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
14580
14817
|
function bridgeExtensionPath() {
|
|
14581
14818
|
const sibling = fileURLToPath2(new URL("./bridge/switchyard-bridge.js", import.meta.url));
|
|
14582
|
-
if (
|
|
14819
|
+
if (existsSync10(sibling))
|
|
14583
14820
|
return sibling;
|
|
14584
14821
|
return fileURLToPath2(new URL("../dist/bridge/switchyard-bridge.js", import.meta.url));
|
|
14585
14822
|
}
|
|
@@ -14595,11 +14832,11 @@ var MAX_SOCKET_PATH = 100;
|
|
|
14595
14832
|
function shortSocketPath(runtimeDir, prefix) {
|
|
14596
14833
|
const name = `${prefix}-${randomUUID().slice(0, 8)}.sock`;
|
|
14597
14834
|
let dir = runtimeDir;
|
|
14598
|
-
if (
|
|
14599
|
-
dir =
|
|
14835
|
+
if (join15(dir, name).length > MAX_SOCKET_PATH) {
|
|
14836
|
+
dir = join15("/tmp", `switchyard-${process.getuid?.() ?? "u"}`);
|
|
14600
14837
|
}
|
|
14601
14838
|
mkdirSync6(dir, { recursive: true, mode: 448 });
|
|
14602
|
-
return
|
|
14839
|
+
return join15(dir, name);
|
|
14603
14840
|
}
|
|
14604
14841
|
var PiRpcProcess = class _PiRpcProcess extends EventEmitter3 {
|
|
14605
14842
|
child;
|
|
@@ -14652,7 +14889,7 @@ var PiRpcProcess = class _PiRpcProcess extends EventEmitter3 {
|
|
|
14652
14889
|
let proc = null;
|
|
14653
14890
|
const early = [];
|
|
14654
14891
|
if (useBridge) {
|
|
14655
|
-
socketPath = shortSocketPath(opts.runtimeDir ??
|
|
14892
|
+
socketPath = shortSocketPath(opts.runtimeDir ?? join15(tmpdir2(), "switchyard"), "bridge");
|
|
14656
14893
|
server = createServer((socket) => {
|
|
14657
14894
|
if (proc)
|
|
14658
14895
|
proc.attachBridgeSocket(socket);
|
|
@@ -14837,20 +15074,52 @@ var PiRpcProcess = class _PiRpcProcess extends EventEmitter3 {
|
|
|
14837
15074
|
// packages/pi-runtime/dist/session-runtime.js
|
|
14838
15075
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
14839
15076
|
import { EventEmitter as EventEmitter5 } from "node:events";
|
|
14840
|
-
import { existsSync as
|
|
15077
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
15078
|
+
|
|
15079
|
+
// packages/pi-runtime/dist/command-hints.js
|
|
15080
|
+
import { readFile as readFile7 } from "node:fs/promises";
|
|
15081
|
+
function parseArgumentHint(markdown) {
|
|
15082
|
+
const lines = markdown.split(/\r?\n/);
|
|
15083
|
+
if (lines[0] !== "---")
|
|
15084
|
+
return null;
|
|
15085
|
+
const end = lines.indexOf("---", 1);
|
|
15086
|
+
if (end < 0)
|
|
15087
|
+
return null;
|
|
15088
|
+
for (const line of lines.slice(1, end)) {
|
|
15089
|
+
const m = /^argument-hint:\s*(.*)$/.exec(line);
|
|
15090
|
+
if (!m)
|
|
15091
|
+
continue;
|
|
15092
|
+
let value = (m[1] ?? "").trim();
|
|
15093
|
+
const q = value[0];
|
|
15094
|
+
if (value.length >= 2 && (q === '"' || q === "'") && value.endsWith(q)) {
|
|
15095
|
+
value = value.slice(1, -1).trim();
|
|
15096
|
+
}
|
|
15097
|
+
return value ? value.slice(0, 80) : null;
|
|
15098
|
+
}
|
|
15099
|
+
return null;
|
|
15100
|
+
}
|
|
15101
|
+
async function readArgumentHint(path) {
|
|
15102
|
+
if (!path.endsWith(".md"))
|
|
15103
|
+
return null;
|
|
15104
|
+
try {
|
|
15105
|
+
return parseArgumentHint(await readFile7(path, "utf8"));
|
|
15106
|
+
} catch {
|
|
15107
|
+
return null;
|
|
15108
|
+
}
|
|
15109
|
+
}
|
|
14841
15110
|
|
|
14842
15111
|
// packages/pi-runtime/dist/terminal-process.js
|
|
14843
15112
|
import { spawn as spawn3 } from "node:child_process";
|
|
14844
15113
|
import { EventEmitter as EventEmitter4 } from "node:events";
|
|
14845
|
-
import { closeSync, existsSync as
|
|
15114
|
+
import { closeSync, existsSync as existsSync11, mkdirSync as mkdirSync7, openSync, readFileSync as readFileSync7 } from "node:fs";
|
|
14846
15115
|
import { createRequire } from "node:module";
|
|
14847
15116
|
import { createConnection } from "node:net";
|
|
14848
15117
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
14849
|
-
import { basename as basename2, dirname as
|
|
15118
|
+
import { basename as basename2, dirname as dirname9, join as join16 } from "node:path";
|
|
14850
15119
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
14851
15120
|
function resolveDist(rel) {
|
|
14852
15121
|
const sibling = fileURLToPath3(new URL(`./${rel}`, import.meta.url));
|
|
14853
|
-
if (
|
|
15122
|
+
if (existsSync11(sibling))
|
|
14854
15123
|
return sibling;
|
|
14855
15124
|
return fileURLToPath3(new URL(`../dist/${rel}`, import.meta.url));
|
|
14856
15125
|
}
|
|
@@ -14867,7 +15136,7 @@ function terminalSupport() {
|
|
|
14867
15136
|
return { ok: false, reason: `node-pty is not available: ${err.message}` };
|
|
14868
15137
|
}
|
|
14869
15138
|
const holder = holderScriptPath();
|
|
14870
|
-
if (!
|
|
15139
|
+
if (!existsSync11(holder))
|
|
14871
15140
|
return { ok: false, reason: `Terminal holder not built: ${holder}` };
|
|
14872
15141
|
return { ok: true };
|
|
14873
15142
|
}
|
|
@@ -14932,7 +15201,7 @@ var PiTerminalProcess = class _PiTerminalProcess extends EventEmitter4 {
|
|
|
14932
15201
|
}
|
|
14933
15202
|
/** Start a holder running interactive Pi and attach to it. */
|
|
14934
15203
|
static async start(opts) {
|
|
14935
|
-
const runtimeDir = opts.runtimeDir ??
|
|
15204
|
+
const runtimeDir = opts.runtimeDir ?? join16(tmpdir3(), "switchyard");
|
|
14936
15205
|
const socketPath = shortSocketPath(runtimeDir, "term");
|
|
14937
15206
|
const env = {};
|
|
14938
15207
|
for (const [k, v] of Object.entries({ ...process.env, ...opts.env ?? {} })) {
|
|
@@ -14953,8 +15222,8 @@ var PiTerminalProcess = class _PiTerminalProcess extends EventEmitter4 {
|
|
|
14953
15222
|
cols: opts.cols ?? 120,
|
|
14954
15223
|
rows: opts.rows ?? 40
|
|
14955
15224
|
};
|
|
14956
|
-
const logPath = opts.logPath ??
|
|
14957
|
-
mkdirSync7(
|
|
15225
|
+
const logPath = opts.logPath ?? join16(dirname9(socketPath), `${basename2(socketPath, ".sock")}.log`);
|
|
15226
|
+
mkdirSync7(dirname9(logPath), { recursive: true, mode: 448 });
|
|
14958
15227
|
const fd = openSync(logPath, "a", 384);
|
|
14959
15228
|
try {
|
|
14960
15229
|
const child = spawn3(process.execPath, [holderScriptPath()], {
|
|
@@ -15287,7 +15556,7 @@ var ManagedSession = class _ManagedSession extends EventEmitter5 {
|
|
|
15287
15556
|
await this.attachTerminal(this.opts.terminalSocket);
|
|
15288
15557
|
return;
|
|
15289
15558
|
}
|
|
15290
|
-
if (this._native && !
|
|
15559
|
+
if (this._native && !existsSync12(this._native.sessionFile)) {
|
|
15291
15560
|
const message = `Native session file not found: ${this._native.sessionFile}`;
|
|
15292
15561
|
this.fail(message);
|
|
15293
15562
|
throw new SwitchyardError("native_session_missing", message, {
|
|
@@ -15414,7 +15683,22 @@ var ManagedSession = class _ManagedSession extends EventEmitter5 {
|
|
|
15414
15683
|
} else {
|
|
15415
15684
|
data = await this.call(proc, { type: "follow_up", ...body });
|
|
15416
15685
|
}
|
|
15417
|
-
|
|
15686
|
+
if (data?.disposition === "view") {
|
|
15687
|
+
const view = CommandView.safeParse(data.view);
|
|
15688
|
+
if (!view.success) {
|
|
15689
|
+
throw new SwitchyardError("invalid_request", "The command returned an invalid view");
|
|
15690
|
+
}
|
|
15691
|
+
return { disposition: "view", view: view.data };
|
|
15692
|
+
}
|
|
15693
|
+
return { disposition: data?.disposition ?? "started", view: null };
|
|
15694
|
+
}
|
|
15695
|
+
/** Reload Pi's extensions, skills, prompts and context files (terminal sessions only). */
|
|
15696
|
+
async reload() {
|
|
15697
|
+
const proc = this.requireAlive();
|
|
15698
|
+
if (!(proc instanceof PiTerminalProcess)) {
|
|
15699
|
+
throw new SwitchyardError("invalid_request", "Reload needs a terminal session");
|
|
15700
|
+
}
|
|
15701
|
+
await this.call(proc, { type: "reload" }, 1e4);
|
|
15418
15702
|
}
|
|
15419
15703
|
/** Abort the current run; with `clearQueue`, first clear and return queued messages. */
|
|
15420
15704
|
async abort(clearQueue = false) {
|
|
@@ -15466,19 +15750,36 @@ var ManagedSession = class _ManagedSession extends EventEmitter5 {
|
|
|
15466
15750
|
type: "get_commands"
|
|
15467
15751
|
});
|
|
15468
15752
|
const out = [];
|
|
15753
|
+
const hints = [];
|
|
15469
15754
|
for (const c of data?.commands ?? []) {
|
|
15470
15755
|
if (typeof c?.name !== "string" || !c.name)
|
|
15471
15756
|
continue;
|
|
15472
15757
|
if (c.source !== "extension" && c.source !== "prompt" && c.source !== "skill")
|
|
15473
15758
|
continue;
|
|
15474
|
-
|
|
15759
|
+
const entry = {
|
|
15475
15760
|
name: c.name,
|
|
15476
15761
|
description: typeof c.description === "string" ? c.description : null,
|
|
15477
|
-
source: c.source
|
|
15478
|
-
|
|
15762
|
+
source: c.source,
|
|
15763
|
+
argumentHint: null
|
|
15764
|
+
};
|
|
15765
|
+
if (c.source === "prompt" && typeof c.sourceInfo?.path === "string") {
|
|
15766
|
+
hints.push(readArgumentHint(c.sourceInfo.path).then((h) => {
|
|
15767
|
+
entry.argumentHint = h;
|
|
15768
|
+
}));
|
|
15769
|
+
}
|
|
15770
|
+
out.push(entry);
|
|
15479
15771
|
}
|
|
15772
|
+
await Promise.all(hints);
|
|
15480
15773
|
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
15481
15774
|
}
|
|
15775
|
+
/** Pi's argument completions for a draft such as `/model cl` (terminal sessions only). */
|
|
15776
|
+
async complete(text) {
|
|
15777
|
+
const proc = this.requireAlive();
|
|
15778
|
+
if (!(proc instanceof PiTerminalProcess))
|
|
15779
|
+
return { items: [], prefix: "" };
|
|
15780
|
+
const data = await this.call(proc, { type: "complete", text }, 5e3);
|
|
15781
|
+
return SessionCompleteResult.parse({ items: data?.items ?? [], prefix: data?.prefix ?? "" });
|
|
15782
|
+
}
|
|
15482
15783
|
async setModel(provider, modelId) {
|
|
15483
15784
|
await this.call(this.requireAlive(), { type: "set_model", provider, modelId });
|
|
15484
15785
|
await this.refreshModelState();
|
|
@@ -16077,8 +16378,9 @@ ${output}`.trimEnd() }));
|
|
|
16077
16378
|
};
|
|
16078
16379
|
|
|
16079
16380
|
// packages/host-agent/dist/supervisor.js
|
|
16080
|
-
import { existsSync as
|
|
16081
|
-
import {
|
|
16381
|
+
import { existsSync as existsSync13, realpathSync as realpathSync2, statSync } from "node:fs";
|
|
16382
|
+
import { readFile as readFile8, stat as stat5 } from "node:fs/promises";
|
|
16383
|
+
import { basename as basename3, dirname as dirname10, join as join17, sep as sep3 } from "node:path";
|
|
16082
16384
|
var RESTART_NOTICE = "Host agent restarted; resume to continue";
|
|
16083
16385
|
var QUEUED_ANSWER_SUMMARY = "Answered from your earlier form";
|
|
16084
16386
|
var PI_CLOSED_SUMMARY = "Pi closed this question (timeout or abort)";
|
|
@@ -16419,7 +16721,7 @@ var SessionSupervisor = class {
|
|
|
16419
16721
|
if (!pi.compatible) {
|
|
16420
16722
|
throw new SwitchyardError("pi_incompatible", pi.reason ?? "Pi is not available on this host");
|
|
16421
16723
|
}
|
|
16422
|
-
if (!
|
|
16724
|
+
if (!existsSync13(p.workspacePath) || !statSync(p.workspacePath).isDirectory()) {
|
|
16423
16725
|
throw new SwitchyardError("not_found", `Workspace ${p.workspacePath} does not exist`);
|
|
16424
16726
|
}
|
|
16425
16727
|
const prev = this.records.get(p.sessionId);
|
|
@@ -16434,7 +16736,7 @@ var SessionSupervisor = class {
|
|
|
16434
16736
|
}
|
|
16435
16737
|
}
|
|
16436
16738
|
let nextOrder = prev?.nextOrder ?? 0;
|
|
16437
|
-
if (!prev && nativeHandle &&
|
|
16739
|
+
if (!prev && nativeHandle && existsSync13(nativeHandle.sessionFile)) {
|
|
16438
16740
|
try {
|
|
16439
16741
|
const native = await readNativeTranscript(nativeHandle.sessionFile, {
|
|
16440
16742
|
piBin: this.opts.piBin
|
|
@@ -16550,8 +16852,8 @@ var SessionSupervisor = class {
|
|
|
16550
16852
|
}
|
|
16551
16853
|
async prompt(sessionId, text, images, mode) {
|
|
16552
16854
|
const ms = this.requireLive(sessionId);
|
|
16553
|
-
const disposition = await ms.prompt(text, images, mode);
|
|
16554
|
-
return { disposition };
|
|
16855
|
+
const { disposition, view } = await ms.prompt(text, images, mode);
|
|
16856
|
+
return { disposition, view };
|
|
16555
16857
|
}
|
|
16556
16858
|
async abort(sessionId, clearQueue) {
|
|
16557
16859
|
const ms = this.requireLive(sessionId);
|
|
@@ -16587,6 +16889,41 @@ var SessionSupervisor = class {
|
|
|
16587
16889
|
async commands(sessionId) {
|
|
16588
16890
|
return { commands: await this.requireLive(sessionId).commands() };
|
|
16589
16891
|
}
|
|
16892
|
+
async complete(sessionId, text) {
|
|
16893
|
+
return this.requireLive(sessionId).complete(text);
|
|
16894
|
+
}
|
|
16895
|
+
async reload(sessionId) {
|
|
16896
|
+
await this.requireLive(sessionId).reload();
|
|
16897
|
+
return { ok: true };
|
|
16898
|
+
}
|
|
16899
|
+
piFile(sessionId) {
|
|
16900
|
+
const file = this.requireRecord(sessionId).nativeHandle?.sessionFile;
|
|
16901
|
+
if (!file)
|
|
16902
|
+
throw new SwitchyardError("invalid_request", "This session has no Pi session file");
|
|
16903
|
+
return file;
|
|
16904
|
+
}
|
|
16905
|
+
async tree(sessionId) {
|
|
16906
|
+
const file = this.piFile(sessionId);
|
|
16907
|
+
const SM = await loadPiSessionManager(this.opts.piBin);
|
|
16908
|
+
if (!SM) {
|
|
16909
|
+
throw new SwitchyardError("invalid_request", "Pi's SessionManager could not be loaded on this host");
|
|
16910
|
+
}
|
|
16911
|
+
return { rows: treeRows(SM.open(file, dirname10(file))) };
|
|
16912
|
+
}
|
|
16913
|
+
async branch(sessionId, entryId, fork) {
|
|
16914
|
+
return branchSession(this.piFile(sessionId), entryId, fork, this.opts.piBin);
|
|
16915
|
+
}
|
|
16916
|
+
async exportSession(sessionId) {
|
|
16917
|
+
const file = this.piFile(sessionId);
|
|
16918
|
+
const info = await stat5(file).catch(() => null);
|
|
16919
|
+
if (!info) {
|
|
16920
|
+
throw new SwitchyardError("native_session_missing", `Native session file not found: ${file}`);
|
|
16921
|
+
}
|
|
16922
|
+
if (info.size > 50 * 1024 * 1024) {
|
|
16923
|
+
throw new SwitchyardError("invalid_request", "Session file is too large to export (over 50 MB)");
|
|
16924
|
+
}
|
|
16925
|
+
return { fileName: basename3(file), content: await readFile8(file, "utf8") };
|
|
16926
|
+
}
|
|
16590
16927
|
async setModel(sessionId, provider, modelId) {
|
|
16591
16928
|
return this.requireLive(sessionId).setModel(provider, modelId);
|
|
16592
16929
|
}
|
|
@@ -16655,7 +16992,7 @@ var SessionSupervisor = class {
|
|
|
16655
16992
|
}
|
|
16656
16993
|
async importable() {
|
|
16657
16994
|
const env = { ...process.env, ...this.opts.env ?? {} };
|
|
16658
|
-
const root =
|
|
16995
|
+
const root = join17(piAgentDir(env), "sessions");
|
|
16659
16996
|
const list = await listImportable(root);
|
|
16660
16997
|
const liveFiles = new Set([...this.live.values()].flatMap((l) => l.rec.nativeHandle ? [realpathOr2(l.rec.nativeHandle.sessionFile)] : []));
|
|
16661
16998
|
return {
|
|
@@ -16666,7 +17003,7 @@ var SessionSupervisor = class {
|
|
|
16666
17003
|
};
|
|
16667
17004
|
}
|
|
16668
17005
|
async readNative(sessionFile) {
|
|
16669
|
-
if (!
|
|
17006
|
+
if (!existsSync13(sessionFile)) {
|
|
16670
17007
|
throw new SwitchyardError("native_session_missing", `Native session file not found: ${sessionFile}`);
|
|
16671
17008
|
}
|
|
16672
17009
|
return readNativeTranscript(sessionFile, { piBin: this.opts.piBin });
|
|
@@ -17692,7 +18029,7 @@ async function runHostAgent(opts = {}) {
|
|
|
17692
18029
|
}
|
|
17693
18030
|
|
|
17694
18031
|
// packages/cli/dist/commands/coordinator.js
|
|
17695
|
-
import { existsSync as
|
|
18032
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync8 } from "node:fs";
|
|
17696
18033
|
import { resolve as resolve10 } from "node:path";
|
|
17697
18034
|
function parseListen(v) {
|
|
17698
18035
|
const s = v.trim();
|
|
@@ -17827,7 +18164,7 @@ function coordinatorMigrate(ctx, flags) {
|
|
|
17827
18164
|
throw new CliError(`Coordinator (pid ${holder}) is running on ${dataDir}; it migrated at start`);
|
|
17828
18165
|
}
|
|
17829
18166
|
const path = dbPath(dataDir);
|
|
17830
|
-
const existed =
|
|
18167
|
+
const existed = existsSync14(path);
|
|
17831
18168
|
mkdirSync8(dataDir, { recursive: true });
|
|
17832
18169
|
const db = openDb(path);
|
|
17833
18170
|
try {
|
|
@@ -17870,7 +18207,7 @@ function upOptions(flags, env) {
|
|
|
17870
18207
|
};
|
|
17871
18208
|
}
|
|
17872
18209
|
async function ensureLocalHost(coordinator, localUrl, configPath, name, fetchImpl) {
|
|
17873
|
-
if (
|
|
18210
|
+
if (existsSync15(configPath)) {
|
|
17874
18211
|
const cfg2 = loadConfig(configPath);
|
|
17875
18212
|
const known = coordinator.store.hosts.get(cfg2.hostId);
|
|
17876
18213
|
if (known && !known.revokedAt) {
|
|
@@ -18121,7 +18458,7 @@ async function login(ctx, flags, deps = {}) {
|
|
|
18121
18458
|
|
|
18122
18459
|
// packages/cli/dist/commands/doctor.js
|
|
18123
18460
|
import { execFile as execFile3 } from "node:child_process";
|
|
18124
|
-
import { existsSync as
|
|
18461
|
+
import { existsSync as existsSync16, readFileSync as readFileSync9 } from "node:fs";
|
|
18125
18462
|
function defaultDoctorDeps(ctx) {
|
|
18126
18463
|
const dataDir = ctx.paths?.dataDir ?? defaultDataDir(ctx.env);
|
|
18127
18464
|
return {
|
|
@@ -18129,7 +18466,7 @@ function defaultDoctorDeps(ctx) {
|
|
|
18129
18466
|
detectPi: () => detectPi(ctx.env.SWITCHYARD_PI_BIN ?? "pi"),
|
|
18130
18467
|
gitVersion: () => new Promise((resolve13) => execFile3("git", ["--version"], (err, stdout) => resolve13(err ? null : stdout.trim()))),
|
|
18131
18468
|
hostConfigPaths: [defaultConfigPath(ctx.env), standaloneHostPaths(dataDir).configPath],
|
|
18132
|
-
readFile: (p) =>
|
|
18469
|
+
readFile: (p) => existsSync16(p) ? readFileSync9(p, "utf8") : null
|
|
18133
18470
|
};
|
|
18134
18471
|
}
|
|
18135
18472
|
function findHostConfig(deps) {
|
|
@@ -18282,8 +18619,8 @@ function printDoctor(ctx, results) {
|
|
|
18282
18619
|
|
|
18283
18620
|
// packages/cli/dist/commands/herdr.js
|
|
18284
18621
|
import { spawn as spawn5 } from "node:child_process";
|
|
18285
|
-
import { closeSync as closeSync2, existsSync as
|
|
18286
|
-
import { join as
|
|
18622
|
+
import { closeSync as closeSync2, existsSync as existsSync20, openSync as openSync2, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
|
|
18623
|
+
import { join as join22 } from "node:path";
|
|
18287
18624
|
|
|
18288
18625
|
// packages/cli/dist/context.js
|
|
18289
18626
|
function defaultCtx() {
|
|
@@ -18334,10 +18671,10 @@ function ago(iso, now = Date.now()) {
|
|
|
18334
18671
|
}
|
|
18335
18672
|
|
|
18336
18673
|
// packages/cli/dist/herdr/client.js
|
|
18337
|
-
import { existsSync as
|
|
18674
|
+
import { existsSync as existsSync17, readdirSync } from "node:fs";
|
|
18338
18675
|
import { createConnection as createConnection3 } from "node:net";
|
|
18339
18676
|
import { homedir as homedir6 } from "node:os";
|
|
18340
|
-
import { join as
|
|
18677
|
+
import { join as join18 } from "node:path";
|
|
18341
18678
|
var HerdrUnavailable = class extends Error {
|
|
18342
18679
|
constructor(message) {
|
|
18343
18680
|
super(message);
|
|
@@ -18355,15 +18692,15 @@ var HerdrError = class extends Error {
|
|
|
18355
18692
|
function herdrSocketPath(env = process.env, home = homedir6()) {
|
|
18356
18693
|
if (env.HERDR_SOCKET_PATH)
|
|
18357
18694
|
return env.HERDR_SOCKET_PATH;
|
|
18358
|
-
const base =
|
|
18695
|
+
const base = join18(home, ".config", "herdr");
|
|
18359
18696
|
if (env.HERDR_SESSION)
|
|
18360
|
-
return
|
|
18361
|
-
const dflt =
|
|
18362
|
-
if (
|
|
18697
|
+
return join18(base, "sessions", env.HERDR_SESSION, "herdr.sock");
|
|
18698
|
+
const dflt = join18(base, "herdr.sock");
|
|
18699
|
+
if (existsSync17(dflt))
|
|
18363
18700
|
return dflt;
|
|
18364
18701
|
let live = [];
|
|
18365
18702
|
try {
|
|
18366
|
-
live = readdirSync(
|
|
18703
|
+
live = readdirSync(join18(base, "sessions")).map((s) => join18(base, "sessions", s, "herdr.sock")).filter((p) => existsSync17(p));
|
|
18367
18704
|
} catch {
|
|
18368
18705
|
live = [];
|
|
18369
18706
|
}
|
|
@@ -18476,11 +18813,11 @@ function nextSeq() {
|
|
|
18476
18813
|
}
|
|
18477
18814
|
|
|
18478
18815
|
// packages/cli/dist/herdr/daemon.js
|
|
18479
|
-
import { existsSync as
|
|
18480
|
-
import { join as
|
|
18816
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
|
|
18817
|
+
import { join as join20 } from "node:path";
|
|
18481
18818
|
|
|
18482
18819
|
// packages/cli/dist/herdr/mirror.js
|
|
18483
|
-
import { basename as
|
|
18820
|
+
import { basename as basename4 } from "node:path";
|
|
18484
18821
|
var SOURCE = "switchyard";
|
|
18485
18822
|
var PENDING_MS = 15e3;
|
|
18486
18823
|
var NEW_SESSION_GRACE_MS = 5e3;
|
|
@@ -18512,10 +18849,10 @@ function projectOfKey(key, st) {
|
|
|
18512
18849
|
function spaceLabel(key, st) {
|
|
18513
18850
|
const w = st.workspaces.find((x) => x.id === key);
|
|
18514
18851
|
if (w) {
|
|
18515
|
-
const project2 = st.projects.find((p) => p.id === w.projectId)?.name ??
|
|
18852
|
+
const project2 = st.projects.find((p) => p.id === w.projectId)?.name ?? basename4(w.path);
|
|
18516
18853
|
let label = project2;
|
|
18517
18854
|
if (w.provenance !== "main")
|
|
18518
|
-
label += ` \xB7 ${w.branch ??
|
|
18855
|
+
label += ` \xB7 ${w.branch ?? basename4(w.path)}`;
|
|
18519
18856
|
const hosts = new Set(st.checkouts.filter((c) => c.projectId === w.projectId).map((c) => c.hostId));
|
|
18520
18857
|
if (hosts.size >= 2)
|
|
18521
18858
|
label += ` \xB7 ${hostName(st, w.hostId)}`;
|
|
@@ -18524,7 +18861,7 @@ function spaceLabel(key, st) {
|
|
|
18524
18861
|
const cwd = key.split(":").slice(2).join(":");
|
|
18525
18862
|
const s = st.sessions.find((x) => spaceKey(x) === key);
|
|
18526
18863
|
const project = s?.projectId && st.projects.find((p) => p.id === s.projectId)?.name || null;
|
|
18527
|
-
return cap2(`${project ??
|
|
18864
|
+
return cap2(`${project ?? basename4(cwd)} \xB7 ${basename4(cwd)}`);
|
|
18528
18865
|
}
|
|
18529
18866
|
function tabLabel(s, overrides = {}) {
|
|
18530
18867
|
return cap2(overrides[s.id] ?? s.title);
|
|
@@ -18763,27 +19100,27 @@ function planMirror(st, h, mem, now) {
|
|
|
18763
19100
|
}
|
|
18764
19101
|
|
|
18765
19102
|
// packages/cli/dist/herdr/util.js
|
|
18766
|
-
import { chmodSync as chmodSync5, existsSync as
|
|
19103
|
+
import { chmodSync as chmodSync5, existsSync as existsSync18, mkdirSync as mkdirSync9, writeFileSync as writeFileSync5 } from "node:fs";
|
|
18767
19104
|
import { homedir as homedir7 } from "node:os";
|
|
18768
|
-
import { join as
|
|
19105
|
+
import { join as join19 } from "node:path";
|
|
18769
19106
|
function herdrStateDir(env = process.env, home = homedir7()) {
|
|
18770
19107
|
const xdg = env.XDG_STATE_HOME?.trim();
|
|
18771
|
-
return
|
|
19108
|
+
return join19(xdg || join19(home, ".local", "state"), "switchyard-herdr");
|
|
18772
19109
|
}
|
|
18773
19110
|
function sessionStateDir(env = process.env) {
|
|
18774
|
-
const dir =
|
|
19111
|
+
const dir = join19(herdrStateDir(env), herdrSessionName(env));
|
|
18775
19112
|
mkdirSync9(dir, { recursive: true, mode: 448 });
|
|
18776
19113
|
return dir;
|
|
18777
19114
|
}
|
|
18778
19115
|
function stableLauncherPath(env = process.env) {
|
|
18779
|
-
return env.SWITCHYARD_HERDR_LAUNCHER ||
|
|
19116
|
+
return env.SWITCHYARD_HERDR_LAUNCHER || join19(herdrStateDir(env), "switchyard-herdr");
|
|
18780
19117
|
}
|
|
18781
19118
|
function writeStableLauncher(stateRoot, pluginRoot) {
|
|
18782
19119
|
if (pluginRoot.includes("'") || stateRoot.includes("'")) {
|
|
18783
19120
|
throw new CliError("Plugin path contains an apostrophe");
|
|
18784
19121
|
}
|
|
18785
19122
|
mkdirSync9(stateRoot, { recursive: true, mode: 448 });
|
|
18786
|
-
const path =
|
|
19123
|
+
const path = join19(stateRoot, "switchyard-herdr");
|
|
18787
19124
|
writeFileSync5(path, `#!/bin/sh
|
|
18788
19125
|
exec sh '${pluginRoot}/switchyard-herdr.sh' "$@"
|
|
18789
19126
|
`, {
|
|
@@ -18793,7 +19130,7 @@ exec sh '${pluginRoot}/switchyard-herdr.sh' "$@"
|
|
|
18793
19130
|
return path;
|
|
18794
19131
|
}
|
|
18795
19132
|
function launcherExists(env = process.env) {
|
|
18796
|
-
return
|
|
19133
|
+
return existsSync18(stableLauncherPath(env));
|
|
18797
19134
|
}
|
|
18798
19135
|
async function isAtShell(call, paneId) {
|
|
18799
19136
|
const info = (await call("pane.process_info", { pane_id: paneId }))?.process_info ?? {};
|
|
@@ -18867,7 +19204,7 @@ var MirrorDaemon = class {
|
|
|
18867
19204
|
return (this.deps.now ?? Date.now)();
|
|
18868
19205
|
}
|
|
18869
19206
|
get spacesFile() {
|
|
18870
|
-
return
|
|
19207
|
+
return join20(this.deps.stateDir, "spaces.json");
|
|
18871
19208
|
}
|
|
18872
19209
|
async start() {
|
|
18873
19210
|
this.load();
|
|
@@ -18992,10 +19329,10 @@ var MirrorDaemon = class {
|
|
|
18992
19329
|
const titleOf = (sid) => st.sessions.find((s) => s.id === sid)?.title ?? sid;
|
|
18993
19330
|
const launcher = this.deps.launcherPath;
|
|
18994
19331
|
const paneCmd = (sid) => launcherCommand(launcher, ["pane", sid]);
|
|
18995
|
-
const exists2 = this.deps.exists ??
|
|
19332
|
+
const exists2 = this.deps.exists ?? existsSync19;
|
|
18996
19333
|
const createdNow = {};
|
|
18997
19334
|
const fallbackCwd = () => {
|
|
18998
|
-
const dir =
|
|
19335
|
+
const dir = join20(this.deps.stateDir, "spaces");
|
|
18999
19336
|
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
19000
19337
|
return dir;
|
|
19001
19338
|
};
|
|
@@ -19221,7 +19558,7 @@ var MirrorDaemon = class {
|
|
|
19221
19558
|
|
|
19222
19559
|
// packages/cli/dist/herdr/new-session.js
|
|
19223
19560
|
import { readFileSync as readFileSync11 } from "node:fs";
|
|
19224
|
-
import { basename as
|
|
19561
|
+
import { basename as basename5, join as join21 } from "node:path";
|
|
19225
19562
|
|
|
19226
19563
|
// packages/cli/dist/herdr/pane.js
|
|
19227
19564
|
var LIVE = /* @__PURE__ */ new Set(["starting", "idle", "running", "waiting_input"]);
|
|
@@ -19848,7 +20185,7 @@ function workspaceItems(st, projectId) {
|
|
|
19848
20185
|
own.length > 0 ? plural(own.length, "session") : "",
|
|
19849
20186
|
w.git?.dirty ? "modified" : ""
|
|
19850
20187
|
].filter(Boolean).join(" \xB7 ");
|
|
19851
|
-
const label = `${w.branch ??
|
|
20188
|
+
const label = `${w.branch ?? basename5(w.path)}${w.provenance === "main" ? " (main checkout)" : ""}`;
|
|
19852
20189
|
return {
|
|
19853
20190
|
item: { label, detail, value: { kind: "existing", workspaceId: w.id } },
|
|
19854
20191
|
active: latest(own.map((x) => x.lastActivityAt)),
|
|
@@ -19895,7 +20232,7 @@ async function chooseSession(st, screen, opts = {}) {
|
|
|
19895
20232
|
if (pick?.kind === "existing") {
|
|
19896
20233
|
const w = st.workspaces.find((x) => x.id === pick.workspaceId);
|
|
19897
20234
|
if (w && w.provenance !== "main")
|
|
19898
|
-
c.push(w.branch ??
|
|
20235
|
+
c.push(w.branch ?? basename5(w.path));
|
|
19899
20236
|
} else if (pick?.kind === "new" && pick.branch) {
|
|
19900
20237
|
c.push(pick.branch);
|
|
19901
20238
|
}
|
|
@@ -20036,7 +20373,7 @@ async function newHere(ctx) {
|
|
|
20036
20373
|
const ws = snap.focused_workspace_id;
|
|
20037
20374
|
let key = null;
|
|
20038
20375
|
try {
|
|
20039
|
-
const raw = JSON.parse(readFileSync11(
|
|
20376
|
+
const raw = JSON.parse(readFileSync11(join21(sessionStateDir(ctx.env), "spaces.json"), "utf8"));
|
|
20040
20377
|
key = ws ? raw.spaces?.[ws]?.key ?? null : null;
|
|
20041
20378
|
} catch {
|
|
20042
20379
|
key = null;
|
|
@@ -20067,8 +20404,8 @@ async function herdrDaemon(ctx, flags) {
|
|
|
20067
20404
|
}
|
|
20068
20405
|
const launcherPath = stableLauncherPath(env);
|
|
20069
20406
|
const dir = sessionStateDir(env);
|
|
20070
|
-
const pidFile =
|
|
20071
|
-
const logFile =
|
|
20407
|
+
const pidFile = join22(dir, "daemon.pid");
|
|
20408
|
+
const logFile = join22(dir, "daemon.log");
|
|
20072
20409
|
const socket = herdrSocketPath(env);
|
|
20073
20410
|
const call = (method, params) => herdrCall(method, params, socket);
|
|
20074
20411
|
if (flags.once) {
|
|
@@ -20098,7 +20435,7 @@ async function herdrDaemon(ctx, flags) {
|
|
|
20098
20435
|
}
|
|
20099
20436
|
return;
|
|
20100
20437
|
}
|
|
20101
|
-
const running =
|
|
20438
|
+
const running = existsSync20(pidFile) ? Number(readFileSync12(pidFile, "utf8").trim()) : 0;
|
|
20102
20439
|
if (flags.ensure) {
|
|
20103
20440
|
if (running && pidAlive2(running))
|
|
20104
20441
|
return;
|
|
@@ -20166,16 +20503,16 @@ async function herdrStatus(ctx, flags) {
|
|
|
20166
20503
|
}
|
|
20167
20504
|
|
|
20168
20505
|
// packages/cli/dist/commands/host.js
|
|
20169
|
-
import { existsSync as
|
|
20506
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync12 } from "node:fs";
|
|
20170
20507
|
import { hostname as hostname4 } from "node:os";
|
|
20171
|
-
import { dirname as
|
|
20508
|
+
import { dirname as dirname12, join as join24, resolve as resolve12 } from "node:path";
|
|
20172
20509
|
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
20173
20510
|
|
|
20174
20511
|
// packages/cli/dist/commands/service.js
|
|
20175
20512
|
import { execFile as execFile4 } from "node:child_process";
|
|
20176
|
-
import { existsSync as
|
|
20513
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "node:fs";
|
|
20177
20514
|
import { homedir as homedir8 } from "node:os";
|
|
20178
|
-
import { dirname as
|
|
20515
|
+
import { dirname as dirname11, join as join23, resolve as resolve11 } from "node:path";
|
|
20179
20516
|
var LAUNCHD_LABEL = "com.switchyard.host";
|
|
20180
20517
|
var SYSTEMD_UNIT = "switchyard-host.service";
|
|
20181
20518
|
var TEMPLATES = {
|
|
@@ -20251,8 +20588,8 @@ WantedBy=default.target
|
|
|
20251
20588
|
`
|
|
20252
20589
|
};
|
|
20253
20590
|
var TEMPLATE_FILES = {
|
|
20254
|
-
launchd:
|
|
20255
|
-
systemd:
|
|
20591
|
+
launchd: join23("launchd", `${LAUNCHD_LABEL}.plist`),
|
|
20592
|
+
systemd: join23("systemd", SYSTEMD_UNIT)
|
|
20256
20593
|
};
|
|
20257
20594
|
function xmlEscape(s) {
|
|
20258
20595
|
return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
@@ -20264,8 +20601,8 @@ function systemdEscape(s, exec = true) {
|
|
|
20264
20601
|
return exec ? out.replaceAll("$", "$$$$") : out;
|
|
20265
20602
|
}
|
|
20266
20603
|
function loadTemplate(kind, dir = deployDir()) {
|
|
20267
|
-
const p =
|
|
20268
|
-
return
|
|
20604
|
+
const p = join23(dir, TEMPLATE_FILES[kind]);
|
|
20605
|
+
return existsSync21(p) ? readFileSync13(p, "utf8") : TEMPLATES[kind];
|
|
20269
20606
|
}
|
|
20270
20607
|
function renderService(kind, vars, template = TEMPLATES[kind]) {
|
|
20271
20608
|
for (const [k, v] of Object.entries(vars)) {
|
|
@@ -20296,7 +20633,7 @@ function serviceNames(instance) {
|
|
|
20296
20633
|
}
|
|
20297
20634
|
function serviceFilePath(kind, home = homedir8(), instance) {
|
|
20298
20635
|
const { label, unit } = serviceNames(instance);
|
|
20299
|
-
return kind === "launchd" ?
|
|
20636
|
+
return kind === "launchd" ? join23(home, "Library", "LaunchAgents", `${label}.plist`) : join23(home, ".config", "systemd", "user", unit);
|
|
20300
20637
|
}
|
|
20301
20638
|
function loadCommands(kind, file, uid, instance) {
|
|
20302
20639
|
const { label, unit } = serviceNames(instance);
|
|
@@ -20309,7 +20646,7 @@ function loadCommands(kind, file, uid, instance) {
|
|
|
20309
20646
|
["systemctl", "--user", "restart", unit]
|
|
20310
20647
|
];
|
|
20311
20648
|
}
|
|
20312
|
-
function stableNodePath(execPath, exists2 =
|
|
20649
|
+
function stableNodePath(execPath, exists2 = existsSync21) {
|
|
20313
20650
|
const m = execPath.match(/^(.*)\/Cellar\/node(?:@[^/]+)?\/[^/]+\/bin\/node$/);
|
|
20314
20651
|
if (m) {
|
|
20315
20652
|
const candidate = `${m[1]}/bin/node`;
|
|
@@ -20333,11 +20670,11 @@ async function installService(ctx, flags, deps = defaultInstallDeps()) {
|
|
|
20333
20670
|
throw new CliError("--instance needs --config and --state-dir, so the second host agent keeps its own enrollment and state");
|
|
20334
20671
|
}
|
|
20335
20672
|
const config = flags.config ?? defaultConfigPath(ctx.env, deps.home);
|
|
20336
|
-
if (!
|
|
20673
|
+
if (!existsSync21(config) && !flags.print) {
|
|
20337
20674
|
throw new CliError(`No host config at ${config}. Enroll first: switchyard host enroll --coordinator <url> --token <t>`);
|
|
20338
20675
|
}
|
|
20339
20676
|
const stateDir = flags.stateDir ? resolve11(flags.stateDir) : defaultStateDir(ctx.env, deps.platform, deps.home);
|
|
20340
|
-
const logDir =
|
|
20677
|
+
const logDir = join23(stateDir, "logs");
|
|
20341
20678
|
const content = renderService(kind, {
|
|
20342
20679
|
node: stableNodePath(process.execPath),
|
|
20343
20680
|
cli: cliEntry(),
|
|
@@ -20353,7 +20690,7 @@ async function installService(ctx, flags, deps = defaultInstallDeps()) {
|
|
|
20353
20690
|
ctx.out(content.trimEnd());
|
|
20354
20691
|
return { file, content };
|
|
20355
20692
|
}
|
|
20356
|
-
mkdirSync11(
|
|
20693
|
+
mkdirSync11(dirname11(file), { recursive: true });
|
|
20357
20694
|
mkdirSync11(logDir, { recursive: true, mode: 448 });
|
|
20358
20695
|
writeFileSync8(file, content, { mode: 420 });
|
|
20359
20696
|
ctx.out(`Wrote ${file}`);
|
|
@@ -20397,7 +20734,7 @@ async function hostEnroll(ctx, flags) {
|
|
|
20397
20734
|
}
|
|
20398
20735
|
async function hostRun(ctx, flags) {
|
|
20399
20736
|
const configPath = flags.config ?? defaultConfigPath(ctx.env);
|
|
20400
|
-
if (!
|
|
20737
|
+
if (!existsSync22(configPath)) {
|
|
20401
20738
|
ctx.err(`No host config at ${configPath} yet: waiting for it (with SWITCHYARD_HOST_ENROLL_SECRET and SWITCHYARD_COORDINATOR_URL set, the host enrolls itself).`);
|
|
20402
20739
|
}
|
|
20403
20740
|
const agent = await runHostAgent({
|
|
@@ -20412,13 +20749,13 @@ async function hostRun(ctx, flags) {
|
|
|
20412
20749
|
}
|
|
20413
20750
|
function hostBackup(ctx, out, flags) {
|
|
20414
20751
|
const stateDir = flags.stateDir ?? defaultStateDir(ctx.env);
|
|
20415
|
-
const src =
|
|
20416
|
-
if (!
|
|
20752
|
+
const src = join24(stateDir, "host.db");
|
|
20753
|
+
if (!existsSync22(src))
|
|
20417
20754
|
throw new CliError(`No host database at ${src}`);
|
|
20418
20755
|
const dest = resolve12(out);
|
|
20419
|
-
if (
|
|
20756
|
+
if (existsSync22(dest))
|
|
20420
20757
|
throw new CliError(`${dest} already exists`);
|
|
20421
|
-
mkdirSync12(
|
|
20758
|
+
mkdirSync12(dirname12(dest), { recursive: true });
|
|
20422
20759
|
const db = new DatabaseSync4(src);
|
|
20423
20760
|
try {
|
|
20424
20761
|
db.exec("PRAGMA busy_timeout = 5000");
|
|
@@ -20433,7 +20770,7 @@ async function hostStatus(ctx, flags) {
|
|
|
20433
20770
|
const configPath = flags.config ?? defaultConfigPath(ctx.env);
|
|
20434
20771
|
const stateDir = flags.stateDir ?? defaultStateDir(ctx.env);
|
|
20435
20772
|
const status = { configPath, stateDir };
|
|
20436
|
-
if (!
|
|
20773
|
+
if (!existsSync22(configPath)) {
|
|
20437
20774
|
status.enrolled = false;
|
|
20438
20775
|
} else {
|
|
20439
20776
|
const cfg = loadConfig(configPath);
|
|
@@ -20466,7 +20803,7 @@ async function hostStatus(ctx, flags) {
|
|
|
20466
20803
|
try {
|
|
20467
20804
|
const kind = serviceKindFor(process.platform);
|
|
20468
20805
|
const file = serviceFilePath(kind);
|
|
20469
|
-
status.service = { kind, file, installed:
|
|
20806
|
+
status.service = { kind, file, installed: existsSync22(file) };
|
|
20470
20807
|
} catch {
|
|
20471
20808
|
status.service = null;
|
|
20472
20809
|
}
|