@motir/cli 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +2826 -405
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ var __export = (target, all) => {
9
9
  import { Command as Command2, Option } from "commander";
10
10
 
11
11
  // package.json
12
- var version = "0.10.0";
12
+ var version = "0.11.0";
13
13
 
14
14
  // src/version.ts
15
15
  var CLI_VERSION = version;
@@ -34670,7 +34670,7 @@ function validate60(data, { instancePath = "", parentData, parentDataProperty, r
34670
34670
  }
34671
34671
  validate60.evaluated = { "props": { "verdict": true, "designCardKey": true, "designCardTitle": true, "design": true, "reason": true }, "dynamicProps": false, "dynamicItems": false };
34672
34672
  var operation_getWorkItemDispatchPrompt = validate61;
34673
- var schema101 = { "type": "object", "properties": { "key": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "prompt": { "type": "string" }, "parentKey": { "anyOf": [{ "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, { "type": "null" }] }, "targetRepo": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "targetRepoCloneUrl": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "targetRepoDefaultBranch": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "targetRepos": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "cloneUrl": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "defaultBranch": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "delivery": { "anyOf": [{ "type": "string", "enum": ["delivered", "awaiting", "unknown", "unestablished", "excluded"] }, { "type": "null" }] } }, "required": ["name", "cloneUrl", "defaultBranch", "delivery"] } }, "workflowMode": { "type": "string", "enum": ["per_item_pr", "session_lineage"] }, "sessionBranch": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "workBranch": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "branch": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "advisories": { "type": "array", "items": { "anyOf": [{ "type": "object", "properties": { "kind": { "type": "string", "const": "shape" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "severity": { "type": "string" }, "criterionIndex": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "phrase": { "type": "string" }, "path": { "type": "string" }, "repo": { "type": "string" }, "reason": { "type": "string" } }, "required": ["kind", "item", "severity", "criterionIndex"] }, { "type": "object", "properties": { "kind": { "type": "string", "const": "shape" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "severity": { "type": "string" }, "threshold": { "type": "string" }, "storyPoints": { "anyOf": [{ "type": "number" }, { "type": "null" }] }, "estimateMinutes": { "anyOf": [{ "type": "number" }, { "type": "null" }] } }, "required": ["kind", "item", "severity", "threshold", "storyPoints", "estimateMinutes"] }, { "type": "object", "properties": { "kind": { "type": "string", "const": "shape" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "severity": { "type": "string" }, "designCriterionIndex": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "surfaceCriterionIndex": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 } }, "required": ["kind", "item", "severity", "designCriterionIndex", "surfaceCriterionIndex"] }, { "type": "object", "properties": { "kind": { "type": "string", "const": "shape" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "severity": { "type": "string" }, "bodyEdit": { "type": "object", "properties": { "at": { "type": "string" }, "fields": { "type": "array", "items": { "type": "string" } } }, "required": ["at", "fields"] }, "fieldMove": { "type": "object", "properties": { "at": { "type": "string" }, "fields": { "type": "array", "items": { "type": "string" } } }, "required": ["at", "fields"] } }, "required": ["kind", "item", "severity", "bodyEdit", "fieldMove"] }, { "type": "object", "properties": { "kind": { "type": "string", "const": "shape" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "severity": { "type": "string" }, "claim": { "type": "string" }, "claimedCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "blockerCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 } }, "required": ["kind", "item", "severity", "claim", "claimedCount", "blockerCount"] }, { "anyOf": [{ "type": "object", "properties": { "kind": { "type": "string", "const": "subsumption" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "severity": { "type": "string" }, "path": { "type": "string" }, "pullRequest": { "type": "string" }, "pullRequestTitle": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "state": { "type": "string", "const": "merged" }, "mergedAt": { "type": "string" } }, "required": ["kind", "item", "severity", "path", "pullRequest", "pullRequestTitle", "state", "mergedAt"] }, { "type": "object", "properties": { "kind": { "type": "string", "const": "subsumption" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "severity": { "type": "string" }, "path": { "type": "string" }, "pullRequest": { "type": "string" }, "pullRequestTitle": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "state": { "type": "string", "const": "open" }, "mergedAt": { "type": "null" } }, "required": ["kind", "item", "severity", "path", "pullRequest", "pullRequestTitle", "state", "mergedAt"] }] }, { "type": "object", "properties": { "kind": { "type": "string", "const": "reference" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "referenced": { "type": "string" }, "referencedStatus": { "type": "string" }, "severity": { "type": "string" } }, "required": ["item", "referenced", "referencedStatus", "severity"] }] } } }, "required": ["key", "prompt", "parentKey", "targetRepo", "targetRepoCloneUrl", "targetRepoDefaultBranch", "targetRepos", "workflowMode", "sessionBranch", "advisories"], "$id": "motir://v1/components/DispatchPrompt" };
34673
+ var schema101 = { "type": "object", "properties": { "key": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "prompt": { "type": "string" }, "parentKey": { "anyOf": [{ "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, { "type": "null" }] }, "targetRepo": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "targetRepoCloneUrl": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "targetRepoDefaultBranch": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "targetRepos": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "cloneUrl": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "defaultBranch": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "delivery": { "anyOf": [{ "type": "string", "enum": ["delivered", "awaiting", "unknown", "unestablished", "excluded", "delivered_without_change_request"] }, { "type": "null" }] } }, "required": ["name", "cloneUrl", "defaultBranch", "delivery"] } }, "workflowMode": { "type": "string", "enum": ["per_item_pr", "session_lineage"] }, "sessionBranch": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "workBranch": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "branch": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "advisories": { "type": "array", "items": { "anyOf": [{ "type": "object", "properties": { "kind": { "type": "string", "const": "shape" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "severity": { "type": "string" }, "criterionIndex": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "phrase": { "type": "string" }, "path": { "type": "string" }, "repo": { "type": "string" }, "reason": { "type": "string" } }, "required": ["kind", "item", "severity", "criterionIndex"] }, { "type": "object", "properties": { "kind": { "type": "string", "const": "shape" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "severity": { "type": "string" }, "threshold": { "type": "string" }, "storyPoints": { "anyOf": [{ "type": "number" }, { "type": "null" }] }, "estimateMinutes": { "anyOf": [{ "type": "number" }, { "type": "null" }] } }, "required": ["kind", "item", "severity", "threshold", "storyPoints", "estimateMinutes"] }, { "type": "object", "properties": { "kind": { "type": "string", "const": "shape" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "severity": { "type": "string" }, "designCriterionIndex": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 }, "surfaceCriterionIndex": { "type": "integer", "minimum": -9007199254740991, "maximum": 9007199254740991 } }, "required": ["kind", "item", "severity", "designCriterionIndex", "surfaceCriterionIndex"] }, { "type": "object", "properties": { "kind": { "type": "string", "const": "shape" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "severity": { "type": "string" }, "bodyEdit": { "type": "object", "properties": { "at": { "type": "string" }, "fields": { "type": "array", "items": { "type": "string" } } }, "required": ["at", "fields"] }, "fieldMove": { "type": "object", "properties": { "at": { "type": "string" }, "fields": { "type": "array", "items": { "type": "string" } } }, "required": ["at", "fields"] } }, "required": ["kind", "item", "severity", "bodyEdit", "fieldMove"] }, { "type": "object", "properties": { "kind": { "type": "string", "const": "shape" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "severity": { "type": "string" }, "claim": { "type": "string" }, "claimedCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 }, "blockerCount": { "type": "integer", "minimum": 0, "maximum": 9007199254740991 } }, "required": ["kind", "item", "severity", "claim", "claimedCount", "blockerCount"] }, { "anyOf": [{ "type": "object", "properties": { "kind": { "type": "string", "const": "subsumption" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "severity": { "type": "string" }, "path": { "type": "string" }, "pullRequest": { "type": "string" }, "pullRequestTitle": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "state": { "type": "string", "const": "merged" }, "mergedAt": { "type": "string" } }, "required": ["kind", "item", "severity", "path", "pullRequest", "pullRequestTitle", "state", "mergedAt"] }, { "type": "object", "properties": { "kind": { "type": "string", "const": "subsumption" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "severity": { "type": "string" }, "path": { "type": "string" }, "pullRequest": { "type": "string" }, "pullRequestTitle": { "anyOf": [{ "type": "string" }, { "type": "null" }] }, "state": { "type": "string", "const": "open" }, "mergedAt": { "type": "null" } }, "required": ["kind", "item", "severity", "path", "pullRequest", "pullRequestTitle", "state", "mergedAt"] }] }, { "type": "object", "properties": { "kind": { "type": "string", "const": "reference" }, "item": { "type": "string", "pattern": "^[A-Z][A-Z0-9]*-\\d+$" }, "referenced": { "type": "string" }, "referencedStatus": { "type": "string" }, "severity": { "type": "string" } }, "required": ["item", "referenced", "referencedStatus", "severity"] }] } } }, "required": ["key", "prompt", "parentKey", "targetRepo", "targetRepoCloneUrl", "targetRepoDefaultBranch", "targetRepos", "workflowMode", "sessionBranch", "advisories"], "$id": "motir://v1/components/DispatchPrompt" };
34674
34674
  function validate61(data, { instancePath = "", parentData, parentDataProperty, rootData = data, dynamicAnchors = {} } = {}) {
34675
34675
  ;
34676
34676
  let vErrors = null;
@@ -35099,7 +35099,7 @@ function validate61(data, { instancePath = "", parentData, parentDataProperty, r
35099
35099
  }
35100
35100
  errors++;
35101
35101
  }
35102
- if (!(data11 === "delivered" || data11 === "awaiting" || data11 === "unknown" || data11 === "unestablished" || data11 === "excluded")) {
35102
+ if (!(data11 === "delivered" || data11 === "awaiting" || data11 === "unknown" || data11 === "unestablished" || data11 === "excluded" || data11 === "delivered_without_change_request")) {
35103
35103
  const err20 = { instancePath: instancePath + "/targetRepos/" + i0 + "/delivery", schemaPath: "motir://v1/components/DispatchPrompt/properties/targetRepos/items/properties/delivery/anyOf/0/enum", keyword: "enum", params: { allowedValues: schema101.properties.targetRepos.items.properties.delivery.anyOf[0].enum }, message: "must be equal to one of the allowed values" };
35104
35104
  if (vErrors === null) {
35105
35105
  vErrors = [err20];
@@ -55659,25 +55659,25 @@ function isInteractive() {
55659
55659
  function promptLine(question, defaultValue) {
55660
55660
  const suffix = defaultValue ? ` [${defaultValue}]` : "";
55661
55661
  const rl = createInterface({ input: process.stdin, output: process.stderr });
55662
- return new Promise((resolve6) => {
55662
+ return new Promise((resolve8) => {
55663
55663
  rl.question(`${question}${suffix}: `, (answer) => {
55664
55664
  rl.close();
55665
55665
  const trimmed = answer.trim();
55666
- resolve6(trimmed.length > 0 ? trimmed : defaultValue ?? "");
55666
+ resolve8(trimmed.length > 0 ? trimmed : defaultValue ?? "");
55667
55667
  });
55668
55668
  });
55669
55669
  }
55670
55670
  function promptLineOrNull(question) {
55671
55671
  const rl = createInterface({ input: process.stdin, output: process.stderr });
55672
- return new Promise((resolve6) => {
55672
+ return new Promise((resolve8) => {
55673
55673
  let answered = false;
55674
55674
  rl.question(`${question}`, (answer) => {
55675
55675
  answered = true;
55676
55676
  rl.close();
55677
- resolve6(answer);
55677
+ resolve8(answer);
55678
55678
  });
55679
55679
  rl.on("close", () => {
55680
- if (!answered) resolve6(null);
55680
+ if (!answered) resolve8(null);
55681
55681
  });
55682
55682
  });
55683
55683
  }
@@ -55686,11 +55686,11 @@ function promptSecret(question) {
55686
55686
  rl._writeToOutput = () => {
55687
55687
  };
55688
55688
  process.stderr.write(`${question}: `);
55689
- return new Promise((resolve6) => {
55689
+ return new Promise((resolve8) => {
55690
55690
  rl.question("", (answer) => {
55691
55691
  rl.close();
55692
55692
  process.stderr.write("\n");
55693
- resolve6(answer.trim());
55693
+ resolve8(answer.trim());
55694
55694
  });
55695
55695
  });
55696
55696
  }
@@ -55849,14 +55849,14 @@ function openUrl(url, opts = {}) {
55849
55849
  const env = opts.env ?? process.env;
55850
55850
  if (!hasDisplay(env, platform)) return Promise.resolve(false);
55851
55851
  const { cmd, args } = launcher(platform);
55852
- return new Promise((resolve6) => {
55852
+ return new Promise((resolve8) => {
55853
55853
  try {
55854
55854
  const child = spawn(cmd, [...args, url], { stdio: "ignore", detached: true });
55855
- child.on("error", () => resolve6(false));
55855
+ child.on("error", () => resolve8(false));
55856
55856
  child.unref();
55857
- setTimeout(() => resolve6(true), 0);
55857
+ setTimeout(() => resolve8(true), 0);
55858
55858
  } catch {
55859
- resolve6(false);
55859
+ resolve8(false);
55860
55860
  }
55861
55861
  });
55862
55862
  }
@@ -56102,11 +56102,11 @@ async function awaitApproval(input) {
56102
56102
  }
56103
56103
  async function loginCommand(opts, deps = {}) {
56104
56104
  const sleep = deps.sleep ?? delay;
56105
- const open = deps.openUrl ?? openUrl;
56105
+ const open2 = deps.openUrl ?? openUrl;
56106
56106
  const serverUrl = resolveServerUrl(opts.server);
56107
56107
  const grant = await startDeviceGrant({ serverUrl, hostname: hostname() });
56108
56108
  printGrant(grant);
56109
- const opened = opts.browser === false ? false : await open(grant.verification_uri_complete);
56109
+ const opened = opts.browser === false ? false : await open2(grant.verification_uri_complete);
56110
56110
  if (opened) info("Opened your browser \u2014 approve the request there.");
56111
56111
  else info("Open that URL on any device, sign in, and enter the code.");
56112
56112
  info("Waiting for approval\u2026");
@@ -56779,16 +56779,16 @@ function renderSprintsTable(sprints, nameWidth = 40) {
56779
56779
  ${formatTable(SPRINTS_HEADERS, rows, { rightAlign: [3, 4] })}`;
56780
56780
  }
56781
56781
  function renderSprintHeader(sprint) {
56782
- const lines = [`${sprint.name} [${sprint.state}]`];
56782
+ const lines2 = [`${sprint.name} [${sprint.state}]`];
56783
56783
  const window = formatSprintWindow(sprint);
56784
- if (window) lines.push(window);
56785
- if (sprint.goal) lines.push(`goal: ${sprint.goal}`);
56784
+ if (window) lines2.push(window);
56785
+ if (sprint.goal) lines2.push(`goal: ${sprint.goal}`);
56786
56786
  if (sprint.committedIssueCount !== null || sprint.committedPoints !== null) {
56787
56787
  const issues = sprint.committedIssueCount === null ? NONE : String(sprint.committedIssueCount);
56788
56788
  const points = sprint.committedPoints === null ? NONE : String(sprint.committedPoints);
56789
- lines.push(`committed: ${issues} issues \xB7 ${points} points`);
56789
+ lines2.push(`committed: ${issues} issues \xB7 ${points} points`);
56790
56790
  }
56791
- return lines.join("\n");
56791
+ return lines2.join("\n");
56792
56792
  }
56793
56793
  var SPRINT_ITEM_HEADERS = ["KEY", "KIND", "STATUS", "PRIORITY", "TITLE"];
56794
56794
  var SPRINT_ITEM_EDGE_HEADERS = [
@@ -56850,25 +56850,25 @@ function prefixMatch(lower) {
56850
56850
  return (sprint) => sprint.name.toLowerCase().startsWith(lower);
56851
56851
  }
56852
56852
  function renderStatusBlock(pulse) {
56853
- const lines = [
56853
+ const lines2 = [
56854
56854
  `Project: ${pulse.projectKey}`,
56855
56855
  `Ready: ${pulse.readyCount}`,
56856
56856
  `In flight: ${pulse.inFlightCount} (in progress + in review)`
56857
56857
  ];
56858
56858
  if (pulse.activeSprint) {
56859
56859
  const s = pulse.activeSprint;
56860
- lines.push(
56860
+ lines2.push(
56861
56861
  `Sprint: ${s.name} [active, ${s.issueCount} issue${s.issueCount === 1 ? "" : "s"}]`
56862
56862
  );
56863
56863
  const window = formatSprintWindow(s);
56864
- if (window) lines.push(` ${window}`);
56865
- if (s.goal) lines.push(` goal: ${s.goal}`);
56864
+ if (window) lines2.push(` ${window}`);
56865
+ if (s.goal) lines2.push(` goal: ${s.goal}`);
56866
56866
  } else if (pulse.totalSprints === 0) {
56867
- lines.push("Sprint: (no sprints)");
56867
+ lines2.push("Sprint: (no sprints)");
56868
56868
  } else {
56869
- lines.push("Sprint: (none active)");
56869
+ lines2.push("Sprint: (none active)");
56870
56870
  }
56871
- return lines.join("\n");
56871
+ return lines2.join("\n");
56872
56872
  }
56873
56873
  var CHILD_HEADERS = ["KEY", "KIND", "STATUS", "TITLE"];
56874
56874
  var EDGE_HEADERS = ["KEY", "STATUS", "TITLE"];
@@ -56953,18 +56953,18 @@ function renderChildrenSection(children, titleWidth = 60) {
56953
56953
  }
56954
56954
  const waves = assignChildWaves(children);
56955
56955
  const rows = waveChildRows(waves, titleWidth);
56956
- const lines = [
56956
+ const lines2 = [
56957
56957
  renderRelationTable("CHILDREN", rows, "no children", WAVE_CHILD_HEADERS, "build order")
56958
56958
  ];
56959
56959
  const legend = markLegend(rows.map((row) => row[4] ?? ""));
56960
- if (legend) lines.push(legend);
56960
+ if (legend) lines2.push(legend);
56961
56961
  const cycle = cycleMembers(waves);
56962
56962
  if (cycle.length > 0) {
56963
- lines.push(
56963
+ lines2.push(
56964
56964
  `\u26A0 dependency CYCLE \u2014 ${cycle.join(", ")} block each other and have no build order. Fix the blocked_by edges.`
56965
56965
  );
56966
56966
  }
56967
- return lines.join("\n");
56967
+ return lines2.join("\n");
56968
56968
  }
56969
56969
  function renderItemHeader(item) {
56970
56970
  const kindType = item.type ? `${item.kind}/${item.type}` : item.kind;
@@ -57282,7 +57282,7 @@ function writeHostedGitConfig(stateDir2, issued, cli) {
57282
57282
  const identityDir = join4(stateDir2, "identity");
57283
57283
  mkdirSync2(identityDir, { recursive: true, mode: 448 });
57284
57284
  const helper = `!${[...cli, "git-credential", "--state", stateDir2].map(shellQuote).join(" ")}`;
57285
- const lines = [
57285
+ const lines2 = [
57286
57286
  "# Written by `motir` for a hosted run (MOTIR-6559). Git reaches GitHub only",
57287
57287
  "# through the helper below, and commits as the App that writes each repository.",
57288
57288
  "[user]",
@@ -57306,14 +57306,14 @@ function writeHostedGitConfig(stateDir2, issued, cli) {
57306
57306
  { mode: 384 }
57307
57307
  );
57308
57308
  for (const suffix of ["", ".git"]) {
57309
- lines.push(
57309
+ lines2.push(
57310
57310
  `[includeIf "hasconfig:remote.*.url:https://${GITHUB_HOST}/${entry.repository}${suffix}"]`,
57311
57311
  ` path = ${gitValue(identity)}`
57312
57312
  );
57313
57313
  }
57314
57314
  });
57315
57315
  const path = join4(stateDir2, GITCONFIG_FILE);
57316
- writePrivate(path, `${lines.join("\n")}
57316
+ writePrivate(path, `${lines2.join("\n")}
57317
57317
  `);
57318
57318
  return path;
57319
57319
  }
@@ -58637,22 +58637,22 @@ function plural2(n, one, many) {
58637
58637
  }
58638
58638
  function renderDoctorReport(report) {
58639
58639
  const labelWidth = Math.max(...report.checks.map((c) => c.label.length), 0);
58640
- const lines = ["motir doctor \u2014 BYOK preflight", ""];
58640
+ const lines2 = ["motir doctor \u2014 BYOK preflight", ""];
58641
58641
  for (const check of report.checks) {
58642
- lines.push(
58642
+ lines2.push(
58643
58643
  `${check.status.toUpperCase().padEnd(4)} ${check.label.padEnd(labelWidth)} ${check.detail}`
58644
58644
  );
58645
- if (check.remediation) lines.push(`${" ".repeat(6 + labelWidth + 2)}\u21B3 ${check.remediation}`);
58645
+ if (check.remediation) lines2.push(`${" ".repeat(6 + labelWidth + 2)}\u21B3 ${check.remediation}`);
58646
58646
  }
58647
58647
  const counts = countByStatus(report.checks);
58648
58648
  const parts = STATUS_ORDER.filter((s) => counts[s] > 0).map(
58649
58649
  (s) => s === "fail" ? plural2(counts.fail, "failed", "failed") : s === "warn" ? plural2(counts.warn, "warning", "warnings") : plural2(counts.pass, "passed", "passed")
58650
58650
  );
58651
- lines.push(
58651
+ lines2.push(
58652
58652
  "",
58653
58653
  report.ok ? `All hard checks passed \u2014 ${parts.join(", ")}.` : `${parts.join(", ")}.`
58654
58654
  );
58655
- return lines.join("\n");
58655
+ return lines2.join("\n");
58656
58656
  }
58657
58657
 
58658
58658
  // src/commands/doctor.ts
@@ -58907,7 +58907,7 @@ async function runAgent(opts) {
58907
58907
  }
58908
58908
  }
58909
58909
  try {
58910
- const result = await new Promise((resolve6, reject) => {
58910
+ const result = await new Promise((resolve8, reject) => {
58911
58911
  const tee = opts.onOutput;
58912
58912
  const promptOnStdin = opts.command.promptOnStdin !== false;
58913
58913
  const args = [...opts.command.args, ...opts.command.promptArgs?.(prompt, promptFile) ?? []];
@@ -58966,7 +58966,7 @@ async function runAgent(opts) {
58966
58966
  stdin.end(prompt);
58967
58967
  }
58968
58968
  child.on("close", (code, signal) => {
58969
- resolve6({ exitCode: signal ? 1 : code ?? 1, signal });
58969
+ resolve8({ exitCode: signal ? 1 : code ?? 1, signal });
58970
58970
  });
58971
58971
  });
58972
58972
  return { ...result, model: readAgentReport(reportFile) };
@@ -59250,17 +59250,17 @@ function materializeDispatchCheckouts(rootDir, targets, opts = {}) {
59250
59250
  };
59251
59251
  }
59252
59252
  function renderMaterialization(result) {
59253
- const lines = [];
59254
- for (const repo of result.cloned) lines.push(`Cloned: ${repo}`);
59253
+ const lines2 = [];
59254
+ for (const repo of result.cloned) lines2.push(`Cloned: ${repo}`);
59255
59255
  for (const failure2 of result.failures) {
59256
- lines.push(`Blocked: ${failure2.repo} \u2014 ${failure2.detail}`);
59257
- if (failure2.gitMessage) lines.push(` git said: ${failure2.gitMessage}`);
59256
+ lines2.push(`Blocked: ${failure2.repo} \u2014 ${failure2.detail}`);
59257
+ if (failure2.gitMessage) lines2.push(` git said: ${failure2.gitMessage}`);
59258
59258
  }
59259
59259
  if (result.failures.length > 0) {
59260
- lines.push(" No agent was started: the prompt it would be handed opens with");
59261
- lines.push(" `git worktree add`, which cannot run without the checkout.");
59260
+ lines2.push(" No agent was started: the prompt it would be handed opens with");
59261
+ lines2.push(" `git worktree add`, which cannot run without the checkout.");
59262
59262
  }
59263
- return lines;
59263
+ return lines2;
59264
59264
  }
59265
59265
  function checkBootstrapCheckout(target, opts = {}) {
59266
59266
  const exists = opts.exists ?? existsSync7;
@@ -59307,9 +59307,9 @@ function renderDispatchAdvisories(dispatch) {
59307
59307
  const shapes = advisories.filter(isOrderingAdvisory);
59308
59308
  const straddles = advisories.filter(isRepoStraddleAdvisory);
59309
59309
  const subsumed = advisories.filter(isSubsumptionAdvisory);
59310
- const lines = [];
59310
+ const lines2 = [];
59311
59311
  if (references.length > 0) {
59312
- lines.push(
59312
+ lines2.push(
59313
59313
  `Advisory: ${dispatch.key}'s acceptance criteria name work items it has no blocked_by edge to.`,
59314
59314
  ...references.map((a) => ` - ${a.referenced} (${a.referencedStatus})`),
59315
59315
  " This is NOT a blocker \u2014 the dispatch proceeds. Before branching,",
@@ -59318,7 +59318,7 @@ function renderDispatchAdvisories(dispatch) {
59318
59318
  );
59319
59319
  }
59320
59320
  for (const s of shapes) {
59321
- lines.push(
59321
+ lines2.push(
59322
59322
  `Advisory: ${dispatch.key}'s acceptance criterion ${s.criterionIndex} says "${s.phrase}" \u2014`,
59323
59323
  " state that exists only after this work item's own PR has merged.",
59324
59324
  " This is NOT a blocker \u2014 the dispatch proceeds. Your boundary ends at",
@@ -59327,7 +59327,7 @@ function renderDispatchAdvisories(dispatch) {
59327
59327
  );
59328
59328
  }
59329
59329
  for (const s of straddles) {
59330
- lines.push(
59330
+ lines2.push(
59331
59331
  `Advisory: ${dispatch.key}'s acceptance criterion ${s.criterionIndex} names ${s.path},`,
59332
59332
  ` which lives in ${s.repo}${s.reason === "contradiction" ? " \u2014 not this work item's pinned repo." : ", and this item pins no repo while its criteria name more than one."}`,
59333
59333
  " This is NOT a blocker \u2014 the dispatch proceeds. One subtask, one repo,",
@@ -59337,7 +59337,7 @@ function renderDispatchAdvisories(dispatch) {
59337
59337
  );
59338
59338
  }
59339
59339
  for (const s of subsumed) {
59340
- lines.push(
59340
+ lines2.push(
59341
59341
  `Advisory: ${dispatch.key}'s body names ${s.path}, which ${s.pullRequest} already`,
59342
59342
  ` changed (merged ${s.mergedAt}).`,
59343
59343
  " This is NOT a blocker \u2014 the dispatch proceeds. But this work item may already",
@@ -59346,7 +59346,7 @@ function renderDispatchAdvisories(dispatch) {
59346
59346
  " of rebuilding it. If the two merely share a file, proceed."
59347
59347
  );
59348
59348
  }
59349
- return lines.length === 0 ? null : lines.join("\n");
59349
+ return lines2.length === 0 ? null : lines2.join("\n");
59350
59350
  }
59351
59351
  function deliveryLabel(state) {
59352
59352
  switch (state) {
@@ -59362,6 +59362,8 @@ function deliveryLabel(state) {
59362
59362
  return "NOT ESTABLISHED \u2014 this repository does not exist yet, so there is nothing to open a pull request against";
59363
59363
  case "excluded":
59364
59364
  return "excluded \u2014 the project is deliberately code-less here; it does not hold this work item";
59365
+ case "delivered_without_change_request":
59366
+ return "delivered without a pull request \u2014 its cards finished without one (a tag, a release, a setting); it does not hold this work item";
59365
59367
  default:
59366
59368
  return state;
59367
59369
  }
@@ -59372,8 +59374,9 @@ function repoSet(dispatch) {
59372
59374
  }
59373
59375
  function renderResumeNotice(dispatch) {
59374
59376
  const repos = repoSet(dispatch);
59375
- const delivered = repos.filter((r) => r.delivery === "delivered").map((r) => r.name);
59376
- const remaining = repos.filter((r) => r.delivery !== "delivered").map((r) => r.name);
59377
+ const done = (d) => d === "delivered" || d === "delivered_without_change_request";
59378
+ const delivered = repos.filter((r) => done(r.delivery)).map((r) => r.name);
59379
+ const remaining = repos.filter((r) => !done(r.delivery)).map((r) => r.name);
59377
59380
  if (delivered.length === 0 || remaining.length === 0) return null;
59378
59381
  return [
59379
59382
  `Resume: ${dispatch.key} is PARTIALLY DELIVERED \u2014 this is not a fresh work item.`,
@@ -59384,25 +59387,25 @@ function renderResumeNotice(dispatch) {
59384
59387
  }
59385
59388
  function renderRepositoriesBlock(targets, delivery) {
59386
59389
  if (targets.length <= 1) return [];
59387
- const lines = [`Repos: ${targets.length} \u2014 this item ships in every one of them:`];
59390
+ const lines2 = [`Repos: ${targets.length} \u2014 this item ships in every one of them:`];
59388
59391
  targets.forEach((t, i) => {
59389
59392
  const where = i === 0 ? "the working directory" : "a sibling checkout";
59390
- lines.push(` - ${t.targetRepo ?? "(unpinned)"} (${where})`);
59391
- lines.push(` ${t.repoPath ?? t.cwd} (${t.repoSource ?? "root"})`);
59393
+ lines2.push(` - ${t.targetRepo ?? "(unpinned)"} (${where})`);
59394
+ lines2.push(` ${t.repoPath ?? t.cwd} (${t.repoSource ?? "root"})`);
59392
59395
  const label2 = deliveryLabel(delivery?.[i] ?? null);
59393
- if (label2) lines.push(` ${label2}`);
59396
+ if (label2) lines2.push(` ${label2}`);
59394
59397
  if (t.reason === "bootstrap_root") {
59395
- lines.push(
59398
+ lines2.push(
59396
59399
  ` \u26A0 no checkout here yet. This is NOT a blocker \u2014 the dispatch`,
59397
59400
  ` proceeds. If it lives elsewhere: motir link add ${t.targetRepo ?? ""} <path>`
59398
59401
  );
59399
59402
  }
59400
59403
  });
59401
- return lines;
59404
+ return lines2;
59402
59405
  }
59403
59406
  function renderDispatchSummary(input) {
59404
59407
  const { key, title, dispatch, target, agent } = input;
59405
- const lines = [
59408
+ const lines2 = [
59406
59409
  `Dispatch: ${key}${title ? ` \u2014 ${title}` : ""}`,
59407
59410
  `Repo: ${dispatch.targetRepo ?? "not pinned (Motir cannot say)"}`,
59408
59411
  `Path: ${target.cwd}`,
@@ -59416,10 +59419,10 @@ function renderDispatchSummary(input) {
59416
59419
  ),
59417
59420
  `Workflow: ${workflowLabel(dispatch.workflowMode, dispatch.sessionBranch)}`
59418
59421
  ];
59419
- lines.push(
59422
+ lines2.push(
59420
59423
  agent ? `Agent: ${agent.command} (${agentSourceLabel(agent.source)})` : "Agent: none \u2014 printing the prompt (copy it into your agent)"
59421
59424
  );
59422
- return lines.join("\n");
59425
+ return lines2.join("\n");
59423
59426
  }
59424
59427
  function renderAgentSuccess(key, dispatch) {
59425
59428
  const repos = repoSet(dispatch);
@@ -59443,29 +59446,29 @@ function renderAgentSuccess(key, dispatch) {
59443
59446
  ].join("\n");
59444
59447
  }
59445
59448
  if (repos.length > 0) {
59446
- const lines = [
59449
+ const lines2 = [
59447
59450
  `${key}: agent finished \u2014 a pull request is expected in EACH of its ${repos.length} repositories:`
59448
59451
  ];
59449
59452
  for (const repo of repos) {
59450
59453
  const label2 = deliveryLabel(repo.delivery);
59451
- lines.push(` - ${repo.name}${label2 ? ` \u2014 ${label2}` : ""}`);
59454
+ lines2.push(` - ${repo.name}${label2 ? ` \u2014 ${label2}` : ""}`);
59452
59455
  }
59453
59456
  const blocked = repos.filter((r) => r.delivery === "unestablished");
59454
59457
  if (blocked.length > 0) {
59455
- lines.push(
59458
+ lines2.push(
59456
59459
  "",
59457
59460
  `\u26A0 ${blocked.map((r) => r.name).join(", ")} cannot be delivered yet: the repository does`,
59458
59461
  " not exist. Establish it on the project before this work item can complete."
59459
59462
  );
59460
59463
  }
59461
- lines.push(
59464
+ lines2.push(
59462
59465
  "",
59463
59466
  "CI decides when it becomes reviewable: the item moves to In Review on its own",
59464
59467
  "when the checks go green.",
59465
59468
  `Next: review + merge every one of them. ${key} completes only when EVERY`,
59466
59469
  "repository's pull request has merged \u2014 a single merge leaves it held."
59467
59470
  );
59468
- return lines.join("\n");
59471
+ return lines2.join("\n");
59469
59472
  }
59470
59473
  return [
59471
59474
  `${key}: agent finished \u2014 its pull request is open (now Implemented).`,
@@ -59485,18 +59488,18 @@ function renderNothingPushed(key, dispatch) {
59485
59488
  ].join("\n");
59486
59489
  }
59487
59490
  function renderAgentFailure(key, exitCode, dispatch) {
59488
- const lines = [
59491
+ const lines2 = [
59489
59492
  `${key}: agent exited ${exitCode}. The item stays In Progress (nothing was reverted).`,
59490
59493
  `It is excluded from the next \`motir next\` in this session; re-run it with \`motir run ${key}\`.`
59491
59494
  ];
59492
59495
  const repos = dispatch ? repoSet(dispatch) : [];
59493
59496
  if (repos.length > 0) {
59494
- lines.push(
59497
+ lines2.push(
59495
59498
  `${key} ships in ${repos.length} repositories (${repos.map((r) => r.name).join(", ")}),`,
59496
59499
  "so partial work may be sitting in more than one checkout."
59497
59500
  );
59498
59501
  }
59499
- return lines.join("\n");
59502
+ return lines2.join("\n");
59500
59503
  }
59501
59504
  function renderClaimRefusal(claim) {
59502
59505
  const where = `It is at ${claim.status.key}.`;
@@ -60033,273 +60036,273 @@ async function watchAndFixCi(input) {
60033
60036
  function renderFixPrompt(input) {
60034
60037
  const bases = [...new Set(input.failing.map((d) => d.baseRef ?? d.defaultBranch))];
60035
60038
  const mergeTarget = bases.length === 1 ? `origin/${bases[0]}` : "origin/<that pull request's base>";
60036
- const lines = [];
60037
- lines.push(`# Make the build pass \u2014 ${input.key}${input.title ? ` (${input.title})` : ""}`);
60038
- lines.push("");
60039
- lines.push(
60039
+ const lines2 = [];
60040
+ lines2.push(`# Make the build pass \u2014 ${input.key}${input.title ? ` (${input.title})` : ""}`);
60041
+ lines2.push("");
60042
+ lines2.push(
60040
60043
  `CI is RED on work that is already committed and pushed. This is attempt ${input.attempt} of ${CI_FIX_ATTEMPTS}.`
60041
60044
  );
60042
- lines.push("");
60043
- lines.push("## The failing pull requests");
60044
- lines.push("");
60045
+ lines2.push("");
60046
+ lines2.push("## The failing pull requests");
60047
+ lines2.push("");
60045
60048
  for (const d of input.failing) {
60046
- lines.push(`- **${d.repo}#${d.number}** (base \`${d.baseRef ?? d.defaultBranch}\`) \u2014 ${d.url}`);
60049
+ lines2.push(`- **${d.repo}#${d.number}** (base \`${d.baseRef ?? d.defaultBranch}\`) \u2014 ${d.url}`);
60047
60050
  }
60048
- lines.push("");
60051
+ lines2.push("");
60049
60052
  if (input.checkouts && input.checkouts.length > 0) {
60050
- lines.push("## Where each branch is checked out");
60051
- lines.push("");
60053
+ lines2.push("## Where each branch is checked out");
60054
+ lines2.push("");
60052
60055
  for (const c of input.checkouts) {
60053
- lines.push(`- **${c.repo}** \u2014 branch \`${c.branch}\` at \`${c.path}\``);
60056
+ lines2.push(`- **${c.repo}** \u2014 branch \`${c.branch}\` at \`${c.path}\``);
60054
60057
  }
60055
- lines.push("");
60056
- lines.push("Work in those checkouts, on those branches. Each push updates its");
60057
- lines.push("existing pull request.");
60058
- lines.push("");
60058
+ lines2.push("");
60059
+ lines2.push("Work in those checkouts, on those branches. Each push updates its");
60060
+ lines2.push("existing pull request.");
60061
+ lines2.push("");
60059
60062
  }
60060
60063
  const ejected = input.failing.filter((d) => d.queueExit);
60061
60064
  if (ejected.length > 0) {
60062
- lines.push("## Why it left the merge queue");
60063
- lines.push("");
60064
- lines.push("The merge queue tested these pull requests together with the work queued");
60065
- lines.push("ahead of them and threw them out. Their OWN checks may well be green \u2014 the");
60066
- lines.push("failure happened in the queue\u2019s merge commit, not on this branch alone.");
60067
- lines.push("");
60065
+ lines2.push("## Why it left the merge queue");
60066
+ lines2.push("");
60067
+ lines2.push("The merge queue tested these pull requests together with the work queued");
60068
+ lines2.push("ahead of them and threw them out. Their OWN checks may well be green \u2014 the");
60069
+ lines2.push("failure happened in the queue\u2019s merge commit, not on this branch alone.");
60070
+ lines2.push("");
60068
60071
  for (const d of ejected) {
60069
60072
  const exit = d.queueExit;
60070
60073
  const base = `origin/${d.baseRef ?? d.defaultBranch}`;
60071
- lines.push(`- **${d.repo}#${d.number}** \u2014 ${queueReasonInWords(exit.rawReason)}`);
60074
+ lines2.push(`- **${d.repo}#${d.number}** \u2014 ${queueReasonInWords(exit.rawReason)}`);
60072
60075
  if (exit.failingCheckName) {
60073
- lines.push(
60076
+ lines2.push(
60074
60077
  ` - The queue\u2019s failing check: **${exit.failingCheckName}**` + (exit.failingCheckUrl ? ` \u2014 ${exit.failingCheckUrl}` : "")
60075
60078
  );
60076
60079
  }
60077
60080
  if (exit.rawReason === "MERGE_CONFLICT") {
60078
- lines.push(` - **Merge \`${base}\` into the branch, resolve the conflicts, run the`);
60079
- lines.push(" checks the conflict touched, and push.** Only new commits clear a");
60080
- lines.push(" conflict; *Queue again* will fail the same way until it is resolved.");
60081
+ lines2.push(` - **Merge \`${base}\` into the branch, resolve the conflicts, run the`);
60082
+ lines2.push(" checks the conflict touched, and push.** Only new commits clear a");
60083
+ lines2.push(" conflict; *Queue again* will fail the same way until it is resolved.");
60081
60084
  } else if (exit.rawReason === "CI_FAILURE" || exit.rawReason === "CI_TIMEOUT") {
60082
- lines.push(` - **Reproduce the named check against the branch merged with \`${base}\`,**`);
60083
- lines.push(" fix what it shows, and push.");
60085
+ lines2.push(` - **Reproduce the named check against the branch merged with \`${base}\`,**`);
60086
+ lines2.push(" fix what it shows, and push.");
60084
60087
  }
60085
- lines.push(" - **If nothing needs changing** (the queue\u2019s failure was flaky, or is");
60086
- lines.push(" already fixed on the base), STILL PUSH: the merge of the base from step 1,");
60087
- lines.push(" or an empty commit if that merge brought nothing. *Queue again* is not");
60088
- lines.push(" offered after a queue failure, so only a new head whose checks go green");
60089
- lines.push(" asks for approval again. Say in your summary that nothing needed changing.");
60088
+ lines2.push(" - **If nothing needs changing** (the queue\u2019s failure was flaky, or is");
60089
+ lines2.push(" already fixed on the base), STILL PUSH: the merge of the base from step 1,");
60090
+ lines2.push(" or an empty commit if that merge brought nothing. *Queue again* is not");
60091
+ lines2.push(" offered after a queue failure, so only a new head whose checks go green");
60092
+ lines2.push(" asks for approval again. Say in your summary that nothing needed changing.");
60090
60093
  }
60091
- lines.push("");
60092
- }
60093
- lines.push("## What to do");
60094
- lines.push("");
60095
- lines.push("1. **MERGE THE LATEST BASE BRANCH FIRST \u2014 before you read anything as a");
60096
- lines.push(` diagnosis.** \`git fetch origin && git merge ${mergeTarget}\` in the`);
60097
- lines.push(" checkout holding the branch. A MERGE, never a rebase.");
60098
- lines.push(" **CI judges your branch MERGED with the branch it targets**, so the");
60099
- lines.push(" failure you are about to read happened in a tree this checkout does");
60100
- lines.push(" not have: the file the log names may be one that landed on the base");
60101
- lines.push(" after this branch started, and the failure may be inherited rather");
60102
- lines.push(" than yours. Build the combination CI judged, then diagnose against");
60103
- lines.push(" it.");
60104
- lines.push(" - **Push the merge like any other iteration.** It re-triggers CI, so");
60105
- lines.push(" if the red was inherited and is already repaired on the base, the");
60106
- lines.push(" merge alone turns the build green and nothing else is owed \u2014");
60107
- lines.push(" change nothing and say so.");
60108
- lines.push(" - **A merge CONFLICT is your own work, and no check can report it.**");
60109
- lines.push(" Resolve it here, in this iteration.");
60110
- lines.push("2. **Read the ACTUAL failure first** \u2014 on the MERGED tree: the assertion,");
60111
- lines.push(" the locator, the stack, the job log. Not the summary line, and not a");
60112
- lines.push(" guess from the test name.");
60113
- lines.push("3. **Decide what kind of failure it is.**");
60114
- lines.push(" - **Caused by this branch** \u2192 fix it. Commit to the SAME branch and");
60115
- lines.push(" push; the existing pull request picks it up. Open no new pull");
60116
- lines.push(" request and link nothing \u2014 this is repair work on a pull request");
60117
- lines.push(" that already exists.");
60118
- lines.push(" **THE PUSH IS THE VERIFICATION.** This loop re-triggers CI on");
60119
- lines.push(" every push, so the next verdict IS your confirmation \u2014 you do");
60120
- lines.push(" not have to produce one. You may run AT MOST the single file");
60121
- lines.push(" the log named, and only because it is seconds. NEVER the suite,");
60122
- lines.push(" never a coverage run, and never a wider sweep to check that");
60123
- lines.push(" nothing else broke: that is the same measurement taken twice,");
60124
- lines.push(" the second time slower, on a shared machine, and NOT merged with");
60125
- lines.push(" the base as CI will merge it at verdict time \u2014 so it is the");
60126
- lines.push(" less trustworthy copy.");
60127
- lines.push(" - **INHERITED** \u2014 the base branch is red on its own, or the log names");
60128
- lines.push(" a file your diff never touched. Say so and change NOTHING here:");
60129
- lines.push(" step 1 was the whole fix if there was one, and repairing somebody");
60130
- lines.push(" else's red belongs in its own pull request, never folded into this");
60131
- lines.push(" one.");
60132
- lines.push(" - **ENVIRONMENTAL** \u2014 a connection reset, a runner or webServer death,");
60133
- lines.push(" a first-hit cold-compile timeout, a different test failing each run \u2014");
60134
- lines.push(" then **say so and change NOTHING**. Pushing a speculative patch to");
60135
- lines.push(" chase a flake makes the diff worse and hides the real signal.");
60136
- lines.push("4. **Keep How to test true.** How to test belongs to the work item, not");
60137
- lines.push(" to a commit. If a commit you push changes anything it describes \u2014 a");
60138
- lines.push(" precondition, a setup, migrate or seed command, a click-path step \u2014");
60139
- lines.push(` publish the whole corrected text with the ${HOW_TO_TEST_TOOL_NAME}`);
60140
- lines.push(` tool, on ${input.key} or on the item the pull request body's`);
60141
- lines.push(' "## How to test" section says it was written onto, and update that');
60142
- lines.push(" section of the body to match. The body shows you the current text. A");
60143
- lines.push(" fix that changes none of them \u2014 the usual case \u2014 publishes nothing.");
60144
- lines.push("5. Do not move the work item: leave its status, and record no");
60145
- lines.push(" transition. The build decides when it moves.");
60146
- lines.push("");
60147
- lines.push("Changing nothing is a legitimate outcome and is sometimes the right one.");
60148
- return lines.join("\n");
60094
+ lines2.push("");
60095
+ }
60096
+ lines2.push("## What to do");
60097
+ lines2.push("");
60098
+ lines2.push("1. **MERGE THE LATEST BASE BRANCH FIRST \u2014 before you read anything as a");
60099
+ lines2.push(` diagnosis.** \`git fetch origin && git merge ${mergeTarget}\` in the`);
60100
+ lines2.push(" checkout holding the branch. A MERGE, never a rebase.");
60101
+ lines2.push(" **CI judges your branch MERGED with the branch it targets**, so the");
60102
+ lines2.push(" failure you are about to read happened in a tree this checkout does");
60103
+ lines2.push(" not have: the file the log names may be one that landed on the base");
60104
+ lines2.push(" after this branch started, and the failure may be inherited rather");
60105
+ lines2.push(" than yours. Build the combination CI judged, then diagnose against");
60106
+ lines2.push(" it.");
60107
+ lines2.push(" - **Push the merge like any other iteration.** It re-triggers CI, so");
60108
+ lines2.push(" if the red was inherited and is already repaired on the base, the");
60109
+ lines2.push(" merge alone turns the build green and nothing else is owed \u2014");
60110
+ lines2.push(" change nothing and say so.");
60111
+ lines2.push(" - **A merge CONFLICT is your own work, and no check can report it.**");
60112
+ lines2.push(" Resolve it here, in this iteration.");
60113
+ lines2.push("2. **Read the ACTUAL failure first** \u2014 on the MERGED tree: the assertion,");
60114
+ lines2.push(" the locator, the stack, the job log. Not the summary line, and not a");
60115
+ lines2.push(" guess from the test name.");
60116
+ lines2.push("3. **Decide what kind of failure it is.**");
60117
+ lines2.push(" - **Caused by this branch** \u2192 fix it. Commit to the SAME branch and");
60118
+ lines2.push(" push; the existing pull request picks it up. Open no new pull");
60119
+ lines2.push(" request and link nothing \u2014 this is repair work on a pull request");
60120
+ lines2.push(" that already exists.");
60121
+ lines2.push(" **THE PUSH IS THE VERIFICATION.** This loop re-triggers CI on");
60122
+ lines2.push(" every push, so the next verdict IS your confirmation \u2014 you do");
60123
+ lines2.push(" not have to produce one. You may run AT MOST the single file");
60124
+ lines2.push(" the log named, and only because it is seconds. NEVER the suite,");
60125
+ lines2.push(" never a coverage run, and never a wider sweep to check that");
60126
+ lines2.push(" nothing else broke: that is the same measurement taken twice,");
60127
+ lines2.push(" the second time slower, on a shared machine, and NOT merged with");
60128
+ lines2.push(" the base as CI will merge it at verdict time \u2014 so it is the");
60129
+ lines2.push(" less trustworthy copy.");
60130
+ lines2.push(" - **INHERITED** \u2014 the base branch is red on its own, or the log names");
60131
+ lines2.push(" a file your diff never touched. Say so and change NOTHING here:");
60132
+ lines2.push(" step 1 was the whole fix if there was one, and repairing somebody");
60133
+ lines2.push(" else's red belongs in its own pull request, never folded into this");
60134
+ lines2.push(" one.");
60135
+ lines2.push(" - **ENVIRONMENTAL** \u2014 a connection reset, a runner or webServer death,");
60136
+ lines2.push(" a first-hit cold-compile timeout, a different test failing each run \u2014");
60137
+ lines2.push(" then **say so and change NOTHING**. Pushing a speculative patch to");
60138
+ lines2.push(" chase a flake makes the diff worse and hides the real signal.");
60139
+ lines2.push("4. **Keep How to test true.** How to test belongs to the work item, not");
60140
+ lines2.push(" to a commit. If a commit you push changes anything it describes \u2014 a");
60141
+ lines2.push(" precondition, a setup, migrate or seed command, a click-path step \u2014");
60142
+ lines2.push(` publish the whole corrected text with the ${HOW_TO_TEST_TOOL_NAME}`);
60143
+ lines2.push(` tool, on ${input.key} or on the item the pull request body's`);
60144
+ lines2.push(' "## How to test" section says it was written onto, and update that');
60145
+ lines2.push(" section of the body to match. The body shows you the current text. A");
60146
+ lines2.push(" fix that changes none of them \u2014 the usual case \u2014 publishes nothing.");
60147
+ lines2.push("5. Do not move the work item: leave its status, and record no");
60148
+ lines2.push(" transition. The build decides when it moves.");
60149
+ lines2.push("");
60150
+ lines2.push("Changing nothing is a legitimate outcome and is sometimes the right one.");
60151
+ return lines2.join("\n");
60149
60152
  }
60150
60153
  function renderRefusalQuote(refusal) {
60151
60154
  const who = refusal.decidedByLabel ?? "The reviewer";
60152
60155
  const reason = refusal.reasonMd?.trim();
60153
- const lines = [`${who} sent it back on ${refusal.decidedAt}, choosing **Re-run**:`, ""];
60156
+ const lines2 = [`${who} sent it back on ${refusal.decidedAt}, choosing **Re-run**:`, ""];
60154
60157
  if (reason) {
60155
- for (const line of reason.split("\n")) lines.push(`> ${line}`);
60158
+ for (const line of reason.split("\n")) lines2.push(`> ${line}`);
60156
60159
  } else {
60157
- lines.push("> (no reason was recorded)");
60160
+ lines2.push("> (no reason was recorded)");
60158
60161
  }
60159
- return lines;
60162
+ return lines2;
60160
60163
  }
60161
60164
  function renderCheckoutList(checkouts) {
60162
- const lines = ["## Where each branch is checked out", ""];
60165
+ const lines2 = ["## Where each branch is checked out", ""];
60163
60166
  for (const c of checkouts)
60164
- lines.push(`- **${c.repo}** \u2014 branch \`${c.branch}\` at \`${c.path}\``);
60165
- lines.push("");
60166
- lines.push("Work in those checkouts, on those branches. Each push updates its");
60167
- lines.push("existing pull request.");
60168
- return lines;
60167
+ lines2.push(`- **${c.repo}** \u2014 branch \`${c.branch}\` at \`${c.path}\``);
60168
+ lines2.push("");
60169
+ lines2.push("Work in those checkouts, on those branches. Each push updates its");
60170
+ lines2.push("existing pull request.");
60171
+ return lines2;
60169
60172
  }
60170
60173
  function renderAcceptanceRerunPrompt(input) {
60171
- const lines = [];
60172
- lines.push(
60174
+ const lines2 = [];
60175
+ lines2.push(
60173
60176
  `# Answer the acceptance review \u2014 ${input.key}${input.title ? ` (${input.title})` : ""}`
60174
60177
  );
60175
- lines.push("");
60176
- lines.push("This story was run as a whole and its pull requests are open. A person watched");
60177
- lines.push("its acceptance video and asked for a FIX to what was built.");
60178
- lines.push("");
60179
- lines.push("## What they said");
60180
- lines.push("");
60181
- lines.push(...renderRefusalQuote(input.refusal));
60182
- lines.push("");
60183
- lines.push("## The pull requests");
60184
- lines.push("");
60185
- for (const pr of input.pullRequests) lines.push(`- **${pr.repo}#${pr.number}** \u2014 ${pr.url}`);
60186
- lines.push("");
60187
- lines.push(...renderCheckoutList(input.checkouts));
60188
- lines.push("");
60189
- lines.push("## The scope");
60190
- lines.push("");
60191
- lines.push("A fix to the delivered work on these branches: what the reviewer saw and named.");
60192
- lines.push("**Anything structural is a Re-plan, not a Re-run** \u2014 a new screen, a different");
60193
- lines.push("flow, work another card owns, a change to what the story is for. If the reason");
60194
- lines.push("asks for that, make NO commit, stop, and say so in one paragraph: the reviewer");
60195
- lines.push("re-plans the story in Motir instead.");
60196
- lines.push("");
60197
- lines.push("## What to do");
60198
- lines.push("");
60199
- lines.push("1. **Merge each pull request\u2019s base branch first** (a merge, never a rebase),");
60200
- lines.push(" so you change the tree CI will judge.");
60201
- lines.push("2. **Find where the reviewer\u2019s complaint lives** \u2014 render or run the surface");
60202
- lines.push(" they watched rather than reasoning from the code, and change the smallest thing");
60203
- lines.push(" that answers it.");
60204
- lines.push("3. **Keep the tests true.** Update or add the tests that cover what you changed.");
60205
- lines.push(" Run ONLY the test files you touched, plus lint and typecheck \u2014 never the full");
60206
- lines.push(" suite: the push re-runs CI, and that is the verdict.");
60207
- lines.push("4. **Commit and push to the same branches.** Open no pull request, link nothing,");
60208
- lines.push(" and move no status \u2014 the board stays where it is.");
60209
- lines.push("5. **Do not record or publish the acceptance video.** Once CI is green on this");
60210
- lines.push(" push, you will be asked to re-record it in a separate step.");
60211
- lines.push("6. If the pull request body\u2019s `## How to test` no longer matches what a person");
60212
- lines.push(` does, publish it again with \`${HOW_TO_TEST_TOOL_NAME}\` on ${input.key}.`);
60213
- return lines.join("\n");
60178
+ lines2.push("");
60179
+ lines2.push("This story was run as a whole and its pull requests are open. A person watched");
60180
+ lines2.push("its acceptance video and asked for a FIX to what was built.");
60181
+ lines2.push("");
60182
+ lines2.push("## What they said");
60183
+ lines2.push("");
60184
+ lines2.push(...renderRefusalQuote(input.refusal));
60185
+ lines2.push("");
60186
+ lines2.push("## The pull requests");
60187
+ lines2.push("");
60188
+ for (const pr of input.pullRequests) lines2.push(`- **${pr.repo}#${pr.number}** \u2014 ${pr.url}`);
60189
+ lines2.push("");
60190
+ lines2.push(...renderCheckoutList(input.checkouts));
60191
+ lines2.push("");
60192
+ lines2.push("## The scope");
60193
+ lines2.push("");
60194
+ lines2.push("A fix to the delivered work on these branches: what the reviewer saw and named.");
60195
+ lines2.push("**Anything structural is a Re-plan, not a Re-run** \u2014 a new screen, a different");
60196
+ lines2.push("flow, work another card owns, a change to what the story is for. If the reason");
60197
+ lines2.push("asks for that, make NO commit, stop, and say so in one paragraph: the reviewer");
60198
+ lines2.push("re-plans the story in Motir instead.");
60199
+ lines2.push("");
60200
+ lines2.push("## What to do");
60201
+ lines2.push("");
60202
+ lines2.push("1. **Merge each pull request\u2019s base branch first** (a merge, never a rebase),");
60203
+ lines2.push(" so you change the tree CI will judge.");
60204
+ lines2.push("2. **Find where the reviewer\u2019s complaint lives** \u2014 render or run the surface");
60205
+ lines2.push(" they watched rather than reasoning from the code, and change the smallest thing");
60206
+ lines2.push(" that answers it.");
60207
+ lines2.push("3. **Keep the tests true.** Update or add the tests that cover what you changed.");
60208
+ lines2.push(" Run ONLY the test files you touched, plus lint and typecheck \u2014 never the full");
60209
+ lines2.push(" suite: the push re-runs CI, and that is the verdict.");
60210
+ lines2.push("4. **Commit and push to the same branches.** Open no pull request, link nothing,");
60211
+ lines2.push(" and move no status \u2014 the board stays where it is.");
60212
+ lines2.push("5. **Do not record or publish the acceptance video.** Once CI is green on this");
60213
+ lines2.push(" push, you will be asked to re-record it in a separate step.");
60214
+ lines2.push("6. If the pull request body\u2019s `## How to test` no longer matches what a person");
60215
+ lines2.push(` does, publish it again with \`${HOW_TO_TEST_TOOL_NAME}\` on ${input.key}.`);
60216
+ return lines2.join("\n");
60214
60217
  }
60215
60218
  function renderAcceptanceRecordPrompt(input) {
60216
- const lines = [];
60217
- lines.push(
60219
+ const lines2 = [];
60220
+ lines2.push(
60218
60221
  `# Re-record the acceptance video \u2014 ${input.key}${input.title ? ` (${input.title})` : ""}`
60219
60222
  );
60220
- lines.push("");
60221
- lines.push("CI is green on the fix you pushed for this review:");
60222
- lines.push("");
60223
- lines.push(...renderRefusalQuote(input.refusal));
60224
- lines.push("");
60225
- lines.push(...renderCheckoutList(input.checkouts));
60226
- lines.push("");
60227
- lines.push("## What to do");
60228
- lines.push("");
60229
- lines.push("1. **Run the story's acceptance spec** (`tests/e2e/acceptance*.spec.ts`, the one");
60230
- lines.push(` that declares \`acceptanceStory('${input.key}')\`) on the acceptance lane, with`);
60231
- lines.push(" recording on, so the clip shows the fixed work.");
60232
- lines.push("2. **Publish it \u2014 two calls.** `create_acceptance_upload` with the card key mints a");
60233
- lines.push(
60223
+ lines2.push("");
60224
+ lines2.push("CI is green on the fix you pushed for this review:");
60225
+ lines2.push("");
60226
+ lines2.push(...renderRefusalQuote(input.refusal));
60227
+ lines2.push("");
60228
+ lines2.push(...renderCheckoutList(input.checkouts));
60229
+ lines2.push("");
60230
+ lines2.push("## What to do");
60231
+ lines2.push("");
60232
+ lines2.push("1. **Run the story's acceptance spec** (`tests/e2e/acceptance*.spec.ts`, the one");
60233
+ lines2.push(` that declares \`acceptanceStory('${input.key}')\`) on the acceptance lane, with`);
60234
+ lines2.push(" recording on, so the clip shows the fixed work.");
60235
+ lines2.push("2. **Publish it \u2014 two calls.** `create_acceptance_upload` with the card key mints a");
60236
+ lines2.push(
60234
60237
  " presigned PUT; PUT the clip\u2019s bytes to it (`Content-Type: video/webm`); then"
60235
60238
  );
60236
- lines.push(" `publish_acceptance_result` with the pathname it returned, the chapters, the");
60237
- lines.push(" commit sha you pushed, and the card key.");
60238
- lines.push(
60239
+ lines2.push(" `publish_acceptance_result` with the pathname it returned, the chapters, the");
60240
+ lines2.push(" commit sha you pushed, and the card key.");
60241
+ lines2.push(
60239
60242
  "3. **Report the receipt id the publish returned.** Without it nothing was published,"
60240
60243
  );
60241
- lines.push(" and the reviewer has nothing new to watch.");
60242
- lines.push("");
60243
- lines.push("Change no code in this step and move no status. If the spec goes red, publish");
60244
- lines.push("nothing and say what failed: a red run records nothing.");
60245
- return lines.join("\n");
60244
+ lines2.push(" and the reviewer has nothing new to watch.");
60245
+ lines2.push("");
60246
+ lines2.push("Change no code in this step and move no status. If the spec goes red, publish");
60247
+ lines2.push("nothing and say what failed: a red run records nothing.");
60248
+ return lines2.join("\n");
60246
60249
  }
60247
60250
  function renderReviewFixPrompt(input) {
60248
60251
  const { refusal } = input;
60249
60252
  const byAgent = refusal.gate === "agent_review";
60250
60253
  const who = refusal.reviewerName ?? (byAgent ? "The review agent" : "The reviewer");
60251
- const lines = [];
60252
- lines.push(`# Answer the code review \u2014 ${input.key}${input.title ? ` (${input.title})` : ""}`);
60253
- lines.push("");
60254
- lines.push("This card's pull requests are open and its run has ended. A review of them");
60255
- lines.push(
60254
+ const lines2 = [];
60255
+ lines2.push(`# Answer the code review \u2014 ${input.key}${input.title ? ` (${input.title})` : ""}`);
60256
+ lines2.push("");
60257
+ lines2.push("This card's pull requests are open and its run has ended. A review of them");
60258
+ lines2.push(
60256
60259
  byAgent ? "by the REVIEW AGENT, against the card, sent the card back with findings." : "by a PERSON, on the approve-and-merge gate, sent the card back \u2014 Request changes."
60257
60260
  );
60258
- lines.push("");
60259
- lines.push("## The findings");
60260
- lines.push("");
60261
- lines.push(`${who} sent it back on ${refusal.decidedAt}:`);
60262
- lines.push("");
60261
+ lines2.push("");
60262
+ lines2.push("## The findings");
60263
+ lines2.push("");
60264
+ lines2.push(`${who} sent it back on ${refusal.decidedAt}:`);
60265
+ lines2.push("");
60263
60266
  const findings = refusal.findingsMd?.trim();
60264
60267
  if (findings) {
60265
- for (const line of findings.split("\n")) lines.push(`> ${line}`);
60268
+ for (const line of findings.split("\n")) lines2.push(`> ${line}`);
60266
60269
  } else {
60267
- lines.push("> (no findings were recorded)");
60268
- }
60269
- lines.push("");
60270
- lines.push("## The pull requests");
60271
- lines.push("");
60272
- for (const pr of input.pullRequests) lines.push(`- **${pr.repo}#${pr.number}** \u2014 ${pr.url}`);
60273
- lines.push("");
60274
- lines.push(...renderCheckoutList(input.checkouts));
60275
- lines.push("");
60276
- lines.push("## The scope");
60277
- lines.push("");
60278
- lines.push("A fix to the delivered work on these branches: what the review named, measured");
60279
- lines.push("against the card. **A finding that asks for a change to what the card is FOR** \u2014");
60280
- lines.push("work another card owns, a new surface, a different design \u2014 is not yours to");
60281
- lines.push("build: leave it, and say so in one paragraph naming the finding.");
60282
- lines.push("");
60283
- lines.push("## What to do");
60284
- lines.push("");
60285
- lines.push("1. **Merge each pull request\u2019s base branch first** (a merge, never a rebase),");
60286
- lines.push(" so you change the tree CI will judge.");
60287
- lines.push("2. **Address EVERY finding**, one by one \u2014 read the code it names, confirm it, and");
60288
- lines.push(" change the smallest thing that answers it. A finding you judge wrong is answered");
60289
- lines.push(" in words, with the evidence, never by silently skipping it.");
60290
- lines.push("3. **Keep the tests true.** Update or add the tests that cover what you changed.");
60291
- lines.push(" Run ONLY the test files you touched, plus lint and typecheck \u2014 never the full");
60292
- lines.push(" suite: the push re-runs CI, and that is the verdict.");
60293
- lines.push("4. **Commit and push to the same branches.** Open no pull request, link nothing,");
60294
- lines.push(" decide no approval and move no status \u2014 the push withdraws the review, and the");
60295
- lines.push(" next green version is reviewed again.");
60296
- lines.push("5. If the pull request body\u2019s `## How to test` no longer matches what a person");
60297
- lines.push(` does, publish it again with \`${HOW_TO_TEST_TOOL_NAME}\` on ${input.key}.`);
60298
- return lines.join("\n");
60270
+ lines2.push("> (no findings were recorded)");
60271
+ }
60272
+ lines2.push("");
60273
+ lines2.push("## The pull requests");
60274
+ lines2.push("");
60275
+ for (const pr of input.pullRequests) lines2.push(`- **${pr.repo}#${pr.number}** \u2014 ${pr.url}`);
60276
+ lines2.push("");
60277
+ lines2.push(...renderCheckoutList(input.checkouts));
60278
+ lines2.push("");
60279
+ lines2.push("## The scope");
60280
+ lines2.push("");
60281
+ lines2.push("A fix to the delivered work on these branches: what the review named, measured");
60282
+ lines2.push("against the card. **A finding that asks for a change to what the card is FOR** \u2014");
60283
+ lines2.push("work another card owns, a new surface, a different design \u2014 is not yours to");
60284
+ lines2.push("build: leave it, and say so in one paragraph naming the finding.");
60285
+ lines2.push("");
60286
+ lines2.push("## What to do");
60287
+ lines2.push("");
60288
+ lines2.push("1. **Merge each pull request\u2019s base branch first** (a merge, never a rebase),");
60289
+ lines2.push(" so you change the tree CI will judge.");
60290
+ lines2.push("2. **Address EVERY finding**, one by one \u2014 read the code it names, confirm it, and");
60291
+ lines2.push(" change the smallest thing that answers it. A finding you judge wrong is answered");
60292
+ lines2.push(" in words, with the evidence, never by silently skipping it.");
60293
+ lines2.push("3. **Keep the tests true.** Update or add the tests that cover what you changed.");
60294
+ lines2.push(" Run ONLY the test files you touched, plus lint and typecheck \u2014 never the full");
60295
+ lines2.push(" suite: the push re-runs CI, and that is the verdict.");
60296
+ lines2.push("4. **Commit and push to the same branches.** Open no pull request, link nothing,");
60297
+ lines2.push(" decide no approval and move no status \u2014 the push withdraws the review, and the");
60298
+ lines2.push(" next green version is reviewed again.");
60299
+ lines2.push("5. If the pull request body\u2019s `## How to test` no longer matches what a person");
60300
+ lines2.push(` does, publish it again with \`${HOW_TO_TEST_TOOL_NAME}\` on ${input.key}.`);
60301
+ return lines2.join("\n");
60299
60302
  }
60300
60303
  var POLL_INTERVAL_MS = 2e4;
60301
- var realWait = () => new Promise((resolve6) => {
60302
- setTimeout(resolve6, POLL_INTERVAL_MS);
60304
+ var realWait = () => new Promise((resolve8) => {
60305
+ setTimeout(resolve8, POLL_INTERVAL_MS);
60303
60306
  });
60304
60307
  async function runCiWatchPhase(input) {
60305
60308
  const runAgentFn = input.runAgentFn ?? runAgent;
@@ -60495,7 +60498,7 @@ function fitTitle(title) {
60495
60498
  function renderSessionPrBody(runId, branch, records, commits = [], howToTest) {
60496
60499
  const carried = records.filter(landedWork);
60497
60500
  const failed = records.filter((r) => r.outcome === "failed");
60498
- const lines = [
60501
+ const lines2 = [
60499
60502
  `Unattended \`motir auto\` run \`${runId}\`, integrated on \`${branch}\`.`,
60500
60503
  "",
60501
60504
  `## Work items carried (${carried.length})`,
@@ -60503,25 +60506,25 @@ function renderSessionPrBody(runId, branch, records, commits = [], howToTest) {
60503
60506
  ...carried.map((r) => `- ${r.key} \u2014 ${r.title ?? "(untitled)"}`)
60504
60507
  ];
60505
60508
  if (commits.length > 0) {
60506
- lines.push("", `## What the commits say (${commits.length})`, "");
60509
+ lines2.push("", `## What the commits say (${commits.length})`, "");
60507
60510
  for (const [index, commit] of commits.entries()) {
60508
- if (index > 0) lines.push("");
60509
- lines.push(`### ${commit.subject}`);
60511
+ if (index > 0) lines2.push("");
60512
+ lines2.push(`### ${commit.subject}`);
60510
60513
  const fallback = carried[index]?.title;
60511
60514
  const beneath = commit.body || (fallback ? `_${fallback}_` : "");
60512
- if (beneath) lines.push("", beneath);
60515
+ if (beneath) lines2.push("", beneath);
60513
60516
  }
60514
60517
  }
60515
- if (howToTest) lines.push("", ...renderHowToTestSection(howToTest, branch));
60518
+ if (howToTest) lines2.push("", ...renderHowToTestSection(howToTest, branch));
60516
60519
  if (failed.length > 0) {
60517
- lines.push(
60520
+ lines2.push(
60518
60521
  "",
60519
60522
  `## Attempted and failed (${failed.length}) \u2014 still In Progress, not in this branch`,
60520
60523
  "",
60521
60524
  ...failed.map((r) => `- ${r.key} \u2014 ${r.title ?? "(untitled)"}`)
60522
60525
  );
60523
60526
  }
60524
- lines.push(
60527
+ lines2.push(
60525
60528
  "",
60526
60529
  "## Close-out",
60527
60530
  "",
@@ -60535,7 +60538,7 @@ function renderSessionPrBody(runId, branch, records, commits = [], howToTest) {
60535
60538
  "branch. If the pull request is rejected instead, the items honestly stay In",
60536
60539
  "Review \u2014 flip them back or re-dispatch them."
60537
60540
  );
60538
- return lines.join("\n");
60541
+ return lines2.join("\n");
60539
60542
  }
60540
60543
  function renderHowToTestSection(howToTest, branch) {
60541
60544
  const { targetKey, record } = howToTest;
@@ -60665,15 +60668,15 @@ function renderLaneBlocks(lanes) {
60665
60668
  const blocks = [];
60666
60669
  const declined = lanes.filter((l) => l.lane === "declined_out_of_lane");
60667
60670
  if (declined.length > 0) {
60668
- const lines = [
60671
+ const lines2 = [
60669
60672
  `Re-plans NOT auto-approved \u2014 wider than the card's own lane, so a person decides (${declined.length}). Not a failure:`
60670
60673
  ];
60671
60674
  for (const record of declined) {
60672
- lines.push(` ${record.key}${record.planId ? ` \u2014 plan ${record.planId}` : ""}`);
60673
- for (const out2 of record.outOfLane ?? []) lines.push(` ${out2}`);
60675
+ lines2.push(` ${record.key}${record.planId ? ` \u2014 plan ${record.planId}` : ""}`);
60676
+ for (const out2 of record.outOfLane ?? []) lines2.push(` ${out2}`);
60674
60677
  }
60675
- lines.push(" Review each plan in Motir, then re-run the card if the corrected one survives.");
60676
- blocks.push(lines.join("\n"));
60678
+ lines2.push(" Review each plan in Motir, then re-run the card if the corrected one survives.");
60679
+ blocks.push(lines2.join("\n"));
60677
60680
  }
60678
60681
  const elsewhere = lanes.filter((l) => l.lane === "anchored_elsewhere");
60679
60682
  if (elsewhere.length > 0) {
@@ -60699,18 +60702,18 @@ function renderLaneBlocks(lanes) {
60699
60702
  }
60700
60703
  const refreshed = lanes.filter((l) => l.refresh && !isEmptyRefresh(l.refresh));
60701
60704
  if (refreshed.length > 0) {
60702
- const lines = [`Queue refreshed after an approved re-plan (${refreshed.length}):`];
60705
+ const lines2 = [`Queue refreshed after an approved re-plan (${refreshed.length}):`];
60703
60706
  for (const record of refreshed) {
60704
60707
  const refresh = record.refresh;
60705
- lines.push(` ${record.key} \u2014 re-read ${refresh.parentKey ?? "no parent"}'s children`);
60708
+ lines2.push(` ${record.key} \u2014 re-read ${refresh.parentKey ?? "no parent"}'s children`);
60706
60709
  if (refresh.removed.length > 0) {
60707
- lines.push(` removed by the plan, NOT run: ${refresh.removed.join(", ")}`);
60710
+ lines2.push(` removed by the plan, NOT run: ${refresh.removed.join(", ")}`);
60708
60711
  }
60709
60712
  if (refresh.added.length > 0) {
60710
- lines.push(` added by the plan, run when ready: ${refresh.added.join(", ")}`);
60713
+ lines2.push(` added by the plan, run when ready: ${refresh.added.join(", ")}`);
60711
60714
  }
60712
60715
  }
60713
- blocks.push(lines.join("\n"));
60716
+ blocks.push(lines2.join("\n"));
60714
60717
  }
60715
60718
  return blocks;
60716
60719
  }
@@ -60834,12 +60837,12 @@ function renderScopeRefusal(claim) {
60834
60837
  ].join("\n");
60835
60838
  }
60836
60839
  case "not_finishable": {
60837
- const lines = claim.blockers.map(
60840
+ const lines2 = claim.blockers.map(
60838
60841
  (b) => ` ${b.item} is blocked by ${b.blockedBy} (${b.blockerStatus}), which is OUTSIDE the scope`
60839
60842
  );
60840
60843
  return [
60841
60844
  `${scope}: NOT claimed \u2014 it cannot be finished from inside itself.`,
60842
- ...lines.length > 0 ? lines : [" (the validator named no specific pair)"],
60845
+ ...lines2.length > 0 ? lines2 : [" (the validator named no specific pair)"],
60843
60846
  "A run that took this scope could not finish it, so it did not take it. Finish the",
60844
60847
  "work above, or pull it into the scope. Nothing was locked."
60845
60848
  ].join("\n");
@@ -60978,7 +60981,7 @@ function sameKey(a, b) {
60978
60981
  return a.toUpperCase() === b.toUpperCase();
60979
60982
  }
60980
60983
  function renderLaneDecline(key, verdict) {
60981
- const lines = [
60984
+ const lines2 = [
60982
60985
  `${key}: its re-plan was NOT auto-approved \u2014 the plan reaches beyond this card's own lane.`,
60983
60986
  "This is not a failure. The plan is submitted and intact; it goes to a person, which is",
60984
60987
  "what a correction wider than one card and its siblings is supposed to do.",
@@ -60988,7 +60991,7 @@ function renderLaneDecline(key, verdict) {
60988
60991
  "",
60989
60992
  `Review the plan in Motir, then re-run ${key} if the corrected card survives.`
60990
60993
  ];
60991
- return lines.join("\n");
60994
+ return lines2.join("\n");
60992
60995
  }
60993
60996
  function renderElsewhereAnchored(key, serverMessage) {
60994
60997
  return [
@@ -61525,16 +61528,16 @@ async function refreshParent(client, lane, opts) {
61525
61528
  return { parentKey: lane.parentKey, removed, added };
61526
61529
  }
61527
61530
  function renderRefresh(refresh) {
61528
- const lines = [];
61531
+ const lines2 = [];
61529
61532
  if (refresh.removed.length > 0) {
61530
- lines.push(
61533
+ lines2.push(
61531
61534
  ` the plan removed ${refresh.removed.join(", ")} \u2014 held out of the rest of this run.`
61532
61535
  );
61533
61536
  }
61534
61537
  if (refresh.added.length > 0) {
61535
- lines.push(` the plan added ${refresh.added.join(", ")} \u2014 picked up when they are ready.`);
61538
+ lines2.push(` the plan added ${refresh.added.join(", ")} \u2014 picked up when they are ready.`);
61536
61539
  }
61537
- return lines;
61540
+ return lines2;
61538
61541
  }
61539
61542
  async function triggerExpansion(client, serverUrl, item) {
61540
61543
  const base = { key: item.key, title: item.title };
@@ -61866,8 +61869,8 @@ async function watchRunCi(summary, session, agent, opts, deps) {
61866
61869
  void opts;
61867
61870
  return allGreen;
61868
61871
  }
61869
- function closeOutRepos(summary, run, open = null, howToTest) {
61870
- const outstanding = open && open.openChildren.length > 0 ? open : null;
61872
+ function closeOutRepos(summary, run, open2 = null, howToTest) {
61873
+ const outstanding = open2 && open2.openChildren.length > 0 ? open2 : null;
61871
61874
  if (outstanding) {
61872
61875
  summary.outstanding = {
61873
61876
  containerKey: outstanding.containerKey,
@@ -62028,23 +62031,23 @@ async function drainScope(input) {
62028
62031
  break drain;
62029
62032
  }
62030
62033
  const item = byKey.get(key);
62031
- const open = [
62034
+ const open2 = [
62032
62035
  .../* @__PURE__ */ new Set([
62033
62036
  ...unsatisfiedBlockers(key, edges, satisfied, inScope),
62034
62037
  ...(edges[key] ?? []).filter((dep) => heldGates.has(dep))
62035
62038
  ])
62036
62039
  ];
62037
62040
  const prior = blockedSkips.get(key);
62038
- if (open.length > 0) {
62039
- if (prior && sameKeys(prior.blockedBy, open)) continue;
62041
+ if (open2.length > 0) {
62042
+ if (prior && sameKeys(prior.blockedBy, open2)) continue;
62040
62043
  if (prior) {
62041
- prior.blockedBy = open;
62044
+ prior.blockedBy = open2;
62042
62045
  } else {
62043
62046
  const record2 = {
62044
62047
  key: item.key,
62045
62048
  title: item.title,
62046
62049
  reason: "blocked_in_scope",
62047
- blockedBy: open
62050
+ blockedBy: open2
62048
62051
  };
62049
62052
  skipped.push(record2);
62050
62053
  blockedSkips.set(key, record2);
@@ -62053,12 +62056,12 @@ async function drainScope(input) {
62053
62056
  workItemKey: item.key,
62054
62057
  disposition: "skipped",
62055
62058
  skipReason: "blocked_in_scope",
62056
- data: { blockedBy: open }
62059
+ data: { blockedBy: open2 }
62057
62060
  });
62058
62061
  }
62059
- const gates = open.filter((dep) => heldGates.has(dep));
62062
+ const gates = open2.filter((dep) => heldGates.has(dep));
62060
62063
  info(
62061
- gates.length === open.length ? `${item.key}: held \u2014 waiting on ${gateLabel(gates)} to be approved.` : `${item.key}: skipped \u2014 waiting on ${open.join(", ")}, which did not land.`
62064
+ gates.length === open2.length ? `${item.key}: held \u2014 waiting on ${gateLabel(gates)} to be approved.` : `${item.key}: skipped \u2014 waiting on ${open2.join(", ")}, which did not land.`
62062
62065
  );
62063
62066
  continue;
62064
62067
  }
@@ -62477,13 +62480,13 @@ async function readRecord(client, key) {
62477
62480
  }
62478
62481
  }
62479
62482
  function checkoutsSection(repos) {
62480
- const lines = ["", "\u2550\u2550\u2550\u2550 CHECKOUTS \u2550\u2550\u2550\u2550", ""];
62483
+ const lines2 = ["", "\u2550\u2550\u2550\u2550 CHECKOUTS \u2550\u2550\u2550\u2550", ""];
62481
62484
  for (const repo of repos) {
62482
- lines.push(
62485
+ lines2.push(
62483
62486
  `- ${repo.repoName ?? "(the project repository)"}: ${repo.cwd} \u2014 branch ${repo.branch}`
62484
62487
  );
62485
62488
  }
62486
- return lines.join("\n") + "\n";
62489
+ return lines2.join("\n") + "\n";
62487
62490
  }
62488
62491
  async function runCloseOutHowToTest(input) {
62489
62492
  const { client, dispatchRunId, targetKey, summary } = input;
@@ -62680,8 +62683,8 @@ async function deliver(input) {
62680
62683
  primary: target,
62681
62684
  sessionBranch: dispatch.sessionBranch,
62682
62685
  reporter,
62683
- onMaterialization: (lines) => {
62684
- for (const line of lines) info(line);
62686
+ onMaterialization: (lines2) => {
62687
+ for (const line of lines2) info(line);
62685
62688
  },
62686
62689
  beforeSpawn: () => {
62687
62690
  info("");
@@ -62985,7 +62988,7 @@ async function runCommand(key, opts, deps = {}) {
62985
62988
  materialize: adopted !== null,
62986
62989
  ...adopted ? adoptedCheckoutPreparer() : {}
62987
62990
  });
62988
- const open = await readOpenChildren(client, decision.target);
62991
+ const open2 = await readOpenChildren(client, decision.target);
62989
62992
  const howToTest = decision.target.kind === "work_item" ? await runCloseOutHowToTest({
62990
62993
  client,
62991
62994
  dispatchRunId: reporter.runId,
@@ -62994,7 +62997,7 @@ async function runCommand(key, opts, deps = {}) {
62994
62997
  agent,
62995
62998
  runAgentFn: deps.runAgentFn ?? runAgent
62996
62999
  }) : void 0;
62997
- closeOutRepos(summary, run, open, howToTest);
63000
+ closeOutRepos(summary, run, open2, howToTest);
62998
63001
  await closeOutContainer(client, summary);
62999
63002
  for (const pr of summary.prs) {
63000
63003
  reporter.event({
@@ -63143,7 +63146,7 @@ async function runClaimedScope(input) {
63143
63146
  runAgentFn: deps.runAgentFn ?? runAgent,
63144
63147
  reporter
63145
63148
  });
63146
- const open = await readOpenChildren(client, decision.target);
63149
+ const open2 = await readOpenChildren(client, decision.target);
63147
63150
  const howToTest = decision.target.kind === "work_item" ? await runCloseOutHowToTest({
63148
63151
  client,
63149
63152
  dispatchRunId: reporter.runId,
@@ -63152,7 +63155,7 @@ async function runClaimedScope(input) {
63152
63155
  agent,
63153
63156
  runAgentFn: deps.runAgentFn ?? runAgent
63154
63157
  }) : void 0;
63155
- closeOutRepos(summary, run, open, howToTest);
63158
+ closeOutRepos(summary, run, open2, howToTest);
63156
63159
  await closeOutContainer(client, summary);
63157
63160
  for (const pr of summary.prs) {
63158
63161
  reporter.event({
@@ -63248,11 +63251,11 @@ function prepareContinueCheckouts(input) {
63248
63251
  exists: input.exists
63249
63252
  });
63250
63253
  const materialized = materializeDispatchCheckouts(input.rootDir, targets, { run: input.run });
63251
- const lines = renderMaterialization(materialized);
63254
+ const lines2 = renderMaterialization(materialized);
63252
63255
  if (materialized.failures.length > 0) {
63253
63256
  return {
63254
63257
  ok: false,
63255
- message: [`${input.key}: a repository could not be cloned.`, ...lines].join("\n")
63258
+ message: [`${input.key}: a repository could not be cloned.`, ...lines2].join("\n")
63256
63259
  };
63257
63260
  }
63258
63261
  const same = (a, b) => a !== null && b !== null && a.toLowerCase() === b.toLowerCase();
@@ -63280,7 +63283,7 @@ function prepareContinueCheckouts(input) {
63280
63283
  if (!prepared2.ok) return prepared2;
63281
63284
  checkouts.push({ repository: repo.name, branch, path: prepared2.path });
63282
63285
  }
63283
- return { ok: true, checkouts, materialized: lines };
63286
+ return { ok: true, checkouts, materialized: lines2 };
63284
63287
  }
63285
63288
  async function continueCommand(key, opts, deps = {}) {
63286
63289
  const trimmed = key.trim();
@@ -63618,15 +63621,15 @@ function prepareCheckouts(input) {
63618
63621
  function renderRepairGaveUp(input) {
63619
63622
  const { key, watch } = input;
63620
63623
  const head = watch.kind === "gave_up" ? `${key}: the repair gave up after ${pluralize(watch.attempts, "attempt")}.` : `${key}: the repair stopped after ${pluralize(watch.attempts, "attempt")} \u2014 ${watch.detail}.`;
63621
- const lines = [head];
63624
+ const lines2 = [head];
63622
63625
  for (const pr of input.pullRequests) {
63623
63626
  const checks = pr.queueExit ? `in the merge queue \u2014 ${pr.queueExit.failingCheckName ?? queueReasonInWords(pr.queueExit.rawReason)}` : pr.failingChecks.length > 0 ? pr.failingChecks.join(", ") : "unknown checks";
63624
- lines.push(` ${pr.repo}#${pr.number} \u2014 failing: ${checks} (${pr.url})`);
63627
+ lines2.push(` ${pr.repo}#${pr.number} \u2014 failing: ${checks} (${pr.url})`);
63625
63628
  }
63626
- lines.push(
63629
+ lines2.push(
63627
63630
  `The card stays where it is. Look at the failure, then run \`motir fix ${key}\` again.`
63628
63631
  );
63629
- return lines.join("\n");
63632
+ return lines2.join("\n");
63630
63633
  }
63631
63634
  async function fixCommand(key, opts, deps = {}) {
63632
63635
  const trimmed = key.trim();
@@ -63691,8 +63694,8 @@ function prepareHostedRepairCheckouts(input) {
63691
63694
  { exists: input.exists }
63692
63695
  );
63693
63696
  const materialized = materializeDispatchCheckouts(input.rootDir, targets, { run: input.run });
63694
- const lines = renderMaterialization(materialized);
63695
- for (const line of lines) info(line);
63697
+ const lines2 = renderMaterialization(materialized);
63698
+ for (const line of lines2) info(line);
63696
63699
  if (materialized.failures.length > 0) {
63697
63700
  return {
63698
63701
  ok: false,
@@ -64103,11 +64106,11 @@ function prepareReviewCheckouts(input) {
64103
64106
  { exists: input.exists }
64104
64107
  );
64105
64108
  const materialized = materializeDispatchCheckouts(input.rootDir, targets, { run: input.run });
64106
- const lines = renderMaterialization(materialized);
64109
+ const lines2 = renderMaterialization(materialized);
64107
64110
  if (materialized.failures.length > 0) {
64108
64111
  return {
64109
64112
  ok: false,
64110
- message: [`${input.key}: a repository under review could not be cloned.`, ...lines].join(
64113
+ message: [`${input.key}: a repository under review could not be cloned.`, ...lines2].join(
64111
64114
  "\n"
64112
64115
  )
64113
64116
  };
@@ -64155,7 +64158,7 @@ function prepareReviewCheckouts(input) {
64155
64158
  used.add(repoPath);
64156
64159
  checkouts.push({ repository: pr.repository, number: pr.number, headSha: pr.headSha, path });
64157
64160
  }
64158
- return { ok: true, checkouts, materialized: lines };
64161
+ return { ok: true, checkouts, materialized: lines2 };
64159
64162
  }
64160
64163
  function checkoutStandards(checkouts, exists = existsSync12) {
64161
64164
  const found = [];
@@ -64167,7 +64170,7 @@ function checkoutStandards(checkouts, exists = existsSync12) {
64167
64170
  return found;
64168
64171
  }
64169
64172
  function reviewRunSection(input) {
64170
- const lines = [
64173
+ const lines2 = [
64171
64174
  "",
64172
64175
  "## HOSTED REVIEW RUN \u2014 where the code is, and how your verdict leaves",
64173
64176
  "",
@@ -64180,7 +64183,7 @@ function reviewRunSection(input) {
64180
64183
  ""
64181
64184
  ];
64182
64185
  if (input.standards.length > 0) {
64183
- lines.push(
64186
+ lines2.push(
64184
64187
  "These repositories carry their own written standard. Read it as a standard for",
64185
64188
  "the CHANGED code \u2014 quote the rule a finding relies on; where the card requires",
64186
64189
  "what it forbids, the card wins:",
@@ -64189,7 +64192,7 @@ function reviewRunSection(input) {
64189
64192
  ""
64190
64193
  );
64191
64194
  }
64192
- lines.push(
64195
+ lines2.push(
64193
64196
  "Pushing is disabled in this run and `gh` is unavailable: you cannot commit, push or",
64194
64197
  "post anything, and must not try.",
64195
64198
  "",
@@ -64210,7 +64213,7 @@ function reviewRunSection(input) {
64210
64213
  "A missing, unparseable or incomplete file, or any other key, is NO verdict: the",
64211
64214
  "review is recorded as one that could not run. Write the file LAST, once you are sure."
64212
64215
  );
64213
- return `${lines.join("\n")}
64216
+ return `${lines2.join("\n")}
64214
64217
  `;
64215
64218
  }
64216
64219
 
@@ -64453,19 +64456,19 @@ function renderSkipGroups(skipped, titleWidth) {
64453
64456
  for (const reason of SKIP_ORDER) {
64454
64457
  const group = skipped.filter((s) => s.reason === reason);
64455
64458
  if (group.length === 0) continue;
64456
- const lines = [
64459
+ const lines2 = [
64457
64460
  `Not in the snapshot \u2014 ${SKIP_LABEL2[reason]} (${group.length}):`,
64458
64461
  ...group.map((s) => ` ${s.key} \u2014 ${truncate(s.title ?? "", titleWidth)}`)
64459
64462
  ];
64460
64463
  if (reason === "integrated_dep") {
64461
- lines.push(" Run these with `motir auto`, which carries that lineage on a session branch.");
64464
+ lines2.push(" Run these with `motir auto`, which carries that lineage on a session branch.");
64462
64465
  }
64463
64466
  if (reason === "replan_submitted") {
64464
- lines.push(
64467
+ lines2.push(
64465
64468
  " Each is in Planning with a plan awaiting approval; nothing was recorded for it."
64466
64469
  );
64467
64470
  }
64468
- blocks.push(lines.join("\n"));
64471
+ blocks.push(lines2.join("\n"));
64469
64472
  }
64470
64473
  return blocks;
64471
64474
  }
@@ -64889,8 +64892,8 @@ async function dispatchOne2(input) {
64889
64892
  // NULL, deliberately, exactly as before: batch has no session branch to
64890
64893
  // offer and must not start believing in one the server happened to mention.
64891
64894
  sessionBranch: null,
64892
- onMaterialization: (lines) => {
64893
- for (const line of lines) info(` ${line.trimStart()}`);
64895
+ onMaterialization: (lines2) => {
64896
+ for (const line of lines2) info(` ${line.trimStart()}`);
64894
64897
  },
64895
64898
  // One block per dispatched item, in the drain's own order.
64896
64899
  beforeSpawn: () => echoPromptIfAsked(input.opts, entry.key, dispatch),
@@ -65044,23 +65047,23 @@ function renderAwaiting(turns) {
65044
65047
  }
65045
65048
  function renderThread(session) {
65046
65049
  const resumed = session.turnCount > 0;
65047
- const lines = [
65050
+ const lines2 = [
65048
65051
  `${resumed ? "Resumed" : "Opened"} the planning conversation (${scopeLabel2(
65049
65052
  session.targetKeys
65050
65053
  )}) \u2014 ${session.turnCount} turn${session.turnCount === 1 ? "" : "s"}.`,
65051
65054
  session.lastSubmittedAt ? `Last submitted ${session.lastSubmittedAt}${session.lastJobId ? ` as job ${session.lastJobId}` : ""}.` : "Never submitted \u2014 nothing has been sent to the planner yet."
65052
65055
  ];
65053
65056
  if (session.turns.length > 0) {
65054
- lines.push("");
65057
+ lines2.push("");
65055
65058
  session.turns.forEach((turn, i) => {
65056
- lines.push(renderTurn(turn, i + 1));
65059
+ lines2.push(renderTurn(turn, i + 1));
65057
65060
  const disposition = dispositionMarkerFor(session.turns, i);
65058
- if (disposition) lines.push(renderDisposition(disposition));
65061
+ if (disposition) lines2.push(renderDisposition(disposition));
65059
65062
  });
65060
65063
  }
65061
65064
  const awaiting = renderAwaiting(session.turns);
65062
- if (awaiting) lines.push("", awaiting);
65063
- return lines.join("\n");
65065
+ if (awaiting) lines2.push("", awaiting);
65066
+ return lines2.join("\n");
65064
65067
  }
65065
65068
  function watchVerdict(outcome) {
65066
65069
  if (outcome.status !== "generating") return { kind: "ready" };
@@ -65129,37 +65132,37 @@ function renderProposalTree(items) {
65129
65132
  roots.push(item);
65130
65133
  }
65131
65134
  }
65132
- const lines = [];
65135
+ const lines2 = [];
65133
65136
  const seen = /* @__PURE__ */ new Set();
65134
65137
  const walk = (item, depth) => {
65135
65138
  if (seen.has(item.id)) return;
65136
65139
  seen.add(item.id);
65137
- lines.push(`${" ".repeat(depth + 1)}${describeProposal(item)}`);
65140
+ lines2.push(`${" ".repeat(depth + 1)}${describeProposal(item)}`);
65138
65141
  for (const child of childrenOf.get(item.id) ?? []) walk(child, depth + 1);
65139
65142
  };
65140
65143
  for (const root of roots) walk(root, 0);
65141
65144
  for (const item of items) walk(item, 0);
65142
- return lines;
65145
+ return lines2;
65143
65146
  }
65144
65147
  function renderPlan(plan) {
65145
- const lines = [
65148
+ const lines2 = [
65146
65149
  `Plan ${plan.id} \u2014 ${plan.status}, ${plan.itemCount} proposal${plan.itemCount === 1 ? "" : "s"}.`
65147
65150
  ];
65148
- if (plan.title) lines.push(`Title: ${plan.title}`);
65149
- if (plan.summary) lines.push(`Summary: ${plan.summary}`);
65150
- lines.push("");
65151
+ if (plan.title) lines2.push(`Title: ${plan.title}`);
65152
+ if (plan.summary) lines2.push(`Summary: ${plan.summary}`);
65153
+ lines2.push("");
65151
65154
  if (plan.items.length === 0) {
65152
- lines.push(
65155
+ lines2.push(
65153
65156
  plan.status === "generating" ? "No proposals have arrived yet \u2014 the planner is still generating this plan." : "This plan bundles no proposals."
65154
65157
  );
65155
65158
  } else {
65156
- lines.push(
65159
+ lines2.push(
65157
65160
  "Proposals (indented under their proposed parent):",
65158
65161
  ...renderProposalTree(plan.items)
65159
65162
  );
65160
65163
  }
65161
- lines.push("", PROPOSALS_NOT_WORK_ITEMS);
65162
- return lines.join("\n");
65164
+ lines2.push("", PROPOSALS_NOT_WORK_ITEMS);
65165
+ return lines2.join("\n");
65163
65166
  }
65164
65167
  function nextStepHint(reviewUrl, anchored) {
65165
65168
  const resume = anchored.length > 0 ? `motir plan ${anchored.join(" ")}` : "motir plan";
@@ -65172,8 +65175,8 @@ function nextStepHint(reviewUrl, anchored) {
65172
65175
 
65173
65176
  // src/commands/plan.ts
65174
65177
  function delay2(ms) {
65175
- return new Promise((resolve6) => {
65176
- setTimeout(resolve6, ms);
65178
+ return new Promise((resolve8) => {
65179
+ setTimeout(resolve8, ms);
65177
65180
  });
65178
65181
  }
65179
65182
  async function planCommand(args = [], opts = {}, deps = {}) {
@@ -65305,7 +65308,7 @@ async function watchAndShow(client, planId, reviewUrl, targetKeys, deps) {
65305
65308
 
65306
65309
  // src/commands/agentTerminal.ts
65307
65310
  import { chmodSync as chmodSync5, mkdirSync as mkdirSync7, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "fs";
65308
- import { join as join21 } from "path";
65311
+ import { join as join24 } from "path";
65309
65312
 
65310
65313
  // src/agentTerminal/control.ts
65311
65314
  import { chmodSync as chmodSync4, mkdirSync as mkdirSync6, rmSync as rmSync5 } from "fs";
@@ -65352,7 +65355,7 @@ function parseControlRequest(text) {
65352
65355
  }
65353
65356
  }
65354
65357
  function readLine(socket) {
65355
- return new Promise((resolve6) => {
65358
+ return new Promise((resolve8) => {
65356
65359
  const chunks = [];
65357
65360
  let size = 0;
65358
65361
  let done = false;
@@ -65363,7 +65366,7 @@ function readLine(socket) {
65363
65366
  socket.off("end", onEnd);
65364
65367
  socket.off("error", onEnd);
65365
65368
  socket.off("close", onEnd);
65366
- resolve6(line);
65369
+ resolve8(line);
65367
65370
  };
65368
65371
  const onData = (chunk) => {
65369
65372
  chunks.push(chunk);
@@ -65397,7 +65400,7 @@ function listenControl(path, handler) {
65397
65400
  `);
65398
65401
  });
65399
65402
  });
65400
- return new Promise((resolve6, reject) => {
65403
+ return new Promise((resolve8, reject) => {
65401
65404
  server.once("error", reject);
65402
65405
  server.listen(path, () => {
65403
65406
  server.off("error", reject);
@@ -65405,7 +65408,7 @@ function listenControl(path, handler) {
65405
65408
  chmodSync4(path, 384);
65406
65409
  } catch {
65407
65410
  }
65408
- resolve6({
65411
+ resolve8({
65409
65412
  server,
65410
65413
  close: () => new Promise((done) => {
65411
65414
  server.close(() => {
@@ -65424,7 +65427,7 @@ var ControlUnavailableError = class extends Error {
65424
65427
  }
65425
65428
  };
65426
65429
  function controlRequest(path, request, timeoutMs) {
65427
- return new Promise((resolve6, reject) => {
65430
+ return new Promise((resolve8, reject) => {
65428
65431
  const socket = connect(path);
65429
65432
  const timer = setTimeout(() => {
65430
65433
  socket.destroy();
@@ -65446,7 +65449,7 @@ function controlRequest(path, request, timeoutMs) {
65446
65449
  return;
65447
65450
  }
65448
65451
  try {
65449
- resolve6(JSON.parse(line));
65452
+ resolve8(JSON.parse(line));
65450
65453
  } catch {
65451
65454
  reject(new ControlUnavailableError());
65452
65455
  }
@@ -65497,10 +65500,2375 @@ function loadNodePty(dir) {
65497
65500
  }
65498
65501
 
65499
65502
  // src/agentTerminal/server.ts
65500
- import { randomUUID } from "crypto";
65503
+ import { randomUUID as randomUUID2 } from "crypto";
65501
65504
  import { existsSync as existsSync15, rmSync as rmSync6, statSync as statSync3 } from "fs";
65502
65505
  import { createServer as createServer2 } from "http";
65506
+ import { join as join23 } from "path";
65507
+
65508
+ // src/agentTerminal/chat/adapters/claude.ts
65509
+ import { execFile as execFile2 } from "child_process";
65510
+ import { randomUUID } from "crypto";
65511
+ import { open, readdir, stat } from "fs/promises";
65512
+ import { join as join19 } from "path";
65513
+
65514
+ // src/agentTerminal/chat/protocol.ts
65515
+ var MAX_PROMPT_BYTES = 64 * 1024;
65516
+ var MAX_TOOL_OUTPUT_BYTES = 64 * 1024;
65517
+ var SESSION_ID = /^[A-Za-z0-9_][A-Za-z0-9_.:-]{0,127}$/;
65518
+ function isSessionId(value) {
65519
+ return SESSION_ID.test(value) && !value.includes("..");
65520
+ }
65521
+ function parseChatClientFrame(text) {
65522
+ let value;
65523
+ try {
65524
+ value = JSON.parse(text);
65525
+ } catch {
65526
+ return { ok: false, code: "bad_frame" };
65527
+ }
65528
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
65529
+ return { ok: false, code: "bad_frame" };
65530
+ }
65531
+ const frame = value;
65532
+ switch (frame["t"]) {
65533
+ case "list":
65534
+ return { ok: true, frame: { t: "list" } };
65535
+ case "stop":
65536
+ return { ok: true, frame: { t: "stop" } };
65537
+ case "ping":
65538
+ return { ok: true, frame: { t: "ping", active: frame["active"] === true } };
65539
+ case "open": {
65540
+ const session = frame["session"];
65541
+ if (session === void 0 || session === null) return { ok: true, frame: { t: "open" } };
65542
+ if (typeof session !== "string" || session.length === 0) {
65543
+ return { ok: false, code: "bad_frame" };
65544
+ }
65545
+ return { ok: true, frame: { t: "open", session } };
65546
+ }
65547
+ case "prompt": {
65548
+ const promptText = frame["text"];
65549
+ if (typeof promptText !== "string" || promptText.length === 0) {
65550
+ return { ok: false, code: "bad_frame" };
65551
+ }
65552
+ if (Buffer.byteLength(promptText, "utf8") > MAX_PROMPT_BYTES) {
65553
+ return { ok: false, code: "too_large" };
65554
+ }
65555
+ return { ok: true, frame: { t: "prompt", text: promptText } };
65556
+ }
65557
+ default:
65558
+ return { ok: false, code: "bad_frame" };
65559
+ }
65560
+ }
65561
+ function encodeChatServerFrame(frame) {
65562
+ return JSON.stringify(frame);
65563
+ }
65564
+ function boundTail(text, maxBytes = MAX_TOOL_OUTPUT_BYTES) {
65565
+ const bytes = Buffer.from(text, "utf8");
65566
+ if (bytes.length <= maxBytes) return { text, truncated: false };
65567
+ let start = bytes.length - maxBytes;
65568
+ while (start < bytes.length && (bytes[start] & 192) === 128) start++;
65569
+ return { text: bytes.subarray(start).toString("utf8"), truncated: true };
65570
+ }
65571
+ function boundEvent(event) {
65572
+ if (event.k === "tool_result" && event.output !== void 0) {
65573
+ const bounded = boundTail(event.output);
65574
+ if (!bounded.truncated) return event;
65575
+ return { ...event, output: bounded.text, truncated: true };
65576
+ }
65577
+ if (event.k === "tool_call" && event.diff !== void 0) {
65578
+ const bounded = boundTail(event.diff);
65579
+ if (!bounded.truncated) return event;
65580
+ return { ...event, diff: bounded.text };
65581
+ }
65582
+ return event;
65583
+ }
65584
+
65585
+ // src/agentTerminal/chat/adapters/claude.ts
65586
+ var BINARY = "claude";
65587
+ var UNSUPPORTED = { supported: false, code: "unsupported" };
65588
+ var MAX_TITLE_CHARS = 120;
65589
+ var PROBE_TIMEOUT_MS = 15e3;
65590
+ var TITLE_SCAN_BYTES = 64 * 1024;
65591
+ var HISTORY_SCAN_BYTES = 8 * 1024 * 1024;
65592
+ function classifyClaudeAuth(status) {
65593
+ const provider = typeof status.apiProvider === "string" ? status.apiProvider : null;
65594
+ if (provider !== null && provider !== "" && provider !== "firstParty") return "cloud_provider";
65595
+ const source = typeof status.apiKeySource === "string" ? status.apiKeySource : null;
65596
+ if (source !== null && source !== "" && source !== "none") return "api_key";
65597
+ return "subscription";
65598
+ }
65599
+ var runClaudeAuthStatus = (ctx) => new Promise((resolve8, reject) => {
65600
+ const env = { ...ctx.env };
65601
+ delete env["MOTIR_TERMINAL_KEY"];
65602
+ execFile2(
65603
+ BINARY,
65604
+ ["auth", "status", "--json"],
65605
+ {
65606
+ cwd: ctx.cwd,
65607
+ env,
65608
+ timeout: PROBE_TIMEOUT_MS,
65609
+ maxBuffer: 64 * 1024,
65610
+ windowsHide: true
65611
+ },
65612
+ (error, stdout) => {
65613
+ const out2 = typeof stdout === "string" ? stdout : String(stdout ?? "");
65614
+ if (out2.trim().length > 0) return resolve8(out2);
65615
+ reject(error ?? new Error("no answer"));
65616
+ }
65617
+ );
65618
+ });
65619
+ function parseAuthStatus(text) {
65620
+ const start = text.indexOf("{");
65621
+ const end = text.lastIndexOf("}");
65622
+ if (start === -1 || end < start) return null;
65623
+ try {
65624
+ const value = JSON.parse(text.slice(start, end + 1));
65625
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
65626
+ return value;
65627
+ } catch {
65628
+ return null;
65629
+ }
65630
+ }
65631
+ function isObject(value) {
65632
+ return typeof value === "object" && value !== null && !Array.isArray(value);
65633
+ }
65634
+ function str(value) {
65635
+ return typeof value === "string" ? value : void 0;
65636
+ }
65637
+ function splitLines(text) {
65638
+ if (text === "") return [];
65639
+ const lines2 = text.split("\n");
65640
+ if (lines2[lines2.length - 1] === "") lines2.pop();
65641
+ return lines2;
65642
+ }
65643
+ function hunk(oldText, newText) {
65644
+ const removed = splitLines(oldText);
65645
+ const added = splitLines(newText);
65646
+ const lines2 = [`@@ -${removed.length} +${added.length} @@`];
65647
+ for (const line of removed) lines2.push(`-${line}`);
65648
+ for (const line of added) lines2.push(`+${line}`);
65649
+ return lines2.join("\n");
65650
+ }
65651
+ function diffOf(name, input, path) {
65652
+ const header = `--- a/${path.replace(/^\/+/, "")}
65653
+ +++ b/${path.replace(/^\/+/, "")}`;
65654
+ if (name === "Edit") {
65655
+ const oldString = str(input["old_string"]);
65656
+ const newString = str(input["new_string"]);
65657
+ if (oldString === void 0 || newString === void 0) return void 0;
65658
+ return `${header}
65659
+ ${hunk(oldString, newString)}
65660
+ `;
65661
+ }
65662
+ if (name === "MultiEdit") {
65663
+ const edits = Array.isArray(input["edits"]) ? input["edits"] : [];
65664
+ const hunks = [];
65665
+ for (const edit of edits) {
65666
+ if (!isObject(edit)) continue;
65667
+ const oldString = str(edit["old_string"]);
65668
+ const newString = str(edit["new_string"]);
65669
+ if (oldString === void 0 || newString === void 0) continue;
65670
+ hunks.push(hunk(oldString, newString));
65671
+ }
65672
+ return hunks.length > 0 ? `${header}
65673
+ ${hunks.join("\n")}
65674
+ ` : void 0;
65675
+ }
65676
+ if (name === "Write") {
65677
+ const content = str(input["content"]);
65678
+ if (content === void 0) return void 0;
65679
+ return `${header}
65680
+ ${hunk("", content)}
65681
+ `;
65682
+ }
65683
+ return void 0;
65684
+ }
65685
+ var EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit"]);
65686
+ function toolCallEvent(block) {
65687
+ const id = str(block["id"]);
65688
+ const name = str(block["name"]);
65689
+ if (!id || !name) return null;
65690
+ const input = isObject(block["input"]) ? block["input"] : {};
65691
+ if (name === "Read") {
65692
+ const path = str(input["file_path"]);
65693
+ return {
65694
+ k: "tool_call",
65695
+ id,
65696
+ kind: "read",
65697
+ name,
65698
+ title: path ?? name,
65699
+ ...path ? { path } : {}
65700
+ };
65701
+ }
65702
+ if (EDIT_TOOLS.has(name)) {
65703
+ const path = str(input["file_path"]);
65704
+ const diff = path ? diffOf(name, input, path) : void 0;
65705
+ return {
65706
+ k: "tool_call",
65707
+ id,
65708
+ kind: "edit",
65709
+ name,
65710
+ title: path ?? name,
65711
+ ...path ? { path } : {},
65712
+ ...diff !== void 0 ? { diff: boundTail(diff).text } : {}
65713
+ };
65714
+ }
65715
+ if (name === "Bash") {
65716
+ const command = str(input["command"]);
65717
+ const description = str(input["description"]);
65718
+ return {
65719
+ k: "tool_call",
65720
+ id,
65721
+ kind: "command",
65722
+ name,
65723
+ title: description || command || name,
65724
+ ...command !== void 0 ? { command } : {}
65725
+ };
65726
+ }
65727
+ const kind = "other";
65728
+ return { k: "tool_call", id, kind, name, title: name };
65729
+ }
65730
+ function resultText(content) {
65731
+ if (typeof content === "string") return content;
65732
+ if (!Array.isArray(content)) return "";
65733
+ const parts = [];
65734
+ for (const part of content) {
65735
+ if (isObject(part) && part["type"] === "text" && typeof part["text"] === "string") {
65736
+ parts.push(part["text"]);
65737
+ }
65738
+ }
65739
+ return parts.join("\n");
65740
+ }
65741
+ function toolResultEvent(block) {
65742
+ const id = str(block["tool_use_id"]);
65743
+ if (!id) return null;
65744
+ const ok = block["is_error"] !== true;
65745
+ const bounded = boundTail(resultText(block["content"]));
65746
+ const exit = ok ? null : /^Exit code (\d+)/.exec(bounded.text);
65747
+ return {
65748
+ k: "tool_result",
65749
+ id,
65750
+ ok,
65751
+ output: bounded.text,
65752
+ ...exit ? { exitCode: Number(exit[1]) } : {},
65753
+ truncated: bounded.truncated
65754
+ };
65755
+ }
65756
+ var DROPPED_SYSTEM = /* @__PURE__ */ new Set(["hook_started", "hook_progress", "hook_response", "status"]);
65757
+ var DROPPED_TYPES = /* @__PURE__ */ new Set(["rate_limit_event", "keep_alive"]);
65758
+ function createClaudeMapper(options = {}) {
65759
+ let session = null;
65760
+ let ended = false;
65761
+ let kill = null;
65762
+ const streamed = /* @__PURE__ */ new Set();
65763
+ let streamingMessage = null;
65764
+ const calls = /* @__PURE__ */ new Set();
65765
+ const results = /* @__PURE__ */ new Set();
65766
+ const onStreamEvent = (value) => {
65767
+ const event = isObject(value["event"]) ? value["event"] : null;
65768
+ if (!event) return [];
65769
+ if (event["type"] === "message_start") {
65770
+ const message = isObject(event["message"]) ? event["message"] : null;
65771
+ streamingMessage = str(message?.["id"]) ?? null;
65772
+ return [];
65773
+ }
65774
+ if (event["type"] !== "content_block_delta") return [];
65775
+ const delta = isObject(event["delta"]) ? event["delta"] : null;
65776
+ if (!delta || delta["type"] !== "text_delta") return [];
65777
+ const text = str(delta["text"]);
65778
+ if (!text || streamingMessage === null) return [];
65779
+ streamed.add(streamingMessage);
65780
+ const index = typeof event["index"] === "number" ? event["index"] : 0;
65781
+ return [{ k: "text", id: `${streamingMessage}:${index}`, delta: text }];
65782
+ };
65783
+ const onAssistant = (value) => {
65784
+ const message = isObject(value["message"]) ? value["message"] : null;
65785
+ if (!message || !Array.isArray(message["content"])) return [];
65786
+ const messageId = str(message["id"]) ?? str(value["uuid"]) ?? "message";
65787
+ const events = [];
65788
+ message["content"].forEach((block, index) => {
65789
+ if (!isObject(block)) return;
65790
+ if (block["type"] === "text") {
65791
+ if (streamed.has(messageId)) return;
65792
+ const text = str(block["text"]);
65793
+ if (text) events.push({ k: "text", id: `${messageId}:t${index}`, delta: text });
65794
+ return;
65795
+ }
65796
+ if (block["type"] === "tool_use") {
65797
+ const call = toolCallEvent(block);
65798
+ if (call && call.k === "tool_call" && !calls.has(call.id)) {
65799
+ calls.add(call.id);
65800
+ events.push(call);
65801
+ }
65802
+ }
65803
+ });
65804
+ return events;
65805
+ };
65806
+ const onUser = (value) => {
65807
+ const message = isObject(value["message"]) ? value["message"] : null;
65808
+ if (!message || !Array.isArray(message["content"])) return [];
65809
+ const events = [];
65810
+ for (const block of message["content"]) {
65811
+ if (!isObject(block) || block["type"] !== "tool_result") continue;
65812
+ const result = toolResultEvent(block);
65813
+ if (result && result.k === "tool_result" && !results.has(result.id)) {
65814
+ results.add(result.id);
65815
+ events.push(result);
65816
+ }
65817
+ }
65818
+ return events;
65819
+ };
65820
+ const onResult = (value) => {
65821
+ if (value["terminal_reason"] === "aborted_streaming") return [];
65822
+ ended = true;
65823
+ if (value["subtype"] === "success" && value["is_error"] !== true) return [];
65824
+ const code = str(value["subtype"]) ?? "error";
65825
+ const message = str(value["result"]);
65826
+ return [{ k: "error", code, ...message ? { message } : {} }];
65827
+ };
65828
+ const onSystem = (value) => {
65829
+ const subtype = str(value["subtype"]) ?? "";
65830
+ if (subtype === "init") {
65831
+ if (value["apiKeySource"] === "none" && !options.trustNoKey) kill = "subscription_signin";
65832
+ return [];
65833
+ }
65834
+ if (subtype === "api_retry") return [{ k: "error", code: "api_retry" }];
65835
+ if (DROPPED_SYSTEM.has(subtype)) return [];
65836
+ return [{ k: "other", name: subtype ? `system/${subtype}` : "system" }];
65837
+ };
65838
+ return {
65839
+ onLine(line) {
65840
+ let value;
65841
+ try {
65842
+ value = JSON.parse(line);
65843
+ } catch {
65844
+ return [];
65845
+ }
65846
+ if (!isObject(value)) return [];
65847
+ const type = str(value["type"]);
65848
+ if (!type) return [];
65849
+ const id = str(value["session_id"]);
65850
+ if (id && session === null && isSessionId(id)) session = id;
65851
+ if (typeof value["parent_tool_use_id"] === "string") return [];
65852
+ switch (type) {
65853
+ case "system":
65854
+ return onSystem(value);
65855
+ case "stream_event":
65856
+ return onStreamEvent(value);
65857
+ case "assistant":
65858
+ return onAssistant(value);
65859
+ case "user":
65860
+ return onUser(value);
65861
+ case "result":
65862
+ return onResult(value);
65863
+ default:
65864
+ return DROPPED_TYPES.has(type) ? [] : [{ k: "other", name: type }];
65865
+ }
65866
+ },
65867
+ sessionId: () => session,
65868
+ sawEnd: () => ended,
65869
+ killCode: () => kill
65870
+ };
65871
+ }
65872
+ function claudeProjectDir(ctx) {
65873
+ const configDir2 = ctx.env["CLAUDE_CONFIG_DIR"] || join19(ctx.home, ".claude");
65874
+ return join19(configDir2, "projects", ctx.cwd.replace(/[^A-Za-z0-9]/g, "-"));
65875
+ }
65876
+ async function readBounded(path, max, from) {
65877
+ const handle = await open(path, "r");
65878
+ try {
65879
+ const { size } = await handle.stat();
65880
+ const length = Math.min(size, max);
65881
+ const position = from === "end" ? size - length : 0;
65882
+ const buffer = Buffer.alloc(length);
65883
+ await handle.read(buffer, 0, length, position);
65884
+ let text = buffer.toString("utf8");
65885
+ const cut = size > max;
65886
+ if (cut && from === "end") text = text.slice(text.indexOf("\n") + 1);
65887
+ if (cut && from === "start") text = text.slice(0, text.lastIndexOf("\n") + 1);
65888
+ return { text, cut };
65889
+ } finally {
65890
+ await handle.close();
65891
+ }
65892
+ }
65893
+ function parseJsonLines(text) {
65894
+ const out2 = [];
65895
+ for (const line of text.split("\n")) {
65896
+ if (line.trim() === "") continue;
65897
+ try {
65898
+ const value = JSON.parse(line);
65899
+ if (isObject(value)) out2.push(value);
65900
+ } catch {
65901
+ }
65902
+ }
65903
+ return out2;
65904
+ }
65905
+ function promptOf(entry) {
65906
+ if (entry["type"] !== "user" || entry["isMeta"] === true || entry["isSidechain"] === true) {
65907
+ return null;
65908
+ }
65909
+ const message = isObject(entry["message"]) ? entry["message"] : null;
65910
+ if (!message) return null;
65911
+ const content = message["content"];
65912
+ let text = null;
65913
+ if (typeof content === "string") text = content;
65914
+ else if (Array.isArray(content)) {
65915
+ if (content.some((part) => isObject(part) && part["type"] === "tool_result")) return null;
65916
+ text = resultText(content);
65917
+ }
65918
+ if (!text || !text.trim()) return null;
65919
+ const trimmed = text.trimStart();
65920
+ if (/^<(command-|local-command-)/.test(trimmed) || trimmed.startsWith("Caveat:")) return null;
65921
+ return text;
65922
+ }
65923
+ function cutTitle(text) {
65924
+ const oneLine = text.replace(/\s+/g, " ").trim();
65925
+ const chars = Array.from(oneLine);
65926
+ return chars.length <= MAX_TITLE_CHARS ? oneLine : chars.slice(0, MAX_TITLE_CHARS).join("");
65927
+ }
65928
+ function titleOf(entries) {
65929
+ let summary = null;
65930
+ let firstPrompt = null;
65931
+ for (const entry of entries) {
65932
+ if (entry["type"] === "summary" && typeof entry["summary"] === "string") {
65933
+ summary = entry["summary"];
65934
+ }
65935
+ if (firstPrompt === null) firstPrompt = promptOf(entry);
65936
+ }
65937
+ const title = summary ?? firstPrompt;
65938
+ return title === null ? null : cutTitle(title);
65939
+ }
65940
+ async function listClaudeSessions(ctx, limit) {
65941
+ const dir = claudeProjectDir(ctx);
65942
+ let names;
65943
+ try {
65944
+ names = await readdir(dir);
65945
+ } catch {
65946
+ return [];
65947
+ }
65948
+ const files = [];
65949
+ for (const name of names) {
65950
+ if (!name.endsWith(".jsonl")) continue;
65951
+ const id = name.slice(0, -".jsonl".length);
65952
+ if (!isSessionId(id)) continue;
65953
+ const path = join19(dir, name);
65954
+ try {
65955
+ const info2 = await stat(path);
65956
+ if (info2.isFile()) files.push({ id, path, mtimeMs: info2.mtimeMs });
65957
+ } catch {
65958
+ }
65959
+ }
65960
+ files.sort((a, b) => b.mtimeMs - a.mtimeMs || a.id.localeCompare(b.id));
65961
+ const out2 = [];
65962
+ for (const file of files) {
65963
+ if (out2.length >= limit) break;
65964
+ let title = null;
65965
+ try {
65966
+ title = titleOf(
65967
+ parseJsonLines((await readBounded(file.path, TITLE_SCAN_BYTES, "start")).text)
65968
+ );
65969
+ } catch {
65970
+ continue;
65971
+ }
65972
+ if (title === null) continue;
65973
+ out2.push({ id: file.id, title, updatedAt: new Date(file.mtimeMs).toISOString() });
65974
+ }
65975
+ return out2;
65976
+ }
65977
+ function historyEvents(entries) {
65978
+ const events = [];
65979
+ const calls = /* @__PURE__ */ new Set();
65980
+ for (const entry of entries) {
65981
+ if (entry["isSidechain"] === true) continue;
65982
+ const prompt = promptOf(entry);
65983
+ if (prompt !== null) {
65984
+ events.push({ k: "user", text: prompt });
65985
+ continue;
65986
+ }
65987
+ const message = isObject(entry["message"]) ? entry["message"] : null;
65988
+ if (!message || !Array.isArray(message["content"])) continue;
65989
+ if (entry["type"] === "assistant") {
65990
+ const messageId = str(message["id"]) ?? str(entry["uuid"]) ?? "message";
65991
+ message["content"].forEach((block, index) => {
65992
+ if (!isObject(block)) return;
65993
+ if (block["type"] === "text" && str(block["text"])) {
65994
+ events.push({ k: "text", id: `${messageId}:h${index}`, delta: block["text"] });
65995
+ } else if (block["type"] === "tool_use") {
65996
+ const call = toolCallEvent(block);
65997
+ if (call && call.k === "tool_call" && !calls.has(call.id)) {
65998
+ calls.add(call.id);
65999
+ events.push(call);
66000
+ }
66001
+ }
66002
+ });
66003
+ } else if (entry["type"] === "user") {
66004
+ for (const block of message["content"]) {
66005
+ if (isObject(block) && block["type"] === "tool_result") {
66006
+ const result = toolResultEvent(block);
66007
+ if (result) events.push(result);
66008
+ }
66009
+ }
66010
+ }
66011
+ }
66012
+ return events;
66013
+ }
66014
+ function withinBudget(events, budgetBytes) {
66015
+ const sizes = events.map((event) => Buffer.byteLength(JSON.stringify(event), "utf8"));
66016
+ let total = sizes.reduce((sum, size) => sum + size, 0);
66017
+ let start = 0;
66018
+ while (total > budgetBytes && start < events.length) {
66019
+ let next = start + 1;
66020
+ while (next < events.length && events[next].k !== "user") next++;
66021
+ for (let i = start; i < next; i++) total -= sizes[i];
66022
+ start = next;
66023
+ }
66024
+ return { events: events.slice(start), dropped: start > 0 };
66025
+ }
66026
+ async function readClaudeHistory(ctx, sessionId, budgetBytes) {
66027
+ if (!isSessionId(sessionId)) return { unavailable: true };
66028
+ let read;
66029
+ try {
66030
+ read = await readBounded(
66031
+ join19(claudeProjectDir(ctx), `${sessionId}.jsonl`),
66032
+ HISTORY_SCAN_BYTES,
66033
+ "end"
66034
+ );
66035
+ } catch {
66036
+ return { unavailable: true };
66037
+ }
66038
+ let events = historyEvents(parseJsonLines(read.text));
66039
+ if (read.cut) {
66040
+ const first = events.findIndex((event) => event.k === "user");
66041
+ events = first === -1 ? [] : events.slice(first);
66042
+ }
66043
+ const bounded = withinBudget(events, budgetBytes);
66044
+ return { events: bounded.events, truncated: read.cut || bounded.dropped };
66045
+ }
66046
+ function createClaudeChatAdapter(options = {}) {
66047
+ const probe = options.probe ?? runClaudeAuthStatus;
66048
+ const newSessionId = options.newSessionId ?? randomUUID;
66049
+ let lastKind = null;
66050
+ return {
66051
+ profile: "claude",
66052
+ async support(ctx) {
66053
+ lastKind = null;
66054
+ let answer;
66055
+ try {
66056
+ answer = await probe(ctx);
66057
+ } catch {
66058
+ return UNSUPPORTED;
66059
+ }
66060
+ const status = parseAuthStatus(answer);
66061
+ if (!status) return UNSUPPORTED;
66062
+ lastKind = classifyClaudeAuth(status);
66063
+ if (lastKind === "subscription") return { supported: false, code: "subscription_signin" };
66064
+ return { supported: true, signedIn: true };
66065
+ },
66066
+ turnCommand({ prompt, sessionId }) {
66067
+ return {
66068
+ file: BINARY,
66069
+ args: [
66070
+ "-p",
66071
+ "--output-format",
66072
+ "stream-json",
66073
+ "--verbose",
66074
+ "--include-partial-messages",
66075
+ ...sessionId === null ? ["--session-id", newSessionId()] : ["--resume", sessionId],
66076
+ "--dangerously-skip-permissions"
66077
+ ],
66078
+ stdin: prompt
66079
+ };
66080
+ },
66081
+ createMapper: () => createClaudeMapper({ trustNoKey: lastKind === "cloud_provider" }),
66082
+ listSessions: listClaudeSessions,
66083
+ readHistory: readClaudeHistory
66084
+ };
66085
+ }
66086
+ var claudeChatAdapter = createClaudeChatAdapter();
66087
+
66088
+ // src/agentTerminal/chat/adapters/codex.ts
66089
+ import { createReadStream } from "fs";
66090
+ import { readdir as readdir2, stat as stat2 } from "fs/promises";
65503
66091
  import { join as join20 } from "path";
66092
+ import { createInterface as createInterface2 } from "readline";
66093
+ var CODEX_PROFILE = "codex";
66094
+ var CODEX_SANDBOX_OVERRIDE = 'sandbox_mode="workspace-write"';
66095
+ var TURN_FLAGS = ["--json", "-c", CODEX_SANDBOX_OVERRIDE];
66096
+ var LIST_SCAN_LINES = 400;
66097
+ var ROLLOUT_FILE = /^rollout-.+\.jsonl$/;
66098
+ function parseObject(line) {
66099
+ let value;
66100
+ try {
66101
+ value = JSON.parse(line);
66102
+ } catch {
66103
+ return null;
66104
+ }
66105
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
66106
+ }
66107
+ var str2 = (value) => typeof value === "string" ? value : void 0;
66108
+ var num = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
66109
+ var obj = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
66110
+ function commandText(value) {
66111
+ if (typeof value === "string") return value;
66112
+ if (Array.isArray(value) && value.every((part) => typeof part === "string")) {
66113
+ return value.join(" ");
66114
+ }
66115
+ return void 0;
66116
+ }
66117
+ function toolOutput(id, ok, output, exitCode) {
66118
+ const bounded = output === void 0 ? void 0 : boundTail(output);
66119
+ return {
66120
+ k: "tool_result",
66121
+ id,
66122
+ ok,
66123
+ ...bounded !== void 0 ? { output: bounded.text } : {},
66124
+ ...exitCode !== void 0 ? { exitCode } : {},
66125
+ truncated: bounded?.truncated ?? false
66126
+ };
66127
+ }
66128
+ function mcpResultText(result) {
66129
+ const content = obj(result)?.["content"];
66130
+ if (!Array.isArray(content)) return void 0;
66131
+ const texts = content.map((block) => obj(block)?.["type"] === "text" ? str2(obj(block)?.["text"]) : void 0).filter((text) => text !== void 0);
66132
+ return texts.length > 0 ? texts.join("\n") : void 0;
66133
+ }
66134
+ function createCodexMapper() {
66135
+ let thread = null;
66136
+ let ended = false;
66137
+ const called = /* @__PURE__ */ new Set();
66138
+ const commandCall = (item, id) => {
66139
+ const command = str2(item["command"]) ?? "";
66140
+ return {
66141
+ k: "tool_call",
66142
+ id,
66143
+ kind: "command",
66144
+ name: "command_execution",
66145
+ title: command,
66146
+ command
66147
+ };
66148
+ };
66149
+ const mcpCall = (item, id) => {
66150
+ const server = str2(item["server"]);
66151
+ const tool = str2(item["tool"]) ?? "mcp_tool_call";
66152
+ const name = server ? `${server}.${tool}` : tool;
66153
+ return { k: "tool_call", id, kind: "other", name, title: name };
66154
+ };
66155
+ const searchCall = (item, id) => ({
66156
+ k: "tool_call",
66157
+ id,
66158
+ kind: "other",
66159
+ name: "web_search",
66160
+ title: str2(item["query"]) ?? "web_search"
66161
+ });
66162
+ const onStarted = (item, id) => {
66163
+ let call = null;
66164
+ switch (item["type"]) {
66165
+ case "command_execution":
66166
+ call = commandCall(item, id);
66167
+ break;
66168
+ case "mcp_tool_call":
66169
+ call = mcpCall(item, id);
66170
+ break;
66171
+ case "web_search":
66172
+ call = searchCall(item, id);
66173
+ break;
66174
+ default:
66175
+ return [];
66176
+ }
66177
+ if (called.has(id)) return [];
66178
+ called.add(id);
66179
+ return [call];
66180
+ };
66181
+ const withCall = (id, call, rest) => called.has(id) ? rest : [call, ...rest];
66182
+ const onCompleted = (item, id) => {
66183
+ const status = str2(item["status"]);
66184
+ switch (item["type"]) {
66185
+ case "agent_message": {
66186
+ const text = str2(item["text"]);
66187
+ return text ? [{ k: "text", id, delta: text }] : [];
66188
+ }
66189
+ case "reasoning":
66190
+ return [];
66191
+ case "command_execution": {
66192
+ const exitCode = num(item["exit_code"]);
66193
+ const ok = status !== "failed" && status !== "declined" && (exitCode ?? 0) === 0;
66194
+ return withCall(id, commandCall(item, id), [
66195
+ toolOutput(id, ok, str2(item["aggregated_output"]) ?? "", exitCode)
66196
+ ]);
66197
+ }
66198
+ case "file_change": {
66199
+ const changes = Array.isArray(item["changes"]) ? item["changes"] : [];
66200
+ const ok = status !== "failed";
66201
+ const events = [];
66202
+ changes.forEach((change, index) => {
66203
+ const path = str2(obj(change)?.["path"]);
66204
+ if (!path) return;
66205
+ const kind = str2(obj(change)?.["kind"]) ?? "update";
66206
+ const callId = changes.length === 1 ? id : `${id}:${index}`;
66207
+ events.push(
66208
+ {
66209
+ k: "tool_call",
66210
+ id: callId,
66211
+ kind: "edit",
66212
+ name: "file_change",
66213
+ title: `${kind} ${path}`,
66214
+ path
66215
+ },
66216
+ toolOutput(callId, ok, void 0, void 0)
66217
+ );
66218
+ });
66219
+ return events;
66220
+ }
66221
+ case "mcp_tool_call": {
66222
+ const error = str2(obj(item["error"])?.["message"]);
66223
+ const ok = status !== "failed" && error === void 0;
66224
+ return withCall(id, mcpCall(item, id), [
66225
+ toolOutput(id, ok, error ?? mcpResultText(item["result"]), void 0)
66226
+ ]);
66227
+ }
66228
+ case "web_search":
66229
+ return withCall(id, searchCall(item, id), [
66230
+ toolOutput(id, status !== "failed", void 0, void 0)
66231
+ ]);
66232
+ case "error": {
66233
+ const message = str2(item["message"]);
66234
+ return [{ k: "error", code: "codex_error", ...message ? { message } : {} }];
66235
+ }
66236
+ default: {
66237
+ const type = str2(item["type"]);
66238
+ return type ? [{ k: "other", name: type }] : [];
66239
+ }
66240
+ }
66241
+ };
66242
+ return {
66243
+ onLine(line) {
66244
+ const event = parseObject(line);
66245
+ if (!event) return [];
66246
+ const type = str2(event["type"]);
66247
+ if (!type) return [];
66248
+ switch (type) {
66249
+ case "thread.started": {
66250
+ const id = str2(event["thread_id"]);
66251
+ if (id) thread = id;
66252
+ return [];
66253
+ }
66254
+ case "turn.started":
66255
+ return [];
66256
+ case "turn.completed":
66257
+ ended = true;
66258
+ return [];
66259
+ case "turn.failed": {
66260
+ const message = str2(obj(event["error"])?.["message"]);
66261
+ return [{ k: "error", code: "turn_failed", ...message ? { message } : {} }];
66262
+ }
66263
+ case "error": {
66264
+ const message = str2(event["message"]);
66265
+ return [{ k: "error", code: "codex_error", ...message ? { message } : {} }];
66266
+ }
66267
+ case "item.started":
66268
+ case "item.updated":
66269
+ case "item.completed": {
66270
+ const item = obj(event["item"]);
66271
+ const id = str2(item?.["id"]);
66272
+ if (!item || !id) return [{ k: "other", name: type }];
66273
+ if (type === "item.completed") return onCompleted(item, id);
66274
+ if (type === "item.started") return onStarted(item, id);
66275
+ return [];
66276
+ }
66277
+ default:
66278
+ return [{ k: "other", name: type }];
66279
+ }
66280
+ },
66281
+ sessionId: () => thread,
66282
+ sawEnd: () => ended,
66283
+ killCode: () => null
66284
+ };
66285
+ }
66286
+ function codexHome(ctx) {
66287
+ const configured = ctx.env["CODEX_HOME"]?.trim();
66288
+ return configured ? configured : join20(ctx.home, ".codex");
66289
+ }
66290
+ async function rolloutFiles(root) {
66291
+ const found = [];
66292
+ const walk = async (dir, depth) => {
66293
+ let entries;
66294
+ try {
66295
+ entries = await readdir2(dir, { withFileTypes: true });
66296
+ } catch {
66297
+ return;
66298
+ }
66299
+ for (const entry of entries) {
66300
+ const path = join20(dir, entry.name);
66301
+ if (entry.isDirectory()) {
66302
+ if (depth < 4) await walk(path, depth + 1);
66303
+ } else if (entry.isFile() && ROLLOUT_FILE.test(entry.name)) {
66304
+ try {
66305
+ found.push({ path, mtimeMs: (await stat2(path)).mtimeMs });
66306
+ } catch {
66307
+ }
66308
+ }
66309
+ }
66310
+ };
66311
+ await walk(join20(root, "sessions"), 0);
66312
+ return found;
66313
+ }
66314
+ async function readLines(path, visit) {
66315
+ const stream = createReadStream(path, { encoding: "utf8" });
66316
+ const lines2 = createInterface2({ input: stream, crlfDelay: Infinity });
66317
+ try {
66318
+ for await (const text of lines2) {
66319
+ const line = parseObject(text);
66320
+ if (line && visit(line) === true) break;
66321
+ }
66322
+ } finally {
66323
+ lines2.close();
66324
+ stream.destroy();
66325
+ }
66326
+ }
66327
+ async function readHead(path) {
66328
+ const head = { id: null, cwd: null, firstPrompt: null };
66329
+ let seen = 0;
66330
+ await readLines(path, (line) => {
66331
+ seen += 1;
66332
+ const payload = obj(line["payload"]);
66333
+ if (line["type"] === "session_meta" && payload && head.id === null) {
66334
+ head.id = str2(payload["id"]) ?? null;
66335
+ head.cwd = str2(payload["cwd"]) ?? null;
66336
+ if (head.cwd === null) return true;
66337
+ } else if (line["type"] === "event_msg" && payload?.["type"] === "user_message") {
66338
+ head.firstPrompt = str2(payload["message"]) ?? null;
66339
+ if (head.firstPrompt !== null) return true;
66340
+ }
66341
+ return seen >= LIST_SCAN_LINES;
66342
+ });
66343
+ return head;
66344
+ }
66345
+ async function listCodexSessions(ctx, limit) {
66346
+ const files = await rolloutFiles(codexHome(ctx));
66347
+ files.sort((a, b) => b.mtimeMs - a.mtimeMs);
66348
+ const items = [];
66349
+ const seen = /* @__PURE__ */ new Set();
66350
+ for (const file of files) {
66351
+ if (items.length >= limit) break;
66352
+ let head;
66353
+ try {
66354
+ head = await readHead(file.path);
66355
+ } catch {
66356
+ continue;
66357
+ }
66358
+ if (head.id === null || head.cwd !== ctx.cwd || seen.has(head.id)) continue;
66359
+ seen.add(head.id);
66360
+ items.push({
66361
+ id: head.id,
66362
+ title: (head.firstPrompt ?? "").trim() || head.id,
66363
+ updatedAt: new Date(file.mtimeMs).toISOString()
66364
+ });
66365
+ }
66366
+ return items;
66367
+ }
66368
+ async function findRollout(ctx, sessionId) {
66369
+ const files = (await rolloutFiles(codexHome(ctx))).filter((file) => file.path.endsWith(`-${sessionId}.jsonl`)).sort((a, b) => b.mtimeMs - a.mtimeMs);
66370
+ for (const file of files) {
66371
+ const head = await readHead(file.path).catch(() => null);
66372
+ if (head?.id === sessionId && head.cwd === ctx.cwd) return file.path;
66373
+ }
66374
+ return null;
66375
+ }
66376
+ async function readCodexHistory(ctx, sessionId, budgetBytes) {
66377
+ const path = await findRollout(ctx, sessionId);
66378
+ if (path === null) return { unavailable: true };
66379
+ const turns = [];
66380
+ let turn = null;
66381
+ let ended = true;
66382
+ let texts = 0;
66383
+ const close = (reason) => {
66384
+ if (turn && !ended) turn.push({ k: "turn_end", reason });
66385
+ ended = true;
66386
+ };
66387
+ await readLines(path, (line) => {
66388
+ const payload = obj(line["payload"]);
66389
+ if (!payload) return;
66390
+ const kind = str2(payload["type"]);
66391
+ if (line["type"] === "event_msg") {
66392
+ if (kind === "user_message") {
66393
+ close("completed");
66394
+ turn = [{ k: "user", text: str2(payload["message"]) ?? "" }];
66395
+ turns.push(turn);
66396
+ ended = false;
66397
+ } else if (kind === "agent_message" && turn && !ended) {
66398
+ const text = str2(payload["message"]);
66399
+ if (text) turn.push({ k: "text", id: `history-${++texts}`, delta: text });
66400
+ } else if (kind === "turn_aborted") {
66401
+ close("stopped");
66402
+ }
66403
+ return;
66404
+ }
66405
+ if (line["type"] !== "response_item" || !turn || ended) return;
66406
+ const callId = str2(payload["call_id"]);
66407
+ if (!callId) return;
66408
+ if (kind === "function_call") {
66409
+ const args = parseObject(str2(payload["arguments"]) ?? "");
66410
+ const command = commandText(args?.["command"] ?? args?.["cmd"]);
66411
+ const name = str2(payload["name"]) ?? "function_call";
66412
+ turn.push(
66413
+ command !== void 0 ? { k: "tool_call", id: callId, kind: "command", name, title: command, command } : { k: "tool_call", id: callId, kind: "other", name, title: name }
66414
+ );
66415
+ } else if (kind === "custom_tool_call") {
66416
+ const name = str2(payload["name"]) ?? "custom_tool_call";
66417
+ const input = str2(payload["input"]);
66418
+ const target = input?.match(/^\*\*\* (?:Update|Add|Delete) File: (.+)$/m)?.[1];
66419
+ turn.push(
66420
+ name === "apply_patch" && input !== void 0 ? {
66421
+ k: "tool_call",
66422
+ id: callId,
66423
+ kind: "edit",
66424
+ name,
66425
+ title: target ?? name,
66426
+ ...target ? { path: target } : {},
66427
+ diff: boundTail(input).text
66428
+ } : { k: "tool_call", id: callId, kind: "other", name, title: name }
66429
+ );
66430
+ } else if (kind === "function_call_output" || kind === "custom_tool_call_output") {
66431
+ const raw = payload["output"];
66432
+ const text = str2(raw) ?? str2(obj(raw)?.["content"]);
66433
+ const wrapped = text !== void 0 ? parseObject(text) : null;
66434
+ const output = str2(wrapped?.["output"]) ?? text;
66435
+ const exitLine = text?.match(/^Exit code: (-?\d+)$/m)?.[1];
66436
+ const exitCode = num(obj(wrapped?.["metadata"])?.["exit_code"]) ?? (exitLine !== void 0 ? Number(exitLine) : void 0);
66437
+ turn.push(toolOutput(callId, (exitCode ?? 0) === 0, output, exitCode));
66438
+ }
66439
+ });
66440
+ close("completed");
66441
+ const kept = [];
66442
+ let bytes = 0;
66443
+ for (let index = turns.length - 1; index >= 0; index--) {
66444
+ const events = turns[index];
66445
+ const size = Buffer.byteLength(JSON.stringify(events), "utf8");
66446
+ if (bytes + size > budgetBytes) break;
66447
+ bytes += size;
66448
+ kept.unshift(events);
66449
+ }
66450
+ return { events: kept.flat(), truncated: kept.length < turns.length };
66451
+ }
66452
+ function codexTurnCommand(input) {
66453
+ const args = input.sessionId === null ? ["exec", ...TURN_FLAGS, "-"] : ["exec", "resume", ...TURN_FLAGS, input.sessionId, "-"];
66454
+ return { file: "codex", args, stdin: input.prompt };
66455
+ }
66456
+ var codexChatAdapter = {
66457
+ profile: CODEX_PROFILE,
66458
+ // Q2: Codex chats on either sign-in. The runner's sign-in gate still applies.
66459
+ support: async () => ({ supported: true }),
66460
+ turnCommand: codexTurnCommand,
66461
+ createMapper: createCodexMapper,
66462
+ listSessions: listCodexSessions,
66463
+ readHistory: readCodexHistory
66464
+ };
66465
+
66466
+ // src/agentTerminal/chat/adapters/opencode.ts
66467
+ import { spawn as spawnChild } from "child_process";
66468
+ var OPENCODE_BINARY = "opencode";
66469
+ var OPENCODE_TURN_FLAGS = ["run", "--format", "json", "--auto"];
66470
+ var MAX_ERROR_MESSAGE_CHARS = 2e3;
66471
+ var HELPER_TIMEOUT_MS = 2e4;
66472
+ var HELPER_MAX_BYTES = 64 * 1024 * 1024;
66473
+ var execOpencode = (args, ctx) => new Promise((resolve8) => {
66474
+ let settled = false;
66475
+ const done = (value) => {
66476
+ if (settled) return;
66477
+ settled = true;
66478
+ clearTimeout(timer);
66479
+ resolve8(value);
66480
+ };
66481
+ let child;
66482
+ try {
66483
+ child = spawnChild(OPENCODE_BINARY, args, {
66484
+ cwd: ctx.cwd,
66485
+ // The app's tsconfig widens `ProcessEnv` with required keys (NODE_ENV).
66486
+ env: { ...ctx.env },
66487
+ stdio: ["ignore", "pipe", "pipe"]
66488
+ });
66489
+ } catch {
66490
+ resolve8(null);
66491
+ return;
66492
+ }
66493
+ const timer = setTimeout(() => {
66494
+ child.kill("SIGKILL");
66495
+ done(null);
66496
+ }, HELPER_TIMEOUT_MS);
66497
+ timer.unref();
66498
+ const chunks = [];
66499
+ let bytes = 0;
66500
+ child.stderr?.resume();
66501
+ child.stdout?.on("data", (chunk) => {
66502
+ bytes += chunk.length;
66503
+ if (bytes > HELPER_MAX_BYTES) {
66504
+ child.kill("SIGKILL");
66505
+ done(null);
66506
+ return;
66507
+ }
66508
+ chunks.push(chunk);
66509
+ });
66510
+ child.on("error", () => done(null));
66511
+ child.on("close", (code) => done(code === 0 ? Buffer.concat(chunks).toString("utf8") : null));
66512
+ });
66513
+ function isObject2(value) {
66514
+ return typeof value === "object" && value !== null && !Array.isArray(value);
66515
+ }
66516
+ function str3(value) {
66517
+ return typeof value === "string" ? value : void 0;
66518
+ }
66519
+ function num2(value) {
66520
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
66521
+ }
66522
+ function parseHelperJson(stdout, open2) {
66523
+ const start = stdout.indexOf(open2);
66524
+ if (start === -1) return void 0;
66525
+ try {
66526
+ return JSON.parse(stdout.slice(start));
66527
+ } catch {
66528
+ return void 0;
66529
+ }
66530
+ }
66531
+ function opencodeToolKind(tool) {
66532
+ switch (tool) {
66533
+ case "read":
66534
+ return "read";
66535
+ case "edit":
66536
+ case "write":
66537
+ case "patch":
66538
+ case "apply_patch":
66539
+ case "multiedit":
66540
+ return "edit";
66541
+ case "bash":
66542
+ return "command";
66543
+ default:
66544
+ return "other";
66545
+ }
66546
+ }
66547
+ function mapToolPart(part, seen, fallbackId) {
66548
+ const name = str3(part["tool"]) ?? "tool";
66549
+ const id = str3(part["callID"]) ?? str3(part["id"]) ?? fallbackId();
66550
+ const state = isObject2(part["state"]) ? part["state"] : {};
66551
+ const status = str3(state["status"]);
66552
+ const input = isObject2(state["input"]) ? state["input"] : {};
66553
+ const metadata = isObject2(state["metadata"]) ? state["metadata"] : {};
66554
+ const kind = opencodeToolKind(name);
66555
+ const events = [];
66556
+ if (!seen.calls.has(id)) {
66557
+ seen.calls.add(id);
66558
+ const path = str3(input["filePath"]) ?? str3(metadata["filepath"]) ?? str3(input["path"]);
66559
+ const command = kind === "command" ? str3(input["command"]) : void 0;
66560
+ const title = str3(state["title"]) || command || path || name;
66561
+ const call = {
66562
+ k: "tool_call",
66563
+ id,
66564
+ kind,
66565
+ name,
66566
+ title
66567
+ };
66568
+ if (path !== void 0 && kind !== "command") call.path = path;
66569
+ if (command !== void 0) call.command = command;
66570
+ const diff = kind === "edit" ? str3(metadata["diff"]) : void 0;
66571
+ if (diff) call.diff = boundTail(diff).text;
66572
+ events.push(call);
66573
+ }
66574
+ if ((status === "completed" || status === "error") && !seen.results.has(id)) {
66575
+ seen.results.add(id);
66576
+ if (status === "error") {
66577
+ const bounded = boundTail(str3(state["error"]) ?? "");
66578
+ events.push({
66579
+ k: "tool_result",
66580
+ id,
66581
+ ok: false,
66582
+ output: bounded.text,
66583
+ truncated: bounded.truncated
66584
+ });
66585
+ } else {
66586
+ const exitCode = kind === "command" ? num2(metadata["exit"]) : void 0;
66587
+ const output = str3(state["output"]) ?? (kind === "command" ? str3(metadata["output"]) : void 0);
66588
+ const result = {
66589
+ k: "tool_result",
66590
+ id,
66591
+ // A command that exited non-zero is drawn failed; anything else completed ran.
66592
+ ok: exitCode === void 0 || exitCode === 0,
66593
+ truncated: false
66594
+ };
66595
+ if (output !== void 0) {
66596
+ const bounded = boundTail(output);
66597
+ result.output = bounded.text;
66598
+ result.truncated = bounded.truncated;
66599
+ }
66600
+ if (exitCode !== void 0) result.exitCode = exitCode;
66601
+ events.push(result);
66602
+ }
66603
+ }
66604
+ return events;
66605
+ }
66606
+ function mapError(error) {
66607
+ const value = isObject2(error) ? error : {};
66608
+ const data = isObject2(value["data"]) ? value["data"] : {};
66609
+ const code = str3(value["name"]) || "error";
66610
+ const message = str3(data["message"]) ?? str3(value["message"]);
66611
+ const event = { k: "error", code };
66612
+ if (message) event.message = Array.from(message).slice(0, MAX_ERROR_MESSAGE_CHARS).join("");
66613
+ return event;
66614
+ }
66615
+ function createOpencodeMapper() {
66616
+ let session = null;
66617
+ let ended = false;
66618
+ let counter = 0;
66619
+ const seen = { calls: /* @__PURE__ */ new Set(), results: /* @__PURE__ */ new Set() };
66620
+ const texts = /* @__PURE__ */ new Set();
66621
+ const fallbackId = () => `opencode-${++counter}`;
66622
+ return {
66623
+ onLine(line) {
66624
+ let value;
66625
+ try {
66626
+ value = JSON.parse(line);
66627
+ } catch {
66628
+ return [];
66629
+ }
66630
+ if (!isObject2(value)) return [];
66631
+ const type = str3(value["type"]);
66632
+ if (!type) return [];
66633
+ const sessionID = str3(value["sessionID"]);
66634
+ if (session === null && sessionID !== void 0 && isSessionId(sessionID))
66635
+ session = sessionID;
66636
+ const part = isObject2(value["part"]) ? value["part"] : {};
66637
+ switch (type) {
66638
+ case "step_start":
66639
+ case "reasoning":
66640
+ return [];
66641
+ case "step_finish":
66642
+ if (str3(part["reason"]) === "stop") ended = true;
66643
+ return [];
66644
+ case "text": {
66645
+ const text = str3(part["text"]);
66646
+ if (!text) return [];
66647
+ const id = str3(part["id"]) ?? fallbackId();
66648
+ if (texts.has(id)) return [];
66649
+ texts.add(id);
66650
+ return [{ k: "text", id, delta: text }];
66651
+ }
66652
+ case "tool_use":
66653
+ return mapToolPart(part, seen, fallbackId).map(boundEvent);
66654
+ case "error":
66655
+ return [mapError(value["error"])];
66656
+ default:
66657
+ return [{ k: "other", name: type }];
66658
+ }
66659
+ },
66660
+ sessionId: () => session,
66661
+ sawEnd: () => ended,
66662
+ killCode: () => null
66663
+ };
66664
+ }
66665
+ function parseOpencodeSessionList(stdout, cwd, limit) {
66666
+ const value = parseHelperJson(stdout, "[");
66667
+ if (!Array.isArray(value)) return [];
66668
+ const rows = [];
66669
+ for (const entry of value) {
66670
+ if (!isObject2(entry)) continue;
66671
+ const id = str3(entry["id"]);
66672
+ const updated = num2(entry["updated"]) ?? num2(entry["created"]);
66673
+ if (!id || !isSessionId(id) || updated === void 0) continue;
66674
+ const directory = str3(entry["directory"]);
66675
+ if (directory !== void 0 && directory !== cwd) continue;
66676
+ rows.push({ id, title: str3(entry["title"]) || id, updated });
66677
+ }
66678
+ rows.sort((a, b) => b.updated - a.updated);
66679
+ return rows.slice(0, Math.max(0, limit)).map((row) => ({
66680
+ id: row.id,
66681
+ title: row.title,
66682
+ updatedAt: new Date(row.updated).toISOString()
66683
+ }));
66684
+ }
66685
+ function turnEndOf(info2) {
66686
+ const error = isObject2(info2["error"]) ? info2["error"] : null;
66687
+ if (!error) return "completed";
66688
+ return str3(error["name"]) === "MessageAbortedError" ? "stopped" : "failed";
66689
+ }
66690
+ function parseOpencodeExport(stdout, budgetBytes) {
66691
+ const value = parseHelperJson(stdout, "{");
66692
+ if (!isObject2(value) || !Array.isArray(value["messages"])) return null;
66693
+ const turns = [];
66694
+ let current = null;
66695
+ let end = "completed";
66696
+ let counter = 0;
66697
+ const seen = { calls: /* @__PURE__ */ new Set(), results: /* @__PURE__ */ new Set() };
66698
+ const fallbackId = () => `opencode-history-${++counter}`;
66699
+ const close = () => {
66700
+ if (current) {
66701
+ current.push({ k: "turn_end", reason: end });
66702
+ turns.push(current);
66703
+ }
66704
+ current = null;
66705
+ end = "completed";
66706
+ };
66707
+ for (const message of value["messages"]) {
66708
+ if (!isObject2(message)) continue;
66709
+ const info2 = isObject2(message["info"]) ? message["info"] : {};
66710
+ const parts = Array.isArray(message["parts"]) ? message["parts"] : [];
66711
+ const role = str3(info2["role"]);
66712
+ if (role === "user") {
66713
+ close();
66714
+ const text = parts.filter(isObject2).filter((part) => str3(part["type"]) === "text" && part["synthetic"] !== true).map((part) => str3(part["text"]) ?? "").join("\n");
66715
+ current = [{ k: "user", text }];
66716
+ } else if (role === "assistant") {
66717
+ current ??= [];
66718
+ end = turnEndOf(info2);
66719
+ for (const part of parts) {
66720
+ if (!isObject2(part)) continue;
66721
+ const type = str3(part["type"]);
66722
+ if (type === "text") {
66723
+ const text = str3(part["text"]);
66724
+ if (text) current.push({ k: "text", id: str3(part["id"]) ?? fallbackId(), delta: text });
66725
+ } else if (type === "tool") {
66726
+ current.push(...mapToolPart(part, seen, fallbackId).map(boundEvent));
66727
+ }
66728
+ }
66729
+ }
66730
+ }
66731
+ close();
66732
+ const kept = [];
66733
+ let bytes = 0;
66734
+ for (let index = turns.length - 1; index >= 0; index--) {
66735
+ const turn = turns[index];
66736
+ const size = turn.reduce(
66737
+ (sum, event) => sum + Buffer.byteLength(JSON.stringify(event), "utf8"),
66738
+ 0
66739
+ );
66740
+ if (bytes + size > budgetBytes) break;
66741
+ bytes += size;
66742
+ kept.unshift(turn);
66743
+ }
66744
+ return { events: kept.flat(), truncated: kept.length < turns.length };
66745
+ }
66746
+ function createOpencodeAdapter(exec = execOpencode) {
66747
+ return {
66748
+ profile: "opencode",
66749
+ // Q1: OpenCode streams, on any sign-in (Q2 names no term against it). The
66750
+ // terminal's sign-in check still gates each turn, in the runner.
66751
+ async support() {
66752
+ return { supported: true };
66753
+ },
66754
+ turnCommand({ prompt, sessionId }) {
66755
+ return {
66756
+ file: OPENCODE_BINARY,
66757
+ args: [
66758
+ ...OPENCODE_TURN_FLAGS,
66759
+ ...sessionId !== null ? ["--session", sessionId] : [],
66760
+ "--",
66761
+ prompt
66762
+ ],
66763
+ stdin: null
66764
+ };
66765
+ },
66766
+ createMapper: createOpencodeMapper,
66767
+ async listSessions(ctx, limit) {
66768
+ const stdout = await exec(["session", "list", "--format", "json"], ctx);
66769
+ return stdout === null ? [] : parseOpencodeSessionList(stdout, ctx.cwd, limit);
66770
+ },
66771
+ async readHistory(ctx, sessionId, budgetBytes) {
66772
+ if (!isSessionId(sessionId)) return { unavailable: true };
66773
+ const stdout = await exec(["export", sessionId], ctx);
66774
+ const history = stdout === null ? null : parseOpencodeExport(stdout, budgetBytes);
66775
+ return history ?? { unavailable: true };
66776
+ }
66777
+ };
66778
+ }
66779
+ var opencodeChatAdapter = createOpencodeAdapter();
66780
+
66781
+ // src/agentTerminal/chat/adapters/kimi.ts
66782
+ import { readFile } from "fs/promises";
66783
+ import { join as join21, resolve as resolve6 } from "path";
66784
+ var KIMI_BINARY = "kimi";
66785
+ var MAX_TITLE_CHARS2 = 120;
66786
+ var COMMAND_TOOLS = /* @__PURE__ */ new Set(["Bash", "Shell", "bash", "shell", "run_shell_command"]);
66787
+ var READ_TOOLS = /* @__PURE__ */ new Set(["ReadFile", "Read", "read_file", "ReadMediaFile"]);
66788
+ var EDIT_TOOLS2 = /* @__PURE__ */ new Set([
66789
+ "WriteFile",
66790
+ "StrReplaceFile",
66791
+ "Write",
66792
+ "Edit",
66793
+ "MultiEdit",
66794
+ "write_file",
66795
+ "str_replace_file"
66796
+ ]);
66797
+ function isObject3(value) {
66798
+ return typeof value === "object" && value !== null && !Array.isArray(value);
66799
+ }
66800
+ function str4(value) {
66801
+ return typeof value === "string" ? value : void 0;
66802
+ }
66803
+ function toolKind(name) {
66804
+ if (COMMAND_TOOLS.has(name)) return "command";
66805
+ if (READ_TOOLS.has(name)) return "read";
66806
+ if (EDIT_TOOLS2.has(name)) return "edit";
66807
+ return "other";
66808
+ }
66809
+ function parseArguments(value) {
66810
+ if (isObject3(value)) return value;
66811
+ if (typeof value !== "string") return {};
66812
+ try {
66813
+ const parsed = JSON.parse(value);
66814
+ return isObject3(parsed) ? parsed : {};
66815
+ } catch {
66816
+ return {};
66817
+ }
66818
+ }
66819
+ function contentText(content) {
66820
+ if (typeof content === "string") return content;
66821
+ if (!Array.isArray(content)) return "";
66822
+ let text = "";
66823
+ for (const part of content) {
66824
+ if (typeof part === "string") text += part;
66825
+ else if (isObject3(part) && part["type"] === "text") text += str4(part["text"]) ?? "";
66826
+ }
66827
+ return text;
66828
+ }
66829
+ function lines(text) {
66830
+ if (text.length === 0) return [];
66831
+ const out2 = text.split("\n");
66832
+ if (out2[out2.length - 1] === "") out2.pop();
66833
+ return out2;
66834
+ }
66835
+ function editDiff(args, path) {
66836
+ const file = path ?? "file";
66837
+ const hunks = [];
66838
+ const replacement = (edit) => {
66839
+ const oldText = str4(edit["old"]) ?? str4(edit["old_string"]) ?? str4(edit["old_str"]);
66840
+ const newText = str4(edit["new"]) ?? str4(edit["new_string"]) ?? str4(edit["new_str"]);
66841
+ if (oldText === void 0 || newText === void 0) return;
66842
+ hunks.push(
66843
+ ["@@ @@", ...lines(oldText).map((l) => `-${l}`), ...lines(newText).map((l) => `+${l}`)].join(
66844
+ "\n"
66845
+ )
66846
+ );
66847
+ };
66848
+ const edits = args["edit"] ?? args["edits"];
66849
+ if (Array.isArray(edits)) {
66850
+ for (const edit of edits) if (isObject3(edit)) replacement(edit);
66851
+ } else if (isObject3(edits)) {
66852
+ replacement(edits);
66853
+ } else {
66854
+ replacement(args);
66855
+ }
66856
+ const content = str4(args["content"]);
66857
+ if (hunks.length === 0 && content !== void 0) {
66858
+ hunks.push(["@@ @@", ...lines(content).map((l) => `+${l}`)].join("\n"));
66859
+ }
66860
+ if (hunks.length === 0) return void 0;
66861
+ return boundTail(`--- a/${file}
66862
+ +++ b/${file}
66863
+ ${hunks.join("\n")}
66864
+ `).text;
66865
+ }
66866
+ var SYSTEM_NOTE = /<system>([\s\S]*?)<\/system>/g;
66867
+ function toolOutcome(content) {
66868
+ const notes = [];
66869
+ const output = content.replace(SYSTEM_NOTE, (_match, note) => {
66870
+ notes.push(note);
66871
+ return "";
66872
+ });
66873
+ const failed = notes.some((note) => /^\s*ERROR\b/i.test(note));
66874
+ const exit = notes.map((note) => /exit code:?\s*(-?\d+)/i.exec(note)).find((m) => m != null);
66875
+ const shown = output.trim().length > 0 ? output : failed ? notes.map((n) => n.trim()).join("\n") : "";
66876
+ return { ok: !failed, output: shown, ...exit ? { exitCode: Number(exit[1]) } : {} };
66877
+ }
66878
+ function createKimiMapper() {
66879
+ let session = null;
66880
+ let ended = false;
66881
+ let messages = 0;
66882
+ const toolCall = (value) => {
66883
+ if (!isObject3(value)) return null;
66884
+ const id = str4(value["id"]);
66885
+ const fn = isObject3(value["function"]) ? value["function"] : null;
66886
+ const name = fn ? str4(fn["name"]) : void 0;
66887
+ if (!id || !name || !fn) return null;
66888
+ const args = parseArguments(fn["arguments"]);
66889
+ const kind = toolKind(name);
66890
+ const path = str4(args["path"]) ?? str4(args["file_path"]);
66891
+ if (kind === "command") {
66892
+ const command = str4(args["command"]) ?? "";
66893
+ return { k: "tool_call", id, kind, name, title: command || name, command };
66894
+ }
66895
+ if (kind === "read") {
66896
+ return { k: "tool_call", id, kind, name, title: path ?? name, ...path ? { path } : {} };
66897
+ }
66898
+ if (kind === "edit") {
66899
+ const diff = editDiff(args, path);
66900
+ return {
66901
+ k: "tool_call",
66902
+ id,
66903
+ kind,
66904
+ name,
66905
+ title: path ?? name,
66906
+ ...path ? { path } : {},
66907
+ ...diff !== void 0 ? { diff } : {}
66908
+ };
66909
+ }
66910
+ return { k: "tool_call", id, kind, name, title: name, ...path ? { path } : {} };
66911
+ };
66912
+ const onLine = (line) => {
66913
+ let parsed;
66914
+ try {
66915
+ parsed = JSON.parse(line);
66916
+ } catch {
66917
+ return [];
66918
+ }
66919
+ if (!isObject3(parsed)) return [];
66920
+ const role = str4(parsed["role"]);
66921
+ const type = str4(parsed["type"]);
66922
+ switch (role) {
66923
+ case "assistant": {
66924
+ const events = [];
66925
+ const text = contentText(parsed["content"]);
66926
+ if (text.length > 0) events.push({ k: "text", id: `kimi-msg-${++messages}`, delta: text });
66927
+ const toolCalls = parsed["tool_calls"];
66928
+ if (Array.isArray(toolCalls)) {
66929
+ for (const call of toolCalls) {
66930
+ const event = toolCall(call);
66931
+ if (event) events.push(event);
66932
+ }
66933
+ }
66934
+ return events;
66935
+ }
66936
+ case "tool": {
66937
+ const id = str4(parsed["tool_call_id"]);
66938
+ if (!id) return type ? [{ k: "other", name: type }] : [];
66939
+ const outcome = toolOutcome(contentText(parsed["content"]));
66940
+ const explicitError = parsed["is_error"] === true;
66941
+ const bounded = boundTail(outcome.output);
66942
+ return [
66943
+ {
66944
+ k: "tool_result",
66945
+ id,
66946
+ ok: outcome.ok && !explicitError,
66947
+ ...bounded.text.length > 0 ? { output: bounded.text } : {},
66948
+ ...outcome.exitCode !== void 0 ? { exitCode: outcome.exitCode } : {},
66949
+ truncated: bounded.truncated
66950
+ }
66951
+ ];
66952
+ }
66953
+ case "meta": {
66954
+ if (type === "session.resume_hint") {
66955
+ const id = str4(parsed["session_id"]);
66956
+ if (id) session = id;
66957
+ ended = true;
66958
+ return [];
66959
+ }
66960
+ if (type === "system.version") return [];
66961
+ return type ? [{ k: "other", name: type }] : [];
66962
+ }
66963
+ case "user":
66964
+ case "system":
66965
+ return [];
66966
+ default:
66967
+ return type ? [{ k: "other", name: type }] : [];
66968
+ }
66969
+ };
66970
+ return {
66971
+ onLine,
66972
+ sessionId: () => session,
66973
+ sawEnd: () => ended,
66974
+ killCode: () => null
66975
+ };
66976
+ }
66977
+ function kimiHome(ctx) {
66978
+ const configured = ctx.env["KIMI_CODE_HOME"]?.trim();
66979
+ return configured ? configured : join21(ctx.home, ".kimi-code");
66980
+ }
66981
+ function sessionIndexPaths(ctx) {
66982
+ const home = kimiHome(ctx);
66983
+ return [join21(home, "sessions", "session_index.jsonl"), join21(home, "session_index.jsonl")];
66984
+ }
66985
+ function firstString(row, names) {
66986
+ for (const name of names) {
66987
+ const value = str4(row[name]);
66988
+ if (value !== void 0 && value.length > 0) return value;
66989
+ }
66990
+ return void 0;
66991
+ }
66992
+ function parseTime(value) {
66993
+ if (typeof value === "number" && Number.isFinite(value)) {
66994
+ return value < 1e12 ? value * 1e3 : value;
66995
+ }
66996
+ if (typeof value === "string" && value.length > 0) {
66997
+ const numeric = Number(value);
66998
+ if (Number.isFinite(numeric)) return parseTime(numeric);
66999
+ const ms = Date.parse(value);
67000
+ return Number.isNaN(ms) ? null : ms;
67001
+ }
67002
+ return null;
67003
+ }
67004
+ function cutTitle2(title) {
67005
+ const chars = Array.from(title.replace(/\s+/g, " ").trim());
67006
+ return chars.length <= MAX_TITLE_CHARS2 ? chars.join("") : chars.slice(0, MAX_TITLE_CHARS2).join("");
67007
+ }
67008
+ function parseSessionIndex(text, cwd, limit) {
67009
+ const scope = resolve6(cwd);
67010
+ const latest = /* @__PURE__ */ new Map();
67011
+ for (const line of text.split("\n")) {
67012
+ if (line.trim().length === 0) continue;
67013
+ let row;
67014
+ try {
67015
+ row = JSON.parse(line);
67016
+ } catch {
67017
+ continue;
67018
+ }
67019
+ if (!isObject3(row)) continue;
67020
+ const id = firstString(row, ["session_id", "id"]);
67021
+ const dir = firstString(row, ["work_dir", "workdir", "cwd", "working_directory", "path"]);
67022
+ if (!id || !dir || resolve6(dir) !== scope) continue;
67023
+ const at = parseTime(row["updated_at"]) ?? parseTime(row["updated"]) ?? parseTime(row["last_updated"]) ?? parseTime(row["created_at"]);
67024
+ if (at === null) continue;
67025
+ const title = firstString(row, ["title", "first_prompt", "summary"]) ?? id;
67026
+ const previous = latest.get(id);
67027
+ if (!previous || at >= previous.at) latest.set(id, { title, at });
67028
+ }
67029
+ return [...latest.entries()].sort((a, b) => b[1].at - a[1].at).slice(0, Math.max(0, limit)).map(([id, { title, at }]) => ({
67030
+ id,
67031
+ title: cutTitle2(title),
67032
+ updatedAt: new Date(at).toISOString()
67033
+ }));
67034
+ }
67035
+ var kimiAdapter = {
67036
+ profile: "kimi",
67037
+ // Q1: supported. kimi's sign-in cannot be told by a file stat, so the
67038
+ // terminal's check reads `unknown`, which Q6 lets through; there is no
67039
+ // sign-in rule of Q2's kind for kimi.
67040
+ async support() {
67041
+ return { supported: true };
67042
+ },
67043
+ // Q3 exactly: the prompt on argv, no permission flag (`-p` is already auto),
67044
+ // no environment addition (Q11).
67045
+ turnCommand({ prompt, sessionId }) {
67046
+ return {
67047
+ file: KIMI_BINARY,
67048
+ args: [
67049
+ "-p",
67050
+ prompt,
67051
+ "--output-format",
67052
+ "stream-json",
67053
+ ...sessionId !== null ? ["--session", sessionId] : []
67054
+ ],
67055
+ stdin: null
67056
+ };
67057
+ },
67058
+ createMapper: createKimiMapper,
67059
+ async listSessions(ctx, limit) {
67060
+ for (const path of sessionIndexPaths(ctx)) {
67061
+ let text;
67062
+ try {
67063
+ text = await readFile(path, "utf8");
67064
+ } catch {
67065
+ continue;
67066
+ }
67067
+ return parseSessionIndex(text, ctx.cwd, limit);
67068
+ }
67069
+ return [];
67070
+ },
67071
+ async readHistory() {
67072
+ return { unavailable: true };
67073
+ }
67074
+ };
67075
+
67076
+ // src/agentTerminal/chat/adapters/goose.ts
67077
+ import { execFile as execFile3 } from "child_process";
67078
+ import { resolve as resolve7 } from "path";
67079
+ var GOOSE_BINARY = "goose";
67080
+ var GOOSE_RUN_ARGS = [
67081
+ "run",
67082
+ "-q",
67083
+ "--output-format",
67084
+ "stream-json",
67085
+ "-i",
67086
+ "-"
67087
+ ];
67088
+ var GOOSE_ENV = { GOOSE_MODE: "auto" };
67089
+ var GOOSE_INTERRUPTED = "Headless run interrupted";
67090
+ var LIST_TIMEOUT_MS = 1e4;
67091
+ var LIST_MAX_BYTES = 8 * 1024 * 1024;
67092
+ var GOOSE_TITLE_CHARS = 120;
67093
+ var MAX_OTHER_NAME = 64;
67094
+ function asObject(value) {
67095
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
67096
+ }
67097
+ function asString(value) {
67098
+ return typeof value === "string" ? value : void 0;
67099
+ }
67100
+ function firstString2(object, names) {
67101
+ for (const name of names) {
67102
+ const value = asString(object[name]);
67103
+ if (value !== void 0) return value;
67104
+ }
67105
+ return void 0;
67106
+ }
67107
+ function otherName(name) {
67108
+ return Array.from(name).slice(0, MAX_OTHER_NAME).join("");
67109
+ }
67110
+ function bareToolName(name) {
67111
+ const cut = name.lastIndexOf("__");
67112
+ return cut === -1 ? name : name.slice(cut + 2);
67113
+ }
67114
+ var COMMAND_TOOLS2 = /* @__PURE__ */ new Set(["shell", "bash", "run_command", "command"]);
67115
+ var EDIT_TOOLS3 = /* @__PURE__ */ new Set([
67116
+ "edit",
67117
+ "write",
67118
+ "create",
67119
+ "str_replace",
67120
+ "write_file",
67121
+ "edit_file",
67122
+ "insert"
67123
+ ]);
67124
+ var READ_TOOLS2 = /* @__PURE__ */ new Set(["view", "read", "read_file", "cat"]);
67125
+ function textLines(text) {
67126
+ if (text === "") return [];
67127
+ const lines2 = text.split("\n");
67128
+ if (lines2[lines2.length - 1] === "") lines2.pop();
67129
+ return lines2;
67130
+ }
67131
+ function unifiedDiff(path, before, after) {
67132
+ const removed = before === null ? [] : textLines(before);
67133
+ const added = textLines(after);
67134
+ const header = [
67135
+ `--- ${before === null ? "/dev/null" : path}`,
67136
+ `+++ ${path}`,
67137
+ `@@ -${removed.length === 0 ? 0 : 1},${removed.length} +${added.length === 0 ? 0 : 1},${added.length} @@`
67138
+ ];
67139
+ return [
67140
+ ...header,
67141
+ ...removed.map((line) => `-${line}`),
67142
+ ...added.map((line) => `+${line}`),
67143
+ ""
67144
+ ].join("\n");
67145
+ }
67146
+ function mapToolCall(id, rawName, args) {
67147
+ const name = bareToolName(rawName);
67148
+ const lower = name.toLowerCase();
67149
+ const path = firstString2(args, ["path", "file_path", "file"]);
67150
+ if (COMMAND_TOOLS2.has(lower)) {
67151
+ const command = firstString2(args, ["command", "cmd"]) ?? "";
67152
+ return { k: "tool_call", id, kind: "command", name, title: command || name, command };
67153
+ }
67154
+ const operation = lower === "text_editor" ? asString(args["command"]) ?? "" : lower;
67155
+ let kind = "other";
67156
+ if (READ_TOOLS2.has(operation)) kind = "read";
67157
+ else if (EDIT_TOOLS3.has(operation) || operation === "undo_edit") kind = "edit";
67158
+ const event = {
67159
+ k: "tool_call",
67160
+ id,
67161
+ kind,
67162
+ name,
67163
+ title: path ?? name,
67164
+ ...path !== void 0 && kind !== "other" ? { path } : {}
67165
+ };
67166
+ if (kind === "edit" && path !== void 0) {
67167
+ const whole = firstString2(args, ["file_text", "content"]);
67168
+ const before = firstString2(args, ["old_str", "old_string", "before"]);
67169
+ const after = firstString2(args, ["new_str", "new_string", "after"]);
67170
+ let diff;
67171
+ if (operation === "write" || operation === "create" || operation === "write_file") {
67172
+ if (whole !== void 0) diff = unifiedDiff(path, null, whole);
67173
+ } else if (operation === "insert") {
67174
+ if (after !== void 0) diff = unifiedDiff(path, "", after);
67175
+ } else if (before !== void 0 && after !== void 0) {
67176
+ diff = unifiedDiff(path, before, after);
67177
+ } else if (whole !== void 0) {
67178
+ diff = unifiedDiff(path, null, whole);
67179
+ }
67180
+ if (diff !== void 0) event.diff = boundTail(diff).text;
67181
+ }
67182
+ return event;
67183
+ }
67184
+ function mapToolResult(id, toolResult) {
67185
+ if (!toolResult || toolResult["status"] !== "success") {
67186
+ const error = toolResult ? asString(toolResult["error"]) : void 0;
67187
+ const bounded2 = boundTail(error ?? "");
67188
+ return {
67189
+ k: "tool_result",
67190
+ id,
67191
+ ok: false,
67192
+ ...error !== void 0 ? { output: bounded2.text } : {},
67193
+ truncated: bounded2.truncated
67194
+ };
67195
+ }
67196
+ const value = asObject(toolResult["value"]) ?? {};
67197
+ const structured = asObject(value["structuredContent"]);
67198
+ let output;
67199
+ let exitCode;
67200
+ if (structured) {
67201
+ const stdout = asString(structured["stdout"]);
67202
+ const stderr = asString(structured["stderr"]);
67203
+ if (stdout !== void 0 || stderr !== void 0) {
67204
+ output = [stdout, stderr].filter((part) => part !== void 0 && part !== "").join("\n");
67205
+ }
67206
+ const code = structured["exit_code"] ?? structured["exitCode"];
67207
+ if (typeof code === "number" && Number.isInteger(code)) exitCode = code;
67208
+ }
67209
+ if (output === void 0 && Array.isArray(value["content"])) {
67210
+ const texts = value["content"].map((part) => asObject(part)).filter((part) => part !== null && part["type"] === "text").map((part) => asString(part["text"]) ?? "");
67211
+ if (texts.length > 0) output = texts.join("\n");
67212
+ }
67213
+ const ok = value["isError"] !== true && (exitCode === void 0 || exitCode === 0);
67214
+ const bounded = output === void 0 ? null : boundTail(output);
67215
+ return {
67216
+ k: "tool_result",
67217
+ id,
67218
+ ok,
67219
+ ...bounded ? { output: bounded.text } : {},
67220
+ ...exitCode !== void 0 ? { exitCode } : {},
67221
+ truncated: bounded?.truncated ?? false
67222
+ };
67223
+ }
67224
+ var DROPPED_CONTENT = /* @__PURE__ */ new Set(["thinking", "redactedThinking", "reasoning"]);
67225
+ var DROPPED_EVENTS = /* @__PURE__ */ new Set(["notification", "model_change"]);
67226
+ function createGooseMapper() {
67227
+ let session = null;
67228
+ let complete = false;
67229
+ let textCounter = 0;
67230
+ let syntheticTextId = null;
67231
+ const textId = (message) => {
67232
+ const id = asString(message["id"]);
67233
+ if (id) return id;
67234
+ syntheticTextId ??= `goose-text-${++textCounter}`;
67235
+ return syntheticTextId;
67236
+ };
67237
+ const mapMessage = (message) => {
67238
+ const role = message["role"];
67239
+ const content = Array.isArray(message["content"]) ? message["content"] : [];
67240
+ const events = [];
67241
+ for (const raw of content) {
67242
+ const part = asObject(raw);
67243
+ const type = part ? asString(part["type"]) : void 0;
67244
+ if (!part || !type || DROPPED_CONTENT.has(type)) continue;
67245
+ if (type === "text") {
67246
+ if (role !== "assistant") continue;
67247
+ const delta = asString(part["text"]) ?? "";
67248
+ if (delta.length > 0) events.push({ k: "text", id: textId(message), delta });
67249
+ continue;
67250
+ }
67251
+ if (type === "toolRequest") {
67252
+ syntheticTextId = null;
67253
+ const id = asString(part["id"]) ?? `goose-tool-${++textCounter}`;
67254
+ const call = asObject(part["toolCall"]);
67255
+ const value = call && call["status"] === "success" ? asObject(call["value"]) : null;
67256
+ const name = value ? asString(value["name"]) : void 0;
67257
+ if (value && name) {
67258
+ events.push(mapToolCall(id, name, asObject(value["arguments"]) ?? {}));
67259
+ } else {
67260
+ events.push({ k: "tool_call", id, kind: "other", name: "tool", title: "tool" });
67261
+ }
67262
+ continue;
67263
+ }
67264
+ if (type === "toolResponse") {
67265
+ syntheticTextId = null;
67266
+ const id = asString(part["id"]) ?? "";
67267
+ events.push(mapToolResult(id, asObject(part["toolResult"])));
67268
+ continue;
67269
+ }
67270
+ events.push({ k: "other", name: otherName(type) });
67271
+ }
67272
+ return events;
67273
+ };
67274
+ return {
67275
+ onLine(line) {
67276
+ let parsed;
67277
+ try {
67278
+ parsed = JSON.parse(line);
67279
+ } catch {
67280
+ return [];
67281
+ }
67282
+ const event = asObject(parsed);
67283
+ if (!event) return [];
67284
+ const id = firstString2(event, ["session_id", "sessionId"]);
67285
+ if (id !== void 0 && isSessionId(id)) session = id;
67286
+ const type = asString(event["type"]);
67287
+ if (type === void 0) return [];
67288
+ switch (type) {
67289
+ case "message": {
67290
+ const message = asObject(event["message"]);
67291
+ return message ? mapMessage(message) : [];
67292
+ }
67293
+ case "complete":
67294
+ complete = true;
67295
+ return [];
67296
+ case "error": {
67297
+ const error = asString(event["error"]) ?? "";
67298
+ if (error === GOOSE_INTERRUPTED) return [];
67299
+ const bounded = boundTail(error, 4 * 1024);
67300
+ return [{ k: "error", code: "goose_error", ...error ? { message: bounded.text } : {} }];
67301
+ }
67302
+ default:
67303
+ if (DROPPED_EVENTS.has(type)) return [];
67304
+ return [{ k: "other", name: otherName(type) }];
67305
+ }
67306
+ },
67307
+ sessionId: () => session,
67308
+ sawEnd: () => complete,
67309
+ killCode: () => null
67310
+ };
67311
+ }
67312
+ var runGooseSessionList = (ctx) => new Promise((resolveList, reject) => {
67313
+ execFile3(
67314
+ GOOSE_BINARY,
67315
+ ["session", "list", "--format", "json"],
67316
+ {
67317
+ cwd: ctx.cwd,
67318
+ env: { ...ctx.env },
67319
+ timeout: LIST_TIMEOUT_MS,
67320
+ maxBuffer: LIST_MAX_BYTES,
67321
+ encoding: "utf8"
67322
+ },
67323
+ (error, stdout) => {
67324
+ if (error) reject(new Error("goose session list failed"));
67325
+ else resolveList(stdout);
67326
+ }
67327
+ );
67328
+ });
67329
+ function gooseTime(value) {
67330
+ let ms;
67331
+ if (typeof value === "number" && Number.isFinite(value)) {
67332
+ ms = value < 1e12 ? value * 1e3 : value;
67333
+ } else if (typeof value === "string" && value.trim() !== "") {
67334
+ let text = value.trim().replace(/ UTC$/, "Z");
67335
+ if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}/.test(text)) text = text.replace(" ", "T");
67336
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?$/.test(text)) text += "Z";
67337
+ ms = Date.parse(text);
67338
+ } else {
67339
+ return null;
67340
+ }
67341
+ return Number.isNaN(ms) ? null : new Date(ms).toISOString();
67342
+ }
67343
+ function samePath(a, b) {
67344
+ const strip = (path) => resolve7(path).replace(/\/+$/, "");
67345
+ return strip(a) === strip(b);
67346
+ }
67347
+ function cutTitle3(title) {
67348
+ const chars = Array.from(title);
67349
+ return chars.length <= GOOSE_TITLE_CHARS ? title : chars.slice(0, GOOSE_TITLE_CHARS).join("");
67350
+ }
67351
+ function parseGooseSessionList(stdout, cwd, limit) {
67352
+ let parsed;
67353
+ try {
67354
+ parsed = JSON.parse(stdout);
67355
+ } catch {
67356
+ return [];
67357
+ }
67358
+ const rows = Array.isArray(parsed) ? parsed : Array.isArray(asObject(parsed)?.["sessions"]) ? asObject(parsed)?.["sessions"] : [];
67359
+ const sessions = [];
67360
+ for (const raw of rows) {
67361
+ const row = asObject(raw);
67362
+ if (!row) continue;
67363
+ const id = asString(row["id"]);
67364
+ if (id === void 0 || !isSessionId(id)) continue;
67365
+ const metadata = asObject(row["metadata"]) ?? {};
67366
+ const dir = firstString2(row, ["working_dir", "workingDir"]) ?? asString(metadata["working_dir"]);
67367
+ if (dir === void 0 || !samePath(dir, cwd)) continue;
67368
+ const updatedAt = gooseTime(row["updated_at"] ?? row["updatedAt"] ?? row["modified"]) ?? gooseTime(row["created_at"] ?? row["createdAt"]);
67369
+ if (updatedAt === null) continue;
67370
+ const title = firstString2(row, ["name", "description"]) ?? asString(metadata["description"]) ?? "";
67371
+ sessions.push({
67372
+ id,
67373
+ title: cutTitle3(title.trim() || id),
67374
+ updatedAt,
67375
+ at: Date.parse(updatedAt)
67376
+ });
67377
+ }
67378
+ sessions.sort((a, b) => b.at - a.at);
67379
+ return sessions.slice(0, Math.max(0, limit)).map(({ id, title, updatedAt }) => ({
67380
+ id,
67381
+ title,
67382
+ updatedAt
67383
+ }));
67384
+ }
67385
+ function createGooseAdapter(options = {}) {
67386
+ const list = options.listSessions ?? runGooseSessionList;
67387
+ return {
67388
+ profile: "goose",
67389
+ // Q1: goose's stream is supported. Its sign-in cannot be told by a file
67390
+ // stat, so the terminal's check answers `unknown`, which Q6 lets through.
67391
+ support: async () => ({ supported: true }),
67392
+ turnCommand({ prompt, sessionId }) {
67393
+ return {
67394
+ file: GOOSE_BINARY,
67395
+ args: [...GOOSE_RUN_ARGS, ...sessionId ? ["--resume", "--session-id", sessionId] : []],
67396
+ stdin: prompt,
67397
+ env: { ...GOOSE_ENV }
67398
+ };
67399
+ },
67400
+ createMapper: createGooseMapper,
67401
+ async listSessions(ctx, limit) {
67402
+ return parseGooseSessionList(await list(ctx), ctx.cwd, limit);
67403
+ },
67404
+ // Q7: goose keeps its messages in `sessions.db`, whose schema it does not
67405
+ // document and whose CLI has no documented machine-readable export, so the
67406
+ // earlier turns are not drawn. The agent still has them on resume.
67407
+ readHistory: async () => ({ unavailable: true })
67408
+ };
67409
+ }
67410
+ var gooseChatAdapter = createGooseAdapter();
67411
+
67412
+ // src/agentTerminal/chat/adapter.ts
67413
+ var MAX_LISTED_SESSIONS = 50;
67414
+ var MAX_TITLE_CHARS3 = 120;
67415
+ var HISTORY_BUDGET_BYTES = 256 * 1024;
67416
+ var CHAT_ENV_ADDITIONS = /* @__PURE__ */ new Set(["GOOSE_MODE"]);
67417
+ var UNSUPPORTED2 = { supported: false, code: "unsupported" };
67418
+ var CHAT_ADAPTERS = [
67419
+ claudeChatAdapter,
67420
+ codexChatAdapter,
67421
+ opencodeChatAdapter,
67422
+ kimiAdapter,
67423
+ gooseChatAdapter
67424
+ ];
67425
+ function resolveChatAdapter(profile, adapters = CHAT_ADAPTERS) {
67426
+ if (!profile) return null;
67427
+ return adapters.find((adapter) => adapter.profile === profile) ?? null;
67428
+ }
67429
+
67430
+ // src/agentTerminal/chat/turns.ts
67431
+ import { spawn as spawnChild2 } from "child_process";
67432
+ import { StringDecoder } from "string_decoder";
67433
+ var STOP_GRACE_MS = 5e3;
67434
+ var TURN_RING_BYTES = 256 * 1024;
67435
+ var MAX_LINE_CHARS = 8 * 1024 * 1024;
67436
+ var BARE_BINARY = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
67437
+ var spawnChatProcess = (options) => {
67438
+ const child = spawnChild2(options.file, options.args, {
67439
+ cwd: options.cwd,
67440
+ // The app's tsconfig widens `ProcessEnv` with required keys (NODE_ENV);
67441
+ // the CLI's does not. The record is a whole environment either way.
67442
+ env: options.env,
67443
+ detached: true,
67444
+ stdio: [options.stdin === null ? "ignore" : "pipe", "pipe", "pipe"]
67445
+ });
67446
+ child.stderr?.resume();
67447
+ if (options.stdin !== null && child.stdin) {
67448
+ child.stdin.on("error", () => {
67449
+ });
67450
+ child.stdin.end(options.stdin);
67451
+ }
67452
+ const listeners = [];
67453
+ let exited = null;
67454
+ const finish = (exit) => {
67455
+ if (exited) return;
67456
+ exited = exit;
67457
+ for (const listener of listeners) listener(exit);
67458
+ };
67459
+ child.on("error", () => {
67460
+ finish({ code: null, signal: null, failedToStart: child.pid === void 0 });
67461
+ });
67462
+ child.on("close", (code, signal) => finish({ code, signal }));
67463
+ return {
67464
+ pid: child.pid ?? -1,
67465
+ onStdout: (listener) => {
67466
+ child.stdout?.on("data", listener);
67467
+ },
67468
+ onExit: (listener) => {
67469
+ if (exited) listener(exited);
67470
+ else listeners.push(listener);
67471
+ },
67472
+ signal: (signal) => {
67473
+ if (child.pid === void 0 || exited) return;
67474
+ try {
67475
+ process.kill(-child.pid, signal);
67476
+ } catch {
67477
+ }
67478
+ }
67479
+ };
67480
+ };
67481
+ var EventRing = class {
67482
+ constructor(capacity = TURN_RING_BYTES) {
67483
+ this.capacity = capacity;
67484
+ }
67485
+ capacity;
67486
+ frames = [];
67487
+ bytes = 0;
67488
+ get size() {
67489
+ return this.bytes;
67490
+ }
67491
+ push(frame) {
67492
+ this.frames.push(frame);
67493
+ this.bytes += Buffer.byteLength(frame, "utf8");
67494
+ while (this.bytes > this.capacity && this.frames.length > 0) {
67495
+ this.bytes -= Buffer.byteLength(this.frames.shift(), "utf8");
67496
+ }
67497
+ }
67498
+ /** The held frames, oldest first. */
67499
+ snapshot() {
67500
+ return [...this.frames];
67501
+ }
67502
+ };
67503
+ function cutTitle4(title) {
67504
+ const chars = Array.from(title);
67505
+ return chars.length <= MAX_TITLE_CHARS3 ? title : chars.slice(0, MAX_TITLE_CHARS3).join("");
67506
+ }
67507
+ function createChatHub(options) {
67508
+ const { adapter, ctx, log } = options;
67509
+ const now = options.now ?? Date.now;
67510
+ const stopGraceMs = options.stopGraceMs ?? STOP_GRACE_MS;
67511
+ const ringBytes = options.ringBytes ?? TURN_RING_BYTES;
67512
+ const connections = /* @__PURE__ */ new Set();
67513
+ const views = /* @__PURE__ */ new Map();
67514
+ let current = null;
67515
+ let turnCounter = 0;
67516
+ let closing = false;
67517
+ const send = (connection, frame) => {
67518
+ if (connection.socket.isOpen) connection.socket.send(encodeChatServerFrame(frame));
67519
+ };
67520
+ const refuse = (connection, code, what) => {
67521
+ send(connection, { t: "error", code });
67522
+ log(`agent-chat: ${what} refused (${code})`);
67523
+ };
67524
+ const support = async () => {
67525
+ if (!adapter) return UNSUPPORTED2;
67526
+ try {
67527
+ return await adapter.support(ctx);
67528
+ } catch {
67529
+ return UNSUPPORTED2;
67530
+ }
67531
+ };
67532
+ const readSignIn = async () => {
67533
+ try {
67534
+ return await options.readSignIn();
67535
+ } catch {
67536
+ return { profile: options.profile, state: "unknown" };
67537
+ }
67538
+ };
67539
+ const forget = (view) => {
67540
+ if (view.holder || view.turn || view.sessionId === null) return;
67541
+ if (views.get(view.sessionId) === view) views.delete(view.sessionId);
67542
+ };
67543
+ const release = (connection) => {
67544
+ const view = connection.view;
67545
+ connection.view = null;
67546
+ if (!view || view.holder !== connection) return;
67547
+ view.holder = null;
67548
+ view.live = false;
67549
+ forget(view);
67550
+ };
67551
+ const emit = (turn, event) => {
67552
+ const frame = encodeChatServerFrame({ t: "event", turn: turn.id, e: boundEvent(event) });
67553
+ turn.ring.push(frame);
67554
+ const holder = turn.view.holder;
67555
+ if (holder && turn.view.live && holder.socket.isOpen) holder.socket.send(frame);
67556
+ };
67557
+ const revealSession = (turn) => {
67558
+ const view = turn.view;
67559
+ let id;
67560
+ try {
67561
+ id = turn.mapper?.sessionId() ?? null;
67562
+ } catch {
67563
+ id = null;
67564
+ }
67565
+ if (id === null || id === view.sessionId || !isSessionId(id)) return;
67566
+ if (view.sessionId !== null && views.get(view.sessionId) === view) views.delete(view.sessionId);
67567
+ view.sessionId = id;
67568
+ views.set(id, view);
67569
+ const holder = view.holder;
67570
+ if (holder && view.live) send(holder, { t: "session", id });
67571
+ };
67572
+ const endTurn = (turn, exit) => {
67573
+ if (turn.ended) return;
67574
+ turn.ended = true;
67575
+ if (turn.killTimer) clearTimeout(turn.killTimer);
67576
+ turn.killTimer = null;
67577
+ let sawEnd = false;
67578
+ try {
67579
+ sawEnd = turn.mapper?.sawEnd() ?? false;
67580
+ } catch {
67581
+ sawEnd = false;
67582
+ }
67583
+ let end;
67584
+ if (turn.stopRequested) {
67585
+ end = { k: "turn_end", reason: "stopped" };
67586
+ } else if (turn.failCode) {
67587
+ end = { k: "turn_end", reason: "failed", code: turn.failCode };
67588
+ } else if (exit.failedToStart) {
67589
+ end = { k: "turn_end", reason: "failed", code: "spawn_failed" };
67590
+ } else if (exit.code === 0 && sawEnd) {
67591
+ end = { k: "turn_end", reason: "completed" };
67592
+ } else if (exit.code === 0) {
67593
+ end = { k: "turn_end", reason: "failed", code: "no_end" };
67594
+ } else {
67595
+ end = { k: "turn_end", reason: "failed", code: "exit_nonzero" };
67596
+ }
67597
+ emit(turn, end);
67598
+ if (current === turn) current = null;
67599
+ const view = turn.view;
67600
+ if (view.turn === turn) view.turn = null;
67601
+ forget(view);
67602
+ const code = end.code ? ` ${end.code}` : "";
67603
+ log(`agent-chat: turn ${turn.id} ended (${end.reason}${code}, ${now() - turn.startedAt} ms)`);
67604
+ };
67605
+ const onLine = (turn, line) => {
67606
+ if (turn.ended || !turn.mapper || turn.failCode) return;
67607
+ let events;
67608
+ try {
67609
+ events = turn.mapper.onLine(line);
67610
+ } catch {
67611
+ events = [];
67612
+ }
67613
+ revealSession(turn);
67614
+ for (const event of events) {
67615
+ if (event.k !== "turn_end") emit(turn, event);
67616
+ }
67617
+ let killCode = null;
67618
+ try {
67619
+ killCode = turn.mapper.killCode();
67620
+ } catch {
67621
+ killCode = null;
67622
+ }
67623
+ if (killCode && !turn.failCode) {
67624
+ turn.failCode = killCode;
67625
+ turn.proc?.signal("SIGKILL");
67626
+ log(`agent-chat: turn ${turn.id} killed by its adapter (${killCode})`);
67627
+ }
67628
+ };
67629
+ const turnEnv = (additions) => {
67630
+ const env = { ...ctx.env };
67631
+ for (const [name, value] of Object.entries(additions ?? {})) {
67632
+ if (!CHAT_ENV_ADDITIONS.has(name)) throw new Error("env addition refused");
67633
+ env[name] = value;
67634
+ }
67635
+ delete env["MOTIR_TERMINAL_KEY"];
67636
+ return env;
67637
+ };
67638
+ const startTurn = (view, prompt) => {
67639
+ const turn = {
67640
+ id: ++turnCounter,
67641
+ view,
67642
+ proc: null,
67643
+ mapper: null,
67644
+ ring: new EventRing(ringBytes),
67645
+ startedAt: now(),
67646
+ stopRequested: false,
67647
+ failCode: null,
67648
+ killTimer: null,
67649
+ ended: false
67650
+ };
67651
+ current = turn;
67652
+ view.turn = turn;
67653
+ emit(turn, { k: "user", text: prompt });
67654
+ let proc;
67655
+ try {
67656
+ const command = adapter.turnCommand({
67657
+ prompt,
67658
+ sessionId: view.sessionId,
67659
+ ctx
67660
+ });
67661
+ if (!BARE_BINARY.test(command.file)) throw new Error("binary refused");
67662
+ const env = turnEnv(command.env);
67663
+ turn.mapper = adapter.createMapper();
67664
+ revealSession(turn);
67665
+ proc = options.spawn({
67666
+ file: command.file,
67667
+ args: [...command.args],
67668
+ cwd: ctx.cwd,
67669
+ env,
67670
+ stdin: command.stdin
67671
+ });
67672
+ } catch {
67673
+ log(`agent-chat: turn ${turn.id} failed to start`);
67674
+ endTurn(turn, { code: null, signal: null, failedToStart: true });
67675
+ return;
67676
+ }
67677
+ turn.proc = proc;
67678
+ const decoder = new StringDecoder("utf8");
67679
+ let partial = "";
67680
+ const feed = (text) => {
67681
+ partial += text;
67682
+ let newline = partial.indexOf("\n");
67683
+ while (newline !== -1) {
67684
+ const line = partial.slice(0, newline).replace(/\r$/, "");
67685
+ partial = partial.slice(newline + 1);
67686
+ if (line.length > 0) onLine(turn, line);
67687
+ newline = partial.indexOf("\n");
67688
+ }
67689
+ if (partial.length > MAX_LINE_CHARS) partial = "";
67690
+ };
67691
+ proc.onStdout((chunk) => feed(decoder.write(chunk)));
67692
+ proc.onExit((exit) => {
67693
+ feed(decoder.end());
67694
+ if (partial.length > 0) onLine(turn, partial);
67695
+ partial = "";
67696
+ endTurn(turn, exit);
67697
+ });
67698
+ log(`agent-chat: turn ${turn.id} started (pid ${proc.pid})`);
67699
+ if (closing) {
67700
+ turn.failCode = "shutdown";
67701
+ proc.signal("SIGKILL");
67702
+ }
67703
+ };
67704
+ const signInFor = async (verdict) => verdict.supported && verdict.signedIn ? "signed_in" : (await readSignIn()).state;
67705
+ const hello = async (connection) => {
67706
+ const verdict = await support();
67707
+ send(connection, {
67708
+ t: "hello",
67709
+ profile: options.profile,
67710
+ supported: verdict.supported,
67711
+ ...verdict.supported ? {} : { reason: verdict.code },
67712
+ signin: await signInFor(verdict)
67713
+ });
67714
+ };
67715
+ const onList = async (connection) => {
67716
+ if (!adapter) return refuse(connection, "unsupported", "list");
67717
+ let items;
67718
+ try {
67719
+ items = await adapter.listSessions(ctx, MAX_LISTED_SESSIONS);
67720
+ } catch {
67721
+ items = [];
67722
+ }
67723
+ send(connection, {
67724
+ t: "sessions",
67725
+ items: items.slice(0, MAX_LISTED_SESSIONS).map((item) => ({
67726
+ id: item.id,
67727
+ title: cutTitle4(item.title),
67728
+ updatedAt: item.updatedAt
67729
+ }))
67730
+ });
67731
+ };
67732
+ const onOpen = async (connection, session) => {
67733
+ if (!adapter) return refuse(connection, "unsupported", "open");
67734
+ if (session !== void 0 && !isSessionId(session)) {
67735
+ return refuse(connection, "unknown_session", "open");
67736
+ }
67737
+ release(connection);
67738
+ if (session === void 0) {
67739
+ const view2 = { sessionId: null, holder: connection, live: true, turn: null };
67740
+ connection.view = view2;
67741
+ send(connection, { t: "ready", session: null, resumed: false });
67742
+ log("agent-chat: chat opened");
67743
+ return;
67744
+ }
67745
+ let view = views.get(session);
67746
+ if (!view) {
67747
+ view = { sessionId: session, holder: null, live: false, turn: null };
67748
+ views.set(session, view);
67749
+ }
67750
+ const previous = view.holder;
67751
+ if (previous && previous !== connection) {
67752
+ previous.view = null;
67753
+ send(previous, { t: "error", code: "taken_over" });
67754
+ log("agent-chat: chat taken over");
67755
+ }
67756
+ view.holder = connection;
67757
+ view.live = false;
67758
+ connection.view = view;
67759
+ send(connection, { t: "ready", session, resumed: true });
67760
+ let history;
67761
+ try {
67762
+ history = await adapter.readHistory(ctx, session, HISTORY_BUDGET_BYTES);
67763
+ } catch {
67764
+ history = { unavailable: true };
67765
+ }
67766
+ if (view.holder !== connection) return;
67767
+ send(
67768
+ connection,
67769
+ "unavailable" in history ? { t: "history", events: [], truncated: true } : {
67770
+ t: "history",
67771
+ events: history.events.map(boundEvent),
67772
+ truncated: history.truncated
67773
+ }
67774
+ );
67775
+ if (view.turn && connection.socket.isOpen) {
67776
+ for (const frame of view.turn.ring.snapshot()) connection.socket.send(frame);
67777
+ }
67778
+ view.live = true;
67779
+ log(`agent-chat: chat resumed${view.turn ? ` (turn ${view.turn.id} running)` : ""}`);
67780
+ };
67781
+ const onPrompt = async (connection, prompt) => {
67782
+ const view = connection.view;
67783
+ if (!view) return refuse(connection, "bad_frame", "prompt");
67784
+ const verdict = await support();
67785
+ if (!verdict.supported) return refuse(connection, verdict.code, "prompt");
67786
+ if (await signInFor(verdict) === "signed_out") {
67787
+ return refuse(connection, "not_signed_in", "prompt");
67788
+ }
67789
+ if (current) return refuse(connection, "turn_running", "prompt");
67790
+ if (closing || connection.view !== view) return;
67791
+ startTurn(view, prompt);
67792
+ };
67793
+ const onStop = (connection) => {
67794
+ const turn = current;
67795
+ if (!turn || turn.ended || turn.view !== connection.view || !turn.proc) {
67796
+ return refuse(connection, "no_turn", "stop");
67797
+ }
67798
+ if (turn.stopRequested) return;
67799
+ turn.stopRequested = true;
67800
+ log(`agent-chat: turn ${turn.id} stopping`);
67801
+ turn.proc.signal("SIGINT");
67802
+ const proc = turn.proc;
67803
+ turn.killTimer = setTimeout(() => {
67804
+ turn.killTimer = null;
67805
+ if (turn.ended) return;
67806
+ proc.signal("SIGKILL");
67807
+ log(`agent-chat: turn ${turn.id} killed (stop grace elapsed)`);
67808
+ }, stopGraceMs);
67809
+ turn.killTimer.unref();
67810
+ };
67811
+ const enqueue = (connection, task) => {
67812
+ connection.queue = connection.queue.then(task).catch(() => log("agent-chat: frame handling failed"));
67813
+ };
67814
+ return {
67815
+ accept(socket) {
67816
+ const connection = { socket, view: null, queue: Promise.resolve() };
67817
+ connections.add(connection);
67818
+ log("agent-chat: connection opened");
67819
+ enqueue(connection, () => hello(connection));
67820
+ return {
67821
+ message(data, isBinary) {
67822
+ const parsed = isBinary ? { ok: false, code: "bad_frame" } : parseChatClientFrame(data.toString("utf8"));
67823
+ if (!parsed.ok) {
67824
+ enqueue(connection, () => refuse(connection, parsed.code, "frame"));
67825
+ return;
67826
+ }
67827
+ const frame = parsed.frame;
67828
+ switch (frame.t) {
67829
+ case "ping":
67830
+ enqueue(connection, () => send(connection, { t: "pong" }));
67831
+ return;
67832
+ case "list":
67833
+ enqueue(connection, () => onList(connection));
67834
+ return;
67835
+ case "open":
67836
+ enqueue(connection, () => onOpen(connection, frame.session));
67837
+ return;
67838
+ case "prompt":
67839
+ enqueue(connection, () => onPrompt(connection, frame.text));
67840
+ return;
67841
+ case "stop":
67842
+ enqueue(connection, () => onStop(connection));
67843
+ return;
67844
+ }
67845
+ },
67846
+ closed() {
67847
+ connections.delete(connection);
67848
+ release(connection);
67849
+ }
67850
+ };
67851
+ },
67852
+ pushSignIn(status) {
67853
+ for (const connection of connections) {
67854
+ send(connection, { t: "signin", profile: status.profile, state: status.state });
67855
+ }
67856
+ },
67857
+ runningTurn: () => current?.id ?? null,
67858
+ connectionCount: () => connections.size,
67859
+ close() {
67860
+ closing = true;
67861
+ const turn = current;
67862
+ if (turn && !turn.ended) {
67863
+ turn.failCode ??= "shutdown";
67864
+ if (turn.killTimer) clearTimeout(turn.killTimer);
67865
+ turn.killTimer = null;
67866
+ turn.proc?.signal("SIGKILL");
67867
+ }
67868
+ connections.clear();
67869
+ }
67870
+ };
67871
+ }
65504
67872
 
65505
67873
  // src/agentTerminal/outputRing.ts
65506
67874
  var REPLAY_BYTES = 256 * 1024;
@@ -65665,24 +68033,31 @@ var NonceMemory = class {
65665
68033
  // src/agentTerminal/signIn.ts
65666
68034
  import { stat as fsStat } from "fs/promises";
65667
68035
  import { homedir as homedir5 } from "os";
65668
- import { join as join19 } from "path";
68036
+ import { join as join22 } from "path";
65669
68037
  function credentialDirsFromEnv(env, home = env["HOME"] || homedir5()) {
65670
68038
  return {
65671
68039
  home,
65672
- xdgConfigHome: env["XDG_CONFIG_HOME"] || join19(home, ".config"),
65673
- xdgDataHome: env["XDG_DATA_HOME"] || join19(home, ".local", "share"),
68040
+ xdgConfigHome: env["XDG_CONFIG_HOME"] || join22(home, ".config"),
68041
+ xdgDataHome: env["XDG_DATA_HOME"] || join22(home, ".local", "share"),
65674
68042
  configHome: (name) => env[name] || void 0
65675
68043
  };
65676
68044
  }
65677
- async function checkSignIn(env, stat = fsStat) {
68045
+ function hasEnv(env, name) {
68046
+ const value = env[name];
68047
+ return typeof value === "string" && value.length > 0;
68048
+ }
68049
+ async function checkSignIn(env, stat3 = fsStat) {
65678
68050
  const id = env["MOTIR_SANDBOX_AGENT"]?.trim() || null;
65679
68051
  const profile = id ? AGENT_PROFILES.find((candidate) => candidate.id === id) : void 0;
65680
68052
  if (!profile) return { profile: id, state: "unknown" };
68053
+ if (profile.credentialEnv.some((name) => hasEnv(env, name))) {
68054
+ return { profile: id, state: "signed_in" };
68055
+ }
65681
68056
  const paths = profile.credentialPaths(credentialDirsFromEnv(env));
65682
68057
  if (paths.length === 0) return { profile: id, state: "unknown" };
65683
68058
  for (const path of paths) {
65684
68059
  try {
65685
- const info2 = await stat(path);
68060
+ const info2 = await stat3(path);
65686
68061
  if (info2.isFile() && info2.size > 0) return { profile: id, state: "signed_in" };
65687
68062
  } catch {
65688
68063
  }
@@ -65929,9 +68304,17 @@ function shellEnv(env) {
65929
68304
  out2["COLORTERM"] = "truecolor";
65930
68305
  return out2;
65931
68306
  }
68307
+ function chatEnv(env) {
68308
+ const out2 = {};
68309
+ for (const [name, value] of Object.entries(env)) {
68310
+ if (name === TERMINAL_KEY_ENV || value === void 0) continue;
68311
+ out2[name] = value;
68312
+ }
68313
+ return out2;
68314
+ }
65932
68315
  function shellCwd(env) {
65933
68316
  const home = env["HOME"] || "/";
65934
- const workspace = join20(home, "workspace");
68317
+ const workspace = join23(home, "workspace");
65935
68318
  try {
65936
68319
  if (existsSync15(workspace) && statSync3(workspace).isDirectory()) return workspace;
65937
68320
  } catch {
@@ -65957,6 +68340,23 @@ function createTerminalServer(options) {
65957
68340
  let signInTimer = null;
65958
68341
  let lastSignIn = null;
65959
68342
  let closing = false;
68343
+ const profile = options.env["MOTIR_SANDBOX_AGENT"]?.trim() || null;
68344
+ const chatContext = {
68345
+ home: options.env["HOME"] || "/",
68346
+ cwd: shellCwd(options.env),
68347
+ env: chatEnv(options.env)
68348
+ };
68349
+ const chat = createChatHub({
68350
+ profile,
68351
+ adapter: resolveChatAdapter(profile, options.chatAdapters),
68352
+ ctx: chatContext,
68353
+ spawn: options.spawnChat ?? spawnChatProcess,
68354
+ readSignIn: () => readSignIn(),
68355
+ log,
68356
+ now,
68357
+ ...options.chatStopGraceMs !== void 0 ? { stopGraceMs: options.chatStopGraceMs } : {}
68358
+ });
68359
+ const chatSockets = /* @__PURE__ */ new Set();
65960
68360
  const send = (attachment, frame) => {
65961
68361
  if (attachment.ws.isOpen) attachment.ws.send(encodeServerFrame(frame));
65962
68362
  };
@@ -65974,9 +68374,10 @@ function createTerminalServer(options) {
65974
68374
  if (closing || sameState(lastSignIn, status)) return;
65975
68375
  lastSignIn = status;
65976
68376
  pushSignIn(status, attachedConnections());
68377
+ chat.pushSignIn(status);
65977
68378
  };
65978
68379
  const syncSignInTimer = () => {
65979
- const wanted = attachedCount() > 0 && !closing;
68380
+ const wanted = (attachedCount() > 0 || chatSockets.size > 0) && !closing;
65980
68381
  if (wanted && signInTimer === null) {
65981
68382
  signInTimer = setInterval(() => void pollSignIn(), signInIntervalMs);
65982
68383
  signInTimer.unref();
@@ -66053,7 +68454,7 @@ function createTerminalServer(options) {
66053
68454
  };
66054
68455
  const registerSession = (pty, kind, runId, onExit) => {
66055
68456
  const session = {
66056
- id: randomUUID(),
68457
+ id: randomUUID2(),
66057
68458
  kind,
66058
68459
  ...runId === void 0 ? {} : { runId },
66059
68460
  pty,
@@ -66145,8 +68546,8 @@ function createTerminalServer(options) {
66145
68546
  return { ok: false, code: "spawn_failed" };
66146
68547
  }
66147
68548
  let markExited = () => void 0;
66148
- const exited = new Promise((resolve6) => {
66149
- markExited = resolve6;
68549
+ const exited = new Promise((resolve8) => {
68550
+ markExited = resolve8;
66150
68551
  });
66151
68552
  const record = {
66152
68553
  runId,
@@ -66195,7 +68596,7 @@ function createTerminalServer(options) {
66195
68596
  }
66196
68597
  await Promise.race([
66197
68598
  record.exited,
66198
- new Promise((resolve6) => setTimeout(resolve6, stopGraceMs + 5e3).unref())
68599
+ new Promise((resolve8) => setTimeout(resolve8, stopGraceMs + 5e3).unref())
66199
68600
  ]);
66200
68601
  }
66201
68602
  return { ok: true, result: "stopped" };
@@ -66260,9 +68661,8 @@ function createTerminalServer(options) {
66260
68661
  const onUpgrade = (req, socket, head) => {
66261
68662
  socket.on("error", () => socket.destroy());
66262
68663
  const path = (req.url ?? "/").split("?")[0];
66263
- if (path !== TERMINAL_PATH) {
68664
+ if (path !== TERMINAL_PATH && path !== CHAT_PATH) {
66264
68665
  refuseUpgrade(socket, 404, "Not Found");
66265
- if (path === CHAT_PATH) log("agent-terminal: upgrade refused (chat not served)");
66266
68666
  return;
66267
68667
  }
66268
68668
  if (!isWebSocketUpgrade(req)) {
@@ -66286,6 +68686,10 @@ function createTerminalServer(options) {
66286
68686
  return;
66287
68687
  }
66288
68688
  const ws = acceptWebSocket(req, socket, head);
68689
+ if (path === CHAT_PATH) {
68690
+ acceptChat(ws);
68691
+ return;
68692
+ }
66289
68693
  const attachment = { ws, session: null, tokenSession: verdict.payload.sessionId };
66290
68694
  connections.add(attachment);
66291
68695
  log("agent-terminal: connection opened");
@@ -66306,8 +68710,21 @@ function createTerminalServer(options) {
66306
68710
  log(`agent-terminal: connection closed (${code})`);
66307
68711
  });
66308
68712
  };
68713
+ const acceptChat = (ws) => {
68714
+ chatSockets.add(ws);
68715
+ syncSignInTimer();
68716
+ const handle = chat.accept(ws);
68717
+ ws.on("message", (data, isBinary) => handle.message(data, isBinary));
68718
+ ws.on("close", (code) => {
68719
+ chatSockets.delete(ws);
68720
+ handle.closed();
68721
+ syncSignInTimer();
68722
+ log(`agent-chat: connection closed (${code})`);
68723
+ });
68724
+ };
66309
68725
  const server = createServer2((req, res) => {
66310
- res.statusCode = req.url?.startsWith(TERMINAL_PATH) ? 426 : 404;
68726
+ const path = (req.url ?? "/").split("?")[0];
68727
+ res.statusCode = path === TERMINAL_PATH || path === CHAT_PATH ? 426 : 404;
66311
68728
  res.end();
66312
68729
  });
66313
68730
  server.on("upgrade", onUpgrade);
@@ -66315,14 +68732,14 @@ function createTerminalServer(options) {
66315
68732
  return {
66316
68733
  server,
66317
68734
  listen(port, host = "0.0.0.0") {
66318
- return new Promise((resolve6, reject) => {
68735
+ return new Promise((resolve8, reject) => {
66319
68736
  server.once("error", reject);
66320
68737
  server.listen(port, host, () => {
66321
68738
  server.off("error", reject);
66322
68739
  const address = server.address();
66323
68740
  const bound = typeof address === "object" && address ? address.port : port;
66324
68741
  log(`agent-terminal: listening on ${host}:${bound}`);
66325
- resolve6(bound);
68742
+ resolve8(bound);
66326
68743
  });
66327
68744
  });
66328
68745
  },
@@ -66347,9 +68764,13 @@ function createTerminalServer(options) {
66347
68764
  }
66348
68765
  for (const attachment of connections) attachment.ws.terminate();
66349
68766
  connections.clear();
66350
- await new Promise((resolve6) => server.close(() => resolve6()));
68767
+ chat.close();
68768
+ for (const ws of chatSockets) ws.terminate();
68769
+ chatSockets.clear();
68770
+ await new Promise((resolve8) => server.close(() => resolve8()));
66351
68771
  },
66352
- sessionCount: () => sessions.size
68772
+ sessionCount: () => sessions.size,
68773
+ chatTurn: () => chat.runningTurn()
66353
68774
  };
66354
68775
  }
66355
68776
 
@@ -66405,7 +68826,7 @@ function print(deps, value) {
66405
68826
  }
66406
68827
  function readStdinBounded(stream = process.stdin) {
66407
68828
  if (stream.isTTY) return Promise.resolve("");
66408
- return new Promise((resolve6, reject) => {
68829
+ return new Promise((resolve8, reject) => {
66409
68830
  const chunks = [];
66410
68831
  let size = 0;
66411
68832
  stream.on("data", (chunk) => {
@@ -66418,7 +68839,7 @@ function readStdinBounded(stream = process.stdin) {
66418
68839
  }
66419
68840
  chunks.push(buf);
66420
68841
  });
66421
- stream.on("end", () => resolve6(Buffer.concat(chunks).toString("utf8")));
68842
+ stream.on("end", () => resolve8(Buffer.concat(chunks).toString("utf8")));
66422
68843
  stream.on(
66423
68844
  "error",
66424
68845
  () => reject(new CliError("Could not read the run\u2019s credentials on stdin."))
@@ -66487,7 +68908,7 @@ async function agentTerminalRunCommand(key, opts, deps = {}) {
66487
68908
  }
66488
68909
  chmodSync5(dir, 448);
66489
68910
  const access = { apiUrl: credentials.apiUrl, runId, token: credentials.token };
66490
- const runFile = join21(dir, "run.json");
68911
+ const runFile = join24(dir, "run.json");
66491
68912
  writeFileSync8(runFile, `${JSON.stringify(access)}
66492
68913
  `, { mode: 384 });
66493
68914
  chmodSync5(runFile, 384);
@@ -67074,13 +69495,13 @@ function buildProgram() {
67074
69495
  // src/staleness.ts
67075
69496
  import { execFileSync } from "child_process";
67076
69497
  import { accessSync as accessSync3, constants as constants2, mkdirSync as mkdirSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync9 } from "fs";
67077
- import { dirname as dirname8, join as join22 } from "path";
69498
+ import { dirname as dirname8, join as join25 } from "path";
67078
69499
  var CLI_PACKAGE = "@motir/cli";
67079
69500
  var REGISTRY_URL = `https://registry.npmjs.org/${CLI_PACKAGE}/latest`;
67080
69501
  var REGISTRY_TIMEOUT_MS = 1500;
67081
69502
  var CACHE_TTL_MS = 6 * 60 * 60 * 1e3;
67082
69503
  function versionCachePath() {
67083
- return join22(stateDir(), "version-check.json");
69504
+ return join25(stateDir(), "version-check.json");
67084
69505
  }
67085
69506
  function compareVersions(a, b) {
67086
69507
  const parts = (value) => value.split(/[-+]/)[0].split(".").map((piece) => Number.parseInt(piece, 10)).map((piece) => Number.isFinite(piece) ? piece : 0);
@@ -67134,7 +69555,7 @@ function writeVersionCache(latest, now, path = versionCachePath()) {
67134
69555
  }
67135
69556
  }
67136
69557
  function globalPrefixDir(execPath = process.execPath) {
67137
- return join22(dirname8(dirname8(execPath)), "lib", "node_modules");
69558
+ return join25(dirname8(dirname8(execPath)), "lib", "node_modules");
67138
69559
  }
67139
69560
  function isWritable(path) {
67140
69561
  try {