@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.
- package/dist/index.js +2826 -405
- 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.
|
|
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((
|
|
55662
|
+
return new Promise((resolve8) => {
|
|
55663
55663
|
rl.question(`${question}${suffix}: `, (answer) => {
|
|
55664
55664
|
rl.close();
|
|
55665
55665
|
const trimmed = answer.trim();
|
|
55666
|
-
|
|
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((
|
|
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
|
-
|
|
55677
|
+
resolve8(answer);
|
|
55678
55678
|
});
|
|
55679
55679
|
rl.on("close", () => {
|
|
55680
|
-
if (!answered)
|
|
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((
|
|
55689
|
+
return new Promise((resolve8) => {
|
|
55690
55690
|
rl.question("", (answer) => {
|
|
55691
55691
|
rl.close();
|
|
55692
55692
|
process.stderr.write("\n");
|
|
55693
|
-
|
|
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((
|
|
55852
|
+
return new Promise((resolve8) => {
|
|
55853
55853
|
try {
|
|
55854
55854
|
const child = spawn(cmd, [...args, url], { stdio: "ignore", detached: true });
|
|
55855
|
-
child.on("error", () =>
|
|
55855
|
+
child.on("error", () => resolve8(false));
|
|
55856
55856
|
child.unref();
|
|
55857
|
-
setTimeout(() =>
|
|
55857
|
+
setTimeout(() => resolve8(true), 0);
|
|
55858
55858
|
} catch {
|
|
55859
|
-
|
|
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
|
|
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
|
|
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
|
|
56782
|
+
const lines2 = [`${sprint.name} [${sprint.state}]`];
|
|
56783
56783
|
const window = formatSprintWindow(sprint);
|
|
56784
|
-
if (window)
|
|
56785
|
-
if (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
|
-
|
|
56789
|
+
lines2.push(`committed: ${issues} issues \xB7 ${points} points`);
|
|
56790
56790
|
}
|
|
56791
|
-
return
|
|
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
|
|
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
|
-
|
|
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)
|
|
56865
|
-
if (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
|
-
|
|
56867
|
+
lines2.push("Sprint: (no sprints)");
|
|
56868
56868
|
} else {
|
|
56869
|
-
|
|
56869
|
+
lines2.push("Sprint: (none active)");
|
|
56870
56870
|
}
|
|
56871
|
-
return
|
|
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
|
|
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)
|
|
56960
|
+
if (legend) lines2.push(legend);
|
|
56961
56961
|
const cycle = cycleMembers(waves);
|
|
56962
56962
|
if (cycle.length > 0) {
|
|
56963
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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, `${
|
|
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
|
|
58640
|
+
const lines2 = ["motir doctor \u2014 BYOK preflight", ""];
|
|
58641
58641
|
for (const check of report.checks) {
|
|
58642
|
-
|
|
58642
|
+
lines2.push(
|
|
58643
58643
|
`${check.status.toUpperCase().padEnd(4)} ${check.label.padEnd(labelWidth)} ${check.detail}`
|
|
58644
58644
|
);
|
|
58645
|
-
if (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
|
-
|
|
58651
|
+
lines2.push(
|
|
58652
58652
|
"",
|
|
58653
58653
|
report.ok ? `All hard checks passed \u2014 ${parts.join(", ")}.` : `${parts.join(", ")}.`
|
|
58654
58654
|
);
|
|
58655
|
-
return
|
|
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((
|
|
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
|
-
|
|
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
|
|
59254
|
-
for (const repo of result.cloned)
|
|
59253
|
+
const lines2 = [];
|
|
59254
|
+
for (const repo of result.cloned) lines2.push(`Cloned: ${repo}`);
|
|
59255
59255
|
for (const failure2 of result.failures) {
|
|
59256
|
-
|
|
59257
|
-
if (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
|
-
|
|
59261
|
-
|
|
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
|
|
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
|
|
59310
|
+
const lines2 = [];
|
|
59311
59311
|
if (references.length > 0) {
|
|
59312
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
59376
|
-
const
|
|
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
|
|
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
|
-
|
|
59391
|
-
|
|
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)
|
|
59396
|
+
if (label2) lines2.push(` ${label2}`);
|
|
59394
59397
|
if (t.reason === "bootstrap_root") {
|
|
59395
|
-
|
|
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
|
|
59404
|
+
return lines2;
|
|
59402
59405
|
}
|
|
59403
59406
|
function renderDispatchSummary(input) {
|
|
59404
59407
|
const { key, title, dispatch, target, agent } = input;
|
|
59405
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
60037
|
-
|
|
60038
|
-
|
|
60039
|
-
|
|
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
|
-
|
|
60043
|
-
|
|
60044
|
-
|
|
60045
|
+
lines2.push("");
|
|
60046
|
+
lines2.push("## The failing pull requests");
|
|
60047
|
+
lines2.push("");
|
|
60045
60048
|
for (const d of input.failing) {
|
|
60046
|
-
|
|
60049
|
+
lines2.push(`- **${d.repo}#${d.number}** (base \`${d.baseRef ?? d.defaultBranch}\`) \u2014 ${d.url}`);
|
|
60047
60050
|
}
|
|
60048
|
-
|
|
60051
|
+
lines2.push("");
|
|
60049
60052
|
if (input.checkouts && input.checkouts.length > 0) {
|
|
60050
|
-
|
|
60051
|
-
|
|
60053
|
+
lines2.push("## Where each branch is checked out");
|
|
60054
|
+
lines2.push("");
|
|
60052
60055
|
for (const c of input.checkouts) {
|
|
60053
|
-
|
|
60056
|
+
lines2.push(`- **${c.repo}** \u2014 branch \`${c.branch}\` at \`${c.path}\``);
|
|
60054
60057
|
}
|
|
60055
|
-
|
|
60056
|
-
|
|
60057
|
-
|
|
60058
|
-
|
|
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
|
-
|
|
60063
|
-
|
|
60064
|
-
|
|
60065
|
-
|
|
60066
|
-
|
|
60067
|
-
|
|
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
|
-
|
|
60074
|
+
lines2.push(`- **${d.repo}#${d.number}** \u2014 ${queueReasonInWords(exit.rawReason)}`);
|
|
60072
60075
|
if (exit.failingCheckName) {
|
|
60073
|
-
|
|
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
|
-
|
|
60079
|
-
|
|
60080
|
-
|
|
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
|
-
|
|
60083
|
-
|
|
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
|
-
|
|
60086
|
-
|
|
60087
|
-
|
|
60088
|
-
|
|
60089
|
-
|
|
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
|
-
|
|
60092
|
-
}
|
|
60093
|
-
|
|
60094
|
-
|
|
60095
|
-
|
|
60096
|
-
|
|
60097
|
-
|
|
60098
|
-
|
|
60099
|
-
|
|
60100
|
-
|
|
60101
|
-
|
|
60102
|
-
|
|
60103
|
-
|
|
60104
|
-
|
|
60105
|
-
|
|
60106
|
-
|
|
60107
|
-
|
|
60108
|
-
|
|
60109
|
-
|
|
60110
|
-
|
|
60111
|
-
|
|
60112
|
-
|
|
60113
|
-
|
|
60114
|
-
|
|
60115
|
-
|
|
60116
|
-
|
|
60117
|
-
|
|
60118
|
-
|
|
60119
|
-
|
|
60120
|
-
|
|
60121
|
-
|
|
60122
|
-
|
|
60123
|
-
|
|
60124
|
-
|
|
60125
|
-
|
|
60126
|
-
|
|
60127
|
-
|
|
60128
|
-
|
|
60129
|
-
|
|
60130
|
-
|
|
60131
|
-
|
|
60132
|
-
|
|
60133
|
-
|
|
60134
|
-
|
|
60135
|
-
|
|
60136
|
-
|
|
60137
|
-
|
|
60138
|
-
|
|
60139
|
-
|
|
60140
|
-
|
|
60141
|
-
|
|
60142
|
-
|
|
60143
|
-
|
|
60144
|
-
|
|
60145
|
-
|
|
60146
|
-
|
|
60147
|
-
|
|
60148
|
-
return
|
|
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
|
|
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"))
|
|
60158
|
+
for (const line of reason.split("\n")) lines2.push(`> ${line}`);
|
|
60156
60159
|
} else {
|
|
60157
|
-
|
|
60160
|
+
lines2.push("> (no reason was recorded)");
|
|
60158
60161
|
}
|
|
60159
|
-
return
|
|
60162
|
+
return lines2;
|
|
60160
60163
|
}
|
|
60161
60164
|
function renderCheckoutList(checkouts) {
|
|
60162
|
-
const
|
|
60165
|
+
const lines2 = ["## Where each branch is checked out", ""];
|
|
60163
60166
|
for (const c of checkouts)
|
|
60164
|
-
|
|
60165
|
-
|
|
60166
|
-
|
|
60167
|
-
|
|
60168
|
-
return
|
|
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
|
|
60172
|
-
|
|
60174
|
+
const lines2 = [];
|
|
60175
|
+
lines2.push(
|
|
60173
60176
|
`# Answer the acceptance review \u2014 ${input.key}${input.title ? ` (${input.title})` : ""}`
|
|
60174
60177
|
);
|
|
60175
|
-
|
|
60176
|
-
|
|
60177
|
-
|
|
60178
|
-
|
|
60179
|
-
|
|
60180
|
-
|
|
60181
|
-
|
|
60182
|
-
|
|
60183
|
-
|
|
60184
|
-
|
|
60185
|
-
for (const pr of input.pullRequests)
|
|
60186
|
-
|
|
60187
|
-
|
|
60188
|
-
|
|
60189
|
-
|
|
60190
|
-
|
|
60191
|
-
|
|
60192
|
-
|
|
60193
|
-
|
|
60194
|
-
|
|
60195
|
-
|
|
60196
|
-
|
|
60197
|
-
|
|
60198
|
-
|
|
60199
|
-
|
|
60200
|
-
|
|
60201
|
-
|
|
60202
|
-
|
|
60203
|
-
|
|
60204
|
-
|
|
60205
|
-
|
|
60206
|
-
|
|
60207
|
-
|
|
60208
|
-
|
|
60209
|
-
|
|
60210
|
-
|
|
60211
|
-
|
|
60212
|
-
|
|
60213
|
-
return
|
|
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
|
|
60217
|
-
|
|
60219
|
+
const lines2 = [];
|
|
60220
|
+
lines2.push(
|
|
60218
60221
|
`# Re-record the acceptance video \u2014 ${input.key}${input.title ? ` (${input.title})` : ""}`
|
|
60219
60222
|
);
|
|
60220
|
-
|
|
60221
|
-
|
|
60222
|
-
|
|
60223
|
-
|
|
60224
|
-
|
|
60225
|
-
|
|
60226
|
-
|
|
60227
|
-
|
|
60228
|
-
|
|
60229
|
-
|
|
60230
|
-
|
|
60231
|
-
|
|
60232
|
-
|
|
60233
|
-
|
|
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
|
-
|
|
60237
|
-
|
|
60238
|
-
|
|
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
|
-
|
|
60242
|
-
|
|
60243
|
-
|
|
60244
|
-
|
|
60245
|
-
return
|
|
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
|
|
60252
|
-
|
|
60253
|
-
|
|
60254
|
-
|
|
60255
|
-
|
|
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
|
-
|
|
60259
|
-
|
|
60260
|
-
|
|
60261
|
-
|
|
60262
|
-
|
|
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"))
|
|
60268
|
+
for (const line of findings.split("\n")) lines2.push(`> ${line}`);
|
|
60266
60269
|
} else {
|
|
60267
|
-
|
|
60268
|
-
}
|
|
60269
|
-
|
|
60270
|
-
|
|
60271
|
-
|
|
60272
|
-
for (const pr of input.pullRequests)
|
|
60273
|
-
|
|
60274
|
-
|
|
60275
|
-
|
|
60276
|
-
|
|
60277
|
-
|
|
60278
|
-
|
|
60279
|
-
|
|
60280
|
-
|
|
60281
|
-
|
|
60282
|
-
|
|
60283
|
-
|
|
60284
|
-
|
|
60285
|
-
|
|
60286
|
-
|
|
60287
|
-
|
|
60288
|
-
|
|
60289
|
-
|
|
60290
|
-
|
|
60291
|
-
|
|
60292
|
-
|
|
60293
|
-
|
|
60294
|
-
|
|
60295
|
-
|
|
60296
|
-
|
|
60297
|
-
|
|
60298
|
-
return
|
|
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((
|
|
60302
|
-
setTimeout(
|
|
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
|
|
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
|
-
|
|
60509
|
+
lines2.push("", `## What the commits say (${commits.length})`, "");
|
|
60507
60510
|
for (const [index, commit] of commits.entries()) {
|
|
60508
|
-
if (index > 0)
|
|
60509
|
-
|
|
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)
|
|
60515
|
+
if (beneath) lines2.push("", beneath);
|
|
60513
60516
|
}
|
|
60514
60517
|
}
|
|
60515
|
-
if (howToTest)
|
|
60518
|
+
if (howToTest) lines2.push("", ...renderHowToTestSection(howToTest, branch));
|
|
60516
60519
|
if (failed.length > 0) {
|
|
60517
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
60673
|
-
for (const out2 of record.outOfLane ?? [])
|
|
60675
|
+
lines2.push(` ${record.key}${record.planId ? ` \u2014 plan ${record.planId}` : ""}`);
|
|
60676
|
+
for (const out2 of record.outOfLane ?? []) lines2.push(` ${out2}`);
|
|
60674
60677
|
}
|
|
60675
|
-
|
|
60676
|
-
blocks.push(
|
|
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
|
|
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
|
-
|
|
60708
|
+
lines2.push(` ${record.key} \u2014 re-read ${refresh.parentKey ?? "no parent"}'s children`);
|
|
60706
60709
|
if (refresh.removed.length > 0) {
|
|
60707
|
-
|
|
60710
|
+
lines2.push(` removed by the plan, NOT run: ${refresh.removed.join(", ")}`);
|
|
60708
60711
|
}
|
|
60709
60712
|
if (refresh.added.length > 0) {
|
|
60710
|
-
|
|
60713
|
+
lines2.push(` added by the plan, run when ready: ${refresh.added.join(", ")}`);
|
|
60711
60714
|
}
|
|
60712
60715
|
}
|
|
60713
|
-
blocks.push(
|
|
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
|
|
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
|
-
...
|
|
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
|
|
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
|
|
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
|
|
61531
|
+
const lines2 = [];
|
|
61529
61532
|
if (refresh.removed.length > 0) {
|
|
61530
|
-
|
|
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
|
-
|
|
61538
|
+
lines2.push(` the plan added ${refresh.added.join(", ")} \u2014 picked up when they are ready.`);
|
|
61536
61539
|
}
|
|
61537
|
-
return
|
|
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,
|
|
61870
|
-
const outstanding =
|
|
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
|
|
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 (
|
|
62039
|
-
if (prior && sameKeys(prior.blockedBy,
|
|
62041
|
+
if (open2.length > 0) {
|
|
62042
|
+
if (prior && sameKeys(prior.blockedBy, open2)) continue;
|
|
62040
62043
|
if (prior) {
|
|
62041
|
-
prior.blockedBy =
|
|
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:
|
|
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:
|
|
62059
|
+
data: { blockedBy: open2 }
|
|
62057
62060
|
});
|
|
62058
62061
|
}
|
|
62059
|
-
const gates =
|
|
62062
|
+
const gates = open2.filter((dep) => heldGates.has(dep));
|
|
62060
62063
|
info(
|
|
62061
|
-
gates.length ===
|
|
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
|
|
62483
|
+
const lines2 = ["", "\u2550\u2550\u2550\u2550 CHECKOUTS \u2550\u2550\u2550\u2550", ""];
|
|
62481
62484
|
for (const repo of repos) {
|
|
62482
|
-
|
|
62485
|
+
lines2.push(
|
|
62483
62486
|
`- ${repo.repoName ?? "(the project repository)"}: ${repo.cwd} \u2014 branch ${repo.branch}`
|
|
62484
62487
|
);
|
|
62485
62488
|
}
|
|
62486
|
-
return
|
|
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: (
|
|
62684
|
-
for (const line of
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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.`, ...
|
|
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:
|
|
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
|
|
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
|
-
|
|
63627
|
+
lines2.push(` ${pr.repo}#${pr.number} \u2014 failing: ${checks} (${pr.url})`);
|
|
63625
63628
|
}
|
|
63626
|
-
|
|
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
|
|
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
|
|
63695
|
-
for (const line of
|
|
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
|
|
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.`, ...
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 `${
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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: (
|
|
64893
|
-
for (const line of
|
|
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
|
|
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
|
-
|
|
65057
|
+
lines2.push("");
|
|
65055
65058
|
session.turns.forEach((turn, i) => {
|
|
65056
|
-
|
|
65059
|
+
lines2.push(renderTurn(turn, i + 1));
|
|
65057
65060
|
const disposition = dispositionMarkerFor(session.turns, i);
|
|
65058
|
-
if (disposition)
|
|
65061
|
+
if (disposition) lines2.push(renderDisposition(disposition));
|
|
65059
65062
|
});
|
|
65060
65063
|
}
|
|
65061
65064
|
const awaiting = renderAwaiting(session.turns);
|
|
65062
|
-
if (awaiting)
|
|
65063
|
-
return
|
|
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
|
|
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
|
-
|
|
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
|
|
65145
|
+
return lines2;
|
|
65143
65146
|
}
|
|
65144
65147
|
function renderPlan(plan) {
|
|
65145
|
-
const
|
|
65148
|
+
const lines2 = [
|
|
65146
65149
|
`Plan ${plan.id} \u2014 ${plan.status}, ${plan.itemCount} proposal${plan.itemCount === 1 ? "" : "s"}.`
|
|
65147
65150
|
];
|
|
65148
|
-
if (plan.title)
|
|
65149
|
-
if (plan.summary)
|
|
65150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65159
|
+
lines2.push(
|
|
65157
65160
|
"Proposals (indented under their proposed parent):",
|
|
65158
65161
|
...renderProposalTree(plan.items)
|
|
65159
65162
|
);
|
|
65160
65163
|
}
|
|
65161
|
-
|
|
65162
|
-
return
|
|
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((
|
|
65176
|
-
setTimeout(
|
|
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
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
|
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"] ||
|
|
65673
|
-
xdgDataHome: env["XDG_DATA_HOME"] ||
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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:
|
|
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((
|
|
66149
|
-
markExited =
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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((
|
|
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", () =>
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
69558
|
+
return join25(dirname8(dirname8(execPath)), "lib", "node_modules");
|
|
67138
69559
|
}
|
|
67139
69560
|
function isWritable(path) {
|
|
67140
69561
|
try {
|