@sleds/cli 0.1.60 → 0.1.61

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 (2) hide show
  1. package/dist/sleds.mjs +1329 -454
  2. package/package.json +1 -1
package/dist/sleds.mjs CHANGED
@@ -797,9 +797,9 @@ var init_verbs = __esm(() => {
797
797
  id: "attach",
798
798
  group: "meter",
799
799
  want: "Put the spec, the prototype, a screenshot or a link on the item so the team can open it",
800
- mcp: "attach {project, name, content, mime?, kind?} · attach {project, url, title?} — intake takes the same as `attachments` when filing",
800
+ mcp: "attach {project, name, content, mime?, kind?, mock?} · attach {project, url, title?} — intake takes the same as `attachments` when filing",
801
801
  app: "Work › the item › Links",
802
- how: "The file itself lives in SLEDS, not only in the chat that made it: a spec as markdown, a prototype as one HTML page (opens and renders in its own tab), a screenshot — or a link to a doc, a Figma, a Loom. Up to 4 MB; the same name again replaces. Anyone on the board opens it from the item; the person who added it, or an admin, can remove it."
802
+ how: "The file itself lives in SLEDS, not only in the chat that made it: a spec as markdown, a prototype as one HTML page (opens and renders in its own tab), a screenshot — or a link to a doc, a Figma, a Loom. Up to 4 MB; the same name again replaces. Mark the prototype or screenshot that shows the change with `mock: true` — the item's Overview shows it as the proposal. Anyone on the board opens it from the item; the person who added it, or an admin, can remove it."
803
803
  },
804
804
  {
805
805
  id: "update",
@@ -1149,6 +1149,8 @@ function readForm(text) {
1149
1149
  const val = (f) => plain(f.value.join(" "));
1150
1150
  const one = (slot) => found.find((f) => f.slot === slot && val(f));
1151
1151
  const ask = one("ask");
1152
+ const summary = one("summary");
1153
+ const expected = one("expected");
1152
1154
  const users = one("users");
1153
1155
  const sid = one("sid");
1154
1156
  const loc = one("location");
@@ -1172,6 +1174,8 @@ function readForm(text) {
1172
1174
  return {
1173
1175
  form: def.name,
1174
1176
  ...ask ? { ask: val(ask) } : {},
1177
+ ...summary ? { summary: val(summary) } : {},
1178
+ ...expected ? { expected: val(expected) } : {},
1175
1179
  ...details ? { details } : {},
1176
1180
  ...touches ? { touches } : {},
1177
1181
  ...demand ? { demand } : {},
@@ -1196,6 +1200,8 @@ var init_forms = __esm(() => {
1196
1200
  [/^sids?\b/, "sid"],
1197
1201
  [/^location ids?\b/, "location"],
1198
1202
  [/^issue description\b/, "ask"],
1203
+ [/^summary\b|^title\b/, "summary"],
1204
+ [/^expected (?:behaviou?r|result|outcome)\b/, "expected"],
1199
1205
  [/^(?:number of )?clients? affected\b|^number of clients\b/, "clients"],
1200
1206
  [/\bimpacting revenue\b|\brevenue impact\b/, "revenue"]
1201
1207
  ],
@@ -1281,8 +1287,12 @@ function draftBrief(input) {
1281
1287
  const form = readForm(text);
1282
1288
  const parsed = sections(text);
1283
1289
  const preamble = parsed.preamble;
1284
- const byKey = form ? { touches: form.touches, demand: form.demand } : parsed.byKey;
1285
- const fromText = form?.ask ? form.ask : askFrom(text, preamble);
1290
+ const byKey = form ? {
1291
+ touches: form.touches,
1292
+ demand: form.demand,
1293
+ ...form.expected ? { done: form.expected } : {}
1294
+ } : parsed.byKey;
1295
+ const fromText = form?.summary ? form.summary : form?.ask ? form.ask : askFrom(text, preamble);
1286
1296
  const askText = a.ask?.trim() || (fromText ? titleOf(fromText) : undefined);
1287
1297
  if (!askText)
1288
1298
  return null;
@@ -3280,7 +3290,8 @@ function parseAttachments(raw) {
3280
3290
  mime,
3281
3291
  content,
3282
3292
  ...str5(a.kind) ? { kind: str5(a.kind) } : {},
3283
- ...a.encoding === "base64" ? { encoding: "base64" } : {}
3293
+ ...a.encoding === "base64" ? { encoding: "base64" } : {},
3294
+ ...typeof a.mock === "boolean" ? { mock: a.mock } : {}
3284
3295
  });
3285
3296
  }
3286
3297
  return out.slice(0, 12);
@@ -4777,7 +4788,11 @@ var init_server = __esm(() => {
4777
4788
  },
4778
4789
  mime: { type: "string", description: "text/markdown, text/html, image/png…" },
4779
4790
  content: { type: "string", description: "The file: text as is, or base64." },
4780
- encoding: { type: "string", enum: ["text", "base64"] }
4791
+ encoding: { type: "string", enum: ["text", "base64"] },
4792
+ mock: {
4793
+ type: "boolean",
4794
+ description: "true on the prototype or screenshot that shows the change — the item's Overview shows it as the proposal."
4795
+ }
4781
4796
  },
4782
4797
  required: ["name", "content"]
4783
4798
  }
@@ -5160,7 +5175,7 @@ var init_server = __esm(() => {
5160
5175
  },
5161
5176
  {
5162
5177
  name: "attach",
5163
- description: "Put a file or a link on an item so anyone on the board can open it — a spec or PRD as markdown, a prototype you made as one HTML page (it opens and renders in its own tab), a screenshot; or a URL (a doc, a Figma, a Loom) with `url` instead of a file. Pass the item's key (QW-142, from status, or what intake said). Files up to 4 MB; the same name again replaces the earlier one. `intake` takes the same files as `attachments` when filing.",
5178
+ description: "Put a file or a link on an item so anyone on the board can open it — a spec or PRD as markdown, a prototype you made as one HTML page (it opens and renders in its own tab), a screenshot; or a URL (a doc, a Figma, a Loom) with `url` instead of a file. Pass the item's key (QW-142, from status, or what intake said). Files up to 4 MB; the same name again replaces the earlier one. Mark the prototype or screenshot that shows the change with `mock: true` — the item's Overview shows it as the proposal. `intake` takes the same files as `attachments` when filing.",
5164
5179
  inputSchema: {
5165
5180
  type: "object",
5166
5181
  properties: {
@@ -5184,7 +5199,11 @@ var init_server = __esm(() => {
5184
5199
  type: "string",
5185
5200
  description: "The file: text as is, or base64 with encoding base64."
5186
5201
  },
5187
- encoding: { type: "string", enum: ["text", "base64"] }
5202
+ encoding: { type: "string", enum: ["text", "base64"] },
5203
+ mock: {
5204
+ type: "boolean",
5205
+ description: "true on the prototype or screenshot that shows the change — the item's Overview shows it as the proposal. false clears the mark; omitted, a replaced file keeps its mark."
5206
+ }
5188
5207
  },
5189
5208
  required: ["project"]
5190
5209
  }
@@ -5708,7 +5727,7 @@ var init_agent = __esm(() => {
5708
5727
  REPLY_RULES = [
5709
5728
  "How to answer this reply:",
5710
5729
  '- If it answers nothing and changes nothing — thanks, "noted", "copy", "will do", or people talking to each other — set quiet to true and post nothing.',
5711
- "- Never ask a question the thread already asked. If it is still open, say only what is still needed, once, in a few words.",
5730
+ "- Never ask a question the thread already asked — by you or by anyone — and never ask one the thread has already answered, in anyone's reply. Read every reply in the thread as an answer first.",
5712
5731
  "- If someone in the thread already gave the cause or a fix, do not ask the poster for details it makes moot. Say in one line what is left, or set quiet.",
5713
5732
  "- Keep it to what changed and what is next. No recap of the ask."
5714
5733
  ].join(`
@@ -6731,12 +6750,14 @@ var init_sentry = () => {};
6731
6750
  var init_outside = () => {};
6732
6751
 
6733
6752
  // ../../packages/shared/src/pack/pack.ts
6753
+ var LANDING_GAP_MS;
6734
6754
  var init_pack = __esm(() => {
6735
6755
  init_flags();
6736
6756
  init_profile();
6737
6757
  init_sentry();
6738
6758
  init_outside();
6739
6759
  init_setup();
6760
+ LANDING_GAP_MS = 10 * 60000;
6740
6761
  });
6741
6762
  // ../../packages/shared/src/pack/prHeader.ts
6742
6763
  var PR_HEADER_OPEN = "<!-- sleds:header -->", PR_HEADER_CLOSE = "<!-- /sleds:header -->";
@@ -6819,6 +6840,11 @@ var init_musher = __esm(() => {
6819
6840
  init_plan4();
6820
6841
  });
6821
6842
 
6843
+ // ../../packages/shared/src/route/handoff.ts
6844
+ var init_handoff = __esm(() => {
6845
+ init_route();
6846
+ });
6847
+
6822
6848
  // ../../packages/shared/src/route/doneChecks.ts
6823
6849
  function shellWords(s) {
6824
6850
  const out = [];
@@ -7034,6 +7060,55 @@ var init_doneChecks = __esm(() => {
7034
7060
  // ../../packages/shared/src/route/routeFix.ts
7035
7061
  var init_routeFix = () => {};
7036
7062
 
7063
+ // ../../packages/shared/src/route/reshape.ts
7064
+ var init_reshape = __esm(() => {
7065
+ init_route();
7066
+ });
7067
+
7068
+ // ../../packages/shared/src/route/say.ts
7069
+ var VERB_MODAL, ORDER_VERB, each = (a, b, f) => a.flatMap((x) => b.map((y) => f(x, y))), FORMS2;
7070
+ var init_say = __esm(() => {
7071
+ init_reshape();
7072
+ init_route();
7073
+ VERB_MODAL = String.raw`(?:(?:should|must|can|has\s+to|have\s+to|needs?\s+to)\s+)?`;
7074
+ ORDER_VERB = String.raw`(?:${VERB_MODAL}(?:comes?|runs?|go|goes|starts?|be|is|are)\s+)?`;
7075
+ FORMS2 = [
7076
+ {
7077
+ kind: "pair",
7078
+ re: /^(?:merge|fold|combine)\s+(.+?)\s+into\s+(.+)$/i,
7079
+ ops: (a, b) => each(a, b, (absorb, into) => ({ op: "merge", absorb, into }))
7080
+ },
7081
+ { kind: "split", re: /^split\s+(.+?)(?:\s*,?\s+(?:moving|move|taking|with|off)\s+(.+))?$/i },
7082
+ { kind: "rename", re: /^(?:rename|retitle)\s+(.+?)\s+(?:to|as)\s+(.+)$/i },
7083
+ { kind: "move", re: /^move\s+(.+?)\s+from\s+(.+?)\s+(?:to|into|onto)\s+(.+)$/i },
7084
+ {
7085
+ kind: "pair",
7086
+ re: /^unblock\s+(.+?)\s+from\s+(.+)$/i,
7087
+ ops: (a, b) => each(a, b, (node, on) => ({ op: "unwait", node, on }))
7088
+ },
7089
+ {
7090
+ kind: "pair",
7091
+ re: /^(.+?)\s+(?:no\s+longer|doesn't|does\s+not|don't|do\s+not|shouldn't|should\s+not|needn't|need\s+not|(?:should\s+|can\s+)?stops?)\s+(?:waits?|waiting|depends?|depending)\s+(?:on|for|upon)\s+(.+)$/i,
7092
+ ops: (a, b) => each(a, b, (node, on) => ({ op: "unwait", node, on }))
7093
+ },
7094
+ {
7095
+ kind: "pair",
7096
+ re: new RegExp(String.raw`^(?:make\s+)?(.+?)\s+${VERB_MODAL}(?:waits?|depends?)\s+(?:on|for|upon)\s+(.+)$`, "i"),
7097
+ ops: (a, b) => each(a, b, (node, on) => ({ op: "wait", node, on }))
7098
+ },
7099
+ {
7100
+ kind: "pair",
7101
+ re: new RegExp(String.raw`^(.+?)\s+${ORDER_VERB}after\s+(.+)$`, "i"),
7102
+ ops: (a, b) => each(a, b, (node, on) => ({ op: "wait", node, on }))
7103
+ },
7104
+ {
7105
+ kind: "pair",
7106
+ re: new RegExp(String.raw`^(.+?)\s+${ORDER_VERB}before\s+(.+)$`, "i"),
7107
+ ops: (a, b) => each(b, a, (node, on) => ({ op: "wait", node, on }))
7108
+ }
7109
+ ];
7110
+ });
7111
+
7037
7112
  // ../../packages/shared/src/route/engine.ts
7038
7113
  function seatSlots(atOnce) {
7039
7114
  if (typeof atOnce !== "number" || !Number.isFinite(atOnce))
@@ -7278,6 +7353,26 @@ function draftPrompt(o) {
7278
7353
  ${claim.pack.trim()}
7279
7354
  `;
7280
7355
  }
7356
+ function mockPrompt(o) {
7357
+ const base = o.base ?? "main";
7358
+ const lines = [
7359
+ `You are drawing a mock for ${o.item} — "${o.title}"${o.engine ? ` — started by ${o.engine}` : ""}. Nobody is at the keyboard: a person signs the mock before the build gets its nodes.`,
7360
+ "",
7361
+ "- Draw one self-contained HTML file: CSS and JS inline, no other files, nothing loaded from the network. Build it from the repo's real design tokens and components (read them in the code: colors, type, spacing, radii) and data shaped like the real thing.",
7362
+ "- Draw it on the screen the change lives on: find that page or component in this checkout and show it as it is today, with the change in place, so a person can compare it with the product. If the change has no screen of its own, draw the one a person sees it from.",
7363
+ `- Write the HTML only to \`${o.out}\` — that one file, outside this checkout. Write nothing anywhere else.`,
7364
+ `- Change no code: this checkout is \`origin/${base}\`, for reading. Do not edit, create or delete a file in it, and make no branch, commit, push or PR. The engine checks: a checkout left changed, or a commit, fails the run and nothing is attached.`,
7365
+ "- Ask nobody: a mock run cannot park. Where the brief leaves something open, draw the plainest reading and say it in a short note inside the HTML.",
7366
+ "- Once the file is written, stop. The engine reads it and attaches it to the item as its mock, marked as the next version."
7367
+ ];
7368
+ return `${lines.join(`
7369
+ `)}
7370
+
7371
+ ---
7372
+
7373
+ ${o.pack.trim()}
7374
+ `;
7375
+ }
7281
7376
  function quote(text) {
7282
7377
  return text.trim().split(`
7283
7378
  `).map((l) => l ? `> ${l}` : ">");
@@ -7542,7 +7637,7 @@ function eventPrompt(o) {
7542
7637
  return reviewPrompt({ ...o, event });
7543
7638
  return ciTwicePrompt({ ...o, event });
7544
7639
  }
7545
- var DEFAULT_SEAT = "\x00default", MAX_SEAT_AT_ONCE = 16, HOLD_DEFAULTS, DRAFT_NODE = "draft", BRANCH_FIELDS, PR_TITLE_FIELDS, ENGINE_MCP_SERVER = "sleds", RUN_TOKEN_ENV = "SLEDS_RUN_TOKEN", NO_RUN_TOKEN = "no run token from SLEDS: this session acts as the seat", SANDBOX_DOMAIN, SANDBOX_DEFAULT_ALLOW, toolName = (name, engineMcp) => engineMcp ? `mcp__${ENGINE_MCP_SERVER}__${name}` : name, ENGINE_MARK = "<!-- sleds:engine -->", VISUAL_PATH, APPROACH_SHAPE, AUTONOMY_SAYS, IRREVERSIBLE = "deleting or migrating data in place; deploying outside the playbook's window; anything that leaves the org (a message to a customer, a publish, a release, an email); spend past the band or the allowance; force-pushing or deleting a branch someone else holds; granting or removing access; changing the brief. When in doubt, it is irreversible.", oneLine = (s, max) => s.replace(/\s+/g, " ").trim().slice(0, max), VERDICT_SHAPE, DECIDER_RULES, codes = (xs) => xs.map((x) => `\`${x}\``).join(", "), RESOLVE_MAX_FILES = 8;
7640
+ var DEFAULT_SEAT = "\x00default", MAX_SEAT_AT_ONCE = 16, HOLD_DEFAULTS, DRAFT_NODE = "draft", MOCK_NODE = "mock", BRANCH_FIELDS, PR_TITLE_FIELDS, ENGINE_MCP_SERVER = "sleds", RUN_TOKEN_ENV = "SLEDS_RUN_TOKEN", NO_RUN_TOKEN = "no run token from SLEDS: this session acts as the seat", SANDBOX_DOMAIN, SANDBOX_DEFAULT_ALLOW, toolName = (name, engineMcp) => engineMcp ? `mcp__${ENGINE_MCP_SERVER}__${name}` : name, ENGINE_MARK = "<!-- sleds:engine -->", VISUAL_PATH, APPROACH_SHAPE, AUTONOMY_SAYS, IRREVERSIBLE = "deleting or migrating data in place; deploying outside the playbook's window; anything that leaves the org (a message to a customer, a publish, a release, an email); spend past the band or the allowance; force-pushing or deleting a branch someone else holds; granting or removing access; changing the brief. When in doubt, it is irreversible.", oneLine = (s, max) => s.replace(/\s+/g, " ").trim().slice(0, max), VERDICT_SHAPE, DECIDER_RULES, codes = (xs) => xs.map((x) => `\`${x}\``).join(", "), RESOLVE_MAX_FILES = 8;
7546
7641
  var init_engine = __esm(() => {
7547
7642
  init_prHeader();
7548
7643
  init_route();
@@ -7727,6 +7822,18 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
7727
7822
  return { ...settings };
7728
7823
  const { [BASE_URL_KEY]: _url, [AUTH_TOKEN_KEY]: _token, ...rest } = settings.env;
7729
7824
  return { ...settings, env: rest };
7825
+ }, bareUrl = (u) => u.trim().replace(/[./]+$/, "").toLowerCase(), isSledsProxyEnv = (settings, proxyUrls = []) => {
7826
+ const env = envOf(settings);
7827
+ const url = env[BASE_URL_KEY];
7828
+ const token = env[AUTH_TOKEN_KEY];
7829
+ if (typeof token === "string" && isSeatToken(token.trim()))
7830
+ return true;
7831
+ if (typeof url !== "string")
7832
+ return false;
7833
+ return proxyUrls.some((p) => p !== undefined && bareUrl(p) === bareUrl(url));
7834
+ }, hasGatewayEnv = (settings) => {
7835
+ const env = envOf(settings);
7836
+ return BASE_URL_KEY in env || AUTH_TOKEN_KEY in env;
7730
7837
  }, readProxy = (settings) => {
7731
7838
  const env = envOf(settings);
7732
7839
  const url = env[BASE_URL_KEY];
@@ -7734,7 +7841,7 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
7734
7841
  if (typeof url !== "string" || typeof token !== "string")
7735
7842
  return null;
7736
7843
  return { url, token };
7737
- }, STATUS_LINE_KEY = "statusLine", statusLineEntry = (command) => ({ type: "command", command }), isSledsStatusLine = (value) => isObject5(value) && value.type === "command" && typeof value.command === "string" && /\bstatusline\b/.test(value.command), applyStatusLine = (settings, command) => {
7844
+ }, STATUS_LINE_KEY = "statusLine", statusLineEntry = (command) => ({ type: "command", command }), invokesSleds = (command) => /\bsleds\b/.test(command), isSledsStatusLine = (value) => isObject5(value) && value.type === "command" && typeof value.command === "string" && invokesSleds(value.command) && /\bstatusline\b/.test(value.command), applyStatusLine = (settings, command) => {
7738
7845
  const current = settings[STATUS_LINE_KEY];
7739
7846
  const prior = current === undefined || isSledsStatusLine(current) ? undefined : current;
7740
7847
  return { settings: { ...settings, [STATUS_LINE_KEY]: statusLineEntry(command) }, prior };
@@ -7743,11 +7850,12 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
7743
7850
  return { ...settings };
7744
7851
  const { [STATUS_LINE_KEY]: _ours, ...rest } = settings;
7745
7852
  return prior === undefined ? rest : { ...rest, [STATUS_LINE_KEY]: prior };
7746
- }, HOOKS_KEY = "hooks", NOTIFICATION_EVENT = "Notification", SEAT_EVENT_MATCHER = "quota_auto_resume_fired|quota_auto_resume_stale|quota_auto_resume_disabled", SEAT_EVENT_TIMEOUT_S = 10, isSledsSeatHook = (entry) => {
7853
+ }, HOOKS_KEY = "hooks", NOTIFICATION_EVENT = "Notification", SEAT_EVENT_MATCHER = "quota_auto_resume_fired|quota_auto_resume_stale|quota_auto_resume_disabled", SEAT_EVENT_TIMEOUT_S = 10, isSledsHookFor = (sub) => (entry) => {
7747
7854
  if (!isObject5(entry) || !Array.isArray(entry.hooks))
7748
7855
  return false;
7749
- return entry.hooks.some((h) => isObject5(h) && h.type === "command" && typeof h.command === "string" && /\bseat-event\b/.test(h.command));
7750
- }, seatHookEntry = (command) => ({
7856
+ const re = new RegExp(`\\b${sub}\\b`);
7857
+ return entry.hooks.some((h) => isObject5(h) && h.type === "command" && typeof h.command === "string" && invokesSleds(h.command) && re.test(h.command));
7858
+ }, isSledsSeatHook, seatHookEntry = (command) => ({
7751
7859
  matcher: SEAT_EVENT_MATCHER,
7752
7860
  hooks: [{ type: "command", command, timeout: SEAT_EVENT_TIMEOUT_S }]
7753
7861
  }), notificationHooks = (settings) => {
@@ -7778,11 +7886,7 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
7778
7886
  if (!list.some(isSledsSeatHook))
7779
7887
  return { ...settings };
7780
7888
  return withNotificationHooks(settings, list.filter((e) => !isSledsSeatHook(e)));
7781
- }, hasSeatHook = (settings) => notificationHooks(settings).some(isSledsSeatHook), POST_TOOL_EVENT = "PostToolUse", SPEC_EVENT_MATCHER = "Write|Edit|MultiEdit", SPEC_EVENT_TIMEOUT_S = 5, isSledsSpecHook = (entry) => {
7782
- if (!isObject5(entry) || !Array.isArray(entry.hooks))
7783
- return false;
7784
- return entry.hooks.some((h) => isObject5(h) && h.type === "command" && typeof h.command === "string" && /\bspec-event\b/.test(h.command));
7785
- }, specHookEntry = (command) => ({
7889
+ }, hasSeatHook = (settings) => notificationHooks(settings).some(isSledsSeatHook), POST_TOOL_EVENT = "PostToolUse", SPEC_EVENT_MATCHER = "Write|Edit|MultiEdit", SPEC_EVENT_TIMEOUT_S = 5, isSledsSpecHook, specHookEntry = (command) => ({
7786
7890
  matcher: SPEC_EVENT_MATCHER,
7787
7891
  hooks: [{ type: "command", command, timeout: SPEC_EVENT_TIMEOUT_S }]
7788
7892
  }), eventHooks = (settings, event) => {
@@ -7813,11 +7917,7 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
7813
7917
  if (!list.some(isSledsSpecHook))
7814
7918
  return { ...settings };
7815
7919
  return withEventHooks(settings, POST_TOOL_EVENT, list.filter((e) => !isSledsSpecHook(e)));
7816
- }, hasSpecHook = (settings) => eventHooks(settings, POST_TOOL_EVENT).some(isSledsSpecHook), PROMPT_EVENT = "UserPromptSubmit", PROMPT_EVENT_TIMEOUT_S = 3, isSledsPromptHook = (entry) => {
7817
- if (!isObject5(entry) || !Array.isArray(entry.hooks))
7818
- return false;
7819
- return entry.hooks.some((h) => isObject5(h) && h.type === "command" && typeof h.command === "string" && /\bprompt-event\b/.test(h.command));
7820
- }, promptHookEntry = (command) => ({
7920
+ }, hasSpecHook = (settings) => eventHooks(settings, POST_TOOL_EVENT).some(isSledsSpecHook), PROMPT_EVENT = "UserPromptSubmit", PROMPT_EVENT_TIMEOUT_S = 3, isSledsPromptHook, promptHookEntry = (command) => ({
7821
7921
  hooks: [{ type: "command", command, timeout: PROMPT_EVENT_TIMEOUT_S }]
7822
7922
  }), applyPromptHook = (settings, command) => {
7823
7923
  const list = eventHooks(settings, PROMPT_EVENT);
@@ -7830,12 +7930,7 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
7830
7930
  if (!list.some(isSledsPromptHook))
7831
7931
  return { ...settings };
7832
7932
  return withEventHooks(settings, PROMPT_EVENT, list.filter((e) => !isSledsPromptHook(e)));
7833
- }, hasPromptHook = (settings) => eventHooks(settings, PROMPT_EVENT).some(isSledsPromptHook), SESSION_EVENT = "SessionStart", SESSION_EVENT_TIMEOUT_S = 3, STOP_EVENT = "Stop", STOP_EVENT_TIMEOUT_S = 3, isSledsHookFor = (sub) => (entry) => {
7834
- if (!isObject5(entry) || !Array.isArray(entry.hooks))
7835
- return false;
7836
- const re = new RegExp(`\\b${sub}\\b`);
7837
- return entry.hooks.some((h) => isObject5(h) && h.type === "command" && typeof h.command === "string" && re.test(h.command));
7838
- }, isSledsSessionHook, isSledsStopHook, sessionHookEntry = (command) => ({
7933
+ }, hasPromptHook = (settings) => eventHooks(settings, PROMPT_EVENT).some(isSledsPromptHook), SESSION_EVENT = "SessionStart", SESSION_EVENT_TIMEOUT_S = 3, STOP_EVENT = "Stop", STOP_EVENT_TIMEOUT_S = 3, isSledsSessionHook, isSledsStopHook, sessionHookEntry = (command) => ({
7839
7934
  hooks: [{ type: "command", command, timeout: SESSION_EVENT_TIMEOUT_S }]
7840
7935
  }), stopHookEntry = (command) => ({
7841
7936
  hooks: [{ type: "command", command, timeout: STOP_EVENT_TIMEOUT_S }]
@@ -7890,6 +7985,9 @@ var BASE_URL_KEY = "ANTHROPIC_BASE_URL", AUTH_TOKEN_KEY = "ANTHROPIC_AUTH_TOKEN"
7890
7985
  var init_settings = __esm(() => {
7891
7986
  LEADING_EXPORTS = /^(?:[A-Za-z_][A-Za-z0-9_]*=(?:'[^']*'|\S+)\s+)*/;
7892
7987
  ORG_EXPORT = /(?:^|\s)SLEDS_ORG=(?:'[^']*'|\S+)(?=\s)/;
7988
+ isSledsSeatHook = isSledsHookFor("seat-event");
7989
+ isSledsSpecHook = isSledsHookFor("spec-event");
7990
+ isSledsPromptHook = isSledsHookFor("prompt-event");
7893
7991
  isSledsSessionHook = isSledsHookFor("session-event");
7894
7992
  isSledsStopHook = isSledsHookFor("stop-event");
7895
7993
  isSledsPermissionHook = isSledsHookFor("permission-event");
@@ -7946,6 +8044,9 @@ var configDir = (home, env = process.env) => {
7946
8044
  cfg.schedule = schedule;
7947
8045
  if (raw.priorStatusLine !== undefined)
7948
8046
  cfg.priorStatusLine = raw.priorStatusLine;
8047
+ const priorGatewayEnv = parsePriorEnv(raw.priorGatewayEnv);
8048
+ if (priorGatewayEnv !== undefined)
8049
+ cfg.priorGatewayEnv = priorGatewayEnv;
7949
8050
  if (raw.wiring === "seat-first" || raw.wiring === "proxy-always")
7950
8051
  cfg.wiring = raw.wiring;
7951
8052
  if (raw.agentKind === "claude-code" || raw.agentKind === "codex")
@@ -8114,9 +8215,11 @@ var init_config = __esm(() => {
8114
8215
  });
8115
8216
 
8116
8217
  // src/meter.ts
8218
+ import { createHash } from "node:crypto";
8117
8219
  import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync6 } from "node:fs";
8118
8220
  import { join as join6 } from "node:path";
8119
- var METER_CACHE_TTL_MS = 1e4, METER_TIMEOUT_MS = 1500, isObject7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), num4 = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined, meterCachePath = (home) => join6(configDir(home), "meter.json"), readMeterCache = (home, now) => {
8221
+ var METER_CACHE_TTL_MS = 1e4, METER_TIMEOUT_MS = 1500, isObject7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v), num4 = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined, meterCachePath = (home) => join6(configDir(home), "meter.json"), meterCacheKey = (src) => createHash("sha256").update(`${src.proxyUrl}
8222
+ ${src.token}`).digest("hex").slice(0, 16), readMeterCache = (home, now, source) => {
8120
8223
  const p = meterCachePath(home);
8121
8224
  if (!existsSync6(p))
8122
8225
  return null;
@@ -8124,6 +8227,8 @@ var METER_CACHE_TTL_MS = 1e4, METER_TIMEOUT_MS = 1500, isObject7 = (v) => typeof
8124
8227
  const raw = JSON.parse(readFileSync6(p, "utf8"));
8125
8228
  if (!isObject7(raw))
8126
8229
  return null;
8230
+ if (source !== undefined && raw.source !== source)
8231
+ return null;
8127
8232
  const fetchedAt = num4(raw.fetchedAt);
8128
8233
  if (fetchedAt === undefined || now - fetchedAt >= METER_CACHE_TTL_MS || fetchedAt > now)
8129
8234
  return null;
@@ -8131,12 +8236,12 @@ var METER_CACHE_TTL_MS = 1e4, METER_TIMEOUT_MS = 1500, isObject7 = (v) => typeof
8131
8236
  } catch {
8132
8237
  return null;
8133
8238
  }
8134
- }, writeMeterCache = (home, meter, now) => {
8239
+ }, writeMeterCache = (home, meter, now, source) => {
8135
8240
  const dir = configDir(home);
8136
8241
  mkdirSync6(dir, { recursive: true, mode: 448 });
8137
8242
  const target = meterCachePath(home);
8138
8243
  const tmp = `${target}.${process.pid}.tmp`;
8139
- const file = { fetchedAt: now, meter };
8244
+ const file = { fetchedAt: now, meter, ...source ? { source } : {} };
8140
8245
  writeFileSync6(tmp, `${JSON.stringify(file)}
8141
8246
  `, { mode: 384 });
8142
8247
  renameSync3(tmp, target);
@@ -8167,16 +8272,17 @@ var METER_CACHE_TTL_MS = 1e4, METER_TIMEOUT_MS = 1500, isObject7 = (v) => typeof
8167
8272
  return proxyUrl && token ? { proxyUrl, token } : null;
8168
8273
  }, loadMeter = async (o) => {
8169
8274
  const now = o.now ?? Date.now();
8170
- const cached = readMeterCache(o.home, now);
8171
- if (cached)
8172
- return cached;
8173
- const src = meterSource(o.home);
8275
+ const src = meterSource(o.home, undefined, { env: o.env });
8174
8276
  if (!src)
8175
8277
  return null;
8278
+ const key = meterCacheKey(src);
8279
+ const cached = readMeterCache(o.home, now, key);
8280
+ if (cached)
8281
+ return cached;
8176
8282
  const fresh = await fetchMeter({ ...src, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs });
8177
8283
  if (fresh) {
8178
8284
  try {
8179
- writeMeterCache(o.home, fresh, now);
8285
+ writeMeterCache(o.home, fresh, now, key);
8180
8286
  } catch {}
8181
8287
  }
8182
8288
  return fresh;
@@ -8241,8 +8347,11 @@ var init_src = __esm(() => {
8241
8347
  init_newrelic();
8242
8348
  init_musher();
8243
8349
  init_route();
8350
+ init_handoff();
8244
8351
  init_doneChecks();
8245
8352
  init_routeFix();
8353
+ init_reshape();
8354
+ init_say();
8246
8355
  init_engine();
8247
8356
  init_sources();
8248
8357
  init_otlp();
@@ -8640,7 +8749,15 @@ function parseRun(v) {
8640
8749
  item,
8641
8750
  node,
8642
8751
  title: typeof title === "string" ? title : "",
8643
- ...v.kind === "draft" ? { kind: "draft" } : {},
8752
+ ...v.kind === "draft" || v.kind === "mock" ? { kind: v.kind } : {},
8753
+ ...isObject8(v.mock) && typeof v.mock.out === "string" && typeof v.mock.base === "string" ? {
8754
+ mock: {
8755
+ out: v.mock.out,
8756
+ base: v.mock.base,
8757
+ ...typeof v.mock.model === "string" ? { model: v.mock.model } : {},
8758
+ ...typeof v.mock.drawn === "string" ? { drawn: v.mock.drawn } : {}
8759
+ }
8760
+ } : {},
8644
8761
  ...typeof v.seatId === "string" ? { seatId: v.seatId } : {},
8645
8762
  ...typeof v.runId === "string" ? { runId: v.runId } : {},
8646
8763
  ...typeof v.leaseUntil === "number" ? { leaseUntil: v.leaseUntil } : {},
@@ -8688,6 +8805,7 @@ function parseRun(v) {
8688
8805
  }
8689
8806
  } : {},
8690
8807
  ...v.pr.clear === true ? { clear: true } : {},
8808
+ ...v.pr.queued === true ? { queued: true } : {},
8691
8809
  ...typeof v.pr.ci === "string" ? { ci: v.pr.ci } : {},
8692
8810
  ...typeof v.pr.review === "string" ? { review: v.pr.review } : {},
8693
8811
  ...typeof v.pr.posted === "string" ? { posted: v.pr.posted } : {},
@@ -8698,13 +8816,15 @@ function parseRun(v) {
8698
8816
  ...conflicted ? { conflicted } : {},
8699
8817
  ...typeof v.pr.reviewed === "string" ? { reviewed: v.pr.reviewed } : {},
8700
8818
  ...typeof v.pr.look === "string" ? { look: v.pr.look } : {},
8819
+ ...typeof v.pr.instructAt === "number" ? { instructAt: v.pr.instructAt } : {},
8701
8820
  ...isObject8(v.pr.instructed) && Array.isArray(v.pr.instructed.ids) && (v.pr.instructed.how === "told" || v.pr.instructed.how === "restarted") ? {
8702
8821
  instructed: {
8703
8822
  ids: v.pr.instructed.ids.filter((x) => typeof x === "string"),
8704
8823
  how: v.pr.instructed.how
8705
8824
  }
8706
8825
  } : {},
8707
- ...typeof v.pr.findings === "string" ? { findings: v.pr.findings } : {}
8826
+ ...typeof v.pr.findings === "string" ? { findings: v.pr.findings } : {},
8827
+ ...v.pr.live === true ? { live: true } : {}
8708
8828
  }
8709
8829
  } : {}
8710
8830
  };
@@ -8996,8 +9116,11 @@ init_sentry();
8996
9116
  init_newrelic();
8997
9117
  init_musher();
8998
9118
  init_route();
9119
+ init_handoff();
8999
9120
  init_doneChecks();
9000
9121
  init_routeFix();
9122
+ init_reshape();
9123
+ init_say();
9001
9124
  init_engine();
9002
9125
  init_sources();
9003
9126
  init_otlp();
@@ -9276,6 +9399,14 @@ import { basename, dirname as dirname5, join as join7 } from "node:path";
9276
9399
  var MUSHER_LABEL = "ai.sleds.musher";
9277
9400
  var MUSHER_INTERVAL_S = 300;
9278
9401
  var DEFAULT_PERMISSION_MODE = "acceptEdits";
9402
+ var PERMISSION_MODES = [
9403
+ "acceptEdits",
9404
+ "auto",
9405
+ "bypassPermissions",
9406
+ "default",
9407
+ "dontAsk",
9408
+ "plan"
9409
+ ];
9279
9410
  var DEFAULT_TIMEOUT_MINUTES = 90;
9280
9411
  var SUMMARY_MAX = 2000;
9281
9412
  var musherLabel = (env = process.env) => {
@@ -9394,11 +9525,12 @@ var defaultExecCapture = (cmd, args, o) => {
9394
9525
  stdio: ["ignore", "pipe", "pipe"],
9395
9526
  env: { ...process.env, SLEDS_MUSHER: "1" }
9396
9527
  });
9528
+ const timedOut = r.error?.code === "ETIMEDOUT";
9397
9529
  return {
9398
9530
  stdout: r.stdout ?? "",
9399
9531
  stderr: r.stderr ?? "",
9400
9532
  status: r.status,
9401
- ...r.error ? { error: r.error.message } : {}
9533
+ ...timedOut ? { timedOut } : r.error ? { error: r.error.message } : {}
9402
9534
  };
9403
9535
  };
9404
9536
  var musherClient = (o) => {
@@ -9474,8 +9606,10 @@ var runJob = async (o) => {
9474
9606
  const r = exec(o.claudePath, args, { cwd, timeoutMs: o.timeoutMs });
9475
9607
  const durationMs = Date.now() - t0;
9476
9608
  const result = r.error ? { ok: false, summary: `could not run ${o.claudePath}: ${r.error}` } : parseClaudeOutput(r.stdout, r.status);
9477
- if (!r.error && r.status === null)
9609
+ if (!r.error && (r.timedOut || r.status === null)) {
9610
+ result.ok = false;
9478
9611
  result.summary = `timed out after ${Math.round(o.timeoutMs / 60000)} min. ${result.summary}`.trim();
9612
+ }
9479
9613
  const outputs = collectOutputs(gitNewCommits(cwd, headBefore, gitHead(cwd, exec), exec), result.summary);
9480
9614
  await o.client.finish({
9481
9615
  jobId: o.job.jobId,
@@ -9910,8 +10044,32 @@ var markWarned = (state, events) => {
9910
10044
  }
9911
10045
  return { ...state, warned, ...lastWallResetsAt !== undefined ? { lastWallResetsAt } : {} };
9912
10046
  };
9913
- var CREDITS_PATTERN = /out of usage credits|cc_cli_limit_message/i;
9914
- var creditsHitIn = (text) => CREDITS_PATTERN.test(text);
10047
+ var CREDITS_LINE = /^\s*you(?:'|’)re out of usage credits\b/i;
10048
+ var entryText = (v) => {
10049
+ if (typeof v === "string")
10050
+ return v;
10051
+ if (!Array.isArray(v))
10052
+ return "";
10053
+ return v.map((b) => isObject9(b) && b.type === "text" && typeof b.text === "string" ? b.text : "").join(`
10054
+ `);
10055
+ };
10056
+ var isCreditsLine = (line) => {
10057
+ if (!line.includes("usage credits"))
10058
+ return false;
10059
+ let raw;
10060
+ try {
10061
+ raw = JSON.parse(line);
10062
+ } catch {
10063
+ return false;
10064
+ }
10065
+ if (!isObject9(raw))
10066
+ return false;
10067
+ const message = isObject9(raw.message) ? raw.message : undefined;
10068
+ const texts = raw.type === "system" ? [entryText(raw.content), entryText(raw.text)] : raw.type === "assistant" && raw.isApiErrorMessage === true ? [entryText(message?.content)] : [];
10069
+ return texts.some((t) => CREDITS_LINE.test(t));
10070
+ };
10071
+ var creditsHitIn = (text) => text.split(`
10072
+ `).some(isCreditsLine);
9915
10073
  var transcriptPathFromStdin = (text) => {
9916
10074
  try {
9917
10075
  const raw = JSON.parse(text.trim());
@@ -9934,12 +10092,14 @@ var scanTranscriptForCredits = (path, prior) => {
9934
10092
  try {
9935
10093
  const buf = Buffer.alloc(size - start);
9936
10094
  readSync(fd, buf, 0, buf.length, start);
9937
- const hit = creditsHitIn(buf.toString("utf8"));
10095
+ const lastNewline = buf.lastIndexOf(10);
10096
+ const whole = lastNewline >= 0 ? buf.subarray(0, lastNewline + 1) : Buffer.alloc(0);
10097
+ const hit = creditsHitIn(whole.toString("utf8"));
9938
10098
  return {
9939
10099
  hit,
9940
10100
  next: {
9941
10101
  path,
9942
- offset: size,
10102
+ offset: lastNewline >= 0 ? start + lastNewline + 1 : start,
9943
10103
  ...hit ? { hitAt: Date.now() } : prior?.hitAt ? { hitAt: prior.hitAt } : {}
9944
10104
  }
9945
10105
  };
@@ -10033,10 +10193,7 @@ var switchOff = (settings, prior) => {
10033
10193
  }
10034
10194
  return { ...stripped, env };
10035
10195
  };
10036
- var isPointedAtProxy = (settings) => {
10037
- const env = envOf2(settings);
10038
- return BASE_URL_KEY in env || AUTH_TOKEN_KEY in env;
10039
- };
10196
+ var envToRestore = (priors, proxyUrls) => priors.find((p) => p !== undefined && !isSledsProxyEnv({ env: p }, proxyUrls)) ?? {};
10040
10197
  var RELATIVE = /^\+(?:(\d+)h)?(?:(\d+)m)?$/;
10041
10198
  var parseUntilReset = (input, nowMs) => {
10042
10199
  const trimmed = input.trim();
@@ -10274,7 +10431,7 @@ var deployCmd = async (o) => {
10274
10431
  return 0;
10275
10432
  }
10276
10433
  const why = typeof r.json?.error === "string" ? r.json.error : `HTTP ${r.status}`;
10277
- if (r.status >= 400 && r.status < 500) {
10434
+ if (r.status >= 400 && r.status < 500 && r.status !== 429 && r.status !== 408) {
10278
10435
  error(r.status === 401 ? `SLEDS refused the deploy token (${why})` : why);
10279
10436
  return 1;
10280
10437
  }
@@ -10290,13 +10447,13 @@ import {
10290
10447
  existsSync as existsSync21,
10291
10448
  lstatSync as lstatSync2,
10292
10449
  mkdtempSync,
10293
- readFileSync as readFileSync19,
10450
+ readFileSync as readFileSync20,
10294
10451
  readlinkSync as readlinkSync4,
10295
10452
  rmSync as rmSync13,
10296
10453
  writeFileSync as writeFileSync19
10297
10454
  } from "node:fs";
10298
- import { tmpdir, userInfo as userInfo2 } from "node:os";
10299
- import { delimiter as delimiter3, dirname as dirname17, join as join26, resolve as resolve9 } from "node:path";
10455
+ import { tmpdir as tmpdir2, userInfo as userInfo2 } from "node:os";
10456
+ import { delimiter as delimiter3, dirname as dirname18, join as join27, resolve as resolve10 } from "node:path";
10300
10457
 
10301
10458
  // src/agentMcp.ts
10302
10459
  import { execFileSync as execFileSync3 } from "node:child_process";
@@ -10402,6 +10559,7 @@ async function offerMcp2(o) {
10402
10559
  }
10403
10560
 
10404
10561
  // src/channels/cockpit.ts
10562
+ import { execFile } from "node:child_process";
10405
10563
  var detectCockpit = (env) => {
10406
10564
  if (env.CMUX_SOCKET_PATH || env.CMUX_WORKSPACE_ID || env.CMUX_SURFACE_ID) {
10407
10565
  const c = { kind: "cmux" };
@@ -10650,23 +10808,19 @@ var fetchFeed = async (o, now) => {
10650
10808
  return null;
10651
10809
  }
10652
10810
  };
10811
+ var execCockpit = (bin, args, timeoutMs) => new Promise((resolve) => {
10812
+ try {
10813
+ const child = execFile(bin, [...args], { timeout: timeoutMs }, () => resolve());
10814
+ child.stdin?.end();
10815
+ } catch {
10816
+ resolve();
10817
+ }
10818
+ });
10653
10819
  var runCockpit = async (cockpit, argv, opts = {}) => {
10654
- const spawn = opts.spawn ?? Bun.spawn;
10820
+ const exec = opts.exec ?? execCockpit;
10655
10821
  const timeoutMs = opts.timeoutMs ?? 300;
10656
10822
  const bin = cockpit.kind === "cmux" ? "cmux" : cockpit.kind;
10657
- await Promise.allSettled(argv.map(async (a) => {
10658
- const proc = spawn([bin, ...a], { stdout: "ignore", stderr: "ignore", stdin: "ignore" });
10659
- const timer = setTimeout(() => {
10660
- try {
10661
- proc.kill();
10662
- } catch {}
10663
- }, timeoutMs);
10664
- try {
10665
- await proc.exited;
10666
- } finally {
10667
- clearTimeout(timer);
10668
- }
10669
- }));
10823
+ await Promise.allSettled(argv.map((a) => exec(bin, a, timeoutMs)));
10670
10824
  };
10671
10825
 
10672
10826
  // src/commands/doctor.ts
@@ -10678,7 +10832,14 @@ import { join as join11 } from "node:path";
10678
10832
  var API_KEY_VAR = "ANTHROPIC_API_KEY";
10679
10833
  var SEAT_TOKEN_VAR = "CLAUDE_CODE_OAUTH_TOKEN";
10680
10834
  function namesVar(text, name) {
10681
- return new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=\\s*\\S`, "m").test(text);
10835
+ const assign = new RegExp(`^[ \\t]*(?:export[ \\t]+)?${name}[ \\t]*=(.*)$`);
10836
+ return text.split(/\r?\n/).some((line) => {
10837
+ const value = assign.exec(line)?.[1]?.trim();
10838
+ if (!value)
10839
+ return false;
10840
+ const quoted = /^(["'])(.*)\1$/.exec(value);
10841
+ return (quoted ? (quoted[2] ?? "").trim() : value) !== "";
10842
+ });
10682
10843
  }
10683
10844
  function loginPath(env, home = homedir()) {
10684
10845
  return join11(env.CLAUDE_CONFIG_DIR?.trim() || join11(home, ".claude"), ".credentials.json");
@@ -10758,12 +10919,18 @@ var readCmuxPassword = (exec) => {
10758
10919
  return null;
10759
10920
  return r.stdout.trim() || null;
10760
10921
  };
10922
+ var securityQuote = (s) => `"${s.replace(/[\\"]/g, (c) => `\\${c}`)}"`;
10761
10923
  var writeCmuxPassword = (exec, pw) => {
10762
- const r = exec("security", ["add-generic-password", "-U", ...item, "-w", pw]);
10763
- if (r.status !== 0) {
10764
- const why = r.stderr.split(pw).join("…").trim() || `exit ${r.status}`;
10765
- throw new Error(`could not store the cmux password in the Keychain: ${why}`);
10766
- }
10924
+ if (/[\r\n\0]/.test(pw))
10925
+ throw new Error("could not store the cmux password in the Keychain: it has a line break");
10926
+ const command = ["add-generic-password", "-U", ...item, "-w"].join(" ");
10927
+ const r = exec("security", ["-i"], { input: `${command} ${securityQuote(pw)}
10928
+ ` });
10929
+ const why = r.stderr.split(pw).join("…").trim();
10930
+ if (r.status !== 0)
10931
+ throw new Error(`could not store the cmux password in the Keychain: ${why || `exit ${r.status}`}`);
10932
+ if (readCmuxPassword(exec) !== pw.trim())
10933
+ throw new Error(`could not store the cmux password in the Keychain: ${why || "it did not read back"}`);
10767
10934
  };
10768
10935
  var clearCmuxPassword = (exec) => exec("security", ["delete-generic-password", ...item]).status === 0;
10769
10936
 
@@ -11595,14 +11762,14 @@ import {
11595
11762
  appendFileSync as appendFileSync2,
11596
11763
  chmodSync as chmodSync5,
11597
11764
  mkdirSync as mkdirSync18,
11598
- readFileSync as readFileSync15,
11765
+ readFileSync as readFileSync16,
11599
11766
  existsSync as realExists,
11600
11767
  rmSync as rmSync10,
11601
- statSync as statSync5,
11768
+ statSync as statSync6,
11602
11769
  writeFileSync as writeFileSync16
11603
11770
  } from "node:fs";
11604
- import { hostname as hostname2, userInfo } from "node:os";
11605
- import { dirname as dirname15, isAbsolute as isAbsolute3, join as join24, resolve as resolve8 } from "node:path";
11771
+ import { hostname as hostname2, tmpdir, userInfo } from "node:os";
11772
+ import { dirname as dirname16, isAbsolute as isAbsolute3, join as join25, resolve as resolve9 } from "node:path";
11606
11773
 
11607
11774
  // src/engine/beat.ts
11608
11775
  var BEAT_EVERY_MS = 60000;
@@ -11614,7 +11781,7 @@ function beatBody(state, flags, host = {}, cap = {}) {
11614
11781
  name: flags.name,
11615
11782
  os: host.os ?? process.platform,
11616
11783
  runner: flags.runner,
11617
- version: host.version ?? "0.1.60" ?? "dev",
11784
+ version: host.version ?? "0.1.61" ?? "dev",
11618
11785
  running: live.length,
11619
11786
  items: live.slice(0, BEAT_ITEMS).map((r) => `${r.item}/${r.node}`),
11620
11787
  ...cap.atOnce !== undefined ? { atOnce: cap.atOnce } : {},
@@ -12054,17 +12221,19 @@ function parsePrView(stdout) {
12054
12221
  const name = String(r.name ?? r.context ?? "");
12055
12222
  if (!name)
12056
12223
  continue;
12057
- latest.set(name, {
12224
+ latest.set(`${String(r.workflowName ?? "")}\x00${name}`, {
12225
+ name,
12058
12226
  status: String(r.status ?? "COMPLETED"),
12059
12227
  conclusion: String(r.conclusion ?? r.state ?? "")
12060
12228
  });
12061
12229
  }
12062
12230
  const failing = [];
12063
12231
  let pending = 0;
12064
- for (const [name, c] of latest) {
12065
- if (FAILED.has(c.conclusion))
12066
- failing.push(name);
12067
- else if (c.status !== "COMPLETED" && c.conclusion !== "SUCCESS")
12232
+ for (const c of latest.values()) {
12233
+ if (FAILED.has(c.conclusion)) {
12234
+ if (!failing.includes(c.name))
12235
+ failing.push(c.name);
12236
+ } else if (c.status !== "COMPLETED" && c.conclusion !== "SUCCESS")
12068
12237
  pending++;
12069
12238
  else if (c.conclusion === "PENDING" || c.conclusion === "EXPECTED")
12070
12239
  pending++;
@@ -12283,6 +12452,7 @@ function prComment(exec, repoPath, url, body) {
12283
12452
 
12284
12453
  // src/engine/jiraSource.ts
12285
12454
  var JIRA_LOG_MAX = 1000;
12455
+ var JIRA_MAX_PAGES = 20;
12286
12456
  function nodeKeyOf(summary) {
12287
12457
  return /^\s*\[([A-Za-z]{1,3})\]/.exec(summary)?.[1]?.toUpperCase();
12288
12458
  }
@@ -12428,10 +12598,20 @@ function jiraSource(o) {
12428
12598
  };
12429
12599
  const read = async () => {
12430
12600
  const jql = encodeURIComponent(`parent = ${epic} ORDER BY key`);
12431
- const r = await call(`/rest/api/3/search/jql?jql=${jql}&fields=summary,description,status,issuelinks&maxResults=100`);
12432
- if (!r.ok)
12433
- throw new Error(`Jira: could not read ${epic}'s children — ${errorOf(r)}`);
12434
- const issues = Array.isArray(r.json?.issues) ? r.json.issues.filter(isObject8) : [];
12601
+ const issues = [];
12602
+ let token;
12603
+ for (let page = 0;; page++) {
12604
+ if (page >= JIRA_MAX_PAGES)
12605
+ throw new Error(`Jira: ${epic} has more than ${JIRA_MAX_PAGES} pages of children`);
12606
+ const r = await call(`/rest/api/3/search/jql?jql=${jql}&fields=summary,description,status,issuelinks&maxResults=100${token ? `&nextPageToken=${encodeURIComponent(token)}` : ""}`);
12607
+ if (!r.ok)
12608
+ throw new Error(`Jira: could not read ${epic}'s children — ${errorOf(r)}`);
12609
+ if (Array.isArray(r.json?.issues))
12610
+ issues.push(...r.json.issues.filter(isObject8));
12611
+ token = str6(r.json?.nextPageToken);
12612
+ if (!token || r.json?.isLast === true)
12613
+ break;
12614
+ }
12435
12615
  const nodes = issues.map(jiraNode);
12436
12616
  byNode.clear();
12437
12617
  for (const n of nodes)
@@ -12763,6 +12943,26 @@ function instructionsOf(s) {
12763
12943
  const i = s;
12764
12944
  return i && typeof i.instructions === "function" ? s : null;
12765
12945
  }
12946
+ function mocksOf(s) {
12947
+ const m = s;
12948
+ return m && typeof m.finishMock === "function" && typeof m.failMock === "function" ? s : null;
12949
+ }
12950
+ function mockRunOf(row) {
12951
+ const m = isObject8(row.mockRun) ? row.mockRun : {};
12952
+ return { asked: isObject8(m.asked), running: isObject8(m.running) };
12953
+ }
12954
+ function askedMocks(r) {
12955
+ const mine = Array.isArray(r.mine) ? r.mine.filter(isObject8) : [];
12956
+ return mine.filter((row) => {
12957
+ const m = mockRunOf(row);
12958
+ return m.asked && !m.running;
12959
+ });
12960
+ }
12961
+ function mockBandText(b) {
12962
+ const lo = num5(b.lowMin);
12963
+ const hi = num5(b.highMin);
12964
+ return lo !== undefined && hi !== undefined ? `${lo}–${hi} min` : undefined;
12965
+ }
12766
12966
  function instructionsFrom(waiting) {
12767
12967
  if (!Array.isArray(waiting))
12768
12968
  return [];
@@ -13049,6 +13249,8 @@ function sledRunning(r) {
13049
13249
  const route = row.route;
13050
13250
  if (isObject8(route) && route.drafting === true)
13051
13251
  running++;
13252
+ if (mockRunOf(row).running)
13253
+ running++;
13052
13254
  if (!isObject8(route) || !isObject8(route.armed))
13053
13255
  continue;
13054
13256
  running += keysIn(isObject8(route.states) ? route.states : {}, "running").length;
@@ -13084,14 +13286,12 @@ function routeText(route, states) {
13084
13286
  }
13085
13287
  function sledsSource(client, o) {
13086
13288
  const routeOf = (q) => client.get(`/proxy/route?q=${encodeURIComponent(q)}`);
13087
- async function pickup(sled, drafted) {
13289
+ const mockRefused = new Map;
13290
+ async function pickup(sled, drafted, profile) {
13088
13291
  const engine = o.engine;
13089
- const profile = await readProfile(client);
13090
- if (profile?.pickup !== "auto")
13292
+ if (!autoHere(profile))
13091
13293
  return null;
13092
- if (profile.engine && !sameEngine(profile.engine, engine))
13093
- return null;
13094
- const atOnce = (o.atOnce ?? profile.atOnce ?? 1) - drafted;
13294
+ const atOnce = (o.atOnce ?? profile?.atOnce ?? 1) - drafted;
13095
13295
  const row = nextPickup(sled, { atOnce, engine });
13096
13296
  if (!row)
13097
13297
  return null;
@@ -13171,8 +13371,94 @@ function sledsSource(client, o) {
13171
13371
  };
13172
13372
  return { item, ready: [], running: [], landed: false, seatIds: [], draft: { claim, why } };
13173
13373
  }
13374
+ function autoHere(profile) {
13375
+ if (profile?.pickup !== "auto")
13376
+ return false;
13377
+ return !profile.engine || sameEngine(profile.engine, o.engine);
13378
+ }
13379
+ async function mocks(sled, held, profile) {
13380
+ const waiting = askedMocks(sled);
13381
+ if (waiting.length === 0 || !autoHere(profile))
13382
+ return [];
13383
+ let room = (o.atOnce ?? profile?.atOnce ?? 1) - sledRunning(sled) - held;
13384
+ const out = [];
13385
+ for (const row of waiting) {
13386
+ if (room <= 0)
13387
+ break;
13388
+ const claim = await takeMock(row);
13389
+ if (!claim)
13390
+ continue;
13391
+ out.push(claim);
13392
+ room--;
13393
+ }
13394
+ return out;
13395
+ }
13396
+ async function takeMock(row) {
13397
+ const project = str6(row.project) ?? str6(row.key);
13398
+ if (!project)
13399
+ return null;
13400
+ const item = str6(row.key) ?? project;
13401
+ const runner = typeof o.runner === "function" ? o.runner() : o.runner;
13402
+ const r = await client.postFull("/proxy/mock", {
13403
+ project,
13404
+ take: true,
13405
+ engine: o.engine,
13406
+ ...runner ? { runner } : {},
13407
+ ...o.leaseMin !== undefined ? { leaseMin: o.leaseMin } : {}
13408
+ });
13409
+ if (!r || r.status >= 300 || !r.json || r.json.ok === false || !str6(r.json.runId)) {
13410
+ const why = r ? serverMessage(r) : UNREACHABLE3;
13411
+ if (mockRefused.get(item) !== why)
13412
+ o.log?.(`Pickup: Auto could not take ${item}'s mock — ${why}`);
13413
+ mockRefused.set(item, why);
13414
+ return null;
13415
+ }
13416
+ mockRefused.delete(item);
13417
+ const j = r.json;
13418
+ const key = str6(j.key) ?? item;
13419
+ const band = isObject8(j.band) ? mockBandText(j.band) : undefined;
13420
+ o.log?.(`Pickup: Auto is drawing ${key}'s mock — a no-code session${band ? `, sized ${band}` : ""}`);
13421
+ const leaseUntil = num5(j.leaseUntil);
13422
+ return {
13423
+ item: key,
13424
+ node: MOCK_NODE,
13425
+ title: str6(j.title) ?? key,
13426
+ pack: str6(j.pack) ?? "",
13427
+ repos: strings(j.repos),
13428
+ kind: "mock",
13429
+ runId: str6(j.runId),
13430
+ ...leaseUntil !== undefined ? { leaseUntil } : {},
13431
+ ...str6(j.seatId) ? { seatId: str6(j.seatId) } : {},
13432
+ ...str6(j.runToken) ? { runToken: str6(j.runToken) } : {}
13433
+ };
13434
+ }
13435
+ async function postMock(body, what) {
13436
+ const r = await client.postFull("/proxy/mock", body);
13437
+ if (!r)
13438
+ throw new Error(`${UNREACHABLE3} — ${what} not recorded`);
13439
+ if (r.status < 300 && r.json?.ok !== false)
13440
+ return { ok: true };
13441
+ if (r.status === 401 || r.status === 403 || r.status === 404 && !r.json)
13442
+ throw new SeatTokenError(r.status);
13443
+ if (r.status === 409)
13444
+ return { gone: serverMessage(r) };
13445
+ if (r.status === 400 || r.status === 413)
13446
+ return { refused: serverMessage(r) };
13447
+ throw new Error(`${what}: ${serverMessage(r)}`);
13448
+ }
13449
+ async function failMock(claim, log, engine) {
13450
+ if (!claim.runId)
13451
+ return { gone: "no run" };
13452
+ return postMock({ runId: claim.runId, engine, fail: { log: log.slice(-FAIL_LOG_MAX) } }, `${claim.item}/${claim.node} fail`);
13453
+ }
13174
13454
  return {
13175
13455
  name: "sleds",
13456
+ async finishMock(claim, html, f) {
13457
+ if (!claim.runId)
13458
+ return { gone: "no run" };
13459
+ return postMock({ runId: claim.runId, engine: f.engine, html, ...f.model ? { model: f.model } : {} }, `${claim.item}/${claim.node} mock`);
13460
+ },
13461
+ failMock,
13176
13462
  async read() {
13177
13463
  if (o.only) {
13178
13464
  const q = o.only;
@@ -13183,6 +13469,11 @@ function sledsSource(client, o) {
13183
13469
  const r = await readOrSay(client, "/proxy/sled", "the sled");
13184
13470
  const items = sledItems(r, o.armedOnly === true, o.engine);
13185
13471
  if (o.armedOnly && o.engine) {
13472
+ let profileRead;
13473
+ const profileNow = () => {
13474
+ profileRead ??= readProfile(client);
13475
+ return profileRead;
13476
+ };
13186
13477
  const mine = Array.isArray(r.mine) ? r.mine.filter(isObject8) : [];
13187
13478
  for (const row of mine) {
13188
13479
  const route = row.route;
@@ -13197,7 +13488,7 @@ function sledsSource(client, o) {
13197
13488
  const waiting = armedDrafts(r, o.engine);
13198
13489
  let drafted = 0;
13199
13490
  if (waiting.length > 0) {
13200
- const cap = o.atOnce ?? (await readProfile(client))?.atOnce ?? 1;
13491
+ const cap = o.atOnce ?? (await profileNow())?.atOnce ?? 1;
13201
13492
  let room = cap - sledRunning(r);
13202
13493
  for (const row of waiting) {
13203
13494
  const item = str6(row.key) ?? String(row.project);
@@ -13215,7 +13506,22 @@ function sledsSource(client, o) {
13215
13506
  }
13216
13507
  }
13217
13508
  }
13218
- const picked = await pickup(r, drafted);
13509
+ const asked = askedMocks(r).length > 0 ? await mocks(r, drafted, await profileNow()) : [];
13510
+ for (const claim of asked) {
13511
+ const it = items.find((x) => x.item === claim.item);
13512
+ if (it)
13513
+ it.mock = { claim };
13514
+ else
13515
+ items.push({
13516
+ item: claim.item,
13517
+ ready: [],
13518
+ running: [],
13519
+ landed: false,
13520
+ seatIds: [],
13521
+ mock: { claim }
13522
+ });
13523
+ }
13524
+ const picked = await pickup(r, drafted + asked.length, await profileNow());
13219
13525
  if (picked)
13220
13526
  items.push(picked);
13221
13527
  }
@@ -13283,6 +13589,8 @@ function sledsSource(client, o) {
13283
13589
  });
13284
13590
  if (r?.status !== 409)
13285
13591
  return "running";
13592
+ if (claim.kind === "mock")
13593
+ return "lost";
13286
13594
  let route;
13287
13595
  try {
13288
13596
  route = await routeOf(claim.item);
@@ -13308,6 +13616,15 @@ function sledsSource(client, o) {
13308
13616
  return "lost";
13309
13617
  },
13310
13618
  async report(claim, outcome, by) {
13619
+ if (claim.kind === "mock") {
13620
+ if (outcome.kind === "pr" || outcome.kind === "merged")
13621
+ return;
13622
+ const log = outcome.kind === "park" ? outcome.question : outcome.kind === "release" ? `released: ${outcome.why}` : outcome.log;
13623
+ const r = await failMock(claim, log, by);
13624
+ if ("refused" in r)
13625
+ throw new ReportRefusedError(400, `${claim.item}/${claim.node} fail: ${r.refused}`, true);
13626
+ return;
13627
+ }
13311
13628
  let path;
13312
13629
  let body;
13313
13630
  if (outcome.kind === "pr" || outcome.kind === "merged") {
@@ -13823,6 +14140,24 @@ function fastForwardUntouched(exec, path, base, log) {
13823
14140
  var INSTALL_TIMEOUT_MS = 15 * 60000;
13824
14141
  var MANAGERS = new Set(["pnpm", "npm", "yarn", "bun"]);
13825
14142
  var INSTALL_VERBS = new Set(["install", "i", "ci"]);
14143
+ var INSTALL_FLAGS = new Set([
14144
+ "--frozen-lockfile",
14145
+ "--immutable",
14146
+ "--prefer-offline",
14147
+ "--offline",
14148
+ "--ignore-scripts",
14149
+ "--prod",
14150
+ "--production",
14151
+ "--no-optional",
14152
+ "--no-audit",
14153
+ "--no-fund",
14154
+ "--non-interactive",
14155
+ "--silent",
14156
+ "--reporter=silent",
14157
+ "--reporter=append-only",
14158
+ "--loglevel=error",
14159
+ "--loglevel=warn"
14160
+ ]);
13826
14161
  function installArgv(command) {
13827
14162
  const line = command?.trim().replace(/^`|`$/g, "") ?? "";
13828
14163
  if (!line || /[;&|<>$`()\n]/.test(line))
@@ -13838,7 +14173,7 @@ function installArgv(command) {
13838
14173
  rest = argv.slice(2);
13839
14174
  else
13840
14175
  return null;
13841
- return rest.every((a) => a.startsWith("-")) ? argv : null;
14176
+ return rest.every((a) => INSTALL_FLAGS.has(a)) ? argv : null;
13842
14177
  }
13843
14178
  function lockfileInstall(path, exists) {
13844
14179
  if (exists(join17(path, "pnpm-lock.yaml")))
@@ -14047,6 +14382,7 @@ function mcpConfigsOf(state) {
14047
14382
  }
14048
14383
  async function tick(deps) {
14049
14384
  deps.setupNoted ??= new Set;
14385
+ deps.noClone ??= new Map;
14050
14386
  const made = new Set(mcpConfigsOf(deps.readState()));
14051
14387
  const write = deps.writeMcpConfig;
14052
14388
  const d = write ? {
@@ -14136,6 +14472,11 @@ async function tickOnce(d) {
14136
14472
  keep.push(run);
14137
14473
  continue;
14138
14474
  }
14475
+ if (run.kind === "mock") {
14476
+ if (await settleMock(d, run, claim, runner, finished))
14477
+ keep.push(run);
14478
+ continue;
14479
+ }
14139
14480
  if (run.parked) {
14140
14481
  if (liveAnswerer(run) && await settleAnswerer(d, run) === "released")
14141
14482
  continue;
@@ -14208,6 +14549,7 @@ async function tickOnce(d) {
14208
14549
  try {
14209
14550
  await d.source.report(claim, outcome, d.engine);
14210
14551
  finished.push({ item: run.item, node: run.node, outcome });
14552
+ endNodeSession(d, runner, run.handle, run.repoPath, ref(run));
14211
14553
  } catch (e) {
14212
14554
  d.log(`${ref(run)}: exited with no PR; the fail report failed, retrying next tick: ${message(e)}`);
14213
14555
  keep.push(run);
@@ -14240,10 +14582,11 @@ async function tickOnce(d) {
14240
14582
  run.seenTicks = (run.seenTicks ?? 0) + 1;
14241
14583
  if (!ghDown) {
14242
14584
  const quiet = now - (run.seenAt ?? run.startedAt);
14585
+ const provider = providerFailure(shown);
14243
14586
  if (quiet >= PROVIDER_RETRY_MIN * 60000)
14244
- failing = providerFailure(shown);
14245
- asked = !failing && run.seenTicks >= STILL_TICKS && !providerFailure(shown) && askedAndStopped(shown);
14246
- idle = !failing && (asked || quiet >= d.idleMin * 60000);
14587
+ failing = provider;
14588
+ asked = !provider && run.seenTicks >= STILL_TICKS && askedAndStopped(shown);
14589
+ idle = !provider && (asked || quiet >= d.idleMin * 60000);
14247
14590
  }
14248
14591
  }
14249
14592
  }
@@ -14263,6 +14606,11 @@ async function tickOnce(d) {
14263
14606
  continue;
14264
14607
  }
14265
14608
  if (beat === "done") {
14609
+ if (ghDown) {
14610
+ d.log(`${ref(run)}: moved on, but its PR is not seen yet — kept until GitHub answers`);
14611
+ keep.push(run);
14612
+ continue;
14613
+ }
14266
14614
  d.log(`${ref(run)}: moved on without the engine (PR or landed) — the session is left open (${runner.watchHint(run.handle)})`);
14267
14615
  continue;
14268
14616
  }
@@ -14374,6 +14722,9 @@ async function tickOnce(d) {
14374
14722
  for (const it of items)
14375
14723
  if (it.draft && !takeNone && !startsHeld(d))
14376
14724
  await startDraft(d, it.draft.claim, it.draft.why, state, started, finished);
14725
+ for (const it of items)
14726
+ if (it.mock && !takeNone && !startsHeld(d))
14727
+ await startMock(d, it.mock.claim, state, started, finished);
14377
14728
  const live = new Map(items.map((i) => [i.item, i]));
14378
14729
  const unheld = state.runs.filter((r) => r.parked && (live.get(r.item)?.landed ?? true));
14379
14730
  if (unheld.length) {
@@ -14400,8 +14751,14 @@ async function tickOnce(d) {
14400
14751
  await rereadAtOnce(d);
14401
14752
  const cap = await machineCap(d);
14402
14753
  const atOnce = startsHeld(d) ? 0 : cap;
14754
+ for (const [k, c] of d.noClone ?? [])
14755
+ if (!alive(c) || typeof d.resolveRepo(c) === "string")
14756
+ d.noClone?.delete(k);
14403
14757
  const takes = planTakes({
14404
- items,
14758
+ items: d.noClone?.size ? items.map((i) => ({
14759
+ ...i,
14760
+ ready: i.ready.filter((n) => !d.noClone?.has(ref({ item: i.item, node: n })))
14761
+ })) : items,
14405
14762
  tracked: [
14406
14763
  ...state.runs.filter((r) => !r.pr && (!r.parked || liveAnswerer(r))).map((r) => ({
14407
14764
  item: r.item,
@@ -14474,6 +14831,7 @@ async function tickOnce(d) {
14474
14831
  d.setupNoted?.add(repoPath.error);
14475
14832
  d.log(`${at}: released, no try spent — ${repoPath.error}`);
14476
14833
  }
14834
+ d.noClone?.set(at, claim);
14477
14835
  await report({ kind: "release", why: repoPath.error });
14478
14836
  continue;
14479
14837
  }
@@ -14496,10 +14854,17 @@ async function tickOnce(d) {
14496
14854
  }
14497
14855
  let start;
14498
14856
  let mcpConfig;
14857
+ let made;
14858
+ const dropMade = () => {
14859
+ if (made)
14860
+ removeEngineWorktree(d, repoPath, made, at);
14861
+ };
14499
14862
  try {
14500
14863
  const base = d.defaultBranch(d.exec, repoPath);
14501
14864
  const { install, gate } = claimRunOf(claim.run, claim.repos[0]);
14502
14865
  const worktree = d.runner.makesWorktree ? repoPath : d.ensureWorktree(d.exec, repoPath, branch, worktreePath(repoPath, name), base, install) ?? worktreePath(repoPath, name);
14866
+ if (!d.runner.makesWorktree)
14867
+ made = worktree;
14503
14868
  const prTitle = d.prTitleOf?.(claim);
14504
14869
  mcpConfig = engineMcpConfigFor(d, name, claim.runToken, at, worktree);
14505
14870
  const allow = gateAllowRules(gate);
@@ -14535,6 +14900,7 @@ async function tickOnce(d) {
14535
14900
  };
14536
14901
  } catch (e) {
14537
14902
  d.log(`${at}: could not start: ${message(e)}`);
14903
+ dropMade();
14538
14904
  await report({ kind: "fail", log: `The engine could not start the session: ${message(e)}` });
14539
14905
  continue;
14540
14906
  }
@@ -14545,6 +14911,7 @@ async function tickOnce(d) {
14545
14911
  const down = probeRunner(d);
14546
14912
  if (down) {
14547
14913
  d.log(`${at}: released — the runner could not start it: ${message(e)}`);
14914
+ dropMade();
14548
14915
  await report({ kind: "release", why: `runner unavailable: ${down}` });
14549
14916
  continue;
14550
14917
  }
@@ -14563,6 +14930,8 @@ async function tickOnce(d) {
14563
14930
  if (slow)
14564
14931
  d.log(`${at}: ${slow} ${inRow} times in a row — failed, not released again`);
14565
14932
  d.log(`${at}: could not start: ${message(e)}`);
14933
+ if (!slow)
14934
+ dropMade();
14566
14935
  await report({ kind: "fail", log: `The engine could not start the session: ${message(e)}` });
14567
14936
  continue;
14568
14937
  }
@@ -14591,18 +14960,9 @@ async function tickOnce(d) {
14591
14960
  setLane(d, d.runner, handle, "working");
14592
14961
  try {
14593
14962
  const beat = await d.source.heartbeat(claim, watch);
14594
- if (beat === "parked" || beat === "done") {
14595
- state.runs = state.runs.filter((r) => r !== run);
14596
- save();
14597
- continue;
14598
- }
14599
- if (beat === "lost") {
14600
- d.log(`${at}: the claim was lost as the session started — stopping it`);
14601
- try {
14602
- d.runner.stop(handle);
14603
- } catch (e) {
14604
- d.log(`${at}: could not stop the session: ${message(e)}`);
14605
- }
14963
+ if (beat === "parked" || beat === "done" || beat === "lost") {
14964
+ d.log(beat === "lost" ? `${at}: the claim was lost as the session started — stopping it` : `${at}: the node moved on as the session started (${beat}) — stopping it`);
14965
+ endNodeSession(d, d.runner, handle, repoPath, at);
14606
14966
  state.runs = state.runs.filter((r) => r !== run);
14607
14967
  save();
14608
14968
  continue;
@@ -15732,26 +16092,250 @@ ${shown}`;
15732
16092
  endDraft(d, run, runner);
15733
16093
  return false;
15734
16094
  }
15735
- function endDraft(d, run, runner) {
16095
+ function endDraft(d, run, runner, who = "drafter") {
16096
+ try {
16097
+ if (runner.status(run.handle) === "running")
16098
+ runner.stop(run.handle);
16099
+ } catch (e) {
16100
+ d.log(`${ref(run)}: could not close the session: ${message(e)}`);
16101
+ }
16102
+ const wt = run.handle.cwd;
16103
+ if (wt === run.repoPath)
16104
+ return;
16105
+ try {
16106
+ if (runner.makesWorktree) {
16107
+ const note = runner.removeWorktree?.(run.handle, run.repoPath);
16108
+ if (note)
16109
+ d.log(`${ref(run)}: ${note}`);
16110
+ } else if (wt.startsWith(`${run.repoPath}/.worktrees/`)) {
16111
+ d.removeWorktree(d.exec, run.repoPath, wt);
16112
+ }
16113
+ } catch (e) {
16114
+ d.log(`${ref(run)}: kept the ${who}'s checkout ${wt}: ${message(e)}`);
16115
+ }
16116
+ }
16117
+ async function startMock(d, claim, state, started, finished) {
16118
+ const at = ref(claim);
16119
+ const fail = async (log) => {
16120
+ d.log(`${at}: the mock was not drawn — ${log}`);
16121
+ try {
16122
+ await d.source.report(claim, { kind: "fail", log }, d.engine);
16123
+ } catch (e) {
16124
+ d.log(`${at}: fail report failed: ${message(e)}`);
16125
+ }
16126
+ finished.push({ item: claim.item, node: claim.node, outcome: { kind: "fail", log } });
16127
+ };
16128
+ if (!mocksOf(d.source) || !d.mockOut || !d.readMock) {
16129
+ await fail("this engine cannot draw mocks (its source or its files do not carry them)");
16130
+ return;
16131
+ }
16132
+ const repoPath = d.resolveRepo(claim);
16133
+ if (typeof repoPath !== "string") {
16134
+ await fail(repoPath.error);
16135
+ return;
16136
+ }
16137
+ const name = sessionName(claim.item, MOCK_NODE);
16138
+ const branch = nodeBranch(claim.item, MOCK_NODE);
16139
+ let handle;
16140
+ let out;
16141
+ let base;
16142
+ let launch;
16143
+ try {
16144
+ base = d.defaultBranch(d.exec, repoPath);
16145
+ const worktree = d.runner.makesWorktree ? repoPath : d.ensureDraftWorktree(d.exec, repoPath, worktreePath(repoPath, name), base) ?? worktreePath(repoPath, name);
16146
+ out = d.mockOut(name);
16147
+ const prompt = mockPrompt({
16148
+ item: claim.item,
16149
+ title: claim.title,
16150
+ pack: claim.pack,
16151
+ out,
16152
+ base,
16153
+ engine: d.engine
16154
+ });
16155
+ launch = nodeLaunchFor(d, at);
16156
+ const allow = [`Edit(/${out})`];
16157
+ handle = d.runner.start({
16158
+ name,
16159
+ repoPath,
16160
+ worktree,
16161
+ branch,
16162
+ base,
16163
+ promptFile: d.writePrompt(name, prompt),
16164
+ permissionMode: d.permissionMode,
16165
+ ...launchOptions(launch),
16166
+ ...sandboxFor(d, name, worktree, allow),
16167
+ ...hooksFor(d, name, allow),
16168
+ ...mainCheckoutFor(d, worktree)
16169
+ });
16170
+ } catch (e) {
16171
+ await fail(`the engine could not start the session: ${message(e)}`);
16172
+ return;
16173
+ }
16174
+ const watch = safeWatch(d.runner, handle);
16175
+ const run = {
16176
+ item: claim.item,
16177
+ node: MOCK_NODE,
16178
+ title: claim.title,
16179
+ kind: "mock",
16180
+ mock: { out, base, ...launch.model ? { model: launch.model } : {} },
16181
+ ...claim.seatId ? { seatId: claim.seatId } : {},
16182
+ ...claim.runId ? { runId: claim.runId } : {},
16183
+ ...claim.leaseUntil !== undefined ? { leaseUntil: claim.leaseUntil } : {},
16184
+ branch,
16185
+ repoPath,
16186
+ handle,
16187
+ startedAt: d.now(),
16188
+ seenAt: d.now()
16189
+ };
16190
+ state.runs.push(run);
16191
+ d.writeState(state);
16192
+ d.log(`${at}: drawing a mock — no code; the HTML goes to ${out}`);
16193
+ try {
16194
+ await d.source.heartbeat(claim, watch);
16195
+ } catch (e) {
16196
+ d.log(`${at}: started, but the watch handle was not recorded: ${message(e)}`);
16197
+ }
16198
+ started.push({ item: claim.item, node: MOCK_NODE, watch, watchTarget: watchTarget(handle) });
16199
+ }
16200
+ function mockChangedCode(exec, cwd, base) {
16201
+ const status = exec("git", ["-C", cwd, "status", "--porcelain"]);
16202
+ if (status.status !== 0)
16203
+ return `git could not read the checkout: ${status.stderr.trim()}`;
16204
+ const changed = status.stdout.split(`
16205
+ `).filter((l) => l.trim());
16206
+ if (changed.length)
16207
+ return `${changed.length} file${changed.length === 1 ? "" : "s"} changed (${changed.slice(0, 5).map((l) => l.slice(3).trim()).join(", ")}${changed.length > 5 ? ", …" : ""})`;
16208
+ const ahead = exec("git", ["-C", cwd, "rev-list", "--count", `origin/${base}..HEAD`]);
16209
+ if (ahead.status !== 0)
16210
+ return `git could not count the commits: ${ahead.stderr.trim()}`;
16211
+ const n = Number.parseInt(ahead.stdout.trim(), 10);
16212
+ if (!Number.isFinite(n))
16213
+ return "git could not count the commits";
16214
+ return n > 0 ? `${n} commit${n === 1 ? "" : "s"} past origin/${base}` : null;
16215
+ }
16216
+ async function settleMock(d, run, claim, runner, finished) {
16217
+ let beat;
16218
+ try {
16219
+ beat = await d.source.heartbeat(claim, safeWatch(runner, run.handle));
16220
+ } catch (e) {
16221
+ d.log(`${ref(run)}: heartbeat failed, retrying next tick: ${message(e)}`);
16222
+ return true;
16223
+ }
16224
+ if (beat !== "running") {
16225
+ d.log(`${ref(run)}: the mock claim was lost — stopping the session`);
16226
+ endDraft(d, run, runner, "mock session");
16227
+ return false;
16228
+ }
16229
+ if (run.runId)
16230
+ run.leaseUntil = d.now() + d.leaseMin * 60000;
16231
+ let status;
16232
+ try {
16233
+ status = runner.status(run.handle);
16234
+ } catch (e) {
16235
+ d.log(`${ref(run)}: could not read the session's status: ${message(e)}`);
16236
+ return true;
16237
+ }
16238
+ const out = run.mock?.out;
16239
+ const read = out ? d.readMock?.(out) ?? null : null;
16240
+ const drawn = read?.trim() ? read : null;
16241
+ if (status === "running") {
16242
+ if (drawn) {
16243
+ const print = fingerprint(drawn);
16244
+ if (run.mock && run.mock.drawn !== print) {
16245
+ run.mock.drawn = print;
16246
+ return true;
16247
+ }
16248
+ } else {
16249
+ if (!runner.readsSession)
16250
+ return true;
16251
+ let shown = "";
16252
+ try {
16253
+ shown = runner.read(run.handle);
16254
+ } catch {
16255
+ shown = "";
16256
+ }
16257
+ const now = d.now();
16258
+ const print = fingerprint(shown);
16259
+ if (run.seen !== print) {
16260
+ run.seen = print;
16261
+ run.seenAt = now;
16262
+ return true;
16263
+ }
16264
+ if (now - (run.seenAt ?? run.startedAt) < d.idleMin * 60000)
16265
+ return true;
16266
+ }
16267
+ }
16268
+ const changed = mockChangedCode(d.exec, run.handle.cwd, run.mock?.base ?? "main");
16269
+ let log = "";
16270
+ if (changed)
16271
+ log = `the mock run changed code: ${changed}. Nothing was attached.`;
16272
+ else if (!drawn) {
16273
+ let tail = "";
16274
+ try {
16275
+ tail = runner.read(run.handle);
16276
+ } catch (e) {
16277
+ tail = `(the session's output could not be read: ${message(e)})`;
16278
+ }
16279
+ log = status === "exited" ? `The session exited with no HTML at ${out ?? "its file"}.${tail ? `
16280
+ ${tail}` : ""}` : `Idle for ${d.idleMin} min with no HTML written.${tail ? ` Last output:
16281
+ ${tail}` : ""}`;
16282
+ }
16283
+ const mocks = mocksOf(d.source);
16284
+ if (!log && mocks && drawn) {
16285
+ let posted;
16286
+ try {
16287
+ posted = await mocks.finishMock(claim, drawn, {
16288
+ engine: d.engine,
16289
+ ...run.mock?.model ? { model: run.mock.model } : {}
16290
+ });
16291
+ } catch (e) {
16292
+ d.log(`${ref(run)}: the mock is drawn; posting it failed, retrying next tick: ${message(e)}`);
16293
+ return true;
16294
+ }
16295
+ if ("ok" in posted) {
16296
+ d.log(`${ref(run)}: mock drawn — attached to ${run.item} as its mock, for a person to sign`);
16297
+ endDraft(d, run, runner, "mock session");
16298
+ return false;
16299
+ }
16300
+ if ("gone" in posted) {
16301
+ d.log(`${ref(run)}: the mock is drawn, but SLEDS no longer runs it (${posted.gone}) — not attached`);
16302
+ endDraft(d, run, runner, "mock session");
16303
+ return false;
16304
+ }
16305
+ log = `SLEDS refused the HTML: ${posted.refused}`;
16306
+ }
16307
+ if (!log)
16308
+ log = "this engine cannot post mocks";
16309
+ const outcome = { kind: "fail", log };
16310
+ try {
16311
+ await d.source.report(claim, outcome, d.engine);
16312
+ } catch (e) {
16313
+ d.log(`${ref(run)}: the mock run failed; the fail report failed, retrying next tick: ${message(e)}`);
16314
+ return true;
16315
+ }
16316
+ d.log(`${ref(run)}: the mock was not drawn — ${log.split(`
16317
+ `)[0]}`);
16318
+ finished.push({ item: run.item, node: run.node, outcome });
16319
+ endDraft(d, run, runner, "mock session");
16320
+ return false;
16321
+ }
16322
+ function endNodeSession(d, runner, h, repoPath, at) {
15736
16323
  try {
15737
- if (runner.status(run.handle) === "running")
15738
- runner.stop(run.handle);
16324
+ if (runner.status(h) === "running")
16325
+ runner.stop(h);
15739
16326
  } catch (e) {
15740
- d.log(`${ref(run)}: could not close the session: ${message(e)}`);
16327
+ d.log(`${at}: could not close the session: ${message(e)}`);
15741
16328
  }
15742
- const wt = run.handle.cwd;
15743
- if (wt === run.repoPath)
16329
+ if (!runner.makesWorktree)
16330
+ removeEngineWorktree(d, repoPath, h.cwd, at);
16331
+ }
16332
+ function removeEngineWorktree(d, repoPath, wt, at) {
16333
+ if (wt === repoPath || !wt.startsWith(`${repoPath}/.worktrees/`))
15744
16334
  return;
15745
16335
  try {
15746
- if (runner.makesWorktree) {
15747
- const note = runner.removeWorktree?.(run.handle, run.repoPath);
15748
- if (note)
15749
- d.log(`${ref(run)}: ${note}`);
15750
- } else if (wt.startsWith(`${run.repoPath}/.worktrees/`)) {
15751
- d.removeWorktree(d.exec, run.repoPath, wt);
15752
- }
16336
+ d.removeWorktree(d.exec, repoPath, wt);
15753
16337
  } catch (e) {
15754
- d.log(`${ref(run)}: kept the drafter's checkout ${wt}: ${message(e)}`);
16338
+ d.log(`${at}: kept the worktree ${wt}: ${message(e)}`);
15755
16339
  }
15756
16340
  }
15757
16341
  function ciNudge(url, failing) {
@@ -15842,6 +16426,11 @@ async function follow(d, state, run, claim, runner, finished, attention, changes
15842
16426
  stopOpen(d, run, runner);
15843
16427
  return false;
15844
16428
  }
16429
+ try {
16430
+ pr.live = runner.status(run.handle) === "running" || undefined;
16431
+ } catch {
16432
+ pr.live = undefined;
16433
+ }
15845
16434
  const at = { item: run.item, node: run.node, url: pr.url };
15846
16435
  const ci = ciLine(view);
15847
16436
  if (ci !== pr.ci)
@@ -16791,16 +17380,60 @@ init_state();
16791
17380
  init_meter();
16792
17381
 
16793
17382
  // src/runners/gitGuard.ts
16794
- import { basename as basename4 } from "node:path";
17383
+ import { statSync as statSync2 } from "node:fs";
17384
+ import { basename as basename4, dirname as dirname12, join as join18, resolve as resolve8 } from "node:path";
16795
17385
  var GIT_GUARD_ENV = {
16796
- GIT_CONFIG_COUNT: "2",
17386
+ GIT_CONFIG_COUNT: "4",
16797
17387
  GIT_CONFIG_KEY_0: "core.fsmonitor",
16798
17388
  GIT_CONFIG_VALUE_0: "false",
16799
17389
  GIT_CONFIG_KEY_1: "core.hooksPath",
16800
- GIT_CONFIG_VALUE_1: "/dev/null"
17390
+ GIT_CONFIG_VALUE_1: "/dev/null",
17391
+ GIT_CONFIG_KEY_2: "remote.origin.uploadpack",
17392
+ GIT_CONFIG_VALUE_2: "git-upload-pack",
17393
+ GIT_CONFIG_KEY_3: "remote.origin.receivepack",
17394
+ GIT_CONFIG_VALUE_3: "git-receive-pack",
17395
+ GIT_ALLOW_PROTOCOL: "file:http:https:ssh",
17396
+ GIT_TERMINAL_PROMPT: "0"
17397
+ };
17398
+ function trustedSshCommand(env = process.env, exec = defaultExec) {
17399
+ if (env.GIT_SSH_COMMAND?.trim())
17400
+ return env.GIT_SSH_COMMAND;
17401
+ const r = exec("git", ["config", "--get", "core.sshCommand"], {
17402
+ cwd: "/",
17403
+ env: { GIT_CEILING_DIRECTORIES: "/" },
17404
+ timeoutMs: 5000
17405
+ });
17406
+ return r.status === 0 && r.stdout.trim() || "ssh";
17407
+ }
17408
+ var ssh;
17409
+ function gitGuardEnv() {
17410
+ ssh ??= trustedSshCommand();
17411
+ return { ...GIT_GUARD_ENV, GIT_SSH_COMMAND: ssh };
17412
+ }
17413
+ var isFile = (p) => statSync2(p, { throwIfNoEntry: false })?.isFile() ?? false;
17414
+ var isDir = (p) => statSync2(p, { throwIfNoEntry: false })?.isDirectory() ?? false;
17415
+ function ownGitDirs(dir, stat = { isFile, isDir }) {
17416
+ const wt = resolve8(dir);
17417
+ if (basename4(dirname12(wt)) !== ".worktrees")
17418
+ return null;
17419
+ const common = join18(dirname12(dirname12(wt)), ".git");
17420
+ if (!stat.isFile(join18(wt, ".git")) || !stat.isDir(common))
17421
+ return null;
17422
+ return {
17423
+ GIT_DIR: join18(common, "worktrees", basename4(wt)),
17424
+ GIT_COMMON_DIR: common,
17425
+ GIT_WORK_TREE: wt
17426
+ };
17427
+ }
17428
+ var callDir = (args, cwd) => {
17429
+ const base = cwd ?? process.cwd();
17430
+ return args[0] === "-C" && args[1] ? resolve8(base, args[1]) : base;
16801
17431
  };
16802
17432
  var GUARDED = new Set(["git", "gh"]);
16803
- var guardGit = (exec) => (cmd, args, o) => GUARDED.has(basename4(cmd)) ? exec(cmd, args, { ...o, env: { ...o?.env, ...GIT_GUARD_ENV } }) : exec(cmd, args, o);
17433
+ var guardGit = (exec, guard = gitGuardEnv) => (cmd, args, o) => GUARDED.has(basename4(cmd)) ? exec(cmd, args, {
17434
+ ...o,
17435
+ env: { ...o?.env, ...guard(), ...ownGitDirs(callDir(args, o?.cwd)) }
17436
+ }) : exec(cmd, args, o);
16804
17437
 
16805
17438
  // src/runners/command.ts
16806
17439
  var expandCommandTemplate = (template, o) => {
@@ -17500,7 +18133,7 @@ function tmuxRunner(exec) {
17500
18133
  return r.status === 0 ? r.stdout : "";
17501
18134
  },
17502
18135
  send(h, text) {
17503
- const typed = exec("tmux", ["send-keys", "-t", tmuxPane(h.id), "-l", text]);
18136
+ const typed = exec("tmux", ["send-keys", "-t", tmuxPane(h.id), "-l", "--", text]);
17504
18137
  if (typed.status !== 0) {
17505
18138
  throw new Error(`tmux send-keys ${h.id} failed: ${typed.stderr.trim() || `exit ${typed.status}`}`);
17506
18139
  }
@@ -17568,9 +18201,20 @@ function restoreRunner(name, exec = defaultExec) {
17568
18201
  init_settings();
17569
18202
 
17570
18203
  // src/update/drain.ts
17571
- import { closeSync as closeSync4, existsSync as existsSync18, mkdirSync as mkdirSync17, openSync as openSync4, rmSync as rmSync9, statSync as statSync4, writeSync as writeSync2 } from "node:fs";
18204
+ import { randomUUID as randomUUID3 } from "node:crypto";
18205
+ import {
18206
+ closeSync as closeSync4,
18207
+ existsSync as existsSync18,
18208
+ mkdirSync as mkdirSync17,
18209
+ openSync as openSync4,
18210
+ readFileSync as readFileSync15,
18211
+ rmSync as rmSync9,
18212
+ statSync as statSync5,
18213
+ utimesSync,
18214
+ writeSync as writeSync2
18215
+ } from "node:fs";
17572
18216
  import { homedir as homedir7 } from "node:os";
17573
- import { join as join23 } from "node:path";
18217
+ import { join as join24 } from "node:path";
17574
18218
 
17575
18219
  // ../../packages/shared/src/updates/manifest.ts
17576
18220
  var CLI_PLATFORMS = [
@@ -17665,7 +18309,7 @@ function belowMinimum(version, minimum) {
17665
18309
  init_state();
17666
18310
 
17667
18311
  // src/update/fetch.ts
17668
- import { createHash } from "node:crypto";
18312
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
17669
18313
  import {
17670
18314
  chmodSync as chmodSync4,
17671
18315
  closeSync as closeSync3,
@@ -17709,16 +18353,16 @@ import {
17709
18353
  readlinkSync as readlinkSync2,
17710
18354
  renameSync as renameSync7,
17711
18355
  rmSync as rmSync4,
17712
- statSync as statSync2,
18356
+ statSync as statSync3,
17713
18357
  writeFileSync as writeFileSync13
17714
18358
  } from "node:fs";
17715
18359
  import { homedir as homedir3 } from "node:os";
17716
- import { dirname as dirname12, join as join18, win32 as win322 } from "node:path";
18360
+ import { dirname as dirname13, join as join19, win32 as win322 } from "node:path";
17717
18361
  var UPDATE_LOCK_STALE_MS = 10 * 60000;
17718
18362
  function sledsRoot(home = homedir3(), platform = process.platform) {
17719
- return platform === "win32" ? win322.join(process.env.LOCALAPPDATA ?? win322.join(home, "AppData", "Local"), "sleds") : join18(home, ".sleds");
18363
+ return platform === "win32" ? win322.join(process.env.LOCALAPPDATA ?? win322.join(home, "AppData", "Local"), "sleds") : join19(home, ".sleds");
17720
18364
  }
17721
- var pathFor = (platform) => platform === "win32" ? win322 : { join: join18 };
18365
+ var pathFor = (platform) => platform === "win32" ? win322 : { join: join19 };
17722
18366
  function versionDir(home, v, platform = process.platform) {
17723
18367
  return pathFor(platform).join(sledsRoot(home, platform), "versions", v);
17724
18368
  }
@@ -17791,36 +18435,87 @@ function readUpdateState(home) {
17791
18435
  }
17792
18436
  function writeUpdateState(home, s) {
17793
18437
  const path = updateStatePath(home);
17794
- mkdirSync12(dirname12(path), { recursive: true, mode: 448 });
18438
+ mkdirSync12(dirname13(path), { recursive: true, mode: 448 });
17795
18439
  const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`;
17796
18440
  writeFileSync13(tmp, `${JSON.stringify(s, null, 2)}
17797
18441
  `, { mode: 384 });
17798
18442
  renameSync7(tmp, path);
17799
18443
  }
17800
- function readLockBody(path) {
18444
+ var BREAK_STALE_MS = 30000;
18445
+ var readText = (path) => {
18446
+ try {
18447
+ return readFileSync13(path, "utf8");
18448
+ } catch {
18449
+ return null;
18450
+ }
18451
+ };
18452
+ function parseLockBody(text) {
18453
+ if (text === null)
18454
+ return null;
17801
18455
  try {
17802
- const v = JSON.parse(readFileSync13(path, "utf8"));
18456
+ const v = JSON.parse(text);
17803
18457
  if (isObj3(v) && num7(v.pid) && num7(v.at))
17804
18458
  return { pid: v.pid, at: v.at, ...str11(v.token) ? { token: v.token } : {} };
17805
18459
  } catch {}
17806
18460
  return null;
17807
18461
  }
17808
- function lockStale(path, now) {
17809
- const body = readLockBody(path);
18462
+ var readLockBody = (path) => parseLockBody(readText(path));
18463
+ function lockStale(path, text, now, maxMs) {
17810
18464
  try {
17811
- const at = body?.at ?? statSync2(path).mtimeMs;
17812
- return now - at > UPDATE_LOCK_STALE_MS;
18465
+ const at = parseLockBody(text)?.at ?? statSync3(path).mtimeMs;
18466
+ return now - at > maxMs;
17813
18467
  } catch {
17814
18468
  return false;
17815
18469
  }
17816
18470
  }
18471
+ function removeIfSame(path, text, aside) {
18472
+ try {
18473
+ renameSync7(path, aside);
18474
+ } catch {
18475
+ return;
18476
+ }
18477
+ if (readText(aside) !== text) {
18478
+ try {
18479
+ linkSync2(aside, path);
18480
+ } catch {}
18481
+ }
18482
+ rmSync4(aside, { force: true });
18483
+ }
18484
+ function breakStale(path, seen, token) {
18485
+ const brk = `${path}.break`;
18486
+ const tmp = `${brk}.${token}.tmp`;
18487
+ const aside = `${path}.${token}.stale`;
18488
+ const body = `${JSON.stringify({ pid: process.pid, at: Date.now(), token })}
18489
+ `;
18490
+ writeFileSync13(tmp, body, { mode: 384 });
18491
+ try {
18492
+ try {
18493
+ linkSync2(tmp, brk);
18494
+ } catch (e) {
18495
+ if (e.code !== "EEXIST")
18496
+ throw e;
18497
+ const held = readText(brk);
18498
+ if (held !== null && lockStale(brk, held, Date.now(), BREAK_STALE_MS))
18499
+ removeIfSame(brk, held, aside);
18500
+ return false;
18501
+ }
18502
+ try {
18503
+ if (readText(path) === seen && lockStale(path, seen, Date.now(), UPDATE_LOCK_STALE_MS))
18504
+ removeIfSame(path, seen, aside);
18505
+ return true;
18506
+ } finally {
18507
+ removeIfSame(brk, body, aside);
18508
+ }
18509
+ } finally {
18510
+ rmSync4(tmp, { force: true });
18511
+ }
18512
+ }
17817
18513
  async function withUpdateLock(home, fn, o = {}) {
17818
18514
  const waitMs = o.waitMs ?? 30000;
17819
18515
  const path = updateLockPath(home);
17820
- mkdirSync12(dirname12(path), { recursive: true, mode: 448 });
18516
+ mkdirSync12(dirname13(path), { recursive: true, mode: 448 });
17821
18517
  const token = randomUUID();
17822
18518
  const tmp = `${path}.${token}.tmp`;
17823
- const aside = `${path}.${token}.stale`;
17824
18519
  writeFileSync13(tmp, `${JSON.stringify({ pid: process.pid, at: Date.now(), token })}
17825
18520
  `, {
17826
18521
  mode: 384
@@ -17835,22 +18530,9 @@ async function withUpdateLock(home, fn, o = {}) {
17835
18530
  if (e.code !== "EEXIST")
17836
18531
  throw e;
17837
18532
  }
17838
- if (lockStale(path, Date.now())) {
17839
- try {
17840
- renameSync7(path, aside);
17841
- } catch (e) {
17842
- if (e.code === "ENOENT")
17843
- continue;
17844
- throw e;
17845
- }
17846
- if (!lockStale(aside, Date.now())) {
17847
- try {
17848
- linkSync2(aside, path);
17849
- } catch {}
17850
- }
17851
- rmSync4(aside, { force: true });
18533
+ const seen = readText(path);
18534
+ if (seen !== null && lockStale(path, seen, Date.now(), UPDATE_LOCK_STALE_MS) && breakStale(path, seen, token))
17852
18535
  continue;
17853
- }
17854
18536
  if (Date.now() - start >= waitMs) {
17855
18537
  const held = readLockBody(path);
17856
18538
  throw new Error(`update.json is locked${held ? ` by pid ${held.pid}` : ""} (${path})`);
@@ -17897,7 +18579,7 @@ async function getText(fetchFn, url) {
17897
18579
  return res.text();
17898
18580
  }
17899
18581
  async function sha256File(path) {
17900
- const hash = createHash("sha256");
18582
+ const hash = createHash2("sha256");
17901
18583
  let size = 0;
17902
18584
  for await (const chunk of createReadStream(path)) {
17903
18585
  hash.update(chunk);
@@ -17910,7 +18592,7 @@ async function fetchVersion(o) {
17910
18592
  const platform = o.platform ?? process.platform;
17911
18593
  const arch = o.arch ?? process.arch;
17912
18594
  const publicKey = o.publicKey ?? RELEASE_PUBLIC_KEY;
17913
- const running = o.running ?? "0.1.60";
18595
+ const running = o.running ?? "0.1.61";
17914
18596
  if (!publicKey)
17915
18597
  return { ok: false, why: "no release key yet" };
17916
18598
  if (!VERSION_RE2.test(o.version))
@@ -17949,12 +18631,12 @@ async function fetchVersion(o) {
17949
18631
  } catch {}
17950
18632
  }
17951
18633
  mkdirSync13(versionDir(o.home, o.version, platform), { recursive: true, mode: 493 });
17952
- const part = `${bin}.part`;
18634
+ const part = `${bin}.${process.pid}.${randomUUID2()}.part`;
17953
18635
  try {
17954
18636
  const res = await fetchFn(asset.url);
17955
18637
  if (!res.ok || !res.body)
17956
18638
  return { ok: false, why: `${asset.url}: HTTP ${res.status}` };
17957
- const hash = createHash("sha256");
18639
+ const hash = createHash2("sha256");
17958
18640
  let size = 0;
17959
18641
  const fd = openSync3(part, "w", 384);
17960
18642
  try {
@@ -17964,6 +18646,7 @@ async function fetchVersion(o) {
17964
18646
  break;
17965
18647
  hash.update(chunk);
17966
18648
  writeSync(fd, chunk);
18649
+ o.progress?.();
17967
18650
  }
17968
18651
  } finally {
17969
18652
  closeSync3(fd);
@@ -17999,24 +18682,24 @@ import {
17999
18682
  readlinkSync as readlinkSync3,
18000
18683
  renameSync as renameSync9,
18001
18684
  rmSync as rmSync7,
18002
- statSync as statSync3,
18685
+ statSync as statSync4,
18003
18686
  symlinkSync,
18004
18687
  writeFileSync as writeFileSync15
18005
18688
  } from "node:fs";
18006
18689
  import { homedir as homedir4 } from "node:os";
18007
- import { basename as basename5, delimiter as delimiter2, dirname as dirname13, isAbsolute as isAbsolute2, join as join21, relative } from "node:path";
18690
+ import { basename as basename5, delimiter as delimiter2, dirname as dirname14, isAbsolute as isAbsolute2, join as join22, relative } from "node:path";
18008
18691
 
18009
18692
  // src/commands/engineSystemd.ts
18010
- import { join as join19 } from "node:path";
18693
+ import { join as join20 } from "node:path";
18011
18694
  var BOX_ENV_UNIT_PATH = "%h/.config/sleds/box.env";
18012
18695
  function boxEnvPath(home) {
18013
- return join19(home, ".config", "sleds", "box.env");
18696
+ return join20(home, ".config", "sleds", "box.env");
18014
18697
  }
18015
18698
  function engineUnitDir(home) {
18016
- return join19(home, ".config", "systemd", "user");
18699
+ return join20(home, ".config", "systemd", "user");
18017
18700
  }
18018
18701
  function engineUnitPath(home, label) {
18019
- return join19(engineUnitDir(home), `${label}.service`);
18702
+ return join20(engineUnitDir(home), `${label}.service`);
18020
18703
  }
18021
18704
  function systemdQuote(s) {
18022
18705
  const body = s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/%/g, "%%").replace(/\$/g, "$$$$");
@@ -18058,7 +18741,7 @@ function parseEngineUnit(text) {
18058
18741
  // src/schedule.ts
18059
18742
  import { spawnSync as spawnSync4 } from "node:child_process";
18060
18743
  import { existsSync as existsSync15, mkdirSync as mkdirSync14, rmSync as rmSync6, writeFileSync as writeFileSync14 } from "node:fs";
18061
- import { join as join20 } from "node:path";
18744
+ import { join as join21 } from "node:path";
18062
18745
  var REVERT_LABEL = "ai.sleds.revert";
18063
18746
  var defaultExec2 = (cmd, args) => {
18064
18747
  const r = spawnSync4(cmd, [...args], { stdio: ["ignore", "ignore", "pipe"], encoding: "utf8" });
@@ -18066,7 +18749,7 @@ var defaultExec2 = (cmd, args) => {
18066
18749
  return { ok: false, stderr: r.error.message };
18067
18750
  return { ok: r.status === 0, stderr: r.stderr ?? "" };
18068
18751
  };
18069
- var plistPath = (home) => join20(home, "Library", "LaunchAgents", `${REVERT_LABEL}.plist`);
18752
+ var plistPath = (home) => join21(home, "Library", "LaunchAgents", `${REVERT_LABEL}.plist`);
18070
18753
  var xmlEscape2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
18071
18754
  var buildLaunchdPlist = (sledsArgv, deadline) => {
18072
18755
  const args = [...sledsArgv, "off"].map((a) => ` <string>${xmlEscape2(a)}</string>`).join(`
@@ -18105,7 +18788,7 @@ var scheduleRevert = (d, deadlineMs) => {
18105
18788
  if (d.platform === "darwin") {
18106
18789
  const p = plistPath(d.home);
18107
18790
  try {
18108
- mkdirSync14(join20(d.home, "Library", "LaunchAgents"), { recursive: true });
18791
+ mkdirSync14(join21(d.home, "Library", "LaunchAgents"), { recursive: true });
18109
18792
  d.exec("launchctl", ["unload", p]);
18110
18793
  writeFileSync14(p, buildLaunchdPlist(d.sledsArgv, deadline));
18111
18794
  const r = d.exec("launchctl", ["load", p]);
@@ -18157,8 +18840,8 @@ var HOOK_SUBS = [
18157
18840
  "stop-event",
18158
18841
  "permission-event"
18159
18842
  ];
18160
- var migrateBakDir = (home) => join21(sledsRoot(home), "migrate.bak");
18161
- var manifestPath = (home) => join21(migrateBakDir(home), "manifest.json");
18843
+ var migrateBakDir = (home) => join22(sledsRoot(home), "migrate.bak");
18844
+ var manifestPath = (home) => join22(migrateBakDir(home), "manifest.json");
18162
18845
  var xmlEscape3 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
18163
18846
  var xmlUnescape = (s) => s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
18164
18847
  var shQuote = (s) => /^[\w./~-]+$/.test(s) ? s : `'${s.replace(/'/g, "'\\''")}'`;
@@ -18258,13 +18941,13 @@ function rewriteSettings(text, link) {
18258
18941
  }
18259
18942
  var listDir = (dir, keep) => {
18260
18943
  try {
18261
- return readdirSync2(dir).filter(keep).sort().map((n) => join21(dir, n));
18944
+ return readdirSync2(dir).filter(keep).sort().map((n) => join22(dir, n));
18262
18945
  } catch {
18263
18946
  return [];
18264
18947
  }
18265
18948
  };
18266
18949
  function unitFiles(home) {
18267
- const agents = listDir(join21(home, "Library", "LaunchAgents"), (n) => n.startsWith("ai.sleds.") && n.endsWith(".plist")).map((path) => ({
18950
+ const agents = listDir(join22(home, "Library", "LaunchAgents"), (n) => n.startsWith("ai.sleds.") && n.endsWith(".plist")).map((path) => ({
18268
18951
  path,
18269
18952
  ...basename5(path).startsWith("ai.sleds.revert") ? {} : { reload: "launchd" }
18270
18953
  }));
@@ -18276,7 +18959,7 @@ function unitClaudeDir(text) {
18276
18959
  return plist ? xmlUnescape(plist) : undefined;
18277
18960
  }
18278
18961
  function claudeDirs(home, env) {
18279
- const dirs = [join21(home, ".claude"), claudeDir(home, env)];
18962
+ const dirs = [join22(home, ".claude"), claudeDir(home, env)];
18280
18963
  for (const u of unitFiles(home)) {
18281
18964
  try {
18282
18965
  const d = unitClaudeDir(readFileSync14(u.path, "utf8"));
@@ -18300,7 +18983,7 @@ function migrationPlan(home, link, env = process.env) {
18300
18983
  out.push({ ...u, text: next });
18301
18984
  }
18302
18985
  for (const dir of claudeDirs(home, env)) {
18303
- const path = join21(dir, "settings.json");
18986
+ const path = join22(dir, "settings.json");
18304
18987
  let text;
18305
18988
  try {
18306
18989
  text = readFileSync14(path, "utf8");
@@ -18315,12 +18998,12 @@ function migrationPlan(home, link, env = process.env) {
18315
18998
  }
18316
18999
  var backupFor = (home, path) => {
18317
19000
  const rel = relative(home, path);
18318
- return rel.startsWith("..") || isAbsolute2(rel) ? join21(migrateBakDir(home), "abs", path) : join21(migrateBakDir(home), rel);
19001
+ return rel.startsWith("..") || isAbsolute2(rel) ? join22(migrateBakDir(home), "abs", path) : join22(migrateBakDir(home), rel);
18319
19002
  };
18320
19003
  function writeKeepingMode(path, text) {
18321
19004
  let mode = 420;
18322
19005
  try {
18323
- mode = statSync3(path).mode & 511;
19006
+ mode = statSync4(path).mode & 511;
18324
19007
  } catch {}
18325
19008
  const tmp = `${path}.${process.pid}.migrate.tmp`;
18326
19009
  writeFileSync15(tmp, text, { mode });
@@ -18332,7 +19015,7 @@ function copyInto(from, to) {
18332
19015
  renameSync9(tmp, to);
18333
19016
  }
18334
19017
  function pointLink(link, bin) {
18335
- mkdirSync15(dirname13(link), { recursive: true, mode: 448 });
19018
+ mkdirSync15(dirname14(link), { recursive: true, mode: 448 });
18336
19019
  const tmp = `${link}.tmp`;
18337
19020
  rmSync7(tmp, { force: true });
18338
19021
  symlinkSync(bin, tmp);
@@ -18357,9 +19040,7 @@ function reloadScript(lock, files) {
18357
19040
  for (const a of agents)
18358
19041
  lines.push(`launchctl unload ${shQuote(a.path)} 2>/dev/null; launchctl load ${shQuote(a.path)}`);
18359
19042
  if (units.length)
18360
- lines.push("systemctl --user daemon-reload");
18361
- for (const u of units)
18362
- lines.push(`systemctl --user restart ${shQuote(basename5(u.path))}`);
19043
+ lines.push("systemctl --user daemon-reload", `systemctl --user --no-block restart ${units.map((u) => shQuote(basename5(u.path))).join(" ")}`);
18363
19044
  if (!lines.length)
18364
19045
  return null;
18365
19046
  return [
@@ -18453,7 +19134,7 @@ async function migrateToBinary(o) {
18453
19134
  rmSync7(migrateBakDir(home), { recursive: true, force: true });
18454
19135
  for (const p of plan) {
18455
19136
  const backup = backupFor(home, p.path);
18456
- mkdirSync15(dirname13(backup), { recursive: true, mode: 448 });
19137
+ mkdirSync15(dirname14(backup), { recursive: true, mode: 448 });
18457
19138
  copyFileSync(p.path, backup);
18458
19139
  manifest.files.push({ path: p.path, backup, ...p.reload ? { reload: p.reload } : {} });
18459
19140
  }
@@ -18487,7 +19168,7 @@ async function migrateToBinary(o) {
18487
19168
  log(`sleds stays on the npm install: ${result.why}`);
18488
19169
  return { ok: false, why: result.why };
18489
19170
  }
18490
- const binDir = dirname13(link);
19171
+ const binDir = dirname14(link);
18491
19172
  if (!(env.PATH ?? "").split(delimiter2).includes(binDir))
18492
19173
  log(`${binDir} is not on your PATH — add it in your shell profile to run this sleds from a terminal (sleds never edits it)`);
18493
19174
  reload(home, result.files, detach);
@@ -18507,10 +19188,10 @@ import {
18507
19188
  symlinkSync as symlinkSync2
18508
19189
  } from "node:fs";
18509
19190
  import { homedir as homedir5 } from "node:os";
18510
- import { dirname as dirname14, join as join22 } from "node:path";
19191
+ import { dirname as dirname15, join as join23 } from "node:path";
18511
19192
  var NO_PREVIOUS = "no previous version on this machine";
18512
19193
  function allStateFiles(home = homedir5(), env = process.env) {
18513
- const roots = [...new Set([configDir(home, env), join22(home, ".config", "sleds")])];
19194
+ const roots = [...new Set([configDir(home, env), join23(home, ".config", "sleds")])];
18514
19195
  const out = [];
18515
19196
  const walk = (dir) => {
18516
19197
  let names;
@@ -18521,20 +19202,20 @@ function allStateFiles(home = homedir5(), env = process.env) {
18521
19202
  }
18522
19203
  for (const n of names.sort())
18523
19204
  if (n.endsWith(".json") && n !== "removed-engines.json")
18524
- out.push(join22(dir, n));
19205
+ out.push(join23(dir, n));
18525
19206
  for (const sub of ["orgs", "seats"]) {
18526
19207
  let kids;
18527
19208
  try {
18528
- kids = readdirSync3(join22(dir, sub));
19209
+ kids = readdirSync3(join23(dir, sub));
18529
19210
  } catch {
18530
19211
  continue;
18531
19212
  }
18532
19213
  for (const k of kids.sort())
18533
- walk(join22(dir, sub, k));
19214
+ walk(join23(dir, sub, k));
18534
19215
  }
18535
19216
  };
18536
19217
  for (const r of roots)
18537
- walk(join22(r, "engine"));
19218
+ walk(join23(r, "engine"));
18538
19219
  return out;
18539
19220
  }
18540
19221
  var backupPath2 = (file, v) => `${file}.${v}.bak`;
@@ -18548,7 +19229,7 @@ function pointBinAt(home, v, platform) {
18548
19229
  const target = versionBin(home, v, platform);
18549
19230
  if (!existsSync16(target))
18550
19231
  throw new Error(`${target} is missing — sleds ${v} is not downloaded`);
18551
- mkdirSync16(dirname14(bin), { recursive: true, mode: 448 });
19232
+ mkdirSync16(dirname15(bin), { recursive: true, mode: 448 });
18552
19233
  if (platform === "win32") {
18553
19234
  replaceAside(bin, target);
18554
19235
  return;
@@ -18620,7 +19301,7 @@ function applyConfirm(s, home, o = {}) {
18620
19301
  if (!s.current)
18621
19302
  return;
18622
19303
  for (const f of o.stateFiles ?? allStateFiles(home)) {
18623
- const dir = dirname14(f);
19304
+ const dir = dirname15(f);
18624
19305
  const prefix = `${f.slice(dir.length + 1)}.`;
18625
19306
  let names;
18626
19307
  try {
@@ -18630,10 +19311,10 @@ function applyConfirm(s, home, o = {}) {
18630
19311
  }
18631
19312
  for (const n of names)
18632
19313
  if (n.startsWith(prefix) && n.endsWith(".bak"))
18633
- rmSync8(join22(dir, n), { force: true });
19314
+ rmSync8(join23(dir, n), { force: true });
18634
19315
  }
18635
19316
  const keep = new Set([s.current, s.previous, s.drain?.to].filter(Boolean));
18636
- const versions = dirname14(versionDir(home, s.current, platform));
19317
+ const versions = dirname15(versionDir(home, s.current, platform));
18637
19318
  let names = [];
18638
19319
  try {
18639
19320
  names = readdirSync3(versions);
@@ -18684,6 +19365,7 @@ function reconcile(s, home, o) {
18684
19365
  // src/update/guard.ts
18685
19366
  var UPDATE_EXIT = 4;
18686
19367
  var FIRST_BEAT_MS = 180000;
19368
+ var FIRE_RETRY_MS = 15000;
18687
19369
  var STARTS_PER_ENGINE = 3;
18688
19370
  function guarded(home, version, platform) {
18689
19371
  return !!version && version !== "dev" && existsSync17(sledsRoot(home, platform));
@@ -18767,9 +19449,16 @@ function firstBeatWatchdog(o) {
18767
19449
  const clearTimer = o.clearTimer ?? ((t) => clearTimeout(t));
18768
19450
  let done = false;
18769
19451
  let confirming = false;
19452
+ const setTimer = o.setTimer ?? ((fn, ms) => {
19453
+ const t = setTimeout(fn, ms);
19454
+ t.unref?.();
19455
+ return t;
19456
+ });
19457
+ let timer;
18770
19458
  const fire = () => {
18771
19459
  if (done)
18772
19460
  return;
19461
+ const lock = o.lockWaitMs !== undefined ? { waitMs: o.lockWaitMs } : {};
18773
19462
  withUpdateLock(home, (s) => {
18774
19463
  if (s.pending?.to === version)
18775
19464
  return rollBackHere(s, home, version, `sleds ${version} did not check in within 3 minutes`, now(), o);
@@ -18781,20 +19470,20 @@ function firstBeatWatchdog(o) {
18781
19470
  why: `sleds ${version} was rolled back on this machine — restarting onto ${at}`
18782
19471
  };
18783
19472
  return null;
18784
- }).then((g) => {
19473
+ }, lock).then((g) => {
18785
19474
  done = true;
18786
19475
  if (g && !g.go) {
18787
19476
  say(g.why);
18788
19477
  exit(g.exit);
18789
19478
  }
18790
- }, (e) => say(`the first-beat check could not run: ${e instanceof Error ? e.message : String(e)}`));
19479
+ }, (e) => {
19480
+ if (done)
19481
+ return;
19482
+ say(`the first-beat check could not run: ${e instanceof Error ? e.message : String(e)} — trying again in ${FIRE_RETRY_MS / 1000} s`);
19483
+ timer = setTimer(fire, FIRE_RETRY_MS);
19484
+ });
18791
19485
  };
18792
- const setTimer = o.setTimer ?? ((fn, ms) => {
18793
- const t = setTimeout(fn, ms);
18794
- t.unref?.();
18795
- return t;
18796
- });
18797
- const timer = setTimer(fire, Math.max(0, pending.at + FIRST_BEAT_MS - now()));
19486
+ timer = setTimer(fire, Math.max(0, pending.at + FIRST_BEAT_MS - now()));
18798
19487
  return {
18799
19488
  beat: async () => {
18800
19489
  if (done || confirming)
@@ -18891,9 +19580,10 @@ async function machineAnswer(client, tokens) {
18891
19580
  return null;
18892
19581
  }
18893
19582
  }
19583
+ var FETCH_LOCK_TOUCH_MS = 30000;
18894
19584
  async function fetchOnce(o) {
18895
19585
  const dir = versionDir(o.home, o.version, o.platform);
18896
- const lock = join23(dir, "fetch.lock");
19586
+ const lock = join24(dir, "fetch.lock");
18897
19587
  mkdirSync17(dir, { recursive: true, mode: 493 });
18898
19588
  let fd;
18899
19589
  try {
@@ -18902,7 +19592,7 @@ async function fetchOnce(o) {
18902
19592
  if (e.code !== "EEXIST")
18903
19593
  throw e;
18904
19594
  try {
18905
- if ((o.now ?? Date.now()) - statSync4(lock).mtimeMs <= FETCH_LOCK_STALE_MS)
19595
+ if ((o.now ?? Date.now()) - statSync5(lock).mtimeMs <= FETCH_LOCK_STALE_MS)
18906
19596
  return null;
18907
19597
  } catch {
18908
19598
  return null;
@@ -18910,14 +19600,28 @@ async function fetchOnce(o) {
18910
19600
  rmSync9(lock, { force: true });
18911
19601
  return fetchOnce(o);
18912
19602
  }
19603
+ const mine = `${process.pid} ${randomUUID3()}
19604
+ `;
18913
19605
  try {
18914
- writeSync2(fd, `${process.pid}
18915
- `);
19606
+ writeSync2(fd, mine);
18916
19607
  closeSync4(fd);
18917
19608
  const { fetch: f = fetchVersion, now: _n, ...rest } = o;
18918
- return await f(rest);
19609
+ let touchedAt = 0;
19610
+ const progress = () => {
19611
+ const t = Date.now();
19612
+ if (t - touchedAt < FETCH_LOCK_TOUCH_MS)
19613
+ return;
19614
+ touchedAt = t;
19615
+ try {
19616
+ utimesSync(lock, t / 1000, t / 1000);
19617
+ } catch {}
19618
+ };
19619
+ return await f({ ...rest, progress });
18919
19620
  } finally {
18920
- rmSync9(lock, { force: true });
19621
+ try {
19622
+ if (readFileSync15(lock, "utf8") === mine)
19623
+ rmSync9(lock, { force: true });
19624
+ } catch {}
18921
19625
  }
18922
19626
  }
18923
19627
  function machineVersion(home, platform) {
@@ -19400,7 +20104,7 @@ function flagsFromProfile(flags, given, profile, home) {
19400
20104
  }
19401
20105
  }
19402
20106
  if (profile.reposDir && !has("repos-dir") && !has("repo")) {
19403
- out.reposDir = splitReposDirs(profile.reposDir).map((d) => home && /^~(\/|$)/.test(d) ? join24(home, d.slice(1)) : d).join(":");
20107
+ out.reposDir = splitReposDirs(profile.reposDir).map((d) => home && /^~(\/|$)/.test(d) ? join25(home, d.slice(1)) : d).join(":");
19404
20108
  used.push(`repos ${profile.reposDir}`);
19405
20109
  }
19406
20110
  if (profile.mcpStrict !== undefined && !has("strict-mcp") && !has("no-strict-mcp")) {
@@ -19495,7 +20199,7 @@ function engineClient(o, org, breaker) {
19495
20199
  ...src,
19496
20200
  ...breaker ? { fetchFn: breaker.fetch } : {},
19497
20201
  timeoutMs: ENGINE_TIMEOUT_MS,
19498
- version: "0.1.60"
20202
+ version: "0.1.61"
19499
20203
  });
19500
20204
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
19501
20205
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -19528,19 +20232,19 @@ function realPidAlive2(pid) {
19528
20232
  function memberMcpConfigs(o, cwd) {
19529
20233
  const read = o.readJson ?? ((p) => {
19530
20234
  try {
19531
- return JSON.parse(readFileSync15(p, "utf8"));
20235
+ return JSON.parse(readFileSync16(p, "utf8"));
19532
20236
  } catch {
19533
20237
  return null;
19534
20238
  }
19535
20239
  });
19536
20240
  const claudeDir = (o.env ?? process.env).CLAUDE_CONFIG_DIR;
19537
20241
  return [
19538
- read(claudeDir ? join24(claudeDir, ".claude.json") : join24(o.home, ".claude.json")),
19539
- ...cwd ? [read(join24(cwd, ".mcp.json"))] : []
20242
+ read(claudeDir ? join25(claudeDir, ".claude.json") : join25(o.home, ".claude.json")),
20243
+ ...cwd ? [read(join25(cwd, ".mcp.json"))] : []
19540
20244
  ];
19541
20245
  }
19542
20246
  function engineStateDir(o, org, seat) {
19543
- return seatStateDir(orgStateDir(o.stateDir ?? join24(configDir(o.home, o.env ?? process.env), "engine"), org), seat);
20247
+ return seatStateDir(orgStateDir(o.stateDir ?? join25(configDir(o.home, o.env ?? process.env), "engine"), org), seat);
19544
20248
  }
19545
20249
  function engineSeat(o, org) {
19546
20250
  try {
@@ -19567,11 +20271,11 @@ function engineDeps(o, flags, source, runner, stateName) {
19567
20271
  const stateDir = engineStateDir(o, flags.org, flags.seat);
19568
20272
  const file = statePath(stateDir, stateName);
19569
20273
  const mcpOrg = flags.org && engineOrg(o, flags.org).seated ? flags.org : undefined;
19570
- const repo = flags.repo ? resolve8(cwd, flags.repo) : undefined;
19571
- const reposDirs = splitReposDirs(flags.reposDir).map((d) => resolve8(cwd, d));
20274
+ const repo = flags.repo ? resolve9(cwd, flags.repo) : undefined;
20275
+ const reposDirs = splitReposDirs(flags.reposDir).map((d) => resolve9(cwd, d));
19572
20276
  const { branch, prTitle } = flags;
19573
- const hooksDir = join24(stateDir, "hooks");
19574
- const permissionMark = (name) => join24(hooksDir, `${name}.permission.json`);
20277
+ const hooksDir = join25(stateDir, "hooks");
20278
+ const permissionMark = (name) => join25(hooksDir, `${name}.permission.json`);
19575
20279
  const withHook = (name, settings) => applyPermissionHook(settings, [...o.sledsArgv ?? ["sleds"], "permission-event", "--mark", permissionMark(name)].map(shq2).join(" "));
19576
20280
  return {
19577
20281
  source,
@@ -19596,9 +20300,9 @@ function engineDeps(o, flags, source, runner, stateName) {
19596
20300
  sandbox: true,
19597
20301
  writeSandboxSettings: o.writeSandboxSettings ?? ((name, cwd, allow) => {
19598
20302
  const gitCommonDir = gitCommonDirOf(exec, cwd);
19599
- const dir = join24(stateDir, "sandbox");
20303
+ const dir = join25(stateDir, "sandbox");
19600
20304
  mkdirSync18(dir, { recursive: true, mode: 448 });
19601
- const p = join24(dir, `${name}.json`);
20305
+ const p = join25(dir, `${name}.json`);
19602
20306
  const walls = engineSandboxSettings(o.home, o.env ?? process.env, gitCommonDir, [
19603
20307
  ...SANDBOX_DEFAULT_ALLOW,
19604
20308
  ...flags.sandboxAllow ?? []
@@ -19615,7 +20319,7 @@ function engineDeps(o, flags, source, runner, stateName) {
19615
20319
  } : {},
19616
20320
  writeHookSettings: (name, allow) => {
19617
20321
  mkdirSync18(hooksDir, { recursive: true, mode: 448 });
19618
- const p = join24(hooksDir, `${name}.json`);
20322
+ const p = join25(hooksDir, `${name}.json`);
19619
20323
  const base = allow?.length ? { permissions: { allow: [...allow] } } : {};
19620
20324
  writeFileSync16(p, `${JSON.stringify(withHook(name, base), null, 2)}
19621
20325
  `, { mode: 384 });
@@ -19643,9 +20347,9 @@ function engineDeps(o, flags, source, runner, stateName) {
19643
20347
  exists
19644
20348
  }),
19645
20349
  writeMcpConfig: o.writeMcpConfig ?? ((name, runToken, worktree) => {
19646
- const dir = join24(stateDir, "mcp");
20350
+ const dir = join25(stateDir, "mcp");
19647
20351
  mkdirSync18(dir, { recursive: true, mode: 448 });
19648
- const p = join24(dir, `${name}.json`);
20352
+ const p = join25(dir, `${name}.json`);
19649
20353
  let others = {};
19650
20354
  if (flags.strictMcp && flags.mcpAllow?.length) {
19651
20355
  const r = mcpPassThrough(flags.mcpAllow, memberMcpConfigs(o, worktree));
@@ -19661,12 +20365,26 @@ function engineDeps(o, flags, source, runner, stateName) {
19661
20365
  }),
19662
20366
  removeMcpConfig: o.removeMcpConfig ?? ((p) => rmSync10(p, { force: true })),
19663
20367
  writePrompt: o.writePrompt ?? ((name, text) => {
19664
- const dir = join24(stateDir, "prompts");
20368
+ const dir = join25(stateDir, "prompts");
19665
20369
  mkdirSync18(dir, { recursive: true, mode: 448 });
19666
- const p = join24(dir, `${name}.md`);
20370
+ const p = join25(dir, `${name}.md`);
19667
20371
  writeFileSync16(p, text, { mode: 384 });
19668
20372
  return p;
19669
20373
  }),
20374
+ mockOut: (name) => {
20375
+ const dir = join25(tmpdir(), "sleds-mocks");
20376
+ mkdirSync18(dir, { recursive: true, mode: 448 });
20377
+ const p = join25(dir, `${name}.html`);
20378
+ rmSync10(p, { force: true });
20379
+ return p;
20380
+ },
20381
+ readMock: (p) => {
20382
+ try {
20383
+ return readFileSync16(p, "utf8");
20384
+ } catch {
20385
+ return null;
20386
+ }
20387
+ },
19670
20388
  ensureWorktree: (e, repoPath, branch, path, base, install) => ensureWorktree(e, repoPath, branch, path, base, o.worktreeFs ?? { ...realWorktreeFs, exists }, { ...install ? { command: install } : {}, log: o.log ?? console.log }),
19671
20389
  ensureDraftWorktree: (e, repoPath, path, base) => ensureDetachedWorktree(e, repoPath, path, base, o.worktreeFs ?? { ...realWorktreeFs, exists }),
19672
20390
  defaultBranch,
@@ -19691,14 +20409,14 @@ function engineDeps(o, flags, source, runner, stateName) {
19691
20409
  }
19692
20410
  function sandboxDenyRead(home, env) {
19693
20411
  return {
19694
- dirs: [...new Set([configDir(home, env), join24(home, ".config", "sleds")])],
20412
+ dirs: [...new Set([configDir(home, env), join25(home, ".config", "sleds")])],
19695
20413
  files: [
19696
20414
  ...new Set([
19697
20415
  tokenPath(home, env),
19698
- join24(home, ".sleds-token"),
20416
+ join25(home, ".sleds-token"),
19699
20417
  settingsPath(home, env),
19700
20418
  backupPath(home, env),
19701
- join24(claudeDir(home, env), "settings.local.json")
20419
+ join25(claudeDir(home, env), "settings.local.json")
19702
20420
  ])
19703
20421
  ]
19704
20422
  };
@@ -19715,21 +20433,21 @@ function gitCommonDirOf(exec, cwd) {
19715
20433
  const dir = r.status === 0 ? r.stdout.trim() : "";
19716
20434
  if (!dir)
19717
20435
  throw new Error(`git has no common dir for ${cwd}, so the sandbox has nowhere to commit`);
19718
- return resolve8(cwd, dir);
20436
+ return resolve9(cwd, dir);
19719
20437
  }
19720
20438
  var LENT_NEEDS_SANDBOX = "a lent engine runs every session sandboxed (spec 48 rule 10) — add --sandbox to --lent-by";
19721
20439
  function lenderHome(accountHome, lentBy) {
19722
- return isAbsolute3(lentBy) ? resolve8(lentBy) : join24(dirname15(accountHome), lentBy);
20440
+ return isAbsolute3(lentBy) ? resolve9(lentBy) : join25(dirname16(accountHome), lentBy);
19723
20441
  }
19724
20442
  function lentHomeProblems(accountHome, lentBy, modeOf) {
19725
20443
  const lender = lenderHome(accountHome, lentBy);
19726
- if (lender === resolve8(accountHome))
20444
+ if (lender === resolve9(accountHome))
19727
20445
  return [
19728
20446
  `--lent-by ${lentBy} is this account — a lent engine runs as the borrower's own OS user, not the lender's`
19729
20447
  ];
19730
20448
  const problems = [];
19731
20449
  for (const [who, home] of [
19732
- ["the borrower's", resolve8(accountHome)],
20450
+ ["the borrower's", resolve9(accountHome)],
19733
20451
  ["the lender's", lender]
19734
20452
  ]) {
19735
20453
  const mode = modeOf(home);
@@ -19742,7 +20460,7 @@ function lentHomeProblems(accountHome, lentBy, modeOf) {
19742
20460
  }
19743
20461
  var homeMode = (path) => {
19744
20462
  try {
19745
- return statSync5(path).mode & 4095;
20463
+ return statSync6(path).mode & 4095;
19746
20464
  } catch {
19747
20465
  return null;
19748
20466
  }
@@ -19787,7 +20505,7 @@ async function driveEngine(o, flags, deps, until = {}) {
19787
20505
  runner: deps.runner.name,
19788
20506
  atOnce: flags.atOnce,
19789
20507
  interval: flags.interval,
19790
- cliVersion: "0.1.60"
20508
+ cliVersion: "0.1.61"
19791
20509
  };
19792
20510
  const beat = (checkedAt) => {
19793
20511
  const m = deps.machine;
@@ -20135,14 +20853,14 @@ init_config();
20135
20853
  import {
20136
20854
  existsSync as existsSync19,
20137
20855
  mkdirSync as mkdirSync19,
20138
- readFileSync as readFileSync16,
20856
+ readFileSync as readFileSync17,
20139
20857
  readdirSync as readdirSync4,
20140
20858
  realpathSync,
20141
20859
  rmSync as rmSync11,
20142
20860
  writeFileSync as writeFileSync17
20143
20861
  } from "node:fs";
20144
20862
  import { hostname as hostname3 } from "node:os";
20145
- import { basename as basename6, dirname as dirname16, isAbsolute as isAbsolute4, join as join25 } from "node:path";
20863
+ import { basename as basename6, dirname as dirname17, isAbsolute as isAbsolute4, join as join26 } from "node:path";
20146
20864
  init_state();
20147
20865
  var ENGINE_LABEL = "ai.sleds.engine";
20148
20866
  var ENGINE_INSTALL_USAGE = `usage: sleds engine install [engine flags] | sleds engine uninstall [--name <n>] [--org <slug>]
@@ -20163,13 +20881,13 @@ function engineLabel(name, org, seat) {
20163
20881
  return [ENGINE_LABEL, org, seat?.trim(), name].filter((p) => Boolean(p)).map((p, i) => i === 0 ? p : engineSlug(p)).join(".");
20164
20882
  }
20165
20883
  function enginePlistPath(home, name, org, seat) {
20166
- return join25(home, "Library", "LaunchAgents", `${engineLabel(name, org, seat)}.plist`);
20884
+ return join26(home, "Library", "LaunchAgents", `${engineLabel(name, org, seat)}.plist`);
20167
20885
  }
20168
20886
  function engineLogPath(stateDir, name) {
20169
- return join25(stateDir, `${engineSlug(name)}.log`);
20887
+ return join26(stateDir, `${engineSlug(name)}.log`);
20170
20888
  }
20171
20889
  function engineEventsPath(stateDir, name) {
20172
- return join25(stateDir, `${engineSlug(name)}.events.jsonl`);
20890
+ return join26(stateDir, `${engineSlug(name)}.events.jsonl`);
20173
20891
  }
20174
20892
  var xmlEscape4 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
20175
20893
  function buildEnginePlist(o) {
@@ -20266,10 +20984,10 @@ function readEngines(dir, suffix, manager, parse) {
20266
20984
  }
20267
20985
  const out = [];
20268
20986
  for (const name of names.filter((n) => n.startsWith(`${ENGINE_LABEL}.`) && n.endsWith(suffix))) {
20269
- const path = join25(dir, name);
20987
+ const path = join26(dir, name);
20270
20988
  let parsed;
20271
20989
  try {
20272
- parsed = parse(readFileSync16(path, "utf8"), name);
20990
+ parsed = parse(readFileSync17(path, "utf8"), name);
20273
20991
  } catch {
20274
20992
  parsed = null;
20275
20993
  }
@@ -20286,7 +21004,7 @@ function readEngines(dir, suffix, manager, parse) {
20286
21004
  }
20287
21005
  function installedEngines(home) {
20288
21006
  return [
20289
- ...readEngines(join25(home, "Library", "LaunchAgents"), ".plist", "launchd", parseEnginePlist),
21007
+ ...readEngines(join26(home, "Library", "LaunchAgents"), ".plist", "launchd", parseEnginePlist),
20290
21008
  ...readEngines(engineUnitDir(home), ".service", "systemd", (text, file) => {
20291
21009
  const parsed = parseEngineUnit(text);
20292
21010
  return parsed && { label: basename6(file, ".service"), ...parsed };
@@ -20336,7 +21054,7 @@ function engineInstall(o) {
20336
21054
  if (platform === "linux")
20337
21055
  return installUnit({ o, parsed, label, sledsArgv, env: agentEnv, logPath, exec });
20338
21056
  const plist = enginePlistPath(o.home, parsed.name, parsed.org, seat);
20339
- mkdirSync19(dirname16(plist), { recursive: true });
21057
+ mkdirSync19(dirname17(plist), { recursive: true });
20340
21058
  if (existsSync19(plist))
20341
21059
  exec("launchctl", ["unload", plist]);
20342
21060
  for (const other of installedEngines(o.home)) {
@@ -20378,7 +21096,7 @@ function installUnit(a) {
20378
21096
  const unit = engineUnitPath(o.home, label);
20379
21097
  const unitName = basename6(unit);
20380
21098
  const systemctl = (...args) => exec("systemctl", ["--user", ...args]);
20381
- mkdirSync19(dirname16(unit), { recursive: true });
21099
+ mkdirSync19(dirname17(unit), { recursive: true });
20382
21100
  if (existsSync19(unit))
20383
21101
  systemctl("stop", unitName);
20384
21102
  for (const other of installedEngines(o.home)) {
@@ -20456,7 +21174,7 @@ function engineUninstall(o) {
20456
21174
  }
20457
21175
 
20458
21176
  // src/commands/musher.ts
20459
- import { existsSync as existsSync20, mkdirSync as mkdirSync20, readFileSync as readFileSync17, rmSync as rmSync12, writeFileSync as writeFileSync18 } from "node:fs";
21177
+ import { existsSync as existsSync20, mkdirSync as mkdirSync20, readFileSync as readFileSync18, rmSync as rmSync12, writeFileSync as writeFileSync18 } from "node:fs";
20460
21178
  init_config();
20461
21179
  init_meter();
20462
21180
  var defaultWhich = (cmd) => {
@@ -20484,12 +21202,14 @@ var findClaude = (home, configured, which) => {
20484
21202
  return p;
20485
21203
  return null;
20486
21204
  };
21205
+ var MUSHER_SWITCHES = new Set(["install", "uninstall", "status"]);
21206
+ var MUSHER_SETTINGS = new Set(["claude", "mode", "timeout"]);
20487
21207
  var musherAgentState = (home, env = process.env) => {
20488
21208
  const plist = musherPlistPath(home, env);
20489
21209
  if (!existsSync20(plist))
20490
21210
  return { installed: false, thisSeat: false };
20491
21211
  try {
20492
- const text = readFileSync17(plist, "utf8");
21212
+ const text = readFileSync18(plist, "utf8");
20493
21213
  const want = env.SLEDS_HOME?.trim();
20494
21214
  const has = /<key>SLEDS_HOME<\/key>\s*<string>([^<]*)<\/string>/.exec(text)?.[1];
20495
21215
  return { installed: true, thisSeat: (want ?? undefined) === (has ?? undefined) };
@@ -20499,7 +21219,7 @@ var musherAgentState = (home, env = process.env) => {
20499
21219
  };
20500
21220
 
20501
21221
  // src/claudeAccount.ts
20502
- import { readFileSync as readFileSync18 } from "node:fs";
21222
+ import { readFileSync as readFileSync19 } from "node:fs";
20503
21223
  var PLANS = {
20504
21224
  claude_max: "Max",
20505
21225
  claude_pro: "Pro",
@@ -20528,7 +21248,7 @@ var parseClaudeAccount = (raw) => {
20528
21248
  };
20529
21249
  var readClaudeAccount = (home, env = process.env) => {
20530
21250
  try {
20531
- return parseClaudeAccount(JSON.parse(readFileSync18(claudeUserConfigPath2(home, env), "utf8")));
21251
+ return parseClaudeAccount(JSON.parse(readFileSync19(claudeUserConfigPath2(home, env), "utf8")));
20532
21252
  } catch {
20533
21253
  return null;
20534
21254
  }
@@ -20665,7 +21385,7 @@ async function runDoctor(o) {
20665
21385
  detail: `seat-event ${seat ? "on" : "missing"} · spec-event ${spec ? "on" : "missing"} · session-event ${session ? "on" : "missing"} · stop-event ${stop ? "on" : "missing"}`,
20666
21386
  fix: all ? undefined : "sleds hooks"
20667
21387
  });
20668
- const want = env.SLEDS_HOME?.trim() ? resolve9(home, env.SLEDS_HOME.trim()) : undefined;
21388
+ const want = env.SLEDS_HOME?.trim() ? resolve10(home, env.SLEDS_HOME.trim()) : undefined;
20669
21389
  const cmds = sledsCommandsIn(settings);
20670
21390
  const wrong = cmds.filter((c) => {
20671
21391
  const got = bakedHome(c);
@@ -20754,7 +21474,7 @@ async function runDoctor(o) {
20754
21474
  checks.push({ level: "ok", name: "path", detail: which[0] ?? "" });
20755
21475
  const link = sledsLink(home, o.platform);
20756
21476
  if (existsSync21(link)) {
20757
- const binDir = dirname17(link);
21477
+ const binDir = dirname18(link);
20758
21478
  const onPath = (env.PATH ?? "").split(delimiter3).includes(binDir);
20759
21479
  const first = which[0];
20760
21480
  const fix = `echo 'export PATH="${binDir}:$PATH"' >> ~/.zshrc`;
@@ -20943,12 +21663,12 @@ function sandboxChecks(o, env) {
20943
21663
  }
20944
21664
  ];
20945
21665
  const nonce = o.nonce ?? `sleds-probe-${randomBytes(16).toString("hex")}`;
20946
- const probe = join26(dir, SANDBOX_PROBE);
20947
- const work = mkdtempSync(join26(tmpdir(), "sleds-sandbox-"));
21666
+ const probe = join27(dir, SANDBOX_PROBE);
21667
+ const work = mkdtempSync(join27(tmpdir2(), "sleds-sandbox-"));
20948
21668
  try {
20949
21669
  writeFileSync19(probe, `${nonce}
20950
21670
  `, { mode: 384 });
20951
- const settingsFile = join26(work, "settings.json");
21671
+ const settingsFile = join27(work, "settings.json");
20952
21672
  const settings = engineSandboxSettings(o.home, env, work, SANDBOX_DEFAULT_ALLOW);
20953
21673
  const claudeSettings = settingsPath(o.home, env);
20954
21674
  writeFileSync19(settingsFile, JSON.stringify(settings, null, 2), { mode: 384 });
@@ -21122,7 +21842,7 @@ function boxChecks(o, env, which, need) {
21122
21842
  const file = boxEnvPath(o.home);
21123
21843
  let boxEnvText = null;
21124
21844
  try {
21125
- boxEnvText = readFileSync19(file, "utf8");
21845
+ boxEnvText = readFileSync20(file, "utf8");
21126
21846
  } catch {
21127
21847
  boxEnvText = null;
21128
21848
  }
@@ -21240,14 +21960,14 @@ import {
21240
21960
  appendFileSync as appendFileSync3,
21241
21961
  chmodSync as chmodSync6,
21242
21962
  mkdirSync as mkdirSync21,
21243
- readFileSync as readFileSync20,
21963
+ readFileSync as readFileSync21,
21244
21964
  existsSync as realExists2,
21245
21965
  rmSync as rmSync14,
21246
- statSync as statSync6,
21966
+ statSync as statSync7,
21247
21967
  writeFileSync as writeFileSync20
21248
21968
  } from "node:fs";
21249
- import { hostname as hostname4, userInfo as userInfo3 } from "node:os";
21250
- import { dirname as dirname18, isAbsolute as isAbsolute5, join as join27, resolve as resolve10 } from "node:path";
21969
+ import { hostname as hostname4, tmpdir as tmpdir3, userInfo as userInfo3 } from "node:os";
21970
+ import { dirname as dirname19, isAbsolute as isAbsolute5, join as join28, resolve as resolve11 } from "node:path";
21251
21971
  init_machine();
21252
21972
  init_state();
21253
21973
  init_meter();
@@ -21507,7 +22227,7 @@ function flagsFromProfile2(flags, given, profile, home) {
21507
22227
  }
21508
22228
  }
21509
22229
  if (profile.reposDir && !has("repos-dir") && !has("repo")) {
21510
- out.reposDir = splitReposDirs(profile.reposDir).map((d) => home && /^~(\/|$)/.test(d) ? join27(home, d.slice(1)) : d).join(":");
22230
+ out.reposDir = splitReposDirs(profile.reposDir).map((d) => home && /^~(\/|$)/.test(d) ? join28(home, d.slice(1)) : d).join(":");
21511
22231
  used.push(`repos ${profile.reposDir}`);
21512
22232
  }
21513
22233
  if (profile.mcpStrict !== undefined && !has("strict-mcp") && !has("no-strict-mcp")) {
@@ -21654,7 +22374,7 @@ function engineClient2(o, org, breaker) {
21654
22374
  ...src,
21655
22375
  ...breaker ? { fetchFn: breaker.fetch } : {},
21656
22376
  timeoutMs: ENGINE_TIMEOUT_MS2,
21657
- version: "0.1.60"
22377
+ version: "0.1.61"
21658
22378
  });
21659
22379
  const asked = org?.trim() || env.SLEDS_ORG?.trim();
21660
22380
  if (asked && !selectOrg(readConfig(o.home), org, env).seat)
@@ -21696,19 +22416,19 @@ function realPidAlive3(pid) {
21696
22416
  function memberMcpConfigs2(o, cwd) {
21697
22417
  const read = o.readJson ?? ((p) => {
21698
22418
  try {
21699
- return JSON.parse(readFileSync20(p, "utf8"));
22419
+ return JSON.parse(readFileSync21(p, "utf8"));
21700
22420
  } catch {
21701
22421
  return null;
21702
22422
  }
21703
22423
  });
21704
22424
  const claudeDir = (o.env ?? process.env).CLAUDE_CONFIG_DIR;
21705
22425
  return [
21706
- read(claudeDir ? join27(claudeDir, ".claude.json") : join27(o.home, ".claude.json")),
21707
- ...cwd ? [read(join27(cwd, ".mcp.json"))] : []
22426
+ read(claudeDir ? join28(claudeDir, ".claude.json") : join28(o.home, ".claude.json")),
22427
+ ...cwd ? [read(join28(cwd, ".mcp.json"))] : []
21708
22428
  ];
21709
22429
  }
21710
22430
  function engineStateDir2(o, org, seat) {
21711
- return seatStateDir(orgStateDir(o.stateDir ?? join27(configDir(o.home, o.env ?? process.env), "engine"), org), seat);
22431
+ return seatStateDir(orgStateDir(o.stateDir ?? join28(configDir(o.home, o.env ?? process.env), "engine"), org), seat);
21712
22432
  }
21713
22433
  function engineSeat2(o, org) {
21714
22434
  try {
@@ -21731,7 +22451,7 @@ function engineCredential(o) {
21731
22451
  const file = boxEnvPath(o.home);
21732
22452
  let boxEnvText = null;
21733
22453
  try {
21734
- boxEnvText = readFileSync20(file, "utf8");
22454
+ boxEnvText = readFileSync21(file, "utf8");
21735
22455
  } catch {}
21736
22456
  return detectCredential(o.env ?? process.env, {
21737
22457
  home: o.home,
@@ -21748,11 +22468,11 @@ function engineDeps2(o, flags, source, runner, stateName) {
21748
22468
  const stateDir = engineStateDir2(o, flags.org, flags.seat);
21749
22469
  const file = statePath(stateDir, stateName);
21750
22470
  const mcpOrg = flags.org && engineOrg2(o, flags.org).seated ? flags.org : undefined;
21751
- const repo = flags.repo ? resolve10(cwd, flags.repo) : undefined;
21752
- const reposDirs = splitReposDirs(flags.reposDir).map((d) => resolve10(cwd, d));
22471
+ const repo = flags.repo ? resolve11(cwd, flags.repo) : undefined;
22472
+ const reposDirs = splitReposDirs(flags.reposDir).map((d) => resolve11(cwd, d));
21753
22473
  const { branch, prTitle } = flags;
21754
- const hooksDir = join27(stateDir, "hooks");
21755
- const permissionMark = (name) => join27(hooksDir, `${name}.permission.json`);
22474
+ const hooksDir = join28(stateDir, "hooks");
22475
+ const permissionMark = (name) => join28(hooksDir, `${name}.permission.json`);
21756
22476
  const withHook = (name, settings) => applyPermissionHook(settings, [...o.sledsArgv ?? ["sleds"], "permission-event", "--mark", permissionMark(name)].map(shq2).join(" "));
21757
22477
  return {
21758
22478
  source,
@@ -21777,9 +22497,9 @@ function engineDeps2(o, flags, source, runner, stateName) {
21777
22497
  sandbox: true,
21778
22498
  writeSandboxSettings: o.writeSandboxSettings ?? ((name, cwd, allow) => {
21779
22499
  const gitCommonDir = gitCommonDirOf2(exec, cwd);
21780
- const dir = join27(stateDir, "sandbox");
22500
+ const dir = join28(stateDir, "sandbox");
21781
22501
  mkdirSync21(dir, { recursive: true, mode: 448 });
21782
- const p = join27(dir, `${name}.json`);
22502
+ const p = join28(dir, `${name}.json`);
21783
22503
  const walls = engineSandboxSettings2(o.home, o.env ?? process.env, gitCommonDir, [
21784
22504
  ...SANDBOX_DEFAULT_ALLOW,
21785
22505
  ...flags.sandboxAllow ?? []
@@ -21796,7 +22516,7 @@ function engineDeps2(o, flags, source, runner, stateName) {
21796
22516
  } : {},
21797
22517
  writeHookSettings: (name, allow) => {
21798
22518
  mkdirSync21(hooksDir, { recursive: true, mode: 448 });
21799
- const p = join27(hooksDir, `${name}.json`);
22519
+ const p = join28(hooksDir, `${name}.json`);
21800
22520
  const base = allow?.length ? { permissions: { allow: [...allow] } } : {};
21801
22521
  writeFileSync20(p, `${JSON.stringify(withHook(name, base), null, 2)}
21802
22522
  `, { mode: 384 });
@@ -21824,9 +22544,9 @@ function engineDeps2(o, flags, source, runner, stateName) {
21824
22544
  exists
21825
22545
  }),
21826
22546
  writeMcpConfig: o.writeMcpConfig ?? ((name, runToken, worktree) => {
21827
- const dir = join27(stateDir, "mcp");
22547
+ const dir = join28(stateDir, "mcp");
21828
22548
  mkdirSync21(dir, { recursive: true, mode: 448 });
21829
- const p = join27(dir, `${name}.json`);
22549
+ const p = join28(dir, `${name}.json`);
21830
22550
  let others = {};
21831
22551
  if (flags.strictMcp && flags.mcpAllow?.length) {
21832
22552
  const r = mcpPassThrough(flags.mcpAllow, memberMcpConfigs2(o, worktree));
@@ -21842,12 +22562,26 @@ function engineDeps2(o, flags, source, runner, stateName) {
21842
22562
  }),
21843
22563
  removeMcpConfig: o.removeMcpConfig ?? ((p) => rmSync14(p, { force: true })),
21844
22564
  writePrompt: o.writePrompt ?? ((name, text) => {
21845
- const dir = join27(stateDir, "prompts");
22565
+ const dir = join28(stateDir, "prompts");
21846
22566
  mkdirSync21(dir, { recursive: true, mode: 448 });
21847
- const p = join27(dir, `${name}.md`);
22567
+ const p = join28(dir, `${name}.md`);
21848
22568
  writeFileSync20(p, text, { mode: 384 });
21849
22569
  return p;
21850
22570
  }),
22571
+ mockOut: (name) => {
22572
+ const dir = join28(tmpdir3(), "sleds-mocks");
22573
+ mkdirSync21(dir, { recursive: true, mode: 448 });
22574
+ const p = join28(dir, `${name}.html`);
22575
+ rmSync14(p, { force: true });
22576
+ return p;
22577
+ },
22578
+ readMock: (p) => {
22579
+ try {
22580
+ return readFileSync21(p, "utf8");
22581
+ } catch {
22582
+ return null;
22583
+ }
22584
+ },
21851
22585
  ensureWorktree: (e, repoPath, branch, path, base, install) => ensureWorktree(e, repoPath, branch, path, base, o.worktreeFs ?? { ...realWorktreeFs, exists }, { ...install ? { command: install } : {}, log: o.log ?? console.log }),
21852
22586
  ensureDraftWorktree: (e, repoPath, path, base) => ensureDetachedWorktree(e, repoPath, path, base, o.worktreeFs ?? { ...realWorktreeFs, exists }),
21853
22587
  defaultBranch,
@@ -21872,14 +22606,14 @@ function engineDeps2(o, flags, source, runner, stateName) {
21872
22606
  }
21873
22607
  function sandboxDenyRead2(home, env) {
21874
22608
  return {
21875
- dirs: [...new Set([configDir(home, env), join27(home, ".config", "sleds")])],
22609
+ dirs: [...new Set([configDir(home, env), join28(home, ".config", "sleds")])],
21876
22610
  files: [
21877
22611
  ...new Set([
21878
22612
  tokenPath(home, env),
21879
- join27(home, ".sleds-token"),
22613
+ join28(home, ".sleds-token"),
21880
22614
  settingsPath(home, env),
21881
22615
  backupPath(home, env),
21882
- join27(claudeDir(home, env), "settings.local.json")
22616
+ join28(claudeDir(home, env), "settings.local.json")
21883
22617
  ])
21884
22618
  ]
21885
22619
  };
@@ -21896,7 +22630,7 @@ function gitCommonDirOf2(exec, cwd) {
21896
22630
  const dir = r.status === 0 ? r.stdout.trim() : "";
21897
22631
  if (!dir)
21898
22632
  throw new Error(`git has no common dir for ${cwd}, so the sandbox has nowhere to commit`);
21899
- return resolve10(cwd, dir);
22633
+ return resolve11(cwd, dir);
21900
22634
  }
21901
22635
  function sandboxRefusal(flags) {
21902
22636
  if (flags.lentBy && !flags.sandbox)
@@ -21913,17 +22647,17 @@ function sandboxRefusal(flags) {
21913
22647
  }
21914
22648
  var LENT_NEEDS_SANDBOX2 = "a lent engine runs every session sandboxed (spec 48 rule 10) — add --sandbox to --lent-by";
21915
22649
  function lenderHome2(accountHome, lentBy) {
21916
- return isAbsolute5(lentBy) ? resolve10(lentBy) : join27(dirname18(accountHome), lentBy);
22650
+ return isAbsolute5(lentBy) ? resolve11(lentBy) : join28(dirname19(accountHome), lentBy);
21917
22651
  }
21918
22652
  function lentHomeProblems2(accountHome, lentBy, modeOf) {
21919
22653
  const lender = lenderHome2(accountHome, lentBy);
21920
- if (lender === resolve10(accountHome))
22654
+ if (lender === resolve11(accountHome))
21921
22655
  return [
21922
22656
  `--lent-by ${lentBy} is this account — a lent engine runs as the borrower's own OS user, not the lender's`
21923
22657
  ];
21924
22658
  const problems = [];
21925
22659
  for (const [who, home] of [
21926
- ["the borrower's", resolve10(accountHome)],
22660
+ ["the borrower's", resolve11(accountHome)],
21927
22661
  ["the lender's", lender]
21928
22662
  ]) {
21929
22663
  const mode = modeOf(home);
@@ -21936,7 +22670,7 @@ function lentHomeProblems2(accountHome, lentBy, modeOf) {
21936
22670
  }
21937
22671
  var homeMode2 = (path) => {
21938
22672
  try {
21939
- return statSync6(path).mode & 4095;
22673
+ return statSync7(path).mode & 4095;
21940
22674
  } catch {
21941
22675
  return null;
21942
22676
  }
@@ -21981,7 +22715,7 @@ async function driveEngine2(o, flags, deps, until = {}) {
21981
22715
  runner: deps.runner.name,
21982
22716
  atOnce: flags.atOnce,
21983
22717
  interval: flags.interval,
21984
- cliVersion: "0.1.60"
22718
+ cliVersion: "0.1.61"
21985
22719
  };
21986
22720
  const beat = (checkedAt) => {
21987
22721
  const m = deps.machine;
@@ -22611,14 +23345,14 @@ init_config();
22611
23345
  import {
22612
23346
  existsSync as existsSync22,
22613
23347
  mkdirSync as mkdirSync22,
22614
- readFileSync as readFileSync21,
23348
+ readFileSync as readFileSync22,
22615
23349
  readdirSync as readdirSync6,
22616
23350
  realpathSync as realpathSync2,
22617
23351
  rmSync as rmSync15,
22618
23352
  writeFileSync as writeFileSync21
22619
23353
  } from "node:fs";
22620
23354
  import { hostname as hostname5 } from "node:os";
22621
- import { basename as basename7, dirname as dirname19, isAbsolute as isAbsolute6, join as join28 } from "node:path";
23355
+ import { basename as basename7, dirname as dirname20, isAbsolute as isAbsolute6, join as join29 } from "node:path";
22622
23356
  init_state();
22623
23357
  var ENGINE_LABEL2 = "ai.sleds.engine";
22624
23358
  var ENGINE_INSTALL_USAGE2 = `usage: sleds engine install [engine flags] | sleds engine uninstall [--name <n>] [--org <slug>]
@@ -22639,13 +23373,13 @@ function engineLabel2(name, org, seat) {
22639
23373
  return [ENGINE_LABEL2, org, seat?.trim(), name].filter((p) => Boolean(p)).map((p, i) => i === 0 ? p : engineSlug(p)).join(".");
22640
23374
  }
22641
23375
  function enginePlistPath2(home, name, org, seat) {
22642
- return join28(home, "Library", "LaunchAgents", `${engineLabel2(name, org, seat)}.plist`);
23376
+ return join29(home, "Library", "LaunchAgents", `${engineLabel2(name, org, seat)}.plist`);
22643
23377
  }
22644
23378
  function engineLogPath2(stateDir, name) {
22645
- return join28(stateDir, `${engineSlug(name)}.log`);
23379
+ return join29(stateDir, `${engineSlug(name)}.log`);
22646
23380
  }
22647
23381
  function engineEventsPath2(stateDir, name) {
22648
- return join28(stateDir, `${engineSlug(name)}.events.jsonl`);
23382
+ return join29(stateDir, `${engineSlug(name)}.events.jsonl`);
22649
23383
  }
22650
23384
  var xmlEscape5 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
22651
23385
  function buildEnginePlist2(o) {
@@ -22742,10 +23476,10 @@ function readEngines2(dir, suffix, manager, parse) {
22742
23476
  }
22743
23477
  const out = [];
22744
23478
  for (const name of names.filter((n) => n.startsWith(`${ENGINE_LABEL2}.`) && n.endsWith(suffix))) {
22745
- const path = join28(dir, name);
23479
+ const path = join29(dir, name);
22746
23480
  let parsed;
22747
23481
  try {
22748
- parsed = parse(readFileSync21(path, "utf8"), name);
23482
+ parsed = parse(readFileSync22(path, "utf8"), name);
22749
23483
  } catch {
22750
23484
  parsed = null;
22751
23485
  }
@@ -22762,7 +23496,7 @@ function readEngines2(dir, suffix, manager, parse) {
22762
23496
  }
22763
23497
  function installedEngines2(home) {
22764
23498
  return [
22765
- ...readEngines2(join28(home, "Library", "LaunchAgents"), ".plist", "launchd", parseEnginePlist2),
23499
+ ...readEngines2(join29(home, "Library", "LaunchAgents"), ".plist", "launchd", parseEnginePlist2),
22766
23500
  ...readEngines2(engineUnitDir(home), ".service", "systemd", (text, file) => {
22767
23501
  const parsed = parseEngineUnit(text);
22768
23502
  return parsed && { label: basename7(file, ".service"), ...parsed };
@@ -22800,7 +23534,7 @@ async function ensureReposDir(o) {
22800
23534
  return false;
22801
23535
  }
22802
23536
  const exists = o.exists ?? existsSync22;
22803
- const missing = dirs.filter((d) => !exists(/^~(\/|$)/.test(d) ? join28(o.home, d.slice(1)) : d));
23537
+ const missing = dirs.filter((d) => !exists(/^~(\/|$)/.test(d) ? join29(o.home, d.slice(1)) : d));
22804
23538
  if (missing.length) {
22805
23539
  error(`Not a folder on this machine: ${missing.join(", ")}. ${REPOS_DIR_NEEDED}`);
22806
23540
  return false;
@@ -22856,7 +23590,7 @@ function engineInstall2(o) {
22856
23590
  if (platform === "linux")
22857
23591
  return installUnit2({ o, parsed, label, sledsArgv, env: agentEnv, logPath, exec });
22858
23592
  const plist = enginePlistPath2(o.home, parsed.name, parsed.org, seat);
22859
- mkdirSync22(dirname19(plist), { recursive: true });
23593
+ mkdirSync22(dirname20(plist), { recursive: true });
22860
23594
  if (existsSync22(plist))
22861
23595
  exec("launchctl", ["unload", plist]);
22862
23596
  for (const other of installedEngines2(o.home)) {
@@ -22898,7 +23632,7 @@ function installUnit2(a) {
22898
23632
  const unit = engineUnitPath(o.home, label);
22899
23633
  const unitName = basename7(unit);
22900
23634
  const systemctl = (...args) => exec("systemctl", ["--user", ...args]);
22901
- mkdirSync22(dirname19(unit), { recursive: true });
23635
+ mkdirSync22(dirname20(unit), { recursive: true });
22902
23636
  if (existsSync22(unit))
22903
23637
  systemctl("stop", unitName);
22904
23638
  for (const other of installedEngines2(o.home)) {
@@ -23099,7 +23833,7 @@ import { closeSync as closeSync5, fstatSync as fstatSync2, openSync as openSync5
23099
23833
  import { hostname as hostname6 } from "node:os";
23100
23834
 
23101
23835
  // src/commands/engineStatus.ts
23102
- import { existsSync as existsSync23, readFileSync as readFileSync22 } from "node:fs";
23836
+ import { existsSync as existsSync23, readFileSync as readFileSync23 } from "node:fs";
23103
23837
  init_state();
23104
23838
 
23105
23839
  // src/engine/status.ts
@@ -23181,7 +23915,10 @@ function engineStatus(i) {
23181
23915
  ...opt("review", r.pr.review || undefined),
23182
23916
  mergeable: r.pr.clear === true,
23183
23917
  queued: r.pr.queued === true,
23184
- ...opt("conflict", conflictOf(r, i.state.decisions, i.watchOf))
23918
+ ...opt("conflict", conflictOf(r, i.state.decisions, i.watchOf)),
23919
+ runner: r.handle.runner,
23920
+ ...r.pr.live ? { watch: i.watchOf(r), watchTarget: watchTarget(r.handle) } : {},
23921
+ ...opt("worktree", r.handle.cwd !== r.repoPath ? r.handle.cwd : undefined)
23185
23922
  });
23186
23923
  continue;
23187
23924
  }
@@ -23255,10 +23992,14 @@ function statusLines(s, now) {
23255
23992
  }
23256
23993
  lines.push(`running ${s.running.length}`);
23257
23994
  for (const r of s.running)
23258
- lines.push(` ${r.item}/${r.node} ${r.title} · ${r.answerer ? `parked, the decider answers${r.answerer.model ? ` on ${r.answerer.model}` : ""}${r.answerer.effort ? ` · ${r.answerer.effort}` : ""}` : r.parked ? "parked, waiting for your answer" : ago(r.startedAt)} · watch: ${r.watch}`);
23995
+ lines.push(` ${r.item}/${r.node} ${r.title} · ${r.answerer ? `parked, the decider answers${r.answerer.model ? ` on ${r.answerer.model}` : ""}${r.answerer.effort ? ` · ${r.answerer.effort}` : ""}` : r.parked ? "parked, waiting for your answer" : `${r.kind === "mock" ? "drawing a mock · " : ""}${ago(r.startedAt)}`} · watch: ${r.watch}`);
23259
23996
  lines.push(`following ${s.following.length}`);
23260
23997
  for (const f of s.following) {
23261
23998
  lines.push(` ${f.item}/${f.node} ${f.pr}${f.checks ? ` checks ${f.checks}` : ""}${f.review ? ` · ${f.review}` : ""}${f.mergeable ? " · ready for your merge" : ""}${f.queued ? " · queued to merge" : ""}`);
23999
+ if (f.watch)
24000
+ lines.push(` watch: ${f.watch}`);
24001
+ else if (f.worktree)
24002
+ lines.push(` worktree: ${f.worktree}`);
23262
24003
  const c = f.conflict;
23263
24004
  if (c)
23264
24005
  lines.push(` conflict on ${c.files.join(", ")}${c.cause ? ` · ${c.cause}` : ""} · ${CONFLICT_STAGE[c.stage]} · since ${ago(c.since)} ago${c.watch ? ` · watch: ${c.watch}` : ""}`);
@@ -23390,7 +24131,7 @@ var engineLogCmd = (o) => {
23390
24131
 
23391
24132
  // src/commands/engineProfile.ts
23392
24133
  init_src();
23393
- import { existsSync as existsSync24, readFileSync as readFileSync23 } from "node:fs";
24134
+ import { existsSync as existsSync24, readFileSync as readFileSync24 } from "node:fs";
23394
24135
  import { hostname as hostname7 } from "node:os";
23395
24136
  var PROFILE_USAGE = "usage: sleds engine profile [--json] | sleds engine profile set <field>=<value>… [--workspace] [--json] | sleds engine profile set --stdin [--workspace] [--json] | sleds engine profile --strict-mcp [--mcp-allow <a,b>] | --no-strict-mcp [--workspace] [--json] | sleds engine profile --sandbox [--sandbox-allow <a,b>] | --no-sandbox [--workspace] [--json] | sleds engine profile set --hold on|off [--workspace] [--json] | sleds engine profile set cmuxPassword=<pw> | --cmux-password <pw> [--json]";
23396
24137
  var PROFILE_FIELDS = [
@@ -23661,7 +24402,7 @@ async function engineProfileCmd(o) {
23661
24402
  const rest = words.slice(1);
23662
24403
  let stdin = null;
23663
24404
  if (fromStdin) {
23664
- const taken = cmuxPasswordFromJson((o.stdin ?? (() => readFileSync23(0, "utf8")))());
24405
+ const taken = cmuxPasswordFromJson((o.stdin ?? (() => readFileSync24(0, "utf8")))());
23665
24406
  if ("error" in taken || taken.password !== undefined && password !== undefined) {
23666
24407
  error("error" in taken ? taken.error : "the cmux password is given once");
23667
24408
  error(PROFILE_USAGE);
@@ -23746,7 +24487,7 @@ var keychainLine = (k) => k === "set" ? "cmuxPassword: set (Keychain)" : k === "
23746
24487
 
23747
24488
  // src/commands/engineStatus.ts
23748
24489
  init_state();
23749
- import { existsSync as existsSync25, readFileSync as readFileSync24 } from "node:fs";
24490
+ import { existsSync as existsSync25, readFileSync as readFileSync25 } from "node:fs";
23750
24491
  import { hostname as hostname8 } from "node:os";
23751
24492
  var ENGINE_STATUS_USAGE = "usage: sleds engine status [--name <n>] [--org <slug>] [--json]";
23752
24493
  function parseStatusArgs(argv, host) {
@@ -23802,7 +24543,7 @@ function readEngineStatus(stateDir, engine, host, pidAlive, seat) {
23802
24543
  }
23803
24544
  function safeRead(path) {
23804
24545
  try {
23805
- return readFileSync24(path, "utf8");
24546
+ return readFileSync25(path, "utf8");
23806
24547
  } catch {
23807
24548
  return "";
23808
24549
  }
@@ -23923,10 +24664,10 @@ init_meter();
23923
24664
 
23924
24665
  // src/owed.ts
23925
24666
  init_config();
23926
- import { existsSync as existsSync26, mkdirSync as mkdirSync23, readFileSync as readFileSync25, renameSync as renameSync11, writeFileSync as writeFileSync22 } from "node:fs";
23927
- import { dirname as dirname20, join as join29 } from "node:path";
23928
- var owedPath = (home) => join29(configDir(home), "owed.json");
23929
- var sessionPath = (home) => join29(configDir(home), "session.json");
24667
+ import { existsSync as existsSync26, mkdirSync as mkdirSync23, readFileSync as readFileSync26, renameSync as renameSync11, writeFileSync as writeFileSync22 } from "node:fs";
24668
+ import { dirname as dirname21, join as join30 } from "node:path";
24669
+ var owedPath = (home) => join30(configDir(home), "owed.json");
24670
+ var sessionPath = (home) => join30(configDir(home), "session.json");
23930
24671
  var OWED_TTL_MS = 3 * 24 * 60 * 60000;
23931
24672
  var str14 = (v) => typeof v === "string" ? v : undefined;
23932
24673
  function sourceKey(s) {
@@ -23944,13 +24685,13 @@ var readJson = (p) => {
23944
24685
  if (!existsSync26(p))
23945
24686
  return null;
23946
24687
  try {
23947
- return JSON.parse(readFileSync25(p, "utf8"));
24688
+ return JSON.parse(readFileSync26(p, "utf8"));
23948
24689
  } catch {
23949
24690
  return null;
23950
24691
  }
23951
24692
  };
23952
24693
  var writeJson = (p, v) => {
23953
- mkdirSync23(dirname20(p), { recursive: true, mode: 448 });
24694
+ mkdirSync23(dirname21(p), { recursive: true, mode: 448 });
23954
24695
  const tmp = `${p}.${process.pid}.tmp`;
23955
24696
  writeFileSync22(tmp, `${JSON.stringify(v, null, 2)}
23956
24697
  `, { mode: 384 });
@@ -24084,7 +24825,7 @@ function contextFor(deps) {
24084
24825
  ...src,
24085
24826
  fetchFn: deps.fetchFn,
24086
24827
  timeoutMs: deps.timeoutMs,
24087
- version: "0.1.60"
24828
+ version: "0.1.61"
24088
24829
  }), deps.home, now) : null;
24089
24830
  return {
24090
24831
  channel: "code",
@@ -24788,19 +25529,27 @@ var init = async (o) => {
24788
25529
  const wrote = [];
24789
25530
  const seatInfo = await fetchSeatInfo(url, resolved.token, o.fetchFn);
24790
25531
  const org = seatInfo.org;
24791
- const wiring = o.proxyAlways ? "proxy-always" : o.seatFirst ? "seat-first" : org?.mode ?? "proxy-always";
25532
+ const current = readSettings(o.home, env);
25533
+ const prior = readConfig(o.home);
25534
+ const proxyUrls = [url, prior.proxyUrl, DEFAULT_PROXY_URL];
25535
+ const ours = isSledsProxyEnv(current, proxyUrls);
25536
+ const gateway = hasGatewayEnv(current) && !ours;
25537
+ const kept = ours ? "proxy-always" : "seat-first";
25538
+ if (!org && !o.proxyAlways && !o.seatFirst)
25539
+ error(`Could not read this seat's org from ${url}/proxy/meter; kept Claude Code's wiring as it was (${kept}). Re-run \`sleds init\` once the proxy answers.`);
25540
+ const wiring = o.proxyAlways ? "proxy-always" : o.seatFirst ? "seat-first" : org?.mode ?? kept;
24792
25541
  if (wiring === "proxy-always" && org && !org.capabilities.upstreamKey) {
24793
25542
  error(`${org.name} has no platform key for the proxy to forward with; --proxy-always would break every request. Using seat-first.`);
24794
25543
  }
24795
25544
  const finalWiring = wiring === "proxy-always" && org && !org.capabilities.upstreamKey ? "seat-first" : wiring;
24796
25545
  if (backupSettings(o.home, env))
24797
25546
  wrote.push(`backed up settings to ${backupPath(o.home, env)}`);
24798
- const current = readSettings(o.home, env);
24799
- const wired = finalWiring === "proxy-always" ? applyProxy(current, { url, token: resolved.token }) : removeProxy(current);
25547
+ const wired = finalWiring === "proxy-always" ? applyProxy(current, { url, token: resolved.token }) : ours ? switchOff(current, envToRestore([prior.priorEnv, prior.priorGatewayEnv], proxyUrls)) : current;
25548
+ const priorGatewayEnv = finalWiring !== "proxy-always" ? undefined : gateway ? capturePriorEnv(current) : prior.priorGatewayEnv;
24800
25549
  const slug = org?.slug ?? resolved.approved?.org.slug;
24801
25550
  const statusLine = applyStatusLine(wired, pinOrg(o.statusLineCommand ?? sledsCommand("statusline", o.home, o.env), slug));
24802
25551
  writeSettings(o.home, statusLine.settings, env);
24803
- wrote.push(finalWiring === "proxy-always" ? `set env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN in ${settingsPath(o.home, env)} (everything through the proxy)` : `left Claude Code on your seat (no ANTHROPIC_BASE_URL in ${settingsPath(o.home, env)}); \`sleds on\` switches to the proxy at a wall`);
25552
+ wrote.push(finalWiring === "proxy-always" ? `set env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN in ${settingsPath(o.home, env)} (everything through the proxy${gateway ? "; your own gateway is kept for `sleds off` and uninstall" : ""})` : gateway ? `left your own ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN in ${settingsPath(o.home, env)} as they were; \`sleds on\` switches to the proxy at a wall` : `left Claude Code on your seat (no ANTHROPIC_BASE_URL in ${settingsPath(o.home, env)}); \`sleds on\` switches to the proxy at a wall`);
24804
25553
  wrote.push(statusLine.prior === undefined ? `set statusLine in ${settingsPath(o.home, env)}` : `set statusLine in ${settingsPath(o.home, env)} (previous one kept for uninstall)`);
24805
25554
  hooks2({
24806
25555
  home: o.home,
@@ -24819,12 +25568,12 @@ var init = async (o) => {
24819
25568
  writeTokenFile(o.home, resolved.token);
24820
25569
  wrote.push(`saved token to ${tokenPath(o.home)} (0600)`);
24821
25570
  }
24822
- const prior = readConfig(o.home);
24823
25571
  const base = {
24824
25572
  ...prior,
24825
25573
  proxyUrl: url,
24826
25574
  agentKind: "claude-code",
24827
- ...statusLine.prior !== undefined ? { priorStatusLine: statusLine.prior } : {}
25575
+ ...statusLine.prior !== undefined ? { priorStatusLine: statusLine.prior } : {},
25576
+ priorGatewayEnv
24828
25577
  };
24829
25578
  const email = seatInfo.email ?? resolved.approved?.email;
24830
25579
  const asked = orgChoice(prior, o.org, env);
@@ -24978,8 +25727,9 @@ var login = async (o) => {
24978
25727
  // src/commands/logout.ts
24979
25728
  init_config();
24980
25729
  init_state();
24981
- import { existsSync as existsSync27, readFileSync as readFileSync26, rmSync as rmSync16 } from "node:fs";
25730
+ import { existsSync as existsSync27, readFileSync as readFileSync27, rmSync as rmSync16 } from "node:fs";
24982
25731
  import { hostname as hostname10 } from "node:os";
25732
+ import { basename as basename8 } from "node:path";
24983
25733
  init_settings();
24984
25734
  var LOGOUT_USAGE = "usage: sleds logout [--org <slug>] [--json]";
24985
25735
  function parseLogoutArgs(argv) {
@@ -25101,8 +25851,15 @@ async function logout(o) {
25101
25851
  if (!name)
25102
25852
  continue;
25103
25853
  if (!e.org) {
25104
- (o.exec ?? defaultExec2)("launchctl", ["unload", e.plist]);
25105
- rmSync16(e.plist, { force: true });
25854
+ const exec = o.exec ?? defaultExec2;
25855
+ if (e.manager === "systemd") {
25856
+ exec("systemctl", ["--user", "disable", "--now", basename8(e.plist)]);
25857
+ rmSync16(e.plist, { force: true });
25858
+ exec("systemctl", ["--user", "daemon-reload"]);
25859
+ } else {
25860
+ exec("launchctl", ["unload", e.plist]);
25861
+ rmSync16(e.plist, { force: true });
25862
+ }
25106
25863
  continue;
25107
25864
  }
25108
25865
  engineUninstall({
@@ -25135,7 +25892,7 @@ async function logout(o) {
25135
25892
  };
25136
25893
  writeConfig(o.home, next);
25137
25894
  const tp = tokenPath(o.home, env);
25138
- if (existsSync27(tp) && readFileSync26(tp, "utf8").trim() === seat.token)
25895
+ if (existsSync27(tp) && readFileSync27(tp, "utf8").trim() === seat.token)
25139
25896
  rmSync16(tp, { force: true });
25140
25897
  const active = next.activeOrg ?? next.org?.slug ?? null;
25141
25898
  const result = { ok: true, email, org: slug, paused, active };
@@ -25213,9 +25970,9 @@ var meter = async (o) => {
25213
25970
  init_config();
25214
25971
  init_meter();
25215
25972
  import { spawnSync as spawnSync7 } from "node:child_process";
25216
- import { existsSync as existsSync28, mkdirSync as mkdirSync25, readFileSync as readFileSync27, rmSync as rmSync17, writeFileSync as writeFileSync24 } from "node:fs";
25973
+ import { existsSync as existsSync28, mkdirSync as mkdirSync25, readFileSync as readFileSync28, rmSync as rmSync17, writeFileSync as writeFileSync24 } from "node:fs";
25217
25974
  import { hostname as hostname11 } from "node:os";
25218
- import { dirname as dirname21 } from "node:path";
25975
+ import { dirname as dirname22 } from "node:path";
25219
25976
  var defaultWhich3 = (cmd) => {
25220
25977
  const path = process.env.PATH ?? "";
25221
25978
  for (const dir of path.split(":").filter(Boolean)) {
@@ -25253,7 +26010,7 @@ var musherInstall = (o) => {
25253
26010
  const plist = musherPlistPath(o.home, env);
25254
26011
  const label = musherLabel(env);
25255
26012
  const dir = configDir(o.home, env);
25256
- mkdirSync25(dirname21(plist), { recursive: true });
26013
+ mkdirSync25(dirname22(plist), { recursive: true });
25257
26014
  mkdirSync25(dir, { recursive: true, mode: 448 });
25258
26015
  const execPath = o.sledsArgv[0] ?? process.execPath;
25259
26016
  const agentEnv = {
@@ -25373,16 +26130,22 @@ var musherTick = async (o) => {
25373
26130
  };
25374
26131
  var musherConfigure = (o, flags) => {
25375
26132
  const log = o.log ?? console.log;
26133
+ const error = o.error ?? console.error;
25376
26134
  const cfg = readConfig(o.home);
25377
26135
  const musher = { ...cfg.musher ?? {} };
25378
26136
  if (flags.claude)
25379
26137
  musher.claudePath = flags.claude;
25380
- if (flags.mode)
26138
+ if (flags.mode) {
26139
+ if (!PERMISSION_MODES.includes(flags.mode)) {
26140
+ error(`--mode takes one of ${PERMISSION_MODES.join(", ")} (got ${flags.mode})`);
26141
+ return 1;
26142
+ }
25381
26143
  musher.permissionMode = flags.mode;
26144
+ }
25382
26145
  if (flags.timeout) {
25383
- const n = Number.parseInt(flags.timeout, 10);
25384
- if (!Number.isFinite(n) || n <= 0) {
25385
- (o.error ?? console.error)("--timeout takes minutes, a positive number");
26146
+ const n = /^\d+$/.test(flags.timeout) ? Number(flags.timeout) : Number.NaN;
26147
+ if (!Number.isSafeInteger(n) || n <= 0) {
26148
+ error("--timeout takes minutes, a positive whole number");
25386
26149
  return 1;
25387
26150
  }
25388
26151
  musher.timeoutMinutes = n;
@@ -25397,22 +26160,38 @@ var defaultExecSync = (cmd, args) => {
25397
26160
  return { ok: false, stderr: r.error.message };
25398
26161
  return { ok: r.status === 0, stderr: r.stderr ?? "" };
25399
26162
  };
25400
- var musher3 = async (o) => {
25401
- const args = [...o.argv];
26163
+ var MUSHER_SWITCHES2 = new Set(["install", "uninstall", "status"]);
26164
+ var MUSHER_SETTINGS2 = new Set(["claude", "mode", "timeout"]);
26165
+ var parseMusherArgs = (argv) => {
25402
26166
  const flags = {};
25403
26167
  const positional = [];
25404
- for (let i = 0;i < args.length; i++) {
25405
- const a = args[i] ?? "";
25406
- if (a.startsWith("--")) {
25407
- const next = args[i + 1];
25408
- if (next !== undefined && !next.startsWith("--")) {
25409
- flags[a.slice(2)] = next;
25410
- i++;
25411
- } else
25412
- flags[a.slice(2)] = "true";
25413
- } else
26168
+ for (let i = 0;i < argv.length; i++) {
26169
+ const a = argv[i] ?? "";
26170
+ if (!a.startsWith("--")) {
25414
26171
  positional.push(a);
26172
+ continue;
26173
+ }
26174
+ const eq = a.indexOf("=");
26175
+ const name = a.slice(2, eq < 0 ? undefined : eq);
26176
+ if (MUSHER_SWITCHES2.has(name) && eq < 0)
26177
+ flags[name] = "true";
26178
+ else if (MUSHER_SETTINGS2.has(name)) {
26179
+ const v = eq < 0 ? argv[++i] : a.slice(eq + 1);
26180
+ if (!v?.trim() || v.startsWith("--"))
26181
+ return { error: `--${name} needs a value` };
26182
+ flags[name] = v.trim();
26183
+ } else
26184
+ return { error: `unknown argument ${a}` };
26185
+ }
26186
+ return { flags, positional };
26187
+ };
26188
+ var musher3 = async (o) => {
26189
+ const parsed = parseMusherArgs(o.argv);
26190
+ if ("error" in parsed) {
26191
+ (o.error ?? console.error)(`sleds musher: ${parsed.error}`);
26192
+ return 1;
25415
26193
  }
26194
+ const { flags, positional } = parsed;
25416
26195
  if (flags.install)
25417
26196
  return musherInstall(o);
25418
26197
  if (flags.uninstall)
@@ -25430,13 +26209,24 @@ var musher3 = async (o) => {
25430
26209
  // src/commands/off.ts
25431
26210
  init_config();
25432
26211
  init_settings();
26212
+
26213
+ // src/commands/init.ts
26214
+ init_config();
26215
+ init_state();
26216
+ init_settings();
26217
+ var DEFAULT_PROXY_URL2 = "https://proxy.sleds.ai";
26218
+ var lineReaders2 = new WeakMap;
26219
+
26220
+ // src/commands/off.ts
25433
26221
  var off = (o) => {
25434
26222
  const log = o.log ?? console.log;
25435
26223
  const cfg = readConfig(o.home);
25436
26224
  const settings = readSettings(o.home);
25437
- const wasOn = cfg.mode === "on" || isPointedAtProxy(settings);
25438
- if (wasOn)
25439
- writeSettings(o.home, switchOff(settings, cfg.priorEnv ?? {}));
26225
+ const proxyUrls = [cfg.proxyUrl, DEFAULT_PROXY_URL2];
26226
+ const pointed = isSledsProxyEnv(settings, proxyUrls);
26227
+ const wasOn = cfg.mode === "on" || pointed;
26228
+ if (pointed)
26229
+ writeSettings(o.home, switchOff(settings, cfg.priorEnv ?? cfg.priorGatewayEnv ?? {}));
25440
26230
  if (o.scheduler)
25441
26231
  cancelRevert(o.scheduler, cfg.schedule);
25442
26232
  const next = {
@@ -25446,6 +26236,7 @@ var off = (o) => {
25446
26236
  untilReset: undefined,
25447
26237
  reason: undefined,
25448
26238
  priorEnv: undefined,
26239
+ priorGatewayEnv: pointed && !cfg.priorEnv ? undefined : cfg.priorGatewayEnv,
25449
26240
  schedule: undefined
25450
26241
  };
25451
26242
  writeConfig(o.home, next);
@@ -25460,15 +26251,6 @@ var off = (o) => {
25460
26251
  // src/commands/on.ts
25461
26252
  init_config();
25462
26253
  init_settings();
25463
-
25464
- // src/commands/init.ts
25465
- init_config();
25466
- init_state();
25467
- init_settings();
25468
- var DEFAULT_PROXY_URL2 = "https://proxy.sleds.ai";
25469
- var lineReaders2 = new WeakMap;
25470
-
25471
- // src/commands/on.ts
25472
26254
  var on = (o) => {
25473
26255
  const log = o.log ?? console.log;
25474
26256
  const error = o.error ?? console.error;
@@ -25618,7 +26400,7 @@ var orgUse = (o) => {
25618
26400
  writeTokenFile(o.home, seat.token);
25619
26401
  if (existsSync29(settingsPath(o.home)) && cfg.mode !== "on") {
25620
26402
  const settings = readSettings(o.home);
25621
- const pointed = readProxy(settings);
26403
+ const pointed = isSledsProxyEnv(settings, [cfg.proxyUrl]) ? readProxy(settings) : null;
25622
26404
  const url = pointed?.url ?? cfg.proxyUrl;
25623
26405
  if (seat.wiring === "proxy-always" && url)
25624
26406
  writeSettings(o.home, applyProxy(settings, { url, token: seat.token }));
@@ -25653,7 +26435,7 @@ var orgCmd = (o) => {
25653
26435
  init_src();
25654
26436
  import { spawnSync as spawnSync8 } from "node:child_process";
25655
26437
  import { writeFileSync as writeFileSync25 } from "node:fs";
25656
- import { join as join30 } from "node:path";
26438
+ import { join as join31 } from "node:path";
25657
26439
  function packFileName(key) {
25658
26440
  return `pack-${slugify(key, 80) || "item"}.md`;
25659
26441
  }
@@ -25681,7 +26463,7 @@ var packCmd = async (o) => {
25681
26463
  log(r.text);
25682
26464
  return 0;
25683
26465
  }
25684
- const path = join30(o.cwd ?? process.cwd(), packFileName(key));
26466
+ const path = join31(o.cwd ?? process.cwd(), packFileName(key));
25685
26467
  (o.write ?? ((p, t) => writeFileSync25(p, t.endsWith(`
25686
26468
  `) ? t : `${t}
25687
26469
  `)))(path, r.text);
@@ -25690,8 +26472,8 @@ var packCmd = async (o) => {
25690
26472
  };
25691
26473
 
25692
26474
  // src/commands/permissionEvent.ts
25693
- import { chmodSync as chmodSync7, mkdirSync as mkdirSync26, readFileSync as readFileSync28, renameSync as renameSync12, rmSync as rmSync19, writeFileSync as writeFileSync26 } from "node:fs";
25694
- import { dirname as dirname22 } from "node:path";
26475
+ import { chmodSync as chmodSync7, mkdirSync as mkdirSync26, readFileSync as readFileSync29, renameSync as renameSync12, rmSync as rmSync19, writeFileSync as writeFileSync26 } from "node:fs";
26476
+ import { dirname as dirname23 } from "node:path";
25695
26477
  var isObject13 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
25696
26478
  var messageOf = (text) => {
25697
26479
  try {
@@ -25715,7 +26497,7 @@ var permissionEvent = async (o) => {
25715
26497
  if (!isToolPermission2(message))
25716
26498
  return 0;
25717
26499
  const mark = { message, at: (o.now ?? Date.now)() };
25718
- mkdirSync26(dirname22(o.mark), { recursive: true, mode: 448 });
26500
+ mkdirSync26(dirname23(o.mark), { recursive: true, mode: 448 });
25719
26501
  const tmp = `${o.mark}.${process.pid}.tmp`;
25720
26502
  writeFileSync26(tmp, `${JSON.stringify(mark)}
25721
26503
  `, { mode: 384 });
@@ -25762,6 +26544,7 @@ var exitsLine = (hasUpstreamKey) => hasUpstreamKey ? "`/sled handoff <what you w
25762
26544
  var approachingContext = (bar, usedPct, resetsAt, hasUpstreamKey, now) => `[SLEDS] This seat is at ${usedPct !== undefined ? `${Math.round(usedPct)}%` : "90%+"} of its ${LABEL[bar]} limit; it resets in ${fmtIn(resetsAt - now)}. Tell the user in one short line before you answer, and offer the exits: ${exitsLine(hasUpstreamKey)} Do not mention this again unless asked.`;
25763
26545
  var hitContext = (bar, resetsAt, hasUpstreamKey, now) => `[SLEDS] This seat hit its ${LABEL[bar]} limit; it resets in ${fmtIn(resetsAt - now)}. If this reply fails or the user is stuck, the exits are: ${exitsLine(hasUpstreamKey)} Say so in one line.`;
25764
26546
  var creditsBlockText = (hasUpstreamKey) => `SLEDS: this seat is out of usage credits — the next reply would fail, so this prompt was not sent. ${hasUpstreamKey ? "Exits: /sled handoff <task> (a buddy picks it up), sleds on (continue on the workspace's platform key), or /model to a cheaper model." : "Exits: /sled handoff <task> (a buddy picks it up), sleds defer (resume when credits are topped up), or /model to a cheaper model."} Re-send your prompt after choosing; SLEDS will not block again.`;
26547
+ var creditsContext = (hasUpstreamKey) => `[SLEDS] This seat is out of usage credits; this reply may fail. Tell the user in one short line, and offer the exits: ${exitsLine(hasUpstreamKey)}`;
25765
26548
  var promptEvent = async (o) => {
25766
26549
  const log = o.log ?? console.log;
25767
26550
  const error = o.error ?? console.error;
@@ -25784,27 +26567,35 @@ var promptEvent = async (o) => {
25784
26567
  const mark = (bar, stage, at) => {
25785
26568
  told[bar] = { ...told[bar] ?? {}, [stage]: at };
25786
26569
  };
25787
- let code = 0;
26570
+ for (const e of pending)
26571
+ mark(e.bar, e.stage, e.resetsAt);
26572
+ let saved = true;
26573
+ try {
26574
+ writeSeatState(o.home, { ...state, told }, key);
26575
+ } catch {
26576
+ saved = false;
26577
+ }
26578
+ let block = false;
25788
26579
  const lines = [];
25789
26580
  for (const e of pending) {
25790
- mark(e.bar, e.stage, e.resetsAt);
25791
26581
  if (e.bar === "credits") {
25792
- if (code !== 2) {
25793
- error(creditsBlockText(hasKey));
25794
- code = 2;
25795
- }
26582
+ if (saved)
26583
+ block = true;
26584
+ else
26585
+ lines.push(creditsContext(hasKey));
25796
26586
  continue;
25797
26587
  }
25798
26588
  const pct = state.window?.[e.bar]?.usedPct;
25799
26589
  lines.push(e.stage === "approaching" ? approachingContext(e.bar, pct, e.resetsAt, hasKey, now) : hitContext(e.bar, e.resetsAt, hasKey, now));
25800
26590
  }
25801
- try {
25802
- writeSeatState(o.home, { ...state, told }, key);
25803
- } catch {}
25804
- if (code === 0 && lines.length > 0)
26591
+ if (block) {
26592
+ error(creditsBlockText(hasKey));
26593
+ return 2;
26594
+ }
26595
+ if (lines.length > 0)
25805
26596
  log(lines.join(`
25806
26597
  `));
25807
- return code;
26598
+ return 0;
25808
26599
  };
25809
26600
 
25810
26601
  // src/commands/reopen.ts
@@ -25878,13 +26669,14 @@ async function reopenCmd(o) {
25878
26669
 
25879
26670
  // src/commands/route.ts
25880
26671
  init_src();
25881
- import { readFileSync as readFileSync29 } from "node:fs";
26672
+ import { readFileSync as readFileSync30 } from "node:fs";
25882
26673
  import { hostname as hostname12 } from "node:os";
25883
26674
  var ARM_USAGE = "usage: sleds route arm <key> [--off] [--json] [--name <engine>] [--at-once <n>] [--runner tmux|headless|command|orca|cmux] [--runner-cmd '<launcher>'] [--agent '<command>'] [--mode <permission-mode>] [--model <id>] [--effort <e>] [--branch '<template>'] [--pr-title '<template>'] [--repos-dir <dir>]";
25884
- var USAGE2 = "usage: sleds route <key> [--long | --dot | --json] | sleds route arm <key> [--off] [--json] | sleds route answer <key>/<node> --stdin | sleds route propose <key> --file <nodes.json> | sleds route approve <key> | sleds route redraft <key> | sleds route single <key> | sleds route elsewhere <key> [--note <text>] | sleds route decisions [<key>] [--flagged] | sleds route decision <key> <id> --seen | --answer --stdin [--route accept|keep|redraft] | --keep-route | --apply [--answer --stdin] | --overrule --stdin | --ready | sleds route run <key> [--at-once <n>] [--runner tmux|headless|command|orca|cmux] [--runner-cmd '<launcher>'] [--agent '<command>'] [--interval <s>] [--lease <min>] [--idle <min>] [--repo <path>] [--repos-dir <dir>] [--mode <permission-mode>] [--model <id>] [--branch '<template>'] [--pr-title '<template>'] [--once]";
26675
+ var USAGE2 = "usage: sleds route <key> [--long | --dot | --json] | sleds route arm <key> [--off] [--json] | sleds route answer <key>/<node> --stdin | sleds route instruct <key>/<node> --stdin [--voice] | sleds route propose <key> --file <nodes.json> | sleds route approve <key> [--voice --stdin] | sleds route redraft <key> | sleds route single <key> | sleds route elsewhere <key> [--note <text>] | sleds route decisions [<key>] [--flagged] | sleds route decision <key> <id> --seen | --answer --stdin [--route accept|keep|redraft] | --keep-route | --apply [--answer --stdin] | --overrule --stdin | --ready | sleds route run <key> [--at-once <n>] [--runner tmux|headless|command|orca|cmux] [--runner-cmd '<launcher>'] [--agent '<command>'] [--interval <s>] [--lease <min>] [--idle <min>] [--repo <path>] [--repos-dir <dir>] [--mode <permission-mode>] [--model <id>] [--branch '<template>'] [--pr-title '<template>'] [--once]";
25885
26676
  var ANSWER_USAGE = "usage: sleds route answer <key>/<node> <answer…> | sleds route answer <key>/<node> --stdin [--json]";
26677
+ var INSTRUCT_USAGE = "usage: sleds route instruct <key>/<node> <instruction…> | sleds route instruct <key>/<node> --stdin [--voice] [--json]";
25886
26678
  var PROPOSE_USAGE = "usage: sleds route propose <key> (--file <nodes.json> | --stdin) [--name <engine>] [--json]";
25887
- var APPROVE_USAGE = "usage: sleds route approve <key> [--file <nodes.json>] [--json]";
26679
+ var APPROVE_USAGE = "usage: sleds route approve <key> [--file <nodes.json>] [--voice --stdin] [--json]";
25888
26680
  var REDRAFT_USAGE = "usage: sleds route redraft <key> [--json]";
25889
26681
  var SINGLE_USAGE = "usage: sleds route single <key> [--json]";
25890
26682
  var ELSEWHERE_USAGE = "usage: sleds route elsewhere <key> [--note <text>] [--json]";
@@ -25933,6 +26725,10 @@ function parseVerbArgs(argv, allowed) {
25933
26725
  out[flag] = true;
25934
26726
  continue;
25935
26727
  }
26728
+ if (flag === "voice") {
26729
+ out.voice = true;
26730
+ continue;
26731
+ }
25936
26732
  const value = argv[i + 1];
25937
26733
  if (value === undefined || value.startsWith("--"))
25938
26734
  return { error: `${a} needs a value` };
@@ -26056,7 +26852,7 @@ async function answerCmd(o, argv) {
26056
26852
  const words = argv.filter((a) => a !== "--json" && a !== "--stdin");
26057
26853
  const ref = words[0] ?? "";
26058
26854
  const slash = ref.lastIndexOf("/");
26059
- const answer = (fromStdin ? (o.stdin ?? (() => readFileSync29(0, "utf8")))() : words.slice(1).join(" ")).trim();
26855
+ const answer = (fromStdin ? (o.stdin ?? (() => readFileSync30(0, "utf8")))() : words.slice(1).join(" ")).trim();
26060
26856
  if (slash <= 0 || slash === ref.length - 1 || fromStdin && words.length > 1) {
26061
26857
  error(ANSWER_USAGE);
26062
26858
  return 2;
@@ -26088,6 +26884,59 @@ async function answerCmd(o, argv) {
26088
26884
  log(r.json?.state === "answered" ? `${ref}: its landing is answered — the nodes it held are released` : `${ref} answered — back to ready; the engine takes it on its next check`);
26089
26885
  return 0;
26090
26886
  }
26887
+ async function instructCmd(o, argv) {
26888
+ const log = o.log ?? console.log;
26889
+ const error = o.error ?? console.error;
26890
+ const json = argv.includes("--json");
26891
+ const fromStdin = argv.includes("--stdin");
26892
+ const voice = argv.includes("--voice");
26893
+ const words = argv.filter((a) => a !== "--json" && a !== "--stdin" && a !== "--voice");
26894
+ const ref = words[0] ?? "";
26895
+ const slash = ref.lastIndexOf("/");
26896
+ if (slash <= 0 || slash === ref.length - 1 || fromStdin && words.length > 1 || voice && !fromStdin || words.some((w) => w.startsWith("--"))) {
26897
+ error(INSTRUCT_USAGE);
26898
+ return 2;
26899
+ }
26900
+ const text = fromStdin ? (o.stdin ?? (() => readFileSync30(0, "utf8")))() : words.slice(1).join(" ");
26901
+ const { md, heard } = voice ? splitVoiceStdin(text) : { md: text.trim(), heard: undefined };
26902
+ if (!md) {
26903
+ error("an instruction needs some text");
26904
+ return 2;
26905
+ }
26906
+ const client = engineClient(o);
26907
+ if ("error" in client) {
26908
+ error(client.error);
26909
+ return 1;
26910
+ }
26911
+ const r = await client.postFull("/proxy/route/instruct", {
26912
+ project: ref,
26913
+ md,
26914
+ ...heard !== undefined ? { voice: { heard } } : {}
26915
+ });
26916
+ if (!r) {
26917
+ error(`${ref}: SLEDS unreachable — nothing sent`);
26918
+ return 1;
26919
+ }
26920
+ const ok = r.status < 300 && r.json?.ok !== false;
26921
+ if (json)
26922
+ log(JSON.stringify(r.json ?? { ok: false, error: `HTTP ${r.status}` }));
26923
+ if (!ok) {
26924
+ const why = typeof r.json?.error === "string" ? r.json.error : `HTTP ${r.status}`;
26925
+ if (!json)
26926
+ error(`${ref}: ${why}`);
26927
+ return 1;
26928
+ }
26929
+ if (!json)
26930
+ log(`${ref} instructed — the engine following its PR hands it to the session`);
26931
+ return 0;
26932
+ }
26933
+ function splitVoiceStdin(text) {
26934
+ const nl = text.indexOf(`
26935
+ `);
26936
+ const md = (nl < 0 ? text : text.slice(0, nl)).trim();
26937
+ const rest = nl < 0 ? "" : text.slice(nl + 1).trim();
26938
+ return { md, heard: rest || md };
26939
+ }
26091
26940
  async function proposeCmd(o, argv) {
26092
26941
  const log = o.log ?? console.log;
26093
26942
  const error = o.error ?? console.error;
@@ -26135,12 +26984,13 @@ async function proposeCmd(o, argv) {
26135
26984
  async function approveCmd(o, argv) {
26136
26985
  const log = o.log ?? console.log;
26137
26986
  const error = o.error ?? console.error;
26138
- const a = parseVerbArgs(argv, ["file", "json"]);
26139
- if ("error" in a) {
26140
- error(a.error);
26987
+ const a = parseVerbArgs(argv, ["file", "json", "voice", "stdin"]);
26988
+ if ("error" in a || Boolean(a.voice) !== a.stdin) {
26989
+ error("error" in a ? a.error : "--voice and --stdin go together: stdin is what was heard");
26141
26990
  error(APPROVE_USAGE);
26142
26991
  return 2;
26143
26992
  }
26993
+ const heard = a.voice ? (o.stdin ?? (() => readFileSync30(0, "utf8")))().trim() : undefined;
26144
26994
  let nodes;
26145
26995
  let approach;
26146
26996
  if (a.file) {
@@ -26160,7 +27010,8 @@ async function approveCmd(o, argv) {
26160
27010
  const r = await client.postFull("/proxy/route/approve", {
26161
27011
  project: a.key,
26162
27012
  ...nodes ? { nodes } : {},
26163
- ...approach ? { approach } : {}
27013
+ ...approach ? { approach } : {},
27014
+ ...heard !== undefined ? { voice: { heard } } : {}
26164
27015
  });
26165
27016
  if (!r) {
26166
27017
  error(`${a.key}: SLEDS unreachable — nothing approved`);
@@ -26262,7 +27113,7 @@ async function decisionCmd(o, argv) {
26262
27113
  error(DECISION_USAGE);
26263
27114
  return 2;
26264
27115
  }
26265
- const md = seen || routeAlone ? "" : (fromStdin ? (o.stdin ?? (() => readFileSync29(0, "utf8")))() : rest.join(" ")).trim();
27116
+ const md = seen || routeAlone ? "" : (fromStdin ? (o.stdin ?? (() => readFileSync30(0, "utf8")))() : rest.join(" ")).trim();
26266
27117
  if (!seen && !routeAlone && (!md || fromStdin && rest.length > 0)) {
26267
27118
  error(md ? DECISION_USAGE : `${answer ? "an answer" : "an overrule"} needs some text`);
26268
27119
  return 2;
@@ -26441,7 +27292,7 @@ function approvedLine(key, j) {
26441
27292
  function readNodes(o, a) {
26442
27293
  let text;
26443
27294
  try {
26444
- text = a.file ? (o.readFile ?? ((p) => readFileSync29(p, "utf8")))(a.file) : (o.stdin ?? (() => readFileSync29(0, "utf8")))();
27295
+ text = a.file ? (o.readFile ?? ((p) => readFileSync30(p, "utf8")))(a.file) : (o.stdin ?? (() => readFileSync30(0, "utf8")))();
26445
27296
  } catch (e) {
26446
27297
  return { error: e instanceof Error ? e.message : String(e) };
26447
27298
  }
@@ -26470,6 +27321,8 @@ var routeCmd = async (o) => {
26470
27321
  return armCmd(o, o.argv.slice(1));
26471
27322
  if (o.argv[0] === "answer")
26472
27323
  return answerCmd(o, o.argv.slice(1));
27324
+ if (o.argv[0] === "instruct")
27325
+ return instructCmd(o, o.argv.slice(1));
26473
27326
  if (o.argv[0] === "propose")
26474
27327
  return proposeCmd(o, o.argv.slice(1));
26475
27328
  if (o.argv[0] === "approve")
@@ -26796,8 +27649,11 @@ var seatEvent = async (o) => {
26796
27649
  if (week && week.resetsAt > now)
26797
27650
  writeSeatState(o.home, { ...state, lastWallResetsAt: week.resetsAt }, key);
26798
27651
  } catch {}
26799
- const line = seatWallLine(readConfig(o.home).org?.capabilities.upstreamKey !== false);
26800
- log(line);
27652
+ let hasKey = true;
27653
+ try {
27654
+ hasKey = readConfig(o.home).org?.capabilities.upstreamKey !== false;
27655
+ } catch {}
27656
+ log(seatWallLine(hasKey));
26801
27657
  return 0;
26802
27658
  };
26803
27659
 
@@ -26873,14 +27729,14 @@ async function seatRevokeCmd(o) {
26873
27729
 
26874
27730
  // src/commands/seatSync.ts
26875
27731
  import { execFileSync as execFileSync5 } from "node:child_process";
26876
- import { existsSync as existsSync30, mkdirSync as mkdirSync27, readFileSync as readFileSync30, readdirSync as readdirSync7, rmSync as rmSync21, writeFileSync as writeFileSync27 } from "node:fs";
27732
+ import { existsSync as existsSync30, mkdirSync as mkdirSync27, readFileSync as readFileSync31, readdirSync as readdirSync7, rmSync as rmSync21, writeFileSync as writeFileSync27 } from "node:fs";
26877
27733
  import { platform as platform2 } from "node:os";
26878
- import { dirname as dirname23, join as join32 } from "node:path";
27734
+ import { dirname as dirname24, join as join33 } from "node:path";
26879
27735
 
26880
27736
  // src/cookieDb.ts
26881
27737
  import { copyFileSync as copyFileSync3, rmSync as rmSync20 } from "node:fs";
26882
- import { tmpdir as tmpdir2 } from "node:os";
26883
- import { join as join31 } from "node:path";
27738
+ import { tmpdir as tmpdir4 } from "node:os";
27739
+ import { join as join32 } from "node:path";
26884
27740
  var QUERY = "SELECT name, hex(encrypted_value) AS ev FROM cookies WHERE host_key LIKE '%claude.ai%'";
26885
27741
  var isBun = () => typeof globalThis.Bun !== "undefined";
26886
27742
  async function queryRows(dbPath) {
@@ -26909,7 +27765,7 @@ function nodeSqlite() {
26909
27765
  return mod;
26910
27766
  }
26911
27767
  async function readCookieRows(dbPath) {
26912
- const copy = join31(tmpdir2(), `sleds-cookies-${process.pid}-${Date.now()}.sqlite`);
27768
+ const copy = join32(tmpdir4(), `sleds-cookies-${process.pid}-${Date.now()}.sqlite`);
26913
27769
  copyFileSync3(dbPath, copy);
26914
27770
  try {
26915
27771
  const rows = await queryRows(copy);
@@ -27202,7 +28058,7 @@ function findFile(root, name) {
27202
28058
  continue;
27203
28059
  }
27204
28060
  for (const e of entries) {
27205
- const p = join32(dir, e.name);
28061
+ const p = join33(dir, e.name);
27206
28062
  if (e.isDirectory())
27207
28063
  stack.push(p);
27208
28064
  else if (e.name === name)
@@ -27213,7 +28069,7 @@ function findFile(root, name) {
27213
28069
  }
27214
28070
  function findCookieDb(home, source) {
27215
28071
  const b = BROWSERS[source.browser];
27216
- const root = join32(home, APP_SUPPORT, b.appDir);
28072
+ const root = join33(home, APP_SUPPORT, b.appDir);
27217
28073
  if (!existsSync30(root))
27218
28074
  throw new Error(`no ${source.browser} data at ${root} (is it installed?)`);
27219
28075
  if (!b.profiled) {
@@ -27222,15 +28078,15 @@ function findCookieDb(home, source) {
27222
28078
  throw new Error(`no cookie store under ${root}`);
27223
28079
  return db;
27224
28080
  }
27225
- const localStatePath = join32(root, "Local State");
27226
- const localState = existsSync30(localStatePath) ? readFileSync30(localStatePath, "utf8") : null;
28081
+ const localStatePath = join33(root, "Local State");
28082
+ const localState = existsSync30(localStatePath) ? readFileSync31(localStatePath, "utf8") : null;
27227
28083
  const wanted = source.profile ?? "Default";
27228
28084
  const { dir, ambiguous } = resolveProfileDir(localState, wanted);
27229
28085
  if (ambiguous)
27230
28086
  throw new Error(`"${wanted}" names ${ambiguous.length} ${source.browser} profiles (${ambiguous.join(", ")}) — pass the directory or the account email instead`);
27231
28087
  if (!dir)
27232
28088
  throw new Error(`no ${source.browser} profile named "${wanted}" (check chrome://version)`);
27233
- const db = cookieDbCandidates(join32(root, dir)).find((p) => existsSync30(p));
28089
+ const db = cookieDbCandidates(join33(root, dir)).find((p) => existsSync30(p));
27234
28090
  if (!db)
27235
28091
  throw new Error(`profile "${dir}" has no cookie store yet (open claude.ai in it once)`);
27236
28092
  return db;
@@ -27274,7 +28130,7 @@ function fetchSeatUsage(jar, org, now, run) {
27274
28130
  }
27275
28131
  function launchAgentPlist(argv, home, source) {
27276
28132
  const label = launchLabel(source);
27277
- const log = join32(home, ".config", "sleds", `${label.slice("ai.sleds.".length)}.log`);
28133
+ const log = join33(home, ".config", "sleds", `${label.slice("ai.sleds.".length)}.log`);
27278
28134
  const esc = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
27279
28135
  const args = [...argv, "seat-sync", ...sourceArgs(source)].map((a) => ` <string>${esc(a)}</string>`).join(`
27280
28136
  `);
@@ -27296,20 +28152,20 @@ ${args}
27296
28152
  `;
27297
28153
  }
27298
28154
  function plistPath2(home, source) {
27299
- return join32(home, "Library", "LaunchAgents", `${launchLabel(source)}.plist`);
28155
+ return join33(home, "Library", "LaunchAgents", `${launchLabel(source)}.plist`);
27300
28156
  }
27301
28157
  function install(home, argv, source, log, launchctl) {
27302
28158
  if (!argv[0]) {
27303
28159
  log("cannot resolve the sleds command to install the timer");
27304
28160
  return 1;
27305
28161
  }
27306
- const dir = join32(home, "Library", "LaunchAgents");
28162
+ const dir = join33(home, "Library", "LaunchAgents");
27307
28163
  mkdirSync27(dir, { recursive: true });
27308
28164
  const path = plistPath2(home, source);
27309
28165
  const tokenPath = seatTokenPath(home, source);
27310
28166
  rmSync21(tokenPath, { force: true });
27311
28167
  if (source.token) {
27312
- mkdirSync27(dirname23(tokenPath), { recursive: true, mode: 448 });
28168
+ mkdirSync27(dirname24(tokenPath), { recursive: true, mode: 448 });
27313
28169
  writeFileSync27(tokenPath, `${source.token}
27314
28170
  `, { mode: 384 });
27315
28171
  }
@@ -27390,10 +28246,12 @@ var seatSync = async (o) => {
27390
28246
  log(`seat-sync: usage read failed — ${why}`);
27391
28247
  return 2;
27392
28248
  }
27393
- try {
27394
- const key = seatKeyFor(o.home, process.env);
27395
- writeSeatState(o.home, { ...readSeatState(o.home, key), window: usageAsWindow(usage) }, key);
27396
- } catch {}
28249
+ if (token === saved?.token) {
28250
+ try {
28251
+ const key = seatKeyFor(o.home, process.env);
28252
+ writeSeatState(o.home, { ...readSeatState(o.home, key), window: usageAsWindow(usage) }, key);
28253
+ } catch {}
28254
+ }
27397
28255
  const posted = await postSeat(proxyUrl, token, seatSyncBody(usage), o.fetchFn ?? fetch);
27398
28256
  const wk = usage.sevenDay ? `wk ${Math.round(usage.sevenDay.usedPct)}%` : "wk —";
27399
28257
  const models = Object.keys(usage.models).length;
@@ -27403,7 +28261,7 @@ var seatSync = async (o) => {
27403
28261
  };
27404
28262
  function readSeatToken(home, source) {
27405
28263
  try {
27406
- return readFileSync30(seatTokenPath(home, source), "utf8").trim() || undefined;
28264
+ return readFileSync31(seatTokenPath(home, source), "utf8").trim() || undefined;
27407
28265
  } catch {
27408
28266
  return;
27409
28267
  }
@@ -27486,8 +28344,8 @@ var sessionEvent = async (o) => {
27486
28344
 
27487
28345
  // src/commands/specEvent.ts
27488
28346
  init_meter();
27489
- import { readFileSync as readFileSync31, statSync as statSync7 } from "node:fs";
27490
- import { basename as basename8, isAbsolute as isAbsolute7, relative as relative2 } from "node:path";
28347
+ import { readFileSync as readFileSync32, statSync as statSync8 } from "node:fs";
28348
+ import { basename as basename9, isAbsolute as isAbsolute7, relative as relative2 } from "node:path";
27491
28349
  var HOOK_STDIN_TIMEOUT_MS3 = 1000;
27492
28350
  var ESTIMATE_TIMEOUT_MS = 4000;
27493
28351
  var MIN_SPEC_CHARS = 200;
@@ -27500,12 +28358,12 @@ function isSpecPath(path) {
27500
28358
  return false;
27501
28359
  if (NOT_SPEC.test(path))
27502
28360
  return false;
27503
- return SPEC_DIRS.test(path) || SPEC_NAMES.test(basename8(path));
28361
+ return SPEC_DIRS.test(path) || SPEC_NAMES.test(basename9(path));
27504
28362
  }
27505
28363
  function specTitle(body, path) {
27506
28364
  const m = /^#\s+(.+?)\s*$/m.exec(body);
27507
28365
  const t = m?.[1]?.trim();
27508
- return (t && t.length > 0 ? t : basename8(path).replace(/\.(md|mdx)$/i, "")).slice(0, 120);
28366
+ return (t && t.length > 0 ? t : basename9(path).replace(/\.(md|mdx)$/i, "")).slice(0, 120);
27509
28367
  }
27510
28368
  function looksLikeSpec(body) {
27511
28369
  if (body.trim().length < MIN_SPEC_CHARS)
@@ -27540,9 +28398,9 @@ function projectKey(filePath, cwd) {
27540
28398
  }
27541
28399
  function readFileOrNull(path) {
27542
28400
  try {
27543
- if (statSync7(path).size > 2000000)
28401
+ if (statSync8(path).size > 2000000)
27544
28402
  return null;
27545
- return readFileSync31(path, "utf8");
28403
+ return readFileSync32(path, "utf8");
27546
28404
  } catch {
27547
28405
  return null;
27548
28406
  }
@@ -27553,7 +28411,12 @@ var specEvent = async (o) => {
27553
28411
  const input = raw ? parseHookInput(raw) : null;
27554
28412
  if (!input || !isSpecPath(input.filePath))
27555
28413
  return 0;
27556
- const src = meterSource(o.home);
28414
+ let src;
28415
+ try {
28416
+ src = meterSource(o.home);
28417
+ } catch {
28418
+ return 0;
28419
+ }
27557
28420
  if (!src)
27558
28421
  return 0;
27559
28422
  const body = (o.readFile ?? readFileOrNull)(input.filePath);
@@ -27563,7 +28426,7 @@ var specEvent = async (o) => {
27563
28426
  const spec = {
27564
28427
  title: specTitle(body, input.filePath),
27565
28428
  body: body.slice(0, MAX_SPEC_CHARS),
27566
- repoHint: input.cwd ? basename8(input.cwd) : undefined
28429
+ repoHint: input.cwd ? basename9(input.cwd) : undefined
27567
28430
  };
27568
28431
  try {
27569
28432
  const res = await (o.fetchFn ?? fetch)(`${src.proxyUrl}/proxy/estimate`, {
@@ -27759,7 +28622,6 @@ var status3 = async (o) => {
27759
28622
  };
27760
28623
 
27761
28624
  // src/commands/statusline.ts
27762
- init_config();
27763
28625
  init_meter();
27764
28626
  var statusline = async (o) => {
27765
28627
  const log = o.log ?? console.log;
@@ -27798,7 +28660,8 @@ var statusline = async (o) => {
27798
28660
  home: o.home,
27799
28661
  now,
27800
28662
  fetchFn: o.fetchFn,
27801
- timeoutMs: o.timeoutMs
28663
+ timeoutMs: o.timeoutMs,
28664
+ env: o.env
27802
28665
  });
27803
28666
  line = formatMeterLine(meter, now, seatFragment);
27804
28667
  } catch {
@@ -27817,9 +28680,9 @@ var speakToCockpit = async (o, key, window, events, now) => {
27817
28680
  try {
27818
28681
  const st = readSeatState(o.home, key);
27819
28682
  let feed;
27820
- const src = meterSource(o.home);
28683
+ const src = meterSource(o.home, undefined, { env: o.env });
27821
28684
  if (src && now - (st.cockpit?.feed?.at ?? 0) >= FEED_MIN_INTERVAL_MS) {
27822
- const got = await fetchFeed({ ...src, email: readConfig(o.home).email, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs }, now);
28685
+ const got = await fetchFeed({ ...src, email: key.email ?? undefined, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs }, now);
27823
28686
  if (got)
27824
28687
  feed = got;
27825
28688
  }
@@ -27832,13 +28695,13 @@ var speakToCockpit = async (o, key, window, events, now) => {
27832
28695
  return;
27833
28696
  writeSeatState(o.home, { ...readSeatState(o.home, key), cockpit: r.state }, key);
27834
28697
  if (r.argv.length)
27835
- await runCockpit(cockpit, r.argv, { spawn: o.spawn });
28698
+ await runCockpit(cockpit, r.argv, { exec: o.exec });
27836
28699
  } catch {}
27837
28700
  };
27838
28701
  var rememberSeat = (o, key, window, session, now, input) => {
27839
28702
  const prior = readSeatState(o.home, key);
27840
28703
  let state = { ...prior, window, ...session ? { session } : {} };
27841
- const src = meterSource(o.home);
28704
+ const src = meterSource(o.home, undefined, { env: o.env });
27842
28705
  const posts = [];
27843
28706
  if (src && shouldPostSeat(prior, window, now)) {
27844
28707
  state = markPosted(state, window, now);
@@ -27868,7 +28731,7 @@ var creditsEvent = (o, prior, input, now) => {
27868
28731
  var announce = (o, events) => {
27869
28732
  if (events.length === 0)
27870
28733
  return [];
27871
- const src = meterSource(o.home);
28734
+ const src = meterSource(o.home, undefined, { env: o.env });
27872
28735
  if (!src)
27873
28736
  return [];
27874
28737
  return events.map((e) => postSeatWall({ ...src, fetchFn: o.fetchFn, timeoutMs: o.timeoutMs ?? SEAT_POST_TIMEOUT_MS }, {
@@ -27882,7 +28745,7 @@ var announce = (o, events) => {
27882
28745
 
27883
28746
  // src/commands/stopEvent.ts
27884
28747
  init_meter();
27885
- import { existsSync as existsSync32, readFileSync as readFileSync32 } from "node:fs";
28748
+ import { existsSync as existsSync32, readFileSync as readFileSync33 } from "node:fs";
27886
28749
 
27887
28750
  // src/transcript.ts
27888
28751
  var isObject16 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
@@ -27975,10 +28838,15 @@ async function postTranscript(o, payload, now) {
27975
28838
  const path = payload.transcript_path?.trim();
27976
28839
  if (!sessionId || !path)
27977
28840
  return false;
27978
- const src = meterSource(o.home);
28841
+ let src;
28842
+ try {
28843
+ src = meterSource(o.home);
28844
+ } catch {
28845
+ return false;
28846
+ }
27979
28847
  if (!src)
27980
28848
  return false;
27981
- const read = o.readFile ?? ((p) => existsSync32(p) ? readFileSync32(p, "utf8") : null);
28849
+ const read = o.readFile ?? ((p) => existsSync32(p) ? readFileSync33(p, "utf8") : null);
27982
28850
  let text;
27983
28851
  try {
27984
28852
  text = read(path);
@@ -28072,21 +28940,27 @@ var uninstall2 = (o) => {
28072
28940
  if (existsSync33(settingsPath(o.home))) {
28073
28941
  let settings = readSettings(o.home);
28074
28942
  let changed = false;
28075
- if (hasEitherKey(settings)) {
28076
- settings = removeProxy(settings);
28943
+ const cfg = readConfig(o.home);
28944
+ const proxyUrls = [cfg.proxyUrl, DEFAULT_PROXY_URL2];
28945
+ const restoredEnv = isSledsProxyEnv(settings, proxyUrls);
28946
+ if (restoredEnv) {
28947
+ const restore = envToRestore([cfg.priorEnv, cfg.priorGatewayEnv], proxyUrls);
28948
+ settings = switchOff(settings, restore);
28077
28949
  changed = true;
28078
- removed.push(`env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN from ${settingsPath(o.home)}`);
28950
+ removed.push(hasGatewayEnv(settings) ? `env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN from ${settingsPath(o.home)} (your own gateway restored)` : `env.ANTHROPIC_BASE_URL and env.ANTHROPIC_AUTH_TOKEN from ${settingsPath(o.home)}`);
28079
28951
  }
28080
- if (isSledsStatusLine(settings[STATUS_LINE_KEY])) {
28081
- const cfg = readConfig(o.home);
28952
+ const restoredLine = isSledsStatusLine(settings[STATUS_LINE_KEY]);
28953
+ if (restoredLine) {
28082
28954
  settings = removeStatusLine(settings, cfg.priorStatusLine);
28083
28955
  changed = true;
28084
28956
  removed.push(cfg.priorStatusLine === undefined ? `statusLine from ${settingsPath(o.home)}` : `statusLine from ${settingsPath(o.home)} (previous one restored)`);
28085
- if (cfg.priorStatusLine !== undefined) {
28086
- const { priorStatusLine: _restored, ...rest } = cfg;
28087
- writeConfig(o.home, rest);
28088
- }
28089
28957
  }
28958
+ if (restoredLine && cfg.priorStatusLine !== undefined || restoredEnv && (cfg.priorEnv !== undefined || cfg.priorGatewayEnv !== undefined))
28959
+ writeConfig(o.home, {
28960
+ ...cfg,
28961
+ ...restoredLine ? { priorStatusLine: undefined } : {},
28962
+ ...restoredEnv ? { priorEnv: undefined, priorGatewayEnv: undefined } : {}
28963
+ });
28090
28964
  if (hasSeatHook(settings)) {
28091
28965
  settings = removeSeatHook(settings);
28092
28966
  changed = true;
@@ -28142,18 +29016,12 @@ var uninstall2 = (o) => {
28142
29016
  log(`Kept ${tokenPath(o.home)} (delete it yourself if you want the token gone).`);
28143
29017
  return 0;
28144
29018
  };
28145
- var hasEitherKey = (settings) => {
28146
- const env = settings.env;
28147
- if (typeof env !== "object" || env === null)
28148
- return false;
28149
- return BASE_URL_KEY in env || AUTH_TOKEN_KEY in env;
28150
- };
28151
29019
 
28152
29020
  // src/commands/update.ts
28153
29021
  init_server();
28154
29022
  import { existsSync as existsSync34 } from "node:fs";
28155
29023
  import { homedir as homedir8, userInfo as userInfo4 } from "node:os";
28156
- import { basename as basename9 } from "node:path";
29024
+ import { basename as basename10 } from "node:path";
28157
29025
  init_config();
28158
29026
  init_state();
28159
29027
  init_meter();
@@ -28218,7 +29086,7 @@ var realPidAlive4 = (pid) => {
28218
29086
  };
28219
29087
  function machineClient(home, env) {
28220
29088
  const src = meterSource(home, undefined, { env });
28221
- return src ? fetchClient({ ...src, version: "0.1.60" }) : null;
29089
+ return src ? fetchClient({ ...src, version: "0.1.61" }) : null;
28222
29090
  }
28223
29091
  async function noteAnswer2(home, a, now) {
28224
29092
  try {
@@ -28230,7 +29098,7 @@ async function noteAnswer2(home, a, now) {
28230
29098
  }
28231
29099
  function busyEngines(files) {
28232
29100
  return files.map((f) => ({
28233
- name: basename9(f, ".json"),
29101
+ name: basename10(f, ".json"),
28234
29102
  running: readState(f).runs.filter(isRunning).length
28235
29103
  })).filter((e) => e.running > 0);
28236
29104
  }
@@ -28543,13 +29411,13 @@ var webhooksCmd = async (o) => {
28543
29411
 
28544
29412
  // src/config.ts
28545
29413
  init_settings();
28546
- import { chmodSync as chmodSync8, existsSync as existsSync35, mkdirSync as mkdirSync28, readFileSync as readFileSync33, renameSync as renameSync13, writeFileSync as writeFileSync28 } from "node:fs";
28547
- import { join as join33, resolve as resolve11 } from "node:path";
29414
+ import { chmodSync as chmodSync8, existsSync as existsSync35, mkdirSync as mkdirSync28, readFileSync as readFileSync34, renameSync as renameSync13, writeFileSync as writeFileSync28 } from "node:fs";
29415
+ import { join as join34, resolve as resolve12 } from "node:path";
28548
29416
  var configDir2 = (home, env = process.env) => {
28549
29417
  const o = env.SLEDS_HOME?.trim();
28550
- return o ? resolve11(home, o) : join33(home, ".config", "sleds");
29418
+ return o ? resolve12(home, o) : join34(home, ".config", "sleds");
28551
29419
  };
28552
- var configPath2 = (home) => join33(configDir2(home), "config.json");
29420
+ var configPath2 = (home) => join34(configDir2(home), "config.json");
28553
29421
  var isObject17 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
28554
29422
  var optString2 = (v) => typeof v === "string" ? v : undefined;
28555
29423
  var parsePriorEnv2 = (v) => {
@@ -28599,6 +29467,9 @@ var parseConfig2 = (raw) => {
28599
29467
  cfg.schedule = schedule;
28600
29468
  if (raw.priorStatusLine !== undefined)
28601
29469
  cfg.priorStatusLine = raw.priorStatusLine;
29470
+ const priorGatewayEnv = parsePriorEnv2(raw.priorGatewayEnv);
29471
+ if (priorGatewayEnv !== undefined)
29472
+ cfg.priorGatewayEnv = priorGatewayEnv;
28602
29473
  if (raw.wiring === "seat-first" || raw.wiring === "proxy-always")
28603
29474
  cfg.wiring = raw.wiring;
28604
29475
  if (raw.agentKind === "claude-code" || raw.agentKind === "codex")
@@ -28683,7 +29554,7 @@ var readConfig2 = (home) => {
28683
29554
  const p = configPath2(home);
28684
29555
  if (!existsSync35(p))
28685
29556
  return {};
28686
- return parseConfig2(JSON.parse(readFileSync33(p, "utf8")));
29557
+ return parseConfig2(JSON.parse(readFileSync34(p, "utf8")));
28687
29558
  };
28688
29559
 
28689
29560
  // src/runners/exec.ts
@@ -28741,9 +29612,9 @@ function runnerFor3(name, exec = defaultExec, o = {}) {
28741
29612
 
28742
29613
  // src/runtime.ts
28743
29614
  import { homedir as homedir9 } from "node:os";
28744
- import { join as join34, win32 as win323 } from "node:path";
29615
+ import { join as join35, win32 as win323 } from "node:path";
28745
29616
  var isCompiled2 = (argv1 = process.argv[1]) => Boolean(argv1 && (argv1.startsWith("/$bunfs/") || argv1.startsWith("B:\\~BUN\\")));
28746
- var sledsLink2 = (home = homedir9(), platform = process.platform) => platform === "win32" ? win323.join(process.env.LOCALAPPDATA ?? win323.join(home, "AppData", "Local"), "sleds", "bin", "sleds.exe") : join34(home, ".sleds", "bin", "sleds");
29617
+ var sledsLink2 = (home = homedir9(), platform = process.platform) => platform === "win32" ? win323.join(process.env.LOCALAPPDATA ?? win323.join(home, "AppData", "Local"), "sleds", "bin", "sleds.exe") : join35(home, ".sleds", "bin", "sleds");
28747
29618
 
28748
29619
  // src/schedule.ts
28749
29620
  import { spawnSync as spawnSync11 } from "node:child_process";
@@ -28755,15 +29626,15 @@ var defaultExec5 = (cmd, args) => {
28755
29626
  };
28756
29627
 
28757
29628
  // src/settings.ts
28758
- import { dirname as dirname24, join as join35, resolve as resolve12 } from "node:path";
29629
+ import { dirname as dirname25, join as join36, resolve as resolve13 } from "node:path";
28759
29630
  var withSeatEnv2 = (command, home, env = process.env, org) => {
28760
29631
  const parts = [];
28761
29632
  const sh = env.SLEDS_HOME?.trim();
28762
29633
  const cc = env.CLAUDE_CONFIG_DIR?.trim();
28763
29634
  if (sh)
28764
- parts.push(`SLEDS_HOME=${shellWord2(resolve12(home, sh))}`);
29635
+ parts.push(`SLEDS_HOME=${shellWord2(resolve13(home, sh))}`);
28765
29636
  if (cc)
28766
- parts.push(`CLAUDE_CONFIG_DIR=${shellWord2(resolve12(home, cc))}`);
29637
+ parts.push(`CLAUDE_CONFIG_DIR=${shellWord2(resolve13(home, cc))}`);
28767
29638
  parts.push(command);
28768
29639
  return pinOrg4(parts.join(" "), org);
28769
29640
  };
@@ -28779,12 +29650,16 @@ var pinOrg4 = (command, org) => {
28779
29650
  };
28780
29651
  var shellWord2 = (v) => /^[\w./~-]+$/.test(v) ? v : `'${v.replace(/'/g, "'\\''")}'`;
28781
29652
  var isObject18 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
29653
+ var invokesSleds2 = (command) => /\bsleds\b/.test(command);
28782
29654
  var isSledsHookFor2 = (sub) => (entry) => {
28783
29655
  if (!isObject18(entry) || !Array.isArray(entry.hooks))
28784
29656
  return false;
28785
29657
  const re = new RegExp(`\\b${sub}\\b`);
28786
- return entry.hooks.some((h) => isObject18(h) && h.type === "command" && typeof h.command === "string" && re.test(h.command));
29658
+ return entry.hooks.some((h) => isObject18(h) && h.type === "command" && typeof h.command === "string" && invokesSleds2(h.command) && re.test(h.command));
28787
29659
  };
29660
+ var isSledsSeatHook2 = isSledsHookFor2("seat-event");
29661
+ var isSledsSpecHook2 = isSledsHookFor2("spec-event");
29662
+ var isSledsPromptHook2 = isSledsHookFor2("prompt-event");
28788
29663
  var isSledsSessionHook2 = isSledsHookFor2("session-event");
28789
29664
  var isSledsStopHook2 = isSledsHookFor2("stop-event");
28790
29665
  var isSledsPermissionHook2 = isSledsHookFor2("permission-event");
@@ -29081,7 +29956,7 @@ var main = async (argv) => {
29081
29956
  now: Date.now(),
29082
29957
  stderr: (line) => console.error(line),
29083
29958
  isTTY: !!process.stderr.isTTY,
29084
- version: "0.1.60"
29959
+ version: "0.1.61"
29085
29960
  });
29086
29961
  switch (cmd) {
29087
29962
  case "update":
@@ -29314,7 +30189,7 @@ ${renderHelpText(argv[1])}`);
29314
30189
  case "version":
29315
30190
  case "--version":
29316
30191
  case "-v":
29317
- console.log("sleds 0.1.60");
30192
+ console.log("sleds 0.1.61");
29318
30193
  return 0;
29319
30194
  default:
29320
30195
  console.error(`unknown command: ${cmd}