@jmanuelcorral/openteam 0.19.0 → 0.20.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.
Files changed (71) hide show
  1. package/README.es.md +10 -2
  2. package/README.md +9 -2
  3. package/dist/certificates/graph-release-certificate.json +2 -2
  4. package/dist/certificates/graph-shadow-certificate.json +2 -2
  5. package/dist/cli/consoleServe.d.ts +16 -0
  6. package/dist/cli/consoleServe.d.ts.map +1 -1
  7. package/dist/cli/graphOperator.d.ts +10 -1
  8. package/dist/cli/graphOperator.d.ts.map +1 -1
  9. package/dist/cli/lifecycleHistoryProvider.d.ts +21 -0
  10. package/dist/cli/lifecycleHistoryProvider.d.ts.map +1 -0
  11. package/dist/cli.d.ts +3 -1
  12. package/dist/cli.d.ts.map +1 -1
  13. package/dist/cli.js +6163 -1632
  14. package/dist/config/errors.d.ts.map +1 -1
  15. package/dist/config/schema.d.ts +31 -1
  16. package/dist/config/schema.d.ts.map +1 -1
  17. package/dist/console/assets.d.ts +22 -1
  18. package/dist/console/assets.d.ts.map +1 -1
  19. package/dist/console/render.d.ts +12 -0
  20. package/dist/console/render.d.ts.map +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +2849 -261
  23. package/dist/lifecycle/contracts.d.ts +618 -0
  24. package/dist/lifecycle/contracts.d.ts.map +1 -0
  25. package/dist/lifecycle/historyReader.d.ts +11 -0
  26. package/dist/lifecycle/historyReader.d.ts.map +1 -0
  27. package/dist/lifecycle/historySourceError.d.ts +7 -0
  28. package/dist/lifecycle/historySourceError.d.ts.map +1 -0
  29. package/dist/lifecycle/project.d.ts +3 -0
  30. package/dist/lifecycle/project.d.ts.map +1 -0
  31. package/dist/lifecycle/root.d.ts +3 -0
  32. package/dist/lifecycle/root.d.ts.map +1 -0
  33. package/dist/lifecycle/runtimeRecorder.d.ts +6 -0
  34. package/dist/lifecycle/runtimeRecorder.d.ts.map +1 -0
  35. package/dist/lifecycle/schema.d.ts +487 -0
  36. package/dist/lifecycle/schema.d.ts.map +1 -0
  37. package/dist/messages/index.d.ts +1 -0
  38. package/dist/messages/index.d.ts.map +1 -1
  39. package/dist/messages/lifecycle.d.ts +95 -0
  40. package/dist/messages/lifecycle.d.ts.map +1 -0
  41. package/dist/messages/lifecycleRuntime.d.ts +4 -0
  42. package/dist/messages/lifecycleRuntime.d.ts.map +1 -0
  43. package/dist/messages/lifecycleStorage.d.ts +4 -0
  44. package/dist/messages/lifecycleStorage.d.ts.map +1 -0
  45. package/dist/orchestrator/coordinator.d.ts +11 -1
  46. package/dist/orchestrator/coordinator.d.ts.map +1 -1
  47. package/dist/orchestrator/graphIngress.d.ts +5 -0
  48. package/dist/orchestrator/graphIngress.d.ts.map +1 -1
  49. package/dist/orchestrator/graphRuntime.d.ts +13 -1
  50. package/dist/orchestrator/graphRuntime.d.ts.map +1 -1
  51. package/dist/orchestrator/subsessions.d.ts +3 -0
  52. package/dist/orchestrator/subsessions.d.ts.map +1 -1
  53. package/dist/plugin/orchestrateTool.d.ts +3 -0
  54. package/dist/plugin/orchestrateTool.d.ts.map +1 -1
  55. package/dist/storage/lifecycle/codec.d.ts +19 -0
  56. package/dist/storage/lifecycle/codec.d.ts.map +1 -0
  57. package/dist/storage/lifecycle/fsLifecycleJournal.d.ts +3 -0
  58. package/dist/storage/lifecycle/fsLifecycleJournal.d.ts.map +1 -0
  59. package/dist/storage/lifecycle/fsLifecycleSupport.d.ts +17 -0
  60. package/dist/storage/lifecycle/fsLifecycleSupport.d.ts.map +1 -0
  61. package/dist/storage/lifecycle/fsLifecycleWriterLivenessProbe.d.ts +3 -0
  62. package/dist/storage/lifecycle/fsLifecycleWriterLivenessProbe.d.ts.map +1 -0
  63. package/dist/web/graphRoute.d.ts +39 -1
  64. package/dist/web/graphRoute.d.ts.map +1 -1
  65. package/dist/web/server.d.ts +19 -0
  66. package/dist/web/server.d.ts.map +1 -1
  67. package/dist/web/start.d.ts +11 -0
  68. package/dist/web/start.d.ts.map +1 -1
  69. package/dist/web/watch.d.ts +17 -2
  70. package/dist/web/watch.d.ts.map +1 -1
  71. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
4
  // src/index.ts
5
5
  import { readFile as readFile3 } from "node:fs/promises";
6
- import { basename as basename2, dirname as dirname4 } from "node:path";
6
+ import { basename as basename3, dirname as dirname5 } from "node:path";
7
7
 
8
8
  // src/capabilities/curated.ts
