@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.
Files changed (60) hide show
  1. package/README.md +1 -1
  2. package/dist/bridge/switchyard-terminal.js +96 -1
  3. package/dist/switchyard.mjs +463 -126
  4. package/package.json +1 -1
  5. package/web-dist/assets/{AutomationView-BIW_pO0M.js → AutomationView-GCthHkym.js} +2 -2
  6. package/web-dist/assets/{AutomationView-BIW_pO0M.js.map → AutomationView-GCthHkym.js.map} +1 -1
  7. package/web-dist/assets/{Badge-BiDp7NKI.js → Badge-CEMI3YyI.js} +2 -2
  8. package/web-dist/assets/{Badge-BiDp7NKI.js.map → Badge-CEMI3YyI.js.map} +1 -1
  9. package/web-dist/assets/{InteractionCard-CWxyDv81.js → InteractionCard-DaLqafmA.js} +2 -2
  10. package/web-dist/assets/{InteractionCard-CWxyDv81.js.map → InteractionCard-DaLqafmA.js.map} +1 -1
  11. package/web-dist/assets/{Menu-BDt1bxwz.js → Menu-BcKiEc6X.js} +2 -2
  12. package/web-dist/assets/{Menu-BDt1bxwz.js.map → Menu-BcKiEc6X.js.map} +1 -1
  13. package/web-dist/assets/{SegmentedControl-D-2BCZzd.js → SegmentedControl-BZPzNU08.js} +2 -2
  14. package/web-dist/assets/{SegmentedControl-D-2BCZzd.js.map → SegmentedControl-BZPzNU08.js.map} +1 -1
  15. package/web-dist/assets/{Tabs-ByiPklAw.js → Tabs-COH5EMql.js} +2 -2
  16. package/web-dist/assets/{Tabs-ByiPklAw.js.map → Tabs-COH5EMql.js.map} +1 -1
  17. package/web-dist/assets/{TextArea-BYr88SnV.js → TextArea-CeMEPZn4.js} +2 -2
  18. package/web-dist/assets/{TextArea-BYr88SnV.js.map → TextArea-CeMEPZn4.js.map} +1 -1
  19. package/web-dist/assets/{attention-Bya8XHKI.js → attention-BAdea2kA.js} +2 -2
  20. package/web-dist/assets/{attention-Bya8XHKI.js.map → attention-BAdea2kA.js.map} +1 -1
  21. package/web-dist/assets/{automations-DiXKRcnE.js → automations-DSFUzB0w.js} +2 -2
  22. package/web-dist/assets/{automations-DiXKRcnE.js.map → automations-DSFUzB0w.js.map} +1 -1
  23. package/web-dist/assets/{board-BrLaJwLJ.js → board-eQ3H8yCp.js} +2 -2
  24. package/web-dist/assets/{board-BrLaJwLJ.js.map → board-eQ3H8yCp.js.map} +1 -1
  25. package/web-dist/assets/code-D7JmYMKe.js +13 -0
  26. package/web-dist/assets/code-D7JmYMKe.js.map +1 -0
  27. package/web-dist/assets/{describe-DnvH6_fl.js → describe-C1YimBXJ.js} +2 -2
  28. package/web-dist/assets/{describe-DnvH6_fl.js.map → describe-C1YimBXJ.js.map} +1 -1
  29. package/web-dist/assets/dist-B_RpNOcK.js +56 -0
  30. package/web-dist/assets/dist-B_RpNOcK.js.map +1 -0
  31. package/web-dist/assets/{enroll-DUDpbJoB.js → enroll-1OCy5r50.js} +2 -2
  32. package/web-dist/assets/{enroll-DUDpbJoB.js.map → enroll-1OCy5r50.js.map} +1 -1
  33. package/web-dist/assets/{index-vIzH1m8u.js → index-BhpEXLav.js} +4 -4
  34. package/web-dist/assets/{index-vIzH1m8u.js.map → index-BhpEXLav.js.map} +1 -1
  35. package/web-dist/assets/index-DUeOwDZ6.css +2 -0
  36. package/web-dist/assets/{logic-xb-QF00E.js → logic-CBvFOyIo.js} +2 -2
  37. package/web-dist/assets/{logic-xb-QF00E.js.map → logic-CBvFOyIo.js.map} +1 -1
  38. package/web-dist/assets/{more-BP5x45cs.js → more-RjVyxPaz.js} +2 -2
  39. package/web-dist/assets/{more-BP5x45cs.js.map → more-RjVyxPaz.js.map} +1 -1
  40. package/web-dist/assets/{now--XjJC37b.js → now-CDjeFSLD.js} +2 -2
  41. package/web-dist/assets/{now--XjJC37b.js.map → now-CDjeFSLD.js.map} +1 -1
  42. package/web-dist/assets/{settings-D8T1esWq.js → settings-tH4UuIfJ.js} +2 -2
  43. package/web-dist/assets/{settings-D8T1esWq.js.map → settings-tH4UuIfJ.js.map} +1 -1
  44. package/web-dist/assets/{shared-CZ7fcu_B.js → shared-CRX6KSBm.js} +2 -2
  45. package/web-dist/assets/{shared-CZ7fcu_B.js.map → shared-CRX6KSBm.js.map} +1 -1
  46. package/web-dist/assets/states-DXozulGZ.js +2 -0
  47. package/web-dist/assets/states-DXozulGZ.js.map +1 -0
  48. package/web-dist/assets/{terminal-login-BJxV78au.js → terminal-login-CW2rE50W.js} +2 -2
  49. package/web-dist/assets/{terminal-login-BJxV78au.js.map → terminal-login-CW2rE50W.js.map} +1 -1
  50. package/web-dist/assets/{useNow-jxdoOC_1.js → useNow-C_B7JgMM.js} +2 -2
  51. package/web-dist/assets/{useNow-jxdoOC_1.js.map → useNow-C_B7JgMM.js.map} +1 -1
  52. package/web-dist/index.html +4 -4
  53. package/web-dist/sw.js +1 -1
  54. package/web-dist/assets/code-DKLO8bzz.js +0 -11
  55. package/web-dist/assets/code-DKLO8bzz.js.map +0 -1
  56. package/web-dist/assets/dist-BE_V9dwJ.js +0 -56
  57. package/web-dist/assets/dist-BE_V9dwJ.js.map +0 -1
  58. package/web-dist/assets/index-DvRcMGEe.css +0 -2
  59. package/web-dist/assets/states--geW2Ntl.js +0 -2
  60. package/web-dist/assets/states--geW2Ntl.js.map +0 -1
