@wrongstack/core 0.302.0 → 0.303.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/README.md +1 -1
- package/dist/agent-status-tracker.d.ts +6 -2
- package/dist/chronicle/index.js +1949 -1671
- package/dist/chronicle/metrics-store.d.ts +14 -0
- package/dist/chronicle/project-server-protocol.d.ts +13 -0
- package/dist/chronicle/project-server.js +1756 -1573
- package/dist/chronicle/rollup-adapter.d.ts +2 -0
- package/dist/chronicle/sqlite-journal.d.ts +59 -0
- package/dist/coordination/agents/index.js +4313 -3516
- package/dist/coordination/agents/project-agent-auto-optimize.d.ts +116 -0
- package/dist/coordination/agents/project-agent-capture-window.d.ts +29 -0
- package/dist/coordination/agents/project-agent-config-io.d.ts +11 -0
- package/dist/coordination/agents/project-agent-consolidation.d.ts +29 -2
- package/dist/coordination/agents/project-agent-files.d.ts +12 -3
- package/dist/coordination/agents/project-agent-identity-types.d.ts +4 -0
- package/dist/coordination/agents/project-agent-identity.d.ts +22 -9
- package/dist/coordination/agents/project-agent-learning-entries.d.ts +8 -2
- package/dist/coordination/agents/project-agent-learning-structured.d.ts +27 -1
- package/dist/coordination/agents/project-agent-optimizer.d.ts +49 -0
- package/dist/coordination/agents/project-agent-skill-layer.d.ts +101 -0
- package/dist/coordination/agents/role-skills.d.ts +11 -1
- package/dist/coordination/index.d.ts +1 -1
- package/dist/coordination/index.js +4927 -3589
- package/dist/coordination/mail-tools.d.ts +3 -3
- package/dist/core/context.d.ts +4 -0
- package/dist/core/continue-intent.d.ts +2 -0
- package/dist/core/conversation-state.d.ts +5 -0
- package/dist/core/index.js +129 -19
- package/dist/defaults/index.js +1620 -768
- package/dist/execution/index.js +2941 -2630
- package/dist/goal/index.js +7 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +12269 -9212
- package/dist/infrastructure/index.js +722 -672
- package/dist/kernel/events/agent-events.d.ts +28 -0
- package/dist/kernel/events/memory-events.d.ts +62 -0
- package/dist/plugin/index.js +2167 -1986
- package/dist/security/index.js +69 -3
- package/dist/security/kanban-boundary.d.ts +5 -1
- package/dist/session-catalog/client.d.ts +62 -0
- package/dist/session-catalog/endpoint.d.ts +6 -0
- package/dist/session-catalog/index.d.ts +6 -0
- package/dist/session-catalog/index.js +2000 -0
- package/dist/session-catalog/project-server.d.ts +3 -0
- package/dist/session-catalog/project-server.js +1861 -0
- package/dist/session-catalog/protocol.d.ts +284 -0
- package/dist/session-catalog/registry.d.ts +59 -0
- package/dist/session-catalog/store.d.ts +71 -0
- package/dist/storage/index.d.ts +42 -38
- package/dist/storage/index.js +13896 -12931
- package/dist/storage/plan-store.d.ts +1 -1
- package/dist/storage/session-event-bridge.d.ts +2 -2
- package/dist/storage/session-store.d.ts +6 -0
- package/dist/tasking/index.js +5 -0
- package/dist/tools/index.js +2832 -2606
- package/dist/types/config/root.d.ts +11 -1
- package/dist/types/config/skills-fleet-brain.d.ts +34 -0
- package/dist/types/config/ui.d.ts +14 -0
- package/dist/types/config.d.ts +1 -0
- package/dist/types/context-evidence.d.ts +2 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +20 -0
- package/dist/types/messages.d.ts +8 -0
- package/dist/types/multi-agent.d.ts +7 -0
- package/dist/types/session.d.ts +19 -0
- package/dist/types/task-graph.d.ts +2 -0
- package/dist/types/tool-executor.d.ts +2 -0
- package/dist/utils/context-evidence.d.ts +13 -1
- package/dist/utils/index.js +29 -2
- package/instructions/system-lite.md +23 -8
- package/instructions/system-pro.md +29 -9
- package/instructions/system.md +29 -9
- package/package.json +7 -3
- package/skills/wrongstack-kanban/SKILL.md +39 -8
package/dist/defaults/index.js
CHANGED
|
@@ -295,7 +295,7 @@ var InMemoryAgentBridge = class {
|
|
|
295
295
|
});
|
|
296
296
|
}
|
|
297
297
|
this.inflightGuards.add(correlationId);
|
|
298
|
-
return new Promise((
|
|
298
|
+
return new Promise((resolve21, reject) => {
|
|
299
299
|
const timer = setTimeout(() => {
|
|
300
300
|
this.inflightGuards.delete(correlationId);
|
|
301
301
|
this.pendingRequests.delete(correlationId);
|
|
@@ -314,7 +314,7 @@ var InMemoryAgentBridge = class {
|
|
|
314
314
|
return;
|
|
315
315
|
}
|
|
316
316
|
this.pendingRequests.set(correlationId, {
|
|
317
|
-
resolve:
|
|
317
|
+
resolve: resolve21,
|
|
318
318
|
reject,
|
|
319
319
|
timer
|
|
320
320
|
});
|
|
@@ -663,13 +663,13 @@ var SubagentBudget = class _SubagentBudget {
|
|
|
663
663
|
if (!bus?.hasListenerFor("budget.threshold_reached")) {
|
|
664
664
|
return Promise.resolve("stop");
|
|
665
665
|
}
|
|
666
|
-
return new Promise((
|
|
666
|
+
return new Promise((resolve21) => {
|
|
667
667
|
let resolved = false;
|
|
668
668
|
const respond = (d) => {
|
|
669
669
|
if (resolved) return;
|
|
670
670
|
resolved = true;
|
|
671
671
|
clearTimeout(fallback);
|
|
672
|
-
|
|
672
|
+
resolve21(d);
|
|
673
673
|
};
|
|
674
674
|
const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
|
|
675
675
|
const sessionId = this.currentSessionId();
|
|
@@ -1277,8 +1277,8 @@ function defaultFormatTaskInput(task) {
|
|
|
1277
1277
|
}
|
|
1278
1278
|
|
|
1279
1279
|
// src/coordination/agents/agent-prompts.ts
|
|
1280
|
-
import { readFileSync as
|
|
1281
|
-
import * as
|
|
1280
|
+
import { readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
|
|
1281
|
+
import * as path6 from "node:path";
|
|
1282
1282
|
import { fileURLToPath } from "node:url";
|
|
1283
1283
|
|
|
1284
1284
|
// src/utils/wstack-paths.ts
|
|
@@ -1421,8 +1421,8 @@ function resolveWstackPaths(opts) {
|
|
|
1421
1421
|
}
|
|
1422
1422
|
|
|
1423
1423
|
// src/coordination/agents/project-agent-identity.ts
|
|
1424
|
-
import { existsSync as existsSync2, readFileSync as
|
|
1425
|
-
import * as
|
|
1424
|
+
import { existsSync as existsSync2, readFileSync as readFileSync5 } from "node:fs";
|
|
1425
|
+
import * as path5 from "node:path";
|
|
1426
1426
|
|
|
1427
1427
|
// src/types/runtime-capability-manifest.ts
|
|
1428
1428
|
var PLAYWRIGHT_ALIASES = {
|
|
@@ -1680,22 +1680,6 @@ function inferRuntimeCapabilities(toolNames) {
|
|
|
1680
1680
|
return [...ids];
|
|
1681
1681
|
}
|
|
1682
1682
|
|
|
1683
|
-
// src/coordination/agents/project-agent-consolidation.ts
|
|
1684
|
-
import {
|
|
1685
|
-
existsSync,
|
|
1686
|
-
mkdirSync,
|
|
1687
|
-
readFileSync as readFileSync2,
|
|
1688
|
-
rmSync
|
|
1689
|
-
} from "node:fs";
|
|
1690
|
-
import * as path3 from "node:path";
|
|
1691
|
-
|
|
1692
|
-
// src/coordination/agents/project-agent-learning-entries.ts
|
|
1693
|
-
function splitLearnedEntries(body) {
|
|
1694
|
-
return body.split(/\n---\n+/).map((entry) => entry.trim()).map(
|
|
1695
|
-
(entry) => entry.replace(/^# Learned wisdom for .+$/im, "").replace(/<!--[\s\S]*?-->/g, "").trim()
|
|
1696
|
-
).filter(Boolean);
|
|
1697
|
-
}
|
|
1698
|
-
|
|
1699
1683
|
// src/coordination/agents/project-agent-paths.ts
|
|
1700
1684
|
import * as path2 from "node:path";
|
|
1701
1685
|
var AGENT_ROLE_PATTERN = /^[a-z0-9][a-z0-9._-]{0,95}$/i;
|
|
@@ -1717,6 +1701,190 @@ function learningPolicyPath(role, projectRoot) {
|
|
|
1717
1701
|
return path2.join(roleDir(role, projectRoot), "learning.json");
|
|
1718
1702
|
}
|
|
1719
1703
|
|
|
1704
|
+
// src/coordination/agents/project-agent-consolidation.ts
|
|
1705
|
+
import { existsSync, mkdirSync, readFileSync as readFileSync2, rmSync } from "node:fs";
|
|
1706
|
+
import * as path3 from "node:path";
|
|
1707
|
+
|
|
1708
|
+
// src/coordination/agents/project-agent-learning-entries.ts
|
|
1709
|
+
function splitLearnedEntries(body) {
|
|
1710
|
+
return body.split(/\n---\n+/).map((entry) => entry.trim()).map(
|
|
1711
|
+
(entry) => entry.replace(/^#\s+Learned (?:wisdom|instructions) for .+$/im, "").replace(/^>\s*Project-specific learning data for[\s\S]*?$/im, "").replace(/^_No learned entries yet\._$/im, "").replace(/^\*Last capture:[^\n]*\*$/im, "").replace(/<!--[\s\S]*?-->/g, "").trim()
|
|
1712
|
+
).filter(Boolean);
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
// src/coordination/agents/project-agent-learning-normalize.ts
|
|
1716
|
+
var MIN_INSTRUCTIVE_LENGTH = 30;
|
|
1717
|
+
function classifyLearnedEntry(text) {
|
|
1718
|
+
const lower = text.toLowerCase();
|
|
1719
|
+
if (/\b(?:avoid|never|don'?t|do not|must not|prevent|watch out|beware|pitfall|gotcha|hazard|risk)\b/.test(
|
|
1720
|
+
lower
|
|
1721
|
+
))
|
|
1722
|
+
return "warning";
|
|
1723
|
+
if (/\b(?:use|prefer|choose|adopt|leverage|apply|switch to|migrate to)\b/.test(lower) || /\b(?:pattern|approach|strategy|technique|idiom)\b/.test(lower))
|
|
1724
|
+
return "pattern";
|
|
1725
|
+
if (/\b(?:always|must|should|require|ensure|verify|check|run|execute|before|after|when)\b/.test(
|
|
1726
|
+
lower
|
|
1727
|
+
))
|
|
1728
|
+
return "convention";
|
|
1729
|
+
return "fact";
|
|
1730
|
+
}
|
|
1731
|
+
function normalizeForComparison(text) {
|
|
1732
|
+
return text.toLowerCase().replace(/[^\w\s]/g, " ").replace(/\s+/g, " ").trim().split(/\s+/).sort().join(" ");
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
// src/coordination/agents/project-agent-learning-structured.ts
|
|
1736
|
+
function parseLearnedEntryStamp(entry) {
|
|
1737
|
+
const structuredMatch = entry.match(
|
|
1738
|
+
/<!--\s*learned-stamp:\s*category=([\w-]+);\s*capturedAt=([^;]+?)\s*-->/
|
|
1739
|
+
);
|
|
1740
|
+
if (structuredMatch) {
|
|
1741
|
+
const rawCategory = structuredMatch[1];
|
|
1742
|
+
const candidate = parseLearnedCategory(rawCategory);
|
|
1743
|
+
return {
|
|
1744
|
+
capturedAt: typeof structuredMatch[2] === "string" ? structuredMatch[2].trim() : "",
|
|
1745
|
+
category: candidate ?? null
|
|
1746
|
+
};
|
|
1747
|
+
}
|
|
1748
|
+
const stampMatch = entry.match(/^>\s*(?:\[\s*([\w-]+)\s*\]\s+)?(?:Captured|Taught)\s+(\S+)/m);
|
|
1749
|
+
if (!stampMatch) {
|
|
1750
|
+
return { capturedAt: "", category: null };
|
|
1751
|
+
}
|
|
1752
|
+
return {
|
|
1753
|
+
capturedAt: typeof stampMatch[2] === "string" ? stampMatch[2] : "",
|
|
1754
|
+
category: parseLearnedCategory(stampMatch[1]) ?? null
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
function parseLearnedCategory(value) {
|
|
1758
|
+
return value === "convention" || value === "pattern" || value === "warning" || value === "fact" ? value : void 0;
|
|
1759
|
+
}
|
|
1760
|
+
function stripStamp(entry) {
|
|
1761
|
+
return entry.replace(/<!--\s*learned-stamp:[\s\S]*?-->/g, "").replace(/^>\s*(?:\[[\w-]+\]\s+)?(?:Captured|Taught)\s+.+$/m, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
1762
|
+
}
|
|
1763
|
+
function directiveKey(text) {
|
|
1764
|
+
return normalizeForComparison(text);
|
|
1765
|
+
}
|
|
1766
|
+
function decomposeLearnedEntry(text, category) {
|
|
1767
|
+
const what = text;
|
|
1768
|
+
const why = deriveWhy(category, text);
|
|
1769
|
+
const how = extractHow(text);
|
|
1770
|
+
return { what, why, how };
|
|
1771
|
+
}
|
|
1772
|
+
var WHY_BY_CATEGORY = {
|
|
1773
|
+
convention: "Established convention for this codebase \u2014 skipping it risks regressions, merge friction, or out-of-sync state with peers.",
|
|
1774
|
+
pattern: "This project's chosen approach \u2014 alternatives were considered and either conflict with existing architecture or were rejected for known reasons.",
|
|
1775
|
+
warning: "Known failure mode \u2014 skipping this has caused real defects in this codebase. The cost of getting it wrong outweighs the cost of the check.",
|
|
1776
|
+
fact: "Current state of the project \u2014 assumed by other conventions, build steps, or peers, so acting on a stale assumption wastes a cycle."
|
|
1777
|
+
};
|
|
1778
|
+
function deriveWhy(category, text) {
|
|
1779
|
+
const base = WHY_BY_CATEGORY[category];
|
|
1780
|
+
const signals = [];
|
|
1781
|
+
const lower = text.toLowerCase();
|
|
1782
|
+
if (/\bbefore\s+(?:merge|commit|deploy|release|shipping|publishing)\b/.test(lower))
|
|
1783
|
+
signals.push("guard before shipping");
|
|
1784
|
+
if (/\bto\s+avoid\b/.test(lower)) {
|
|
1785
|
+
const m = text.match(/to avoid ([^.!?]+)/i);
|
|
1786
|
+
if (m?.[1]) signals.push(`avoid ${m[1].trim()}`);
|
|
1787
|
+
}
|
|
1788
|
+
if (/\bso\s+(?:that|we|the project)\b/.test(lower)) {
|
|
1789
|
+
const m = text.match(/so (?:that |we |the project )?([^.!?]+)/i);
|
|
1790
|
+
if (m?.[1]) signals.push(`so ${m[1].trim()}`);
|
|
1791
|
+
}
|
|
1792
|
+
if (signals.length === 0) return base;
|
|
1793
|
+
return `${base} Project signals: ${signals.join("; ")}.`;
|
|
1794
|
+
}
|
|
1795
|
+
function extractHow(text) {
|
|
1796
|
+
const anchors = /* @__PURE__ */ new Set();
|
|
1797
|
+
const backticked = text.match(/`([^`]+)`/g) ?? [];
|
|
1798
|
+
for (const raw of backticked) {
|
|
1799
|
+
const inner = raw.replace(/`/g, "").trim();
|
|
1800
|
+
if (inner.length > 0 && inner.length <= 120) anchors.add(inner);
|
|
1801
|
+
}
|
|
1802
|
+
const pathMatches = text.match(
|
|
1803
|
+
/(?:[a-zA-Z0-9_.-]+\/)+[a-zA-Z0-9_.-]+\.(?:tsx|jsonc|json|jsx|yaml|mjs|cjs|yml|ts|js|md)\b/g
|
|
1804
|
+
) ?? [];
|
|
1805
|
+
for (const p of pathMatches) anchors.add(p);
|
|
1806
|
+
const scoped = text.match(/@[a-z0-9][\w.-]*\/[a-z0-9][\w.-]*/gi) ?? [];
|
|
1807
|
+
for (const p of scoped) anchors.add(p);
|
|
1808
|
+
if (anchors.size === 0) return "";
|
|
1809
|
+
return [...anchors].map((anchor) => `\`${anchor}\``).join("\n");
|
|
1810
|
+
}
|
|
1811
|
+
function cleanHowLine(line) {
|
|
1812
|
+
let value = line.trim();
|
|
1813
|
+
let previous;
|
|
1814
|
+
do {
|
|
1815
|
+
previous = value;
|
|
1816
|
+
value = value.replace(/^[-*]\s+/, "").replace(/^\*How:\*\s*/i, "").trim();
|
|
1817
|
+
} while (value !== previous);
|
|
1818
|
+
return value;
|
|
1819
|
+
}
|
|
1820
|
+
function parseStampAttributes(body) {
|
|
1821
|
+
const attributes = {};
|
|
1822
|
+
for (const part of body.split(";")) {
|
|
1823
|
+
const eq = part.indexOf("=");
|
|
1824
|
+
if (eq === -1) continue;
|
|
1825
|
+
const key = part.slice(0, eq).trim();
|
|
1826
|
+
const value = part.slice(eq + 1).trim();
|
|
1827
|
+
if (key) attributes[key] = value;
|
|
1828
|
+
}
|
|
1829
|
+
return attributes;
|
|
1830
|
+
}
|
|
1831
|
+
function parseEntryBody(body) {
|
|
1832
|
+
const lines = [];
|
|
1833
|
+
for (const line of body.split("\n")) {
|
|
1834
|
+
if (/^##\s/.test(line) || /^---\s*$/.test(line) || /^<!--/.test(line)) break;
|
|
1835
|
+
lines.push(line);
|
|
1836
|
+
}
|
|
1837
|
+
const text = lines.join("\n");
|
|
1838
|
+
const what = /^-\s+\*\*([\s\S]+?)\*\*\s*$/m.exec(text)?.[1]?.replace(/\s+/g, " ").trim() ?? "";
|
|
1839
|
+
if (!what) return void 0;
|
|
1840
|
+
const why = /^\s+-\s+\*Why:\*\s+(.+)$/m.exec(text)?.[1]?.trim() ?? "";
|
|
1841
|
+
const how = lines.filter((line) => /^\s+-\s+\*How:\*/i.test(line)).map((line) => cleanHowLine(line)).filter(Boolean).join("\n");
|
|
1842
|
+
return { what, why, how };
|
|
1843
|
+
}
|
|
1844
|
+
function parseStructuredLearnedEntriesFromContent(raw, legacyEntries = splitLearnedEntries(raw)) {
|
|
1845
|
+
if (!raw) return [];
|
|
1846
|
+
const structured = [];
|
|
1847
|
+
const stampPattern = /<!--\s*learned-stamp:\s*([^>]*?)\s*-->/g;
|
|
1848
|
+
const stamps = [...raw.matchAll(stampPattern)];
|
|
1849
|
+
for (const [index, stamp] of stamps.entries()) {
|
|
1850
|
+
const attributes = parseStampAttributes(stamp[1] ?? "");
|
|
1851
|
+
const category = parseLearnedCategory(attributes["category"]) ?? "fact";
|
|
1852
|
+
const capturedAt = attributes["capturedAt"] ?? "";
|
|
1853
|
+
const skill = attributes["skill"];
|
|
1854
|
+
const start = (stamp.index ?? 0) + stamp[0].length;
|
|
1855
|
+
const end = stamps[index + 1]?.index ?? raw.length;
|
|
1856
|
+
const parsed = parseEntryBody(raw.slice(start, end));
|
|
1857
|
+
if (!parsed || parsed.what.length < MIN_INSTRUCTIVE_LENGTH) continue;
|
|
1858
|
+
structured.push({
|
|
1859
|
+
key: directiveKey(parsed.what),
|
|
1860
|
+
category,
|
|
1861
|
+
what: parsed.what,
|
|
1862
|
+
why: parsed.why || WHY_BY_CATEGORY[category],
|
|
1863
|
+
how: parsed.how,
|
|
1864
|
+
capturedAt,
|
|
1865
|
+
...skill ? { skill } : {}
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
if (structured.length === 0) {
|
|
1869
|
+
for (const chunk of legacyEntries) {
|
|
1870
|
+
const stamp = parseLearnedEntryStamp(chunk);
|
|
1871
|
+
const directive = stamp.capturedAt || stamp.category ? stripStamp(chunk) : chunk;
|
|
1872
|
+
if (directive.length < MIN_INSTRUCTIVE_LENGTH) continue;
|
|
1873
|
+
const category = stamp.category ?? classifyLearnedEntry(directive);
|
|
1874
|
+
const { what, why, how } = decomposeLearnedEntry(directive, category);
|
|
1875
|
+
structured.push({
|
|
1876
|
+
key: directiveKey(directive),
|
|
1877
|
+
category,
|
|
1878
|
+
what,
|
|
1879
|
+
why,
|
|
1880
|
+
how,
|
|
1881
|
+
capturedAt: stamp.capturedAt
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
return structured;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1720
1888
|
// src/coordination/agents/project-agent-consolidation.ts
|
|
1721
1889
|
function consolidationPath(role, projectRoot) {
|
|
1722
1890
|
return path3.join(roleDir(role, projectRoot), "consolidated.md");
|
|
@@ -1831,27 +1999,317 @@ function loadProjectAgentLearningPolicy(role, projectRoot) {
|
|
|
1831
1999
|
}
|
|
1832
2000
|
}
|
|
1833
2001
|
|
|
2002
|
+
// src/coordination/agents/project-agent-skill-layer.ts
|
|
2003
|
+
import { readdirSync, readFileSync as readFileSync4, rmSync as rmSync2 } from "node:fs";
|
|
2004
|
+
import * as path4 from "node:path";
|
|
2005
|
+
|
|
2006
|
+
// src/coordination/agents/role-skills.ts
|
|
2007
|
+
var skillSet = (...names) => names;
|
|
2008
|
+
var ROLE_SKILL_SETS = {
|
|
2009
|
+
explore: skillSet("research-web", "node-modern", "typescript-strict"),
|
|
2010
|
+
search: skillSet("bug-hunter", "typescript-strict", "research-web"),
|
|
2011
|
+
research: skillSet("research-web", "tech-stack", "security-scanner", "api-design"),
|
|
2012
|
+
analyst: skillSet("sdd", "api-design", "testing", "security-scanner"),
|
|
2013
|
+
planner: skillSet("sdd", "multi-agent", "refactor-planner", "testing"),
|
|
2014
|
+
architect: skillSet("sdd", "api-design", "refactor-planner", "security-scanner", "observability"),
|
|
2015
|
+
critic: skillSet("chimera", "bug-hunter", "security-scanner", "refactor-planner"),
|
|
2016
|
+
"refactor-planner": skillSet(
|
|
2017
|
+
"refactor-planner",
|
|
2018
|
+
"sdd",
|
|
2019
|
+
"typescript-strict",
|
|
2020
|
+
"testing",
|
|
2021
|
+
"bug-hunter"
|
|
2022
|
+
),
|
|
2023
|
+
executor: skillSet("sdd", "typescript-strict", "node-modern", "testing", "git-flow"),
|
|
2024
|
+
refactor: skillSet("refactor-planner", "typescript-strict", "testing", "bug-hunter"),
|
|
2025
|
+
simplifier: skillSet("bug-hunter", "typescript-strict", "testing", "refactor-planner"),
|
|
2026
|
+
migration: skillSet("tech-stack", "node-modern", "typescript-strict", "testing", "git-flow"),
|
|
2027
|
+
vision: skillSet("react-modern", "testing", "typescript-strict", "security-scanner"),
|
|
2028
|
+
debugger: skillSet("bug-hunter", "testing", "typescript-strict", "observability", "audit-log"),
|
|
2029
|
+
tracer: skillSet("observability", "audit-log", "bug-hunter", "testing"),
|
|
2030
|
+
verifier: skillSet("testing", "typescript-strict", "security-scanner", "chimera"),
|
|
2031
|
+
test: skillSet("testing", "typescript-strict", "bug-hunter", "security-scanner"),
|
|
2032
|
+
e2e: skillSet("testing", "react-modern", "api-design", "security-scanner"),
|
|
2033
|
+
browser: skillSet("testing", "react-modern", "security-scanner", "research-web"),
|
|
2034
|
+
performance: skillSet("observability", "testing", "node-modern", "audit-log"),
|
|
2035
|
+
chaos: skillSet("testing", "observability", "security-scanner", "audit-log"),
|
|
2036
|
+
"security-scanner": skillSet("security-scanner", "bug-hunter", "api-design", "tech-stack"),
|
|
2037
|
+
"bug-hunter": skillSet("bug-hunter", "typescript-strict", "testing", "security-scanner"),
|
|
2038
|
+
"audit-log": skillSet("audit-log", "observability", "bug-hunter", "output-standards"),
|
|
2039
|
+
reviewer: skillSet("chimera", "bug-hunter", "security-scanner", "testing"),
|
|
2040
|
+
"code-reviewer": skillSet("chimera", "bug-hunter", "typescript-strict", "testing"),
|
|
2041
|
+
"security-reviewer": skillSet("security-scanner", "chimera", "api-design", "tech-stack"),
|
|
2042
|
+
accessibility: skillSet("react-modern", "testing", "chimera", "security-scanner"),
|
|
2043
|
+
compliance: skillSet("security-scanner", "audit-log", "tech-stack", "chimera"),
|
|
2044
|
+
database: skillSet("api-design", "testing", "security-scanner", "observability"),
|
|
2045
|
+
api: skillSet("api-design", "typescript-strict", "node-modern", "testing", "security-scanner"),
|
|
2046
|
+
auth: skillSet("security-scanner", "api-design", "testing", "typescript-strict", "audit-log"),
|
|
2047
|
+
data: skillSet("typescript-strict", "testing", "observability", "security-scanner"),
|
|
2048
|
+
frontend: skillSet("react-modern", "typescript-strict", "testing", "security-scanner"),
|
|
2049
|
+
backend: skillSet("node-modern", "typescript-strict", "api-design", "testing", "observability"),
|
|
2050
|
+
designer: skillSet("react-modern", "prompt-engineering", "research-web", "output-standards"),
|
|
2051
|
+
ios: skillSet("sdd", "testing", "security-scanner", "research-web"),
|
|
2052
|
+
document: skillSet("output-standards", "research-web", "api-design", "prompt-engineering"),
|
|
2053
|
+
uml: skillSet("sdd", "refactor-planner", "api-design", "output-standards"),
|
|
2054
|
+
i18n: skillSet("react-modern", "testing", "typescript-strict", "output-standards"),
|
|
2055
|
+
prompt: skillSet("prompt-engineering", "skill-creator", "output-standards", "testing"),
|
|
2056
|
+
git: skillSet("git-flow", "chimera", "testing", "security-scanner"),
|
|
2057
|
+
release: skillSet("git-flow", "tech-stack", "chimera", "security-scanner"),
|
|
2058
|
+
devops: skillSet("docker-deploy", "observability", "security-scanner", "git-flow", "tech-stack"),
|
|
2059
|
+
observability: skillSet("observability", "node-modern", "testing", "audit-log"),
|
|
2060
|
+
dependency: skillSet("tech-stack", "security-scanner", "node-modern", "git-flow"),
|
|
2061
|
+
"skill-manage": skillSet(
|
|
2062
|
+
"skill-creator",
|
|
2063
|
+
"prompt-engineering",
|
|
2064
|
+
"security-scanner",
|
|
2065
|
+
"output-standards"
|
|
2066
|
+
),
|
|
2067
|
+
"self-improving": skillSet(
|
|
2068
|
+
"audit-log",
|
|
2069
|
+
"mnemosyne",
|
|
2070
|
+
"prompt-engineering",
|
|
2071
|
+
"bug-hunter",
|
|
2072
|
+
"observability"
|
|
2073
|
+
),
|
|
2074
|
+
context: skillSet("mnemosyne", "output-standards", "prompt-engineering", "audit-log"),
|
|
2075
|
+
cost: skillSet("audit-log", "tech-stack", "observability", "research-web"),
|
|
2076
|
+
"tech-stack": skillSet("tech-stack", "research-web", "security-scanner", "node-modern"),
|
|
2077
|
+
// ── Wave 1: Platform and systems engineering ──────────────────────────
|
|
2078
|
+
android: skillSet(
|
|
2079
|
+
"react-modern",
|
|
2080
|
+
"typescript-strict",
|
|
2081
|
+
"testing",
|
|
2082
|
+
"security-scanner",
|
|
2083
|
+
"tech-stack"
|
|
2084
|
+
),
|
|
2085
|
+
desktop: skillSet(
|
|
2086
|
+
"node-modern",
|
|
2087
|
+
"typescript-strict",
|
|
2088
|
+
"testing",
|
|
2089
|
+
"security-scanner",
|
|
2090
|
+
"tech-stack"
|
|
2091
|
+
),
|
|
2092
|
+
realtime: skillSet(
|
|
2093
|
+
"api-design",
|
|
2094
|
+
"observability",
|
|
2095
|
+
"node-modern",
|
|
2096
|
+
"typescript-strict",
|
|
2097
|
+
"testing",
|
|
2098
|
+
"tech-stack"
|
|
2099
|
+
),
|
|
2100
|
+
"distributed-systems": skillSet(
|
|
2101
|
+
"api-design",
|
|
2102
|
+
"observability",
|
|
2103
|
+
"typescript-strict",
|
|
2104
|
+
"testing",
|
|
2105
|
+
"security-scanner",
|
|
2106
|
+
"tech-stack"
|
|
2107
|
+
),
|
|
2108
|
+
concurrency: skillSet(
|
|
2109
|
+
"bug-hunter",
|
|
2110
|
+
"typescript-strict",
|
|
2111
|
+
"testing",
|
|
2112
|
+
"observability",
|
|
2113
|
+
"node-modern",
|
|
2114
|
+
"tech-stack"
|
|
2115
|
+
),
|
|
2116
|
+
"platform-engineer": skillSet(
|
|
2117
|
+
"node-modern",
|
|
2118
|
+
"typescript-strict",
|
|
2119
|
+
"git-flow",
|
|
2120
|
+
"docker-deploy",
|
|
2121
|
+
"tech-stack"
|
|
2122
|
+
),
|
|
2123
|
+
// ── Wave 3: Product and data specialists ──────────────────────────────
|
|
2124
|
+
payments: skillSet(
|
|
2125
|
+
"api-design",
|
|
2126
|
+
"security-scanner",
|
|
2127
|
+
"audit-log",
|
|
2128
|
+
"testing",
|
|
2129
|
+
"typescript-strict",
|
|
2130
|
+
"tech-stack"
|
|
2131
|
+
),
|
|
2132
|
+
messaging: skillSet(
|
|
2133
|
+
"api-design",
|
|
2134
|
+
"observability",
|
|
2135
|
+
"node-modern",
|
|
2136
|
+
"testing",
|
|
2137
|
+
"security-scanner",
|
|
2138
|
+
"tech-stack"
|
|
2139
|
+
),
|
|
2140
|
+
"search-relevance": skillSet(
|
|
2141
|
+
"api-design",
|
|
2142
|
+
"observability",
|
|
2143
|
+
"testing",
|
|
2144
|
+
"typescript-strict",
|
|
2145
|
+
"data-governance",
|
|
2146
|
+
"tech-stack"
|
|
2147
|
+
),
|
|
2148
|
+
storage: skillSet(
|
|
2149
|
+
"api-design",
|
|
2150
|
+
"security-scanner",
|
|
2151
|
+
"testing",
|
|
2152
|
+
"observability",
|
|
2153
|
+
"node-modern",
|
|
2154
|
+
"tech-stack"
|
|
2155
|
+
),
|
|
2156
|
+
"ml-engineer": skillSet(
|
|
2157
|
+
"observability",
|
|
2158
|
+
"typescript-strict",
|
|
2159
|
+
"testing",
|
|
2160
|
+
"security-scanner",
|
|
2161
|
+
"data-governance",
|
|
2162
|
+
"tech-stack"
|
|
2163
|
+
),
|
|
2164
|
+
"data-governance": skillSet(
|
|
2165
|
+
"audit-log",
|
|
2166
|
+
"security-scanner",
|
|
2167
|
+
"observability",
|
|
2168
|
+
"testing",
|
|
2169
|
+
"output-standards",
|
|
2170
|
+
"tech-stack"
|
|
2171
|
+
),
|
|
2172
|
+
// ── Wave 4: Agent platform and quality ───────────────────────────────
|
|
2173
|
+
"plugin-author": skillSet(
|
|
2174
|
+
"plugin-author",
|
|
2175
|
+
"node-modern",
|
|
2176
|
+
"typescript-strict",
|
|
2177
|
+
"testing",
|
|
2178
|
+
"security-scanner",
|
|
2179
|
+
"tech-stack"
|
|
2180
|
+
),
|
|
2181
|
+
"tool-author": skillSet(
|
|
2182
|
+
"typescript-strict",
|
|
2183
|
+
"security-scanner",
|
|
2184
|
+
"testing",
|
|
2185
|
+
"output-standards",
|
|
2186
|
+
"tech-stack"
|
|
2187
|
+
),
|
|
2188
|
+
"prompt-evaluator": skillSet(
|
|
2189
|
+
"prompt-engineering",
|
|
2190
|
+
"testing",
|
|
2191
|
+
"output-standards",
|
|
2192
|
+
"security-scanner",
|
|
2193
|
+
"tech-stack"
|
|
2194
|
+
),
|
|
2195
|
+
"benchmark-engineer": skillSet("observability", "testing", "output-standards", "tech-stack"),
|
|
2196
|
+
"memory-curator": skillSet("mnemosyne", "audit-log", "testing", "output-standards", "tech-stack"),
|
|
2197
|
+
"fleet-coordinator": skillSet("multi-agent", "sdd", "output-standards", "testing", "tech-stack"),
|
|
2198
|
+
// ── Wave 2: Security, resilience, governance and agent platform ────────
|
|
2199
|
+
"threat-modeler": skillSet(
|
|
2200
|
+
"security-scanner",
|
|
2201
|
+
"api-design",
|
|
2202
|
+
"tech-stack",
|
|
2203
|
+
"audit-log",
|
|
2204
|
+
"testing"
|
|
2205
|
+
),
|
|
2206
|
+
"secure-coding-coach": skillSet(
|
|
2207
|
+
"security-scanner",
|
|
2208
|
+
"testing",
|
|
2209
|
+
"typescript-strict",
|
|
2210
|
+
"api-design",
|
|
2211
|
+
"tech-stack"
|
|
2212
|
+
),
|
|
2213
|
+
"resilience-engineer": skillSet(
|
|
2214
|
+
"observability",
|
|
2215
|
+
"testing",
|
|
2216
|
+
"typescript-strict",
|
|
2217
|
+
"api-design",
|
|
2218
|
+
"tech-stack"
|
|
2219
|
+
),
|
|
2220
|
+
"chaos-engineer": skillSet(
|
|
2221
|
+
"observability",
|
|
2222
|
+
"testing",
|
|
2223
|
+
"security-scanner",
|
|
2224
|
+
"git-flow",
|
|
2225
|
+
"tech-stack"
|
|
2226
|
+
),
|
|
2227
|
+
"compliance-auditor": skillSet(
|
|
2228
|
+
"audit-log",
|
|
2229
|
+
"security-scanner",
|
|
2230
|
+
"output-standards",
|
|
2231
|
+
"testing",
|
|
2232
|
+
"tech-stack"
|
|
2233
|
+
),
|
|
2234
|
+
"privacy-engineer": skillSet(
|
|
2235
|
+
"security-scanner",
|
|
2236
|
+
"audit-log",
|
|
2237
|
+
"api-design",
|
|
2238
|
+
"data-governance",
|
|
2239
|
+
"tech-stack"
|
|
2240
|
+
)
|
|
2241
|
+
};
|
|
2242
|
+
var SHADOW_AGENT_SKILLS = skillSet(
|
|
2243
|
+
"wrongstack-mailbox",
|
|
2244
|
+
"multi-agent",
|
|
2245
|
+
"audit-log",
|
|
2246
|
+
"observability",
|
|
2247
|
+
"security-scanner"
|
|
2248
|
+
);
|
|
2249
|
+
var MAX_EAGER_ROSTER_SKILLS = 3;
|
|
2250
|
+
function assignSkillsToAgents(definitions) {
|
|
2251
|
+
return definitions.map((definition) => {
|
|
2252
|
+
const role = definition.config.role;
|
|
2253
|
+
if (!role || !Object.hasOwn(ROLE_SKILL_SETS, role)) {
|
|
2254
|
+
throw new Error(
|
|
2255
|
+
`Missing curated skill set for roster role: "${role ?? definition.config.name}"`
|
|
2256
|
+
);
|
|
2257
|
+
}
|
|
2258
|
+
const skills = ROLE_SKILL_SETS[role];
|
|
2259
|
+
return {
|
|
2260
|
+
...definition,
|
|
2261
|
+
config: {
|
|
2262
|
+
...definition.config,
|
|
2263
|
+
capabilities: inferRuntimeCapabilities(definition.config.tools ?? []),
|
|
2264
|
+
skillNames: skills.slice(0, MAX_EAGER_ROSTER_SKILLS),
|
|
2265
|
+
skillPool: [...skills]
|
|
2266
|
+
}
|
|
2267
|
+
};
|
|
2268
|
+
});
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
// src/coordination/agents/project-agent-skill-layer.ts
|
|
2272
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
2273
|
+
function isProjectSkillName(name) {
|
|
2274
|
+
return SKILL_NAME_PATTERN.test(name);
|
|
2275
|
+
}
|
|
2276
|
+
function projectSkillsDir(role, projectRoot) {
|
|
2277
|
+
return path4.join(roleDir(role, projectRoot), "skills");
|
|
2278
|
+
}
|
|
2279
|
+
function listProjectSkillAugmentations(role, projectRoot) {
|
|
2280
|
+
try {
|
|
2281
|
+
return readdirSync(projectSkillsDir(role, projectRoot)).filter((name) => name.endsWith(".md")).map((name) => name.slice(0, -3)).filter(isProjectSkillName).sort();
|
|
2282
|
+
} catch {
|
|
2283
|
+
return [];
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
// src/types/blocks.ts
|
|
2288
|
+
function isTextBlock(b) {
|
|
2289
|
+
return b.type === "text";
|
|
2290
|
+
}
|
|
2291
|
+
|
|
1834
2292
|
// src/coordination/agents/project-agent-identity.ts
|
|
1835
2293
|
function loadProjectAgentIdentity(role, projectRoot) {
|
|
1836
|
-
const identityPath =
|
|
2294
|
+
const identityPath = path5.join(roleDir(role, projectRoot), "identity.md");
|
|
1837
2295
|
try {
|
|
1838
|
-
return
|
|
2296
|
+
return readFileSync5(identityPath, "utf8").trim();
|
|
1839
2297
|
} catch {
|
|
1840
2298
|
return "";
|
|
1841
2299
|
}
|
|
1842
2300
|
}
|
|
1843
2301
|
function loadProjectAgentLearned(role, projectRoot) {
|
|
1844
|
-
const learnedPath =
|
|
2302
|
+
const learnedPath = path5.join(roleDir(role, projectRoot), "learned.md");
|
|
1845
2303
|
try {
|
|
1846
|
-
return
|
|
2304
|
+
return readFileSync5(learnedPath, "utf8").trim();
|
|
1847
2305
|
} catch {
|
|
1848
2306
|
return "";
|
|
1849
2307
|
}
|
|
1850
2308
|
}
|
|
1851
2309
|
function loadRoleKnowledgeManifest(role, projectRoot) {
|
|
1852
|
-
const projectPath =
|
|
2310
|
+
const projectPath = path5.join(roleDir(role, projectRoot), "knowledge.json");
|
|
1853
2311
|
try {
|
|
1854
|
-
const raw =
|
|
2312
|
+
const raw = readFileSync5(projectPath, "utf8");
|
|
1855
2313
|
return JSON.parse(raw);
|
|
1856
2314
|
} catch {
|
|
1857
2315
|
return BUILT_IN_KNOWLEDGE_MANIFESTS[role];
|
|
@@ -1879,31 +2337,35 @@ ${identity}`);
|
|
|
1879
2337
|
let learnedLabel = "";
|
|
1880
2338
|
if (learningPolicy.enabled) {
|
|
1881
2339
|
const consolidated = loadProjectAgentConsolidated(role, projectRoot);
|
|
2340
|
+
const rawLearned = loadProjectAgentLearned(role, projectRoot);
|
|
1882
2341
|
if (consolidated) {
|
|
1883
2342
|
const meta = loadConsolidationMetadata(role, projectRoot);
|
|
1884
|
-
const
|
|
1885
|
-
|
|
2343
|
+
const rawEntries = parseStructuredLearnedEntriesFromContent(
|
|
2344
|
+
rawLearned,
|
|
2345
|
+
splitLearnedEntries(rawLearned)
|
|
2346
|
+
);
|
|
1886
2347
|
const rawBytes = Buffer.byteLength(rawLearned, "utf8");
|
|
1887
|
-
const
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
2348
|
+
const freshEntries = meta === void 0 ? [] : rawEntries.filter((entry) => entry.capturedAt > meta.consolidatedAt);
|
|
2349
|
+
const stale = meta === void 0 || freshEntries.length > 0 || rawEntries.length > meta.sourceEntryCount || rawBytes > meta.sourceBytes;
|
|
2350
|
+
if (!stale) {
|
|
2351
|
+
learnedContent = consolidated;
|
|
2352
|
+
learnedLabel = "Consolidated knowledge for this project";
|
|
2353
|
+
} else if (freshEntries.length > 0) {
|
|
2354
|
+
const delta = freshEntries.map((entry) => `- **${entry.what}**`).join("\n");
|
|
2355
|
+
learnedContent = `${consolidated}
|
|
1892
2356
|
|
|
1893
2357
|
---
|
|
1894
2358
|
|
|
1895
2359
|
## Recently captured (pending next optimization)
|
|
1896
2360
|
|
|
1897
|
-
${
|
|
1898
|
-
|
|
1899
|
-
learnedContent = rawLearned;
|
|
1900
|
-
}
|
|
2361
|
+
${delta}`;
|
|
2362
|
+
learnedLabel = "Consolidated knowledge for this project";
|
|
1901
2363
|
} else {
|
|
1902
|
-
learnedContent =
|
|
2364
|
+
learnedContent = rawLearned;
|
|
2365
|
+
learnedLabel = "Learned instructions for this project (structured: what / why / how)";
|
|
1903
2366
|
}
|
|
1904
|
-
learnedLabel = "Consolidated knowledge for this project";
|
|
1905
2367
|
} else {
|
|
1906
|
-
learnedContent =
|
|
2368
|
+
learnedContent = rawLearned;
|
|
1907
2369
|
learnedLabel = "Learned instructions for this project (structured: what / why / how)";
|
|
1908
2370
|
}
|
|
1909
2371
|
}
|
|
@@ -1918,7 +2380,7 @@ ${learnedContent}`);
|
|
|
1918
2380
|
}
|
|
1919
2381
|
}
|
|
1920
2382
|
const effectiveProjectRoot = projectRoot || process.cwd();
|
|
1921
|
-
const learnedFilePath =
|
|
2383
|
+
const learnedFilePath = path5.join(
|
|
1922
2384
|
effectiveProjectRoot,
|
|
1923
2385
|
".wrongstack",
|
|
1924
2386
|
"agents",
|
|
@@ -1945,6 +2407,8 @@ The file below stores **learning data for this project's "${role}" agent** \u201
|
|
|
1945
2407
|
- **Self-contained** \u2014 understandable without the surrounding session context.
|
|
1946
2408
|
- **Front-load concrete anchors** \u2014 commands in backticks, package names like \`@wrongstack/core\`, file paths like \`packages/core/src/.../foo.ts\`. The structured-list renderer extracts these as the "how" for the entry.
|
|
1947
2409
|
|
|
2410
|
+
**Tag the skill you are developing.** When a directive refines one of your skills, mark it: \`## LEARNED [skill: testing]\`. Tagged directives are distilled into that skill's project addendum, so the lesson arrives as part of the skill itself on every future run instead of as a loose fact. Untagged directives are routed automatically when the wording makes the target obvious, and stay role-level otherwise.
|
|
2411
|
+
|
|
1948
2412
|
**Bad** (session log \u2014 rejected at capture time):
|
|
1949
2413
|
|
|
1950
2414
|
\`\`\`
|
|
@@ -1963,15 +2427,40 @@ Capture only durable, cross-session knowledge \u2014 never ephemeral task detail
|
|
|
1963
2427
|
);
|
|
1964
2428
|
}
|
|
1965
2429
|
const knowledge = loadRoleKnowledgeManifest(role, projectRoot);
|
|
1966
|
-
if (knowledge
|
|
1967
|
-
const
|
|
2430
|
+
if (knowledge) {
|
|
2431
|
+
const knowledgeSections = [];
|
|
2432
|
+
if (knowledge.checklist.length > 0) {
|
|
2433
|
+
knowledgeSections.push(
|
|
2434
|
+
`Verify these before answering:
|
|
2435
|
+
${knowledge.checklist.map((item) => `- ${item}`).join("\n")}`
|
|
2436
|
+
);
|
|
2437
|
+
}
|
|
2438
|
+
const liveQueries = Object.entries(knowledge.liveQueries ?? {});
|
|
2439
|
+
if (liveQueries.length > 0) {
|
|
2440
|
+
const threshold = Number.isFinite(knowledge.verifyThreshold) ? knowledge.verifyThreshold : 0.5;
|
|
2441
|
+
knowledgeSections.push(
|
|
2442
|
+
`When your confidence in any of the following is below ${threshold}, fetch the live value rather than recalling it:
|
|
2443
|
+
` + liveQueries.map(
|
|
2444
|
+
([topic, query]) => `- **${topic}** \u2014 ${query.description}: \`${query.registry}\` \u2192 field \`${query.key}\``
|
|
2445
|
+
).join("\n")
|
|
2446
|
+
);
|
|
2447
|
+
}
|
|
2448
|
+
if (knowledgeSections.length > 0) {
|
|
2449
|
+
parts.push(`
|
|
2450
|
+
|
|
2451
|
+
## Current knowledge requirements
|
|
2452
|
+
|
|
2453
|
+
${knowledgeSections.join("\n\n")}`);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2456
|
+
const developedSkills = listProjectSkillAugmentations(role, projectRoot);
|
|
2457
|
+
if (developedSkills.length > 0) {
|
|
1968
2458
|
parts.push(
|
|
1969
2459
|
`
|
|
1970
2460
|
|
|
1971
|
-
##
|
|
2461
|
+
## Project-developed skills
|
|
1972
2462
|
|
|
1973
|
-
|
|
1974
|
-
${checklist}`
|
|
2463
|
+
These skills have been extended with practice learned in this project: ${developedSkills.map((skill) => `\`${skill}\``).join(", ")}. Their project addendum is attached to the skill body \u2014 follow it over the generic method when the two differ.`
|
|
1975
2464
|
);
|
|
1976
2465
|
}
|
|
1977
2466
|
const additions = parts.slice(1).join("\n\n").trim();
|
|
@@ -2060,38 +2549,38 @@ function loadTechVersionPolicy() {
|
|
|
2060
2549
|
return techVersionPolicyBody;
|
|
2061
2550
|
}
|
|
2062
2551
|
function policyCandidateDirs() {
|
|
2063
|
-
const here =
|
|
2552
|
+
const here = path6.dirname(fileURLToPath(import.meta.url));
|
|
2064
2553
|
const profileInstructions = resolveWstackPaths({ projectRoot: process.cwd() }).globalInstructions;
|
|
2065
2554
|
return [
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2555
|
+
path6.resolve(here, "../../../../instructions/agents"),
|
|
2556
|
+
path6.resolve(here, "../../../instructions/agents"),
|
|
2557
|
+
path6.resolve(here, "../../instructions/agents"),
|
|
2558
|
+
path6.resolve(here, "../instructions/agents"),
|
|
2559
|
+
path6.resolve(here, "instructions/agents"),
|
|
2560
|
+
path6.join(profileInstructions, "agents"),
|
|
2072
2561
|
profileInstructions
|
|
2073
2562
|
].filter((dir, index, all) => all.indexOf(dir) === index);
|
|
2074
2563
|
}
|
|
2075
2564
|
function policyBody() {
|
|
2076
|
-
const here =
|
|
2565
|
+
const here = path6.dirname(fileURLToPath(import.meta.url));
|
|
2077
2566
|
const roots = [
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2567
|
+
path6.resolve(here, "_policy/tech-version.md"),
|
|
2568
|
+
path6.resolve(here, "../_policy/tech-version.md"),
|
|
2569
|
+
path6.resolve(here, "../../../instructions/agents/_policy/tech-version.md"),
|
|
2570
|
+
path6.resolve(here, "../../instructions/agents/_policy/tech-version.md"),
|
|
2571
|
+
path6.resolve(here, "../instructions/agents/_policy/tech-version.md"),
|
|
2572
|
+
path6.resolve(here, "../../../../instructions/agents/_policy/tech-version.md"),
|
|
2573
|
+
path6.resolve(here, "instructions/agents/_policy/tech-version.md")
|
|
2085
2574
|
];
|
|
2086
2575
|
for (const file of roots) {
|
|
2087
2576
|
try {
|
|
2088
|
-
return
|
|
2577
|
+
return readFileSync6(file, "utf8").trim();
|
|
2089
2578
|
} catch {
|
|
2090
2579
|
}
|
|
2091
2580
|
}
|
|
2092
2581
|
for (const dir of policyCandidateDirs()) {
|
|
2093
2582
|
try {
|
|
2094
|
-
return
|
|
2583
|
+
return readFileSync6(path6.join(dir, "_policy", "tech-version.md"), "utf8").trim();
|
|
2095
2584
|
} catch {
|
|
2096
2585
|
}
|
|
2097
2586
|
}
|
|
@@ -2102,14 +2591,15 @@ function agentPrompt(id) {
|
|
|
2102
2591
|
const envDir = process.env["WRONGSTACK_AGENT_INSTRUCTIONS_DIR"] ?? "";
|
|
2103
2592
|
const policyOn = process.env["WRONGSTACK_AGENT_POLICY"] === "on";
|
|
2104
2593
|
const cacheBypass = globalThis.__WS_DISABLE_PROMPT_CACHE__ === true;
|
|
2105
|
-
const
|
|
2594
|
+
const promptProjectRoot = process.env["WRONGSTACK_PROJECT_ROOT"] || process.cwd();
|
|
2595
|
+
const cacheKey = `${envDir}\0${policyOn ? 1 : 0}\0${promptProjectRoot}\0${id}`;
|
|
2106
2596
|
const cached = cacheBypass ? void 0 : promptCache.get(cacheKey);
|
|
2107
2597
|
if (cached !== void 0) return cached;
|
|
2108
2598
|
const fileName = `${id}.md`;
|
|
2109
2599
|
let resolved = "";
|
|
2110
2600
|
for (const dir of agentPromptDirCandidates(envDir)) {
|
|
2111
2601
|
try {
|
|
2112
|
-
resolved =
|
|
2602
|
+
resolved = readFileSync6(path6.join(dir, fileName), "utf8").trim();
|
|
2113
2603
|
break;
|
|
2114
2604
|
} catch {
|
|
2115
2605
|
}
|
|
@@ -2126,8 +2616,7 @@ function agentPrompt(id) {
|
|
|
2126
2616
|
${policy}`;
|
|
2127
2617
|
}
|
|
2128
2618
|
}
|
|
2129
|
-
|
|
2130
|
-
resolved = buildProjectContextualizedPrompt(resolved, id, projectRoot);
|
|
2619
|
+
resolved = buildProjectContextualizedPrompt(resolved, id, promptProjectRoot);
|
|
2131
2620
|
if (!cacheBypass) promptCache.set(cacheKey, resolved);
|
|
2132
2621
|
return resolved;
|
|
2133
2622
|
}
|
|
@@ -2136,16 +2625,16 @@ function agentPromptDirCandidates(envDir) {
|
|
|
2136
2625
|
const candKey = `${envDir}\0${profileInstructions}`;
|
|
2137
2626
|
const cached = candidateCache.get(candKey);
|
|
2138
2627
|
if (cached !== void 0) return cached;
|
|
2139
|
-
const here =
|
|
2628
|
+
const here = path6.dirname(fileURLToPath(import.meta.url));
|
|
2140
2629
|
const explicitDir = envDir || void 0;
|
|
2141
2630
|
const candidates = [
|
|
2142
|
-
...explicitDir ? [
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2631
|
+
...explicitDir ? [path6.resolve(explicitDir)] : [],
|
|
2632
|
+
path6.join(profileInstructions, "agents"),
|
|
2633
|
+
path6.resolve(here, "../../../../instructions/agents"),
|
|
2634
|
+
path6.resolve(here, "../../../instructions/agents"),
|
|
2635
|
+
path6.resolve(here, "../../instructions/agents"),
|
|
2636
|
+
path6.resolve(here, "../instructions/agents"),
|
|
2637
|
+
path6.resolve(here, "instructions/agents")
|
|
2149
2638
|
];
|
|
2150
2639
|
const ordered = candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
|
|
2151
2640
|
candidateCache.set(candKey, ordered);
|
|
@@ -4641,270 +5130,6 @@ var WAVE4_ROLE_METAS = {
|
|
|
4641
5130
|
};
|
|
4642
5131
|
var WAVE4_ROLES = new Set(Object.keys(WAVE4_ROLE_METAS));
|
|
4643
5132
|
|
|
4644
|
-
// src/coordination/agents/role-skills.ts
|
|
4645
|
-
var skillSet = (...names) => names;
|
|
4646
|
-
var ROLE_SKILL_SETS = {
|
|
4647
|
-
explore: skillSet("research-web", "node-modern", "typescript-strict"),
|
|
4648
|
-
search: skillSet("bug-hunter", "typescript-strict", "research-web"),
|
|
4649
|
-
research: skillSet("research-web", "tech-stack", "security-scanner", "api-design"),
|
|
4650
|
-
analyst: skillSet("sdd", "api-design", "testing", "security-scanner"),
|
|
4651
|
-
planner: skillSet("sdd", "multi-agent", "refactor-planner", "testing"),
|
|
4652
|
-
architect: skillSet("sdd", "api-design", "refactor-planner", "security-scanner", "observability"),
|
|
4653
|
-
critic: skillSet("chimera", "bug-hunter", "security-scanner", "refactor-planner"),
|
|
4654
|
-
"refactor-planner": skillSet(
|
|
4655
|
-
"refactor-planner",
|
|
4656
|
-
"sdd",
|
|
4657
|
-
"typescript-strict",
|
|
4658
|
-
"testing",
|
|
4659
|
-
"bug-hunter"
|
|
4660
|
-
),
|
|
4661
|
-
executor: skillSet("sdd", "typescript-strict", "node-modern", "testing", "git-flow"),
|
|
4662
|
-
refactor: skillSet("refactor-planner", "typescript-strict", "testing", "bug-hunter"),
|
|
4663
|
-
simplifier: skillSet("bug-hunter", "typescript-strict", "testing", "refactor-planner"),
|
|
4664
|
-
migration: skillSet("tech-stack", "node-modern", "typescript-strict", "testing", "git-flow"),
|
|
4665
|
-
vision: skillSet("react-modern", "testing", "typescript-strict", "security-scanner"),
|
|
4666
|
-
debugger: skillSet("bug-hunter", "testing", "typescript-strict", "observability", "audit-log"),
|
|
4667
|
-
tracer: skillSet("observability", "audit-log", "bug-hunter", "testing"),
|
|
4668
|
-
verifier: skillSet("testing", "typescript-strict", "security-scanner", "chimera"),
|
|
4669
|
-
test: skillSet("testing", "typescript-strict", "bug-hunter", "security-scanner"),
|
|
4670
|
-
e2e: skillSet("testing", "react-modern", "api-design", "security-scanner"),
|
|
4671
|
-
browser: skillSet("testing", "react-modern", "security-scanner", "research-web"),
|
|
4672
|
-
performance: skillSet("observability", "testing", "node-modern", "audit-log"),
|
|
4673
|
-
chaos: skillSet("testing", "observability", "security-scanner", "audit-log"),
|
|
4674
|
-
"security-scanner": skillSet("security-scanner", "bug-hunter", "api-design", "tech-stack"),
|
|
4675
|
-
"bug-hunter": skillSet("bug-hunter", "typescript-strict", "testing", "security-scanner"),
|
|
4676
|
-
"audit-log": skillSet("audit-log", "observability", "bug-hunter", "output-standards"),
|
|
4677
|
-
reviewer: skillSet("chimera", "bug-hunter", "security-scanner", "testing"),
|
|
4678
|
-
"code-reviewer": skillSet("chimera", "bug-hunter", "typescript-strict", "testing"),
|
|
4679
|
-
"security-reviewer": skillSet("security-scanner", "chimera", "api-design", "tech-stack"),
|
|
4680
|
-
accessibility: skillSet("react-modern", "testing", "chimera", "security-scanner"),
|
|
4681
|
-
compliance: skillSet("security-scanner", "audit-log", "tech-stack", "chimera"),
|
|
4682
|
-
database: skillSet("api-design", "testing", "security-scanner", "observability"),
|
|
4683
|
-
api: skillSet("api-design", "typescript-strict", "node-modern", "testing", "security-scanner"),
|
|
4684
|
-
auth: skillSet("security-scanner", "api-design", "testing", "typescript-strict", "audit-log"),
|
|
4685
|
-
data: skillSet("typescript-strict", "testing", "observability", "security-scanner"),
|
|
4686
|
-
frontend: skillSet("react-modern", "typescript-strict", "testing", "security-scanner"),
|
|
4687
|
-
backend: skillSet("node-modern", "typescript-strict", "api-design", "testing", "observability"),
|
|
4688
|
-
designer: skillSet("react-modern", "prompt-engineering", "research-web", "output-standards"),
|
|
4689
|
-
ios: skillSet("sdd", "testing", "security-scanner", "research-web"),
|
|
4690
|
-
document: skillSet("output-standards", "research-web", "api-design", "prompt-engineering"),
|
|
4691
|
-
uml: skillSet("sdd", "refactor-planner", "api-design", "output-standards"),
|
|
4692
|
-
i18n: skillSet("react-modern", "testing", "typescript-strict", "output-standards"),
|
|
4693
|
-
prompt: skillSet("prompt-engineering", "skill-creator", "output-standards", "testing"),
|
|
4694
|
-
git: skillSet("git-flow", "chimera", "testing", "security-scanner"),
|
|
4695
|
-
release: skillSet("git-flow", "tech-stack", "chimera", "security-scanner"),
|
|
4696
|
-
devops: skillSet("docker-deploy", "observability", "security-scanner", "git-flow", "tech-stack"),
|
|
4697
|
-
observability: skillSet("observability", "node-modern", "testing", "audit-log"),
|
|
4698
|
-
dependency: skillSet("tech-stack", "security-scanner", "node-modern", "git-flow"),
|
|
4699
|
-
"skill-manage": skillSet(
|
|
4700
|
-
"skill-creator",
|
|
4701
|
-
"prompt-engineering",
|
|
4702
|
-
"security-scanner",
|
|
4703
|
-
"output-standards"
|
|
4704
|
-
),
|
|
4705
|
-
"self-improving": skillSet(
|
|
4706
|
-
"audit-log",
|
|
4707
|
-
"mnemosyne",
|
|
4708
|
-
"prompt-engineering",
|
|
4709
|
-
"bug-hunter",
|
|
4710
|
-
"observability"
|
|
4711
|
-
),
|
|
4712
|
-
context: skillSet("mnemosyne", "output-standards", "prompt-engineering", "audit-log"),
|
|
4713
|
-
cost: skillSet("audit-log", "tech-stack", "observability", "research-web"),
|
|
4714
|
-
"tech-stack": skillSet("tech-stack", "research-web", "security-scanner", "node-modern"),
|
|
4715
|
-
// ── Wave 1: Platform and systems engineering ──────────────────────────
|
|
4716
|
-
android: skillSet(
|
|
4717
|
-
"react-modern",
|
|
4718
|
-
"typescript-strict",
|
|
4719
|
-
"testing",
|
|
4720
|
-
"security-scanner",
|
|
4721
|
-
"tech-stack"
|
|
4722
|
-
),
|
|
4723
|
-
desktop: skillSet(
|
|
4724
|
-
"node-modern",
|
|
4725
|
-
"typescript-strict",
|
|
4726
|
-
"testing",
|
|
4727
|
-
"security-scanner",
|
|
4728
|
-
"tech-stack"
|
|
4729
|
-
),
|
|
4730
|
-
realtime: skillSet(
|
|
4731
|
-
"api-design",
|
|
4732
|
-
"observability",
|
|
4733
|
-
"node-modern",
|
|
4734
|
-
"typescript-strict",
|
|
4735
|
-
"testing",
|
|
4736
|
-
"tech-stack"
|
|
4737
|
-
),
|
|
4738
|
-
"distributed-systems": skillSet(
|
|
4739
|
-
"api-design",
|
|
4740
|
-
"observability",
|
|
4741
|
-
"typescript-strict",
|
|
4742
|
-
"testing",
|
|
4743
|
-
"security-scanner",
|
|
4744
|
-
"tech-stack"
|
|
4745
|
-
),
|
|
4746
|
-
concurrency: skillSet(
|
|
4747
|
-
"bug-hunter",
|
|
4748
|
-
"typescript-strict",
|
|
4749
|
-
"testing",
|
|
4750
|
-
"observability",
|
|
4751
|
-
"node-modern",
|
|
4752
|
-
"tech-stack"
|
|
4753
|
-
),
|
|
4754
|
-
"platform-engineer": skillSet(
|
|
4755
|
-
"node-modern",
|
|
4756
|
-
"typescript-strict",
|
|
4757
|
-
"git-flow",
|
|
4758
|
-
"docker-deploy",
|
|
4759
|
-
"tech-stack"
|
|
4760
|
-
),
|
|
4761
|
-
// ── Wave 3: Product and data specialists ──────────────────────────────
|
|
4762
|
-
payments: skillSet(
|
|
4763
|
-
"api-design",
|
|
4764
|
-
"security-scanner",
|
|
4765
|
-
"audit-log",
|
|
4766
|
-
"testing",
|
|
4767
|
-
"typescript-strict",
|
|
4768
|
-
"tech-stack"
|
|
4769
|
-
),
|
|
4770
|
-
messaging: skillSet(
|
|
4771
|
-
"api-design",
|
|
4772
|
-
"observability",
|
|
4773
|
-
"node-modern",
|
|
4774
|
-
"testing",
|
|
4775
|
-
"security-scanner",
|
|
4776
|
-
"tech-stack"
|
|
4777
|
-
),
|
|
4778
|
-
"search-relevance": skillSet(
|
|
4779
|
-
"api-design",
|
|
4780
|
-
"observability",
|
|
4781
|
-
"testing",
|
|
4782
|
-
"typescript-strict",
|
|
4783
|
-
"data-governance",
|
|
4784
|
-
"tech-stack"
|
|
4785
|
-
),
|
|
4786
|
-
storage: skillSet(
|
|
4787
|
-
"api-design",
|
|
4788
|
-
"security-scanner",
|
|
4789
|
-
"testing",
|
|
4790
|
-
"observability",
|
|
4791
|
-
"node-modern",
|
|
4792
|
-
"tech-stack"
|
|
4793
|
-
),
|
|
4794
|
-
"ml-engineer": skillSet(
|
|
4795
|
-
"observability",
|
|
4796
|
-
"typescript-strict",
|
|
4797
|
-
"testing",
|
|
4798
|
-
"security-scanner",
|
|
4799
|
-
"data-governance",
|
|
4800
|
-
"tech-stack"
|
|
4801
|
-
),
|
|
4802
|
-
"data-governance": skillSet(
|
|
4803
|
-
"audit-log",
|
|
4804
|
-
"security-scanner",
|
|
4805
|
-
"observability",
|
|
4806
|
-
"testing",
|
|
4807
|
-
"output-standards",
|
|
4808
|
-
"tech-stack"
|
|
4809
|
-
),
|
|
4810
|
-
// ── Wave 4: Agent platform and quality ───────────────────────────────
|
|
4811
|
-
"plugin-author": skillSet(
|
|
4812
|
-
"plugin-author",
|
|
4813
|
-
"node-modern",
|
|
4814
|
-
"typescript-strict",
|
|
4815
|
-
"testing",
|
|
4816
|
-
"security-scanner",
|
|
4817
|
-
"tech-stack"
|
|
4818
|
-
),
|
|
4819
|
-
"tool-author": skillSet(
|
|
4820
|
-
"typescript-strict",
|
|
4821
|
-
"security-scanner",
|
|
4822
|
-
"testing",
|
|
4823
|
-
"output-standards",
|
|
4824
|
-
"tech-stack"
|
|
4825
|
-
),
|
|
4826
|
-
"prompt-evaluator": skillSet(
|
|
4827
|
-
"prompt-engineering",
|
|
4828
|
-
"testing",
|
|
4829
|
-
"output-standards",
|
|
4830
|
-
"security-scanner",
|
|
4831
|
-
"tech-stack"
|
|
4832
|
-
),
|
|
4833
|
-
"benchmark-engineer": skillSet("observability", "testing", "output-standards", "tech-stack"),
|
|
4834
|
-
"memory-curator": skillSet("mnemosyne", "audit-log", "testing", "output-standards", "tech-stack"),
|
|
4835
|
-
"fleet-coordinator": skillSet("multi-agent", "sdd", "output-standards", "testing", "tech-stack"),
|
|
4836
|
-
// ── Wave 2: Security, resilience, governance and agent platform ────────
|
|
4837
|
-
"threat-modeler": skillSet(
|
|
4838
|
-
"security-scanner",
|
|
4839
|
-
"api-design",
|
|
4840
|
-
"tech-stack",
|
|
4841
|
-
"audit-log",
|
|
4842
|
-
"testing"
|
|
4843
|
-
),
|
|
4844
|
-
"secure-coding-coach": skillSet(
|
|
4845
|
-
"security-scanner",
|
|
4846
|
-
"testing",
|
|
4847
|
-
"typescript-strict",
|
|
4848
|
-
"api-design",
|
|
4849
|
-
"tech-stack"
|
|
4850
|
-
),
|
|
4851
|
-
"resilience-engineer": skillSet(
|
|
4852
|
-
"observability",
|
|
4853
|
-
"testing",
|
|
4854
|
-
"typescript-strict",
|
|
4855
|
-
"api-design",
|
|
4856
|
-
"tech-stack"
|
|
4857
|
-
),
|
|
4858
|
-
"chaos-engineer": skillSet(
|
|
4859
|
-
"observability",
|
|
4860
|
-
"testing",
|
|
4861
|
-
"security-scanner",
|
|
4862
|
-
"git-flow",
|
|
4863
|
-
"tech-stack"
|
|
4864
|
-
),
|
|
4865
|
-
"compliance-auditor": skillSet(
|
|
4866
|
-
"audit-log",
|
|
4867
|
-
"security-scanner",
|
|
4868
|
-
"output-standards",
|
|
4869
|
-
"testing",
|
|
4870
|
-
"tech-stack"
|
|
4871
|
-
),
|
|
4872
|
-
"privacy-engineer": skillSet(
|
|
4873
|
-
"security-scanner",
|
|
4874
|
-
"audit-log",
|
|
4875
|
-
"api-design",
|
|
4876
|
-
"data-governance",
|
|
4877
|
-
"tech-stack"
|
|
4878
|
-
)
|
|
4879
|
-
};
|
|
4880
|
-
var SHADOW_AGENT_SKILLS = skillSet(
|
|
4881
|
-
"wrongstack-mailbox",
|
|
4882
|
-
"multi-agent",
|
|
4883
|
-
"audit-log",
|
|
4884
|
-
"observability",
|
|
4885
|
-
"security-scanner"
|
|
4886
|
-
);
|
|
4887
|
-
var MAX_EAGER_ROSTER_SKILLS = 3;
|
|
4888
|
-
function assignSkillsToAgents(definitions) {
|
|
4889
|
-
return definitions.map((definition) => {
|
|
4890
|
-
const role = definition.config.role;
|
|
4891
|
-
if (!role || !Object.hasOwn(ROLE_SKILL_SETS, role)) {
|
|
4892
|
-
throw new Error(
|
|
4893
|
-
`Missing curated skill set for roster role: "${role ?? definition.config.name}"`
|
|
4894
|
-
);
|
|
4895
|
-
}
|
|
4896
|
-
const skills = ROLE_SKILL_SETS[role];
|
|
4897
|
-
return {
|
|
4898
|
-
...definition,
|
|
4899
|
-
config: {
|
|
4900
|
-
...definition.config,
|
|
4901
|
-
capabilities: inferRuntimeCapabilities(definition.config.tools ?? []),
|
|
4902
|
-
skillNames: skills.slice(0, MAX_EAGER_ROSTER_SKILLS)
|
|
4903
|
-
}
|
|
4904
|
-
};
|
|
4905
|
-
});
|
|
4906
|
-
}
|
|
4907
|
-
|
|
4908
5133
|
// src/coordination/agents/index.ts
|
|
4909
5134
|
var ALL_AGENT_DEFINITIONS = assignSkillsToAgents([
|
|
4910
5135
|
...DISCOVERY_AGENTS,
|
|
@@ -5020,7 +5245,7 @@ function attachAutoExtend(events, policy = {}) {
|
|
|
5020
5245
|
// src/coordination/delegate-tool.ts
|
|
5021
5246
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
5022
5247
|
import * as fsp from "node:fs/promises";
|
|
5023
|
-
import * as
|
|
5248
|
+
import * as path7 from "node:path";
|
|
5024
5249
|
|
|
5025
5250
|
// src/utils/safe-json.ts
|
|
5026
5251
|
function safeParse(input, maxBytes = 5e6) {
|
|
@@ -5510,7 +5735,7 @@ function createDelegateTool(opts) {
|
|
|
5510
5735
|
};
|
|
5511
5736
|
}
|
|
5512
5737
|
async function awaitDelegateAttempt(director, subagentId, taskId, timeoutMs, abortSignal) {
|
|
5513
|
-
return new Promise((
|
|
5738
|
+
return new Promise((resolve21) => {
|
|
5514
5739
|
let settled = false;
|
|
5515
5740
|
let timer;
|
|
5516
5741
|
let offAbort = () => {
|
|
@@ -5523,7 +5748,7 @@ async function awaitDelegateAttempt(director, subagentId, taskId, timeoutMs, abo
|
|
|
5523
5748
|
offIter();
|
|
5524
5749
|
offProgress();
|
|
5525
5750
|
offAbort();
|
|
5526
|
-
|
|
5751
|
+
resolve21(value);
|
|
5527
5752
|
};
|
|
5528
5753
|
const arm = () => {
|
|
5529
5754
|
if (timer) clearTimeout(timer);
|
|
@@ -5683,13 +5908,13 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
5683
5908
|
if (!opts.sessionsRoot) return void 0;
|
|
5684
5909
|
const candidates = [];
|
|
5685
5910
|
if (opts.directorRunId) {
|
|
5686
|
-
candidates.push(
|
|
5911
|
+
candidates.push(path7.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
|
|
5687
5912
|
} else {
|
|
5688
5913
|
try {
|
|
5689
5914
|
const entries = await fsp.readdir(opts.sessionsRoot, { withFileTypes: true });
|
|
5690
5915
|
for (const entry of entries) {
|
|
5691
5916
|
if (entry.isDirectory()) {
|
|
5692
|
-
candidates.push(
|
|
5917
|
+
candidates.push(path7.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
|
|
5693
5918
|
}
|
|
5694
5919
|
}
|
|
5695
5920
|
} catch {
|
|
@@ -5734,7 +5959,7 @@ async function readSubagentPartial(opts, subagentId) {
|
|
|
5734
5959
|
}
|
|
5735
5960
|
|
|
5736
5961
|
// src/coordination/director.ts
|
|
5737
|
-
import { randomUUID as
|
|
5962
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
5738
5963
|
import * as fsp19 from "node:fs/promises";
|
|
5739
5964
|
|
|
5740
5965
|
// src/core/instruction-template.ts
|
|
@@ -6116,7 +6341,7 @@ var DirectorStateCheckpoint = class {
|
|
|
6116
6341
|
|
|
6117
6342
|
// src/coordination/checkpoint-wiring.ts
|
|
6118
6343
|
import * as fsp3 from "node:fs/promises";
|
|
6119
|
-
import * as
|
|
6344
|
+
import * as path8 from "node:path";
|
|
6120
6345
|
async function appendSessionEvent(host, event) {
|
|
6121
6346
|
if (!host.sessionWriter) return;
|
|
6122
6347
|
try {
|
|
@@ -6164,7 +6389,7 @@ async function writeManifest(host) {
|
|
|
6164
6389
|
}),
|
|
6165
6390
|
usage: host.usage.snapshot()
|
|
6166
6391
|
};
|
|
6167
|
-
await fsp3.mkdir(
|
|
6392
|
+
await fsp3.mkdir(path8.dirname(host.manifestPath), { recursive: true });
|
|
6168
6393
|
await atomicWrite(host.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
|
|
6169
6394
|
return host.manifestPath;
|
|
6170
6395
|
}
|
|
@@ -7524,10 +7749,10 @@ var DirectorTaskRegistry = class _DirectorTaskRegistry {
|
|
|
7524
7749
|
pending: taskIds.filter((id) => !done.has(id))
|
|
7525
7750
|
});
|
|
7526
7751
|
}
|
|
7527
|
-
return new Promise((
|
|
7752
|
+
return new Promise((resolve21) => {
|
|
7528
7753
|
const entry = {
|
|
7529
7754
|
ids: new Set(taskIds),
|
|
7530
|
-
resolve: (result) =>
|
|
7755
|
+
resolve: (result) => resolve21({
|
|
7531
7756
|
completed: [result],
|
|
7532
7757
|
pending: taskIds.filter((id) => id !== result.taskId)
|
|
7533
7758
|
})
|
|
@@ -7535,7 +7760,7 @@ var DirectorTaskRegistry = class _DirectorTaskRegistry {
|
|
|
7535
7760
|
if (opts?.timeoutMs !== void 0) {
|
|
7536
7761
|
entry.timer = setTimeout(() => {
|
|
7537
7762
|
this.anyWaiters.delete(entry);
|
|
7538
|
-
|
|
7763
|
+
resolve21({ completed: [], pending: [...taskIds], timedOut: true });
|
|
7539
7764
|
}, opts.timeoutMs);
|
|
7540
7765
|
}
|
|
7541
7766
|
this.anyWaiters.add(entry);
|
|
@@ -7653,11 +7878,11 @@ ${JSON.stringify(result.result, null, 2)}
|
|
|
7653
7878
|
this.makeStoppedResult(taskId, "director", `Unknown task id "${taskId}" \u2014 never assigned`)
|
|
7654
7879
|
);
|
|
7655
7880
|
}
|
|
7656
|
-
let
|
|
7881
|
+
let resolve21;
|
|
7657
7882
|
const promise = new Promise((done) => {
|
|
7658
|
-
|
|
7883
|
+
resolve21 = done;
|
|
7659
7884
|
});
|
|
7660
|
-
this.taskWaiters.set(taskId, { promise, resolve:
|
|
7885
|
+
this.taskWaiters.set(taskId, { promise, resolve: resolve21 });
|
|
7661
7886
|
return promise;
|
|
7662
7887
|
}
|
|
7663
7888
|
recordAssignment(task) {
|
|
@@ -7918,8 +8143,8 @@ function ensureKanbanTool(tools) {
|
|
|
7918
8143
|
}
|
|
7919
8144
|
|
|
7920
8145
|
// src/utils/instruction-file.ts
|
|
7921
|
-
import { readFileSync as
|
|
7922
|
-
import * as
|
|
8146
|
+
import { readFileSync as readFileSync7, statSync as statSync3 } from "node:fs";
|
|
8147
|
+
import * as path9 from "node:path";
|
|
7923
8148
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
7924
8149
|
var textCache = /* @__PURE__ */ new Map();
|
|
7925
8150
|
var rootCandidates;
|
|
@@ -7929,7 +8154,7 @@ function readBundledInstructionText(relativePath) {
|
|
|
7929
8154
|
let resolved = "";
|
|
7930
8155
|
for (const root of instructionRootCandidates()) {
|
|
7931
8156
|
try {
|
|
7932
|
-
resolved =
|
|
8157
|
+
resolved = readFileSync7(path9.join(root, relativePath), "utf8").trimEnd();
|
|
7933
8158
|
break;
|
|
7934
8159
|
} catch {
|
|
7935
8160
|
}
|
|
@@ -7945,11 +8170,11 @@ function renderInstructionTemplate(template, values) {
|
|
|
7945
8170
|
}
|
|
7946
8171
|
function instructionRootCandidates() {
|
|
7947
8172
|
if (rootCandidates !== void 0) return rootCandidates;
|
|
7948
|
-
const here =
|
|
8173
|
+
const here = path9.dirname(fileURLToPath2(import.meta.url));
|
|
7949
8174
|
const candidates = [
|
|
7950
|
-
|
|
7951
|
-
|
|
7952
|
-
|
|
8175
|
+
path9.resolve(here, "../../instructions"),
|
|
8176
|
+
path9.resolve(here, "../instructions"),
|
|
8177
|
+
path9.resolve(here, "instructions")
|
|
7953
8178
|
];
|
|
7954
8179
|
rootCandidates = candidates.sort((a, b) => Number(!isDirectory2(a)) - Number(!isDirectory2(b)));
|
|
7955
8180
|
return rootCandidates;
|
|
@@ -9547,11 +9772,12 @@ function rosterSummaryFromConfigs(roster) {
|
|
|
9547
9772
|
|
|
9548
9773
|
// src/coordination/director-session.ts
|
|
9549
9774
|
import * as fsp18 from "node:fs/promises";
|
|
9550
|
-
import * as
|
|
9775
|
+
import * as path24 from "node:path";
|
|
9551
9776
|
|
|
9552
9777
|
// src/storage/session-store.ts
|
|
9778
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
9553
9779
|
import * as fsp17 from "node:fs/promises";
|
|
9554
|
-
import * as
|
|
9780
|
+
import * as path23 from "node:path";
|
|
9555
9781
|
|
|
9556
9782
|
// src/security/file-permissions.ts
|
|
9557
9783
|
import { chmod } from "node:fs/promises";
|
|
@@ -9881,6 +10107,388 @@ var DefaultSecretScrubber = class {
|
|
|
9881
10107
|
}
|
|
9882
10108
|
};
|
|
9883
10109
|
|
|
10110
|
+
// src/session-catalog/client.ts
|
|
10111
|
+
import { spawn } from "node:child_process";
|
|
10112
|
+
import * as fs2 from "node:fs";
|
|
10113
|
+
import * as net from "node:net";
|
|
10114
|
+
import * as path11 from "node:path";
|
|
10115
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
10116
|
+
|
|
10117
|
+
// src/session-catalog/endpoint.ts
|
|
10118
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
10119
|
+
import * as os2 from "node:os";
|
|
10120
|
+
import * as path10 from "node:path";
|
|
10121
|
+
|
|
10122
|
+
// src/session-catalog/protocol.ts
|
|
10123
|
+
var SESSION_CATALOG_PROTOCOL_VERSION = 1;
|
|
10124
|
+
var SESSION_CATALOG_MAX_FRAME_CHARS = 4 * 1024 * 1024;
|
|
10125
|
+
function encodeSessionCatalogMessage(message) {
|
|
10126
|
+
return `${JSON.stringify(message)}
|
|
10127
|
+
`;
|
|
10128
|
+
}
|
|
10129
|
+
|
|
10130
|
+
// src/session-catalog/endpoint.ts
|
|
10131
|
+
var SESSION_CATALOG_METADATA_FILE = ".session-catalog-server.json";
|
|
10132
|
+
function normalizedPath(value) {
|
|
10133
|
+
const resolved = path10.resolve(value);
|
|
10134
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
10135
|
+
}
|
|
10136
|
+
function sessionCatalogProjectServerKey(projectDir) {
|
|
10137
|
+
return createHash2("sha256").update(normalizedPath(projectDir)).digest("hex").slice(0, 24);
|
|
10138
|
+
}
|
|
10139
|
+
function sessionCatalogProjectServerEndpoint(projectDir) {
|
|
10140
|
+
const key = sessionCatalogProjectServerKey(projectDir);
|
|
10141
|
+
if (process.platform === "win32") {
|
|
10142
|
+
return `\\\\.\\pipe\\wrongstack-session-catalog-v${SESSION_CATALOG_PROTOCOL_VERSION}-${key}`;
|
|
10143
|
+
}
|
|
10144
|
+
return path10.join(os2.tmpdir(), `wssc-v${SESSION_CATALOG_PROTOCOL_VERSION}`, `${key}.sock`);
|
|
10145
|
+
}
|
|
10146
|
+
function sessionCatalogProjectServerMetadataPath(projectDir) {
|
|
10147
|
+
return path10.join(projectDir, SESSION_CATALOG_METADATA_FILE);
|
|
10148
|
+
}
|
|
10149
|
+
|
|
10150
|
+
// src/session-catalog/client.ts
|
|
10151
|
+
var CONNECT_TIMEOUT_MS = 750;
|
|
10152
|
+
var START_TIMEOUT_MS = 1e4;
|
|
10153
|
+
var CALL_TIMEOUT_MS = 3e4;
|
|
10154
|
+
var MAX_PENDING_REQUESTS = 1024;
|
|
10155
|
+
var MAX_EVENT_LISTENERS = 64;
|
|
10156
|
+
function locateServer(moduleUrl, exists) {
|
|
10157
|
+
for (const candidate of [
|
|
10158
|
+
"./project-server.js",
|
|
10159
|
+
"../session-catalog/project-server.js",
|
|
10160
|
+
"./session-catalog/project-server.js",
|
|
10161
|
+
"../../dist/session-catalog/project-server.js"
|
|
10162
|
+
]) {
|
|
10163
|
+
try {
|
|
10164
|
+
const url = new URL(candidate, moduleUrl);
|
|
10165
|
+
if (url.protocol === "file:" && exists(fileURLToPath3(url))) return url;
|
|
10166
|
+
} catch {
|
|
10167
|
+
}
|
|
10168
|
+
}
|
|
10169
|
+
return null;
|
|
10170
|
+
}
|
|
10171
|
+
function resolveSessionCatalogDaemonAvailability(moduleUrl = import.meta.url, exists = fs2.existsSync) {
|
|
10172
|
+
if (process.env["WRONGSTACK_SESSION_CATALOG_INLINE"] || process.env["WRONGSTACK_SESSION_CATALOG_SERVER"] === "0")
|
|
10173
|
+
return { kind: "inline-requested" };
|
|
10174
|
+
const url = locateServer(moduleUrl, exists);
|
|
10175
|
+
return url ? { kind: "available", url } : { kind: "missing-build" };
|
|
10176
|
+
}
|
|
10177
|
+
function resolveSessionCatalogProjectServerUrl(moduleUrl = import.meta.url, exists = fs2.existsSync) {
|
|
10178
|
+
const availability = resolveSessionCatalogDaemonAvailability(moduleUrl, exists);
|
|
10179
|
+
return availability.kind === "available" ? availability.url : null;
|
|
10180
|
+
}
|
|
10181
|
+
function normalize2(value) {
|
|
10182
|
+
const resolved = path11.resolve(value);
|
|
10183
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
10184
|
+
}
|
|
10185
|
+
function delay(ms) {
|
|
10186
|
+
return new Promise((resolve21) => setTimeout(resolve21, ms));
|
|
10187
|
+
}
|
|
10188
|
+
var SessionCatalogProjectClient = class {
|
|
10189
|
+
constructor(options) {
|
|
10190
|
+
this.options = options;
|
|
10191
|
+
this.endpoint = sessionCatalogProjectServerEndpoint(options.projectDir);
|
|
10192
|
+
}
|
|
10193
|
+
options;
|
|
10194
|
+
endpoint;
|
|
10195
|
+
socket = null;
|
|
10196
|
+
info = null;
|
|
10197
|
+
buffer = "";
|
|
10198
|
+
connecting = null;
|
|
10199
|
+
connectResolve = null;
|
|
10200
|
+
connectReject = null;
|
|
10201
|
+
authToken;
|
|
10202
|
+
nextId = 1;
|
|
10203
|
+
pending = /* @__PURE__ */ new Map();
|
|
10204
|
+
eventListeners = /* @__PURE__ */ new Set();
|
|
10205
|
+
reconnectTimer;
|
|
10206
|
+
explicitlyClosed = false;
|
|
10207
|
+
async call(op, args, options = {}) {
|
|
10208
|
+
await this.ensureConnected(true);
|
|
10209
|
+
return this.request({ type: "request", op, args }, options.timeoutMs ?? CALL_TIMEOUT_MS);
|
|
10210
|
+
}
|
|
10211
|
+
/**
|
|
10212
|
+
* Call an already-running project daemon without starting one.
|
|
10213
|
+
*
|
|
10214
|
+
* Cross-project discovery must use this path: observing another project is
|
|
10215
|
+
* never sufficient authority to wake that project's IPC owner.
|
|
10216
|
+
*/
|
|
10217
|
+
async callExisting(op, args, options = {}) {
|
|
10218
|
+
await this.ensureConnected(false);
|
|
10219
|
+
return this.request({ type: "request", op, args }, options.timeoutMs ?? CALL_TIMEOUT_MS);
|
|
10220
|
+
}
|
|
10221
|
+
ping() {
|
|
10222
|
+
return this.call("ping", {}, { timeoutMs: 3e3 });
|
|
10223
|
+
}
|
|
10224
|
+
async subscribe(listener) {
|
|
10225
|
+
if (!this.eventListeners.has(listener) && this.eventListeners.size >= MAX_EVENT_LISTENERS) {
|
|
10226
|
+
throw new Error(`Session Catalog listener limit reached (${MAX_EVENT_LISTENERS})`);
|
|
10227
|
+
}
|
|
10228
|
+
this.explicitlyClosed = false;
|
|
10229
|
+
this.eventListeners.add(listener);
|
|
10230
|
+
try {
|
|
10231
|
+
await this.call("subscribe", {});
|
|
10232
|
+
} catch (error2) {
|
|
10233
|
+
this.eventListeners.delete(listener);
|
|
10234
|
+
throw error2;
|
|
10235
|
+
}
|
|
10236
|
+
return async () => {
|
|
10237
|
+
this.eventListeners.delete(listener);
|
|
10238
|
+
if (this.eventListeners.size === 0)
|
|
10239
|
+
await this.callExisting("unsubscribe", {}).catch(() => void 0);
|
|
10240
|
+
};
|
|
10241
|
+
}
|
|
10242
|
+
async shutdown(reason) {
|
|
10243
|
+
try {
|
|
10244
|
+
await this.ensureConnected(false);
|
|
10245
|
+
} catch {
|
|
10246
|
+
return { stopped: false };
|
|
10247
|
+
}
|
|
10248
|
+
const result = await this.request({ type: "shutdown", ...reason !== void 0 ? { reason } : {} }, 5e3).catch(
|
|
10249
|
+
() => ({ stopped: false })
|
|
10250
|
+
);
|
|
10251
|
+
if (result.stopped) {
|
|
10252
|
+
const metadataPath = sessionCatalogProjectServerMetadataPath(this.options.projectDir);
|
|
10253
|
+
const deadline = Date.now() + 5e3;
|
|
10254
|
+
while (fs2.existsSync(metadataPath) && Date.now() < deadline) await delay(20);
|
|
10255
|
+
if (result.pid && result.pid !== process.pid) {
|
|
10256
|
+
while (Date.now() < deadline) {
|
|
10257
|
+
try {
|
|
10258
|
+
process.kill(result.pid, 0);
|
|
10259
|
+
await delay(20);
|
|
10260
|
+
} catch {
|
|
10261
|
+
break;
|
|
10262
|
+
}
|
|
10263
|
+
}
|
|
10264
|
+
}
|
|
10265
|
+
}
|
|
10266
|
+
return result;
|
|
10267
|
+
}
|
|
10268
|
+
async close() {
|
|
10269
|
+
this.explicitlyClosed = true;
|
|
10270
|
+
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
10271
|
+
this.reconnectTimer = void 0;
|
|
10272
|
+
const socket = this.socket;
|
|
10273
|
+
this.socket = null;
|
|
10274
|
+
this.info = null;
|
|
10275
|
+
if (socket && !socket.destroyed)
|
|
10276
|
+
await new Promise((resolve21) => {
|
|
10277
|
+
socket.once("close", resolve21);
|
|
10278
|
+
socket.end();
|
|
10279
|
+
});
|
|
10280
|
+
}
|
|
10281
|
+
async ensureConnected(spawnIfMissing) {
|
|
10282
|
+
if (this.socket && !this.socket.destroyed && this.info) return;
|
|
10283
|
+
if (this.connecting) return this.connecting;
|
|
10284
|
+
this.connecting = this.connectWithElection(spawnIfMissing).finally(() => {
|
|
10285
|
+
this.connecting = null;
|
|
10286
|
+
});
|
|
10287
|
+
return this.connecting;
|
|
10288
|
+
}
|
|
10289
|
+
async connectWithElection(spawnIfMissing) {
|
|
10290
|
+
const deadline = Date.now() + (spawnIfMissing ? START_TIMEOUT_MS : CONNECT_TIMEOUT_MS);
|
|
10291
|
+
let spawned = false;
|
|
10292
|
+
let lastError = new Error("Session Catalog project server unavailable");
|
|
10293
|
+
while (Date.now() < deadline) {
|
|
10294
|
+
try {
|
|
10295
|
+
await this.connectOnce();
|
|
10296
|
+
return;
|
|
10297
|
+
} catch (error2) {
|
|
10298
|
+
lastError = error2;
|
|
10299
|
+
}
|
|
10300
|
+
if (!spawnIfMissing) break;
|
|
10301
|
+
if (!spawned) {
|
|
10302
|
+
this.spawnDetached();
|
|
10303
|
+
spawned = true;
|
|
10304
|
+
}
|
|
10305
|
+
await delay(75);
|
|
10306
|
+
}
|
|
10307
|
+
throw lastError;
|
|
10308
|
+
}
|
|
10309
|
+
connectOnce() {
|
|
10310
|
+
this.socket?.destroy();
|
|
10311
|
+
this.socket = null;
|
|
10312
|
+
this.info = null;
|
|
10313
|
+
this.authToken = void 0;
|
|
10314
|
+
this.buffer = "";
|
|
10315
|
+
return new Promise((resolve21, reject) => {
|
|
10316
|
+
const socket = net.createConnection(this.endpoint);
|
|
10317
|
+
this.socket = socket;
|
|
10318
|
+
socket.setEncoding("utf8");
|
|
10319
|
+
const timer = setTimeout(() => {
|
|
10320
|
+
reject(new Error("Session Catalog handshake timed out"));
|
|
10321
|
+
socket.destroy();
|
|
10322
|
+
}, CONNECT_TIMEOUT_MS);
|
|
10323
|
+
timer.unref?.();
|
|
10324
|
+
this.connectResolve = () => {
|
|
10325
|
+
clearTimeout(timer);
|
|
10326
|
+
this.connectResolve = null;
|
|
10327
|
+
this.connectReject = null;
|
|
10328
|
+
resolve21();
|
|
10329
|
+
};
|
|
10330
|
+
this.connectReject = (error2) => {
|
|
10331
|
+
clearTimeout(timer);
|
|
10332
|
+
this.connectResolve = null;
|
|
10333
|
+
this.connectReject = null;
|
|
10334
|
+
reject(error2);
|
|
10335
|
+
};
|
|
10336
|
+
socket.on("data", (chunk) => this.onData(socket, chunk));
|
|
10337
|
+
socket.on("error", (error2) => {
|
|
10338
|
+
if (!this.info) this.connectReject?.(error2);
|
|
10339
|
+
});
|
|
10340
|
+
socket.on("close", () => this.onClose(socket));
|
|
10341
|
+
});
|
|
10342
|
+
}
|
|
10343
|
+
currentAuthToken() {
|
|
10344
|
+
if (this.authToken === void 0) {
|
|
10345
|
+
try {
|
|
10346
|
+
const parsed = JSON.parse(
|
|
10347
|
+
fs2.readFileSync(sessionCatalogProjectServerMetadataPath(this.options.projectDir), "utf8")
|
|
10348
|
+
);
|
|
10349
|
+
if (typeof parsed.authToken === "string" && parsed.authToken)
|
|
10350
|
+
this.authToken = parsed.authToken;
|
|
10351
|
+
} catch {
|
|
10352
|
+
}
|
|
10353
|
+
}
|
|
10354
|
+
return this.authToken;
|
|
10355
|
+
}
|
|
10356
|
+
request(message, timeoutMs) {
|
|
10357
|
+
const socket = this.socket;
|
|
10358
|
+
if (!socket || socket.destroyed)
|
|
10359
|
+
return Promise.reject(new Error("Session Catalog connection is unavailable"));
|
|
10360
|
+
const id = this.nextId++;
|
|
10361
|
+
if (this.pending.size >= MAX_PENDING_REQUESTS) {
|
|
10362
|
+
return Promise.reject(
|
|
10363
|
+
new Error(`Session Catalog pending request limit reached (${MAX_PENDING_REQUESTS})`)
|
|
10364
|
+
);
|
|
10365
|
+
}
|
|
10366
|
+
const encoded = encodeSessionCatalogMessage({
|
|
10367
|
+
...message,
|
|
10368
|
+
id,
|
|
10369
|
+
authToken: this.currentAuthToken()
|
|
10370
|
+
});
|
|
10371
|
+
if (encoded.length > SESSION_CATALOG_MAX_FRAME_CHARS)
|
|
10372
|
+
return Promise.reject(new Error("Session Catalog request exceeded frame limit"));
|
|
10373
|
+
return new Promise((resolve21, reject) => {
|
|
10374
|
+
const timer = setTimeout(() => {
|
|
10375
|
+
const pending = this.pending.get(id);
|
|
10376
|
+
if (!pending) return;
|
|
10377
|
+
this.pending.delete(id);
|
|
10378
|
+
pending.reject(
|
|
10379
|
+
new Error(
|
|
10380
|
+
`Session Catalog ${message.type === "request" ? message.op : message.type} timed out`
|
|
10381
|
+
)
|
|
10382
|
+
);
|
|
10383
|
+
}, timeoutMs);
|
|
10384
|
+
timer.unref?.();
|
|
10385
|
+
this.pending.set(id, { resolve: resolve21, reject, timer });
|
|
10386
|
+
socket.write(encoded);
|
|
10387
|
+
});
|
|
10388
|
+
}
|
|
10389
|
+
onData(socket, chunk) {
|
|
10390
|
+
if (socket !== this.socket) return;
|
|
10391
|
+
this.buffer += chunk;
|
|
10392
|
+
if (this.buffer.length > SESSION_CATALOG_MAX_FRAME_CHARS) {
|
|
10393
|
+
socket.destroy(new Error("Session Catalog response exceeded frame limit"));
|
|
10394
|
+
return;
|
|
10395
|
+
}
|
|
10396
|
+
while (true) {
|
|
10397
|
+
const newline = this.buffer.indexOf("\n");
|
|
10398
|
+
if (newline < 0) return;
|
|
10399
|
+
const line = this.buffer.slice(0, newline);
|
|
10400
|
+
this.buffer = this.buffer.slice(newline + 1);
|
|
10401
|
+
if (!line) continue;
|
|
10402
|
+
try {
|
|
10403
|
+
this.onMessage(JSON.parse(line));
|
|
10404
|
+
} catch {
|
|
10405
|
+
socket.destroy(new Error("Invalid Session Catalog response"));
|
|
10406
|
+
return;
|
|
10407
|
+
}
|
|
10408
|
+
}
|
|
10409
|
+
}
|
|
10410
|
+
onMessage(message) {
|
|
10411
|
+
if (message.type === "hello") {
|
|
10412
|
+
if (message.protocolVersion !== SESSION_CATALOG_PROTOCOL_VERSION) {
|
|
10413
|
+
this.connectReject?.(
|
|
10414
|
+
new Error(
|
|
10415
|
+
`Session Catalog protocol mismatch: client=${SESSION_CATALOG_PROTOCOL_VERSION}, server=${message.protocolVersion}`
|
|
10416
|
+
)
|
|
10417
|
+
);
|
|
10418
|
+
this.socket?.destroy();
|
|
10419
|
+
return;
|
|
10420
|
+
}
|
|
10421
|
+
if (normalize2(message.projectDir) !== normalize2(this.options.projectDir) || normalize2(message.projectRoot) !== normalize2(this.options.projectRoot)) {
|
|
10422
|
+
this.connectReject?.(new Error("Session Catalog project identity mismatch"));
|
|
10423
|
+
this.socket?.destroy();
|
|
10424
|
+
return;
|
|
10425
|
+
}
|
|
10426
|
+
this.info = message;
|
|
10427
|
+
this.connectResolve?.();
|
|
10428
|
+
return;
|
|
10429
|
+
}
|
|
10430
|
+
if (message.type === "event") {
|
|
10431
|
+
for (const listener of this.eventListeners) {
|
|
10432
|
+
try {
|
|
10433
|
+
listener(message.event);
|
|
10434
|
+
} catch {
|
|
10435
|
+
}
|
|
10436
|
+
}
|
|
10437
|
+
return;
|
|
10438
|
+
}
|
|
10439
|
+
const pending = this.pending.get(message.id);
|
|
10440
|
+
if (!pending) return;
|
|
10441
|
+
this.pending.delete(message.id);
|
|
10442
|
+
clearTimeout(pending.timer);
|
|
10443
|
+
if (message.ok) pending.resolve(message.result);
|
|
10444
|
+
else {
|
|
10445
|
+
const error2 = new Error(message.error);
|
|
10446
|
+
if (message.errorName) error2.name = message.errorName;
|
|
10447
|
+
pending.reject(error2);
|
|
10448
|
+
}
|
|
10449
|
+
}
|
|
10450
|
+
onClose(socket) {
|
|
10451
|
+
if (socket !== this.socket) return;
|
|
10452
|
+
this.socket = null;
|
|
10453
|
+
this.info = null;
|
|
10454
|
+
this.authToken = void 0;
|
|
10455
|
+
const error2 = new Error("Session Catalog connection closed");
|
|
10456
|
+
this.connectReject?.(error2);
|
|
10457
|
+
this.connectResolve = null;
|
|
10458
|
+
this.connectReject = null;
|
|
10459
|
+
for (const pending of this.pending.values()) {
|
|
10460
|
+
clearTimeout(pending.timer);
|
|
10461
|
+
pending.reject(error2);
|
|
10462
|
+
}
|
|
10463
|
+
this.pending.clear();
|
|
10464
|
+
this.scheduleSubscriptionReconnect();
|
|
10465
|
+
}
|
|
10466
|
+
scheduleSubscriptionReconnect() {
|
|
10467
|
+
if (this.explicitlyClosed || this.eventListeners.size === 0 || this.reconnectTimer) return;
|
|
10468
|
+
this.reconnectTimer = setTimeout(() => {
|
|
10469
|
+
this.reconnectTimer = void 0;
|
|
10470
|
+
void this.call("subscribe", {}).catch(() => this.scheduleSubscriptionReconnect());
|
|
10471
|
+
}, 250);
|
|
10472
|
+
this.reconnectTimer.unref?.();
|
|
10473
|
+
}
|
|
10474
|
+
spawnDetached() {
|
|
10475
|
+
const url = resolveSessionCatalogProjectServerUrl();
|
|
10476
|
+
if (!url) throw new Error("Built Session Catalog project server is unavailable");
|
|
10477
|
+
const child = spawn(
|
|
10478
|
+
process.execPath,
|
|
10479
|
+
[
|
|
10480
|
+
fileURLToPath3(url),
|
|
10481
|
+
"--project-dir",
|
|
10482
|
+
this.options.projectDir,
|
|
10483
|
+
"--project-root",
|
|
10484
|
+
this.options.projectRoot
|
|
10485
|
+
],
|
|
10486
|
+
{ detached: true, stdio: "ignore", windowsHide: true, env: process.env }
|
|
10487
|
+
);
|
|
10488
|
+
child.unref();
|
|
10489
|
+
}
|
|
10490
|
+
};
|
|
10491
|
+
|
|
9884
10492
|
// src/utils/assert-never.ts
|
|
9885
10493
|
function assertNever(x, message) {
|
|
9886
10494
|
const err = new Error(
|
|
@@ -10087,20 +10695,20 @@ var color = {
|
|
|
10087
10695
|
};
|
|
10088
10696
|
|
|
10089
10697
|
// src/utils/config-backup.ts
|
|
10090
|
-
import * as
|
|
10091
|
-
import * as
|
|
10698
|
+
import * as fs3 from "node:fs/promises";
|
|
10699
|
+
import * as path12 from "node:path";
|
|
10092
10700
|
function configHistoryDir(globalRoot) {
|
|
10093
|
-
return
|
|
10701
|
+
return path12.join(globalRoot, "config-history");
|
|
10094
10702
|
}
|
|
10095
10703
|
function configSlug(absolutePath, globalRoot) {
|
|
10096
|
-
const rel =
|
|
10704
|
+
const rel = path12.relative(globalRoot, absolutePath);
|
|
10097
10705
|
const normalized = rel.replace(/\\/g, "/").replace(/\.json$/i, "");
|
|
10098
10706
|
return normalized.replace(/\//g, "-");
|
|
10099
10707
|
}
|
|
10100
10708
|
async function backupConfigFile(filePath, paths) {
|
|
10101
10709
|
let currentContent;
|
|
10102
10710
|
try {
|
|
10103
|
-
currentContent = await
|
|
10711
|
+
currentContent = await fs3.readFile(filePath, "utf8");
|
|
10104
10712
|
if (!currentContent.trim()) return;
|
|
10105
10713
|
} catch {
|
|
10106
10714
|
return;
|
|
@@ -10109,10 +10717,10 @@ async function backupConfigFile(filePath, paths) {
|
|
|
10109
10717
|
const ts = now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
|
|
10110
10718
|
const slug = configSlug(filePath, paths.globalRoot);
|
|
10111
10719
|
const backupDir = configHistoryDir(paths.globalRoot);
|
|
10112
|
-
const backupFile =
|
|
10720
|
+
const backupFile = path12.join(backupDir, `${slug}-${ts}.json`);
|
|
10113
10721
|
try {
|
|
10114
|
-
await
|
|
10115
|
-
await
|
|
10722
|
+
await fs3.mkdir(backupDir, { recursive: true });
|
|
10723
|
+
await fs3.writeFile(backupFile, currentContent, { mode: 384, encoding: "utf8" });
|
|
10116
10724
|
} catch {
|
|
10117
10725
|
}
|
|
10118
10726
|
}
|
|
@@ -10176,15 +10784,11 @@ function deepMerge(base, patch, options = {}) {
|
|
|
10176
10784
|
return out;
|
|
10177
10785
|
}
|
|
10178
10786
|
|
|
10179
|
-
// src/types/blocks.ts
|
|
10180
|
-
function isTextBlock(b) {
|
|
10181
|
-
return b.type === "text";
|
|
10182
|
-
}
|
|
10183
|
-
|
|
10184
10787
|
// src/utils/context-evidence.ts
|
|
10185
10788
|
var MAX_DIGEST_CHARS = 4e3;
|
|
10186
10789
|
function createContextEvidenceState() {
|
|
10187
10790
|
return {
|
|
10791
|
+
recentUserTurns: [],
|
|
10188
10792
|
sessionGoals: [],
|
|
10189
10793
|
implicitFacts: [],
|
|
10190
10794
|
activeErrors: [],
|
|
@@ -10201,6 +10805,11 @@ function buildContextEvidenceDigest(ctx) {
|
|
|
10201
10805
|
if (state.currentIntent?.text) {
|
|
10202
10806
|
lines.push(`intent: ${state.currentIntent.text}`);
|
|
10203
10807
|
}
|
|
10808
|
+
const priorTurns = (state.recentUserTurns ?? []).slice(-6, -1);
|
|
10809
|
+
if (priorTurns.length > 0) {
|
|
10810
|
+
lines.push("recent_human_instructions:");
|
|
10811
|
+
for (const turn of priorTurns) lines.push(`- ${turn.text}`);
|
|
10812
|
+
}
|
|
10204
10813
|
const goals = state.sessionGoals.slice(-3);
|
|
10205
10814
|
if (goals.length > 0) {
|
|
10206
10815
|
lines.push("session_goals:");
|
|
@@ -10290,6 +10899,7 @@ function ensureEvidence(ctx) {
|
|
|
10290
10899
|
ctx.contextEvidence = createContextEvidenceState();
|
|
10291
10900
|
}
|
|
10292
10901
|
ctx.contextEvidence.completedWork ??= [];
|
|
10902
|
+
ctx.contextEvidence.recentUserTurns ??= [];
|
|
10293
10903
|
return ctx.contextEvidence;
|
|
10294
10904
|
}
|
|
10295
10905
|
|
|
@@ -10400,7 +11010,7 @@ function isEmptyMessage(msg) {
|
|
|
10400
11010
|
}
|
|
10401
11011
|
|
|
10402
11012
|
// src/core/context.ts
|
|
10403
|
-
import * as
|
|
11013
|
+
import * as path13 from "node:path";
|
|
10404
11014
|
|
|
10405
11015
|
// src/utils/tool-wire-compact.ts
|
|
10406
11016
|
var TOOL_DESCRIPTION_MAX_CHARS = 400;
|
|
@@ -10804,12 +11414,11 @@ var ConversationState = class {
|
|
|
10804
11414
|
}
|
|
10805
11415
|
this.ctx.messages.splice(this.ctx.messages.length, 0, message);
|
|
10806
11416
|
const overflow = this.overflowCount(this.ctx.messages);
|
|
11417
|
+
this.emit({ kind: "message_appended", message });
|
|
10807
11418
|
if (overflow > 0) {
|
|
10808
11419
|
this.ctx.messages.splice(0, overflow);
|
|
10809
11420
|
this.ctx.toolAdjacencyDirty = true;
|
|
10810
|
-
this.emit({ kind: "
|
|
10811
|
-
} else {
|
|
10812
|
-
this.emit({ kind: "message_appended", message });
|
|
11421
|
+
this.emit({ kind: "messages_dropped", count: overflow });
|
|
10813
11422
|
}
|
|
10814
11423
|
}
|
|
10815
11424
|
/**
|
|
@@ -11307,6 +11916,11 @@ var Context = class _Context {
|
|
|
11307
11916
|
ts,
|
|
11308
11917
|
version: 1,
|
|
11309
11918
|
messages: [...change.messages]
|
|
11919
|
+
} : change.kind === "messages_dropped" ? {
|
|
11920
|
+
type: "messages_dropped",
|
|
11921
|
+
ts,
|
|
11922
|
+
version: 1,
|
|
11923
|
+
count: change.count
|
|
11310
11924
|
} : null;
|
|
11311
11925
|
if (!event) return;
|
|
11312
11926
|
this.enqueueConversationJournal(event, this.session);
|
|
@@ -11499,6 +12113,12 @@ var Context = class _Context {
|
|
|
11499
12113
|
setCurrentKanbanTask(taskId, boardId) {
|
|
11500
12114
|
this.currentKanbanTaskId = taskId;
|
|
11501
12115
|
this.currentKanbanBoardId = boardId;
|
|
12116
|
+
const existing = this.meta["kanban"] && typeof this.meta["kanban"] === "object" ? this.meta["kanban"] : {};
|
|
12117
|
+
this.state.setMeta("kanban", {
|
|
12118
|
+
...existing,
|
|
12119
|
+
...taskId ? { taskId } : { taskId: void 0 },
|
|
12120
|
+
...boardId ? { boardId } : { boardId: void 0 }
|
|
12121
|
+
});
|
|
11502
12122
|
}
|
|
11503
12123
|
/**
|
|
11504
12124
|
* Record a comprehensive file event for the audit trail.
|
|
@@ -11592,11 +12212,11 @@ var Context = class _Context {
|
|
|
11592
12212
|
* Returns the resolved absolute path.
|
|
11593
12213
|
*/
|
|
11594
12214
|
setWorkingDir(dir) {
|
|
11595
|
-
const resolved =
|
|
12215
|
+
const resolved = path13.isAbsolute(dir) ? path13.resolve(dir) : path13.resolve(this.projectRoot, dir);
|
|
11596
12216
|
if (!this.allowOutsideProjectRoot) {
|
|
11597
|
-
const root =
|
|
11598
|
-
const rel =
|
|
11599
|
-
if (rel.startsWith("..") ||
|
|
12217
|
+
const root = path13.resolve(this.projectRoot);
|
|
12218
|
+
const rel = path13.relative(root, resolved);
|
|
12219
|
+
if (rel.startsWith("..") || path13.isAbsolute(rel)) {
|
|
11600
12220
|
throw new Error(`Working directory "${resolved}" is outside project root "${root}"`);
|
|
11601
12221
|
}
|
|
11602
12222
|
}
|
|
@@ -12025,10 +12645,10 @@ function validateAgainstSchema(value, schema) {
|
|
|
12025
12645
|
return { ok: errors.length === 0, errors };
|
|
12026
12646
|
}
|
|
12027
12647
|
var MAX_SCHEMA_DEPTH = 64;
|
|
12028
|
-
function walk(value, schema,
|
|
12648
|
+
function walk(value, schema, path45, errors, depth) {
|
|
12029
12649
|
if (depth > MAX_SCHEMA_DEPTH) {
|
|
12030
12650
|
errors.push({
|
|
12031
|
-
path:
|
|
12651
|
+
path: path45 || "<root>",
|
|
12032
12652
|
message: `schema nesting exceeds maximum depth (${MAX_SCHEMA_DEPTH})`
|
|
12033
12653
|
});
|
|
12034
12654
|
return;
|
|
@@ -12036,7 +12656,7 @@ function walk(value, schema, path42, errors, depth) {
|
|
|
12036
12656
|
if (schema.enum !== void 0) {
|
|
12037
12657
|
if (!enumIncludes(schema.enum, value)) {
|
|
12038
12658
|
errors.push({
|
|
12039
|
-
path:
|
|
12659
|
+
path: path45 || "<root>",
|
|
12040
12660
|
message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
|
|
12041
12661
|
});
|
|
12042
12662
|
return;
|
|
@@ -12045,7 +12665,7 @@ function walk(value, schema, path42, errors, depth) {
|
|
|
12045
12665
|
if (typeof schema.type === "string") {
|
|
12046
12666
|
if (!checkType(value, schema.type)) {
|
|
12047
12667
|
errors.push({
|
|
12048
|
-
path:
|
|
12668
|
+
path: path45 || "<root>",
|
|
12049
12669
|
message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
|
|
12050
12670
|
});
|
|
12051
12671
|
return;
|
|
@@ -12057,7 +12677,7 @@ function walk(value, schema, path42, errors, depth) {
|
|
|
12057
12677
|
if (!(req in obj)) {
|
|
12058
12678
|
const expected = schema.properties?.[req]?.type;
|
|
12059
12679
|
errors.push({
|
|
12060
|
-
path: joinPath(
|
|
12680
|
+
path: joinPath(path45, req),
|
|
12061
12681
|
message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
|
|
12062
12682
|
});
|
|
12063
12683
|
}
|
|
@@ -12065,14 +12685,14 @@ function walk(value, schema, path42, errors, depth) {
|
|
|
12065
12685
|
if (schema.properties) {
|
|
12066
12686
|
for (const [key, subSchema] of Object.entries(schema.properties)) {
|
|
12067
12687
|
if (key in obj) {
|
|
12068
|
-
walk(obj[key], subSchema, joinPath(
|
|
12688
|
+
walk(obj[key], subSchema, joinPath(path45, key), errors, depth + 1);
|
|
12069
12689
|
}
|
|
12070
12690
|
}
|
|
12071
12691
|
}
|
|
12072
12692
|
}
|
|
12073
12693
|
if (schema.type === "array" && Array.isArray(value) && schema.items) {
|
|
12074
12694
|
for (let i = 0; i < value.length; i++) {
|
|
12075
|
-
walk(value[i], schema.items, `${
|
|
12695
|
+
walk(value[i], schema.items, `${path45}[${i}]`, errors, depth + 1);
|
|
12076
12696
|
}
|
|
12077
12697
|
}
|
|
12078
12698
|
}
|
|
@@ -12432,14 +13052,14 @@ function compileUserRegex(pattern, flags) {
|
|
|
12432
13052
|
var MAX_SUBJECT_LEN = 64 * 1024;
|
|
12433
13053
|
|
|
12434
13054
|
// src/utils/session-scoped-path.ts
|
|
12435
|
-
import * as
|
|
13055
|
+
import * as path14 from "node:path";
|
|
12436
13056
|
function sessionScopedPath(dir, sessionId, suffix) {
|
|
12437
13057
|
if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
|
|
12438
13058
|
throw invalid(sessionId);
|
|
12439
13059
|
}
|
|
12440
|
-
const resolved =
|
|
12441
|
-
const rel =
|
|
12442
|
-
if (rel.startsWith("..") ||
|
|
13060
|
+
const resolved = path14.resolve(dir, `${sessionId}${suffix}`);
|
|
13061
|
+
const rel = path14.relative(path14.resolve(dir), resolved);
|
|
13062
|
+
if (rel.startsWith("..") || path14.isAbsolute(rel)) {
|
|
12443
13063
|
throw invalid(sessionId);
|
|
12444
13064
|
}
|
|
12445
13065
|
return resolved;
|
|
@@ -12455,7 +13075,7 @@ function invalid(sessionId) {
|
|
|
12455
13075
|
|
|
12456
13076
|
// src/utils/sleep.ts
|
|
12457
13077
|
function sleep(ms) {
|
|
12458
|
-
return new Promise((
|
|
13078
|
+
return new Promise((resolve21) => setTimeout(resolve21, ms));
|
|
12459
13079
|
}
|
|
12460
13080
|
|
|
12461
13081
|
// src/utils/slug.ts
|
|
@@ -13194,6 +13814,9 @@ function subjectForToolInput(toolName, input, subjectKey) {
|
|
|
13194
13814
|
const obj = input;
|
|
13195
13815
|
if (subjectKey) {
|
|
13196
13816
|
const value = obj[subjectKey];
|
|
13817
|
+
if (Array.isArray(value)) {
|
|
13818
|
+
return escapeGlobSubject(JSON.stringify(value));
|
|
13819
|
+
}
|
|
13197
13820
|
if (typeof value === "string") {
|
|
13198
13821
|
if (isPathSubjectKey(subjectKey)) {
|
|
13199
13822
|
const normalized = normalizePathSubject(value);
|
|
@@ -13238,7 +13861,7 @@ function subjectForToolInput(toolName, input, subjectKey) {
|
|
|
13238
13861
|
// src/storage/file-session-writer.ts
|
|
13239
13862
|
import { closeSync, createReadStream, fsyncSync, openSync, writeSync } from "node:fs";
|
|
13240
13863
|
import * as fsp7 from "node:fs/promises";
|
|
13241
|
-
import * as
|
|
13864
|
+
import * as path15 from "node:path";
|
|
13242
13865
|
import { createInterface } from "node:readline";
|
|
13243
13866
|
|
|
13244
13867
|
// src/storage/session-workspace-checkpoints.ts
|
|
@@ -13504,7 +14127,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
13504
14127
|
this.meta = meta;
|
|
13505
14128
|
this.events = events;
|
|
13506
14129
|
this.resumed = opts.resumed ?? false;
|
|
13507
|
-
this.manifestFile = opts.dir ?
|
|
14130
|
+
this.manifestFile = opts.dir ? path15.join(opts.dir, `${path15.basename(id)}.summary.json`) : "";
|
|
13508
14131
|
this.filePath = opts.filePath ?? "";
|
|
13509
14132
|
this.secretScrubber = opts.secretScrubber;
|
|
13510
14133
|
this.checkpointCas = opts.checkpointCas;
|
|
@@ -14393,10 +15016,10 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
14393
15016
|
};
|
|
14394
15017
|
|
|
14395
15018
|
// src/storage/session-checkpoint-cas.ts
|
|
14396
|
-
import { spawn } from "node:child_process";
|
|
14397
|
-
import { createHash as
|
|
15019
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
15020
|
+
import { createHash as createHash3, randomUUID as randomUUID9 } from "node:crypto";
|
|
14398
15021
|
import * as fsp8 from "node:fs/promises";
|
|
14399
|
-
import * as
|
|
15022
|
+
import * as path16 from "node:path";
|
|
14400
15023
|
|
|
14401
15024
|
// src/storage/storage-concurrency.ts
|
|
14402
15025
|
async function mapWithConcurrency(items, concurrency, mapper) {
|
|
@@ -14424,16 +15047,16 @@ var MAX_GIT_OUTPUT = 16 * 1024 * 1024;
|
|
|
14424
15047
|
var MAX_BLOB_BYTES = 64 * 1024 * 1024;
|
|
14425
15048
|
var MAX_CHECKPOINT_BYTES = 512 * 1024 * 1024;
|
|
14426
15049
|
function sha256(content) {
|
|
14427
|
-
return
|
|
15050
|
+
return createHash3("sha256").update(content).digest("hex");
|
|
14428
15051
|
}
|
|
14429
15052
|
function isInside(root, target) {
|
|
14430
|
-
const relative8 =
|
|
14431
|
-
return relative8 === "" || !relative8.startsWith("..") && !
|
|
15053
|
+
const relative8 = path16.relative(root, target);
|
|
15054
|
+
return relative8 === "" || !relative8.startsWith("..") && !path16.isAbsolute(relative8);
|
|
14432
15055
|
}
|
|
14433
15056
|
function normalizeRelative(input) {
|
|
14434
|
-
if (!input ||
|
|
15057
|
+
if (!input || path16.isAbsolute(input)) return null;
|
|
14435
15058
|
const normalized = input.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
14436
|
-
const resolved =
|
|
15059
|
+
const resolved = path16.posix.normalize(normalized);
|
|
14437
15060
|
if (!resolved || resolved === "." || resolved === ".." || resolved.startsWith("../")) return null;
|
|
14438
15061
|
return resolved;
|
|
14439
15062
|
}
|
|
@@ -14448,8 +15071,8 @@ var SessionCheckpointCas = class {
|
|
|
14448
15071
|
projectRoot;
|
|
14449
15072
|
runGit;
|
|
14450
15073
|
constructor(opts) {
|
|
14451
|
-
this.rootDir =
|
|
14452
|
-
this.projectRoot =
|
|
15074
|
+
this.rootDir = path16.resolve(opts.rootDir);
|
|
15075
|
+
this.projectRoot = path16.resolve(opts.projectRoot);
|
|
14453
15076
|
this.runGit = opts.runGit ?? defaultRunGit;
|
|
14454
15077
|
}
|
|
14455
15078
|
async capture(_sessionId, _promptIndex) {
|
|
@@ -14474,7 +15097,7 @@ var SessionCheckpointCas = class {
|
|
|
14474
15097
|
relativePaths,
|
|
14475
15098
|
CAPTURE_CONCURRENCY,
|
|
14476
15099
|
async (relative8) => {
|
|
14477
|
-
const absolute =
|
|
15100
|
+
const absolute = path16.resolve(this.projectRoot, ...relative8.split("/"));
|
|
14478
15101
|
if (!isInside(this.projectRoot, absolute)) {
|
|
14479
15102
|
unresolved.push({ path: relative8, reason: "path escapes project root" });
|
|
14480
15103
|
return null;
|
|
@@ -14483,8 +15106,8 @@ var SessionCheckpointCas = class {
|
|
|
14483
15106
|
const stat15 = await fsp8.lstat(absolute);
|
|
14484
15107
|
if (stat15.isSymbolicLink()) {
|
|
14485
15108
|
const linkTarget = await fsp8.readlink(absolute);
|
|
14486
|
-
const resolvedLink =
|
|
14487
|
-
if (
|
|
15109
|
+
const resolvedLink = path16.resolve(path16.dirname(absolute), linkTarget);
|
|
15110
|
+
if (path16.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
|
|
14488
15111
|
unresolved.push({
|
|
14489
15112
|
path: relative8,
|
|
14490
15113
|
reason: "symlink target escapes project root"
|
|
@@ -14546,7 +15169,7 @@ var SessionCheckpointCas = class {
|
|
|
14546
15169
|
};
|
|
14547
15170
|
}
|
|
14548
15171
|
async materialize(checkpoint, targetRoot) {
|
|
14549
|
-
const target =
|
|
15172
|
+
const target = path16.resolve(targetRoot);
|
|
14550
15173
|
if (target === this.projectRoot) {
|
|
14551
15174
|
throw new Error("Refusing to materialize a workspace checkpoint over the parent project root");
|
|
14552
15175
|
}
|
|
@@ -14586,10 +15209,10 @@ var SessionCheckpointCas = class {
|
|
|
14586
15209
|
try {
|
|
14587
15210
|
const output = await this.safeOutputPath(target, realTarget, entry.path);
|
|
14588
15211
|
if (entry.state === "symlink") {
|
|
14589
|
-
if (
|
|
15212
|
+
if (path16.isAbsolute(entry.linkTarget)) {
|
|
14590
15213
|
throw new Error("absolute symlink target refused");
|
|
14591
15214
|
}
|
|
14592
|
-
const resolvedLink =
|
|
15215
|
+
const resolvedLink = path16.resolve(path16.dirname(output), entry.linkTarget);
|
|
14593
15216
|
if (!isInside(target, resolvedLink)) throw new Error("symlink target escapes checkpoint root");
|
|
14594
15217
|
}
|
|
14595
15218
|
prepared.push({
|
|
@@ -14617,7 +15240,7 @@ var SessionCheckpointCas = class {
|
|
|
14617
15240
|
await fsp8.unlink(output).catch((err) => {
|
|
14618
15241
|
if (err.code !== "ENOENT") throw err;
|
|
14619
15242
|
});
|
|
14620
|
-
await fsp8.mkdir(
|
|
15243
|
+
await fsp8.mkdir(path16.dirname(output), { recursive: true });
|
|
14621
15244
|
await fsp8.symlink(entry.linkTarget, output);
|
|
14622
15245
|
writtenFiles.push(entry.path);
|
|
14623
15246
|
continue;
|
|
@@ -14634,15 +15257,15 @@ var SessionCheckpointCas = class {
|
|
|
14634
15257
|
}
|
|
14635
15258
|
objectPath(hash2) {
|
|
14636
15259
|
if (!HASH_RE.test(hash2)) throw new Error(`Invalid CAS object hash: ${hash2}`);
|
|
14637
|
-
return
|
|
15260
|
+
return path16.join(this.rootDir, "objects", hash2.slice(0, 2), hash2.slice(2));
|
|
14638
15261
|
}
|
|
14639
15262
|
manifestPath(hash2) {
|
|
14640
15263
|
if (!HASH_RE.test(hash2)) throw new Error(`Invalid checkpoint manifest hash: ${hash2}`);
|
|
14641
|
-
return
|
|
15264
|
+
return path16.join(this.rootDir, "manifests", `${hash2}.json`);
|
|
14642
15265
|
}
|
|
14643
15266
|
async putBlob(hash2, content) {
|
|
14644
15267
|
const target = this.objectPath(hash2);
|
|
14645
|
-
await fsp8.mkdir(
|
|
15268
|
+
await fsp8.mkdir(path16.dirname(target), { recursive: true });
|
|
14646
15269
|
try {
|
|
14647
15270
|
const existing = await fsp8.readFile(target);
|
|
14648
15271
|
if (sha256(existing) !== hash2) throw new Error(`Corrupt CAS object collision: ${hash2}`);
|
|
@@ -14650,9 +15273,9 @@ var SessionCheckpointCas = class {
|
|
|
14650
15273
|
} catch (err) {
|
|
14651
15274
|
if (err.code !== "ENOENT") throw err;
|
|
14652
15275
|
}
|
|
14653
|
-
const temp =
|
|
14654
|
-
|
|
14655
|
-
`.${
|
|
15276
|
+
const temp = path16.join(
|
|
15277
|
+
path16.dirname(target),
|
|
15278
|
+
`.${path16.basename(target)}.${process.pid}.${randomUUID9()}.tmp`
|
|
14656
15279
|
);
|
|
14657
15280
|
let handle;
|
|
14658
15281
|
try {
|
|
@@ -14713,7 +15336,7 @@ var SessionCheckpointCas = class {
|
|
|
14713
15336
|
async safeOutputPath(target, realTarget, relative8) {
|
|
14714
15337
|
const normalized = normalizeRelative(relative8);
|
|
14715
15338
|
if (!normalized) throw new Error("invalid relative path");
|
|
14716
|
-
const output =
|
|
15339
|
+
const output = path16.resolve(target, ...normalized.split("/"));
|
|
14717
15340
|
if (!isInside(target, output)) throw new Error("path escapes checkpoint target");
|
|
14718
15341
|
let probe = output;
|
|
14719
15342
|
for (; ; ) {
|
|
@@ -14723,7 +15346,7 @@ var SessionCheckpointCas = class {
|
|
|
14723
15346
|
return output;
|
|
14724
15347
|
} catch (err) {
|
|
14725
15348
|
if (err.code !== "ENOENT") throw err;
|
|
14726
|
-
const parent =
|
|
15349
|
+
const parent = path16.dirname(probe);
|
|
14727
15350
|
if (parent === probe) throw err;
|
|
14728
15351
|
probe = parent;
|
|
14729
15352
|
}
|
|
@@ -14731,14 +15354,14 @@ var SessionCheckpointCas = class {
|
|
|
14731
15354
|
}
|
|
14732
15355
|
};
|
|
14733
15356
|
function defaultRunGit(args, cwd) {
|
|
14734
|
-
return new Promise((
|
|
15357
|
+
return new Promise((resolve21) => {
|
|
14735
15358
|
const stdoutChunks = [];
|
|
14736
15359
|
const stderrChunks = [];
|
|
14737
15360
|
let stdoutBytes = 0;
|
|
14738
15361
|
let stderrBytes = 0;
|
|
14739
15362
|
let stdoutTruncated = false;
|
|
14740
15363
|
let stderrTruncated = false;
|
|
14741
|
-
const child =
|
|
15364
|
+
const child = spawn2("git", args, {
|
|
14742
15365
|
cwd,
|
|
14743
15366
|
env: buildChildEnv(),
|
|
14744
15367
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -14774,8 +15397,8 @@ function defaultRunGit(args, cwd) {
|
|
|
14774
15397
|
stdoutTruncated,
|
|
14775
15398
|
stderrTruncated
|
|
14776
15399
|
});
|
|
14777
|
-
child.on("error", (err) =>
|
|
14778
|
-
child.on("close", (code) =>
|
|
15400
|
+
child.on("error", (err) => resolve21(result(1, err.message)));
|
|
15401
|
+
child.on("close", (code) => resolve21(result(code ?? 1)));
|
|
14779
15402
|
});
|
|
14780
15403
|
}
|
|
14781
15404
|
|
|
@@ -14787,13 +15410,13 @@ function generateSessionId(startedAt, _model) {
|
|
|
14787
15410
|
}
|
|
14788
15411
|
|
|
14789
15412
|
// src/storage/session-id-resolver.ts
|
|
14790
|
-
import * as
|
|
15413
|
+
import * as path17 from "node:path";
|
|
14791
15414
|
function resolveSessionId(query, candidateIds) {
|
|
14792
15415
|
const normalized = query.trim();
|
|
14793
15416
|
if (!normalized) return { status: "missing", query: normalized };
|
|
14794
15417
|
const uniqueIds = [...new Set(candidateIds)];
|
|
14795
15418
|
if (uniqueIds.includes(normalized)) return { status: "resolved", id: normalized };
|
|
14796
|
-
const leaf = (id) =>
|
|
15419
|
+
const leaf = (id) => path17.posix.basename(id.replace(/\\/g, "/"));
|
|
14797
15420
|
const exactLeafMatches = uniqueIds.filter((id) => leaf(id) === normalized);
|
|
14798
15421
|
if (exactLeafMatches.length === 1) {
|
|
14799
15422
|
return { status: "resolved", id: exactLeafMatches[0] };
|
|
@@ -14845,15 +15468,15 @@ function scrubPersistedSessionSummary(summary, scrubber2) {
|
|
|
14845
15468
|
}
|
|
14846
15469
|
|
|
14847
15470
|
// src/storage/session-resume-validation.ts
|
|
14848
|
-
import { createHash as
|
|
15471
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
14849
15472
|
import * as fsp9 from "node:fs/promises";
|
|
14850
|
-
import * as
|
|
15473
|
+
import * as path18 from "node:path";
|
|
14851
15474
|
var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
|
|
14852
15475
|
var VALIDATION_CONCURRENCY = 8;
|
|
14853
15476
|
var NOTICE_PATH_LIMIT = 20;
|
|
14854
15477
|
function isInside2(root, target) {
|
|
14855
|
-
const relative8 =
|
|
14856
|
-
return relative8 === "" || !relative8.startsWith("..") && !
|
|
15478
|
+
const relative8 = path18.relative(root, target);
|
|
15479
|
+
return relative8 === "" || !relative8.startsWith("..") && !path18.isAbsolute(relative8);
|
|
14857
15480
|
}
|
|
14858
15481
|
function errno(err) {
|
|
14859
15482
|
return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
|
|
@@ -14864,7 +15487,7 @@ function latestObservations(events, projectRoot) {
|
|
|
14864
15487
|
if (event.type !== "file_observation" || typeof event.path !== "string" || event.path.length === 0 || typeof event.hash !== "string" || !/^[a-f\d]{64}$/i.test(event.hash)) {
|
|
14865
15488
|
continue;
|
|
14866
15489
|
}
|
|
14867
|
-
const normalized =
|
|
15490
|
+
const normalized = path18.resolve(projectRoot, event.path);
|
|
14868
15491
|
latest.set(normalized, {
|
|
14869
15492
|
path: normalized,
|
|
14870
15493
|
hash: event.hash.toLowerCase(),
|
|
@@ -14913,7 +15536,7 @@ async function validateOne(observation, lexicalRoot, realRoot) {
|
|
|
14913
15536
|
};
|
|
14914
15537
|
}
|
|
14915
15538
|
const content = await fsp9.readFile(realFile, "utf8");
|
|
14916
|
-
const actualHash =
|
|
15539
|
+
const actualHash = createHash4("sha256").update(content, "utf8").digest("hex");
|
|
14917
15540
|
if (actualHash === observation.hash) return null;
|
|
14918
15541
|
return { ...base, status: "modified", actualHash };
|
|
14919
15542
|
} catch (err) {
|
|
@@ -14922,7 +15545,7 @@ async function validateOne(observation, lexicalRoot, realRoot) {
|
|
|
14922
15545
|
}
|
|
14923
15546
|
}
|
|
14924
15547
|
async function validateResumeFileObservations(events, projectRoot) {
|
|
14925
|
-
const lexicalRoot =
|
|
15548
|
+
const lexicalRoot = path18.resolve(projectRoot);
|
|
14926
15549
|
const realRoot = await fsp9.realpath(lexicalRoot).catch(() => lexicalRoot);
|
|
14927
15550
|
const observations = latestObservations(events, lexicalRoot);
|
|
14928
15551
|
const results = await mapWithConcurrency(
|
|
@@ -14938,9 +15561,9 @@ async function validateResumeFileObservations(events, projectRoot) {
|
|
|
14938
15561
|
}
|
|
14939
15562
|
function formatResumeValidationNotice(validation, projectRoot) {
|
|
14940
15563
|
if (validation.staleFiles.length === 0) return null;
|
|
14941
|
-
const root =
|
|
15564
|
+
const root = path18.resolve(projectRoot);
|
|
14942
15565
|
const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
|
|
14943
|
-
const relative8 =
|
|
15566
|
+
const relative8 = path18.relative(root, entry.path);
|
|
14944
15567
|
const display = isInside2(root, entry.path) ? relative8 || "." : entry.path;
|
|
14945
15568
|
return `- ${JSON.stringify(display)} [${entry.status}]`;
|
|
14946
15569
|
});
|
|
@@ -14965,22 +15588,22 @@ function formatInterruptedToolNotice(pendingToolUseCount) {
|
|
|
14965
15588
|
|
|
14966
15589
|
// src/storage/session-store/delete-session-artifacts.ts
|
|
14967
15590
|
import * as fsp10 from "node:fs/promises";
|
|
14968
|
-
import * as
|
|
15591
|
+
import * as path20 from "node:path";
|
|
14969
15592
|
|
|
14970
15593
|
// src/storage/session-store/paths.ts
|
|
14971
|
-
import * as
|
|
15594
|
+
import * as path19 from "node:path";
|
|
14972
15595
|
function sessionPath(storeDir, id, ext) {
|
|
14973
15596
|
return sessionScopedPath(storeDir, id, ext);
|
|
14974
15597
|
}
|
|
14975
15598
|
function shardManifestPath(storeDir, shardKey) {
|
|
14976
|
-
return shardKey ?
|
|
15599
|
+
return shardKey ? path19.join(storeDir, shardKey, "_manifest.json") : path19.join(storeDir, "_manifest.json");
|
|
14977
15600
|
}
|
|
14978
15601
|
function shardKeyForSessionId(id) {
|
|
14979
|
-
const dirName =
|
|
15602
|
+
const dirName = path19.dirname(id);
|
|
14980
15603
|
return dirName === "." ? "" : dirName;
|
|
14981
15604
|
}
|
|
14982
15605
|
async function ensureShardDir(storeDir, id) {
|
|
14983
|
-
const dirPath =
|
|
15606
|
+
const dirPath = path19.dirname(sessionScopedPath(storeDir, id, ""));
|
|
14984
15607
|
await ensureDir(dirPath);
|
|
14985
15608
|
return dirPath;
|
|
14986
15609
|
}
|
|
@@ -14991,9 +15614,9 @@ async function deleteSessionArtifacts({
|
|
|
14991
15614
|
id,
|
|
14992
15615
|
jsonlPath
|
|
14993
15616
|
}) {
|
|
14994
|
-
const shardDir =
|
|
14995
|
-
const base =
|
|
14996
|
-
const sessDir =
|
|
15617
|
+
const shardDir = path20.dirname(jsonlPath);
|
|
15618
|
+
const base = path20.basename(id);
|
|
15619
|
+
const sessDir = path20.join(shardDir, base);
|
|
14997
15620
|
const deletions = [
|
|
14998
15621
|
fsp10.unlink(jsonlPath),
|
|
14999
15622
|
fsp10.unlink(sessionPath(rootDir, id, ".summary.json")),
|
|
@@ -15052,7 +15675,7 @@ function isSessionJsonlFileName(name) {
|
|
|
15052
15675
|
|
|
15053
15676
|
// src/storage/session-store/directory-session-files.ts
|
|
15054
15677
|
import * as fsp11 from "node:fs/promises";
|
|
15055
|
-
import * as
|
|
15678
|
+
import * as path21 from "node:path";
|
|
15056
15679
|
function sessionIdForFile(prefix, name) {
|
|
15057
15680
|
const base = name.replace(/\.jsonl$/, "");
|
|
15058
15681
|
return prefix ? `${prefix}/${base}` : base;
|
|
@@ -15074,12 +15697,12 @@ async function collectSessionFiles(dir, prefix = "", depth = 0) {
|
|
|
15074
15697
|
if (entry.isDirectory()) {
|
|
15075
15698
|
dirEntries.push(entry);
|
|
15076
15699
|
} else if (entry.isFile() && isSessionJsonlFileName(entry.name)) {
|
|
15077
|
-
files.push({ id: sessionIdForFile(prefix, entry.name), filePath:
|
|
15700
|
+
files.push({ id: sessionIdForFile(prefix, entry.name), filePath: path21.join(dir, entry.name) });
|
|
15078
15701
|
}
|
|
15079
15702
|
}
|
|
15080
15703
|
const childFileArrays = await Promise.all(
|
|
15081
15704
|
dirEntries.map(
|
|
15082
|
-
(entry) => collectSessionFiles(
|
|
15705
|
+
(entry) => collectSessionFiles(path21.join(dir, entry.name), childPrefixFor(entry, prefix, depth), depth + 1)
|
|
15083
15706
|
)
|
|
15084
15707
|
);
|
|
15085
15708
|
return [...childFileArrays.flat(), ...files];
|
|
@@ -15103,7 +15726,7 @@ async function collectSessionIds(dir, prefix = "", depth = 0) {
|
|
|
15103
15726
|
}
|
|
15104
15727
|
const childIdArrays = await Promise.all(
|
|
15105
15728
|
dirEntries.map(
|
|
15106
|
-
(entry) => collectSessionIds(
|
|
15729
|
+
(entry) => collectSessionIds(path21.join(dir, entry.name), childPrefixFor(entry, prefix, depth), depth + 1)
|
|
15107
15730
|
)
|
|
15108
15731
|
);
|
|
15109
15732
|
return [...childIdArrays.flat(), ...fileIds];
|
|
@@ -15145,7 +15768,7 @@ function emitSessionStoreError(events, sessionId, filePath, operation, error2, r
|
|
|
15145
15768
|
}
|
|
15146
15769
|
|
|
15147
15770
|
// src/storage/session-store/fork-session.ts
|
|
15148
|
-
import { createHash as
|
|
15771
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
15149
15772
|
|
|
15150
15773
|
// src/storage/session-store/replay.ts
|
|
15151
15774
|
function isReplayableMessage(value) {
|
|
@@ -15217,7 +15840,7 @@ async function forkSession(host, id, opts = {}) {
|
|
|
15217
15840
|
}
|
|
15218
15841
|
const parentPrefix = parent.events.slice(0, boundary + 1);
|
|
15219
15842
|
const workspaceCheckpoint = targetCheckpoint?.workspaceCheckpoint;
|
|
15220
|
-
const checkpointHash =
|
|
15843
|
+
const checkpointHash = createHash5("sha256").update(parentPrefix.map((event) => JSON.stringify(event)).join("\n") + "\n", "utf8").digest("hex");
|
|
15221
15844
|
const inherited = parentPrefix.filter(inheritsIntoFork);
|
|
15222
15845
|
const writer = await host.create({
|
|
15223
15846
|
id: "",
|
|
@@ -15560,6 +16183,16 @@ function replaySessionEvent(params) {
|
|
|
15560
16183
|
} else {
|
|
15561
16184
|
emitDamaged(params, "Ignored malformed messages_replaced event");
|
|
15562
16185
|
}
|
|
16186
|
+
} else if (ev.type === "messages_dropped" && ev.version === 1) {
|
|
16187
|
+
if (exactJournalActive && Number.isInteger(ev.count) && ev.count > 0) {
|
|
16188
|
+
messages.splice(0, Math.min(ev.count, messages.length));
|
|
16189
|
+
openToolUses.clear();
|
|
16190
|
+
for (const current of messages) trackMessageToolState(current, openToolUses);
|
|
16191
|
+
} else if (!exactJournalActive) {
|
|
16192
|
+
emitDamaged(params, "Ignored messages_dropped event outside the exact journal");
|
|
16193
|
+
} else {
|
|
16194
|
+
emitDamaged(params, `Ignored malformed messages_dropped event (count ${String(ev.count)})`);
|
|
16195
|
+
}
|
|
15563
16196
|
} else if (ev.type === "context_snapshot") {
|
|
15564
16197
|
if (!applyContextSnapshot(messages, openToolUses, ev.messages)) {
|
|
15565
16198
|
emitDamaged(params, "Ignored malformed context_snapshot event");
|
|
@@ -15614,7 +16247,7 @@ function emitDamaged(params, detail) {
|
|
|
15614
16247
|
|
|
15615
16248
|
// src/storage/session-store/prune-helpers.ts
|
|
15616
16249
|
import * as fsp13 from "node:fs/promises";
|
|
15617
|
-
import * as
|
|
16250
|
+
import * as path22 from "node:path";
|
|
15618
16251
|
function isPrunableSessionJsonl(name) {
|
|
15619
16252
|
return name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
|
|
15620
16253
|
}
|
|
@@ -15622,7 +16255,7 @@ async function pruneSessionFiles(storeDir, maxAgeDays, deleteSession) {
|
|
|
15622
16255
|
const cutoff = Date.now() - maxAgeDays * 864e5;
|
|
15623
16256
|
let deleted = 0;
|
|
15624
16257
|
const pruneFile = async (dir, name, prefix) => {
|
|
15625
|
-
const jsonlPath =
|
|
16258
|
+
const jsonlPath = path22.join(dir, name);
|
|
15626
16259
|
try {
|
|
15627
16260
|
const stat15 = await fsp13.stat(jsonlPath);
|
|
15628
16261
|
if (stat15.mtimeMs >= cutoff) return;
|
|
@@ -15641,7 +16274,7 @@ async function pruneSessionFiles(storeDir, maxAgeDays, deleteSession) {
|
|
|
15641
16274
|
continue;
|
|
15642
16275
|
}
|
|
15643
16276
|
if (!entry.isDirectory()) continue;
|
|
15644
|
-
const dateDir =
|
|
16277
|
+
const dateDir = path22.join(storeDir, entry.name);
|
|
15645
16278
|
const files = await fsp13.readdir(dateDir, { withFileTypes: true }).catch(() => []);
|
|
15646
16279
|
for (const file of files) {
|
|
15647
16280
|
if (!file.isFile() || !isPrunableSessionJsonl(file.name)) continue;
|
|
@@ -15650,7 +16283,7 @@ async function pruneSessionFiles(storeDir, maxAgeDays, deleteSession) {
|
|
|
15650
16283
|
}
|
|
15651
16284
|
for (const entry of entries) {
|
|
15652
16285
|
if (!entry.isDirectory()) continue;
|
|
15653
|
-
const dateDir =
|
|
16286
|
+
const dateDir = path22.join(storeDir, entry.name);
|
|
15654
16287
|
try {
|
|
15655
16288
|
const remaining = await fsp13.readdir(dateDir);
|
|
15656
16289
|
if (remaining.length === 0) {
|
|
@@ -15983,6 +16616,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
15983
16616
|
logger;
|
|
15984
16617
|
onAppend;
|
|
15985
16618
|
onAppendBatch;
|
|
16619
|
+
/** Present in built production output; source-only tests retain the local compatibility path. */
|
|
16620
|
+
catalogClient;
|
|
16621
|
+
maintenanceHolderId = randomUUID10();
|
|
15986
16622
|
/**
|
|
15987
16623
|
* In-memory cache for load() results, keyed by session ID. The cache is
|
|
15988
16624
|
* invalidated when the file's mtimeMs or size changes (indicating the
|
|
@@ -16000,9 +16636,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16000
16636
|
static LIST_SCAN_CONCURRENCY = 32;
|
|
16001
16637
|
constructor(opts) {
|
|
16002
16638
|
this.dir = opts.dir;
|
|
16003
|
-
this.projectRoot = opts.projectRoot ?
|
|
16639
|
+
this.projectRoot = opts.projectRoot ? path23.resolve(opts.projectRoot) : void 0;
|
|
16004
16640
|
this.checkpointCas = this.projectRoot ? new SessionCheckpointCas({
|
|
16005
|
-
rootDir:
|
|
16641
|
+
rootDir: path23.join(this.dir, "_cas"),
|
|
16006
16642
|
projectRoot: this.projectRoot
|
|
16007
16643
|
}) : void 0;
|
|
16008
16644
|
this.events = opts.events;
|
|
@@ -16011,6 +16647,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16011
16647
|
this.logger = opts.logger;
|
|
16012
16648
|
this.onAppend = opts.onAppend;
|
|
16013
16649
|
this.onAppendBatch = opts.onAppendBatch;
|
|
16650
|
+
const builtRuntime = import.meta.url.includes("/dist/");
|
|
16651
|
+
this.catalogClient = this.projectRoot && (builtRuntime || process.env["WRONGSTACK_SESSION_CATALOG_FORCE"] === "1") && resolveSessionCatalogProjectServerUrl() ? new SessionCatalogProjectClient({
|
|
16652
|
+
projectDir: path23.dirname(this.dir),
|
|
16653
|
+
projectRoot: this.projectRoot
|
|
16654
|
+
}) : void 0;
|
|
16014
16655
|
}
|
|
16015
16656
|
/**
|
|
16016
16657
|
* Emit a structured warning. Uses the configured Logger when available;
|
|
@@ -16033,10 +16674,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16033
16674
|
clearLoadCache(sessionId) {
|
|
16034
16675
|
this.loadCache.clear(sessionId);
|
|
16035
16676
|
}
|
|
16677
|
+
async dispose() {
|
|
16678
|
+
await this.catalogClient?.close();
|
|
16679
|
+
this.clearLoadCache();
|
|
16680
|
+
}
|
|
16036
16681
|
// ── Storage event helpers ───────────────────────────────────────────────────
|
|
16037
16682
|
/** Absolute path to the session index file. */
|
|
16038
16683
|
get indexFile() {
|
|
16039
|
-
return
|
|
16684
|
+
return path23.join(this.dir, "_index.jsonl");
|
|
16040
16685
|
}
|
|
16041
16686
|
/** Join session ID to its absolute path within the store directory. */
|
|
16042
16687
|
sessionPath(id, ext) {
|
|
@@ -16094,8 +16739,23 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16094
16739
|
if (!current) return null;
|
|
16095
16740
|
return current.name === void 0 ? {} : { name: sessionContentText(this.secretScrubber.scrub(current.name)) };
|
|
16096
16741
|
},
|
|
16097
|
-
onClose: (s) => this.
|
|
16742
|
+
onClose: (s) => this.persistCatalogSummary(s)
|
|
16098
16743
|
});
|
|
16744
|
+
if (this.catalogClient) {
|
|
16745
|
+
await this.catalogClient.call("upsert_summary", {
|
|
16746
|
+
summary: {
|
|
16747
|
+
id,
|
|
16748
|
+
title: meta.title ?? "",
|
|
16749
|
+
startedAt,
|
|
16750
|
+
model: meta.model ?? "",
|
|
16751
|
+
provider: meta.provider ?? "",
|
|
16752
|
+
tokenTotal: 0,
|
|
16753
|
+
lastActivityAt: startedAt
|
|
16754
|
+
},
|
|
16755
|
+
transcriptRelativePath: `${id}.jsonl`,
|
|
16756
|
+
summaryRelativePath: `${id}.summary.json`
|
|
16757
|
+
});
|
|
16758
|
+
}
|
|
16099
16759
|
emitSessionStoreWrite(this.events, id, file, "create", "success", Date.now() - t0);
|
|
16100
16760
|
return writer;
|
|
16101
16761
|
} catch (err) {
|
|
@@ -16125,6 +16785,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16125
16785
|
return materializeCheckpoint(this.checkpointCas, checkpoint, targetRoot);
|
|
16126
16786
|
}
|
|
16127
16787
|
async resolveId(query) {
|
|
16788
|
+
if (this.catalogClient) {
|
|
16789
|
+
return this.catalogClient.call("resolve_id", { query });
|
|
16790
|
+
}
|
|
16128
16791
|
const normalized = query.trim();
|
|
16129
16792
|
if (!normalized) throw new Error("Session not found: (empty query)");
|
|
16130
16793
|
if (normalized) {
|
|
@@ -16231,7 +16894,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16231
16894
|
// Shard directory (sessions/<date>/) — must match create() so the
|
|
16232
16895
|
// .summary.json sidecar lands next to the JSONL instead of the
|
|
16233
16896
|
// sessions root (where summaryFor() would never find it).
|
|
16234
|
-
dir:
|
|
16897
|
+
dir: path23.dirname(file),
|
|
16235
16898
|
filePath: file,
|
|
16236
16899
|
secretScrubber: this.secretScrubber,
|
|
16237
16900
|
checkpointCas: this.checkpointCas,
|
|
@@ -16242,7 +16905,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16242
16905
|
if (!current) return null;
|
|
16243
16906
|
return current.name === void 0 ? {} : { name: sessionContentText(this.secretScrubber.scrub(current.name)) };
|
|
16244
16907
|
},
|
|
16245
|
-
onClose: (s) => this.
|
|
16908
|
+
onClose: (s) => this.persistCatalogSummary(s)
|
|
16246
16909
|
}
|
|
16247
16910
|
);
|
|
16248
16911
|
emitSessionStoreWrite(this.events, canonicalId, file, "resume", "success", Date.now() - t0);
|
|
@@ -16355,6 +17018,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16355
17018
|
});
|
|
16356
17019
|
}
|
|
16357
17020
|
async list(limit = 20) {
|
|
17021
|
+
if (this.catalogClient) {
|
|
17022
|
+
const records = await this.catalogClient.call("list_catalog", { limit });
|
|
17023
|
+
return this.scrubSummaries(records);
|
|
17024
|
+
}
|
|
16358
17025
|
try {
|
|
16359
17026
|
const indexed = await this.readIndex();
|
|
16360
17027
|
if (indexed.length > 0) {
|
|
@@ -16377,6 +17044,13 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16377
17044
|
*/
|
|
16378
17045
|
async listFiltered(criteria) {
|
|
16379
17046
|
const limit = criteria.limit ?? 100;
|
|
17047
|
+
if (this.catalogClient) {
|
|
17048
|
+
const records = await this.catalogClient.call("list_catalog", {
|
|
17049
|
+
limit: Math.min(1e3, Math.max(limit, 100)),
|
|
17050
|
+
...criteria.titleContains ? { search: criteria.titleContains } : {}
|
|
17051
|
+
});
|
|
17052
|
+
return this.scrubSummaries(records).filter((summary) => matchesSessionFilter(summary, criteria)).slice(0, limit);
|
|
17053
|
+
}
|
|
16380
17054
|
try {
|
|
16381
17055
|
const indexed = await this.readIndex();
|
|
16382
17056
|
if (indexed.length === 0) {
|
|
@@ -16426,6 +17100,18 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16426
17100
|
await this.appendToIndexStrict(summary).catch(() => {
|
|
16427
17101
|
});
|
|
16428
17102
|
}
|
|
17103
|
+
/** Final summary boundary: daemon is authoritative when available. */
|
|
17104
|
+
async persistCatalogSummary(summary) {
|
|
17105
|
+
if (!this.catalogClient) {
|
|
17106
|
+
await this.appendToIndex(summary);
|
|
17107
|
+
return;
|
|
17108
|
+
}
|
|
17109
|
+
await this.catalogClient.call("upsert_summary", {
|
|
17110
|
+
summary,
|
|
17111
|
+
transcriptRelativePath: `${summary.id}.jsonl`,
|
|
17112
|
+
summaryRelativePath: `${summary.id}.summary.json`
|
|
17113
|
+
});
|
|
17114
|
+
}
|
|
16429
17115
|
/** Append a tombstone entry for a deleted session. */
|
|
16430
17116
|
async writeTombstone(id) {
|
|
16431
17117
|
try {
|
|
@@ -16535,6 +17221,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16535
17221
|
* fresh _index.jsonl. Useful after manual cleanup or index corruption.
|
|
16536
17222
|
*/
|
|
16537
17223
|
async rebuildIndex() {
|
|
17224
|
+
if (this.catalogClient) {
|
|
17225
|
+
const result = await this.catalogClient.call("rebuild_catalog", {}, { timeoutMs: 12e4 });
|
|
17226
|
+
return result.indexed;
|
|
17227
|
+
}
|
|
16538
17228
|
const ids = await this.collectSessionIds(this.dir);
|
|
16539
17229
|
const summaries = await Promise.all(
|
|
16540
17230
|
ids.map((id) => this.summaryFor(id).catch(() => null))
|
|
@@ -16604,7 +17294,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16604
17294
|
});
|
|
16605
17295
|
}
|
|
16606
17296
|
async collectSessionFilesInShard(shardKey) {
|
|
16607
|
-
const dir = shardKey ?
|
|
17297
|
+
const dir = shardKey ? path23.join(this.dir, shardKey) : this.dir;
|
|
16608
17298
|
const entries = await this.collectSessionFiles(dir, shardKey);
|
|
16609
17299
|
return shardKey ? entries.filter((entry) => entry.id.startsWith(`${shardKey}/`)) : entries.filter((entry) => !entry.id.includes("/"));
|
|
16610
17300
|
}
|
|
@@ -16700,10 +17390,35 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16700
17390
|
await this.writeTombstone(id);
|
|
16701
17391
|
}
|
|
16702
17392
|
async delete(id) {
|
|
17393
|
+
if (this.catalogClient) {
|
|
17394
|
+
const canonical = await this.resolveId(id);
|
|
17395
|
+
const lease = await this.catalogClient.call("acquire_maintenance", {
|
|
17396
|
+
sessionId: canonical,
|
|
17397
|
+
operation: "delete",
|
|
17398
|
+
holderId: this.maintenanceHolderId
|
|
17399
|
+
});
|
|
17400
|
+
try {
|
|
17401
|
+
await this.catalogClient.call("delete", { sessionId: canonical, lease });
|
|
17402
|
+
} catch (error2) {
|
|
17403
|
+
await this.catalogClient.call("release_maintenance", { lease }).catch(() => void 0);
|
|
17404
|
+
throw error2;
|
|
17405
|
+
}
|
|
17406
|
+
this.clearLoadCache(canonical);
|
|
17407
|
+
return;
|
|
17408
|
+
}
|
|
16703
17409
|
await assertSessionCanBeDeleted(id, this.isSessionInUse);
|
|
16704
17410
|
await this.deleteSession(id);
|
|
16705
17411
|
}
|
|
16706
17412
|
async rename(id, name) {
|
|
17413
|
+
if (this.catalogClient) {
|
|
17414
|
+
const canonical = await this.resolveId(id);
|
|
17415
|
+
const summary = await this.catalogClient.call("rename", {
|
|
17416
|
+
sessionId: canonical,
|
|
17417
|
+
name: sessionContentText(this.secretScrubber.scrub(name))
|
|
17418
|
+
});
|
|
17419
|
+
this.clearLoadCache(canonical);
|
|
17420
|
+
return summary;
|
|
17421
|
+
}
|
|
16707
17422
|
const trimmed = sessionContentText(this.secretScrubber.scrub(name));
|
|
16708
17423
|
const manifest = this.sessionPath(id, ".summary.json");
|
|
16709
17424
|
const jsonlPath = this.sessionPath(id, ".jsonl");
|
|
@@ -16755,6 +17470,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16755
17470
|
return updated;
|
|
16756
17471
|
}
|
|
16757
17472
|
async prune(maxAgeDays = 30) {
|
|
17473
|
+
if (this.catalogClient) {
|
|
17474
|
+
return this.catalogClient.call("prune", {
|
|
17475
|
+
maxAgeDays,
|
|
17476
|
+
holderId: this.maintenanceHolderId
|
|
17477
|
+
});
|
|
17478
|
+
}
|
|
16758
17479
|
const deleted = await pruneSessionFiles(this.dir, maxAgeDays, (id) => this.deleteSession(id));
|
|
16759
17480
|
if (deleted > 0) {
|
|
16760
17481
|
await this.compactIndex().catch(() => void 0);
|
|
@@ -16762,20 +17483,85 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
16762
17483
|
return deleted;
|
|
16763
17484
|
}
|
|
16764
17485
|
async clearHistory(id) {
|
|
16765
|
-
await this.
|
|
16766
|
-
const
|
|
16767
|
-
|
|
17486
|
+
const canonical = this.catalogClient ? await this.resolveId(id) : id;
|
|
17487
|
+
const maintenance = this.catalogClient ? await this.catalogClient.call("acquire_maintenance", {
|
|
17488
|
+
sessionId: canonical,
|
|
17489
|
+
operation: "clear",
|
|
17490
|
+
holderId: this.maintenanceHolderId,
|
|
17491
|
+
// The catalog store runs inside the detached project daemon; without
|
|
17492
|
+
// our pid it cannot tell "this TUI clearing its own session" from
|
|
17493
|
+
// "another running wstack" and refuses every /clear as `is live`.
|
|
17494
|
+
holderPid: process.pid
|
|
17495
|
+
}) : void 0;
|
|
17496
|
+
await this.ensureShardDir(canonical);
|
|
17497
|
+
const file = this.sessionPath(canonical, ".jsonl");
|
|
17498
|
+
const meta = this.sessionPath(canonical, ".summary.json");
|
|
17499
|
+
const backupSuffix = maintenance ? `.${maintenance.leaseId}.clear-backup` : void 0;
|
|
17500
|
+
const fileBackup = backupSuffix ? `${file}${backupSuffix}` : void 0;
|
|
17501
|
+
const metaBackup = backupSuffix ? `${meta}${backupSuffix}` : void 0;
|
|
17502
|
+
let fileStaged = false;
|
|
17503
|
+
let metaStaged = false;
|
|
16768
17504
|
const record = `${JSON.stringify({
|
|
16769
17505
|
type: "session_start",
|
|
16770
17506
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
16771
|
-
id,
|
|
17507
|
+
id: canonical,
|
|
16772
17508
|
model: "unknown",
|
|
16773
17509
|
provider: "unknown"
|
|
16774
17510
|
})}
|
|
16775
17511
|
`;
|
|
16776
|
-
|
|
16777
|
-
|
|
16778
|
-
|
|
17512
|
+
try {
|
|
17513
|
+
if (fileBackup) {
|
|
17514
|
+
try {
|
|
17515
|
+
await fsp17.rename(file, fileBackup);
|
|
17516
|
+
fileStaged = true;
|
|
17517
|
+
} catch (error2) {
|
|
17518
|
+
if (error2.code !== "ENOENT") throw error2;
|
|
17519
|
+
}
|
|
17520
|
+
}
|
|
17521
|
+
if (metaBackup) {
|
|
17522
|
+
try {
|
|
17523
|
+
await fsp17.rename(meta, metaBackup);
|
|
17524
|
+
metaStaged = true;
|
|
17525
|
+
} catch (error2) {
|
|
17526
|
+
if (error2.code !== "ENOENT") throw error2;
|
|
17527
|
+
}
|
|
17528
|
+
}
|
|
17529
|
+
await atomicWrite(file, record);
|
|
17530
|
+
if (!metaBackup) await fsp17.unlink(meta).catch(() => void 0);
|
|
17531
|
+
if (this.catalogClient) {
|
|
17532
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
17533
|
+
await this.catalogClient.call("upsert_summary", {
|
|
17534
|
+
summary: {
|
|
17535
|
+
id: canonical,
|
|
17536
|
+
title: "",
|
|
17537
|
+
startedAt: now,
|
|
17538
|
+
model: "unknown",
|
|
17539
|
+
provider: "unknown",
|
|
17540
|
+
tokenTotal: 0,
|
|
17541
|
+
lastActivityAt: now
|
|
17542
|
+
},
|
|
17543
|
+
transcriptRelativePath: `${canonical}.jsonl`,
|
|
17544
|
+
summaryRelativePath: `${canonical}.summary.json`
|
|
17545
|
+
});
|
|
17546
|
+
}
|
|
17547
|
+
if (fileStaged && fileBackup) await fsp17.unlink(fileBackup).catch(() => void 0);
|
|
17548
|
+
if (metaStaged && metaBackup) await fsp17.unlink(metaBackup).catch(() => void 0);
|
|
17549
|
+
} catch (error2) {
|
|
17550
|
+
if (fileStaged && fileBackup) {
|
|
17551
|
+
await fsp17.unlink(file).catch(() => void 0);
|
|
17552
|
+
await fsp17.rename(fileBackup, file).catch(() => void 0);
|
|
17553
|
+
}
|
|
17554
|
+
if (metaStaged && metaBackup) {
|
|
17555
|
+
await fsp17.unlink(meta).catch(() => void 0);
|
|
17556
|
+
await fsp17.rename(metaBackup, meta).catch(() => void 0);
|
|
17557
|
+
}
|
|
17558
|
+
throw error2;
|
|
17559
|
+
} finally {
|
|
17560
|
+
if (maintenance && this.catalogClient) {
|
|
17561
|
+
await this.catalogClient.call("release_maintenance", { lease: maintenance }).catch(() => void 0);
|
|
17562
|
+
}
|
|
17563
|
+
}
|
|
17564
|
+
this.clearLoadCache(canonical);
|
|
16779
17565
|
}
|
|
16780
17566
|
async summarize(id, mtime) {
|
|
16781
17567
|
return summarizeSessionFile({
|
|
@@ -16795,9 +17581,9 @@ function makeDirectorSessionFactory(opts) {
|
|
|
16795
17581
|
let dir;
|
|
16796
17582
|
if (opts.store) {
|
|
16797
17583
|
store = opts.store;
|
|
16798
|
-
dir = opts.sessionsRoot ?
|
|
17584
|
+
dir = opts.sessionsRoot ? path24.join(opts.sessionsRoot, runId) : "(caller-managed)";
|
|
16799
17585
|
} else if (opts.sessionsRoot) {
|
|
16800
|
-
dir =
|
|
17586
|
+
dir = path24.join(opts.sessionsRoot, runId);
|
|
16801
17587
|
store = new DefaultSessionStore({ dir });
|
|
16802
17588
|
} else {
|
|
16803
17589
|
throw new Error("makeDirectorSessionFactory requires either `store` or `sessionsRoot`");
|
|
@@ -16821,7 +17607,7 @@ function makeDirectorSessionFactory(opts) {
|
|
|
16821
17607
|
}
|
|
16822
17608
|
async function readDirectorSubagentSession(args) {
|
|
16823
17609
|
if (!args.sessionsRoot) return null;
|
|
16824
|
-
const filePath =
|
|
17610
|
+
const filePath = path24.join(args.sessionsRoot, args.directorRunId, `${args.subagentId}.jsonl`);
|
|
16825
17611
|
let raw;
|
|
16826
17612
|
try {
|
|
16827
17613
|
raw = await fsp18.readFile(filePath, "utf8");
|
|
@@ -17393,7 +18179,7 @@ function formatRole(role) {
|
|
|
17393
18179
|
}
|
|
17394
18180
|
|
|
17395
18181
|
// src/coordination/fleet-spawn.ts
|
|
17396
|
-
async function
|
|
18182
|
+
async function spawn3(host, config, priceLookup) {
|
|
17397
18183
|
if (host.workCompleteFlag) {
|
|
17398
18184
|
throw new FleetSpawnBudgetError(
|
|
17399
18185
|
"max_spawns",
|
|
@@ -17677,7 +18463,7 @@ function hashStr(s) {
|
|
|
17677
18463
|
}
|
|
17678
18464
|
|
|
17679
18465
|
// src/coordination/multi-agent-coordinator.ts
|
|
17680
|
-
import { randomUUID as
|
|
18466
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
17681
18467
|
import { EventEmitter as EventEmitter2 } from "node:events";
|
|
17682
18468
|
|
|
17683
18469
|
// src/coordination/coordinator/error-classifier.ts
|
|
@@ -17816,12 +18602,12 @@ async function executeSubagentWithTimeout({
|
|
|
17816
18602
|
}
|
|
17817
18603
|
return new Promise((resolveDecision) => {
|
|
17818
18604
|
let settled = false;
|
|
17819
|
-
const
|
|
18605
|
+
const resolve21 = (d) => {
|
|
17820
18606
|
if (settled) return;
|
|
17821
18607
|
settled = true;
|
|
17822
18608
|
resolveDecision(d);
|
|
17823
18609
|
};
|
|
17824
|
-
const fallback = setTimeout(() =>
|
|
18610
|
+
const fallback = setTimeout(() => resolve21("stop"), DECISION_TIMEOUT_MS);
|
|
17825
18611
|
const sessionId = currentSessionId();
|
|
17826
18612
|
budget._events?.emit("budget.threshold_reached", {
|
|
17827
18613
|
...sessionId ? { sessionId } : {},
|
|
@@ -17831,11 +18617,11 @@ async function executeSubagentWithTimeout({
|
|
|
17831
18617
|
timeoutMs: DECISION_TIMEOUT_MS,
|
|
17832
18618
|
extend: (extra) => {
|
|
17833
18619
|
clearTimeout(fallback);
|
|
17834
|
-
queueMicrotask(() =>
|
|
18620
|
+
queueMicrotask(() => resolve21({ extend: extra }));
|
|
17835
18621
|
},
|
|
17836
18622
|
deny: () => {
|
|
17837
18623
|
clearTimeout(fallback);
|
|
17838
|
-
|
|
18624
|
+
resolve21("stop");
|
|
17839
18625
|
}
|
|
17840
18626
|
});
|
|
17841
18627
|
});
|
|
@@ -18047,7 +18833,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
18047
18833
|
return { ...subagent, name: display };
|
|
18048
18834
|
}
|
|
18049
18835
|
async spawn(subagent) {
|
|
18050
|
-
const id = subagent.id ||
|
|
18836
|
+
const id = subagent.id || randomUUID11();
|
|
18051
18837
|
const cfg = this.withNickname(subagent, id);
|
|
18052
18838
|
if (this.subagents.has(id)) {
|
|
18053
18839
|
throw new Error(`Subagent id "${id}" already exists \u2014 refusing to overwrite`);
|
|
@@ -18222,7 +19008,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
18222
19008
|
taskIds.map((id) => {
|
|
18223
19009
|
const cached = this.completedResults.find((r) => r.taskId === id);
|
|
18224
19010
|
if (cached) return cached;
|
|
18225
|
-
return new Promise((
|
|
19011
|
+
return new Promise((resolve21, reject) => {
|
|
18226
19012
|
const timeout = setTimeout(() => {
|
|
18227
19013
|
this.off("task.completed", handler);
|
|
18228
19014
|
reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
|
|
@@ -18231,7 +19017,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
18231
19017
|
if (result.taskId === id) {
|
|
18232
19018
|
clearTimeout(timeout);
|
|
18233
19019
|
this.off("task.completed", handler);
|
|
18234
|
-
|
|
19020
|
+
resolve21(result);
|
|
18235
19021
|
}
|
|
18236
19022
|
};
|
|
18237
19023
|
this.on("task.completed", handler);
|
|
@@ -18256,13 +19042,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
18256
19042
|
const done = new Set(completed.map((r) => r.taskId));
|
|
18257
19043
|
return { completed, pending: taskIds.filter((id) => !done.has(id)) };
|
|
18258
19044
|
}
|
|
18259
|
-
return new Promise((
|
|
19045
|
+
return new Promise((resolve21) => {
|
|
18260
19046
|
let timer;
|
|
18261
19047
|
const handler = ({ result }) => {
|
|
18262
19048
|
if (!ids.has(result.taskId)) return;
|
|
18263
19049
|
if (timer) clearTimeout(timer);
|
|
18264
19050
|
this.off("task.completed", handler);
|
|
18265
|
-
|
|
19051
|
+
resolve21({
|
|
18266
19052
|
completed: [result],
|
|
18267
19053
|
pending: taskIds.filter((id) => id !== result.taskId)
|
|
18268
19054
|
});
|
|
@@ -18270,7 +19056,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
|
|
|
18270
19056
|
if (opts?.timeoutMs !== void 0) {
|
|
18271
19057
|
timer = setTimeout(() => {
|
|
18272
19058
|
this.off("task.completed", handler);
|
|
18273
|
-
|
|
19059
|
+
resolve21({ completed: [], pending: [...taskIds], timedOut: true });
|
|
18274
19060
|
}, opts.timeoutMs);
|
|
18275
19061
|
}
|
|
18276
19062
|
this.on("task.completed", handler);
|
|
@@ -18932,7 +19718,7 @@ var Director = class _Director {
|
|
|
18932
19718
|
sessionProvider;
|
|
18933
19719
|
sessionModel;
|
|
18934
19720
|
constructor(opts) {
|
|
18935
|
-
this.id = opts.config.coordinatorId ||
|
|
19721
|
+
this.id = opts.config.coordinatorId || randomUUID12();
|
|
18936
19722
|
this.manifestPath = opts.manifestPath;
|
|
18937
19723
|
this.roster = opts.roster;
|
|
18938
19724
|
this.directorPreamble = opts.directorPreamble ?? DEFAULT_DIRECTOR_PREAMBLE;
|
|
@@ -19194,7 +19980,7 @@ var Director = class _Director {
|
|
|
19194
19980
|
}
|
|
19195
19981
|
const config = { ...callerConfig };
|
|
19196
19982
|
this.resolveSpawnModel(config);
|
|
19197
|
-
const subagentId = await
|
|
19983
|
+
const subagentId = await spawn3(this, config, priceLookup);
|
|
19198
19984
|
const perSubagentIdleMs = typeof config.idleTimeoutMs === "number" && Number.isFinite(config.idleTimeoutMs) && config.idleTimeoutMs >= 0 ? config.idleTimeoutMs : this.subagentIdleTimeoutMs;
|
|
19199
19985
|
this.armSubagentIdleRetirement(subagentId, perSubagentIdleMs);
|
|
19200
19986
|
return subagentId;
|
|
@@ -19215,7 +20001,7 @@ var Director = class _Director {
|
|
|
19215
20001
|
);
|
|
19216
20002
|
}
|
|
19217
20003
|
const msg = {
|
|
19218
|
-
id:
|
|
20004
|
+
id: randomUUID12(),
|
|
19219
20005
|
type: "task",
|
|
19220
20006
|
from: this.id,
|
|
19221
20007
|
to: subagentId,
|
|
@@ -21135,7 +21921,7 @@ function resolveContextWindowPolicy(config = {}, overrideMode) {
|
|
|
21135
21921
|
|
|
21136
21922
|
// src/infrastructure/logger.ts
|
|
21137
21923
|
import * as fsp20 from "node:fs/promises";
|
|
21138
|
-
import * as
|
|
21924
|
+
import * as path25 from "node:path";
|
|
21139
21925
|
var LEVEL_RANK2 = {
|
|
21140
21926
|
error: 0,
|
|
21141
21927
|
warn: 1,
|
|
@@ -21206,7 +21992,7 @@ var DefaultLogger = class _DefaultLogger {
|
|
|
21206
21992
|
this.stderr = opts.stderr !== false;
|
|
21207
21993
|
this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
|
|
21208
21994
|
if (this.file) {
|
|
21209
|
-
const dir =
|
|
21995
|
+
const dir = path25.dirname(this.file);
|
|
21210
21996
|
this._tail = this._tail.then(async () => {
|
|
21211
21997
|
await fsp20.mkdir(dir, { recursive: true });
|
|
21212
21998
|
}).catch(() => void 0);
|
|
@@ -21964,10 +22750,10 @@ var KNOWN_TOKEN_GROUPS = {
|
|
|
21964
22750
|
var KNOWN_TOKEN_NAMES = Object.values(KNOWN_TOKEN_GROUPS).flat();
|
|
21965
22751
|
|
|
21966
22752
|
// src/execution/design-kit-loader.ts
|
|
21967
|
-
import { existsSync as
|
|
21968
|
-
import * as
|
|
21969
|
-
import * as
|
|
21970
|
-
import { fileURLToPath as
|
|
22753
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
22754
|
+
import * as fs4 from "node:fs/promises";
|
|
22755
|
+
import * as path26 from "node:path";
|
|
22756
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
21971
22757
|
var KIT_FILE = "KIT.md";
|
|
21972
22758
|
var TOKENS_FILE = "tokens.json";
|
|
21973
22759
|
var FOUNDATIONS_ID = "_foundations";
|
|
@@ -22066,15 +22852,15 @@ var DefaultDesignKitLoader = class {
|
|
|
22066
22852
|
for (const { dir, source } of this.dirs) {
|
|
22067
22853
|
let entries;
|
|
22068
22854
|
try {
|
|
22069
|
-
entries = await
|
|
22855
|
+
entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
22070
22856
|
} catch {
|
|
22071
22857
|
continue;
|
|
22072
22858
|
}
|
|
22073
22859
|
for (const e of entries) {
|
|
22074
22860
|
if (!e.isDirectory()) continue;
|
|
22075
|
-
const kitFile =
|
|
22861
|
+
const kitFile = path26.join(dir, e.name, KIT_FILE);
|
|
22076
22862
|
try {
|
|
22077
|
-
const raw = await
|
|
22863
|
+
const raw = await fs4.readFile(kitFile, "utf8");
|
|
22078
22864
|
const fm = parseKitFrontmatter(raw);
|
|
22079
22865
|
const id = fm.id ?? e.name;
|
|
22080
22866
|
if (!fm.name) continue;
|
|
@@ -22135,7 +22921,7 @@ var DefaultDesignKitLoader = class {
|
|
|
22135
22921
|
if (cached !== void 0) return cached;
|
|
22136
22922
|
const m = await this.find(id);
|
|
22137
22923
|
if (!m) throw new Error(`Design kit "${id}" not found`);
|
|
22138
|
-
const raw = await
|
|
22924
|
+
const raw = await fs4.readFile(m.path, "utf8");
|
|
22139
22925
|
const body = narrowStackSections(stripFrontmatter(raw), stack);
|
|
22140
22926
|
this.bodyCache.set(key, body);
|
|
22141
22927
|
return body;
|
|
@@ -22147,9 +22933,9 @@ var DefaultDesignKitLoader = class {
|
|
|
22147
22933
|
const m = await this.find(id);
|
|
22148
22934
|
let tokens;
|
|
22149
22935
|
if (m) {
|
|
22150
|
-
const tokensPath =
|
|
22936
|
+
const tokensPath = path26.join(path26.dirname(m.path), TOKENS_FILE);
|
|
22151
22937
|
try {
|
|
22152
|
-
const raw = await
|
|
22938
|
+
const raw = await fs4.readFile(tokensPath, "utf8");
|
|
22153
22939
|
tokens = JSON.parse(raw);
|
|
22154
22940
|
} catch {
|
|
22155
22941
|
tokens = void 0;
|
|
@@ -22209,15 +22995,15 @@ function mergeKitTokens(base, own) {
|
|
|
22209
22995
|
}
|
|
22210
22996
|
function resolveBundledDesignKitsDir() {
|
|
22211
22997
|
try {
|
|
22212
|
-
const here =
|
|
22998
|
+
const here = path26.dirname(fileURLToPath4(import.meta.url));
|
|
22213
22999
|
const candidates = [
|
|
22214
|
-
|
|
22215
|
-
|
|
22216
|
-
|
|
22217
|
-
|
|
23000
|
+
path26.join(here, "design-kits"),
|
|
23001
|
+
path26.join(here, "..", "design-kits"),
|
|
23002
|
+
path26.join(here, "..", "..", "design-kits"),
|
|
23003
|
+
path26.join(here, "..", "..", "..", "design-kits")
|
|
22218
23004
|
];
|
|
22219
23005
|
for (const c of candidates) {
|
|
22220
|
-
if (
|
|
23006
|
+
if (existsSync4(c)) return c;
|
|
22221
23007
|
}
|
|
22222
23008
|
} catch {
|
|
22223
23009
|
}
|
|
@@ -22251,12 +23037,12 @@ function _resetDesignKitLoaderMemo() {
|
|
|
22251
23037
|
}
|
|
22252
23038
|
|
|
22253
23039
|
// src/execution/design-project-store.ts
|
|
22254
|
-
import { existsSync as
|
|
22255
|
-
import * as
|
|
22256
|
-
import * as
|
|
23040
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
23041
|
+
import * as fs5 from "node:fs/promises";
|
|
23042
|
+
import * as path27 from "node:path";
|
|
22257
23043
|
var DESIGN_DIR = ".design";
|
|
22258
23044
|
function designProjectDir(projectRoot) {
|
|
22259
|
-
return
|
|
23045
|
+
return path27.join(projectRoot, DESIGN_DIR);
|
|
22260
23046
|
}
|
|
22261
23047
|
var RULE_FILES = ["rules.md", "RULES.md", "design.md"];
|
|
22262
23048
|
var rulesCache = /* @__PURE__ */ new Map();
|
|
@@ -22271,7 +23057,7 @@ async function loadProjectDesignRules(projectRoot) {
|
|
|
22271
23057
|
let rules;
|
|
22272
23058
|
for (const name of RULE_FILES) {
|
|
22273
23059
|
try {
|
|
22274
|
-
const txt = await
|
|
23060
|
+
const txt = await fs5.readFile(path27.join(designProjectDir(projectRoot), name), "utf8");
|
|
22275
23061
|
if (txt.trim()) {
|
|
22276
23062
|
rules = txt.trim();
|
|
22277
23063
|
break;
|
|
@@ -22297,7 +23083,7 @@ function parseOverrides(value) {
|
|
|
22297
23083
|
}
|
|
22298
23084
|
async function loadActiveKit(projectRoot) {
|
|
22299
23085
|
try {
|
|
22300
|
-
const raw = await
|
|
23086
|
+
const raw = await fs5.readFile(path27.join(designProjectDir(projectRoot), "active.json"), "utf8");
|
|
22301
23087
|
const parsed = JSON.parse(raw);
|
|
22302
23088
|
if (parsed && typeof parsed.kit === "string") {
|
|
22303
23089
|
return {
|
|
@@ -22330,11 +23116,11 @@ function applyTokenOverrides(tokens, overrides) {
|
|
|
22330
23116
|
}
|
|
22331
23117
|
async function ensureDesignDir(projectRoot) {
|
|
22332
23118
|
const dir = designProjectDir(projectRoot);
|
|
22333
|
-
await
|
|
22334
|
-
const gi =
|
|
22335
|
-
if (!
|
|
23119
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
23120
|
+
const gi = path27.join(dir, ".gitignore");
|
|
23121
|
+
if (!existsSync5(gi)) {
|
|
22336
23122
|
try {
|
|
22337
|
-
await
|
|
23123
|
+
await fs5.writeFile(gi, "*\n");
|
|
22338
23124
|
} catch {
|
|
22339
23125
|
}
|
|
22340
23126
|
}
|
|
@@ -22345,11 +23131,11 @@ async function recordKitChoice(projectRoot, kit, stack, source, isoTime, overrid
|
|
|
22345
23131
|
const dir = await ensureDesignDir(projectRoot);
|
|
22346
23132
|
const record = { kit, stack: stack ?? null };
|
|
22347
23133
|
if (overrides && Object.keys(overrides).length > 0) record.overrides = overrides;
|
|
22348
|
-
await
|
|
23134
|
+
await fs5.writeFile(path27.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
|
|
22349
23135
|
`);
|
|
22350
23136
|
const line = `- ${isoTime} \xB7 kit=${kit}${stack ? ` stack=${stack}` : ""} \xB7 via=${source}
|
|
22351
23137
|
`;
|
|
22352
|
-
await
|
|
23138
|
+
await fs5.appendFile(path27.join(dir, "decisions.md"), line);
|
|
22353
23139
|
} catch {
|
|
22354
23140
|
}
|
|
22355
23141
|
}
|
|
@@ -22365,11 +23151,11 @@ async function recordOverrides(projectRoot, patch, isoTime) {
|
|
|
22365
23151
|
const dir = await ensureDesignDir(projectRoot);
|
|
22366
23152
|
const record = { kit: active.kit, stack: active.stack ?? null };
|
|
22367
23153
|
if (Object.keys(merged).length > 0) record.overrides = merged;
|
|
22368
|
-
await
|
|
23154
|
+
await fs5.writeFile(path27.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
|
|
22369
23155
|
`);
|
|
22370
23156
|
const keys = Object.keys(patch).join(",");
|
|
22371
|
-
await
|
|
22372
|
-
|
|
23157
|
+
await fs5.appendFile(
|
|
23158
|
+
path27.join(dir, "decisions.md"),
|
|
22373
23159
|
`- ${isoTime} \xB7 kit=${active.kit} \xB7 override=${keys} \xB7 via=set
|
|
22374
23160
|
`
|
|
22375
23161
|
);
|
|
@@ -22379,7 +23165,7 @@ async function recordOverrides(projectRoot, patch, isoTime) {
|
|
|
22379
23165
|
}
|
|
22380
23166
|
async function clearPersistedActiveKit(projectRoot) {
|
|
22381
23167
|
try {
|
|
22382
|
-
await
|
|
23168
|
+
await fs5.rm(path27.join(designProjectDir(projectRoot), "active.json"), { force: true });
|
|
22383
23169
|
} catch {
|
|
22384
23170
|
}
|
|
22385
23171
|
}
|
|
@@ -22519,12 +23305,12 @@ function verifyFiles(tokens, files) {
|
|
|
22519
23305
|
const violations = [];
|
|
22520
23306
|
let onPalette = 0;
|
|
22521
23307
|
let offPalette = 0;
|
|
22522
|
-
for (const { path:
|
|
23308
|
+
for (const { path: path45, text } of files) {
|
|
22523
23309
|
const lines = text.split("\n");
|
|
22524
23310
|
lines.forEach((lineText, i) => {
|
|
22525
23311
|
const lineNo = i + 1;
|
|
22526
23312
|
const flag = (snippet, reason, axis = "color") => {
|
|
22527
|
-
violations.push({ file:
|
|
23313
|
+
violations.push({ file: path45, line: lineNo, snippet: snippet.slice(0, 80), reason, axis });
|
|
22528
23314
|
};
|
|
22529
23315
|
for (const re of [HEX_RE, FUNC_COLOR_RE]) {
|
|
22530
23316
|
re.lastIndex = 0;
|
|
@@ -22591,14 +23377,14 @@ var SKIP_DIR = /* @__PURE__ */ new Set([
|
|
|
22591
23377
|
".design"
|
|
22592
23378
|
]);
|
|
22593
23379
|
async function walkUiFiles(root, max = 200) {
|
|
22594
|
-
const { default:
|
|
23380
|
+
const { default: fs16 } = await import("node:fs/promises");
|
|
22595
23381
|
const { default: nodePath } = await import("node:path");
|
|
22596
23382
|
const found = [];
|
|
22597
23383
|
async function rec(dir, depth) {
|
|
22598
23384
|
if (found.length >= max || depth > 8) return;
|
|
22599
23385
|
let entries;
|
|
22600
23386
|
try {
|
|
22601
|
-
entries = await
|
|
23387
|
+
entries = await fs16.readdir(dir, { withFileTypes: true });
|
|
22602
23388
|
} catch {
|
|
22603
23389
|
return;
|
|
22604
23390
|
}
|
|
@@ -22616,13 +23402,13 @@ async function walkUiFiles(root, max = 200) {
|
|
|
22616
23402
|
return found;
|
|
22617
23403
|
}
|
|
22618
23404
|
async function runDesignVerify(projectRoot, tokens, explicitFiles) {
|
|
22619
|
-
const { default:
|
|
23405
|
+
const { default: fs16 } = await import("node:fs/promises");
|
|
22620
23406
|
const { default: nodePath } = await import("node:path");
|
|
22621
23407
|
const abs = explicitFiles && explicitFiles.length > 0 ? explicitFiles.map((f) => nodePath.isAbsolute(f) ? f : nodePath.join(projectRoot, f)) : await walkUiFiles(projectRoot);
|
|
22622
23408
|
const files = [];
|
|
22623
23409
|
for (const a of abs) {
|
|
22624
23410
|
try {
|
|
22625
|
-
files.push({ path: nodePath.relative(projectRoot, a), text: await
|
|
23411
|
+
files.push({ path: nodePath.relative(projectRoot, a), text: await fs16.readFile(a, "utf8") });
|
|
22626
23412
|
} catch {
|
|
22627
23413
|
}
|
|
22628
23414
|
}
|
|
@@ -22753,10 +23539,10 @@ function makeDesignVerifyToolCallMiddleware() {
|
|
|
22753
23539
|
const p = typeof input?.path === "string" ? input.path : "";
|
|
22754
23540
|
if (!p || !detectFrontendFile(p)) return out;
|
|
22755
23541
|
const ctx = out.ctx;
|
|
22756
|
-
const { default:
|
|
23542
|
+
const { default: fs16 } = await import("node:fs/promises");
|
|
22757
23543
|
const { default: nodePath } = await import("node:path");
|
|
22758
23544
|
const abs = nodePath.isAbsolute(p) ? p : nodePath.join(ctx.projectRoot, p);
|
|
22759
|
-
const text = await
|
|
23545
|
+
const text = await fs16.readFile(abs, "utf8").catch(() => "");
|
|
22760
23546
|
if (!text) return out;
|
|
22761
23547
|
const loader = getDesignKitLoader(ctx.projectRoot);
|
|
22762
23548
|
const rawTokens = await loader.readTokens(state.activeKit);
|
|
@@ -23205,11 +23991,11 @@ function buildRecoveryStrategies(opts) {
|
|
|
23205
23991
|
async attempt(err, ctx) {
|
|
23206
23992
|
if (!(err instanceof ProviderError) || err.kind !== "rate_limit") return null;
|
|
23207
23993
|
const delayMs = err.body?.retryAfterMs ?? 5e3;
|
|
23208
|
-
const
|
|
23994
|
+
const delay2 = Math.min(6e4, Math.max(1e3, delayMs));
|
|
23209
23995
|
if (ctx?.signal) {
|
|
23210
|
-
await new Promise((
|
|
23996
|
+
await new Promise((resolve21) => {
|
|
23211
23997
|
if (ctx.signal.aborted) {
|
|
23212
|
-
|
|
23998
|
+
resolve21();
|
|
23213
23999
|
return;
|
|
23214
24000
|
}
|
|
23215
24001
|
let settled = false;
|
|
@@ -23220,13 +24006,13 @@ function buildRecoveryStrategies(opts) {
|
|
|
23220
24006
|
settled = true;
|
|
23221
24007
|
if (timer !== void 0) clearTimeout(timer);
|
|
23222
24008
|
ctx.signal.removeEventListener("abort", onAbort);
|
|
23223
|
-
|
|
24009
|
+
resolve21();
|
|
23224
24010
|
};
|
|
23225
|
-
timer = setTimeout(finish,
|
|
24011
|
+
timer = setTimeout(finish, delay2);
|
|
23226
24012
|
ctx.signal.addEventListener("abort", onAbort, { once: true });
|
|
23227
24013
|
});
|
|
23228
24014
|
} else {
|
|
23229
|
-
await new Promise((r) => setTimeout(r,
|
|
24015
|
+
await new Promise((r) => setTimeout(r, delay2));
|
|
23230
24016
|
}
|
|
23231
24017
|
return { action: "retry", reason: "rate_limit_backoff" };
|
|
23232
24018
|
}
|
|
@@ -23856,10 +24642,10 @@ var EternalAutonomyEngine = class {
|
|
|
23856
24642
|
if (status === "failure") {
|
|
23857
24643
|
if (isTransientFailure) {
|
|
23858
24644
|
this.consecutiveTransientRetries++;
|
|
23859
|
-
const
|
|
23860
|
-
if (
|
|
23861
|
-
emit2({ phase: "sleep", ms:
|
|
23862
|
-
await this.sleepInterruptible(
|
|
24645
|
+
const delay2 = this.computeTransientBackoffMs();
|
|
24646
|
+
if (delay2 > 0) {
|
|
24647
|
+
emit2({ phase: "sleep", ms: delay2 });
|
|
24648
|
+
await this.sleepInterruptible(delay2);
|
|
23863
24649
|
}
|
|
23864
24650
|
return false;
|
|
23865
24651
|
}
|
|
@@ -24361,7 +25147,7 @@ function buildGoalPreamble(goal, deliverables) {
|
|
|
24361
25147
|
}
|
|
24362
25148
|
|
|
24363
25149
|
// src/execution/compaction-summary-cache.ts
|
|
24364
|
-
import { createHash as
|
|
25150
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
24365
25151
|
|
|
24366
25152
|
// src/utils/lru-cache.ts
|
|
24367
25153
|
var LruCache = class {
|
|
@@ -24499,7 +25285,7 @@ function semanticBlock(block) {
|
|
|
24499
25285
|
}
|
|
24500
25286
|
}
|
|
24501
25287
|
function compactionSummaryKey(model, prompt, messages) {
|
|
24502
|
-
const hash2 =
|
|
25288
|
+
const hash2 = createHash6("sha256");
|
|
24503
25289
|
hash2.update("wrongstack-compaction-summary-v2\0").update(model).update("\0").update(prompt);
|
|
24504
25290
|
for (const message of messages) {
|
|
24505
25291
|
hash2.update("\0").update(message.role).update("\0");
|
|
@@ -24713,7 +25499,7 @@ ${summaryText}` : summaryText;
|
|
|
24713
25499
|
};
|
|
24714
25500
|
|
|
24715
25501
|
// src/execution/parallel-eternal-engine.ts
|
|
24716
|
-
import { randomUUID as
|
|
25502
|
+
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
24717
25503
|
var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
|
|
24718
25504
|
var ParallelEternalEngine = class {
|
|
24719
25505
|
constructor(opts) {
|
|
@@ -24790,7 +25576,7 @@ var ParallelEternalEngine = class {
|
|
|
24790
25576
|
this.state = "running";
|
|
24791
25577
|
await this.persistState("running");
|
|
24792
25578
|
const config = {
|
|
24793
|
-
coordinatorId: `parallel-${
|
|
25579
|
+
coordinatorId: `parallel-${randomUUID13().slice(0, 8)}`,
|
|
24794
25580
|
maxConcurrent: this.slots,
|
|
24795
25581
|
doneCondition: { type: "all_tasks_done" }
|
|
24796
25582
|
};
|
|
@@ -24844,7 +25630,7 @@ var ParallelEternalEngine = class {
|
|
|
24844
25630
|
}
|
|
24845
25631
|
if (!this.coordinator) {
|
|
24846
25632
|
const config = {
|
|
24847
|
-
coordinatorId: `parallel-${
|
|
25633
|
+
coordinatorId: `parallel-${randomUUID13().slice(0, 8)}`,
|
|
24848
25634
|
maxConcurrent: this.slots,
|
|
24849
25635
|
doneCondition: { type: "all_tasks_done" }
|
|
24850
25636
|
};
|
|
@@ -24930,7 +25716,7 @@ ${recentJournal}` : "No prior iterations.",
|
|
|
24930
25716
|
const task = expectDefined(tasks[i]);
|
|
24931
25717
|
const route = routes[i] ?? null;
|
|
24932
25718
|
const subagentId = `parallel-${this.iterations}-${i}`;
|
|
24933
|
-
const taskId =
|
|
25719
|
+
const taskId = randomUUID13();
|
|
24934
25720
|
const personaLine = route ? `Acting agent: ${route.definition.config.name} \u2014 ${route.definition.capability.summary}
|
|
24935
25721
|
` : "";
|
|
24936
25722
|
const spec = {
|
|
@@ -25145,7 +25931,7 @@ ${lastFew}` : "No prior iterations.",
|
|
|
25145
25931
|
};
|
|
25146
25932
|
|
|
25147
25933
|
// src/core/streaming-response-builder.ts
|
|
25148
|
-
import { randomUUID as
|
|
25934
|
+
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
25149
25935
|
var STREAM_DRAIN_TIMEOUT_MS = 500;
|
|
25150
25936
|
function buildResponse(state) {
|
|
25151
25937
|
const content = [];
|
|
@@ -25205,7 +25991,7 @@ function handleContentBlockStart(state, ev) {
|
|
|
25205
25991
|
state.textBuffers.push("");
|
|
25206
25992
|
state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
|
|
25207
25993
|
} else if (kind === "tool_use") {
|
|
25208
|
-
const id = ev.id ??
|
|
25994
|
+
const id = ev.id ?? randomUUID14();
|
|
25209
25995
|
state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
|
|
25210
25996
|
state.blockOrder.push({ kind: "tool", id });
|
|
25211
25997
|
state.currentTextIndex = -1;
|
|
@@ -25414,8 +26200,8 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
25414
26200
|
});
|
|
25415
26201
|
await Promise.race([
|
|
25416
26202
|
drainPromise,
|
|
25417
|
-
new Promise((
|
|
25418
|
-
drainTimer = setTimeout(
|
|
26203
|
+
new Promise((resolve21) => {
|
|
26204
|
+
drainTimer = setTimeout(resolve21, STREAM_DRAIN_TIMEOUT_MS);
|
|
25419
26205
|
})
|
|
25420
26206
|
]);
|
|
25421
26207
|
} finally {
|
|
@@ -25439,7 +26225,7 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
|
|
|
25439
26225
|
}
|
|
25440
26226
|
|
|
25441
26227
|
// src/chronicle/prompt-manifest.ts
|
|
25442
|
-
import { createHash as
|
|
26228
|
+
import { createHash as createHash7 } from "node:crypto";
|
|
25443
26229
|
function createChroniclePromptManifest(request) {
|
|
25444
26230
|
const roleCounts = {}, blockCounts = {};
|
|
25445
26231
|
const messages = request.messages.map((message, index) => messageSummary(message, index, roleCounts, blockCounts));
|
|
@@ -25526,11 +26312,11 @@ function countBlocks(blocks) {
|
|
|
25526
26312
|
return counts;
|
|
25527
26313
|
}
|
|
25528
26314
|
function hash(value) {
|
|
25529
|
-
return
|
|
26315
|
+
return createHash7("sha256").update(value).digest("hex");
|
|
25530
26316
|
}
|
|
25531
26317
|
function contentIdentity(content) {
|
|
25532
26318
|
if (typeof content === "string") return { hash: hash(content), bytes: Buffer.byteLength(content) };
|
|
25533
|
-
const digest =
|
|
26319
|
+
const digest = createHash7("sha256");
|
|
25534
26320
|
let bytes = 0;
|
|
25535
26321
|
const add = (value) => {
|
|
25536
26322
|
if (!value) return;
|
|
@@ -25612,7 +26398,7 @@ function replaceAllCaseInsensitive(haystack, needle, replacement) {
|
|
|
25612
26398
|
}
|
|
25613
26399
|
|
|
25614
26400
|
// src/core/provider-runner.ts
|
|
25615
|
-
import { randomUUID as
|
|
26401
|
+
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
25616
26402
|
function scrubProviderBody(body) {
|
|
25617
26403
|
if (!body) return void 0;
|
|
25618
26404
|
return {
|
|
@@ -25634,11 +26420,11 @@ function providerLogCtx(p, r) {
|
|
|
25634
26420
|
}
|
|
25635
26421
|
async function runProviderWithRetry(opts) {
|
|
25636
26422
|
const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
|
|
25637
|
-
const logicalRequestId =
|
|
26423
|
+
const logicalRequestId = randomUUID15();
|
|
25638
26424
|
const promptManifest = createChroniclePromptManifest(request);
|
|
25639
26425
|
let attempt = 0;
|
|
25640
26426
|
for (; ; ) {
|
|
25641
|
-
const attemptId =
|
|
26427
|
+
const attemptId = randomUUID15();
|
|
25642
26428
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
25643
26429
|
const startedNs = process.hrtime.bigint();
|
|
25644
26430
|
const correlation = {
|
|
@@ -25711,7 +26497,7 @@ async function runProviderWithRetry(opts) {
|
|
|
25711
26497
|
const description = scrubErrorText(
|
|
25712
26498
|
isProviderErr ? err.describe() : errAsErr.message
|
|
25713
26499
|
);
|
|
25714
|
-
const
|
|
26500
|
+
const delay2 = canRetry ? Math.round(retry.delayMs(attempt, isProviderErr ? err : errAsErr)) : void 0;
|
|
25715
26501
|
events.emit("provider.attempt.failed", {
|
|
25716
26502
|
...correlation,
|
|
25717
26503
|
startedAt,
|
|
@@ -25722,7 +26508,7 @@ async function runProviderWithRetry(opts) {
|
|
|
25722
26508
|
description,
|
|
25723
26509
|
retryable: canRetry,
|
|
25724
26510
|
retryScheduled: canRetry,
|
|
25725
|
-
...
|
|
26511
|
+
...delay2 !== void 0 ? { retryDelayMs: delay2 } : {},
|
|
25726
26512
|
...providerErrorBody?.requestId ? { providerRequestId: providerErrorBody.requestId } : {},
|
|
25727
26513
|
...providerErrorBody ? { errorBody: providerErrorBody } : {}
|
|
25728
26514
|
});
|
|
@@ -25748,11 +26534,11 @@ async function runProviderWithRetry(opts) {
|
|
|
25748
26534
|
}
|
|
25749
26535
|
const attemptNum = attempt + 1;
|
|
25750
26536
|
const maxAttempts = retry.maxAttempts(isProviderErr ? err : errAsErr);
|
|
25751
|
-
logger.warn(`Provider retry ${attemptNum}/${maxAttempts} in ${
|
|
26537
|
+
logger.warn(`Provider retry ${attemptNum}/${maxAttempts} in ${delay2}ms \u2014 ${description}`, {
|
|
25752
26538
|
...providerLogCtx(provider, request),
|
|
25753
26539
|
attempt: attemptNum,
|
|
25754
26540
|
maxAttempts,
|
|
25755
|
-
delayMs:
|
|
26541
|
+
delayMs: delay2,
|
|
25756
26542
|
errorDescription: description,
|
|
25757
26543
|
status: isProviderErr ? err.status : void 0,
|
|
25758
26544
|
errorBody: providerErrorBody
|
|
@@ -25761,12 +26547,12 @@ async function runProviderWithRetry(opts) {
|
|
|
25761
26547
|
sessionId: resolveEventSessionId(ctx),
|
|
25762
26548
|
providerId: isProviderErr ? err.providerId : provider.id,
|
|
25763
26549
|
attempt: attemptNum,
|
|
25764
|
-
delayMs:
|
|
26550
|
+
delayMs: delay2,
|
|
25765
26551
|
status: isProviderErr ? err.status : 0,
|
|
25766
26552
|
description,
|
|
25767
26553
|
...providerErrorBody ? { errorBody: providerErrorBody } : {}
|
|
25768
26554
|
});
|
|
25769
|
-
await new Promise((
|
|
26555
|
+
await new Promise((resolve21, reject) => {
|
|
25770
26556
|
let settled = false;
|
|
25771
26557
|
const cleanup = () => {
|
|
25772
26558
|
clearTimeout(t);
|
|
@@ -25782,8 +26568,8 @@ async function runProviderWithRetry(opts) {
|
|
|
25782
26568
|
if (settled) return;
|
|
25783
26569
|
settled = true;
|
|
25784
26570
|
cleanup();
|
|
25785
|
-
|
|
25786
|
-
},
|
|
26571
|
+
resolve21();
|
|
26572
|
+
}, delay2);
|
|
25787
26573
|
if (signal.aborted) {
|
|
25788
26574
|
onAbort();
|
|
25789
26575
|
return;
|
|
@@ -26406,8 +27192,8 @@ Summarize the following message range:`;
|
|
|
26406
27192
|
};
|
|
26407
27193
|
|
|
26408
27194
|
// src/execution/skill-loader.ts
|
|
26409
|
-
import * as
|
|
26410
|
-
import * as
|
|
27195
|
+
import * as fs6 from "node:fs/promises";
|
|
27196
|
+
import * as path28 from "node:path";
|
|
26411
27197
|
|
|
26412
27198
|
// src/skills/foreign-sources.ts
|
|
26413
27199
|
var FOREIGN_SKILL_TOOLS = [
|
|
@@ -26471,7 +27257,7 @@ async function entryIsDirectory(dir, entry) {
|
|
|
26471
27257
|
if (entry.isDirectory()) return true;
|
|
26472
27258
|
if (entry.isSymbolicLink()) {
|
|
26473
27259
|
try {
|
|
26474
|
-
return (await
|
|
27260
|
+
return (await fs6.stat(path28.join(dir, entry.name))).isDirectory();
|
|
26475
27261
|
} catch {
|
|
26476
27262
|
return false;
|
|
26477
27263
|
}
|
|
@@ -26495,7 +27281,7 @@ var DefaultSkillLoader = class {
|
|
|
26495
27281
|
for (const tool of FOREIGN_SKILL_TOOLS) {
|
|
26496
27282
|
if (!foreignIds.includes(tool.id)) continue;
|
|
26497
27283
|
dirs.push({
|
|
26498
|
-
dir:
|
|
27284
|
+
dir: path28.join(root, "." + tool.id, tool.subdir),
|
|
26499
27285
|
source: "foreign",
|
|
26500
27286
|
originTool: tool.id
|
|
26501
27287
|
});
|
|
@@ -26520,15 +27306,15 @@ var DefaultSkillLoader = class {
|
|
|
26520
27306
|
this.shadowed = [];
|
|
26521
27307
|
for (const { dir, source, originTool } of this.dirs) {
|
|
26522
27308
|
try {
|
|
26523
|
-
const entries = (await
|
|
27309
|
+
const entries = (await fs6.readdir(dir, { withFileTypes: true })).sort(
|
|
26524
27310
|
(a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
|
|
26525
27311
|
);
|
|
26526
27312
|
for (const e of entries) {
|
|
26527
27313
|
if (!await entryIsDirectory(dir, e)) continue;
|
|
26528
|
-
const skillFile =
|
|
27314
|
+
const skillFile = path28.join(dir, e.name, "SKILL.md");
|
|
26529
27315
|
let raw;
|
|
26530
27316
|
try {
|
|
26531
|
-
raw = await
|
|
27317
|
+
raw = await fs6.readFile(skillFile, "utf8");
|
|
26532
27318
|
} catch {
|
|
26533
27319
|
continue;
|
|
26534
27320
|
}
|
|
@@ -26647,7 +27433,7 @@ var DefaultSkillLoader = class {
|
|
|
26647
27433
|
if (cached !== void 0) return cached;
|
|
26648
27434
|
const m = await this.find(name);
|
|
26649
27435
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
26650
|
-
const body = await
|
|
27436
|
+
const body = await fs6.readFile(m.path, "utf8");
|
|
26651
27437
|
this.bodyCache.set(key, body);
|
|
26652
27438
|
return body;
|
|
26653
27439
|
}
|
|
@@ -26657,12 +27443,12 @@ var DefaultSkillLoader = class {
|
|
|
26657
27443
|
if (cached !== void 0) return cached;
|
|
26658
27444
|
const m = await this.find(name);
|
|
26659
27445
|
if (!m) throw new Error(`Skill "${name}" not found`);
|
|
26660
|
-
const savePath =
|
|
27446
|
+
const savePath = path28.join(path28.dirname(m.path), "SKILL.save.md");
|
|
26661
27447
|
let result;
|
|
26662
27448
|
try {
|
|
26663
|
-
result = await
|
|
27449
|
+
result = await fs6.readFile(savePath, "utf8");
|
|
26664
27450
|
} catch {
|
|
26665
|
-
const full = await
|
|
27451
|
+
const full = await fs6.readFile(m.path, "utf8");
|
|
26666
27452
|
const body = stripFrontmatter(full);
|
|
26667
27453
|
const compact = compactSkillBody(body);
|
|
26668
27454
|
if (compact) {
|
|
@@ -26690,12 +27476,12 @@ function parseDescriptionFromText(desc) {
|
|
|
26690
27476
|
}
|
|
26691
27477
|
|
|
26692
27478
|
// src/execution/prompt-loader.ts
|
|
26693
|
-
import * as
|
|
26694
|
-
import * as
|
|
27479
|
+
import * as fs8 from "node:fs/promises";
|
|
27480
|
+
import * as path30 from "node:path";
|
|
26695
27481
|
|
|
26696
27482
|
// src/storage/prompt-store.ts
|
|
26697
|
-
import * as
|
|
26698
|
-
import * as
|
|
27483
|
+
import * as fs7 from "node:fs/promises";
|
|
27484
|
+
import * as path29 from "node:path";
|
|
26699
27485
|
var SCHEMA_VERSION = 2;
|
|
26700
27486
|
function migratePromptEntry(raw) {
|
|
26701
27487
|
if (!raw || typeof raw !== "object") return null;
|
|
@@ -26756,12 +27542,12 @@ var DefaultPromptStore = class {
|
|
|
26756
27542
|
await ensureDir(this.dir);
|
|
26757
27543
|
const entries = [];
|
|
26758
27544
|
try {
|
|
26759
|
-
const files = await
|
|
27545
|
+
const files = await fs7.readdir(this.dir);
|
|
26760
27546
|
for (const file of files) {
|
|
26761
27547
|
if (!file.endsWith(".json")) continue;
|
|
26762
27548
|
try {
|
|
26763
27549
|
const raw = JSON.parse(
|
|
26764
|
-
await
|
|
27550
|
+
await fs7.readFile(path29.join(this.dir, file), "utf8")
|
|
26765
27551
|
);
|
|
26766
27552
|
const migrated = migratePromptEntry(raw.entry);
|
|
26767
27553
|
if (migrated) entries.push(migrated);
|
|
@@ -26775,9 +27561,9 @@ var DefaultPromptStore = class {
|
|
|
26775
27561
|
);
|
|
26776
27562
|
}
|
|
26777
27563
|
async get(id) {
|
|
26778
|
-
const file =
|
|
27564
|
+
const file = path29.join(this.dir, `${id}.json`);
|
|
26779
27565
|
try {
|
|
26780
|
-
const raw = JSON.parse(await
|
|
27566
|
+
const raw = JSON.parse(await fs7.readFile(file, "utf8"));
|
|
26781
27567
|
return migratePromptEntry(raw.entry);
|
|
26782
27568
|
} catch {
|
|
26783
27569
|
return null;
|
|
@@ -26785,14 +27571,14 @@ var DefaultPromptStore = class {
|
|
|
26785
27571
|
}
|
|
26786
27572
|
async save(entry) {
|
|
26787
27573
|
await ensureDir(this.dir);
|
|
26788
|
-
const file =
|
|
27574
|
+
const file = path29.join(this.dir, `${entry.id}.json`);
|
|
26789
27575
|
const raw = { version: SCHEMA_VERSION, entry };
|
|
26790
27576
|
await atomicWrite(file, JSON.stringify(raw, null, 2));
|
|
26791
27577
|
}
|
|
26792
27578
|
async delete(id) {
|
|
26793
|
-
const file =
|
|
27579
|
+
const file = path29.join(this.dir, `${id}.json`);
|
|
26794
27580
|
try {
|
|
26795
|
-
await
|
|
27581
|
+
await fs7.unlink(file);
|
|
26796
27582
|
return true;
|
|
26797
27583
|
} catch {
|
|
26798
27584
|
return false;
|
|
@@ -26879,7 +27665,7 @@ var DefaultPromptLoader = class {
|
|
|
26879
27665
|
constructor(opts) {
|
|
26880
27666
|
this.projectStore = typeof opts.paths.inProjectPrompts === "string" ? new DefaultPromptStore(opts.paths.inProjectPrompts) : void 0;
|
|
26881
27667
|
this.userStore = typeof opts.paths.globalPrompts === "string" ? new DefaultPromptStore(opts.paths.globalPrompts) : void 0;
|
|
26882
|
-
this.builtinDir = opts.bundledDir ?
|
|
27668
|
+
this.builtinDir = opts.bundledDir ? path30.join(opts.bundledDir, "prompts") : void 0;
|
|
26883
27669
|
}
|
|
26884
27670
|
async list() {
|
|
26885
27671
|
if (this.cache) return this.cache;
|
|
@@ -26992,7 +27778,7 @@ var DefaultPromptLoader = class {
|
|
|
26992
27778
|
const files = await walkJson(dir);
|
|
26993
27779
|
for (const file of files) {
|
|
26994
27780
|
try {
|
|
26995
|
-
const parsed = JSON.parse(await
|
|
27781
|
+
const parsed = JSON.parse(await fs8.readFile(file, "utf8"));
|
|
26996
27782
|
const migrated = migratePromptEntry(parsed);
|
|
26997
27783
|
if (migrated) out.push({ ...migrated, source: "builtin" });
|
|
26998
27784
|
} catch {
|
|
@@ -27006,12 +27792,12 @@ async function walkJson(dir) {
|
|
|
27006
27792
|
const out = [];
|
|
27007
27793
|
let entries;
|
|
27008
27794
|
try {
|
|
27009
|
-
entries = await
|
|
27795
|
+
entries = await fs8.readdir(dir, { withFileTypes: true });
|
|
27010
27796
|
} catch {
|
|
27011
27797
|
return out;
|
|
27012
27798
|
}
|
|
27013
27799
|
for (const e of entries) {
|
|
27014
|
-
const full =
|
|
27800
|
+
const full = path30.join(dir, e.name);
|
|
27015
27801
|
if (e.isDirectory()) {
|
|
27016
27802
|
out.push(...await walkJson(full));
|
|
27017
27803
|
} else if (e.name.endsWith(".json") && e.name !== "index.json" && e.name !== "schema.json") {
|
|
@@ -27198,9 +27984,9 @@ function readPolicy(ctx) {
|
|
|
27198
27984
|
}
|
|
27199
27985
|
|
|
27200
27986
|
// src/execution/tool-executor.ts
|
|
27201
|
-
import { randomUUID as
|
|
27202
|
-
import * as
|
|
27203
|
-
import * as
|
|
27987
|
+
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
27988
|
+
import * as fs10 from "node:fs/promises";
|
|
27989
|
+
import * as path33 from "node:path";
|
|
27204
27990
|
import { isDeepStrictEqual } from "node:util";
|
|
27205
27991
|
|
|
27206
27992
|
// src/observability/process-telemetry.ts
|
|
@@ -27212,13 +27998,15 @@ function runWithProcessTelemetry(context, run) {
|
|
|
27212
27998
|
|
|
27213
27999
|
// src/security/kanban-boundary.ts
|
|
27214
28000
|
import { realpath as realpath3 } from "node:fs/promises";
|
|
27215
|
-
import * as
|
|
28001
|
+
import * as path31 from "node:path";
|
|
27216
28002
|
import {
|
|
28003
|
+
evaluateContractGraphReadiness,
|
|
27217
28004
|
evaluateKanbanBoundaryOpaque,
|
|
27218
28005
|
evaluateKanbanBoundaryPath,
|
|
27219
28006
|
readBoard,
|
|
27220
28007
|
resolveKanbanBoundaryLayers
|
|
27221
28008
|
} from "@wrongstack/kanban";
|
|
28009
|
+
var GOVERNANCE_CONTROL_TOOLS = /* @__PURE__ */ new Set(["kanban", "plan", "task", "todo"]);
|
|
27222
28010
|
var PATH_KEYS = /* @__PURE__ */ new Set([
|
|
27223
28011
|
"path",
|
|
27224
28012
|
"paths",
|
|
@@ -27243,12 +28031,60 @@ var TOOL_PATH_KEYS = {
|
|
|
27243
28031
|
// filesystem target in language_info. Keep ambiguous names tool-specific.
|
|
27244
28032
|
language_info: ["target"]
|
|
27245
28033
|
};
|
|
27246
|
-
async function evaluateToolKanbanBoundary(tool, input, ctx) {
|
|
28034
|
+
async function evaluateToolKanbanBoundary(tool, input, ctx, options = {}) {
|
|
28035
|
+
if (tool.name === "kanban") return { decision: "allow" };
|
|
27247
28036
|
const identity = resolveKanbanIdentity(ctx);
|
|
27248
|
-
|
|
28037
|
+
const governanceRequired = options.requireGovernance && isGovernedMutation(tool, input);
|
|
28038
|
+
if (!identity.boardId) {
|
|
28039
|
+
return governanceRequired ? {
|
|
28040
|
+
decision: "block",
|
|
28041
|
+
reason: "Kanban governance is mandatory before product mutation. Create a managed card, add its required details and executable acceptance criteria, then call kanban start_task to bind it to this run."
|
|
28042
|
+
} : { decision: "allow" };
|
|
28043
|
+
}
|
|
27249
28044
|
const board = await readBoard(identity.policyRoot ?? ctx.projectRoot, identity.boardId);
|
|
27250
|
-
if (!board)
|
|
28045
|
+
if (!board) {
|
|
28046
|
+
return governanceRequired ? {
|
|
28047
|
+
decision: "block",
|
|
28048
|
+
reason: `Active Kanban board not found: ${identity.boardId}. Recreate or select a valid managed card before mutation.`,
|
|
28049
|
+
boardId: identity.boardId,
|
|
28050
|
+
...identity.taskId ? { taskId: identity.taskId } : {}
|
|
28051
|
+
} : { decision: "allow" };
|
|
28052
|
+
}
|
|
27251
28053
|
const task = identity.taskId ? board.tasks.find((candidate) => candidate.id === identity.taskId) : void 0;
|
|
28054
|
+
if (governanceRequired) {
|
|
28055
|
+
if (!identity.taskId) {
|
|
28056
|
+
return {
|
|
28057
|
+
decision: "block",
|
|
28058
|
+
reason: "Kanban governance requires an active task, not only a board. Complete the card details and call kanban start_task before mutation.",
|
|
28059
|
+
boardId: board.id
|
|
28060
|
+
};
|
|
28061
|
+
}
|
|
28062
|
+
if (!task) {
|
|
28063
|
+
return {
|
|
28064
|
+
decision: "block",
|
|
28065
|
+
reason: `Active Kanban task not found: ${identity.taskId}. Call kanban start_task with a valid card.`,
|
|
28066
|
+
boardId: board.id,
|
|
28067
|
+
taskId: identity.taskId
|
|
28068
|
+
};
|
|
28069
|
+
}
|
|
28070
|
+
const readiness = evaluateContractGraphReadiness(board, task.id);
|
|
28071
|
+
if (!readiness.ready) {
|
|
28072
|
+
return {
|
|
28073
|
+
decision: "block",
|
|
28074
|
+
reason: "Active card is not implementation-ready: " + readiness.issues.map((issue) => issue.message).join(" | "),
|
|
28075
|
+
boardId: board.id,
|
|
28076
|
+
taskId: task.id
|
|
28077
|
+
};
|
|
28078
|
+
}
|
|
28079
|
+
if (task.lifecycle?.currentStage !== "running" || task.assignment?.status !== "running") {
|
|
28080
|
+
return {
|
|
28081
|
+
decision: "block",
|
|
28082
|
+
reason: "Active card must be in Running with a live assignment before product mutation. Call kanban start_task after completing the required card details.",
|
|
28083
|
+
boardId: board.id,
|
|
28084
|
+
taskId: task.id
|
|
28085
|
+
};
|
|
28086
|
+
}
|
|
28087
|
+
}
|
|
27252
28088
|
if (identity.leaseId && task?.assignment) {
|
|
27253
28089
|
const caps2 = tool.capabilities ?? [];
|
|
27254
28090
|
const isWrite = caps2.includes("fs.write") || caps2.includes("fs.write.outside-project") || caps2.some((c) => c.startsWith("shell."));
|
|
@@ -27284,6 +28120,19 @@ async function evaluateToolKanbanBoundary(tool, input, ctx) {
|
|
|
27284
28120
|
...task ? { taskId: task.id } : {}
|
|
27285
28121
|
};
|
|
27286
28122
|
}
|
|
28123
|
+
function isGovernedMutation(tool, input) {
|
|
28124
|
+
if (!tool.mutating || GOVERNANCE_CONTROL_TOOLS.has(tool.name)) return false;
|
|
28125
|
+
if (tool.capabilities?.includes("tool.meta")) return false;
|
|
28126
|
+
if (tool.name === "git") {
|
|
28127
|
+
const command = input["command"];
|
|
28128
|
+
if (command === "status" || command === "log" || command === "diff") return false;
|
|
28129
|
+
if (command === "worktree" && input["worktreeAction"] === "list") return false;
|
|
28130
|
+
}
|
|
28131
|
+
const capabilities = tool.capabilities ?? [];
|
|
28132
|
+
return capabilities.some(
|
|
28133
|
+
(capability) => capability === "fs.write" || capability === "fs.write.outside-project" || capability === "package.install" || capability === "tool.mutate.any" || capability.startsWith("shell.") || capability === "net.outbound"
|
|
28134
|
+
);
|
|
28135
|
+
}
|
|
27287
28136
|
function resolveKanbanIdentity(ctx) {
|
|
27288
28137
|
const metaKanban = ctx.meta["kanban"];
|
|
27289
28138
|
const record = metaKanban && typeof metaKanban === "object" ? metaKanban : void 0;
|
|
@@ -27301,10 +28150,10 @@ function resolveKanbanIdentity(ctx) {
|
|
|
27301
28150
|
async function extractCandidatePaths(toolName, input, ctx) {
|
|
27302
28151
|
if (toolName === "patch" && typeof input["patch"] === "string") {
|
|
27303
28152
|
const directoryInput = stringValue(input["directory"]) ?? ctx.workingDir;
|
|
27304
|
-
const directory =
|
|
28153
|
+
const directory = path31.isAbsolute(directoryInput) ? directoryInput : path31.resolve(ctx.workingDir, directoryInput);
|
|
27305
28154
|
const strip = Math.max(1, numericValue(input["strip"]) ?? 1);
|
|
27306
28155
|
const targets = extractPatchTargets(input["patch"], strip).map(
|
|
27307
|
-
(target) => relativeToProject(
|
|
28156
|
+
(target) => relativeToProject(path31.resolve(directory, target), ctx.projectRoot)
|
|
27308
28157
|
);
|
|
27309
28158
|
return Promise.all(targets.map((target) => canonicalizeCandidatePath(target, ctx)));
|
|
27310
28159
|
}
|
|
@@ -27314,7 +28163,7 @@ async function extractCandidatePaths(toolName, input, ctx) {
|
|
|
27314
28163
|
collectPathValues(input, values, pathKeys);
|
|
27315
28164
|
if (toolName === "scaffold" && typeof input["name"] === "string") {
|
|
27316
28165
|
const cwd = stringValue(input["cwd"]) ?? ctx.workingDir;
|
|
27317
|
-
values.push(
|
|
28166
|
+
values.push(path31.join(cwd, input["name"]));
|
|
27318
28167
|
}
|
|
27319
28168
|
const candidates = [
|
|
27320
28169
|
...new Set(values.flatMap(splitPathList).map((value) => resolveInputPath(value, ctx)))
|
|
@@ -27322,21 +28171,21 @@ async function extractCandidatePaths(toolName, input, ctx) {
|
|
|
27322
28171
|
return Promise.all(candidates.map((candidate) => canonicalizeCandidatePath(candidate, ctx)));
|
|
27323
28172
|
}
|
|
27324
28173
|
async function canonicalizeCandidatePath(candidate, ctx) {
|
|
27325
|
-
if (
|
|
27326
|
-
const absolute =
|
|
28174
|
+
if (path31.isAbsolute(candidate)) return candidate;
|
|
28175
|
+
const absolute = path31.resolve(ctx.projectRoot, candidate);
|
|
27327
28176
|
const canonicalRoot = await realpath3(ctx.projectRoot).catch(() => ctx.projectRoot);
|
|
27328
28177
|
let probe = absolute;
|
|
27329
28178
|
const missingSegments = [];
|
|
27330
28179
|
while (true) {
|
|
27331
28180
|
try {
|
|
27332
|
-
const canonical =
|
|
28181
|
+
const canonical = path31.join(await realpath3(probe), ...missingSegments);
|
|
27333
28182
|
return relativeToProject(canonical, canonicalRoot);
|
|
27334
28183
|
} catch (cause) {
|
|
27335
28184
|
const code = cause.code;
|
|
27336
28185
|
if (code !== "ENOENT" && code !== "ENOTDIR") return absolute;
|
|
27337
|
-
const parent =
|
|
28186
|
+
const parent = path31.dirname(probe);
|
|
27338
28187
|
if (parent === probe) return absolute;
|
|
27339
|
-
missingSegments.unshift(
|
|
28188
|
+
missingSegments.unshift(path31.basename(probe));
|
|
27340
28189
|
probe = parent;
|
|
27341
28190
|
}
|
|
27342
28191
|
}
|
|
@@ -27359,12 +28208,12 @@ function splitPathList(value) {
|
|
|
27359
28208
|
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
27360
28209
|
}
|
|
27361
28210
|
function resolveInputPath(value, ctx) {
|
|
27362
|
-
const absolute =
|
|
28211
|
+
const absolute = path31.isAbsolute(value) ? value : path31.resolve(ctx.workingDir, value);
|
|
27363
28212
|
return relativeToProject(absolute, ctx.projectRoot);
|
|
27364
28213
|
}
|
|
27365
28214
|
function relativeToProject(absolute, projectRoot) {
|
|
27366
|
-
const relative8 =
|
|
27367
|
-
return relative8.startsWith("../") ||
|
|
28215
|
+
const relative8 = path31.relative(projectRoot, absolute).replace(/\\/g, "/");
|
|
28216
|
+
return relative8.startsWith("../") || path31.isAbsolute(relative8) ? absolute : relative8 || ".";
|
|
27368
28217
|
}
|
|
27369
28218
|
function extractPatchTargets(patchText, strip) {
|
|
27370
28219
|
const targets = [];
|
|
@@ -27387,9 +28236,9 @@ function numericValue(value) {
|
|
|
27387
28236
|
var GOVERNED_TOOL_EXECUTOR_META_KEY = "toolExecutor.executeGoverned";
|
|
27388
28237
|
|
|
27389
28238
|
// src/execution/tool-executor-support.ts
|
|
27390
|
-
import { createHash as
|
|
27391
|
-
import * as
|
|
27392
|
-
import * as
|
|
28239
|
+
import { createHash as createHash8, randomUUID as randomUUID16 } from "node:crypto";
|
|
28240
|
+
import * as fs9 from "node:fs/promises";
|
|
28241
|
+
import * as path32 from "node:path";
|
|
27393
28242
|
|
|
27394
28243
|
// src/types/tool-markers.ts
|
|
27395
28244
|
var MALFORMED_ARG_MARKERS = ["__raw", "__raw_arguments", "_raw"];
|
|
@@ -27514,12 +28363,12 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
|
|
|
27514
28363
|
return content;
|
|
27515
28364
|
}
|
|
27516
28365
|
try {
|
|
27517
|
-
const dir =
|
|
27518
|
-
await
|
|
28366
|
+
const dir = path32.join(wstackGlobalRoot(), "tool-output");
|
|
28367
|
+
await fs9.mkdir(dir, { recursive: true });
|
|
27519
28368
|
const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
|
|
27520
28369
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
27521
|
-
const filePath =
|
|
27522
|
-
await
|
|
28370
|
+
const filePath = path32.join(dir, `${stamp}-${safeTool}-${randomUUID16()}.log`);
|
|
28371
|
+
await fs9.writeFile(filePath, content, "utf8");
|
|
27523
28372
|
const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
|
|
27524
28373
|
const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
|
|
27525
28374
|
const previewBytes = Math.min(
|
|
@@ -27544,7 +28393,7 @@ function hashPermissionInput(input, scrubber2) {
|
|
|
27544
28393
|
} catch {
|
|
27545
28394
|
serialized = String(input);
|
|
27546
28395
|
}
|
|
27547
|
-
return
|
|
28396
|
+
return createHash8("sha256").update(scrubber2.scrub(serialized), "utf8").digest("hex");
|
|
27548
28397
|
}
|
|
27549
28398
|
function sliceUtf8Prefix(text, maxBytes) {
|
|
27550
28399
|
if (maxBytes <= 0) return "";
|
|
@@ -27577,6 +28426,41 @@ function sliceUtf8Suffix(text, maxBytes) {
|
|
|
27577
28426
|
return text.slice(start);
|
|
27578
28427
|
}
|
|
27579
28428
|
|
|
28429
|
+
// src/execution/tool-error-taxonomy.ts
|
|
28430
|
+
function toolErrorResult(use, err, opts = {}) {
|
|
28431
|
+
const { category, retryable, detail } = classifyToolError(err);
|
|
28432
|
+
const rawMessage = err instanceof Error ? err.message : typeof err === "string" ? err : "Unknown error";
|
|
28433
|
+
const userMessage = opts.scrubber ? opts.scrubber(rawMessage) : rawMessage;
|
|
28434
|
+
const info = {
|
|
28435
|
+
category,
|
|
28436
|
+
retryable,
|
|
28437
|
+
userMessage,
|
|
28438
|
+
...detail !== void 0 ? { detail } : {}
|
|
28439
|
+
};
|
|
28440
|
+
const prefix = formatErrorPrefix(category, retryable);
|
|
28441
|
+
const detailSuffix = detail ? ` [${detail}]` : "";
|
|
28442
|
+
return {
|
|
28443
|
+
type: "tool_result",
|
|
28444
|
+
tool_use_id: use.id,
|
|
28445
|
+
content: `${prefix}: ${userMessage}${detailSuffix}`,
|
|
28446
|
+
is_error: true,
|
|
28447
|
+
// Attach structured info for programmatic consumers (retry logic,
|
|
28448
|
+
// audit log, observability).
|
|
28449
|
+
_toolErrorInfo: info
|
|
28450
|
+
};
|
|
28451
|
+
}
|
|
28452
|
+
function formatErrorPrefix(category, retryable) {
|
|
28453
|
+
const label = CATEGORY_LABELS[category] ?? "Error";
|
|
28454
|
+
return retryable ? `${label} (retryable)` : label;
|
|
28455
|
+
}
|
|
28456
|
+
var CATEGORY_LABELS = {
|
|
28457
|
+
["transient" /* TRANSIENT */]: "Transient error",
|
|
28458
|
+
["not_found" /* NOT_FOUND */]: "Not found",
|
|
28459
|
+
["permission" /* PERMISSION */]: "Permission denied",
|
|
28460
|
+
["validation" /* VALIDATION */]: "Validation error",
|
|
28461
|
+
["fatal" /* FATAL */]: "Error"
|
|
28462
|
+
};
|
|
28463
|
+
|
|
27580
28464
|
// src/execution/tool-executor-logging.ts
|
|
27581
28465
|
function toolLogBase(ctx, use, toolName, durationMs) {
|
|
27582
28466
|
return {
|
|
@@ -27682,41 +28566,6 @@ function blockedByHookResult(use, reason) {
|
|
|
27682
28566
|
};
|
|
27683
28567
|
}
|
|
27684
28568
|
|
|
27685
|
-
// src/execution/tool-error-taxonomy.ts
|
|
27686
|
-
function toolErrorResult(use, err, opts = {}) {
|
|
27687
|
-
const { category, retryable, detail } = classifyToolError(err);
|
|
27688
|
-
const rawMessage = err instanceof Error ? err.message : typeof err === "string" ? err : "Unknown error";
|
|
27689
|
-
const userMessage = opts.scrubber ? opts.scrubber(rawMessage) : rawMessage;
|
|
27690
|
-
const info = {
|
|
27691
|
-
category,
|
|
27692
|
-
retryable,
|
|
27693
|
-
userMessage,
|
|
27694
|
-
...detail !== void 0 ? { detail } : {}
|
|
27695
|
-
};
|
|
27696
|
-
const prefix = formatErrorPrefix(category, retryable);
|
|
27697
|
-
const detailSuffix = detail ? ` [${detail}]` : "";
|
|
27698
|
-
return {
|
|
27699
|
-
type: "tool_result",
|
|
27700
|
-
tool_use_id: use.id,
|
|
27701
|
-
content: `${prefix}: ${userMessage}${detailSuffix}`,
|
|
27702
|
-
is_error: true,
|
|
27703
|
-
// Attach structured info for programmatic consumers (retry logic,
|
|
27704
|
-
// audit log, observability).
|
|
27705
|
-
_toolErrorInfo: info
|
|
27706
|
-
};
|
|
27707
|
-
}
|
|
27708
|
-
function formatErrorPrefix(category, retryable) {
|
|
27709
|
-
const label = CATEGORY_LABELS[category] ?? "Error";
|
|
27710
|
-
return retryable ? `${label} (retryable)` : label;
|
|
27711
|
-
}
|
|
27712
|
-
var CATEGORY_LABELS = {
|
|
27713
|
-
["transient" /* TRANSIENT */]: "Transient error",
|
|
27714
|
-
["not_found" /* NOT_FOUND */]: "Not found",
|
|
27715
|
-
["permission" /* PERMISSION */]: "Permission denied",
|
|
27716
|
-
["validation" /* VALIDATION */]: "Validation error",
|
|
27717
|
-
["fatal" /* FATAL */]: "Error"
|
|
27718
|
-
};
|
|
27719
|
-
|
|
27720
28569
|
// src/execution/tool-executor-stream.ts
|
|
27721
28570
|
async function executeStreamedTool({
|
|
27722
28571
|
tool,
|
|
@@ -28005,7 +28854,9 @@ ${errorDetails}`,
|
|
|
28005
28854
|
return { result, tool, durationMs: Date.now() - start };
|
|
28006
28855
|
}
|
|
28007
28856
|
}
|
|
28008
|
-
const boundary = await evaluateToolKanbanBoundary(tool, use.input, ctx
|
|
28857
|
+
const boundary = await evaluateToolKanbanBoundary(tool, use.input, ctx, {
|
|
28858
|
+
requireGovernance: this.opts.requireKanbanGovernance
|
|
28859
|
+
});
|
|
28009
28860
|
if (boundary.decision === "block") {
|
|
28010
28861
|
const result = {
|
|
28011
28862
|
type: "tool_result",
|
|
@@ -28059,18 +28910,18 @@ ${errorDetails}`,
|
|
|
28059
28910
|
if (this.opts.confirmAwaiter) {
|
|
28060
28911
|
const awaiter = this.opts.confirmAwaiter;
|
|
28061
28912
|
const choice = await new Promise(
|
|
28062
|
-
(
|
|
28913
|
+
(resolve21, reject) => {
|
|
28063
28914
|
const signal = ctx.signal;
|
|
28064
|
-
const onAbort = () =>
|
|
28915
|
+
const onAbort = () => resolve21("abort");
|
|
28065
28916
|
if (signal.aborted) {
|
|
28066
|
-
|
|
28917
|
+
resolve21("abort");
|
|
28067
28918
|
return;
|
|
28068
28919
|
}
|
|
28069
28920
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
28070
28921
|
awaiter(tool, use.input, use.id, suggestedPattern).then(
|
|
28071
28922
|
(c) => {
|
|
28072
28923
|
signal.removeEventListener("abort", onAbort);
|
|
28073
|
-
|
|
28924
|
+
resolve21(c);
|
|
28074
28925
|
},
|
|
28075
28926
|
(e) => {
|
|
28076
28927
|
signal.removeEventListener("abort", onAbort);
|
|
@@ -28116,10 +28967,10 @@ ${errorDetails}`,
|
|
|
28116
28967
|
const inputPath = use.input && typeof use.input === "object" ? use.input.path : void 0;
|
|
28117
28968
|
const caps = tool.capabilities ?? [];
|
|
28118
28969
|
const hasFileCapability = caps.includes("fs.read") || caps.includes("fs.write");
|
|
28119
|
-
const absPath = hasFileCapability && typeof inputPath === "string" ?
|
|
28970
|
+
const absPath = hasFileCapability && typeof inputPath === "string" ? path33.isAbsolute(inputPath) ? inputPath : path33.resolve(ctx.projectRoot, inputPath) : void 0;
|
|
28120
28971
|
let writeTargetExisted;
|
|
28121
28972
|
if (tool.name === "write" && caps.includes("fs.write") && absPath) {
|
|
28122
|
-
writeTargetExisted = await
|
|
28973
|
+
writeTargetExisted = await fs10.stat(absPath).then(
|
|
28123
28974
|
(stat15) => stat15.isFile(),
|
|
28124
28975
|
(error2) => error2.code === "ENOENT" ? false : void 0
|
|
28125
28976
|
);
|
|
@@ -28281,7 +29132,7 @@ ${post.additionalContext}`;
|
|
|
28281
29132
|
const bridge = async (toolName, input) => {
|
|
28282
29133
|
const nestedUse = {
|
|
28283
29134
|
type: "tool_use",
|
|
28284
|
-
id: `nested-${
|
|
29135
|
+
id: `nested-${randomUUID17()}`,
|
|
28285
29136
|
name: toolName,
|
|
28286
29137
|
input
|
|
28287
29138
|
};
|
|
@@ -28404,7 +29255,7 @@ ${preToolContext.text}` : preToolContext.text;
|
|
|
28404
29255
|
]);
|
|
28405
29256
|
let output;
|
|
28406
29257
|
const execute = () => typeof tool.executeStream === "function" ? this.runStreamedTool(tool, input, ctx, combined, toolUseId) : (async () => tool.execute(input, ctx, { signal: combined }))();
|
|
28407
|
-
const telemetryToolCallId = toolUseId ?? `nested-${
|
|
29258
|
+
const telemetryToolCallId = toolUseId ?? `nested-${randomUUID17()}`;
|
|
28408
29259
|
const toolPromise = this.opts.events ? runWithNetworkTelemetry(
|
|
28409
29260
|
{
|
|
28410
29261
|
events: this.opts.events,
|
|
@@ -28430,7 +29281,7 @@ ${preToolContext.text}` : preToolContext.text;
|
|
|
28430
29281
|
toolPromise.catch(() => {
|
|
28431
29282
|
});
|
|
28432
29283
|
try {
|
|
28433
|
-
output = await new Promise((
|
|
29284
|
+
output = await new Promise((resolve21, reject) => {
|
|
28434
29285
|
const onAbort = () => {
|
|
28435
29286
|
setTimeout(() => reject(abortReasonToError(combined.reason)), 0);
|
|
28436
29287
|
};
|
|
@@ -28438,7 +29289,7 @@ ${preToolContext.text}` : preToolContext.text;
|
|
|
28438
29289
|
toolPromise.then(
|
|
28439
29290
|
(v) => {
|
|
28440
29291
|
combined.removeEventListener("abort", onAbort);
|
|
28441
|
-
|
|
29292
|
+
resolve21(v);
|
|
28442
29293
|
},
|
|
28443
29294
|
(e) => {
|
|
28444
29295
|
combined.removeEventListener("abort", onAbort);
|
|
@@ -28991,28 +29842,28 @@ function codexModelMeta(id) {
|
|
|
28991
29842
|
}
|
|
28992
29843
|
|
|
28993
29844
|
// src/models/mode-store.ts
|
|
28994
|
-
import * as
|
|
28995
|
-
import * as
|
|
29845
|
+
import * as fs11 from "node:fs/promises";
|
|
29846
|
+
import * as path35 from "node:path";
|
|
28996
29847
|
|
|
28997
29848
|
// src/types/mode-prompts.ts
|
|
28998
|
-
import { readFileSync as
|
|
28999
|
-
import * as
|
|
29000
|
-
import { fileURLToPath as
|
|
29849
|
+
import { readFileSync as readFileSync9, statSync as statSync4 } from "node:fs";
|
|
29850
|
+
import * as path34 from "node:path";
|
|
29851
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
29001
29852
|
function modePrompt(id) {
|
|
29002
29853
|
for (const dir of modePromptDirCandidates()) {
|
|
29003
29854
|
try {
|
|
29004
|
-
return
|
|
29855
|
+
return readFileSync9(path34.join(dir, `${id}.md`), "utf8").trimEnd();
|
|
29005
29856
|
} catch {
|
|
29006
29857
|
}
|
|
29007
29858
|
}
|
|
29008
29859
|
return "";
|
|
29009
29860
|
}
|
|
29010
29861
|
function modePromptDirCandidates() {
|
|
29011
|
-
const here =
|
|
29862
|
+
const here = path34.dirname(fileURLToPath5(import.meta.url));
|
|
29012
29863
|
const candidates = [
|
|
29013
|
-
|
|
29014
|
-
|
|
29015
|
-
|
|
29864
|
+
path34.resolve(here, "../../instructions/modes"),
|
|
29865
|
+
path34.resolve(here, "../instructions/modes"),
|
|
29866
|
+
path34.resolve(here, "instructions/modes")
|
|
29016
29867
|
];
|
|
29017
29868
|
return candidates.sort((a, b) => Number(!isDirectory3(a)) - Number(!isDirectory3(b)));
|
|
29018
29869
|
}
|
|
@@ -29244,8 +30095,8 @@ var DefaultModeStore = class {
|
|
|
29244
30095
|
}
|
|
29245
30096
|
async loadActiveMode() {
|
|
29246
30097
|
try {
|
|
29247
|
-
const configPath =
|
|
29248
|
-
const content = await
|
|
30098
|
+
const configPath = path35.join(this.configDir, "mode.json");
|
|
30099
|
+
const content = await fs11.readFile(configPath, "utf8");
|
|
29249
30100
|
const data = JSON.parse(content);
|
|
29250
30101
|
this.activeModeId = data.activeMode ?? null;
|
|
29251
30102
|
} catch {
|
|
@@ -29254,8 +30105,8 @@ var DefaultModeStore = class {
|
|
|
29254
30105
|
}
|
|
29255
30106
|
async saveActiveMode() {
|
|
29256
30107
|
try {
|
|
29257
|
-
await
|
|
29258
|
-
const configPath =
|
|
30108
|
+
await fs11.mkdir(this.configDir, { recursive: true });
|
|
30109
|
+
const configPath = path35.join(this.configDir, "mode.json");
|
|
29259
30110
|
await atomicWrite(
|
|
29260
30111
|
configPath,
|
|
29261
30112
|
JSON.stringify({ activeMode: this.activeModeId }, null, 2)
|
|
@@ -29267,14 +30118,14 @@ var DefaultModeStore = class {
|
|
|
29267
30118
|
async function loadProjectModes(modesDir) {
|
|
29268
30119
|
const modes = [];
|
|
29269
30120
|
try {
|
|
29270
|
-
const entries = await
|
|
30121
|
+
const entries = await fs11.readdir(modesDir);
|
|
29271
30122
|
for (const entry of entries) {
|
|
29272
30123
|
if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
|
|
29273
|
-
const filePath =
|
|
29274
|
-
const stat15 = await
|
|
30124
|
+
const filePath = path35.join(modesDir, entry);
|
|
30125
|
+
const stat15 = await fs11.stat(filePath);
|
|
29275
30126
|
if (!stat15.isFile()) continue;
|
|
29276
|
-
const content = await
|
|
29277
|
-
const id =
|
|
30127
|
+
const content = await fs11.readFile(filePath, "utf8");
|
|
30128
|
+
const id = path35.basename(entry, path35.extname(entry));
|
|
29278
30129
|
modes.push({
|
|
29279
30130
|
id,
|
|
29280
30131
|
name: id.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
|
@@ -29290,8 +30141,8 @@ async function loadProjectModes(modesDir) {
|
|
|
29290
30141
|
async function loadUserModes(modesDir) {
|
|
29291
30142
|
const modes = [];
|
|
29292
30143
|
try {
|
|
29293
|
-
const manifestPath =
|
|
29294
|
-
const content = await
|
|
30144
|
+
const manifestPath = path35.join(modesDir, "modes.json");
|
|
30145
|
+
const content = await fs11.readFile(manifestPath, "utf8");
|
|
29295
30146
|
const manifest = JSON.parse(content);
|
|
29296
30147
|
for (const mode of manifest.modes) {
|
|
29297
30148
|
modes.push(mode);
|
|
@@ -29302,8 +30153,8 @@ async function loadUserModes(modesDir) {
|
|
|
29302
30153
|
}
|
|
29303
30154
|
|
|
29304
30155
|
// src/models/models-registry.ts
|
|
29305
|
-
import * as
|
|
29306
|
-
import * as
|
|
30156
|
+
import * as fs12 from "node:fs/promises";
|
|
30157
|
+
import * as path36 from "node:path";
|
|
29307
30158
|
var DEFAULT_URL = "https://models.dev/api.json";
|
|
29308
30159
|
var ENV_URL_KEY = "WRONGSTACK_MODELS_DEV_URL";
|
|
29309
30160
|
var DEFAULT_TTL_SECONDS = 3 * 3600;
|
|
@@ -29380,7 +30231,7 @@ var DefaultModelsRegistry = class {
|
|
|
29380
30231
|
this.overlay = opts.overlay;
|
|
29381
30232
|
this.overlayUrl = opts.overlayUrl;
|
|
29382
30233
|
this.overlayFile = opts.overlayFile;
|
|
29383
|
-
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ?
|
|
30234
|
+
this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path36.join(path36.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
|
|
29384
30235
|
this.logger = opts.logger ?? noOpLogger;
|
|
29385
30236
|
}
|
|
29386
30237
|
async load(opts = {}) {
|
|
@@ -29568,7 +30419,7 @@ var DefaultModelsRegistry = class {
|
|
|
29568
30419
|
async readOverlayFile() {
|
|
29569
30420
|
if (!this.overlayFile) return void 0;
|
|
29570
30421
|
try {
|
|
29571
|
-
const raw = await
|
|
30422
|
+
const raw = await fs12.readFile(this.overlayFile, "utf8");
|
|
29572
30423
|
return JSON.parse(raw);
|
|
29573
30424
|
} catch {
|
|
29574
30425
|
return void 0;
|
|
@@ -29647,7 +30498,7 @@ var DefaultModelsRegistry = class {
|
|
|
29647
30498
|
}
|
|
29648
30499
|
async readCacheAt(file) {
|
|
29649
30500
|
try {
|
|
29650
|
-
const raw = await
|
|
30501
|
+
const raw = await fs12.readFile(file, "utf8");
|
|
29651
30502
|
return JSON.parse(raw);
|
|
29652
30503
|
} catch {
|
|
29653
30504
|
return void 0;
|
|
@@ -29655,7 +30506,7 @@ var DefaultModelsRegistry = class {
|
|
|
29655
30506
|
}
|
|
29656
30507
|
/** Used by `wstack models refresh` to expose where the cache lives. */
|
|
29657
30508
|
cacheLocation() {
|
|
29658
|
-
return
|
|
30509
|
+
return path36.resolve(this.cacheFile);
|
|
29659
30510
|
}
|
|
29660
30511
|
};
|
|
29661
30512
|
var REASONING_EFFORTS = /* @__PURE__ */ new Set([
|
|
@@ -30006,9 +30857,9 @@ var DefaultHealthRegistry = class {
|
|
|
30006
30857
|
}
|
|
30007
30858
|
async runOne(check) {
|
|
30008
30859
|
let timer = null;
|
|
30009
|
-
const timeout = new Promise((
|
|
30860
|
+
const timeout = new Promise((resolve21) => {
|
|
30010
30861
|
timer = setTimeout(
|
|
30011
|
-
() =>
|
|
30862
|
+
() => resolve21({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
|
|
30012
30863
|
this.timeoutMs
|
|
30013
30864
|
);
|
|
30014
30865
|
});
|
|
@@ -30206,7 +31057,7 @@ async function startMetricsServer(opts) {
|
|
|
30206
31057
|
const tls = opts.tls;
|
|
30207
31058
|
const useHttps = !!(tls?.cert && tls?.key);
|
|
30208
31059
|
const host = opts.host ?? "127.0.0.1";
|
|
30209
|
-
const
|
|
31060
|
+
const path45 = opts.path ?? "/metrics";
|
|
30210
31061
|
const healthPath = opts.healthPath ?? "/healthz";
|
|
30211
31062
|
const healthRegistry = opts.healthRegistry;
|
|
30212
31063
|
const token = opts.token;
|
|
@@ -30229,7 +31080,7 @@ async function startMetricsServer(opts) {
|
|
|
30229
31080
|
return;
|
|
30230
31081
|
}
|
|
30231
31082
|
const url = req.url.split("?")[0];
|
|
30232
|
-
if (url ===
|
|
31083
|
+
if (url === path45) {
|
|
30233
31084
|
let body;
|
|
30234
31085
|
try {
|
|
30235
31086
|
body = renderPrometheus(opts.sink.snapshot());
|
|
@@ -30266,23 +31117,23 @@ async function startMetricsServer(opts) {
|
|
|
30266
31117
|
let server;
|
|
30267
31118
|
if (useHttps && tls) {
|
|
30268
31119
|
const { createServer } = await import("node:https");
|
|
30269
|
-
const { readFileSync:
|
|
31120
|
+
const { readFileSync: readFileSync11 } = await import("node:fs");
|
|
30270
31121
|
server = createServer(
|
|
30271
|
-
{ cert:
|
|
31122
|
+
{ cert: readFileSync11(tls.cert), key: readFileSync11(tls.key) },
|
|
30272
31123
|
listener
|
|
30273
31124
|
);
|
|
30274
31125
|
} else {
|
|
30275
31126
|
const { createServer } = await import("node:http");
|
|
30276
31127
|
server = createServer(listener);
|
|
30277
31128
|
}
|
|
30278
|
-
await new Promise((
|
|
31129
|
+
await new Promise((resolve21, reject) => {
|
|
30279
31130
|
const onError = (err) => {
|
|
30280
31131
|
server.off("listening", onListening);
|
|
30281
31132
|
reject(err);
|
|
30282
31133
|
};
|
|
30283
31134
|
const onListening = () => {
|
|
30284
31135
|
server.off("error", onError);
|
|
30285
|
-
|
|
31136
|
+
resolve21();
|
|
30286
31137
|
};
|
|
30287
31138
|
server.once("error", onError);
|
|
30288
31139
|
server.once("listening", onListening);
|
|
@@ -30293,9 +31144,9 @@ async function startMetricsServer(opts) {
|
|
|
30293
31144
|
const protocol = useHttps ? "https" : "http";
|
|
30294
31145
|
return {
|
|
30295
31146
|
port: boundPort,
|
|
30296
|
-
url: `${protocol}://${host}:${boundPort}${
|
|
30297
|
-
close: () => new Promise((
|
|
30298
|
-
server.close((err) => err ? reject(err) :
|
|
31147
|
+
url: `${protocol}://${host}:${boundPort}${path45}`,
|
|
31148
|
+
close: () => new Promise((resolve21, reject) => {
|
|
31149
|
+
server.close((err) => err ? reject(err) : resolve21());
|
|
30299
31150
|
})
|
|
30300
31151
|
};
|
|
30301
31152
|
}
|
|
@@ -30565,8 +31416,8 @@ function startOtlpTraceExporter(opts) {
|
|
|
30565
31416
|
}
|
|
30566
31417
|
|
|
30567
31418
|
// src/security/permission-policy.ts
|
|
30568
|
-
import * as
|
|
30569
|
-
import * as
|
|
31419
|
+
import * as fs13 from "node:fs/promises";
|
|
31420
|
+
import * as path39 from "node:path";
|
|
30570
31421
|
|
|
30571
31422
|
// src/security/permission-policy-schema.ts
|
|
30572
31423
|
var TRUST_POLICY_LIMITS = Object.freeze({
|
|
@@ -30611,19 +31462,19 @@ var UNSAFE_PROPERTY_NAMES = /* @__PURE__ */ new Set(["__proto__", "prototype", "
|
|
|
30611
31462
|
function isRecord5(value) {
|
|
30612
31463
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30613
31464
|
}
|
|
30614
|
-
function error(diagnostics, code,
|
|
30615
|
-
diagnostics.push({ severity: "error", code, path:
|
|
31465
|
+
function error(diagnostics, code, path45, message) {
|
|
31466
|
+
diagnostics.push({ severity: "error", code, path: path45, message });
|
|
30616
31467
|
}
|
|
30617
|
-
function validatePatterns(value,
|
|
31468
|
+
function validatePatterns(value, path45, diagnostics) {
|
|
30618
31469
|
if (!Array.isArray(value)) {
|
|
30619
|
-
error(diagnostics, "invalid_pattern_list",
|
|
31470
|
+
error(diagnostics, "invalid_pattern_list", path45, "must be an array of strings");
|
|
30620
31471
|
return void 0;
|
|
30621
31472
|
}
|
|
30622
31473
|
if (value.length > TRUST_POLICY_LIMITS.maxPatternsPerRule) {
|
|
30623
31474
|
error(
|
|
30624
31475
|
diagnostics,
|
|
30625
31476
|
"too_many_patterns",
|
|
30626
|
-
|
|
31477
|
+
path45,
|
|
30627
31478
|
`must contain at most ${TRUST_POLICY_LIMITS.maxPatternsPerRule} patterns`
|
|
30628
31479
|
);
|
|
30629
31480
|
return void 0;
|
|
@@ -30631,7 +31482,7 @@ function validatePatterns(value, path42, diagnostics) {
|
|
|
30631
31482
|
const patterns = [];
|
|
30632
31483
|
const seen = /* @__PURE__ */ new Set();
|
|
30633
31484
|
for (const [index, pattern] of value.entries()) {
|
|
30634
|
-
const itemPath = `${
|
|
31485
|
+
const itemPath = `${path45}[${index}]`;
|
|
30635
31486
|
if (typeof pattern !== "string" || pattern.length === 0 || pattern.length > TRUST_POLICY_LIMITS.maxPatternChars) {
|
|
30636
31487
|
error(
|
|
30637
31488
|
diagnostics,
|
|
@@ -30735,7 +31586,7 @@ function validateTrustPolicy(value) {
|
|
|
30735
31586
|
}
|
|
30736
31587
|
|
|
30737
31588
|
// src/security/yolo-risk.ts
|
|
30738
|
-
import * as
|
|
31589
|
+
import * as path37 from "node:path";
|
|
30739
31590
|
var CATASTROPHIC_PATTERNS = [
|
|
30740
31591
|
/\b(?:mkfs(?:\.[a-z0-9]+)?|mke2fs|newfs)\b/i,
|
|
30741
31592
|
// make a filesystem — wipes a partition
|
|
@@ -30795,9 +31646,9 @@ function getInputString(input, key) {
|
|
|
30795
31646
|
function pathLooksInsideProject(rawPath, projectRoot) {
|
|
30796
31647
|
if (!projectRoot) return false;
|
|
30797
31648
|
if (rawPath === "~" || rawPath.startsWith("~/") || rawPath.startsWith("~\\")) return false;
|
|
30798
|
-
const resolved =
|
|
30799
|
-
const relative8 =
|
|
30800
|
-
return !!relative8 && !relative8.startsWith("..") && !
|
|
31649
|
+
const resolved = path37.resolve(projectRoot, rawPath);
|
|
31650
|
+
const relative8 = path37.relative(projectRoot, resolved);
|
|
31651
|
+
return !!relative8 && !relative8.startsWith("..") && !path37.isAbsolute(relative8);
|
|
30801
31652
|
}
|
|
30802
31653
|
function tokenizeShell(command) {
|
|
30803
31654
|
return command.match(/"[^"]*"|'[^']*'|\S+/g)?.map((token) => token.replace(/^['"]|['"]$/g, "")) ?? [];
|
|
@@ -31047,7 +31898,7 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
31047
31898
|
|
|
31048
31899
|
// src/security/permission-helpers.ts
|
|
31049
31900
|
import { realpathSync } from "node:fs";
|
|
31050
|
-
import * as
|
|
31901
|
+
import * as path38 from "node:path";
|
|
31051
31902
|
function matchesTrust(patterns, subject) {
|
|
31052
31903
|
return patterns.includes(subject) || matchAny(patterns, subject);
|
|
31053
31904
|
}
|
|
@@ -31131,24 +31982,24 @@ function realpathOfNearestExisting(p) {
|
|
|
31131
31982
|
const tail = [];
|
|
31132
31983
|
for (; ; ) {
|
|
31133
31984
|
try {
|
|
31134
|
-
return tail.length === 0 ? realpathSync(probe) :
|
|
31985
|
+
return tail.length === 0 ? realpathSync(probe) : path38.join(realpathSync(probe), ...tail);
|
|
31135
31986
|
} catch {
|
|
31136
|
-
const parent =
|
|
31987
|
+
const parent = path38.dirname(probe);
|
|
31137
31988
|
if (parent === probe) return p;
|
|
31138
|
-
tail.unshift(
|
|
31989
|
+
tail.unshift(path38.basename(probe));
|
|
31139
31990
|
probe = parent;
|
|
31140
31991
|
}
|
|
31141
31992
|
}
|
|
31142
31993
|
}
|
|
31143
31994
|
var agentStateRootRealCache = /* @__PURE__ */ new Map();
|
|
31144
31995
|
function isInsideAgentStateRoot(absPath) {
|
|
31145
|
-
const lexicalRoot = normalizeForCompare(
|
|
31996
|
+
const lexicalRoot = normalizeForCompare(path38.resolve(wstackGlobalRoot()));
|
|
31146
31997
|
if (!lexicalRoot) return false;
|
|
31147
31998
|
const target = normalizeForCompare(absPath);
|
|
31148
31999
|
if (target === lexicalRoot || target.startsWith(`${lexicalRoot}/`)) return true;
|
|
31149
32000
|
let realRoot = agentStateRootRealCache.get(lexicalRoot);
|
|
31150
32001
|
if (realRoot === void 0) {
|
|
31151
|
-
realRoot = normalizeForCompare(realpathOfNearestExisting(
|
|
32002
|
+
realRoot = normalizeForCompare(realpathOfNearestExisting(path38.resolve(wstackGlobalRoot())));
|
|
31152
32003
|
agentStateRootRealCache.set(lexicalRoot, realRoot);
|
|
31153
32004
|
}
|
|
31154
32005
|
if (!realRoot || realRoot === lexicalRoot) return false;
|
|
@@ -31157,7 +32008,7 @@ function isInsideAgentStateRoot(absPath) {
|
|
|
31157
32008
|
}
|
|
31158
32009
|
function isProtectedAgentStatePath(absPath) {
|
|
31159
32010
|
if (!isInsideAgentStateRoot(absPath)) return false;
|
|
31160
|
-
return AGENT_STATE_SENSITIVE_BASENAMES.test(
|
|
32011
|
+
return AGENT_STATE_SENSITIVE_BASENAMES.test(path38.basename(normalizeForCompare(absPath)));
|
|
31161
32012
|
}
|
|
31162
32013
|
function pathLooksSensitive(rawPath) {
|
|
31163
32014
|
const normalized = stripShellQuotes(rawPath).replace(/\\/g, "/");
|
|
@@ -31318,7 +32169,7 @@ var DefaultPermissionPolicy = class {
|
|
|
31318
32169
|
if (!hasCapability(tool, ToolCapabilities.FS_WRITE)) return false;
|
|
31319
32170
|
for (const targetPath of fsWriteTargetPaths(input)) {
|
|
31320
32171
|
const base = ctx.workingDir ?? ctx.cwd;
|
|
31321
|
-
const resolved = base ?
|
|
32172
|
+
const resolved = base ? path39.resolve(base, targetPath) : path39.resolve(targetPath);
|
|
31322
32173
|
if (isInsideAgentStateRoot(resolved)) return true;
|
|
31323
32174
|
}
|
|
31324
32175
|
return false;
|
|
@@ -31363,7 +32214,7 @@ var DefaultPermissionPolicy = class {
|
|
|
31363
32214
|
this.policyDiagnostics = [];
|
|
31364
32215
|
this.policyInvalid = false;
|
|
31365
32216
|
try {
|
|
31366
|
-
const raw = await
|
|
32217
|
+
const raw = await fs13.readFile(this.trustFile, "utf8");
|
|
31367
32218
|
const parsed = safeParse(raw);
|
|
31368
32219
|
if (!parsed.ok) {
|
|
31369
32220
|
this.policy = {};
|
|
@@ -31985,9 +32836,9 @@ var DefaultPermissionPolicy = class {
|
|
|
31985
32836
|
|
|
31986
32837
|
// src/security/secret-vault.ts
|
|
31987
32838
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes3, scryptSync } from "node:crypto";
|
|
31988
|
-
import * as
|
|
32839
|
+
import * as fs14 from "node:fs";
|
|
31989
32840
|
import * as fsp22 from "node:fs/promises";
|
|
31990
|
-
import * as
|
|
32841
|
+
import * as path40 from "node:path";
|
|
31991
32842
|
|
|
31992
32843
|
// src/types/secret-vault.ts
|
|
31993
32844
|
var ENCRYPTED_PREFIX_PATTERN = /^enc:v(\d+):/;
|
|
@@ -32127,7 +32978,7 @@ function checkKeyFilePermissions(keyFile, opts) {
|
|
|
32127
32978
|
if (process.platform === "win32") return;
|
|
32128
32979
|
const warn = opts?.warn ?? ((msg) => console.warn(msg));
|
|
32129
32980
|
try {
|
|
32130
|
-
const stat15 =
|
|
32981
|
+
const stat15 = fs14.statSync(keyFile);
|
|
32131
32982
|
const actualMode = stat15.mode & 511;
|
|
32132
32983
|
if (actualMode !== KEY_FILE_MODE) {
|
|
32133
32984
|
void restrictFilePermissions(keyFile, {
|
|
@@ -32143,21 +32994,21 @@ function checkKeyFilePermissions(keyFile, opts) {
|
|
|
32143
32994
|
}
|
|
32144
32995
|
function writeKeyFileAtomicSync(keyFile, content) {
|
|
32145
32996
|
const tmp = `${keyFile}.${randomBytes3(4).toString("hex")}.tmp`;
|
|
32146
|
-
const fd =
|
|
32997
|
+
const fd = fs14.openSync(tmp, "w", 384);
|
|
32147
32998
|
try {
|
|
32148
|
-
|
|
32999
|
+
fs14.writeFileSync(fd, content);
|
|
32149
33000
|
try {
|
|
32150
|
-
|
|
33001
|
+
fs14.fsyncSync(fd);
|
|
32151
33002
|
} catch {
|
|
32152
33003
|
}
|
|
32153
33004
|
} finally {
|
|
32154
|
-
|
|
33005
|
+
fs14.closeSync(fd);
|
|
32155
33006
|
}
|
|
32156
33007
|
try {
|
|
32157
|
-
|
|
33008
|
+
fs14.renameSync(tmp, keyFile);
|
|
32158
33009
|
} catch (err) {
|
|
32159
33010
|
try {
|
|
32160
|
-
|
|
33011
|
+
fs14.unlinkSync(tmp);
|
|
32161
33012
|
} catch {
|
|
32162
33013
|
}
|
|
32163
33014
|
throw err;
|
|
@@ -32280,7 +33131,7 @@ var DefaultSecretVault = class {
|
|
|
32280
33131
|
const oldVersion = this._keyVersion;
|
|
32281
33132
|
const newKey = randomBytes3(KEY_BYTES);
|
|
32282
33133
|
const newVersion = oldVersion + 1;
|
|
32283
|
-
|
|
33134
|
+
fs14.mkdirSync(path40.dirname(this.keyFile), { recursive: true });
|
|
32284
33135
|
const passphrase = getVaultPassphrase();
|
|
32285
33136
|
if (passphrase) {
|
|
32286
33137
|
writeKeyFileAtomicSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase));
|
|
@@ -32320,7 +33171,7 @@ var DefaultSecretVault = class {
|
|
|
32320
33171
|
loadOrCreateKey() {
|
|
32321
33172
|
if (this.key) return this.key;
|
|
32322
33173
|
try {
|
|
32323
|
-
const buf =
|
|
33174
|
+
const buf = fs14.readFileSync(this.keyFile);
|
|
32324
33175
|
if (isWrappedKeyFile(buf)) {
|
|
32325
33176
|
const { key: key2, version } = unwrapDataKey(buf, this.keyFile);
|
|
32326
33177
|
this.key = key2;
|
|
@@ -32367,17 +33218,17 @@ var DefaultSecretVault = class {
|
|
|
32367
33218
|
} catch (err) {
|
|
32368
33219
|
if (err.code !== "ENOENT") throw err;
|
|
32369
33220
|
}
|
|
32370
|
-
|
|
33221
|
+
fs14.mkdirSync(path40.dirname(this.keyFile), { recursive: true });
|
|
32371
33222
|
const key = randomBytes3(KEY_BYTES);
|
|
32372
33223
|
const passphrase = getVaultPassphrase();
|
|
32373
33224
|
const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
|
|
32374
33225
|
try {
|
|
32375
|
-
|
|
33226
|
+
fs14.writeFileSync(this.keyFile, initialBytes, { mode: 384, flag: "wx" });
|
|
32376
33227
|
this.scheduleKeyHardening();
|
|
32377
33228
|
} catch (err) {
|
|
32378
33229
|
if (err.code !== "EEXIST") throw err;
|
|
32379
33230
|
this.scheduleKeyHardening();
|
|
32380
|
-
const buf =
|
|
33231
|
+
const buf = fs14.readFileSync(this.keyFile);
|
|
32381
33232
|
if (isWrappedKeyFile(buf)) {
|
|
32382
33233
|
const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
|
|
32383
33234
|
this.key = winnerKey;
|
|
@@ -32427,7 +33278,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
|
32427
33278
|
}
|
|
32428
33279
|
const merged = deepMerge(current, patch ?? {});
|
|
32429
33280
|
const encrypted = encryptConfigSecrets(merged, vault);
|
|
32430
|
-
await fsp22.mkdir(
|
|
33281
|
+
await fsp22.mkdir(path40.dirname(configPath), { recursive: true });
|
|
32431
33282
|
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
32432
33283
|
await restrictFilePermissions2(configPath);
|
|
32433
33284
|
await vault.flushHardening?.();
|
|
@@ -32482,7 +33333,7 @@ function walkCount(node, vault, counter) {
|
|
|
32482
33333
|
// src/storage/attachment-store.ts
|
|
32483
33334
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
32484
33335
|
import * as fsp23 from "node:fs/promises";
|
|
32485
|
-
import * as
|
|
33336
|
+
import * as path41 from "node:path";
|
|
32486
33337
|
var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
|
|
32487
33338
|
var PLACEHOLDER_RE2 = /\[(pasted|image|file) #(\d+)[^\]]*\]|\[file:([^\]]+)\]/g;
|
|
32488
33339
|
var DefaultAttachmentStore = class {
|
|
@@ -32503,7 +33354,7 @@ var DefaultAttachmentStore = class {
|
|
|
32503
33354
|
let data = input.data;
|
|
32504
33355
|
if (this.spoolDir && bytes >= this.spoolThreshold) {
|
|
32505
33356
|
await fsp23.mkdir(this.spoolDir, { recursive: true });
|
|
32506
|
-
spooledPath =
|
|
33357
|
+
spooledPath = path41.join(this.spoolDir, `${id}.bin`);
|
|
32507
33358
|
await atomicWrite(spooledPath, input.data, {
|
|
32508
33359
|
encoding: input.kind === "image" ? "base64" : "utf8"
|
|
32509
33360
|
});
|
|
@@ -32622,7 +33473,7 @@ function mergeAdjacentText(blocks) {
|
|
|
32622
33473
|
}
|
|
32623
33474
|
|
|
32624
33475
|
// src/storage/config-loader.ts
|
|
32625
|
-
import * as
|
|
33476
|
+
import * as fs15 from "node:fs/promises";
|
|
32626
33477
|
|
|
32627
33478
|
// src/storage/config-loader/bootstrap.ts
|
|
32628
33479
|
var BOOTSTRAP_KEYS = /* @__PURE__ */ new Set(["version", "activeProfile"]);
|
|
@@ -33041,8 +33892,8 @@ var IN_PROJECT_DENIED_PATHS = [
|
|
|
33041
33892
|
reason: "The other half of the filesystem confinement switch."
|
|
33042
33893
|
}
|
|
33043
33894
|
];
|
|
33044
|
-
function deleteNestedPath(target,
|
|
33045
|
-
const segments =
|
|
33895
|
+
function deleteNestedPath(target, path45) {
|
|
33896
|
+
const segments = path45.split(".");
|
|
33046
33897
|
const last = segments[segments.length - 1];
|
|
33047
33898
|
if (last === void 0) return false;
|
|
33048
33899
|
let cursor = target;
|
|
@@ -33099,16 +33950,16 @@ function assertInProjectAllowListComplete() {
|
|
|
33099
33950
|
);
|
|
33100
33951
|
}
|
|
33101
33952
|
const orphanedPaths = IN_PROJECT_DENIED_PATHS.filter(
|
|
33102
|
-
({ path:
|
|
33103
|
-
).map(({ path:
|
|
33953
|
+
({ path: path45 }) => !IN_PROJECT_ALLOWED_KEYS.has(path45.split(".")[0] ?? "")
|
|
33954
|
+
).map(({ path: path45 }) => path45);
|
|
33104
33955
|
if (orphanedPaths.length > 0) {
|
|
33105
33956
|
problems.push(
|
|
33106
33957
|
`IN_PROJECT_DENIED_PATHS entr(ies) whose top-level parent is not allowed: ` + orphanedPaths.join(", ") + ". The parent is already stripped wholesale, so the nested denial is dead \u2014 remove it."
|
|
33107
33958
|
);
|
|
33108
33959
|
}
|
|
33109
33960
|
const malformedPaths = IN_PROJECT_DENIED_PATHS.filter(
|
|
33110
|
-
({ path:
|
|
33111
|
-
).map(({ path:
|
|
33961
|
+
({ path: path45 }) => path45.split(".").length < 2 || path45.split(".").some((s) => s.length === 0)
|
|
33962
|
+
).map(({ path: path45 }) => path45);
|
|
33112
33963
|
if (malformedPaths.length > 0) {
|
|
33113
33964
|
problems.push(
|
|
33114
33965
|
`IN_PROJECT_DENIED_PATHS entr(ies) are not dotted nested paths: ` + malformedPaths.join(", ") + ". Top-level keys belong in KNOWN_DENIED_IN_PROJECT instead."
|
|
@@ -33136,8 +33987,8 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
|
|
|
33136
33987
|
}
|
|
33137
33988
|
stripped.push(k);
|
|
33138
33989
|
}
|
|
33139
|
-
for (const { path:
|
|
33140
|
-
if (deleteNestedPath(out,
|
|
33990
|
+
for (const { path: path45 } of IN_PROJECT_DENIED_PATHS) {
|
|
33991
|
+
if (deleteNestedPath(out, path45)) stripped.push(path45);
|
|
33141
33992
|
}
|
|
33142
33993
|
if (stripped.length > 0) {
|
|
33143
33994
|
warn(
|
|
@@ -33326,10 +34177,10 @@ function removeLegacySageEngine(config) {
|
|
|
33326
34177
|
}
|
|
33327
34178
|
|
|
33328
34179
|
// src/storage/config-loader/path-identity.ts
|
|
33329
|
-
import * as
|
|
34180
|
+
import * as path42 from "node:path";
|
|
33330
34181
|
function samePath(a, b) {
|
|
33331
|
-
let ra =
|
|
33332
|
-
let rb =
|
|
34182
|
+
let ra = path42.resolve(a);
|
|
34183
|
+
let rb = path42.resolve(b);
|
|
33333
34184
|
if (process.platform === "win32" || process.platform === "darwin") {
|
|
33334
34185
|
ra = ra.toLowerCase();
|
|
33335
34186
|
rb = rb.toLowerCase();
|
|
@@ -33489,7 +34340,7 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
|
|
|
33489
34340
|
let parsed;
|
|
33490
34341
|
let fileExisted = true;
|
|
33491
34342
|
try {
|
|
33492
|
-
const raw = await
|
|
34343
|
+
const raw = await fs15.readFile(fp, "utf8");
|
|
33493
34344
|
const result = safeParse(raw);
|
|
33494
34345
|
if (!result.ok || !isPlainRecord(result.value)) {
|
|
33495
34346
|
return;
|
|
@@ -33585,7 +34436,7 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
|
|
|
33585
34436
|
let parsed;
|
|
33586
34437
|
let existed = true;
|
|
33587
34438
|
try {
|
|
33588
|
-
const raw = await
|
|
34439
|
+
const raw = await fs15.readFile(profileFp, "utf8");
|
|
33589
34440
|
const result = safeParse(raw);
|
|
33590
34441
|
if (!result.ok || !isPlainRecord(result.value)) {
|
|
33591
34442
|
this.logWarn("Profile config parse failed \u2014 falling back to defaults", {
|
|
@@ -33684,7 +34535,7 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
|
|
|
33684
34535
|
const fp = this.paths.syncConfig;
|
|
33685
34536
|
const t0 = Date.now();
|
|
33686
34537
|
try {
|
|
33687
|
-
const raw = await
|
|
34538
|
+
const raw = await fs15.readFile(fp, "utf8");
|
|
33688
34539
|
const parsed = safeParse(raw);
|
|
33689
34540
|
if (!parsed.ok || !parsed.value) {
|
|
33690
34541
|
this.events?.emit("storage.read", {
|
|
@@ -33746,7 +34597,7 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
|
|
|
33746
34597
|
const t0 = Date.now();
|
|
33747
34598
|
let mtimeMs = null;
|
|
33748
34599
|
try {
|
|
33749
|
-
const stat15 = await
|
|
34600
|
+
const stat15 = await fs15.stat(file);
|
|
33750
34601
|
mtimeMs = stat15.mtimeMs;
|
|
33751
34602
|
const cached = this.jsonCache.get(file);
|
|
33752
34603
|
if (cached && cached.mtimeMs === mtimeMs) {
|
|
@@ -33776,7 +34627,7 @@ var DefaultConfigLoader = class _DefaultConfigLoader {
|
|
|
33776
34627
|
}
|
|
33777
34628
|
let raw;
|
|
33778
34629
|
try {
|
|
33779
|
-
raw = await
|
|
34630
|
+
raw = await fs15.readFile(file, "utf8");
|
|
33780
34631
|
} catch (err) {
|
|
33781
34632
|
if (err.code !== "ENOENT") {
|
|
33782
34633
|
this.events?.emit("storage.read", {
|
|
@@ -34011,8 +34862,8 @@ function deepFreeze(obj) {
|
|
|
34011
34862
|
}
|
|
34012
34863
|
|
|
34013
34864
|
// src/storage/plan-store.ts
|
|
34865
|
+
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
34014
34866
|
import * as fsp24 from "node:fs/promises";
|
|
34015
|
-
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
34016
34867
|
async function loadPlan(filePath, events) {
|
|
34017
34868
|
const t0 = Date.now();
|
|
34018
34869
|
let raw;
|
|
@@ -34110,7 +34961,7 @@ function emptyPlan(sessionId, title) {
|
|
|
34110
34961
|
function addPlanItem(plan, title, details) {
|
|
34111
34962
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
34112
34963
|
const item = {
|
|
34113
|
-
id: `plan_${Date.now()}_${
|
|
34964
|
+
id: `plan_${Date.now()}_${randomUUID18().slice(0, 6)}`,
|
|
34114
34965
|
title,
|
|
34115
34966
|
details,
|
|
34116
34967
|
status: "open",
|
|
@@ -34182,7 +35033,7 @@ function deriveTodosFromPlanItem(plan, idOrIndex, subtasks) {
|
|
|
34182
35033
|
if (subtasks && subtasks.length > 0) {
|
|
34183
35034
|
for (const st of subtasks) {
|
|
34184
35035
|
todos.push({
|
|
34185
|
-
id: `todo_${Date.now()}_${
|
|
35036
|
+
id: `todo_${Date.now()}_${randomUUID18().slice(0, 6)}`,
|
|
34186
35037
|
content: st,
|
|
34187
35038
|
status: "pending",
|
|
34188
35039
|
promotedFromPlan: item.id
|
|
@@ -34310,7 +35161,7 @@ ${cat}:`);
|
|
|
34310
35161
|
|
|
34311
35162
|
// src/storage/queue-store.ts
|
|
34312
35163
|
import * as fsp25 from "node:fs/promises";
|
|
34313
|
-
import * as
|
|
35164
|
+
import * as path43 from "node:path";
|
|
34314
35165
|
var QUEUE_MAX_ITEMS = 100;
|
|
34315
35166
|
var QUEUE_MAX_BYTES = 16 * 1024 * 1024;
|
|
34316
35167
|
var QUEUE_MAX_ITEM_BYTES = 8 * 1024 * 1024;
|
|
@@ -34343,7 +35194,7 @@ var QueueStore = class {
|
|
|
34343
35194
|
traceId;
|
|
34344
35195
|
logger;
|
|
34345
35196
|
constructor(opts) {
|
|
34346
|
-
this.file =
|
|
35197
|
+
this.file = path43.join(opts.dir, "queue.json");
|
|
34347
35198
|
this.events = opts.events;
|
|
34348
35199
|
this.traceId = opts.traceId;
|
|
34349
35200
|
this.logger = opts.logger;
|
|
@@ -34558,8 +35409,8 @@ function isPersistedQueueItem(v) {
|
|
|
34558
35409
|
|
|
34559
35410
|
// src/storage/recovery-lock.ts
|
|
34560
35411
|
import * as fsp26 from "node:fs/promises";
|
|
34561
|
-
import * as
|
|
34562
|
-
import * as
|
|
35412
|
+
import * as os3 from "node:os";
|
|
35413
|
+
import * as path44 from "node:path";
|
|
34563
35414
|
var LOCK_FILE = "active.json";
|
|
34564
35415
|
var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
34565
35416
|
var RecoveryLock = class {
|
|
@@ -34570,9 +35421,9 @@ var RecoveryLock = class {
|
|
|
34570
35421
|
sessionStore;
|
|
34571
35422
|
probe;
|
|
34572
35423
|
constructor(opts) {
|
|
34573
|
-
this.file =
|
|
35424
|
+
this.file = path44.join(opts.dir, LOCK_FILE);
|
|
34574
35425
|
this.pid = opts.pid ?? process.pid;
|
|
34575
|
-
this.hostname = opts.hostname ??
|
|
35426
|
+
this.hostname = opts.hostname ?? os3.hostname();
|
|
34576
35427
|
this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
34577
35428
|
this.sessionStore = opts.sessionStore;
|
|
34578
35429
|
this.probe = opts.isPidAlive ?? defaultIsPidAlive;
|
|
@@ -34644,7 +35495,7 @@ var RecoveryLock = class {
|
|
|
34644
35495
|
* null return before calling this.
|
|
34645
35496
|
*/
|
|
34646
35497
|
async write(sessionId) {
|
|
34647
|
-
await ensureDir(
|
|
35498
|
+
await ensureDir(path44.dirname(this.file));
|
|
34648
35499
|
const lock = {
|
|
34649
35500
|
v: 1,
|
|
34650
35501
|
sessionId,
|
|
@@ -34812,6 +35663,7 @@ var CORE_RECONSTRUCT_EVENTS = /* @__PURE__ */ new Set([
|
|
|
34812
35663
|
"message_appended",
|
|
34813
35664
|
"message_updated",
|
|
34814
35665
|
"messages_replaced",
|
|
35666
|
+
"messages_dropped",
|
|
34815
35667
|
"context_snapshot",
|
|
34816
35668
|
"checkpoint",
|
|
34817
35669
|
"file_snapshot",
|