@wrongstack/core 0.5.7 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/{agent-bridge-BKNiE1VH.d.ts → agent-bridge-DaCvA_uK.d.ts} +1 -1
  2. package/dist/coordination/index.d.ts +5 -5
  3. package/dist/coordination/index.js +218 -78
  4. package/dist/coordination/index.js.map +1 -1
  5. package/dist/defaults/index.d.ts +10 -9
  6. package/dist/defaults/index.js +1127 -180
  7. package/dist/defaults/index.js.map +1 -1
  8. package/dist/{events-DPQKFX7W.d.ts → events-CzkeaVVl.d.ts} +8 -2
  9. package/dist/execution/index.d.ts +281 -5
  10. package/dist/execution/index.js +785 -3
  11. package/dist/execution/index.js.map +1 -1
  12. package/dist/extension/index.d.ts +2 -2
  13. package/dist/extension/index.js +2 -1
  14. package/dist/extension/index.js.map +1 -1
  15. package/dist/goal-store-DVCfj7Ff.d.ts +95 -0
  16. package/dist/{index-j2WyAyML.d.ts → index-BkKLQjea.d.ts} +34 -2
  17. package/dist/{index-Bf9Bpkdc.d.ts → index-i9rPR53g.d.ts} +11 -4
  18. package/dist/index.d.ts +51 -16
  19. package/dist/index.js +1506 -247
  20. package/dist/index.js.map +1 -1
  21. package/dist/infrastructure/index.d.ts +2 -2
  22. package/dist/kernel/index.d.ts +2 -2
  23. package/dist/kernel/index.js.map +1 -1
  24. package/dist/models/index.js +2 -3
  25. package/dist/models/index.js.map +1 -1
  26. package/dist/{multi-agent-Cpp7FXUl.d.ts → multi-agent-MfI6dmv-.d.ts} +30 -5
  27. package/dist/observability/index.d.ts +1 -1
  28. package/dist/{path-resolver--g_hKJ7V.d.ts → path-resolver-CWINz5XR.d.ts} +1 -1
  29. package/dist/{plan-templates-Bveo2W8n.d.ts → plan-templates-Bne1pB4d.d.ts} +6 -5
  30. package/dist/{provider-runner-hl4Il3xS.d.ts → provider-runner-CZYIzeBp.d.ts} +1 -1
  31. package/dist/sdd/index.d.ts +5 -3
  32. package/dist/sdd/index.js +102 -68
  33. package/dist/sdd/index.js.map +1 -1
  34. package/dist/security/index.js +82 -82
  35. package/dist/security/index.js.map +1 -1
  36. package/dist/skills/index.js +102 -38
  37. package/dist/skills/index.js.map +1 -1
  38. package/dist/storage/index.d.ts +3 -2
  39. package/dist/storage/index.js +134 -22
  40. package/dist/storage/index.js.map +1 -1
  41. package/dist/{tool-executor-DbAFkHdP.d.ts → tool-executor-40Q6shR-.d.ts} +1 -1
  42. package/dist/types/index.d.ts +7 -7
  43. package/dist/types/index.js +116 -103
  44. package/dist/types/index.js.map +1 -1
  45. package/dist/utils/index.js +5 -0
  46. package/dist/utils/index.js.map +1 -1
  47. package/package.json +5 -1
@@ -1,5 +1,8 @@
1
+ import { execFile } from 'child_process';
2
+ import { promisify } from 'util';
1
3
  import * as fs from 'fs/promises';
2
4
  import * as path from 'path';
5
+ import { randomBytes } from 'crypto';
3
6
 
4
7
  // src/utils/token-estimate.ts
5
8
  var RoughTokenEstimate = (text) => Math.max(1, Math.ceil(text.length / 4));
