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