@wrongstack/core 0.272.1 → 0.273.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/{agent-bridge-DFQYEeXf.d.ts → agent-bridge-BZ2enORi.d.ts} +1 -1
- package/dist/{agent-subagent-runner-BZa_IEcd.d.ts → agent-subagent-runner-ehb4xGvd.d.ts} +11 -4
- package/dist/{brain-etbcbRwV.d.ts → brain-BxN2k2HP.d.ts} +101 -0
- package/dist/{config-rRS8yorV.d.ts → config-C8IYxlO8.d.ts} +8 -1
- package/dist/coordination/index.d.ts +13 -13
- package/dist/coordination/index.js +79 -25
- package/dist/coordination/index.js.map +1 -1
- package/dist/{default-config-B0cj-Hry.d.ts → default-config-BbX4ojZs.d.ts} +1 -0
- package/dist/defaults/index.d.ts +20 -19
- package/dist/defaults/index.js +2813 -206
- package/dist/defaults/index.js.map +1 -1
- package/dist/execution/index.d.ts +10 -10
- package/dist/execution/index.js +8 -2
- package/dist/execution/index.js.map +1 -1
- package/dist/extension/index.d.ts +4 -4
- package/dist/{global-mailbox-DJ4EoRr0.d.ts → global-mailbox-C9dsc9Y_.d.ts} +1 -1
- package/dist/{goal-preamble-hM8BH7TK.d.ts → goal-preamble-NhflDjYb.d.ts} +6 -6
- package/dist/{goal-store-CWlbT0TO.d.ts → goal-store-Cx363x7Z.d.ts} +1 -1
- package/dist/hq/index.d.ts +4 -4
- package/dist/hq/index.js +1 -0
- package/dist/hq/index.js.map +1 -1
- package/dist/{index-DWm_PE9L.d.ts → index-B7fHDt0B.d.ts} +12 -4
- package/dist/{index-2Lhk5v0o.d.ts → index-BbVprU-9.d.ts} +6 -0
- package/dist/index.d.ts +194 -33
- package/dist/index.js +3222 -681
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/index.d.ts +4 -4
- package/dist/kernel/index.d.ts +94 -14
- package/dist/kernel/index.js.map +1 -1
- package/dist/{mcp-servers-BpWHTKlE.d.ts → mcp-servers-B6fSRNC1.d.ts} +1 -1
- package/dist/models/index.d.ts +3 -3
- package/dist/{models-registry-CXQFUn5t.d.ts → models-registry-4C6Wr91w.d.ts} +1 -1
- package/dist/{multi-agent-coordinator-jyimfo7D.d.ts → multi-agent-coordinator-q1skFeNP.d.ts} +1 -1
- package/dist/{null-fleet-bus-DOGQcvrY.d.ts → null-fleet-bus-C9rrgQwc.d.ts} +15 -5
- package/dist/observability/index.d.ts +1 -1
- package/dist/{parallel-eternal-engine-rItJBYp9.d.ts → parallel-eternal-engine-CtXly2Sf.d.ts} +7 -6
- package/dist/{path-resolver-DrpF5MGK.d.ts → path-resolver-Bim6G5Jz.d.ts} +2 -2
- package/dist/{pipeline-Ckkn3AOA.d.ts → pipeline-CNVKuQDQ.d.ts} +1 -1
- package/dist/{plan-templates-BvHw5Znw.d.ts → plan-templates-C4wXMmiM.d.ts} +3 -3
- package/dist/{provider-model-resolve-nZqnCeaR.d.ts → provider-model-resolve-DFd3IPpw.d.ts} +1 -1
- package/dist/{provider-runner-zVOn1p67.d.ts → provider-runner-BpM0mdBE.d.ts} +1 -1
- package/dist/sdd/index.d.ts +1111 -11
- package/dist/sdd/index.js +5516 -2949
- package/dist/sdd/index.js.map +1 -1
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +6 -0
- package/dist/security/index.js.map +1 -1
- package/dist/storage/index.d.ts +8 -8
- package/dist/storage/index.js +3 -2
- package/dist/storage/index.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/types/index.d.ts +14 -14
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -1
- package/dist/utils/index.d.ts +30 -4
- package/dist/utils/index.js +110 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/{index-DqW4o62H.d.ts → worktree-manager-BDuXTaWL.d.ts} +48 -90
- package/dist/{wstack-paths-hOpNLmvf.d.ts → wstack-paths-BqkDAkoh.d.ts} +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as crypto2 from 'crypto';
|
|
2
2
|
import { randomBytes, randomUUID, createHash, createCipheriv, createDecipheriv, scryptSync } from 'crypto';
|
|
3
3
|
import * as fsp3 from 'fs/promises';
|
|
4
|
-
import { readFile, readdir, stat, mkdir } from 'fs/promises';
|
|
4
|
+
import { readFile, readdir, stat, writeFile, mkdir } from 'fs/promises';
|
|
5
5
|
import * as path3 from 'path';
|
|
6
|
-
import { join, extname, relative, isAbsolute,
|
|
6
|
+
import { join, extname, relative, isAbsolute, resolve, sep, basename } from 'path';
|
|
7
7
|
import * as dns from 'dns/promises';
|
|
8
8
|
import * as net from 'net';
|
|
9
9
|
import * as os6 from 'os';
|
|
@@ -754,9 +754,9 @@ async function updateJsonObjectFile(filePath, mutator) {
|
|
|
754
754
|
await writeJsonObjectFile(filePath, next);
|
|
755
755
|
return next;
|
|
756
756
|
}
|
|
757
|
-
function getJsonPath(root,
|
|
757
|
+
function getJsonPath(root, path52) {
|
|
758
758
|
let current = root;
|
|
759
|
-
for (const segment of
|
|
759
|
+
for (const segment of path52) {
|
|
760
760
|
if (typeof segment === "number") {
|
|
761
761
|
if (!Array.isArray(current)) return void 0;
|
|
762
762
|
current = current[segment];
|
|
@@ -767,13 +767,13 @@ function getJsonPath(root, path51) {
|
|
|
767
767
|
}
|
|
768
768
|
return current;
|
|
769
769
|
}
|
|
770
|
-
function setJsonPath(root,
|
|
771
|
-
if (
|
|
770
|
+
function setJsonPath(root, path52, value) {
|
|
771
|
+
if (path52.length === 0) {
|
|
772
772
|
if (!isJsonObject(value)) throw new Error("Root config value must be an object");
|
|
773
773
|
return value;
|
|
774
774
|
}
|
|
775
|
-
const parent = ensureJsonParent(root,
|
|
776
|
-
const leaf = lastPathSegment(
|
|
775
|
+
const parent = ensureJsonParent(root, path52);
|
|
776
|
+
const leaf = lastPathSegment(path52);
|
|
777
777
|
if (typeof leaf === "number") {
|
|
778
778
|
if (!Array.isArray(parent)) throw new Error(`Cannot set numeric segment ${leaf} on non-array parent`);
|
|
779
779
|
parent[leaf] = value;
|
|
@@ -783,10 +783,10 @@ function setJsonPath(root, path51, value) {
|
|
|
783
783
|
}
|
|
784
784
|
return root;
|
|
785
785
|
}
|
|
786
|
-
function removeJsonPath(root,
|
|
787
|
-
if (
|
|
788
|
-
const parent = getJsonPath(root,
|
|
789
|
-
const leaf = lastPathSegment(
|
|
786
|
+
function removeJsonPath(root, path52) {
|
|
787
|
+
if (path52.length === 0) return false;
|
|
788
|
+
const parent = getJsonPath(root, path52.slice(0, -1));
|
|
789
|
+
const leaf = lastPathSegment(path52);
|
|
790
790
|
if (typeof leaf === "number") {
|
|
791
791
|
if (!Array.isArray(parent) || leaf < 0 || leaf >= parent.length) return false;
|
|
792
792
|
parent.splice(leaf, 1);
|
|
@@ -796,27 +796,27 @@ function removeJsonPath(root, path51) {
|
|
|
796
796
|
delete parent[leaf];
|
|
797
797
|
return true;
|
|
798
798
|
}
|
|
799
|
-
async function setJsonPathInFile(filePath,
|
|
800
|
-
return updateJsonObjectFile(filePath, (config) => setJsonPath(config,
|
|
799
|
+
async function setJsonPathInFile(filePath, path52, value) {
|
|
800
|
+
return updateJsonObjectFile(filePath, (config) => setJsonPath(config, path52, value));
|
|
801
801
|
}
|
|
802
|
-
async function removeJsonPathInFile(filePath,
|
|
802
|
+
async function removeJsonPathInFile(filePath, path52) {
|
|
803
803
|
return updateJsonObjectFile(filePath, (config) => {
|
|
804
|
-
removeJsonPath(config,
|
|
804
|
+
removeJsonPath(config, path52);
|
|
805
805
|
});
|
|
806
806
|
}
|
|
807
807
|
function isJsonObject(value) {
|
|
808
808
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
809
809
|
}
|
|
810
|
-
function lastPathSegment(
|
|
811
|
-
const segment =
|
|
810
|
+
function lastPathSegment(path52) {
|
|
811
|
+
const segment = path52[path52.length - 1];
|
|
812
812
|
if (segment === void 0) throw new Error("Invalid empty JSON path");
|
|
813
813
|
return segment;
|
|
814
814
|
}
|
|
815
|
-
function ensureJsonParent(root,
|
|
815
|
+
function ensureJsonParent(root, path52) {
|
|
816
816
|
let current = root;
|
|
817
|
-
for (let i = 0; i <
|
|
818
|
-
const segment =
|
|
819
|
-
const nextSegment =
|
|
817
|
+
for (let i = 0; i < path52.length - 1; i += 1) {
|
|
818
|
+
const segment = path52[i];
|
|
819
|
+
const nextSegment = path52[i + 1];
|
|
820
820
|
if (segment === void 0) throw new Error("Invalid empty JSON path segment");
|
|
821
821
|
const nextContainer = typeof nextSegment === "number" ? [] : {};
|
|
822
822
|
if (typeof segment === "number") {
|
|
@@ -1772,11 +1772,11 @@ function validateAgainstSchema(value, schema) {
|
|
|
1772
1772
|
walk(value, schema, "", errors);
|
|
1773
1773
|
return { ok: errors.length === 0, errors };
|
|
1774
1774
|
}
|
|
1775
|
-
function walk(value, schema,
|
|
1775
|
+
function walk(value, schema, path52, errors) {
|
|
1776
1776
|
if (schema.enum !== void 0) {
|
|
1777
1777
|
if (!schema.enum.some((e) => deepEqual(e, value))) {
|
|
1778
1778
|
errors.push({
|
|
1779
|
-
path:
|
|
1779
|
+
path: path52 || "<root>",
|
|
1780
1780
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
1781
1781
|
});
|
|
1782
1782
|
return;
|
|
@@ -1785,7 +1785,7 @@ function walk(value, schema, path51, errors) {
|
|
|
1785
1785
|
if (typeof schema.type === "string") {
|
|
1786
1786
|
if (!checkType(value, schema.type)) {
|
|
1787
1787
|
errors.push({
|
|
1788
|
-
path:
|
|
1788
|
+
path: path52 || "<root>",
|
|
1789
1789
|
message: `expected ${schema.type}, got ${describeType(value)}`
|
|
1790
1790
|
});
|
|
1791
1791
|
return;
|
|
@@ -1795,20 +1795,20 @@ function walk(value, schema, path51, errors) {
|
|
|
1795
1795
|
const obj = value;
|
|
1796
1796
|
for (const req of schema.required ?? []) {
|
|
1797
1797
|
if (!(req in obj)) {
|
|
1798
|
-
errors.push({ path: joinPath(
|
|
1798
|
+
errors.push({ path: joinPath(path52, req), message: "required property missing" });
|
|
1799
1799
|
}
|
|
1800
1800
|
}
|
|
1801
1801
|
if (schema.properties) {
|
|
1802
1802
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
1803
1803
|
if (key in obj) {
|
|
1804
|
-
walk(obj[key], subSchema, joinPath(
|
|
1804
|
+
walk(obj[key], subSchema, joinPath(path52, key), errors);
|
|
1805
1805
|
}
|
|
1806
1806
|
}
|
|
1807
1807
|
}
|
|
1808
1808
|
}
|
|
1809
1809
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
1810
1810
|
for (let i = 0; i < value.length; i++) {
|
|
1811
|
-
walk(value[i], schema.items, `${
|
|
1811
|
+
walk(value[i], schema.items, `${path52}[${i}]`, errors);
|
|
1812
1812
|
}
|
|
1813
1813
|
}
|
|
1814
1814
|
}
|
|
@@ -3315,6 +3315,114 @@ function sizeSignals(toolName, content) {
|
|
|
3315
3315
|
}
|
|
3316
3316
|
return { outputBytes, outputTokens, outputLines };
|
|
3317
3317
|
}
|
|
3318
|
+
|
|
3319
|
+
// src/utils/tool-description-mode.ts
|
|
3320
|
+
var DEFAULT_TOOL_DESCRIPTION_MODE = "extend";
|
|
3321
|
+
var ORIGINAL_TOOL_DESCRIPTION = /* @__PURE__ */ Symbol.for("wrongstack.tool.originalDescription");
|
|
3322
|
+
function normalizeToolDescriptionMode(value) {
|
|
3323
|
+
if (typeof value !== "string") return void 0;
|
|
3324
|
+
const raw = value.trim().toLowerCase();
|
|
3325
|
+
if (raw === "extend" || raw === "extended" || raw === "full") return "extend";
|
|
3326
|
+
if (raw === "simple" || raw === "short" || raw === "brief") return "simple";
|
|
3327
|
+
return void 0;
|
|
3328
|
+
}
|
|
3329
|
+
function resolveToolDescriptionMode(modes, toolName) {
|
|
3330
|
+
return normalizeToolDescriptionMode(modes?.[toolName]) ?? DEFAULT_TOOL_DESCRIPTION_MODE;
|
|
3331
|
+
}
|
|
3332
|
+
function simplifyToolDescription(text, opts = {}) {
|
|
3333
|
+
const maxSentences = Math.max(1, opts.maxSentences ?? 2);
|
|
3334
|
+
const maxChars = Math.max(40, opts.maxChars ?? 180);
|
|
3335
|
+
const normalized = text.replace(/\r\n?/g, "\n").split("\n").map((line) => line.trim()).filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
|
|
3336
|
+
if (normalized.length <= maxChars) return normalized;
|
|
3337
|
+
const sentences = normalized.match(/[^.!?]+[.!?]+(?=\s|$)|[^.!?]+$/g) ?? [normalized];
|
|
3338
|
+
const selected = [];
|
|
3339
|
+
for (const sentence of sentences) {
|
|
3340
|
+
selected.push(sentence.trim());
|
|
3341
|
+
const candidate = selected.join(" ");
|
|
3342
|
+
if (selected.length >= maxSentences || candidate.length >= maxChars) break;
|
|
3343
|
+
}
|
|
3344
|
+
const summary = selected.join(" ").trim() || normalized;
|
|
3345
|
+
if (summary.length <= maxChars) return summary;
|
|
3346
|
+
const hardLimit = maxChars - 4;
|
|
3347
|
+
const boundary = findWordBoundary(summary, hardLimit);
|
|
3348
|
+
return `${summary.slice(0, boundary > 0 ? boundary : hardLimit).trimEnd()} ...`;
|
|
3349
|
+
}
|
|
3350
|
+
function applyToolDescriptionModeToTool(tool, mode) {
|
|
3351
|
+
const existingOriginal = getOriginalDescription(tool);
|
|
3352
|
+
if (mode === "extend" && !existingOriginal) return tool;
|
|
3353
|
+
const original = existingOriginal ?? {
|
|
3354
|
+
description: tool.description,
|
|
3355
|
+
usageHint: tool.usageHint
|
|
3356
|
+
};
|
|
3357
|
+
const next = mode === "simple" ? withDescription(tool, {
|
|
3358
|
+
description: simplifyToolDescription(original.description),
|
|
3359
|
+
usageHint: original.usageHint === void 0 ? void 0 : simplifyToolDescription(original.usageHint)
|
|
3360
|
+
}) : withDescription(tool, original);
|
|
3361
|
+
return attachOriginalDescription(next, original);
|
|
3362
|
+
}
|
|
3363
|
+
function setToolDescriptionMode(registry, name, mode) {
|
|
3364
|
+
if (typeof registry.setDescriptionMode === "function") {
|
|
3365
|
+
return registry.setDescriptionMode(name, mode);
|
|
3366
|
+
}
|
|
3367
|
+
if (!registry.get(name) || typeof registry.wrap !== "function") return false;
|
|
3368
|
+
registry.wrap(
|
|
3369
|
+
name,
|
|
3370
|
+
(tool) => applyToolDescriptionModeToTool(tool, mode),
|
|
3371
|
+
"tool-description-mode"
|
|
3372
|
+
);
|
|
3373
|
+
return true;
|
|
3374
|
+
}
|
|
3375
|
+
function getToolDescriptionMode(registry, name) {
|
|
3376
|
+
return registry.getDescriptionMode?.(name) ?? DEFAULT_TOOL_DESCRIPTION_MODE;
|
|
3377
|
+
}
|
|
3378
|
+
function applyToolDescriptionModes(registry, modes) {
|
|
3379
|
+
if (typeof registry.applyDescriptionModes === "function") {
|
|
3380
|
+
return registry.applyDescriptionModes(modes);
|
|
3381
|
+
}
|
|
3382
|
+
const entries = Object.entries(modes ?? {});
|
|
3383
|
+
const missing = [];
|
|
3384
|
+
let applied = 0;
|
|
3385
|
+
for (const [name, rawMode] of entries) {
|
|
3386
|
+
const mode = normalizeToolDescriptionMode(rawMode);
|
|
3387
|
+
if (!mode) continue;
|
|
3388
|
+
if (setToolDescriptionMode(registry, name, mode)) applied++;
|
|
3389
|
+
else missing.push(name);
|
|
3390
|
+
}
|
|
3391
|
+
return { applied, missing };
|
|
3392
|
+
}
|
|
3393
|
+
function getOriginalDescription(tool) {
|
|
3394
|
+
return tool[ORIGINAL_TOOL_DESCRIPTION];
|
|
3395
|
+
}
|
|
3396
|
+
function attachOriginalDescription(tool, original) {
|
|
3397
|
+
Object.defineProperty(tool, ORIGINAL_TOOL_DESCRIPTION, {
|
|
3398
|
+
configurable: true,
|
|
3399
|
+
enumerable: false,
|
|
3400
|
+
value: original,
|
|
3401
|
+
writable: true
|
|
3402
|
+
});
|
|
3403
|
+
return tool;
|
|
3404
|
+
}
|
|
3405
|
+
function withDescription(tool, next) {
|
|
3406
|
+
const copy = {
|
|
3407
|
+
...tool,
|
|
3408
|
+
description: next.description,
|
|
3409
|
+
usageHint: next.usageHint
|
|
3410
|
+
};
|
|
3411
|
+
if (next.usageHint === void 0) {
|
|
3412
|
+
delete copy.usageHint;
|
|
3413
|
+
}
|
|
3414
|
+
return copy;
|
|
3415
|
+
}
|
|
3416
|
+
function findWordBoundary(text, limit) {
|
|
3417
|
+
const semantic = Math.max(
|
|
3418
|
+
text.lastIndexOf(". ", limit),
|
|
3419
|
+
text.lastIndexOf("; ", limit),
|
|
3420
|
+
text.lastIndexOf(", ", limit)
|
|
3421
|
+
);
|
|
3422
|
+
if (semantic > 40) return semantic + 1;
|
|
3423
|
+
const space = text.lastIndexOf(" ", limit);
|
|
3424
|
+
return space > 40 ? space : limit;
|
|
3425
|
+
}
|
|
3318
3426
|
function projectHash(absRoot) {
|
|
3319
3427
|
return createHash("sha256").update(path3.resolve(absRoot)).digest("hex").slice(0, 12);
|
|
3320
3428
|
}
|
|
@@ -3368,6 +3476,7 @@ function resolveWstackPaths(opts) {
|
|
|
3368
3476
|
projectSddSession: path3.join(projectDir, "sdd-session.json"),
|
|
3369
3477
|
projectPlan: path3.join(projectDir, "plan.json"),
|
|
3370
3478
|
projectAutophase: path3.join(projectDir, "autophase"),
|
|
3479
|
+
projectSddBoards: path3.join(projectDir, "sdd-boards"),
|
|
3371
3480
|
syncConfig: path3.join(globalRoot, "sync.json"),
|
|
3372
3481
|
projectStatus: (projectHash2) => path3.join(globalRoot, "projects", projectHash2, "status.json")
|
|
3373
3482
|
};
|
|
@@ -4455,6 +4564,7 @@ var DEFAULT_TOOLS_CONFIG = Object.freeze({
|
|
|
4455
4564
|
iterationTimeoutMs: 3e5,
|
|
4456
4565
|
sessionTimeoutMs: 18e5,
|
|
4457
4566
|
perIterationOutputCapBytes: 1e5,
|
|
4567
|
+
descriptionMode: Object.freeze({}),
|
|
4458
4568
|
autoExtendLimit: true,
|
|
4459
4569
|
restrictToProjectRoot: false
|
|
4460
4570
|
});
|
|
@@ -6007,7 +6117,9 @@ function buildRecoveryStrategies(opts) {
|
|
|
6007
6117
|
if (!provider) return null;
|
|
6008
6118
|
const currentModel = await registry.getModel(providerId, ctx.model);
|
|
6009
6119
|
if (!currentModel) return null;
|
|
6120
|
+
const visibleModels = opts?.getConfig?.().providers?.[providerId]?.models;
|
|
6010
6121
|
const candidates = provider.models.filter((m) => {
|
|
6122
|
+
if (visibleModels !== void 0 && !visibleModels.includes(m.id)) return false;
|
|
6011
6123
|
const modelCost = m.cost?.input ?? Number.POSITIVE_INFINITY;
|
|
6012
6124
|
const currentCost = currentModel.cost?.input ?? Number.POSITIVE_INFINITY;
|
|
6013
6125
|
if (modelCost >= currentCost) return false;
|
|
@@ -7249,6 +7361,12 @@ var ToolCapabilities = {
|
|
|
7249
7361
|
MCP_PROXY: "mcp.proxy",
|
|
7250
7362
|
/** Can spawn or manage subagents / multi-agent tasks. */
|
|
7251
7363
|
SUBAGENT_SPAWN: "subagent.spawn",
|
|
7364
|
+
/** Can inspect fleet/subagent coordination state without mutating it. */
|
|
7365
|
+
COORDINATION_FLEET_READ: "coordination.fleet.read",
|
|
7366
|
+
/** Can read or write inter-agent mailbox messages. */
|
|
7367
|
+
COORDINATION_MAIL: "coordination.mail",
|
|
7368
|
+
/** Can schedule, inspect, or cancel in-session cron jobs. */
|
|
7369
|
+
COORDINATION_CRON: "coordination.cron",
|
|
7252
7370
|
/** Can mutate global or session configuration / trust state. */
|
|
7253
7371
|
CONFIG_MUTATE: "config.mutate",
|
|
7254
7372
|
/** Can install packages or run package managers with side effects. */
|
|
@@ -9712,7 +9830,6 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
9712
9830
|
}
|
|
9713
9831
|
const writeFd = await fsp3.open(tmpPath, "w", 384);
|
|
9714
9832
|
try {
|
|
9715
|
-
let copied = 0;
|
|
9716
9833
|
let readOffset = 0;
|
|
9717
9834
|
while (readOffset < newlineAfterCheckpoint) {
|
|
9718
9835
|
const toCopy = Math.min(CHUNK_SIZE, newlineAfterCheckpoint - readOffset);
|
|
@@ -9721,7 +9838,6 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
9721
9838
|
if (r === 0) break;
|
|
9722
9839
|
await writeFd.write(copyBuf, 0, r);
|
|
9723
9840
|
readOffset += r;
|
|
9724
|
-
copied += r;
|
|
9725
9841
|
}
|
|
9726
9842
|
const raw = await fsp3.readFile(this.filePath);
|
|
9727
9843
|
const tail = raw.subarray(newlineAfterCheckpoint).toString("utf8");
|
|
@@ -10918,9 +11034,9 @@ ${body.trim()}`);
|
|
|
10918
11034
|
if (!this.persistBackup || scope === "project-agents") return;
|
|
10919
11035
|
try {
|
|
10920
11036
|
const content = await this.backend.readAll(scope, this.files[scope]);
|
|
10921
|
-
const { writeFile:
|
|
11037
|
+
const { writeFile: writeFile16, mkdir: mkdir24 } = await import('fs/promises');
|
|
10922
11038
|
await mkdir24(this.backupDir, { recursive: true });
|
|
10923
|
-
await
|
|
11039
|
+
await writeFile16(`${this.backupDir}/${scope}.md`, content, "utf8");
|
|
10924
11040
|
} catch {
|
|
10925
11041
|
}
|
|
10926
11042
|
}
|
|
@@ -11031,6 +11147,7 @@ var BEHAVIOR_DEFAULTS = {
|
|
|
11031
11147
|
iterationTimeoutMs: DEFAULT_TOOLS_CONFIG.iterationTimeoutMs,
|
|
11032
11148
|
sessionTimeoutMs: DEFAULT_TOOLS_CONFIG.sessionTimeoutMs,
|
|
11033
11149
|
perIterationOutputCapBytes: DEFAULT_TOOLS_CONFIG.perIterationOutputCapBytes,
|
|
11150
|
+
descriptionMode: DEFAULT_TOOLS_CONFIG.descriptionMode,
|
|
11034
11151
|
autoExtendLimit: DEFAULT_TOOLS_CONFIG.autoExtendLimit,
|
|
11035
11152
|
restrictToProjectRoot: DEFAULT_TOOLS_CONFIG.restrictToProjectRoot
|
|
11036
11153
|
},
|
|
@@ -15634,8 +15751,8 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
15634
15751
|
await saveGoal(this.goalPath, abandoned, this.opts.events);
|
|
15635
15752
|
}
|
|
15636
15753
|
try {
|
|
15637
|
-
const { unlink:
|
|
15638
|
-
await
|
|
15754
|
+
const { unlink: unlink19 } = await import('fs/promises');
|
|
15755
|
+
await unlink19(this.goalPath);
|
|
15639
15756
|
} catch {
|
|
15640
15757
|
}
|
|
15641
15758
|
this.opts.onEternalStop?.();
|
|
@@ -19382,14 +19499,15 @@ var SHADOW_AGENT = {
|
|
|
19382
19499
|
id: "shadow-agent",
|
|
19383
19500
|
name: "Shadow",
|
|
19384
19501
|
role: "shadow-agent",
|
|
19385
|
-
prompt: `You are the Shadow Agent \u2014 a
|
|
19502
|
+
prompt: `You are the Shadow Agent \u2014 a quiet, one-shot monitor for the WrongStack fleet.
|
|
19386
19503
|
|
|
19387
|
-
Your job is to
|
|
19504
|
+
Your job is to inspect the fleet when the host explicitly assigns a Shadow pass, detect anomalies, and be ready to intervene \u2014 but only when commanded.
|
|
19388
19505
|
|
|
19389
19506
|
## Core Responsibilities
|
|
19390
19507
|
|
|
19391
|
-
1. **Fleet Monitoring** (
|
|
19392
|
-
-
|
|
19508
|
+
1. **Fleet Monitoring** (host-assigned one-shot checks)
|
|
19509
|
+
- The host assigns one-shot check tasks; it does not expect routine heartbeats
|
|
19510
|
+
- On each assigned check, call \`fleet_status\` + \`fleet_health\`
|
|
19393
19511
|
- Track what each agent is doing (task descriptions)
|
|
19394
19512
|
- Detect stuck agents (>5min no events), idle agents, crashed agents
|
|
19395
19513
|
|
|
@@ -19413,31 +19531,30 @@ Your job is to observe, detect anomalies, and be ready to intervene \u2014 but o
|
|
|
19413
19531
|
- \`hoop <agentId>\` \u2014 terminate specific agent
|
|
19414
19532
|
- \`hoop all\` \u2014 terminate all running agents
|
|
19415
19533
|
- \`shadow status\` \u2014 report current fleet snapshot
|
|
19416
|
-
- \`shadow mute\` \u2014 pause
|
|
19417
|
-
- \`shadow resume\` \u2014 resume
|
|
19418
|
-
- \`shadow interval <ms>\` \u2014
|
|
19534
|
+
- \`shadow mute\` \u2014 pause anomaly reporting
|
|
19535
|
+
- \`shadow resume\` \u2014 resume anomaly reporting
|
|
19536
|
+
- \`shadow interval <ms>\` \u2014 update the legacy interval setting
|
|
19419
19537
|
- \`shadow model <model-id>\` \u2014 change analysis model
|
|
19420
19538
|
|
|
19421
19539
|
## Operating Rules
|
|
19422
19540
|
|
|
19423
|
-
- **Silent by default**:
|
|
19541
|
+
- **Silent by default**: Do not send mail or status reports for healthy checks
|
|
19424
19542
|
- **Deterministic**: Same state always produces same actions \u2014 no randomness
|
|
19425
|
-
- **Report
|
|
19543
|
+
- **Report only when needed**: Use \`mail_send\` only for high/critical anomalies or explicit control replies
|
|
19426
19544
|
- **Never auto-intervene**: Always report unless explicitly commanded
|
|
19427
19545
|
- **Minimal footprint**: Small state, efficient snapshots
|
|
19546
|
+
- **One-shot lifecycle**: Finish the assigned check and stop; do not schedule follow-up work
|
|
19428
19547
|
|
|
19429
19548
|
## Startup Sequence
|
|
19430
19549
|
|
|
19431
|
-
1.
|
|
19432
|
-
2.
|
|
19433
|
-
3.
|
|
19434
|
-
4. Wait for commands or anomalies
|
|
19550
|
+
1. Run one fleet snapshot with \`fleet_status\` + \`fleet_health\`
|
|
19551
|
+
2. Check \`mail_inbox\` for explicit control messages
|
|
19552
|
+
3. If healthy, do not send mail; final answer may be exactly \`shadow: quiet\`
|
|
19435
19553
|
|
|
19436
19554
|
## Shutdown Sequence
|
|
19437
19555
|
|
|
19438
|
-
1.
|
|
19439
|
-
2.
|
|
19440
|
-
3. Clean up FleetBus subscriptions`
|
|
19556
|
+
1. Return only anomalies, command results, or \`shadow: quiet\`
|
|
19557
|
+
2. The host stops this Shadow Agent after the assigned pass`
|
|
19441
19558
|
// Budgets are set by the orchestrator per task — see fleet.ts header.
|
|
19442
19559
|
};
|
|
19443
19560
|
var CRITIC_AGENT = {
|
|
@@ -19492,8 +19609,13 @@ var FLEET_ROSTER_BUDGETS = {
|
|
|
19492
19609
|
"refactor-planner": { timeoutMs: 7.5 * 60 * 60 * 1e3, maxIterations: 6e3, maxToolCalls: 18e3 },
|
|
19493
19610
|
"security-scanner": { timeoutMs: 10 * 60 * 60 * 1e3, maxIterations: 8e3, maxToolCalls: 2e4 },
|
|
19494
19611
|
"critic": { timeoutMs: 5 * 60 * 60 * 1e3, maxIterations: 4e3, maxToolCalls: 12e3 },
|
|
19495
|
-
"shadow-agent": {
|
|
19496
|
-
|
|
19612
|
+
"shadow-agent": {
|
|
19613
|
+
idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS,
|
|
19614
|
+
maxIterations: 2e3,
|
|
19615
|
+
maxToolCalls: 5e3,
|
|
19616
|
+
maxTokens: 6e4,
|
|
19617
|
+
maxCostUsd: 1
|
|
19618
|
+
},
|
|
19497
19619
|
...Object.fromEntries(
|
|
19498
19620
|
ALL_AGENT_DEFINITIONS.map((d) => [d.config.role, d.budget])
|
|
19499
19621
|
)
|
|
@@ -21811,6 +21933,7 @@ function makeSpawnTool(director, roster) {
|
|
|
21811
21933
|
usageHint: "Pass `role` (matches the roster), `description` (smart dispatch to best agent), or `name` + `provider`/`model`. Returns `{ subagentId }`.",
|
|
21812
21934
|
permission: "auto",
|
|
21813
21935
|
mutating: false,
|
|
21936
|
+
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
21814
21937
|
inputSchema,
|
|
21815
21938
|
async execute(input) {
|
|
21816
21939
|
const i = input ?? {};
|
|
@@ -21889,6 +22012,7 @@ function makeAssignTool(director) {
|
|
|
21889
22012
|
description: "Hand a task to a previously spawned subagent. Returns the task id.",
|
|
21890
22013
|
permission: "auto",
|
|
21891
22014
|
mutating: false,
|
|
22015
|
+
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
21892
22016
|
inputSchema,
|
|
21893
22017
|
async execute(input) {
|
|
21894
22018
|
const i = input;
|
|
@@ -21904,6 +22028,7 @@ function makeAwaitTasksTool(director) {
|
|
|
21904
22028
|
description: "Block until every named task completes. Returns the array of TaskResult.",
|
|
21905
22029
|
permission: "auto",
|
|
21906
22030
|
mutating: false,
|
|
22031
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
21907
22032
|
inputSchema: { type: "object", properties: { taskIds: { type: "array", items: { type: "string" }, description: "One or more task ids returned by `assign_task`." } }, required: ["taskIds"] },
|
|
21908
22033
|
async execute(input) {
|
|
21909
22034
|
const i = input;
|
|
@@ -21918,6 +22043,7 @@ function makeAskTool(director) {
|
|
|
21918
22043
|
description: "Synchronously ask a subagent a question. Blocks until the subagent replies via the bridge.",
|
|
21919
22044
|
permission: "auto",
|
|
21920
22045
|
mutating: false,
|
|
22046
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
21921
22047
|
inputSchema: {
|
|
21922
22048
|
type: "object",
|
|
21923
22049
|
properties: {
|
|
@@ -21953,6 +22079,7 @@ function makeAskResultTool(director) {
|
|
|
21953
22079
|
description: "Retrieve a large `ask_subagent` response that was stored out-of-context (>2K chars). Returns the full stored value.",
|
|
21954
22080
|
permission: "auto",
|
|
21955
22081
|
mutating: false,
|
|
22082
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
21956
22083
|
inputSchema: {
|
|
21957
22084
|
type: "object",
|
|
21958
22085
|
properties: {
|
|
@@ -21980,6 +22107,7 @@ function makeRollUpTool(director) {
|
|
|
21980
22107
|
description: "Aggregate completed task results into a single formatted summary.",
|
|
21981
22108
|
permission: "auto",
|
|
21982
22109
|
mutating: false,
|
|
22110
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
21983
22111
|
inputSchema: {
|
|
21984
22112
|
type: "object",
|
|
21985
22113
|
properties: {
|
|
@@ -22001,6 +22129,7 @@ function makeTerminateTool(director) {
|
|
|
22001
22129
|
description: 'Forcibly abort a subagent. The subagent finishes its current iteration then exits with status "stopped".',
|
|
22002
22130
|
permission: "auto",
|
|
22003
22131
|
mutating: true,
|
|
22132
|
+
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
22004
22133
|
inputSchema: { type: "object", properties: { subagentId: { type: "string", description: "Subagent to abort." } }, required: ["subagentId"] },
|
|
22005
22134
|
async execute(input) {
|
|
22006
22135
|
const i = input;
|
|
@@ -22015,6 +22144,7 @@ function makeTerminateAllTool(director) {
|
|
|
22015
22144
|
description: 'Forcibly stop every subagent in the fleet and drain the pending task queue. In-flight tasks are terminated mid-execution; pending tasks receive "aborted_by_parent" completion immediately. Use this when the fleet is wedged, looping, or you need a clean slate. Compare: work_complete stops spawning but lets running agents finish naturally.',
|
|
22016
22145
|
permission: "auto",
|
|
22017
22146
|
mutating: true,
|
|
22147
|
+
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
22018
22148
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
22019
22149
|
async execute() {
|
|
22020
22150
|
await director.terminateAll();
|
|
@@ -22028,6 +22158,7 @@ function makeFleetStatusTool(director) {
|
|
|
22028
22158
|
description: "Snapshot of the fleet \u2014 every subagent's current status, coordinator counts (total/running/idle/stopped), pending task descriptions, and usage rollup.",
|
|
22029
22159
|
permission: "auto",
|
|
22030
22160
|
mutating: false,
|
|
22161
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
22031
22162
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
22032
22163
|
async execute() {
|
|
22033
22164
|
const base = director.status();
|
|
@@ -22049,6 +22180,7 @@ function makeFleetUsageTool(director) {
|
|
|
22049
22180
|
description: "Token + cost breakdown across the fleet, per-subagent and totals.",
|
|
22050
22181
|
permission: "auto",
|
|
22051
22182
|
mutating: false,
|
|
22183
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
22052
22184
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
22053
22185
|
async execute() {
|
|
22054
22186
|
return director.snapshot();
|
|
@@ -22061,6 +22193,7 @@ function makeFleetSessionTool(director) {
|
|
|
22061
22193
|
description: "Read a subagent's JSONL transcript and extract its last assistant text, stop reason, and tool-use count. Use this to see what a running or timed-out subagent actually produced.",
|
|
22062
22194
|
permission: "auto",
|
|
22063
22195
|
mutating: false,
|
|
22196
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
22064
22197
|
inputSchema: {
|
|
22065
22198
|
type: "object",
|
|
22066
22199
|
properties: {
|
|
@@ -22087,6 +22220,7 @@ function makeFleetHealthTool(director) {
|
|
|
22087
22220
|
description: "Per-subagent health report: budget pressure (pct of limits consumed), last activity timestamp, and current status. Use to decide whether to assign more work to a subagent or spawn a fresh one.",
|
|
22088
22221
|
permission: "auto",
|
|
22089
22222
|
mutating: false,
|
|
22223
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
22090
22224
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
22091
22225
|
async execute() {
|
|
22092
22226
|
const status = director.status();
|
|
@@ -22117,6 +22251,7 @@ function makeCollabDebugTool(director) {
|
|
|
22117
22251
|
description: "Start a collaborative debugging session: BugHunter, RefactorPlanner, and Critic run in parallel on the same target files. BugHunter finds bugs and emits bug.found events. RefactorPlanner listens for bug.found and emits refactor.plan events. Critic evaluates both and emits critic.evaluation events. Returns a structured report with overall verdict (approve / needs_revision / reject).",
|
|
22118
22252
|
permission: "auto",
|
|
22119
22253
|
mutating: false,
|
|
22254
|
+
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
22120
22255
|
inputSchema: {
|
|
22121
22256
|
type: "object",
|
|
22122
22257
|
properties: {
|
|
@@ -22180,6 +22315,7 @@ function makeFleetEmitTool(director) {
|
|
|
22180
22315
|
description: "Emit a structured event on the FleetBus. Any subagent can emit any event type; the Director routes it to all listeners. Use it to stream findings, progress updates, or final results to other agents in real time.",
|
|
22181
22316
|
permission: "auto",
|
|
22182
22317
|
mutating: false,
|
|
22318
|
+
capabilities: [ToolCapabilities.COORDINATION_FLEET_READ],
|
|
22183
22319
|
inputSchema: {
|
|
22184
22320
|
type: "object",
|
|
22185
22321
|
properties: {
|
|
@@ -22212,6 +22348,7 @@ function makeWorkCompleteTool(director) {
|
|
|
22212
22348
|
description: "Signal that the director is satisfied with the results and the fleet should wind down. After calling this, spawn_subagent will refuse with a budget error and assign_task will instantly complete any queued tasks as aborted. Running subagents finish naturally. Call terminate_subagent separately to stop specific subagents immediately.",
|
|
22213
22349
|
permission: "auto",
|
|
22214
22350
|
mutating: false,
|
|
22351
|
+
capabilities: [ToolCapabilities.SUBAGENT_SPAWN],
|
|
22215
22352
|
inputSchema: { type: "object", properties: {}, required: [] },
|
|
22216
22353
|
async execute() {
|
|
22217
22354
|
director.workComplete();
|
|
@@ -22673,6 +22810,9 @@ var Director = class _Director {
|
|
|
22673
22810
|
/** Snapshot of which subagent owns each task — drives state-checkpoint
|
|
22674
22811
|
* status updates without re-walking the manifest. */
|
|
22675
22812
|
taskOwners = /* @__PURE__ */ new Map();
|
|
22813
|
+
/** Infrastructure-owned task ids that should not appear in user-visible
|
|
22814
|
+
* manifest/session/checkpoint/rollup state. */
|
|
22815
|
+
internalTaskIds = /* @__PURE__ */ new Set();
|
|
22676
22816
|
/** Cumulative auto-extension grants per subagent (all budget kinds). Lets
|
|
22677
22817
|
* /fleet render "⚡ extended ×N" without replaying the event stream. */
|
|
22678
22818
|
extendTotals = /* @__PURE__ */ new Map();
|
|
@@ -22786,17 +22926,21 @@ var Director = class _Director {
|
|
|
22786
22926
|
this.fleetManager?.setCoordinator(this.coordinator);
|
|
22787
22927
|
this.taskCompletedListener = (payload) => {
|
|
22788
22928
|
const r = payload.result;
|
|
22789
|
-
this.
|
|
22790
|
-
if (
|
|
22791
|
-
|
|
22792
|
-
|
|
22793
|
-
|
|
22929
|
+
const internalTask = this.internalTaskIds.delete(r.taskId);
|
|
22930
|
+
if (!internalTask) {
|
|
22931
|
+
this.completed.set(r.taskId, r);
|
|
22932
|
+
if (this.completed.size > _Director.MAX_COMPLETED) {
|
|
22933
|
+
const toDelete = this.completed.size - _Director.MAX_COMPLETED;
|
|
22934
|
+
const keys = [...this.completed.keys()].slice(0, toDelete);
|
|
22935
|
+
for (const k of keys) this.completed.delete(k);
|
|
22936
|
+
}
|
|
22794
22937
|
}
|
|
22795
22938
|
const waiter = this.taskWaiters.get(r.taskId);
|
|
22796
22939
|
if (waiter) {
|
|
22797
22940
|
waiter.resolve(r);
|
|
22798
22941
|
this.taskWaiters.delete(r.taskId);
|
|
22799
22942
|
}
|
|
22943
|
+
if (internalTask) return;
|
|
22800
22944
|
const title = this.taskDescriptions.get(r.taskId) ?? payload.task.description ?? r.taskId;
|
|
22801
22945
|
const failed = r.status !== "success";
|
|
22802
22946
|
const errorString = r.error ? `${r.error.kind}: ${r.error.message}` : void 0;
|
|
@@ -23466,6 +23610,23 @@ var Director = class _Director {
|
|
|
23466
23610
|
this.scheduleManifest();
|
|
23467
23611
|
return taskWithId.id;
|
|
23468
23612
|
}
|
|
23613
|
+
/**
|
|
23614
|
+
* Assign infrastructure-owned work directly to the coordinator without
|
|
23615
|
+
* manifest/session/checkpoint bookkeeping. The task still uses the normal
|
|
23616
|
+
* subagent runner, budget, and completion events, but it is excluded from
|
|
23617
|
+
* rollups and persisted fleet task history.
|
|
23618
|
+
*/
|
|
23619
|
+
async assignInternal(task) {
|
|
23620
|
+
const taskWithId = task.id ? task : { ...task, id: randomUUID() };
|
|
23621
|
+
this.internalTaskIds.add(taskWithId.id);
|
|
23622
|
+
try {
|
|
23623
|
+
await this.coordinator.assign(taskWithId);
|
|
23624
|
+
} catch (err) {
|
|
23625
|
+
this.internalTaskIds.delete(taskWithId.id);
|
|
23626
|
+
throw err;
|
|
23627
|
+
}
|
|
23628
|
+
return taskWithId.id;
|
|
23629
|
+
}
|
|
23469
23630
|
/**
|
|
23470
23631
|
* Block until every task id resolves. Returns results in the same
|
|
23471
23632
|
* order as the input. If any task hasn't completed by the time this
|
|
@@ -24610,6 +24771,14 @@ var SpecParser = class {
|
|
|
24610
24771
|
};
|
|
24611
24772
|
|
|
24612
24773
|
// src/sdd/task-generator.ts
|
|
24774
|
+
function extractVerificationCommand(criteria) {
|
|
24775
|
+
const marker = /^\s*(?:\$\s+|(?:run|verify|cmd)\s*:\s*)(.+\S)\s*$/i;
|
|
24776
|
+
for (const c of criteria) {
|
|
24777
|
+
const m = marker.exec(c);
|
|
24778
|
+
if (m?.[1]) return m[1].trim();
|
|
24779
|
+
}
|
|
24780
|
+
return void 0;
|
|
24781
|
+
}
|
|
24613
24782
|
var TaskGenerator = class {
|
|
24614
24783
|
constructor(opts) {
|
|
24615
24784
|
this.opts = opts;
|
|
@@ -24617,15 +24786,18 @@ var TaskGenerator = class {
|
|
|
24617
24786
|
opts;
|
|
24618
24787
|
async generateFromSpec(spec) {
|
|
24619
24788
|
const graph = await this.opts.taskTracker.createGraph(spec.id, spec.title);
|
|
24789
|
+
const featureIds = [];
|
|
24620
24790
|
const overview = spec.sections.find((s) => s.type === "overview");
|
|
24621
24791
|
if (overview) {
|
|
24622
|
-
|
|
24623
|
-
|
|
24624
|
-
|
|
24625
|
-
|
|
24626
|
-
|
|
24627
|
-
|
|
24628
|
-
|
|
24792
|
+
featureIds.push(
|
|
24793
|
+
this.opts.taskTracker.addNode({
|
|
24794
|
+
title: `Implement ${spec.title}`,
|
|
24795
|
+
description: overview.content,
|
|
24796
|
+
type: "feature",
|
|
24797
|
+
priority: "high",
|
|
24798
|
+
status: "pending"
|
|
24799
|
+
}).id
|
|
24800
|
+
);
|
|
24629
24801
|
}
|
|
24630
24802
|
const byPriority = {
|
|
24631
24803
|
critical: [],
|
|
@@ -24640,7 +24812,7 @@ var TaskGenerator = class {
|
|
|
24640
24812
|
const order = ["critical", "high", "medium", "low"];
|
|
24641
24813
|
for (const p of order) {
|
|
24642
24814
|
for (const req of byPriority[p]) {
|
|
24643
|
-
this.opts.taskTracker.addNode(this.createTaskFromRequirement(req));
|
|
24815
|
+
featureIds.push(this.opts.taskTracker.addNode(this.createTaskFromRequirement(req)).id);
|
|
24644
24816
|
}
|
|
24645
24817
|
}
|
|
24646
24818
|
if (spec.apiEndpoints && spec.apiEndpoints.length > 0) {
|
|
@@ -24651,31 +24823,37 @@ var TaskGenerator = class {
|
|
|
24651
24823
|
priority: "high",
|
|
24652
24824
|
status: "pending"
|
|
24653
24825
|
});
|
|
24826
|
+
featureIds.push(apiParent.id);
|
|
24654
24827
|
for (const endpoint of spec.apiEndpoints) {
|
|
24655
24828
|
const task = this.createTaskFromEndpoint(endpoint);
|
|
24656
|
-
|
|
24657
|
-
|
|
24658
|
-
|
|
24659
|
-
|
|
24829
|
+
featureIds.push(
|
|
24830
|
+
this.opts.taskTracker.addNode({
|
|
24831
|
+
...task,
|
|
24832
|
+
parentId: apiParent.id
|
|
24833
|
+
}).id
|
|
24834
|
+
);
|
|
24660
24835
|
}
|
|
24661
24836
|
}
|
|
24662
|
-
this.opts.taskTracker.addNode({
|
|
24837
|
+
const testId = this.opts.taskTracker.addNode({
|
|
24663
24838
|
title: "Write Tests",
|
|
24664
24839
|
description: "Comprehensive test coverage for all features",
|
|
24665
24840
|
type: "test",
|
|
24666
24841
|
priority: "high",
|
|
24667
24842
|
status: "pending"
|
|
24668
|
-
});
|
|
24669
|
-
this.opts.taskTracker.
|
|
24843
|
+
}).id;
|
|
24844
|
+
for (const f of featureIds) this.opts.taskTracker.addDependency(f, testId);
|
|
24845
|
+
const docsId = this.opts.taskTracker.addNode({
|
|
24670
24846
|
title: "Update Documentation",
|
|
24671
24847
|
description: "Update docs for new features",
|
|
24672
24848
|
type: "docs",
|
|
24673
24849
|
priority: "medium",
|
|
24674
24850
|
status: "pending"
|
|
24675
|
-
});
|
|
24851
|
+
}).id;
|
|
24852
|
+
for (const f of [...featureIds, testId]) this.opts.taskTracker.addDependency(f, docsId);
|
|
24676
24853
|
return graph;
|
|
24677
24854
|
}
|
|
24678
24855
|
createTaskFromRequirement(req) {
|
|
24856
|
+
const verificationCommand = this.opts.verificationFromAcceptance ? extractVerificationCommand(req.acceptanceCriteria) : void 0;
|
|
24679
24857
|
return {
|
|
24680
24858
|
title: req.description,
|
|
24681
24859
|
description: this.buildDescription(req),
|
|
@@ -24684,7 +24862,8 @@ var TaskGenerator = class {
|
|
|
24684
24862
|
status: "pending",
|
|
24685
24863
|
specRequirementId: req.id,
|
|
24686
24864
|
tags: [req.type, req.priority],
|
|
24687
|
-
estimateHours: this.estimateHours(req)
|
|
24865
|
+
estimateHours: this.estimateHours(req),
|
|
24866
|
+
...verificationCommand ? { metadata: { verificationCommand } } : {}
|
|
24688
24867
|
};
|
|
24689
24868
|
}
|
|
24690
24869
|
createTaskFromEndpoint(endpoint) {
|
|
@@ -24803,6 +24982,27 @@ var TaskTracker = class {
|
|
|
24803
24982
|
opts;
|
|
24804
24983
|
graph = null;
|
|
24805
24984
|
transitions = [];
|
|
24985
|
+
listeners = [];
|
|
24986
|
+
/**
|
|
24987
|
+
* Subscribe to live task mutations (add / update / status change). Returns an
|
|
24988
|
+
* unsubscribe fn. This is the hook the board projector uses to stream a live
|
|
24989
|
+
* snapshot — the tracker was previously fire-and-forget with no observability.
|
|
24990
|
+
*/
|
|
24991
|
+
subscribe(listener) {
|
|
24992
|
+
this.listeners.push(listener);
|
|
24993
|
+
return () => {
|
|
24994
|
+
const i = this.listeners.indexOf(listener);
|
|
24995
|
+
if (i >= 0) this.listeners.splice(i, 1);
|
|
24996
|
+
};
|
|
24997
|
+
}
|
|
24998
|
+
notifyChange(change) {
|
|
24999
|
+
for (const l of this.listeners) {
|
|
25000
|
+
try {
|
|
25001
|
+
l(change);
|
|
25002
|
+
} catch {
|
|
25003
|
+
}
|
|
25004
|
+
}
|
|
25005
|
+
}
|
|
24806
25006
|
/**
|
|
24807
25007
|
* Attach an existing graph (used by PhaseOrchestrator to associate a tracker
|
|
24808
25008
|
* with a phase's pre-built task graph without re-creating it).
|
|
@@ -24847,6 +25047,7 @@ var TaskTracker = class {
|
|
|
24847
25047
|
}
|
|
24848
25048
|
this.graph.updatedAt = now;
|
|
24849
25049
|
this.persist();
|
|
25050
|
+
this.notifyChange({ type: "node_added", nodeId: newNode.id, node: newNode });
|
|
24850
25051
|
return newNode;
|
|
24851
25052
|
}
|
|
24852
25053
|
addEdge(from, to, type = "depends_on") {
|
|
@@ -24863,6 +25064,68 @@ var TaskTracker = class {
|
|
|
24863
25064
|
this.graph.updatedAt = Date.now();
|
|
24864
25065
|
this.persist();
|
|
24865
25066
|
}
|
|
25067
|
+
/**
|
|
25068
|
+
* Declare that `taskId` depends on `depId` (a `depends_on` edge `depId → taskId`),
|
|
25069
|
+
* guarding against self-loops, duplicates, missing nodes, and cycles. Returns
|
|
25070
|
+
* true if the dependency now holds (added or already present), false if it was
|
|
25071
|
+
* rejected (would create a cycle / unknown node). This is the safe entry point
|
|
25072
|
+
* for wiring agent-declared `dependsOn` references into the graph.
|
|
25073
|
+
*/
|
|
25074
|
+
addDependency(depId, taskId) {
|
|
25075
|
+
if (!this.graph) return false;
|
|
25076
|
+
if (depId === taskId) return false;
|
|
25077
|
+
if (!this.graph.nodes.has(depId) || !this.graph.nodes.has(taskId)) return false;
|
|
25078
|
+
if (this.getBlockers(taskId).includes(depId)) return true;
|
|
25079
|
+
if (this.dependsOnTransitively(depId, taskId, /* @__PURE__ */ new Set())) return false;
|
|
25080
|
+
this.addEdge(depId, taskId, "depends_on");
|
|
25081
|
+
return true;
|
|
25082
|
+
}
|
|
25083
|
+
/** True when `taskId` transitively depends on `targetId` (follows depends_on blockers). */
|
|
25084
|
+
dependsOnTransitively(taskId, targetId, seen) {
|
|
25085
|
+
if (taskId === targetId) return true;
|
|
25086
|
+
if (seen.has(taskId)) return false;
|
|
25087
|
+
seen.add(taskId);
|
|
25088
|
+
for (const blocker of this.getBlockers(taskId)) {
|
|
25089
|
+
if (this.dependsOnTransitively(blocker, targetId, seen)) return true;
|
|
25090
|
+
}
|
|
25091
|
+
return false;
|
|
25092
|
+
}
|
|
25093
|
+
/**
|
|
25094
|
+
* Merge `patch` into a node's `metadata` (used for per-task model/provider/
|
|
25095
|
+
* fallback assignment and the cancel marker). Persists + notifies as a node
|
|
25096
|
+
* update. No-op if the node is missing.
|
|
25097
|
+
*/
|
|
25098
|
+
patchMetadata(id, patch) {
|
|
25099
|
+
if (!this.graph) return;
|
|
25100
|
+
const node = this.graph.nodes.get(id);
|
|
25101
|
+
if (!node) return;
|
|
25102
|
+
node.metadata = { ...node.metadata, ...patch };
|
|
25103
|
+
node.updatedAt = Date.now();
|
|
25104
|
+
this.graph.updatedAt = node.updatedAt;
|
|
25105
|
+
this.persist();
|
|
25106
|
+
this.notifyChange({ type: "node_updated", nodeId: id, node });
|
|
25107
|
+
}
|
|
25108
|
+
/**
|
|
25109
|
+
* Remove a node and every edge touching it. Intended for deleting a task that
|
|
25110
|
+
* has not started yet — callers must gate on status (do not remove a running
|
|
25111
|
+
* task). Dependents simply lose this blocker (re-evaluated by `canStart`).
|
|
25112
|
+
* Returns true if a node was removed.
|
|
25113
|
+
*/
|
|
25114
|
+
removeNode(id) {
|
|
25115
|
+
if (!this.graph) return false;
|
|
25116
|
+
const node = this.graph.nodes.get(id);
|
|
25117
|
+
if (!node) return false;
|
|
25118
|
+
this.graph.nodes.delete(id);
|
|
25119
|
+
this.graph.edges = this.graph.edges.filter((e) => e.from !== id && e.to !== id);
|
|
25120
|
+
this.graph.rootNodes = this.graph.rootNodes.filter((r) => r !== id);
|
|
25121
|
+
for (const n of this.graph.nodes.values()) {
|
|
25122
|
+
if (n.children?.includes(id)) n.children = n.children.filter((c) => c !== id);
|
|
25123
|
+
}
|
|
25124
|
+
this.graph.updatedAt = Date.now();
|
|
25125
|
+
this.persist();
|
|
25126
|
+
this.notifyChange({ type: "node_removed", nodeId: id, node });
|
|
25127
|
+
return true;
|
|
25128
|
+
}
|
|
24866
25129
|
updateNodeStatus(id, status, reason) {
|
|
24867
25130
|
if (!this.graph) throw new SddError({
|
|
24868
25131
|
message: "No graph loaded",
|
|
@@ -24894,6 +25157,12 @@ var TaskTracker = class {
|
|
|
24894
25157
|
}
|
|
24895
25158
|
this.graph.updatedAt = now;
|
|
24896
25159
|
this.persist();
|
|
25160
|
+
this.notifyChange({
|
|
25161
|
+
type: "status_changed",
|
|
25162
|
+
nodeId: id,
|
|
25163
|
+
node,
|
|
25164
|
+
transition: { from, to: status, timestamp: now, reason }
|
|
25165
|
+
});
|
|
24897
25166
|
}
|
|
24898
25167
|
updateNode(id, patch) {
|
|
24899
25168
|
if (!this.graph) throw new SddError({
|
|
@@ -24911,9 +25180,11 @@ var TaskTracker = class {
|
|
|
24911
25180
|
if (patch.priority !== void 0) node.priority = patch.priority;
|
|
24912
25181
|
if (patch.estimateHours !== void 0) node.estimateHours = patch.estimateHours;
|
|
24913
25182
|
if (patch.tags !== void 0) node.tags = patch.tags;
|
|
25183
|
+
if (patch.assignee !== void 0) node.assignee = patch.assignee;
|
|
24914
25184
|
node.updatedAt = Date.now();
|
|
24915
25185
|
this.graph.updatedAt = node.updatedAt;
|
|
24916
25186
|
this.persist();
|
|
25187
|
+
this.notifyChange({ type: "node_updated", nodeId: id, node });
|
|
24917
25188
|
}
|
|
24918
25189
|
getNode(id) {
|
|
24919
25190
|
return this.graph?.nodes.get(id);
|
|
@@ -25092,7 +25363,10 @@ var TaskFlow = class {
|
|
|
25092
25363
|
throw err;
|
|
25093
25364
|
}
|
|
25094
25365
|
this.setPhase("generating");
|
|
25095
|
-
const generator = new TaskGenerator({
|
|
25366
|
+
const generator = new TaskGenerator({
|
|
25367
|
+
taskTracker: this.opts.tracker,
|
|
25368
|
+
verificationFromAcceptance: process.env["WRONGSTACK_SDD_VERIFY_FROM_ACCEPTANCE"] === "1"
|
|
25369
|
+
});
|
|
25096
25370
|
this.graph = await generator.generateFromSpec(this.spec);
|
|
25097
25371
|
return this.graph;
|
|
25098
25372
|
}
|
|
@@ -25571,27 +25845,37 @@ function buildImplementationPrompt(session) {
|
|
|
25571
25845
|
"```json",
|
|
25572
25846
|
"[",
|
|
25573
25847
|
" {",
|
|
25848
|
+
' "id": "t1",',
|
|
25574
25849
|
' "title": "Create auth middleware",',
|
|
25575
25850
|
' "description": "Implement JWT verification middleware for protected routes",',
|
|
25576
25851
|
' "type": "feature",',
|
|
25577
25852
|
' "priority": "critical",',
|
|
25578
25853
|
' "estimateHours": 3,',
|
|
25854
|
+
' "dependsOn": [],',
|
|
25579
25855
|
' "tags": ["auth", "middleware"]',
|
|
25580
25856
|
" },",
|
|
25581
25857
|
" {",
|
|
25858
|
+
' "id": "t2",',
|
|
25582
25859
|
' "title": "Write auth tests",',
|
|
25583
25860
|
' "description": "Unit and integration tests for authentication flow",',
|
|
25584
25861
|
' "type": "test",',
|
|
25585
25862
|
' "priority": "high",',
|
|
25586
25863
|
' "estimateHours": 2,',
|
|
25864
|
+
' "dependsOn": ["t1"],',
|
|
25587
25865
|
' "tags": ["test", "auth"]',
|
|
25588
25866
|
" }",
|
|
25589
25867
|
"]",
|
|
25590
25868
|
"```",
|
|
25591
25869
|
"",
|
|
25592
25870
|
"Rules:",
|
|
25593
|
-
|
|
25594
|
-
"
|
|
25871
|
+
'- Give every task a short stable "id" (t1, t2, \u2026). Reference prerequisites in "dependsOn"',
|
|
25872
|
+
" as a list of those ids \u2014 this builds the real dependency graph that drives parallel vs",
|
|
25873
|
+
" sequential execution.",
|
|
25874
|
+
'- "dependsOn": [] means the task is independent and may run in parallel with other roots.',
|
|
25875
|
+
"- A task with dependsOn runs ONLY after every listed task completes. Model true ordering:",
|
|
25876
|
+
" tests depend on the feature they test, docs/integration depend on the parts they cover.",
|
|
25877
|
+
"- Do NOT create cycles (t1\u2192t2\u2192t1). Keep chains as shallow as correctness allows so",
|
|
25878
|
+
" independent work runs concurrently.",
|
|
25595
25879
|
'- Use type: "feature" for code, "test" for tests, "docs" for documentation, "chore" for config',
|
|
25596
25880
|
'- Use priority: "critical" for blockers, "high" for core features, "medium" for nice-to-haves, "low" for polish'
|
|
25597
25881
|
].join("\n");
|
|
@@ -25660,10 +25944,10 @@ var AISpecBuilder = class {
|
|
|
25660
25944
|
async saveSession() {
|
|
25661
25945
|
if (!this.sessionPath) return;
|
|
25662
25946
|
try {
|
|
25663
|
-
const
|
|
25664
|
-
const
|
|
25947
|
+
const fsp28 = await import('fs/promises');
|
|
25948
|
+
const path52 = await import('path');
|
|
25665
25949
|
const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
|
|
25666
|
-
await
|
|
25950
|
+
await fsp28.mkdir(path52.dirname(this.sessionPath), { recursive: true });
|
|
25667
25951
|
await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
|
|
25668
25952
|
} catch {
|
|
25669
25953
|
}
|
|
@@ -25672,8 +25956,8 @@ var AISpecBuilder = class {
|
|
|
25672
25956
|
async loadSession() {
|
|
25673
25957
|
if (!this.sessionPath) return false;
|
|
25674
25958
|
try {
|
|
25675
|
-
const
|
|
25676
|
-
const raw = await
|
|
25959
|
+
const fsp28 = await import('fs/promises');
|
|
25960
|
+
const raw = await fsp28.readFile(this.sessionPath, "utf8");
|
|
25677
25961
|
const loaded = JSON.parse(raw);
|
|
25678
25962
|
if (loaded?.id && loaded?.phase && loaded?.title) {
|
|
25679
25963
|
this.session = loaded;
|
|
@@ -25687,8 +25971,8 @@ var AISpecBuilder = class {
|
|
|
25687
25971
|
async deleteSession() {
|
|
25688
25972
|
if (!this.sessionPath) return;
|
|
25689
25973
|
try {
|
|
25690
|
-
const
|
|
25691
|
-
await
|
|
25974
|
+
const fsp28 = await import('fs/promises');
|
|
25975
|
+
await fsp28.unlink(this.sessionPath);
|
|
25692
25976
|
} catch {
|
|
25693
25977
|
}
|
|
25694
25978
|
}
|
|
@@ -26390,15 +26674,15 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
|
|
|
26390
26674
|
maxId = id;
|
|
26391
26675
|
}
|
|
26392
26676
|
}
|
|
26393
|
-
const
|
|
26677
|
+
const path52 = [];
|
|
26394
26678
|
let current = maxId;
|
|
26395
26679
|
const visited = /* @__PURE__ */ new Set();
|
|
26396
26680
|
while (current && !visited.has(current)) {
|
|
26397
26681
|
visited.add(current);
|
|
26398
|
-
|
|
26682
|
+
path52.unshift(current);
|
|
26399
26683
|
current = prev.get(current) ?? null;
|
|
26400
26684
|
}
|
|
26401
|
-
return
|
|
26685
|
+
return path52;
|
|
26402
26686
|
}
|
|
26403
26687
|
function computeParallelGroups(graph, blockedByMap) {
|
|
26404
26688
|
const groups = [];
|
|
@@ -26817,6 +27101,24 @@ var SddTaskDecomposer = class {
|
|
|
26817
27101
|
getWaveCount() {
|
|
26818
27102
|
return this.wave;
|
|
26819
27103
|
}
|
|
27104
|
+
/**
|
|
27105
|
+
* All ready (dependency-satisfied) pending tasks, priority-sorted — UNSLICED.
|
|
27106
|
+
* The continuous scheduler fills its own free slots from this list, so unlike
|
|
27107
|
+
* `nextBatch()` it does not cap at `slots`.
|
|
27108
|
+
*/
|
|
27109
|
+
readyNodes() {
|
|
27110
|
+
return this.pendingReadyNodes();
|
|
27111
|
+
}
|
|
27112
|
+
/**
|
|
27113
|
+
* True when every node has reached a terminal state (completed or failed).
|
|
27114
|
+
* This — not `isDone()` (which requires ALL completed) — is the correct loop
|
|
27115
|
+
* exit for the continuous scheduler: a terminally-failed task must not keep
|
|
27116
|
+
* the run spinning to its backstop.
|
|
27117
|
+
*/
|
|
27118
|
+
isSettled() {
|
|
27119
|
+
const nodes = this.tracker.getAllNodes();
|
|
27120
|
+
return nodes.length > 0 && nodes.every((n) => n.status === "completed" || n.status === "failed");
|
|
27121
|
+
}
|
|
26820
27122
|
// -------------------------------------------------------------------
|
|
26821
27123
|
// Internal helpers
|
|
26822
27124
|
// -------------------------------------------------------------------
|
|
@@ -26856,65 +27158,478 @@ var SddTaskDecomposer = class {
|
|
|
26856
27158
|
var SddParallelRun = class {
|
|
26857
27159
|
constructor(opts) {
|
|
26858
27160
|
this.opts = opts;
|
|
26859
|
-
this.slots = Math.min(16, Math.max(1, opts.parallelSlots ??
|
|
26860
|
-
this.timeoutMs = opts.taskTimeoutMs
|
|
26861
|
-
this.
|
|
27161
|
+
this.slots = Math.min(16, Math.max(1, opts.parallelSlots ?? 2));
|
|
27162
|
+
this.timeoutMs = opts.taskTimeoutMs;
|
|
27163
|
+
this.idleTimeoutMs = Math.max(1, opts.taskIdleTimeoutMs ?? 6e5);
|
|
27164
|
+
this.maxRetries = Math.max(0, opts.maxRetries ?? 3);
|
|
27165
|
+
this.maxSupervisorEscalations = Math.max(0, opts.maxSupervisorEscalations ?? 2);
|
|
27166
|
+
this.maxFailedSweeps = Math.max(0, opts.maxFailedRetrySweeps ?? 2);
|
|
27167
|
+
this.runId = opts.runId ?? `sdd-${randomUUID().slice(0, 8)}`;
|
|
27168
|
+
this.events = opts.events;
|
|
27169
|
+
this.maxTotalWaves = opts.maxTotalWaves ?? opts.graph.nodes.size * (this.maxRetries + 2) + 10;
|
|
27170
|
+
this.maxWallClockMs = opts.maxWallClockMs;
|
|
27171
|
+
this.maxRecoveryRounds = Math.max(0, opts.maxRecoveryRounds ?? 0);
|
|
26862
27172
|
this.decomposer = new SddTaskDecomposer(opts.tracker, opts.graph, { parallelSlots: this.slots });
|
|
26863
27173
|
}
|
|
26864
27174
|
opts;
|
|
26865
27175
|
slots;
|
|
27176
|
+
/** Opt-in hard wall-clock cap (undefined → no cap; idle reaper guards instead). */
|
|
26866
27177
|
timeoutMs;
|
|
27178
|
+
/** Idle reaper window (ms) — resets on activity; reaps only a genuine stall. */
|
|
27179
|
+
idleTimeoutMs;
|
|
26867
27180
|
maxRetries;
|
|
27181
|
+
/** Max supervisor rescues per task before it must terminal-fail (loop guard). */
|
|
27182
|
+
maxSupervisorEscalations;
|
|
27183
|
+
/** Per-task count of supervisor rescues used (resets nothing — bounds the loop). */
|
|
27184
|
+
supervisorEscalations = /* @__PURE__ */ new Map();
|
|
27185
|
+
/** Max end-of-run failed-task sweeps (see `maxFailedRetrySweeps`). */
|
|
27186
|
+
maxFailedSweeps;
|
|
27187
|
+
/** How many failed-task sweeps have run this `run()` so far. */
|
|
27188
|
+
failedSweeps = 0;
|
|
27189
|
+
/** Completed-count snapshot at the last sweep, to detect a no-progress sweep. */
|
|
27190
|
+
lastSweepCompleted = 0;
|
|
26868
27191
|
decomposer;
|
|
26869
27192
|
coordinator = null;
|
|
26870
27193
|
stopRequested = false;
|
|
26871
27194
|
retryMap = /* @__PURE__ */ new Map();
|
|
27195
|
+
runId;
|
|
27196
|
+
events;
|
|
27197
|
+
maxTotalWaves;
|
|
27198
|
+
maxWallClockMs;
|
|
27199
|
+
maxRecoveryRounds;
|
|
27200
|
+
recoveryRounds = 0;
|
|
27201
|
+
/** Per-run worker identities, so the board shows "who is on what". */
|
|
27202
|
+
usedNicknames = /* @__PURE__ */ new Set();
|
|
27203
|
+
/** Per-task git worktree cwd (Layer 2 worktree isolation; empty otherwise). */
|
|
27204
|
+
taskCwds = /* @__PURE__ */ new Map();
|
|
27205
|
+
/** Per-task git worktree branch, for board display. */
|
|
27206
|
+
taskBranches = /* @__PURE__ */ new Map();
|
|
27207
|
+
/** Live worktree handles keyed by task id (for commit/merge/release). */
|
|
27208
|
+
taskWorktrees = /* @__PURE__ */ new Map();
|
|
27209
|
+
/** Live subagent id per running task — lets cancelTask() abort exactly one. */
|
|
27210
|
+
taskSubagents = /* @__PURE__ */ new Map();
|
|
27211
|
+
/** Tasks the user cancelled mid-flight — skip retry, mark terminal-cancelled. */
|
|
27212
|
+
cancelledTasks = /* @__PURE__ */ new Set();
|
|
27213
|
+
/**
|
|
27214
|
+
* Base branch the run's squash commits land on (captured once at start when
|
|
27215
|
+
* worktrees are enabled). Anchors a later `rollback()`.
|
|
27216
|
+
*/
|
|
27217
|
+
baseBranch;
|
|
27218
|
+
/**
|
|
27219
|
+
* Squash-merge commits this run landed on the base branch, in landing order.
|
|
27220
|
+
* `rollback()` reverts these (newest → oldest). Persisted via the board
|
|
27221
|
+
* snapshot so a post-run rollback can read them off disk.
|
|
27222
|
+
*/
|
|
27223
|
+
mergedCommits = [];
|
|
27224
|
+
/** Monotonic dispatch counter (unique subagent ids) + dispatch-round counter. */
|
|
27225
|
+
dispatchSeq = 0;
|
|
27226
|
+
round = 0;
|
|
27227
|
+
/** Type-safe emit on the optional EventBus (no-op when unwired). */
|
|
27228
|
+
emit(event, payload) {
|
|
27229
|
+
this.events?.emit(event, payload);
|
|
27230
|
+
}
|
|
26872
27231
|
// -------------------------------------------------------------------
|
|
26873
27232
|
// Public API
|
|
26874
27233
|
// -------------------------------------------------------------------
|
|
27234
|
+
paused = false;
|
|
26875
27235
|
/** Trigger stop — causes run() to abort after the current wave. */
|
|
26876
27236
|
stop() {
|
|
26877
27237
|
this.stopRequested = true;
|
|
27238
|
+
this.paused = false;
|
|
26878
27239
|
this.coordinator?.stopAll();
|
|
26879
27240
|
}
|
|
26880
|
-
/**
|
|
27241
|
+
/** Pause: no new wave starts until resume() (the current wave finishes). */
|
|
27242
|
+
pause() {
|
|
27243
|
+
this.paused = true;
|
|
27244
|
+
}
|
|
27245
|
+
resume() {
|
|
27246
|
+
this.paused = false;
|
|
27247
|
+
}
|
|
27248
|
+
isPaused() {
|
|
27249
|
+
return this.paused;
|
|
27250
|
+
}
|
|
27251
|
+
isRunning() {
|
|
27252
|
+
return !this.stopRequested && !this.decomposer.isSettled();
|
|
27253
|
+
}
|
|
27254
|
+
/** Base branch the run's squash commits land on (undefined when worktrees off). */
|
|
27255
|
+
getBaseBranch() {
|
|
27256
|
+
return this.baseBranch;
|
|
27257
|
+
}
|
|
27258
|
+
/** Squash commits this run landed on the base branch, in landing order. */
|
|
27259
|
+
getMergedCommits() {
|
|
27260
|
+
return this.mergedCommits;
|
|
27261
|
+
}
|
|
27262
|
+
/**
|
|
27263
|
+
* Remove every git worktree + branch this run (and any prior run) created.
|
|
27264
|
+
* Refuses while the run is still live — cleaning a checkout under an active
|
|
27265
|
+
* worker would corrupt it. Stop first. Returns the number of worktrees removed
|
|
27266
|
+
* (0 when worktrees are disabled). Idempotent.
|
|
27267
|
+
*/
|
|
27268
|
+
async cleanupWorktrees() {
|
|
27269
|
+
if (this.isRunning()) return 0;
|
|
27270
|
+
const wt = this.opts.worktrees;
|
|
27271
|
+
if (!wt) return 0;
|
|
27272
|
+
for (const [taskId, handle] of [...this.taskWorktrees]) {
|
|
27273
|
+
await wt.release(handle, { keep: false }).catch(() => {
|
|
27274
|
+
});
|
|
27275
|
+
this.forgetWorktree(taskId);
|
|
27276
|
+
}
|
|
27277
|
+
const { removed } = await wt.cleanupAllManaged();
|
|
27278
|
+
return removed;
|
|
27279
|
+
}
|
|
27280
|
+
/**
|
|
27281
|
+
* Undo the run's merged commits by reverting each on the base branch (history
|
|
27282
|
+
* preserving). Refuses while the run is still live (stop first). Returns the
|
|
27283
|
+
* revert outcome; a dirty tree or revert conflict surfaces as `ok:false`.
|
|
27284
|
+
*/
|
|
27285
|
+
async rollback() {
|
|
27286
|
+
if (this.isRunning()) return { ok: false, reverted: 0, reason: "run still active \u2014 stop it first" };
|
|
27287
|
+
const wt = this.opts.worktrees;
|
|
27288
|
+
if (!wt || !this.baseBranch) {
|
|
27289
|
+
return { ok: false, reverted: 0, reason: "no worktree run to roll back" };
|
|
27290
|
+
}
|
|
27291
|
+
return wt.revertCommits(
|
|
27292
|
+
this.baseBranch,
|
|
27293
|
+
this.mergedCommits.map((c) => c.sha)
|
|
27294
|
+
);
|
|
27295
|
+
}
|
|
27296
|
+
/** Requeue a task to `pending` so the scheduler re-runs it (clears retries + cancel marker). */
|
|
27297
|
+
retryTask(taskId) {
|
|
27298
|
+
if (!this.opts.tracker.getNode(taskId)) return false;
|
|
27299
|
+
this.retryMap.delete(taskId);
|
|
27300
|
+
this.persistRetries(taskId, 0);
|
|
27301
|
+
this.cancelledTasks.delete(taskId);
|
|
27302
|
+
this.opts.tracker.patchMetadata(taskId, { cancelled: void 0 });
|
|
27303
|
+
this.opts.tracker.updateNodeStatus(taskId, "pending", "manual retry");
|
|
27304
|
+
return true;
|
|
27305
|
+
}
|
|
27306
|
+
/** Reassign a task to a specific agent name (reflected on the board). */
|
|
27307
|
+
reassignTask(taskId, agentName) {
|
|
27308
|
+
if (!this.opts.tracker.getNode(taskId)) return false;
|
|
27309
|
+
this.opts.tracker.updateNode(taskId, { assignee: agentName });
|
|
27310
|
+
return true;
|
|
27311
|
+
}
|
|
27312
|
+
/**
|
|
27313
|
+
* Set/override a task's worker model (and optionally provider) — applied on its
|
|
27314
|
+
* NEXT dispatch (a running task must be cancelled + retried to take effect). The
|
|
27315
|
+
* assignment lives on node metadata so it survives crash → resume.
|
|
27316
|
+
*/
|
|
27317
|
+
setTaskModel(taskId, model, provider) {
|
|
27318
|
+
if (!this.opts.tracker.getNode(taskId)) return false;
|
|
27319
|
+
this.opts.tracker.patchMetadata(taskId, { model, ...provider !== void 0 ? { provider } : {} });
|
|
27320
|
+
return true;
|
|
27321
|
+
}
|
|
27322
|
+
/** Set/override a task's fallback model chain (applied on its next dispatch). */
|
|
27323
|
+
setTaskFallbacks(taskId, fallbackModels) {
|
|
27324
|
+
if (!this.opts.tracker.getNode(taskId)) return false;
|
|
27325
|
+
this.opts.tracker.patchMetadata(taskId, { fallbackModels });
|
|
27326
|
+
return true;
|
|
27327
|
+
}
|
|
27328
|
+
/**
|
|
27329
|
+
* Set/override a task's verification command (the completion gate runs it in
|
|
27330
|
+
* the task's cwd and only lets the task complete on exit 0). Empty/undefined
|
|
27331
|
+
* clears it. Applied on the task's next verification — i.e. its next dispatch.
|
|
27332
|
+
*/
|
|
27333
|
+
setTaskVerification(taskId, verificationCommand) {
|
|
27334
|
+
if (!this.opts.tracker.getNode(taskId)) return false;
|
|
27335
|
+
const cmd = verificationCommand?.trim();
|
|
27336
|
+
this.opts.tracker.patchMetadata(taskId, { verificationCommand: cmd ? cmd : void 0 });
|
|
27337
|
+
return true;
|
|
27338
|
+
}
|
|
27339
|
+
/**
|
|
27340
|
+
* Cancel a task. If it is currently running, abort its subagent and mark the
|
|
27341
|
+
* node terminally failed+cancelled (so the scheduler frees the slot and does
|
|
27342
|
+
* NOT retry it). If it has not started, it is simply marked cancelled. Use
|
|
27343
|
+
* `retryTask` to bring a cancelled task back. Returns false for an unknown task.
|
|
27344
|
+
*/
|
|
27345
|
+
async cancelTask(taskId) {
|
|
27346
|
+
const node = this.opts.tracker.getNode(taskId);
|
|
27347
|
+
if (!node) return false;
|
|
27348
|
+
this.cancelledTasks.add(taskId);
|
|
27349
|
+
this.opts.tracker.patchMetadata(taskId, { cancelled: true });
|
|
27350
|
+
this.opts.tracker.updateNodeStatus(taskId, "failed", "cancelled by user");
|
|
27351
|
+
this.emit("sdd.task.failed", { runId: this.runId, taskId, subagentId: "", error: "cancelled by user" });
|
|
27352
|
+
const subagentId = this.taskSubagents.get(taskId);
|
|
27353
|
+
if (subagentId && this.coordinator) {
|
|
27354
|
+
await this.coordinator.stop(subagentId).catch(() => {
|
|
27355
|
+
});
|
|
27356
|
+
}
|
|
27357
|
+
return true;
|
|
27358
|
+
}
|
|
27359
|
+
/**
|
|
27360
|
+
* Delete a not-yet-started task from the graph (pending/blocked/failed only —
|
|
27361
|
+
* never a running task; cancel it first). Removes the node and every edge
|
|
27362
|
+
* touching it; dependents lose this blocker. Returns false if missing or running.
|
|
27363
|
+
*/
|
|
27364
|
+
deleteTask(taskId) {
|
|
27365
|
+
const node = this.opts.tracker.getNode(taskId);
|
|
27366
|
+
if (!node) return false;
|
|
27367
|
+
if (node.status === "in_progress" || this.taskSubagents.has(taskId)) return false;
|
|
27368
|
+
this.cancelledTasks.delete(taskId);
|
|
27369
|
+
this.retryMap.delete(taskId);
|
|
27370
|
+
return this.opts.tracker.removeNode(taskId);
|
|
27371
|
+
}
|
|
27372
|
+
/**
|
|
27373
|
+
* Split a task into sub-tasks and delegate them to separate workers. The new
|
|
27374
|
+
* leaves inherit the parent's blockers (so they don't start before the
|
|
27375
|
+
* parent's dependencies are met), every existing dependent is rewired to
|
|
27376
|
+
* depend on ALL leaves (so downstream work waits for the whole split), and the
|
|
27377
|
+
* parent becomes a `completed` container. Refuses a running task (cancel it
|
|
27378
|
+
* first) or empty subtask list. Returns the new leaf ids (empty on refusal).
|
|
27379
|
+
* The scheduler picks the new pending leaves up on its next dispatch pass.
|
|
27380
|
+
*/
|
|
27381
|
+
splitTask(taskId, subtasks) {
|
|
27382
|
+
const tracker = this.opts.tracker;
|
|
27383
|
+
const node = tracker.getNode(taskId);
|
|
27384
|
+
if (!node) return [];
|
|
27385
|
+
if (node.status === "in_progress" || this.taskSubagents.has(taskId)) return [];
|
|
27386
|
+
if (!subtasks.length) return [];
|
|
27387
|
+
const blockers = tracker.getBlockers(taskId);
|
|
27388
|
+
const dependents = tracker.getDependents(taskId);
|
|
27389
|
+
const leafIds = subtasks.map(
|
|
27390
|
+
(s) => tracker.addNode({
|
|
27391
|
+
title: s.title,
|
|
27392
|
+
description: s.description,
|
|
27393
|
+
type: s.type ?? node.type,
|
|
27394
|
+
priority: s.priority ?? node.priority,
|
|
27395
|
+
status: "pending",
|
|
27396
|
+
parentId: taskId
|
|
27397
|
+
}).id
|
|
27398
|
+
);
|
|
27399
|
+
for (const leaf of leafIds) {
|
|
27400
|
+
for (const b of blockers) tracker.addDependency(b, leaf);
|
|
27401
|
+
for (const dep of dependents) tracker.addDependency(leaf, dep);
|
|
27402
|
+
}
|
|
27403
|
+
this.retryMap.delete(taskId);
|
|
27404
|
+
this.persistRetries(taskId, 0);
|
|
27405
|
+
tracker.updateNodeStatus(taskId, "completed", `split into ${leafIds.length} subtasks`);
|
|
27406
|
+
this.emit("sdd.task.split", { runId: this.runId, taskId, subtaskIds: leafIds });
|
|
27407
|
+
return leafIds;
|
|
27408
|
+
}
|
|
27409
|
+
async waitWhilePaused() {
|
|
27410
|
+
while (this.paused && !this.stopRequested) {
|
|
27411
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
27412
|
+
}
|
|
27413
|
+
}
|
|
27414
|
+
/**
|
|
27415
|
+
* Continuous dependency-driven execution. Unlike a wave-barrier loop (where a
|
|
27416
|
+
* whole batch must finish before the next starts), this fills free worker
|
|
27417
|
+
* slots the instant a task's dependencies are satisfied: a fast task's
|
|
27418
|
+
* dependent starts immediately rather than waiting for a slow sibling. Truly
|
|
27419
|
+
* independent tasks run in parallel; dependency chains run in order. Returns
|
|
27420
|
+
* the final summary when the graph settles, deadlocks, stops, or hits a backstop.
|
|
27421
|
+
*/
|
|
26881
27422
|
async run() {
|
|
26882
27423
|
this.stopRequested = false;
|
|
26883
|
-
this.
|
|
27424
|
+
this.restoreRetryMap();
|
|
26884
27425
|
const startTime = Date.now();
|
|
26885
|
-
|
|
26886
|
-
|
|
26887
|
-
let
|
|
27426
|
+
this.round = 0;
|
|
27427
|
+
this.dispatchSeq = 0;
|
|
27428
|
+
let totalDispatched = 0;
|
|
26888
27429
|
this.buildCoordinator();
|
|
26889
|
-
|
|
26890
|
-
const
|
|
26891
|
-
if (
|
|
27430
|
+
if (this.opts.worktrees && !this.baseBranch) {
|
|
27431
|
+
const base = await this.opts.worktrees.currentBase().catch(() => null);
|
|
27432
|
+
if (base) this.baseBranch = base.branch;
|
|
27433
|
+
}
|
|
27434
|
+
this.emit("sdd.run.started", {
|
|
27435
|
+
runId: this.runId,
|
|
27436
|
+
graphId: this.opts.graph.id,
|
|
27437
|
+
specId: this.opts.graph.specId,
|
|
27438
|
+
total: this.opts.graph.nodes.size,
|
|
27439
|
+
baseBranch: this.baseBranch
|
|
27440
|
+
});
|
|
27441
|
+
this.recoveryRounds = 0;
|
|
27442
|
+
this.failedSweeps = 0;
|
|
27443
|
+
this.lastSweepCompleted = 0;
|
|
27444
|
+
let deadlocked = false;
|
|
27445
|
+
const running = /* @__PURE__ */ new Map();
|
|
27446
|
+
const dispatch = (task) => {
|
|
27447
|
+
totalDispatched++;
|
|
27448
|
+
const tracked = (async () => {
|
|
27449
|
+
try {
|
|
27450
|
+
return await this.executeOne(task);
|
|
27451
|
+
} catch (err) {
|
|
27452
|
+
this.opts.tracker.updateNodeStatus(task.id, "failed", `dispatch error: ${String(err)}`);
|
|
27453
|
+
this.emit("sdd.task.failed", { runId: this.runId, taskId: task.id, subagentId: "", error: String(err) });
|
|
27454
|
+
return { taskId: task.id, success: false };
|
|
27455
|
+
} finally {
|
|
27456
|
+
running.delete(task.id);
|
|
27457
|
+
}
|
|
27458
|
+
})();
|
|
27459
|
+
running.set(task.id, tracked);
|
|
27460
|
+
};
|
|
27461
|
+
while (!this.stopRequested) {
|
|
27462
|
+
if (totalDispatched >= this.maxTotalWaves) break;
|
|
27463
|
+
if (this.maxWallClockMs && Date.now() - startTime >= this.maxWallClockMs) break;
|
|
27464
|
+
await this.waitWhilePaused();
|
|
27465
|
+
if (this.stopRequested) break;
|
|
27466
|
+
let dispatchedThisRound = 0;
|
|
27467
|
+
if (running.size < this.slots) {
|
|
27468
|
+
const ready = this.decomposer.readyNodes().filter((t2) => !running.has(t2.id));
|
|
27469
|
+
for (const task of ready) {
|
|
27470
|
+
if (running.size >= this.slots) break;
|
|
27471
|
+
dispatch(task);
|
|
27472
|
+
dispatchedThisRound++;
|
|
27473
|
+
}
|
|
27474
|
+
}
|
|
27475
|
+
if (dispatchedThisRound > 0) {
|
|
27476
|
+
this.emit("sdd.wave", { runId: this.runId, wave: this.round, batchSize: dispatchedThisRound });
|
|
27477
|
+
this.round++;
|
|
27478
|
+
}
|
|
27479
|
+
if (running.size === 0) {
|
|
27480
|
+
if (this.decomposer.isSettled()) {
|
|
27481
|
+
const completed = this.opts.tracker.getProgress().completed;
|
|
27482
|
+
const madeProgress = this.failedSweeps === 0 || completed > this.lastSweepCompleted;
|
|
27483
|
+
if (this.failedSweeps < this.maxFailedSweeps && madeProgress && this.requeueFailedTasks() > 0) {
|
|
27484
|
+
this.lastSweepCompleted = completed;
|
|
27485
|
+
this.failedSweeps++;
|
|
27486
|
+
continue;
|
|
27487
|
+
}
|
|
27488
|
+
break;
|
|
27489
|
+
}
|
|
27490
|
+
const chains = this.computeDeadlockChains();
|
|
27491
|
+
if (chains.length > 0) {
|
|
27492
|
+
this.emit("sdd.deadlock", { runId: this.runId, chains });
|
|
27493
|
+
if (this.recoveryRounds < this.maxRecoveryRounds && this.recoverFailedBlockers()) {
|
|
27494
|
+
this.recoveryRounds++;
|
|
27495
|
+
continue;
|
|
27496
|
+
}
|
|
27497
|
+
deadlocked = true;
|
|
27498
|
+
}
|
|
26892
27499
|
break;
|
|
26893
27500
|
}
|
|
26894
|
-
|
|
26895
|
-
|
|
27501
|
+
const moreReadyNow = running.size < this.slots && this.decomposer.readyNodes().some((t2) => !running.has(t2.id));
|
|
27502
|
+
if (!moreReadyNow) {
|
|
27503
|
+
await Promise.race(running.values());
|
|
27504
|
+
this.opts.onProgress?.(this.buildProgress());
|
|
26896
27505
|
}
|
|
26897
|
-
const waveResult = await this.executeWave(batch);
|
|
26898
|
-
totalWaves++;
|
|
26899
|
-
totalCompleted += waveResult.successCount;
|
|
26900
|
-
totalFailed += waveResult.failCount;
|
|
26901
|
-
this.decomposer.acknowledgeBatch(batch.tasks.map((t2) => t2.id));
|
|
26902
|
-
this.opts.onWave?.(waveResult);
|
|
26903
|
-
const progress = this.buildProgress();
|
|
26904
|
-
this.opts.onProgress?.(progress);
|
|
26905
|
-
if (this.stopRequested) break;
|
|
26906
27506
|
}
|
|
27507
|
+
if (running.size > 0) await Promise.allSettled(running.values());
|
|
27508
|
+
if (this.stopRequested) await this.teardown();
|
|
26907
27509
|
const finalProgress = this.opts.tracker.getProgress();
|
|
27510
|
+
this.emit("sdd.run.finished", {
|
|
27511
|
+
runId: this.runId,
|
|
27512
|
+
deadlocked,
|
|
27513
|
+
completed: finalProgress.completed,
|
|
27514
|
+
failed: finalProgress.failed,
|
|
27515
|
+
stopped: this.stopRequested
|
|
27516
|
+
});
|
|
26908
27517
|
return {
|
|
26909
|
-
totalWaves,
|
|
26910
|
-
totalCompleted,
|
|
26911
|
-
totalFailed,
|
|
27518
|
+
totalWaves: this.round,
|
|
27519
|
+
totalCompleted: finalProgress.completed,
|
|
27520
|
+
totalFailed: finalProgress.failed,
|
|
26912
27521
|
totalDurationMs: Date.now() - startTime,
|
|
26913
|
-
deadlocked
|
|
27522
|
+
deadlocked,
|
|
26914
27523
|
stopRequested: this.stopRequested,
|
|
26915
27524
|
finalProgress
|
|
26916
27525
|
};
|
|
26917
27526
|
}
|
|
27527
|
+
/**
|
|
27528
|
+
* Compute the blocking chains for a deadlock: every still-incomplete task and
|
|
27529
|
+
* the blockers (by node id) that are NOT completed. Failed blockers are
|
|
27530
|
+
* included since they're the usual deadlock cause once retries are exhausted.
|
|
27531
|
+
*/
|
|
27532
|
+
computeDeadlockChains() {
|
|
27533
|
+
const tracker = this.opts.tracker;
|
|
27534
|
+
const chains = [];
|
|
27535
|
+
for (const node of tracker.getAllNodes()) {
|
|
27536
|
+
if (node.status === "completed" || node.status === "failed") continue;
|
|
27537
|
+
const blockedBy = tracker.getBlockers(node.id).filter((id) => tracker.getNode(id)?.status !== "completed");
|
|
27538
|
+
if (blockedBy.length > 0) chains.push({ blocked: node.id, blockedBy });
|
|
27539
|
+
}
|
|
27540
|
+
return chains;
|
|
27541
|
+
}
|
|
27542
|
+
/** Requeue failed tasks that block an incomplete dependent. Returns true if any. */
|
|
27543
|
+
recoverFailedBlockers() {
|
|
27544
|
+
const tracker = this.opts.tracker;
|
|
27545
|
+
let recovered = false;
|
|
27546
|
+
for (const node of tracker.getAllNodes({ status: ["failed"] })) {
|
|
27547
|
+
const blocksIncomplete = tracker.getDependents(node.id).some((d) => {
|
|
27548
|
+
const s = tracker.getNode(d)?.status;
|
|
27549
|
+
return s !== "completed" && s !== "failed";
|
|
27550
|
+
});
|
|
27551
|
+
if (blocksIncomplete) {
|
|
27552
|
+
this.retryMap.delete(node.id);
|
|
27553
|
+
this.persistRetries(node.id, 0);
|
|
27554
|
+
tracker.updateNodeStatus(node.id, "pending", "deadlock recovery");
|
|
27555
|
+
recovered = true;
|
|
27556
|
+
}
|
|
27557
|
+
}
|
|
27558
|
+
return recovered;
|
|
27559
|
+
}
|
|
27560
|
+
/**
|
|
27561
|
+
* Requeue every terminal-failed task that the user did NOT cancel, giving each
|
|
27562
|
+
* a fresh `maxRetries` budget. Shared by the automatic end-of-run sweep and
|
|
27563
|
+
* the manual "retry all failed" control. Returns the number requeued.
|
|
27564
|
+
*/
|
|
27565
|
+
requeueFailedTasks(reason = "retry failed sweep") {
|
|
27566
|
+
const tracker = this.opts.tracker;
|
|
27567
|
+
let n = 0;
|
|
27568
|
+
for (const node of tracker.getAllNodes({ status: ["failed"] })) {
|
|
27569
|
+
if (this.cancelledTasks.has(node.id) || node.metadata?.cancelled) continue;
|
|
27570
|
+
this.retryMap.delete(node.id);
|
|
27571
|
+
this.persistRetries(node.id, 0);
|
|
27572
|
+
tracker.updateNodeStatus(node.id, "pending", reason);
|
|
27573
|
+
this.emit("sdd.task.retrying", {
|
|
27574
|
+
runId: this.runId,
|
|
27575
|
+
taskId: node.id,
|
|
27576
|
+
attempt: 0,
|
|
27577
|
+
maxRetries: this.maxRetries
|
|
27578
|
+
});
|
|
27579
|
+
n++;
|
|
27580
|
+
}
|
|
27581
|
+
return n;
|
|
27582
|
+
}
|
|
27583
|
+
/**
|
|
27584
|
+
* Manually requeue all failed tasks to `pending` (board "Retry all failed").
|
|
27585
|
+
* Unlike the automatic sweep this also clears any `cancelled` marker, so a
|
|
27586
|
+
* user can bring cancelled tasks back in the same action — mirroring
|
|
27587
|
+
* `retryTask`. Picked up by the running scheduler on its next dispatch pass.
|
|
27588
|
+
* Returns the number of tasks requeued.
|
|
27589
|
+
*/
|
|
27590
|
+
retryAllFailed() {
|
|
27591
|
+
const failed = this.opts.tracker.getAllNodes({ status: ["failed"] });
|
|
27592
|
+
for (const node of failed) {
|
|
27593
|
+
this.cancelledTasks.delete(node.id);
|
|
27594
|
+
this.opts.tracker.patchMetadata(node.id, { cancelled: void 0 });
|
|
27595
|
+
}
|
|
27596
|
+
return this.requeueFailedTasks("manual retry all");
|
|
27597
|
+
}
|
|
27598
|
+
/** Restore per-task retry counts persisted in node metadata (resume support). */
|
|
27599
|
+
restoreRetryMap() {
|
|
27600
|
+
this.retryMap.clear();
|
|
27601
|
+
for (const node of this.opts.tracker.getAllNodes()) {
|
|
27602
|
+
const r = node.metadata?.retries;
|
|
27603
|
+
if (typeof r === "number" && r > 0) this.retryMap.set(node.id, r);
|
|
27604
|
+
}
|
|
27605
|
+
}
|
|
27606
|
+
/**
|
|
27607
|
+
* Reset orphaned `in_progress` tasks (no agent runs them after a crash) back
|
|
27608
|
+
* to `pending` so a fresh run re-executes them. Call before constructing a run
|
|
27609
|
+
* from a reloaded graph. Static so callers don't need a run instance.
|
|
27610
|
+
*/
|
|
27611
|
+
static resetOrphans(tracker) {
|
|
27612
|
+
let n = 0;
|
|
27613
|
+
for (const node of tracker.getAllNodes({ status: ["in_progress"] })) {
|
|
27614
|
+
tracker.updateNodeStatus(node.id, "pending", "resume: orphaned in_progress");
|
|
27615
|
+
n++;
|
|
27616
|
+
}
|
|
27617
|
+
return n;
|
|
27618
|
+
}
|
|
27619
|
+
/** Clean teardown after a stop: reset interrupted tasks + release worktrees. */
|
|
27620
|
+
async teardown() {
|
|
27621
|
+
for (const node of this.opts.tracker.getAllNodes({ status: ["in_progress"] })) {
|
|
27622
|
+
this.opts.tracker.updateNodeStatus(node.id, "pending", "run stopped");
|
|
27623
|
+
}
|
|
27624
|
+
const wt = this.opts.worktrees;
|
|
27625
|
+
if (wt) {
|
|
27626
|
+
for (const [taskId, handle] of [...this.taskWorktrees]) {
|
|
27627
|
+
await wt.release(handle, { keep: true }).catch(() => {
|
|
27628
|
+
});
|
|
27629
|
+
this.forgetWorktree(taskId);
|
|
27630
|
+
}
|
|
27631
|
+
}
|
|
27632
|
+
}
|
|
26918
27633
|
// -------------------------------------------------------------------
|
|
26919
27634
|
// Internal
|
|
26920
27635
|
// -------------------------------------------------------------------
|
|
@@ -26922,7 +27637,14 @@ var SddParallelRun = class {
|
|
|
26922
27637
|
const config = {
|
|
26923
27638
|
coordinatorId: `sdd-parallel-${randomUUID().slice(0, 8)}`,
|
|
26924
27639
|
maxConcurrent: this.slots,
|
|
26925
|
-
doneCondition: { type: "all_tasks_done" }
|
|
27640
|
+
doneCondition: { type: "all_tasks_done" },
|
|
27641
|
+
// Default budget guard for every spawned worker: idle reaper (resets on
|
|
27642
|
+
// activity) plus the opt-in wall-clock cap when one was configured. This
|
|
27643
|
+
// ensures the reaper applies even if a per-spawn config path is bypassed.
|
|
27644
|
+
defaultBudget: {
|
|
27645
|
+
idleTimeoutMs: this.idleTimeoutMs,
|
|
27646
|
+
...this.timeoutMs ? { timeoutMs: this.timeoutMs } : {}
|
|
27647
|
+
}
|
|
26926
27648
|
};
|
|
26927
27649
|
this.coordinator = new DefaultMultiAgentCoordinator(config);
|
|
26928
27650
|
const baseFactory = this.opts.subagentFactory ?? this.defaultFactory();
|
|
@@ -26936,22 +27658,89 @@ var SddParallelRun = class {
|
|
|
26936
27658
|
events: this.opts.agent.events
|
|
26937
27659
|
});
|
|
26938
27660
|
}
|
|
27661
|
+
/**
|
|
27662
|
+
* Execute a batch of tasks together. Retained as a thin wrapper over the
|
|
27663
|
+
* single-task primitive `executeOne` so the wave-oriented tests and any
|
|
27664
|
+
* batch callers keep working; the continuous scheduler in `run()` calls
|
|
27665
|
+
* `executeOne` directly. Throws if no coordinator is wired or a spawn fails
|
|
27666
|
+
* (surfaced from `executeOne`), preserving the original all-or-nothing contract.
|
|
27667
|
+
*/
|
|
26939
27668
|
async executeWave(batch) {
|
|
26940
|
-
const wave = batch.wave;
|
|
26941
|
-
const tasks = batch.tasks;
|
|
26942
27669
|
const waveStart = Date.now();
|
|
26943
|
-
|
|
26944
|
-
|
|
27670
|
+
const outcomes = await Promise.all(batch.tasks.map((task) => this.executeOne(task)));
|
|
27671
|
+
const results = outcomes.map((o) => o.result).filter((r) => Boolean(r));
|
|
27672
|
+
const successCount = outcomes.filter((o) => o.success).length;
|
|
27673
|
+
const failCount = outcomes.length - successCount;
|
|
27674
|
+
return {
|
|
27675
|
+
wave: batch.wave,
|
|
27676
|
+
batch,
|
|
27677
|
+
results,
|
|
27678
|
+
successCount,
|
|
27679
|
+
failCount,
|
|
27680
|
+
durationMs: Date.now() - waveStart,
|
|
27681
|
+
stopRequested: this.stopRequested
|
|
27682
|
+
};
|
|
27683
|
+
}
|
|
27684
|
+
/**
|
|
27685
|
+
* Execute one task end-to-end: assign a worker identity, allocate its worktree,
|
|
27686
|
+
* spawn + assign the subagent, await its result, then update tracker status
|
|
27687
|
+
* (success / retry / terminal-fail / cancelled) and resolve the worktree. This
|
|
27688
|
+
* is the unit the continuous scheduler dispatches into a free slot. Throws on a
|
|
27689
|
+
* missing coordinator or failed spawn so callers can enforce all-or-nothing.
|
|
27690
|
+
*/
|
|
27691
|
+
async executeOne(task) {
|
|
27692
|
+
const taskId = task.id;
|
|
27693
|
+
let agentName = task.assignee;
|
|
27694
|
+
if (!agentName) {
|
|
27695
|
+
const nick = assignNickname("executor", this.usedNicknames);
|
|
27696
|
+
this.usedNicknames.add(nick.key);
|
|
27697
|
+
agentName = nick.display.replace(/\s*\([^)]*\)\s*$/, "");
|
|
27698
|
+
this.opts.tracker.updateNode(taskId, { assignee: agentName });
|
|
27699
|
+
}
|
|
27700
|
+
this.opts.tracker.updateNodeStatus(taskId, "in_progress");
|
|
27701
|
+
await this.allocateWorktrees([task]);
|
|
27702
|
+
if (!this.coordinator) throw new SddError({
|
|
27703
|
+
message: "SDD parallel runner requires a coordinator",
|
|
27704
|
+
code: ERROR_CODES.SDD_INVALID_STATE
|
|
27705
|
+
});
|
|
27706
|
+
const coordinator = this.coordinator;
|
|
27707
|
+
const subagentId = `sdd-d${this.dispatchSeq++}`;
|
|
27708
|
+
const correlationId = randomUUID();
|
|
27709
|
+
const meta = task.metadata ?? {};
|
|
27710
|
+
const model = (typeof meta.model === "string" ? meta.model : void 0) ?? this.opts.defaultModel;
|
|
27711
|
+
const provider = (typeof meta.provider === "string" ? meta.provider : void 0) ?? this.opts.defaultProvider;
|
|
27712
|
+
const fallbackModels = Array.isArray(meta.fallbackModels) ? meta.fallbackModels : this.opts.fallbackModels;
|
|
27713
|
+
const spawnResult = await coordinator.spawn({
|
|
27714
|
+
id: subagentId,
|
|
27715
|
+
name: agentName ?? subagentId,
|
|
27716
|
+
role: "executor",
|
|
27717
|
+
// Idle reaper is always on; the hard wall-clock cap only when opted in.
|
|
27718
|
+
idleTimeoutMs: this.idleTimeoutMs,
|
|
27719
|
+
...this.timeoutMs ? { timeoutMs: this.timeoutMs } : {},
|
|
27720
|
+
cwd: this.taskCwds.get(taskId),
|
|
27721
|
+
disabledTools: ["delegate"],
|
|
27722
|
+
...model ? { model } : {},
|
|
27723
|
+
...provider ? { provider } : {},
|
|
27724
|
+
...fallbackModels && fallbackModels.length ? { fallbackModels } : {}
|
|
27725
|
+
});
|
|
27726
|
+
if (!spawnResult.subagentId) {
|
|
27727
|
+
throw new SddError({
|
|
27728
|
+
message: "One or more subagent spawns failed",
|
|
27729
|
+
code: ERROR_CODES.SDD_INVALID_STATE
|
|
27730
|
+
});
|
|
26945
27731
|
}
|
|
26946
|
-
|
|
26947
|
-
|
|
26948
|
-
|
|
27732
|
+
this.taskSubagents.set(taskId, subagentId);
|
|
27733
|
+
this.emit("sdd.task.started", {
|
|
27734
|
+
runId: this.runId,
|
|
27735
|
+
taskId,
|
|
27736
|
+
subagentId,
|
|
27737
|
+
agentName: agentName ?? "",
|
|
27738
|
+
worktreeBranch: this.taskBranches.get(taskId)
|
|
27739
|
+
});
|
|
26949
27740
|
const directivePreamble = [
|
|
26950
27741
|
"\u2550\u2550\u2550 SDD PARALLEL EXECUTION \u2550\u2550\u2550",
|
|
26951
27742
|
"",
|
|
26952
|
-
`Wave ${wave + 1} of ~${Math.ceil(progress.total / this.slots)}`,
|
|
26953
27743
|
`Graph: ${this.opts.graph.title}`,
|
|
26954
|
-
`Parallel slots: ${tasks.length}`,
|
|
26955
27744
|
"",
|
|
26956
27745
|
"\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
|
|
26957
27746
|
"\u2022 Execute the assigned SDD task end-to-end using multiple tool calls.",
|
|
@@ -26959,91 +27748,297 @@ var SddParallelRun = class {
|
|
|
26959
27748
|
"\u2022 Do not ask before routine in-project tool use; if a permission gate appears, wait for that flow.",
|
|
26960
27749
|
"\u2022 Keep output concise \u2014 summarize changes, do not transcribe files."
|
|
26961
27750
|
].join("\n");
|
|
26962
|
-
|
|
26963
|
-
|
|
26964
|
-
|
|
26965
|
-
|
|
26966
|
-
|
|
26967
|
-
|
|
26968
|
-
|
|
26969
|
-
|
|
26970
|
-
|
|
26971
|
-
|
|
26972
|
-
|
|
26973
|
-
|
|
26974
|
-
disabledTools: ["delegate"]
|
|
26975
|
-
})
|
|
26976
|
-
);
|
|
26977
|
-
const spawnResults = await Promise.all(spawns);
|
|
26978
|
-
if (!spawnResults.every((r) => Boolean(r.subagentId))) {
|
|
26979
|
-
throw new SddError({
|
|
26980
|
-
message: "One or more subagent spawns failed",
|
|
26981
|
-
code: ERROR_CODES.SDD_INVALID_STATE
|
|
26982
|
-
});
|
|
26983
|
-
}
|
|
26984
|
-
const assignPromises = tasks.map((task, i) => {
|
|
26985
|
-
const spec = {
|
|
26986
|
-
id: taskIds[i] ?? task.id,
|
|
26987
|
-
description: [
|
|
26988
|
-
directivePreamble,
|
|
26989
|
-
"",
|
|
26990
|
-
`\u2500\u2500 TASK ${i + 1}/${tasks.length} \u2500\u2500`,
|
|
26991
|
-
`[${task.priority.toUpperCase()}] ${task.title}`,
|
|
26992
|
-
"",
|
|
26993
|
-
task.description
|
|
26994
|
-
].join("\n"),
|
|
26995
|
-
subagentId: subagentIds[i] ?? spawnResults[i]?.subagentId ?? task.id,
|
|
26996
|
-
timeoutMs: this.timeoutMs
|
|
26997
|
-
};
|
|
26998
|
-
return this.coordinator?.assign(spec);
|
|
27751
|
+
await coordinator.assign({
|
|
27752
|
+
id: correlationId,
|
|
27753
|
+
description: [
|
|
27754
|
+
directivePreamble,
|
|
27755
|
+
"",
|
|
27756
|
+
`\u2500\u2500 TASK \u2500\u2500`,
|
|
27757
|
+
`[${task.priority.toUpperCase()}] ${task.title}`,
|
|
27758
|
+
"",
|
|
27759
|
+
task.description
|
|
27760
|
+
].join("\n"),
|
|
27761
|
+
subagentId,
|
|
27762
|
+
...this.timeoutMs ? { timeoutMs: this.timeoutMs } : {}
|
|
26999
27763
|
});
|
|
27000
|
-
|
|
27001
|
-
let results;
|
|
27764
|
+
let result;
|
|
27002
27765
|
try {
|
|
27003
|
-
|
|
27766
|
+
const got = await coordinator.awaitTasks([correlationId]);
|
|
27767
|
+
result = expectDefined(got[0]);
|
|
27004
27768
|
} catch (err) {
|
|
27005
|
-
|
|
27006
|
-
subagentId
|
|
27007
|
-
taskId:
|
|
27769
|
+
result = {
|
|
27770
|
+
subagentId,
|
|
27771
|
+
taskId: correlationId,
|
|
27008
27772
|
status: "failed",
|
|
27009
27773
|
error: { kind: "unknown", message: String(err), retryable: false },
|
|
27010
27774
|
iterations: 0,
|
|
27011
27775
|
toolCalls: 0,
|
|
27012
27776
|
durationMs: 0
|
|
27013
|
-
}
|
|
27777
|
+
};
|
|
27014
27778
|
}
|
|
27015
|
-
|
|
27016
|
-
|
|
27017
|
-
|
|
27018
|
-
|
|
27019
|
-
|
|
27020
|
-
|
|
27779
|
+
this.taskSubagents.delete(taskId);
|
|
27780
|
+
if (this.cancelledTasks.has(taskId)) {
|
|
27781
|
+
await this.resolveWorktrees([task]);
|
|
27782
|
+
return { taskId, success: false, result };
|
|
27783
|
+
}
|
|
27784
|
+
let verificationFailReason;
|
|
27785
|
+
if (result.status === "success" && this.opts.verifyTask) {
|
|
27786
|
+
const cwd = this.taskCwds.get(taskId) ?? this.opts.projectRoot;
|
|
27787
|
+
try {
|
|
27788
|
+
const verdict = await this.opts.verifyTask({ task, result, cwd });
|
|
27789
|
+
if (!verdict.ok) {
|
|
27790
|
+
verificationFailReason = `verification failed: ${verdict.reason ?? "acceptance criteria not met"}`;
|
|
27791
|
+
}
|
|
27792
|
+
} catch (err) {
|
|
27793
|
+
verificationFailReason = `verification error: ${String(err)}`;
|
|
27794
|
+
}
|
|
27795
|
+
if (verificationFailReason) {
|
|
27796
|
+
this.emit("sdd.task.verification_failed", {
|
|
27797
|
+
runId: this.runId,
|
|
27798
|
+
taskId,
|
|
27799
|
+
reason: verificationFailReason
|
|
27800
|
+
});
|
|
27801
|
+
}
|
|
27802
|
+
}
|
|
27803
|
+
let success = false;
|
|
27804
|
+
if (result.status === "success" && !verificationFailReason) {
|
|
27805
|
+
const merged = await this.integrateWorktree(task, result);
|
|
27806
|
+
if (merged.ok) {
|
|
27807
|
+
success = true;
|
|
27021
27808
|
this.opts.tracker.updateNodeStatus(taskId, "completed");
|
|
27022
27809
|
this.retryMap.delete(taskId);
|
|
27810
|
+
this.persistRetries(taskId, 0);
|
|
27811
|
+
this.emit("sdd.task.completed", {
|
|
27812
|
+
runId: this.runId,
|
|
27813
|
+
taskId,
|
|
27814
|
+
subagentId,
|
|
27815
|
+
durationMs: result.durationMs
|
|
27816
|
+
});
|
|
27817
|
+
} else if (merged.reason) {
|
|
27818
|
+
this.emit("sdd.task.verification_failed", {
|
|
27819
|
+
runId: this.runId,
|
|
27820
|
+
taskId,
|
|
27821
|
+
reason: merged.reason
|
|
27822
|
+
});
|
|
27823
|
+
await this.applyTaskFailure(taskId, subagentId, merged.reason);
|
|
27023
27824
|
} else {
|
|
27024
|
-
|
|
27025
|
-
|
|
27026
|
-
|
|
27027
|
-
|
|
27028
|
-
|
|
27029
|
-
|
|
27030
|
-
|
|
27031
|
-
|
|
27032
|
-
|
|
27825
|
+
this.emit("sdd.task.conflict", {
|
|
27826
|
+
runId: this.runId,
|
|
27827
|
+
taskId,
|
|
27828
|
+
conflictFiles: merged.conflictFiles ?? []
|
|
27829
|
+
});
|
|
27830
|
+
const reason = `merge conflict${merged.conflictFiles?.length ? `: ${merged.conflictFiles.join(", ")}` : ""}`;
|
|
27831
|
+
await this.applyTaskFailure(taskId, subagentId, reason);
|
|
27832
|
+
}
|
|
27833
|
+
} else {
|
|
27834
|
+
const errMsg = verificationFailReason ?? (result.error?.kind ? `${result.error.kind}: ${result.error.message}` : result.error?.message ?? "unknown error");
|
|
27835
|
+
await this.applyTaskFailure(taskId, subagentId, errMsg);
|
|
27836
|
+
await this.resolveWorktrees([task]);
|
|
27837
|
+
}
|
|
27838
|
+
return { taskId, success, result };
|
|
27839
|
+
}
|
|
27840
|
+
/**
|
|
27841
|
+
* Apply a task failure: retry (→ pending, bump retry count) while attempts
|
|
27842
|
+
* remain, else consult the optional supervisor (which can rescue via
|
|
27843
|
+
* retry/reassign/split), else terminal-fail (→ failed). Shared by the
|
|
27844
|
+
* worker-failure, verification-gate, and merge-conflict paths so all three
|
|
27845
|
+
* negotiate the same retry budget and emit the same events.
|
|
27846
|
+
*/
|
|
27847
|
+
async applyTaskFailure(taskId, subagentId, errMsg) {
|
|
27848
|
+
const currentRetries = this.retryMap.get(taskId) ?? 0;
|
|
27849
|
+
if (currentRetries < this.maxRetries) {
|
|
27850
|
+
this.retryMap.set(taskId, currentRetries + 1);
|
|
27851
|
+
this.persistRetries(taskId, currentRetries + 1);
|
|
27852
|
+
this.opts.tracker.updateNodeStatus(
|
|
27853
|
+
taskId,
|
|
27854
|
+
"pending",
|
|
27855
|
+
`Retry ${currentRetries + 1}/${this.maxRetries}: ${errMsg}`
|
|
27856
|
+
);
|
|
27857
|
+
this.emit("sdd.task.retrying", {
|
|
27858
|
+
runId: this.runId,
|
|
27859
|
+
taskId,
|
|
27860
|
+
attempt: currentRetries + 1,
|
|
27861
|
+
maxRetries: this.maxRetries
|
|
27862
|
+
});
|
|
27863
|
+
return;
|
|
27864
|
+
}
|
|
27865
|
+
if (await this.trySupervisorRescue(taskId, errMsg)) return;
|
|
27866
|
+
this.opts.tracker.updateNodeStatus(taskId, "failed", errMsg);
|
|
27867
|
+
this.emit("sdd.task.failed", { runId: this.runId, taskId, subagentId, error: errMsg });
|
|
27868
|
+
}
|
|
27869
|
+
/**
|
|
27870
|
+
* Consult `superviseFailure` for a task that has exhausted its retries.
|
|
27871
|
+
* Applies the verdict (retry / reassign+retry / split) and returns true when
|
|
27872
|
+
* the task was rescued (caller must NOT terminal-fail it). Bounded per task by
|
|
27873
|
+
* `maxSupervisorEscalations` so an always-"retry" supervisor can't loop forever.
|
|
27874
|
+
*/
|
|
27875
|
+
async trySupervisorRescue(taskId, errMsg) {
|
|
27876
|
+
const supervise = this.opts.superviseFailure;
|
|
27877
|
+
if (!supervise) return false;
|
|
27878
|
+
const used = this.supervisorEscalations.get(taskId) ?? 0;
|
|
27879
|
+
if (used >= this.maxSupervisorEscalations) return false;
|
|
27880
|
+
const node = this.opts.tracker.getNode(taskId);
|
|
27881
|
+
if (!node) return false;
|
|
27882
|
+
let verdict;
|
|
27883
|
+
try {
|
|
27884
|
+
verdict = await supervise({ task: node, error: errMsg, attempts: used });
|
|
27885
|
+
} catch {
|
|
27886
|
+
return false;
|
|
27887
|
+
}
|
|
27888
|
+
if (!verdict || verdict.action === "fail") return false;
|
|
27889
|
+
this.supervisorEscalations.set(taskId, used + 1);
|
|
27890
|
+
const requeue = (reason) => {
|
|
27891
|
+
this.retryMap.delete(taskId);
|
|
27892
|
+
this.persistRetries(taskId, 0);
|
|
27893
|
+
this.opts.tracker.updateNodeStatus(taskId, "pending", reason);
|
|
27894
|
+
};
|
|
27895
|
+
if (verdict.action === "reassign") {
|
|
27896
|
+
this.setTaskModel(taskId, verdict.model, verdict.provider);
|
|
27897
|
+
requeue(`supervisor reassign: ${verdict.model ?? "default"}`);
|
|
27898
|
+
this.emit("sdd.supervisor.decision", { runId: this.runId, taskId, action: "reassign" });
|
|
27899
|
+
return true;
|
|
27900
|
+
}
|
|
27901
|
+
if (verdict.action === "split") {
|
|
27902
|
+
const ids = this.splitTask(taskId, verdict.subtasks);
|
|
27903
|
+
if (ids.length === 0) return false;
|
|
27904
|
+
this.emit("sdd.supervisor.decision", { runId: this.runId, taskId, action: "split" });
|
|
27905
|
+
return true;
|
|
27906
|
+
}
|
|
27907
|
+
requeue("supervisor retry");
|
|
27908
|
+
this.emit("sdd.supervisor.decision", { runId: this.runId, taskId, action: "retry" });
|
|
27909
|
+
return true;
|
|
27910
|
+
}
|
|
27911
|
+
/**
|
|
27912
|
+
* Integrate a verified-successful task's worktree into the base branch.
|
|
27913
|
+
* Commits, squash-merges (optionally running `conflictResolver` first), and on
|
|
27914
|
+
* success releases the worktree. On an UNRESOLVED conflict it returns
|
|
27915
|
+
* `{ok:false}` with the conflicting files so the caller routes the task into
|
|
27916
|
+
* the failure path (a retry forks a fresh worktree off the now-advanced base,
|
|
27917
|
+
* which usually clears the conflict). No-op `{ok:true}` when worktrees are
|
|
27918
|
+
* disabled or none was allocated for this task. Never throws — a merge hiccup
|
|
27919
|
+
* degrades to a (retryable) failure rather than wedging the run.
|
|
27920
|
+
*/
|
|
27921
|
+
async integrateWorktree(task, result) {
|
|
27922
|
+
const wt = this.opts.worktrees;
|
|
27923
|
+
if (!wt) return { ok: true };
|
|
27924
|
+
const handle = this.taskWorktrees.get(task.id);
|
|
27925
|
+
if (!handle) return { ok: true };
|
|
27926
|
+
try {
|
|
27927
|
+
await wt.commitAll(handle, `sdd(${task.title}): ${task.id}`);
|
|
27928
|
+
const baseShaBefore = await wt.baseHead(handle);
|
|
27929
|
+
const baseSha = this.opts.conflictResolver ? baseShaBefore : null;
|
|
27930
|
+
const res = await wt.merge(handle, {
|
|
27931
|
+
squash: true,
|
|
27932
|
+
...this.opts.conflictResolver ? {
|
|
27933
|
+
resolve: (info) => this.opts.conflictResolver({ task, conflictFiles: info.conflictFiles, cwd: info.cwd })
|
|
27934
|
+
} : {}
|
|
27935
|
+
});
|
|
27936
|
+
if (res.ok) {
|
|
27937
|
+
if (res.resolved && this.opts.verifyTask && baseSha) {
|
|
27938
|
+
let regressed;
|
|
27939
|
+
try {
|
|
27940
|
+
const verdict = await this.opts.verifyTask({
|
|
27941
|
+
task,
|
|
27942
|
+
result: result ?? {},
|
|
27943
|
+
cwd: this.opts.projectRoot
|
|
27944
|
+
});
|
|
27945
|
+
if (!verdict.ok) regressed = verdict.reason ?? "verification failed after conflict resolution";
|
|
27946
|
+
} catch (err) {
|
|
27947
|
+
regressed = `verification error after conflict resolution: ${String(err)}`;
|
|
27948
|
+
}
|
|
27949
|
+
if (regressed) {
|
|
27950
|
+
await wt.revertBaseTo(handle, baseSha).catch(() => {
|
|
27951
|
+
});
|
|
27952
|
+
await wt.release(handle, { keep: false }).catch(() => {
|
|
27953
|
+
});
|
|
27954
|
+
this.forgetWorktree(task.id, { keepBranchLabel: true });
|
|
27955
|
+
return { ok: false, conflictFiles: [], reason: regressed };
|
|
27956
|
+
}
|
|
27957
|
+
}
|
|
27958
|
+
const baseShaAfter = await wt.baseHead(handle);
|
|
27959
|
+
if (baseShaAfter && baseShaAfter !== baseShaBefore) {
|
|
27960
|
+
this.mergedCommits.push({ taskId: task.id, sha: baseShaAfter, title: task.title });
|
|
27961
|
+
this.emit("sdd.task.merged", { runId: this.runId, taskId: task.id, sha: baseShaAfter });
|
|
27962
|
+
}
|
|
27963
|
+
await wt.release(handle, { keep: false });
|
|
27964
|
+
this.forgetWorktree(task.id);
|
|
27965
|
+
return { ok: true };
|
|
27966
|
+
}
|
|
27967
|
+
await wt.release(handle, { keep: false }).catch(() => {
|
|
27968
|
+
});
|
|
27969
|
+
this.forgetWorktree(task.id, { keepBranchLabel: true });
|
|
27970
|
+
return { ok: false, conflictFiles: res.conflictFiles ?? [] };
|
|
27971
|
+
} catch {
|
|
27972
|
+
this.forgetWorktree(task.id);
|
|
27973
|
+
return { ok: false, conflictFiles: [] };
|
|
27974
|
+
}
|
|
27975
|
+
}
|
|
27976
|
+
/** Allocate a fresh git worktree per task in the batch (no-op without a manager). */
|
|
27977
|
+
async allocateWorktrees(tasks) {
|
|
27978
|
+
const wt = this.opts.worktrees;
|
|
27979
|
+
if (!wt) return;
|
|
27980
|
+
for (const task of tasks) {
|
|
27981
|
+
if (this.taskWorktrees.has(task.id)) continue;
|
|
27982
|
+
try {
|
|
27983
|
+
const handle = await wt.allocate(`sdd-${task.id}`, {
|
|
27984
|
+
slugHint: task.title,
|
|
27985
|
+
ownerLabel: task.title
|
|
27986
|
+
});
|
|
27987
|
+
if (handle.status === "active") {
|
|
27988
|
+
this.taskWorktrees.set(task.id, handle);
|
|
27989
|
+
this.taskCwds.set(task.id, handle.dir);
|
|
27990
|
+
this.taskBranches.set(task.id, handle.branch);
|
|
27991
|
+
const node = this.opts.tracker.getNode(task.id);
|
|
27992
|
+
if (node) node.metadata = { ...node.metadata, worktreeBranch: handle.branch };
|
|
27993
|
+
}
|
|
27994
|
+
} catch {
|
|
27995
|
+
}
|
|
27996
|
+
}
|
|
27997
|
+
}
|
|
27998
|
+
/**
|
|
27999
|
+
* Resolve each task's worktree after its result is known. Serialized merges
|
|
28000
|
+
* (one at a time) keep the base branch consistent; the wave structure already
|
|
28001
|
+
* guarantees dependency order (a task's blockers merged in an earlier wave).
|
|
28002
|
+
*/
|
|
28003
|
+
async resolveWorktrees(tasks) {
|
|
28004
|
+
const wt = this.opts.worktrees;
|
|
28005
|
+
if (!wt) return;
|
|
28006
|
+
for (const task of tasks) {
|
|
28007
|
+
const handle = this.taskWorktrees.get(task.id);
|
|
28008
|
+
if (!handle) continue;
|
|
28009
|
+
const node = this.opts.tracker.getNode(task.id);
|
|
28010
|
+
const status = node?.status;
|
|
28011
|
+
const cancelled = Boolean(node?.metadata?.cancelled);
|
|
28012
|
+
try {
|
|
28013
|
+
if (cancelled) {
|
|
28014
|
+
await wt.release(handle, { keep: false });
|
|
28015
|
+
this.forgetWorktree(task.id, { keepBranchLabel: false });
|
|
28016
|
+
} else if (status === "completed") {
|
|
28017
|
+
await wt.commitAll(handle, `sdd(${task.title}): ${task.id}`);
|
|
28018
|
+
await wt.merge(handle, { squash: true });
|
|
28019
|
+
await wt.release(handle, { keep: false });
|
|
28020
|
+
this.forgetWorktree(task.id);
|
|
28021
|
+
} else if (status === "failed") {
|
|
28022
|
+
await wt.release(handle, { keep: false });
|
|
28023
|
+
this.forgetWorktree(task.id, { keepBranchLabel: false });
|
|
27033
28024
|
} else {
|
|
27034
|
-
|
|
28025
|
+
await wt.release(handle, { keep: false });
|
|
28026
|
+
this.forgetWorktree(task.id, { keepBranchLabel: false });
|
|
27035
28027
|
}
|
|
28028
|
+
} catch {
|
|
28029
|
+
this.forgetWorktree(task.id);
|
|
27036
28030
|
}
|
|
27037
28031
|
}
|
|
27038
|
-
|
|
27039
|
-
|
|
27040
|
-
|
|
27041
|
-
|
|
27042
|
-
|
|
27043
|
-
|
|
27044
|
-
|
|
27045
|
-
|
|
27046
|
-
|
|
28032
|
+
}
|
|
28033
|
+
forgetWorktree(taskId, opts = {}) {
|
|
28034
|
+
this.taskWorktrees.delete(taskId);
|
|
28035
|
+
this.taskCwds.delete(taskId);
|
|
28036
|
+
if (!opts.keepBranchLabel) this.taskBranches.delete(taskId);
|
|
28037
|
+
}
|
|
28038
|
+
/** Persist a task's retry count into node metadata (survives crash → resume). */
|
|
28039
|
+
persistRetries(taskId, retries) {
|
|
28040
|
+
const node = this.opts.tracker.getNode(taskId);
|
|
28041
|
+
if (node) node.metadata = { ...node.metadata, retries };
|
|
27047
28042
|
}
|
|
27048
28043
|
buildProgress() {
|
|
27049
28044
|
const gp = this.opts.tracker.getProgress();
|
|
@@ -27062,145 +28057,1881 @@ var SddParallelRun = class {
|
|
|
27062
28057
|
}
|
|
27063
28058
|
};
|
|
27064
28059
|
|
|
27065
|
-
// src/
|
|
27066
|
-
|
|
27067
|
-
|
|
27068
|
-
|
|
27069
|
-
const keys = Object.keys(labels).sort();
|
|
27070
|
-
return keys.map((k) => `${k}=${labels[k]}`).join(",");
|
|
27071
|
-
}
|
|
27072
|
-
function quantile(sorted, q) {
|
|
27073
|
-
if (sorted.length === 0) return 0;
|
|
27074
|
-
const idx = Math.min(sorted.length - 1, Math.floor(q * sorted.length));
|
|
27075
|
-
return sorted[idx] ?? 0;
|
|
28060
|
+
// src/core/fallback-model.ts
|
|
28061
|
+
function visibleProviderModels(config, providerId, providerModels) {
|
|
28062
|
+
const entry = config.providers?.[providerId];
|
|
28063
|
+
return entry?.models !== void 0 ? [...entry.models] : providerModels;
|
|
27076
28064
|
}
|
|
27077
|
-
|
|
27078
|
-
|
|
27079
|
-
|
|
27080
|
-
|
|
27081
|
-
|
|
27082
|
-
|
|
27083
|
-
|
|
27084
|
-
|
|
27085
|
-
state.value += value;
|
|
27086
|
-
series.set(key, state);
|
|
28065
|
+
function parseModelRef(ref) {
|
|
28066
|
+
const trimmed = ref.trim();
|
|
28067
|
+
const slash = trimmed.indexOf("/");
|
|
28068
|
+
if (slash !== -1) {
|
|
28069
|
+
return {
|
|
28070
|
+
provider: trimmed.slice(0, slash) || void 0,
|
|
28071
|
+
model: trimmed.slice(slash + 1).trim()
|
|
28072
|
+
};
|
|
27087
28073
|
}
|
|
27088
|
-
|
|
27089
|
-
|
|
27090
|
-
|
|
28074
|
+
const parts = trimmed.split(/\s+/);
|
|
28075
|
+
if (parts.length >= 2) {
|
|
28076
|
+
return { provider: parts[0], model: parts.slice(1).join(" ") };
|
|
27091
28077
|
}
|
|
27092
|
-
|
|
27093
|
-
|
|
27094
|
-
|
|
27095
|
-
|
|
27096
|
-
|
|
27097
|
-
state = { count: 0, sum: 0, min: value, max: value, samples: [] };
|
|
27098
|
-
series.set(key, state);
|
|
27099
|
-
}
|
|
27100
|
-
state.count++;
|
|
27101
|
-
state.sum += value;
|
|
27102
|
-
if (value < state.min) state.min = value;
|
|
27103
|
-
if (value > state.max) state.max = value;
|
|
27104
|
-
if (state.samples.length < RESERVOIR_SIZE) {
|
|
27105
|
-
state.samples.push(value);
|
|
27106
|
-
} else {
|
|
27107
|
-
const r = Math.floor(Math.random() * state.count);
|
|
27108
|
-
if (r < RESERVOIR_SIZE) state.samples[r] = value;
|
|
27109
|
-
}
|
|
28078
|
+
return { model: trimmed };
|
|
28079
|
+
}
|
|
28080
|
+
function shouldFallback(err) {
|
|
28081
|
+
if (err instanceof StreamHangError) {
|
|
28082
|
+
return 599;
|
|
27110
28083
|
}
|
|
27111
|
-
|
|
27112
|
-
|
|
27113
|
-
|
|
27114
|
-
|
|
27115
|
-
|
|
27116
|
-
|
|
27117
|
-
|
|
27118
|
-
|
|
27119
|
-
|
|
27120
|
-
|
|
27121
|
-
|
|
27122
|
-
|
|
27123
|
-
|
|
27124
|
-
|
|
27125
|
-
|
|
27126
|
-
|
|
27127
|
-
|
|
27128
|
-
|
|
27129
|
-
|
|
27130
|
-
|
|
28084
|
+
if (!(err instanceof ProviderError)) return null;
|
|
28085
|
+
const s = err.status;
|
|
28086
|
+
if (s === 0) return s;
|
|
28087
|
+
if (s === 429 || s === 529 || s >= 500) return s;
|
|
28088
|
+
return null;
|
|
28089
|
+
}
|
|
28090
|
+
function providerHasKey(entry) {
|
|
28091
|
+
if (!entry) return false;
|
|
28092
|
+
if (typeof entry.apiKey === "string" && entry.apiKey.length > 0) return true;
|
|
28093
|
+
if (Array.isArray(entry.apiKeys) && entry.apiKeys.some((k) => k?.apiKey)) return true;
|
|
28094
|
+
if (Array.isArray(entry.envVars) && entry.envVars.some((v) => !!process.env[v])) return true;
|
|
28095
|
+
return false;
|
|
28096
|
+
}
|
|
28097
|
+
var SMART_DEFAULT_MAX = 4;
|
|
28098
|
+
function smartDefaultFallbackChain(config) {
|
|
28099
|
+
const leaderProvider = config.provider;
|
|
28100
|
+
const leaderModel = config.model;
|
|
28101
|
+
const providers = config.providers ?? {};
|
|
28102
|
+
const seen = /* @__PURE__ */ new Set();
|
|
28103
|
+
const sameProvider = [];
|
|
28104
|
+
const crossProvider = [];
|
|
28105
|
+
const ids = Object.keys(providers).sort(
|
|
28106
|
+
(a, b) => a === leaderProvider ? -1 : b === leaderProvider ? 1 : a.localeCompare(b)
|
|
28107
|
+
);
|
|
28108
|
+
for (const id of ids) {
|
|
28109
|
+
const entry = providers[id];
|
|
28110
|
+
if (!providerHasKey(entry)) continue;
|
|
28111
|
+
const models = visibleProviderModels(config, id, entry?.models ?? []);
|
|
28112
|
+
for (const model of models) {
|
|
28113
|
+
if (id === leaderProvider && model === leaderModel) continue;
|
|
28114
|
+
const ref = `${id}/${model}`;
|
|
28115
|
+
if (seen.has(ref)) continue;
|
|
28116
|
+
seen.add(ref);
|
|
28117
|
+
(id === leaderProvider ? sameProvider : crossProvider).push(ref);
|
|
28118
|
+
}
|
|
28119
|
+
}
|
|
28120
|
+
return [...sameProvider, ...crossProvider].slice(0, SMART_DEFAULT_MAX);
|
|
28121
|
+
}
|
|
28122
|
+
function effectiveFallbackChain(config) {
|
|
28123
|
+
const explicit = config.fallbackModels ?? [];
|
|
28124
|
+
const filteredExplicit = explicit.filter((ref) => {
|
|
28125
|
+
const parsed = parseModelRef(ref);
|
|
28126
|
+
if (!parsed.model) return false;
|
|
28127
|
+
const providerId = parsed.provider ?? config.provider;
|
|
28128
|
+
const entry = config.providers?.[providerId];
|
|
28129
|
+
if (!entry?.models) return true;
|
|
28130
|
+
return entry.models.includes(parsed.model);
|
|
28131
|
+
});
|
|
28132
|
+
if (filteredExplicit.length > 0) return filteredExplicit;
|
|
28133
|
+
if (config.fallbackAuto === false) return [];
|
|
28134
|
+
return smartDefaultFallbackChain(config);
|
|
28135
|
+
}
|
|
28136
|
+
function createFallbackModelExtension(deps) {
|
|
28137
|
+
let dirty = false;
|
|
28138
|
+
return {
|
|
28139
|
+
name: "fallback-model",
|
|
28140
|
+
beforeRun: async (ctx) => {
|
|
28141
|
+
if (!dirty) return;
|
|
28142
|
+
const cfg = deps.getConfig();
|
|
28143
|
+
try {
|
|
28144
|
+
ctx.provider = await deps.buildProvider(cfg.provider);
|
|
28145
|
+
ctx.model = cfg.model;
|
|
28146
|
+
await deps.onModelSwitch?.(cfg.provider, cfg.model);
|
|
28147
|
+
} catch (err) {
|
|
28148
|
+
deps.logger?.warn(
|
|
28149
|
+
`fallback-model: could not restore primary "${cfg.provider}/${cfg.model}": ${err instanceof Error ? err.message : String(err)}`
|
|
28150
|
+
);
|
|
27131
28151
|
}
|
|
27132
|
-
|
|
27133
|
-
|
|
27134
|
-
|
|
27135
|
-
|
|
27136
|
-
|
|
27137
|
-
|
|
27138
|
-
|
|
27139
|
-
|
|
27140
|
-
|
|
27141
|
-
|
|
27142
|
-
|
|
27143
|
-
|
|
27144
|
-
|
|
27145
|
-
|
|
27146
|
-
|
|
27147
|
-
|
|
28152
|
+
dirty = false;
|
|
28153
|
+
},
|
|
28154
|
+
wrapProviderRunner: async (ctx, request, inner) => {
|
|
28155
|
+
try {
|
|
28156
|
+
return await inner(ctx, request);
|
|
28157
|
+
} catch (firstErr) {
|
|
28158
|
+
let lastErr = firstErr;
|
|
28159
|
+
const cfg = deps.getConfig();
|
|
28160
|
+
const chain = effectiveFallbackChain(cfg);
|
|
28161
|
+
for (const ref of chain) {
|
|
28162
|
+
const status = shouldFallback(lastErr);
|
|
28163
|
+
if (status === null) break;
|
|
28164
|
+
const parsed = parseModelRef(ref);
|
|
28165
|
+
if (!parsed.model) continue;
|
|
28166
|
+
const targetProviderId = parsed.provider ?? cfg.provider;
|
|
28167
|
+
const from = { providerId: ctx.provider.id, model: ctx.model };
|
|
28168
|
+
let nextProvider;
|
|
28169
|
+
try {
|
|
28170
|
+
nextProvider = await deps.buildProvider(targetProviderId);
|
|
28171
|
+
} catch (err) {
|
|
28172
|
+
deps.logger?.warn(
|
|
28173
|
+
`fallback-model: skipping "${ref}" \u2014 cannot build provider "${targetProviderId}": ${err instanceof Error ? err.message : String(err)}`
|
|
28174
|
+
);
|
|
28175
|
+
continue;
|
|
27148
28176
|
}
|
|
27149
|
-
|
|
28177
|
+
const providerSwitched = nextProvider.id !== from.providerId;
|
|
28178
|
+
ctx.provider = nextProvider;
|
|
28179
|
+
ctx.model = parsed.model;
|
|
28180
|
+
request.model = parsed.model;
|
|
28181
|
+
dirty = true;
|
|
28182
|
+
await deps.onModelSwitch?.(targetProviderId, parsed.model);
|
|
28183
|
+
deps.events.emit("provider.fallback", {
|
|
28184
|
+
from,
|
|
28185
|
+
to: { providerId: nextProvider.id, model: parsed.model },
|
|
28186
|
+
status,
|
|
28187
|
+
providerSwitched
|
|
28188
|
+
});
|
|
28189
|
+
try {
|
|
28190
|
+
return await inner(ctx, request);
|
|
28191
|
+
} catch (err) {
|
|
28192
|
+
lastErr = err;
|
|
28193
|
+
}
|
|
28194
|
+
}
|
|
28195
|
+
throw lastErr;
|
|
27150
28196
|
}
|
|
27151
28197
|
}
|
|
27152
|
-
|
|
27153
|
-
}
|
|
27154
|
-
reset() {
|
|
27155
|
-
this.counters.clear();
|
|
27156
|
-
this.gauges.clear();
|
|
27157
|
-
this.histograms.clear();
|
|
27158
|
-
}
|
|
27159
|
-
getOrCreate(bag, name) {
|
|
27160
|
-
let series = bag.get(name);
|
|
27161
|
-
if (!series) {
|
|
27162
|
-
series = /* @__PURE__ */ new Map();
|
|
27163
|
-
bag.set(name, series);
|
|
27164
|
-
}
|
|
27165
|
-
return series;
|
|
27166
|
-
}
|
|
27167
|
-
};
|
|
27168
|
-
function parseLabelKey(key) {
|
|
27169
|
-
if (!key) return {};
|
|
27170
|
-
const labels = {};
|
|
27171
|
-
for (const pair of key.split(",")) {
|
|
27172
|
-
const eq = pair.indexOf("=");
|
|
27173
|
-
if (eq > 0) labels[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
27174
|
-
}
|
|
27175
|
-
return labels;
|
|
28198
|
+
};
|
|
27176
28199
|
}
|
|
27177
|
-
|
|
27178
|
-
|
|
27179
|
-
|
|
27180
|
-
|
|
27181
|
-
|
|
27182
|
-
histogram() {
|
|
27183
|
-
}
|
|
27184
|
-
snapshot() {
|
|
27185
|
-
return { timestamp: Date.now(), series: [] };
|
|
27186
|
-
}
|
|
27187
|
-
reset() {
|
|
28200
|
+
|
|
28201
|
+
// src/sdd/sdd-supervisor.ts
|
|
28202
|
+
var SddSupervisor = class {
|
|
28203
|
+
constructor(opts) {
|
|
28204
|
+
this.opts = opts;
|
|
27188
28205
|
}
|
|
28206
|
+
opts;
|
|
28207
|
+
/**
|
|
28208
|
+
* Bind this as `SddParallelRunOptions.superviseFailure`. Returns a verdict the
|
|
28209
|
+
* run applies, or `undefined`/`{action:'fail'}` to let the task terminal-fail.
|
|
28210
|
+
*/
|
|
28211
|
+
superviseFailure = async (info) => {
|
|
28212
|
+
const { task, error, attempts } = info;
|
|
28213
|
+
const canReassign = (this.opts.reassignModels?.length ?? 0) > 0;
|
|
28214
|
+
const canSplit = Boolean(this.opts.generateSubtasks);
|
|
28215
|
+
const decision = await this.opts.brain.decide({
|
|
28216
|
+
id: `sdd-supervisor-${task.id}-${attempts}`,
|
|
28217
|
+
source: "system",
|
|
28218
|
+
question: `SDD task "${task.title}" exhausted its retries. How should the run proceed?`,
|
|
28219
|
+
context: `Error: ${error}
|
|
28220
|
+
Supervisor rescues already used: ${attempts}`,
|
|
28221
|
+
options: [
|
|
28222
|
+
{ id: "retry", label: "Retry the task as-is", recommended: true },
|
|
28223
|
+
...canReassign ? [{ id: "reassign", label: "Reassign to a different model" }] : [],
|
|
28224
|
+
...canSplit ? [{ id: "split", label: "Split into smaller sub-tasks" }] : [],
|
|
28225
|
+
{ id: "fail", label: "Give up and mark the task failed" }
|
|
28226
|
+
],
|
|
28227
|
+
// Higher risk once we've already rescued it once — pushes a wired LLM/human
|
|
28228
|
+
// toward a decisive verdict instead of looping retries.
|
|
28229
|
+
risk: attempts >= 1 ? "high" : "medium",
|
|
28230
|
+
// `continue` → policy answers in place (bounded retry, LLM never runs).
|
|
28231
|
+
// `ask_human` → policy escalates so the autonomous LLM layer can actually
|
|
28232
|
+
// pick reassign/split (see requestLlmVerdict's safety contract).
|
|
28233
|
+
fallback: this.opts.requestLlmVerdict ? "ask_human" : "continue"
|
|
28234
|
+
});
|
|
28235
|
+
if (decision.type === "deny") return { action: "fail" };
|
|
28236
|
+
if (decision.type !== "answer") return { action: "retry" };
|
|
28237
|
+
const choice = decision.optionId ?? "retry";
|
|
28238
|
+
if (choice === "fail") return { action: "fail" };
|
|
28239
|
+
if (choice === "reassign" && canReassign) {
|
|
28240
|
+
const models = this.opts.reassignModels;
|
|
28241
|
+
const ref = models[attempts % models.length];
|
|
28242
|
+
const parsed = ref ? parseModelRef(ref) : void 0;
|
|
28243
|
+
return { action: "reassign", model: parsed?.model, provider: parsed?.provider };
|
|
28244
|
+
}
|
|
28245
|
+
if (choice === "split" && this.opts.generateSubtasks) {
|
|
28246
|
+
const subtasks = await this.opts.generateSubtasks({ task, error }).catch(() => []);
|
|
28247
|
+
return subtasks.length ? { action: "split", subtasks } : { action: "retry" };
|
|
28248
|
+
}
|
|
28249
|
+
return { action: "retry" };
|
|
28250
|
+
};
|
|
27189
28251
|
};
|
|
28252
|
+
function makeCommandVerifier(options = {}) {
|
|
28253
|
+
const metadataKey = options.metadataKey ?? "verificationCommand";
|
|
28254
|
+
const timeoutMs = options.timeoutMs ?? 18e4;
|
|
28255
|
+
return async function verifyTask(info) {
|
|
28256
|
+
const cmd = info.task.metadata?.[metadataKey];
|
|
28257
|
+
if (typeof cmd !== "string" || !cmd.trim()) return { ok: true };
|
|
28258
|
+
return await new Promise((resolve19) => {
|
|
28259
|
+
const child = spawn(cmd, { cwd: info.cwd, shell: true, windowsHide: true, stdio: "ignore" });
|
|
28260
|
+
const timer = setTimeout(() => {
|
|
28261
|
+
child.kill();
|
|
28262
|
+
resolve19({ ok: false, reason: `verification timed out: ${cmd}` });
|
|
28263
|
+
}, timeoutMs);
|
|
28264
|
+
child.on("exit", (code) => {
|
|
28265
|
+
clearTimeout(timer);
|
|
28266
|
+
resolve19(
|
|
28267
|
+
code === 0 ? { ok: true } : { ok: false, reason: `verification failed (exit ${code}): ${cmd}` }
|
|
28268
|
+
);
|
|
28269
|
+
});
|
|
28270
|
+
child.on("error", (err) => {
|
|
28271
|
+
clearTimeout(timer);
|
|
28272
|
+
resolve19({ ok: false, reason: `verification spawn error: ${String(err)}` });
|
|
28273
|
+
});
|
|
28274
|
+
});
|
|
28275
|
+
};
|
|
28276
|
+
}
|
|
27190
28277
|
|
|
27191
|
-
// src/
|
|
27192
|
-
var
|
|
27193
|
-
|
|
27194
|
-
|
|
27195
|
-
|
|
27196
|
-
|
|
27197
|
-
|
|
27198
|
-
|
|
27199
|
-
|
|
27200
|
-
|
|
27201
|
-
|
|
27202
|
-
|
|
27203
|
-
|
|
28278
|
+
// src/sdd/decompose-task.ts
|
|
28279
|
+
var TASK_TYPES = /* @__PURE__ */ new Set(["feature", "bugfix", "refactor", "docs", "test", "chore"]);
|
|
28280
|
+
var PRIORITIES = /* @__PURE__ */ new Set(["critical", "high", "medium", "low"]);
|
|
28281
|
+
function extractJsonArray(text) {
|
|
28282
|
+
const fence = text.match(/```(?:json)?\s*(\[[\s\S]*?\])\s*```/);
|
|
28283
|
+
if (fence?.[1]) return fence[1].trim();
|
|
28284
|
+
const bare = text.match(/(\[[\s\S]*\])/);
|
|
28285
|
+
if (bare?.[1]) {
|
|
28286
|
+
try {
|
|
28287
|
+
if (Array.isArray(JSON.parse(bare[1]))) return bare[1];
|
|
28288
|
+
} catch {
|
|
28289
|
+
}
|
|
28290
|
+
}
|
|
28291
|
+
return null;
|
|
28292
|
+
}
|
|
28293
|
+
function buildPrompt(task, error, min, max) {
|
|
28294
|
+
return [
|
|
28295
|
+
"You are an engineering lead triaging a software task that FAILED after every",
|
|
28296
|
+
"automated retry was exhausted. Break it into smaller, independently-executable",
|
|
28297
|
+
`sub-tasks (between ${min} and ${max}) so separate workers can each tackle a`,
|
|
28298
|
+
"narrower slice. Each sub-task must be strictly smaller than the parent \u2014 never",
|
|
28299
|
+
"restate the whole task as one sub-task.",
|
|
28300
|
+
"",
|
|
28301
|
+
`Parent task title: ${task.title}`,
|
|
28302
|
+
`Parent description: ${task.description}`,
|
|
28303
|
+
`Failure / error: ${error || "(none recorded)"}`,
|
|
28304
|
+
"",
|
|
28305
|
+
"Respond with ONLY a JSON array (no prose) of objects with this shape:",
|
|
28306
|
+
'[{"title": "...", "description": "...", "type": "feature|bugfix|refactor|docs|test|chore", "priority": "critical|high|medium|low"}]',
|
|
28307
|
+
"`type` and `priority` are optional (they default to the parent's)."
|
|
28308
|
+
].join("\n");
|
|
28309
|
+
}
|
|
28310
|
+
function makeLlmSubtaskGenerator(opts) {
|
|
28311
|
+
const min = Math.max(2, opts.minSubtasks ?? 2);
|
|
28312
|
+
const max = Math.max(min, opts.maxSubtasks ?? 4);
|
|
28313
|
+
return async function generateSubtasks(info) {
|
|
28314
|
+
let text;
|
|
28315
|
+
try {
|
|
28316
|
+
text = await opts.run(buildPrompt(info.task, info.error, min, max));
|
|
28317
|
+
} catch {
|
|
28318
|
+
return [];
|
|
28319
|
+
}
|
|
28320
|
+
const json = extractJsonArray(text ?? "");
|
|
28321
|
+
if (!json) return [];
|
|
28322
|
+
let raw;
|
|
28323
|
+
try {
|
|
28324
|
+
raw = JSON.parse(json);
|
|
28325
|
+
} catch {
|
|
28326
|
+
return [];
|
|
28327
|
+
}
|
|
28328
|
+
if (!Array.isArray(raw)) return [];
|
|
28329
|
+
const specs = [];
|
|
28330
|
+
for (const item of raw) {
|
|
28331
|
+
if (!item || typeof item !== "object") continue;
|
|
28332
|
+
const r = item;
|
|
28333
|
+
const title = typeof r["title"] === "string" ? r["title"].trim() : "";
|
|
28334
|
+
const description = typeof r["description"] === "string" ? r["description"].trim() : "";
|
|
28335
|
+
if (!title || !description) continue;
|
|
28336
|
+
const type = TASK_TYPES.has(r["type"]) ? r["type"] : void 0;
|
|
28337
|
+
const priority = PRIORITIES.has(r["priority"]) ? r["priority"] : void 0;
|
|
28338
|
+
specs.push({ title, description, type, priority });
|
|
28339
|
+
if (specs.length >= max) break;
|
|
28340
|
+
}
|
|
28341
|
+
return specs.length >= min ? specs : [];
|
|
28342
|
+
};
|
|
28343
|
+
}
|
|
28344
|
+
var START = "<<<<<<<";
|
|
28345
|
+
var BASE = "|||||||";
|
|
28346
|
+
var SEP2 = "=======";
|
|
28347
|
+
var END = ">>>>>>>";
|
|
28348
|
+
function resolveConflictText(text, side) {
|
|
28349
|
+
const out = [];
|
|
28350
|
+
let state = "normal";
|
|
28351
|
+
for (const line of text.split("\n")) {
|
|
28352
|
+
const marker = line.slice(0, 7);
|
|
28353
|
+
if (state === "normal" && marker === START) {
|
|
28354
|
+
state = "ours";
|
|
28355
|
+
continue;
|
|
28356
|
+
}
|
|
28357
|
+
if (state !== "normal" && marker === BASE) {
|
|
28358
|
+
state = "base";
|
|
28359
|
+
continue;
|
|
28360
|
+
}
|
|
28361
|
+
if (state !== "normal" && marker === SEP2) {
|
|
28362
|
+
state = "theirs";
|
|
28363
|
+
continue;
|
|
28364
|
+
}
|
|
28365
|
+
if (state !== "normal" && marker === END) {
|
|
28366
|
+
state = "normal";
|
|
28367
|
+
continue;
|
|
28368
|
+
}
|
|
28369
|
+
if (state === "normal") out.push(line);
|
|
28370
|
+
else if (state === "ours" && side === "base") out.push(line);
|
|
28371
|
+
else if (state === "theirs" && side === "incoming") out.push(line);
|
|
28372
|
+
}
|
|
28373
|
+
return out.join("\n");
|
|
28374
|
+
}
|
|
28375
|
+
function hasConflictMarkers(text) {
|
|
28376
|
+
return text.split("\n").some((l) => {
|
|
28377
|
+
const m = l.slice(0, 7);
|
|
28378
|
+
return m === START || m === SEP2 || m === END || m === BASE;
|
|
28379
|
+
});
|
|
28380
|
+
}
|
|
28381
|
+
function makePreferSideConflictResolver(side) {
|
|
28382
|
+
return async function conflictResolver(info) {
|
|
28383
|
+
if (info.conflictFiles.length === 0) return false;
|
|
28384
|
+
for (const rel of info.conflictFiles) {
|
|
28385
|
+
const abs = isAbsolute(rel) ? rel : join(info.cwd, rel);
|
|
28386
|
+
let content;
|
|
28387
|
+
try {
|
|
28388
|
+
content = await readFile(abs, "utf8");
|
|
28389
|
+
} catch {
|
|
28390
|
+
return false;
|
|
28391
|
+
}
|
|
28392
|
+
const resolved = resolveConflictText(content, side);
|
|
28393
|
+
if (hasConflictMarkers(resolved)) return false;
|
|
28394
|
+
try {
|
|
28395
|
+
await writeFile(abs, resolved, "utf8");
|
|
28396
|
+
} catch {
|
|
28397
|
+
return false;
|
|
28398
|
+
}
|
|
28399
|
+
}
|
|
28400
|
+
return true;
|
|
28401
|
+
};
|
|
28402
|
+
}
|
|
28403
|
+
function unfence(text) {
|
|
28404
|
+
const m = text.match(/^[\s\S]*?```[^\n]*\n([\s\S]*?)\n```[\s\S]*$/);
|
|
28405
|
+
return m?.[1] !== void 0 ? m[1] : text.trim();
|
|
28406
|
+
}
|
|
28407
|
+
function nonMarkerLineCount(text) {
|
|
28408
|
+
return text.split("\n").filter((l) => {
|
|
28409
|
+
const m = l.slice(0, 7);
|
|
28410
|
+
return m !== START && m !== SEP2 && m !== END && m !== BASE;
|
|
28411
|
+
}).length;
|
|
28412
|
+
}
|
|
28413
|
+
function makeLlmConflictResolver(opts) {
|
|
28414
|
+
const minFraction = opts.minRetainedFraction ?? 0.5;
|
|
28415
|
+
return async function conflictResolver(info) {
|
|
28416
|
+
if (info.conflictFiles.length === 0) return false;
|
|
28417
|
+
for (const rel of info.conflictFiles) {
|
|
28418
|
+
const abs = isAbsolute(rel) ? rel : join(info.cwd, rel);
|
|
28419
|
+
let content;
|
|
28420
|
+
try {
|
|
28421
|
+
content = await readFile(abs, "utf8");
|
|
28422
|
+
} catch {
|
|
28423
|
+
return false;
|
|
28424
|
+
}
|
|
28425
|
+
if (!hasConflictMarkers(content)) continue;
|
|
28426
|
+
const prompt = [
|
|
28427
|
+
"You are resolving a git MERGE CONFLICT in a single file. Below is the",
|
|
28428
|
+
"full file with conflict markers (<<<<<<<, =======, >>>>>>>, and possibly",
|
|
28429
|
+
"||||||| for diff3). Combine both sides into the correct, complete file \u2014",
|
|
28430
|
+
"keep ALL non-conflicting content verbatim and reconcile each hunk sensibly.",
|
|
28431
|
+
"Return ONLY the fully resolved file contents (no conflict markers, no",
|
|
28432
|
+
"commentary), optionally wrapped in a single ``` code fence.",
|
|
28433
|
+
"",
|
|
28434
|
+
`File: ${rel}`,
|
|
28435
|
+
"--- BEGIN ---",
|
|
28436
|
+
content,
|
|
28437
|
+
"--- END ---"
|
|
28438
|
+
].join("\n");
|
|
28439
|
+
let out;
|
|
28440
|
+
try {
|
|
28441
|
+
out = await opts.run(prompt);
|
|
28442
|
+
} catch {
|
|
28443
|
+
return false;
|
|
28444
|
+
}
|
|
28445
|
+
const resolved = unfence(out ?? "");
|
|
28446
|
+
if (!resolved.trim() || hasConflictMarkers(resolved)) return false;
|
|
28447
|
+
if (resolved.split("\n").length < Math.floor(nonMarkerLineCount(content) * minFraction)) {
|
|
28448
|
+
return false;
|
|
28449
|
+
}
|
|
28450
|
+
try {
|
|
28451
|
+
await writeFile(abs, resolved, "utf8");
|
|
28452
|
+
} catch {
|
|
28453
|
+
return false;
|
|
28454
|
+
}
|
|
28455
|
+
}
|
|
28456
|
+
return true;
|
|
28457
|
+
};
|
|
28458
|
+
}
|
|
28459
|
+
|
|
28460
|
+
// src/sdd/board-types.ts
|
|
28461
|
+
function shortIdMap(graph) {
|
|
28462
|
+
const nodes = Array.from(graph.nodes.values()).sort((a, b) => a.createdAt - b.createdAt);
|
|
28463
|
+
const m = /* @__PURE__ */ new Map();
|
|
28464
|
+
nodes.forEach((n, i) => {
|
|
28465
|
+
m.set(n.id, `t${String(i + 1).padStart(2, "0")}`);
|
|
28466
|
+
});
|
|
28467
|
+
return m;
|
|
28468
|
+
}
|
|
28469
|
+
function buildBoardTasks(graph) {
|
|
28470
|
+
const nodes = Array.from(graph.nodes.values()).sort((a, b) => a.createdAt - b.createdAt);
|
|
28471
|
+
const shortId = shortIdMap(graph);
|
|
28472
|
+
const blockers = /* @__PURE__ */ new Map();
|
|
28473
|
+
for (const n of nodes) blockers.set(n.id, []);
|
|
28474
|
+
for (const e of graph.edges) {
|
|
28475
|
+
if (e.type === "depends_on") blockers.get(e.to)?.push(e.from);
|
|
28476
|
+
}
|
|
28477
|
+
const statusOf = (id) => graph.nodes.get(id)?.status;
|
|
28478
|
+
const depthCache = /* @__PURE__ */ new Map();
|
|
28479
|
+
const depthOf = (id, seen = /* @__PURE__ */ new Set()) => {
|
|
28480
|
+
const cached = depthCache.get(id);
|
|
28481
|
+
if (cached !== void 0) return cached;
|
|
28482
|
+
if (seen.has(id)) return 0;
|
|
28483
|
+
seen.add(id);
|
|
28484
|
+
const deps = blockers.get(id) ?? [];
|
|
28485
|
+
const d = deps.length === 0 ? 0 : 1 + Math.max(...deps.map((b) => depthOf(b, seen)));
|
|
28486
|
+
depthCache.set(id, d);
|
|
28487
|
+
return d;
|
|
28488
|
+
};
|
|
28489
|
+
const toTask = (n) => {
|
|
28490
|
+
const deps = blockers.get(n.id) ?? [];
|
|
28491
|
+
const allDepsDone = deps.every((b) => statusOf(b) === "completed");
|
|
28492
|
+
const meta = n.metadata ?? {};
|
|
28493
|
+
const cancelled = Boolean(meta["cancelled"]);
|
|
28494
|
+
const displayStatus = cancelled ? "cancelled" : n.status === "pending" && deps.length > 0 && allDepsDone ? "queued" : n.status;
|
|
28495
|
+
return {
|
|
28496
|
+
id: n.id,
|
|
28497
|
+
shortId: shortId.get(n.id) ?? n.id.slice(0, 6),
|
|
28498
|
+
title: n.title,
|
|
28499
|
+
description: n.description,
|
|
28500
|
+
status: n.status,
|
|
28501
|
+
displayStatus,
|
|
28502
|
+
priority: n.priority,
|
|
28503
|
+
type: n.type,
|
|
28504
|
+
deps: deps.map((b) => shortId.get(b) ?? b.slice(0, 6)),
|
|
28505
|
+
agentName: n.assignee,
|
|
28506
|
+
worktreeBranch: typeof meta["worktreeBranch"] === "string" ? meta["worktreeBranch"] : void 0,
|
|
28507
|
+
startedAt: n.startedAt,
|
|
28508
|
+
completedAt: n.completedAt,
|
|
28509
|
+
retries: typeof meta["retries"] === "number" ? meta["retries"] : 0,
|
|
28510
|
+
model: typeof meta["model"] === "string" ? meta["model"] : void 0,
|
|
28511
|
+
provider: typeof meta["provider"] === "string" ? meta["provider"] : void 0,
|
|
28512
|
+
fallbackModels: Array.isArray(meta["fallbackModels"]) ? meta["fallbackModels"] : void 0,
|
|
28513
|
+
verificationCommand: typeof meta["verificationCommand"] === "string" ? meta["verificationCommand"] : void 0
|
|
28514
|
+
};
|
|
28515
|
+
};
|
|
28516
|
+
const tasks = nodes.map(toTask);
|
|
28517
|
+
const byDepth = /* @__PURE__ */ new Map();
|
|
28518
|
+
for (const n of nodes) {
|
|
28519
|
+
const d = depthOf(n.id);
|
|
28520
|
+
if (!byDepth.has(d)) byDepth.set(d, []);
|
|
28521
|
+
byDepth.get(d)?.push(shortId.get(n.id) ?? n.id.slice(0, 6));
|
|
28522
|
+
}
|
|
28523
|
+
const columns = [...byDepth.keys()].sort((a, b) => a - b).map((d) => ({ label: d === 0 ? "Start" : `Phase ${d}`, taskIds: byDepth.get(d) ?? [] }));
|
|
28524
|
+
return { tasks, columns };
|
|
28525
|
+
}
|
|
28526
|
+
function buildBoardSnapshot(graph, run, now) {
|
|
28527
|
+
const { tasks, columns } = buildBoardTasks(graph);
|
|
28528
|
+
return {
|
|
28529
|
+
runId: run.runId,
|
|
28530
|
+
specId: run.specId,
|
|
28531
|
+
graphId: graph.id,
|
|
28532
|
+
title: graph.title,
|
|
28533
|
+
status: run.status,
|
|
28534
|
+
startedAt: run.startedAt,
|
|
28535
|
+
updatedAt: now,
|
|
28536
|
+
progress: computeTaskProgress(graph),
|
|
28537
|
+
wave: run.wave,
|
|
28538
|
+
tasks,
|
|
28539
|
+
columns,
|
|
28540
|
+
diagnostics: run.deadlockChains?.length ? { deadlockChains: run.deadlockChains } : void 0,
|
|
28541
|
+
defaultModel: run.defaultModel,
|
|
28542
|
+
defaultProvider: run.defaultProvider,
|
|
28543
|
+
fallbackModels: run.fallbackModels,
|
|
28544
|
+
baseBranch: run.baseBranch,
|
|
28545
|
+
mergedCommits: run.mergedCommits?.length ? run.mergedCommits : void 0
|
|
28546
|
+
};
|
|
28547
|
+
}
|
|
28548
|
+
|
|
28549
|
+
// src/sdd/sdd-board-store.ts
|
|
28550
|
+
init_atomic_write();
|
|
28551
|
+
var SddBoardStore = class {
|
|
28552
|
+
baseDir;
|
|
28553
|
+
indexPath;
|
|
28554
|
+
constructor(opts) {
|
|
28555
|
+
this.baseDir = opts.baseDir;
|
|
28556
|
+
this.indexPath = path3.join(this.baseDir, "_index.json");
|
|
28557
|
+
}
|
|
28558
|
+
snapshotPath(runId) {
|
|
28559
|
+
return path3.join(this.baseDir, `${this.safe(runId)}.json`);
|
|
28560
|
+
}
|
|
28561
|
+
eventsPath(runId) {
|
|
28562
|
+
return path3.join(this.baseDir, `${this.safe(runId)}.events.jsonl`);
|
|
28563
|
+
}
|
|
28564
|
+
controlPath(runId) {
|
|
28565
|
+
return path3.join(this.baseDir, `${this.safe(runId)}.control.jsonl`);
|
|
28566
|
+
}
|
|
28567
|
+
async saveSnapshot(snapshot) {
|
|
28568
|
+
await ensureDir(this.baseDir);
|
|
28569
|
+
await atomicWrite(this.snapshotPath(snapshot.runId), JSON.stringify(snapshot, null, 2), {
|
|
28570
|
+
mode: 384
|
|
28571
|
+
});
|
|
28572
|
+
await this.updateIndex(snapshot);
|
|
28573
|
+
}
|
|
28574
|
+
async load(runId) {
|
|
28575
|
+
try {
|
|
28576
|
+
const raw = await fsp3.readFile(this.snapshotPath(runId), "utf8");
|
|
28577
|
+
return JSON.parse(raw);
|
|
28578
|
+
} catch {
|
|
28579
|
+
return null;
|
|
28580
|
+
}
|
|
28581
|
+
}
|
|
28582
|
+
async list() {
|
|
28583
|
+
const index = await this.readIndex();
|
|
28584
|
+
return index.entries.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
28585
|
+
}
|
|
28586
|
+
async loadLatestForSpec(specId) {
|
|
28587
|
+
const entry = (await this.list()).find((e) => e.specId === specId);
|
|
28588
|
+
return entry ? this.load(entry.runId) : null;
|
|
28589
|
+
}
|
|
28590
|
+
/** Append one line to the board's JSONL event log (best-effort, never throws). */
|
|
28591
|
+
async appendEvent(runId, event) {
|
|
28592
|
+
try {
|
|
28593
|
+
await ensureDir(this.baseDir);
|
|
28594
|
+
await fsp3.appendFile(this.eventsPath(runId), `${JSON.stringify(event)}
|
|
28595
|
+
`, { mode: 384 });
|
|
28596
|
+
} catch {
|
|
28597
|
+
}
|
|
28598
|
+
}
|
|
28599
|
+
/** Append a control command (used by readers to steer a CLI-owned run). */
|
|
28600
|
+
async appendControl(runId, command) {
|
|
28601
|
+
await ensureDir(this.baseDir);
|
|
28602
|
+
await fsp3.appendFile(this.controlPath(runId), `${JSON.stringify(command)}
|
|
28603
|
+
`, { mode: 384 });
|
|
28604
|
+
}
|
|
28605
|
+
/** Read + truncate the control queue (the run drains it). Returns parsed commands. */
|
|
28606
|
+
async drainControl(runId) {
|
|
28607
|
+
const p = this.controlPath(runId);
|
|
28608
|
+
let raw;
|
|
28609
|
+
try {
|
|
28610
|
+
raw = await fsp3.readFile(p, "utf8");
|
|
28611
|
+
} catch {
|
|
28612
|
+
return [];
|
|
28613
|
+
}
|
|
28614
|
+
try {
|
|
28615
|
+
await fsp3.writeFile(p, "", { mode: 384 });
|
|
28616
|
+
} catch {
|
|
28617
|
+
}
|
|
28618
|
+
return raw.split("\n").filter((l) => l.trim()).map((l) => {
|
|
28619
|
+
try {
|
|
28620
|
+
return JSON.parse(l);
|
|
28621
|
+
} catch {
|
|
28622
|
+
return null;
|
|
28623
|
+
}
|
|
28624
|
+
}).filter((c) => c !== null);
|
|
28625
|
+
}
|
|
28626
|
+
async delete(runId) {
|
|
28627
|
+
await Promise.allSettled([
|
|
28628
|
+
fsp3.unlink(this.snapshotPath(runId)),
|
|
28629
|
+
fsp3.unlink(this.eventsPath(runId)),
|
|
28630
|
+
fsp3.unlink(this.controlPath(runId))
|
|
28631
|
+
]);
|
|
28632
|
+
await this.removeFromIndex(runId);
|
|
28633
|
+
}
|
|
28634
|
+
// ── internal ────────────────────────────────────────────────────────────
|
|
28635
|
+
safe(runId) {
|
|
28636
|
+
return runId.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
28637
|
+
}
|
|
28638
|
+
async readIndex() {
|
|
28639
|
+
try {
|
|
28640
|
+
const raw = await fsp3.readFile(this.indexPath, "utf8");
|
|
28641
|
+
const parsed = JSON.parse(raw);
|
|
28642
|
+
if (parsed?.version === 1) return parsed;
|
|
28643
|
+
} catch {
|
|
28644
|
+
}
|
|
28645
|
+
return { version: 1, entries: [] };
|
|
28646
|
+
}
|
|
28647
|
+
async updateIndex(snapshot) {
|
|
28648
|
+
const index = await this.readIndex();
|
|
28649
|
+
const entry = {
|
|
28650
|
+
runId: snapshot.runId,
|
|
28651
|
+
specId: snapshot.specId,
|
|
28652
|
+
title: snapshot.title,
|
|
28653
|
+
status: snapshot.status,
|
|
28654
|
+
total: snapshot.progress.total,
|
|
28655
|
+
completed: snapshot.progress.completed,
|
|
28656
|
+
updatedAt: snapshot.updatedAt
|
|
28657
|
+
};
|
|
28658
|
+
const idx = index.entries.findIndex((e) => e.runId === snapshot.runId);
|
|
28659
|
+
if (idx >= 0) index.entries[idx] = entry;
|
|
28660
|
+
else index.entries.push(entry);
|
|
28661
|
+
await atomicWrite(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
|
|
28662
|
+
}
|
|
28663
|
+
async removeFromIndex(runId) {
|
|
28664
|
+
const index = await this.readIndex();
|
|
28665
|
+
index.entries = index.entries.filter((e) => e.runId !== runId);
|
|
28666
|
+
await atomicWrite(this.indexPath, JSON.stringify(index, null, 2), { mode: 384 });
|
|
28667
|
+
}
|
|
28668
|
+
};
|
|
28669
|
+
|
|
28670
|
+
// src/sdd/sdd-board-projector.ts
|
|
28671
|
+
var SddBoardProjector = class _SddBoardProjector {
|
|
28672
|
+
o;
|
|
28673
|
+
now;
|
|
28674
|
+
throttleMs;
|
|
28675
|
+
shortId;
|
|
28676
|
+
status = "idle";
|
|
28677
|
+
wave = 0;
|
|
28678
|
+
startedAt;
|
|
28679
|
+
deadlockChains = [];
|
|
28680
|
+
/** Live activity feed, most recent first (capped). */
|
|
28681
|
+
feed = [];
|
|
28682
|
+
static FEED_CAP = 60;
|
|
28683
|
+
finished = false;
|
|
28684
|
+
runDeadlocked = false;
|
|
28685
|
+
runStopped = false;
|
|
28686
|
+
/** Squash commits the run landed on the base branch (for post-run rollback). */
|
|
28687
|
+
mergedCommits = [];
|
|
28688
|
+
/** Base branch reported by the run at start (overrides the constructor option). */
|
|
28689
|
+
runBaseBranch;
|
|
28690
|
+
dirty = false;
|
|
28691
|
+
timer = null;
|
|
28692
|
+
unsubs = [];
|
|
28693
|
+
/** Tail of in-flight persistence, so callers can await a settled state. */
|
|
28694
|
+
lastSave = Promise.resolve();
|
|
28695
|
+
constructor(opts) {
|
|
28696
|
+
this.o = opts;
|
|
28697
|
+
this.now = opts.now ?? Date.now;
|
|
28698
|
+
this.throttleMs = opts.throttleMs ?? 250;
|
|
28699
|
+
this.shortId = shortIdMap(opts.graph);
|
|
28700
|
+
this.startedAt = this.now();
|
|
28701
|
+
this.unsubs.push(opts.tracker.subscribe(() => this.markDirty()));
|
|
28702
|
+
this.onRun("sdd.run.started", (e) => {
|
|
28703
|
+
this.status = "running";
|
|
28704
|
+
this.startedAt = this.now();
|
|
28705
|
+
if (e.baseBranch) this.runBaseBranch = e.baseBranch;
|
|
28706
|
+
this.markDirty();
|
|
28707
|
+
});
|
|
28708
|
+
this.onRun("sdd.run.finished", (e) => {
|
|
28709
|
+
this.finished = true;
|
|
28710
|
+
this.runDeadlocked = e.deadlocked;
|
|
28711
|
+
this.runStopped = e.stopped;
|
|
28712
|
+
this.flush();
|
|
28713
|
+
});
|
|
28714
|
+
this.onRun("sdd.wave", (e) => {
|
|
28715
|
+
this.wave = e.wave;
|
|
28716
|
+
this.pushFeed({ ts: this.now(), kind: "wave", text: `Wave ${e.wave + 1} started \xB7 ${e.batchSize} task(s) in parallel` });
|
|
28717
|
+
this.markDirty();
|
|
28718
|
+
});
|
|
28719
|
+
this.onRun("sdd.deadlock", (e) => {
|
|
28720
|
+
this.deadlockChains = e.chains.map((c) => ({
|
|
28721
|
+
blocked: this.shortId.get(c.blocked) ?? c.blocked.slice(0, 6),
|
|
28722
|
+
blockedBy: c.blockedBy.map((b) => this.shortId.get(b) ?? b.slice(0, 6))
|
|
28723
|
+
}));
|
|
28724
|
+
this.pushFeed({ ts: this.now(), kind: "deadlock", text: `Deadlock \u2014 ${e.chains.length} task(s) blocked by failed work` });
|
|
28725
|
+
this.markDirty();
|
|
28726
|
+
});
|
|
28727
|
+
this.onRun("sdd.task.started", (e) => {
|
|
28728
|
+
const sid = this.shortId.get(e.taskId);
|
|
28729
|
+
this.pushFeed({
|
|
28730
|
+
ts: this.now(),
|
|
28731
|
+
kind: "started",
|
|
28732
|
+
taskShortId: sid,
|
|
28733
|
+
agentName: e.agentName,
|
|
28734
|
+
text: `${e.agentName || "a worker"} picked up ${sid ?? "a task"}${this.titleOf(e.taskId)}`
|
|
28735
|
+
});
|
|
28736
|
+
this.markDirty();
|
|
28737
|
+
});
|
|
28738
|
+
this.onRun("sdd.task.completed", (e) => {
|
|
28739
|
+
const sid = this.shortId.get(e.taskId);
|
|
28740
|
+
const agent = this.assigneeOf(e.taskId);
|
|
28741
|
+
this.pushFeed({
|
|
28742
|
+
ts: this.now(),
|
|
28743
|
+
kind: "completed",
|
|
28744
|
+
taskShortId: sid,
|
|
28745
|
+
agentName: agent,
|
|
28746
|
+
text: `${sid ?? "task"}${this.titleOf(e.taskId)} completed${agent ? ` by ${agent}` : ""} \xB7 ${(e.durationMs / 1e3).toFixed(1)}s`
|
|
28747
|
+
});
|
|
28748
|
+
this.markDirty();
|
|
28749
|
+
});
|
|
28750
|
+
this.onRun("sdd.task.failed", (e) => {
|
|
28751
|
+
const sid = this.shortId.get(e.taskId);
|
|
28752
|
+
this.pushFeed({
|
|
28753
|
+
ts: this.now(),
|
|
28754
|
+
kind: "failed",
|
|
28755
|
+
taskShortId: sid,
|
|
28756
|
+
agentName: this.assigneeOf(e.taskId),
|
|
28757
|
+
text: `${sid ?? "task"}${this.titleOf(e.taskId)} failed \u2014 ${e.error}`
|
|
28758
|
+
});
|
|
28759
|
+
this.markDirty();
|
|
28760
|
+
});
|
|
28761
|
+
this.onRun("sdd.task.retrying", (e) => {
|
|
28762
|
+
const sid = this.shortId.get(e.taskId);
|
|
28763
|
+
this.pushFeed({
|
|
28764
|
+
ts: this.now(),
|
|
28765
|
+
kind: "retrying",
|
|
28766
|
+
taskShortId: sid,
|
|
28767
|
+
text: `${sid ?? "task"}${this.titleOf(e.taskId)} retrying (${e.attempt}/${e.maxRetries})`
|
|
28768
|
+
});
|
|
28769
|
+
this.markDirty();
|
|
28770
|
+
});
|
|
28771
|
+
this.onRun("sdd.task.verification_failed", (e) => {
|
|
28772
|
+
const sid = this.shortId.get(e.taskId);
|
|
28773
|
+
this.pushFeed({
|
|
28774
|
+
ts: this.now(),
|
|
28775
|
+
kind: "verification_failed",
|
|
28776
|
+
taskShortId: sid,
|
|
28777
|
+
agentName: this.assigneeOf(e.taskId),
|
|
28778
|
+
text: `${sid ?? "task"}${this.titleOf(e.taskId)} failed verification \u2014 ${e.reason}`
|
|
28779
|
+
});
|
|
28780
|
+
this.markDirty();
|
|
28781
|
+
});
|
|
28782
|
+
this.onRun("sdd.task.conflict", (e) => {
|
|
28783
|
+
const sid = this.shortId.get(e.taskId);
|
|
28784
|
+
const files = e.conflictFiles.length;
|
|
28785
|
+
this.pushFeed({
|
|
28786
|
+
ts: this.now(),
|
|
28787
|
+
kind: "conflict",
|
|
28788
|
+
taskShortId: sid,
|
|
28789
|
+
agentName: this.assigneeOf(e.taskId),
|
|
28790
|
+
text: `${sid ?? "task"}${this.titleOf(e.taskId)} merge conflict \u2014 ${files} file(s)${files ? `: ${e.conflictFiles.slice(0, 3).join(", ")}${files > 3 ? "\u2026" : ""}` : ""}`
|
|
28791
|
+
});
|
|
28792
|
+
this.markDirty();
|
|
28793
|
+
});
|
|
28794
|
+
this.onRun("sdd.task.merged", (e) => {
|
|
28795
|
+
const title = this.o.graph.nodes.get(e.taskId)?.title ?? "";
|
|
28796
|
+
this.mergedCommits.push({ taskId: e.taskId, sha: e.sha, title });
|
|
28797
|
+
const sid = this.shortId.get(e.taskId);
|
|
28798
|
+
this.pushFeed({
|
|
28799
|
+
ts: this.now(),
|
|
28800
|
+
kind: "completed",
|
|
28801
|
+
taskShortId: sid,
|
|
28802
|
+
text: `${sid ?? "task"}${this.titleOf(e.taskId)} merged \u2192 ${this.runBaseBranch ?? this.o.baseBranch ?? "base"} (${e.sha.slice(0, 8)})`
|
|
28803
|
+
});
|
|
28804
|
+
this.markDirty();
|
|
28805
|
+
});
|
|
28806
|
+
this.onRun("sdd.task.split", (e) => {
|
|
28807
|
+
const sid = this.shortId.get(e.taskId);
|
|
28808
|
+
this.pushFeed({
|
|
28809
|
+
ts: this.now(),
|
|
28810
|
+
kind: "split",
|
|
28811
|
+
taskShortId: sid,
|
|
28812
|
+
text: `${sid ?? "task"}${this.titleOf(e.taskId)} split into ${e.subtaskIds.length} sub-task(s)`
|
|
28813
|
+
});
|
|
28814
|
+
this.markDirty();
|
|
28815
|
+
});
|
|
28816
|
+
this.onRun("sdd.supervisor.decision", (e) => {
|
|
28817
|
+
const sid = this.shortId.get(e.taskId);
|
|
28818
|
+
this.pushFeed({
|
|
28819
|
+
ts: this.now(),
|
|
28820
|
+
kind: "supervisor",
|
|
28821
|
+
taskShortId: sid,
|
|
28822
|
+
text: `supervisor \u2192 ${e.action} for ${sid ?? "task"}${this.titleOf(e.taskId)}${e.rationale ? ` (${e.rationale})` : ""}`
|
|
28823
|
+
});
|
|
28824
|
+
this.markDirty();
|
|
28825
|
+
});
|
|
28826
|
+
}
|
|
28827
|
+
pushFeed(entry) {
|
|
28828
|
+
this.feed.unshift(entry);
|
|
28829
|
+
if (this.feed.length > _SddBoardProjector.FEED_CAP) this.feed.length = _SddBoardProjector.FEED_CAP;
|
|
28830
|
+
}
|
|
28831
|
+
/** ` (title…)` suffix for a feed line, or '' when the node/title is missing. */
|
|
28832
|
+
titleOf(taskId) {
|
|
28833
|
+
const t2 = this.o.graph.nodes.get(taskId)?.title;
|
|
28834
|
+
if (!t2) return "";
|
|
28835
|
+
return ` (${t2.length > 40 ? `${t2.slice(0, 39)}\u2026` : t2})`;
|
|
28836
|
+
}
|
|
28837
|
+
assigneeOf(taskId) {
|
|
28838
|
+
return this.o.graph.nodes.get(taskId)?.assignee;
|
|
28839
|
+
}
|
|
28840
|
+
/** Latest snapshot, built on demand (e.g. for a late-joining client). */
|
|
28841
|
+
snapshot() {
|
|
28842
|
+
return this.build();
|
|
28843
|
+
}
|
|
28844
|
+
/** Resolve once all in-flight snapshot persistence has settled. */
|
|
28845
|
+
async drain() {
|
|
28846
|
+
await this.lastSave;
|
|
28847
|
+
}
|
|
28848
|
+
/** Stop projecting and release subscriptions. */
|
|
28849
|
+
dispose() {
|
|
28850
|
+
if (this.timer) {
|
|
28851
|
+
clearTimeout(this.timer);
|
|
28852
|
+
this.timer = null;
|
|
28853
|
+
}
|
|
28854
|
+
for (const u of this.unsubs) u();
|
|
28855
|
+
this.unsubs.length = 0;
|
|
28856
|
+
}
|
|
28857
|
+
// ── internal ────────────────────────────────────────────────────────────
|
|
28858
|
+
/** Subscribe to a run event scoped to this run id; also append to JSONL. */
|
|
28859
|
+
onRun(event, handler) {
|
|
28860
|
+
const wrapped = (e) => {
|
|
28861
|
+
if (e.runId !== this.o.runId) return;
|
|
28862
|
+
void this.o.store?.appendEvent(this.o.runId, { ts: this.now(), type: event, payload: e });
|
|
28863
|
+
handler(e);
|
|
28864
|
+
};
|
|
28865
|
+
const off = this.o.events.on(event, wrapped);
|
|
28866
|
+
this.unsubs.push(off);
|
|
28867
|
+
}
|
|
28868
|
+
resolveStatus(completed, total) {
|
|
28869
|
+
if (!this.finished) return this.status;
|
|
28870
|
+
if (this.runDeadlocked) return "deadlocked";
|
|
28871
|
+
if (total > 0 && completed >= total) return "completed";
|
|
28872
|
+
if (this.runStopped) return "paused";
|
|
28873
|
+
return "failed";
|
|
28874
|
+
}
|
|
28875
|
+
build() {
|
|
28876
|
+
const snap = buildBoardSnapshot(
|
|
28877
|
+
this.o.graph,
|
|
28878
|
+
{
|
|
28879
|
+
runId: this.o.runId,
|
|
28880
|
+
specId: this.o.specId,
|
|
28881
|
+
status: "running",
|
|
28882
|
+
startedAt: this.startedAt,
|
|
28883
|
+
wave: this.wave,
|
|
28884
|
+
deadlockChains: this.deadlockChains,
|
|
28885
|
+
defaultModel: this.o.defaultModel,
|
|
28886
|
+
defaultProvider: this.o.defaultProvider,
|
|
28887
|
+
fallbackModels: this.o.fallbackModels,
|
|
28888
|
+
baseBranch: this.runBaseBranch ?? this.o.baseBranch,
|
|
28889
|
+
mergedCommits: this.mergedCommits
|
|
28890
|
+
},
|
|
28891
|
+
this.now()
|
|
28892
|
+
);
|
|
28893
|
+
snap.status = this.resolveStatus(snap.progress.completed, snap.progress.total);
|
|
28894
|
+
snap.feed = this.feed.slice(0, _SddBoardProjector.FEED_CAP);
|
|
28895
|
+
return snap;
|
|
28896
|
+
}
|
|
28897
|
+
markDirty() {
|
|
28898
|
+
this.dirty = true;
|
|
28899
|
+
if (this.timer || this.finished) return;
|
|
28900
|
+
this.timer = setTimeout(() => {
|
|
28901
|
+
this.timer = null;
|
|
28902
|
+
if (this.dirty) this.flush();
|
|
28903
|
+
}, this.throttleMs);
|
|
28904
|
+
}
|
|
28905
|
+
flush() {
|
|
28906
|
+
this.dirty = false;
|
|
28907
|
+
if (this.timer) {
|
|
28908
|
+
clearTimeout(this.timer);
|
|
28909
|
+
this.timer = null;
|
|
28910
|
+
}
|
|
28911
|
+
const snap = this.build();
|
|
28912
|
+
this.o.events.emit("sdd.board.snapshot", { runId: this.o.runId, snapshot: snap });
|
|
28913
|
+
if (this.o.store) {
|
|
28914
|
+
const store = this.o.store;
|
|
28915
|
+
this.lastSave = this.lastSave.then(() => store.saveSnapshot(snap)).catch(() => {
|
|
28916
|
+
});
|
|
28917
|
+
}
|
|
28918
|
+
}
|
|
28919
|
+
};
|
|
28920
|
+
|
|
28921
|
+
// src/sdd/sdd-run-registry.ts
|
|
28922
|
+
var SddRunRegistry = class {
|
|
28923
|
+
current = null;
|
|
28924
|
+
register(control) {
|
|
28925
|
+
this.current = control;
|
|
28926
|
+
}
|
|
28927
|
+
clear(runId) {
|
|
28928
|
+
if (this.current?.runId === runId) this.current = null;
|
|
28929
|
+
}
|
|
28930
|
+
getActive() {
|
|
28931
|
+
return this.current;
|
|
28932
|
+
}
|
|
28933
|
+
};
|
|
28934
|
+
|
|
28935
|
+
// src/sdd/sdd-interview-driver.ts
|
|
28936
|
+
var SddInterviewDriver = class {
|
|
28937
|
+
builder;
|
|
28938
|
+
o;
|
|
28939
|
+
minQuestions;
|
|
28940
|
+
maxQuestions;
|
|
28941
|
+
tracker = null;
|
|
28942
|
+
graph = null;
|
|
28943
|
+
constructor(opts) {
|
|
28944
|
+
this.o = opts;
|
|
28945
|
+
this.minQuestions = opts.minQuestions ?? 2;
|
|
28946
|
+
this.maxQuestions = opts.maxQuestions ?? 10;
|
|
28947
|
+
this.builder = new AISpecBuilder({
|
|
28948
|
+
store: opts.specStore,
|
|
28949
|
+
sessionPath: opts.sessionPath,
|
|
28950
|
+
projectContext: opts.projectContext,
|
|
28951
|
+
minQuestions: this.minQuestions,
|
|
28952
|
+
maxQuestions: this.maxQuestions
|
|
28953
|
+
});
|
|
28954
|
+
}
|
|
28955
|
+
/** Begin a fresh interview. Returns the first AI prompt (a question kickoff). */
|
|
28956
|
+
start(title, intent) {
|
|
28957
|
+
this.builder.startSession(title, intent);
|
|
28958
|
+
this.tracker = null;
|
|
28959
|
+
this.graph = null;
|
|
28960
|
+
return this.builder.getAIPrompt();
|
|
28961
|
+
}
|
|
28962
|
+
/**
|
|
28963
|
+
* Resume a previously-persisted interview from disk. Re-hydrates the task
|
|
28964
|
+
* graph too when one was already produced. Returns true if a session loaded.
|
|
28965
|
+
*/
|
|
28966
|
+
async loadExisting() {
|
|
28967
|
+
const loaded = await this.builder.loadSession();
|
|
28968
|
+
if (!loaded) return false;
|
|
28969
|
+
const graphId = this.builder.getTaskGraphId();
|
|
28970
|
+
if (graphId) {
|
|
28971
|
+
const graph = await this.o.graphStore.load(graphId);
|
|
28972
|
+
if (graph) {
|
|
28973
|
+
this.graph = graph;
|
|
28974
|
+
const tracker = new TaskTracker({ store: new DefaultTaskStore() });
|
|
28975
|
+
tracker.setGraph(graph);
|
|
28976
|
+
this.tracker = tracker;
|
|
28977
|
+
}
|
|
28978
|
+
}
|
|
28979
|
+
return true;
|
|
28980
|
+
}
|
|
28981
|
+
phase() {
|
|
28982
|
+
return this.builder.getPhase();
|
|
28983
|
+
}
|
|
28984
|
+
currentPrompt() {
|
|
28985
|
+
return this.builder.getAIPrompt();
|
|
28986
|
+
}
|
|
28987
|
+
getTracker() {
|
|
28988
|
+
return this.tracker;
|
|
28989
|
+
}
|
|
28990
|
+
getGraph() {
|
|
28991
|
+
return this.graph;
|
|
28992
|
+
}
|
|
28993
|
+
/** Record a Q/A pair (the agent asked `question`, the user replied `answer`). */
|
|
28994
|
+
submitAnswer(question, answer) {
|
|
28995
|
+
this.builder.addAnswer(question, answer);
|
|
28996
|
+
}
|
|
28997
|
+
/**
|
|
28998
|
+
* Feed the agent's text output back into the interview. Detects, in order:
|
|
28999
|
+
* 1. a Specification JSON → setSpec (phase → spec_review) + persist to SpecStore
|
|
29000
|
+
* 2. an implementation plan (implementation phase) → setImplementation
|
|
29001
|
+
* 3. a task JSON array → build + persist a TaskGraph
|
|
29002
|
+
* Each step is independent and best-effort; a malformed payload is ignored
|
|
29003
|
+
* rather than thrown, so a chatty agent turn never breaks the interview.
|
|
29004
|
+
*/
|
|
29005
|
+
async ingestAgentOutput(text) {
|
|
29006
|
+
const result = {
|
|
29007
|
+
specDetected: false,
|
|
29008
|
+
implementationDetected: false,
|
|
29009
|
+
tasksDetected: false
|
|
29010
|
+
};
|
|
29011
|
+
if (!this.builder.getSession().spec) {
|
|
29012
|
+
const spec = this.builder.tryParseSpecFromOutput(text);
|
|
29013
|
+
if (spec) {
|
|
29014
|
+
this.builder.setSpec(spec);
|
|
29015
|
+
await this.persistSpec(spec);
|
|
29016
|
+
result.specDetected = true;
|
|
29017
|
+
}
|
|
29018
|
+
}
|
|
29019
|
+
if (this.builder.getPhase() === "implementation") {
|
|
29020
|
+
if (this.trySaveImplementationPlan(text)) result.implementationDetected = true;
|
|
29021
|
+
}
|
|
29022
|
+
const session = this.builder.getSession();
|
|
29023
|
+
if (session.spec) {
|
|
29024
|
+
const built = await this.tryBuildTasksFromOutput(text);
|
|
29025
|
+
if (built) {
|
|
29026
|
+
result.tasksDetected = true;
|
|
29027
|
+
result.graphId = built;
|
|
29028
|
+
}
|
|
29029
|
+
}
|
|
29030
|
+
return result;
|
|
29031
|
+
}
|
|
29032
|
+
/**
|
|
29033
|
+
* Advance to the next phase (mirrors `/sdd approve`). When moving into the
|
|
29034
|
+
* executing phase, guarantees a task graph exists — deterministically
|
|
29035
|
+
* generating one from the approved spec if the agent never emitted a valid
|
|
29036
|
+
* task array. Returns the new phase and its AI prompt.
|
|
29037
|
+
*/
|
|
29038
|
+
async approve() {
|
|
29039
|
+
const phase = this.builder.approve();
|
|
29040
|
+
if (phase === "executing") {
|
|
29041
|
+
await this.ensureTaskGraph();
|
|
29042
|
+
}
|
|
29043
|
+
return { phase, prompt: this.builder.getAIPrompt() };
|
|
29044
|
+
}
|
|
29045
|
+
/**
|
|
29046
|
+
* Ensure a TaskGraph exists for the approved spec. If the agent already
|
|
29047
|
+
* produced one (via `ingestAgentOutput`), returns it; otherwise builds a
|
|
29048
|
+
* deterministic graph from the spec's requirements via TaskGenerator. This is
|
|
29049
|
+
* the robustness backstop: a run can always start, even if the model never
|
|
29050
|
+
* emitted a parseable task array.
|
|
29051
|
+
*/
|
|
29052
|
+
async ensureTaskGraph() {
|
|
29053
|
+
if (this.graph) return this.graph;
|
|
29054
|
+
const spec = this.builder.getSession().spec;
|
|
29055
|
+
if (!spec) return null;
|
|
29056
|
+
const tracker = new TaskTracker({ store: new DefaultTaskStore() });
|
|
29057
|
+
const generator = new TaskGenerator({
|
|
29058
|
+
taskTracker: tracker,
|
|
29059
|
+
verificationFromAcceptance: process.env["WRONGSTACK_SDD_VERIFY_FROM_ACCEPTANCE"] === "1"
|
|
29060
|
+
});
|
|
29061
|
+
const graph = await generator.generateFromSpec(spec);
|
|
29062
|
+
this.tracker = tracker;
|
|
29063
|
+
this.graph = graph;
|
|
29064
|
+
await this.persistGraph(graph);
|
|
29065
|
+
this.builder.setTaskGraphId(graph.id);
|
|
29066
|
+
await this.builder.saveSession();
|
|
29067
|
+
return graph;
|
|
29068
|
+
}
|
|
29069
|
+
snapshot() {
|
|
29070
|
+
const s = this.builder.getSession();
|
|
29071
|
+
const spec = s.spec;
|
|
29072
|
+
return {
|
|
29073
|
+
sessionId: s.id,
|
|
29074
|
+
phase: s.phase,
|
|
29075
|
+
title: s.title,
|
|
29076
|
+
questionCount: s.questionCount,
|
|
29077
|
+
minQuestions: this.minQuestions,
|
|
29078
|
+
maxQuestions: this.maxQuestions,
|
|
29079
|
+
answers: s.answers.map((a) => ({ question: a.question, answer: a.answer })),
|
|
29080
|
+
spec: spec ? {
|
|
29081
|
+
id: spec.id,
|
|
29082
|
+
title: spec.title,
|
|
29083
|
+
overview: spec.overview,
|
|
29084
|
+
requirements: spec.requirements.map((r) => ({
|
|
29085
|
+
priority: r.priority,
|
|
29086
|
+
description: r.description
|
|
29087
|
+
}))
|
|
29088
|
+
} : void 0,
|
|
29089
|
+
graphId: s.taskGraphId,
|
|
29090
|
+
taskCount: this.graph ? this.graph.nodes.size : 0,
|
|
29091
|
+
board: this.graph ? buildBoardTasks(this.graph) : void 0,
|
|
29092
|
+
prompt: this.builder.getAIPrompt()
|
|
29093
|
+
};
|
|
29094
|
+
}
|
|
29095
|
+
// ── internals ────────────────────────────────────────────────────────────
|
|
29096
|
+
async persistSpec(spec) {
|
|
29097
|
+
try {
|
|
29098
|
+
await this.o.specStore.save(spec);
|
|
29099
|
+
} catch {
|
|
29100
|
+
}
|
|
29101
|
+
}
|
|
29102
|
+
async persistGraph(graph) {
|
|
29103
|
+
try {
|
|
29104
|
+
await this.o.graphStore.save(graph);
|
|
29105
|
+
} catch {
|
|
29106
|
+
}
|
|
29107
|
+
}
|
|
29108
|
+
/**
|
|
29109
|
+
* Port of the CLI `trySaveImplementationPlan` operating on this driver's
|
|
29110
|
+
* builder. Captures the prose plan that precedes the task JSON block.
|
|
29111
|
+
*/
|
|
29112
|
+
trySaveImplementationPlan(text) {
|
|
29113
|
+
const current = this.builder.getSession().implementation ?? "";
|
|
29114
|
+
const jsonStart = text.match(/```json\s*\[/);
|
|
29115
|
+
if (jsonStart?.index && jsonStart.index > 0) {
|
|
29116
|
+
const plan = text.substring(0, jsonStart.index).trim();
|
|
29117
|
+
if (plan.length > 50 && plan !== current && !isExplanatoryText(plan)) {
|
|
29118
|
+
this.builder.setImplementation(plan);
|
|
29119
|
+
return true;
|
|
29120
|
+
}
|
|
29121
|
+
}
|
|
29122
|
+
if (text.length > 100 && !text.includes("```json") && text.trim() !== current && !isExplanatoryText(text)) {
|
|
29123
|
+
this.builder.setImplementation(text.trim());
|
|
29124
|
+
return true;
|
|
29125
|
+
}
|
|
29126
|
+
return false;
|
|
29127
|
+
}
|
|
29128
|
+
/**
|
|
29129
|
+
* Port of the CLI `trySaveTasksFromAIOutput`: parse a task JSON array from the
|
|
29130
|
+
* agent output, build (or extend) the tracker + graph, persist to disk, and
|
|
29131
|
+
* link the graphId to the session. Returns the graphId on success.
|
|
29132
|
+
*/
|
|
29133
|
+
async tryBuildTasksFromOutput(text) {
|
|
29134
|
+
const json = this.builder.extractJSONArray(text);
|
|
29135
|
+
if (!json) return void 0;
|
|
29136
|
+
let tasks;
|
|
29137
|
+
try {
|
|
29138
|
+
tasks = JSON.parse(json);
|
|
29139
|
+
} catch {
|
|
29140
|
+
return void 0;
|
|
29141
|
+
}
|
|
29142
|
+
const valid = tasks.filter(
|
|
29143
|
+
(t2) => t2 && typeof t2 === "object" && typeof t2.title === "string" && t2.title.length > 0
|
|
29144
|
+
);
|
|
29145
|
+
if (valid.length === 0) return void 0;
|
|
29146
|
+
const spec = this.builder.getSession().spec;
|
|
29147
|
+
if (!spec) return void 0;
|
|
29148
|
+
if (!this.tracker || !this.graph) {
|
|
29149
|
+
const tracker = new TaskTracker({ store: new DefaultTaskStore() });
|
|
29150
|
+
this.graph = await tracker.createGraph(spec.id, spec.title);
|
|
29151
|
+
this.tracker = tracker;
|
|
29152
|
+
}
|
|
29153
|
+
const refMap = /* @__PURE__ */ new Map();
|
|
29154
|
+
const created = [];
|
|
29155
|
+
valid.forEach((task, i) => {
|
|
29156
|
+
const node = addTaskToTracker(this.tracker, task);
|
|
29157
|
+
created.push({ nodeId: node.id, task });
|
|
29158
|
+
if (typeof task.id === "string" && task.id.trim()) {
|
|
29159
|
+
refMap.set(task.id.trim().toLowerCase(), node.id);
|
|
29160
|
+
}
|
|
29161
|
+
refMap.set(`t${i + 1}`, node.id);
|
|
29162
|
+
refMap.set(String(i + 1), node.id);
|
|
29163
|
+
refMap.set(normalizeTaskRef(String(task.title)), node.id);
|
|
29164
|
+
});
|
|
29165
|
+
for (const { nodeId, task } of created) {
|
|
29166
|
+
const deps = Array.isArray(task.dependsOn) ? task.dependsOn : [];
|
|
29167
|
+
for (const ref of deps) {
|
|
29168
|
+
const depId = refMap.get(normalizeTaskRef(String(ref)));
|
|
29169
|
+
if (depId && depId !== nodeId) this.tracker.addDependency(depId, nodeId);
|
|
29170
|
+
}
|
|
29171
|
+
}
|
|
29172
|
+
await this.persistGraph(this.graph);
|
|
29173
|
+
this.builder.setTaskGraphId(this.graph.id);
|
|
29174
|
+
await this.builder.saveSession();
|
|
29175
|
+
return this.graph.id;
|
|
29176
|
+
}
|
|
29177
|
+
};
|
|
29178
|
+
var TASK_TYPES2 = ["feature", "bugfix", "refactor", "docs", "test", "chore"];
|
|
29179
|
+
var TASK_PRIORITIES = ["critical", "high", "medium", "low"];
|
|
29180
|
+
function normalizeTaskRef(ref) {
|
|
29181
|
+
return ref.trim().toLowerCase();
|
|
29182
|
+
}
|
|
29183
|
+
function addTaskToTracker(tracker, task) {
|
|
29184
|
+
return tracker.addNode({
|
|
29185
|
+
title: String(task.title),
|
|
29186
|
+
description: String(task.description ?? ""),
|
|
29187
|
+
type: TASK_TYPES2.includes(String(task.type)) ? String(task.type) : "feature",
|
|
29188
|
+
priority: TASK_PRIORITIES.includes(String(task.priority)) ? String(task.priority) : "medium",
|
|
29189
|
+
status: "pending",
|
|
29190
|
+
estimateHours: Number(task.estimateHours) || 2,
|
|
29191
|
+
tags: Array.isArray(task.tags) ? task.tags.map(String) : []
|
|
29192
|
+
});
|
|
29193
|
+
}
|
|
29194
|
+
function isExplanatoryText(text) {
|
|
29195
|
+
const lower = text.toLowerCase();
|
|
29196
|
+
return lower.startsWith("i'") || lower.startsWith("i will") || lower.startsWith("let me") || lower.startsWith("here's my") || lower.startsWith("here is my") || lower.startsWith("i'm going to") || lower.startsWith("first, let me") || lower.startsWith("sure") || lower.startsWith("of course") || lower.startsWith("okay") || lower.startsWith("ok,") || lower.startsWith("sounds good") || lower.startsWith("no problem") || text.split("\n").length < 3 && !text.includes(".");
|
|
29197
|
+
}
|
|
29198
|
+
|
|
29199
|
+
// src/sdd/start-sdd-run.ts
|
|
29200
|
+
function startSddRun(opts) {
|
|
29201
|
+
SddParallelRun.resetOrphans(opts.tracker);
|
|
29202
|
+
const run = new SddParallelRun({
|
|
29203
|
+
tracker: opts.tracker,
|
|
29204
|
+
graph: opts.graph,
|
|
29205
|
+
agent: opts.agent,
|
|
29206
|
+
projectRoot: opts.projectRoot,
|
|
29207
|
+
parallelSlots: opts.parallelSlots,
|
|
29208
|
+
taskTimeoutMs: opts.taskTimeoutMs,
|
|
29209
|
+
taskIdleTimeoutMs: opts.taskIdleTimeoutMs,
|
|
29210
|
+
maxFailedRetrySweeps: opts.maxFailedRetrySweeps,
|
|
29211
|
+
verifyTask: opts.verifyTask,
|
|
29212
|
+
conflictResolver: opts.conflictResolver,
|
|
29213
|
+
superviseFailure: opts.superviseFailure,
|
|
29214
|
+
subagentFactory: opts.subagentFactory,
|
|
29215
|
+
events: opts.events,
|
|
29216
|
+
worktrees: opts.worktrees,
|
|
29217
|
+
maxRecoveryRounds: opts.maxRecoveryRounds ?? 1,
|
|
29218
|
+
onProgress: opts.onProgress,
|
|
29219
|
+
defaultModel: opts.defaultModel,
|
|
29220
|
+
defaultProvider: opts.defaultProvider,
|
|
29221
|
+
fallbackModels: opts.fallbackModels
|
|
29222
|
+
});
|
|
29223
|
+
const projector = new SddBoardProjector({
|
|
29224
|
+
runId: run.runId,
|
|
29225
|
+
graph: opts.graph,
|
|
29226
|
+
tracker: opts.tracker,
|
|
29227
|
+
events: opts.events,
|
|
29228
|
+
store: opts.boardStore,
|
|
29229
|
+
specId: opts.graph.specId,
|
|
29230
|
+
defaultModel: opts.defaultModel,
|
|
29231
|
+
defaultProvider: opts.defaultProvider,
|
|
29232
|
+
fallbackModels: opts.fallbackModels
|
|
29233
|
+
});
|
|
29234
|
+
opts.registry?.register({
|
|
29235
|
+
runId: run.runId,
|
|
29236
|
+
specId: opts.graph.specId,
|
|
29237
|
+
pause: () => run.pause(),
|
|
29238
|
+
resume: () => run.resume(),
|
|
29239
|
+
stop: () => run.stop(),
|
|
29240
|
+
retryTask: (id) => run.retryTask(id),
|
|
29241
|
+
retryAllFailed: () => run.retryAllFailed(),
|
|
29242
|
+
reassignTask: (id, name) => run.reassignTask(id, name),
|
|
29243
|
+
setTaskModel: (id, model, provider) => run.setTaskModel(id, model, provider),
|
|
29244
|
+
setTaskFallbacks: (id, fb) => run.setTaskFallbacks(id, fb),
|
|
29245
|
+
setTaskVerification: (id, cmd) => run.setTaskVerification(id, cmd),
|
|
29246
|
+
cancelTask: (id) => run.cancelTask(id),
|
|
29247
|
+
deleteTask: (id) => run.deleteTask(id),
|
|
29248
|
+
splitTask: (id, subtasks) => run.splitTask(id, subtasks),
|
|
29249
|
+
cleanupWorktrees: () => run.cleanupWorktrees(),
|
|
29250
|
+
rollback: () => run.rollback(),
|
|
29251
|
+
getBaseBranch: () => run.getBaseBranch(),
|
|
29252
|
+
getMergedCommits: () => run.getMergedCommits(),
|
|
29253
|
+
snapshot: () => projector.snapshot(),
|
|
29254
|
+
isRunning: () => run.isRunning()
|
|
29255
|
+
});
|
|
29256
|
+
const drainMs = opts.controlDrainMs ?? 500;
|
|
29257
|
+
const controlTimer = setInterval(() => {
|
|
29258
|
+
void opts.boardStore.drainControl(run.runId).then((cmds) => {
|
|
29259
|
+
for (const c of cmds) {
|
|
29260
|
+
const p = c.payload ?? {};
|
|
29261
|
+
if (c.type === "pause") run.pause();
|
|
29262
|
+
else if (c.type === "resume") run.resume();
|
|
29263
|
+
else if (c.type === "stop") run.stop();
|
|
29264
|
+
else if (c.type === "retry" && p.taskId) run.retryTask(p.taskId);
|
|
29265
|
+
else if (c.type === "retry_all_failed") run.retryAllFailed();
|
|
29266
|
+
else if (c.type === "reassign" && p.taskId) run.reassignTask(p.taskId, p.agentName ?? "");
|
|
29267
|
+
else if (c.type === "set_task_model" && p.taskId) run.setTaskModel(p.taskId, p.model, p.provider);
|
|
29268
|
+
else if (c.type === "set_task_fallbacks" && p.taskId) run.setTaskFallbacks(p.taskId, p.fallbackModels);
|
|
29269
|
+
else if (c.type === "set_task_verification" && p.taskId)
|
|
29270
|
+
run.setTaskVerification(p.taskId, p.verificationCommand);
|
|
29271
|
+
else if (c.type === "cancel_task" && p.taskId) void run.cancelTask(p.taskId);
|
|
29272
|
+
else if (c.type === "delete_task" && p.taskId) run.deleteTask(p.taskId);
|
|
29273
|
+
else if (c.type === "split_task" && p.taskId && p.subtasks?.length) run.splitTask(p.taskId, p.subtasks);
|
|
29274
|
+
else if (c.type === "cleanup_worktrees") void run.cleanupWorktrees();
|
|
29275
|
+
else if (c.type === "rollback") void run.rollback();
|
|
29276
|
+
}
|
|
29277
|
+
});
|
|
29278
|
+
}, drainMs);
|
|
29279
|
+
controlTimer.unref?.();
|
|
29280
|
+
const completion = (async () => {
|
|
29281
|
+
try {
|
|
29282
|
+
return await run.run();
|
|
29283
|
+
} finally {
|
|
29284
|
+
clearInterval(controlTimer);
|
|
29285
|
+
await projector.drain().catch(() => {
|
|
29286
|
+
});
|
|
29287
|
+
projector.dispose();
|
|
29288
|
+
opts.registry?.clear(run.runId);
|
|
29289
|
+
}
|
|
29290
|
+
})();
|
|
29291
|
+
return {
|
|
29292
|
+
run,
|
|
29293
|
+
runId: run.runId,
|
|
29294
|
+
projector,
|
|
29295
|
+
completion,
|
|
29296
|
+
stop: () => run.stop()
|
|
29297
|
+
};
|
|
29298
|
+
}
|
|
29299
|
+
var MAX_SLUG = 40;
|
|
29300
|
+
var WorktreeManager = class {
|
|
29301
|
+
projectRoot;
|
|
29302
|
+
events;
|
|
29303
|
+
gitBin;
|
|
29304
|
+
runGit;
|
|
29305
|
+
/** Keyed by ownerId. */
|
|
29306
|
+
handles = /* @__PURE__ */ new Map();
|
|
29307
|
+
usedSlugs = /* @__PURE__ */ new Set();
|
|
29308
|
+
constructor(opts) {
|
|
29309
|
+
this.projectRoot = resolve(opts.projectRoot);
|
|
29310
|
+
this.events = opts.events;
|
|
29311
|
+
this.gitBin = opts.gitBin ?? "git";
|
|
29312
|
+
this.runGit = opts.run ?? ((args, cwd) => this.defaultRun(args, cwd));
|
|
29313
|
+
}
|
|
29314
|
+
/** Create a fresh worktree + branch forked from the current base branch. */
|
|
29315
|
+
async allocate(ownerId, opts = {}) {
|
|
29316
|
+
const existing = this.handles.get(ownerId);
|
|
29317
|
+
if (existing && (existing.status === "allocating" || existing.status === "active")) {
|
|
29318
|
+
return existing;
|
|
29319
|
+
}
|
|
29320
|
+
const slug = this.makeSlug(opts.slugHint ?? ownerId);
|
|
29321
|
+
const branch = `wstack/ap/${slug}`;
|
|
29322
|
+
const dir = join(this.worktreesRoot(), slug);
|
|
29323
|
+
const absDir = resolve(dir);
|
|
29324
|
+
const absRoot = resolve(this.projectRoot);
|
|
29325
|
+
if (!absDir.startsWith(absRoot + sep)) {
|
|
29326
|
+
throw new Error(`Worktree dir "${absDir}" resolves outside project root`);
|
|
29327
|
+
}
|
|
29328
|
+
const baseBranch = opts.baseBranch ?? await this.detectBaseBranch();
|
|
29329
|
+
const handle = {
|
|
29330
|
+
id: slug,
|
|
29331
|
+
ownerId,
|
|
29332
|
+
ownerLabel: opts.ownerLabel ?? opts.slugHint ?? ownerId,
|
|
29333
|
+
slug,
|
|
29334
|
+
dir,
|
|
29335
|
+
branch,
|
|
29336
|
+
baseBranch,
|
|
29337
|
+
status: "allocating",
|
|
29338
|
+
createdAt: Date.now(),
|
|
29339
|
+
updatedAt: Date.now(),
|
|
29340
|
+
insertions: 0,
|
|
29341
|
+
deletions: 0,
|
|
29342
|
+
files: 0
|
|
29343
|
+
};
|
|
29344
|
+
this.handles.set(ownerId, handle);
|
|
29345
|
+
try {
|
|
29346
|
+
await mkdir(this.worktreesRoot(), { recursive: true });
|
|
29347
|
+
const res = await this.runGit(
|
|
29348
|
+
["worktree", "add", "-b", branch, dir, baseBranch],
|
|
29349
|
+
this.projectRoot
|
|
29350
|
+
);
|
|
29351
|
+
if (res.code !== 0) {
|
|
29352
|
+
return this.fail(handle, res.stderr || "git worktree add failed");
|
|
29353
|
+
}
|
|
29354
|
+
} catch (err) {
|
|
29355
|
+
return this.fail(handle, toErrorMessage(err));
|
|
29356
|
+
}
|
|
29357
|
+
this.setStatus(handle, "active");
|
|
29358
|
+
this.emit("worktree.allocated", {
|
|
29359
|
+
handleId: handle.id,
|
|
29360
|
+
ownerId: handle.ownerId,
|
|
29361
|
+
ownerLabel: handle.ownerLabel,
|
|
29362
|
+
slug: handle.slug,
|
|
29363
|
+
dir: handle.dir,
|
|
29364
|
+
branch: handle.branch,
|
|
29365
|
+
baseBranch: handle.baseBranch
|
|
29366
|
+
});
|
|
29367
|
+
return handle;
|
|
29368
|
+
}
|
|
29369
|
+
/** Stage everything and commit inside the worktree. */
|
|
29370
|
+
async commitAll(handle, message) {
|
|
29371
|
+
this.setStatus(handle, "committing");
|
|
29372
|
+
await this.runGit(["add", "-A"], handle.dir);
|
|
29373
|
+
const staged = await this.runGit(["diff", "--cached", "--quiet"], handle.dir);
|
|
29374
|
+
if (staged.code === 0) {
|
|
29375
|
+
this.emitCommitted(handle, false);
|
|
29376
|
+
return { committed: false };
|
|
29377
|
+
}
|
|
29378
|
+
const idArgs = await this.identityArgs(handle.dir);
|
|
29379
|
+
const committed = await this.runGit([...idArgs, "commit", "-m", message], handle.dir);
|
|
29380
|
+
if (committed.code !== 0) {
|
|
29381
|
+
this.fail(handle, committed.stderr || "git commit failed");
|
|
29382
|
+
return { committed: false };
|
|
29383
|
+
}
|
|
29384
|
+
const stats = await this.collectStats(handle.dir);
|
|
29385
|
+
handle.insertions = stats.insertions;
|
|
29386
|
+
handle.deletions = stats.deletions;
|
|
29387
|
+
handle.files = stats.files;
|
|
29388
|
+
handle.sha = stats.sha;
|
|
29389
|
+
handle.updatedAt = Date.now();
|
|
29390
|
+
this.emitCommitted(handle, true);
|
|
29391
|
+
return { committed: true };
|
|
29392
|
+
}
|
|
29393
|
+
/** Merge the worktree branch back into the base branch (squash by default). */
|
|
29394
|
+
async merge(handle, opts = {}) {
|
|
29395
|
+
const squash = opts.squash ?? true;
|
|
29396
|
+
this.setStatus(handle, "merging");
|
|
29397
|
+
const checkout = await this.runGit(["checkout", handle.baseBranch], this.projectRoot);
|
|
29398
|
+
if (checkout.code !== 0) {
|
|
29399
|
+
this.fail(handle, checkout.stderr || `checkout ${handle.baseBranch} failed`);
|
|
29400
|
+
return { ok: false, stderr: checkout.stderr };
|
|
29401
|
+
}
|
|
29402
|
+
const mergeArgs = squash ? ["merge", "--squash", handle.branch] : ["merge", "--no-ff", handle.branch];
|
|
29403
|
+
const merged = await this.runGit(mergeArgs, this.projectRoot);
|
|
29404
|
+
if (merged.code !== 0) {
|
|
29405
|
+
const fromOutput = parseConflictPaths(`${merged.stdout}
|
|
29406
|
+
${merged.stderr}`);
|
|
29407
|
+
const fromIndex = await this.unmergedFiles();
|
|
29408
|
+
const conflictFiles = [.../* @__PURE__ */ new Set([...fromOutput, ...fromIndex])];
|
|
29409
|
+
if (opts.resolve) {
|
|
29410
|
+
const finalized = await this.tryResolveConflict(handle, conflictFiles, opts);
|
|
29411
|
+
if (finalized) return finalized;
|
|
29412
|
+
}
|
|
29413
|
+
await this.runGit(["reset", "--hard", "HEAD"], this.projectRoot);
|
|
29414
|
+
handle.conflictFiles = conflictFiles;
|
|
29415
|
+
this.setStatus(handle, "needs-review", { lastError: merged.stderr });
|
|
29416
|
+
this.emit("worktree.conflict", {
|
|
29417
|
+
handleId: handle.id,
|
|
29418
|
+
ownerId: handle.ownerId,
|
|
29419
|
+
branch: handle.branch,
|
|
29420
|
+
conflictFiles
|
|
29421
|
+
});
|
|
29422
|
+
return { ok: false, conflict: true, conflictFiles, stderr: merged.stderr };
|
|
29423
|
+
}
|
|
29424
|
+
if (squash) {
|
|
29425
|
+
const msg = opts.message ?? `merge ${handle.branch} (squash)`;
|
|
29426
|
+
const idArgs = await this.identityArgs(this.projectRoot);
|
|
29427
|
+
const commit = await this.runGit([...idArgs, "commit", "-m", msg], this.projectRoot);
|
|
29428
|
+
if (commit.code !== 0 && !/nothing to commit/i.test(commit.stdout + commit.stderr)) {
|
|
29429
|
+
this.fail(handle, commit.stderr || "squash commit failed");
|
|
29430
|
+
return { ok: false, stderr: commit.stderr };
|
|
29431
|
+
}
|
|
29432
|
+
}
|
|
29433
|
+
this.setStatus(handle, "merged");
|
|
29434
|
+
this.emit("worktree.merged", {
|
|
29435
|
+
handleId: handle.id,
|
|
29436
|
+
ownerId: handle.ownerId,
|
|
29437
|
+
branch: handle.branch,
|
|
29438
|
+
baseBranch: handle.baseBranch,
|
|
29439
|
+
squash
|
|
29440
|
+
});
|
|
29441
|
+
return { ok: true };
|
|
29442
|
+
}
|
|
29443
|
+
/**
|
|
29444
|
+
* Current tip SHA of a handle's base branch (without checking it out). Capture
|
|
29445
|
+
* this before a merge so a regressed merge can be reverted to exactly this
|
|
29446
|
+
* commit — unambiguous even when a squash produced no diff. Returns null on
|
|
29447
|
+
* failure (caller then skips the revert).
|
|
29448
|
+
*/
|
|
29449
|
+
async baseHead(handle) {
|
|
29450
|
+
const res = await this.runGit(["rev-parse", handle.baseBranch], this.projectRoot);
|
|
29451
|
+
const sha = res.stdout.trim();
|
|
29452
|
+
return res.code === 0 && sha ? sha : null;
|
|
29453
|
+
}
|
|
29454
|
+
/**
|
|
29455
|
+
* Hard-reset the base branch back to `sha` (a value previously returned by
|
|
29456
|
+
* {@link baseHead}). Used to undo a squash-merge whose integrated result failed
|
|
29457
|
+
* re-verification, so an auto-resolved-but-broken merge never sticks on base.
|
|
29458
|
+
* Safe because SDD merges are serialized — no other commit lands in between.
|
|
29459
|
+
*/
|
|
29460
|
+
async revertBaseTo(handle, sha) {
|
|
29461
|
+
const co = await this.runGit(["checkout", handle.baseBranch], this.projectRoot);
|
|
29462
|
+
if (co.code !== 0) return false;
|
|
29463
|
+
const reset = await this.runGit(["reset", "--hard", sha], this.projectRoot);
|
|
29464
|
+
return reset.code === 0;
|
|
29465
|
+
}
|
|
29466
|
+
/**
|
|
29467
|
+
* Current base branch + tip SHA, captured WITHOUT a handle. The SDD run calls
|
|
29468
|
+
* this once at start so a later rollback knows which branch the run's squash
|
|
29469
|
+
* commits landed on. Returns null when not in a usable git state.
|
|
29470
|
+
*/
|
|
29471
|
+
async currentBase() {
|
|
29472
|
+
const branch = await this.detectBaseBranch();
|
|
29473
|
+
const head = await this.runGit(["rev-parse", "HEAD"], this.projectRoot);
|
|
29474
|
+
const sha = head.stdout.trim();
|
|
29475
|
+
return head.code === 0 && sha ? { branch, sha } : null;
|
|
29476
|
+
}
|
|
29477
|
+
/**
|
|
29478
|
+
* Force-remove EVERY managed worktree + branch this project owns, without
|
|
29479
|
+
* relying on the in-memory `handles` map — so it works post-run (a fresh
|
|
29480
|
+
* manager can clean up a previous run's leftovers). Enumerates
|
|
29481
|
+
* `git worktree list --porcelain`, removes every checkout living under the
|
|
29482
|
+
* `.wrongstack/worktrees` root, deletes every `wstack/ap/*` branch, then prunes.
|
|
29483
|
+
* Returns the number of worktrees removed. Never throws — best-effort cleanup.
|
|
29484
|
+
*/
|
|
29485
|
+
async cleanupAllManaged() {
|
|
29486
|
+
const root = resolve(this.worktreesRoot());
|
|
29487
|
+
let removed = 0;
|
|
29488
|
+
try {
|
|
29489
|
+
const listed = await this.runGit(["worktree", "list", "--porcelain"], this.projectRoot);
|
|
29490
|
+
for (const line of listed.stdout.split("\n")) {
|
|
29491
|
+
const m = line.match(/^worktree\s+(.+?)\s*$/);
|
|
29492
|
+
if (!m?.[1]) continue;
|
|
29493
|
+
const dir = resolve(m[1]);
|
|
29494
|
+
if (dir !== root && (dir === root || dir.startsWith(root + sep))) {
|
|
29495
|
+
const rm6 = await this.runGit(["worktree", "remove", "--force", dir], this.projectRoot);
|
|
29496
|
+
if (rm6.code === 0) removed++;
|
|
29497
|
+
}
|
|
29498
|
+
}
|
|
29499
|
+
} catch {
|
|
29500
|
+
}
|
|
29501
|
+
try {
|
|
29502
|
+
const branches = await this.runGit(
|
|
29503
|
+
["branch", "--list", "--format=%(refname:short)", "wstack/ap/*"],
|
|
29504
|
+
this.projectRoot
|
|
29505
|
+
);
|
|
29506
|
+
for (const b of branches.stdout.split("\n").map((s) => s.trim()).filter(Boolean)) {
|
|
29507
|
+
await this.runGit(["branch", "-D", b], this.projectRoot);
|
|
29508
|
+
}
|
|
29509
|
+
} catch {
|
|
29510
|
+
}
|
|
29511
|
+
await this.runGit(["worktree", "prune"], this.projectRoot).catch(() => void 0);
|
|
29512
|
+
this.handles.clear();
|
|
29513
|
+
this.emit("worktree.released", {
|
|
29514
|
+
handleId: "cleanup-all",
|
|
29515
|
+
ownerId: "cleanup-all",
|
|
29516
|
+
branch: "wstack/ap/*",
|
|
29517
|
+
kept: false
|
|
29518
|
+
});
|
|
29519
|
+
return { removed };
|
|
29520
|
+
}
|
|
29521
|
+
/**
|
|
29522
|
+
* Undo a run's squash commits by reverting each (newest → oldest) on the base
|
|
29523
|
+
* branch — history-preserving, never a destructive reset. Refuses on a dirty
|
|
29524
|
+
* working tree (so uncommitted work is never clobbered) and aborts cleanly if a
|
|
29525
|
+
* revert conflicts, reporting which SHA. `shas` are the run commit SHAs in the
|
|
29526
|
+
* order they landed; this reverses them. Returns the count reverted.
|
|
29527
|
+
*/
|
|
29528
|
+
async revertCommits(baseBranch, shas) {
|
|
29529
|
+
if (shas.length === 0) return { ok: true, reverted: 0, reason: "nothing to revert" };
|
|
29530
|
+
const status = await this.runGit(["status", "--porcelain"], this.projectRoot);
|
|
29531
|
+
if (status.stdout.trim().length > 0) {
|
|
29532
|
+
return { ok: false, reverted: 0, reason: "working tree has uncommitted changes \u2014 commit or stash first" };
|
|
29533
|
+
}
|
|
29534
|
+
const co = await this.runGit(["checkout", baseBranch], this.projectRoot);
|
|
29535
|
+
if (co.code !== 0) {
|
|
29536
|
+
return { ok: false, reverted: 0, reason: co.stderr || `checkout ${baseBranch} failed` };
|
|
29537
|
+
}
|
|
29538
|
+
const idArgs = await this.identityArgs(this.projectRoot);
|
|
29539
|
+
let reverted = 0;
|
|
29540
|
+
for (const sha of [...shas].reverse()) {
|
|
29541
|
+
const res = await this.runGit([...idArgs, "revert", "--no-edit", sha], this.projectRoot);
|
|
29542
|
+
if (res.code !== 0) {
|
|
29543
|
+
await this.runGit(["revert", "--abort"], this.projectRoot).catch(() => void 0);
|
|
29544
|
+
return {
|
|
29545
|
+
ok: false,
|
|
29546
|
+
reverted,
|
|
29547
|
+
reason: `revert of ${sha.slice(0, 8)} failed: ${(res.stderr || res.stdout).trim().split("\n")[0] ?? "conflict"}`
|
|
29548
|
+
};
|
|
29549
|
+
}
|
|
29550
|
+
reverted++;
|
|
29551
|
+
}
|
|
29552
|
+
return { ok: true, reverted };
|
|
29553
|
+
}
|
|
29554
|
+
/**
|
|
29555
|
+
* Run the caller-supplied resolver against a conflicted squash-merge, then
|
|
29556
|
+
* commit if it cleared every marker. Returns a successful `MergeResult` on a
|
|
29557
|
+
* clean resolution, or `null` to signal the caller should fall back to the
|
|
29558
|
+
* abort path. Never leaves the base tree committed-but-dirty: a partial or
|
|
29559
|
+
* failed resolution returns `null` and the caller hard-resets.
|
|
29560
|
+
*/
|
|
29561
|
+
async tryResolveConflict(handle, conflictFiles, opts) {
|
|
29562
|
+
let resolved = false;
|
|
29563
|
+
try {
|
|
29564
|
+
resolved = opts.resolve ? await opts.resolve({ conflictFiles, cwd: this.projectRoot }) : false;
|
|
29565
|
+
} catch {
|
|
29566
|
+
resolved = false;
|
|
29567
|
+
}
|
|
29568
|
+
if (!resolved) return null;
|
|
29569
|
+
await this.runGit(["add", "-A"], this.projectRoot);
|
|
29570
|
+
if (await this.hasConflictMarkers()) return null;
|
|
29571
|
+
const idArgs = await this.identityArgs(this.projectRoot);
|
|
29572
|
+
const msg = opts.message ?? `merge ${handle.branch} (squash, conflict resolved)`;
|
|
29573
|
+
const commit = await this.runGit([...idArgs, "commit", "-m", msg], this.projectRoot);
|
|
29574
|
+
if (commit.code !== 0 && !/nothing to commit/i.test(commit.stdout + commit.stderr)) {
|
|
29575
|
+
return null;
|
|
29576
|
+
}
|
|
29577
|
+
handle.conflictFiles = conflictFiles;
|
|
29578
|
+
this.setStatus(handle, "merged");
|
|
29579
|
+
this.emit("worktree.merged", {
|
|
29580
|
+
handleId: handle.id,
|
|
29581
|
+
ownerId: handle.ownerId,
|
|
29582
|
+
branch: handle.branch,
|
|
29583
|
+
baseBranch: handle.baseBranch,
|
|
29584
|
+
squash: true
|
|
29585
|
+
});
|
|
29586
|
+
return { ok: true, resolved: true, conflictFiles };
|
|
29587
|
+
}
|
|
29588
|
+
/**
|
|
29589
|
+
* True when staged content still carries conflict markers. `git diff --cached
|
|
29590
|
+
* --check` exits nonzero and prints a "leftover conflict marker" line for each
|
|
29591
|
+
* survivor; whitespace-only errors (also flagged by --check) are ignored so a
|
|
29592
|
+
* clean resolution with unrelated whitespace is not rejected.
|
|
29593
|
+
*/
|
|
29594
|
+
async hasConflictMarkers() {
|
|
29595
|
+
const check = await this.runGit(["diff", "--cached", "--check"], this.projectRoot);
|
|
29596
|
+
if (check.code === 0) return false;
|
|
29597
|
+
return /conflict marker/i.test(`${check.stdout}
|
|
29598
|
+
${check.stderr}`);
|
|
29599
|
+
}
|
|
29600
|
+
/**
|
|
29601
|
+
* Remove the worktree + branch. Conflicted/failed handles (or `keep:true`)
|
|
29602
|
+
* are left on disk for inspection.
|
|
29603
|
+
*/
|
|
29604
|
+
async release(handle, opts = {}) {
|
|
29605
|
+
const keep = opts.keep || handle.status === "needs-review" || handle.status === "failed";
|
|
29606
|
+
if (!keep) {
|
|
29607
|
+
await this.runGit(["worktree", "remove", "--force", handle.dir], this.projectRoot);
|
|
29608
|
+
await this.runGit(["branch", "-D", handle.branch], this.projectRoot);
|
|
29609
|
+
await this.runGit(["worktree", "prune"], this.projectRoot);
|
|
29610
|
+
this.handles.delete(handle.ownerId);
|
|
29611
|
+
}
|
|
29612
|
+
this.emit("worktree.released", {
|
|
29613
|
+
handleId: handle.id,
|
|
29614
|
+
ownerId: handle.ownerId,
|
|
29615
|
+
branch: handle.branch,
|
|
29616
|
+
kept: keep
|
|
29617
|
+
});
|
|
29618
|
+
}
|
|
29619
|
+
get(ownerId) {
|
|
29620
|
+
return this.handles.get(ownerId);
|
|
29621
|
+
}
|
|
29622
|
+
list() {
|
|
29623
|
+
return [...this.handles.values()];
|
|
29624
|
+
}
|
|
29625
|
+
// ── internals ────────────────────────────────────────────────────────────
|
|
29626
|
+
worktreesRoot() {
|
|
29627
|
+
return join(this.projectRoot, ".wrongstack", "worktrees");
|
|
29628
|
+
}
|
|
29629
|
+
async detectBaseBranch() {
|
|
29630
|
+
const head = await this.runGit(["rev-parse", "--abbrev-ref", "HEAD"], this.projectRoot);
|
|
29631
|
+
const name = head.stdout.trim();
|
|
29632
|
+
if (name && name !== "HEAD") return name;
|
|
29633
|
+
const sha = await this.runGit(["rev-parse", "HEAD"], this.projectRoot);
|
|
29634
|
+
return sha.stdout.trim() || "HEAD";
|
|
29635
|
+
}
|
|
29636
|
+
makeSlug(hint) {
|
|
29637
|
+
let base = hint.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[-.]+/, "").replace(/[-.]+$/, "").slice(0, MAX_SLUG).replace(/[-.]+$/, "");
|
|
29638
|
+
if (!base) base = "wt";
|
|
29639
|
+
let slug = `${base}-${crypto.randomUUID().slice(0, 6)}`;
|
|
29640
|
+
while (this.usedSlugs.has(slug)) slug = `${base}-${crypto.randomUUID().slice(0, 6)}`;
|
|
29641
|
+
this.usedSlugs.add(slug);
|
|
29642
|
+
return slug;
|
|
29643
|
+
}
|
|
29644
|
+
async collectStats(dir) {
|
|
29645
|
+
const sha = (await this.runGit(["rev-parse", "HEAD"], dir)).stdout.trim();
|
|
29646
|
+
const numstat = await this.runGit(["show", "--numstat", "--format=", "HEAD"], dir);
|
|
29647
|
+
let insertions = 0;
|
|
29648
|
+
let deletions = 0;
|
|
29649
|
+
let files = 0;
|
|
29650
|
+
for (const line of numstat.stdout.split("\n")) {
|
|
29651
|
+
const m = line.trim().match(/^(\d+|-)\t(\d+|-)\t(.+)$/);
|
|
29652
|
+
if (!m) continue;
|
|
29653
|
+
files++;
|
|
29654
|
+
if (m[1] !== "-") insertions += Number(m[1]);
|
|
29655
|
+
if (m[2] !== "-") deletions += Number(m[2]);
|
|
29656
|
+
}
|
|
29657
|
+
return { insertions, deletions, files, sha };
|
|
29658
|
+
}
|
|
29659
|
+
/**
|
|
29660
|
+
* `git -c user.*` fallback so commits succeed on machines and CI runners
|
|
29661
|
+
* that have no global git identity configured. Returns `[]` when both
|
|
29662
|
+
* `user.name` and `user.email` are already set (the common case), so a real
|
|
29663
|
+
* user's identity is never overridden. The worktree branch commits are
|
|
29664
|
+
* squashed away on merge, so the fallback identity never reaches the base
|
|
29665
|
+
* branch history.
|
|
29666
|
+
*/
|
|
29667
|
+
async identityArgs(cwd) {
|
|
29668
|
+
const name = (await this.runGit(["config", "user.name"], cwd)).stdout.trim();
|
|
29669
|
+
const email = (await this.runGit(["config", "user.email"], cwd)).stdout.trim();
|
|
29670
|
+
if (name && email) return [];
|
|
29671
|
+
return [
|
|
29672
|
+
"-c",
|
|
29673
|
+
`user.name=${name || "AutoPhase"}`,
|
|
29674
|
+
"-c",
|
|
29675
|
+
`user.email=${email || "autophase@agent.local"}`
|
|
29676
|
+
];
|
|
29677
|
+
}
|
|
29678
|
+
async unmergedFiles() {
|
|
29679
|
+
const res = await this.runGit(["diff", "--name-only", "--diff-filter=U"], this.projectRoot);
|
|
29680
|
+
return res.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
29681
|
+
}
|
|
29682
|
+
emitCommitted(handle, committed) {
|
|
29683
|
+
this.emit("worktree.committed", {
|
|
29684
|
+
handleId: handle.id,
|
|
29685
|
+
ownerId: handle.ownerId,
|
|
29686
|
+
branch: handle.branch,
|
|
29687
|
+
committed,
|
|
29688
|
+
insertions: handle.insertions,
|
|
29689
|
+
deletions: handle.deletions,
|
|
29690
|
+
files: handle.files,
|
|
29691
|
+
sha: handle.sha
|
|
29692
|
+
});
|
|
29693
|
+
}
|
|
29694
|
+
fail(handle, error) {
|
|
29695
|
+
this.setStatus(handle, "failed", { lastError: error });
|
|
29696
|
+
this.emit("worktree.failed", {
|
|
29697
|
+
handleId: handle.id,
|
|
29698
|
+
ownerId: handle.ownerId,
|
|
29699
|
+
branch: handle.branch,
|
|
29700
|
+
error
|
|
29701
|
+
});
|
|
29702
|
+
return handle;
|
|
29703
|
+
}
|
|
29704
|
+
setStatus(handle, status, patch) {
|
|
29705
|
+
handle.status = status;
|
|
29706
|
+
handle.updatedAt = Date.now();
|
|
29707
|
+
if (patch) Object.assign(handle, patch);
|
|
29708
|
+
}
|
|
29709
|
+
emit(event, payload) {
|
|
29710
|
+
this.events?.emit(event, payload);
|
|
29711
|
+
}
|
|
29712
|
+
defaultRun(args, cwd) {
|
|
29713
|
+
return new Promise((res) => {
|
|
29714
|
+
let stdout = "";
|
|
29715
|
+
let stderr = "";
|
|
29716
|
+
const MAX_GIT_OUTPUT = 1e6;
|
|
29717
|
+
const child = spawn(this.gitBin, args, {
|
|
29718
|
+
cwd,
|
|
29719
|
+
env: buildChildEnv(),
|
|
29720
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
29721
|
+
signal: AbortSignal.timeout(3e4),
|
|
29722
|
+
windowsHide: true
|
|
29723
|
+
});
|
|
29724
|
+
child.stdout?.on("data", (c) => {
|
|
29725
|
+
if (stdout.length < MAX_GIT_OUTPUT) stdout += c.toString();
|
|
29726
|
+
});
|
|
29727
|
+
child.stderr?.on("data", (c) => {
|
|
29728
|
+
if (stderr.length < MAX_GIT_OUTPUT) stderr += c.toString();
|
|
29729
|
+
});
|
|
29730
|
+
child.on("error", (err) => res({ code: 1, stdout, stderr: err.message }));
|
|
29731
|
+
child.on("close", (code) => res({ code: code ?? 1, stdout, stderr }));
|
|
29732
|
+
});
|
|
29733
|
+
}
|
|
29734
|
+
};
|
|
29735
|
+
function parseConflictPaths(output) {
|
|
29736
|
+
const paths = /* @__PURE__ */ new Set();
|
|
29737
|
+
for (const line of output.split("\n")) {
|
|
29738
|
+
const m = line.match(/^CONFLICT \([^)]*\): Merge conflict in (.+?)\s*$/);
|
|
29739
|
+
if (m?.[1]) paths.add(m[1]);
|
|
29740
|
+
}
|
|
29741
|
+
return [...paths];
|
|
29742
|
+
}
|
|
29743
|
+
function assertSafePath(dir, projectRoot) {
|
|
29744
|
+
const root = resolve(projectRoot);
|
|
29745
|
+
const abs = resolve(dir);
|
|
29746
|
+
if (abs !== root && !abs.startsWith(root + sep)) {
|
|
29747
|
+
throw new Error(`worktree path escapes project root: ${dir}`);
|
|
29748
|
+
}
|
|
29749
|
+
}
|
|
29750
|
+
|
|
29751
|
+
// src/sdd/sdd-lifecycle.ts
|
|
29752
|
+
async function cleanupSddWorktrees(projectRoot) {
|
|
29753
|
+
const wt = new WorktreeManager({ projectRoot });
|
|
29754
|
+
return wt.cleanupAllManaged();
|
|
29755
|
+
}
|
|
29756
|
+
async function rollbackSddRunFromDisk(opts) {
|
|
29757
|
+
const store = new SddBoardStore({ baseDir: opts.boardsDir });
|
|
29758
|
+
const runId = opts.runId ?? (await store.list())[0]?.runId;
|
|
29759
|
+
if (!runId) return { ok: false, reverted: 0, reason: "no SDD board found to roll back" };
|
|
29760
|
+
const snap = await store.load(runId);
|
|
29761
|
+
if (!snap) return { ok: false, reverted: 0, reason: `board "${runId}" not found` };
|
|
29762
|
+
if (!snap.baseBranch) {
|
|
29763
|
+
return { ok: false, reverted: 0, reason: "this run did not record a base branch (no worktree run)" };
|
|
29764
|
+
}
|
|
29765
|
+
const shas = (snap.mergedCommits ?? []).map((c) => c.sha);
|
|
29766
|
+
if (shas.length === 0) {
|
|
29767
|
+
return { ok: false, reverted: 0, reason: "no merged commits recorded for this run" };
|
|
29768
|
+
}
|
|
29769
|
+
const wt = new WorktreeManager({ projectRoot: opts.projectRoot });
|
|
29770
|
+
return wt.revertCommits(snap.baseBranch, shas);
|
|
29771
|
+
}
|
|
29772
|
+
async function destroySddProject(opts) {
|
|
29773
|
+
const { removed } = await cleanupSddWorktrees(opts.projectRoot).catch(() => ({ removed: 0 }));
|
|
29774
|
+
const deleted = [];
|
|
29775
|
+
const rmDir = async (dir, label) => {
|
|
29776
|
+
try {
|
|
29777
|
+
await fsp3.rm(dir, { recursive: true, force: true });
|
|
29778
|
+
deleted.push(label);
|
|
29779
|
+
} catch {
|
|
29780
|
+
}
|
|
29781
|
+
};
|
|
29782
|
+
const rmFile = async (file, label) => {
|
|
29783
|
+
try {
|
|
29784
|
+
await fsp3.unlink(file);
|
|
29785
|
+
deleted.push(label);
|
|
29786
|
+
} catch {
|
|
29787
|
+
}
|
|
29788
|
+
};
|
|
29789
|
+
await rmFile(opts.paths.projectSddSession, "session");
|
|
29790
|
+
await rmDir(opts.paths.projectSpecs, "specs");
|
|
29791
|
+
await rmDir(opts.paths.projectTaskGraphs, "task-graphs");
|
|
29792
|
+
await rmDir(opts.paths.projectSddBoards, "boards");
|
|
29793
|
+
return { worktreesRemoved: removed, deleted };
|
|
29794
|
+
}
|
|
29795
|
+
|
|
29796
|
+
// src/observability/metrics.ts
|
|
29797
|
+
var RESERVOIR_SIZE = 1024;
|
|
29798
|
+
function labelKey(labels) {
|
|
29799
|
+
if (!labels) return "";
|
|
29800
|
+
const keys = Object.keys(labels).sort();
|
|
29801
|
+
return keys.map((k) => `${k}=${labels[k]}`).join(",");
|
|
29802
|
+
}
|
|
29803
|
+
function quantile(sorted, q) {
|
|
29804
|
+
if (sorted.length === 0) return 0;
|
|
29805
|
+
const idx = Math.min(sorted.length - 1, Math.floor(q * sorted.length));
|
|
29806
|
+
return sorted[idx] ?? 0;
|
|
29807
|
+
}
|
|
29808
|
+
var InMemoryMetricsSink = class {
|
|
29809
|
+
counters = /* @__PURE__ */ new Map();
|
|
29810
|
+
gauges = /* @__PURE__ */ new Map();
|
|
29811
|
+
histograms = /* @__PURE__ */ new Map();
|
|
29812
|
+
counter(name, value = 1, labels) {
|
|
29813
|
+
const series = this.getOrCreate(this.counters, name);
|
|
29814
|
+
const key = labelKey(labels);
|
|
29815
|
+
const state = series.get(key) ?? { value: 0 };
|
|
29816
|
+
state.value += value;
|
|
29817
|
+
series.set(key, state);
|
|
29818
|
+
}
|
|
29819
|
+
gauge(name, value, labels) {
|
|
29820
|
+
const series = this.getOrCreate(this.gauges, name);
|
|
29821
|
+
series.set(labelKey(labels), { value });
|
|
29822
|
+
}
|
|
29823
|
+
histogram(name, value, labels) {
|
|
29824
|
+
const series = this.getOrCreate(this.histograms, name);
|
|
29825
|
+
const key = labelKey(labels);
|
|
29826
|
+
let state = series.get(key);
|
|
29827
|
+
if (!state) {
|
|
29828
|
+
state = { count: 0, sum: 0, min: value, max: value, samples: [] };
|
|
29829
|
+
series.set(key, state);
|
|
29830
|
+
}
|
|
29831
|
+
state.count++;
|
|
29832
|
+
state.sum += value;
|
|
29833
|
+
if (value < state.min) state.min = value;
|
|
29834
|
+
if (value > state.max) state.max = value;
|
|
29835
|
+
if (state.samples.length < RESERVOIR_SIZE) {
|
|
29836
|
+
state.samples.push(value);
|
|
29837
|
+
} else {
|
|
29838
|
+
const r = Math.floor(Math.random() * state.count);
|
|
29839
|
+
if (r < RESERVOIR_SIZE) state.samples[r] = value;
|
|
29840
|
+
}
|
|
29841
|
+
}
|
|
29842
|
+
snapshot() {
|
|
29843
|
+
const series = [];
|
|
29844
|
+
for (const [name, byLabel] of this.counters) {
|
|
29845
|
+
for (const [key, state] of byLabel) {
|
|
29846
|
+
series.push({
|
|
29847
|
+
name,
|
|
29848
|
+
type: "counter",
|
|
29849
|
+
labels: parseLabelKey(key),
|
|
29850
|
+
values: { value: state.value }
|
|
29851
|
+
});
|
|
29852
|
+
}
|
|
29853
|
+
}
|
|
29854
|
+
for (const [name, byLabel] of this.gauges) {
|
|
29855
|
+
for (const [key, state] of byLabel) {
|
|
29856
|
+
series.push({
|
|
29857
|
+
name,
|
|
29858
|
+
type: "gauge",
|
|
29859
|
+
labels: parseLabelKey(key),
|
|
29860
|
+
values: { value: state.value }
|
|
29861
|
+
});
|
|
29862
|
+
}
|
|
29863
|
+
}
|
|
29864
|
+
for (const [name, byLabel] of this.histograms) {
|
|
29865
|
+
for (const [key, state] of byLabel) {
|
|
29866
|
+
const sorted = [...state.samples].sort((a, b) => a - b);
|
|
29867
|
+
series.push({
|
|
29868
|
+
name,
|
|
29869
|
+
type: "histogram",
|
|
29870
|
+
labels: parseLabelKey(key),
|
|
29871
|
+
values: {
|
|
29872
|
+
count: state.count,
|
|
29873
|
+
sum: state.sum,
|
|
29874
|
+
min: state.min,
|
|
29875
|
+
max: state.max,
|
|
29876
|
+
p50: quantile(sorted, 0.5),
|
|
29877
|
+
p95: quantile(sorted, 0.95),
|
|
29878
|
+
p99: quantile(sorted, 0.99)
|
|
29879
|
+
}
|
|
29880
|
+
});
|
|
29881
|
+
}
|
|
29882
|
+
}
|
|
29883
|
+
return { timestamp: Date.now(), series };
|
|
29884
|
+
}
|
|
29885
|
+
reset() {
|
|
29886
|
+
this.counters.clear();
|
|
29887
|
+
this.gauges.clear();
|
|
29888
|
+
this.histograms.clear();
|
|
29889
|
+
}
|
|
29890
|
+
getOrCreate(bag, name) {
|
|
29891
|
+
let series = bag.get(name);
|
|
29892
|
+
if (!series) {
|
|
29893
|
+
series = /* @__PURE__ */ new Map();
|
|
29894
|
+
bag.set(name, series);
|
|
29895
|
+
}
|
|
29896
|
+
return series;
|
|
29897
|
+
}
|
|
29898
|
+
};
|
|
29899
|
+
function parseLabelKey(key) {
|
|
29900
|
+
if (!key) return {};
|
|
29901
|
+
const labels = {};
|
|
29902
|
+
for (const pair of key.split(",")) {
|
|
29903
|
+
const eq = pair.indexOf("=");
|
|
29904
|
+
if (eq > 0) labels[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
29905
|
+
}
|
|
29906
|
+
return labels;
|
|
29907
|
+
}
|
|
29908
|
+
var NoopMetricsSink = class {
|
|
29909
|
+
counter() {
|
|
29910
|
+
}
|
|
29911
|
+
gauge() {
|
|
29912
|
+
}
|
|
29913
|
+
histogram() {
|
|
29914
|
+
}
|
|
29915
|
+
snapshot() {
|
|
29916
|
+
return { timestamp: Date.now(), series: [] };
|
|
29917
|
+
}
|
|
29918
|
+
reset() {
|
|
29919
|
+
}
|
|
29920
|
+
};
|
|
29921
|
+
|
|
29922
|
+
// src/observability/health.ts
|
|
29923
|
+
var SEVERITY = {
|
|
29924
|
+
healthy: 0,
|
|
29925
|
+
degraded: 1,
|
|
29926
|
+
unhealthy: 2
|
|
29927
|
+
};
|
|
29928
|
+
var DefaultHealthRegistry = class {
|
|
29929
|
+
checks = /* @__PURE__ */ new Map();
|
|
29930
|
+
timeoutMs;
|
|
29931
|
+
constructor(opts = {}) {
|
|
29932
|
+
this.timeoutMs = opts.timeoutMs ?? 5e3;
|
|
29933
|
+
}
|
|
29934
|
+
register(check) {
|
|
27204
29935
|
this.checks.set(check.name, check);
|
|
27205
29936
|
}
|
|
27206
29937
|
unregister(name) {
|
|
@@ -27406,7 +30137,7 @@ async function startMetricsServer(opts) {
|
|
|
27406
30137
|
const tls = opts.tls;
|
|
27407
30138
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
27408
30139
|
const host = opts.host ?? "127.0.0.1";
|
|
27409
|
-
const
|
|
30140
|
+
const path52 = opts.path ?? "/metrics";
|
|
27410
30141
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
27411
30142
|
const healthRegistry = opts.healthRegistry;
|
|
27412
30143
|
const listener = (req, res) => {
|
|
@@ -27416,7 +30147,7 @@ async function startMetricsServer(opts) {
|
|
|
27416
30147
|
return;
|
|
27417
30148
|
}
|
|
27418
30149
|
const url = req.url.split("?")[0];
|
|
27419
|
-
if (url ===
|
|
30150
|
+
if (url === path52) {
|
|
27420
30151
|
let body;
|
|
27421
30152
|
try {
|
|
27422
30153
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -27480,7 +30211,7 @@ async function startMetricsServer(opts) {
|
|
|
27480
30211
|
const protocol = useHttps ? "https" : "http";
|
|
27481
30212
|
return {
|
|
27482
30213
|
port: boundPort,
|
|
27483
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
30214
|
+
url: `${protocol}://${host}:${boundPort}${path52}`,
|
|
27484
30215
|
close: () => new Promise((resolve19, reject) => {
|
|
27485
30216
|
server.close((err) => err ? reject(err) : resolve19());
|
|
27486
30217
|
})
|
|
@@ -35306,16 +38037,16 @@ Use \`/security report <number>\` to view a specific report.` };
|
|
|
35306
38037
|
}
|
|
35307
38038
|
const index = Number.parseInt(reportId, 10) - 1;
|
|
35308
38039
|
if (!Number.isNaN(index) && reports[index]) {
|
|
35309
|
-
const { readFile:
|
|
35310
|
-
const content = await
|
|
38040
|
+
const { readFile: readFile51 } = await import('fs/promises');
|
|
38041
|
+
const content = await readFile51(join(reportsDir, reports[index]), "utf-8");
|
|
35311
38042
|
return { message: `# Security Report
|
|
35312
38043
|
|
|
35313
38044
|
${content}` };
|
|
35314
38045
|
}
|
|
35315
38046
|
const match = reports.find((r) => r.includes(reportId));
|
|
35316
38047
|
if (match) {
|
|
35317
|
-
const { readFile:
|
|
35318
|
-
const content = await
|
|
38048
|
+
const { readFile: readFile51 } = await import('fs/promises');
|
|
38049
|
+
const content = await readFile51(join(reportsDir, match), "utf-8");
|
|
35319
38050
|
return { message: `# Security Report
|
|
35320
38051
|
|
|
35321
38052
|
${content}` };
|
|
@@ -36413,6 +39144,7 @@ function makeMailboxTool(opts = {}) {
|
|
|
36413
39144
|
category: "coordination",
|
|
36414
39145
|
permission: "auto",
|
|
36415
39146
|
mutating: true,
|
|
39147
|
+
capabilities: [ToolCapabilities.COORDINATION_MAIL],
|
|
36416
39148
|
inputSchema: {
|
|
36417
39149
|
type: "object",
|
|
36418
39150
|
properties: {
|
|
@@ -36676,6 +39408,7 @@ function makeMailSendTool(opts = {}) {
|
|
|
36676
39408
|
category: "coordination",
|
|
36677
39409
|
permission: "auto",
|
|
36678
39410
|
mutating: true,
|
|
39411
|
+
capabilities: [ToolCapabilities.COORDINATION_MAIL],
|
|
36679
39412
|
inputSchema: {
|
|
36680
39413
|
type: "object",
|
|
36681
39414
|
properties: {
|
|
@@ -36735,6 +39468,7 @@ function makeMailInboxTool(opts = {}) {
|
|
|
36735
39468
|
category: "coordination",
|
|
36736
39469
|
permission: "auto",
|
|
36737
39470
|
mutating: false,
|
|
39471
|
+
capabilities: [ToolCapabilities.COORDINATION_MAIL],
|
|
36738
39472
|
inputSchema: {
|
|
36739
39473
|
type: "object",
|
|
36740
39474
|
properties: {
|
|
@@ -42620,8 +45354,8 @@ var InputBuilder = class {
|
|
|
42620
45354
|
async registerFile(input) {
|
|
42621
45355
|
const ref = await this.store.add({ ...input, kind: "file" });
|
|
42622
45356
|
this.refs.push(ref);
|
|
42623
|
-
const
|
|
42624
|
-
return `[file:${
|
|
45357
|
+
const path52 = ref.meta.filename ?? ref.meta.label ?? String(ref.seq);
|
|
45358
|
+
return `[file:${path52}]`;
|
|
42625
45359
|
}
|
|
42626
45360
|
/**
|
|
42627
45361
|
* Whether `appendPaste(text)` would collapse the text to a placeholder
|
|
@@ -43614,6 +46348,7 @@ function compactTrigger(trigger) {
|
|
|
43614
46348
|
// src/registry/tool-registry.ts
|
|
43615
46349
|
var ToolRegistry = class _ToolRegistry {
|
|
43616
46350
|
tools = /* @__PURE__ */ new Map();
|
|
46351
|
+
descriptionModes = /* @__PURE__ */ new Map();
|
|
43617
46352
|
/** Monotonic version bumped on every registry mutation. */
|
|
43618
46353
|
_version = 0;
|
|
43619
46354
|
/** Cached `list()` result, frozen after build. Invalidated on _version change. */
|
|
@@ -43625,6 +46360,10 @@ var ToolRegistry = class _ToolRegistry {
|
|
|
43625
46360
|
tool._estDefTokens = estimateToolDefTokens(tool);
|
|
43626
46361
|
}
|
|
43627
46362
|
}
|
|
46363
|
+
_prepareForStorage(tool) {
|
|
46364
|
+
const mode = this.descriptionModes.get(tool.name) ?? "extend";
|
|
46365
|
+
return applyToolDescriptionModeToTool(tool, mode);
|
|
46366
|
+
}
|
|
43628
46367
|
register(tool, owner = "core") {
|
|
43629
46368
|
if (this.tools.has(tool.name)) {
|
|
43630
46369
|
throw new WrongStackError({
|
|
@@ -43642,8 +46381,9 @@ var ToolRegistry = class _ToolRegistry {
|
|
|
43642
46381
|
context: { tool: tool.name }
|
|
43643
46382
|
});
|
|
43644
46383
|
}
|
|
43645
|
-
this.
|
|
43646
|
-
this.
|
|
46384
|
+
const stored = this._prepareForStorage(tool);
|
|
46385
|
+
this._stampDefTokens(stored);
|
|
46386
|
+
this.tools.set(tool.name, { tool: stored, owner });
|
|
43647
46387
|
this._version++;
|
|
43648
46388
|
}
|
|
43649
46389
|
/**
|
|
@@ -43656,8 +46396,9 @@ var ToolRegistry = class _ToolRegistry {
|
|
|
43656
46396
|
if (!tool.inputSchema || typeof tool.inputSchema !== "object") {
|
|
43657
46397
|
return false;
|
|
43658
46398
|
}
|
|
43659
|
-
this.
|
|
43660
|
-
this.
|
|
46399
|
+
const stored = this._prepareForStorage(tool);
|
|
46400
|
+
this._stampDefTokens(stored);
|
|
46401
|
+
this.tools.set(tool.name, { tool: stored, owner });
|
|
43661
46402
|
this._version++;
|
|
43662
46403
|
return true;
|
|
43663
46404
|
}
|
|
@@ -43683,11 +46424,15 @@ var ToolRegistry = class _ToolRegistry {
|
|
|
43683
46424
|
*/
|
|
43684
46425
|
registerDefault(tool, owner = "core") {
|
|
43685
46426
|
if (this.tools.has(tool.name)) return;
|
|
43686
|
-
this.
|
|
43687
|
-
this.
|
|
46427
|
+
const stored = this._prepareForStorage(tool);
|
|
46428
|
+
this._stampDefTokens(stored);
|
|
46429
|
+
this.tools.set(tool.name, { tool: stored, owner });
|
|
46430
|
+
this._version++;
|
|
43688
46431
|
}
|
|
43689
46432
|
unregister(name) {
|
|
43690
|
-
|
|
46433
|
+
const deleted = this.tools.delete(name);
|
|
46434
|
+
if (deleted) this._version++;
|
|
46435
|
+
return deleted;
|
|
43691
46436
|
}
|
|
43692
46437
|
/**
|
|
43693
46438
|
* Override an existing tool. Throws if the tool is not already registered.
|
|
@@ -43702,8 +46447,10 @@ var ToolRegistry = class _ToolRegistry {
|
|
|
43702
46447
|
context: { tool: name }
|
|
43703
46448
|
});
|
|
43704
46449
|
}
|
|
43705
|
-
this.
|
|
43706
|
-
this.
|
|
46450
|
+
const stored = this._prepareForStorage(tool);
|
|
46451
|
+
this._stampDefTokens(stored);
|
|
46452
|
+
this.tools.set(name, { tool: stored, owner });
|
|
46453
|
+
this._version++;
|
|
43707
46454
|
}
|
|
43708
46455
|
/**
|
|
43709
46456
|
* Wrap (decorate) an existing tool. The wrapper receives the current
|
|
@@ -43726,12 +46473,49 @@ var ToolRegistry = class _ToolRegistry {
|
|
|
43726
46473
|
context: { tool: name }
|
|
43727
46474
|
});
|
|
43728
46475
|
}
|
|
43729
|
-
const
|
|
46476
|
+
const current = applyToolDescriptionModeToTool(entry.tool, "extend");
|
|
46477
|
+
const wrapped = this._prepareForStorage(wrapper(current));
|
|
43730
46478
|
wrapped._estDefTokens = void 0;
|
|
43731
46479
|
this._stampDefTokens(wrapped);
|
|
43732
46480
|
this.tools.set(name, { tool: wrapped, owner: `${entry.owner}+${owner}` });
|
|
43733
46481
|
this._version++;
|
|
43734
46482
|
}
|
|
46483
|
+
setDescriptionMode(name, mode) {
|
|
46484
|
+
const normalized = normalizeToolDescriptionMode(mode);
|
|
46485
|
+
if (!normalized) return false;
|
|
46486
|
+
const entry = this.tools.get(name);
|
|
46487
|
+
if (!entry) return false;
|
|
46488
|
+
if (normalized === "extend") {
|
|
46489
|
+
this.descriptionModes.delete(name);
|
|
46490
|
+
} else {
|
|
46491
|
+
this.descriptionModes.set(name, normalized);
|
|
46492
|
+
}
|
|
46493
|
+
const stored = applyToolDescriptionModeToTool(entry.tool, normalized);
|
|
46494
|
+
stored._estDefTokens = void 0;
|
|
46495
|
+
this._stampDefTokens(stored);
|
|
46496
|
+
this.tools.set(name, { ...entry, tool: stored });
|
|
46497
|
+
this._version++;
|
|
46498
|
+
return true;
|
|
46499
|
+
}
|
|
46500
|
+
getDescriptionMode(name) {
|
|
46501
|
+
return this.descriptionModes.get(name) ?? "extend";
|
|
46502
|
+
}
|
|
46503
|
+
applyDescriptionModes(modes = {}) {
|
|
46504
|
+
const missing = [];
|
|
46505
|
+
let applied = 0;
|
|
46506
|
+
for (const [name, rawMode] of Object.entries(modes)) {
|
|
46507
|
+
const mode = normalizeToolDescriptionMode(rawMode);
|
|
46508
|
+
if (!mode) continue;
|
|
46509
|
+
if (this.tools.has(name)) {
|
|
46510
|
+
if (this.setDescriptionMode(name, mode)) applied++;
|
|
46511
|
+
} else {
|
|
46512
|
+
if (mode === "simple") this.descriptionModes.set(name, mode);
|
|
46513
|
+
else this.descriptionModes.delete(name);
|
|
46514
|
+
missing.push(name);
|
|
46515
|
+
}
|
|
46516
|
+
}
|
|
46517
|
+
return { applied, missing };
|
|
46518
|
+
}
|
|
43735
46519
|
get(name) {
|
|
43736
46520
|
return this.tools.get(name)?.tool;
|
|
43737
46521
|
}
|
|
@@ -43770,6 +46554,8 @@ var ToolRegistry = class _ToolRegistry {
|
|
|
43770
46554
|
}
|
|
43771
46555
|
clear() {
|
|
43772
46556
|
this.tools.clear();
|
|
46557
|
+
this.descriptionModes.clear();
|
|
46558
|
+
this._version++;
|
|
43773
46559
|
}
|
|
43774
46560
|
/**
|
|
43775
46561
|
* Return a new ToolRegistry with the same registered tools and owners.
|
|
@@ -43777,6 +46563,9 @@ var ToolRegistry = class _ToolRegistry {
|
|
|
43777
46563
|
*/
|
|
43778
46564
|
clone() {
|
|
43779
46565
|
const copy = new _ToolRegistry();
|
|
46566
|
+
for (const [name, mode] of this.descriptionModes) {
|
|
46567
|
+
copy.descriptionModes.set(name, mode);
|
|
46568
|
+
}
|
|
43780
46569
|
for (const { tool, owner } of this.listWithOwner()) copy.register(tool, owner);
|
|
43781
46570
|
return copy;
|
|
43782
46571
|
}
|
|
@@ -45138,6 +47927,12 @@ var PhaseOrchestrator = class {
|
|
|
45138
47927
|
async executeSingleTask(task, phase) {
|
|
45139
47928
|
const tracker = this.getTrackerForPhase(phase);
|
|
45140
47929
|
tracker.updateNodeStatus(task.id, "in_progress");
|
|
47930
|
+
this.emit("phase.taskStarted", {
|
|
47931
|
+
phaseId: phase.id,
|
|
47932
|
+
taskId: task.id,
|
|
47933
|
+
taskTitle: task.title,
|
|
47934
|
+
agentName: task.assignee
|
|
47935
|
+
});
|
|
45141
47936
|
const handle = this.phaseWorktrees.get(phase.id);
|
|
45142
47937
|
return this.ctx.executeTask(task, phase.id, { cwd: handle?.dir, branch: handle?.branch });
|
|
45143
47938
|
}
|
|
@@ -45342,6 +48137,92 @@ var PhaseOrchestrator = class {
|
|
|
45342
48137
|
phase.assignedAgents = phase.assignedAgents.filter((id) => id !== agentId);
|
|
45343
48138
|
this.emit("agent.released", { phaseId, agentId });
|
|
45344
48139
|
}
|
|
48140
|
+
// ─── Interactive board mutations ──────────────────────────────────────────
|
|
48141
|
+
//
|
|
48142
|
+
// These are driven by an interactive board (WebUI/TUI), not the autonomous
|
|
48143
|
+
// loop. Each mutates the live graph, emits a typed event so every surface
|
|
48144
|
+
// stays in sync, and bumps updatedAt so the host re-persists.
|
|
48145
|
+
/** Find the phase whose task graph currently holds `taskId`. */
|
|
48146
|
+
findPhaseOfTask(taskId) {
|
|
48147
|
+
for (const phase of this.graph.phases.values()) {
|
|
48148
|
+
if (phase.taskGraph.nodes.has(taskId)) return phase;
|
|
48149
|
+
}
|
|
48150
|
+
return void 0;
|
|
48151
|
+
}
|
|
48152
|
+
/**
|
|
48153
|
+
* Move a task to another phase's task graph. Edges that referenced the task
|
|
48154
|
+
* are dropped (cross-phase dependencies are not modeled). No-op when the task
|
|
48155
|
+
* or target phase is missing, or it is already in the target phase.
|
|
48156
|
+
*/
|
|
48157
|
+
moveTask(taskId, toPhaseId) {
|
|
48158
|
+
const from = this.findPhaseOfTask(taskId);
|
|
48159
|
+
const to = this.graph.phases.get(toPhaseId);
|
|
48160
|
+
if (!from || !to || from.id === toPhaseId) return false;
|
|
48161
|
+
const node = from.taskGraph.nodes.get(taskId);
|
|
48162
|
+
if (!node) return false;
|
|
48163
|
+
from.taskGraph.nodes.delete(taskId);
|
|
48164
|
+
from.taskGraph.rootNodes = from.taskGraph.rootNodes.filter((id) => id !== taskId);
|
|
48165
|
+
from.taskGraph.edges = from.taskGraph.edges.filter((e) => e.from !== taskId && e.to !== taskId);
|
|
48166
|
+
from.taskGraph.updatedAt = Date.now();
|
|
48167
|
+
node.parentId = void 0;
|
|
48168
|
+
node.children = void 0;
|
|
48169
|
+
node.updatedAt = Date.now();
|
|
48170
|
+
to.taskGraph.nodes.set(taskId, node);
|
|
48171
|
+
to.taskGraph.rootNodes.push(taskId);
|
|
48172
|
+
to.taskGraph.updatedAt = Date.now();
|
|
48173
|
+
this.trackerCache.delete(from.id);
|
|
48174
|
+
this.trackerCache.delete(to.id);
|
|
48175
|
+
this.graph.updatedAt = Date.now();
|
|
48176
|
+
this.emit("phase.taskMoved", { taskId, fromPhaseId: from.id, toPhaseId });
|
|
48177
|
+
return true;
|
|
48178
|
+
}
|
|
48179
|
+
/** (Re)assign a task to a specific agent (or clear with agentName/agentId omitted). */
|
|
48180
|
+
setTaskAssignee(taskId, agentId, agentName) {
|
|
48181
|
+
const phase = this.findPhaseOfTask(taskId);
|
|
48182
|
+
if (!phase) return false;
|
|
48183
|
+
const tracker = this.getTrackerForPhase(phase);
|
|
48184
|
+
tracker.updateNode(taskId, { assignee: agentName ?? agentId ?? "" });
|
|
48185
|
+
this.graph.updatedAt = Date.now();
|
|
48186
|
+
this.emit("phase.taskAssigned", { phaseId: phase.id, taskId, agentId, agentName });
|
|
48187
|
+
return true;
|
|
48188
|
+
}
|
|
48189
|
+
/** Add a new task to a phase. Returns the created task id, or null if the phase is missing. */
|
|
48190
|
+
addTask(phaseId, spec) {
|
|
48191
|
+
const phase = this.graph.phases.get(phaseId);
|
|
48192
|
+
if (!phase) return null;
|
|
48193
|
+
const tracker = this.getTrackerForPhase(phase);
|
|
48194
|
+
const node = tracker.addNode({
|
|
48195
|
+
title: spec.title,
|
|
48196
|
+
description: spec.description ?? "",
|
|
48197
|
+
type: spec.type ?? "feature",
|
|
48198
|
+
priority: spec.priority ?? "medium",
|
|
48199
|
+
status: "pending"
|
|
48200
|
+
});
|
|
48201
|
+
this.graph.updatedAt = Date.now();
|
|
48202
|
+
this.emit("phase.taskAdded", { phaseId, taskId: node.id, taskTitle: node.title });
|
|
48203
|
+
return node.id;
|
|
48204
|
+
}
|
|
48205
|
+
/**
|
|
48206
|
+
* Requeue a task to `pending` (clearing its retry counter) and nudge a
|
|
48207
|
+
* terminal/paused phase back to `ready` so the loop re-runs it. Backs both the
|
|
48208
|
+
* board's "retry" and "start" affordances.
|
|
48209
|
+
*/
|
|
48210
|
+
requeueTask(taskId) {
|
|
48211
|
+
const phase = this.findPhaseOfTask(taskId);
|
|
48212
|
+
if (!phase) return false;
|
|
48213
|
+
const tracker = this.getTrackerForPhase(phase);
|
|
48214
|
+
tracker.updateNodeStatus(taskId, "pending");
|
|
48215
|
+
this.taskRetryCounts.delete(`${phase.id}:${taskId}`);
|
|
48216
|
+
if (phase.status === "completed" || phase.status === "failed" || phase.status === "paused") {
|
|
48217
|
+
this.graph.failedPhaseIds = this.graph.failedPhaseIds.filter((id) => id !== phase.id);
|
|
48218
|
+
this.graph.completedPhaseIds = this.graph.completedPhaseIds.filter((id) => id !== phase.id);
|
|
48219
|
+
this.graph.activePhaseIds = this.graph.activePhaseIds.filter((id) => id !== phase.id);
|
|
48220
|
+
this.phaseWorktrees.delete(phase.id);
|
|
48221
|
+
this.updatePhaseStatus(phase, "pending");
|
|
48222
|
+
}
|
|
48223
|
+
this.graph.updatedAt = Date.now();
|
|
48224
|
+
return true;
|
|
48225
|
+
}
|
|
45345
48226
|
// ─── Events ───────────────────────────────────────────────────────────────
|
|
45346
48227
|
emit(event, payload) {
|
|
45347
48228
|
this.events.emit(event, payload);
|
|
@@ -46067,346 +48948,6 @@ var CheckpointManager = class {
|
|
|
46067
48948
|
}
|
|
46068
48949
|
}
|
|
46069
48950
|
};
|
|
46070
|
-
var MAX_SLUG = 40;
|
|
46071
|
-
var WorktreeManager = class {
|
|
46072
|
-
projectRoot;
|
|
46073
|
-
events;
|
|
46074
|
-
gitBin;
|
|
46075
|
-
runGit;
|
|
46076
|
-
/** Keyed by ownerId. */
|
|
46077
|
-
handles = /* @__PURE__ */ new Map();
|
|
46078
|
-
usedSlugs = /* @__PURE__ */ new Set();
|
|
46079
|
-
constructor(opts) {
|
|
46080
|
-
this.projectRoot = resolve(opts.projectRoot);
|
|
46081
|
-
this.events = opts.events;
|
|
46082
|
-
this.gitBin = opts.gitBin ?? "git";
|
|
46083
|
-
this.runGit = opts.run ?? ((args, cwd) => this.defaultRun(args, cwd));
|
|
46084
|
-
}
|
|
46085
|
-
/** Create a fresh worktree + branch forked from the current base branch. */
|
|
46086
|
-
async allocate(ownerId, opts = {}) {
|
|
46087
|
-
const existing = this.handles.get(ownerId);
|
|
46088
|
-
if (existing && (existing.status === "allocating" || existing.status === "active")) {
|
|
46089
|
-
return existing;
|
|
46090
|
-
}
|
|
46091
|
-
const slug = this.makeSlug(opts.slugHint ?? ownerId);
|
|
46092
|
-
const branch = `wstack/ap/${slug}`;
|
|
46093
|
-
const dir = join(this.worktreesRoot(), slug);
|
|
46094
|
-
const absDir = resolve(dir);
|
|
46095
|
-
const absRoot = resolve(this.projectRoot);
|
|
46096
|
-
if (!absDir.startsWith(absRoot + sep)) {
|
|
46097
|
-
throw new Error(`Worktree dir "${absDir}" resolves outside project root`);
|
|
46098
|
-
}
|
|
46099
|
-
const baseBranch = opts.baseBranch ?? await this.detectBaseBranch();
|
|
46100
|
-
const handle = {
|
|
46101
|
-
id: slug,
|
|
46102
|
-
ownerId,
|
|
46103
|
-
ownerLabel: opts.ownerLabel ?? opts.slugHint ?? ownerId,
|
|
46104
|
-
slug,
|
|
46105
|
-
dir,
|
|
46106
|
-
branch,
|
|
46107
|
-
baseBranch,
|
|
46108
|
-
status: "allocating",
|
|
46109
|
-
createdAt: Date.now(),
|
|
46110
|
-
updatedAt: Date.now(),
|
|
46111
|
-
insertions: 0,
|
|
46112
|
-
deletions: 0,
|
|
46113
|
-
files: 0
|
|
46114
|
-
};
|
|
46115
|
-
this.handles.set(ownerId, handle);
|
|
46116
|
-
try {
|
|
46117
|
-
await mkdir(this.worktreesRoot(), { recursive: true });
|
|
46118
|
-
const res = await this.runGit(
|
|
46119
|
-
["worktree", "add", "-b", branch, dir, baseBranch],
|
|
46120
|
-
this.projectRoot
|
|
46121
|
-
);
|
|
46122
|
-
if (res.code !== 0) {
|
|
46123
|
-
return this.fail(handle, res.stderr || "git worktree add failed");
|
|
46124
|
-
}
|
|
46125
|
-
} catch (err) {
|
|
46126
|
-
return this.fail(handle, toErrorMessage(err));
|
|
46127
|
-
}
|
|
46128
|
-
this.setStatus(handle, "active");
|
|
46129
|
-
this.emit("worktree.allocated", {
|
|
46130
|
-
handleId: handle.id,
|
|
46131
|
-
ownerId: handle.ownerId,
|
|
46132
|
-
ownerLabel: handle.ownerLabel,
|
|
46133
|
-
slug: handle.slug,
|
|
46134
|
-
dir: handle.dir,
|
|
46135
|
-
branch: handle.branch,
|
|
46136
|
-
baseBranch: handle.baseBranch
|
|
46137
|
-
});
|
|
46138
|
-
return handle;
|
|
46139
|
-
}
|
|
46140
|
-
/** Stage everything and commit inside the worktree. */
|
|
46141
|
-
async commitAll(handle, message) {
|
|
46142
|
-
this.setStatus(handle, "committing");
|
|
46143
|
-
await this.runGit(["add", "-A"], handle.dir);
|
|
46144
|
-
const staged = await this.runGit(["diff", "--cached", "--quiet"], handle.dir);
|
|
46145
|
-
if (staged.code === 0) {
|
|
46146
|
-
this.emitCommitted(handle, false);
|
|
46147
|
-
return { committed: false };
|
|
46148
|
-
}
|
|
46149
|
-
const idArgs = await this.identityArgs(handle.dir);
|
|
46150
|
-
const committed = await this.runGit([...idArgs, "commit", "-m", message], handle.dir);
|
|
46151
|
-
if (committed.code !== 0) {
|
|
46152
|
-
this.fail(handle, committed.stderr || "git commit failed");
|
|
46153
|
-
return { committed: false };
|
|
46154
|
-
}
|
|
46155
|
-
const stats = await this.collectStats(handle.dir);
|
|
46156
|
-
handle.insertions = stats.insertions;
|
|
46157
|
-
handle.deletions = stats.deletions;
|
|
46158
|
-
handle.files = stats.files;
|
|
46159
|
-
handle.sha = stats.sha;
|
|
46160
|
-
handle.updatedAt = Date.now();
|
|
46161
|
-
this.emitCommitted(handle, true);
|
|
46162
|
-
return { committed: true };
|
|
46163
|
-
}
|
|
46164
|
-
/** Merge the worktree branch back into the base branch (squash by default). */
|
|
46165
|
-
async merge(handle, opts = {}) {
|
|
46166
|
-
const squash = opts.squash ?? true;
|
|
46167
|
-
this.setStatus(handle, "merging");
|
|
46168
|
-
const checkout = await this.runGit(["checkout", handle.baseBranch], this.projectRoot);
|
|
46169
|
-
if (checkout.code !== 0) {
|
|
46170
|
-
this.fail(handle, checkout.stderr || `checkout ${handle.baseBranch} failed`);
|
|
46171
|
-
return { ok: false, stderr: checkout.stderr };
|
|
46172
|
-
}
|
|
46173
|
-
const mergeArgs = squash ? ["merge", "--squash", handle.branch] : ["merge", "--no-ff", handle.branch];
|
|
46174
|
-
const merged = await this.runGit(mergeArgs, this.projectRoot);
|
|
46175
|
-
if (merged.code !== 0) {
|
|
46176
|
-
const fromOutput = parseConflictPaths(`${merged.stdout}
|
|
46177
|
-
${merged.stderr}`);
|
|
46178
|
-
const fromIndex = await this.unmergedFiles();
|
|
46179
|
-
const conflictFiles = [.../* @__PURE__ */ new Set([...fromOutput, ...fromIndex])];
|
|
46180
|
-
if (opts.resolve) {
|
|
46181
|
-
const finalized = await this.tryResolveConflict(handle, conflictFiles, opts);
|
|
46182
|
-
if (finalized) return finalized;
|
|
46183
|
-
}
|
|
46184
|
-
await this.runGit(["reset", "--hard", "HEAD"], this.projectRoot);
|
|
46185
|
-
handle.conflictFiles = conflictFiles;
|
|
46186
|
-
this.setStatus(handle, "needs-review", { lastError: merged.stderr });
|
|
46187
|
-
this.emit("worktree.conflict", {
|
|
46188
|
-
handleId: handle.id,
|
|
46189
|
-
ownerId: handle.ownerId,
|
|
46190
|
-
branch: handle.branch,
|
|
46191
|
-
conflictFiles
|
|
46192
|
-
});
|
|
46193
|
-
return { ok: false, conflict: true, conflictFiles, stderr: merged.stderr };
|
|
46194
|
-
}
|
|
46195
|
-
if (squash) {
|
|
46196
|
-
const msg = opts.message ?? `merge ${handle.branch} (squash)`;
|
|
46197
|
-
const idArgs = await this.identityArgs(this.projectRoot);
|
|
46198
|
-
const commit = await this.runGit([...idArgs, "commit", "-m", msg], this.projectRoot);
|
|
46199
|
-
if (commit.code !== 0 && !/nothing to commit/i.test(commit.stdout + commit.stderr)) {
|
|
46200
|
-
this.fail(handle, commit.stderr || "squash commit failed");
|
|
46201
|
-
return { ok: false, stderr: commit.stderr };
|
|
46202
|
-
}
|
|
46203
|
-
}
|
|
46204
|
-
this.setStatus(handle, "merged");
|
|
46205
|
-
this.emit("worktree.merged", {
|
|
46206
|
-
handleId: handle.id,
|
|
46207
|
-
ownerId: handle.ownerId,
|
|
46208
|
-
branch: handle.branch,
|
|
46209
|
-
baseBranch: handle.baseBranch,
|
|
46210
|
-
squash
|
|
46211
|
-
});
|
|
46212
|
-
return { ok: true };
|
|
46213
|
-
}
|
|
46214
|
-
/**
|
|
46215
|
-
* Run the caller-supplied resolver against a conflicted squash-merge, then
|
|
46216
|
-
* commit if it cleared every marker. Returns a successful `MergeResult` on a
|
|
46217
|
-
* clean resolution, or `null` to signal the caller should fall back to the
|
|
46218
|
-
* abort path. Never leaves the base tree committed-but-dirty: a partial or
|
|
46219
|
-
* failed resolution returns `null` and the caller hard-resets.
|
|
46220
|
-
*/
|
|
46221
|
-
async tryResolveConflict(handle, conflictFiles, opts) {
|
|
46222
|
-
let resolved = false;
|
|
46223
|
-
try {
|
|
46224
|
-
resolved = opts.resolve ? await opts.resolve({ conflictFiles, cwd: this.projectRoot }) : false;
|
|
46225
|
-
} catch {
|
|
46226
|
-
resolved = false;
|
|
46227
|
-
}
|
|
46228
|
-
if (!resolved) return null;
|
|
46229
|
-
await this.runGit(["add", "-A"], this.projectRoot);
|
|
46230
|
-
if (await this.hasConflictMarkers()) return null;
|
|
46231
|
-
const idArgs = await this.identityArgs(this.projectRoot);
|
|
46232
|
-
const msg = opts.message ?? `merge ${handle.branch} (squash, conflict resolved)`;
|
|
46233
|
-
const commit = await this.runGit([...idArgs, "commit", "-m", msg], this.projectRoot);
|
|
46234
|
-
if (commit.code !== 0 && !/nothing to commit/i.test(commit.stdout + commit.stderr)) {
|
|
46235
|
-
return null;
|
|
46236
|
-
}
|
|
46237
|
-
handle.conflictFiles = conflictFiles;
|
|
46238
|
-
this.setStatus(handle, "merged");
|
|
46239
|
-
this.emit("worktree.merged", {
|
|
46240
|
-
handleId: handle.id,
|
|
46241
|
-
ownerId: handle.ownerId,
|
|
46242
|
-
branch: handle.branch,
|
|
46243
|
-
baseBranch: handle.baseBranch,
|
|
46244
|
-
squash: true
|
|
46245
|
-
});
|
|
46246
|
-
return { ok: true, resolved: true, conflictFiles };
|
|
46247
|
-
}
|
|
46248
|
-
/**
|
|
46249
|
-
* True when staged content still carries conflict markers. `git diff --cached
|
|
46250
|
-
* --check` exits nonzero and prints a "leftover conflict marker" line for each
|
|
46251
|
-
* survivor; whitespace-only errors (also flagged by --check) are ignored so a
|
|
46252
|
-
* clean resolution with unrelated whitespace is not rejected.
|
|
46253
|
-
*/
|
|
46254
|
-
async hasConflictMarkers() {
|
|
46255
|
-
const check = await this.runGit(["diff", "--cached", "--check"], this.projectRoot);
|
|
46256
|
-
if (check.code === 0) return false;
|
|
46257
|
-
return /conflict marker/i.test(`${check.stdout}
|
|
46258
|
-
${check.stderr}`);
|
|
46259
|
-
}
|
|
46260
|
-
/**
|
|
46261
|
-
* Remove the worktree + branch. Conflicted/failed handles (or `keep:true`)
|
|
46262
|
-
* are left on disk for inspection.
|
|
46263
|
-
*/
|
|
46264
|
-
async release(handle, opts = {}) {
|
|
46265
|
-
const keep = opts.keep || handle.status === "needs-review" || handle.status === "failed";
|
|
46266
|
-
if (!keep) {
|
|
46267
|
-
await this.runGit(["worktree", "remove", "--force", handle.dir], this.projectRoot);
|
|
46268
|
-
await this.runGit(["branch", "-D", handle.branch], this.projectRoot);
|
|
46269
|
-
await this.runGit(["worktree", "prune"], this.projectRoot);
|
|
46270
|
-
this.handles.delete(handle.ownerId);
|
|
46271
|
-
}
|
|
46272
|
-
this.emit("worktree.released", {
|
|
46273
|
-
handleId: handle.id,
|
|
46274
|
-
ownerId: handle.ownerId,
|
|
46275
|
-
branch: handle.branch,
|
|
46276
|
-
kept: keep
|
|
46277
|
-
});
|
|
46278
|
-
}
|
|
46279
|
-
get(ownerId) {
|
|
46280
|
-
return this.handles.get(ownerId);
|
|
46281
|
-
}
|
|
46282
|
-
list() {
|
|
46283
|
-
return [...this.handles.values()];
|
|
46284
|
-
}
|
|
46285
|
-
// ── internals ────────────────────────────────────────────────────────────
|
|
46286
|
-
worktreesRoot() {
|
|
46287
|
-
return join(this.projectRoot, ".wrongstack", "worktrees");
|
|
46288
|
-
}
|
|
46289
|
-
async detectBaseBranch() {
|
|
46290
|
-
const head = await this.runGit(["rev-parse", "--abbrev-ref", "HEAD"], this.projectRoot);
|
|
46291
|
-
const name = head.stdout.trim();
|
|
46292
|
-
if (name && name !== "HEAD") return name;
|
|
46293
|
-
const sha = await this.runGit(["rev-parse", "HEAD"], this.projectRoot);
|
|
46294
|
-
return sha.stdout.trim() || "HEAD";
|
|
46295
|
-
}
|
|
46296
|
-
makeSlug(hint) {
|
|
46297
|
-
let base = hint.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").replace(/^[-.]+/, "").replace(/[-.]+$/, "").slice(0, MAX_SLUG).replace(/[-.]+$/, "");
|
|
46298
|
-
if (!base) base = "wt";
|
|
46299
|
-
let slug = `${base}-${crypto.randomUUID().slice(0, 6)}`;
|
|
46300
|
-
while (this.usedSlugs.has(slug)) slug = `${base}-${crypto.randomUUID().slice(0, 6)}`;
|
|
46301
|
-
this.usedSlugs.add(slug);
|
|
46302
|
-
return slug;
|
|
46303
|
-
}
|
|
46304
|
-
async collectStats(dir) {
|
|
46305
|
-
const sha = (await this.runGit(["rev-parse", "HEAD"], dir)).stdout.trim();
|
|
46306
|
-
const numstat = await this.runGit(["show", "--numstat", "--format=", "HEAD"], dir);
|
|
46307
|
-
let insertions = 0;
|
|
46308
|
-
let deletions = 0;
|
|
46309
|
-
let files = 0;
|
|
46310
|
-
for (const line of numstat.stdout.split("\n")) {
|
|
46311
|
-
const m = line.trim().match(/^(\d+|-)\t(\d+|-)\t(.+)$/);
|
|
46312
|
-
if (!m) continue;
|
|
46313
|
-
files++;
|
|
46314
|
-
if (m[1] !== "-") insertions += Number(m[1]);
|
|
46315
|
-
if (m[2] !== "-") deletions += Number(m[2]);
|
|
46316
|
-
}
|
|
46317
|
-
return { insertions, deletions, files, sha };
|
|
46318
|
-
}
|
|
46319
|
-
/**
|
|
46320
|
-
* `git -c user.*` fallback so commits succeed on machines and CI runners
|
|
46321
|
-
* that have no global git identity configured. Returns `[]` when both
|
|
46322
|
-
* `user.name` and `user.email` are already set (the common case), so a real
|
|
46323
|
-
* user's identity is never overridden. The worktree branch commits are
|
|
46324
|
-
* squashed away on merge, so the fallback identity never reaches the base
|
|
46325
|
-
* branch history.
|
|
46326
|
-
*/
|
|
46327
|
-
async identityArgs(cwd) {
|
|
46328
|
-
const name = (await this.runGit(["config", "user.name"], cwd)).stdout.trim();
|
|
46329
|
-
const email = (await this.runGit(["config", "user.email"], cwd)).stdout.trim();
|
|
46330
|
-
if (name && email) return [];
|
|
46331
|
-
return [
|
|
46332
|
-
"-c",
|
|
46333
|
-
`user.name=${name || "AutoPhase"}`,
|
|
46334
|
-
"-c",
|
|
46335
|
-
`user.email=${email || "autophase@agent.local"}`
|
|
46336
|
-
];
|
|
46337
|
-
}
|
|
46338
|
-
async unmergedFiles() {
|
|
46339
|
-
const res = await this.runGit(["diff", "--name-only", "--diff-filter=U"], this.projectRoot);
|
|
46340
|
-
return res.stdout.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
46341
|
-
}
|
|
46342
|
-
emitCommitted(handle, committed) {
|
|
46343
|
-
this.emit("worktree.committed", {
|
|
46344
|
-
handleId: handle.id,
|
|
46345
|
-
ownerId: handle.ownerId,
|
|
46346
|
-
branch: handle.branch,
|
|
46347
|
-
committed,
|
|
46348
|
-
insertions: handle.insertions,
|
|
46349
|
-
deletions: handle.deletions,
|
|
46350
|
-
files: handle.files,
|
|
46351
|
-
sha: handle.sha
|
|
46352
|
-
});
|
|
46353
|
-
}
|
|
46354
|
-
fail(handle, error) {
|
|
46355
|
-
this.setStatus(handle, "failed", { lastError: error });
|
|
46356
|
-
this.emit("worktree.failed", {
|
|
46357
|
-
handleId: handle.id,
|
|
46358
|
-
ownerId: handle.ownerId,
|
|
46359
|
-
branch: handle.branch,
|
|
46360
|
-
error
|
|
46361
|
-
});
|
|
46362
|
-
return handle;
|
|
46363
|
-
}
|
|
46364
|
-
setStatus(handle, status, patch) {
|
|
46365
|
-
handle.status = status;
|
|
46366
|
-
handle.updatedAt = Date.now();
|
|
46367
|
-
if (patch) Object.assign(handle, patch);
|
|
46368
|
-
}
|
|
46369
|
-
emit(event, payload) {
|
|
46370
|
-
this.events?.emit(event, payload);
|
|
46371
|
-
}
|
|
46372
|
-
defaultRun(args, cwd) {
|
|
46373
|
-
return new Promise((res) => {
|
|
46374
|
-
let stdout = "";
|
|
46375
|
-
let stderr = "";
|
|
46376
|
-
const MAX_GIT_OUTPUT = 1e6;
|
|
46377
|
-
const child = spawn(this.gitBin, args, {
|
|
46378
|
-
cwd,
|
|
46379
|
-
env: buildChildEnv(),
|
|
46380
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
46381
|
-
signal: AbortSignal.timeout(3e4),
|
|
46382
|
-
windowsHide: true
|
|
46383
|
-
});
|
|
46384
|
-
child.stdout?.on("data", (c) => {
|
|
46385
|
-
if (stdout.length < MAX_GIT_OUTPUT) stdout += c.toString();
|
|
46386
|
-
});
|
|
46387
|
-
child.stderr?.on("data", (c) => {
|
|
46388
|
-
if (stderr.length < MAX_GIT_OUTPUT) stderr += c.toString();
|
|
46389
|
-
});
|
|
46390
|
-
child.on("error", (err) => res({ code: 1, stdout, stderr: err.message }));
|
|
46391
|
-
child.on("close", (code) => res({ code: code ?? 1, stdout, stderr }));
|
|
46392
|
-
});
|
|
46393
|
-
}
|
|
46394
|
-
};
|
|
46395
|
-
function parseConflictPaths(output) {
|
|
46396
|
-
const paths = /* @__PURE__ */ new Set();
|
|
46397
|
-
for (const line of output.split("\n")) {
|
|
46398
|
-
const m = line.match(/^CONFLICT \([^)]*\): Merge conflict in (.+?)\s*$/);
|
|
46399
|
-
if (m?.[1]) paths.add(m[1]);
|
|
46400
|
-
}
|
|
46401
|
-
return [...paths];
|
|
46402
|
-
}
|
|
46403
|
-
function assertSafePath(dir, projectRoot) {
|
|
46404
|
-
const root = resolve(projectRoot);
|
|
46405
|
-
const abs = resolve(dir);
|
|
46406
|
-
if (abs !== root && !abs.startsWith(root + sep)) {
|
|
46407
|
-
throw new Error(`worktree path escapes project root: ${dir}`);
|
|
46408
|
-
}
|
|
46409
|
-
}
|
|
46410
48951
|
|
|
46411
48952
|
// src/coordination/collab-bus.ts
|
|
46412
48953
|
var CollaborationBus = class {
|
|
@@ -46905,8 +49446,8 @@ function extractManifestPath(msg) {
|
|
|
46905
49446
|
}
|
|
46906
49447
|
return void 0;
|
|
46907
49448
|
}
|
|
46908
|
-
function isManifestFile(
|
|
46909
|
-
const name = pathBasename(
|
|
49449
|
+
function isManifestFile(path52) {
|
|
49450
|
+
const name = pathBasename(path52).toLowerCase();
|
|
46910
49451
|
const manifests = [
|
|
46911
49452
|
"package.json",
|
|
46912
49453
|
"package-lock.json",
|
|
@@ -48416,6 +50957,6 @@ function createChimeraPlugin() {
|
|
|
48416
50957
|
};
|
|
48417
50958
|
}
|
|
48418
50959
|
|
|
48419
|
-
export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, ALL_SYNC_CATEGORIES, AUDIT_LOG_AGENT, AdaptiveConcurrencyController, Agent, AgentError, AgentMonitorService, AgentStatusTracker, AnnotationsStore, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousBrain, AutonomousCoordinator, AutonomousRunner, BUG_HUNTER_AGENT, BUILD_AGENTS, BrainDecisionQueue, BrainMonitor, BudgetExceededError, BudgetThresholdSignal, CHIMERA_REVIEW_PROMPT, CONTEXT_WINDOW_MODES, CORE_RECONSTRUCT_EVENTS, ChangeManager, CheckpointManager, CloudSync, CollabSession, CollaborationBus, ConfigError, ConfigMigrationError, ConsensusProtocol, Container, Context, ConversationState, DANGEROUS_FOR_SUBAGENTS, DECISION_TIMEOUT_MS, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CIRCUIT_BREAKER_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_HQ_REDACTION_POLICY, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_QUALITY_CHECKS, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SESSION_PRUNE_DAYS, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DEFAULT_TUI_THINKING_WORD, DELIVERY_AGENTS, DEPENDENCY_FILE_PATTERNS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultAttachmentStore, DefaultBrainArbiter, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMailbox, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultPromptStore, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorAlertLevel, DirectorStateCheckpoint, DoneConditionChecker, ENHANCER_SYSTEM_PROMPT, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FORBIDDEN_PROTO_KEYS, FileMemoryBackend, FleetBus, FleetCostCapError, FleetManager, FleetNotifier, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, GlobalMailbox, GraphMemoryBackend, HEAVY_BUDGET, HQ_AUTH_FILE_VERSION, HQ_PROTOCOL_VERSION, HookRegistry, HookRunner, HqPublisher, HumanEscalatingBrainArbiter, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, KNOWLEDGE_AGENTS, KnowledgeGraph, LAYER_1_IDENTITY, LIGHT_BUDGET, LLMSelector, LargeAnswerStore, MATRIX_PHASE_KEYS, MAX_JOURNAL_ENTRIES, MAX_PROGRESS_HISTORY, MAX_TUI_THINKING_WORD_LENGTH, MEDIUM_BUDGET, MEMORY_TYPE_LABELS, META_AGENTS, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, ObservableBrainArbiter, PLANNING_AGENTS, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, RecoveryLock, ReplayLogStore, ReplayProviderRunner, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, STANDARD_AUDIT_EVENTS, ScopedEventBus, SddError, SddParallelRun, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SessionMemoryConsolidator, SessionRecovery, SessionRegistry, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, StreamHangError, SubagentBudget, TIMEOUT_PREEMPT_FRACTION, TOKENS, TaskAuctioneer, TaskDAG, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolAuditLog, ToolCapabilities, ToolError, ToolErrorCategory, ToolExecutor, ToolRegistry, VERIFY_AGENTS, WIDE_SUBAGENT_CAPABILITIES, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyModelRuntime, applyRosterBudget, asBlocks, asText, assertNever, assertNotPrivateHost, assertSafePath, assessCommitSafety, atomicWrite, attachAutoExtend, attachDepWatcherBridge, attachMailboxChecker, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, bootConfig, braveSearchServer, buildBtwBlock, buildChildEnv, buildContextEvidenceDigest, buildGoalPreamble, buildLosslessDigest, buildMailboxBlock, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildQueuedMessagesBlock, buildRecoveryStrategies, buildSmartDigest, buildTranscriptFromEvents, classifyFamily, clearPlan, collabInjectMiddleware, collabPauseMiddleware, color, compactLog, compactSchemaDescriptions, compactToolDefinitionForWire, compileGlob, compileUserRegex, completePartialObject, composeDirectorPrompt, composeSubagentPrompt, computeMessageTokens, computeTaskItemProgress, computeTaskProgress, consumeBtwNotes, consumeQueuedMessagesUpdate, context7Server, contextManagerTool, createAgentMonitorService, createAutoExecutor, createAutoPhaseFromTaskGraph, createAutonomyBrain, createChimeraPlugin, createContextEvidenceState, createContextManagerTool, createDefaultPipelines, createDelegateTool, createGitPlugin, createGlobalMailbox, createHqEventEnvelope, createHqPublisherFromEnv, createMailboxChecker, createMailboxEventPayload, createMailboxHooks, createMailboxSnapshotPayload, createMailboxSnapshotPayloadFromMailbox, createMcpControlTool, createMcpUseTool, createMessage, createObservabilityPlugin, createPlanPlugin, createPromptsPlugin, createSecurityPlugin, createSecuritySlashCommand, createSessionEventBridge, createSkillsPlugin, createStrategyCompactor, createSyncPlugin, createTieredBrainArbiter, createToolOutputSerializer, decryptConfigSecrets, deepMerge, defaultGitignoreUpdater, defaultHqDataDir, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, definePlugin, deriveTodosFromPlanItem, describeCatalogModel, detectEcosystem, detectNewlineStyle, detectEcosystem as detectPackageEcosystem, dispatchAgent, downloadGitHubTarball, eliseOldToolResults, emptyGoal, emptyHqAuthFile, emptyPlan, emptyTaskFile, encryptConfigSecrets, encryptedPrefixForVersion, enhanceUserPrompt, ensureDir, ensureHqFirstRunAuthFile, escapeGlobSubject, estimateMessageTokens, estimateMessages, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, expandGlob, expandIPv6, expectDefined, extractRunEnv, extractText, filesystemServer, findCriticalPath, findPreserveStart, flagsToConfigPatch, formatContextWindowModeList, formatDecisionSummary, formatGoal, formatHumanPrompt, formatPlan, formatPlanTemplates, formatTaskList, formatTaskProgress, formatTodosList, gatedEnhancerReasoning, generateSessionId, getAgentDefinition, getCalibrationState, getContextWindowMode, getDangerousCapabilities, getFileHistory, getFilesByAgent, getFullLog, getFullPackageLog, getJsonPath, getLastAuthor, getManifestPackages, getPackageAuthor, getPackagesByAgent, getPlanTemplate, getSessionRegistry, getTemplate, getTermSize, githubServer, goalFilePath, googleMapsServer, hasCapability, hasDangerousCapabilityForSubagents, hasSessionRegistry, hasTextContent, hashRequest, hookMatcherMatches, hqAuthFilePath, hqRuntimeFilePath, injectPendingMailboxMessages, isAgentError, isConfigError, isContextWindowModeId, isFsError, isImageBlock, isInteractive, isJsonObject, isPathSubjectKey, isPluginError, isPrimitiveArray, isPrivateIPv4, isPrivateIPv6, isSddError, isSecretField, isSessionError, isStdinTTY, isStdoutTTY, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isValidMatrixKey, isWrongStackError, jsonObjectFileExists, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTasks, loadTodosCheckpoint, loadUserModes, mailboxSessionTag, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeContinueToNextIterationTool, makeDependencyWatcherConfig, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeMailInboxTool, makeMailSendTool, makeMailboxTool, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, mapMailboxAgentToHqSummary, mapMailboxMessageToHqSummary, mapSessionEventToEntries, markAssistantReferencedEvidence, matchAny, matchGlob, matrixKeyKind, mergeCustomModelDefs, mergeModelsPayload, mergeToolResults, migratePlaintextSecrets, miniMaxVisionServer, mintHqBrowserToken, mintHqToken, mutateHqAuthFile, mutatePlan, mutateTasks, noOpLogger, noOpVault, normalizePathSubject, normalizeRecipient, normalizeToLf, normalizeTokenSavingTier, normalizeTuiThinkingWord, normalizedEqual, onResize, parseContinueDirective, parseEncryptedVersion, parseEntries, parseHqEventPayload, parseHqFrame, parseProgressFromText, parseSkillRef, peekQueuedMessages, pendingBtwCount, phaseForRole, playwrightServer, projectHash, projectSlug, readHqAuthFile, readHqRuntimeFileSync, readJsonObjectFile, recentTextTurns, recordActualUsage, recordFileAction, recordPackageAction, recordProgress, recordToolOutputEvidence, recordUserIntentEvidence, redactHqEvent, redactHqValue, removeJsonPath, removeJsonPathInFile, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, repeatedReadPressure, resetCalibration, resolveAuditLevel, resolveCacheForRequest, resolveChimeraConfig, resolveContextWindowPolicy, resolveHqConfig, resolveHqConfigFromEnv, resolveHqDataDir, resolveMailboxIdentity, resolveModelMatrix, resolveModelRuntime, resolveProjectDir, resolveProviderModelList, resolveReasoningForRequest, resolveSessionLoggingConfig, resolveWstackPaths, rewriteConfigEncrypted, rosterSummaryFromConfigs, rotateConfigKeys, runConfigMigrations, runProviderWithRetry, runShellHook, safeParse, safeStringify, sanitizeJsonString, sanitizeModel, sanitizeNodeOptions, saveGoal, savePlan, saveTasks, saveTodosCheckpoint, scoreAgents, scoreMessage, scrubAndTruncateHqPreview, securitySlashCommand, sentinelServer, setBtwNote, setJsonPath, setJsonPathInFile, setOutputLineGuard, setPlanItemStatus, setProgress, setQueuedMessagesSnapshot, setRawMode, shouldEnhance, slackServer, sleep, sshManagerServer, stableStringify, startAgentMonitorEventBridge, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, startPackageOutdatedWatcher, startSessionTelemetryBridge, startTechStackConsumer, stripAnsi, subjectForToolInput, summarizeHqToolArgs, summarizeUsage, templateToMarkdown, toErrorMessage, toStyle, toWrongStackError, topologicalSort, truncate, unifiedDiff, unloadPlugins, updateJsonObjectFile, updatePackageOutdatedStatus, validateAgainstSchema, watchHqAuthFile, wireMetricsToEvents, withDisabledToolFiltering, withFileLock, wrapAsState, writeErr, writeHqAuthFile, writeHqRuntimeFile, writeJsonObjectFile, writeOut, wstackGlobalRoot, zaiVisionServer };
|
|
50960
|
+
export { ACP_AGENTS, AGENTS_BY_PHASE, AGENT_CATALOG, TOOLS as AGENT_TOOL_PRESETS, AISpecBuilder, ALL_AGENT_DEFINITIONS, ALL_FLEET_AGENTS, ALL_SYNC_CATEGORIES, AUDIT_LOG_AGENT, AdaptiveConcurrencyController, Agent, AgentError, AgentMonitorService, AgentStatusTracker, AnnotationsStore, AutoApprovePermissionPolicy, AutoCompactionMiddleware, AutoExecutor, AutoPhasePlanner, AutoPhaseRunner, AutonomousBrain, AutonomousCoordinator, AutonomousRunner, BUG_HUNTER_AGENT, BUILD_AGENTS, BrainDecisionQueue, BrainMonitor, BudgetExceededError, BudgetThresholdSignal, CHIMERA_REVIEW_PROMPT, CONTEXT_WINDOW_MODES, CORE_RECONSTRUCT_EVENTS, ChangeManager, CheckpointManager, CloudSync, CollabSession, CollaborationBus, ConfigError, ConfigMigrationError, ConsensusProtocol, Container, Context, ConversationState, DANGEROUS_FOR_SUBAGENTS, DECISION_TIMEOUT_MS, DEFAULT_AUTONOMY_CONFIG, DEFAULT_CIRCUIT_BREAKER_CONFIG, DEFAULT_CONFIG_MIGRATIONS, DEFAULT_CONTEXT_CONFIG, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEFAULT_DIRECTOR_PREAMBLE, DEFAULT_DISPATCH_ROLE, DEFAULT_HQ_REDACTION_POLICY, DEFAULT_MAX_ITERATIONS, DEFAULT_MODES, DEFAULT_QUALITY_CHECKS, DEFAULT_RECOVERY_STRATEGIES, DEFAULT_SESSION_LOGGING_CONFIG, DEFAULT_SESSION_PRUNE_DAYS, DEFAULT_SPEC_TEMPLATE, DEFAULT_SUBAGENT_BASELINE, DEFAULT_TOOLS_CONFIG, DEFAULT_TOOL_DESCRIPTION_MODE, DEFAULT_TUI_THINKING_WORD, DELIVERY_AGENTS, DEPENDENCY_FILE_PATTERNS, DISCOVERY_AGENTS, DOMAIN_AGENTS, DefaultAttachmentStore, DefaultBrainArbiter, DefaultConfigLoader, DefaultConfigStore, DefaultErrorHandler, DefaultHealthRegistry, DefaultLogger, DefaultMailbox, DefaultMemoryStore, DefaultModeStore, DefaultModelsRegistry, DefaultMultiAgentCoordinator, DefaultPathResolver, DefaultPermissionPolicy, DefaultPluginAPI, DefaultPromptStore, DefaultProviderRunner, DefaultRetryPolicy, DefaultSecretScrubber, DefaultSecretVault, DefaultSessionReader, DefaultSessionRewinder, DefaultSessionStore, DefaultSkillLoader, DefaultSystemPromptBuilder, DefaultTaskStore, DefaultTokenCounter, Director, DirectorAlertLevel, DirectorStateCheckpoint, DoneConditionChecker, ENHANCER_SYSTEM_PROMPT, ERROR_CODES, EternalAutonomyEngine, EventBus, ExtensionRegistry, FLEET_ROSTER, FLEET_ROSTER_BUDGETS, FLEET_ROSTER_WITHACP, FORBIDDEN_PROTO_KEYS, FileMemoryBackend, FleetBus, FleetCostCapError, FleetManager, FleetNotifier, FleetSpawnBudgetError, FleetUsageAggregator, FsError, GitignoreUpdater, GlobalMailbox, GraphMemoryBackend, HEAVY_BUDGET, HQ_AUTH_FILE_VERSION, HQ_PROTOCOL_VERSION, HookRegistry, HookRunner, HqPublisher, HumanEscalatingBrainArbiter, HybridCompactor, InMemoryAgentBridge, InMemoryBridgeTransport, InMemoryMetricsSink, InputBuilder, IntelligentCompactor, KERNEL_API_VERSION, KNOWLEDGE_AGENTS, KnowledgeGraph, LAYER_1_IDENTITY, LIGHT_BUDGET, LLMSelector, LargeAnswerStore, MATRIX_PHASE_KEYS, MAX_JOURNAL_ENTRIES, MAX_PROGRESS_HISTORY, MAX_TUI_THINKING_WORD_LENGTH, MEDIUM_BUDGET, MEMORY_TYPE_LABELS, META_AGENTS, NULL_FLEET_BUS, NoopMetricsSink, NoopTracer, OTelTracer, ObservableBrainArbiter, PLANNING_AGENTS, PROMETHEUS_CONTENT_TYPE, ParallelEternalEngine, PhaseGraphBuilder, PhaseOrchestrator, PhaseStore, Pipeline, PluginError, ProviderError, ProviderRegistry, QueueStore, REFACTOR_PLANNER_AGENT, REVIEW_AGENTS, RecoveryLock, ReplayLogStore, ReplayProviderRunner, ReportGenerator, RunController, SECURITY_SCANNER_AGENT, SPEC_TEMPLATES, STANDARD_AUDIT_EVENTS, ScopedEventBus, SddBoardProjector, SddBoardStore, SddError, SddInterviewDriver, SddParallelRun, SddRunRegistry, SddSupervisor, SddTaskDecomposer, SecurityScanner, SecurityScannerOrchestrator, SelectiveCompactor, SessionAnalyzer, SessionError, SessionMemoryConsolidator, SessionRecovery, SessionRegistry, SkillGenerator, SkillInstaller, SkillManifestStore, SlashCommandRegistry, SpecDrivenDev, SpecParser, SpecStore, SpecVersioning, StreamHangError, SubagentBudget, TIMEOUT_PREEMPT_FRACTION, TOKENS, TaskAuctioneer, TaskDAG, TaskFlow, TaskGenerator, TaskGraphStore, TaskTracker, TechStackDetector, ToolAuditLog, ToolCapabilities, ToolError, ToolErrorCategory, ToolExecutor, ToolRegistry, VERIFY_AGENTS, WIDE_SUBAGENT_CAPABILITIES, WorktreeManager, WrongStackError, addPlanItem, allServers, analyzeCriticalPath, appendJournal, applyModelRuntime, applyRosterBudget, applyToolDescriptionModeToTool, applyToolDescriptionModes, asBlocks, asText, assertNever, assertNotPrivateHost, assertSafePath, assessCommitSafety, assignNickname, atomicWrite, attachAutoExtend, attachDepWatcherBridge, attachMailboxChecker, attachPlanCheckpoint, attachTodosCheckpoint, awsServer, blockServer, bootConfig, braveSearchServer, buildBoardSnapshot, buildBoardTasks, buildBtwBlock, buildChildEnv, buildContextEvidenceDigest, buildGoalPreamble, buildLosslessDigest, buildMailboxBlock, buildOtlpMetricsRequest, buildOtlpTracesRequest, buildQueuedMessagesBlock, buildRecoveryStrategies, buildSmartDigest, buildTranscriptFromEvents, classifyFamily, cleanupSddWorktrees, clearPlan, collabInjectMiddleware, collabPauseMiddleware, color, compactLog, compactSchemaDescriptions, compactToolDefinitionForWire, compileGlob, compileUserRegex, completePartialObject, composeDirectorPrompt, composeSubagentPrompt, computeMessageTokens, computeTaskItemProgress, computeTaskProgress, consumeBtwNotes, consumeQueuedMessagesUpdate, context7Server, contextManagerTool, createAgentMonitorService, createAutoExecutor, createAutoPhaseFromTaskGraph, createAutonomyBrain, createChimeraPlugin, createContextEvidenceState, createContextManagerTool, createDefaultPipelines, createDelegateTool, createFallbackModelExtension, createGitPlugin, createGlobalMailbox, createHqEventEnvelope, createHqPublisherFromEnv, createMailboxChecker, createMailboxEventPayload, createMailboxHooks, createMailboxSnapshotPayload, createMailboxSnapshotPayloadFromMailbox, createMcpControlTool, createMcpUseTool, createMessage, createObservabilityPlugin, createPlanPlugin, createPromptsPlugin, createSecurityPlugin, createSecuritySlashCommand, createSessionEventBridge, createSkillsPlugin, createStrategyCompactor, createSyncPlugin, createTieredBrainArbiter, createToolOutputSerializer, decryptConfigSecrets, deepMerge, defaultGitignoreUpdater, defaultHqDataDir, defaultOrchestrator, defaultReportGenerator, defaultSecurityScanner, defaultSkillGenerator, defaultTechStackDetector, definePlugin, deriveTodosFromPlanItem, describeCatalogModel, destroySddProject, detectEcosystem, detectNewlineStyle, detectEcosystem as detectPackageEcosystem, dispatchAgent, downloadGitHubTarball, effectiveFallbackChain, eliseOldToolResults, emptyGoal, emptyHqAuthFile, emptyPlan, emptyTaskFile, encryptConfigSecrets, encryptedPrefixForVersion, enhanceUserPrompt, ensureDir, ensureHqFirstRunAuthFile, escapeGlobSubject, estimateMessageTokens, estimateMessages, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, everArtServer, expandGlob, expandIPv6, expectDefined, extractRunEnv, extractText, extractVerificationCommand, filesystemServer, findCriticalPath, findPreserveStart, flagsToConfigPatch, formatContextWindowModeList, formatDecisionSummary, formatGoal, formatHumanPrompt, formatPlan, formatPlanTemplates, formatTaskList, formatTaskProgress, formatTodosList, gatedEnhancerReasoning, generateSessionId, getAgentDefinition, getCalibrationState, getContextWindowMode, getDangerousCapabilities, getFileHistory, getFilesByAgent, getFullLog, getFullPackageLog, getJsonPath, getLastAuthor, getManifestPackages, getPackageAuthor, getPackagesByAgent, getPlanTemplate, getSessionRegistry, getTemplate, getTermSize, getToolDescriptionMode, githubServer, goalFilePath, googleMapsServer, hasCapability, hasConflictMarkers, hasDangerousCapabilityForSubagents, hasSessionRegistry, hasTextContent, hashRequest, hookMatcherMatches, hqAuthFilePath, hqRuntimeFilePath, injectPendingMailboxMessages, isAgentError, isConfigError, isContextWindowModeId, isExplanatoryText, isFsError, isImageBlock, isInteractive, isJsonObject, isPathSubjectKey, isPluginError, isPrimitiveArray, isPrivateIPv4, isPrivateIPv6, isSddError, isSecretField, isSessionError, isStdinTTY, isStdoutTTY, isTextBlock, isThinkingBlock, isToolError, isToolResultBlock, isToolUseBlock, isValidMatrixKey, isWrongStackError, jsonObjectFileExists, listContextWindowModes, listPlanTemplates, listTemplates, loadDirectorState, loadGoal, loadPlan, loadPlugins, loadProjectModes, loadTasks, loadTodosCheckpoint, loadUserModes, mailboxSessionTag, makeAgentSubagentRunner, makeAskResultTool, makeAskTool, makeAssignTool, makeAutonomyPromptContributor, makeAwaitTasksTool, makeCollabDebugTool, makeCommandVerifier, makeContinueToNextIterationTool, makeDependencyWatcherConfig, makeDirectorSessionFactory, makeFleetEmitTool, makeFleetHealthTool, makeFleetSessionTool, makeFleetStatusTool, makeFleetUsageTool, makeLLMClassifier, makeLlmConflictResolver, makeLlmSubtaskGenerator, makeMailInboxTool, makeMailSendTool, makeMailboxTool, makePreferSideConflictResolver, makeRollUpTool, makeSpawnTool, makeTerminateTool, makeWorkCompleteTool, mapMailboxAgentToHqSummary, mapMailboxMessageToHqSummary, mapSessionEventToEntries, markAssistantReferencedEvidence, matchAny, matchGlob, matrixKeyKind, mergeCustomModelDefs, mergeModelsPayload, mergeToolResults, migratePlaintextSecrets, miniMaxVisionServer, mintHqBrowserToken, mintHqToken, mutateHqAuthFile, mutatePlan, mutateTasks, nicknameKeyFromDisplay, noOpLogger, noOpVault, normalizePathSubject, normalizeRecipient, normalizeToLf, normalizeTokenSavingTier, normalizeToolDescriptionMode, normalizeTuiThinkingWord, normalizedEqual, onResize, parseContinueDirective, parseEncryptedVersion, parseEntries, parseHqEventPayload, parseHqFrame, parseModelRef, parseProgressFromText, parseSkillRef, peekQueuedMessages, pendingBtwCount, phaseForRole, playwrightServer, projectHash, projectSlug, readHqAuthFile, readHqRuntimeFileSync, readJsonObjectFile, recentTextTurns, recordActualUsage, recordFileAction, recordPackageAction, recordProgress, recordToolOutputEvidence, recordUserIntentEvidence, redactHqEvent, redactHqValue, removeJsonPath, removeJsonPathInFile, removePlanItem, renderProgress, renderPrometheus, renderSpecAnalysis, renderTaskGraph, renderTaskList, repairToolUseAdjacency, repeatedReadPressure, resetCalibration, resolveAuditLevel, resolveCacheForRequest, resolveChimeraConfig, resolveConflictText, resolveContextWindowPolicy, resolveHqConfig, resolveHqConfigFromEnv, resolveHqDataDir, resolveMailboxIdentity, resolveModelMatrix, resolveModelRuntime, resolveProjectDir, resolveProviderModelList, resolveReasoningForRequest, resolveSessionLoggingConfig, resolveToolDescriptionMode, resolveWstackPaths, rewriteConfigEncrypted, rollbackSddRunFromDisk, rosterSummaryFromConfigs, rotateConfigKeys, runConfigMigrations, runProviderWithRetry, runShellHook, safeParse, safeStringify, sanitizeJsonString, sanitizeModel, sanitizeNodeOptions, saveGoal, savePlan, saveTasks, saveTodosCheckpoint, scoreAgents, scoreMessage, scrubAndTruncateHqPreview, securitySlashCommand, sentinelServer, setBtwNote, setJsonPath, setJsonPathInFile, setOutputLineGuard, setPlanItemStatus, setProgress, setQueuedMessagesSnapshot, setRawMode, setToolDescriptionMode, shortIdMap, shouldEnhance, simplifyToolDescription, slackServer, sleep, smartDefaultFallbackChain, sshManagerServer, stableStringify, startAgentMonitorEventBridge, startMetricsServer, startOtlpMetricsExporter, startOtlpTraceExporter, startPackageOutdatedWatcher, startSddRun, startSessionTelemetryBridge, startTechStackConsumer, stripAnsi, subjectForToolInput, summarizeHqToolArgs, summarizeUsage, templateToMarkdown, toErrorMessage, toStyle, toWrongStackError, topologicalSort, truncate, unifiedDiff, unloadPlugins, updateJsonObjectFile, updatePackageOutdatedStatus, validateAgainstSchema, watchHqAuthFile, wireMetricsToEvents, withDisabledToolFiltering, withFileLock, wrapAsState, writeErr, writeHqAuthFile, writeHqRuntimeFile, writeJsonObjectFile, writeOut, wstackGlobalRoot, zaiVisionServer };
|
|
48420
50961
|
//# sourceMappingURL=index.js.map
|
|
48421
50962
|
//# sourceMappingURL=index.js.map
|