@@ -1242,13 +1245,17 @@ var ToolExecutor = class {
1242
1245
  const ctrl = new AbortController();
1243
1246
  const timer = setTimeout(() => ctrl.abort(new Error("tool timeout")), timeoutMs);
1244
1247
  const combined = AbortSignal.any([parentSignal, ctrl.signal]);
1248
+ let cleanupCalled = false;
1249
+ let caught = false;
1245
1250
  try {
1246
1251
  if (typeof tool.executeStream === "function") {
1247
1252
  return await this.runStreamedTool(tool, input, ctx, combined, toolUseId);
1248
1253
  }
1249
1254
  return await tool.execute(input, ctx, { signal: combined });
1250
1255
  } catch (err) {
1256
+ caught = true;
1251
1257
  if (combined.aborted && typeof tool.cleanup === "function") {
1258
+ cleanupCalled = true;
1252
1259
  try {
1253
1260
  await tool.cleanup(input, ctx);
1254
1261
  } catch {
@@ -1257,6 +1264,16 @@ var ToolExecutor = class {
1257
1264
  throw err;
1258
1265
  } finally {
1259
1266
  clearTimeout(timer);
1267
+ if (combined.aborted && !caught) {
1268
+ if (!cleanupCalled && typeof tool.cleanup === "function") {
1269
+ try {
1270
+ await tool.cleanup(input, ctx);
1271
+ } catch {
1272
+ }
1273
+ }
1274
+ const reason = combined.reason instanceof Error ? combined.reason : new Error(typeof combined.reason === "string" ? combined.reason : "aborted");
1275
+ throw reason;
1276
+ }
1260
1277
  }
1261
1278
  }
1262
1279
  async runStreamedTool(tool, input, ctx, signal, toolUseId) {
@@ -1314,7 +1331,8 @@ var ToolExecutor = class {
1314
1331
  if (subjectKey) {
1315
1332
  const v = obj[subjectKey];
1316
1333
  if (typeof v === "string") {
1317
- return subjectKey === "path" || subjectKey === "file" || subjectKey === "files" ? normalizePath(v) : escapeGlob(v);
1334
+ const isPathKey = subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
1335
+ return isPathKey ? normalizePath(v) : escapeGlob(v);
1318
1336
  }
1319
1337
  }
1320
1338
  if (toolName === "bash" && typeof obj.command === "string") {
@@ -1533,6 +1551,770 @@ var AutonomousRunner = class {
1533
1551
  this.stopped = true;
1534
1552
  }
1535
1553
  };
1554
+ async function atomicWrite(targetPath, content, opts = {}) {
1555
+ const dir = path.dirname(targetPath);
1556
+ await fs.mkdir(dir, { recursive: true });
1557
+ const tmp = path.join(dir, `.${path.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
1558
+ try {
1559
+ if (typeof content === "string") {
1560
+ await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
1561
+ } else {
1562
+ await fs.writeFile(tmp, content, { flag: "wx" });
1563
+ }
1564
+ try {
1565
+ const fh = await fs.open(tmp, "r+");
1566
+ try {
1567
+ await fh.sync();
1568
+ } finally {
1569
+ await fh.close();
1570
+ }
1571
+ } catch {
1572
+ }
1573
+ let mode;
1574
+ try {
1575
+ const stat2 = await fs.stat(targetPath);
1576
+ mode = stat2.mode & 511;
1577
+ } catch {
1578
+ mode = opts.mode;
1579
+ }
1580
+ if (mode !== void 0) {
1581
+ await fs.chmod(tmp, mode);
1582
+ }
1583
+ await renameWithRetry(tmp, targetPath);
1584
+ } catch (err) {
1585
+ try {
1586
+ await fs.unlink(tmp);
1587
+ } catch {
1588
+ }
1589
+ throw err;
1590
+ }
1591
+ }
1592
+ var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
1593
+ async function renameWithRetry(from, to) {
1594
+ if (process.platform !== "win32") {
1595
+ await fs.rename(from, to);
1596
+ return;
1597
+ }
1598
+ const delays = [10, 25, 60, 120, 250];
1599
+ let lastErr;
1600
+ for (let i = 0; i <= delays.length; i++) {
1601
+ try {
1602
+ await fs.rename(from, to);
1603
+ return;
1604
+ } catch (err) {
1605
+ lastErr = err;
1606
+ const code = err?.code;
1607
+ if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
1608
+ throw err;
1609
+ }
1610
+ await new Promise((resolve) => setTimeout(resolve, delays[i]));
1611
+ }
1612
+ }
1613
+ throw lastErr;
1614
+ }
1615
+
1616
+ // src/storage/goal-store.ts
1617
+ var MAX_JOURNAL_ENTRIES = 500;
1618
+ function goalFilePath(projectRoot) {
1619
+ return path.join(projectRoot, ".wrongstack", "goal.json");
1620
+ }
1621
+ async function loadGoal(filePath) {
1622
+ let raw;
1623
+ try {
1624
+ raw = await fs.readFile(filePath, "utf8");
1625
+ } catch {
1626
+ return null;
1627
+ }
1628
+ try {
1629
+ const parsed = JSON.parse(raw);
1630
+ if (parsed?.version !== 1 || typeof parsed.goal !== "string" || !Array.isArray(parsed.journal)) {
1631
+ return null;
1632
+ }
1633
+ return parsed;
1634
+ } catch {
1635
+ return null;
1636
+ }
1637
+ }
1638
+ async function saveGoal(filePath, goal) {
1639
+ await atomicWrite(filePath, JSON.stringify(goal, null, 2), { mode: 384 });
1640
+ }
1641
+ function appendJournal(goal, entry) {
1642
+ const iteration = goal.iterations + 1;
1643
+ const at = (/* @__PURE__ */ new Date()).toISOString();
1644
+ const full = { ...entry, iteration, at };
1645
+ const journal = [...goal.journal, full];
1646
+ const trimmed = journal.length > MAX_JOURNAL_ENTRIES ? journal.slice(journal.length - MAX_JOURNAL_ENTRIES) : journal;
1647
+ return {
1648
+ ...goal,
1649
+ iterations: iteration,
1650
+ lastActivityAt: at,
1651
+ journal: trimmed
1652
+ };
1653
+ }
1654
+
1655
+ // src/execution/eternal-autonomy.ts
1656
+ var execFileP = promisify(execFile);
1657
+ var BRAINSTORM_DONE = /* @__PURE__ */ Symbol("brainstorm-done");
1658
+ var GOAL_COMPLETE_MARKER = /^\s*\[goal[_\s-]?complete\]\s*$/im;
1659
+ var EternalAutonomyEngine = class {
1660
+ constructor(opts) {
1661
+ this.opts = opts;
1662
+ this.goalPath = goalFilePath(opts.projectRoot);
1663
+ }
1664
+ opts;
1665
+ state = "idle";
1666
+ stopRequested = false;
1667
+ consecutiveFailures = 0;
1668
+ consecutiveBrainstormDone = 0;
1669
+ /**
1670
+ * Count of consecutive transient (recoverable) provider failures. Drives
1671
+ * the exponential backoff between iterations. Reset on the first
1672
+ * successful iteration so a single bad afternoon doesn't permanently
1673
+ * slow the loop down.
1674
+ */
1675
+ consecutiveTransientRetries = 0;
1676
+ currentCtrl = null;
1677
+ iterationsSinceCompact = 0;
1678
+ goalPath;
1679
+ /** Current engine state — readable for UIs. */
1680
+ get currentState() {
1681
+ return this.state;
1682
+ }
1683
+ /** Synchronously request stop. Resolves once the running iteration aborts. */
1684
+ stop() {
1685
+ this.stopRequested = true;
1686
+ this.currentCtrl?.abort();
1687
+ void this.persistEngineState("stopped").catch(() => {
1688
+ });
1689
+ this.state = "stopped";
1690
+ }
1691
+ /**
1692
+ * Mark the engine as 'running' on disk + reset stop state so a new
1693
+ * batch of `runOneIteration()` calls can proceed. Called by the REPL
1694
+ * when the user invokes `/autonomy eternal`. Idempotent.
1695
+ */
1696
+ async prime() {
1697
+ this.stopRequested = false;
1698
+ this.state = "running";
1699
+ await this.persistEngineState("running").catch(() => {
1700
+ });
1701
+ }
1702
+ /**
1703
+ * Main loop. Returns when stop() is called or the goal file is removed.
1704
+ * Does NOT throw — every iteration is wrapped to keep the loop alive.
1705
+ */
1706
+ async run() {
1707
+ this.state = "running";
1708
+ await this.persistEngineState("running");
1709
+ try {
1710
+ while (!this.stopRequested) {
1711
+ let iterationOk = false;
1712
+ try {
1713
+ iterationOk = await this.runOneIteration();
1714
+ } catch (err) {
1715
+ this.consecutiveFailures++;
1716
+ this.opts.onError?.(err instanceof Error ? err : new Error(String(err)), this.consecutiveFailures);
1717
+ await this.appendFailure("engine error", err instanceof Error ? err.message : String(err));
1718
+ }
1719
+ if (iterationOk) {
1720
+ this.consecutiveFailures = 0;
1721
+ }
1722
+ if (this.stopRequested) break;
1723
+ await sleep(this.opts.cycleGapMs ?? 1e3);
1724
+ }
1725
+ } finally {
1726
+ this.state = "stopped";
1727
+ await this.persistEngineState("stopped").catch(() => {
1728
+ });
1729
+ }
1730
+ }
1731
+ /**
1732
+ * Execute a single sense-decide-execute-reflect cycle.
1733
+ * Returns true on success, false on handled failure / no-op.
1734
+ *
1735
+ * Exposed publicly so the REPL can pace iterations from its main loop
1736
+ * — running the engine and the REPL as a single sequential consumer of
1737
+ * `agent.run()` avoids race conditions on the shared Context.
1738
+ */
1739
+ async runOneIteration() {
1740
+ const goal = await loadGoal(this.goalPath);
1741
+ if (!goal) {
1742
+ this.stopRequested = true;
1743
+ return false;
1744
+ }
1745
+ const missionState = goal.goalState ?? "active";
1746
+ if (missionState !== "active") {
1747
+ this.stopRequested = true;
1748
+ return false;
1749
+ }
1750
+ const action = await this.decide(goal);
1751
+ if (!action) {
1752
+ if (!this.stopRequested) {
1753
+ await sleep(5e3);
1754
+ }
1755
+ return false;
1756
+ }
1757
+ const ctrl = new AbortController();
1758
+ this.currentCtrl = ctrl;
1759
+ const timer = setTimeout(
1760
+ () => ctrl.abort(),
1761
+ this.opts.iterationTimeoutMs ?? 5 * 6e4
1762
+ );
1763
+ let status = "success";
1764
+ let note;
1765
+ let finalText = "";
1766
+ let isTransientFailure = false;
1767
+ const tc = this.opts.agent.ctx?.tokenCounter;
1768
+ const beforeUsage = tc?.total?.();
1769
+ const beforeCost = tc?.estimateCost?.().total;
1770
+ try {
1771
+ const result = await this.opts.agent.run(
1772
+ [{ type: "text", text: action.directive }],
1773
+ {
1774
+ signal: ctrl.signal,
1775
+ // Enable per-call autonomous continuation so the agent can chain
1776
+ // multiple internal tool/response cycles end-to-end on one
1777
+ // directive instead of returning to the engine after a single
1778
+ // round-trip. The model uses `[continue]` / `[done]` markers
1779
+ // (or the `continue_to_next_iteration` tool) to control the
1780
+ // inner loop. Without this flag the engine produced shallow
1781
+ // iterations and almost never let a real task finish.
1782
+ autonomousContinue: true,
1783
+ // Cap the inner loop so a runaway agent.run can't burn through
1784
+ // the iteration timeout — the engine's own outer loop is the
1785
+ // long-running thing, each tick should be bounded.
1786
+ maxIterations: this.opts.iterationMaxAgentSteps ?? 50
1787
+ }
1788
+ );
1789
+ if (result.status === "aborted") {
1790
+ status = "aborted";
1791
+ note = "stopped by user";
1792
+ } else if (result.status === "failed") {
1793
+ status = "failure";
1794
+ note = result.error?.describe?.() ?? "agent run failed";
1795
+ isTransientFailure = result.error?.recoverable === true;
1796
+ } else if (result.status === "max_iterations") {
1797
+ status = "failure";
1798
+ note = `max iterations (${result.iterations})`;
1799
+ } else {
1800
+ status = "success";
1801
+ finalText = result.finalText ?? "";
1802
+ const tail = finalText.slice(0, 240).replace(/\s+/g, " ").trim();
1803
+ if (tail) note = tail;
1804
+ }
1805
+ } catch (err) {
1806
+ const isAbort = err instanceof Error && (err.name === "AbortError" || err.message.includes("abort"));
1807
+ status = isAbort ? "aborted" : "failure";
1808
+ note = err instanceof Error ? err.message : String(err);
1809
+ if (!isAbort && typeof err?.recoverable === "boolean") {
1810
+ isTransientFailure = err.recoverable;
1811
+ }
1812
+ } finally {
1813
+ clearTimeout(timer);
1814
+ this.currentCtrl = null;
1815
+ }
1816
+ if (action.source === "todo" && action.todoId && status !== "success") {
1817
+ await this.bumpTodoAttempt(action.todoId);
1818
+ }
1819
+ const afterUsage = tc?.total?.();
1820
+ const afterCost = tc?.estimateCost?.().total;
1821
+ const tokens = beforeUsage && afterUsage ? {
1822
+ input: Math.max(0, afterUsage.input - beforeUsage.input),
1823
+ output: Math.max(0, afterUsage.output - beforeUsage.output)
1824
+ } : void 0;
1825
+ const costUsd = typeof beforeCost === "number" && typeof afterCost === "number" ? Math.max(0, afterCost - beforeCost) : void 0;
1826
+ await this.appendIterationEntry({
1827
+ source: action.source,
1828
+ task: action.task,
1829
+ status,
1830
+ note,
1831
+ tokens,
1832
+ costUsd
1833
+ });
1834
+ let iterationIndex = 0;
1835
+ try {
1836
+ const reloaded = await loadGoal(this.goalPath);
1837
+ iterationIndex = reloaded?.iterations ?? 0;
1838
+ } catch {
1839
+ }
1840
+ this.opts.onIteration?.({
1841
+ at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
1842
+ iteration: iterationIndex,
1843
+ source: action.source,
1844
+ task: action.task,
1845
+ status,
1846
+ note,
1847
+ tokens,
1848
+ costUsd
1849
+ });
1850
+ if (status === "failure") {
1851
+ if (isTransientFailure) {
1852
+ this.consecutiveTransientRetries++;
1853
+ const delay = this.computeTransientBackoffMs();
1854
+ if (delay > 0) {
1855
+ await this.sleepInterruptible(delay);
1856
+ }
1857
+ return false;
1858
+ }
1859
+ this.consecutiveFailures++;
1860
+ return false;
1861
+ }
1862
+ if (status === "aborted") {
1863
+ if (this.stopRequested) return false;
1864
+ this.consecutiveFailures++;
1865
+ return false;
1866
+ }
1867
+ this.consecutiveTransientRetries = 0;
1868
+ if (GOAL_COMPLETE_MARKER.test(finalText)) {
1869
+ await this.markGoalCompleted(action, finalText);
1870
+ this.stopRequested = true;
1871
+ return true;
1872
+ }
1873
+ this.iterationsSinceCompact++;
1874
+ await this.maybeCompact().catch((err) => {
1875
+ this.opts.onError?.(
1876
+ err instanceof Error ? err : new Error(String(err)),
1877
+ this.consecutiveFailures
1878
+ );
1879
+ });
1880
+ return true;
1881
+ }
1882
+ /**
1883
+ * Run compaction when either trigger fires:
1884
+ * - We've done >= compactEveryNIterations since the last compact.
1885
+ * - Current request tokens exceed aggressiveCompactRatio * maxContext.
1886
+ *
1887
+ * The second check uses *aggressive* mode to free more headroom; the
1888
+ * cadence check uses non-aggressive (cheaper).
1889
+ */
1890
+ async maybeCompact() {
1891
+ const compactor = this.opts.compactor;
1892
+ if (!compactor) return;
1893
+ const ctx = this.opts.agent.ctx;
1894
+ if (!ctx) return;
1895
+ const cadence = this.opts.compactEveryNIterations ?? 25;
1896
+ const threshold = this.opts.aggressiveCompactRatio ?? 0.85;
1897
+ const maxCtx = this.opts.maxContextTokens;
1898
+ let aggressive = false;
1899
+ let shouldRun = false;
1900
+ if (this.iterationsSinceCompact >= cadence) {
1901
+ shouldRun = true;
1902
+ }
1903
+ if (maxCtx && maxCtx > 0) {
1904
+ const used = ctx.tokenCounter?.currentRequestTokens?.();
1905
+ if (used) {
1906
+ const total = used.input + used.cacheRead;
1907
+ if (total / maxCtx >= threshold) {
1908
+ shouldRun = true;
1909
+ aggressive = true;
1910
+ }
1911
+ }
1912
+ }
1913
+ if (!shouldRun) return;
1914
+ const report = await compactor.compact(ctx, { aggressive });
1915
+ this.iterationsSinceCompact = 0;
1916
+ const saved = report.before - report.after;
1917
+ await this.appendIterationEntry({
1918
+ source: "manual",
1919
+ task: `compaction (${aggressive ? "aggressive" : "cadence"})`,
1920
+ status: "success",
1921
+ note: `saved ~${saved} tokens (${report.before}\u2192${report.after})`
1922
+ });
1923
+ }
1924
+ /**
1925
+ * Hybrid idea source.
1926
+ * 1. Pending todos on the agent's context.
1927
+ * 2. Dirty git working tree → propose a "review and finish this" task.
1928
+ * 3. Otherwise: brainstorm via the LLM against the goal.
1929
+ *
1930
+ * After failureBudget consecutive failures, force brainstorm so the
1931
+ * engine doesn't loop on the same broken todo or stuck git state.
1932
+ */
1933
+ async decide(goal) {
1934
+ const forceBrainstorm = this.consecutiveFailures >= (this.opts.failureBudget ?? 3);
1935
+ if (!forceBrainstorm) {
1936
+ const todo = this.pickPendingTodo(goal);
1937
+ if (todo) {
1938
+ return {
1939
+ source: "todo",
1940
+ task: todo.content,
1941
+ todoId: todo.id,
1942
+ directive: this.buildDirective(goal, "todo", todo.content)
1943
+ };
1944
+ }
1945
+ const gitTask = await this.pickGitTask();
1946
+ if (gitTask) {
1947
+ return {
1948
+ source: "git",
1949
+ task: gitTask,
1950
+ directive: this.buildDirective(goal, "git", gitTask)
1951
+ };
1952
+ }
1953
+ }
1954
+ const brainstormed = await this.brainstormTask(goal);
1955
+ if (brainstormed === BRAINSTORM_DONE) {
1956
+ this.consecutiveBrainstormDone++;
1957
+ const threshold = this.opts.brainstormDoneStopThreshold ?? 3;
1958
+ if (this.consecutiveBrainstormDone >= threshold) {
1959
+ await this.markGoalCompleted(
1960
+ { source: "brainstorm", task: "no further work", directive: "" },
1961
+ `brainstorm returned DONE ${this.consecutiveBrainstormDone}x in a row`
1962
+ );
1963
+ this.stopRequested = true;
1964
+ }
1965
+ return null;
1966
+ }
1967
+ if (!brainstormed) return null;
1968
+ this.consecutiveBrainstormDone = 0;
1969
+ return {
1970
+ source: "brainstorm",
1971
+ task: brainstormed,
1972
+ directive: this.buildDirective(goal, "brainstorm", brainstormed)
1973
+ };
1974
+ }
1975
+ pickPendingTodo(goal) {
1976
+ const todos = this.opts.agent.ctx.todos;
1977
+ if (!Array.isArray(todos)) return null;
1978
+ const attempts = goal.todoAttempts ?? {};
1979
+ const ceiling = this.opts.todoMaxAttempts ?? 3;
1980
+ for (const t of todos) {
1981
+ if (t.status !== "pending") continue;
1982
+ const used = attempts[t.id] ?? 0;
1983
+ if (used >= ceiling) continue;
1984
+ return t;
1985
+ }
1986
+ return null;
1987
+ }
1988
+ async pickGitTask() {
1989
+ let out;
1990
+ try {
1991
+ out = await (this.opts.gitStatusReader?.() ?? this.readGitStatus());
1992
+ } catch {
1993
+ return null;
1994
+ }
1995
+ const dirty = out.trim();
1996
+ if (!dirty) return null;
1997
+ const lines = dirty.split("\n").slice(0, 8);
1998
+ const preview = lines.join(", ");
1999
+ return `Inspect the dirty working tree and either finish the in-progress work or revert it. Files: ${preview}`;
2000
+ }
2001
+ async readGitStatus() {
2002
+ const { stdout } = await execFileP("git", ["status", "--porcelain"], {
2003
+ cwd: this.opts.projectRoot,
2004
+ timeout: 5e3
2005
+ });
2006
+ return stdout;
2007
+ }
2008
+ async brainstormTask(goal) {
2009
+ const lastFew = goal.journal.slice(-5).map((e) => ` - [${e.status}] ${e.task}`).join("\n");
2010
+ const directive = [
2011
+ "You are deciding the next action in an autonomous loop pursuing a long-running goal.",
2012
+ "",
2013
+ `Goal: ${goal.goal}`,
2014
+ "",
2015
+ lastFew ? `Recent iterations:
2016
+ ${lastFew}` : "No prior iterations yet.",
2017
+ "",
2018
+ "Output ONE concrete, immediately-actionable task that advances the goal.",
2019
+ "Constraints:",
2020
+ "- One sentence, imperative form, under 200 chars.",
2021
+ "- No preamble, no explanation, no markdown \u2014 just the task line.",
2022
+ "- If recent iterations show repeated failures on the same target, pivot.",
2023
+ "- If the goal appears fully accomplished AND you can name a concrete",
2024
+ " artifact / test / output that proves it, output exactly: DONE",
2025
+ "- Be conservative with DONE: if the recent journal contains failures",
2026
+ " or aborted entries, the goal is almost certainly NOT done."
2027
+ ].join("\n");
2028
+ try {
2029
+ const ctrl = new AbortController();
2030
+ const timer = setTimeout(() => ctrl.abort(), 6e4);
2031
+ try {
2032
+ const result = await this.opts.agent.run(
2033
+ [{ type: "text", text: directive }],
2034
+ { signal: ctrl.signal, maxIterations: 1 }
2035
+ );
2036
+ if (result.status !== "done") return null;
2037
+ const text = (result.finalText ?? "").trim();
2038
+ if (!text) return null;
2039
+ if (/^DONE\.?$/i.test(text)) return BRAINSTORM_DONE;
2040
+ const firstLine = text.split("\n").find((l) => l.trim().length > 0)?.trim();
2041
+ if (!firstLine) return null;
2042
+ if (/^DONE\.?$/i.test(firstLine)) return BRAINSTORM_DONE;
2043
+ return firstLine.slice(0, 240);
2044
+ } finally {
2045
+ clearTimeout(timer);
2046
+ }
2047
+ } catch {
2048
+ return null;
2049
+ }
2050
+ }
2051
+ buildDirective(goal, source, task) {
2052
+ const recentJournal = goal.journal.slice(-5).map((e) => ` #${e.iteration} [${e.status}] ${e.task}${e.note ? ` \u2014 ${e.note.slice(0, 80)}` : ""}`).join("\n");
2053
+ return [
2054
+ "\u2550\u2550\u2550 ETERNAL AUTONOMY \u2014 iteration directive \u2550\u2550\u2550",
2055
+ "",
2056
+ `Mission: ${goal.goal}`,
2057
+ `Iteration: #${goal.iterations + 1}`,
2058
+ `Source: ${source}`,
2059
+ `Task: ${task}`,
2060
+ "",
2061
+ recentJournal ? `Recent journal (last 5):
2062
+ ${recentJournal}` : "No prior iterations.",
2063
+ "",
2064
+ "\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
2065
+ "You are inside a long-running autonomous loop. Each iteration you",
2066
+ "execute ONE concrete task that advances the Mission. No user is",
2067
+ "available to clarify \u2014 make defensible decisions and move forward.",
2068
+ "",
2069
+ "1. EXECUTE END-TO-END",
2070
+ " \u2022 Use multiple tool calls freely. Emit `[continue]` on its own line",
2071
+ " to chain to the next internal step without returning.",
2072
+ " \u2022 When this iteration's Task is finished (real artifact / passing",
2073
+ " test / applied diff / clean output), emit `[done]` on its own line.",
2074
+ " \u2022 Do not stop on the first obstacle \u2014 try at least 3 distinct",
2075
+ " approaches before giving up. YOLO is active; no confirmations.",
2076
+ "",
2077
+ "2. UPDATE TODO STATE (when Source is `todo`)",
2078
+ " \u2022 Mark this todo `in_progress` via the todos tool before tool work.",
2079
+ " \u2022 Mark it `completed` on success, with a one-line outcome note.",
2080
+ " \u2022 If you cannot make progress after 2 distinct attempts, mark it",
2081
+ " `cancelled` with the obstacle. The loop will skip it next time.",
2082
+ "",
2083
+ "3. MISSION-COMPLETE PROTOCOL",
2084
+ " \u2022 If \u2014 and ONLY if \u2014 the OVERALL Mission (not just this Task) is",
2085
+ " verifiably accomplished, emit on its own line:",
2086
+ " [GOAL_COMPLETE]",
2087
+ " followed by a one-paragraph verification recipe (artifact path,",
2088
+ " test command, or 10-second reproduction). This halts the loop.",
2089
+ " \u2022 NEVER emit [GOAL_COMPLETE] on optimism, partial progress, or",
2090
+ ' "looks fine". Required: a concrete artifact that proves it AND',
2091
+ " no recent journal failures contradicting completion.",
2092
+ " \u2022 If unsure, emit `[done]` instead and let the next iteration",
2093
+ " decide. The loop is patient; false completion is not.",
2094
+ "",
2095
+ "4. NO INTERACTIVITY",
2096
+ " \u2022 Do not ask questions, do not request confirmation, do not propose",
2097
+ " options. Pick the best path and execute. The user is asleep."
2098
+ ].join("\n");
2099
+ }
2100
+ /**
2101
+ * Exponential backoff for transient provider errors. `2^N * base`
2102
+ * capped at `transientBackoffMaxMs`. Zero base disables backoff.
2103
+ * Public-private to keep `runOneIteration` readable; the value is
2104
+ * recomputed each call from the current retry count, so callers
2105
+ * don't have to track state.
2106
+ */
2107
+ computeTransientBackoffMs() {
2108
+ const base = this.opts.transientBackoffBaseMs ?? 2e3;
2109
+ const cap = this.opts.transientBackoffMaxMs ?? 6e4;
2110
+ if (base <= 0) return 0;
2111
+ const exponent = Math.max(0, this.consecutiveTransientRetries - 1);
2112
+ return Math.min(cap, base * Math.pow(2, exponent));
2113
+ }
2114
+ /**
2115
+ * Sleep that wakes early if `stopRequested` flips. Polls every 250 ms
2116
+ * so SIGINT / `/autonomy stop` can land in the middle of a long
2117
+ * backoff instead of waiting up to a minute for the timer.
2118
+ */
2119
+ async sleepInterruptible(totalMs) {
2120
+ const step = 250;
2121
+ let remaining = totalMs;
2122
+ while (remaining > 0 && !this.stopRequested) {
2123
+ const chunk = Math.min(step, remaining);
2124
+ await sleep(chunk);
2125
+ remaining -= chunk;
2126
+ }
2127
+ }
2128
+ async appendIterationEntry(entry) {
2129
+ const current = await loadGoal(this.goalPath);
2130
+ if (!current) {
2131
+ return;
2132
+ }
2133
+ const updated = appendJournal(current, entry);
2134
+ await saveGoal(this.goalPath, updated);
2135
+ }
2136
+ /**
2137
+ * Persistent per-todo failure counter. Skipped silently when the goal
2138
+ * file has been removed (graceful clear). Each non-success iteration
2139
+ * against a todo source bumps the counter by 1; `pickPendingTodo` reads
2140
+ * the counter to rotate past stuck todos once they cross `todoMaxAttempts`.
2141
+ */
2142
+ async bumpTodoAttempt(todoId) {
2143
+ const current = await loadGoal(this.goalPath);
2144
+ if (!current) return;
2145
+ const attempts = { ...current.todoAttempts ?? {} };
2146
+ attempts[todoId] = (attempts[todoId] ?? 0) + 1;
2147
+ await saveGoal(this.goalPath, { ...current, todoAttempts: attempts });
2148
+ }
2149
+ /**
2150
+ * Flip the mission to `completed` and journal it. Called from two
2151
+ * paths: (a) `[GOAL_COMPLETE]` marker in a successful iteration's
2152
+ * finalText, (b) `brainstorm` returning DONE consecutively past the
2153
+ * configured threshold. Idempotent — re-entry is a no-op once the
2154
+ * goal is already `completed`.
2155
+ */
2156
+ async markGoalCompleted(action, note) {
2157
+ const current = await loadGoal(this.goalPath);
2158
+ if (!current) return;
2159
+ if (current.goalState === "completed") return;
2160
+ const withFlag = { ...current, goalState: "completed" };
2161
+ const withEntry = appendJournal(withFlag, {
2162
+ source: action.source,
2163
+ task: `MISSION COMPLETE \u2014 ${action.task}`.slice(0, 240),
2164
+ status: "success",
2165
+ note: note.slice(0, 240)
2166
+ });
2167
+ await saveGoal(this.goalPath, withEntry);
2168
+ }
2169
+ async appendFailure(task, note) {
2170
+ await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
2171
+ }
2172
+ async persistEngineState(state) {
2173
+ const current = await loadGoal(this.goalPath);
2174
+ if (!current) return;
2175
+ if (current.engineState === state) return;
2176
+ await saveGoal(this.goalPath, { ...current, engineState: state });
2177
+ }
2178
+ };
2179
+ function sleep(ms) {
2180
+ return new Promise((resolve) => setTimeout(resolve, ms));
2181
+ }
2182
+
2183
+ // src/execution/autonomy-prompt-contributor.ts
2184
+ function makeAutonomyPromptContributor(opts) {
2185
+ return async (ctx) => {
2186
+ if (ctx.subagent) return [];
2187
+ if (!opts.enabled()) return [];
2188
+ let goal;
2189
+ try {
2190
+ goal = await loadGoal(opts.goalPath);
2191
+ } catch {
2192
+ return [];
2193
+ }
2194
+ if (!goal) return [];
2195
+ const missionState = goal.goalState ?? "active";
2196
+ if (missionState !== "active") return [];
2197
+ const tailSize = opts.journalTailSize ?? 5;
2198
+ const journalTail = goal.journal.slice(-tailSize).map((e) => {
2199
+ const note = e.note ? ` \u2014 ${e.note.slice(0, 80)}` : "";
2200
+ return ` #${e.iteration} [${e.status}] ${e.task}${note}`;
2201
+ });
2202
+ const text = [
2203
+ "## ETERNAL AUTONOMY \u2014 active mission",
2204
+ "",
2205
+ "You are inside a long-running autonomous loop. The user is asleep",
2206
+ "and is not available to confirm decisions. Each turn you receive a",
2207
+ "directive describing one concrete sub-task that advances the mission.",
2208
+ "",
2209
+ `Mission: ${goal.goal}`,
2210
+ `Iteration: #${goal.iterations}`,
2211
+ journalTail.length > 0 ? `Recent journal (last ${journalTail.length}):
2212
+ ${journalTail.join("\n")}` : "Recent journal: (none \u2014 this is the first iteration)",
2213
+ "",
2214
+ "### Loop control markers",
2215
+ "Emit these on their own line in your final text \u2014 case-insensitive,",
2216
+ "whitespace-tolerant, but they must occupy the entire line:",
2217
+ "- `[continue]` \u2014 chain to the next internal step without returning.",
2218
+ "- `[done]` \u2014 the current sub-task is finished; return to the engine.",
2219
+ "- `[GOAL_COMPLETE]` \u2014 emit ONLY when the OVERALL mission is",
2220
+ " verifiably done. Must be followed by a one-paragraph verification",
2221
+ " recipe (artifact path, test command, or 10-second reproduction).",
2222
+ " The engine halts on this marker \u2014 false positives waste real",
2223
+ " human time. If unsure, emit `[done]` and let the next iteration",
2224
+ " decide.",
2225
+ "",
2226
+ "### Operating principles",
2227
+ "- YOLO is active. Do NOT ask for confirmation, do NOT propose",
2228
+ " options. Pick the best path and execute it.",
2229
+ "- Use tools freely; multiple calls per turn are normal and expected.",
2230
+ "- When working on a todo, mark it `in_progress` via the todos tool",
2231
+ " before tool work and `completed` (or `cancelled` with a reason)",
2232
+ " when done. The loop reads todo state between iterations.",
2233
+ "- If an approach fails twice in a row, pivot. Don't grind on the",
2234
+ " same wall \u2014 try a different angle, file a cancel on the todo, or",
2235
+ " surface the obstacle via `[done]` and let the next iteration",
2236
+ " re-plan."
2237
+ ].join("\n");
2238
+ return [
2239
+ {
2240
+ type: "text",
2241
+ text,
2242
+ cache_control: { type: "ephemeral" }
2243
+ }
2244
+ ];
2245
+ };
2246
+ }
2247
+
2248
+ // src/execution/goal-preamble.ts
2249
+ function buildGoalPreamble(goal) {
2250
+ return [
2251
+ "[GOAL \u2014 LOCKED IN. You will work on this until it is verifiably done.",
2252
+ "The user granted you full autonomy. Read these constraints once, then act.",
2253
+ "",
2254
+ "YOUR GOAL:",
2255
+ "---",
2256
+ goal,
2257
+ "---",
2258
+ "",
2259
+ "AUTHORITY YOU HAVE:",
2260
+ "- Spawn as many subagents as the work needs (delegate / spawn_subagent).",
2261
+ " Parallel + recursive fan-out are both fine. There is no spawn budget.",
2262
+ "- Use any provider/model per subagent \u2014 pick the right tool for each",
2263
+ " piece of work. Heavy reasoning model for planning, fast model for",
2264
+ " batch work, specialist model for domain code.",
2265
+ "- Run unlimited tool calls and iterations. There is NO hidden budget.",
2266
+ " The Agent loop auto-extends every 100 iterations forever.",
2267
+ "- Retry failed tools with different inputs, alternative paths, fresh",
2268
+ " subagents. Switch providers mid-run if one is rate-limited.",
2269
+ "- Re-plan freely when an approach hits a dead end. You are not obliged",
2270
+ " to stick with the first plan you proposed.",
2271
+ "",
2272
+ 'WHAT "DONE" MEANS \u2014 non-negotiable:',
2273
+ "- You can name a concrete artifact (a passing test, a written file at",
2274
+ " a specific path, a fixed bug verified by re-running the failing case,",
2275
+ " a clean grep that previously had matches).",
2276
+ "- You can tell the user HOW to verify it themselves in 10 seconds.",
2277
+ '- You have NOT hedged. None of: "looks like it should work", "I',
2278
+ ' believe this fixes it", "the changes appear correct".',
2279
+ "",
2280
+ "WHAT IS NOT DONE \u2014 never report any of these as completion:",
2281
+ "- An error message you didn't recover from.",
2282
+ '- An empty result, a 0-line file, a "no matches found" you accepted',
2283
+ " without questioning the search.",
2284
+ '- "Should I continue?" / "Want me to also...?" / "Let me know if you',
2285
+ ' want X." Those are hedges. The user already told you to finish the',
2286
+ " goal \u2014 just do it.",
2287
+ "- Partial progress dressed up as success. Fixed 3 of 5 bugs = 60%",
2288
+ " done, not done.",
2289
+ "- A subagent's failed/timeout/stopped TaskResult that you didn't",
2290
+ " respond to with a fresh attempt (different role, different model,",
2291
+ " tighter prompt).",
2292
+ "",
2293
+ "PERSISTENCE PROTOCOL:",
2294
+ "- If blocked, try at least 3 different angles before reporting the",
2295
+ " problem to the user. Different tool inputs, different subagent",
2296
+ " roles, different providers, different decomposition of the task.",
2297
+ "- If a tool fails, read its error, alter the input, try again. Do",
2298
+ " not just report the failure back.",
2299
+ "- If a subagent returns useless output, respawn with a tighter prompt",
2300
+ ' or a different role. Do not accept "I could not determine\u2026" as the',
2301
+ " final answer.",
2302
+ "- Use `ask_subagent` for one-shot questions when you don't need a",
2303
+ " full delegated task.",
2304
+ "",
2305
+ "REPORTING:",
2306
+ "- Stream short progress notes between major actions so the user can",
2307
+ " monitor. Do not go silent for 50 tool calls then dump a wall of",
2308
+ " text \u2014 but also do not narrate every tool call.",
2309
+ "- Use the shared scratchpad (if available) to leave breadcrumbs",
2310
+ " subagents can read.",
2311
+ "- Final response must include: (a) what was accomplished, (b) how",
2312
+ " to verify, (c) any caveats (residual TODOs, things the user",
2313
+ " should know about).",
2314
+ "",
2315
+ "BEGIN.]"
2316
+ ].join("\n");
2317
+ }
1536
2318
 
1537
2319
  // src/types/provider.ts
1538
2320
  var ProviderError = class extends WrongStackError {
@@ -1664,7 +2446,7 @@ function buildRecoveryStrategies(opts) {
1664
2446
  async attempt(err) {
1665
2447
  if (!(err instanceof ProviderError) || err.status !== 429) return null;
1666
2448
  const delayMs = err.body?.retryAfterMs ?? 5e3;
1667
- const delay = Math.max(1e3, Math.min(delayMs, 6e4));
2449
+ const delay = Math.min(6e4, Math.max(1e3, delayMs));
1668
2450
  await new Promise((r) => setTimeout(r, delay));
1669
2451
  return { action: "retry", reason: "rate_limit_backoff" };
1670
2452
  }
@@ -1846,6 +2628,6 @@ function parseDescription(raw) {
1846
2628
  return { trigger, scope };
1847
2629
  }
1848
2630
 
1849
- export { AutoCompactionMiddleware, AutonomousRunner, DefaultErrorHandler, DefaultRetryPolicy, DefaultSkillLoader, DoneConditionChecker, HybridCompactor, IntelligentCompactor, SelectiveCompactor, ToolExecutor };
2631
+ export { AutoCompactionMiddleware, AutonomousRunner, DefaultErrorHandler, DefaultRetryPolicy, DefaultSkillLoader, DoneConditionChecker, EternalAutonomyEngine, HybridCompactor, IntelligentCompactor, SelectiveCompactor, ToolExecutor, buildGoalPreamble, makeAutonomyPromptContributor };
1850
2632
  //# sourceMappingURL=index.js.map
1851
2633
  //# sourceMappingURL=index.js.map