@@ -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 existsSync14 } from "node:fs";
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
- app.get("/api/sessions/importable", async (req) => {
9365
- const q = parse(ImportableQuery, req.query, "query");
9366
- requireConnected(ctx, q.hostId);
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(body.hostId);
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: body.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
- const s = get(idOf(req));
9525
+ let s = get(idOf(req));
9462
9526
  const body = parse(PromptRequest, req.body);
9463
- requireLiveSession(ctx, s);
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
- app.post("/api/sessions/:id/resume", async (req) => {
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 existsSync9, mkdirSync as mkdirSync6, rmSync as rmSync2 } from "node:fs";
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 join14 } from "node:path";
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 (existsSync9(sibling))
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 (join14(dir, name).length > MAX_SOCKET_PATH) {
14599
- dir = join14("/tmp", `switchyard-${process.getuid?.() ?? "u"}`);
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 join14(dir, name);
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 ?? join14(tmpdir2(), "switchyard"), "bridge");
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 existsSync11 } from "node:fs";
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 existsSync10, mkdirSync as mkdirSync7, openSync, readFileSync as readFileSync7 } from "node:fs";
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 dirname8, join as join15 } from "node:path";
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 (existsSync10(sibling))
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 (!existsSync10(holder))
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 ?? join15(tmpdir3(), "switchyard");
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 ?? join15(dirname8(socketPath), `${basename2(socketPath, ".sock")}.log`);
14957
- mkdirSync7(dirname8(logPath), { recursive: true, mode: 448 });
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 && !existsSync11(this._native.sessionFile)) {
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
- return data?.disposition ?? "started";
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
- out.push({
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 existsSync12, realpathSync as realpathSync2, statSync } from "node:fs";
16081
- import { join as join16, sep as sep3 } from "node:path";
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 (!existsSync12(p.workspacePath) || !statSync(p.workspacePath).isDirectory()) {
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 && existsSync12(nativeHandle.sessionFile)) {
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 = join16(piAgentDir(env), "sessions");
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 (!existsSync12(sessionFile)) {
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 existsSync13, mkdirSync as mkdirSync8 } from "node:fs";
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 = existsSync13(path);
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 (existsSync14(configPath)) {
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 existsSync15, readFileSync as readFileSync9 } from "node:fs";
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) => existsSync15(p) ? readFileSync9(p, "utf8") : null
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 existsSync19, openSync as openSync2, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "node:fs";
18286
- import { join as join21 } from "node:path";
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 existsSync16, readdirSync } from "node:fs";
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 join17 } from "node:path";
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 = join17(home, ".config", "herdr");
18695
+ const base = join18(home, ".config", "herdr");
18359
18696
  if (env.HERDR_SESSION)
