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