9
9
  var CURATED_FRONTIER_PROFILES = [
@@ -799,6 +799,29 @@ var GraphModeSchema = z3.enum(["off", "shadow", "active"]);
799
799
  var GraphEngineSchema = z3.enum(["builtin", "langgraph"]);
800
800
  var DEFAULT_GRAPH_JOURNAL_ROOT = ".opencode/openteam-local/graph/runs";
801
801
  var DEFAULT_GRAPH_WORKTREES_DIR = ".opencode/openteam-local/graph/worktrees";
802
+ var DEFAULT_LIFECYCLE_ROOT = ".opencode/openteam-local/lifecycle/runs";
803
+ var DEFAULT_LIFECYCLE_MAX_TERMINAL_RUNS = 200;
804
+ var DEFAULT_LIFECYCLE_MAX_AGE_DAYS = 30;
805
+ var DEFAULT_LIFECYCLE_MAX_BYTES = 268435456;
806
+ var LifecycleConfigSchema = z3.object({
807
+ root: z3.string().min(1).default(DEFAULT_LIFECYCLE_ROOT),
808
+ retention: z3.object({
809
+ maxTerminalRuns: z3.number().int().nonnegative().default(DEFAULT_LIFECYCLE_MAX_TERMINAL_RUNS),
810
+ maxAgeDays: z3.number().nonnegative().default(DEFAULT_LIFECYCLE_MAX_AGE_DAYS),
811
+ maxBytes: z3.number().int().nonnegative().default(DEFAULT_LIFECYCLE_MAX_BYTES)
812
+ }).strict().default({
813
+ maxTerminalRuns: DEFAULT_LIFECYCLE_MAX_TERMINAL_RUNS,
814
+ maxAgeDays: DEFAULT_LIFECYCLE_MAX_AGE_DAYS,
815
+ maxBytes: DEFAULT_LIFECYCLE_MAX_BYTES
816
+ })
817
+ }).strict().default({
818
+ root: DEFAULT_LIFECYCLE_ROOT,
819
+ retention: {
820
+ maxTerminalRuns: DEFAULT_LIFECYCLE_MAX_TERMINAL_RUNS,
821
+ maxAgeDays: DEFAULT_LIFECYCLE_MAX_AGE_DAYS,
822
+ maxBytes: DEFAULT_LIFECYCLE_MAX_BYTES
823
+ }
824
+ });
802
825
  var GraphConfigSchema = z3.object({
803
826
  mode: GraphModeSchema.default("active"),
804
827
  engine: GraphEngineSchema.optional(),
@@ -1102,6 +1125,7 @@ var OpenTeamConfigObjectSchema = z3.object({
1102
1125
  console: ConsoleConfigSchema,
1103
1126
  storage: StorageConfigSchema,
1104
1127
  memory: MemoryConfigSchema.optional(),
1128
+ lifecycle: LifecycleConfigSchema,
1105
1129
  graph: GraphConfigSchema.default({
1106
1130
  mode: "active",
1107
1131
  killSwitch: false,
@@ -1571,6 +1595,32 @@ function runCancelled(runID, seq) {
1571
1595
  return { v: GRAPH_EVENT_VERSION, type: "run.cancelled", runID, seq };
1572
1596
  }
1573
1597
 
1598
+ // src/graph/ids.ts
1599
+ var NUL = "\x00";
1600
+ function operationID(runID, nodeID, revision = 0) {
1601
+ return sha256Hex(`op${NUL}${runID}${NUL}${nodeID}${NUL}${revision}`).slice(0, 32);
1602
+ }
1603
+ function attemptID(operation, attempt) {
1604
+ return sha256Hex(`att${NUL}${operation}${NUL}${attempt}`).slice(0, 32);
1605
+ }
1606
+ function canonicalSpec(spec) {
1607
+ const nodes = spec.nodes.map((node) => ({
1608
+ id: node.id,
1609
+ role: node.role,
1610
+ dependsOn: [...node.dependsOn],
1611
+ anchor: { taskID: node.anchor.taskID, specDigest: node.anchor.specDigest },
1612
+ ...node.reviews === undefined ? {} : { reviews: node.reviews }
1613
+ }));
1614
+ return JSON.stringify({
1615
+ version: spec.version,
1616
+ runID: spec.runID,
1617
+ nodes
1618
+ });
1619
+ }
1620
+ function graphSpecDigest(spec) {
1621
+ return sha256Hex(canonicalSpec(spec));
1622
+ }
1623
+
1574
1624
  // src/storage/graph/provider.ts
1575
1625
  class JournalError extends Error {
1576
1626
  code;
@@ -1714,7 +1764,7 @@ function createRunRegistry(root) {
1714
1764
  }
1715
1765
 
1716
1766
  // src/storage/graph/workspaceLease.ts
1717
- var NUL = "\x00";
1767
+ var NUL2 = "\x00";
1718
1768
  var leaseRecordSchema = z8.object({
1719
1769
  runID: z8.string().min(1),
1720
1770
  holder: z8.string().min(1),
@@ -1734,7 +1784,7 @@ class LeaseError extends Error {
1734
1784
  }
1735
1785
  function createWorkspaceLease(root, registry = createRunRegistry(root)) {
1736
1786
  const leasePath = (runID) => join3(root, `lease-${runID}.json`);
1737
- const deriveToken = (runID, holder) => sha256Hex(`lease${NUL}${runID}${NUL}${holder}`).slice(0, 32);
1787
+ const deriveToken = (runID, holder) => sha256Hex(`lease${NUL2}${runID}${NUL2}${holder}`).slice(0, 32);
1738
1788
  const parse2 = (text) => {
1739
1789
  let raw;
1740
1790
  try {
@@ -1815,32 +1865,6 @@ function planCancellation(state) {
1815
1865
  // src/orchestrator/graphReview.ts
1816
1866
  import { z as z9 } from "zod";
1817
1867
 
1818
- // src/graph/ids.ts
1819
- var NUL2 = "\x00";
1820
- function operationID(runID, nodeID, revision = 0) {
1821
- return sha256Hex(`op${NUL2}${runID}${NUL2}${nodeID}${NUL2}${revision}`).slice(0, 32);
1822
- }
1823
- function attemptID(operation, attempt) {
1824
- return sha256Hex(`att${NUL2}${operation}${NUL2}${attempt}`).slice(0, 32);
1825
- }
1826
- function canonicalSpec(spec) {
1827
- const nodes = spec.nodes.map((node) => ({
1828
- id: node.id,
1829
- role: node.role,
1830
- dependsOn: [...node.dependsOn],
1831
- anchor: { taskID: node.anchor.taskID, specDigest: node.anchor.specDigest },
1832
- ...node.reviews === undefined ? {} : { reviews: node.reviews }
1833
- }));
1834
- return JSON.stringify({
1835
- version: spec.version,
1836
- runID: spec.runID,
1837
- nodes
1838
- });
1839
- }
1840
- function graphSpecDigest(spec) {
1841
- return sha256Hex(canonicalSpec(spec));
1842
- }
1843
-
1844
1868
  // src/graph/policies.ts
1845
1869
  var DEFAULT_RETRY_LIMIT = 3;
1846
1870
  var DEFAULT_REVISION_LIMIT = 2;
@@ -1932,6 +1956,18 @@ function plan(spec, state) {
1932
1956
  function specDigest(spec) {
1933
1957
  return sha256Hex(JSON.stringify(spec));
1934
1958
  }
1959
+ function graphFailure(errorClass, retryable) {
1960
+ return {
1961
+ class: errorClass === "cancelled" ? "cancelled" : "unknown",
1962
+ retryable
1963
+ };
1964
+ }
1965
+ function graphFailureOutcome(errorClass) {
1966
+ if (errorClass === "cancelled") {
1967
+ return "cancelled";
1968
+ }
1969
+ return errorClass === "unknown" ? "unknown" : "failed";
1970
+ }
1935
1971
  function findNode(spec, nodeID) {
1936
1972
  return spec.nodes.find((candidate) => candidate.id === nodeID);
1937
1973
  }
@@ -1953,21 +1989,126 @@ function nextRetry(loaded, limit) {
1953
1989
  const first = [...byNode.keys()].sort()[0];
1954
1990
  return first === undefined ? undefined : byNode.get(first);
1955
1991
  }
1992
+ function advanceAttemptExpectation(attempts, attemptID2, minimumPhase) {
1993
+ const current = attempts.get(attemptID2);
1994
+ if (current === undefined || current === "queued" && minimumPhase !== "queued" || current === "started" && minimumPhase === "terminal") {
1995
+ attempts.set(attemptID2, minimumPhase);
1996
+ }
1997
+ }
1998
+ function graphLifecycleExpectations(loaded, options = {}) {
1999
+ const limit = options.retryLimit ?? DEFAULT_RETRY_LIMIT;
2000
+ const cancelledNodeIDs = new Set(options.cancelledNodeIDs ?? []);
2001
+ return loaded.spec.nodes.map((node, nodeIndex) => {
2002
+ const state = loaded.state.nodes[node.id];
2003
+ const authoritativeStatus = cancelledNodeIDs.has(node.id) ? "cancelled" : state?.status ?? "pending";
2004
+ const operation = state?.operationID ?? operationID(loaded.spec.runID, node.id);
2005
+ const attempts = new Map;
2006
+ for (const event of loaded.events) {
2007
+ if (event.type === "node.dispatched" && event.nodeID === node.id) {
2008
+ advanceAttemptExpectation(attempts, event.attemptID, "started");
2009
+ } else if ((event.type === "node.succeeded" || event.type === "node.failed") && event.nodeID === node.id) {
2010
+ advanceAttemptExpectation(attempts, event.attemptID, "terminal");
2011
+ }
2012
+ }
2013
+ if (cancelledNodeIDs.has(node.id) && state !== undefined && state.attempts > 0 && state.attemptID !== undefined) {
2014
+ advanceAttemptExpectation(attempts, state.attemptID, "terminal");
2015
+ }
2016
+ const attemptExpectations = [
2017
+ ...attempts
2018
+ ].map(([attemptID2, minimumPhase]) => ({
2019
+ attemptID: attemptID2,
2020
+ minimumPhase
2021
+ }));
2022
+ if (state !== undefined && (attempts.size !== state.attempts || state.attemptID !== undefined && !attempts.has(state.attemptID))) {
2023
+ attemptExpectations.push({
2024
+ attemptID: null,
2025
+ minimumPhase: authoritativeStatus === "succeeded" || authoritativeStatus === "failed" || authoritativeStatus === "cancelled" ? "terminal" : "started"
2026
+ });
2027
+ }
2028
+ let operationTerminalRequired = authoritativeStatus === "succeeded" || authoritativeStatus === "cancelled";
2029
+ if (authoritativeStatus === "failed") {
2030
+ let lastFailure;
2031
+ for (const event of loaded.events) {
2032
+ if (event.type === "node.failed" && event.nodeID === node.id && event.attemptID === state?.attemptID) {
2033
+ lastFailure = event;
2034
+ }
2035
+ }
2036
+ const retryRemains = options.closingStatus === undefined && lastFailure !== undefined && state !== undefined && retryAllowed(lastFailure.errorClass, state.attempts, limit);
2037
+ operationTerminalRequired = !retryRemains;
2038
+ }
2039
+ if (authoritativeStatus === "pending" && options.closingStatus !== undefined) {
2040
+ operationTerminalRequired = true;
2041
+ }
2042
+ return {
2043
+ operationID: operation,
2044
+ operationQueuedRequired: nodeIndex > 0,
2045
+ operationTerminalRequired,
2046
+ attempts: attemptExpectations
2047
+ };
2048
+ });
2049
+ }
1956
2050
  function createGraphRuntime(deps) {
1957
- const { spec, journal, lease, effects, holder } = deps;
2051
+ const { spec, journal, lease, effects, holder, lifecycle } = deps;
1958
2052
  const retryLimit = deps.retryLimit ?? DEFAULT_RETRY_LIMIT;
1959
2053
  const runID = spec.runID;
2054
+ const rootNode = spec.nodes[0];
2055
+ const rootOperationID = operationID(runID, rootNode.id);
1960
2056
  let writer;
1961
2057
  let leaseHandle;
2058
+ let lifecycleWritable = lifecycle !== undefined;
2059
+ async function recordLifecycleEvent(event) {
2060
+ if (lifecycle === undefined || !lifecycleWritable) {
2061
+ return;
2062
+ }
2063
+ try {
2064
+ const result = await lifecycle.append(event);
2065
+ lifecycleWritable = result.ok && result.status.state === "healthy";
2066
+ } catch {
2067
+ lifecycleWritable = false;
2068
+ }
2069
+ }
2070
+ async function verifyLifecycle(loaded, options = {}) {
2071
+ if (lifecycle === undefined || !lifecycleWritable) {
2072
+ return;
2073
+ }
2074
+ try {
2075
+ const status = await lifecycle.verifyOperations(graphLifecycleExpectations(loaded, {
2076
+ ...options,
2077
+ retryLimit
2078
+ }), {
2079
+ requireCompleteObservedWork: options.closingStatus !== undefined
2080
+ });
2081
+ lifecycleWritable = status.state === "healthy";
2082
+ } catch {
2083
+ lifecycleWritable = false;
2084
+ }
2085
+ }
1962
2086
  async function ensureStarted() {
1963
2087
  if (writer !== undefined) {
1964
2088
  return writer;
1965
2089
  }
1966
2090
  leaseHandle = await lease.acquire(runID, holder);
1967
- writer = await journal.exists(runID) ? await journal.takeOver(runID) : await journal.create(spec);
2091
+ const existed = await journal.exists(runID);
2092
+ writer = existed ? await journal.takeOver(runID) : await journal.create(spec);
1968
2093
  const loaded = await journal.load(runID);
1969
2094
  if (loaded.events.length === 0) {
1970
2095
  await journal.append(writer, runStarted(runID, 0, specDigest(spec)), 0);
2096
+ for (const node of spec.nodes) {
2097
+ if (node.id === rootNode.id) {
2098
+ continue;
2099
+ }
2100
+ await recordLifecycleEvent({
2101
+ type: "operation.queued",
2102
+ operationID: operationID(runID, node.id),
2103
+ taskID: node.anchor.taskID,
2104
+ kind: "graph-node",
2105
+ roleID: node.role,
2106
+ parentOperationID: rootOperationID,
2107
+ dependsOnOperationIDs: node.dependsOn.map((nodeID) => operationID(runID, nodeID))
2108
+ });
2109
+ }
2110
+ } else if (lifecycle !== undefined) {
2111
+ await verifyLifecycle(loaded);
1971
2112
  }
1972
2113
  return writer;
1973
2114
  }
@@ -1980,8 +2121,24 @@ function createGraphRuntime(deps) {
1980
2121
  await lease.release(handle);
1981
2122
  }
1982
2123
  async function dispatchNode2(handle, loaded, node, operation, attempt, attemptIndex) {
2124
+ const attemptRef = {
2125
+ operationID: operation,
2126
+ taskID: node.anchor.taskID,
2127
+ attemptID: attempt,
2128
+ attempt: attemptIndex,
2129
+ modelIndex: 0,
2130
+ retryIndex: attemptIndex
2131
+ };
2132
+ await recordLifecycleEvent({
2133
+ type: "attempt.queued",
2134
+ ...attemptRef
2135
+ });
1983
2136
  const intentSeq = loaded.events.length;
1984
2137
  await journal.append(handle, nodeDispatched(runID, intentSeq, node.id, operation, attempt), intentSeq);
2138
+ await recordLifecycleEvent({
2139
+ type: "attempt.started",
2140
+ ...attemptRef
2141
+ });
1985
2142
  const receipt = await effects.execute({
1986
2143
  runID,
1987
2144
  node,
@@ -1991,8 +2148,40 @@ function createGraphRuntime(deps) {
1991
2148
  const receiptSeq = intentSeq + 1;
1992
2149
  if (receipt.outcome === "succeeded") {
1993
2150
  await journal.append(handle, nodeSucceeded(runID, receiptSeq, node.id, operation, attempt, receipt.artifact), receiptSeq);
2151
+ await recordLifecycleEvent({
2152
+ type: "attempt.terminal",
2153
+ ...attemptRef,
2154
+ outcome: "succeeded",
2155
+ continuation: "none"
2156
+ });
2157
+ await recordLifecycleEvent({
2158
+ type: "operation.terminal",
2159
+ operationID: operation,
2160
+ taskID: node.anchor.taskID,
2161
+ outcome: "succeeded",
2162
+ attemptCount: attemptIndex + 1
2163
+ });
1994
2164
  } else {
1995
2165
  await journal.append(handle, nodeFailed(runID, receiptSeq, node.id, operation, attempt, receipt.errorClass), receiptSeq);
2166
+ const willRetry = retryAllowed(receipt.errorClass, attemptIndex + 1, retryLimit);
2167
+ const failure = graphFailure(receipt.errorClass, willRetry);
2168
+ await recordLifecycleEvent({
2169
+ type: "attempt.terminal",
2170
+ ...attemptRef,
2171
+ outcome: graphFailureOutcome(receipt.errorClass),
2172
+ continuation: willRetry ? "retry-same-effect" : "none",
2173
+ failure
2174
+ });
2175
+ if (!willRetry) {
2176
+ await recordLifecycleEvent({
2177
+ type: "operation.terminal",
2178
+ operationID: operation,
2179
+ taskID: node.anchor.taskID,
2180
+ outcome: graphFailureOutcome(receipt.errorClass),
2181
+ attemptCount: attemptIndex + 1,
2182
+ failure
2183
+ });
2184
+ }
1996
2185
  }
1997
2186
  return {
1998
2187
  kind: "dispatched",
@@ -2003,16 +2192,44 @@ function createGraphRuntime(deps) {
2003
2192
  backoffMs: backoffDelayMs(attemptIndex)
2004
2193
  };
2005
2194
  }
2006
- async function finishRun(handle, head, state) {
2195
+ async function finishRun(handle, loaded) {
2196
+ const { state } = loaded;
2197
+ const head = loaded.events.length;
2007
2198
  const statuses = Object.values(state.nodes).map((node) => node.status);
2008
2199
  if (statuses.every((s) => s === "succeeded" || s === "cancelled")) {
2009
2200
  await journal.append(handle, runCompleted(runID, head), head);
2201
+ await verifyLifecycle(loaded, { closingStatus: "completed" });
2202
+ await recordLifecycleEvent({
2203
+ type: "run.terminal",
2204
+ outcome: "succeeded",
2205
+ operationCount: spec.nodes.length
2206
+ });
2010
2207
  return { kind: "completed" };
2011
2208
  }
2012
2209
  if (statuses.some((s) => s === "running")) {
2013
2210
  return { kind: "blocked", reason: "in-flight-effect" };
2014
2211
  }
2015
2212
  await journal.append(handle, runFailed(runID, head), head);
2213
+ for (const node of spec.nodes) {
2214
+ if (state.nodes[node.id]?.status !== "pending") {
2215
+ continue;
2216
+ }
2217
+ await recordLifecycleEvent({
2218
+ type: "operation.terminal",
2219
+ operationID: operationID(runID, node.id),
2220
+ taskID: node.anchor.taskID,
2221
+ outcome: "blocked",
2222
+ attemptCount: 0,
2223
+ failure: { class: "dependency-failed", retryable: false }
2224
+ });
2225
+ }
2226
+ await verifyLifecycle(loaded, { closingStatus: "failed" });
2227
+ await recordLifecycleEvent({
2228
+ type: "run.terminal",
2229
+ outcome: "failed",
2230
+ operationCount: spec.nodes.length,
2231
+ failure: { class: "unknown", retryable: false }
2232
+ });
2016
2233
  return { kind: "failed" };
2017
2234
  }
2018
2235
  return {
@@ -2042,7 +2259,7 @@ function createGraphRuntime(deps) {
2042
2259
  const nextAttemptID = attemptID(retry.operationID, retry.attempt);
2043
2260
  return dispatchNode2(handle, loaded, node, retry.operationID, nextAttemptID, retry.attempt);
2044
2261
  }
2045
- const outcome = await finishRun(handle, loaded.events.length, loaded.state);
2262
+ const outcome = await finishRun(handle, loaded);
2046
2263
  if (outcome.kind === "completed" || outcome.kind === "failed") {
2047
2264
  await releaseLeaseIfHeld();
2048
2265
  }
@@ -2284,7 +2501,126 @@ function toErrorOutcome(error) {
2284
2501
  throw error;
2285
2502
  }
2286
2503
  function createGraphIngress(deps) {
2287
- const { policy, journal, lease, effects, holder } = deps;
2504
+ const {
2505
+ policy,
2506
+ journal,
2507
+ lease,
2508
+ effects,
2509
+ holder,
2510
+ lifecycleRecorder,
2511
+ lifecycleSource
2512
+ } = deps;
2513
+ const activeLifecycleRuns = new Map;
2514
+ function unavailableRecording(runID) {
2515
+ return {
2516
+ runID,
2517
+ state: "unavailable",
2518
+ lastCommittedHead: 0,
2519
+ failure: { code: "unavailable", recoverable: true, runID }
2520
+ };
2521
+ }
2522
+ async function beginLifecycle(spec) {
2523
+ if (lifecycleRecorder === undefined) {
2524
+ return {};
2525
+ }
2526
+ if (lifecycleSource === undefined) {
2527
+ return { status: unavailableRecording(spec.runID) };
2528
+ }
2529
+ let begun;
2530
+ try {
2531
+ const root = spec.nodes[0];
2532
+ begun = await lifecycleRecorder.beginRun({
2533
+ runID: spec.runID,
2534
+ source: lifecycleSource,
2535
+ executionAuthority: "graph",
2536
+ root: {
2537
+ operationID: operationID(spec.runID, root.id),
2538
+ taskID: root.anchor.taskID,
2539
+ kind: "graph-node",
2540
+ roleID: root.role
2541
+ }
2542
+ });
2543
+ } catch {
2544
+ return { status: unavailableRecording(spec.runID) };
2545
+ }
2546
+ if (!begun.ok) {
2547
+ return { status: begun.status };
2548
+ }
2549
+ activeLifecycleRuns.set(spec.runID, begun.recorder);
2550
+ return { recorder: begun.recorder, status: begun.status };
2551
+ }
2552
+ async function continueLifecycle(runID) {
2553
+ const active = activeLifecycleRuns.get(runID);
2554
+ if (active !== undefined) {
2555
+ return { recorder: active, status: active.status() };
2556
+ }
2557
+ if (lifecycleRecorder === undefined || lifecycleSource === undefined) {
2558
+ return lifecycleRecorder === undefined ? {} : { status: unavailableRecording(runID) };
2559
+ }
2560
+ if (lifecycleRecorder.continueRun === undefined) {
2561
+ return { status: unavailableRecording(runID) };
2562
+ }
2563
+ let continued;
2564
+ try {
2565
+ continued = await lifecycleRecorder.continueRun(runID);
2566
+ } catch {
2567
+ return { status: unavailableRecording(runID) };
2568
+ }
2569
+ if (!continued.ok) {
2570
+ return { status: continued.status };
2571
+ }
2572
+ activeLifecycleRuns.set(runID, continued.recorder);
2573
+ return {
2574
+ recorder: continued.recorder,
2575
+ status: continued.status
2576
+ };
2577
+ }
2578
+ async function appendLifecycle(recorder, event) {
2579
+ if (recorder === undefined) {
2580
+ return;
2581
+ }
2582
+ try {
2583
+ await recorder.append(event);
2584
+ } catch {}
2585
+ }
2586
+ async function finishObservation(runID, recorder, terminal) {
2587
+ if (recorder === undefined) {
2588
+ return;
2589
+ }
2590
+ let status = recorder.status();
2591
+ if (terminal || status.state !== "healthy") {
2592
+ activeLifecycleRuns.delete(runID);
2593
+ try {
2594
+ status = await recorder.release();
2595
+ } catch {
2596
+ status = unavailableRecording(runID);
2597
+ }
2598
+ }
2599
+ return status;
2600
+ }
2601
+ async function abandonObservation(runID, recorder) {
2602
+ activeLifecycleRuns.delete(runID);
2603
+ try {
2604
+ await recorder.release();
2605
+ } catch {}
2606
+ }
2607
+ async function verifyObservation(runID, observation, expectations, verificationOptions = {}) {
2608
+ const recorder = observation.recorder;
2609
+ if (recorder === undefined) {
2610
+ return observation;
2611
+ }
2612
+ try {
2613
+ const status = await recorder.verifyOperations(expectations, verificationOptions);
2614
+ if (status.state === "healthy") {
2615
+ return { recorder, status };
2616
+ }
2617
+ await abandonObservation(runID, recorder);
2618
+ return { status };
2619
+ } catch {
2620
+ await abandonObservation(runID, recorder);
2621
+ return { status: unavailableRecording(runID) };
2622
+ }
2623
+ }
2288
2624
  function taskGate(taskID) {
2289
2625
  const decision = decideIngress(policy, taskID);
2290
2626
  return decision.route === "legacy" ? {
@@ -2303,21 +2639,33 @@ function createGraphIngress(deps) {
2303
2639
  if (blocked !== undefined) {
2304
2640
  return blocked;
2305
2641
  }
2642
+ let observation;
2306
2643
  try {
2307
2644
  const spec = compileSddTask(task);
2645
+ observation = await beginLifecycle(spec);
2308
2646
  const runtime = createGraphRuntime({
2309
2647
  spec,
2310
2648
  journal,
2311
2649
  lease,
2312
2650
  effects,
2313
- holder
2651
+ holder,
2652
+ ...observation.recorder === undefined ? {} : { lifecycle: observation.recorder }
2314
2653
  });
2654
+ const step = await runtime.step();
2655
+ const recording = await finishObservation(spec.runID, observation.recorder, step.kind === "completed" || step.kind === "failed") ?? observation.status;
2315
2656
  return {
2316
2657
  kind: "accepted",
2317
2658
  runID: spec.runID,
2318
- step: await runtime.step()
2659
+ step,
2660
+ ...recording === undefined ? {} : { recording }
2319
2661
  };
2320
2662
  } catch (error) {
2663
+ if (observation?.recorder !== undefined) {
2664
+ activeLifecycleRuns.delete(observation.recorder.runID);
2665
+ try {
2666
+ await observation.recorder.release();
2667
+ } catch {}
2668
+ }
2321
2669
  return toErrorOutcome(error);
2322
2670
  }
2323
2671
  },
@@ -2326,17 +2674,36 @@ function createGraphIngress(deps) {
2326
2674
  if (blocked !== undefined) {
2327
2675
  return blocked;
2328
2676
  }
2677
+ let observation;
2329
2678
  try {
2330
2679
  const loaded = await journal.load(runID);
2680
+ observation = loaded.state.status === "running" ? await continueLifecycle(runID) : {};
2681
+ if (loaded.state.status === "running") {
2682
+ observation = await verifyObservation(runID, observation, graphLifecycleExpectations(loaded));
2683
+ }
2331
2684
  const runtime = createGraphRuntime({
2332
2685
  spec: loaded.spec,
2333
2686
  journal,
2334
2687
  lease,
2335
2688
  effects,
2336
- holder
2689
+ holder,
2690
+ ...observation.recorder === undefined ? {} : { lifecycle: observation.recorder }
2337
2691
  });
2338
- return { kind: "accepted", runID, step: await runtime.step() };
2692
+ const step = await runtime.step();
2693
+ const recording = await finishObservation(runID, observation.recorder, step.kind === "completed" || step.kind === "failed") ?? observation.status;
2694
+ return {
2695
+ kind: "accepted",
2696
+ runID,
2697
+ step,
2698
+ ...recording === undefined ? {} : { recording }
2699
+ };
2339
2700
  } catch (error) {
2701
+ if (observation?.recorder !== undefined) {
2702
+ activeLifecycleRuns.delete(runID);
2703
+ try {
2704
+ await observation.recorder.release();
2705
+ } catch {}
2706
+ }
2340
2707
  return toErrorOutcome(error);
2341
2708
  }
2342
2709
  },
@@ -2379,6 +2746,7 @@ function createGraphIngress(deps) {
2379
2746
  if (blocked !== undefined) {
2380
2747
  return blocked;
2381
2748
  }
2749
+ let observation;
2382
2750
  try {
2383
2751
  const loaded = await journal.load(runID);
2384
2752
  const plan2 = planCancellation(loaded.state);
@@ -2390,24 +2758,95 @@ function createGraphIngress(deps) {
2390
2758
  abortNodeIDs: []
2391
2759
  };
2392
2760
  }
2761
+ observation = await continueLifecycle(runID);
2762
+ const observationsBeforeCancellation = graphLifecycleExpectations(loaded);
2763
+ observation = await verifyObservation(runID, observation, observationsBeforeCancellation);
2393
2764
  const leaseHandle = await lease.acquire(runID, holder);
2394
- const handle = await journal.takeOver(runID);
2395
- let seq = loaded.events.length;
2396
- for (const nodeID of plan2.cancelNodeIDs) {
2397
- await journal.append(handle, nodeCancelled(runID, seq, nodeID), seq);
2398
- seq += 1;
2399
- }
2400
- if (plan2.cancelRun) {
2401
- await journal.append(handle, runCancelled(runID, seq), seq);
2765
+ try {
2766
+ const handle = await journal.takeOver(runID);
2767
+ let seq = loaded.events.length;
2768
+ for (const nodeID of plan2.cancelNodeIDs) {
2769
+ await journal.append(handle, nodeCancelled(runID, seq, nodeID), seq);
2770
+ seq += 1;
2771
+ const node = loaded.spec.nodes.find((candidate) => candidate.id === nodeID);
2772
+ const state = loaded.state.nodes[nodeID];
2773
+ if (node === undefined || state === undefined) {
2774
+ continue;
2775
+ }
2776
+ if (state.status === "running" && state.operationID !== undefined && state.attemptID !== undefined && state.attempts > 0) {
2777
+ const attempt = state.attempts - 1;
2778
+ await appendLifecycle(observation.recorder, {
2779
+ type: "attempt.terminal",
2780
+ operationID: state.operationID,
2781
+ taskID: node.anchor.taskID,
2782
+ attemptID: state.attemptID,
2783
+ attempt,
2784
+ modelIndex: 0,
2785
+ retryIndex: attempt,
2786
+ outcome: "cancelled",
2787
+ continuation: "none",
2788
+ failure: { class: "cancelled", retryable: false }
2789
+ });
2790
+ }
2791
+ await appendLifecycle(observation.recorder, {
2792
+ type: "operation.terminal",
2793
+ operationID: state.operationID ?? operationID(runID, nodeID),
2794
+ taskID: node.anchor.taskID,
2795
+ outcome: "cancelled",
2796
+ attemptCount: state.attempts,
2797
+ failure: { class: "cancelled", retryable: false }
2798
+ });
2799
+ }
2800
+ if (plan2.cancelRun) {
2801
+ await journal.append(handle, runCancelled(runID, seq), seq);
2802
+ for (const node of loaded.spec.nodes) {
2803
+ const state = loaded.state.nodes[node.id];
2804
+ if (state?.status !== "failed")
2805
+ continue;
2806
+ const operation = state.operationID ?? operationID(runID, node.id);
2807
+ const awaitingRetry = observationsBeforeCancellation.some((expected) => expected.operationID === operation && !expected.operationTerminalRequired);
2808
+ if (!awaitingRetry)
2809
+ continue;
2810
+ await appendLifecycle(observation.recorder, {
2811
+ type: "operation.terminal",
2812
+ operationID: operation,
2813
+ taskID: node.anchor.taskID,
2814
+ outcome: "cancelled",
2815
+ attemptCount: state.attempts,
2816
+ failure: { class: "cancelled", retryable: false }
2817
+ });
2818
+ }
2819
+ observation = await verifyObservation(runID, observation, graphLifecycleExpectations(loaded, {
2820
+ closingStatus: "cancelled",
2821
+ cancelledNodeIDs: plan2.cancelNodeIDs
2822
+ }), { requireCompleteObservedWork: true });
2823
+ if (observation.recorder !== undefined) {
2824
+ await appendLifecycle(observation.recorder, {
2825
+ type: "run.terminal",
2826
+ outcome: "cancelled",
2827
+ operationCount: loaded.spec.nodes.length,
2828
+ failure: { class: "cancelled", retryable: false }
2829
+ });
2830
+ }
2831
+ }
2832
+ } finally {
2833
+ await lease.release(leaseHandle);
2402
2834
  }
2403
- await lease.release(leaseHandle);
2835
+ const recording = await finishObservation(runID, observation.recorder, plan2.cancelRun) ?? observation.status;
2404
2836
  return {
2405
2837
  kind: "cancelled",
2406
2838
  runID,
2407
2839
  cancelledNodeIDs: plan2.cancelNodeIDs,
2408
- abortNodeIDs: plan2.abortNodeIDs
2840
+ abortNodeIDs: plan2.abortNodeIDs,
2841
+ ...recording === undefined ? {} : { recording }
2409
2842
  };
2410
2843
  } catch (error) {
2844
+ if (observation?.recorder !== undefined) {
2845
+ activeLifecycleRuns.delete(runID);
2846
+ try {
2847
+ await observation.recorder.release();
2848
+ } catch {}
2849
+ }
2411
2850
  return toErrorOutcome(error);
2412
2851
  }
2413
2852
  }
@@ -3184,14 +3623,46 @@ function createGraphOperator(options) {
3184
3623
  evaluateCutoverGate,
3185
3624
  createIngress(policy, config) {
3186
3625
  const root = journalRoot(config);
3626
+ const lifecycleRecorder = options.createLifecycleRecorder === undefined ? options.lifecycleRecorder : (() => {
3627
+ let recorder;
3628
+ const getRecorder = () => {
3629
+ recorder ??= options.createLifecycleRecorder(config);
3630
+ return recorder;
3631
+ };
3632
+ return {
3633
+ beginRun: (input) => getRecorder().beginRun(input),
3634
+ async continueRun(runID) {
3635
+ const continuation = getRecorder().continueRun;
3636
+ if (continuation === undefined) {
3637
+ return {
3638
+ ok: false,
3639
+ status: {
3640
+ runID,
3641
+ state: "unavailable",
3642
+ lastCommittedHead: 0,
3643
+ failure: {
3644
+ code: "unavailable",
3645
+ recoverable: true,
3646
+ runID
3647
+ }
3648
+ }
3649
+ };
3650
+ }
3651
+ return continuation(runID);
3652
+ }
3653
+ };
3654
+ })();
3187
3655
  const effects = resolveGraphEngine(config) === "langgraph" ? createLangGraphEffects(options.nodeExecutor) : inertGraphEffects();
3188
- return createGraphIngress({
3656
+ const ingressOptions = {
3189
3657
  policy,
3190
3658
  journal: createFsGraphJournal(root),
3191
3659
  lease: createWorkspaceLease(root),
3192
3660
  effects,
3193
- holder: options.holder
3194
- });
3661
+ holder: options.holder,
3662
+ lifecycleRecorder,
3663
+ lifecycleSource: options.lifecycleSource
3664
+ };
3665
+ return createGraphIngress(ingressOptions);
3195
3666
  }
3196
3667
  };
3197
3668
  }
@@ -3311,6 +3782,7 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
3311
3782
  "storage",
3312
3783
  "memory",
3313
3784
  "graph",
3785
+ "lifecycle",
3314
3786
  "orchestrator",
3315
3787
  "configVersion"
3316
3788
  ]
@@ -3409,6 +3881,8 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
3409
3881
  ]
3410
3882
  ],
3411
3883
  ["graph.worktrees", ["enabled"]],
3884
+ ["lifecycle", ["root", "retention"]],
3885
+ ["lifecycle.retention", ["maxTerminalRuns", "maxAgeDays", "maxBytes"]],
3412
3886
  ["orchestrator", ["roles", "primary", "maxRetries", "maxModelsPerNode"]],
3413
3887
  ["orchestrator.primary", ["executionMode", "model", "fallbacks"]],
3414
3888
  ["orchestrator.primary.fallbacks[]", MODEL_REF_KEYS]
@@ -5658,46 +6132,922 @@ function parseSoakCertificate(value, expectedPolicy, expectedLedgerDigest) {
5658
6132
  return certificate;
5659
6133
  }
5660
6134
 
5661
- // src/local/cacheAdapter.ts
5662
- import * as fs2 from "node:fs";
5663
- import * as os from "node:os";
5664
- import * as path from "node:path";
5665
- var SCOPE_PREFIX = "@jmanuelcorral/openteam";
5666
- var SCOPE_SEGMENTS = SCOPE_PREFIX.split("/");
5667
- function resolveCacheRoot(override) {
5668
- if (override !== undefined && override.trim() !== "") {
5669
- return override;
6135
+ // src/lifecycle/root.ts
6136
+ import { isAbsolute, normalize, relative, resolve as resolve3 } from "node:path";
6137
+
6138
+ // src/messages/lifecycleStorage.ts
6139
+ var lifecycleStorageMessages = {
6140
+ configuredRootEscapesWorkspace: "Configured lifecycle root must remain beneath the workspace root."
6141
+ };
6142
+
6143
+ // src/lifecycle/root.ts
6144
+ var resolveLifecycleRoot = (options) => {
6145
+ const workspaceRoot = resolve3(options.workspaceRoot);
6146
+ if (isAbsolute(options.configuredRoot)) {
6147
+ return normalize(resolve3(options.configuredRoot));
6148
+ }
6149
+ const resolved = resolve3(workspaceRoot, options.configuredRoot);
6150
+ const workspaceRelative = relative(workspaceRoot, resolved);
6151
+ if (workspaceRelative === ".." || workspaceRelative.startsWith(`..\\`) || workspaceRelative.startsWith("../") || isAbsolute(workspaceRelative)) {
6152
+ throw new Error(lifecycleStorageMessages.configuredRootEscapesWorkspace);
6153
+ }
6154
+ return normalize(resolved);
6155
+ };
6156
+
6157
+ // src/lifecycle/schema.ts
6158
+ import { z as z17 } from "zod";
6159
+ var nonEmptyString = z17.string().min(1);
6160
+ var nonNegativeInteger = z17.number().int().nonnegative();
6161
+ var positiveInteger = z17.number().int().positive();
6162
+ var modelSelectionSchema2 = z17.object({
6163
+ providerID: nonEmptyString,
6164
+ modelID: nonEmptyString
6165
+ }).strict();
6166
+ var lifecycleFailureSchema = z17.object({
6167
+ class: z17.enum([
6168
+ "routing-blocked",
6169
+ "create-rejected",
6170
+ "create-no-id",
6171
+ "prompt-rejected",
6172
+ "prompt-timeout",
6173
+ "prompt-rate-limited",
6174
+ "prompt-auth",
6175
+ "prompt-content-filter",
6176
+ "prompt-context-length",
6177
+ "prompt-validation",
6178
+ "prompt-connection",
6179
+ "prompt-server",
6180
+ "prompt-stop-unconfirmed",
6181
+ "report-incomplete",
6182
+ "report-not-durable",
6183
+ "budget-exhausted",
6184
+ "dependency-failed",
6185
+ "cancelled",
6186
+ "interrupted",
6187
+ "unknown"
6188
+ ]),
6189
+ statusCode: z17.number().int().min(100).max(599).optional(),
6190
+ retryable: z17.boolean().optional()
6191
+ }).strict();
6192
+ var lifecycleRootOperationSchema = z17.object({
6193
+ operationID: nonEmptyString,
6194
+ taskID: nonEmptyString,
6195
+ kind: z17.enum(["coordinator", "graph-node"]),
6196
+ roleID: nonEmptyString,
6197
+ agentID: nonEmptyString.optional(),
6198
+ sessionID: nonEmptyString.optional()
6199
+ }).strict();
6200
+ var lifecycleRunStartInputSchema = z17.object({
6201
+ runID: nonEmptyString,
6202
+ source: z17.enum(["plugin", "cli"]),
6203
+ executionAuthority: z17.enum(["coordinator", "graph"]),
6204
+ root: lifecycleRootOperationSchema
6205
+ }).strict();
6206
+ var LifecycleRunMetadataSchema = lifecycleRunStartInputSchema.extend({
6207
+ version: z17.literal(1),
6208
+ createdAt: nonNegativeInteger
6209
+ }).strict();
6210
+ var attemptRefShape = {
6211
+ operationID: nonEmptyString,
6212
+ taskID: nonEmptyString,
6213
+ attemptID: nonEmptyString,
6214
+ attempt: nonNegativeInteger,
6215
+ modelIndex: nonNegativeInteger,
6216
+ retryIndex: nonNegativeInteger
6217
+ };
6218
+ var eventEnvelopeShape = {
6219
+ v: z17.literal(1),
6220
+ seq: nonNegativeInteger,
6221
+ runID: nonEmptyString,
6222
+ at: nonNegativeInteger
6223
+ };
6224
+ var runStartedBodyShape = {
6225
+ type: z17.literal("run.started"),
6226
+ source: z17.enum(["plugin", "cli"]),
6227
+ executionAuthority: z17.enum(["coordinator", "graph"]),
6228
+ root: lifecycleRootOperationSchema
6229
+ };
6230
+ var operationQueuedBodyShape = {
6231
+ type: z17.literal("operation.queued"),
6232
+ operationID: nonEmptyString,
6233
+ taskID: nonEmptyString,
6234
+ kind: z17.enum(["role", "graph-node"]),
6235
+ roleID: nonEmptyString,
6236
+ agentID: nonEmptyString.optional(),
6237
+ parentOperationID: nonEmptyString,
6238
+ dependsOnOperationIDs: z17.array(nonEmptyString).readonly()
6239
+ };
6240
+ var modelSelectedBodyShape = {
6241
+ type: z17.literal("attempt.model-selected"),
6242
+ ...attemptRefShape,
6243
+ decisionID: nonEmptyString,
6244
+ model: modelSelectionSchema2,
6245
+ routeKind: z17.enum(["local", "frontier"]),
6246
+ selectionCause: z17.enum([
6247
+ "initial",
6248
+ "retry-incomplete",
6249
+ "retry-transport",
6250
+ "fallback-incomplete",
6251
+ "fallback-transport"
6252
+ ]),
6253
+ runtimeID: nonEmptyString.optional()
6254
+ };
6255
+ var attemptQueuedBodyShape = {
6256
+ type: z17.literal("attempt.queued"),
6257
+ ...attemptRefShape,
6258
+ runtimeID: nonEmptyString.optional()
6259
+ };
6260
+ var attemptStartedBodyShape = {
6261
+ type: z17.literal("attempt.started"),
6262
+ ...attemptRefShape
6263
+ };
6264
+ var sessionCreatedBodyShape = {
6265
+ type: z17.literal("session.created"),
6266
+ ...attemptRefShape,
6267
+ sessionID: nonEmptyString,
6268
+ parentSessionID: nonEmptyString.optional()
6269
+ };
6270
+ var attemptTerminalBodyShape = {
6271
+ type: z17.literal("attempt.terminal"),
6272
+ ...attemptRefShape,
6273
+ outcome: z17.enum([
6274
+ "succeeded",
6275
+ "failed",
6276
+ "cancelled",
6277
+ "interrupted",
6278
+ "unknown"
6279
+ ]),
6280
+ continuation: z17.enum([
6281
+ "none",
6282
+ "retry-same-model",
6283
+ "fallback-next-model",
6284
+ "retry-same-effect"
6285
+ ]),
6286
+ sessionID: nonEmptyString.optional(),
6287
+ failure: lifecycleFailureSchema.optional()
6288
+ };
6289
+ var operationTerminalBodyShape = {
6290
+ type: z17.literal("operation.terminal"),
6291
+ operationID: nonEmptyString,
6292
+ taskID: nonEmptyString,
6293
+ outcome: z17.enum([
6294
+ "succeeded",
6295
+ "failed",
6296
+ "cancelled",
6297
+ "interrupted",
6298
+ "unknown",
6299
+ "blocked"
6300
+ ]),
6301
+ attemptCount: nonNegativeInteger,
6302
+ finalSessionID: nonEmptyString.optional(),
6303
+ failure: lifecycleFailureSchema.optional()
6304
+ };
6305
+ var runTerminalBodyShape = {
6306
+ type: z17.literal("run.terminal"),
6307
+ outcome: z17.enum([
6308
+ "succeeded",
6309
+ "failed",
6310
+ "cancelled",
6311
+ "interrupted",
6312
+ "unknown"
6313
+ ]),
6314
+ operationCount: nonNegativeInteger,
6315
+ failure: lifecycleFailureSchema.optional()
6316
+ };
6317
+ var bodySchemas = [
6318
+ z17.object(runStartedBodyShape).strict(),
6319
+ z17.object(operationQueuedBodyShape).strict(),
6320
+ z17.object(modelSelectedBodyShape).strict(),
6321
+ z17.object(attemptQueuedBodyShape).strict(),
6322
+ z17.object(attemptStartedBodyShape).strict(),
6323
+ z17.object(sessionCreatedBodyShape).strict(),
6324
+ z17.object(attemptTerminalBodyShape).strict(),
6325
+ z17.object(operationTerminalBodyShape).strict(),
6326
+ z17.object(runTerminalBodyShape).strict()
6327
+ ];
6328
+ var appendBodySchemas = bodySchemas.slice(1);
6329
+ var LifecycleAppendEventSchema = z17.discriminatedUnion("type", appendBodySchemas);
6330
+ var LifecycleEventSchema = z17.discriminatedUnion("type", [
6331
+ z17.object({ ...eventEnvelopeShape, ...runStartedBodyShape }).strict(),
6332
+ z17.object({ ...eventEnvelopeShape, ...operationQueuedBodyShape }).strict(),
6333
+ z17.object({ ...eventEnvelopeShape, ...modelSelectedBodyShape }).strict(),
6334
+ z17.object({ ...eventEnvelopeShape, ...attemptQueuedBodyShape }).strict(),
6335
+ z17.object({ ...eventEnvelopeShape, ...attemptStartedBodyShape }).strict(),
6336
+ z17.object({ ...eventEnvelopeShape, ...sessionCreatedBodyShape }).strict(),
6337
+ z17.object({ ...eventEnvelopeShape, ...attemptTerminalBodyShape }).strict(),
6338
+ z17.object({ ...eventEnvelopeShape, ...operationTerminalBodyShape }).strict(),
6339
+ z17.object({ ...eventEnvelopeShape, ...runTerminalBodyShape }).strict()
6340
+ ]);
6341
+ var LifecycleWriterLeaseInputSchema = z17.object({
6342
+ writer: z17.object({
6343
+ processID: positiveInteger,
6344
+ processInstanceID: nonEmptyString
6345
+ }).strict(),
6346
+ now: nonNegativeInteger,
6347
+ leaseDurationMs: positiveInteger
6348
+ }).strict();
6349
+ var LifecycleWriterLeaseSchema = z17.object({
6350
+ version: z17.literal(1),
6351
+ runID: nonEmptyString,
6352
+ writerToken: nonEmptyString,
6353
+ processID: positiveInteger,
6354
+ processInstanceID: nonEmptyString,
6355
+ acquiredAt: nonNegativeInteger,
6356
+ heartbeatAt: nonNegativeInteger,
6357
+ expiresAt: nonNegativeInteger
6358
+ }).strict().superRefine((lease, context) => {
6359
+ if (lease.heartbeatAt < lease.acquiredAt) {
6360
+ context.addIssue({
6361
+ code: "custom",
6362
+ path: ["heartbeatAt"],
6363
+ message: "heartbeatAt precedes acquiredAt"
6364
+ });
5670
6365
  }
5671
- const base = process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache");
5672
- return path.join(base, "opencode", "packages");
5673
- }
5674
- function readJsonSafe(filePath) {
5675
- try {
5676
- const text = fs2.readFileSync(filePath, "utf8");
5677
- const parsed = JSON.parse(text);
5678
- if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
5679
- return parsed;
5680
- }
5681
- return;
5682
- } catch {
5683
- return;
6366
+ if (lease.expiresAt <= lease.heartbeatAt) {
6367
+ context.addIssue({
6368
+ code: "custom",
6369
+ path: ["expiresAt"],
6370
+ message: "expiresAt must follow heartbeatAt"
6371
+ });
6372
+ }
6373
+ });
6374
+ function omitBoundedLifecycleSource(value) {
6375
+ if (Array.isArray(value)) {
6376
+ return value.map(omitBoundedLifecycleSource);
6377
+ }
6378
+ if (value === null || typeof value !== "object") {
6379
+ return value;
5684
6380
  }
6381
+ return Object.fromEntries(Object.entries(value).flatMap(([key, child]) => {
6382
+ if (key === "source" && (child === "plugin" || child === "cli" || child === "lifecycle")) {
6383
+ return [];
6384
+ }
6385
+ return [[key, omitBoundedLifecycleSource(child)]];
6386
+ }));
5685
6387
  }
5686
- function extractVersion(pkg) {
5687
- if (pkg === undefined)
5688
- return "";
5689
- const v = pkg.version;
5690
- return typeof v === "string" ? v : "";
6388
+ function assertLifecycleReferenceOnly(value) {
6389
+ assertReferenceOnly(omitBoundedLifecycleSource(value));
5691
6390
  }
5692
- function readDirectoryEntries(dir) {
5693
- try {
5694
- return fs2.readdirSync(dir, { withFileTypes: true });
5695
- } catch {
5696
- return [];
6391
+ function parseLifecycleRunStartInput(value) {
6392
+ const parsed = lifecycleRunStartInputSchema.parse(value);
6393
+ assertLifecycleReferenceOnly(parsed);
6394
+ return parsed;
6395
+ }
6396
+ function parseLifecycleRunMetadata(value) {
6397
+ const parsed = LifecycleRunMetadataSchema.parse(value);
6398
+ assertLifecycleReferenceOnly(parsed);
6399
+ return parsed;
6400
+ }
6401
+ function parseLifecycleAppendEvent(value) {
6402
+ const parsed = LifecycleAppendEventSchema.parse(value);
6403
+ assertLifecycleReferenceOnly(parsed);
6404
+ return parsed;
6405
+ }
6406
+ function parseLifecycleEvent(value) {
6407
+ const parsed = LifecycleEventSchema.parse(value);
6408
+ assertLifecycleReferenceOnly(parsed);
6409
+ return parsed;
6410
+ }
6411
+ function parseLifecycleWriterLeaseInput(value) {
6412
+ return LifecycleWriterLeaseInputSchema.parse(value);
6413
+ }
6414
+ function parseLifecycleWriterLease(value) {
6415
+ const parsed = LifecycleWriterLeaseSchema.parse(value);
6416
+ assertLifecycleReferenceOnly(parsed);
6417
+ return parsed;
6418
+ }
6419
+
6420
+ // src/lifecycle/project.ts
6421
+ function operationForEvent(operations, event) {
6422
+ const operation = operations.get(event.operationID);
6423
+ if (operation === undefined) {
6424
+ throw new Error(`lifecycle event references unknown operation ${event.operationID}`);
6425
+ }
6426
+ if (operation.taskID !== event.taskID) {
6427
+ throw new Error(`lifecycle task identity changed for ${event.operationID}`);
5697
6428
  }
6429
+ return operation;
5698
6430
  }
5699
- function lstatPlainDirectory(dir) {
5700
- try {
6431
+ function attemptForEvent(operations, event) {
6432
+ const operation = operationForEvent(operations, event);
6433
+ const attempt = operation.attempts.find((candidate) => candidate.attemptID === event.attemptID);
6434
+ if (attempt === undefined) {
6435
+ throw new Error(`lifecycle event references unknown attempt ${event.attemptID}`);
6436
+ }
6437
+ if (attempt.attempt !== event.attempt || attempt.modelIndex !== event.modelIndex || attempt.retryIndex !== event.retryIndex) {
6438
+ throw new Error(`lifecycle attempt identity changed for ${event.attemptID}`);
6439
+ }
6440
+ return { operation, attempt };
6441
+ }
6442
+ function terminalOperationState(outcome) {
6443
+ if (outcome === undefined) {
6444
+ throw new Error("terminal lifecycle operation is missing its outcome");
6445
+ }
6446
+ return outcome;
6447
+ }
6448
+ var projectLifecyclePrefix = (input) => {
6449
+ const metadata = parseLifecycleRunMetadata(input.metadata);
6450
+ if (!Number.isInteger(input.cursor) || input.cursor < 0 || input.cursor > input.events.length) {
6451
+ throw new RangeError(`lifecycle cursor ${input.cursor} is outside 0..${input.events.length}`);
6452
+ }
6453
+ const events = input.events.map((event, index) => {
6454
+ const parsed = parseLifecycleEvent(event);
6455
+ if (parsed.seq !== index || parsed.runID !== metadata.runID) {
6456
+ throw new Error(`invalid lifecycle event envelope at sequence ${index}`);
6457
+ }
6458
+ return parsed;
6459
+ });
6460
+ const root = {
6461
+ operationID: metadata.root.operationID,
6462
+ taskID: metadata.root.taskID,
6463
+ kind: metadata.root.kind,
6464
+ roleID: metadata.root.roleID,
6465
+ ...metadata.root.agentID === undefined ? {} : { agentID: metadata.root.agentID },
6466
+ state: "waiting",
6467
+ dependsOnOperationIDs: [],
6468
+ attempts: []
6469
+ };
6470
+ const operations = new Map([
6471
+ [root.operationID, root]
6472
+ ]);
6473
+ const edges = [];
6474
+ let observedState = "pending";
6475
+ for (const event of events.slice(0, input.cursor)) {
6476
+ switch (event.type) {
6477
+ case "run.started": {
6478
+ if (event.root.operationID !== metadata.root.operationID || event.root.taskID !== metadata.root.taskID || event.source !== metadata.source || event.executionAuthority !== metadata.executionAuthority) {
6479
+ throw new Error("run.started does not match lifecycle metadata");
6480
+ }
6481
+ root.state = "running";
6482
+ observedState = "running";
6483
+ break;
6484
+ }
6485
+ case "operation.queued": {
6486
+ if (operations.has(event.operationID)) {
6487
+ throw new Error(`duplicate lifecycle operation ${event.operationID}`);
6488
+ }
6489
+ operations.set(event.operationID, {
6490
+ operationID: event.operationID,
6491
+ taskID: event.taskID,
6492
+ kind: event.kind,
6493
+ roleID: event.roleID,
6494
+ ...event.agentID === undefined ? {} : { agentID: event.agentID },
6495
+ state: "queued",
6496
+ parentOperationID: event.parentOperationID,
6497
+ dependsOnOperationIDs: [...event.dependsOnOperationIDs],
6498
+ attempts: [],
6499
+ queuedAt: event.at
6500
+ });
6501
+ edges.push({
6502
+ kind: "hierarchy",
6503
+ from: event.parentOperationID,
6504
+ to: event.operationID
6505
+ });
6506
+ for (const dependency of event.dependsOnOperationIDs) {
6507
+ edges.push({
6508
+ kind: "dependency",
6509
+ from: dependency,
6510
+ to: event.operationID
6511
+ });
6512
+ }
6513
+ break;
6514
+ }
6515
+ case "attempt.model-selected": {
6516
+ const operation = operationForEvent(operations, event);
6517
+ if (operation.attempts.some((attempt) => attempt.attemptID === event.attemptID || attempt.attempt === event.attempt)) {
6518
+ throw new Error(`duplicate lifecycle attempt ${event.attemptID}`);
6519
+ }
6520
+ operation.attempts.push({
6521
+ operationID: event.operationID,
6522
+ taskID: event.taskID,
6523
+ attemptID: event.attemptID,
6524
+ attempt: event.attempt,
6525
+ modelIndex: event.modelIndex,
6526
+ retryIndex: event.retryIndex,
6527
+ state: "selected",
6528
+ decisionID: event.decisionID,
6529
+ model: event.model,
6530
+ routeKind: event.routeKind,
6531
+ selectionCause: event.selectionCause,
6532
+ ...event.runtimeID === undefined ? {} : { runtimeID: event.runtimeID },
6533
+ selectedAt: event.at
6534
+ });
6535
+ break;
6536
+ }
6537
+ case "attempt.queued": {
6538
+ const operation = operationForEvent(operations, event);
6539
+ let attempt = operation.attempts.find((candidate) => candidate.attemptID === event.attemptID);
6540
+ if (attempt === undefined) {
6541
+ if (operation.kind !== "graph-node" || operation.attempts.some((candidate) => candidate.attempt === event.attempt)) {
6542
+ throw new Error(`lifecycle event references unknown attempt ${event.attemptID}`);
6543
+ }
6544
+ attempt = {
6545
+ operationID: event.operationID,
6546
+ taskID: event.taskID,
6547
+ attemptID: event.attemptID,
6548
+ attempt: event.attempt,
6549
+ modelIndex: event.modelIndex,
6550
+ retryIndex: event.retryIndex,
6551
+ state: "queued"
6552
+ };
6553
+ operation.attempts.push(attempt);
6554
+ } else if (attempt.attempt !== event.attempt || attempt.modelIndex !== event.modelIndex || attempt.retryIndex !== event.retryIndex) {
6555
+ throw new Error(`lifecycle attempt identity changed for ${event.attemptID}`);
6556
+ }
6557
+ attempt.state = "queued";
6558
+ attempt.queuedAt = event.at;
6559
+ if (event.runtimeID !== undefined) {
6560
+ attempt.runtimeID = event.runtimeID;
6561
+ }
6562
+ break;
6563
+ }
6564
+ case "attempt.started": {
6565
+ const { operation, attempt } = attemptForEvent(operations, event);
6566
+ attempt.state = "running";
6567
+ attempt.startedAt = event.at;
6568
+ operation.state = "running";
6569
+ break;
6570
+ }
6571
+ case "session.created": {
6572
+ const { attempt } = attemptForEvent(operations, event);
6573
+ attempt.sessionID = event.sessionID;
6574
+ if (event.parentSessionID !== undefined) {
6575
+ attempt.parentSessionID = event.parentSessionID;
6576
+ }
6577
+ break;
6578
+ }
6579
+ case "attempt.terminal": {
6580
+ const { attempt } = attemptForEvent(operations, event);
6581
+ attempt.state = event.outcome;
6582
+ attempt.outcome = event.outcome;
6583
+ attempt.continuation = event.continuation;
6584
+ attempt.terminalAt = event.at;
6585
+ if (event.sessionID !== undefined) {
6586
+ attempt.sessionID = event.sessionID;
6587
+ }
6588
+ if (event.failure !== undefined) {
6589
+ attempt.failure = event.failure;
6590
+ }
6591
+ break;
6592
+ }
6593
+ case "operation.terminal": {
6594
+ const operation = operationForEvent(operations, event);
6595
+ operation.outcome = event.outcome;
6596
+ operation.state = terminalOperationState(event.outcome);
6597
+ operation.terminalAt = event.at;
6598
+ if (event.failure !== undefined) {
6599
+ operation.failure = event.failure;
6600
+ }
6601
+ break;
6602
+ }
6603
+ case "run.terminal":
6604
+ observedState = event.outcome;
6605
+ root.state = event.outcome;
6606
+ root.outcome = event.outcome;
6607
+ root.terminalAt = event.at;
6608
+ if (event.failure !== undefined) {
6609
+ root.failure = event.failure;
6610
+ }
6611
+ break;
6612
+ }
6613
+ }
6614
+ const timeline = events.slice(0, input.cursor);
6615
+ const projection = {
6616
+ version: 1,
6617
+ source: "lifecycle",
6618
+ runID: metadata.runID,
6619
+ cursor: input.cursor,
6620
+ head: events.length,
6621
+ observedState,
6622
+ rootOperationID: metadata.root.operationID,
6623
+ operations: [...operations.values()].map((operation) => ({
6624
+ ...operation,
6625
+ dependsOnOperationIDs: [...operation.dependsOnOperationIDs],
6626
+ attempts: operation.attempts.map((attempt) => ({ ...attempt }))
6627
+ })),
6628
+ edges,
6629
+ timeline,
6630
+ lastEventAt: timeline.at(-1)?.at ?? null
6631
+ };
6632
+ assertLifecycleReferenceOnly(projection);
6633
+ return projection;
6634
+ };
6635
+
6636
+ // src/lifecycle/runtimeRecorder.ts
6637
+ var DEFAULT_LIFECYCLE_HEARTBEAT_INTERVAL_MS = 5000;
6638
+ var DEFAULT_LIFECYCLE_WRITER_LEASE_MS = 30000;
6639
+ var createNodeLifecycleHeartbeatScheduler = () => ({
6640
+ start(intervalMs, heartbeat) {
6641
+ let stopped = false;
6642
+ let running = false;
6643
+ const timer = setInterval(() => {
6644
+ if (stopped || running) {
6645
+ return;
6646
+ }
6647
+ running = true;
6648
+ heartbeat().catch(() => {}).finally(() => {
6649
+ running = false;
6650
+ });
6651
+ }, Math.max(1, intervalMs));
6652
+ timer.unref?.();
6653
+ return {
6654
+ stop() {
6655
+ if (stopped) {
6656
+ return;
6657
+ }
6658
+ stopped = true;
6659
+ clearInterval(timer);
6660
+ }
6661
+ };
6662
+ }
6663
+ });
6664
+ function thrownJournalFailure(runID) {
6665
+ return {
6666
+ code: "unavailable",
6667
+ recoverable: true,
6668
+ runID
6669
+ };
6670
+ }
6671
+ function failedStatus(current, failure) {
6672
+ return {
6673
+ runID: current.runID,
6674
+ state: failure.recoverable ? "degraded" : "unavailable",
6675
+ lastCommittedHead: current.lastCommittedHead,
6676
+ failure
6677
+ };
6678
+ }
6679
+ function attemptObservationMissing(attempt, expectation) {
6680
+ if (expectation.attemptID === null || attempt?.queuedAt === undefined) {
6681
+ return true;
6682
+ }
6683
+ if (expectation.minimumPhase !== "queued" && attempt.startedAt === undefined) {
6684
+ return true;
6685
+ }
6686
+ return expectation.minimumPhase === "terminal" && attempt.terminalAt === undefined;
6687
+ }
6688
+ function operationObservationMissing(projection, expectation) {
6689
+ const operation = projection.operations.find((candidate) => candidate.operationID === expectation.operationID);
6690
+ if (operation === undefined || expectation.operationQueuedRequired && operation.queuedAt === undefined || expectation.operationTerminalRequired && operation.terminalAt === undefined) {
6691
+ return true;
6692
+ }
6693
+ return expectation.attempts.some((attemptExpectation) => attemptObservationMissing(operation.attempts.find((attempt) => attempt.attemptID === attemptExpectation.attemptID), attemptExpectation));
6694
+ }
6695
+ function hasIncompleteObservedWork(projection) {
6696
+ return projection.operations.some((operation) => operation.operationID !== projection.rootOperationID && operation.terminalAt === undefined || operation.attempts.some((attempt) => attempt.terminalAt === undefined));
6697
+ }
6698
+ function createActiveRunRecorder(options, handle, initialHead) {
6699
+ const runID = handle.runID;
6700
+ let current = {
6701
+ runID,
6702
+ state: "healthy",
6703
+ lastCommittedHead: initialHead
6704
+ };
6705
+ let schedule;
6706
+ let released = false;
6707
+ let queue = Promise.resolve();
6708
+ const stopSchedule = () => {
6709
+ schedule?.stop();
6710
+ schedule = undefined;
6711
+ };
6712
+ const degrade = (failure) => {
6713
+ current = failedStatus(current, failure);
6714
+ stopSchedule();
6715
+ };
6716
+ const serialize = (work) => {
6717
+ const result = queue.then(work, work);
6718
+ queue = result.then(() => {}, () => {});
6719
+ return result;
6720
+ };
6721
+ const releaseOwner = async () => {
6722
+ if (released) {
6723
+ return;
6724
+ }
6725
+ released = true;
6726
+ stopSchedule();
6727
+ try {
6728
+ const release = await options.journal.release(handle);
6729
+ if (!release.ok) {
6730
+ degrade(release.error);
6731
+ }
6732
+ } catch {
6733
+ degrade(thrownJournalFailure(runID));
6734
+ }
6735
+ };
6736
+ const verifyCommittedProjection = async (expectations, verificationOptions) => {
6737
+ try {
6738
+ const loaded = await options.journal.load(runID);
6739
+ if (!loaded.ok) {
6740
+ degrade(loaded.error);
6741
+ return false;
6742
+ }
6743
+ if (loaded.value.integrity !== "clean" || loaded.value.head !== current.lastCommittedHead) {
6744
+ degrade({ code: "corrupt", recoverable: false, runID });
6745
+ return false;
6746
+ }
6747
+ const projection = projectLifecyclePrefix({
6748
+ ...loaded.value,
6749
+ cursor: loaded.value.head
6750
+ });
6751
+ if (expectations.some((expectation) => operationObservationMissing(projection, expectation)) || verificationOptions.requireCompleteObservedWork === true && hasIncompleteObservedWork(projection)) {
6752
+ degrade({ code: "unavailable", recoverable: false, runID });
6753
+ return false;
6754
+ }
6755
+ return true;
6756
+ } catch {
6757
+ degrade({ code: "unavailable", recoverable: false, runID });
6758
+ return false;
6759
+ }
6760
+ };
6761
+ const heartbeat = () => serialize(async () => {
6762
+ if (released || current.state !== "healthy") {
6763
+ return;
6764
+ }
6765
+ try {
6766
+ const result = await options.journal.heartbeat(handle, {
6767
+ now: options.now(),
6768
+ leaseDurationMs: options.leaseDurationMs
6769
+ });
6770
+ if (!result.ok) {
6771
+ degrade(result.error);
6772
+ }
6773
+ } catch {
6774
+ degrade(thrownJournalFailure(runID));
6775
+ }
6776
+ });
6777
+ const recorder = {
6778
+ runID,
6779
+ append(event) {
6780
+ return serialize(async () => {
6781
+ if (released || current.state !== "healthy") {
6782
+ return { ok: false, status: current };
6783
+ }
6784
+ if (event.type === "run.terminal" && !await verifyCommittedProjection([], {
6785
+ requireCompleteObservedWork: true
6786
+ })) {
6787
+ await releaseOwner();
6788
+ return { ok: false, status: current };
6789
+ }
6790
+ let appended;
6791
+ try {
6792
+ appended = await options.journal.append(handle, event, current.lastCommittedHead);
6793
+ } catch {
6794
+ degrade(thrownJournalFailure(runID));
6795
+ return { ok: false, status: current };
6796
+ }
6797
+ if (!appended.ok) {
6798
+ degrade(appended.error);
6799
+ return { ok: false, status: current };
6800
+ }
6801
+ current = {
6802
+ runID,
6803
+ state: "healthy",
6804
+ lastCommittedHead: appended.value.head
6805
+ };
6806
+ if (event.type === "run.terminal") {
6807
+ await releaseOwner();
6808
+ if (current.state === "healthy" && options.retention !== undefined) {
6809
+ try {
6810
+ const retained = await options.journal.pruneTerminal(options.retention, options.now());
6811
+ if (!retained.ok) {
6812
+ degrade(retained.error);
6813
+ }
6814
+ } catch {
6815
+ degrade(thrownJournalFailure(runID));
6816
+ }
6817
+ }
6818
+ }
6819
+ return {
6820
+ ok: true,
6821
+ receipt: appended.value,
6822
+ status: current
6823
+ };
6824
+ });
6825
+ },
6826
+ status() {
6827
+ return current;
6828
+ },
6829
+ verifyOperations(expectations, verificationOptions = {}) {
6830
+ return serialize(async () => {
6831
+ if (released || current.state !== "healthy") {
6832
+ return current;
6833
+ }
6834
+ await verifyCommittedProjection(expectations, verificationOptions);
6835
+ if (current.state !== "healthy") {
6836
+ await releaseOwner();
6837
+ }
6838
+ return current;
6839
+ });
6840
+ },
6841
+ release() {
6842
+ return serialize(async () => {
6843
+ await releaseOwner();
6844
+ return current;
6845
+ });
6846
+ }
6847
+ };
6848
+ schedule = options.heartbeatScheduler.start(options.heartbeatIntervalMs, heartbeat);
6849
+ return recorder;
6850
+ }
6851
+ function continuationFailure(runID, lastCommittedHead, failure) {
6852
+ return {
6853
+ ok: false,
6854
+ status: failedStatus({
6855
+ runID,
6856
+ state: "healthy",
6857
+ lastCommittedHead
6858
+ }, failure)
6859
+ };
6860
+ }
6861
+ var createRuntimeLifecycleRecorder = (options) => {
6862
+ const runtime = {
6863
+ async beginRun(input) {
6864
+ const startedAt = options.now();
6865
+ let started;
6866
+ try {
6867
+ started = await options.journal.start(input, {
6868
+ writer: options.writer,
6869
+ now: startedAt,
6870
+ leaseDurationMs: options.leaseDurationMs
6871
+ });
6872
+ } catch {
6873
+ return {
6874
+ ok: false,
6875
+ status: {
6876
+ runID: input.runID,
6877
+ state: "unavailable",
6878
+ lastCommittedHead: 0,
6879
+ failure: thrownJournalFailure(input.runID)
6880
+ }
6881
+ };
6882
+ }
6883
+ if (!started.ok) {
6884
+ return {
6885
+ ok: false,
6886
+ status: {
6887
+ runID: input.runID,
6888
+ state: "unavailable",
6889
+ lastCommittedHead: 0,
6890
+ failure: started.error
6891
+ }
6892
+ };
6893
+ }
6894
+ const receipt = started.value;
6895
+ const recorder = createActiveRunRecorder(options, receipt.handle, receipt.head);
6896
+ return {
6897
+ ok: true,
6898
+ recorder,
6899
+ receipt,
6900
+ status: recorder.status()
6901
+ };
6902
+ }
6903
+ };
6904
+ const writerLiveness = options.writerLiveness;
6905
+ if (writerLiveness === undefined) {
6906
+ return runtime;
6907
+ }
6908
+ return {
6909
+ ...runtime,
6910
+ async continueRun(runID) {
6911
+ const releaseTaken = async (handle) => {
6912
+ try {
6913
+ await options.journal.release(handle);
6914
+ } catch {}
6915
+ };
6916
+ let loaded;
6917
+ try {
6918
+ loaded = await options.journal.load(runID);
6919
+ } catch {
6920
+ return continuationFailure(runID, 0, thrownJournalFailure(runID));
6921
+ }
6922
+ if (!loaded.ok) {
6923
+ return continuationFailure(runID, 0, loaded.error);
6924
+ }
6925
+ if (loaded.value.integrity !== "clean") {
6926
+ return continuationFailure(runID, loaded.value.head, {
6927
+ code: "corrupt",
6928
+ recoverable: false,
6929
+ runID
6930
+ });
6931
+ }
6932
+ if (loaded.value.events.at(-1)?.type === "run.terminal") {
6933
+ return continuationFailure(runID, loaded.value.head, {
6934
+ code: "terminal-run",
6935
+ recoverable: false,
6936
+ runID
6937
+ });
6938
+ }
6939
+ let observed;
6940
+ try {
6941
+ observed = await writerLiveness.observe(runID);
6942
+ } catch {
6943
+ return continuationFailure(runID, loaded.value.head, thrownJournalFailure(runID));
6944
+ }
6945
+ let expected;
6946
+ if (observed.kind === "absent") {
6947
+ expected = { kind: "absent" };
6948
+ } else if (observed.kind === "expired") {
6949
+ expected = {
6950
+ kind: "expired",
6951
+ writerToken: observed.lease.writerToken,
6952
+ expiresAt: observed.lease.expiresAt
6953
+ };
6954
+ } else {
6955
+ return continuationFailure(runID, loaded.value.head, {
6956
+ code: observed.kind === "active" ? "active-run" : "unavailable",
6957
+ recoverable: true,
6958
+ runID
6959
+ });
6960
+ }
6961
+ let taken;
6962
+ try {
6963
+ taken = await options.journal.takeOver(runID, expected, {
6964
+ writer: options.writer,
6965
+ now: options.now(),
6966
+ leaseDurationMs: options.leaseDurationMs
6967
+ });
6968
+ } catch {
6969
+ return continuationFailure(runID, loaded.value.head, thrownJournalFailure(runID));
6970
+ }
6971
+ if (!taken.ok) {
6972
+ return continuationFailure(runID, loaded.value.head, taken.error);
6973
+ }
6974
+ let confirmed;
6975
+ try {
6976
+ confirmed = await options.journal.load(runID);
6977
+ } catch {
6978
+ await releaseTaken(taken.value);
6979
+ return continuationFailure(runID, loaded.value.head, thrownJournalFailure(runID));
6980
+ }
6981
+ if (!confirmed.ok) {
6982
+ await releaseTaken(taken.value);
6983
+ return continuationFailure(runID, loaded.value.head, confirmed.error);
6984
+ }
6985
+ if (confirmed.value.integrity !== "clean") {
6986
+ await releaseTaken(taken.value);
6987
+ return continuationFailure(runID, confirmed.value.head, {
6988
+ code: "corrupt",
6989
+ recoverable: false,
6990
+ runID
6991
+ });
6992
+ }
6993
+ if (confirmed.value.events.at(-1)?.type === "run.terminal") {
6994
+ await releaseTaken(taken.value);
6995
+ return continuationFailure(runID, confirmed.value.head, {
6996
+ code: "terminal-run",
6997
+ recoverable: false,
6998
+ runID
6999
+ });
7000
+ }
7001
+ const recorder = createActiveRunRecorder(options, taken.value, confirmed.value.head);
7002
+ return {
7003
+ ok: true,
7004
+ recorder,
7005
+ status: recorder.status()
7006
+ };
7007
+ }
7008
+ };
7009
+ };
7010
+
7011
+ // src/local/cacheAdapter.ts
7012
+ import * as fs2 from "node:fs";
7013
+ import * as os from "node:os";
7014
+ import * as path from "node:path";
7015
+ var SCOPE_PREFIX = "@jmanuelcorral/openteam";
7016
+ var SCOPE_SEGMENTS = SCOPE_PREFIX.split("/");
7017
+ function resolveCacheRoot(override) {
7018
+ if (override !== undefined && override.trim() !== "") {
7019
+ return override;
7020
+ }
7021
+ const base = process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache");
7022
+ return path.join(base, "opencode", "packages");
7023
+ }
7024
+ function readJsonSafe(filePath) {
7025
+ try {
7026
+ const text = fs2.readFileSync(filePath, "utf8");
7027
+ const parsed = JSON.parse(text);
7028
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
7029
+ return parsed;
7030
+ }
7031
+ return;
7032
+ } catch {
7033
+ return;
7034
+ }
7035
+ }
7036
+ function extractVersion(pkg) {
7037
+ if (pkg === undefined)
7038
+ return "";
7039
+ const v = pkg.version;
7040
+ return typeof v === "string" ? v : "";
7041
+ }
7042
+ function readDirectoryEntries(dir) {
7043
+ try {
7044
+ return fs2.readdirSync(dir, { withFileTypes: true });
7045
+ } catch {
7046
+ return [];
7047
+ }
7048
+ }
7049
+ function lstatPlainDirectory(dir) {
7050
+ try {
5701
7051
  const stat2 = fs2.lstatSync(dir);
5702
7052
  if (!stat2.isDirectory() || stat2.isSymbolicLink()) {
5703
7053
  return;
@@ -5882,9 +7232,9 @@ function createRuntimeConcurrencyLimiter(options) {
5882
7232
  }
5883
7233
  const state = stateFor(runtimeId);
5884
7234
  const queuedAt = options.now();
5885
- return new Promise((resolve3) => {
7235
+ return new Promise((resolve4) => {
5886
7236
  state.queues[priority].push((release) => {
5887
- resolve3({ release, waitedMs: options.now() - queuedAt });
7237
+ resolve4({ release, waitedMs: options.now() - queuedAt });
5888
7238
  });
5889
7239
  });
5890
7240
  },
@@ -6592,14 +7942,14 @@ var readReportArtifact = async (reportPath) => {
6592
7942
  };
6593
7943
 
6594
7944
  // src/orchestrator/roster.ts
6595
- import { z as z17 } from "zod";
6596
- var RosterEntrySchema = z17.object({
6597
- roleID: z17.string().min(1),
6598
- agentName: z17.string().min(1)
7945
+ import { z as z18 } from "zod";
7946
+ var RosterEntrySchema = z18.object({
7947
+ roleID: z18.string().min(1),
7948
+ agentName: z18.string().min(1)
6599
7949
  }).strict();
6600
- var RosterSchema = z17.object({
6601
- universe: z17.string().min(1),
6602
- entries: z17.array(RosterEntrySchema)
7950
+ var RosterSchema = z18.object({
7951
+ universe: z18.string().min(1),
7952
+ entries: z18.array(RosterEntrySchema)
6603
7953
  }).strict();
6604
7954
  var ROSTER_JSON_FENCE = /```json\s*([\s\S]*?)```/;
6605
7955
 
@@ -6772,6 +8122,18 @@ function enforceGuaranteedRoles(roster) {
6772
8122
  return { universe: roster.universe, entries: [...roster.entries, ...added] };
6773
8123
  }
6774
8124
 
8125
+ // src/messages/lifecycleRuntime.ts
8126
+ function normalizedSessionFailureMessage(failure) {
8127
+ return `session request failed (${failure.failureClass})`;
8128
+ }
8129
+ function lifecycleRecordingAdvisory(status) {
8130
+ if (status.state === "healthy") {
8131
+ return;
8132
+ }
8133
+ const failure = status.failure === undefined ? "" : ` (${status.failure.code})`;
8134
+ return `Lifecycle history recording is ${status.state}${failure}; model execution continued without claiming complete history.`;
8135
+ }
8136
+
6775
8137
  // src/orchestrator/subsessions.ts
6776
8138
  var DEFAULT_ABORT_GRACE_MS = 2000;
6777
8139
  var DEFAULT_STATUS_POLL_MS = 100;
@@ -6911,6 +8273,9 @@ function errorReason(error) {
6911
8273
  }
6912
8274
  return safeToString(error);
6913
8275
  }
8276
+ function isNormalizedSessionFailure(value) {
8277
+ return isObject3(value) && safeGet(value, "kind") === "normalized-session-failure" && typeof safeGet(value, "failureClass") === "string" && typeof safeGet(value, "retryable") === "boolean";
8278
+ }
6914
8279
  function createArgs(req) {
6915
8280
  const body = {};
6916
8281
  const args = { body };
@@ -6953,9 +8318,9 @@ async function withTimeout(promise, timeoutMs) {
6953
8318
  return { kind: "settled", value: await promise };
6954
8319
  }
6955
8320
  let timeoutID;
6956
- const timeout = new Promise((resolve3) => {
8321
+ const timeout = new Promise((resolve4) => {
6957
8322
  timeoutID = setTimeout(() => {
6958
- resolve3({ kind: "timeout" });
8323
+ resolve4({ kind: "timeout" });
6959
8324
  }, timeoutMs);
6960
8325
  });
6961
8326
  try {
@@ -6975,8 +8340,8 @@ async function beforeDeadline(request, deadline, deps) {
6975
8340
  const controller = new AbortController;
6976
8341
  let timeoutID;
6977
8342
  const observedRequest = Promise.resolve().then(() => request(controller.signal)).then((value) => ({ kind: "settled", value }), (error) => ({ kind: "rejected", error }));
6978
- const timeout = new Promise((resolve3) => {
6979
- timeoutID = setTimeout(() => resolve3({ kind: "timeout" }), remainingMs);
8343
+ const timeout = new Promise((resolve4) => {
8344
+ timeoutID = setTimeout(() => resolve4({ kind: "timeout" }), remainingMs);
6980
8345
  });
6981
8346
  try {
6982
8347
  const outcome = await Promise.race([observedRequest, timeout]);
@@ -7110,13 +8475,15 @@ async function runSubsession(client, req, deps) {
7110
8475
  try {
7111
8476
  const created = await client.session.create(createArgs(req));
7112
8477
  if (created.error !== undefined) {
8478
+ const normalizedFailure = isNormalizedSessionFailure(created.error) ? created.error : undefined;
7113
8479
  return {
7114
8480
  success: false,
7115
8481
  roleID: req.roleID,
7116
8482
  model: req.model,
7117
8483
  failureStage: "create",
7118
- failureReason: errorReason(created.error),
7119
- retrySafe: true
8484
+ failureReason: normalizedFailure === undefined ? errorReason(created.error) : normalizedSessionFailureMessage(normalizedFailure),
8485
+ retrySafe: true,
8486
+ ...normalizedFailure === undefined ? {} : { normalizedFailure }
7120
8487
  };
7121
8488
  }
7122
8489
  sessionID = created.data?.id;
@@ -7130,6 +8497,9 @@ async function runSubsession(client, req, deps) {
7130
8497
  retrySafe: true
7131
8498
  };
7132
8499
  }
8500
+ try {
8501
+ await deps.onSessionCreated?.(sessionID);
8502
+ } catch {}
7133
8503
  } catch (error) {
7134
8504
  return {
7135
8505
  success: false,
@@ -7168,14 +8538,16 @@ async function runSubsession(client, req, deps) {
7168
8538
  }
7169
8539
  const prompted = outcome.value;
7170
8540
  if (prompted.error !== undefined) {
8541
+ const normalizedFailure = isNormalizedSessionFailure(prompted.error) ? prompted.error : undefined;
7171
8542
  return {
7172
8543
  success: false,
7173
8544
  sessionID,
7174
8545
  roleID: req.roleID,
7175
8546
  model: req.model,
7176
8547
  failureStage: "prompt",
7177
- failureReason: errorReason(prompted.error),
7178
- retrySafe: true
8548
+ failureReason: normalizedFailure === undefined ? errorReason(prompted.error) : normalizedSessionFailureMessage(normalizedFailure),
8549
+ retrySafe: true,
8550
+ ...normalizedFailure === undefined ? {} : { normalizedFailure }
7179
8551
  };
7180
8552
  }
7181
8553
  return {
@@ -7797,7 +9169,7 @@ function planWorktreeReconciliation(input) {
7797
9169
  }
7798
9170
 
7799
9171
  // src/telemetry/otelConfig.ts
7800
- import { z as z18 } from "zod";
9172
+ import { z as z19 } from "zod";
7801
9173
 
7802
9174
  // src/telemetry/fanout.ts
7803
9175
  function createFanOutSink(deps) {
@@ -7875,10 +9247,10 @@ function createOtelSink(deps) {
7875
9247
  }
7876
9248
 
7877
9249
  // src/telemetry/otelConfig.ts
7878
- var OtelBackendConfigSchema = z18.object({
7879
- backend: z18.literal("opentelemetry"),
7880
- connectionEnv: z18.string().min(1),
7881
- serviceName: z18.string().min(1).optional()
9250
+ var OtelBackendConfigSchema = z19.object({
9251
+ backend: z19.literal("opentelemetry"),
9252
+ connectionEnv: z19.string().min(1),
9253
+ serviceName: z19.string().min(1).optional()
7882
9254
  }).strict();
7883
9255
  function parseConnectionString(raw) {
7884
9256
  const pairs = new Map;
@@ -11165,36 +12537,36 @@ function formatResponse(response) {
11165
12537
  }
11166
12538
 
11167
12539
  // src/plugin/reportRunSchema.ts
11168
- import { z as z20 } from "zod";
12540
+ import { z as z21 } from "zod";
11169
12541
 
11170
12542
  // src/plugin/graphShadowIngress.ts
11171
- import { z as z19 } from "zod";
12543
+ import { z as z20 } from "zod";
11172
12544
  var LEGACY_EXECUTION_TRACE_VERSION = 1;
11173
- var LegacyExecutionStatusSchema = z19.enum([
12545
+ var LegacyExecutionStatusSchema = z20.enum([
11174
12546
  "completed",
11175
12547
  "failed",
11176
12548
  "cancelled"
11177
12549
  ]);
11178
- var LegacyReviewOutcomeSchema = z19.enum([
12550
+ var LegacyReviewOutcomeSchema = z20.enum([
11179
12551
  "approved",
11180
12552
  "rejected",
11181
12553
  "inconclusive"
11182
12554
  ]);
11183
- var LegacyTraceNodeV1Schema = z19.object({
12555
+ var LegacyTraceNodeV1Schema = z20.object({
11184
12556
  id: NodeIDSchema,
11185
12557
  role: NodeRoleSchema,
11186
- model: z19.string().min(1),
11187
- sessionRef: z19.string().min(1).optional(),
12558
+ model: z20.string().min(1),
12559
+ sessionRef: z20.string().min(1).optional(),
11188
12560
  errorClass: ErrorClassSchema.optional()
11189
12561
  }).strict();
11190
- var LegacyExecutionTraceV1Schema = z19.object({
11191
- version: z19.literal(LEGACY_EXECUTION_TRACE_VERSION),
12562
+ var LegacyExecutionTraceV1Schema = z20.object({
12563
+ version: z20.literal(LEGACY_EXECUTION_TRACE_VERSION),
11192
12564
  runID: NodeIDSchema,
11193
12565
  status: LegacyExecutionStatusSchema,
11194
12566
  outcome: LegacyReviewOutcomeSchema,
11195
- fixes: z19.number().int().nonnegative(),
11196
- nodes: z19.array(LegacyTraceNodeV1Schema).min(1),
11197
- parentSessionRef: z19.string().min(1).optional()
12567
+ fixes: z20.number().int().nonnegative(),
12568
+ nodes: z20.array(LegacyTraceNodeV1Schema).min(1),
12569
+ parentSessionRef: z20.string().min(1).optional()
11198
12570
  }).strict();
11199
12571
  function gateReason2(gate) {
11200
12572
  if (gate === undefined || gate.mode === "off") {
@@ -11265,20 +12637,20 @@ function parseLegacyExecutionTrace(input) {
11265
12637
  }
11266
12638
 
11267
12639
  // src/plugin/reportRunSchema.ts
11268
- var ReportRunNodeSchema = z20.object({
12640
+ var ReportRunNodeSchema = z21.object({
11269
12641
  id: NodeIDSchema,
11270
12642
  role: NodeRoleSchema,
11271
- model: z20.string().min(1),
11272
- ok: z20.boolean(),
11273
- sessionRef: z20.string().min(1).optional(),
12643
+ model: z21.string().min(1),
12644
+ ok: z21.boolean(),
12645
+ sessionRef: z21.string().min(1).optional(),
11274
12646
  errorClass: ErrorClassSchema.optional()
11275
12647
  }).strict();
11276
- var ReportRunPayloadSchema = z20.object({
12648
+ var ReportRunPayloadSchema = z21.object({
11277
12649
  runID: NodeIDSchema,
11278
12650
  status: LegacyExecutionStatusSchema,
11279
- fixes: z20.number().int().nonnegative(),
11280
- nodes: z20.array(ReportRunNodeSchema).min(1),
11281
- parentSessionRef: z20.string().min(1).optional()
12651
+ fixes: z21.number().int().nonnegative(),
12652
+ nodes: z21.array(ReportRunNodeSchema).min(1),
12653
+ parentSessionRef: z21.string().min(1).optional()
11282
12654
  }).strict();
11283
12655
 
11284
12656
  // src/orchestrator/graphShadow.ts
@@ -12579,62 +13951,62 @@ ${lines.join(`
12579
13951
  }
12580
13952
 
12581
13953
  // src/memory/types.ts
12582
- import { z as z21 } from "zod";
13954
+ import { z as z22 } from "zod";
12583
13955
  var SHARED_OWNER_KEY = "*";
12584
- var OwnerKeySchema = z21.string().min(1);
12585
- var MemoryKindSchema = z21.enum(["fact", "preference", "entity"]);
12586
- var MemoryBaseSchema = z21.object({
12587
- id: z21.string().min(1),
13956
+ var OwnerKeySchema = z22.string().min(1);
13957
+ var MemoryKindSchema = z22.enum(["fact", "preference", "entity"]);
13958
+ var MemoryBaseSchema = z22.object({
13959
+ id: z22.string().min(1),
12588
13960
  ownerKey: OwnerKeySchema,
12589
- confidence: z21.number().min(0).max(1),
12590
- validFrom: z21.number().finite(),
12591
- validUntil: z21.number().finite().nullable().default(null),
12592
- createdAt: z21.number().finite(),
12593
- invalidatedAt: z21.number().finite().nullable().default(null),
12594
- sourceHash: z21.string().min(1),
12595
- supersededBy: z21.string().min(1).nullable().default(null)
13961
+ confidence: z22.number().min(0).max(1),
13962
+ validFrom: z22.number().finite(),
13963
+ validUntil: z22.number().finite().nullable().default(null),
13964
+ createdAt: z22.number().finite(),
13965
+ invalidatedAt: z22.number().finite().nullable().default(null),
13966
+ sourceHash: z22.string().min(1),
13967
+ supersededBy: z22.string().min(1).nullable().default(null)
12596
13968
  }).strict();
12597
13969
  var FactSchema = MemoryBaseSchema.extend({
12598
- kind: z21.literal("fact"),
12599
- subject: z21.string().min(1),
12600
- predicate: z21.string().min(1),
12601
- object: z21.string().min(1),
12602
- category: z21.string().min(1).nullable().default(null)
13970
+ kind: z22.literal("fact"),
13971
+ subject: z22.string().min(1),
13972
+ predicate: z22.string().min(1),
13973
+ object: z22.string().min(1),
13974
+ category: z22.string().min(1).nullable().default(null)
12603
13975
  });
12604
13976
  var PreferenceSchema = MemoryBaseSchema.extend({
12605
- kind: z21.literal("preference"),
12606
- category: z21.string().min(1),
12607
- preference: z21.string().min(1),
12608
- context: z21.string().min(1).nullable().default(null),
12609
- lastAccessedAt: z21.number().finite().nullable().default(null),
12610
- accessCount: z21.number().int().min(0).default(0)
13977
+ kind: z22.literal("preference"),
13978
+ category: z22.string().min(1),
13979
+ preference: z22.string().min(1),
13980
+ context: z22.string().min(1).nullable().default(null),
13981
+ lastAccessedAt: z22.number().finite().nullable().default(null),
13982
+ accessCount: z22.number().int().min(0).default(0)
12611
13983
  });
12612
13984
  var EntitySchema = MemoryBaseSchema.extend({
12613
- kind: z21.literal("entity"),
12614
- canonicalName: z21.string().min(1),
12615
- type: z21.string().min(1),
12616
- aliases: z21.array(z21.string().min(1)).default([])
13985
+ kind: z22.literal("entity"),
13986
+ canonicalName: z22.string().min(1),
13987
+ type: z22.string().min(1),
13988
+ aliases: z22.array(z22.string().min(1)).default([])
12617
13989
  });
12618
13990
  var RelationSchema = MemoryBaseSchema.extend({
12619
- kind: z21.literal("relation"),
12620
- from: z21.string().min(1),
12621
- to: z21.string().min(1),
12622
- predicate: z21.string().min(1),
12623
- annotation: z21.string().min(1).nullable().default(null)
13991
+ kind: z22.literal("relation"),
13992
+ from: z22.string().min(1),
13993
+ to: z22.string().min(1),
13994
+ predicate: z22.string().min(1),
13995
+ annotation: z22.string().min(1).nullable().default(null)
12624
13996
  });
12625
- var MemoryRecordSchema = z21.discriminatedUnion("kind", [
13997
+ var MemoryRecordSchema = z22.discriminatedUnion("kind", [
12626
13998
  FactSchema,
12627
13999
  PreferenceSchema,
12628
14000
  EntitySchema,
12629
14001
  RelationSchema
12630
14002
  ]);
12631
- var RecallQuerySchema = z21.object({
14003
+ var RecallQuerySchema = z22.object({
12632
14004
  ownerKey: OwnerKeySchema,
12633
- kinds: z21.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
12634
- asOf: z21.number().finite().nullable().default(null),
12635
- limit: z21.number().int().positive().default(8),
12636
- minSimilarity: z21.number().min(0).max(1).default(0.2),
12637
- includeShared: z21.boolean().default(true)
14005
+ kinds: z22.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
14006
+ asOf: z22.number().finite().nullable().default(null),
14007
+ limit: z22.number().int().positive().default(8),
14008
+ minSimilarity: z22.number().min(0).max(1).default(0.2),
14009
+ includeShared: z22.boolean().default(true)
12638
14010
  }).strict();
12639
14011
 
12640
14012
  // src/memory/rank.ts
@@ -13205,42 +14577,42 @@ function buildMemoryInjector(deps, policy) {
13205
14577
 
13206
14578
  // src/plugin/memoryTool.ts
13207
14579
  import { tool as tool3 } from "@opencode-ai/plugin";
13208
- import { z as z23 } from "zod";
14580
+ import { z as z24 } from "zod";
13209
14581
 
13210
14582
  // src/memory/extract.ts
13211
- import { z as z22 } from "zod";
14583
+ import { z as z23 } from "zod";
13212
14584
  var REDACTED = "[redacted]";
13213
- var RawFactSchema = z22.object({
13214
- subject: z22.string().min(1),
13215
- predicate: z22.string().min(1),
13216
- object: z22.string().min(1),
13217
- category: z22.string().min(1).nullable().optional(),
13218
- confidence: z22.number().min(0).max(1).optional()
14585
+ var RawFactSchema = z23.object({
14586
+ subject: z23.string().min(1),
14587
+ predicate: z23.string().min(1),
14588
+ object: z23.string().min(1),
14589
+ category: z23.string().min(1).nullable().optional(),
14590
+ confidence: z23.number().min(0).max(1).optional()
13219
14591
  });
13220
- var RawPreferenceSchema = z22.object({
13221
- category: z22.string().min(1),
13222
- preference: z22.string().min(1),
13223
- context: z22.string().min(1).nullable().optional(),
13224
- confidence: z22.number().min(0).max(1).optional()
14592
+ var RawPreferenceSchema = z23.object({
14593
+ category: z23.string().min(1),
14594
+ preference: z23.string().min(1),
14595
+ context: z23.string().min(1).nullable().optional(),
14596
+ confidence: z23.number().min(0).max(1).optional()
13225
14597
  });
13226
- var RawEntitySchema = z22.object({
13227
- canonicalName: z22.string().min(1),
13228
- type: z22.string().min(1),
13229
- aliases: z22.array(z22.string().min(1)).optional(),
13230
- confidence: z22.number().min(0).max(1).optional()
14598
+ var RawEntitySchema = z23.object({
14599
+ canonicalName: z23.string().min(1),
14600
+ type: z23.string().min(1),
14601
+ aliases: z23.array(z23.string().min(1)).optional(),
14602
+ confidence: z23.number().min(0).max(1).optional()
13231
14603
  });
13232
- var RawRelationSchema = z22.object({
13233
- from: z22.string().min(1),
13234
- to: z22.string().min(1),
13235
- predicate: z22.string().min(1),
13236
- annotation: z22.string().min(1).nullable().optional(),
13237
- confidence: z22.number().min(0).max(1).optional()
14604
+ var RawRelationSchema = z23.object({
14605
+ from: z23.string().min(1),
14606
+ to: z23.string().min(1),
14607
+ predicate: z23.string().min(1),
14608
+ annotation: z23.string().min(1).nullable().optional(),
14609
+ confidence: z23.number().min(0).max(1).optional()
13238
14610
  }).strict();
13239
- var RawExtractionSchema = z22.object({
13240
- facts: z22.array(RawFactSchema).default([]),
13241
- preferences: z22.array(RawPreferenceSchema).default([]),
13242
- entities: z22.array(RawEntitySchema).default([]),
13243
- relations: z22.array(RawRelationSchema).default([])
14611
+ var RawExtractionSchema = z23.object({
14612
+ facts: z23.array(RawFactSchema).default([]),
14613
+ preferences: z23.array(RawPreferenceSchema).default([]),
14614
+ entities: z23.array(RawEntitySchema).default([]),
14615
+ relations: z23.array(RawRelationSchema).default([])
13244
14616
  });
13245
14617
  var DEFAULT_CONFIDENCE = 0.6;
13246
14618
  var SYSTEM_PROMPT = [
@@ -13494,13 +14866,13 @@ var memoryToolMessages = {
13494
14866
 
13495
14867
  // src/plugin/memoryTool.ts
13496
14868
  var SCRIBE_ROLE_ID = "scribe";
13497
- var MemoryMessageSchema = z23.object({
13498
- role: z23.enum(["system", "user", "assistant"]).describe(memoryToolMessages.messageRoleDescription),
13499
- content: z23.string().min(1).describe(memoryToolMessages.messageContentDescription)
14869
+ var MemoryMessageSchema = z24.object({
14870
+ role: z24.enum(["system", "user", "assistant"]).describe(memoryToolMessages.messageRoleDescription),
14871
+ content: z24.string().min(1).describe(memoryToolMessages.messageContentDescription)
13500
14872
  }).strict();
13501
- var MemoryWritePayloadSchema = z23.object({
13502
- messages: z23.array(MemoryMessageSchema).min(1).describe(memoryToolMessages.messagesDescription),
13503
- roleID: z23.string().min(1).default(SCRIBE_ROLE_ID).describe(memoryToolMessages.roleIDDescription)
14873
+ var MemoryWritePayloadSchema = z24.object({
14874
+ messages: z24.array(MemoryMessageSchema).min(1).describe(memoryToolMessages.messagesDescription),
14875
+ roleID: z24.string().min(1).default(SCRIBE_ROLE_ID).describe(memoryToolMessages.roleIDDescription)
13504
14876
  }).strict();
13505
14877
  function createMemoryTool(deps) {
13506
14878
  return tool3({
@@ -13625,10 +14997,10 @@ function createOrchestratedModelOwnership() {
13625
14997
 
13626
14998
  // src/plugin/orchestrateTool.ts
13627
14999
  import { tool as tool4 } from "@opencode-ai/plugin";
13628
- import { z as z25 } from "zod";
15000
+ import { z as z26 } from "zod";
13629
15001
 
13630
15002
  // src/orchestrator/coordinator.ts
13631
- import { z as z24 } from "zod";
15003
+ import { z as z25 } from "zod";
13632
15004
 
13633
15005
  // src/orchestrator/nodeIteration.ts
13634
15006
  var DEFAULT_MAX_RETRIES = 2;
@@ -14091,9 +15463,12 @@ function buildRecord(input, decision2, subsession, deps, decisionID, batchID) {
14091
15463
  if (subsession.failureStage !== undefined) {
14092
15464
  record.failureStage = subsession.failureStage;
14093
15465
  }
15466
+ if (subsession.normalizedFailure !== undefined) {
15467
+ record.normalizedFailure = subsession.normalizedFailure;
15468
+ }
14094
15469
  return record;
14095
15470
  }
14096
- var TelemetryFailureClassSchema = z24.enum([
15471
+ var TelemetryFailureClassSchema = z25.enum([
14097
15472
  "create-rejected",
14098
15473
  "create-no-id",
14099
15474
  "prompt-rejected",
@@ -14241,7 +15616,9 @@ function correlatedCostRecordToRouteEvent(record) {
14241
15616
  if (record.taskID !== undefined) {
14242
15617
  event.taskID = record.taskID;
14243
15618
  }
14244
- if (record.failureReason !== undefined) {
15619
+ if (record.normalizedFailure !== undefined) {
15620
+ event.failureReason = record.normalizedFailure.failureClass;
15621
+ } else if (record.failureReason !== undefined) {
14245
15622
  event.failureReason = classifyFailureForTelemetry(record.failureReason, record.failureStage);
14246
15623
  }
14247
15624
  if (record.failureStage !== undefined) {
@@ -14269,6 +15646,48 @@ async function safeEmit(sink, event) {
14269
15646
  await sink.emit(event);
14270
15647
  } catch {}
14271
15648
  }
15649
+ async function recordLifecycle(lifecycle, event) {
15650
+ if (lifecycle === undefined) {
15651
+ return;
15652
+ }
15653
+ await lifecycle.recorder.append(event);
15654
+ }
15655
+ function lifecycleFailureFor(subsession) {
15656
+ const normalized = subsession.normalizedFailure;
15657
+ if (normalized !== undefined) {
15658
+ return {
15659
+ class: normalized.failureClass,
15660
+ retryable: normalized.retryable,
15661
+ ...normalized.statusCode === undefined ? {} : { statusCode: normalized.statusCode }
15662
+ };
15663
+ }
15664
+ if (subsession.failureStage === "report") {
15665
+ return {
15666
+ class: subsession.failureReason === REPORT_NOT_DURABLE_REASON ? "report-not-durable" : "report-incomplete",
15667
+ retryable: true
15668
+ };
15669
+ }
15670
+ if (subsession.retrySafe === false) {
15671
+ return { class: "prompt-stop-unconfirmed", retryable: false };
15672
+ }
15673
+ if (subsession.failureReason === undefined) {
15674
+ return;
15675
+ }
15676
+ return {
15677
+ class: classifyFailureForTelemetry(subsession.failureReason, subsession.failureStage),
15678
+ retryable: failedPassDisposition(subsession) !== "terminal"
15679
+ };
15680
+ }
15681
+ function lifecycleOutcomeFor(subsession) {
15682
+ if (subsession.success) {
15683
+ return "succeeded";
15684
+ }
15685
+ const failureClass = lifecycleFailureFor(subsession)?.class;
15686
+ if (failureClass === "cancelled" || failureClass === "interrupted" || failureClass === "unknown") {
15687
+ return failureClass;
15688
+ }
15689
+ return "failed";
15690
+ }
14272
15691
  function buildQueueEvent(observation, sessionID, ts, correlation) {
14273
15692
  const event = {
14274
15693
  v: EVENT_SCHEMA_VERSION,
@@ -14303,7 +15722,7 @@ function buildNodeBudgetExhaustedEvent(roleID, passCount, sessionID, ts) {
14303
15722
  passCount
14304
15723
  };
14305
15724
  }
14306
- async function runRoleTaskWithCorrelation(input, deps, correlation, batch, routed, handoff, reportBatchID) {
15725
+ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, routed, handoff, reportBatchID, attemptLifecycle) {
14307
15726
  const role = roleFor(input);
14308
15727
  if (routed.routeKind === "blocked") {
14309
15728
  throw new Error(coordinatorRoutingBlocked(input.roleID, routed.blockReason));
@@ -14343,9 +15762,48 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
14343
15762
  statusPollMs: deps.statusPollMs,
14344
15763
  now: deps.now,
14345
15764
  delay: deps.delay,
14346
- ...deps.timeoutMs === undefined ? {} : { timeoutMs: deps.timeoutMs }
15765
+ ...deps.timeoutMs === undefined ? {} : { timeoutMs: deps.timeoutMs },
15766
+ ...attemptLifecycle === undefined ? {} : {
15767
+ onSessionCreated: (sessionID) => recordLifecycle(attemptLifecycle.lifecycle, {
15768
+ type: "session.created",
15769
+ operationID: attemptLifecycle.operationID,
15770
+ taskID: attemptLifecycle.taskID,
15771
+ attemptID: attemptLifecycle.attemptID,
15772
+ attempt: attemptLifecycle.attempt,
15773
+ modelIndex: attemptLifecycle.modelIndex,
15774
+ retryIndex: attemptLifecycle.retryIndex,
15775
+ sessionID,
15776
+ ...input.parentSessionID === undefined ? {} : { parentSessionID: input.parentSessionID }
15777
+ })
15778
+ }
14347
15779
  };
14348
15780
  const runtimeId = selection.runtimeId;
15781
+ if (attemptLifecycle !== undefined) {
15782
+ await recordLifecycle(attemptLifecycle.lifecycle, {
15783
+ type: "attempt.model-selected",
15784
+ operationID: attemptLifecycle.operationID,
15785
+ taskID: attemptLifecycle.taskID,
15786
+ attemptID: attemptLifecycle.attemptID,
15787
+ attempt: attemptLifecycle.attempt,
15788
+ modelIndex: attemptLifecycle.modelIndex,
15789
+ retryIndex: attemptLifecycle.retryIndex,
15790
+ decisionID: correlation.decisionID,
15791
+ model: decision2.selected,
15792
+ routeKind: decision2.routeKind === "local" ? "local" : "frontier",
15793
+ selectionCause: attemptLifecycle.selectionCause,
15794
+ ...runtimeId === undefined ? {} : { runtimeID: runtimeId }
15795
+ });
15796
+ await recordLifecycle(attemptLifecycle.lifecycle, {
15797
+ type: "attempt.queued",
15798
+ operationID: attemptLifecycle.operationID,
15799
+ taskID: attemptLifecycle.taskID,
15800
+ attemptID: attemptLifecycle.attemptID,
15801
+ attempt: attemptLifecycle.attempt,
15802
+ modelIndex: attemptLifecycle.modelIndex,
15803
+ retryIndex: attemptLifecycle.retryIndex,
15804
+ ...runtimeId === undefined ? {} : { runtimeID: runtimeId }
15805
+ });
15806
+ }
14349
15807
  let release;
14350
15808
  let queueObservation;
14351
15809
  if (runtimeId !== undefined && deps.runtimeLimiter !== undefined) {
@@ -14368,6 +15826,17 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
14368
15826
  };
14369
15827
  }
14370
15828
  try {
15829
+ if (attemptLifecycle !== undefined) {
15830
+ await recordLifecycle(attemptLifecycle.lifecycle, {
15831
+ type: "attempt.started",
15832
+ operationID: attemptLifecycle.operationID,
15833
+ taskID: attemptLifecycle.taskID,
15834
+ attemptID: attemptLifecycle.attemptID,
15835
+ attempt: attemptLifecycle.attempt,
15836
+ modelIndex: attemptLifecycle.modelIndex,
15837
+ retryIndex: attemptLifecycle.retryIndex
15838
+ });
15839
+ }
14371
15840
  const subsession = await runSubsession(deps.client, request, subsessionDeps);
14372
15841
  const record = buildRecord(input, decision2, subsession, deps, correlation.decisionID, correlation.batchID);
14373
15842
  await safeEmit(deps.sink, correlatedCostRecordToRouteEvent(record));
@@ -14515,6 +15984,9 @@ function failedPassDisposition(subsession) {
14515
15984
  return "terminal";
14516
15985
  if (subsession.failureStage === "report")
14517
15986
  return "retry-incomplete";
15987
+ if (subsession.normalizedFailure !== undefined) {
15988
+ return subsession.normalizedFailure.retryable ? "retry-transport" : "terminal";
15989
+ }
14518
15990
  const failureReason = subsession.failureReason ?? "";
14519
15991
  const causeClass = classifyFailureForTelemetry(failureReason, undefined);
14520
15992
  if (causeClass === "prompt-auth" || causeClass === "prompt-content-filter" || causeClass === "prompt-validation") {
@@ -14529,7 +16001,59 @@ function failedPassDisposition(subsession) {
14529
16001
  }
14530
16002
  return "terminal";
14531
16003
  }
14532
- async function runNodeWithIteration(input, deps, correlation, batch) {
16004
+ function lifecycleSelectionCause(attempt, modelIndex, retryReason, escalationReason) {
16005
+ if (attempt === 0) {
16006
+ return "initial";
16007
+ }
16008
+ if (modelIndex > 0) {
16009
+ return escalationReason === "transport" ? "fallback-transport" : "fallback-incomplete";
16010
+ }
16011
+ return retryReason === "transport" ? "retry-transport" : "retry-incomplete";
16012
+ }
16013
+ function lifecycleContinuation(disposition, pass, passBudget, modelIndex, candidateCount) {
16014
+ if (disposition === "terminal") {
16015
+ return "none";
16016
+ }
16017
+ if (pass < passBudget) {
16018
+ return "retry-same-model";
16019
+ }
16020
+ return modelIndex + 1 < candidateCount ? "fallback-next-model" : "none";
16021
+ }
16022
+ async function recordAttemptTerminal(attempt, result, continuation, succeeded) {
16023
+ if (attempt === undefined) {
16024
+ return;
16025
+ }
16026
+ const failure = succeeded ? undefined : lifecycleFailureFor(result.subsession) ?? {
16027
+ class: "report-incomplete",
16028
+ retryable: true
16029
+ };
16030
+ await recordLifecycle(attempt.lifecycle, {
16031
+ type: "attempt.terminal",
16032
+ operationID: attempt.operationID,
16033
+ taskID: attempt.taskID,
16034
+ attemptID: attempt.attemptID,
16035
+ attempt: attempt.attempt,
16036
+ modelIndex: attempt.modelIndex,
16037
+ retryIndex: attempt.retryIndex,
16038
+ outcome: succeeded ? "succeeded" : result.subsession.success ? "failed" : lifecycleOutcomeFor(result.subsession),
16039
+ continuation,
16040
+ ...result.subsession.sessionID === undefined ? {} : { sessionID: result.subsession.sessionID },
16041
+ ...failure === undefined ? {} : { failure }
16042
+ });
16043
+ }
16044
+ async function recordOperationTerminal(lifecycle, input, batchID, result, attemptCount) {
16045
+ const failure = lifecycleFailureFor(result.subsession);
16046
+ await recordLifecycle(lifecycle, {
16047
+ type: "operation.terminal",
16048
+ operationID: operationID(batchID, input.roleID),
16049
+ taskID: input.taskID ?? batchID,
16050
+ outcome: lifecycleOutcomeFor(result.subsession),
16051
+ attemptCount,
16052
+ ...result.subsession.sessionID === undefined ? {} : { finalSessionID: result.subsession.sessionID },
16053
+ ...failure === undefined ? {} : { failure }
16054
+ });
16055
+ }
16056
+ async function runNodeWithIteration(input, deps, correlation, batch, lifecycle) {
14533
16057
  if (getRoleProfile(input.roleID, input.config.orchestrator?.roles) === undefined) {
14534
16058
  warnUnknownRole(input.roleID, deps.warn, deps.warnedRoles);
14535
16059
  }
@@ -14539,9 +16063,20 @@ async function runNodeWithIteration(input, deps, correlation, batch) {
14539
16063
  usd: 0,
14540
16064
  frontierTokens: 0
14541
16065
  });
16066
+ const lifecycleBatchID = correlation.batchID ?? lifecycle?.recorder.runID;
14542
16067
  const executionPolicy = roleCapablePolicy(input, role);
14543
16068
  const plan2 = chooseModel(routingInput(input, role, executionPolicy, initialBudget));
14544
16069
  if (plan2.routeKind === "blocked") {
16070
+ if (lifecycleBatchID !== undefined) {
16071
+ await recordLifecycle(lifecycle, {
16072
+ type: "operation.terminal",
16073
+ operationID: operationID(lifecycleBatchID, input.roleID),
16074
+ taskID: input.taskID ?? lifecycleBatchID,
16075
+ outcome: "blocked",
16076
+ attemptCount: 0,
16077
+ failure: { class: "routing-blocked", retryable: false }
16078
+ });
16079
+ }
14545
16080
  throw new Error(coordinatorRoutingBlocked(input.roleID, plan2.blockReason));
14546
16081
  }
14547
16082
  const candidates = uniqueModels([plan2.selected, ...plan2.fallbackChain]);
@@ -14566,7 +16101,29 @@ async function runNodeWithIteration(input, deps, correlation, batch) {
14566
16101
  continue modelLoop;
14567
16102
  }
14568
16103
  if (lastResult !== undefined) {
14569
- return withFailure(lastResult, routed.blockReason ?? REPORT_INCOMPLETE_REASON, "report");
16104
+ const blocked = withFailure(lastResult, routed.blockReason ?? REPORT_INCOMPLETE_REASON, "report");
16105
+ if (lifecycleBatchID !== undefined) {
16106
+ await recordLifecycle(lifecycle, {
16107
+ type: "operation.terminal",
16108
+ operationID: operationID(lifecycleBatchID, input.roleID),
16109
+ taskID: input.taskID ?? lifecycleBatchID,
16110
+ outcome: "failed",
16111
+ attemptCount: totalPasses,
16112
+ ...blocked.subsession.sessionID === undefined ? {} : { finalSessionID: blocked.subsession.sessionID },
16113
+ failure: { class: "budget-exhausted", retryable: false }
16114
+ });
16115
+ }
16116
+ return blocked;
16117
+ }
16118
+ if (lifecycleBatchID !== undefined) {
16119
+ await recordLifecycle(lifecycle, {
16120
+ type: "operation.terminal",
16121
+ operationID: operationID(lifecycleBatchID, input.roleID),
16122
+ taskID: input.taskID ?? lifecycleBatchID,
16123
+ outcome: "blocked",
16124
+ attemptCount: 0,
16125
+ failure: { class: "routing-blocked", retryable: false }
16126
+ });
14570
16127
  }
14571
16128
  throw new Error(coordinatorRoutingBlocked(input.roleID, routed.blockReason));
14572
16129
  }
@@ -14589,7 +16146,17 @@ async function runNodeWithIteration(input, deps, correlation, batch) {
14589
16146
  }).reportPath;
14590
16147
  const resolvedReportPath = resolveReportProbePath(input.directory, reportPath);
14591
16148
  const before = await deps.readReportArtifact(resolvedReportPath);
14592
- let result = await runRoleTaskWithCorrelation(input, deps, attemptCorrelation, batch, attemptDecision, modelIndex > 0, reportBatchID);
16149
+ const attemptLifecycle = lifecycle === undefined || lifecycleBatchID === undefined ? undefined : {
16150
+ lifecycle,
16151
+ operationID: operationID(lifecycleBatchID, input.roleID),
16152
+ taskID: input.taskID ?? lifecycleBatchID,
16153
+ attemptID: attemptID(operationID(lifecycleBatchID, input.roleID), totalPasses),
16154
+ attempt: totalPasses,
16155
+ modelIndex,
16156
+ retryIndex: pass - 1,
16157
+ selectionCause: lifecycleSelectionCause(totalPasses, modelIndex, retryReason, escalationReason)
16158
+ };
16159
+ let result = await runRoleTaskWithCorrelation(input, deps, attemptCorrelation, batch, attemptDecision, modelIndex > 0, reportBatchID, attemptLifecycle);
14593
16160
  totalPasses += 1;
14594
16161
  spend = {
14595
16162
  usd: attemptDecision.estimatedCostUSD === null ? spend.usd : spend.usd + attemptDecision.estimatedCostUSD,
@@ -14599,10 +16166,19 @@ async function runNodeWithIteration(input, deps, correlation, batch) {
14599
16166
  result = assessment.result;
14600
16167
  lastResult = result;
14601
16168
  if (assessment.done) {
16169
+ await recordAttemptTerminal(attemptLifecycle, result, "none", true);
16170
+ if (lifecycleBatchID !== undefined) {
16171
+ await recordOperationTerminal(lifecycle, input, lifecycleBatchID, result, totalPasses);
16172
+ }
14602
16173
  return result;
14603
16174
  }
14604
16175
  const disposition = failedPassDisposition(result.subsession);
16176
+ const continuation = lifecycleContinuation(disposition, pass, passBudget, modelIndex, candidates.length);
16177
+ await recordAttemptTerminal(attemptLifecycle, result, continuation, false);
14605
16178
  if (disposition === "terminal") {
16179
+ if (lifecycleBatchID !== undefined) {
16180
+ await recordOperationTerminal(lifecycle, input, lifecycleBatchID, result, totalPasses);
16181
+ }
14606
16182
  return result;
14607
16183
  }
14608
16184
  retryReason = disposition === "retry-transport" ? "transport" : "incomplete";
@@ -14617,14 +16193,34 @@ async function runNodeWithIteration(input, deps, correlation, batch) {
14617
16193
  }
14618
16194
  const sessionID = recordSessionID(input, lastResult.subsession) ?? correlation.batchID ?? input.roleID;
14619
16195
  await safeEmit(deps.sink, buildNodeBudgetExhaustedEvent(input.roleID, totalPasses, sessionID, deps.now()));
16196
+ if (lifecycleBatchID !== undefined) {
16197
+ await recordOperationTerminal(lifecycle, input, lifecycleBatchID, lastResult, totalPasses);
16198
+ }
14620
16199
  return lastResult;
14621
16200
  }
14622
- async function runRoleTasks(inputs, deps) {
14623
- const batchID = deps.newDecisionID();
16201
+ async function runRoleTasks(inputs, deps, options = {}) {
16202
+ const batchID = options.batchID ?? deps.newDecisionID();
16203
+ const lifecycle = options.lifecycle;
14624
16204
  const planned = inputs.map((input) => ({
14625
16205
  input,
14626
16206
  correlation: { decisionID: deps.newDecisionID(), batchID }
14627
16207
  }));
16208
+ if (lifecycle !== undefined) {
16209
+ for (const input of inputs) {
16210
+ const role = roleFor(input);
16211
+ const agentID = input.agentOverride ?? role.opencodeAgent;
16212
+ await recordLifecycle(lifecycle, {
16213
+ type: "operation.queued",
16214
+ operationID: operationID(batchID, input.roleID),
16215
+ taskID: input.taskID ?? batchID,
16216
+ kind: "role",
16217
+ roleID: input.roleID,
16218
+ ...agentID === undefined ? {} : { agentID },
16219
+ parentOperationID: lifecycle.rootOperationID,
16220
+ dependsOnOperationIDs: (input.dependsOn ?? []).map((roleID) => operationID(batchID, roleID))
16221
+ });
16222
+ }
16223
+ }
14628
16224
  const batch = await resolveBatchReachability(inputs, deps);
14629
16225
  const meeting = buildMeetingEvent(inputs, batchID, planned.map((entry) => entry.correlation.decisionID), deps.now());
14630
16226
  if (meeting !== undefined) {
@@ -14643,6 +16239,14 @@ async function runRoleTasks(inputs, deps) {
14643
16239
  runnable.push(input);
14644
16240
  } else {
14645
16241
  cancelled.push(input.roleID);
16242
+ await recordLifecycle(lifecycle, {
16243
+ type: "operation.terminal",
16244
+ operationID: operationID(batchID, input.roleID),
16245
+ taskID: input.taskID ?? batchID,
16246
+ outcome: "blocked",
16247
+ attemptCount: 0,
16248
+ failure: { class: "dependency-failed", retryable: false }
16249
+ });
14646
16250
  }
14647
16251
  }
14648
16252
  const waveResults = await Promise.all(runnable.map((input) => {
@@ -14650,7 +16254,7 @@ async function runRoleTasks(inputs, deps) {
14650
16254
  if (correlation === undefined) {
14651
16255
  throw new Error(`missing correlation for ${input.roleID}`);
14652
16256
  }
14653
- return runNodeWithIteration(input, deps, correlation, batch);
16257
+ return runNodeWithIteration(input, deps, correlation, batch, lifecycle);
14654
16258
  }));
14655
16259
  for (const result of waveResults) {
14656
16260
  allResults.push(result);
@@ -14659,6 +16263,20 @@ async function runRoleTasks(inputs, deps) {
14659
16263
  }
14660
16264
  }
14661
16265
  }
16266
+ const firstFailure = allResults.find((result) => !result.subsession.success);
16267
+ const succeededRun = cancelled.length === 0 && allResults.length === inputs.length && firstFailure === undefined;
16268
+ const runOutcome = succeededRun ? "succeeded" : firstFailure === undefined ? "failed" : lifecycleOutcomeFor(firstFailure.subsession);
16269
+ await recordLifecycle(lifecycle, {
16270
+ type: "run.terminal",
16271
+ outcome: runOutcome,
16272
+ operationCount: inputs.length,
16273
+ ...succeededRun ? {} : {
16274
+ failure: firstFailure === undefined ? { class: "dependency-failed", retryable: false } : lifecycleFailureFor(firstFailure.subsession) ?? {
16275
+ class: "unknown",
16276
+ retryable: false
16277
+ }
16278
+ }
16279
+ });
14662
16280
  return { batchID, results: allResults, cancelled };
14663
16281
  }
14664
16282
 
@@ -14666,7 +16284,7 @@ async function runRoleTasks(inputs, deps) {
14666
16284
  var _pluginWarnedRoles = new Set;
14667
16285
  var EMPTY_ROLE_ID_SET = new Set;
14668
16286
  var SAFE_ROLE_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
14669
- var RoleIDSchema = z25.string().min(1).superRefine((roleID, ctx) => {
16287
+ var RoleIDSchema = z26.string().min(1).superRefine((roleID, ctx) => {
14670
16288
  if (!SAFE_ROLE_ID.test(roleID)) {
14671
16289
  ctx.addIssue({
14672
16290
  code: "custom",
@@ -14674,16 +16292,16 @@ var RoleIDSchema = z25.string().min(1).superRefine((roleID, ctx) => {
14674
16292
  });
14675
16293
  }
14676
16294
  });
14677
- var RoleAssignmentSchema = z25.object({
16295
+ var RoleAssignmentSchema = z26.object({
14678
16296
  roleID: RoleIDSchema.describe("Role identifier from the roster"),
14679
- prompt: z25.string().min(1).describe("Work prompt for this role"),
14680
- title: z25.string().min(1).optional().describe("Human-readable title"),
14681
- dependsOn: z25.array(RoleIDSchema).optional().describe("Role IDs this assignment depends on (DAG edges)")
16297
+ prompt: z26.string().min(1).describe("Work prompt for this role"),
16298
+ title: z26.string().min(1).optional().describe("Human-readable title"),
16299
+ dependsOn: z26.array(RoleIDSchema).optional().describe("Role IDs this assignment depends on (DAG edges)")
14682
16300
  }).strict();
14683
- var OrchestratePayloadSchema = z25.object({
14684
- assignments: z25.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
14685
- parentSessionID: z25.string().min(1).optional().describe("Parent session for subsessions"),
14686
- directory: z25.string().min(1).optional().describe("Working directory override")
16301
+ var OrchestratePayloadSchema = z26.object({
16302
+ assignments: z26.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
16303
+ parentSessionID: z26.string().min(1).optional().describe("Parent session for subsessions"),
16304
+ directory: z26.string().min(1).optional().describe("Working directory override")
14687
16305
  }).strict();
14688
16306
  function validateAssignmentDependencies(assignments) {
14689
16307
  const ids = new Set;
@@ -14741,17 +16359,118 @@ function validateAssignmentDependencies(assignments) {
14741
16359
  }
14742
16360
  return;
14743
16361
  }
16362
+ function isRecord5(value) {
16363
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16364
+ }
16365
+ function nestedRecord2(value, key) {
16366
+ const nested = value[key];
16367
+ return isRecord5(nested) ? nested : undefined;
16368
+ }
16369
+ function numberField(records, keys) {
16370
+ for (const record of records) {
16371
+ for (const key of keys) {
16372
+ const value = record[key];
16373
+ if (typeof value === "number" && Number.isInteger(value)) {
16374
+ return value;
16375
+ }
16376
+ }
16377
+ }
16378
+ return;
16379
+ }
16380
+ function booleanField(records, keys) {
16381
+ for (const record of records) {
16382
+ for (const key of keys) {
16383
+ const value = record[key];
16384
+ if (typeof value === "boolean") {
16385
+ return value;
16386
+ }
16387
+ }
16388
+ }
16389
+ return;
16390
+ }
16391
+ function boundedErrorTokens(records) {
16392
+ const tokens = [];
16393
+ for (const record of records) {
16394
+ for (const key of ["name", "code", "type", "message"]) {
16395
+ const value = record[key];
16396
+ if (typeof value === "string" && value.length <= 100) {
16397
+ tokens.push(value.toLowerCase());
16398
+ }
16399
+ }
16400
+ }
16401
+ return tokens.join(" ");
16402
+ }
16403
+ function promptFailureClass(statusCode, tokens) {
16404
+ if (statusCode === 401 || statusCode === 403 || /\bauth(?:entication|orization)?\b|unauthorized|forbidden/u.test(tokens)) {
16405
+ return "prompt-auth";
16406
+ }
16407
+ if (/content[-_ ]?filter|moderation|safety/u.test(tokens)) {
16408
+ return "prompt-content-filter";
16409
+ }
16410
+ if (/context[-_ ]?(?:length|window)|token[-_ ]?limit/u.test(tokens)) {
16411
+ return "prompt-context-length";
16412
+ }
16413
+ if (statusCode === 429 || /rate[-_ ]?limit|quota/u.test(tokens)) {
16414
+ return "prompt-rate-limited";
16415
+ }
16416
+ if (statusCode === 408 || /timeout|timedout|deadline/u.test(tokens)) {
16417
+ return "prompt-timeout";
16418
+ }
16419
+ if (/connection|network|socket|econnrefused|econnreset|enotfound/u.test(tokens)) {
16420
+ return "prompt-connection";
16421
+ }
16422
+ if (statusCode === 400 || statusCode === 422 || /validation|invalid[-_ ]?request|bad[-_ ]?request/u.test(tokens)) {
16423
+ return "prompt-validation";
16424
+ }
16425
+ if (statusCode !== undefined && statusCode >= 500 && statusCode <= 599) {
16426
+ return "prompt-server";
16427
+ }
16428
+ return "unknown";
16429
+ }
16430
+ function retryableFailure(failureClass, statusCode, declared) {
16431
+ if (failureClass === "prompt-auth" || failureClass === "prompt-content-filter" || failureClass === "prompt-context-length" || failureClass === "prompt-validation") {
16432
+ return false;
16433
+ }
16434
+ if (declared !== undefined) {
16435
+ return declared;
16436
+ }
16437
+ return failureClass === "prompt-timeout" || failureClass === "prompt-rate-limited" || failureClass === "prompt-connection" || failureClass === "prompt-server" || statusCode !== undefined && statusCode >= 500 && statusCode <= 599;
16438
+ }
16439
+ function normalizeSdkSessionFailure(error, stage) {
16440
+ const root = isRecord5(error) ? error : {};
16441
+ const data = nestedRecord2(root, "data");
16442
+ const nestedError = nestedRecord2(root, "error");
16443
+ const records = [
16444
+ root,
16445
+ ...data === undefined ? [] : [data],
16446
+ ...nestedError === undefined ? [] : [nestedError]
16447
+ ];
16448
+ const statusCode = numberField(records, ["statusCode", "status"]);
16449
+ const declaredRetryable = booleanField(records, ["isRetryable", "retryable"]);
16450
+ const failureClass = stage === "create" ? "create-rejected" : promptFailureClass(statusCode, boundedErrorTokens(records));
16451
+ return {
16452
+ kind: "normalized-session-failure",
16453
+ failureClass,
16454
+ retryable: retryableFailure(failureClass, statusCode, declaredRetryable),
16455
+ ...statusCode === undefined ? {} : { statusCode }
16456
+ };
16457
+ }
16458
+ function nestedAssistantFailure(data) {
16459
+ if (!isRecord5(data)) {
16460
+ return;
16461
+ }
16462
+ return nestedRecord2(data, "info")?.error;
16463
+ }
14744
16464
  function adaptSdkClient(sdk, orchestratedModelOwnership) {
14745
16465
  return {
14746
16466
  session: {
14747
16467
  create: async (args) => {
14748
16468
  const result = await sdk.session.create(args);
14749
16469
  const out = {};
14750
- if (result.data !== undefined) {
14751
- out.data = result.data;
14752
- }
14753
16470
  if (result.error !== undefined) {
14754
- out.error = result.error;
16471
+ out.error = normalizeSdkSessionFailure(result.error, "create");
16472
+ } else if (result.data !== undefined) {
16473
+ out.data = result.data;
14755
16474
  }
14756
16475
  return out;
14757
16476
  },
@@ -14764,11 +16483,15 @@ function adaptSdkClient(sdk, orchestratedModelOwnership) {
14764
16483
  try {
14765
16484
  const result = await sdk.session.prompt(args);
14766
16485
  const out = {};
14767
- if (result.data !== undefined) {
14768
- out.data = result.data;
14769
- }
14770
16486
  if (result.error !== undefined) {
14771
- out.error = result.error;
16487
+ out.error = normalizeSdkSessionFailure(result.error, "prompt");
16488
+ } else {
16489
+ const nestedFailure = nestedAssistantFailure(result.data);
16490
+ if (nestedFailure !== undefined) {
16491
+ out.error = normalizeSdkSessionFailure(nestedFailure, "prompt");
16492
+ } else if (result.data !== undefined) {
16493
+ out.data = result.data;
16494
+ }
14772
16495
  }
14773
16496
  return out;
14774
16497
  } finally {
@@ -14778,22 +16501,20 @@ function adaptSdkClient(sdk, orchestratedModelOwnership) {
14778
16501
  abort: async (args) => {
14779
16502
  const result = await sdk.session.abort(args);
14780
16503
  const out = {};
14781
- if (result.data !== undefined) {
14782
- out.data = result.data;
14783
- }
14784
16504
  if (result.error !== undefined) {
14785
- out.error = result.error;
16505
+ out.error = normalizeSdkSessionFailure(result.error, "prompt");
16506
+ } else if (result.data !== undefined) {
16507
+ out.data = result.data;
14786
16508
  }
14787
16509
  return out;
14788
16510
  },
14789
16511
  status: async (args) => {
14790
16512
  const result = await sdk.session.status(args);
14791
16513
  const out = {};
14792
- if (result.data !== undefined) {
14793
- out.data = result.data;
14794
- }
14795
16514
  if (result.error !== undefined) {
14796
- out.error = result.error;
16515
+ out.error = normalizeSdkSessionFailure(result.error, "prompt");
16516
+ } else if (result.data !== undefined) {
16517
+ out.data = result.data;
14797
16518
  }
14798
16519
  return out;
14799
16520
  }
@@ -15208,6 +16929,8 @@ function createOrchestrateTool(deps) {
15208
16929
  if (depError !== undefined) {
15209
16930
  return dependencyValidationError(depError.code, depError.detail);
15210
16931
  }
16932
+ let activeLifecycleRecorder;
16933
+ let lifecycleStatus;
15211
16934
  try {
15212
16935
  const rosterEntries = await deps.rosterEntries();
15213
16936
  const rosterRoleIDs = rosterEntries.length === 0 ? EMPTY_ROLE_ID_SET : new Set(rosterEntries.map((entry) => entry.roleID));
@@ -15224,6 +16947,27 @@ function createOrchestrateTool(deps) {
15224
16947
  return missingAgentDefinition(agentName, input.roleID);
15225
16948
  }
15226
16949
  }
16950
+ const batchID = deps.newDecisionID();
16951
+ if (deps.lifecycleRecorder !== undefined) {
16952
+ const rootOperationID = operationID(batchID, "openteam");
16953
+ const begun = await deps.lifecycleRecorder.beginRun({
16954
+ runID: batchID,
16955
+ source: "plugin",
16956
+ executionAuthority: "coordinator",
16957
+ root: {
16958
+ operationID: rootOperationID,
16959
+ taskID: batchID,
16960
+ kind: "coordinator",
16961
+ roleID: "openteam",
16962
+ agentID: "openteam",
16963
+ ...parsed.data.parentSessionID === undefined ? {} : { sessionID: parsed.data.parentSessionID }
16964
+ }
16965
+ });
16966
+ lifecycleStatus = begun.status;
16967
+ if (begun.ok) {
16968
+ activeLifecycleRecorder = begun.recorder;
16969
+ }
16970
+ }
15227
16971
  const coordinatorDeps = {
15228
16972
  client: deps.client,
15229
16973
  sink: deps.sink,
@@ -15242,7 +16986,16 @@ function createOrchestrateTool(deps) {
15242
16986
  delay: deps.delay,
15243
16987
  ...deps.timeoutMs === undefined ? {} : { timeoutMs: deps.timeoutMs }
15244
16988
  };
15245
- const batch = await runRoleTasks(inputs, coordinatorDeps);
16989
+ const batch = await runRoleTasks(inputs, coordinatorDeps, {
16990
+ batchID,
16991
+ ...activeLifecycleRecorder === undefined ? {} : {
16992
+ lifecycle: {
16993
+ recorder: activeLifecycleRecorder,
16994
+ rootOperationID: operationID(batchID, "openteam")
16995
+ }
16996
+ }
16997
+ });
16998
+ lifecycleStatus = activeLifecycleRecorder?.status() ?? lifecycleStatus;
15246
16999
  const projected = projectResults(batch.results, batch.cancelled);
15247
17000
  const shadowResult = await runShadowProducer(deps, parsed.data, batch);
15248
17001
  const base = formatResult(projected);
@@ -15250,15 +17003,26 @@ function createOrchestrateTool(deps) {
15250
17003
  const withAdvisory = advisory === undefined ? base : `${base}
15251
17004
 
15252
17005
  ${advisory}`;
17006
+ const recordingAdvisory = lifecycleStatus === undefined ? undefined : lifecycleRecordingAdvisory(lifecycleStatus);
17007
+ const withRecordingStatus = recordingAdvisory === undefined ? withAdvisory : `${withAdvisory}
17008
+
17009
+ ⚠ ${recordingAdvisory}`;
15253
17010
  if (!shadowResult.ok) {
15254
- return `${withAdvisory}
17011
+ return `${withRecordingStatus}
15255
17012
  ⚠ shadow observation failed: ${shadowResult.failureClass}`;
15256
17013
  }
15257
- return withAdvisory;
17014
+ return withRecordingStatus;
15258
17015
  } catch (error) {
17016
+ if (activeLifecycleRecorder !== undefined) {
17017
+ lifecycleStatus = await activeLifecycleRecorder.release();
17018
+ }
15259
17019
  const reason = error instanceof Error ? error.message : String(error);
15260
17020
  const result = { kind: "error", reason };
15261
- return formatResult(result);
17021
+ const base = formatResult(result);
17022
+ const advisory = lifecycleStatus === undefined ? undefined : lifecycleRecordingAdvisory(lifecycleStatus);
17023
+ return advisory === undefined ? base : `${base}
17024
+
17025
+ ⚠ ${advisory}`;
15262
17026
  }
15263
17027
  }
15264
17028
  });
@@ -15266,7 +17030,7 @@ ${advisory}`;
15266
17030
 
15267
17031
  // src/plugin/registerCastTool.ts
15268
17032
  import { tool as tool5 } from "@opencode-ai/plugin";
15269
- import { z as z26 } from "zod";
17033
+ import { z as z27 } from "zod";
15270
17034
 
15271
17035
  // src/orchestrator/rosterPersistence.ts
15272
17036
  function rosterFence(roster) {
@@ -15301,13 +17065,13 @@ async function persistRosterPreservingProse(storage, roster, path4 = OPENTEAM_RO
15301
17065
  }
15302
17066
 
15303
17067
  // src/plugin/registerCastTool.ts
15304
- var CastEntrySchema = z26.object({
15305
- roleID: z26.string().min(1).describe("Stable role identifier, e.g. scribe or guardian"),
15306
- agentName: z26.string().min(1).describe("Themed character name assigned to the role")
17068
+ var CastEntrySchema = z27.object({
17069
+ roleID: z27.string().min(1).describe("Stable role identifier, e.g. scribe or guardian"),
17070
+ agentName: z27.string().min(1).describe("Themed character name assigned to the role")
15307
17071
  }).strict();
15308
- var RegisterCastPayloadSchema = z26.object({
15309
- universe: z26.string().min(1).describe("Casting universe for this roster"),
15310
- entries: z26.array(CastEntrySchema).min(1).describe("Role-to-name mappings")
17072
+ var RegisterCastPayloadSchema = z27.object({
17073
+ universe: z27.string().min(1).describe("Casting universe for this roster"),
17074
+ entries: z27.array(CastEntrySchema).min(1).describe("Role-to-name mappings")
15311
17075
  }).strict();
15312
17076
  function offendingFields(error) {
15313
17077
  const fields = error.issues.flatMap((issue) => {
@@ -15489,12 +17253,12 @@ async function createBunSqliteDatabase(path4) {
15489
17253
  }
15490
17254
 
15491
17255
  // src/memory/embedText.ts
15492
- function normalize(parts) {
17256
+ function normalize2(parts) {
15493
17257
  return parts.map((part) => part.trim()).filter((part) => part.length > 0).join(" ");
15494
17258
  }
15495
17259
  function recordEmbeddingText(record) {
15496
17260
  if (record.kind === "fact") {
15497
- return normalize([
17261
+ return normalize2([
15498
17262
  record.subject,
15499
17263
  record.predicate,
15500
17264
  record.object,
@@ -15502,21 +17266,21 @@ function recordEmbeddingText(record) {
15502
17266
  ]);
15503
17267
  }
15504
17268
  if (record.kind === "preference") {
15505
- return normalize([
17269
+ return normalize2([
15506
17270
  record.category,
15507
17271
  record.preference,
15508
17272
  record.context ?? ""
15509
17273
  ]);
15510
17274
  }
15511
17275
  if (record.kind === "relation") {
15512
- return normalize([
17276
+ return normalize2([
15513
17277
  record.from,
15514
17278
  record.predicate,
15515
17279
  record.to,
15516
17280
  record.annotation ?? ""
15517
17281
  ]);
15518
17282
  }
15519
- return normalize([record.canonicalName, record.type, ...record.aliases]);
17283
+ return normalize2([record.canonicalName, record.type, ...record.aliases]);
15520
17284
  }
15521
17285
 
15522
17286
  // src/storage/index/memoryEmbeddings.ts
@@ -15596,6 +17360,799 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
15596
17360
  }
15597
17361
  }
15598
17362
 
17363
+ // src/storage/lifecycle/fsLifecycleJournal.ts
17364
+ import { randomUUID as randomUUID2 } from "node:crypto";
17365
+ import {
17366
+ lstatSync as lstatSync3,
17367
+ mkdirSync as mkdirSync3,
17368
+ readdirSync as readdirSync2,
17369
+ renameSync as renameSync2,
17370
+ rmSync as rmSync3,
17371
+ statSync,
17372
+ unlinkSync as unlinkSync2
17373
+ } from "node:fs";
17374
+ import { join as join12, resolve as resolve7 } from "node:path";
17375
+ import { ZodError } from "zod";
17376
+
17377
+ // src/storage/lifecycle/codec.ts
17378
+ import { z as z28 } from "zod";
17379
+ var LIFECYCLE_CODEC_VERSION = 1;
17380
+ var LIFECYCLE_GENESIS_DIGEST = sha256Hex("openteam/lifecycle-journal/genesis/v1");
17381
+ var NUL5 = "\x00";
17382
+ var NEWLINE2 = `
17383
+ `;
17384
+ var frameSchema = z28.object({
17385
+ v: z28.literal(LIFECYCLE_CODEC_VERSION),
17386
+ seq: z28.number().int().nonnegative(),
17387
+ prev: z28.string().min(1),
17388
+ sum: z28.string().min(1),
17389
+ event: z28.unknown()
17390
+ }).strict();
17391
+ function canonical2(value) {
17392
+ if (Array.isArray(value)) {
17393
+ return `[${value.map(canonical2).join(",")}]`;
17394
+ }
17395
+ if (value === null || typeof value !== "object") {
17396
+ return JSON.stringify(value);
17397
+ }
17398
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key2, child]) => `${JSON.stringify(key2)}:${canonical2(child)}`).join(",")}}`;
17399
+ }
17400
+ function lifecycleFrameDigest(previousDigest, event) {
17401
+ return sha256Hex(`${previousDigest}${NUL5}${canonical2(event)}`);
17402
+ }
17403
+
17404
+ class LifecycleCodecError extends Error {
17405
+ code;
17406
+ constructor(code) {
17407
+ super(code);
17408
+ this.name = "LifecycleCodecError";
17409
+ this.code = code;
17410
+ }
17411
+ }
17412
+ function encodeLifecycleFrame(previousDigest, event) {
17413
+ const digest = lifecycleFrameDigest(previousDigest, event);
17414
+ return {
17415
+ line: canonical2({
17416
+ v: LIFECYCLE_CODEC_VERSION,
17417
+ seq: event.seq,
17418
+ prev: previousDigest,
17419
+ sum: digest,
17420
+ event
17421
+ }),
17422
+ digest
17423
+ };
17424
+ }
17425
+ function encodeLifecycleJournal(events) {
17426
+ let previousDigest = LIFECYCLE_GENESIS_DIGEST;
17427
+ let encoded = "";
17428
+ for (const event of events) {
17429
+ const frame = encodeLifecycleFrame(previousDigest, event);
17430
+ encoded += `${frame.line}${NEWLINE2}`;
17431
+ previousDigest = frame.digest;
17432
+ }
17433
+ return encoded;
17434
+ }
17435
+ function decodeLifecycleJournal(text) {
17436
+ const segments = text.split(NEWLINE2);
17437
+ const trailing = segments.pop() ?? "";
17438
+ const partialTail = trailing.length > 0;
17439
+ const events = [];
17440
+ let previousDigest = LIFECYCLE_GENESIS_DIGEST;
17441
+ for (const [index, raw] of segments.entries()) {
17442
+ let unknownFrame;
17443
+ try {
17444
+ unknownFrame = JSON.parse(raw);
17445
+ } catch {
17446
+ throw new LifecycleCodecError("bad-frame");
17447
+ }
17448
+ const parsedFrame = frameSchema.safeParse(unknownFrame);
17449
+ if (!parsedFrame.success) {
17450
+ const version = typeof unknownFrame === "object" && unknownFrame !== null && "v" in unknownFrame ? unknownFrame.v : undefined;
17451
+ throw new LifecycleCodecError(version !== undefined && version !== LIFECYCLE_CODEC_VERSION ? "bad-version" : "bad-frame");
17452
+ }
17453
+ const frame = parsedFrame.data;
17454
+ let event;
17455
+ try {
17456
+ event = parseLifecycleEvent(frame.event);
17457
+ } catch {
17458
+ throw new LifecycleCodecError("bad-frame");
17459
+ }
17460
+ if (frame.seq !== index || event.seq !== index) {
17461
+ throw new LifecycleCodecError("bad-sequence");
17462
+ }
17463
+ if (frame.prev !== previousDigest) {
17464
+ throw new LifecycleCodecError("chain-break");
17465
+ }
17466
+ if (frame.sum !== lifecycleFrameDigest(previousDigest, event)) {
17467
+ throw new LifecycleCodecError("bad-checksum");
17468
+ }
17469
+ events.push(event);
17470
+ previousDigest = frame.sum;
17471
+ }
17472
+ return { events, digest: previousDigest, partialTail };
17473
+ }
17474
+
17475
+ // src/storage/lifecycle/fsLifecycleSupport.ts
17476
+ import { randomUUID } from "node:crypto";
17477
+ import {
17478
+ appendFileSync,
17479
+ closeSync as closeSync2,
17480
+ existsSync as existsSync2,
17481
+ fsyncSync as fsyncSync2,
17482
+ lstatSync as lstatSync2,
17483
+ mkdirSync as mkdirSync2,
17484
+ openSync as openSync2,
17485
+ readFileSync as readFileSync3,
17486
+ readSync,
17487
+ realpathSync as realpathSync2,
17488
+ renameSync,
17489
+ rmSync as rmSync2,
17490
+ unlinkSync,
17491
+ writeSync as writeSync2
17492
+ } from "node:fs";
17493
+ import { basename as basename2, dirname as dirname4, join as join11, relative as relative2, resolve as resolve6 } from "node:path";
17494
+ var SAFE_RUN_ID = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,126}[A-Za-z0-9])?$/u;
17495
+
17496
+ class LifecycleFsError extends Error {
17497
+ kind;
17498
+ constructor(kind) {
17499
+ super(kind);
17500
+ this.name = "LifecycleFsError";
17501
+ this.kind = kind;
17502
+ }
17503
+ }
17504
+ function isMissingFsError(error) {
17505
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
17506
+ }
17507
+ function isExistsFsError(error) {
17508
+ return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
17509
+ }
17510
+ function validateRunID(runID) {
17511
+ if (!SAFE_RUN_ID.test(runID) || runID === "." || runID === "..") {
17512
+ throw new LifecycleFsError("corrupt");
17513
+ }
17514
+ }
17515
+ function resolvedLifecycleRoot(root) {
17516
+ try {
17517
+ const absolute = resolve6(root);
17518
+ mkdirSync2(absolute, { recursive: true });
17519
+ const rootStat = lstatSync2(absolute);
17520
+ if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
17521
+ throw new LifecycleFsError("unavailable");
17522
+ }
17523
+ return realpathSync2.native(absolute);
17524
+ } catch (error) {
17525
+ if (error instanceof LifecycleFsError) {
17526
+ throw error;
17527
+ }
17528
+ throw new LifecycleFsError("unavailable");
17529
+ }
17530
+ }
17531
+ function directRunPath(root, runID) {
17532
+ validateRunID(runID);
17533
+ const path4 = join11(root, runID);
17534
+ if (dirname4(path4) !== root || basename2(path4) !== runID) {
17535
+ throw new LifecycleFsError("corrupt");
17536
+ }
17537
+ return path4;
17538
+ }
17539
+ function assertExistingSafeRun(root, runID) {
17540
+ const path4 = directRunPath(root, runID);
17541
+ let stats;
17542
+ try {
17543
+ stats = lstatSync2(path4);
17544
+ } catch (error) {
17545
+ if (isMissingFsError(error)) {
17546
+ throw new LifecycleFsError("missing");
17547
+ }
17548
+ throw new LifecycleFsError("unavailable");
17549
+ }
17550
+ if (!stats.isDirectory() || stats.isSymbolicLink()) {
17551
+ throw new LifecycleFsError("corrupt");
17552
+ }
17553
+ let real;
17554
+ try {
17555
+ real = realpathSync2.native(path4);
17556
+ } catch {
17557
+ throw new LifecycleFsError("unavailable");
17558
+ }
17559
+ const child = relative2(root, real);
17560
+ if (child === "" || child.startsWith("..") || resolve6(root, child) !== real || dirname4(real) !== root) {
17561
+ throw new LifecycleFsError("corrupt");
17562
+ }
17563
+ return real;
17564
+ }
17565
+ function assertSafeRegularFile(path4) {
17566
+ let stats;
17567
+ try {
17568
+ stats = lstatSync2(path4);
17569
+ } catch (error) {
17570
+ if (isMissingFsError(error)) {
17571
+ throw new LifecycleFsError("missing");
17572
+ }
17573
+ throw new LifecycleFsError("unavailable");
17574
+ }
17575
+ if (!stats.isFile() || stats.isSymbolicLink()) {
17576
+ throw new LifecycleFsError("corrupt");
17577
+ }
17578
+ }
17579
+ function readUtf8(path4) {
17580
+ try {
17581
+ assertSafeRegularFile(path4);
17582
+ return readFileSync3(path4, "utf8");
17583
+ } catch (error) {
17584
+ if (error instanceof LifecycleFsError && error.kind === "missing") {
17585
+ return;
17586
+ }
17587
+ throw error;
17588
+ }
17589
+ }
17590
+ function writeDurable2(path4, content) {
17591
+ const descriptor = openSync2(path4, "w");
17592
+ try {
17593
+ writeSync2(descriptor, content);
17594
+ fsyncSync2(descriptor);
17595
+ } finally {
17596
+ closeSync2(descriptor);
17597
+ }
17598
+ }
17599
+ function appendDurable2(path4, content) {
17600
+ const descriptor = openSync2(path4, "a");
17601
+ try {
17602
+ appendFileSync(descriptor, content, "utf8");
17603
+ fsyncSync2(descriptor);
17604
+ } finally {
17605
+ closeSync2(descriptor);
17606
+ }
17607
+ }
17608
+ function writeDurableAtomically(path4, content) {
17609
+ const temporary = join11(dirname4(path4), `.${basename2(path4)}.${randomUUID()}.next`);
17610
+ try {
17611
+ writeDurable2(temporary, content);
17612
+ renameSync(temporary, path4);
17613
+ } finally {
17614
+ rmSync2(temporary, { force: true });
17615
+ }
17616
+ }
17617
+ function readLockContents(path4) {
17618
+ if (!existsSync2(path4)) {
17619
+ return;
17620
+ }
17621
+ const descriptor = openSync2(path4, "r");
17622
+ try {
17623
+ const buffer = Buffer.alloc(128);
17624
+ const bytes = readSync(descriptor, buffer, 0, buffer.length, 0);
17625
+ return buffer.subarray(0, bytes).toString("utf8");
17626
+ } finally {
17627
+ closeSync2(descriptor);
17628
+ }
17629
+ }
17630
+ function withRunMutationLock(runDir, mutate) {
17631
+ const lockPath = join11(runDir, ".mutation-lock");
17632
+ const lockToken = randomUUID();
17633
+ let descriptor;
17634
+ try {
17635
+ descriptor = openSync2(lockPath, "wx");
17636
+ } catch (error) {
17637
+ if (isExistsFsError(error)) {
17638
+ throw new LifecycleFsError("conflict");
17639
+ }
17640
+ throw new LifecycleFsError("unavailable");
17641
+ }
17642
+ try {
17643
+ writeSync2(descriptor, lockToken);
17644
+ fsyncSync2(descriptor);
17645
+ } finally {
17646
+ closeSync2(descriptor);
17647
+ }
17648
+ try {
17649
+ return mutate();
17650
+ } finally {
17651
+ try {
17652
+ if (readLockContents(lockPath) === lockToken) {
17653
+ unlinkSync(lockPath);
17654
+ }
17655
+ } catch {}
17656
+ }
17657
+ }
17658
+
17659
+ // src/storage/lifecycle/fsLifecycleJournal.ts
17660
+ var EVENTS_FILE = "events.ndjson";
17661
+ var METADATA_FILE = "run.json";
17662
+ var OWNER_FILE2 = "owner";
17663
+ var KNOWN_RUN_FILES = new Set([EVENTS_FILE, METADATA_FILE, OWNER_FILE2]);
17664
+ function failure(code, options = {}) {
17665
+ const { recoverable = false, ...context } = options;
17666
+ return { code, recoverable, ...context };
17667
+ }
17668
+ function ok(value) {
17669
+ return { ok: true, value };
17670
+ }
17671
+ function failed(error) {
17672
+ return { ok: false, error };
17673
+ }
17674
+ function mapFsFailure(error, runID) {
17675
+ const context = runID === undefined ? {} : { runID };
17676
+ if (error instanceof ZodError || error instanceof RedactionError) {
17677
+ return failure("corrupt", context);
17678
+ }
17679
+ if (error instanceof LifecycleFsError) {
17680
+ switch (error.kind) {
17681
+ case "missing":
17682
+ return failure("not-found", context);
17683
+ case "conflict":
17684
+ return failure("writer-conflict", { recoverable: true, ...context });
17685
+ case "corrupt":
17686
+ return failure("corrupt", context);
17687
+ case "unavailable":
17688
+ return failure("unavailable", { recoverable: true, ...context });
17689
+ }
17690
+ }
17691
+ return failure("unavailable", { recoverable: true, ...context });
17692
+ }
17693
+ function parseJson(text, parse2) {
17694
+ try {
17695
+ return parse2(JSON.parse(text));
17696
+ } catch {
17697
+ throw new LifecycleFsError("corrupt");
17698
+ }
17699
+ }
17700
+ function issueWriterToken() {
17701
+ return randomUUID2().replaceAll("-", "");
17702
+ }
17703
+ function createLease(runID, writerToken, input) {
17704
+ return parseLifecycleWriterLease({
17705
+ version: 1,
17706
+ runID,
17707
+ writerToken,
17708
+ processID: input.writer.processID,
17709
+ processInstanceID: input.writer.processInstanceID,
17710
+ acquiredAt: input.now,
17711
+ heartbeatAt: input.now,
17712
+ expiresAt: input.now + input.leaseDurationMs
17713
+ });
17714
+ }
17715
+ function readLease2(runDir) {
17716
+ const text = readUtf8(join12(runDir, OWNER_FILE2));
17717
+ return text === undefined ? undefined : parseJson(text, parseLifecycleWriterLease);
17718
+ }
17719
+ function readRun(runDir, runID) {
17720
+ const metadataPath = join12(runDir, METADATA_FILE);
17721
+ const eventsPath = join12(runDir, EVENTS_FILE);
17722
+ assertSafeRegularFile(metadataPath);
17723
+ assertSafeRegularFile(eventsPath);
17724
+ const metadataText = readUtf8(metadataPath);
17725
+ const eventsText = readUtf8(eventsPath);
17726
+ if (metadataText === undefined || eventsText === undefined) {
17727
+ throw new LifecycleFsError("corrupt");
17728
+ }
17729
+ const metadata = parseJson(metadataText, parseLifecycleRunMetadata);
17730
+ if (metadata.runID !== runID) {
17731
+ throw new LifecycleFsError("corrupt");
17732
+ }
17733
+ let decoded;
17734
+ try {
17735
+ decoded = decodeLifecycleJournal(eventsText);
17736
+ } catch {
17737
+ throw new LifecycleFsError("corrupt");
17738
+ }
17739
+ for (const [index, event] of decoded.events.entries()) {
17740
+ if (event.runID !== runID || event.seq !== index) {
17741
+ throw new LifecycleFsError("corrupt");
17742
+ }
17743
+ }
17744
+ const first = decoded.events[0];
17745
+ if (first === undefined || first.type !== "run.started" || first.source !== metadata.source || first.executionAuthority !== metadata.executionAuthority || first.root.operationID !== metadata.root.operationID || first.root.taskID !== metadata.root.taskID) {
17746
+ throw new LifecycleFsError("corrupt");
17747
+ }
17748
+ return { metadata, decoded };
17749
+ }
17750
+ function terminalOutcome(events) {
17751
+ const last = events.at(-1);
17752
+ return last?.type === "run.terminal" ? last.outcome : undefined;
17753
+ }
17754
+ function summaryForRun(runDir, runID) {
17755
+ const loaded = readRun(runDir, runID);
17756
+ const lastEvent = loaded.decoded.events.at(-1);
17757
+ const terminal = terminalOutcome(loaded.decoded.events);
17758
+ return {
17759
+ metadata: loaded.metadata,
17760
+ updatedAt: lastEvent?.at ?? loaded.metadata.createdAt,
17761
+ ...terminal === undefined ? {} : { terminalOutcome: terminal },
17762
+ head: loaded.decoded.events.length,
17763
+ integrity: loaded.decoded.partialTail ? "partial-tail" : "clean"
17764
+ };
17765
+ }
17766
+ function runBytes(runDir) {
17767
+ return readdirSync2(runDir).reduce((total, entry) => {
17768
+ const path4 = join12(runDir, entry);
17769
+ const stats = lstatSync3(path4);
17770
+ if (!stats.isFile() || stats.isSymbolicLink()) {
17771
+ throw new LifecycleFsError("corrupt");
17772
+ }
17773
+ return total + stats.size;
17774
+ }, 0);
17775
+ }
17776
+ function validateRetention(policy) {
17777
+ if (!Number.isInteger(policy.maxTerminalRuns) || policy.maxTerminalRuns < 0 || !Number.isFinite(policy.maxAgeMs) || policy.maxAgeMs < 0 || !Number.isFinite(policy.maxBytes) || policy.maxBytes < 0) {
17778
+ throw new LifecycleFsError("corrupt");
17779
+ }
17780
+ }
17781
+ function canDeleteTerminalRun(runDir, runID, now) {
17782
+ const summary = summaryForRun(runDir, runID);
17783
+ if (summary.integrity !== "clean" || summary.terminalOutcome === undefined) {
17784
+ return false;
17785
+ }
17786
+ const entries = readdirSync2(runDir);
17787
+ if (entries.some((entry) => entry !== ".mutation-lock" && !KNOWN_RUN_FILES.has(entry))) {
17788
+ return false;
17789
+ }
17790
+ const lease = readLease2(runDir);
17791
+ if (lease === undefined) {
17792
+ return true;
17793
+ }
17794
+ if (lease.runID !== runID || now < lease.heartbeatAt) {
17795
+ return false;
17796
+ }
17797
+ return now >= lease.expiresAt;
17798
+ }
17799
+ var createFsLifecycleJournal = (options) => {
17800
+ const configuredRoot = resolve7(options.root);
17801
+ const now = options.now ?? Date.now;
17802
+ const mutateRun = options.withMutationLock ?? withRunMutationLock;
17803
+ return {
17804
+ async start(input, leaseInput) {
17805
+ let runID;
17806
+ let stagingPath;
17807
+ try {
17808
+ const root = resolvedLifecycleRoot(configuredRoot);
17809
+ const parsedInput = parseLifecycleRunStartInput(input);
17810
+ const parsedLeaseInput = parseLifecycleWriterLeaseInput(leaseInput);
17811
+ runID = parsedInput.runID;
17812
+ validateRunID(runID);
17813
+ const runPath = directRunPath(root, runID);
17814
+ try {
17815
+ statSync(runPath);
17816
+ return failed(failure("already-exists", { runID }));
17817
+ } catch (error) {
17818
+ if (!isMissingFsError(error)) {
17819
+ throw error;
17820
+ }
17821
+ }
17822
+ stagingPath = join12(root, `.start-${runID}-${randomUUID2()}`);
17823
+ mkdirSync3(stagingPath);
17824
+ const writerToken = issueWriterToken();
17825
+ const eventAt = now();
17826
+ const metadata = parseLifecycleRunMetadata({
17827
+ ...parsedInput,
17828
+ version: 1,
17829
+ createdAt: eventAt
17830
+ });
17831
+ const lease = createLease(runID, writerToken, parsedLeaseInput);
17832
+ const event = parseLifecycleEvent({
17833
+ v: 1,
17834
+ seq: 0,
17835
+ runID,
17836
+ at: eventAt,
17837
+ type: "run.started",
17838
+ source: parsedInput.source,
17839
+ executionAuthority: parsedInput.executionAuthority,
17840
+ root: parsedInput.root
17841
+ });
17842
+ writeDurable2(join12(stagingPath, METADATA_FILE), `${JSON.stringify(metadata)}
17843
+ `);
17844
+ writeDurable2(join12(stagingPath, EVENTS_FILE), encodeLifecycleJournal([event]));
17845
+ writeDurable2(join12(stagingPath, OWNER_FILE2), `${JSON.stringify(lease)}
17846
+ `);
17847
+ try {
17848
+ renameSync2(stagingPath, runPath);
17849
+ } catch (error) {
17850
+ if (typeof error === "object" && error !== null && "code" in error && ["EEXIST", "ENOTEMPTY"].includes(String(error.code))) {
17851
+ return failed(failure("already-exists", { runID }));
17852
+ }
17853
+ throw error;
17854
+ }
17855
+ stagingPath = undefined;
17856
+ return ok({
17857
+ handle: { runID, writerToken },
17858
+ metadata,
17859
+ event,
17860
+ lease,
17861
+ head: 1
17862
+ });
17863
+ } catch (error) {
17864
+ return failed(mapFsFailure(error, runID));
17865
+ } finally {
17866
+ if (stagingPath !== undefined) {
17867
+ rmSync3(stagingPath, { recursive: true, force: true });
17868
+ }
17869
+ }
17870
+ },
17871
+ async takeOver(runID, expected, leaseInput) {
17872
+ try {
17873
+ const root = resolvedLifecycleRoot(configuredRoot);
17874
+ const parsedLeaseInput = parseLifecycleWriterLeaseInput(leaseInput);
17875
+ const runDir = assertExistingSafeRun(root, runID);
17876
+ return mutateRun(runDir, () => {
17877
+ const current = readLease2(runDir);
17878
+ if (expected.kind === "absent") {
17879
+ if (current !== undefined) {
17880
+ return failed(failure("writer-conflict", { recoverable: true, runID }));
17881
+ }
17882
+ } else if (current === undefined || current.writerToken !== expected.writerToken || current.expiresAt !== expected.expiresAt || parsedLeaseInput.now < current.expiresAt) {
17883
+ return failed(failure("writer-conflict", { recoverable: true, runID }));
17884
+ }
17885
+ const writerToken = issueWriterToken();
17886
+ const lease = createLease(runID, writerToken, parsedLeaseInput);
17887
+ writeDurableAtomically(join12(runDir, OWNER_FILE2), `${JSON.stringify(lease)}
17888
+ `);
17889
+ return ok({ runID, writerToken });
17890
+ });
17891
+ } catch (error) {
17892
+ return failed(mapFsFailure(error, runID));
17893
+ }
17894
+ },
17895
+ async append(handle, event, expectedHead) {
17896
+ try {
17897
+ const root = resolvedLifecycleRoot(configuredRoot);
17898
+ const parsedBody = parseLifecycleAppendEvent(event);
17899
+ const runDir = assertExistingSafeRun(root, handle.runID);
17900
+ return mutateRun(runDir, () => {
17901
+ const lease = readLease2(runDir);
17902
+ if (lease === undefined || lease.runID !== handle.runID || lease.writerToken !== handle.writerToken) {
17903
+ return failed(failure("writer-conflict", {
17904
+ recoverable: true,
17905
+ runID: handle.runID
17906
+ }));
17907
+ }
17908
+ const loaded = readRun(runDir, handle.runID);
17909
+ if (loaded.decoded.partialTail) {
17910
+ return failed(failure("corrupt", { runID: handle.runID }));
17911
+ }
17912
+ const actualHead = loaded.decoded.events.length;
17913
+ if (expectedHead < actualHead) {
17914
+ return failed(failure("duplicate-event", {
17915
+ runID: handle.runID,
17916
+ expectedHead,
17917
+ actualHead
17918
+ }));
17919
+ }
17920
+ if (expectedHead !== actualHead) {
17921
+ return failed(failure("sequence-mismatch", {
17922
+ recoverable: true,
17923
+ runID: handle.runID,
17924
+ expectedHead,
17925
+ actualHead
17926
+ }));
17927
+ }
17928
+ const previous = loaded.decoded.events.at(-1);
17929
+ if (previous?.type === "run.terminal") {
17930
+ return failed(failure("duplicate-event", {
17931
+ runID: handle.runID,
17932
+ expectedHead,
17933
+ actualHead
17934
+ }));
17935
+ }
17936
+ const committedEvent = parseLifecycleEvent({
17937
+ v: 1,
17938
+ seq: actualHead,
17939
+ runID: handle.runID,
17940
+ at: now(),
17941
+ ...parsedBody
17942
+ });
17943
+ const frame = encodeLifecycleFrame(loaded.decoded.digest, committedEvent);
17944
+ appendDurable2(join12(runDir, EVENTS_FILE), `${frame.line}
17945
+ `);
17946
+ return ok({ event: committedEvent, head: actualHead + 1 });
17947
+ });
17948
+ } catch (error) {
17949
+ return failed(mapFsFailure(error, handle.runID));
17950
+ }
17951
+ },
17952
+ async heartbeat(handle, leaseInput) {
17953
+ try {
17954
+ const root = resolvedLifecycleRoot(configuredRoot);
17955
+ const parsed = parseLifecycleWriterLeaseInput({
17956
+ writer: { processID: 1, processInstanceID: "heartbeat-validation" },
17957
+ now: leaseInput.now,
17958
+ leaseDurationMs: leaseInput.leaseDurationMs
17959
+ });
17960
+ const runDir = assertExistingSafeRun(root, handle.runID);
17961
+ return mutateRun(runDir, () => {
17962
+ const current = readLease2(runDir);
17963
+ if (current === undefined || current.writerToken !== handle.writerToken || parsed.now < current.heartbeatAt || parsed.now >= current.expiresAt) {
17964
+ return failed(failure("writer-conflict", {
17965
+ recoverable: true,
17966
+ runID: handle.runID
17967
+ }));
17968
+ }
17969
+ const lease = parseLifecycleWriterLease({
17970
+ ...current,
17971
+ heartbeatAt: parsed.now,
17972
+ expiresAt: parsed.now + parsed.leaseDurationMs
17973
+ });
17974
+ writeDurableAtomically(join12(runDir, OWNER_FILE2), `${JSON.stringify(lease)}
17975
+ `);
17976
+ return ok({ lease });
17977
+ });
17978
+ } catch (error) {
17979
+ return failed(mapFsFailure(error, handle.runID));
17980
+ }
17981
+ },
17982
+ async release(handle) {
17983
+ try {
17984
+ const root = resolvedLifecycleRoot(configuredRoot);
17985
+ const runDir = assertExistingSafeRun(root, handle.runID);
17986
+ return mutateRun(runDir, () => {
17987
+ const lease = readLease2(runDir);
17988
+ if (lease === undefined || lease.writerToken !== handle.writerToken) {
17989
+ return failed(failure("writer-conflict", {
17990
+ recoverable: true,
17991
+ runID: handle.runID
17992
+ }));
17993
+ }
17994
+ unlinkSync2(join12(runDir, OWNER_FILE2));
17995
+ return ok(undefined);
17996
+ });
17997
+ } catch (error) {
17998
+ return failed(mapFsFailure(error, handle.runID));
17999
+ }
18000
+ },
18001
+ async load(runID) {
18002
+ try {
18003
+ const root = resolvedLifecycleRoot(configuredRoot);
18004
+ const runDir = assertExistingSafeRun(root, runID);
18005
+ const loaded = readRun(runDir, runID);
18006
+ return ok({
18007
+ metadata: loaded.metadata,
18008
+ events: loaded.decoded.events,
18009
+ head: loaded.decoded.events.length,
18010
+ integrity: loaded.decoded.partialTail ? "partial-tail" : "clean"
18011
+ });
18012
+ } catch (error) {
18013
+ return failed(mapFsFailure(error, runID));
18014
+ }
18015
+ },
18016
+ async list() {
18017
+ try {
18018
+ const root = resolvedLifecycleRoot(configuredRoot);
18019
+ const summaries = [];
18020
+ for (const entry of readdirSync2(root, { withFileTypes: true })) {
18021
+ if (!entry.isDirectory() || entry.isSymbolicLink() || entry.name.startsWith(".")) {
18022
+ continue;
18023
+ }
18024
+ try {
18025
+ validateRunID(entry.name);
18026
+ const runDir = assertExistingSafeRun(root, entry.name);
18027
+ summaries.push(summaryForRun(runDir, entry.name));
18028
+ } catch (error) {
18029
+ return failed(mapFsFailure(error, entry.name));
18030
+ }
18031
+ }
18032
+ summaries.sort((left, right) => right.updatedAt - left.updatedAt || right.metadata.runID.localeCompare(left.metadata.runID));
18033
+ return ok(summaries);
18034
+ } catch (error) {
18035
+ return failed(mapFsFailure(error));
18036
+ }
18037
+ },
18038
+ async pruneTerminal(policy, now2) {
18039
+ try {
18040
+ const root = resolvedLifecycleRoot(configuredRoot);
18041
+ validateRetention(policy);
18042
+ if (!Number.isFinite(now2) || now2 < 0) {
18043
+ throw new LifecycleFsError("corrupt");
18044
+ }
18045
+ const candidates = [];
18046
+ for (const entry of readdirSync2(root, { withFileTypes: true })) {
18047
+ if (!entry.isDirectory() || entry.isSymbolicLink() || entry.name.startsWith(".")) {
18048
+ continue;
18049
+ }
18050
+ try {
18051
+ validateRunID(entry.name);
18052
+ const runDir = assertExistingSafeRun(root, entry.name);
18053
+ const summary = summaryForRun(runDir, entry.name);
18054
+ if (summary.terminalOutcome === undefined || summary.integrity !== "clean") {
18055
+ continue;
18056
+ }
18057
+ candidates.push({
18058
+ runID: entry.name,
18059
+ updatedAt: summary.updatedAt,
18060
+ bytes: runBytes(runDir),
18061
+ canDelete: canDeleteTerminalRun(runDir, entry.name, now2)
18062
+ });
18063
+ } catch (error) {
18064
+ return failed(mapFsFailure(error, entry.name));
18065
+ }
18066
+ }
18067
+ candidates.sort((left, right) => left.updatedAt - right.updatedAt || left.runID.localeCompare(right.runID));
18068
+ let retainedRuns = candidates.length;
18069
+ let retainedBytes = candidates.reduce((total, candidate) => total + candidate.bytes, 0);
18070
+ const deletedRunIDs = [];
18071
+ for (const candidate of candidates) {
18072
+ const tooOld = now2 - candidate.updatedAt > policy.maxAgeMs;
18073
+ const tooMany = retainedRuns > policy.maxTerminalRuns;
18074
+ const tooLarge = retainedBytes > policy.maxBytes;
18075
+ if (!candidate.canDelete || !tooOld && !tooMany && !tooLarge) {
18076
+ continue;
18077
+ }
18078
+ const runDir = assertExistingSafeRun(root, candidate.runID);
18079
+ const deleted = mutateRun(runDir, () => {
18080
+ if (!canDeleteTerminalRun(runDir, candidate.runID, now2)) {
18081
+ return false;
18082
+ }
18083
+ rmSync3(runDir, { recursive: true });
18084
+ return true;
18085
+ });
18086
+ if (deleted) {
18087
+ deletedRunIDs.push(candidate.runID);
18088
+ retainedRuns -= 1;
18089
+ retainedBytes -= candidate.bytes;
18090
+ }
18091
+ }
18092
+ return ok({
18093
+ deletedRunIDs,
18094
+ retainedTerminalRuns: retainedRuns,
18095
+ retainedBytes
18096
+ });
18097
+ } catch (error) {
18098
+ return failed(mapFsFailure(error));
18099
+ }
18100
+ }
18101
+ };
18102
+ };
18103
+
18104
+ // src/storage/lifecycle/fsLifecycleWriterLivenessProbe.ts
18105
+ import { join as join13 } from "node:path";
18106
+ var createFsLifecycleWriterLivenessProbe = (options) => ({
18107
+ async observe(runID) {
18108
+ const observedAt = options.now();
18109
+ try {
18110
+ const root = resolvedLifecycleRoot(options.root);
18111
+ const runDir = assertExistingSafeRun(root, runID);
18112
+ const ownerText = readUtf8(join13(runDir, "owner"));
18113
+ if (ownerText === undefined) {
18114
+ return { kind: "absent", observedAt };
18115
+ }
18116
+ let lease;
18117
+ try {
18118
+ lease = parseLifecycleWriterLease(JSON.parse(ownerText));
18119
+ } catch {
18120
+ return { kind: "unknown", observedAt, reason: "owner-invalid" };
18121
+ }
18122
+ if (lease.runID !== runID) {
18123
+ return {
18124
+ kind: "unknown",
18125
+ observedAt,
18126
+ reason: "owner-invalid",
18127
+ lease
18128
+ };
18129
+ }
18130
+ if (observedAt < lease.heartbeatAt) {
18131
+ return {
18132
+ kind: "unknown",
18133
+ observedAt,
18134
+ reason: "clock-regressed",
18135
+ lease
18136
+ };
18137
+ }
18138
+ if (observedAt >= lease.expiresAt) {
18139
+ return { kind: "expired", observedAt, lease };
18140
+ }
18141
+ return { kind: "active", observedAt, lease };
18142
+ } catch (error) {
18143
+ if (error instanceof LifecycleFsError) {
18144
+ if (error.kind === "missing") {
18145
+ return { kind: "absent", observedAt };
18146
+ }
18147
+ if (error.kind === "corrupt") {
18148
+ return { kind: "unknown", observedAt, reason: "owner-invalid" };
18149
+ }
18150
+ }
18151
+ return { kind: "unknown", observedAt, reason: "probe-unavailable" };
18152
+ }
18153
+ }
18154
+ });
18155
+
15599
18156
  // src/telemetry/otelExporter.ts
15600
18157
  var DEFAULT_SERVICE_NAME = "openteam";
15601
18158
  function tracesUrl(ingestionEndpoint) {
@@ -15642,7 +18199,7 @@ var createOtlpSpanExporter = (connection, config) => {
15642
18199
  // package.json
15643
18200
  var package_default = {
15644
18201
  name: "@jmanuelcorral/openteam",
15645
- version: "0.19.0",
18202
+ version: "0.20.0",
15646
18203
  packageManager: "bun@1.3.14",
15647
18204
  description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
15648
18205
  license: "MIT",
@@ -15746,6 +18303,30 @@ var package_default = {
15746
18303
  var PACKAGE_VERSION = package_default.version;
15747
18304
 
15748
18305
  // src/index.ts
18306
+ var LIFECYCLE_WRITER_IDENTITY = {
18307
+ processID: process.pid,
18308
+ processInstanceID: crypto.randomUUID()
18309
+ };
18310
+ function createConfiguredLifecycleRecorder(config, workspaceRoot, now) {
18311
+ const root = resolveLifecycleRoot({
18312
+ workspaceRoot,
18313
+ configuredRoot: config.lifecycle?.root ?? DEFAULT_LIFECYCLE_ROOT
18314
+ });
18315
+ return createRuntimeLifecycleRecorder({
18316
+ journal: createFsLifecycleJournal({ root, now }),
18317
+ writerLiveness: createFsLifecycleWriterLivenessProbe({ root, now }),
18318
+ writer: LIFECYCLE_WRITER_IDENTITY,
18319
+ now,
18320
+ heartbeatScheduler: createNodeLifecycleHeartbeatScheduler(),
18321
+ heartbeatIntervalMs: DEFAULT_LIFECYCLE_HEARTBEAT_INTERVAL_MS,
18322
+ leaseDurationMs: DEFAULT_LIFECYCLE_WRITER_LEASE_MS,
18323
+ retention: {
18324
+ maxTerminalRuns: config.lifecycle?.retention.maxTerminalRuns ?? DEFAULT_LIFECYCLE_MAX_TERMINAL_RUNS,
18325
+ maxAgeMs: (config.lifecycle?.retention.maxAgeDays ?? DEFAULT_LIFECYCLE_MAX_AGE_DAYS) * 24 * 60 * 60 * 1000,
18326
+ maxBytes: config.lifecycle?.retention.maxBytes ?? DEFAULT_LIFECYCLE_MAX_BYTES
18327
+ }
18328
+ });
18329
+ }
15749
18330
  function createShellExec($) {
15750
18331
  return async (command, args) => {
15751
18332
  const output = await $`${command} ${[...args]}`.nothrow().quiet();
@@ -15821,7 +18402,7 @@ var RELEASE_CERT_PATH = "artifacts/graph-release-certificate.json";
15821
18402
  var SOAK_CERT_PATH = "artifacts/graph-soak-certificate.json";
15822
18403
  async function readPackagedCertificate(path4) {
15823
18404
  try {
15824
- return await readFile3(new URL(`./certificates/${basename2(path4)}`, import.meta.url), "utf8");
18405
+ return await readFile3(new URL(`./certificates/${basename3(path4)}`, import.meta.url), "utf8");
15825
18406
  } catch {
15826
18407
  return;
15827
18408
  }
@@ -15953,8 +18534,8 @@ async function readOpencodeVersion(serverUrl, options = {}) {
15953
18534
  }
15954
18535
  var DEFAULT_OPENCODE_VERSION_BACKOFF_MS = [50, 250];
15955
18536
  function defaultSleep(ms) {
15956
- return new Promise((resolve5) => {
15957
- setTimeout(resolve5, ms);
18537
+ return new Promise((resolve8) => {
18538
+ setTimeout(resolve8, ms);
15958
18539
  });
15959
18540
  }
15960
18541
  function createLazyOpencodeVersionReader(serverUrl, options = {}) {
@@ -16197,13 +18778,15 @@ function createCliDeps(config, registry, telemetryPath, sessionsDir = DEFAULT_SE
16197
18778
  graphOperator: createGraphOperator({
16198
18779
  storage,
16199
18780
  holder: "openteam-cli",
18781
+ lifecycleSource: "cli",
18782
+ createLifecycleRecorder: (graphConfig) => createConfiguredLifecycleRecorder(graphConfig, process.cwd(), () => Date.now()),
16200
18783
  evaluateCutoverGate: async (gateConfig) => evaluateCutoverGateForBoundary(gateConfig, storage, resolveOpencodeVersion === undefined ? undefined : await resolveOpencodeVersion())
16201
18784
  }),
16202
18785
  configPath: DEFAULT_CONFIG_PATH,
16203
18786
  telemetryPath,
16204
18787
  opencodeConfigPaths: OPENCODE_CONFIG_CANDIDATES,
16205
18788
  orchestratorAgentPath: ORCHESTRATOR_AGENT_PATH,
16206
- agentDir: dirname4(ORCHESTRATOR_AGENT_PATH),
18789
+ agentDir: dirname5(ORCHESTRATOR_AGENT_PATH),
16207
18790
  cachePort: realCacheAdapter,
16208
18791
  purge: createPluginPurgeRuntime(sessionsDir),
16209
18792
  loadRosterForDoctor: () => readStoredRosterForDoctor(storage),
@@ -16259,6 +18842,8 @@ var server = async (ctx, rawOptions) => {
16259
18842
  const storage = createFsStorageProvider(ctx.directory || ctx.worktree || process.cwd());
16260
18843
  const runtimeConfig = await loadRuntimeOpenTeamConfig(storage, rawOptions);
16261
18844
  const config = runtimeConfig.config;
18845
+ const workspaceRoot = ctx.directory || ctx.worktree || process.cwd();
18846
+ const lifecycleRecorder = createConfiguredLifecycleRecorder(config, workspaceRoot, now);
16262
18847
  const registry = new RuntimeRegistry({
16263
18848
  fetch: globalThis.fetch,
16264
18849
  clock: () => new Date().toISOString(),
@@ -16393,6 +18978,8 @@ var server = async (ctx, rawOptions) => {
16393
18978
  const graphOperator = createGraphOperator({
16394
18979
  storage,
16395
18980
  holder: "openteam-plugin",
18981
+ lifecycleSource: "plugin",
18982
+ lifecycleRecorder,
16396
18983
  exec: createShellExec(ctx.$),
16397
18984
  repoRoot: ctx.directory
16398
18985
  });
@@ -16468,10 +19055,11 @@ var server = async (ctx, rawOptions) => {
16468
19055
  capabilityProfiles,
16469
19056
  reportArtifactExists,
16470
19057
  readReportArtifact,
19058
+ lifecycleRecorder,
16471
19059
  abortGraceMs: DEFAULT_ABORT_GRACE_MS,
16472
19060
  statusPollMs: DEFAULT_STATUS_POLL_MS,
16473
- delay: (ms) => new Promise((resolve5) => {
16474
- setTimeout(resolve5, ms);
19061
+ delay: (ms) => new Promise((resolve8) => {
19062
+ setTimeout(resolve8, ms);
16475
19063
  }),
16476
19064
  warn: diagnostics.warn("unknown-role")
16477
19065
  });