codeam-cli 2.75.8 → 2.75.10
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/CHANGELOG.md +12 -0
- package/dist/index.js +718 -164
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2865,6 +2865,11 @@ function normalizeGuardrailPolicy(raw) {
|
|
|
2865
2865
|
return out2;
|
|
2866
2866
|
}
|
|
2867
2867
|
|
|
2868
|
+
// ../../packages/shared/src/packs/types.ts
|
|
2869
|
+
var PACK_FINDINGS_FILE = "REVIEW-FINDINGS.pack.json";
|
|
2870
|
+
var MAX_PACK_FINDINGS = 50;
|
|
2871
|
+
var PACK_HANDOFF_HEADING = "## Handoff";
|
|
2872
|
+
|
|
2868
2873
|
// ../../packages/shared/src/packs/roles.ts
|
|
2869
2874
|
var SPECIFIER_PROMPT = `# Role: Specifier
|
|
2870
2875
|
|
|
@@ -2880,6 +2885,8 @@ Method:
|
|
|
2880
2885
|
- **Verification plan** \u2014 for each criterion, the level that proves it (unit / integration / manual) and why.
|
|
2881
2886
|
3. Right-size: if the task is clearly too large for one pipeline run, narrow the criteria to a coherent first slice and record the rest under "Out of scope / next".
|
|
2882
2887
|
|
|
2888
|
+
4. Close with a \`## Handoff\` section: the criteria count, what you scoped out, and any assumption the Coder must know.
|
|
2889
|
+
|
|
2883
2890
|
Handoff bar: the spec file is committed; every acceptance criterion is testable as written; a competent implementer could start without asking you anything.`;
|
|
2884
2891
|
var CODER_PROMPT = `# Role: Coder
|
|
2885
2892
|
|
|
@@ -2890,6 +2897,7 @@ Method:
|
|
|
2890
2897
|
2. Test-first where it fits: write the test that proves a criterion, watch it fail, implement until it passes. Where strict test-first doesn't fit, still land tests alongside the change.
|
|
2891
2898
|
3. Match the project's existing style, structure, and conventions. Simplest design that fully solves the problem \u2014 no speculative abstractions, no "while I'm here" changes.
|
|
2892
2899
|
4. Run the project's tests / linters / build and make them pass.
|
|
2900
|
+
5. Close with a \`## Handoff\` section: which criteria are implemented (by number) and where their tests live, anything you consciously did NOT do, and what the Reviewer should look at first.
|
|
2893
2901
|
|
|
2894
2902
|
Handoff bar: every acceptance criterion is implemented and covered by a test; the project's checks pass; the work is committed in focused commits.`;
|
|
2895
2903
|
var REVIEWER_PROMPT = `# Role: Reviewer
|
|
@@ -2905,21 +2913,42 @@ Method:
|
|
|
2905
2913
|
- **Conventions & naming** \u2014 matches the surrounding code; names say what things are.
|
|
2906
2914
|
- **Safety** \u2014 no secrets, credentials, or debugging remnants in code, tests, or fixtures.
|
|
2907
2915
|
3. Fix what is justified \u2014 smallest change that resolves the finding, keeping behavior. Re-run the checks after material fixes.
|
|
2908
|
-
4.
|
|
2909
|
-
|
|
2910
|
-
|
|
2916
|
+
4. Write your findings as DATA to \`REVIEW-FINDINGS.pack.json\` at the repo root \u2014 the next stage verifies them one by one, so what is not in this file does not get verified. Exact shape:
|
|
2917
|
+
\`\`\`json
|
|
2918
|
+
{
|
|
2919
|
+
"findings": [
|
|
2920
|
+
{
|
|
2921
|
+
"id": "R1",
|
|
2922
|
+
"severity": "blocker | major | minor | nit",
|
|
2923
|
+
"title": "one line: what is wrong",
|
|
2924
|
+
"detail": "why it matters and what you did about it",
|
|
2925
|
+
"file": "src/path/to/file.ts",
|
|
2926
|
+
"line": 42,
|
|
2927
|
+
"resolution": "fixed | deferred | wont_fix | needs_verification",
|
|
2928
|
+
"commit": "abcdef1234"
|
|
2929
|
+
}
|
|
2930
|
+
],
|
|
2931
|
+
"checked": ["what you audited when the list is empty: e.g. error paths of X, test coverage of Y"]
|
|
2932
|
+
}
|
|
2933
|
+
\`\`\`
|
|
2934
|
+
Every finding you fixed says \`fixed\` with its commit; anything you deliberately left says \`deferred\` or \`wont_fix\` with the reason in \`detail\`; anything you could not verify says \`needs_verification\`. An empty \`findings\` array is a valid, honest result \u2014 then \`checked\` must say what you looked at. Commit this file with your stage.
|
|
2935
|
+
5. Close with a \`## Handoff\` section: what you found (counts by severity), what you fixed, what you deliberately left, and what the next stage must verify.
|
|
2936
|
+
|
|
2937
|
+
Handoff bar: checks pass on YOUR final commit; every fix is committed; \`REVIEW-FINDINGS.pack.json\` is committed and lists every finding with its resolution (an empty list says what you checked).`;
|
|
2911
2938
|
var QA_PROMPT = `# Role: QA
|
|
2912
2939
|
|
|
2913
2940
|
You are the final gate. You verify the delivered work against the acceptance criteria as a whole \u2014 end to end, the way a demanding user would \u2014 and produce the run's closing report.
|
|
2914
2941
|
|
|
2915
2942
|
Method:
|
|
2916
2943
|
1. Read the task and \`SPEC.pack.md\` (when present). Your contract is the acceptance criteria; without a spec, derive them from the task.
|
|
2917
|
-
2.
|
|
2918
|
-
3.
|
|
2919
|
-
4.
|
|
2920
|
-
5.
|
|
2944
|
+
2. Read \`REVIEW-FINDINGS.pack.json\` (when present \u2014 the Reviewer's structured findings, also summarized in your handoff input). Every finding there is an item on YOUR checklist: a \`fixed\` one must be verified fixed without regression; a \`deferred\`/\`wont_fix\` one must be judged acceptable or escalated; a \`needs_verification\` one is yours to settle.
|
|
2945
|
+
3. For EACH criterion, verify it against the real project: run the relevant tests, execute the code paths where feasible, inspect actual behavior/output. Do not take earlier stages' word for anything.
|
|
2946
|
+
4. Run the project's full checks (tests, lint, types, build) one final time.
|
|
2947
|
+
5. Write \`QA-REPORT.pack.md\` at the repo root with two sections: **Acceptance criteria** \u2014 per-criterion verdict (\u2705 verified / \u26A0\uFE0F partially / \u274C failed \u2014 with evidence for each); **Review findings** \u2014 per finding id, verdict (\u2705 verified fixed / \u26A0\uFE0F still open / \u274C regressed \u2014 with evidence). Then the checks' results, anything not verifiable in this environment (stated plainly), and a short "ready to ship?" conclusion.
|
|
2948
|
+
6. If a criterion FAILS: fix it only when the fix is small and unambiguous; otherwise mark it failed with exact evidence \u2014 the user decides. Never paper over a failure.
|
|
2949
|
+
7. Close with a \`## Handoff\` section: the ship/no-ship conclusion, the failed or open items by id, and what you could not verify.
|
|
2921
2950
|
|
|
2922
|
-
Handoff bar: the report is committed; every verdict carries evidence; the conclusion is honest about anything unverified.`;
|
|
2951
|
+
Handoff bar: the report is committed; every verdict carries evidence; every finding id from the Reviewer appears in the report; the conclusion is honest about anything unverified.`;
|
|
2923
2952
|
|
|
2924
2953
|
// ../../packages/shared/src/packs/registry.ts
|
|
2925
2954
|
var PACK_REGISTRY = {
|
|
@@ -2943,7 +2972,9 @@ var PACK_REGISTRY = {
|
|
|
2943
2972
|
skillIds: ["code-review", "code-naming"],
|
|
2944
2973
|
prompt: REVIEWER_PROMPT,
|
|
2945
2974
|
// Review-only: approving clean with no change is success, not a stall.
|
|
2946
|
-
requiresCommit: false
|
|
2975
|
+
requiresCommit: false,
|
|
2976
|
+
// Leaves REVIEW-FINDINGS.pack.json — the next stage gets a list, not prose.
|
|
2977
|
+
producesFindings: true
|
|
2947
2978
|
}
|
|
2948
2979
|
]
|
|
2949
2980
|
},
|
|
@@ -2974,7 +3005,9 @@ var PACK_REGISTRY = {
|
|
|
2974
3005
|
skillIds: ["code-review", "code-naming"],
|
|
2975
3006
|
prompt: REVIEWER_PROMPT,
|
|
2976
3007
|
// Review-only: approving clean with no change is success, not a stall.
|
|
2977
|
-
requiresCommit: false
|
|
3008
|
+
requiresCommit: false,
|
|
3009
|
+
// Leaves REVIEW-FINDINGS.pack.json — the next stage gets a list, not prose.
|
|
3010
|
+
producesFindings: true
|
|
2978
3011
|
},
|
|
2979
3012
|
{
|
|
2980
3013
|
role: "qa",
|
|
@@ -3004,8 +3037,140 @@ You are ONE specialist role in a multi-role pipeline running on this repository.
|
|
|
3004
3037
|
- **Never leave the tree broken.** Run the project's checks before finishing when the project has them; your stage ends with a working tree the next role can build on.
|
|
3005
3038
|
- **Do not push, force-push, or touch remotes** \u2014 the pipeline works locally; publishing is the user's call at the end.
|
|
3006
3039
|
- **Never read, edit, or commit anything under \`.codeam/\`** \u2014 that is the pipeline's own ledger, not project code.
|
|
3007
|
-
- **Finish
|
|
3008
|
-
- **If you
|
|
3040
|
+
- **Finish with a \`## Handoff\` section.** When your stage's job is done and committed, end your reply with a heading \`## Handoff\` followed by 2-6 lines: what you did, what you verified, anything the next stage must know. That section \u2014 not the rest of your reply \u2014 is what the next role receives. Don't ask "should I continue?" \u2014 the pipeline advances automatically.
|
|
3041
|
+
- **If you need the user's decision** (contradictory requirements, a real product choice), ask ONE clear question and end your reply with 2-4 numbered options on their own lines (\`1. \u2026\`), then stop. The pipeline pauses until they answer in this conversation; keep working in it once they do, and commit as usual.
|
|
3042
|
+
- **If you are genuinely blocked** (missing access, a broken environment you cannot fix), say exactly what is blocking you and stop \u2014 the user is supervising and will decide.`;
|
|
3043
|
+
|
|
3044
|
+
// ../../packages/shared/src/packs/findings.ts
|
|
3045
|
+
var SEVERITIES = /* @__PURE__ */ new Set([
|
|
3046
|
+
"blocker",
|
|
3047
|
+
"major",
|
|
3048
|
+
"minor",
|
|
3049
|
+
"nit"
|
|
3050
|
+
]);
|
|
3051
|
+
var RESOLUTIONS = /* @__PURE__ */ new Set([
|
|
3052
|
+
"fixed",
|
|
3053
|
+
"deferred",
|
|
3054
|
+
"wont_fix",
|
|
3055
|
+
"needs_verification"
|
|
3056
|
+
]);
|
|
3057
|
+
var TITLE_MAX = 200;
|
|
3058
|
+
var DETAIL_MAX = 1200;
|
|
3059
|
+
function parsePackFindings(raw) {
|
|
3060
|
+
const text = stripFence(raw).trim();
|
|
3061
|
+
if (text.length === 0) return { ok: false, error: "findings file is empty" };
|
|
3062
|
+
let data;
|
|
3063
|
+
try {
|
|
3064
|
+
data = JSON.parse(text);
|
|
3065
|
+
} catch {
|
|
3066
|
+
const salvaged = salvageJson(text);
|
|
3067
|
+
if (salvaged === null) return { ok: false, error: "findings file is not valid JSON" };
|
|
3068
|
+
data = salvaged;
|
|
3069
|
+
}
|
|
3070
|
+
const container = Array.isArray(data) ? { findings: data } : data;
|
|
3071
|
+
if (!isRecord(container) || !Array.isArray(container.findings)) {
|
|
3072
|
+
return { ok: false, error: 'findings file has no "findings" array' };
|
|
3073
|
+
}
|
|
3074
|
+
const findings = [];
|
|
3075
|
+
let dropped = 0;
|
|
3076
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3077
|
+
for (const entry of container.findings) {
|
|
3078
|
+
const f = coerceFinding(entry, findings.length + 1);
|
|
3079
|
+
if (!f) {
|
|
3080
|
+
dropped += 1;
|
|
3081
|
+
continue;
|
|
3082
|
+
}
|
|
3083
|
+
if (seen.has(f.id)) f.id = `${f.id}-${findings.length + 1}`;
|
|
3084
|
+
seen.add(f.id);
|
|
3085
|
+
findings.push(f);
|
|
3086
|
+
if (findings.length >= MAX_PACK_FINDINGS) break;
|
|
3087
|
+
}
|
|
3088
|
+
const truncated = container.findings.length - dropped - findings.length;
|
|
3089
|
+
const checked = Array.isArray(container.checked) ? container.checked.filter((c2) => typeof c2 === "string" && c2.trim().length > 0).slice(0, 20) : void 0;
|
|
3090
|
+
const notes = [];
|
|
3091
|
+
if (dropped > 0) notes.push(`${dropped} malformed entr${dropped === 1 ? "y" : "ies"} dropped`);
|
|
3092
|
+
if (truncated > 0)
|
|
3093
|
+
notes.push(`list truncated to ${MAX_PACK_FINDINGS} (${truncated} more in the file)`);
|
|
3094
|
+
return {
|
|
3095
|
+
ok: true,
|
|
3096
|
+
value: {
|
|
3097
|
+
findings,
|
|
3098
|
+
...checked && checked.length > 0 ? { checked } : {},
|
|
3099
|
+
...notes.length > 0 ? { note: notes.join("; ") } : {}
|
|
3100
|
+
}
|
|
3101
|
+
};
|
|
3102
|
+
}
|
|
3103
|
+
function coerceFinding(entry, ordinal) {
|
|
3104
|
+
if (!isRecord(entry)) return null;
|
|
3105
|
+
const title = str(entry.title) ?? str(entry.summary);
|
|
3106
|
+
if (!title) return null;
|
|
3107
|
+
const id = str(entry.id) ?? `R${ordinal}`;
|
|
3108
|
+
const severityRaw = (str(entry.severity) ?? "").toLowerCase();
|
|
3109
|
+
const resolutionRaw = (str(entry.resolution) ?? str(entry.status) ?? "").toLowerCase().replace(/[\s-]+/g, "_");
|
|
3110
|
+
const line = typeof entry.line === "number" && Number.isFinite(entry.line) && entry.line > 0 ? Math.floor(entry.line) : void 0;
|
|
3111
|
+
const detail = str(entry.detail) ?? str(entry.description);
|
|
3112
|
+
const file = str(entry.file) ?? str(entry.path);
|
|
3113
|
+
const commit = str(entry.commit);
|
|
3114
|
+
return {
|
|
3115
|
+
id: id.slice(0, 32),
|
|
3116
|
+
severity: SEVERITIES.has(severityRaw) ? severityRaw : "major",
|
|
3117
|
+
title: title.slice(0, TITLE_MAX),
|
|
3118
|
+
...detail ? { detail: detail.slice(0, DETAIL_MAX) } : {},
|
|
3119
|
+
...file ? { file: file.slice(0, 300) } : {},
|
|
3120
|
+
...line !== void 0 ? { line } : {},
|
|
3121
|
+
resolution: RESOLUTIONS.has(resolutionRaw) ? resolutionRaw : "needs_verification",
|
|
3122
|
+
...commit && /^[0-9a-f]{7,40}$/i.test(commit) ? { commit: commit.slice(0, 10) } : {}
|
|
3123
|
+
};
|
|
3124
|
+
}
|
|
3125
|
+
function extractHandoffSection(reply) {
|
|
3126
|
+
const lines = reply.split("\n");
|
|
3127
|
+
const want = PACK_HANDOFF_HEADING.replace(/^#+\s*/, "").toLowerCase();
|
|
3128
|
+
let start2 = -1;
|
|
3129
|
+
let level = 0;
|
|
3130
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
3131
|
+
const m = lines[i].match(/^\s{0,3}(#{1,4})\s+(.+?)\s*#*\s*$/);
|
|
3132
|
+
if (m && m[2].trim().toLowerCase().replace(/[:.]+$/, "") === want) {
|
|
3133
|
+
start2 = i;
|
|
3134
|
+
level = m[1].length;
|
|
3135
|
+
break;
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
if (start2 < 0) return null;
|
|
3139
|
+
const body = [];
|
|
3140
|
+
for (let i = start2 + 1; i < lines.length; i++) {
|
|
3141
|
+
const m = lines[i].match(/^\s{0,3}(#{1,6})\s+/);
|
|
3142
|
+
if (m && m[1].length <= level) break;
|
|
3143
|
+
body.push(lines[i]);
|
|
3144
|
+
}
|
|
3145
|
+
const text = body.join("\n").trim();
|
|
3146
|
+
return text.length > 0 ? text : null;
|
|
3147
|
+
}
|
|
3148
|
+
function isRecord(v) {
|
|
3149
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3150
|
+
}
|
|
3151
|
+
function str(v) {
|
|
3152
|
+
return typeof v === "string" && v.trim().length > 0 ? v.trim() : void 0;
|
|
3153
|
+
}
|
|
3154
|
+
function stripFence(raw) {
|
|
3155
|
+
const m = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
|
|
3156
|
+
return m ? m[1] : raw;
|
|
3157
|
+
}
|
|
3158
|
+
function salvageJson(text) {
|
|
3159
|
+
for (const [open, close] of [
|
|
3160
|
+
["{", "}"],
|
|
3161
|
+
["[", "]"]
|
|
3162
|
+
]) {
|
|
3163
|
+
const a = text.indexOf(open);
|
|
3164
|
+
const b = text.lastIndexOf(close);
|
|
3165
|
+
if (a >= 0 && b > a) {
|
|
3166
|
+
try {
|
|
3167
|
+
return JSON.parse(text.slice(a, b + 1));
|
|
3168
|
+
} catch {
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
return null;
|
|
3173
|
+
}
|
|
3009
3174
|
|
|
3010
3175
|
// ../../packages/shared/src/api-url.ts
|
|
3011
3176
|
var DEFAULT_API_BASE_URL = "https://api.codeagent-mobile.com";
|
|
@@ -8689,7 +8854,7 @@ var TOPLEVEL_SENTINELS = [
|
|
|
8689
8854
|
field: "window_id"
|
|
8690
8855
|
}
|
|
8691
8856
|
];
|
|
8692
|
-
function
|
|
8857
|
+
function isRecord2(value) {
|
|
8693
8858
|
return "object" == typeof value && null !== value && !Array.isArray(value);
|
|
8694
8859
|
}
|
|
8695
8860
|
function toRfc3339(timestamp) {
|
|
@@ -8708,7 +8873,7 @@ function relocateInto(properties, key, value) {
|
|
|
8708
8873
|
if (void 0 !== value && !(key in properties)) properties[key] = value;
|
|
8709
8874
|
}
|
|
8710
8875
|
function buildV1Event(message) {
|
|
8711
|
-
const sourceProperties =
|
|
8876
|
+
const sourceProperties = isRecord2(message.properties) ? message.properties : {};
|
|
8712
8877
|
const properties = {
|
|
8713
8878
|
...sourceProperties
|
|
8714
8879
|
};
|
|
@@ -10344,7 +10509,7 @@ function readAnonId() {
|
|
|
10344
10509
|
}
|
|
10345
10510
|
function superProperties() {
|
|
10346
10511
|
return {
|
|
10347
|
-
cliVersion: true ? "2.75.
|
|
10512
|
+
cliVersion: true ? "2.75.10" : "0.0.0-dev",
|
|
10348
10513
|
nodeVersion: process.version,
|
|
10349
10514
|
platform: process.platform,
|
|
10350
10515
|
arch: process.arch,
|
|
@@ -10586,7 +10751,7 @@ var http = __toESM(require("http"));
|
|
|
10586
10751
|
// package.json
|
|
10587
10752
|
var package_default = {
|
|
10588
10753
|
name: "codeam-cli",
|
|
10589
|
-
version: "2.75.
|
|
10754
|
+
version: "2.75.10",
|
|
10590
10755
|
description: "Workflow-continuity bridge for AI coding agents. Wrap Claude Code or Codex in a PTY and supervise, approve, and redirect the session from any device \u2014 async. The terminal companion for CodeAgent Mobile.",
|
|
10591
10756
|
type: "commonjs",
|
|
10592
10757
|
main: "dist/index.js",
|
|
@@ -11384,7 +11549,7 @@ var streamHost = new StreamHostSelector(
|
|
|
11384
11549
|
);
|
|
11385
11550
|
|
|
11386
11551
|
// src/services/command-relay.service.ts
|
|
11387
|
-
function
|
|
11552
|
+
function isRecord3(v) {
|
|
11388
11553
|
return typeof v === "object" && v !== null;
|
|
11389
11554
|
}
|
|
11390
11555
|
function httpStatusOf(err) {
|
|
@@ -11916,14 +12081,14 @@ var CommandRelayService = class _CommandRelayService {
|
|
|
11916
12081
|
// fresh + clear the "CLI update available" banner after a self-update
|
|
11917
12082
|
// (a codespace that reinstalls @latest reconnects via heartbeat, not
|
|
11918
12083
|
// pair/reconnect). Older backends ignore the extra field.
|
|
11919
|
-
..."2.75.
|
|
12084
|
+
..."2.75.10" ? { ideVersion: "2.75.10" } : {}
|
|
11920
12085
|
};
|
|
11921
12086
|
try {
|
|
11922
12087
|
const res = await _postJson(`${API_BASE2}/api/plugin/heartbeat`, body);
|
|
11923
12088
|
this.heartbeatFailStreak = 0;
|
|
11924
12089
|
log.trace("relay", `heartbeat ok online=${online}`);
|
|
11925
12090
|
const data = res?.data;
|
|
11926
|
-
if (online &&
|
|
12091
|
+
if (online && isRecord3(data) && data.paired === false) this.handleSessionGone();
|
|
11927
12092
|
} catch (err) {
|
|
11928
12093
|
if (!online) {
|
|
11929
12094
|
log.trace("relay", "goodbye heartbeat failed", err);
|
|
@@ -31297,14 +31462,14 @@ var zGuardCreateElicitationResponseCancel = z7.object({
|
|
|
31297
31462
|
|
|
31298
31463
|
// ../../node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js
|
|
31299
31464
|
var CANCEL_REQUEST_METHOD = "$/cancel_request";
|
|
31300
|
-
function
|
|
31465
|
+
function isRecord4(value) {
|
|
31301
31466
|
return typeof value === "object" && value !== null;
|
|
31302
31467
|
}
|
|
31303
31468
|
function isJsonRpcId(value) {
|
|
31304
31469
|
return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
|
|
31305
31470
|
}
|
|
31306
31471
|
function cancelRequestId(params) {
|
|
31307
|
-
if (!
|
|
31472
|
+
if (!isRecord4(params) || !isJsonRpcId(params["requestId"])) {
|
|
31308
31473
|
return void 0;
|
|
31309
31474
|
}
|
|
31310
31475
|
return params["requestId"];
|
|
@@ -31736,7 +31901,7 @@ var Connection = class {
|
|
|
31736
31901
|
if (this.abortController.signal.aborted) {
|
|
31737
31902
|
return;
|
|
31738
31903
|
}
|
|
31739
|
-
if (!
|
|
31904
|
+
if (!isRecord4(message)) {
|
|
31740
31905
|
console.error("Invalid message", { message });
|
|
31741
31906
|
return;
|
|
31742
31907
|
}
|
|
@@ -31829,7 +31994,7 @@ var Connection = class {
|
|
|
31829
31994
|
pendingResponse.cleanup?.();
|
|
31830
31995
|
if ("result" in response) {
|
|
31831
31996
|
pendingResponse.resolve(response.result);
|
|
31832
|
-
} else if ("error" in response &&
|
|
31997
|
+
} else if ("error" in response && isRecord4(response.error)) {
|
|
31833
31998
|
const { code, message, data } = response.error;
|
|
31834
31999
|
pendingResponse.reject(new RequestError(code, message, data));
|
|
31835
32000
|
} else {
|
|
@@ -32102,7 +32267,7 @@ function ndJsonStream(output, input) {
|
|
|
32102
32267
|
if (trimmedLine) {
|
|
32103
32268
|
try {
|
|
32104
32269
|
const message = JSON.parse(trimmedLine);
|
|
32105
|
-
if (
|
|
32270
|
+
if (isRecord4(message)) {
|
|
32106
32271
|
controller.enqueue(message);
|
|
32107
32272
|
} else {
|
|
32108
32273
|
console.warn("Skipping JSON line that is not an object:", trimmedLine);
|
|
@@ -33630,7 +33795,7 @@ function internalPathPermissionOutcome(request2, homeDir2) {
|
|
|
33630
33795
|
var INTERNAL_BLOCK_REASON = "This path is a CodeAgent platform internal (CodeAgent's own runtime plumbing, not part of the user's project) and is off-limits. Continue with the user's own project files.";
|
|
33631
33796
|
|
|
33632
33797
|
// src/agents/acp/guardrails.ts
|
|
33633
|
-
var SECRET_RE = /(^|[\s"'`=(/\\])(\.env(\.[\w.-]+)?|[\w.-]+\.(pem|key|pfx|p12|jks|keystore)|id_rsa|id_ed25519|\.npmrc|\.pgpass|\.netrc|\.git-credentials|kubeconfig
|
|
33798
|
+
var SECRET_RE = /(^|[\s"'`=(/\\])(\.env(\.[\w.-]+)?|[\w.-]+\.(pem|key|pfx|p12|jks|keystore)|id_rsa|id_ed25519|\.npmrc|\.pgpass|\.netrc|\.git-credentials|kubeconfig|\.credentials(\.[\w-]+)?)\b|[/\\]credentials(\.[\w-]+)?\b/i;
|
|
33634
33799
|
var DESTRUCTIVE_RES = [
|
|
33635
33800
|
/\brm\s+-[a-z]*f/i,
|
|
33636
33801
|
// rm -rf / -fr / -f
|
|
@@ -33656,6 +33821,15 @@ var OUTWARD_RES = [
|
|
|
33656
33821
|
/\bnetlify\s+deploy\b/i,
|
|
33657
33822
|
/\beas\s+(submit|build)\b/i
|
|
33658
33823
|
];
|
|
33824
|
+
function editTargetHaystack(call) {
|
|
33825
|
+
const parts = [typeof call.title === "string" ? call.title : ""];
|
|
33826
|
+
if (call.rawInput && typeof call.rawInput === "object" && !Array.isArray(call.rawInput)) {
|
|
33827
|
+
for (const [key, value] of Object.entries(call.rawInput)) {
|
|
33828
|
+
if (typeof value === "string" && /path|file|dest|target/i.test(key)) parts.push(value);
|
|
33829
|
+
}
|
|
33830
|
+
}
|
|
33831
|
+
return parts.join("\n");
|
|
33832
|
+
}
|
|
33659
33833
|
function haystack(call) {
|
|
33660
33834
|
let s = typeof call.title === "string" ? call.title : "";
|
|
33661
33835
|
if (call.rawInput != null) {
|
|
@@ -33684,7 +33858,7 @@ function toolPathIsSecret(p2) {
|
|
|
33684
33858
|
var GUARDRAIL_SECRET_READ_BLOCK_REASON = "Reading this secret file is blocked by this session's guardrails (Reading secrets = Deny). Adjust it in the session Guardrails settings if intended.";
|
|
33685
33859
|
function guardrailDecision(request2, policy) {
|
|
33686
33860
|
if (isProseOnlyToolKind(request2.toolCall.kind)) return null;
|
|
33687
|
-
const hay = haystack(request2.toolCall);
|
|
33861
|
+
const hay = request2.toolCall.kind === "edit" ? editTargetHaystack(request2.toolCall) : haystack(request2.toolCall);
|
|
33688
33862
|
if (!hay) return null;
|
|
33689
33863
|
const matched = matchedCategories(hay);
|
|
33690
33864
|
if (matched.size === 0) return null;
|
|
@@ -33708,7 +33882,11 @@ function guardrailDecision(request2, policy) {
|
|
|
33708
33882
|
};
|
|
33709
33883
|
}
|
|
33710
33884
|
if (confirmCat) {
|
|
33711
|
-
return {
|
|
33885
|
+
return {
|
|
33886
|
+
kind: "confirm",
|
|
33887
|
+
category: confirmCat,
|
|
33888
|
+
reason: GUARDRAIL_CATEGORY_META[confirmCat].description
|
|
33889
|
+
};
|
|
33712
33890
|
}
|
|
33713
33891
|
return null;
|
|
33714
33892
|
}
|
|
@@ -37356,11 +37534,16 @@ async function postCredentialSync(opts, fetchImpl = fetch) {
|
|
|
37356
37534
|
var import_node_child_process22 = require("child_process");
|
|
37357
37535
|
var fs60 = __toESM(require("fs"));
|
|
37358
37536
|
var path70 = __toESM(require("path"));
|
|
37359
|
-
var defaultCommandRunner = (file, args2, cwd, timeoutMs) => new Promise((resolve11) => {
|
|
37537
|
+
var defaultCommandRunner = (file, args2, cwd, timeoutMs, env) => new Promise((resolve11) => {
|
|
37360
37538
|
(0, import_node_child_process22.execFile)(
|
|
37361
37539
|
file,
|
|
37362
37540
|
args2,
|
|
37363
|
-
{
|
|
37541
|
+
{
|
|
37542
|
+
cwd,
|
|
37543
|
+
timeout: timeoutMs,
|
|
37544
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
37545
|
+
env: env ? { ...process.env, ...env } : process.env
|
|
37546
|
+
},
|
|
37364
37547
|
(err, stdout, stderr) => {
|
|
37365
37548
|
let code = 0;
|
|
37366
37549
|
if (err) {
|
|
@@ -37377,7 +37560,12 @@ async function gitHead(run, cwd) {
|
|
|
37377
37560
|
return res.code === 0 ? res.stdout.trim() : null;
|
|
37378
37561
|
}
|
|
37379
37562
|
async function canonicalCommit(run, cwd, sha) {
|
|
37380
|
-
const verify = await run(
|
|
37563
|
+
const verify = await run(
|
|
37564
|
+
"git",
|
|
37565
|
+
["rev-parse", "--verify", `${sha}^{commit}`],
|
|
37566
|
+
cwd,
|
|
37567
|
+
GIT_TIMEOUT_MS
|
|
37568
|
+
);
|
|
37381
37569
|
if (verify.code !== 0) return null;
|
|
37382
37570
|
const short = await run("git", ["rev-parse", "--short=10", sha], cwd, GIT_TIMEOUT_MS);
|
|
37383
37571
|
return short.code === 0 ? short.stdout.trim() : null;
|
|
@@ -37388,6 +37576,18 @@ async function diffStat(run, cwd, from, to) {
|
|
|
37388
37576
|
const lines = res.stdout.trim().split("\n").filter(Boolean);
|
|
37389
37577
|
return lines.length > 0 ? lines[lines.length - 1].trim() : "";
|
|
37390
37578
|
}
|
|
37579
|
+
async function changedFiles(run, cwd, from, to) {
|
|
37580
|
+
const res = await run("git", ["diff", "--name-only", `${from}..${to}`], cwd, GIT_TIMEOUT_MS);
|
|
37581
|
+
if (res.code !== 0) return [];
|
|
37582
|
+
return res.stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
37583
|
+
}
|
|
37584
|
+
function readWorkspaceFile(cwd, relPath) {
|
|
37585
|
+
try {
|
|
37586
|
+
return fs60.readFileSync(path70.join(cwd, relPath), "utf8");
|
|
37587
|
+
} catch {
|
|
37588
|
+
return null;
|
|
37589
|
+
}
|
|
37590
|
+
}
|
|
37391
37591
|
var NO_TEST_PLACEHOLDER = 'echo "Error: no test specified"';
|
|
37392
37592
|
var CHECKS_TIMEOUT_MS = 5 * 6e4;
|
|
37393
37593
|
function detectChecksCommand(cwd) {
|
|
@@ -37409,7 +37609,7 @@ function detectChecksCommand(cwd) {
|
|
|
37409
37609
|
return null;
|
|
37410
37610
|
}
|
|
37411
37611
|
async function runChecks(run, cwd, command2) {
|
|
37412
|
-
const res = await run("sh", ["-c", command2], cwd, CHECKS_TIMEOUT_MS);
|
|
37612
|
+
const res = await run("sh", ["-c", command2], cwd, CHECKS_TIMEOUT_MS, { CI: "1" });
|
|
37413
37613
|
const combined = `${res.stdout}
|
|
37414
37614
|
${res.stderr}`.trim();
|
|
37415
37615
|
const tail = combined.split("\n").slice(-12).join("\n").slice(-1500);
|
|
@@ -37506,33 +37706,109 @@ async function postPackState(opts, state, fetchImpl = fetch) {
|
|
|
37506
37706
|
}
|
|
37507
37707
|
|
|
37508
37708
|
// src/packs/runner.ts
|
|
37509
|
-
var
|
|
37510
|
-
|
|
37511
|
-
|
|
37709
|
+
var TERMINAL_PACK_STATUSES = /* @__PURE__ */ new Set([
|
|
37710
|
+
"completed",
|
|
37711
|
+
"aborted",
|
|
37712
|
+
"failed"
|
|
37713
|
+
]);
|
|
37714
|
+
var NUDGE_PROMPT = "Your stage is not committed yet. Commit your completed work now (focused commits, ending with your role byline `By <role>.` on its own line), then close with your `## Handoff` section and stop. If you are blocked, say exactly what is blocking you instead.";
|
|
37715
|
+
var SUMMARY_TAIL_MAX_CHARS = 600;
|
|
37716
|
+
var SUMMARY_SECTION_MAX_CHARS = 1500;
|
|
37717
|
+
var EARLIER_HANDOFF_MAX_CHARS = 400;
|
|
37718
|
+
function composeStagePrompt(pack, stageIndex, task, ctx) {
|
|
37512
37719
|
const stage = pack.stages[stageIndex];
|
|
37513
37720
|
const pipeline2 = pack.stages.map((s, i) => i === stageIndex ? `[${s.name}]` : s.name).join(" \u2192 ");
|
|
37721
|
+
const position = [
|
|
37722
|
+
`${pack.name}: ${pipeline2} \u2014 you are stage ${stageIndex + 1} of ${pack.stages.length}. Your commit byline: \`By ${stage.role}.\``
|
|
37723
|
+
];
|
|
37724
|
+
if (ctx.baseCommit) {
|
|
37725
|
+
position.push(
|
|
37726
|
+
`Pipeline base commit: \`${ctx.baseCommit}\` \u2014 everything the pipeline has done so far is \`git log ${ctx.baseCommit}..HEAD\` / \`git diff ${ctx.baseCommit}..HEAD\`. Use that range; do not guess it.`
|
|
37727
|
+
);
|
|
37728
|
+
}
|
|
37514
37729
|
const parts = [
|
|
37515
37730
|
stage.prompt,
|
|
37516
37731
|
PACK_WORKFLOW_ARTICLE,
|
|
37517
37732
|
`## Your pipeline position
|
|
37518
|
-
${
|
|
37733
|
+
${position.join("\n")}`,
|
|
37519
37734
|
`## Task
|
|
37520
37735
|
${task}`
|
|
37521
37736
|
];
|
|
37522
|
-
|
|
37737
|
+
const done = ctx.prior.filter((p2) => p2.status === "done" && p2.handoff);
|
|
37738
|
+
const skipped = ctx.prior.filter((p2) => p2.status === "skipped");
|
|
37739
|
+
if (done.length > 0) {
|
|
37740
|
+
const last = done[done.length - 1];
|
|
37741
|
+
const earlier = done.slice(0, -1);
|
|
37742
|
+
if (earlier.length > 0) {
|
|
37743
|
+
parts.push(
|
|
37744
|
+
`## Earlier handoffs
|
|
37745
|
+
${earlier.map((p2) => renderHandoff(p2, EARLIER_HANDOFF_MAX_CHARS)).join("\n\n")}`
|
|
37746
|
+
);
|
|
37747
|
+
}
|
|
37523
37748
|
parts.push(
|
|
37524
|
-
`## Previous stage handoff (${
|
|
37525
|
-
|
|
37526
|
-
|
|
37527
|
-
|
|
37528
|
-
|
|
37749
|
+
`## Previous stage handoff (${last.role})
|
|
37750
|
+
${renderHandoff(last, SUMMARY_SECTION_MAX_CHARS)}`
|
|
37751
|
+
);
|
|
37752
|
+
}
|
|
37753
|
+
if (skipped.length > 0) {
|
|
37754
|
+
parts.push(
|
|
37755
|
+
`## Skipped stages
|
|
37756
|
+
${skipped.map((p2) => `- ${p2.name} (${p2.role}) was skipped by the user \u2014 its artifacts do not exist; do not assume them.`).join("\n")}`
|
|
37757
|
+
);
|
|
37758
|
+
}
|
|
37759
|
+
if (ctx.attempt > 1) {
|
|
37760
|
+
parts.push(
|
|
37761
|
+
`## Previous attempt of this stage
|
|
37762
|
+
Attempt ${ctx.attempt - 1} did not hand off${ctx.previousAttemptError ? `: ${ctx.previousAttemptError}` : "."}
|
|
37763
|
+
The working tree may contain that attempt's partial work. Inspect it first, keep what is right, and finish the stage properly.`
|
|
37529
37764
|
);
|
|
37530
37765
|
}
|
|
37531
37766
|
return parts.join("\n\n");
|
|
37532
37767
|
}
|
|
37768
|
+
function renderHandoff(p2, summaryMax) {
|
|
37769
|
+
const h = p2.handoff;
|
|
37770
|
+
const head = [`### ${p2.name} (${p2.role})`, `commit: ${h.commit}`];
|
|
37771
|
+
if (h.diffStat) head.push(h.diffStat);
|
|
37772
|
+
if (h.checks)
|
|
37773
|
+
head.push(`checks (\`${h.checks.command}\`): ${h.checks.passed ? "passed" : "FAILED"}`);
|
|
37774
|
+
const lines = [head.join("\n")];
|
|
37775
|
+
const summary = h.summary.trim();
|
|
37776
|
+
if (summary) lines.push(summary.length > summaryMax ? `\u2026${summary.slice(-summaryMax)}` : summary);
|
|
37777
|
+
if (h.checks && !h.checks.passed && h.checks.tail) {
|
|
37778
|
+
lines.push(`Checks output (tail):
|
|
37779
|
+
${h.checks.tail}`);
|
|
37780
|
+
}
|
|
37781
|
+
if (h.findings) {
|
|
37782
|
+
lines.push(renderFindings(h.findings, h.findingsNote));
|
|
37783
|
+
} else if (h.findingsNote) {
|
|
37784
|
+
lines.push(`Structured findings: ${h.findingsNote}`);
|
|
37785
|
+
}
|
|
37786
|
+
return lines.join("\n\n");
|
|
37787
|
+
}
|
|
37788
|
+
function renderFindings(findings, note) {
|
|
37789
|
+
const list = findings ?? [];
|
|
37790
|
+
const header = `Structured findings (${PACK_FINDINGS_FILE}): ${list.length}${note ? ` \u2014 ${note}` : ""}`;
|
|
37791
|
+
if (list.length === 0) return header;
|
|
37792
|
+
const rows = list.map((f) => {
|
|
37793
|
+
const where = f.file ? ` @ ${f.file}${f.line ? `:${f.line}` : ""}` : "";
|
|
37794
|
+
return `- [${f.id}] ${f.severity.toUpperCase()} \u2014 ${f.title}${where} \u2192 ${f.resolution}${f.commit ? ` (${f.commit})` : ""}`;
|
|
37795
|
+
});
|
|
37796
|
+
return `${header}
|
|
37797
|
+
${rows.join("\n")}`;
|
|
37798
|
+
}
|
|
37799
|
+
function summarizeReply(text) {
|
|
37800
|
+
const section = extractHandoffSection(text);
|
|
37801
|
+
if (section)
|
|
37802
|
+
return section.length > SUMMARY_SECTION_MAX_CHARS ? section.slice(0, SUMMARY_SECTION_MAX_CHARS) : section;
|
|
37803
|
+
return text.trim().slice(-SUMMARY_TAIL_MAX_CHARS);
|
|
37804
|
+
}
|
|
37533
37805
|
function nowIso() {
|
|
37534
37806
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
37535
37807
|
}
|
|
37808
|
+
function sameCommit(a, b) {
|
|
37809
|
+
if (!a || !b) return a === b;
|
|
37810
|
+
return a.startsWith(b) || b.startsWith(a);
|
|
37811
|
+
}
|
|
37536
37812
|
var PackRunner = class _PackRunner {
|
|
37537
37813
|
constructor(deps, pack, initial) {
|
|
37538
37814
|
this.deps = deps;
|
|
@@ -37544,6 +37820,10 @@ var PackRunner = class _PackRunner {
|
|
|
37544
37820
|
state;
|
|
37545
37821
|
control = "run";
|
|
37546
37822
|
looping = false;
|
|
37823
|
+
inTurn = false;
|
|
37824
|
+
/** Next `runStage` continues the current stage in place (no new prompt). */
|
|
37825
|
+
resumeInPlace = false;
|
|
37826
|
+
mountedSkills = [];
|
|
37547
37827
|
static create(deps, packId, task, runId) {
|
|
37548
37828
|
const pack = getPackDefinition(packId);
|
|
37549
37829
|
if (!pack) throw new Error(`unknown pack: ${packId}`);
|
|
@@ -37564,9 +37844,51 @@ var PackRunner = class _PackRunner {
|
|
|
37564
37844
|
};
|
|
37565
37845
|
return new _PackRunner(deps, pack, state);
|
|
37566
37846
|
}
|
|
37847
|
+
/**
|
|
37848
|
+
* Rebuild a runner from the workspace ledger after a CLI restart. The
|
|
37849
|
+
* in-memory run died with the process; its conversation is gone. A stage
|
|
37850
|
+
* that was mid-turn is marked failed with the real reason (never left
|
|
37851
|
+
* "active" forever), and the run lands paused/stalled so Resume / Retry /
|
|
37852
|
+
* Skip / Abort all work again. Null when the pack id is unknown.
|
|
37853
|
+
*/
|
|
37854
|
+
static rehydrate(deps, stored) {
|
|
37855
|
+
const pack = getPackDefinition(stored.packId);
|
|
37856
|
+
if (!pack) return null;
|
|
37857
|
+
const interrupted = stored.stages.some((s) => s.status === "active");
|
|
37858
|
+
const stages = stored.stages.map(
|
|
37859
|
+
(s) => s.status === "active" ? {
|
|
37860
|
+
...s,
|
|
37861
|
+
status: "failed",
|
|
37862
|
+
error: "the CLI restarted while this stage was running",
|
|
37863
|
+
conversationId: void 0,
|
|
37864
|
+
awaitingUser: void 0
|
|
37865
|
+
} : s
|
|
37866
|
+
);
|
|
37867
|
+
let status2 = stored.status;
|
|
37868
|
+
let stalledReason = stored.stalledReason;
|
|
37869
|
+
if (interrupted) {
|
|
37870
|
+
status2 = "stalled";
|
|
37871
|
+
stalledReason = "The CLI restarted mid-stage. Resume re-checks whether the stage committed its work; Retry stage runs it again in a fresh conversation.";
|
|
37872
|
+
} else if (status2 === "running") {
|
|
37873
|
+
status2 = "paused";
|
|
37874
|
+
stalledReason = "The CLI restarted between stages \u2014 tap Resume to continue.";
|
|
37875
|
+
}
|
|
37876
|
+
const state = {
|
|
37877
|
+
...stored,
|
|
37878
|
+
stages,
|
|
37879
|
+
status: status2,
|
|
37880
|
+
stalledReason,
|
|
37881
|
+
pendingControl: void 0
|
|
37882
|
+
};
|
|
37883
|
+
return new _PackRunner(deps, pack, state);
|
|
37884
|
+
}
|
|
37567
37885
|
getState() {
|
|
37568
37886
|
return this.state;
|
|
37569
37887
|
}
|
|
37888
|
+
/** Persist + post the current state without changing it (rehydration announce). */
|
|
37889
|
+
async announce() {
|
|
37890
|
+
await this.publish();
|
|
37891
|
+
}
|
|
37570
37892
|
/** Persist + post the current state (ledger first — it's the truth). */
|
|
37571
37893
|
async publish() {
|
|
37572
37894
|
this.state = { ...this.state, updatedAt: nowIso() };
|
|
@@ -37578,64 +37900,101 @@ var PackRunner = class _PackRunner {
|
|
|
37578
37900
|
await this.deps.postState(this.state);
|
|
37579
37901
|
}
|
|
37580
37902
|
async settle(status2, stalledReason) {
|
|
37581
|
-
this.state = { ...this.state, status: status2, stalledReason };
|
|
37903
|
+
this.state = { ...this.state, status: status2, stalledReason, pendingControl: void 0 };
|
|
37582
37904
|
await this.publish();
|
|
37583
37905
|
}
|
|
37906
|
+
patchStage(index, patch) {
|
|
37907
|
+
const stages = this.state.stages.slice();
|
|
37908
|
+
stages[index] = { ...stages[index], ...patch };
|
|
37909
|
+
this.state = { ...this.state, stages };
|
|
37910
|
+
}
|
|
37584
37911
|
// ── Control surface (relay `pack_action`) ────────────────────────────────
|
|
37585
37912
|
async applyAction(action) {
|
|
37913
|
+
const status2 = this.state.status;
|
|
37914
|
+
const idx = this.state.currentStage;
|
|
37915
|
+
const reject = (why) => ({ state: this.state, rejected: why });
|
|
37916
|
+
if (TERMINAL_PACK_STATUSES.has(status2)) return reject(`the run already finished (${status2})`);
|
|
37586
37917
|
switch (action) {
|
|
37587
|
-
case "pause":
|
|
37918
|
+
case "pause": {
|
|
37919
|
+
if (status2 === "paused" || status2 === "stalled") return { state: this.state };
|
|
37588
37920
|
this.control = "pause";
|
|
37589
|
-
|
|
37590
|
-
|
|
37591
|
-
await this.publish();
|
|
37592
|
-
}
|
|
37921
|
+
this.state = { ...this.state, pendingControl: "pause" };
|
|
37922
|
+
await this.publish();
|
|
37593
37923
|
break;
|
|
37594
|
-
|
|
37924
|
+
}
|
|
37925
|
+
case "resume": {
|
|
37926
|
+
if (status2 !== "paused" && status2 !== "stalled")
|
|
37927
|
+
return reject(`nothing to resume \u2014 the run is ${status2}`);
|
|
37595
37928
|
this.control = "run";
|
|
37596
|
-
|
|
37597
|
-
|
|
37598
|
-
|
|
37599
|
-
|
|
37600
|
-
|
|
37929
|
+
const stage = this.state.stages[idx];
|
|
37930
|
+
this.resumeInPlace = !!stage && (stage.awaitingUser === true || stage.status === "failed");
|
|
37931
|
+
this.state = {
|
|
37932
|
+
...this.state,
|
|
37933
|
+
status: "running",
|
|
37934
|
+
stalledReason: void 0,
|
|
37935
|
+
pendingControl: void 0
|
|
37936
|
+
};
|
|
37937
|
+
await this.publish();
|
|
37938
|
+
void this.run();
|
|
37601
37939
|
break;
|
|
37940
|
+
}
|
|
37602
37941
|
case "retry_stage": {
|
|
37603
|
-
|
|
37604
|
-
|
|
37605
|
-
|
|
37606
|
-
|
|
37607
|
-
|
|
37608
|
-
|
|
37609
|
-
|
|
37610
|
-
void
|
|
37611
|
-
|
|
37942
|
+
if (status2 === "running" || this.inTurn)
|
|
37943
|
+
return reject("the stage is still running \u2014 pause or abort first");
|
|
37944
|
+
if (idx >= this.state.stages.length) return reject("no stage left to retry");
|
|
37945
|
+
const prev = this.state.stages[idx];
|
|
37946
|
+
this.patchStage(idx, {
|
|
37947
|
+
status: "pending",
|
|
37948
|
+
conversationId: void 0,
|
|
37949
|
+
handoff: void 0,
|
|
37950
|
+
awaitingUser: void 0,
|
|
37951
|
+
startCommit: void 0,
|
|
37952
|
+
attempts: prev.attempts,
|
|
37953
|
+
error: prev.error
|
|
37954
|
+
});
|
|
37955
|
+
this.resumeInPlace = false;
|
|
37956
|
+
this.control = "run";
|
|
37957
|
+
this.state = {
|
|
37958
|
+
...this.state,
|
|
37959
|
+
status: "running",
|
|
37960
|
+
stalledReason: void 0,
|
|
37961
|
+
pendingControl: void 0
|
|
37962
|
+
};
|
|
37963
|
+
await this.publish();
|
|
37964
|
+
void this.run();
|
|
37612
37965
|
break;
|
|
37613
37966
|
}
|
|
37614
37967
|
case "skip_stage": {
|
|
37615
|
-
|
|
37616
|
-
|
|
37617
|
-
|
|
37618
|
-
|
|
37619
|
-
|
|
37620
|
-
|
|
37621
|
-
|
|
37622
|
-
|
|
37623
|
-
|
|
37624
|
-
|
|
37625
|
-
|
|
37626
|
-
|
|
37627
|
-
|
|
37628
|
-
|
|
37629
|
-
|
|
37968
|
+
if (status2 === "running" || this.inTurn)
|
|
37969
|
+
return reject("the stage is still running \u2014 pause or abort first");
|
|
37970
|
+
if (idx >= this.state.stages.length) return reject("no stage left to skip");
|
|
37971
|
+
this.patchStage(idx, { status: "skipped", awaitingUser: void 0, error: void 0 });
|
|
37972
|
+
this.resumeInPlace = false;
|
|
37973
|
+
this.control = "run";
|
|
37974
|
+
this.state = {
|
|
37975
|
+
...this.state,
|
|
37976
|
+
currentStage: idx + 1,
|
|
37977
|
+
status: "running",
|
|
37978
|
+
stalledReason: void 0,
|
|
37979
|
+
pendingControl: void 0
|
|
37980
|
+
};
|
|
37981
|
+
await this.publish();
|
|
37982
|
+
void this.run();
|
|
37630
37983
|
break;
|
|
37631
37984
|
}
|
|
37632
|
-
case "abort":
|
|
37985
|
+
case "abort": {
|
|
37633
37986
|
this.control = "abort";
|
|
37634
|
-
|
|
37635
|
-
|
|
37987
|
+
if (this.looping) {
|
|
37988
|
+
this.state = { ...this.state, pendingControl: "abort" };
|
|
37989
|
+
await this.publish();
|
|
37990
|
+
await this.deps.driver.cancel().catch(() => void 0);
|
|
37991
|
+
} else {
|
|
37992
|
+
await this.settle("aborted");
|
|
37993
|
+
}
|
|
37636
37994
|
break;
|
|
37995
|
+
}
|
|
37637
37996
|
}
|
|
37638
|
-
return this.state;
|
|
37997
|
+
return { state: this.state };
|
|
37639
37998
|
}
|
|
37640
37999
|
// ── The loop ──────────────────────────────────────────────────────────────
|
|
37641
38000
|
async run() {
|
|
@@ -37660,104 +38019,234 @@ var PackRunner = class _PackRunner {
|
|
|
37660
38019
|
await this.settle("failed", err.message).catch(() => void 0);
|
|
37661
38020
|
} finally {
|
|
37662
38021
|
this.looping = false;
|
|
38022
|
+
this.inTurn = false;
|
|
37663
38023
|
}
|
|
37664
38024
|
}
|
|
37665
38025
|
/** Run one stage to its handoff. True = advanced; false = run settled. */
|
|
37666
38026
|
async runStage(index) {
|
|
37667
38027
|
const stageDef = this.pack.stages[index];
|
|
38028
|
+
const inPlace = this.resumeInPlace;
|
|
38029
|
+
this.resumeInPlace = false;
|
|
37668
38030
|
const startedMs = Date.now();
|
|
37669
|
-
const startSha = await this.deps.gates.head();
|
|
37670
|
-
let stages = this.state.stages.slice();
|
|
37671
|
-
stages[index] = { ...stages[index], status: "active" };
|
|
37672
|
-
this.state = { ...this.state, stages, status: "running" };
|
|
37673
38031
|
try {
|
|
37674
|
-
|
|
37675
|
-
|
|
37676
|
-
|
|
37677
|
-
|
|
37678
|
-
|
|
37679
|
-
|
|
37680
|
-
|
|
37681
|
-
|
|
37682
|
-
|
|
37683
|
-
|
|
37684
|
-
|
|
38032
|
+
let startSha;
|
|
38033
|
+
let turn;
|
|
38034
|
+
if (inPlace) {
|
|
38035
|
+
startSha = this.state.stages[index].startCommit ?? await this.deps.gates.head();
|
|
38036
|
+
this.patchStage(index, { status: "active", awaitingUser: void 0, error: void 0 });
|
|
38037
|
+
this.state = { ...this.state, status: "running" };
|
|
38038
|
+
await this.publish();
|
|
38039
|
+
turn = { text: "", awaitingUser: false };
|
|
38040
|
+
} else {
|
|
38041
|
+
startSha = await this.deps.gates.head();
|
|
38042
|
+
const prevStage = this.state.stages[index];
|
|
38043
|
+
const attempt = (prevStage.attempts ?? 0) + 1;
|
|
38044
|
+
const previousAttemptError = attempt > 1 ? prevStage.error : void 0;
|
|
38045
|
+
const startCommit = startSha ? await this.deps.gates.canonicalCommit(startSha) : null;
|
|
38046
|
+
this.patchStage(index, {
|
|
38047
|
+
status: "active",
|
|
38048
|
+
attempts: attempt,
|
|
38049
|
+
startCommit: startCommit ?? void 0,
|
|
38050
|
+
conversationId: void 0,
|
|
38051
|
+
handoff: void 0,
|
|
38052
|
+
awaitingUser: void 0,
|
|
38053
|
+
error: void 0
|
|
38054
|
+
});
|
|
38055
|
+
if (index === 0 && !this.state.baseCommit && startCommit) {
|
|
38056
|
+
this.state = { ...this.state, baseCommit: startCommit };
|
|
38057
|
+
}
|
|
38058
|
+
this.state = { ...this.state, status: "running" };
|
|
38059
|
+
const conversationId = await this.deps.driver.newConversation();
|
|
38060
|
+
this.patchStage(index, { conversationId });
|
|
38061
|
+
await this.publish();
|
|
38062
|
+
this.swapSkills(stageDef.skillIds);
|
|
38063
|
+
const prompt = composeStagePrompt(this.pack, index, this.state.task, {
|
|
38064
|
+
baseCommit: this.state.baseCommit,
|
|
38065
|
+
prior: this.priorOutcomes(index),
|
|
38066
|
+
attempt,
|
|
38067
|
+
previousAttemptError
|
|
38068
|
+
});
|
|
38069
|
+
const displayLine = `\u25B6 ${this.pack.name} \u2014 stage ${index + 1}/${this.pack.stages.length}: ${stageDef.name}`;
|
|
38070
|
+
turn = await this.drive(prompt, displayLine, index);
|
|
38071
|
+
}
|
|
38072
|
+
if (this.aborted()) {
|
|
37685
38073
|
await this.settle("aborted");
|
|
37686
38074
|
return false;
|
|
37687
38075
|
}
|
|
38076
|
+
if (turn.awaitingUser) return this.awaitUser(index);
|
|
37688
38077
|
const commitOptional = stageDef.requiresCommit === false;
|
|
38078
|
+
let text = turn.text;
|
|
37689
38079
|
let endSha = await this.deps.gates.head();
|
|
37690
|
-
if (!endSha || endSha
|
|
37691
|
-
if (commitOptional &&
|
|
37692
|
-
|
|
37693
|
-
|
|
37694
|
-
|
|
37695
|
-
|
|
37696
|
-
|
|
37697
|
-
|
|
37698
|
-
|
|
37699
|
-
return this.
|
|
38080
|
+
if (!endSha || sameCommit(endSha, startSha)) {
|
|
38081
|
+
if (commitOptional && text.trim().length > 0) {
|
|
38082
|
+
return this.recordHandoff(
|
|
38083
|
+
index,
|
|
38084
|
+
stageDef,
|
|
38085
|
+
await this.noChangeHandoff(stageDef, startSha, text, startedMs)
|
|
38086
|
+
);
|
|
38087
|
+
}
|
|
38088
|
+
if (!this.state.stages[index].conversationId) {
|
|
38089
|
+
return this.stall(
|
|
38090
|
+
index,
|
|
38091
|
+
"stage produced no commit \u2014 Retry stage to run it again in a fresh conversation"
|
|
38092
|
+
);
|
|
37700
38093
|
}
|
|
37701
|
-
|
|
38094
|
+
const nudged = await this.drive(NUDGE_PROMPT, "\u25B6 Waiting for the stage commit\u2026", index);
|
|
38095
|
+
if (this.aborted()) {
|
|
38096
|
+
await this.settle("aborted");
|
|
38097
|
+
return false;
|
|
38098
|
+
}
|
|
38099
|
+
if (nudged.awaitingUser) return this.awaitUser(index);
|
|
38100
|
+
text = nudged.text.trim().length > 0 ? nudged.text : text;
|
|
37702
38101
|
endSha = await this.deps.gates.head();
|
|
37703
|
-
if (!endSha || endSha
|
|
37704
|
-
if (commitOptional &&
|
|
37705
|
-
|
|
37706
|
-
|
|
37707
|
-
|
|
37708
|
-
|
|
37709
|
-
|
|
37710
|
-
durationMs: Date.now() - startedMs
|
|
37711
|
-
});
|
|
38102
|
+
if (!endSha || sameCommit(endSha, startSha)) {
|
|
38103
|
+
if (commitOptional && text.trim().length > 0) {
|
|
38104
|
+
return this.recordHandoff(
|
|
38105
|
+
index,
|
|
38106
|
+
stageDef,
|
|
38107
|
+
await this.noChangeHandoff(stageDef, startSha, text, startedMs)
|
|
38108
|
+
);
|
|
37712
38109
|
}
|
|
37713
|
-
return this.stall(index, "stage produced no commit",
|
|
38110
|
+
return this.stall(index, "stage produced no commit", text);
|
|
37714
38111
|
}
|
|
37715
38112
|
}
|
|
37716
38113
|
const commit = await this.deps.gates.canonicalCommit(endSha);
|
|
37717
|
-
if (!commit) return this.stall(index, "stage HEAD did not resolve to a commit",
|
|
38114
|
+
if (!commit) return this.stall(index, "stage HEAD did not resolve to a commit", text);
|
|
37718
38115
|
const handoff = {
|
|
37719
38116
|
commit,
|
|
37720
|
-
summary:
|
|
38117
|
+
summary: summarizeReply(text),
|
|
37721
38118
|
diffStat: startSha ? await this.deps.gates.diffStat(startSha, endSha) : "",
|
|
37722
38119
|
checks: await this.deps.gates.runChecks() ?? void 0,
|
|
37723
38120
|
durationMs: Date.now() - startedMs
|
|
37724
38121
|
};
|
|
37725
|
-
|
|
38122
|
+
if (stageDef.producesFindings) {
|
|
38123
|
+
Object.assign(handoff, await this.captureFindings(startSha, endSha));
|
|
38124
|
+
}
|
|
38125
|
+
return this.recordHandoff(index, stageDef, handoff);
|
|
37726
38126
|
} catch (err) {
|
|
37727
|
-
if (this.
|
|
38127
|
+
if (this.aborted()) {
|
|
37728
38128
|
await this.settle("aborted");
|
|
37729
38129
|
return false;
|
|
37730
38130
|
}
|
|
37731
38131
|
return this.stall(index, err.message);
|
|
37732
38132
|
}
|
|
37733
38133
|
}
|
|
38134
|
+
/** Read through a method: `control` flips from another call while a turn is
|
|
38135
|
+
* awaited, which TS's property narrowing would otherwise hide. */
|
|
38136
|
+
aborted() {
|
|
38137
|
+
return this.control === "abort";
|
|
38138
|
+
}
|
|
38139
|
+
async drive(prompt, displayLine, index) {
|
|
38140
|
+
this.inTurn = true;
|
|
38141
|
+
try {
|
|
38142
|
+
return await this.deps.driver.runTurn(prompt, displayLine, (pending) => {
|
|
38143
|
+
this.patchStage(index, { awaitingUser: pending ? true : void 0 });
|
|
38144
|
+
void this.publish();
|
|
38145
|
+
});
|
|
38146
|
+
} finally {
|
|
38147
|
+
this.inTurn = false;
|
|
38148
|
+
}
|
|
38149
|
+
}
|
|
38150
|
+
swapSkills(next) {
|
|
38151
|
+
const stale = this.mountedSkills.filter((id) => !next.includes(id));
|
|
38152
|
+
const fresh = next.filter((id) => !this.mountedSkills.includes(id));
|
|
38153
|
+
if (stale.length > 0) this.deps.driver.unmountSkills(stale);
|
|
38154
|
+
if (fresh.length > 0) this.deps.driver.mountSkills(fresh);
|
|
38155
|
+
this.mountedSkills = next.slice();
|
|
38156
|
+
}
|
|
38157
|
+
async noChangeHandoff(stageDef, startSha, text, startedMs) {
|
|
38158
|
+
const reviewedCommit = startSha ? await this.deps.gates.canonicalCommit(startSha) : null;
|
|
38159
|
+
return {
|
|
38160
|
+
commit: reviewedCommit ?? "(no changes)",
|
|
38161
|
+
summary: summarizeReply(text),
|
|
38162
|
+
diffStat: "reviewed \u2014 no changes needed",
|
|
38163
|
+
durationMs: Date.now() - startedMs,
|
|
38164
|
+
// A findings-producing stage that hands off without a commit never
|
|
38165
|
+
// wrote its findings file (its contract says: commit it even when
|
|
38166
|
+
// empty). Say so — the next stage must not assume a clean bill.
|
|
38167
|
+
...stageDef.producesFindings ? {
|
|
38168
|
+
findingsNote: `no ${PACK_FINDINGS_FILE} committed \u2014 the stage handed off without structured findings`
|
|
38169
|
+
} : {}
|
|
38170
|
+
};
|
|
38171
|
+
}
|
|
38172
|
+
/**
|
|
38173
|
+
* Parse the stage's `PACK_FINDINGS_FILE` into the handoff. Only a file the
|
|
38174
|
+
* stage itself touched counts — a stale one left by an earlier run must not
|
|
38175
|
+
* masquerade as this review's verdict. Every failure path leaves an honest
|
|
38176
|
+
* `findingsNote`; none of them stalls the run.
|
|
38177
|
+
*/
|
|
38178
|
+
async captureFindings(startSha, endSha) {
|
|
38179
|
+
try {
|
|
38180
|
+
if (startSha) {
|
|
38181
|
+
const changed = await this.deps.gates.changedFiles(startSha, endSha);
|
|
38182
|
+
if (!changed.includes(PACK_FINDINGS_FILE)) {
|
|
38183
|
+
return {
|
|
38184
|
+
findingsNote: `${PACK_FINDINGS_FILE} was not written by this stage \u2014 no structured findings`
|
|
38185
|
+
};
|
|
38186
|
+
}
|
|
38187
|
+
}
|
|
38188
|
+
const raw = await this.deps.gates.readFile(PACK_FINDINGS_FILE);
|
|
38189
|
+
if (raw === null)
|
|
38190
|
+
return { findingsNote: `no ${PACK_FINDINGS_FILE} \u2014 the stage left no structured findings` };
|
|
38191
|
+
const parsed = parsePackFindings(raw);
|
|
38192
|
+
if (!parsed.ok) return { findingsNote: `${PACK_FINDINGS_FILE}: ${parsed.error}` };
|
|
38193
|
+
const notes = [];
|
|
38194
|
+
if (parsed.value.findings.length === 0 && parsed.value.checked) {
|
|
38195
|
+
notes.push(`no findings; checked: ${parsed.value.checked.join(", ")}`);
|
|
38196
|
+
}
|
|
38197
|
+
if (parsed.value.note) notes.push(parsed.value.note);
|
|
38198
|
+
return {
|
|
38199
|
+
findings: parsed.value.findings,
|
|
38200
|
+
...notes.length > 0 ? { findingsNote: notes.join(" \u2014 ") } : {}
|
|
38201
|
+
};
|
|
38202
|
+
} catch (err) {
|
|
38203
|
+
return { findingsNote: `could not read ${PACK_FINDINGS_FILE}: ${err.message}` };
|
|
38204
|
+
}
|
|
38205
|
+
}
|
|
37734
38206
|
/** Mark a stage `done` with its handoff, persist, and advance. */
|
|
37735
|
-
async recordHandoff(index,
|
|
37736
|
-
|
|
37737
|
-
|
|
37738
|
-
this.state = { ...this.state, stages, currentStage: index + 1 };
|
|
38207
|
+
async recordHandoff(index, stageDef, handoff) {
|
|
38208
|
+
this.patchStage(index, { status: "done", handoff, awaitingUser: void 0, error: void 0 });
|
|
38209
|
+
this.state = { ...this.state, currentStage: index + 1 };
|
|
37739
38210
|
try {
|
|
37740
|
-
this.deps.ledger.saveStageHandoff(this.state.runId, index, role, handoff);
|
|
38211
|
+
this.deps.ledger.saveStageHandoff(this.state.runId, index, stageDef.role, handoff);
|
|
37741
38212
|
} catch (err) {
|
|
37742
38213
|
this.deps.log(`pack handoff save failed: ${err.message}`);
|
|
37743
38214
|
}
|
|
37744
38215
|
await this.publish();
|
|
37745
38216
|
return true;
|
|
37746
38217
|
}
|
|
38218
|
+
/** The stage asked the user something: park the run, keep the conversation. */
|
|
38219
|
+
async awaitUser(index) {
|
|
38220
|
+
const name = this.state.stages[index].name;
|
|
38221
|
+
this.patchStage(index, { awaitingUser: true });
|
|
38222
|
+
this.control = "pause";
|
|
38223
|
+
await this.settle(
|
|
38224
|
+
"paused",
|
|
38225
|
+
`${name} asked you a question \u2014 answer it in the stage chat, then tap Resume.`
|
|
38226
|
+
);
|
|
38227
|
+
this.deps.log(
|
|
38228
|
+
`pack run ${this.state.runId} paused at stage ${index + 1}: awaiting the user's answer`
|
|
38229
|
+
);
|
|
38230
|
+
return false;
|
|
38231
|
+
}
|
|
37747
38232
|
async stall(index, reason, lastReply) {
|
|
37748
|
-
|
|
37749
|
-
|
|
37750
|
-
|
|
37751
|
-
|
|
38233
|
+
this.patchStage(index, { status: "failed", error: reason, awaitingUser: void 0 });
|
|
38234
|
+
await this.settle(
|
|
38235
|
+
"stalled",
|
|
38236
|
+
lastReply ? `${reason} \u2014 last reply: ${lastReply.slice(-300)}` : reason
|
|
38237
|
+
);
|
|
37752
38238
|
this.deps.log(`pack run ${this.state.runId} stalled at stage ${index + 1}: ${reason}`);
|
|
37753
38239
|
return false;
|
|
37754
38240
|
}
|
|
37755
|
-
|
|
37756
|
-
|
|
38241
|
+
priorOutcomes(index) {
|
|
38242
|
+
const out2 = [];
|
|
38243
|
+
for (let i = 0; i < index; i++) {
|
|
37757
38244
|
const s = this.state.stages[i];
|
|
37758
|
-
if (s.status === "done" && s.handoff)
|
|
38245
|
+
if (s.status === "done" && s.handoff)
|
|
38246
|
+
out2.push({ role: s.role, name: s.name, status: "done", handoff: s.handoff });
|
|
38247
|
+
else if (s.status === "skipped") out2.push({ role: s.role, name: s.name, status: "skipped" });
|
|
37759
38248
|
}
|
|
37760
|
-
return
|
|
38249
|
+
return out2;
|
|
37761
38250
|
}
|
|
37762
38251
|
};
|
|
37763
38252
|
|
|
@@ -37771,7 +38260,7 @@ function getActivePackRunner() {
|
|
|
37771
38260
|
}
|
|
37772
38261
|
|
|
37773
38262
|
// src/packs/handlers.ts
|
|
37774
|
-
var TERMINAL_STATUSES =
|
|
38263
|
+
var TERMINAL_STATUSES = TERMINAL_PACK_STATUSES;
|
|
37775
38264
|
var PACK_ACTIONS = /* @__PURE__ */ new Set(["pause", "resume", "retry_stage", "skip_stage", "abort"]);
|
|
37776
38265
|
function buildPackRunnerDeps(ctx) {
|
|
37777
38266
|
const cwd = ctx.opts.cwd;
|
|
@@ -37786,15 +38275,20 @@ function buildPackRunnerDeps(ctx) {
|
|
|
37786
38275
|
ctx.onActiveSessionChanged?.(id);
|
|
37787
38276
|
return id;
|
|
37788
38277
|
},
|
|
37789
|
-
runTurn: async (prompt, displayLine) => {
|
|
38278
|
+
runTurn: async (prompt, displayLine, onAwaiting) => {
|
|
37790
38279
|
await ctx.streaming.beginTurn();
|
|
37791
38280
|
ctx.history.appendUserPrompt(displayLine);
|
|
37792
|
-
|
|
37793
|
-
|
|
37794
|
-
|
|
37795
|
-
|
|
37796
|
-
|
|
37797
|
-
|
|
38281
|
+
ctx.streaming.setPendingListener(onAwaiting ?? null);
|
|
38282
|
+
try {
|
|
38283
|
+
await ctx.client.prompt(prompt);
|
|
38284
|
+
const text = ctx.streaming.getCurrentText();
|
|
38285
|
+
const awaitingUser = await ctx.streaming.closeTurnWithInteractiveDetection();
|
|
38286
|
+
ctx.history.appendAgentReply(text);
|
|
38287
|
+
await ctx.history.flush();
|
|
38288
|
+
return { text, awaitingUser };
|
|
38289
|
+
} finally {
|
|
38290
|
+
ctx.streaming.setPendingListener(null);
|
|
38291
|
+
}
|
|
37798
38292
|
},
|
|
37799
38293
|
cancel: () => ctx.client.cancel(),
|
|
37800
38294
|
mountSkills: (skillIds) => {
|
|
@@ -37805,16 +38299,27 @@ function buildPackRunnerDeps(ctx) {
|
|
|
37805
38299
|
log.warn("packs", `skill mount failed for ${id}: ${err.message}`);
|
|
37806
38300
|
}
|
|
37807
38301
|
}
|
|
38302
|
+
},
|
|
38303
|
+
unmountSkills: (skillIds) => {
|
|
38304
|
+
for (const id of skillIds) {
|
|
38305
|
+
try {
|
|
38306
|
+
configureSkill("remove", id);
|
|
38307
|
+
} catch (err) {
|
|
38308
|
+
log.warn("packs", `skill unmount failed for ${id}: ${err.message}`);
|
|
38309
|
+
}
|
|
38310
|
+
}
|
|
37808
38311
|
}
|
|
37809
38312
|
},
|
|
37810
38313
|
gates: {
|
|
37811
38314
|
head: () => gitHead(run, cwd),
|
|
37812
38315
|
canonicalCommit: (sha) => canonicalCommit(run, cwd, sha),
|
|
37813
38316
|
diffStat: (from, to) => diffStat(run, cwd, from, to),
|
|
38317
|
+
changedFiles: (from, to) => changedFiles(run, cwd, from, to),
|
|
37814
38318
|
runChecks: async () => {
|
|
37815
38319
|
const command2 = detectChecksCommand(cwd);
|
|
37816
38320
|
return command2 ? runChecks(run, cwd, command2) : null;
|
|
37817
|
-
}
|
|
38321
|
+
},
|
|
38322
|
+
readFile: async (relPath) => readWorkspaceFile(cwd, relPath)
|
|
37818
38323
|
},
|
|
37819
38324
|
ledger: {
|
|
37820
38325
|
saveRun: (state) => saveRun(cwd, state),
|
|
@@ -37832,19 +38337,38 @@ function buildPackRunnerDeps(ctx) {
|
|
|
37832
38337
|
log: (message) => log.info("packs", message)
|
|
37833
38338
|
};
|
|
37834
38339
|
}
|
|
38340
|
+
async function ensureRunner(ctx) {
|
|
38341
|
+
const active2 = getActivePackRunner();
|
|
38342
|
+
if (active2) return active2;
|
|
38343
|
+
const stored = loadLatestRun(ctx.opts.cwd);
|
|
38344
|
+
if (!stored || TERMINAL_STATUSES.has(stored.status)) return null;
|
|
38345
|
+
const runner = PackRunner.rehydrate(buildPackRunnerDeps(ctx), stored);
|
|
38346
|
+
if (!runner) return null;
|
|
38347
|
+
setActivePackRunner(runner);
|
|
38348
|
+
log.info(
|
|
38349
|
+
"packs",
|
|
38350
|
+
`rehydrated run ${stored.runId} from the ledger (was ${stored.status}) \u2192 ${runner.getState().status}`
|
|
38351
|
+
);
|
|
38352
|
+
await runner.announce();
|
|
38353
|
+
return runner;
|
|
38354
|
+
}
|
|
37835
38355
|
var packStartH = async (ctx) => {
|
|
37836
38356
|
const payload = ctx.cmd.payload;
|
|
37837
38357
|
const packId = typeof payload?.packId === "string" ? payload.packId : "";
|
|
37838
38358
|
const task = typeof payload?.task === "string" ? payload.task.trim() : "";
|
|
37839
38359
|
if (!isPackId(packId)) {
|
|
37840
|
-
await ctx.relay.sendResult(ctx.cmd.id, "failed", {
|
|
38360
|
+
await ctx.relay.sendResult(ctx.cmd.id, "failed", {
|
|
38361
|
+
error: `unknown pack: ${packId || "(none)"}`
|
|
38362
|
+
});
|
|
37841
38363
|
return;
|
|
37842
38364
|
}
|
|
37843
38365
|
if (task.length === 0) {
|
|
37844
|
-
await ctx.relay.sendResult(ctx.cmd.id, "failed", {
|
|
38366
|
+
await ctx.relay.sendResult(ctx.cmd.id, "failed", {
|
|
38367
|
+
error: "pack_start requires a non-empty task"
|
|
38368
|
+
});
|
|
37845
38369
|
return;
|
|
37846
38370
|
}
|
|
37847
|
-
const existing =
|
|
38371
|
+
const existing = await ensureRunner(ctx);
|
|
37848
38372
|
if (existing && !TERMINAL_STATUSES.has(existing.getState().status)) {
|
|
37849
38373
|
await ctx.relay.sendResult(ctx.cmd.id, "failed", {
|
|
37850
38374
|
error: "a pack run is already active on this session \u2014 pause/abort it first",
|
|
@@ -37867,10 +38391,12 @@ var packActionH = async (ctx) => {
|
|
|
37867
38391
|
const payload = ctx.cmd.payload;
|
|
37868
38392
|
const action = typeof payload?.action === "string" ? payload.action : "";
|
|
37869
38393
|
if (!PACK_ACTIONS.has(action)) {
|
|
37870
|
-
await ctx.relay.sendResult(ctx.cmd.id, "failed", {
|
|
38394
|
+
await ctx.relay.sendResult(ctx.cmd.id, "failed", {
|
|
38395
|
+
error: `unknown pack action: ${action || "(none)"}`
|
|
38396
|
+
});
|
|
37871
38397
|
return;
|
|
37872
38398
|
}
|
|
37873
|
-
const runner =
|
|
38399
|
+
const runner = await ensureRunner(ctx);
|
|
37874
38400
|
if (!runner) {
|
|
37875
38401
|
await ctx.relay.sendResult(ctx.cmd.id, "failed", {
|
|
37876
38402
|
error: "no active pack run in this session",
|
|
@@ -37878,11 +38404,20 @@ var packActionH = async (ctx) => {
|
|
|
37878
38404
|
});
|
|
37879
38405
|
return;
|
|
37880
38406
|
}
|
|
37881
|
-
const
|
|
37882
|
-
|
|
38407
|
+
const outcome = await runner.applyAction(action);
|
|
38408
|
+
if (outcome.rejected) {
|
|
38409
|
+
log.info("packs", `pack_action ${action} rejected: ${outcome.rejected}`);
|
|
38410
|
+
await ctx.relay.sendResult(ctx.cmd.id, "failed", {
|
|
38411
|
+
error: outcome.rejected,
|
|
38412
|
+
state: outcome.state
|
|
38413
|
+
});
|
|
38414
|
+
return;
|
|
38415
|
+
}
|
|
38416
|
+
await ctx.relay.sendResult(ctx.cmd.id, "completed", { state: outcome.state });
|
|
37883
38417
|
};
|
|
37884
38418
|
var packStatusH = async (ctx) => {
|
|
37885
|
-
const
|
|
38419
|
+
const runner = await ensureRunner(ctx);
|
|
38420
|
+
const state = runner?.getState() ?? loadLatestRun(ctx.opts.cwd);
|
|
37886
38421
|
await ctx.relay.sendResult(ctx.cmd.id, "completed", { state });
|
|
37887
38422
|
};
|
|
37888
38423
|
|
|
@@ -39556,6 +40091,25 @@ var StreamingState = class {
|
|
|
39556
40091
|
*/
|
|
39557
40092
|
text = "";
|
|
39558
40093
|
pending = null;
|
|
40094
|
+
/** Observer for "is a question pending?" flips. The Agent Packs runner marks
|
|
40095
|
+
* its stage `awaitingUser` while a permission prompt waits on the user
|
|
40096
|
+
* MID-turn, instead of a stage that looks silently "working" for the 5-min
|
|
40097
|
+
* permission TTL (live run 2026-09-23). Never throws into the turn. */
|
|
40098
|
+
pendingListener = null;
|
|
40099
|
+
setPendingListener(listener) {
|
|
40100
|
+
this.pendingListener = listener;
|
|
40101
|
+
}
|
|
40102
|
+
setPending(next) {
|
|
40103
|
+
const was = this.pending !== null;
|
|
40104
|
+
this.pending = next;
|
|
40105
|
+
const is = next !== null;
|
|
40106
|
+
if (was !== is) {
|
|
40107
|
+
try {
|
|
40108
|
+
this.pendingListener?.(is);
|
|
40109
|
+
} catch {
|
|
40110
|
+
}
|
|
40111
|
+
}
|
|
40112
|
+
}
|
|
39559
40113
|
/**
|
|
39560
40114
|
* While true, {@link append} drops incoming session/update deltas instead of
|
|
39561
40115
|
* publishing them. Used by the baton {@link AcpDriver} to swallow the
|
|
@@ -39658,7 +40212,7 @@ var StreamingState = class {
|
|
|
39658
40212
|
"acpRunner",
|
|
39659
40213
|
`permission ${args2.questionId.slice(0, 8)} TTL expired \u2014 auto-cancel`
|
|
39660
40214
|
);
|
|
39661
|
-
this.
|
|
40215
|
+
this.setPending(null);
|
|
39662
40216
|
void this.publisher.publishOutput({
|
|
39663
40217
|
type: "text",
|
|
39664
40218
|
content: permissionExpiredNotice(),
|
|
@@ -39667,13 +40221,13 @@ var StreamingState = class {
|
|
|
39667
40221
|
resolve11({ outcome: { outcome: "cancelled" } });
|
|
39668
40222
|
}
|
|
39669
40223
|
}, PERMISSION_TIMEOUT_MS);
|
|
39670
|
-
this.
|
|
40224
|
+
this.setPending({
|
|
39671
40225
|
kind: "permission",
|
|
39672
40226
|
questionId: args2.questionId,
|
|
39673
40227
|
options: args2.options,
|
|
39674
40228
|
resolve: resolve11,
|
|
39675
40229
|
timeoutTimer
|
|
39676
|
-
};
|
|
40230
|
+
});
|
|
39677
40231
|
});
|
|
39678
40232
|
}
|
|
39679
40233
|
/**
|
|
@@ -39682,7 +40236,7 @@ var StreamingState = class {
|
|
|
39682
40236
|
* `resolveSelection()` (user picked).
|
|
39683
40237
|
*/
|
|
39684
40238
|
registerFreeformOptions(options) {
|
|
39685
|
-
this.
|
|
40239
|
+
this.setPending({ kind: "free-form", options });
|
|
39686
40240
|
}
|
|
39687
40241
|
/**
|
|
39688
40242
|
* Apply a `select_option` command (delivered via the CLI's SSE
|
|
@@ -39709,7 +40263,7 @@ var StreamingState = class {
|
|
|
39709
40263
|
const picked = byId ?? this.pending.options[index];
|
|
39710
40264
|
clearTimeout(this.pending.timeoutTimer);
|
|
39711
40265
|
const resolve11 = this.pending.resolve;
|
|
39712
|
-
this.
|
|
40266
|
+
this.setPending(null);
|
|
39713
40267
|
if (!picked) {
|
|
39714
40268
|
log.warn("acpRunner", `select_option index=${index} out of bounds \u2014 cancel`);
|
|
39715
40269
|
resolve11({ outcome: { outcome: "cancelled" } });
|
|
@@ -39719,7 +40273,7 @@ var StreamingState = class {
|
|
|
39719
40273
|
return { kind: "resolved", optionId: picked.optionId };
|
|
39720
40274
|
}
|
|
39721
40275
|
const text = this.pending.options[index];
|
|
39722
|
-
this.
|
|
40276
|
+
this.setPending(null);
|
|
39723
40277
|
if (!text) {
|
|
39724
40278
|
log.warn("acpRunner", `select_option index=${index} out of bounds (free-form) \u2014 drop`);
|
|
39725
40279
|
return { kind: "none" };
|
|
@@ -39775,7 +40329,7 @@ var StreamingState = class {
|
|
|
39775
40329
|
if (this.pending?.kind === "permission") {
|
|
39776
40330
|
clearTimeout(this.pending.timeoutTimer);
|
|
39777
40331
|
}
|
|
39778
|
-
this.
|
|
40332
|
+
this.setPending(null);
|
|
39779
40333
|
if (opts?.clear !== false) {
|
|
39780
40334
|
await this.publisher.publishOutput({ type: "clear" });
|
|
39781
40335
|
}
|
|
@@ -39962,7 +40516,7 @@ var StreamingState = class {
|
|
|
39962
40516
|
done: true
|
|
39963
40517
|
});
|
|
39964
40518
|
}
|
|
39965
|
-
this.
|
|
40519
|
+
this.setPending({ kind: "free-form", options: extracted.options });
|
|
39966
40520
|
await this.publisher.publishOutput({
|
|
39967
40521
|
type: "select_prompt",
|
|
39968
40522
|
content: extracted.question ?? "Pick an option",
|
|
@@ -46506,7 +47060,7 @@ async function autoUpgradeBeforeCriticalCommand() {
|
|
|
46506
47060
|
if (process.env.NODE_ENV === "test") return;
|
|
46507
47061
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
46508
47062
|
if (process.env.CI) return;
|
|
46509
|
-
const current2 = true ? "2.75.
|
|
47063
|
+
const current2 = true ? "2.75.10" : null;
|
|
46510
47064
|
if (!current2) return;
|
|
46511
47065
|
const cache = readCache();
|
|
46512
47066
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -46523,7 +47077,7 @@ function checkForUpdates() {
|
|
|
46523
47077
|
if (process.env.CODEAM_DISABLE_UPDATE_CHECK === "1") return;
|
|
46524
47078
|
if (process.env.CI) return;
|
|
46525
47079
|
if (!process.stdout.isTTY) return;
|
|
46526
|
-
const current2 = true ? "2.75.
|
|
47080
|
+
const current2 = true ? "2.75.10" : null;
|
|
46527
47081
|
if (!current2) return;
|
|
46528
47082
|
const cache = readCache();
|
|
46529
47083
|
const fresh = cache && Date.now() - cache.fetchedAt < TTL_MS;
|
|
@@ -46546,7 +47100,7 @@ var SELF_UPDATE_INSTALL_TIMEOUT_MS = 18e4;
|
|
|
46546
47100
|
var SELF_UPDATE_LS_TIMEOUT_MS = 15e3;
|
|
46547
47101
|
var SUDO_PREFLIGHT_TIMEOUT_MS = 5e3;
|
|
46548
47102
|
function currentCliVersion() {
|
|
46549
|
-
return true ? "2.75.
|
|
47103
|
+
return true ? "2.75.10" : null;
|
|
46550
47104
|
}
|
|
46551
47105
|
async function installedVersion(deps) {
|
|
46552
47106
|
const ls = await deps.run(
|
|
@@ -48286,7 +48840,7 @@ function checkChokidar() {
|
|
|
48286
48840
|
}
|
|
48287
48841
|
async function doctor(args2 = []) {
|
|
48288
48842
|
const json = args2.includes("--json");
|
|
48289
|
-
const cliVersion = true ? "2.75.
|
|
48843
|
+
const cliVersion = true ? "2.75.10" : "0.0.0-dev";
|
|
48290
48844
|
const apiBase2 = resolveApiBaseUrl();
|
|
48291
48845
|
const diagnosticId = (0, import_node_crypto12.randomUUID)();
|
|
48292
48846
|
log.info("doctor", `run id=${diagnosticId} cli=${cliVersion}`);
|
|
@@ -48810,7 +49364,7 @@ async function mcpWarm(argv = [], opts = {}) {
|
|
|
48810
49364
|
// src/commands/version.ts
|
|
48811
49365
|
var import_picocolors15 = __toESM(require("picocolors"));
|
|
48812
49366
|
function version2() {
|
|
48813
|
-
const v = true ? "2.75.
|
|
49367
|
+
const v = true ? "2.75.10" : "unknown";
|
|
48814
49368
|
console.log(`${import_picocolors15.default.bold("codeam-cli")} ${import_picocolors15.default.cyan(v)}`);
|
|
48815
49369
|
}
|
|
48816
49370
|
|