@jmanuelcorral/openteam 0.19.0 → 0.21.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 (95) 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 +17 -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 +6862 -798
  14. package/dist/config/errors.d.ts.map +1 -1
  15. package/dist/config/historyEndpoint.d.ts +3 -0
  16. package/dist/config/historyEndpoint.d.ts.map +1 -0
  17. package/dist/config/schema.d.ts +43 -1
  18. package/dist/config/schema.d.ts.map +1 -1
  19. package/dist/console/assets.d.ts +23 -2
  20. package/dist/console/assets.d.ts.map +1 -1
  21. package/dist/console/conversation.d.ts +75 -0
  22. package/dist/console/conversation.d.ts.map +1 -0
  23. package/dist/console/historyBoundary.d.ts +6 -0
  24. package/dist/console/historyBoundary.d.ts.map +1 -0
  25. package/dist/console/inspector.d.ts +3 -0
  26. package/dist/console/inspector.d.ts.map +1 -0
  27. package/dist/console/labels.d.ts +12 -0
  28. package/dist/console/labels.d.ts.map +1 -0
  29. package/dist/console/opencodeClient.d.ts.map +1 -1
  30. package/dist/console/refresh.d.ts +3 -0
  31. package/dist/console/refresh.d.ts.map +1 -0
  32. package/dist/console/render.d.ts +19 -0
  33. package/dist/console/render.d.ts.map +1 -1
  34. package/dist/console/rootAssociation.d.ts +7 -0
  35. package/dist/console/rootAssociation.d.ts.map +1 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2935 -265
  38. package/dist/lifecycle/contracts.d.ts +618 -0
  39. package/dist/lifecycle/contracts.d.ts.map +1 -0
  40. package/dist/lifecycle/historyReader.d.ts +11 -0
  41. package/dist/lifecycle/historyReader.d.ts.map +1 -0
  42. package/dist/lifecycle/historySourceError.d.ts +7 -0
  43. package/dist/lifecycle/historySourceError.d.ts.map +1 -0
  44. package/dist/lifecycle/project.d.ts +3 -0
  45. package/dist/lifecycle/project.d.ts.map +1 -0
  46. package/dist/lifecycle/root.d.ts +3 -0
  47. package/dist/lifecycle/root.d.ts.map +1 -0
  48. package/dist/lifecycle/runtimeRecorder.d.ts +6 -0
  49. package/dist/lifecycle/runtimeRecorder.d.ts.map +1 -0
  50. package/dist/lifecycle/schema.d.ts +487 -0
  51. package/dist/lifecycle/schema.d.ts.map +1 -0
  52. package/dist/messages/consoleDetail.d.ts +54 -0
  53. package/dist/messages/consoleDetail.d.ts.map +1 -0
  54. package/dist/messages/index.d.ts +1 -0
  55. package/dist/messages/index.d.ts.map +1 -1
  56. package/dist/messages/lifecycle.d.ts +95 -0
  57. package/dist/messages/lifecycle.d.ts.map +1 -0
  58. package/dist/messages/lifecycleRuntime.d.ts +4 -0
  59. package/dist/messages/lifecycleRuntime.d.ts.map +1 -0
  60. package/dist/messages/lifecycleStorage.d.ts +4 -0
  61. package/dist/messages/lifecycleStorage.d.ts.map +1 -0
  62. package/dist/messages/orchestration.d.ts +3 -0
  63. package/dist/messages/orchestration.d.ts.map +1 -1
  64. package/dist/orchestrator/coordinator.d.ts +11 -1
  65. package/dist/orchestrator/coordinator.d.ts.map +1 -1
  66. package/dist/orchestrator/graphIngress.d.ts +5 -0
  67. package/dist/orchestrator/graphIngress.d.ts.map +1 -1
  68. package/dist/orchestrator/graphRuntime.d.ts +13 -1
  69. package/dist/orchestrator/graphRuntime.d.ts.map +1 -1
  70. package/dist/orchestrator/subsessions.d.ts +3 -0
  71. package/dist/orchestrator/subsessions.d.ts.map +1 -1
  72. package/dist/plugin/orchestrateTool.d.ts +3 -0
  73. package/dist/plugin/orchestrateTool.d.ts.map +1 -1
  74. package/dist/storage/lifecycle/codec.d.ts +19 -0
  75. package/dist/storage/lifecycle/codec.d.ts.map +1 -0
  76. package/dist/storage/lifecycle/fsLifecycleJournal.d.ts +3 -0
  77. package/dist/storage/lifecycle/fsLifecycleJournal.d.ts.map +1 -0
  78. package/dist/storage/lifecycle/fsLifecycleSupport.d.ts +17 -0
  79. package/dist/storage/lifecycle/fsLifecycleSupport.d.ts.map +1 -0
  80. package/dist/storage/lifecycle/fsLifecycleWriterLivenessProbe.d.ts +3 -0
  81. package/dist/storage/lifecycle/fsLifecycleWriterLivenessProbe.d.ts.map +1 -0
  82. package/dist/web/configApi.d.ts +1 -1
  83. package/dist/web/configApi.d.ts.map +1 -1
  84. package/dist/web/console.d.ts.map +1 -1
  85. package/dist/web/conversationRoute.d.ts +16 -0
  86. package/dist/web/conversationRoute.d.ts.map +1 -0
  87. package/dist/web/graphRoute.d.ts +39 -1
  88. package/dist/web/graphRoute.d.ts.map +1 -1
  89. package/dist/web/server.d.ts +24 -0
  90. package/dist/web/server.d.ts.map +1 -1
  91. package/dist/web/start.d.ts +17 -0
  92. package/dist/web/start.d.ts.map +1 -1
  93. package/dist/web/watch.d.ts +17 -2
  94. package/dist/web/watch.d.ts.map +1 -1
  95. 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 = [
@@ -782,6 +782,72 @@ function migrateConfig(raw) {
782
782
  };
783
783
  }
784
784
 
785
+ // src/messages/consoleDetail.ts
786
+ var consoleDetailMessages = {
787
+ follow: "Follow new activity",
788
+ metadataOnly: "Lifecycle and telemetry stay metadata-only.",
789
+ filter: "Filter sessions",
790
+ incoming: "New activity available — show updates",
791
+ refreshError: "Live updates unavailable; retrying.",
792
+ inspectConversation: "Show messages and tool calls",
793
+ refresh: "Refresh conversation",
794
+ close: "Hide messages and tool calls",
795
+ title: "Recorded messages and tool calls",
796
+ orchestrator: "Orchestrator",
797
+ worker: "Worker",
798
+ session: "Session",
799
+ runStarted: "Run started",
800
+ sessionCreated: "Session created",
801
+ firstObserved: "First observed",
802
+ timeUnknown: "Time unknown",
803
+ utc: "UTC",
804
+ technicalID: "Technical ID",
805
+ currentMetadata: "Current opencode title; not historical metadata",
806
+ partialMetadata: "Some current session titles are unavailable or outside the bounded batch; fallback labels remain.",
807
+ rootCurrent: "Current whole orchestrator session; may contain multiple batches. Not a historical or batch-only transcript.",
808
+ recovered: "Read-only association verified from current opencode history; not an immutable lifecycle event.",
809
+ rootUnlinked: "Orchestrator session not recorded. Reveal to try a bounded, read-only relationship search.",
810
+ rootUnavailable: "Orchestrator session unavailable: no unique, verifiable relationship was found in local history.",
811
+ rootAmbiguous: "Orchestrator session unavailable: conflicting or copied relationships are ambiguous.",
812
+ searchIncomplete: "Orchestrator association search incomplete or exceeded its bounded budget. No session was guessed.",
813
+ prompt: "Received prompts",
814
+ assistant: "Visible agent responses",
815
+ tools: "Tool calls ({count})",
816
+ noPrompts: "No received prompt text retained.",
817
+ noOutput: "No visible agent response text retained.",
818
+ noTools: "No tool calls retained.",
819
+ arguments: "Arguments",
820
+ result: "Result",
821
+ loading: "Loading local conversation…",
822
+ current: "Present-day session, not a timeline reconstruction. May include later messages and tools. Refresh explicitly for updates.",
823
+ historical: "Historical cursor: conversation content is hidden to avoid showing future mutable text. Return to live before revealing this operation.",
824
+ disabled: "Content is disabled. Set console.sessionHistory.contentEnabled to true in .opencode/openteam.json and restart the Console. Then reveal content here.",
825
+ unavailable: "Conversation unavailable. Start an operator-owned local opencode serve against the same workspace and user history, configure console.sessionHistory.endpoint, then retry. No inference was requested.",
826
+ empty: "No readable user messages, assistant output or tool calls remain in this session.",
827
+ invalid: "This attempt has no recorded session in the selected run, or its history is incomplete.",
828
+ unauthorized: "Conversation access denied.",
829
+ pending: "Still streaming or unavailable.",
830
+ compacted: "Tool result was compacted by opencode.",
831
+ invalidEndpoint: "Expected a loopback HTTP origin without credentials, path, query or fragment.",
832
+ readFailed: "Local session history could not be read.",
833
+ oversized: "Conversation exceeds the local size limit (4 MiB). No content was loaded.",
834
+ truncated: "Conversation display is truncated by local safety limits; omitted content is not empty history.",
835
+ truncatedMarker: `
836
+ [truncated]`,
837
+ terminalGap: "Live collection was interrupted; events during the gap are unavailable. This is not complete session history.",
838
+ terminalTrimmed: "Older live output was discarded by the local display limit."
839
+ };
840
+
841
+ // src/config/historyEndpoint.ts
842
+ function isLocalHistoryEndpoint(value) {
843
+ try {
844
+ const url = new URL(value);
845
+ return url.protocol === "http:" && ["127.0.0.1", "[::1]"].includes(url.hostname) && !url.username && !url.password && url.pathname === "/" && !url.search && !url.hash;
846
+ } catch {
847
+ return false;
848
+ }
849
+ }
850
+
785
851
  // src/config/schema.ts