18360
- return join17(base, "sessions", env.HERDR_SESSION, "herdr.sock");
18361
- const dflt = join17(base, "herdr.sock");
18362
- if (existsSync16(dflt))
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(join17(base, "sessions")).map((s) => join17(base, "sessions", s, "herdr.sock")).filter((p) => existsSync16(p));
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 existsSync18, mkdirSync as mkdirSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "node:fs";
18480
- import { join as join19 } from "node:path";
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 basename3 } from "node:path";
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 ?? basename3(w.path);
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 ?? basename3(w.path)}`;
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 ?? basename3(cwd)} \xB7 ${basename3(cwd)}`);
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 existsSync17, mkdirSync as mkdirSync9, writeFileSync as writeFileSync5 } from "node:fs";
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 join18 } from "node:path";
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 join18(xdg || join18(home, ".local", "state"), "switchyard-herdr");
19108
+ return join19(xdg || join19(home, ".local", "state"), "switchyard-herdr");
18772
19109
  }
18773
19110
  function sessionStateDir(env = process.env) {
18774
- const dir = join18(herdrStateDir(env), herdrSessionName(env));
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 || join18(herdrStateDir(env), "switchyard-herdr");
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 = join18(stateRoot, "switchyard-herdr");
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 existsSync17(stableLauncherPath(env));
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 join19(this.deps.stateDir, "spaces.json");
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 ?? existsSync18;
19332
+ const exists2 = this.deps.exists ?? existsSync19;
18996
19333
  const createdNow = {};
18997
19334
  const fallbackCwd = () => {
18998
- const dir = join19(this.deps.stateDir, "spaces");
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 basename4, join as join20 } from "node:path";
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 ?? basename4(w.path)}${w.provenance === "main" ? " (main checkout)" : ""}`;
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 ?? basename4(w.path));
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(join20(sessionStateDir(ctx.env), "spaces.json"), "utf8"));
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 = join21(dir, "daemon.pid");
20071
- const logFile = join21(dir, "daemon.log");
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 = existsSync19(pidFile) ? Number(readFileSync12(pidFile, "utf8").trim()) : 0;
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 existsSync21, mkdirSync as mkdirSync12 } from "node:fs";
20506
+ import { existsSync as existsSync22, mkdirSync as mkdirSync12 } from "node:fs";
20170
20507
  import { hostname as hostname4 } from "node:os";
20171
- import { dirname as dirname10, join as join23, resolve as resolve12 } from "node:path";
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 existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "node:fs";
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 dirname9, join as join22, resolve as resolve11 } from "node:path";
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: join22("launchd", `${LAUNCHD_LABEL}.plist`),
20255
- systemd: join22("systemd", SYSTEMD_UNIT)
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("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
@@ -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 = join22(dir, TEMPLATE_FILES[kind]);
20268
- return existsSync20(p) ? readFileSync13(p, "utf8") : TEMPLATES[kind];
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" ? join22(home, "Library", "LaunchAgents", `${label}.plist`) : join22(home, ".config", "systemd", "user", unit);
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 = existsSync20) {
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 (!existsSync20(config) && !flags.print) {
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 = join22(stateDir, "logs");
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(dirname9(file), { recursive: true });
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 (!existsSync21(configPath)) {
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 = join23(stateDir, "host.db");
20416
- if (!existsSync21(src))
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 (existsSync21(dest))
20756
+ if (existsSync22(dest))
20420
20757
  throw new CliError(`${dest} already exists`);
20421
- mkdirSync12(dirname10(dest), { recursive: true });
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 (!existsSync21(configPath)) {
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: existsSync21(file) };
20806
+ status.service = { kind, file, installed: existsSync22(file) };
20470
20807
  } catch {
20471
20808
  status.service = null;
20472
20809
  }