786
852
  var ModelRefSchema = z3.object({
787
853
  providerID: z3.string().min(1),
@@ -799,6 +865,29 @@ var GraphModeSchema = z3.enum(["off", "shadow", "active"]);
799
865
  var GraphEngineSchema = z3.enum(["builtin", "langgraph"]);
800
866
  var DEFAULT_GRAPH_JOURNAL_ROOT = ".opencode/openteam-local/graph/runs";
801
867
  var DEFAULT_GRAPH_WORKTREES_DIR = ".opencode/openteam-local/graph/worktrees";
868
+ var DEFAULT_LIFECYCLE_ROOT = ".opencode/openteam-local/lifecycle/runs";
869
+ var DEFAULT_LIFECYCLE_MAX_TERMINAL_RUNS = 200;
870
+ var DEFAULT_LIFECYCLE_MAX_AGE_DAYS = 30;
871
+ var DEFAULT_LIFECYCLE_MAX_BYTES = 268435456;
872
+ var LifecycleConfigSchema = z3.object({
873
+ root: z3.string().min(1).default(DEFAULT_LIFECYCLE_ROOT),
874
+ retention: z3.object({
875
+ maxTerminalRuns: z3.number().int().nonnegative().default(DEFAULT_LIFECYCLE_MAX_TERMINAL_RUNS),
876
+ maxAgeDays: z3.number().nonnegative().default(DEFAULT_LIFECYCLE_MAX_AGE_DAYS),
877
+ maxBytes: z3.number().int().nonnegative().default(DEFAULT_LIFECYCLE_MAX_BYTES)
878
+ }).strict().default({
879
+ maxTerminalRuns: DEFAULT_LIFECYCLE_MAX_TERMINAL_RUNS,
880
+ maxAgeDays: DEFAULT_LIFECYCLE_MAX_AGE_DAYS,
881
+ maxBytes: DEFAULT_LIFECYCLE_MAX_BYTES
882
+ })
883
+ }).strict().default({
884
+ root: DEFAULT_LIFECYCLE_ROOT,
885
+ retention: {
886
+ maxTerminalRuns: DEFAULT_LIFECYCLE_MAX_TERMINAL_RUNS,
887
+ maxAgeDays: DEFAULT_LIFECYCLE_MAX_AGE_DAYS,
888
+ maxBytes: DEFAULT_LIFECYCLE_MAX_BYTES
889
+ }
890
+ });
802
891
  var GraphConfigSchema = z3.object({
803
892
  mode: GraphModeSchema.default("active"),
804
893
  engine: GraphEngineSchema.optional(),
@@ -818,6 +907,10 @@ var ConsoleConfigSchema = z3.object({
818
907
  recentRoutes: z3.number().int().positive().default(50),
819
908
  openBrowser: z3.boolean().default(false),
820
909
  remoteStorage: z3.boolean().default(false),
910
+ sessionHistory: z3.object({
911
+ contentEnabled: z3.boolean().default(false),
912
+ endpoint: z3.string().refine(isLocalHistoryEndpoint, consoleDetailMessages.invalidEndpoint).optional()
913
+ }).strict().optional(),
821
914
  terminal: z3.object({
822
915
  enabled: z3.boolean().default(true),
823
916
  pty: z3.boolean().optional()
@@ -1102,6 +1195,7 @@ var OpenTeamConfigObjectSchema = z3.object({
1102
1195
  console: ConsoleConfigSchema,
1103
1196
  storage: StorageConfigSchema,
1104
1197
  memory: MemoryConfigSchema.optional(),
1198
+ lifecycle: LifecycleConfigSchema,
1105
1199
  graph: GraphConfigSchema.default({
1106
1200
  mode: "active",
1107
1201
  killSwitch: false,
@@ -1571,6 +1665,32 @@ function runCancelled(runID, seq) {
1571
1665
  return { v: GRAPH_EVENT_VERSION, type: "run.cancelled", runID, seq };
1572
1666
  }
1573
1667
 
1668
+ // src/graph/ids.ts
1669
+ var NUL = "\x00";
1670
+ function operationID(runID, nodeID, revision = 0) {
1671
+ return sha256Hex(`op${NUL}${runID}${NUL}${nodeID}${NUL}${revision}`).slice(0, 32);
1672
+ }
1673
+ function attemptID(operation, attempt) {
1674
+ return sha256Hex(`att${NUL}${operation}${NUL}${attempt}`).slice(0, 32);
1675
+ }
1676
+ function canonicalSpec(spec) {
1677
+ const nodes = spec.nodes.map((node) => ({
1678
+ id: node.id,
1679
+ role: node.role,
1680
+ dependsOn: [...node.dependsOn],
1681
+ anchor: { taskID: node.anchor.taskID, specDigest: node.anchor.specDigest },
1682
+ ...node.reviews === undefined ? {} : { reviews: node.reviews }
1683
+ }));
1684
+ return JSON.stringify({
1685
+ version: spec.version,
1686
+ runID: spec.runID,
1687
+ nodes
1688
+ });
1689
+ }
1690
+ function graphSpecDigest(spec) {
1691
+ return sha256Hex(canonicalSpec(spec));
1692
+ }
1693
+
1574
1694
  // src/storage/graph/provider.ts
1575
1695
  class JournalError extends Error {
1576
1696
  code;
@@ -1714,7 +1834,7 @@ function createRunRegistry(root) {
1714
1834
  }
1715
1835
 
1716
1836
  // src/storage/graph/workspaceLease.ts
1717
- var NUL = "\x00";
1837
+ var NUL2 = "\x00";
1718
1838
  var leaseRecordSchema = z8.object({
1719
1839
  runID: z8.string().min(1),
1720
1840
  holder: z8.string().min(1),
@@ -1734,7 +1854,7 @@ class LeaseError extends Error {
1734
1854
  }
1735
1855
  function createWorkspaceLease(root, registry = createRunRegistry(root)) {
1736
1856
  const leasePath = (runID) => join3(root, `lease-${runID}.json`);
1737
- const deriveToken = (runID, holder) => sha256Hex(`lease${NUL}${runID}${NUL}${holder}`).slice(0, 32);
1857
+ const deriveToken = (runID, holder) => sha256Hex(`lease${NUL2}${runID}${NUL2}${holder}`).slice(0, 32);
1738
1858
  const parse2 = (text) => {
1739
1859
  let raw;
1740
1860
  try {
@@ -1815,32 +1935,6 @@ function planCancellation(state) {
1815
1935
  // src/orchestrator/graphReview.ts
1816
1936
  import { z as z9 } from "zod";
1817
1937
 
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
1938
  // src/graph/policies.ts
1845
1939
  var DEFAULT_RETRY_LIMIT = 3;
1846
1940
  var DEFAULT_REVISION_LIMIT = 2;
@@ -1932,6 +2026,18 @@ function plan(spec, state) {
1932
2026
  function specDigest(spec) {
1933
2027
  return sha256Hex(JSON.stringify(spec));
1934
2028
  }
2029
+ function graphFailure(errorClass, retryable) {
2030
+ return {
2031
+ class: errorClass === "cancelled" ? "cancelled" : "unknown",
2032
+ retryable
2033
+ };
2034
+ }
2035
+ function graphFailureOutcome(errorClass) {
2036
+ if (errorClass === "cancelled") {
2037
+ return "cancelled";
2038
+ }
2039
+ return errorClass === "unknown" ? "unknown" : "failed";
2040
+ }
1935
2041
  function findNode(spec, nodeID) {
1936
2042
  return spec.nodes.find((candidate) => candidate.id === nodeID);
1937
2043
  }
@@ -1953,21 +2059,126 @@ function nextRetry(loaded, limit) {
1953
2059
  const first = [...byNode.keys()].sort()[0];
1954
2060
  return first === undefined ? undefined : byNode.get(first);
1955
2061
  }
2062
+ function advanceAttemptExpectation(attempts, attemptID2, minimumPhase) {
2063
+ const current = attempts.get(attemptID2);
2064
+ if (current === undefined || current === "queued" && minimumPhase !== "queued" || current === "started" && minimumPhase === "terminal") {
2065
+ attempts.set(attemptID2, minimumPhase);
2066
+ }
2067
+ }
2068
+ function graphLifecycleExpectations(loaded, options = {}) {
2069
+ const limit = options.retryLimit ?? DEFAULT_RETRY_LIMIT;
2070
+ const cancelledNodeIDs = new Set(options.cancelledNodeIDs ?? []);
2071
+ return loaded.spec.nodes.map((node, nodeIndex) => {
2072
+ const state = loaded.state.nodes[node.id];
2073
+ const authoritativeStatus = cancelledNodeIDs.has(node.id) ? "cancelled" : state?.status ?? "pending";
2074
+ const operation = state?.operationID ?? operationID(loaded.spec.runID, node.id);
2075
+ const attempts = new Map;
2076
+ for (const event of loaded.events) {
2077
+ if (event.type === "node.dispatched" && event.nodeID === node.id) {
2078
+ advanceAttemptExpectation(attempts, event.attemptID, "started");
2079
+ } else if ((event.type === "node.succeeded" || event.type === "node.failed") && event.nodeID === node.id) {
2080
+ advanceAttemptExpectation(attempts, event.attemptID, "terminal");
2081
+ }
2082
+ }
2083
+ if (cancelledNodeIDs.has(node.id) && state !== undefined && state.attempts > 0 && state.attemptID !== undefined) {
2084
+ advanceAttemptExpectation(attempts, state.attemptID, "terminal");
2085
+ }
2086
+ const attemptExpectations = [
2087
+ ...attempts
2088
+ ].map(([attemptID2, minimumPhase]) => ({
2089
+ attemptID: attemptID2,
2090
+ minimumPhase
2091
+ }));
2092
+ if (state !== undefined && (attempts.size !== state.attempts || state.attemptID !== undefined && !attempts.has(state.attemptID))) {
2093
+ attemptExpectations.push({
2094
+ attemptID: null,
2095
+ minimumPhase: authoritativeStatus === "succeeded" || authoritativeStatus === "failed" || authoritativeStatus === "cancelled" ? "terminal" : "started"
2096
+ });
2097
+ }
2098
+ let operationTerminalRequired = authoritativeStatus === "succeeded" || authoritativeStatus === "cancelled";
2099
+ if (authoritativeStatus === "failed") {
2100
+ let lastFailure;
2101
+ for (const event of loaded.events) {
2102
+ if (event.type === "node.failed" && event.nodeID === node.id && event.attemptID === state?.attemptID) {
2103
+ lastFailure = event;
2104
+ }
2105
+ }
2106
+ const retryRemains = options.closingStatus === undefined && lastFailure !== undefined && state !== undefined && retryAllowed(lastFailure.errorClass, state.attempts, limit);
2107
+ operationTerminalRequired = !retryRemains;
2108
+ }
2109
+ if (authoritativeStatus === "pending" && options.closingStatus !== undefined) {
2110
+ operationTerminalRequired = true;
2111
+ }
2112
+ return {
2113
+ operationID: operation,
2114
+ operationQueuedRequired: nodeIndex > 0,
2115
+ operationTerminalRequired,
2116
+ attempts: attemptExpectations
2117
+ };
2118
+ });
2119
+ }
1956
2120
  function createGraphRuntime(deps) {
1957
- const { spec, journal, lease, effects, holder } = deps;
2121
+ const { spec, journal, lease, effects, holder, lifecycle } = deps;
1958
2122
  const retryLimit = deps.retryLimit ?? DEFAULT_RETRY_LIMIT;
1959
2123
  const runID = spec.runID;
2124
+ const rootNode = spec.nodes[0];
2125
+ const rootOperationID = operationID(runID, rootNode.id);
1960
2126
  let writer;
1961
2127
  let leaseHandle;
2128
+ let lifecycleWritable = lifecycle !== undefined;
2129
+ async function recordLifecycleEvent(event) {
2130
+ if (lifecycle === undefined || !lifecycleWritable) {
2131
+ return;
2132
+ }
2133
+ try {
2134
+ const result = await lifecycle.append(event);
2135
+ lifecycleWritable = result.ok && result.status.state === "healthy";
2136
+ } catch {
2137
+ lifecycleWritable = false;
2138
+ }
2139
+ }
2140
+ async function verifyLifecycle(loaded, options = {}) {
2141
+ if (lifecycle === undefined || !lifecycleWritable) {
2142
+ return;
2143
+ }
2144
+ try {
2145
+ const status = await lifecycle.verifyOperations(graphLifecycleExpectations(loaded, {
2146
+ ...options,
2147
+ retryLimit
2148
+ }), {
2149
+ requireCompleteObservedWork: options.closingStatus !== undefined
2150
+ });
2151
+ lifecycleWritable = status.state === "healthy";
2152
+ } catch {
2153
+ lifecycleWritable = false;
2154
+ }
2155
+ }
1962
2156
  async function ensureStarted() {
1963
2157
  if (writer !== undefined) {
1964
2158
  return writer;
1965
2159
  }
1966
2160
  leaseHandle = await lease.acquire(runID, holder);
1967
- writer = await journal.exists(runID) ? await journal.takeOver(runID) : await journal.create(spec);
2161
+ const existed = await journal.exists(runID);
2162
+ writer = existed ? await journal.takeOver(runID) : await journal.create(spec);
1968
2163
  const loaded = await journal.load(runID);
1969
2164
  if (loaded.events.length === 0) {
1970
2165
  await journal.append(writer, runStarted(runID, 0, specDigest(spec)), 0);
2166
+ for (const node of spec.nodes) {
2167
+ if (node.id === rootNode.id) {
2168
+ continue;
2169
+ }
2170
+ await recordLifecycleEvent({
2171
+ type: "operation.queued",
2172
+ operationID: operationID(runID, node.id),
2173
+ taskID: node.anchor.taskID,
2174
+ kind: "graph-node",
2175
+ roleID: node.role,
2176
+ parentOperationID: rootOperationID,
2177
+ dependsOnOperationIDs: node.dependsOn.map((nodeID) => operationID(runID, nodeID))
2178
+ });
2179
+ }
2180
+ } else if (lifecycle !== undefined) {
2181
+ await verifyLifecycle(loaded);
1971
2182
  }
1972
2183
  return writer;
1973
2184
  }
@@ -1980,8 +2191,24 @@ function createGraphRuntime(deps) {
1980
2191
  await lease.release(handle);
1981
2192
  }
1982
2193
  async function dispatchNode2(handle, loaded, node, operation, attempt, attemptIndex) {
2194
+ const attemptRef = {
2195
+ operationID: operation,
2196
+ taskID: node.anchor.taskID,
2197
+ attemptID: attempt,
2198
+ attempt: attemptIndex,
2199
+ modelIndex: 0,
2200
+ retryIndex: attemptIndex
2201
+ };
2202
+ await recordLifecycleEvent({
2203
+ type: "attempt.queued",
2204
+ ...attemptRef
2205
+ });
1983
2206
  const intentSeq = loaded.events.length;
1984
2207
  await journal.append(handle, nodeDispatched(runID, intentSeq, node.id, operation, attempt), intentSeq);
2208
+ await recordLifecycleEvent({
2209
+ type: "attempt.started",
2210
+ ...attemptRef
2211
+ });
1985
2212
  const receipt = await effects.execute({
1986
2213
  runID,
1987
2214
  node,
@@ -1991,8 +2218,40 @@ function createGraphRuntime(deps) {
1991
2218
  const receiptSeq = intentSeq + 1;
1992
2219
  if (receipt.outcome === "succeeded") {
1993
2220
  await journal.append(handle, nodeSucceeded(runID, receiptSeq, node.id, operation, attempt, receipt.artifact), receiptSeq);
2221
+ await recordLifecycleEvent({
2222
+ type: "attempt.terminal",
2223
+ ...attemptRef,
2224
+ outcome: "succeeded",
2225
+ continuation: "none"
2226
+ });
2227
+ await recordLifecycleEvent({
2228
+ type: "operation.terminal",
2229
+ operationID: operation,
2230
+ taskID: node.anchor.taskID,
2231
+ outcome: "succeeded",
2232
+ attemptCount: attemptIndex + 1
2233
+ });
1994
2234
  } else {
1995
2235
  await journal.append(handle, nodeFailed(runID, receiptSeq, node.id, operation, attempt, receipt.errorClass), receiptSeq);
2236
+ const willRetry = retryAllowed(receipt.errorClass, attemptIndex + 1, retryLimit);
2237
+ const failure = graphFailure(receipt.errorClass, willRetry);
2238
+ await recordLifecycleEvent({
2239
+ type: "attempt.terminal",
2240
+ ...attemptRef,
2241
+ outcome: graphFailureOutcome(receipt.errorClass),
2242
+ continuation: willRetry ? "retry-same-effect" : "none",
2243
+ failure
2244
+ });
2245
+ if (!willRetry) {
2246
+ await recordLifecycleEvent({
2247
+ type: "operation.terminal",
2248
+ operationID: operation,
2249
+ taskID: node.anchor.taskID,
2250
+ outcome: graphFailureOutcome(receipt.errorClass),
2251
+ attemptCount: attemptIndex + 1,
2252
+ failure
2253
+ });
2254
+ }
1996
2255
  }
1997
2256
  return {
1998
2257
  kind: "dispatched",
@@ -2003,16 +2262,44 @@ function createGraphRuntime(deps) {
2003
2262
  backoffMs: backoffDelayMs(attemptIndex)
2004
2263
  };
2005
2264
  }
2006
- async function finishRun(handle, head, state) {
2265
+ async function finishRun(handle, loaded) {
2266
+ const { state } = loaded;
2267
+ const head = loaded.events.length;
2007
2268
  const statuses = Object.values(state.nodes).map((node) => node.status);
2008
2269
  if (statuses.every((s) => s === "succeeded" || s === "cancelled")) {
2009
2270
  await journal.append(handle, runCompleted(runID, head), head);
2271
+ await verifyLifecycle(loaded, { closingStatus: "completed" });
2272
+ await recordLifecycleEvent({
2273
+ type: "run.terminal",
2274
+ outcome: "succeeded",
2275
+ operationCount: spec.nodes.length
2276
+ });
2010
2277
  return { kind: "completed" };
2011
2278
  }
2012
2279
  if (statuses.some((s) => s === "running")) {
2013
2280
  return { kind: "blocked", reason: "in-flight-effect" };
2014
2281
  }
2015
2282
  await journal.append(handle, runFailed(runID, head), head);
2283
+ for (const node of spec.nodes) {
2284
+ if (state.nodes[node.id]?.status !== "pending") {
2285
+ continue;
2286
+ }
2287
+ await recordLifecycleEvent({
2288
+ type: "operation.terminal",
2289
+ operationID: operationID(runID, node.id),
2290
+ taskID: node.anchor.taskID,
2291
+ outcome: "blocked",
2292
+ attemptCount: 0,
2293
+ failure: { class: "dependency-failed", retryable: false }
2294
+ });
2295
+ }
2296
+ await verifyLifecycle(loaded, { closingStatus: "failed" });
2297
+ await recordLifecycleEvent({
2298
+ type: "run.terminal",
2299
+ outcome: "failed",
2300
+ operationCount: spec.nodes.length,
2301
+ failure: { class: "unknown", retryable: false }
2302
+ });
2016
2303
  return { kind: "failed" };
2017
2304
  }
2018
2305
  return {
@@ -2042,7 +2329,7 @@ function createGraphRuntime(deps) {
2042
2329
  const nextAttemptID = attemptID(retry.operationID, retry.attempt);
2043
2330
  return dispatchNode2(handle, loaded, node, retry.operationID, nextAttemptID, retry.attempt);
2044
2331
  }
2045
- const outcome = await finishRun(handle, loaded.events.length, loaded.state);
2332
+ const outcome = await finishRun(handle, loaded);
2046
2333
  if (outcome.kind === "completed" || outcome.kind === "failed") {
2047
2334
  await releaseLeaseIfHeld();
2048
2335
  }
@@ -2284,7 +2571,126 @@ function toErrorOutcome(error) {
2284
2571
  throw error;
2285
2572
  }
2286
2573
  function createGraphIngress(deps) {
2287
- const { policy, journal, lease, effects, holder } = deps;
2574
+ const {
2575
+ policy,
2576
+ journal,
2577
+ lease,
2578
+ effects,
2579
+ holder,
2580
+ lifecycleRecorder,
2581
+ lifecycleSource
2582
+ } = deps;
2583
+ const activeLifecycleRuns = new Map;
2584
+ function unavailableRecording(runID) {
2585
+ return {
2586
+ runID,
2587
+ state: "unavailable",
2588
+ lastCommittedHead: 0,
2589
+ failure: { code: "unavailable", recoverable: true, runID }
2590
+ };
2591
+ }
2592
+ async function beginLifecycle(spec) {
2593
+ if (lifecycleRecorder === undefined) {
2594
+ return {};
2595
+ }
2596
+ if (lifecycleSource === undefined) {
2597
+ return { status: unavailableRecording(spec.runID) };
2598
+ }
2599
+ let begun;
2600
+ try {
2601
+ const root = spec.nodes[0];
2602
+ begun = await lifecycleRecorder.beginRun({
2603
+ runID: spec.runID,
2604
+ source: lifecycleSource,
2605
+ executionAuthority: "graph",
2606
+ root: {
2607
+ operationID: operationID(spec.runID, root.id),
2608
+ taskID: root.anchor.taskID,
2609
+ kind: "graph-node",
2610
+ roleID: root.role
2611
+ }
2612
+ });
2613
+ } catch {
2614
+ return { status: unavailableRecording(spec.runID) };
2615
+ }
2616
+ if (!begun.ok) {
2617
+ return { status: begun.status };
2618
+ }
2619
+ activeLifecycleRuns.set(spec.runID, begun.recorder);
2620
+ return { recorder: begun.recorder, status: begun.status };
2621
+ }
2622
+ async function continueLifecycle(runID) {
2623
+ const active = activeLifecycleRuns.get(runID);
2624
+ if (active !== undefined) {
2625
+ return { recorder: active, status: active.status() };
2626
+ }
2627
+ if (lifecycleRecorder === undefined || lifecycleSource === undefined) {
2628
+ return lifecycleRecorder === undefined ? {} : { status: unavailableRecording(runID) };
2629
+ }
2630
+ if (lifecycleRecorder.continueRun === undefined) {
2631
+ return { status: unavailableRecording(runID) };
2632
+ }
2633
+ let continued;
2634
+ try {
2635
+ continued = await lifecycleRecorder.continueRun(runID);
2636
+ } catch {
2637
+ return { status: unavailableRecording(runID) };
2638
+ }
2639
+ if (!continued.ok) {
2640
+ return { status: continued.status };
2641
+ }
2642
+ activeLifecycleRuns.set(runID, continued.recorder);
2643
+ return {
2644
+ recorder: continued.recorder,
2645
+ status: continued.status
2646
+ };
2647
+ }
2648
+ async function appendLifecycle(recorder, event) {
2649
+ if (recorder === undefined) {
2650
+ return;
2651
+ }
2652
+ try {
2653
+ await recorder.append(event);
2654
+ } catch {}
2655
+ }
2656
+ async function finishObservation(runID, recorder, terminal) {
2657
+ if (recorder === undefined) {
2658
+ return;
2659
+ }
2660
+ let status = recorder.status();
2661
+ if (terminal || status.state !== "healthy") {
2662
+ activeLifecycleRuns.delete(runID);
2663
+ try {
2664
+ status = await recorder.release();
2665
+ } catch {
2666
+ status = unavailableRecording(runID);
2667
+ }
2668
+ }
2669
+ return status;
2670
+ }
2671
+ async function abandonObservation(runID, recorder) {
2672
+ activeLifecycleRuns.delete(runID);
2673
+ try {
2674
+ await recorder.release();
2675
+ } catch {}
2676
+ }
2677
+ async function verifyObservation(runID, observation, expectations, verificationOptions = {}) {
2678
+ const recorder = observation.recorder;
2679
+ if (recorder === undefined) {
2680
+ return observation;
2681
+ }
2682
+ try {
2683
+ const status = await recorder.verifyOperations(expectations, verificationOptions);
2684
+ if (status.state === "healthy") {
2685
+ return { recorder, status };
2686
+ }
2687
+ await abandonObservation(runID, recorder);
2688
+ return { status };
2689
+ } catch {
2690
+ await abandonObservation(runID, recorder);
2691
+ return { status: unavailableRecording(runID) };
2692
+ }
2693
+ }
2288
2694
  function taskGate(taskID) {
2289
2695
  const decision = decideIngress(policy, taskID);
2290
2696
  return decision.route === "legacy" ? {
@@ -2303,21 +2709,33 @@ function createGraphIngress(deps) {
2303
2709
  if (blocked !== undefined) {
2304
2710
  return blocked;
2305
2711
  }
2712
+ let observation;
2306
2713
  try {
2307
2714
  const spec = compileSddTask(task);
2715
+ observation = await beginLifecycle(spec);
2308
2716
  const runtime = createGraphRuntime({
2309
2717
  spec,
2310
2718
  journal,
2311
2719
  lease,
2312
2720
  effects,
2313
- holder
2721
+ holder,
2722
+ ...observation.recorder === undefined ? {} : { lifecycle: observation.recorder }
2314
2723
  });
2724
+ const step = await runtime.step();
2725
+ const recording = await finishObservation(spec.runID, observation.recorder, step.kind === "completed" || step.kind === "failed") ?? observation.status;
2315
2726
  return {
2316
2727
  kind: "accepted",
2317
2728
  runID: spec.runID,
2318
- step: await runtime.step()
2729
+ step,
2730
+ ...recording === undefined ? {} : { recording }
2319
2731
  };
2320
2732
  } catch (error) {
2733
+ if (observation?.recorder !== undefined) {
2734
+ activeLifecycleRuns.delete(observation.recorder.runID);
2735
+ try {
2736
+ await observation.recorder.release();
2737
+ } catch {}
2738
+ }
2321
2739
  return toErrorOutcome(error);
2322
2740
  }
2323
2741
  },
@@ -2326,17 +2744,36 @@ function createGraphIngress(deps) {
2326
2744
  if (blocked !== undefined) {
2327
2745
  return blocked;
2328
2746
  }
2747
+ let observation;
2329
2748
  try {
2330
2749
  const loaded = await journal.load(runID);
2750
+ observation = loaded.state.status === "running" ? await continueLifecycle(runID) : {};
2751
+ if (loaded.state.status === "running") {
2752
+ observation = await verifyObservation(runID, observation, graphLifecycleExpectations(loaded));
2753
+ }
2331
2754
  const runtime = createGraphRuntime({
2332
2755
  spec: loaded.spec,
2333
2756
  journal,
2334
2757
  lease,
2335
2758
  effects,
2336
- holder
2759
+ holder,
2760
+ ...observation.recorder === undefined ? {} : { lifecycle: observation.recorder }
2337
2761
  });
2338
- return { kind: "accepted", runID, step: await runtime.step() };
2762
+ const step = await runtime.step();
2763
+ const recording = await finishObservation(runID, observation.recorder, step.kind === "completed" || step.kind === "failed") ?? observation.status;
2764
+ return {
2765
+ kind: "accepted",
2766
+ runID,
2767
+ step,
2768
+ ...recording === undefined ? {} : { recording }
2769
+ };
2339
2770
  } catch (error) {
2771
+ if (observation?.recorder !== undefined) {
2772
+ activeLifecycleRuns.delete(runID);
2773
+ try {
2774
+ await observation.recorder.release();
2775
+ } catch {}
2776
+ }
2340
2777
  return toErrorOutcome(error);
2341
2778
  }
2342
2779
  },
@@ -2379,6 +2816,7 @@ function createGraphIngress(deps) {
2379
2816
  if (blocked !== undefined) {
2380
2817
  return blocked;
2381
2818
  }
2819
+ let observation;
2382
2820
  try {
2383
2821
  const loaded = await journal.load(runID);
2384
2822
  const plan2 = planCancellation(loaded.state);
@@ -2390,24 +2828,95 @@ function createGraphIngress(deps) {
2390
2828
  abortNodeIDs: []
2391
2829
  };
2392
2830
  }
2831
+ observation = await continueLifecycle(runID);
2832
+ const observationsBeforeCancellation = graphLifecycleExpectations(loaded);
2833
+ observation = await verifyObservation(runID, observation, observationsBeforeCancellation);
2393
2834
  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);
2835
+ try {
2836
+ const handle = await journal.takeOver(runID);
2837
+ let seq = loaded.events.length;
2838
+ for (const nodeID of plan2.cancelNodeIDs) {
2839
+ await journal.append(handle, nodeCancelled(runID, seq, nodeID), seq);
2840
+ seq += 1;
2841
+ const node = loaded.spec.nodes.find((candidate) => candidate.id === nodeID);
2842
+ const state = loaded.state.nodes[nodeID];
2843
+ if (node === undefined || state === undefined) {
2844
+ continue;
2845
+ }
2846
+ if (state.status === "running" && state.operationID !== undefined && state.attemptID !== undefined && state.attempts > 0) {
2847
+ const attempt = state.attempts - 1;
2848
+ await appendLifecycle(observation.recorder, {
2849
+ type: "attempt.terminal",
2850
+ operationID: state.operationID,
2851
+ taskID: node.anchor.taskID,
2852
+ attemptID: state.attemptID,
2853
+ attempt,
2854
+ modelIndex: 0,
2855
+ retryIndex: attempt,
2856
+ outcome: "cancelled",
2857
+ continuation: "none",
2858
+ failure: { class: "cancelled", retryable: false }
2859
+ });
2860
+ }
2861
+ await appendLifecycle(observation.recorder, {
2862
+ type: "operation.terminal",
2863
+ operationID: state.operationID ?? operationID(runID, nodeID),
2864
+ taskID: node.anchor.taskID,
2865
+ outcome: "cancelled",
2866
+ attemptCount: state.attempts,
2867
+ failure: { class: "cancelled", retryable: false }
2868
+ });
2869
+ }
2870
+ if (plan2.cancelRun) {
2871
+ await journal.append(handle, runCancelled(runID, seq), seq);
2872
+ for (const node of loaded.spec.nodes) {
2873
+ const state = loaded.state.nodes[node.id];
2874
+ if (state?.status !== "failed")
2875
+ continue;
2876
+ const operation = state.operationID ?? operationID(runID, node.id);
2877
+ const awaitingRetry = observationsBeforeCancellation.some((expected) => expected.operationID === operation && !expected.operationTerminalRequired);
2878
+ if (!awaitingRetry)
2879
+ continue;
2880
+ await appendLifecycle(observation.recorder, {
2881
+ type: "operation.terminal",
2882
+ operationID: operation,
2883
+ taskID: node.anchor.taskID,
2884
+ outcome: "cancelled",
2885
+ attemptCount: state.attempts,
2886
+ failure: { class: "cancelled", retryable: false }
2887
+ });
2888
+ }
2889
+ observation = await verifyObservation(runID, observation, graphLifecycleExpectations(loaded, {
2890
+ closingStatus: "cancelled",
2891
+ cancelledNodeIDs: plan2.cancelNodeIDs
2892
+ }), { requireCompleteObservedWork: true });
2893
+ if (observation.recorder !== undefined) {
2894
+ await appendLifecycle(observation.recorder, {
2895
+ type: "run.terminal",
2896
+ outcome: "cancelled",
2897
+ operationCount: loaded.spec.nodes.length,
2898
+ failure: { class: "cancelled", retryable: false }
2899
+ });
2900
+ }
2901
+ }
2902
+ } finally {
2903
+ await lease.release(leaseHandle);
2402
2904
  }
2403
- await lease.release(leaseHandle);
2905
+ const recording = await finishObservation(runID, observation.recorder, plan2.cancelRun) ?? observation.status;
2404
2906
  return {
2405
2907
  kind: "cancelled",
2406
2908
  runID,
2407
2909
  cancelledNodeIDs: plan2.cancelNodeIDs,
2408
- abortNodeIDs: plan2.abortNodeIDs
2910
+ abortNodeIDs: plan2.abortNodeIDs,
2911
+ ...recording === undefined ? {} : { recording }
2409
2912
  };
2410
2913
  } catch (error) {
2914
+ if (observation?.recorder !== undefined) {
2915
+ activeLifecycleRuns.delete(runID);
2916
+ try {
2917
+ await observation.recorder.release();
2918
+ } catch {}
2919
+ }
2411
2920
  return toErrorOutcome(error);
2412
2921
  }
2413
2922
  }
@@ -3184,14 +3693,46 @@ function createGraphOperator(options) {
3184
3693
  evaluateCutoverGate,
3185
3694
  createIngress(policy, config) {
3186
3695
  const root = journalRoot(config);
3696
+ const lifecycleRecorder = options.createLifecycleRecorder === undefined ? options.lifecycleRecorder : (() => {
3697
+ let recorder;
3698
+ const getRecorder = () => {
3699
+ recorder ??= options.createLifecycleRecorder(config);
3700
+ return recorder;
3701
+ };
3702
+ return {
3703
+ beginRun: (input) => getRecorder().beginRun(input),
3704
+ async continueRun(runID) {
3705
+ const continuation = getRecorder().continueRun;
3706
+ if (continuation === undefined) {
3707
+ return {
3708
+ ok: false,
3709
+ status: {
3710
+ runID,
3711
+ state: "unavailable",
3712
+ lastCommittedHead: 0,
3713
+ failure: {
3714
+ code: "unavailable",
3715
+ recoverable: true,
3716
+ runID
3717
+ }
3718
+ }
3719
+ };
3720
+ }
3721
+ return continuation(runID);
3722
+ }
3723
+ };
3724
+ })();
3187
3725
  const effects = resolveGraphEngine(config) === "langgraph" ? createLangGraphEffects(options.nodeExecutor) : inertGraphEffects();
3188
- return createGraphIngress({
3726
+ const ingressOptions = {
3189
3727
  policy,
3190
3728
  journal: createFsGraphJournal(root),
3191
3729
  lease: createWorkspaceLease(root),
3192
3730
  effects,
3193
- holder: options.holder
3194
- });
3731
+ holder: options.holder,
3732
+ lifecycleRecorder,
3733
+ lifecycleSource: options.lifecycleSource
3734
+ };
3735
+ return createGraphIngress(ingressOptions);
3195
3736
  }
3196
3737
  };
3197
3738
  }
@@ -3311,6 +3852,7 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
3311
3852
  "storage",
3312
3853
  "memory",
3313
3854
  "graph",
3855
+ "lifecycle",
3314
3856
  "orchestrator",
3315
3857
  "configVersion"
3316
3858
  ]
@@ -3369,11 +3911,13 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
3369
3911
  "openBrowser",
3370
3912
  "remoteStorage",
3371
3913
  "terminal",
3372
- "graphView"
3914
+ "graphView",
3915
+ "sessionHistory"
3373
3916
  ]
3374
3917
  ],
3375
3918
  ["console.terminal", ["enabled", "pty"]],
3376
3919
  ["console.graphView", ["enabled"]],
3920
+ ["console.sessionHistory", ["contentEnabled", "endpoint"]],
3377
3921
  ["storage", ["sqliteIndex"]],
3378
3922
  ["storage.sqliteIndex", ["enabled", "path"]],
3379
3923
  ["memory", ["semantic"]],
@@ -3409,6 +3953,8 @@ var KNOWN_CONFIG_KEYS_BY_PATH = new Map([
3409
3953
  ]
3410
3954
  ],
3411
3955
  ["graph.worktrees", ["enabled"]],
3956
+ ["lifecycle", ["root", "retention"]],
3957
+ ["lifecycle.retention", ["maxTerminalRuns", "maxAgeDays", "maxBytes"]],
3412
3958
  ["orchestrator", ["roles", "primary", "maxRetries", "maxModelsPerNode"]],
3413
3959
  ["orchestrator.primary", ["executionMode", "model", "fallbacks"]],
3414
3960
  ["orchestrator.primary.fallbacks[]", MODEL_REF_KEYS]
@@ -5658,45 +6204,921 @@ function parseSoakCertificate(value, expectedPolicy, expectedLedgerDigest) {
5658
6204
  return certificate;
5659
6205
  }
5660
6206
 
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;
6207
+ // src/lifecycle/root.ts
6208
+ import { isAbsolute, normalize, relative, resolve as resolve3 } from "node:path";
6209
+
6210
+ // src/messages/lifecycleStorage.ts
6211
+ var lifecycleStorageMessages = {
6212
+ configuredRootEscapesWorkspace: "Configured lifecycle root must remain beneath the workspace root."
6213
+ };
6214
+
6215
+ // src/lifecycle/root.ts
6216
+ var resolveLifecycleRoot = (options) => {
6217
+ const workspaceRoot = resolve3(options.workspaceRoot);
6218
+ if (isAbsolute(options.configuredRoot)) {
6219
+ return normalize(resolve3(options.configuredRoot));
6220
+ }
6221
+ const resolved = resolve3(workspaceRoot, options.configuredRoot);
6222
+ const workspaceRelative = relative(workspaceRoot, resolved);
6223
+ if (workspaceRelative === ".." || workspaceRelative.startsWith(`..\\`) || workspaceRelative.startsWith("../") || isAbsolute(workspaceRelative)) {
6224
+ throw new Error(lifecycleStorageMessages.configuredRootEscapesWorkspace);
6225
+ }
6226
+ return normalize(resolved);
6227
+ };
6228
+
6229
+ // src/lifecycle/schema.ts
6230
+ import { z as z17 } from "zod";
6231
+ var nonEmptyString = z17.string().min(1);
6232
+ var nonNegativeInteger = z17.number().int().nonnegative();
6233
+ var positiveInteger = z17.number().int().positive();
6234
+ var modelSelectionSchema2 = z17.object({
6235
+ providerID: nonEmptyString,
6236
+ modelID: nonEmptyString
6237
+ }).strict();
6238
+ var lifecycleFailureSchema = z17.object({
6239
+ class: z17.enum([
6240
+ "routing-blocked",
6241
+ "create-rejected",
6242
+ "create-no-id",
6243
+ "prompt-rejected",
6244
+ "prompt-timeout",
6245
+ "prompt-rate-limited",
6246
+ "prompt-auth",
6247
+ "prompt-content-filter",
6248
+ "prompt-context-length",
6249
+ "prompt-validation",
6250
+ "prompt-connection",
6251
+ "prompt-server",
6252
+ "prompt-stop-unconfirmed",
6253
+ "report-incomplete",
6254
+ "report-not-durable",
6255
+ "budget-exhausted",
6256
+ "dependency-failed",
6257
+ "cancelled",
6258
+ "interrupted",
6259
+ "unknown"
6260
+ ]),
6261
+ statusCode: z17.number().int().min(100).max(599).optional(),
6262
+ retryable: z17.boolean().optional()
6263
+ }).strict();
6264
+ var lifecycleRootOperationSchema = z17.object({
6265
+ operationID: nonEmptyString,
6266
+ taskID: nonEmptyString,
6267
+ kind: z17.enum(["coordinator", "graph-node"]),
6268
+ roleID: nonEmptyString,
6269
+ agentID: nonEmptyString.optional(),
6270
+ sessionID: nonEmptyString.optional()
6271
+ }).strict();
6272
+ var lifecycleRunStartInputSchema = z17.object({
6273
+ runID: nonEmptyString,
6274
+ source: z17.enum(["plugin", "cli"]),
6275
+ executionAuthority: z17.enum(["coordinator", "graph"]),
6276
+ root: lifecycleRootOperationSchema
6277
+ }).strict();
6278
+ var LifecycleRunMetadataSchema = lifecycleRunStartInputSchema.extend({
6279
+ version: z17.literal(1),
6280
+ createdAt: nonNegativeInteger
6281
+ }).strict();
6282
+ var attemptRefShape = {
6283
+ operationID: nonEmptyString,
6284
+ taskID: nonEmptyString,
6285
+ attemptID: nonEmptyString,
6286
+ attempt: nonNegativeInteger,
6287
+ modelIndex: nonNegativeInteger,
6288
+ retryIndex: nonNegativeInteger
6289
+ };
6290
+ var eventEnvelopeShape = {
6291
+ v: z17.literal(1),
6292
+ seq: nonNegativeInteger,
6293
+ runID: nonEmptyString,
6294
+ at: nonNegativeInteger
6295
+ };
6296
+ var runStartedBodyShape = {
6297
+ type: z17.literal("run.started"),
6298
+ source: z17.enum(["plugin", "cli"]),
6299
+ executionAuthority: z17.enum(["coordinator", "graph"]),
6300
+ root: lifecycleRootOperationSchema
6301
+ };
6302
+ var operationQueuedBodyShape = {
6303
+ type: z17.literal("operation.queued"),
6304
+ operationID: nonEmptyString,
6305
+ taskID: nonEmptyString,
6306
+ kind: z17.enum(["role", "graph-node"]),
6307
+ roleID: nonEmptyString,
6308
+ agentID: nonEmptyString.optional(),
6309
+ parentOperationID: nonEmptyString,
6310
+ dependsOnOperationIDs: z17.array(nonEmptyString).readonly()
6311
+ };
6312
+ var modelSelectedBodyShape = {
6313
+ type: z17.literal("attempt.model-selected"),
6314
+ ...attemptRefShape,
6315
+ decisionID: nonEmptyString,
6316
+ model: modelSelectionSchema2,
6317
+ routeKind: z17.enum(["local", "frontier"]),
6318
+ selectionCause: z17.enum([
6319
+ "initial",
6320
+ "retry-incomplete",
6321
+ "retry-transport",
6322
+ "fallback-incomplete",
6323
+ "fallback-transport"
6324
+ ]),
6325
+ runtimeID: nonEmptyString.optional()
6326
+ };
6327
+ var attemptQueuedBodyShape = {
6328
+ type: z17.literal("attempt.queued"),
6329
+ ...attemptRefShape,
6330
+ runtimeID: nonEmptyString.optional()
6331
+ };
6332
+ var attemptStartedBodyShape = {
6333
+ type: z17.literal("attempt.started"),
6334
+ ...attemptRefShape
6335
+ };
6336
+ var sessionCreatedBodyShape = {
6337
+ type: z17.literal("session.created"),
6338
+ ...attemptRefShape,
6339
+ sessionID: nonEmptyString,
6340
+ parentSessionID: nonEmptyString.optional()
6341
+ };
6342
+ var attemptTerminalBodyShape = {
6343
+ type: z17.literal("attempt.terminal"),
6344
+ ...attemptRefShape,
6345
+ outcome: z17.enum([
6346
+ "succeeded",
6347
+ "failed",
6348
+ "cancelled",
6349
+ "interrupted",
6350
+ "unknown"
6351
+ ]),
6352
+ continuation: z17.enum([
6353
+ "none",
6354
+ "retry-same-model",
6355
+ "fallback-next-model",
6356
+ "retry-same-effect"
6357
+ ]),
6358
+ sessionID: nonEmptyString.optional(),
6359
+ failure: lifecycleFailureSchema.optional()
6360
+ };
6361
+ var operationTerminalBodyShape = {
6362
+ type: z17.literal("operation.terminal"),
6363
+ operationID: nonEmptyString,
6364
+ taskID: nonEmptyString,
6365
+ outcome: z17.enum([
6366
+ "succeeded",
6367
+ "failed",
6368
+ "cancelled",
6369
+ "interrupted",
6370
+ "unknown",
6371
+ "blocked"
6372
+ ]),
6373
+ attemptCount: nonNegativeInteger,
6374
+ finalSessionID: nonEmptyString.optional(),
6375
+ failure: lifecycleFailureSchema.optional()
6376
+ };
6377
+ var runTerminalBodyShape = {
6378
+ type: z17.literal("run.terminal"),
6379
+ outcome: z17.enum([
6380
+ "succeeded",
6381
+ "failed",
6382
+ "cancelled",
6383
+ "interrupted",
6384
+ "unknown"
6385
+ ]),
6386
+ operationCount: nonNegativeInteger,
6387
+ failure: lifecycleFailureSchema.optional()
6388
+ };
6389
+ var bodySchemas = [
6390
+ z17.object(runStartedBodyShape).strict(),
6391
+ z17.object(operationQueuedBodyShape).strict(),
6392
+ z17.object(modelSelectedBodyShape).strict(),
6393
+ z17.object(attemptQueuedBodyShape).strict(),
6394
+ z17.object(attemptStartedBodyShape).strict(),
6395
+ z17.object(sessionCreatedBodyShape).strict(),
6396
+ z17.object(attemptTerminalBodyShape).strict(),
6397
+ z17.object(operationTerminalBodyShape).strict(),
6398
+ z17.object(runTerminalBodyShape).strict()
6399
+ ];
6400
+ var appendBodySchemas = bodySchemas.slice(1);
6401
+ var LifecycleAppendEventSchema = z17.discriminatedUnion("type", appendBodySchemas);
6402
+ var LifecycleEventSchema = z17.discriminatedUnion("type", [
6403
+ z17.object({ ...eventEnvelopeShape, ...runStartedBodyShape }).strict(),
6404
+ z17.object({ ...eventEnvelopeShape, ...operationQueuedBodyShape }).strict(),
6405
+ z17.object({ ...eventEnvelopeShape, ...modelSelectedBodyShape }).strict(),
6406
+ z17.object({ ...eventEnvelopeShape, ...attemptQueuedBodyShape }).strict(),
6407
+ z17.object({ ...eventEnvelopeShape, ...attemptStartedBodyShape }).strict(),
6408
+ z17.object({ ...eventEnvelopeShape, ...sessionCreatedBodyShape }).strict(),
6409
+ z17.object({ ...eventEnvelopeShape, ...attemptTerminalBodyShape }).strict(),
6410
+ z17.object({ ...eventEnvelopeShape, ...operationTerminalBodyShape }).strict(),
6411
+ z17.object({ ...eventEnvelopeShape, ...runTerminalBodyShape }).strict()
6412
+ ]);
6413
+ var LifecycleWriterLeaseInputSchema = z17.object({
6414
+ writer: z17.object({
6415
+ processID: positiveInteger,
6416
+ processInstanceID: nonEmptyString
6417
+ }).strict(),
6418
+ now: nonNegativeInteger,
6419
+ leaseDurationMs: positiveInteger
6420
+ }).strict();
6421
+ var LifecycleWriterLeaseSchema = z17.object({
6422
+ version: z17.literal(1),
6423
+ runID: nonEmptyString,
6424
+ writerToken: nonEmptyString,
6425
+ processID: positiveInteger,
6426
+ processInstanceID: nonEmptyString,
6427
+ acquiredAt: nonNegativeInteger,
6428
+ heartbeatAt: nonNegativeInteger,
6429
+ expiresAt: nonNegativeInteger
6430
+ }).strict().superRefine((lease, context) => {
6431
+ if (lease.heartbeatAt < lease.acquiredAt) {
6432
+ context.addIssue({
6433
+ code: "custom",
6434
+ path: ["heartbeatAt"],
6435
+ message: "heartbeatAt precedes acquiredAt"
6436
+ });
5670
6437
  }
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;
6438
+ if (lease.expiresAt <= lease.heartbeatAt) {
6439
+ context.addIssue({
6440
+ code: "custom",
6441
+ path: ["expiresAt"],
6442
+ message: "expiresAt must follow heartbeatAt"
6443
+ });
6444
+ }
6445
+ });
6446
+ function omitBoundedLifecycleSource(value) {
6447
+ if (Array.isArray(value)) {
6448
+ return value.map(omitBoundedLifecycleSource);
6449
+ }
6450
+ if (value === null || typeof value !== "object") {
6451
+ return value;
5684
6452
  }
6453
+ return Object.fromEntries(Object.entries(value).flatMap(([key, child]) => {
6454
+ if (key === "source" && (child === "plugin" || child === "cli" || child === "lifecycle")) {
6455
+ return [];
6456
+ }
6457
+ return [[key, omitBoundedLifecycleSource(child)]];
6458
+ }));
5685
6459
  }
5686
- function extractVersion(pkg) {
5687
- if (pkg === undefined)
5688
- return "";
5689
- const v = pkg.version;
5690
- return typeof v === "string" ? v : "";
6460
+ function assertLifecycleReferenceOnly(value) {
6461
+ assertReferenceOnly(omitBoundedLifecycleSource(value));
5691
6462
  }
5692
- function readDirectoryEntries(dir) {
5693
- try {
5694
- return fs2.readdirSync(dir, { withFileTypes: true });
5695
- } catch {
5696
- return [];
5697
- }
6463
+ function parseLifecycleRunStartInput(value) {
6464
+ const parsed = lifecycleRunStartInputSchema.parse(value);
6465
+ assertLifecycleReferenceOnly(parsed);
6466
+ return parsed;
5698
6467
  }
5699
- function lstatPlainDirectory(dir) {
6468
+ function parseLifecycleRunMetadata(value) {
6469
+ const parsed = LifecycleRunMetadataSchema.parse(value);
6470
+ assertLifecycleReferenceOnly(parsed);
6471
+ return parsed;
6472
+ }
6473
+ function parseLifecycleAppendEvent(value) {
6474
+ const parsed = LifecycleAppendEventSchema.parse(value);
6475
+ assertLifecycleReferenceOnly(parsed);
6476
+ return parsed;
6477
+ }
6478
+ function parseLifecycleEvent(value) {
6479
+ const parsed = LifecycleEventSchema.parse(value);
6480
+ assertLifecycleReferenceOnly(parsed);
6481
+ return parsed;
6482
+ }
6483
+ function parseLifecycleWriterLeaseInput(value) {
6484
+ return LifecycleWriterLeaseInputSchema.parse(value);
6485
+ }
6486
+ function parseLifecycleWriterLease(value) {
6487
+ const parsed = LifecycleWriterLeaseSchema.parse(value);
6488
+ assertLifecycleReferenceOnly(parsed);
6489
+ return parsed;
6490
+ }
6491
+
6492
+ // src/lifecycle/project.ts
6493
+ function operationForEvent(operations, event) {
6494
+ const operation = operations.get(event.operationID);
6495
+ if (operation === undefined) {
6496
+ throw new Error(`lifecycle event references unknown operation ${event.operationID}`);
6497
+ }
6498
+ if (operation.taskID !== event.taskID) {
6499
+ throw new Error(`lifecycle task identity changed for ${event.operationID}`);
6500
+ }
6501
+ return operation;
6502
+ }
6503
+ function attemptForEvent(operations, event) {
6504
+ const operation = operationForEvent(operations, event);
6505
+ const attempt = operation.attempts.find((candidate) => candidate.attemptID === event.attemptID);
6506
+ if (attempt === undefined) {
6507
+ throw new Error(`lifecycle event references unknown attempt ${event.attemptID}`);
6508
+ }
6509
+ if (attempt.attempt !== event.attempt || attempt.modelIndex !== event.modelIndex || attempt.retryIndex !== event.retryIndex) {
6510
+ throw new Error(`lifecycle attempt identity changed for ${event.attemptID}`);
6511
+ }
6512
+ return { operation, attempt };
6513
+ }
6514
+ function terminalOperationState(outcome) {
6515
+ if (outcome === undefined) {
6516
+ throw new Error("terminal lifecycle operation is missing its outcome");
6517
+ }
6518
+ return outcome;
6519
+ }
6520
+ var projectLifecyclePrefix = (input) => {
6521
+ const metadata = parseLifecycleRunMetadata(input.metadata);
6522
+ if (!Number.isInteger(input.cursor) || input.cursor < 0 || input.cursor > input.events.length) {
6523
+ throw new RangeError(`lifecycle cursor ${input.cursor} is outside 0..${input.events.length}`);
6524
+ }
6525
+ const events = input.events.map((event, index) => {
6526
+ const parsed = parseLifecycleEvent(event);
6527
+ if (parsed.seq !== index || parsed.runID !== metadata.runID) {
6528
+ throw new Error(`invalid lifecycle event envelope at sequence ${index}`);
6529
+ }
6530
+ return parsed;
6531
+ });
6532
+ const root = {
6533
+ operationID: metadata.root.operationID,
6534
+ taskID: metadata.root.taskID,
6535
+ kind: metadata.root.kind,
6536
+ roleID: metadata.root.roleID,
6537
+ ...metadata.root.agentID === undefined ? {} : { agentID: metadata.root.agentID },
6538
+ state: "waiting",
6539
+ dependsOnOperationIDs: [],
6540
+ attempts: []
6541
+ };
6542
+ const operations = new Map([
6543
+ [root.operationID, root]
6544
+ ]);
6545
+ const edges = [];
6546
+ let observedState = "pending";
6547
+ for (const event of events.slice(0, input.cursor)) {
6548
+ switch (event.type) {
6549
+ case "run.started": {
6550
+ if (event.root.operationID !== metadata.root.operationID || event.root.taskID !== metadata.root.taskID || event.source !== metadata.source || event.executionAuthority !== metadata.executionAuthority) {
6551
+ throw new Error("run.started does not match lifecycle metadata");
6552
+ }
6553
+ root.state = "running";
6554
+ observedState = "running";
6555
+ break;
6556
+ }
6557
+ case "operation.queued": {
6558
+ if (operations.has(event.operationID)) {
6559
+ throw new Error(`duplicate lifecycle operation ${event.operationID}`);
6560
+ }
6561
+ operations.set(event.operationID, {
6562
+ operationID: event.operationID,
6563
+ taskID: event.taskID,
6564
+ kind: event.kind,
6565
+ roleID: event.roleID,
6566
+ ...event.agentID === undefined ? {} : { agentID: event.agentID },
6567
+ state: "queued",
6568
+ parentOperationID: event.parentOperationID,
6569
+ dependsOnOperationIDs: [...event.dependsOnOperationIDs],
6570
+ attempts: [],
6571
+ queuedAt: event.at
6572
+ });
6573
+ edges.push({
6574
+ kind: "hierarchy",
6575
+ from: event.parentOperationID,
6576
+ to: event.operationID
6577
+ });
6578
+ for (const dependency of event.dependsOnOperationIDs) {
6579
+ edges.push({
6580
+ kind: "dependency",
6581
+ from: dependency,
6582
+ to: event.operationID
6583
+ });
6584
+ }
6585
+ break;
6586
+ }
6587
+ case "attempt.model-selected": {
6588
+ const operation = operationForEvent(operations, event);
6589
+ if (operation.attempts.some((attempt) => attempt.attemptID === event.attemptID || attempt.attempt === event.attempt)) {
6590
+ throw new Error(`duplicate lifecycle attempt ${event.attemptID}`);
6591
+ }
6592
+ operation.attempts.push({
6593
+ operationID: event.operationID,
6594
+ taskID: event.taskID,
6595
+ attemptID: event.attemptID,
6596
+ attempt: event.attempt,
6597
+ modelIndex: event.modelIndex,
6598
+ retryIndex: event.retryIndex,
6599
+ state: "selected",
6600
+ decisionID: event.decisionID,
6601
+ model: event.model,
6602
+ routeKind: event.routeKind,
6603
+ selectionCause: event.selectionCause,
6604
+ ...event.runtimeID === undefined ? {} : { runtimeID: event.runtimeID },
6605
+ selectedAt: event.at
6606
+ });
6607
+ break;
6608
+ }
6609
+ case "attempt.queued": {
6610
+ const operation = operationForEvent(operations, event);
6611
+ let attempt = operation.attempts.find((candidate) => candidate.attemptID === event.attemptID);
6612
+ if (attempt === undefined) {
6613
+ if (operation.kind !== "graph-node" || operation.attempts.some((candidate) => candidate.attempt === event.attempt)) {
6614
+ throw new Error(`lifecycle event references unknown attempt ${event.attemptID}`);
6615
+ }
6616
+ attempt = {
6617
+ operationID: event.operationID,
6618
+ taskID: event.taskID,
6619
+ attemptID: event.attemptID,
6620
+ attempt: event.attempt,
6621
+ modelIndex: event.modelIndex,
6622
+ retryIndex: event.retryIndex,
6623
+ state: "queued"
6624
+ };
6625
+ operation.attempts.push(attempt);
6626
+ } else if (attempt.attempt !== event.attempt || attempt.modelIndex !== event.modelIndex || attempt.retryIndex !== event.retryIndex) {
6627
+ throw new Error(`lifecycle attempt identity changed for ${event.attemptID}`);
6628
+ }
6629
+ attempt.state = "queued";
6630
+ attempt.queuedAt = event.at;
6631
+ if (event.runtimeID !== undefined) {
6632
+ attempt.runtimeID = event.runtimeID;
6633
+ }
6634
+ break;
6635
+ }
6636
+ case "attempt.started": {
6637
+ const { operation, attempt } = attemptForEvent(operations, event);
6638
+ attempt.state = "running";
6639
+ attempt.startedAt = event.at;
6640
+ operation.state = "running";
6641
+ break;
6642
+ }
6643
+ case "session.created": {
6644
+ const { attempt } = attemptForEvent(operations, event);
6645
+ attempt.sessionID = event.sessionID;
6646
+ if (event.parentSessionID !== undefined) {
6647
+ attempt.parentSessionID = event.parentSessionID;
6648
+ }
6649
+ break;
6650
+ }
6651
+ case "attempt.terminal": {
6652
+ const { attempt } = attemptForEvent(operations, event);
6653
+ attempt.state = event.outcome;
6654
+ attempt.outcome = event.outcome;
6655
+ attempt.continuation = event.continuation;
6656
+ attempt.terminalAt = event.at;
6657
+ if (event.sessionID !== undefined) {
6658
+ attempt.sessionID = event.sessionID;
6659
+ }
6660
+ if (event.failure !== undefined) {
6661
+ attempt.failure = event.failure;
6662
+ }
6663
+ break;
6664
+ }
6665
+ case "operation.terminal": {
6666
+ const operation = operationForEvent(operations, event);
6667
+ operation.outcome = event.outcome;
6668
+ operation.state = terminalOperationState(event.outcome);
6669
+ operation.terminalAt = event.at;
6670
+ if (event.failure !== undefined) {
6671
+ operation.failure = event.failure;
6672
+ }
6673
+ break;
6674
+ }
6675
+ case "run.terminal":
6676
+ observedState = event.outcome;
6677
+ root.state = event.outcome;
6678
+ root.outcome = event.outcome;
6679
+ root.terminalAt = event.at;
6680
+ if (event.failure !== undefined) {
6681
+ root.failure = event.failure;
6682
+ }
6683
+ break;
6684
+ }
6685
+ }
6686
+ const timeline = events.slice(0, input.cursor);
6687
+ const projection = {
6688
+ version: 1,
6689
+ source: "lifecycle",
6690
+ runID: metadata.runID,
6691
+ cursor: input.cursor,
6692
+ head: events.length,
6693
+ observedState,
6694
+ rootOperationID: metadata.root.operationID,
6695
+ operations: [...operations.values()].map((operation) => ({
6696
+ ...operation,
6697
+ dependsOnOperationIDs: [...operation.dependsOnOperationIDs],
6698
+ attempts: operation.attempts.map((attempt) => ({ ...attempt }))
6699
+ })),
6700
+ edges,
6701
+ timeline,
6702
+ lastEventAt: timeline.at(-1)?.at ?? null
6703
+ };
6704
+ assertLifecycleReferenceOnly(projection);
6705
+ return projection;
6706
+ };
6707
+
6708
+ // src/lifecycle/runtimeRecorder.ts
6709
+ var DEFAULT_LIFECYCLE_HEARTBEAT_INTERVAL_MS = 5000;
6710
+ var DEFAULT_LIFECYCLE_WRITER_LEASE_MS = 30000;
6711
+ var createNodeLifecycleHeartbeatScheduler = () => ({
6712
+ start(intervalMs, heartbeat) {
6713
+ let stopped = false;
6714
+ let running = false;
6715
+ const timer = setInterval(() => {
6716
+ if (stopped || running) {
6717
+ return;
6718
+ }
6719
+ running = true;
6720
+ heartbeat().catch(() => {}).finally(() => {
6721
+ running = false;
6722
+ });
6723
+ }, Math.max(1, intervalMs));
6724
+ timer.unref?.();
6725
+ return {
6726
+ stop() {
6727
+ if (stopped) {
6728
+ return;
6729
+ }
6730
+ stopped = true;
6731
+ clearInterval(timer);
6732
+ }
6733
+ };
6734
+ }
6735
+ });
6736
+ function thrownJournalFailure(runID) {
6737
+ return {
6738
+ code: "unavailable",
6739
+ recoverable: true,
6740
+ runID
6741
+ };
6742
+ }
6743
+ function failedStatus(current, failure) {
6744
+ return {
6745
+ runID: current.runID,
6746
+ state: failure.recoverable ? "degraded" : "unavailable",
6747
+ lastCommittedHead: current.lastCommittedHead,
6748
+ failure
6749
+ };
6750
+ }
6751
+ function attemptObservationMissing(attempt, expectation) {
6752
+ if (expectation.attemptID === null || attempt?.queuedAt === undefined) {
6753
+ return true;
6754
+ }
6755
+ if (expectation.minimumPhase !== "queued" && attempt.startedAt === undefined) {
6756
+ return true;
6757
+ }
6758
+ return expectation.minimumPhase === "terminal" && attempt.terminalAt === undefined;
6759
+ }
6760
+ function operationObservationMissing(projection, expectation) {
6761
+ const operation = projection.operations.find((candidate) => candidate.operationID === expectation.operationID);
6762
+ if (operation === undefined || expectation.operationQueuedRequired && operation.queuedAt === undefined || expectation.operationTerminalRequired && operation.terminalAt === undefined) {
6763
+ return true;
6764
+ }
6765
+ return expectation.attempts.some((attemptExpectation) => attemptObservationMissing(operation.attempts.find((attempt) => attempt.attemptID === attemptExpectation.attemptID), attemptExpectation));
6766
+ }
6767
+ function hasIncompleteObservedWork(projection) {
6768
+ return projection.operations.some((operation) => operation.operationID !== projection.rootOperationID && operation.terminalAt === undefined || operation.attempts.some((attempt) => attempt.terminalAt === undefined));
6769
+ }
6770
+ function createActiveRunRecorder(options, handle, initialHead) {
6771
+ const runID = handle.runID;
6772
+ let current = {
6773
+ runID,
6774
+ state: "healthy",
6775
+ lastCommittedHead: initialHead
6776
+ };
6777
+ let schedule;
6778
+ let released = false;
6779
+ let queue = Promise.resolve();
6780
+ const stopSchedule = () => {
6781
+ schedule?.stop();
6782
+ schedule = undefined;
6783
+ };
6784
+ const degrade = (failure) => {
6785
+ current = failedStatus(current, failure);
6786
+ stopSchedule();
6787
+ };
6788
+ const serialize = (work) => {
6789
+ const result = queue.then(work, work);
6790
+ queue = result.then(() => {}, () => {});
6791
+ return result;
6792
+ };
6793
+ const releaseOwner = async () => {
6794
+ if (released) {
6795
+ return;
6796
+ }
6797
+ released = true;
6798
+ stopSchedule();
6799
+ try {
6800
+ const release = await options.journal.release(handle);
6801
+ if (!release.ok) {
6802
+ degrade(release.error);
6803
+ }
6804
+ } catch {
6805
+ degrade(thrownJournalFailure(runID));
6806
+ }
6807
+ };
6808
+ const verifyCommittedProjection = async (expectations, verificationOptions) => {
6809
+ try {
6810
+ const loaded = await options.journal.load(runID);
6811
+ if (!loaded.ok) {
6812
+ degrade(loaded.error);
6813
+ return false;
6814
+ }
6815
+ if (loaded.value.integrity !== "clean" || loaded.value.head !== current.lastCommittedHead) {
6816
+ degrade({ code: "corrupt", recoverable: false, runID });
6817
+ return false;
6818
+ }
6819
+ const projection = projectLifecyclePrefix({
6820
+ ...loaded.value,
6821
+ cursor: loaded.value.head
6822
+ });
6823
+ if (expectations.some((expectation) => operationObservationMissing(projection, expectation)) || verificationOptions.requireCompleteObservedWork === true && hasIncompleteObservedWork(projection)) {
6824
+ degrade({ code: "unavailable", recoverable: false, runID });
6825
+ return false;
6826
+ }
6827
+ return true;
6828
+ } catch {
6829
+ degrade({ code: "unavailable", recoverable: false, runID });
6830
+ return false;
6831
+ }
6832
+ };
6833
+ const heartbeat = () => serialize(async () => {
6834
+ if (released || current.state !== "healthy") {
6835
+ return;
6836
+ }
6837
+ try {
6838
+ const result = await options.journal.heartbeat(handle, {
6839
+ now: options.now(),
6840
+ leaseDurationMs: options.leaseDurationMs
6841
+ });
6842
+ if (!result.ok) {
6843
+ degrade(result.error);
6844
+ }
6845
+ } catch {
6846
+ degrade(thrownJournalFailure(runID));
6847
+ }
6848
+ });
6849
+ const recorder = {
6850
+ runID,
6851
+ append(event) {
6852
+ return serialize(async () => {
6853
+ if (released || current.state !== "healthy") {
6854
+ return { ok: false, status: current };
6855
+ }
6856
+ if (event.type === "run.terminal" && !await verifyCommittedProjection([], {
6857
+ requireCompleteObservedWork: true
6858
+ })) {
6859
+ await releaseOwner();
6860
+ return { ok: false, status: current };
6861
+ }
6862
+ let appended;
6863
+ try {
6864
+ appended = await options.journal.append(handle, event, current.lastCommittedHead);
6865
+ } catch {
6866
+ degrade(thrownJournalFailure(runID));
6867
+ return { ok: false, status: current };
6868
+ }
6869
+ if (!appended.ok) {
6870
+ degrade(appended.error);
6871
+ return { ok: false, status: current };
6872
+ }
6873
+ current = {
6874
+ runID,
6875
+ state: "healthy",
6876
+ lastCommittedHead: appended.value.head
6877
+ };
6878
+ if (event.type === "run.terminal") {
6879
+ await releaseOwner();
6880
+ if (current.state === "healthy" && options.retention !== undefined) {
6881
+ try {
6882
+ const retained = await options.journal.pruneTerminal(options.retention, options.now());
6883
+ if (!retained.ok) {
6884
+ degrade(retained.error);
6885
+ }
6886
+ } catch {
6887
+ degrade(thrownJournalFailure(runID));
6888
+ }
6889
+ }
6890
+ }
6891
+ return {
6892
+ ok: true,
6893
+ receipt: appended.value,
6894
+ status: current
6895
+ };
6896
+ });
6897
+ },
6898
+ status() {
6899
+ return current;
6900
+ },
6901
+ verifyOperations(expectations, verificationOptions = {}) {
6902
+ return serialize(async () => {
6903
+ if (released || current.state !== "healthy") {
6904
+ return current;
6905
+ }
6906
+ await verifyCommittedProjection(expectations, verificationOptions);
6907
+ if (current.state !== "healthy") {
6908
+ await releaseOwner();
6909
+ }
6910
+ return current;
6911
+ });
6912
+ },
6913
+ release() {
6914
+ return serialize(async () => {
6915
+ await releaseOwner();
6916
+ return current;
6917
+ });
6918
+ }
6919
+ };
6920
+ schedule = options.heartbeatScheduler.start(options.heartbeatIntervalMs, heartbeat);
6921
+ return recorder;
6922
+ }
6923
+ function continuationFailure(runID, lastCommittedHead, failure) {
6924
+ return {
6925
+ ok: false,
6926
+ status: failedStatus({
6927
+ runID,
6928
+ state: "healthy",
6929
+ lastCommittedHead
6930
+ }, failure)
6931
+ };
6932
+ }
6933
+ var createRuntimeLifecycleRecorder = (options) => {
6934
+ const runtime = {
6935
+ async beginRun(input) {
6936
+ const startedAt = options.now();
6937
+ let started;
6938
+ try {
6939
+ started = await options.journal.start(input, {
6940
+ writer: options.writer,
6941
+ now: startedAt,
6942
+ leaseDurationMs: options.leaseDurationMs
6943
+ });
6944
+ } catch {
6945
+ return {
6946
+ ok: false,
6947
+ status: {
6948
+ runID: input.runID,
6949
+ state: "unavailable",
6950
+ lastCommittedHead: 0,
6951
+ failure: thrownJournalFailure(input.runID)
6952
+ }
6953
+ };
6954
+ }
6955
+ if (!started.ok) {
6956
+ return {
6957
+ ok: false,
6958
+ status: {
6959
+ runID: input.runID,
6960
+ state: "unavailable",
6961
+ lastCommittedHead: 0,
6962
+ failure: started.error
6963
+ }
6964
+ };
6965
+ }
6966
+ const receipt = started.value;
6967
+ const recorder = createActiveRunRecorder(options, receipt.handle, receipt.head);
6968
+ return {
6969
+ ok: true,
6970
+ recorder,
6971
+ receipt,
6972
+ status: recorder.status()
6973
+ };
6974
+ }
6975
+ };
6976
+ const writerLiveness = options.writerLiveness;
6977
+ if (writerLiveness === undefined) {
6978
+ return runtime;
6979
+ }
6980
+ return {
6981
+ ...runtime,
6982
+ async continueRun(runID) {
6983
+ const releaseTaken = async (handle) => {
6984
+ try {
6985
+ await options.journal.release(handle);
6986
+ } catch {}
6987
+ };
6988
+ let loaded;
6989
+ try {
6990
+ loaded = await options.journal.load(runID);
6991
+ } catch {
6992
+ return continuationFailure(runID, 0, thrownJournalFailure(runID));
6993
+ }
6994
+ if (!loaded.ok) {
6995
+ return continuationFailure(runID, 0, loaded.error);
6996
+ }
6997
+ if (loaded.value.integrity !== "clean") {
6998
+ return continuationFailure(runID, loaded.value.head, {
6999
+ code: "corrupt",
7000
+ recoverable: false,
7001
+ runID
7002
+ });
7003
+ }
7004
+ if (loaded.value.events.at(-1)?.type === "run.terminal") {
7005
+ return continuationFailure(runID, loaded.value.head, {
7006
+ code: "terminal-run",
7007
+ recoverable: false,
7008
+ runID
7009
+ });
7010
+ }
7011
+ let observed;
7012
+ try {
7013
+ observed = await writerLiveness.observe(runID);
7014
+ } catch {
7015
+ return continuationFailure(runID, loaded.value.head, thrownJournalFailure(runID));
7016
+ }
7017
+ let expected;
7018
+ if (observed.kind === "absent") {
7019
+ expected = { kind: "absent" };
7020
+ } else if (observed.kind === "expired") {
7021
+ expected = {
7022
+ kind: "expired",
7023
+ writerToken: observed.lease.writerToken,
7024
+ expiresAt: observed.lease.expiresAt
7025
+ };
7026
+ } else {
7027
+ return continuationFailure(runID, loaded.value.head, {
7028
+ code: observed.kind === "active" ? "active-run" : "unavailable",
7029
+ recoverable: true,
7030
+ runID
7031
+ });
7032
+ }
7033
+ let taken;
7034
+ try {
7035
+ taken = await options.journal.takeOver(runID, expected, {
7036
+ writer: options.writer,
7037
+ now: options.now(),
7038
+ leaseDurationMs: options.leaseDurationMs
7039
+ });
7040
+ } catch {
7041
+ return continuationFailure(runID, loaded.value.head, thrownJournalFailure(runID));
7042
+ }
7043
+ if (!taken.ok) {
7044
+ return continuationFailure(runID, loaded.value.head, taken.error);
7045
+ }
7046
+ let confirmed;
7047
+ try {
7048
+ confirmed = await options.journal.load(runID);
7049
+ } catch {
7050
+ await releaseTaken(taken.value);
7051
+ return continuationFailure(runID, loaded.value.head, thrownJournalFailure(runID));
7052
+ }
7053
+ if (!confirmed.ok) {
7054
+ await releaseTaken(taken.value);
7055
+ return continuationFailure(runID, loaded.value.head, confirmed.error);
7056
+ }
7057
+ if (confirmed.value.integrity !== "clean") {
7058
+ await releaseTaken(taken.value);
7059
+ return continuationFailure(runID, confirmed.value.head, {
7060
+ code: "corrupt",
7061
+ recoverable: false,
7062
+ runID
7063
+ });
7064
+ }
7065
+ if (confirmed.value.events.at(-1)?.type === "run.terminal") {
7066
+ await releaseTaken(taken.value);
7067
+ return continuationFailure(runID, confirmed.value.head, {
7068
+ code: "terminal-run",
7069
+ recoverable: false,
7070
+ runID
7071
+ });
7072
+ }
7073
+ const recorder = createActiveRunRecorder(options, taken.value, confirmed.value.head);
7074
+ return {
7075
+ ok: true,
7076
+ recorder,
7077
+ status: recorder.status()
7078
+ };
7079
+ }
7080
+ };
7081
+ };
7082
+
7083
+ // src/local/cacheAdapter.ts
7084
+ import * as fs2 from "node:fs";
7085
+ import * as os from "node:os";
7086
+ import * as path from "node:path";
7087
+ var SCOPE_PREFIX = "@jmanuelcorral/openteam";
7088
+ var SCOPE_SEGMENTS = SCOPE_PREFIX.split("/");
7089
+ function resolveCacheRoot(override) {
7090
+ if (override !== undefined && override.trim() !== "") {
7091
+ return override;
7092
+ }
7093
+ const base = process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), ".cache");
7094
+ return path.join(base, "opencode", "packages");
7095
+ }
7096
+ function readJsonSafe(filePath) {
7097
+ try {
7098
+ const text = fs2.readFileSync(filePath, "utf8");
7099
+ const parsed = JSON.parse(text);
7100
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
7101
+ return parsed;
7102
+ }
7103
+ return;
7104
+ } catch {
7105
+ return;
7106
+ }
7107
+ }
7108
+ function extractVersion(pkg) {
7109
+ if (pkg === undefined)
7110
+ return "";
7111
+ const v = pkg.version;
7112
+ return typeof v === "string" ? v : "";
7113
+ }
7114
+ function readDirectoryEntries(dir) {
7115
+ try {
7116
+ return fs2.readdirSync(dir, { withFileTypes: true });
7117
+ } catch {
7118
+ return [];
7119
+ }
7120
+ }
7121
+ function lstatPlainDirectory(dir) {
5700
7122
  try {
5701
7123
  const stat2 = fs2.lstatSync(dir);
5702
7124
  if (!stat2.isDirectory() || stat2.isSymbolicLink()) {
@@ -5882,9 +7304,9 @@ function createRuntimeConcurrencyLimiter(options) {
5882
7304
  }
5883
7305
  const state = stateFor(runtimeId);
5884
7306
  const queuedAt = options.now();
5885
- return new Promise((resolve3) => {
7307
+ return new Promise((resolve4) => {
5886
7308
  state.queues[priority].push((release) => {
5887
- resolve3({ release, waitedMs: options.now() - queuedAt });
7309
+ resolve4({ release, waitedMs: options.now() - queuedAt });
5888
7310
  });
5889
7311
  });
5890
7312
  },
@@ -6506,6 +7928,9 @@ function withReportedModelSlots(runtime, reported) {
6506
7928
  }
6507
7929
 
6508
7930
  // src/messages/orchestration.ts
7931
+ var HOST_SESSION_REQUIRED = "Orchestration requires a nonempty host ToolContext sessionID.";
7932
+ var PARENT_OVERRIDE_IGNORED = "parentSessionID override ignored: the trusted host ToolContext session owns this orchestration and its children. Programmatic callers must supply their parent as the ToolContext sessionID.";
7933
+ var PARENT_SESSION_DESCRIPTION = "Legacy parent reference. Omit it: the trusted host ToolContext session is the parent. A differing payload value is ignored with an advisory.";
6509
7934
  var SESSION_CREATE_MISSING_ID = "create response did not include a session id";
6510
7935
  function orchestrationSummaryHeading(roleCount, completed) {
6511
7936
  return `orchestration ${completed ? "completed" : "incomplete"} (${roleCount} role(s)):`;
@@ -6592,14 +8017,14 @@ var readReportArtifact = async (reportPath) => {
6592
8017
  };
6593
8018
 
6594
8019
  // 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)
8020
+ import { z as z18 } from "zod";
8021
+ var RosterEntrySchema = z18.object({
8022
+ roleID: z18.string().min(1),
8023
+ agentName: z18.string().min(1)
6599
8024
  }).strict();
6600
- var RosterSchema = z17.object({
6601
- universe: z17.string().min(1),
6602
- entries: z17.array(RosterEntrySchema)
8025
+ var RosterSchema = z18.object({
8026
+ universe: z18.string().min(1),
8027
+ entries: z18.array(RosterEntrySchema)
6603
8028
  }).strict();
6604
8029
  var ROSTER_JSON_FENCE = /```json\s*([\s\S]*?)```/;
6605
8030
 
@@ -6772,6 +8197,18 @@ function enforceGuaranteedRoles(roster) {
6772
8197
  return { universe: roster.universe, entries: [...roster.entries, ...added] };
6773
8198
  }
6774
8199
 
8200
+ // src/messages/lifecycleRuntime.ts
8201
+ function normalizedSessionFailureMessage(failure) {
8202
+ return `session request failed (${failure.failureClass})`;
8203
+ }
8204
+ function lifecycleRecordingAdvisory(status) {
8205
+ if (status.state === "healthy") {
8206
+ return;
8207
+ }
8208
+ const failure = status.failure === undefined ? "" : ` (${status.failure.code})`;
8209
+ return `Lifecycle history recording is ${status.state}${failure}; model execution continued without claiming complete history.`;
8210
+ }
8211
+
6775
8212
  // src/orchestrator/subsessions.ts
6776
8213
  var DEFAULT_ABORT_GRACE_MS = 2000;
6777
8214
  var DEFAULT_STATUS_POLL_MS = 100;
@@ -6911,6 +8348,9 @@ function errorReason(error) {
6911
8348
  }
6912
8349
  return safeToString(error);
6913
8350
  }
8351
+ function isNormalizedSessionFailure(value) {
8352
+ return isObject3(value) && safeGet(value, "kind") === "normalized-session-failure" && typeof safeGet(value, "failureClass") === "string" && typeof safeGet(value, "retryable") === "boolean";
8353
+ }
6914
8354
  function createArgs(req) {
6915
8355
  const body = {};
6916
8356
  const args = { body };
@@ -6953,9 +8393,9 @@ async function withTimeout(promise, timeoutMs) {
6953
8393
  return { kind: "settled", value: await promise };
6954
8394
  }
6955
8395
  let timeoutID;
6956
- const timeout = new Promise((resolve3) => {
8396
+ const timeout = new Promise((resolve4) => {
6957
8397
  timeoutID = setTimeout(() => {
6958
- resolve3({ kind: "timeout" });
8398
+ resolve4({ kind: "timeout" });
6959
8399
  }, timeoutMs);
6960
8400
  });
6961
8401
  try {
@@ -6975,8 +8415,8 @@ async function beforeDeadline(request, deadline, deps) {
6975
8415
  const controller = new AbortController;
6976
8416
  let timeoutID;
6977
8417
  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);
8418
+ const timeout = new Promise((resolve4) => {
8419
+ timeoutID = setTimeout(() => resolve4({ kind: "timeout" }), remainingMs);
6980
8420
  });
6981
8421
  try {
6982
8422
  const outcome = await Promise.race([observedRequest, timeout]);
@@ -7110,13 +8550,15 @@ async function runSubsession(client, req, deps) {
7110
8550
  try {
7111
8551
  const created = await client.session.create(createArgs(req));
7112
8552
  if (created.error !== undefined) {
8553
+ const normalizedFailure = isNormalizedSessionFailure(created.error) ? created.error : undefined;
7113
8554
  return {
7114
8555
  success: false,
7115
8556
  roleID: req.roleID,
7116
8557
  model: req.model,
7117
8558
  failureStage: "create",
7118
- failureReason: errorReason(created.error),
7119
- retrySafe: true
8559
+ failureReason: normalizedFailure === undefined ? errorReason(created.error) : normalizedSessionFailureMessage(normalizedFailure),
8560
+ retrySafe: true,
8561
+ ...normalizedFailure === undefined ? {} : { normalizedFailure }
7120
8562
  };
7121
8563
  }
7122
8564
  sessionID = created.data?.id;
@@ -7130,6 +8572,9 @@ async function runSubsession(client, req, deps) {
7130
8572
  retrySafe: true
7131
8573
  };
7132
8574
  }
8575
+ try {
8576
+ await deps.onSessionCreated?.(sessionID);
8577
+ } catch {}
7133
8578
  } catch (error) {
7134
8579
  return {
7135
8580
  success: false,
@@ -7168,14 +8613,16 @@ async function runSubsession(client, req, deps) {
7168
8613
  }
7169
8614
  const prompted = outcome.value;
7170
8615
  if (prompted.error !== undefined) {
8616
+ const normalizedFailure = isNormalizedSessionFailure(prompted.error) ? prompted.error : undefined;
7171
8617
  return {
7172
8618
  success: false,
7173
8619
  sessionID,
7174
8620
  roleID: req.roleID,
7175
8621
  model: req.model,
7176
8622
  failureStage: "prompt",
7177
- failureReason: errorReason(prompted.error),
7178
- retrySafe: true
8623
+ failureReason: normalizedFailure === undefined ? errorReason(prompted.error) : normalizedSessionFailureMessage(normalizedFailure),
8624
+ retrySafe: true,
8625
+ ...normalizedFailure === undefined ? {} : { normalizedFailure }
7179
8626
  };
7180
8627
  }
7181
8628
  return {
@@ -7797,7 +9244,7 @@ function planWorktreeReconciliation(input) {
7797
9244
  }
7798
9245
 
7799
9246
  // src/telemetry/otelConfig.ts
7800
- import { z as z18 } from "zod";
9247
+ import { z as z19 } from "zod";
7801
9248
 
7802
9249
  // src/telemetry/fanout.ts
7803
9250
  function createFanOutSink(deps) {
@@ -7875,10 +9322,10 @@ function createOtelSink(deps) {
7875
9322
  }
7876
9323
 
7877
9324
  // 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()
9325
+ var OtelBackendConfigSchema = z19.object({
9326
+ backend: z19.literal("opentelemetry"),
9327
+ connectionEnv: z19.string().min(1),
9328
+ serviceName: z19.string().min(1).optional()
7882
9329
  }).strict();
7883
9330
  function parseConnectionString(raw) {
7884
9331
  const pairs = new Map;
@@ -11165,36 +12612,36 @@ function formatResponse(response) {
11165
12612
  }
11166
12613
 
11167
12614
  // src/plugin/reportRunSchema.ts
11168
- import { z as z20 } from "zod";
12615
+ import { z as z21 } from "zod";
11169
12616
 
11170
12617
  // src/plugin/graphShadowIngress.ts
11171
- import { z as z19 } from "zod";
12618
+ import { z as z20 } from "zod";
11172
12619
  var LEGACY_EXECUTION_TRACE_VERSION = 1;
11173
- var LegacyExecutionStatusSchema = z19.enum([
12620
+ var LegacyExecutionStatusSchema = z20.enum([
11174
12621
  "completed",
11175
12622
  "failed",
11176
12623
  "cancelled"
11177
12624
  ]);
11178
- var LegacyReviewOutcomeSchema = z19.enum([
12625
+ var LegacyReviewOutcomeSchema = z20.enum([
11179
12626
  "approved",
11180
12627
  "rejected",
11181
12628
  "inconclusive"
11182
12629
  ]);
11183
- var LegacyTraceNodeV1Schema = z19.object({
12630
+ var LegacyTraceNodeV1Schema = z20.object({
11184
12631
  id: NodeIDSchema,
11185
12632
  role: NodeRoleSchema,
11186
- model: z19.string().min(1),
11187
- sessionRef: z19.string().min(1).optional(),
12633
+ model: z20.string().min(1),
12634
+ sessionRef: z20.string().min(1).optional(),
11188
12635
  errorClass: ErrorClassSchema.optional()
11189
12636
  }).strict();
11190
- var LegacyExecutionTraceV1Schema = z19.object({
11191
- version: z19.literal(LEGACY_EXECUTION_TRACE_VERSION),
12637
+ var LegacyExecutionTraceV1Schema = z20.object({
12638
+ version: z20.literal(LEGACY_EXECUTION_TRACE_VERSION),
11192
12639
  runID: NodeIDSchema,
11193
12640
  status: LegacyExecutionStatusSchema,
11194
12641
  outcome: LegacyReviewOutcomeSchema,
11195
- fixes: z19.number().int().nonnegative(),
11196
- nodes: z19.array(LegacyTraceNodeV1Schema).min(1),
11197
- parentSessionRef: z19.string().min(1).optional()
12642
+ fixes: z20.number().int().nonnegative(),
12643
+ nodes: z20.array(LegacyTraceNodeV1Schema).min(1),
12644
+ parentSessionRef: z20.string().min(1).optional()
11198
12645
  }).strict();
11199
12646
  function gateReason2(gate) {
11200
12647
  if (gate === undefined || gate.mode === "off") {
@@ -11265,20 +12712,20 @@ function parseLegacyExecutionTrace(input) {
11265
12712
  }
11266
12713
 
11267
12714
  // src/plugin/reportRunSchema.ts
11268
- var ReportRunNodeSchema = z20.object({
12715
+ var ReportRunNodeSchema = z21.object({
11269
12716
  id: NodeIDSchema,
11270
12717
  role: NodeRoleSchema,
11271
- model: z20.string().min(1),
11272
- ok: z20.boolean(),
11273
- sessionRef: z20.string().min(1).optional(),
12718
+ model: z21.string().min(1),
12719
+ ok: z21.boolean(),
12720
+ sessionRef: z21.string().min(1).optional(),
11274
12721
  errorClass: ErrorClassSchema.optional()
11275
12722
  }).strict();
11276
- var ReportRunPayloadSchema = z20.object({
12723
+ var ReportRunPayloadSchema = z21.object({
11277
12724
  runID: NodeIDSchema,
11278
12725
  status: LegacyExecutionStatusSchema,
11279
- fixes: z20.number().int().nonnegative(),
11280
- nodes: z20.array(ReportRunNodeSchema).min(1),
11281
- parentSessionRef: z20.string().min(1).optional()
12726
+ fixes: z21.number().int().nonnegative(),
12727
+ nodes: z21.array(ReportRunNodeSchema).min(1),
12728
+ parentSessionRef: z21.string().min(1).optional()
11282
12729
  }).strict();
11283
12730
 
11284
12731
  // src/orchestrator/graphShadow.ts
@@ -12579,62 +14026,62 @@ ${lines.join(`
12579
14026
  }
12580
14027
 
12581
14028
  // src/memory/types.ts
12582
- import { z as z21 } from "zod";
14029
+ import { z as z22 } from "zod";
12583
14030
  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),
14031
+ var OwnerKeySchema = z22.string().min(1);
14032
+ var MemoryKindSchema = z22.enum(["fact", "preference", "entity"]);
14033
+ var MemoryBaseSchema = z22.object({
14034
+ id: z22.string().min(1),
12588
14035
  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)
14036
+ confidence: z22.number().min(0).max(1),
14037
+ validFrom: z22.number().finite(),
14038
+ validUntil: z22.number().finite().nullable().default(null),
14039
+ createdAt: z22.number().finite(),
14040
+ invalidatedAt: z22.number().finite().nullable().default(null),
14041
+ sourceHash: z22.string().min(1),
14042
+ supersededBy: z22.string().min(1).nullable().default(null)
12596
14043
  }).strict();
12597
14044
  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)
14045
+ kind: z22.literal("fact"),
14046
+ subject: z22.string().min(1),
14047
+ predicate: z22.string().min(1),
14048
+ object: z22.string().min(1),
14049
+ category: z22.string().min(1).nullable().default(null)
12603
14050
  });
12604
14051
  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)
14052
+ kind: z22.literal("preference"),
14053
+ category: z22.string().min(1),
14054
+ preference: z22.string().min(1),
14055
+ context: z22.string().min(1).nullable().default(null),
14056
+ lastAccessedAt: z22.number().finite().nullable().default(null),
14057
+ accessCount: z22.number().int().min(0).default(0)
12611
14058
  });
12612
14059
  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([])
14060
+ kind: z22.literal("entity"),
14061
+ canonicalName: z22.string().min(1),
14062
+ type: z22.string().min(1),
14063
+ aliases: z22.array(z22.string().min(1)).default([])
12617
14064
  });
12618
14065
  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)
14066
+ kind: z22.literal("relation"),
14067
+ from: z22.string().min(1),
14068
+ to: z22.string().min(1),
14069
+ predicate: z22.string().min(1),
14070
+ annotation: z22.string().min(1).nullable().default(null)
12624
14071
  });
12625
- var MemoryRecordSchema = z21.discriminatedUnion("kind", [
14072
+ var MemoryRecordSchema = z22.discriminatedUnion("kind", [
12626
14073
  FactSchema,
12627
14074
  PreferenceSchema,
12628
14075
  EntitySchema,
12629
14076
  RelationSchema
12630
14077
  ]);
12631
- var RecallQuerySchema = z21.object({
14078
+ var RecallQuerySchema = z22.object({
12632
14079
  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)
14080
+ kinds: z22.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
14081
+ asOf: z22.number().finite().nullable().default(null),
14082
+ limit: z22.number().int().positive().default(8),
14083
+ minSimilarity: z22.number().min(0).max(1).default(0.2),
14084
+ includeShared: z22.boolean().default(true)
12638
14085
  }).strict();
12639
14086
 
12640
14087
  // src/memory/rank.ts
@@ -13205,42 +14652,42 @@ function buildMemoryInjector(deps, policy) {
13205
14652
 
13206
14653
  // src/plugin/memoryTool.ts
13207
14654
  import { tool as tool3 } from "@opencode-ai/plugin";
13208
- import { z as z23 } from "zod";
14655
+ import { z as z24 } from "zod";
13209
14656
 
13210
14657
  // src/memory/extract.ts
13211
- import { z as z22 } from "zod";
14658
+ import { z as z23 } from "zod";
13212
14659
  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()
14660
+ var RawFactSchema = z23.object({
14661
+ subject: z23.string().min(1),
14662
+ predicate: z23.string().min(1),
14663
+ object: z23.string().min(1),
14664
+ category: z23.string().min(1).nullable().optional(),
14665
+ confidence: z23.number().min(0).max(1).optional()
13219
14666
  });
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()
14667
+ var RawPreferenceSchema = z23.object({
14668
+ category: z23.string().min(1),
14669
+ preference: z23.string().min(1),
14670
+ context: z23.string().min(1).nullable().optional(),
14671
+ confidence: z23.number().min(0).max(1).optional()
13225
14672
  });
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()
14673
+ var RawEntitySchema = z23.object({
14674
+ canonicalName: z23.string().min(1),
14675
+ type: z23.string().min(1),
14676
+ aliases: z23.array(z23.string().min(1)).optional(),
14677
+ confidence: z23.number().min(0).max(1).optional()
13231
14678
  });
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()
14679
+ var RawRelationSchema = z23.object({
14680
+ from: z23.string().min(1),
14681
+ to: z23.string().min(1),
14682
+ predicate: z23.string().min(1),
14683
+ annotation: z23.string().min(1).nullable().optional(),
14684
+ confidence: z23.number().min(0).max(1).optional()
13238
14685
  }).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([])
14686
+ var RawExtractionSchema = z23.object({
14687
+ facts: z23.array(RawFactSchema).default([]),
14688
+ preferences: z23.array(RawPreferenceSchema).default([]),
14689
+ entities: z23.array(RawEntitySchema).default([]),
14690
+ relations: z23.array(RawRelationSchema).default([])
13244
14691
  });
13245
14692
  var DEFAULT_CONFIDENCE = 0.6;
13246
14693
  var SYSTEM_PROMPT = [
@@ -13494,13 +14941,13 @@ var memoryToolMessages = {
13494
14941
 
13495
14942
  // src/plugin/memoryTool.ts
13496
14943
  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)
14944
+ var MemoryMessageSchema = z24.object({
14945
+ role: z24.enum(["system", "user", "assistant"]).describe(memoryToolMessages.messageRoleDescription),
14946
+ content: z24.string().min(1).describe(memoryToolMessages.messageContentDescription)
13500
14947
  }).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)
14948
+ var MemoryWritePayloadSchema = z24.object({
14949
+ messages: z24.array(MemoryMessageSchema).min(1).describe(memoryToolMessages.messagesDescription),
14950
+ roleID: z24.string().min(1).default(SCRIBE_ROLE_ID).describe(memoryToolMessages.roleIDDescription)
13504
14951
  }).strict();
13505
14952
  function createMemoryTool(deps) {
13506
14953
  return tool3({
@@ -13625,10 +15072,10 @@ function createOrchestratedModelOwnership() {
13625
15072
 
13626
15073
  // src/plugin/orchestrateTool.ts
13627
15074
  import { tool as tool4 } from "@opencode-ai/plugin";
13628
- import { z as z25 } from "zod";
15075
+ import { z as z26 } from "zod";
13629
15076
 
13630
15077
  // src/orchestrator/coordinator.ts
13631
- import { z as z24 } from "zod";
15078
+ import { z as z25 } from "zod";
13632
15079
 
13633
15080
  // src/orchestrator/nodeIteration.ts
13634
15081
  var DEFAULT_MAX_RETRIES = 2;
@@ -14091,9 +15538,12 @@ function buildRecord(input, decision2, subsession, deps, decisionID, batchID) {
14091
15538
  if (subsession.failureStage !== undefined) {
14092
15539
  record.failureStage = subsession.failureStage;
14093
15540
  }
15541
+ if (subsession.normalizedFailure !== undefined) {
15542
+ record.normalizedFailure = subsession.normalizedFailure;
15543
+ }
14094
15544
  return record;
14095
15545
  }
14096
- var TelemetryFailureClassSchema = z24.enum([
15546
+ var TelemetryFailureClassSchema = z25.enum([
14097
15547
  "create-rejected",
14098
15548
  "create-no-id",
14099
15549
  "prompt-rejected",
@@ -14241,7 +15691,9 @@ function correlatedCostRecordToRouteEvent(record) {
14241
15691
  if (record.taskID !== undefined) {
14242
15692
  event.taskID = record.taskID;
14243
15693
  }
14244
- if (record.failureReason !== undefined) {
15694
+ if (record.normalizedFailure !== undefined) {
15695
+ event.failureReason = record.normalizedFailure.failureClass;
15696
+ } else if (record.failureReason !== undefined) {
14245
15697
  event.failureReason = classifyFailureForTelemetry(record.failureReason, record.failureStage);
14246
15698
  }
14247
15699
  if (record.failureStage !== undefined) {
@@ -14269,6 +15721,48 @@ async function safeEmit(sink, event) {
14269
15721
  await sink.emit(event);
14270
15722
  } catch {}
14271
15723
  }
15724
+ async function recordLifecycle(lifecycle, event) {
15725
+ if (lifecycle === undefined) {
15726
+ return;
15727
+ }
15728
+ await lifecycle.recorder.append(event);
15729
+ }
15730
+ function lifecycleFailureFor(subsession) {
15731
+ const normalized = subsession.normalizedFailure;
15732
+ if (normalized !== undefined) {
15733
+ return {
15734
+ class: normalized.failureClass,
15735
+ retryable: normalized.retryable,
15736
+ ...normalized.statusCode === undefined ? {} : { statusCode: normalized.statusCode }
15737
+ };
15738
+ }
15739
+ if (subsession.failureStage === "report") {
15740
+ return {
15741
+ class: subsession.failureReason === REPORT_NOT_DURABLE_REASON ? "report-not-durable" : "report-incomplete",
15742
+ retryable: true
15743
+ };
15744
+ }
15745
+ if (subsession.retrySafe === false) {
15746
+ return { class: "prompt-stop-unconfirmed", retryable: false };
15747
+ }
15748
+ if (subsession.failureReason === undefined) {
15749
+ return;
15750
+ }
15751
+ return {
15752
+ class: classifyFailureForTelemetry(subsession.failureReason, subsession.failureStage),
15753
+ retryable: failedPassDisposition(subsession) !== "terminal"
15754
+ };
15755
+ }
15756
+ function lifecycleOutcomeFor(subsession) {
15757
+ if (subsession.success) {
15758
+ return "succeeded";
15759
+ }
15760
+ const failureClass = lifecycleFailureFor(subsession)?.class;
15761
+ if (failureClass === "cancelled" || failureClass === "interrupted" || failureClass === "unknown") {
15762
+ return failureClass;
15763
+ }
15764
+ return "failed";
15765
+ }
14272
15766
  function buildQueueEvent(observation, sessionID, ts, correlation) {
14273
15767
  const event = {
14274
15768
  v: EVENT_SCHEMA_VERSION,
@@ -14303,7 +15797,7 @@ function buildNodeBudgetExhaustedEvent(roleID, passCount, sessionID, ts) {
14303
15797
  passCount
14304
15798
  };
14305
15799
  }
14306
- async function runRoleTaskWithCorrelation(input, deps, correlation, batch, routed, handoff, reportBatchID) {
15800
+ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, routed, handoff, reportBatchID, attemptLifecycle) {
14307
15801
  const role = roleFor(input);
14308
15802
  if (routed.routeKind === "blocked") {
14309
15803
  throw new Error(coordinatorRoutingBlocked(input.roleID, routed.blockReason));
@@ -14343,9 +15837,48 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
14343
15837
  statusPollMs: deps.statusPollMs,
14344
15838
  now: deps.now,
14345
15839
  delay: deps.delay,
14346
- ...deps.timeoutMs === undefined ? {} : { timeoutMs: deps.timeoutMs }
15840
+ ...deps.timeoutMs === undefined ? {} : { timeoutMs: deps.timeoutMs },
15841
+ ...attemptLifecycle === undefined ? {} : {
15842
+ onSessionCreated: (sessionID) => recordLifecycle(attemptLifecycle.lifecycle, {
15843
+ type: "session.created",
15844
+ operationID: attemptLifecycle.operationID,
15845
+ taskID: attemptLifecycle.taskID,
15846
+ attemptID: attemptLifecycle.attemptID,
15847
+ attempt: attemptLifecycle.attempt,
15848
+ modelIndex: attemptLifecycle.modelIndex,
15849
+ retryIndex: attemptLifecycle.retryIndex,
15850
+ sessionID,
15851
+ ...input.parentSessionID === undefined ? {} : { parentSessionID: input.parentSessionID }
15852
+ })
15853
+ }
14347
15854
  };
14348
15855
  const runtimeId = selection.runtimeId;
15856
+ if (attemptLifecycle !== undefined) {
15857
+ await recordLifecycle(attemptLifecycle.lifecycle, {
15858
+ type: "attempt.model-selected",
15859
+ operationID: attemptLifecycle.operationID,
15860
+ taskID: attemptLifecycle.taskID,
15861
+ attemptID: attemptLifecycle.attemptID,
15862
+ attempt: attemptLifecycle.attempt,
15863
+ modelIndex: attemptLifecycle.modelIndex,
15864
+ retryIndex: attemptLifecycle.retryIndex,
15865
+ decisionID: correlation.decisionID,
15866
+ model: decision2.selected,
15867
+ routeKind: decision2.routeKind === "local" ? "local" : "frontier",
15868
+ selectionCause: attemptLifecycle.selectionCause,
15869
+ ...runtimeId === undefined ? {} : { runtimeID: runtimeId }
15870
+ });
15871
+ await recordLifecycle(attemptLifecycle.lifecycle, {
15872
+ type: "attempt.queued",
15873
+ operationID: attemptLifecycle.operationID,
15874
+ taskID: attemptLifecycle.taskID,
15875
+ attemptID: attemptLifecycle.attemptID,
15876
+ attempt: attemptLifecycle.attempt,
15877
+ modelIndex: attemptLifecycle.modelIndex,
15878
+ retryIndex: attemptLifecycle.retryIndex,
15879
+ ...runtimeId === undefined ? {} : { runtimeID: runtimeId }
15880
+ });
15881
+ }
14349
15882
  let release;
14350
15883
  let queueObservation;
14351
15884
  if (runtimeId !== undefined && deps.runtimeLimiter !== undefined) {
@@ -14368,6 +15901,17 @@ async function runRoleTaskWithCorrelation(input, deps, correlation, batch, route
14368
15901
  };
14369
15902
  }
14370
15903
  try {
15904
+ if (attemptLifecycle !== undefined) {
15905
+ await recordLifecycle(attemptLifecycle.lifecycle, {
15906
+ type: "attempt.started",
15907
+ operationID: attemptLifecycle.operationID,
15908
+ taskID: attemptLifecycle.taskID,
15909
+ attemptID: attemptLifecycle.attemptID,
15910
+ attempt: attemptLifecycle.attempt,
15911
+ modelIndex: attemptLifecycle.modelIndex,
15912
+ retryIndex: attemptLifecycle.retryIndex
15913
+ });
15914
+ }
14371
15915
  const subsession = await runSubsession(deps.client, request, subsessionDeps);
14372
15916
  const record = buildRecord(input, decision2, subsession, deps, correlation.decisionID, correlation.batchID);
14373
15917
  await safeEmit(deps.sink, correlatedCostRecordToRouteEvent(record));
@@ -14515,6 +16059,9 @@ function failedPassDisposition(subsession) {
14515
16059
  return "terminal";
14516
16060
  if (subsession.failureStage === "report")
14517
16061
  return "retry-incomplete";
16062
+ if (subsession.normalizedFailure !== undefined) {
16063
+ return subsession.normalizedFailure.retryable ? "retry-transport" : "terminal";
16064
+ }
14518
16065
  const failureReason = subsession.failureReason ?? "";
14519
16066
  const causeClass = classifyFailureForTelemetry(failureReason, undefined);
14520
16067
  if (causeClass === "prompt-auth" || causeClass === "prompt-content-filter" || causeClass === "prompt-validation") {
@@ -14529,7 +16076,59 @@ function failedPassDisposition(subsession) {
14529
16076
  }
14530
16077
  return "terminal";
14531
16078
  }
14532
- async function runNodeWithIteration(input, deps, correlation, batch) {
16079
+ function lifecycleSelectionCause(attempt, modelIndex, retryReason, escalationReason) {
16080
+ if (attempt === 0) {
16081
+ return "initial";
16082
+ }
16083
+ if (modelIndex > 0) {
16084
+ return escalationReason === "transport" ? "fallback-transport" : "fallback-incomplete";
16085
+ }
16086
+ return retryReason === "transport" ? "retry-transport" : "retry-incomplete";
16087
+ }
16088
+ function lifecycleContinuation(disposition, pass, passBudget, modelIndex, candidateCount) {
16089
+ if (disposition === "terminal") {
16090
+ return "none";
16091
+ }
16092
+ if (pass < passBudget) {
16093
+ return "retry-same-model";
16094
+ }
16095
+ return modelIndex + 1 < candidateCount ? "fallback-next-model" : "none";
16096
+ }
16097
+ async function recordAttemptTerminal(attempt, result, continuation, succeeded) {
16098
+ if (attempt === undefined) {
16099
+ return;
16100
+ }
16101
+ const failure = succeeded ? undefined : lifecycleFailureFor(result.subsession) ?? {
16102
+ class: "report-incomplete",
16103
+ retryable: true
16104
+ };
16105
+ await recordLifecycle(attempt.lifecycle, {
16106
+ type: "attempt.terminal",
16107
+ operationID: attempt.operationID,
16108
+ taskID: attempt.taskID,
16109
+ attemptID: attempt.attemptID,
16110
+ attempt: attempt.attempt,
16111
+ modelIndex: attempt.modelIndex,
16112
+ retryIndex: attempt.retryIndex,
16113
+ outcome: succeeded ? "succeeded" : result.subsession.success ? "failed" : lifecycleOutcomeFor(result.subsession),
16114
+ continuation,
16115
+ ...result.subsession.sessionID === undefined ? {} : { sessionID: result.subsession.sessionID },
16116
+ ...failure === undefined ? {} : { failure }
16117
+ });
16118
+ }
16119
+ async function recordOperationTerminal(lifecycle, input, batchID, result, attemptCount) {
16120
+ const failure = lifecycleFailureFor(result.subsession);
16121
+ await recordLifecycle(lifecycle, {
16122
+ type: "operation.terminal",
16123
+ operationID: operationID(batchID, input.roleID),
16124
+ taskID: input.taskID ?? batchID,
16125
+ outcome: lifecycleOutcomeFor(result.subsession),
16126
+ attemptCount,
16127
+ ...result.subsession.sessionID === undefined ? {} : { finalSessionID: result.subsession.sessionID },
16128
+ ...failure === undefined ? {} : { failure }
16129
+ });
16130
+ }
16131
+ async function runNodeWithIteration(input, deps, correlation, batch, lifecycle) {
14533
16132
  if (getRoleProfile(input.roleID, input.config.orchestrator?.roles) === undefined) {
14534
16133
  warnUnknownRole(input.roleID, deps.warn, deps.warnedRoles);
14535
16134
  }
@@ -14539,9 +16138,20 @@ async function runNodeWithIteration(input, deps, correlation, batch) {
14539
16138
  usd: 0,
14540
16139
  frontierTokens: 0
14541
16140
  });
16141
+ const lifecycleBatchID = correlation.batchID ?? lifecycle?.recorder.runID;
14542
16142
  const executionPolicy = roleCapablePolicy(input, role);
14543
16143
  const plan2 = chooseModel(routingInput(input, role, executionPolicy, initialBudget));
14544
16144
  if (plan2.routeKind === "blocked") {
16145
+ if (lifecycleBatchID !== undefined) {
16146
+ await recordLifecycle(lifecycle, {
16147
+ type: "operation.terminal",
16148
+ operationID: operationID(lifecycleBatchID, input.roleID),
16149
+ taskID: input.taskID ?? lifecycleBatchID,
16150
+ outcome: "blocked",
16151
+ attemptCount: 0,
16152
+ failure: { class: "routing-blocked", retryable: false }
16153
+ });
16154
+ }
14545
16155
  throw new Error(coordinatorRoutingBlocked(input.roleID, plan2.blockReason));
14546
16156
  }
14547
16157
  const candidates = uniqueModels([plan2.selected, ...plan2.fallbackChain]);
@@ -14566,7 +16176,29 @@ async function runNodeWithIteration(input, deps, correlation, batch) {
14566
16176
  continue modelLoop;
14567
16177
  }
14568
16178
  if (lastResult !== undefined) {
14569
- return withFailure(lastResult, routed.blockReason ?? REPORT_INCOMPLETE_REASON, "report");
16179
+ const blocked = withFailure(lastResult, routed.blockReason ?? REPORT_INCOMPLETE_REASON, "report");
16180
+ if (lifecycleBatchID !== undefined) {
16181
+ await recordLifecycle(lifecycle, {
16182
+ type: "operation.terminal",
16183
+ operationID: operationID(lifecycleBatchID, input.roleID),
16184
+ taskID: input.taskID ?? lifecycleBatchID,
16185
+ outcome: "failed",
16186
+ attemptCount: totalPasses,
16187
+ ...blocked.subsession.sessionID === undefined ? {} : { finalSessionID: blocked.subsession.sessionID },
16188
+ failure: { class: "budget-exhausted", retryable: false }
16189
+ });
16190
+ }
16191
+ return blocked;
16192
+ }
16193
+ if (lifecycleBatchID !== undefined) {
16194
+ await recordLifecycle(lifecycle, {
16195
+ type: "operation.terminal",
16196
+ operationID: operationID(lifecycleBatchID, input.roleID),
16197
+ taskID: input.taskID ?? lifecycleBatchID,
16198
+ outcome: "blocked",
16199
+ attemptCount: 0,
16200
+ failure: { class: "routing-blocked", retryable: false }
16201
+ });
14570
16202
  }
14571
16203
  throw new Error(coordinatorRoutingBlocked(input.roleID, routed.blockReason));
14572
16204
  }
@@ -14589,7 +16221,17 @@ async function runNodeWithIteration(input, deps, correlation, batch) {
14589
16221
  }).reportPath;
14590
16222
  const resolvedReportPath = resolveReportProbePath(input.directory, reportPath);
14591
16223
  const before = await deps.readReportArtifact(resolvedReportPath);
14592
- let result = await runRoleTaskWithCorrelation(input, deps, attemptCorrelation, batch, attemptDecision, modelIndex > 0, reportBatchID);
16224
+ const attemptLifecycle = lifecycle === undefined || lifecycleBatchID === undefined ? undefined : {
16225
+ lifecycle,
16226
+ operationID: operationID(lifecycleBatchID, input.roleID),
16227
+ taskID: input.taskID ?? lifecycleBatchID,
16228
+ attemptID: attemptID(operationID(lifecycleBatchID, input.roleID), totalPasses),
16229
+ attempt: totalPasses,
16230
+ modelIndex,
16231
+ retryIndex: pass - 1,
16232
+ selectionCause: lifecycleSelectionCause(totalPasses, modelIndex, retryReason, escalationReason)
16233
+ };
16234
+ let result = await runRoleTaskWithCorrelation(input, deps, attemptCorrelation, batch, attemptDecision, modelIndex > 0, reportBatchID, attemptLifecycle);
14593
16235
  totalPasses += 1;
14594
16236
  spend = {
14595
16237
  usd: attemptDecision.estimatedCostUSD === null ? spend.usd : spend.usd + attemptDecision.estimatedCostUSD,
@@ -14599,10 +16241,19 @@ async function runNodeWithIteration(input, deps, correlation, batch) {
14599
16241
  result = assessment.result;
14600
16242
  lastResult = result;
14601
16243
  if (assessment.done) {
16244
+ await recordAttemptTerminal(attemptLifecycle, result, "none", true);
16245
+ if (lifecycleBatchID !== undefined) {
16246
+ await recordOperationTerminal(lifecycle, input, lifecycleBatchID, result, totalPasses);
16247
+ }
14602
16248
  return result;
14603
16249
  }
14604
16250
  const disposition = failedPassDisposition(result.subsession);
16251
+ const continuation = lifecycleContinuation(disposition, pass, passBudget, modelIndex, candidates.length);
16252
+ await recordAttemptTerminal(attemptLifecycle, result, continuation, false);
14605
16253
  if (disposition === "terminal") {
16254
+ if (lifecycleBatchID !== undefined) {
16255
+ await recordOperationTerminal(lifecycle, input, lifecycleBatchID, result, totalPasses);
16256
+ }
14606
16257
  return result;
14607
16258
  }
14608
16259
  retryReason = disposition === "retry-transport" ? "transport" : "incomplete";
@@ -14617,14 +16268,34 @@ async function runNodeWithIteration(input, deps, correlation, batch) {
14617
16268
  }
14618
16269
  const sessionID = recordSessionID(input, lastResult.subsession) ?? correlation.batchID ?? input.roleID;
14619
16270
  await safeEmit(deps.sink, buildNodeBudgetExhaustedEvent(input.roleID, totalPasses, sessionID, deps.now()));
16271
+ if (lifecycleBatchID !== undefined) {
16272
+ await recordOperationTerminal(lifecycle, input, lifecycleBatchID, lastResult, totalPasses);
16273
+ }
14620
16274
  return lastResult;
14621
16275
  }
14622
- async function runRoleTasks(inputs, deps) {
14623
- const batchID = deps.newDecisionID();
16276
+ async function runRoleTasks(inputs, deps, options = {}) {
16277
+ const batchID = options.batchID ?? deps.newDecisionID();
16278
+ const lifecycle = options.lifecycle;
14624
16279
  const planned = inputs.map((input) => ({
14625
16280
  input,
14626
16281
  correlation: { decisionID: deps.newDecisionID(), batchID }
14627
16282
  }));
16283
+ if (lifecycle !== undefined) {
16284
+ for (const input of inputs) {
16285
+ const role = roleFor(input);
16286
+ const agentID = input.agentOverride ?? role.opencodeAgent;
16287
+ await recordLifecycle(lifecycle, {
16288
+ type: "operation.queued",
16289
+ operationID: operationID(batchID, input.roleID),
16290
+ taskID: input.taskID ?? batchID,
16291
+ kind: "role",
16292
+ roleID: input.roleID,
16293
+ ...agentID === undefined ? {} : { agentID },
16294
+ parentOperationID: lifecycle.rootOperationID,
16295
+ dependsOnOperationIDs: (input.dependsOn ?? []).map((roleID) => operationID(batchID, roleID))
16296
+ });
16297
+ }
16298
+ }
14628
16299
  const batch = await resolveBatchReachability(inputs, deps);
14629
16300
  const meeting = buildMeetingEvent(inputs, batchID, planned.map((entry) => entry.correlation.decisionID), deps.now());
14630
16301
  if (meeting !== undefined) {
@@ -14643,6 +16314,14 @@ async function runRoleTasks(inputs, deps) {
14643
16314
  runnable.push(input);
14644
16315
  } else {
14645
16316
  cancelled.push(input.roleID);
16317
+ await recordLifecycle(lifecycle, {
16318
+ type: "operation.terminal",
16319
+ operationID: operationID(batchID, input.roleID),
16320
+ taskID: input.taskID ?? batchID,
16321
+ outcome: "blocked",
16322
+ attemptCount: 0,
16323
+ failure: { class: "dependency-failed", retryable: false }
16324
+ });
14646
16325
  }
14647
16326
  }
14648
16327
  const waveResults = await Promise.all(runnable.map((input) => {
@@ -14650,7 +16329,7 @@ async function runRoleTasks(inputs, deps) {
14650
16329
  if (correlation === undefined) {
14651
16330
  throw new Error(`missing correlation for ${input.roleID}`);
14652
16331
  }
14653
- return runNodeWithIteration(input, deps, correlation, batch);
16332
+ return runNodeWithIteration(input, deps, correlation, batch, lifecycle);
14654
16333
  }));
14655
16334
  for (const result of waveResults) {
14656
16335
  allResults.push(result);
@@ -14659,6 +16338,20 @@ async function runRoleTasks(inputs, deps) {
14659
16338
  }
14660
16339
  }
14661
16340
  }
16341
+ const firstFailure = allResults.find((result) => !result.subsession.success);
16342
+ const succeededRun = cancelled.length === 0 && allResults.length === inputs.length && firstFailure === undefined;
16343
+ const runOutcome = succeededRun ? "succeeded" : firstFailure === undefined ? "failed" : lifecycleOutcomeFor(firstFailure.subsession);
16344
+ await recordLifecycle(lifecycle, {
16345
+ type: "run.terminal",
16346
+ outcome: runOutcome,
16347
+ operationCount: inputs.length,
16348
+ ...succeededRun ? {} : {
16349
+ failure: firstFailure === undefined ? { class: "dependency-failed", retryable: false } : lifecycleFailureFor(firstFailure.subsession) ?? {
16350
+ class: "unknown",
16351
+ retryable: false
16352
+ }
16353
+ }
16354
+ });
14662
16355
  return { batchID, results: allResults, cancelled };
14663
16356
  }
14664
16357
 
@@ -14666,7 +16359,7 @@ async function runRoleTasks(inputs, deps) {
14666
16359
  var _pluginWarnedRoles = new Set;
14667
16360
  var EMPTY_ROLE_ID_SET = new Set;
14668
16361
  var SAFE_ROLE_ID = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
14669
- var RoleIDSchema = z25.string().min(1).superRefine((roleID, ctx) => {
16362
+ var RoleIDSchema = z26.string().min(1).superRefine((roleID, ctx) => {
14670
16363
  if (!SAFE_ROLE_ID.test(roleID)) {
14671
16364
  ctx.addIssue({
14672
16365
  code: "custom",
@@ -14674,16 +16367,16 @@ var RoleIDSchema = z25.string().min(1).superRefine((roleID, ctx) => {
14674
16367
  });
14675
16368
  }
14676
16369
  });
14677
- var RoleAssignmentSchema = z25.object({
16370
+ var RoleAssignmentSchema = z26.object({
14678
16371
  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)")
16372
+ prompt: z26.string().min(1).describe("Work prompt for this role"),
16373
+ title: z26.string().min(1).optional().describe("Human-readable title"),
16374
+ dependsOn: z26.array(RoleIDSchema).optional().describe("Role IDs this assignment depends on (DAG edges)")
14682
16375
  }).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")
16376
+ var OrchestratePayloadSchema = z26.object({
16377
+ assignments: z26.array(RoleAssignmentSchema).min(1).describe("Role assignments to distribute"),
16378
+ parentSessionID: z26.string().min(1).optional().describe(PARENT_SESSION_DESCRIPTION),
16379
+ directory: z26.string().min(1).optional().describe("Working directory override")
14687
16380
  }).strict();
14688
16381
  function validateAssignmentDependencies(assignments) {
14689
16382
  const ids = new Set;
@@ -14741,17 +16434,118 @@ function validateAssignmentDependencies(assignments) {
14741
16434
  }
14742
16435
  return;
14743
16436
  }
16437
+ function isRecord5(value) {
16438
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16439
+ }
16440
+ function nestedRecord2(value, key) {
16441
+ const nested = value[key];
16442
+ return isRecord5(nested) ? nested : undefined;
16443
+ }
16444
+ function numberField(records, keys) {
16445
+ for (const record of records) {
16446
+ for (const key of keys) {
16447
+ const value = record[key];
16448
+ if (typeof value === "number" && Number.isInteger(value)) {
16449
+ return value;
16450
+ }
16451
+ }
16452
+ }
16453
+ return;
16454
+ }
16455
+ function booleanField(records, keys) {
16456
+ for (const record of records) {
16457
+ for (const key of keys) {
16458
+ const value = record[key];
16459
+ if (typeof value === "boolean") {
16460
+ return value;
16461
+ }
16462
+ }
16463
+ }
16464
+ return;
16465
+ }
16466
+ function boundedErrorTokens(records) {
16467
+ const tokens = [];
16468
+ for (const record of records) {
16469
+ for (const key of ["name", "code", "type", "message"]) {
16470
+ const value = record[key];
16471
+ if (typeof value === "string" && value.length <= 100) {
16472
+ tokens.push(value.toLowerCase());
16473
+ }
16474
+ }
16475
+ }
16476
+ return tokens.join(" ");
16477
+ }
16478
+ function promptFailureClass(statusCode, tokens) {
16479
+ if (statusCode === 401 || statusCode === 403 || /\bauth(?:entication|orization)?\b|unauthorized|forbidden/u.test(tokens)) {
16480
+ return "prompt-auth";
16481
+ }
16482
+ if (/content[-_ ]?filter|moderation|safety/u.test(tokens)) {
16483
+ return "prompt-content-filter";
16484
+ }
16485
+ if (/context[-_ ]?(?:length|window)|token[-_ ]?limit/u.test(tokens)) {
16486
+ return "prompt-context-length";
16487
+ }
16488
+ if (statusCode === 429 || /rate[-_ ]?limit|quota/u.test(tokens)) {
16489
+ return "prompt-rate-limited";
16490
+ }
16491
+ if (statusCode === 408 || /timeout|timedout|deadline/u.test(tokens)) {
16492
+ return "prompt-timeout";
16493
+ }
16494
+ if (/connection|network|socket|econnrefused|econnreset|enotfound/u.test(tokens)) {
16495
+ return "prompt-connection";
16496
+ }
16497
+ if (statusCode === 400 || statusCode === 422 || /validation|invalid[-_ ]?request|bad[-_ ]?request/u.test(tokens)) {
16498
+ return "prompt-validation";
16499
+ }
16500
+ if (statusCode !== undefined && statusCode >= 500 && statusCode <= 599) {
16501
+ return "prompt-server";
16502
+ }
16503
+ return "unknown";
16504
+ }
16505
+ function retryableFailure(failureClass, statusCode, declared) {
16506
+ if (failureClass === "prompt-auth" || failureClass === "prompt-content-filter" || failureClass === "prompt-context-length" || failureClass === "prompt-validation") {
16507
+ return false;
16508
+ }
16509
+ if (declared !== undefined) {
16510
+ return declared;
16511
+ }
16512
+ return failureClass === "prompt-timeout" || failureClass === "prompt-rate-limited" || failureClass === "prompt-connection" || failureClass === "prompt-server" || statusCode !== undefined && statusCode >= 500 && statusCode <= 599;
16513
+ }
16514
+ function normalizeSdkSessionFailure(error, stage) {
16515
+ const root = isRecord5(error) ? error : {};
16516
+ const data = nestedRecord2(root, "data");
16517
+ const nestedError = nestedRecord2(root, "error");
16518
+ const records = [
16519
+ root,
16520
+ ...data === undefined ? [] : [data],
16521
+ ...nestedError === undefined ? [] : [nestedError]
16522
+ ];
16523
+ const statusCode = numberField(records, ["statusCode", "status"]);
16524
+ const declaredRetryable = booleanField(records, ["isRetryable", "retryable"]);
16525
+ const failureClass = stage === "create" ? "create-rejected" : promptFailureClass(statusCode, boundedErrorTokens(records));
16526
+ return {
16527
+ kind: "normalized-session-failure",
16528
+ failureClass,
16529
+ retryable: retryableFailure(failureClass, statusCode, declaredRetryable),
16530
+ ...statusCode === undefined ? {} : { statusCode }
16531
+ };
16532
+ }
16533
+ function nestedAssistantFailure(data) {
16534
+ if (!isRecord5(data)) {
16535
+ return;
16536
+ }
16537
+ return nestedRecord2(data, "info")?.error;
16538
+ }
14744
16539
  function adaptSdkClient(sdk, orchestratedModelOwnership) {
14745
16540
  return {
14746
16541
  session: {
14747
16542
  create: async (args) => {
14748
16543
  const result = await sdk.session.create(args);
14749
16544
  const out = {};
14750
- if (result.data !== undefined) {
14751
- out.data = result.data;
14752
- }
14753
16545
  if (result.error !== undefined) {
14754
- out.error = result.error;
16546
+ out.error = normalizeSdkSessionFailure(result.error, "create");
16547
+ } else if (result.data !== undefined) {
16548
+ out.data = result.data;
14755
16549
  }
14756
16550
  return out;
14757
16551
  },
@@ -14764,11 +16558,15 @@ function adaptSdkClient(sdk, orchestratedModelOwnership) {
14764
16558
  try {
14765
16559
  const result = await sdk.session.prompt(args);
14766
16560
  const out = {};
14767
- if (result.data !== undefined) {
14768
- out.data = result.data;
14769
- }
14770
16561
  if (result.error !== undefined) {
14771
- out.error = result.error;
16562
+ out.error = normalizeSdkSessionFailure(result.error, "prompt");
16563
+ } else {
16564
+ const nestedFailure = nestedAssistantFailure(result.data);
16565
+ if (nestedFailure !== undefined) {
16566
+ out.error = normalizeSdkSessionFailure(nestedFailure, "prompt");
16567
+ } else if (result.data !== undefined) {
16568
+ out.data = result.data;
16569
+ }
14772
16570
  }
14773
16571
  return out;
14774
16572
  } finally {
@@ -14778,22 +16576,20 @@ function adaptSdkClient(sdk, orchestratedModelOwnership) {
14778
16576
  abort: async (args) => {
14779
16577
  const result = await sdk.session.abort(args);
14780
16578
  const out = {};
14781
- if (result.data !== undefined) {
14782
- out.data = result.data;
14783
- }
14784
16579
  if (result.error !== undefined) {
14785
- out.error = result.error;
16580
+ out.error = normalizeSdkSessionFailure(result.error, "prompt");
16581
+ } else if (result.data !== undefined) {
16582
+ out.data = result.data;
14786
16583
  }
14787
16584
  return out;
14788
16585
  },
14789
16586
  status: async (args) => {
14790
16587
  const result = await sdk.session.status(args);
14791
16588
  const out = {};
14792
- if (result.data !== undefined) {
14793
- out.data = result.data;
14794
- }
14795
16589
  if (result.error !== undefined) {
14796
- out.error = result.error;
16590
+ out.error = normalizeSdkSessionFailure(result.error, "prompt");
16591
+ } else if (result.data !== undefined) {
16592
+ out.data = result.data;
14797
16593
  }
14798
16594
  return out;
14799
16595
  }
@@ -15196,18 +16992,25 @@ function createOrchestrateTool(deps) {
15196
16992
  title: tool4.schema.string().optional().describe("Human-readable title"),
15197
16993
  dependsOn: tool4.schema.array(tool4.schema.string()).optional().describe("Role IDs this assignment depends on")
15198
16994
  })).describe("Role assignments to distribute"),
15199
- parentSessionID: tool4.schema.string().optional().describe("Parent session for subsessions"),
16995
+ parentSessionID: tool4.schema.string().optional().describe(PARENT_SESSION_DESCRIPTION),
15200
16996
  directory: tool4.schema.string().optional().describe("Working directory override")
15201
16997
  },
15202
- async execute(args) {
16998
+ async execute(args, context) {
15203
16999
  const parsed = OrchestratePayloadSchema.safeParse(args);
15204
17000
  if (!parsed.success) {
15205
17001
  return `validation error: ${parsed.error.message}`;
15206
17002
  }
17003
+ if (typeof context.sessionID !== "string" || !context.sessionID.trim()) {
17004
+ return HOST_SESSION_REQUIRED;
17005
+ }
17006
+ const parentOverrideIgnored = parsed.data.parentSessionID !== undefined && parsed.data.parentSessionID !== context.sessionID;
17007
+ parsed.data.parentSessionID = context.sessionID;
15207
17008
  const depError = validateAssignmentDependencies(parsed.data.assignments);
15208
17009
  if (depError !== undefined) {
15209
17010
  return dependencyValidationError(depError.code, depError.detail);
15210
17011
  }
17012
+ let activeLifecycleRecorder;
17013
+ let lifecycleStatus;
15211
17014
  try {
15212
17015
  const rosterEntries = await deps.rosterEntries();
15213
17016
  const rosterRoleIDs = rosterEntries.length === 0 ? EMPTY_ROLE_ID_SET : new Set(rosterEntries.map((entry) => entry.roleID));
@@ -15224,6 +17027,27 @@ function createOrchestrateTool(deps) {
15224
17027
  return missingAgentDefinition(agentName, input.roleID);
15225
17028
  }
15226
17029
  }
17030
+ const batchID = deps.newDecisionID();
17031
+ if (deps.lifecycleRecorder !== undefined) {
17032
+ const rootOperationID = operationID(batchID, "openteam");
17033
+ const begun = await deps.lifecycleRecorder.beginRun({
17034
+ runID: batchID,
17035
+ source: "plugin",
17036
+ executionAuthority: "coordinator",
17037
+ root: {
17038
+ operationID: rootOperationID,
17039
+ taskID: batchID,
17040
+ kind: "coordinator",
17041
+ roleID: "openteam",
17042
+ agentID: context.agent,
17043
+ ...parsed.data.parentSessionID === undefined ? {} : { sessionID: parsed.data.parentSessionID }
17044
+ }
17045
+ });
17046
+ lifecycleStatus = begun.status;
17047
+ if (begun.ok) {
17048
+ activeLifecycleRecorder = begun.recorder;
17049
+ }
17050
+ }
15227
17051
  const coordinatorDeps = {
15228
17052
  client: deps.client,
15229
17053
  sink: deps.sink,
@@ -15242,23 +17066,45 @@ function createOrchestrateTool(deps) {
15242
17066
  delay: deps.delay,
15243
17067
  ...deps.timeoutMs === undefined ? {} : { timeoutMs: deps.timeoutMs }
15244
17068
  };
15245
- const batch = await runRoleTasks(inputs, coordinatorDeps);
17069
+ const batch = await runRoleTasks(inputs, coordinatorDeps, {
17070
+ batchID,
17071
+ ...activeLifecycleRecorder === undefined ? {} : {
17072
+ lifecycle: {
17073
+ recorder: activeLifecycleRecorder,
17074
+ rootOperationID: operationID(batchID, "openteam")
17075
+ }
17076
+ }
17077
+ });
17078
+ lifecycleStatus = activeLifecycleRecorder?.status() ?? lifecycleStatus;
15246
17079
  const projected = projectResults(batch.results, batch.cancelled);
15247
17080
  const shadowResult = await runShadowProducer(deps, parsed.data, batch);
15248
- const base = formatResult(projected);
17081
+ const base = formatResult(projected) + (parentOverrideIgnored ? `
17082
+
17083
+ ${PARENT_OVERRIDE_IGNORED}` : "");
15249
17084
  const advisory = singleAssignmentAdvisory(parsed.data.assignments.length, assignableRoleIDs(deps.config).length);
15250
17085
  const withAdvisory = advisory === undefined ? base : `${base}
15251
17086
 
15252
17087
  ${advisory}`;
17088
+ const recordingAdvisory = lifecycleStatus === undefined ? undefined : lifecycleRecordingAdvisory(lifecycleStatus);
17089
+ const withRecordingStatus = recordingAdvisory === undefined ? withAdvisory : `${withAdvisory}
17090
+
17091
+ ⚠ ${recordingAdvisory}`;
15253
17092
  if (!shadowResult.ok) {
15254
- return `${withAdvisory}
17093
+ return `${withRecordingStatus}
15255
17094
  ⚠ shadow observation failed: ${shadowResult.failureClass}`;
15256
17095
  }
15257
- return withAdvisory;
17096
+ return withRecordingStatus;
15258
17097
  } catch (error) {
17098
+ if (activeLifecycleRecorder !== undefined) {
17099
+ lifecycleStatus = await activeLifecycleRecorder.release();
17100
+ }
15259
17101
  const reason = error instanceof Error ? error.message : String(error);
15260
17102
  const result = { kind: "error", reason };
15261
- return formatResult(result);
17103
+ const base = formatResult(result);
17104
+ const advisory = lifecycleStatus === undefined ? undefined : lifecycleRecordingAdvisory(lifecycleStatus);
17105
+ return advisory === undefined ? base : `${base}
17106
+
17107
+ ⚠ ${advisory}`;
15262
17108
  }
15263
17109
  }
15264
17110
  });
@@ -15266,7 +17112,7 @@ ${advisory}`;
15266
17112
 
15267
17113
  // src/plugin/registerCastTool.ts
15268
17114
  import { tool as tool5 } from "@opencode-ai/plugin";
15269
- import { z as z26 } from "zod";
17115
+ import { z as z27 } from "zod";
15270
17116
 
15271
17117
  // src/orchestrator/rosterPersistence.ts
15272
17118
  function rosterFence(roster) {
@@ -15301,13 +17147,13 @@ async function persistRosterPreservingProse(storage, roster, path4 = OPENTEAM_RO
15301
17147
  }
15302
17148
 
15303
17149
  // 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")
17150
+ var CastEntrySchema = z27.object({
17151
+ roleID: z27.string().min(1).describe("Stable role identifier, e.g. scribe or guardian"),
17152
+ agentName: z27.string().min(1).describe("Themed character name assigned to the role")
15307
17153
  }).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")
17154
+ var RegisterCastPayloadSchema = z27.object({
17155
+ universe: z27.string().min(1).describe("Casting universe for this roster"),
17156
+ entries: z27.array(CastEntrySchema).min(1).describe("Role-to-name mappings")
15311
17157
  }).strict();
15312
17158
  function offendingFields(error) {
15313
17159
  const fields = error.issues.flatMap((issue) => {
@@ -15489,12 +17335,12 @@ async function createBunSqliteDatabase(path4) {
15489
17335
  }
15490
17336
 
15491
17337
  // src/memory/embedText.ts
15492
- function normalize(parts) {
17338
+ function normalize2(parts) {
15493
17339
  return parts.map((part) => part.trim()).filter((part) => part.length > 0).join(" ");
15494
17340
  }
15495
17341
  function recordEmbeddingText(record) {
15496
17342
  if (record.kind === "fact") {
15497
- return normalize([
17343
+ return normalize2([
15498
17344
  record.subject,
15499
17345
  record.predicate,
15500
17346
  record.object,
@@ -15502,21 +17348,21 @@ function recordEmbeddingText(record) {
15502
17348
  ]);
15503
17349
  }
15504
17350
  if (record.kind === "preference") {
15505
- return normalize([
17351
+ return normalize2([
15506
17352
  record.category,
15507
17353
  record.preference,
15508
17354
  record.context ?? ""
15509
17355
  ]);
15510
17356
  }
15511
17357
  if (record.kind === "relation") {
15512
- return normalize([
17358
+ return normalize2([
15513
17359
  record.from,
15514
17360
  record.predicate,
15515
17361
  record.to,
15516
17362
  record.annotation ?? ""
15517
17363
  ]);
15518
17364
  }
15519
- return normalize([record.canonicalName, record.type, ...record.aliases]);
17365
+ return normalize2([record.canonicalName, record.type, ...record.aliases]);
15520
17366
  }
15521
17367
 
15522
17368
  // src/storage/index/memoryEmbeddings.ts
@@ -15596,6 +17442,799 @@ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
15596
17442
  }
15597
17443
  }
15598
17444
 
17445
+ // src/storage/lifecycle/fsLifecycleJournal.ts
17446
+ import { randomUUID as randomUUID2 } from "node:crypto";
17447
+ import {
17448
+ lstatSync as lstatSync3,
17449
+ mkdirSync as mkdirSync3,
17450
+ readdirSync as readdirSync2,
17451
+ renameSync as renameSync2,
17452
+ rmSync as rmSync3,
17453
+ statSync,
17454
+ unlinkSync as unlinkSync2
17455
+ } from "node:fs";
17456
+ import { join as join12, resolve as resolve7 } from "node:path";
17457
+ import { ZodError } from "zod";
17458
+
17459
+ // src/storage/lifecycle/codec.ts
17460
+ import { z as z28 } from "zod";
17461
+ var LIFECYCLE_CODEC_VERSION = 1;
17462
+ var LIFECYCLE_GENESIS_DIGEST = sha256Hex("openteam/lifecycle-journal/genesis/v1");
17463
+ var NUL5 = "\x00";
17464
+ var NEWLINE2 = `
17465
+ `;
17466
+ var frameSchema = z28.object({
17467
+ v: z28.literal(LIFECYCLE_CODEC_VERSION),
17468
+ seq: z28.number().int().nonnegative(),
17469
+ prev: z28.string().min(1),
17470
+ sum: z28.string().min(1),
17471
+ event: z28.unknown()
17472
+ }).strict();
17473
+ function canonical2(value) {
17474
+ if (Array.isArray(value)) {
17475
+ return `[${value.map(canonical2).join(",")}]`;
17476
+ }
17477
+ if (value === null || typeof value !== "object") {
17478
+ return JSON.stringify(value);
17479
+ }
17480
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key2, child]) => `${JSON.stringify(key2)}:${canonical2(child)}`).join(",")}}`;
17481
+ }
17482
+ function lifecycleFrameDigest(previousDigest, event) {
17483
+ return sha256Hex(`${previousDigest}${NUL5}${canonical2(event)}`);
17484
+ }
17485
+
17486
+ class LifecycleCodecError extends Error {
17487
+ code;
17488
+ constructor(code) {
17489
+ super(code);
17490
+ this.name = "LifecycleCodecError";
17491
+ this.code = code;
17492
+ }
17493
+ }
17494
+ function encodeLifecycleFrame(previousDigest, event) {
17495
+ const digest = lifecycleFrameDigest(previousDigest, event);
17496
+ return {
17497
+ line: canonical2({
17498
+ v: LIFECYCLE_CODEC_VERSION,
17499
+ seq: event.seq,
17500
+ prev: previousDigest,
17501
+ sum: digest,
17502
+ event
17503
+ }),
17504
+ digest
17505
+ };
17506
+ }
17507
+ function encodeLifecycleJournal(events) {
17508
+ let previousDigest = LIFECYCLE_GENESIS_DIGEST;
17509
+ let encoded = "";
17510
+ for (const event of events) {
17511
+ const frame = encodeLifecycleFrame(previousDigest, event);
17512
+ encoded += `${frame.line}${NEWLINE2}`;
17513
+ previousDigest = frame.digest;
17514
+ }
17515
+ return encoded;
17516
+ }
17517
+ function decodeLifecycleJournal(text) {
17518
+ const segments = text.split(NEWLINE2);
17519
+ const trailing = segments.pop() ?? "";
17520
+ const partialTail = trailing.length > 0;
17521
+ const events = [];
17522
+ let previousDigest = LIFECYCLE_GENESIS_DIGEST;
17523
+ for (const [index, raw] of segments.entries()) {
17524
+ let unknownFrame;
17525
+ try {
17526
+ unknownFrame = JSON.parse(raw);
17527
+ } catch {
17528
+ throw new LifecycleCodecError("bad-frame");
17529
+ }
17530
+ const parsedFrame = frameSchema.safeParse(unknownFrame);
17531
+ if (!parsedFrame.success) {
17532
+ const version = typeof unknownFrame === "object" && unknownFrame !== null && "v" in unknownFrame ? unknownFrame.v : undefined;
17533
+ throw new LifecycleCodecError(version !== undefined && version !== LIFECYCLE_CODEC_VERSION ? "bad-version" : "bad-frame");
17534
+ }
17535
+ const frame = parsedFrame.data;
17536
+ let event;
17537
+ try {
17538
+ event = parseLifecycleEvent(frame.event);
17539
+ } catch {
17540
+ throw new LifecycleCodecError("bad-frame");
17541
+ }
17542
+ if (frame.seq !== index || event.seq !== index) {
17543
+ throw new LifecycleCodecError("bad-sequence");
17544
+ }
17545
+ if (frame.prev !== previousDigest) {
17546
+ throw new LifecycleCodecError("chain-break");
17547
+ }
17548
+ if (frame.sum !== lifecycleFrameDigest(previousDigest, event)) {
17549
+ throw new LifecycleCodecError("bad-checksum");
17550
+ }
17551
+ events.push(event);
17552
+ previousDigest = frame.sum;
17553
+ }
17554
+ return { events, digest: previousDigest, partialTail };
17555
+ }
17556
+
17557
+ // src/storage/lifecycle/fsLifecycleSupport.ts
17558
+ import { randomUUID } from "node:crypto";
17559
+ import {
17560
+ appendFileSync,
17561
+ closeSync as closeSync2,
17562
+ existsSync as existsSync2,
17563
+ fsyncSync as fsyncSync2,
17564
+ lstatSync as lstatSync2,
17565
+ mkdirSync as mkdirSync2,
17566
+ openSync as openSync2,
17567
+ readFileSync as readFileSync3,
17568
+ readSync,
17569
+ realpathSync as realpathSync2,
17570
+ renameSync,
17571
+ rmSync as rmSync2,
17572
+ unlinkSync,
17573
+ writeSync as writeSync2
17574
+ } from "node:fs";
17575
+ import { basename as basename2, dirname as dirname4, join as join11, relative as relative2, resolve as resolve6 } from "node:path";
17576
+ var SAFE_RUN_ID = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,126}[A-Za-z0-9])?$/u;
17577
+
17578
+ class LifecycleFsError extends Error {
17579
+ kind;
17580
+ constructor(kind) {
17581
+ super(kind);
17582
+ this.name = "LifecycleFsError";
17583
+ this.kind = kind;
17584
+ }
17585
+ }
17586
+ function isMissingFsError(error) {
17587
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
17588
+ }
17589
+ function isExistsFsError(error) {
17590
+ return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
17591
+ }
17592
+ function validateRunID(runID) {
17593
+ if (!SAFE_RUN_ID.test(runID) || runID === "." || runID === "..") {
17594
+ throw new LifecycleFsError("corrupt");
17595
+ }
17596
+ }
17597
+ function resolvedLifecycleRoot(root) {
17598
+ try {
17599
+ const absolute = resolve6(root);
17600
+ mkdirSync2(absolute, { recursive: true });
17601
+ const rootStat = lstatSync2(absolute);
17602
+ if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) {
17603
+ throw new LifecycleFsError("unavailable");
17604
+ }
17605
+ return realpathSync2.native(absolute);
17606
+ } catch (error) {
17607
+ if (error instanceof LifecycleFsError) {
17608
+ throw error;
17609
+ }
17610
+ throw new LifecycleFsError("unavailable");
17611
+ }
17612
+ }
17613
+ function directRunPath(root, runID) {
17614
+ validateRunID(runID);
17615
+ const path4 = join11(root, runID);
17616
+ if (dirname4(path4) !== root || basename2(path4) !== runID) {
17617
+ throw new LifecycleFsError("corrupt");
17618
+ }
17619
+ return path4;
17620
+ }
17621
+ function assertExistingSafeRun(root, runID) {
17622
+ const path4 = directRunPath(root, runID);
17623
+ let stats;
17624
+ try {
17625
+ stats = lstatSync2(path4);
17626
+ } catch (error) {
17627
+ if (isMissingFsError(error)) {
17628
+ throw new LifecycleFsError("missing");
17629
+ }
17630
+ throw new LifecycleFsError("unavailable");
17631
+ }
17632
+ if (!stats.isDirectory() || stats.isSymbolicLink()) {
17633
+ throw new LifecycleFsError("corrupt");
17634
+ }
17635
+ let real;
17636
+ try {
17637
+ real = realpathSync2.native(path4);
17638
+ } catch {
17639
+ throw new LifecycleFsError("unavailable");
17640
+ }
17641
+ const child = relative2(root, real);
17642
+ if (child === "" || child.startsWith("..") || resolve6(root, child) !== real || dirname4(real) !== root) {
17643
+ throw new LifecycleFsError("corrupt");
17644
+ }
17645
+ return real;
17646
+ }
17647
+ function assertSafeRegularFile(path4) {
17648
+ let stats;
17649
+ try {
17650
+ stats = lstatSync2(path4);
17651
+ } catch (error) {
17652
+ if (isMissingFsError(error)) {
17653
+ throw new LifecycleFsError("missing");
17654
+ }
17655
+ throw new LifecycleFsError("unavailable");
17656
+ }
17657
+ if (!stats.isFile() || stats.isSymbolicLink()) {
17658
+ throw new LifecycleFsError("corrupt");
17659
+ }
17660
+ }
17661
+ function readUtf8(path4) {
17662
+ try {
17663
+ assertSafeRegularFile(path4);
17664
+ return readFileSync3(path4, "utf8");
17665
+ } catch (error) {
17666
+ if (error instanceof LifecycleFsError && error.kind === "missing") {
17667
+ return;
17668
+ }
17669
+ throw error;
17670
+ }
17671
+ }
17672
+ function writeDurable2(path4, content) {
17673
+ const descriptor = openSync2(path4, "w");
17674
+ try {
17675
+ writeSync2(descriptor, content);
17676
+ fsyncSync2(descriptor);
17677
+ } finally {
17678
+ closeSync2(descriptor);
17679
+ }
17680
+ }
17681
+ function appendDurable2(path4, content) {
17682
+ const descriptor = openSync2(path4, "a");
17683
+ try {
17684
+ appendFileSync(descriptor, content, "utf8");
17685
+ fsyncSync2(descriptor);
17686
+ } finally {
17687
+ closeSync2(descriptor);
17688
+ }
17689
+ }
17690
+ function writeDurableAtomically(path4, content) {
17691
+ const temporary = join11(dirname4(path4), `.${basename2(path4)}.${randomUUID()}.next`);
17692
+ try {
17693
+ writeDurable2(temporary, content);
17694
+ renameSync(temporary, path4);
17695
+ } finally {
17696
+ rmSync2(temporary, { force: true });
17697
+ }
17698
+ }
17699
+ function readLockContents(path4) {
17700
+ if (!existsSync2(path4)) {
17701
+ return;
17702
+ }
17703
+ const descriptor = openSync2(path4, "r");
17704
+ try {
17705
+ const buffer = Buffer.alloc(128);
17706
+ const bytes = readSync(descriptor, buffer, 0, buffer.length, 0);
17707
+ return buffer.subarray(0, bytes).toString("utf8");
17708
+ } finally {
17709
+ closeSync2(descriptor);
17710
+ }
17711
+ }
17712
+ function withRunMutationLock(runDir, mutate) {
17713
+ const lockPath = join11(runDir, ".mutation-lock");
17714
+ const lockToken = randomUUID();
17715
+ let descriptor;
17716
+ try {
17717
+ descriptor = openSync2(lockPath, "wx");
17718
+ } catch (error) {
17719
+ if (isExistsFsError(error)) {
17720
+ throw new LifecycleFsError("conflict");
17721
+ }
17722
+ throw new LifecycleFsError("unavailable");
17723
+ }
17724
+ try {
17725
+ writeSync2(descriptor, lockToken);
17726
+ fsyncSync2(descriptor);
17727
+ } finally {
17728
+ closeSync2(descriptor);
17729
+ }
17730
+ try {
17731
+ return mutate();
17732
+ } finally {
17733
+ try {
17734
+ if (readLockContents(lockPath) === lockToken) {
17735
+ unlinkSync(lockPath);
17736
+ }
17737
+ } catch {}
17738
+ }
17739
+ }
17740
+
17741
+ // src/storage/lifecycle/fsLifecycleJournal.ts
17742
+ var EVENTS_FILE = "events.ndjson";
17743
+ var METADATA_FILE = "run.json";
17744
+ var OWNER_FILE2 = "owner";
17745
+ var KNOWN_RUN_FILES = new Set([EVENTS_FILE, METADATA_FILE, OWNER_FILE2]);
17746
+ function failure(code, options = {}) {
17747
+ const { recoverable = false, ...context } = options;
17748
+ return { code, recoverable, ...context };
17749
+ }
17750
+ function ok(value) {
17751
+ return { ok: true, value };
17752
+ }
17753
+ function failed(error) {
17754
+ return { ok: false, error };
17755
+ }
17756
+ function mapFsFailure(error, runID) {
17757
+ const context = runID === undefined ? {} : { runID };
17758
+ if (error instanceof ZodError || error instanceof RedactionError) {
17759
+ return failure("corrupt", context);
17760
+ }
17761
+ if (error instanceof LifecycleFsError) {
17762
+ switch (error.kind) {
17763
+ case "missing":
17764
+ return failure("not-found", context);
17765
+ case "conflict":
17766
+ return failure("writer-conflict", { recoverable: true, ...context });
17767
+ case "corrupt":
17768
+ return failure("corrupt", context);
17769
+ case "unavailable":
17770
+ return failure("unavailable", { recoverable: true, ...context });
17771
+ }
17772
+ }
17773
+ return failure("unavailable", { recoverable: true, ...context });
17774
+ }
17775
+ function parseJson(text, parse2) {
17776
+ try {
17777
+ return parse2(JSON.parse(text));
17778
+ } catch {
17779
+ throw new LifecycleFsError("corrupt");
17780
+ }
17781
+ }
17782
+ function issueWriterToken() {
17783
+ return randomUUID2().replaceAll("-", "");
17784
+ }
17785
+ function createLease(runID, writerToken, input) {
17786
+ return parseLifecycleWriterLease({
17787
+ version: 1,
17788
+ runID,
17789
+ writerToken,
17790
+ processID: input.writer.processID,
17791
+ processInstanceID: input.writer.processInstanceID,
17792
+ acquiredAt: input.now,
17793
+ heartbeatAt: input.now,
17794
+ expiresAt: input.now + input.leaseDurationMs
17795
+ });
17796
+ }
17797
+ function readLease2(runDir) {
17798
+ const text = readUtf8(join12(runDir, OWNER_FILE2));
17799
+ return text === undefined ? undefined : parseJson(text, parseLifecycleWriterLease);
17800
+ }
17801
+ function readRun(runDir, runID) {
17802
+ const metadataPath = join12(runDir, METADATA_FILE);
17803
+ const eventsPath = join12(runDir, EVENTS_FILE);
17804
+ assertSafeRegularFile(metadataPath);
17805
+ assertSafeRegularFile(eventsPath);
17806
+ const metadataText = readUtf8(metadataPath);
17807
+ const eventsText = readUtf8(eventsPath);
17808
+ if (metadataText === undefined || eventsText === undefined) {
17809
+ throw new LifecycleFsError("corrupt");
17810
+ }
17811
+ const metadata = parseJson(metadataText, parseLifecycleRunMetadata);
17812
+ if (metadata.runID !== runID) {
17813
+ throw new LifecycleFsError("corrupt");
17814
+ }
17815
+ let decoded;
17816
+ try {
17817
+ decoded = decodeLifecycleJournal(eventsText);
17818
+ } catch {
17819
+ throw new LifecycleFsError("corrupt");
17820
+ }
17821
+ for (const [index, event] of decoded.events.entries()) {
17822
+ if (event.runID !== runID || event.seq !== index) {
17823
+ throw new LifecycleFsError("corrupt");
17824
+ }
17825
+ }
17826
+ const first = decoded.events[0];
17827
+ 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) {
17828
+ throw new LifecycleFsError("corrupt");
17829
+ }
17830
+ return { metadata, decoded };
17831
+ }
17832
+ function terminalOutcome(events) {
17833
+ const last = events.at(-1);
17834
+ return last?.type === "run.terminal" ? last.outcome : undefined;
17835
+ }
17836
+ function summaryForRun(runDir, runID) {
17837
+ const loaded = readRun(runDir, runID);
17838
+ const lastEvent = loaded.decoded.events.at(-1);
17839
+ const terminal = terminalOutcome(loaded.decoded.events);
17840
+ return {
17841
+ metadata: loaded.metadata,
17842
+ updatedAt: lastEvent?.at ?? loaded.metadata.createdAt,
17843
+ ...terminal === undefined ? {} : { terminalOutcome: terminal },
17844
+ head: loaded.decoded.events.length,
17845
+ integrity: loaded.decoded.partialTail ? "partial-tail" : "clean"
17846
+ };
17847
+ }
17848
+ function runBytes(runDir) {
17849
+ return readdirSync2(runDir).reduce((total, entry) => {
17850
+ const path4 = join12(runDir, entry);
17851
+ const stats = lstatSync3(path4);
17852
+ if (!stats.isFile() || stats.isSymbolicLink()) {
17853
+ throw new LifecycleFsError("corrupt");
17854
+ }
17855
+ return total + stats.size;
17856
+ }, 0);
17857
+ }
17858
+ function validateRetention(policy) {
17859
+ if (!Number.isInteger(policy.maxTerminalRuns) || policy.maxTerminalRuns < 0 || !Number.isFinite(policy.maxAgeMs) || policy.maxAgeMs < 0 || !Number.isFinite(policy.maxBytes) || policy.maxBytes < 0) {
17860
+ throw new LifecycleFsError("corrupt");
17861
+ }
17862
+ }
17863
+ function canDeleteTerminalRun(runDir, runID, now) {
17864
+ const summary = summaryForRun(runDir, runID);
17865
+ if (summary.integrity !== "clean" || summary.terminalOutcome === undefined) {
17866
+ return false;
17867
+ }
17868
+ const entries = readdirSync2(runDir);
17869
+ if (entries.some((entry) => entry !== ".mutation-lock" && !KNOWN_RUN_FILES.has(entry))) {
17870
+ return false;
17871
+ }
17872
+ const lease = readLease2(runDir);
17873
+ if (lease === undefined) {
17874
+ return true;
17875
+ }
17876
+ if (lease.runID !== runID || now < lease.heartbeatAt) {
17877
+ return false;
17878
+ }
17879
+ return now >= lease.expiresAt;
17880
+ }
17881
+ var createFsLifecycleJournal = (options) => {
17882
+ const configuredRoot = resolve7(options.root);
17883
+ const now = options.now ?? Date.now;
17884
+ const mutateRun = options.withMutationLock ?? withRunMutationLock;
17885
+ return {
17886
+ async start(input, leaseInput) {
17887
+ let runID;
17888
+ let stagingPath;
17889
+ try {
17890
+ const root = resolvedLifecycleRoot(configuredRoot);
17891
+ const parsedInput = parseLifecycleRunStartInput(input);
17892
+ const parsedLeaseInput = parseLifecycleWriterLeaseInput(leaseInput);
17893
+ runID = parsedInput.runID;
17894
+ validateRunID(runID);
17895
+ const runPath = directRunPath(root, runID);
17896
+ try {
17897
+ statSync(runPath);
17898
+ return failed(failure("already-exists", { runID }));
17899
+ } catch (error) {
17900
+ if (!isMissingFsError(error)) {
17901
+ throw error;
17902
+ }
17903
+ }
17904
+ stagingPath = join12(root, `.start-${runID}-${randomUUID2()}`);
17905
+ mkdirSync3(stagingPath);
17906
+ const writerToken = issueWriterToken();
17907
+ const eventAt = now();
17908
+ const metadata = parseLifecycleRunMetadata({
17909
+ ...parsedInput,
17910
+ version: 1,
17911
+ createdAt: eventAt
17912
+ });
17913
+ const lease = createLease(runID, writerToken, parsedLeaseInput);
17914
+ const event = parseLifecycleEvent({
17915
+ v: 1,
17916
+ seq: 0,
17917
+ runID,
17918
+ at: eventAt,
17919
+ type: "run.started",
17920
+ source: parsedInput.source,
17921
+ executionAuthority: parsedInput.executionAuthority,
17922
+ root: parsedInput.root
17923
+ });
17924
+ writeDurable2(join12(stagingPath, METADATA_FILE), `${JSON.stringify(metadata)}
17925
+ `);
17926
+ writeDurable2(join12(stagingPath, EVENTS_FILE), encodeLifecycleJournal([event]));
17927
+ writeDurable2(join12(stagingPath, OWNER_FILE2), `${JSON.stringify(lease)}
17928
+ `);
17929
+ try {
17930
+ renameSync2(stagingPath, runPath);
17931
+ } catch (error) {
17932
+ if (typeof error === "object" && error !== null && "code" in error && ["EEXIST", "ENOTEMPTY"].includes(String(error.code))) {
17933
+ return failed(failure("already-exists", { runID }));
17934
+ }
17935
+ throw error;
17936
+ }
17937
+ stagingPath = undefined;
17938
+ return ok({
17939
+ handle: { runID, writerToken },
17940
+ metadata,
17941
+ event,
17942
+ lease,
17943
+ head: 1
17944
+ });
17945
+ } catch (error) {
17946
+ return failed(mapFsFailure(error, runID));
17947
+ } finally {
17948
+ if (stagingPath !== undefined) {
17949
+ rmSync3(stagingPath, { recursive: true, force: true });
17950
+ }
17951
+ }
17952
+ },
17953
+ async takeOver(runID, expected, leaseInput) {
17954
+ try {
17955
+ const root = resolvedLifecycleRoot(configuredRoot);
17956
+ const parsedLeaseInput = parseLifecycleWriterLeaseInput(leaseInput);
17957
+ const runDir = assertExistingSafeRun(root, runID);
17958
+ return mutateRun(runDir, () => {
17959
+ const current = readLease2(runDir);
17960
+ if (expected.kind === "absent") {
17961
+ if (current !== undefined) {
17962
+ return failed(failure("writer-conflict", { recoverable: true, runID }));
17963
+ }
17964
+ } else if (current === undefined || current.writerToken !== expected.writerToken || current.expiresAt !== expected.expiresAt || parsedLeaseInput.now < current.expiresAt) {
17965
+ return failed(failure("writer-conflict", { recoverable: true, runID }));
17966
+ }
17967
+ const writerToken = issueWriterToken();
17968
+ const lease = createLease(runID, writerToken, parsedLeaseInput);
17969
+ writeDurableAtomically(join12(runDir, OWNER_FILE2), `${JSON.stringify(lease)}
17970
+ `);
17971
+ return ok({ runID, writerToken });
17972
+ });
17973
+ } catch (error) {
17974
+ return failed(mapFsFailure(error, runID));
17975
+ }
17976
+ },
17977
+ async append(handle, event, expectedHead) {
17978
+ try {
17979
+ const root = resolvedLifecycleRoot(configuredRoot);
17980
+ const parsedBody = parseLifecycleAppendEvent(event);
17981
+ const runDir = assertExistingSafeRun(root, handle.runID);
17982
+ return mutateRun(runDir, () => {
17983
+ const lease = readLease2(runDir);
17984
+ if (lease === undefined || lease.runID !== handle.runID || lease.writerToken !== handle.writerToken) {
17985
+ return failed(failure("writer-conflict", {
17986
+ recoverable: true,
17987
+ runID: handle.runID
17988
+ }));
17989
+ }
17990
+ const loaded = readRun(runDir, handle.runID);
17991
+ if (loaded.decoded.partialTail) {
17992
+ return failed(failure("corrupt", { runID: handle.runID }));
17993
+ }
17994
+ const actualHead = loaded.decoded.events.length;
17995
+ if (expectedHead < actualHead) {
17996
+ return failed(failure("duplicate-event", {
17997
+ runID: handle.runID,
17998
+ expectedHead,
17999
+ actualHead
18000
+ }));
18001
+ }
18002
+ if (expectedHead !== actualHead) {
18003
+ return failed(failure("sequence-mismatch", {
18004
+ recoverable: true,
18005
+ runID: handle.runID,
18006
+ expectedHead,
18007
+ actualHead
18008
+ }));
18009
+ }
18010
+ const previous = loaded.decoded.events.at(-1);
18011
+ if (previous?.type === "run.terminal") {
18012
+ return failed(failure("duplicate-event", {
18013
+ runID: handle.runID,
18014
+ expectedHead,
18015
+ actualHead
18016
+ }));
18017
+ }
18018
+ const committedEvent = parseLifecycleEvent({
18019
+ v: 1,
18020
+ seq: actualHead,
18021
+ runID: handle.runID,
18022
+ at: now(),
18023
+ ...parsedBody
18024
+ });
18025
+ const frame = encodeLifecycleFrame(loaded.decoded.digest, committedEvent);
18026
+ appendDurable2(join12(runDir, EVENTS_FILE), `${frame.line}
18027
+ `);
18028
+ return ok({ event: committedEvent, head: actualHead + 1 });
18029
+ });
18030
+ } catch (error) {
18031
+ return failed(mapFsFailure(error, handle.runID));
18032
+ }
18033
+ },
18034
+ async heartbeat(handle, leaseInput) {
18035
+ try {
18036
+ const root = resolvedLifecycleRoot(configuredRoot);
18037
+ const parsed = parseLifecycleWriterLeaseInput({
18038
+ writer: { processID: 1, processInstanceID: "heartbeat-validation" },
18039
+ now: leaseInput.now,
18040
+ leaseDurationMs: leaseInput.leaseDurationMs
18041
+ });
18042
+ const runDir = assertExistingSafeRun(root, handle.runID);
18043
+ return mutateRun(runDir, () => {
18044
+ const current = readLease2(runDir);
18045
+ if (current === undefined || current.writerToken !== handle.writerToken || parsed.now < current.heartbeatAt || parsed.now >= current.expiresAt) {
18046
+ return failed(failure("writer-conflict", {
18047
+ recoverable: true,
18048
+ runID: handle.runID
18049
+ }));
18050
+ }
18051
+ const lease = parseLifecycleWriterLease({
18052
+ ...current,
18053
+ heartbeatAt: parsed.now,
18054
+ expiresAt: parsed.now + parsed.leaseDurationMs
18055
+ });
18056
+ writeDurableAtomically(join12(runDir, OWNER_FILE2), `${JSON.stringify(lease)}
18057
+ `);
18058
+ return ok({ lease });
18059
+ });
18060
+ } catch (error) {
18061
+ return failed(mapFsFailure(error, handle.runID));
18062
+ }
18063
+ },
18064
+ async release(handle) {
18065
+ try {
18066
+ const root = resolvedLifecycleRoot(configuredRoot);
18067
+ const runDir = assertExistingSafeRun(root, handle.runID);
18068
+ return mutateRun(runDir, () => {
18069
+ const lease = readLease2(runDir);
18070
+ if (lease === undefined || lease.writerToken !== handle.writerToken) {
18071
+ return failed(failure("writer-conflict", {
18072
+ recoverable: true,
18073
+ runID: handle.runID
18074
+ }));
18075
+ }
18076
+ unlinkSync2(join12(runDir, OWNER_FILE2));
18077
+ return ok(undefined);
18078
+ });
18079
+ } catch (error) {
18080
+ return failed(mapFsFailure(error, handle.runID));
18081
+ }
18082
+ },
18083
+ async load(runID) {
18084
+ try {
18085
+ const root = resolvedLifecycleRoot(configuredRoot);
18086
+ const runDir = assertExistingSafeRun(root, runID);
18087
+ const loaded = readRun(runDir, runID);
18088
+ return ok({
18089
+ metadata: loaded.metadata,
18090
+ events: loaded.decoded.events,
18091
+ head: loaded.decoded.events.length,
18092
+ integrity: loaded.decoded.partialTail ? "partial-tail" : "clean"
18093
+ });
18094
+ } catch (error) {
18095
+ return failed(mapFsFailure(error, runID));
18096
+ }
18097
+ },
18098
+ async list() {
18099
+ try {
18100
+ const root = resolvedLifecycleRoot(configuredRoot);
18101
+ const summaries = [];
18102
+ for (const entry of readdirSync2(root, { withFileTypes: true })) {
18103
+ if (!entry.isDirectory() || entry.isSymbolicLink() || entry.name.startsWith(".")) {
18104
+ continue;
18105
+ }
18106
+ try {
18107
+ validateRunID(entry.name);
18108
+ const runDir = assertExistingSafeRun(root, entry.name);
18109
+ summaries.push(summaryForRun(runDir, entry.name));
18110
+ } catch (error) {
18111
+ return failed(mapFsFailure(error, entry.name));
18112
+ }
18113
+ }
18114
+ summaries.sort((left, right) => right.updatedAt - left.updatedAt || right.metadata.runID.localeCompare(left.metadata.runID));
18115
+ return ok(summaries);
18116
+ } catch (error) {
18117
+ return failed(mapFsFailure(error));
18118
+ }
18119
+ },
18120
+ async pruneTerminal(policy, now2) {
18121
+ try {
18122
+ const root = resolvedLifecycleRoot(configuredRoot);
18123
+ validateRetention(policy);
18124
+ if (!Number.isFinite(now2) || now2 < 0) {
18125
+ throw new LifecycleFsError("corrupt");
18126
+ }
18127
+ const candidates = [];
18128
+ for (const entry of readdirSync2(root, { withFileTypes: true })) {
18129
+ if (!entry.isDirectory() || entry.isSymbolicLink() || entry.name.startsWith(".")) {
18130
+ continue;
18131
+ }
18132
+ try {
18133
+ validateRunID(entry.name);
18134
+ const runDir = assertExistingSafeRun(root, entry.name);
18135
+ const summary = summaryForRun(runDir, entry.name);
18136
+ if (summary.terminalOutcome === undefined || summary.integrity !== "clean") {
18137
+ continue;
18138
+ }
18139
+ candidates.push({
18140
+ runID: entry.name,
18141
+ updatedAt: summary.updatedAt,
18142
+ bytes: runBytes(runDir),
18143
+ canDelete: canDeleteTerminalRun(runDir, entry.name, now2)
18144
+ });
18145
+ } catch (error) {
18146
+ return failed(mapFsFailure(error, entry.name));
18147
+ }
18148
+ }
18149
+ candidates.sort((left, right) => left.updatedAt - right.updatedAt || left.runID.localeCompare(right.runID));
18150
+ let retainedRuns = candidates.length;
18151
+ let retainedBytes = candidates.reduce((total, candidate) => total + candidate.bytes, 0);
18152
+ const deletedRunIDs = [];
18153
+ for (const candidate of candidates) {
18154
+ const tooOld = now2 - candidate.updatedAt > policy.maxAgeMs;
18155
+ const tooMany = retainedRuns > policy.maxTerminalRuns;
18156
+ const tooLarge = retainedBytes > policy.maxBytes;
18157
+ if (!candidate.canDelete || !tooOld && !tooMany && !tooLarge) {
18158
+ continue;
18159
+ }
18160
+ const runDir = assertExistingSafeRun(root, candidate.runID);
18161
+ const deleted = mutateRun(runDir, () => {
18162
+ if (!canDeleteTerminalRun(runDir, candidate.runID, now2)) {
18163
+ return false;
18164
+ }
18165
+ rmSync3(runDir, { recursive: true });
18166
+ return true;
18167
+ });
18168
+ if (deleted) {
18169
+ deletedRunIDs.push(candidate.runID);
18170
+ retainedRuns -= 1;
18171
+ retainedBytes -= candidate.bytes;
18172
+ }
18173
+ }
18174
+ return ok({
18175
+ deletedRunIDs,
18176
+ retainedTerminalRuns: retainedRuns,
18177
+ retainedBytes
18178
+ });
18179
+ } catch (error) {
18180
+ return failed(mapFsFailure(error));
18181
+ }
18182
+ }
18183
+ };
18184
+ };
18185
+
18186
+ // src/storage/lifecycle/fsLifecycleWriterLivenessProbe.ts
18187
+ import { join as join13 } from "node:path";
18188
+ var createFsLifecycleWriterLivenessProbe = (options) => ({
18189
+ async observe(runID) {
18190
+ const observedAt = options.now();
18191
+ try {
18192
+ const root = resolvedLifecycleRoot(options.root);
18193
+ const runDir = assertExistingSafeRun(root, runID);
18194
+ const ownerText = readUtf8(join13(runDir, "owner"));
18195
+ if (ownerText === undefined) {
18196
+ return { kind: "absent", observedAt };
18197
+ }
18198
+ let lease;
18199
+ try {
18200
+ lease = parseLifecycleWriterLease(JSON.parse(ownerText));
18201
+ } catch {
18202
+ return { kind: "unknown", observedAt, reason: "owner-invalid" };
18203
+ }
18204
+ if (lease.runID !== runID) {
18205
+ return {
18206
+ kind: "unknown",
18207
+ observedAt,
18208
+ reason: "owner-invalid",
18209
+ lease
18210
+ };
18211
+ }
18212
+ if (observedAt < lease.heartbeatAt) {
18213
+ return {
18214
+ kind: "unknown",
18215
+ observedAt,
18216
+ reason: "clock-regressed",
18217
+ lease
18218
+ };
18219
+ }
18220
+ if (observedAt >= lease.expiresAt) {
18221
+ return { kind: "expired", observedAt, lease };
18222
+ }
18223
+ return { kind: "active", observedAt, lease };
18224
+ } catch (error) {
18225
+ if (error instanceof LifecycleFsError) {
18226
+ if (error.kind === "missing") {
18227
+ return { kind: "absent", observedAt };
18228
+ }
18229
+ if (error.kind === "corrupt") {
18230
+ return { kind: "unknown", observedAt, reason: "owner-invalid" };
18231
+ }
18232
+ }
18233
+ return { kind: "unknown", observedAt, reason: "probe-unavailable" };
18234
+ }
18235
+ }
18236
+ });
18237
+
15599
18238
  // src/telemetry/otelExporter.ts
15600
18239
  var DEFAULT_SERVICE_NAME = "openteam";
15601
18240
  function tracesUrl(ingestionEndpoint) {
@@ -15642,7 +18281,7 @@ var createOtlpSpanExporter = (connection, config) => {
15642
18281
  // package.json
15643
18282
  var package_default = {
15644
18283
  name: "@jmanuelcorral/openteam",
15645
- version: "0.19.0",
18284
+ version: "0.21.0",
15646
18285
  packageManager: "bun@1.3.14",
15647
18286
  description: "Cost-aware, local-first routing plugin for opencode with cheapest-capable frontier fallback and multi-agent orchestration.",
15648
18287
  license: "MIT",
@@ -15746,6 +18385,30 @@ var package_default = {
15746
18385
  var PACKAGE_VERSION = package_default.version;
15747
18386
 
15748
18387
  // src/index.ts
18388
+ var LIFECYCLE_WRITER_IDENTITY = {
18389
+ processID: process.pid,
18390
+ processInstanceID: crypto.randomUUID()
18391
+ };
18392
+ function createConfiguredLifecycleRecorder(config, workspaceRoot, now) {
18393
+ const root = resolveLifecycleRoot({
18394
+ workspaceRoot,
18395
+ configuredRoot: config.lifecycle?.root ?? DEFAULT_LIFECYCLE_ROOT
18396
+ });
18397
+ return createRuntimeLifecycleRecorder({
18398
+ journal: createFsLifecycleJournal({ root, now }),
18399
+ writerLiveness: createFsLifecycleWriterLivenessProbe({ root, now }),
18400
+ writer: LIFECYCLE_WRITER_IDENTITY,
18401
+ now,
18402
+ heartbeatScheduler: createNodeLifecycleHeartbeatScheduler(),
18403
+ heartbeatIntervalMs: DEFAULT_LIFECYCLE_HEARTBEAT_INTERVAL_MS,
18404
+ leaseDurationMs: DEFAULT_LIFECYCLE_WRITER_LEASE_MS,
18405
+ retention: {
18406
+ maxTerminalRuns: config.lifecycle?.retention.maxTerminalRuns ?? DEFAULT_LIFECYCLE_MAX_TERMINAL_RUNS,
18407
+ maxAgeMs: (config.lifecycle?.retention.maxAgeDays ?? DEFAULT_LIFECYCLE_MAX_AGE_DAYS) * 24 * 60 * 60 * 1000,
18408
+ maxBytes: config.lifecycle?.retention.maxBytes ?? DEFAULT_LIFECYCLE_MAX_BYTES
18409
+ }
18410
+ });
18411
+ }
15749
18412
  function createShellExec($) {
15750
18413
  return async (command, args) => {
15751
18414
  const output = await $`${command} ${[...args]}`.nothrow().quiet();
@@ -15821,7 +18484,7 @@ var RELEASE_CERT_PATH = "artifacts/graph-release-certificate.json";
15821
18484
  var SOAK_CERT_PATH = "artifacts/graph-soak-certificate.json";
15822
18485
  async function readPackagedCertificate(path4) {
15823
18486
  try {
15824
- return await readFile3(new URL(`./certificates/${basename2(path4)}`, import.meta.url), "utf8");
18487
+ return await readFile3(new URL(`./certificates/${basename3(path4)}`, import.meta.url), "utf8");
15825
18488
  } catch {
15826
18489
  return;
15827
18490
  }
@@ -15953,8 +18616,8 @@ async function readOpencodeVersion(serverUrl, options = {}) {
15953
18616
  }
15954
18617
  var DEFAULT_OPENCODE_VERSION_BACKOFF_MS = [50, 250];
15955
18618
  function defaultSleep(ms) {
15956
- return new Promise((resolve5) => {
15957
- setTimeout(resolve5, ms);
18619
+ return new Promise((resolve8) => {
18620
+ setTimeout(resolve8, ms);
15958
18621
  });
15959
18622
  }
15960
18623
  function createLazyOpencodeVersionReader(serverUrl, options = {}) {
@@ -16197,13 +18860,15 @@ function createCliDeps(config, registry, telemetryPath, sessionsDir = DEFAULT_SE
16197
18860
  graphOperator: createGraphOperator({
16198
18861
  storage,
16199
18862
  holder: "openteam-cli",
18863
+ lifecycleSource: "cli",
18864
+ createLifecycleRecorder: (graphConfig) => createConfiguredLifecycleRecorder(graphConfig, process.cwd(), () => Date.now()),
16200
18865
  evaluateCutoverGate: async (gateConfig) => evaluateCutoverGateForBoundary(gateConfig, storage, resolveOpencodeVersion === undefined ? undefined : await resolveOpencodeVersion())
16201
18866
  }),
16202
18867
  configPath: DEFAULT_CONFIG_PATH,
16203
18868
  telemetryPath,
16204
18869
  opencodeConfigPaths: OPENCODE_CONFIG_CANDIDATES,
16205
18870
  orchestratorAgentPath: ORCHESTRATOR_AGENT_PATH,
16206
- agentDir: dirname4(ORCHESTRATOR_AGENT_PATH),
18871
+ agentDir: dirname5(ORCHESTRATOR_AGENT_PATH),
16207
18872
  cachePort: realCacheAdapter,
16208
18873
  purge: createPluginPurgeRuntime(sessionsDir),
16209
18874
  loadRosterForDoctor: () => readStoredRosterForDoctor(storage),
@@ -16259,6 +18924,8 @@ var server = async (ctx, rawOptions) => {
16259
18924
  const storage = createFsStorageProvider(ctx.directory || ctx.worktree || process.cwd());
16260
18925
  const runtimeConfig = await loadRuntimeOpenTeamConfig(storage, rawOptions);
16261
18926
  const config = runtimeConfig.config;
18927
+ const workspaceRoot = ctx.directory || ctx.worktree || process.cwd();
18928
+ const lifecycleRecorder = createConfiguredLifecycleRecorder(config, workspaceRoot, now);
16262
18929
  const registry = new RuntimeRegistry({
16263
18930
  fetch: globalThis.fetch,
16264
18931
  clock: () => new Date().toISOString(),
@@ -16393,6 +19060,8 @@ var server = async (ctx, rawOptions) => {
16393
19060
  const graphOperator = createGraphOperator({
16394
19061
  storage,
16395
19062
  holder: "openteam-plugin",
19063
+ lifecycleSource: "plugin",
19064
+ lifecycleRecorder,
16396
19065
  exec: createShellExec(ctx.$),
16397
19066
  repoRoot: ctx.directory
16398
19067
  });
@@ -16468,10 +19137,11 @@ var server = async (ctx, rawOptions) => {
16468
19137
  capabilityProfiles,
16469
19138
  reportArtifactExists,
16470
19139
  readReportArtifact,
19140
+ lifecycleRecorder,
16471
19141
  abortGraceMs: DEFAULT_ABORT_GRACE_MS,
16472
19142
  statusPollMs: DEFAULT_STATUS_POLL_MS,
16473
- delay: (ms) => new Promise((resolve5) => {
16474
- setTimeout(resolve5, ms);
19143
+ delay: (ms) => new Promise((resolve8) => {
19144
+ setTimeout(resolve8, ms);
16475
19145
  }),
16476
19146
  warn: diagnostics.warn("unknown-role")
16477
19147
  });