@kal-elsam/kairo-runtime 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +43 -11
  2. package/global-template/components/catalog.json +4 -1
  3. package/global-template/components/orchestrator/extensions/pi/kairo-minion.js +604 -0
  4. package/package.json +1 -1
  5. package/scripts/cockpit-smoke.mjs +4 -4
  6. package/src/cli.js +34 -2
  7. package/src/global/adapters/pi.js +1 -1
  8. package/src/global/global-doctor.js +2 -0
  9. package/src/global/ink/cockpit/primitives.js +96 -31
  10. package/src/global/ink/cockpit-alerts.js +36 -0
  11. package/src/global/ink/cockpit-changes.js +59 -35
  12. package/src/global/ink/cockpit-control-center.js +129 -51
  13. package/src/global/ink/cockpit-controller.js +58 -11
  14. package/src/global/ink/cockpit-focus.js +4 -2
  15. package/src/global/ink/cockpit-models.js +89 -47
  16. package/src/global/ink/cockpit-palette.js +98 -0
  17. package/src/global/ink/cockpit-path-label.js +19 -0
  18. package/src/global/ink/cockpit-recovery.js +78 -15
  19. package/src/global/ink/cockpit-reviews.js +14 -10
  20. package/src/global/ink/cockpit-runs.js +13 -4
  21. package/src/global/ink/cockpit-settings.js +194 -0
  22. package/src/global/ink/cockpit-views.js +88 -57
  23. package/src/global/ink/orchestrator-app.js +137 -46
  24. package/src/global/ink/orchestrator-state.js +24 -14
  25. package/src/global/ink/use-orchestrator-data.js +58 -0
  26. package/src/global/paths.js +3 -0
  27. package/src/global/runtime/alerts/alert-store.js +216 -0
  28. package/src/global/runtime/alerts/alert-types.js +59 -0
  29. package/src/global/runtime/alerts/alert-validate.js +117 -0
  30. package/src/global/runtime/execution-adapters/pi.js +12 -2
  31. package/src/global/runtime/monitor/monitor-cli.js +62 -0
  32. package/src/global/runtime/monitor/monitor-platform.js +95 -0
  33. package/src/global/runtime/monitor/monitor.js +249 -0
  34. package/src/global/runtime/orchestration/index.js +23 -0
  35. package/src/global/runtime/orchestration/orch-receipts.js +234 -0
  36. package/src/global/runtime/orchestration/orch-types.js +173 -0
  37. package/src/global/runtime/orchestration/orch-validate.js +63 -0
  38. package/src/global/runtime/run-cli.js +1 -0
  39. package/src/global/runtime/run-manager.js +59 -4
  40. package/src/global/runtime/run-strategy.js +71 -0
  41. package/src/global/runtime/run-supervisor.js +22 -2
  42. package/src/global/runtime/run-types.js +5 -1
@@ -0,0 +1,173 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ export const RUN_STRATEGIES = Object.freeze({ DIRECT: "direct", ORCHESTRATED: "orchestrated" });
3
+ export const DAG_NODE_STATES = Object.freeze({
4
+ PENDING: "pending", READY: "ready", RUNNING: "running", COMPACTING: "compacting",
5
+ COMPLETED: "completed", FAILED: "failed", CANCELLED: "cancelled", BLOCKED: "blocked"
6
+ });
7
+ export const DAG_TERMINAL_STATES = new Set([
8
+ DAG_NODE_STATES.COMPLETED, DAG_NODE_STATES.FAILED, DAG_NODE_STATES.CANCELLED
9
+ ]);
10
+ export const ORCH_LIMITS = Object.freeze({
11
+ MAX_DEPTH: 1, DEFAULT_CONCURRENCY: 2, MAX_ATTEMPTS: 2, COMPACT_RATIO: 0.7, STOP_RATIO: 0.9
12
+ });
13
+ export const ORCH_ERROR_CODES = Object.freeze({
14
+ INVALID_STRATEGY: "invalid_strategy", INVALID_DEPTH: "invalid_depth",
15
+ INVALID_LINEAGE: "invalid_lineage", INVALID_HANDOFF: "invalid_handoff",
16
+ INVALID_NODE: "invalid_node", LIMIT_EXCEEDED: "limit_exceeded",
17
+ FORBIDDEN_FIELD: "forbidden_field", RECEIPT_EXISTS: "receipt_exists"
18
+ });
19
+ export class OrchContractError extends Error {
20
+ constructor(message, { code, details = null } = {}) {
21
+ super(message);
22
+ this.name = "OrchContractError";
23
+ this.code = code;
24
+ this.details = details;
25
+ }
26
+ }
27
+ export function createTaskId() {
28
+ return `task_${randomBytes(8).toString("hex")}`;
29
+ }
30
+ export function isTerminalDagState(state) {
31
+ return DAG_TERMINAL_STATES.has(state);
32
+ }
33
+
34
+ function assertDepth(depth) {
35
+ const d = Number(depth);
36
+ if (!Number.isInteger(d) || d < 0) {
37
+ throw new OrchContractError(`Invalid depth "${depth}".`, {
38
+ code: ORCH_ERROR_CODES.INVALID_DEPTH, details: { depth }
39
+ });
40
+ }
41
+ if (d > ORCH_LIMITS.MAX_DEPTH) {
42
+ throw new OrchContractError(`Orchestration depth ${d} exceeds max ${ORCH_LIMITS.MAX_DEPTH}.`, {
43
+ code: ORCH_ERROR_CODES.INVALID_DEPTH, details: { depth: d }
44
+ });
45
+ }
46
+ return d;
47
+ }
48
+
49
+ function assertParentForDepth(depth, parentId, { rootMsg, minionMsg, code }) {
50
+ if (depth === 0 && parentId != null) {
51
+ throw new OrchContractError(rootMsg, { code });
52
+ }
53
+ if (depth > 0 && !parentId) {
54
+ throw new OrchContractError(minionMsg, { code });
55
+ }
56
+ }
57
+
58
+ export function normalizeRunStrategy(value = RUN_STRATEGIES.DIRECT) {
59
+ const strategy = String(value ?? RUN_STRATEGIES.DIRECT).trim().toLowerCase();
60
+ if (!Object.values(RUN_STRATEGIES).includes(strategy)) {
61
+ throw new OrchContractError(`Invalid run strategy "${value}". Use direct or orchestrated.`, {
62
+ code: ORCH_ERROR_CODES.INVALID_STRATEGY, details: { value }
63
+ });
64
+ }
65
+ return strategy;
66
+ }
67
+
68
+ /** Lineage: max depth 1 (root=0, minion=1). */
69
+ export function createOrchLineage({ rootRunId, parentRunId = null, taskId = null, depth = 0 } = {}) {
70
+ if (typeof rootRunId !== "string" || !rootRunId) {
71
+ throw new OrchContractError("rootRunId is required.", { code: ORCH_ERROR_CODES.INVALID_LINEAGE });
72
+ }
73
+ const d = assertDepth(depth);
74
+ assertParentForDepth(d, parentRunId, {
75
+ rootMsg: "Root nodes must not set parentRunId.",
76
+ minionMsg: "Minion nodes require parentRunId.",
77
+ code: ORCH_ERROR_CODES.INVALID_LINEAGE
78
+ });
79
+ return { rootRunId, parentRunId: parentRunId ?? null, taskId: taskId ?? createTaskId(), depth: d };
80
+ }
81
+
82
+ export function createBudgetUsage({
83
+ contextTokens = 0, contextLimit = 0,
84
+ compactRatio = ORCH_LIMITS.COMPACT_RATIO, stopRatio = ORCH_LIMITS.STOP_RATIO
85
+ } = {}) {
86
+ const tokens = Math.max(0, Number(contextTokens) || 0);
87
+ const limit = Math.max(0, Number(contextLimit) || 0);
88
+ const ratio = limit > 0 ? tokens / limit : 0;
89
+ return {
90
+ contextTokens: tokens, contextLimit: limit, ratio,
91
+ shouldCompact: limit > 0 && ratio >= compactRatio && ratio < stopRatio,
92
+ shouldStop: limit > 0 && ratio >= stopRatio
93
+ };
94
+ }
95
+
96
+ export function createDagNode({
97
+ taskId = null, runId = null, parentTaskId = null, depth = 0,
98
+ state = DAG_NODE_STATES.PENDING, dependsOn = [], attempt = 0,
99
+ objectiveDigest = null, budget = null, resultDigest = null, error = null
100
+ } = {}) {
101
+ if (!Object.values(DAG_NODE_STATES).includes(state)) {
102
+ throw new OrchContractError(`Invalid DAG node state "${state}".`, {
103
+ code: ORCH_ERROR_CODES.INVALID_NODE, details: { state }
104
+ });
105
+ }
106
+ const d = assertDepth(depth);
107
+ assertParentForDepth(d, parentTaskId, {
108
+ rootMsg: "Root nodes must not set parentTaskId.",
109
+ minionMsg: "Minion nodes require parentTaskId.",
110
+ code: ORCH_ERROR_CODES.INVALID_NODE
111
+ });
112
+ const attempts = Number(attempt) || 0;
113
+ if (attempts < 0 || attempts > ORCH_LIMITS.MAX_ATTEMPTS) {
114
+ throw new OrchContractError(`Attempt ${attempts} outside 0..${ORCH_LIMITS.MAX_ATTEMPTS}.`, {
115
+ code: ORCH_ERROR_CODES.LIMIT_EXCEEDED, details: { attempt: attempts }
116
+ });
117
+ }
118
+ const deps = [...new Set((dependsOn ?? []).map((id) => {
119
+ if (typeof id !== "string" || !id) {
120
+ throw new OrchContractError("Dependency taskId is required.", { code: ORCH_ERROR_CODES.INVALID_NODE });
121
+ }
122
+ return id;
123
+ }))];
124
+ return {
125
+ taskId: taskId ?? createTaskId(), runId: runId ?? null, parentTaskId: parentTaskId ?? null,
126
+ depth: d, state, dependsOn: deps, attempt: attempts,
127
+ objectiveDigest, budget: budget ?? null, resultDigest, error: error ?? null
128
+ };
129
+ }
130
+
131
+ export function createMinionBrief({
132
+ objective, constraints = [], admittedPaths = [], exitCriteria = [],
133
+ parentTaskId = null, taskId = null
134
+ } = {}) {
135
+ if (typeof objective !== "string" || !objective.trim()) {
136
+ throw new OrchContractError("Minion brief requires a non-empty objective.", {
137
+ code: ORCH_ERROR_CODES.INVALID_HANDOFF
138
+ });
139
+ }
140
+ return {
141
+ taskId: taskId ?? createTaskId(), parentTaskId: parentTaskId ?? null,
142
+ objective: objective.trim(), constraints: (constraints ?? []).map(String),
143
+ admittedPaths: (admittedPaths ?? []).map(String), exitCriteria: (exitCriteria ?? []).map(String)
144
+ };
145
+ }
146
+
147
+ export function createMinionResult({
148
+ taskId = null, summary, decisions = [], files = [], risks = [],
149
+ evidence = [], usage = null, compact = false
150
+ } = {}) {
151
+ if (typeof taskId !== "string" || !taskId) {
152
+ throw new OrchContractError("Minion result requires taskId.", { code: ORCH_ERROR_CODES.INVALID_HANDOFF });
153
+ }
154
+ if (typeof summary !== "string" || !summary.trim()) {
155
+ throw new OrchContractError("Minion result requires a non-empty summary.", {
156
+ code: ORCH_ERROR_CODES.INVALID_HANDOFF
157
+ });
158
+ }
159
+ return {
160
+ taskId, summary: summary.trim(),
161
+ decisions: (decisions ?? []).map(String), files: (files ?? []).map(String),
162
+ risks: (risks ?? []).map(String), evidence: (evidence ?? []).map(String),
163
+ usage: {
164
+ inputTokens: usage?.inputTokens ?? null, outputTokens: usage?.outputTokens ?? null,
165
+ totalTokens: usage?.totalTokens ?? null, cost: usage?.cost ?? null
166
+ },
167
+ compact: Boolean(compact)
168
+ };
169
+ }
170
+
171
+ export function digestAllowlisted(value) {
172
+ return createHash("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16);
173
+ }
@@ -0,0 +1,63 @@
1
+ import {
2
+ ORCH_ERROR_CODES, OrchContractError, createBudgetUsage, createDagNode,
3
+ createMinionResult, createOrchLineage, normalizeRunStrategy
4
+ } from "./orch-types.js";
5
+ export const FORBIDDEN_KEYS = new Set([
6
+ "prompt", "diff", "transcript", "raw", "rawOutput", "stdout", "stderr", "output",
7
+ "message", "messages", "content", "secret", "secrets", "token", "apiKey",
8
+ "conversation", "history", "toolArgs", "arguments", "objective"
9
+ ]);
10
+ export function walkForbiddenKeys(value, path = "") {
11
+ if (!value || typeof value !== "object") return;
12
+ if (Array.isArray(value)) {
13
+ value.forEach((item, i) => walkForbiddenKeys(item, `${path}[${i}]`));
14
+ return;
15
+ }
16
+ for (const [key, child] of Object.entries(value)) {
17
+ if (FORBIDDEN_KEYS.has(key)) {
18
+ throw new OrchContractError(`Forbidden field "${path}${key}" in orchestration receipt.`, {
19
+ code: ORCH_ERROR_CODES.FORBIDDEN_FIELD, details: { key, path }
20
+ });
21
+ }
22
+ walkForbiddenKeys(child, `${path}${key}.`);
23
+ }
24
+ }
25
+ export function assertOrchReceiptSecretFree(receipt) {
26
+ if (!receipt || typeof receipt !== "object" || Array.isArray(receipt)) {
27
+ throw new OrchContractError("Invalid orchestration receipt: expected object.", {
28
+ code: ORCH_ERROR_CODES.FORBIDDEN_FIELD
29
+ });
30
+ }
31
+ walkForbiddenKeys(receipt);
32
+ if (receipt.version !== 1) {
33
+ throw new OrchContractError("Orchestration receipt version must be 1.", {
34
+ code: ORCH_ERROR_CODES.FORBIDDEN_FIELD
35
+ });
36
+ }
37
+ normalizeRunStrategy(receipt.strategy);
38
+ if (typeof receipt.rootRunId !== "string" || !receipt.rootRunId) {
39
+ throw new OrchContractError("rootRunId is required on receipt.", {
40
+ code: ORCH_ERROR_CODES.INVALID_LINEAGE
41
+ });
42
+ }
43
+ createOrchLineage(receipt.lineage);
44
+ if (receipt.lineage.rootRunId !== receipt.rootRunId) {
45
+ throw new OrchContractError("lineage.rootRunId must match receipt.rootRunId.", {
46
+ code: ORCH_ERROR_CODES.INVALID_LINEAGE
47
+ });
48
+ }
49
+ if (!Array.isArray(receipt.nodes) || !Array.isArray(receipt.results)) {
50
+ throw new OrchContractError("Receipt requires nodes[] and results[].", {
51
+ code: ORCH_ERROR_CODES.INVALID_NODE
52
+ });
53
+ }
54
+ for (const node of receipt.nodes) {
55
+ createDagNode(node);
56
+ if (node.budget) createBudgetUsage(node.budget);
57
+ }
58
+ for (const result of receipt.results) createMinionResult(result);
59
+ if (typeof receipt.createdAt !== "string" || !receipt.createdAt) {
60
+ throw new OrchContractError("createdAt is required.", { code: ORCH_ERROR_CODES.FORBIDDEN_FIELD });
61
+ }
62
+ return receipt;
63
+ }
@@ -45,6 +45,7 @@ export async function runGlobalRun(options, packageManifest, { startRunImpl = st
45
45
  captureTranscript,
46
46
  cliVersion: packageManifest.version,
47
47
  profile: profileResolved,
48
+ strategy: options.strategy ?? "direct",
48
49
  follow: options.follow,
49
50
  timeoutMs: options.timeoutMs,
50
51
  wait: options.wait !== false
@@ -5,6 +5,7 @@ import {
5
5
  appendRunEvent,
6
6
  appendRunStartedEvent,
7
7
  createRunRecord,
8
+ listRunRecords,
8
9
  readRunState,
9
10
  reconcileActiveRuns,
10
11
  writeRunState
@@ -22,6 +23,20 @@ import {
22
23
  readSupervisorLockForRun,
23
24
  supervisePreparedRun
24
25
  } from "./run-supervisor.js";
26
+ import {
27
+ assertManagedMinionExtension,
28
+ assertOrchestratedAgent,
29
+ createRootRunLineage,
30
+ normalizeRunStrategy,
31
+ RUN_STRATEGIES
32
+ } from "./run-strategy.js";
33
+ import {
34
+ DAG_NODE_STATES,
35
+ createDagNode,
36
+ createOrchState,
37
+ reconcileOrchState,
38
+ saveOrchState
39
+ } from "./orchestration/index.js";
25
40
 
26
41
  const activeProcesses = new Map();
27
42
  const cancelledRuns = new Set();
@@ -55,6 +70,19 @@ export async function recoverRuns(homeDir) {
55
70
  exceptRunIds: listActiveRunIds(),
56
71
  isRunAliveImpl: isRunSupervisedAlive
57
72
  });
73
+ for (const run of await listRunRecords(homeDir)) {
74
+ if (normalizeRunStrategy(run.strategy ?? RUN_STRATEGIES.DIRECT) !== RUN_STRATEGIES.ORCHESTRATED) {
75
+ continue;
76
+ }
77
+ if (await isRunSupervisedAlive(homeDir, run)) {
78
+ continue;
79
+ }
80
+ try {
81
+ await reconcileOrchState(run.runId, { homeDir });
82
+ } catch {
83
+ // Fail closed per root: never invent receipt evidence from corrupt state.
84
+ }
85
+ }
58
86
  return interrupted;
59
87
  }
60
88
 
@@ -67,8 +95,10 @@ async function prepareRun({
67
95
  permissions = [],
68
96
  captureTranscript = false,
69
97
  cliVersion,
70
- profile = null
98
+ profile = null,
99
+ strategy = "direct"
71
100
  }) {
101
+ const normalizedStrategy = assertOrchestratedAgent(agentId, strategy);
72
102
  const adapter = resolveExecutionAdapter(agentId);
73
103
  const availability = adapter.availability({ cwd });
74
104
 
@@ -83,7 +113,12 @@ async function prepareRun({
83
113
  );
84
114
  }
85
115
 
116
+ if (normalizedStrategy === RUN_STRATEGIES.ORCHESTRATED) {
117
+ await assertManagedMinionExtension(homeDir);
118
+ }
119
+
86
120
  const runId = createRunId();
121
+ const lineage = createRootRunLineage(runId);
87
122
  const metadata = createRunMetadata({
88
123
  runId,
89
124
  agentId,
@@ -94,7 +129,9 @@ async function prepareRun({
94
129
  permissions,
95
130
  captureTranscript,
96
131
  cliVersion,
97
- profileSources: profile?.sources ?? null
132
+ profileSources: profile?.sources ?? null,
133
+ strategy: normalizedStrategy,
134
+ lineage
98
135
  });
99
136
 
100
137
  await createRunRecord(homeDir, metadata);
@@ -107,9 +144,25 @@ async function prepareRun({
107
144
  permissions,
108
145
  captureTranscript,
109
146
  cliVersion,
110
- profile: profile?.profile ?? null
147
+ profile: profile?.profile ?? null,
148
+ strategy: normalizedStrategy
111
149
  });
112
150
 
151
+ if (normalizedStrategy === RUN_STRATEGIES.ORCHESTRATED) {
152
+ await saveOrchState(createOrchState({
153
+ rootRunId: runId,
154
+ strategy: normalizedStrategy,
155
+ lineage,
156
+ nodes: [createDagNode({
157
+ taskId: lineage.taskId,
158
+ runId,
159
+ depth: 0,
160
+ state: DAG_NODE_STATES.RUNNING
161
+ })],
162
+ cliVersion
163
+ }), { homeDir });
164
+ }
165
+
113
166
  return { runId, metadata };
114
167
  }
115
168
 
@@ -123,6 +176,7 @@ export async function startRun({
123
176
  captureTranscript = false,
124
177
  cliVersion,
125
178
  profile = null,
179
+ strategy = "direct",
126
180
  follow = false,
127
181
  timeoutMs = null,
128
182
  wait = true,
@@ -138,7 +192,8 @@ export async function startRun({
138
192
  permissions,
139
193
  captureTranscript,
140
194
  cliVersion,
141
- profile
195
+ profile,
196
+ strategy: normalizeRunStrategy(strategy)
142
197
  });
143
198
 
144
199
  if (!wait) {
@@ -0,0 +1,71 @@
1
+ import { stat } from "node:fs/promises";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import {
5
+ RUN_STRATEGIES,
6
+ normalizeRunStrategy,
7
+ createOrchLineage,
8
+ resolveKairoMinionExtensionPath
9
+ } from "./orchestration/index.js";
10
+
11
+ export { RUN_STRATEGIES, normalizeRunStrategy };
12
+
13
+ const ORCH_MODULE_PATH = join(dirname(fileURLToPath(import.meta.url)), "orchestration", "index.js");
14
+
15
+ export const ORCH_RUNTIME_ENV = Object.freeze({
16
+ HOME: "KAIRO_ORCH_HOME",
17
+ ROOT_RUN_ID: "KAIRO_ORCH_ROOT_RUN_ID",
18
+ ROOT_TASK_ID: "KAIRO_ORCH_ROOT_TASK_ID",
19
+ CLI_VERSION: "KAIRO_ORCH_CLI_VERSION",
20
+ MODULE: "KAIRO_ORCH_MODULE"
21
+ });
22
+
23
+ /** Reject orchestrated for non-Pi before any run I/O. */
24
+ export function assertOrchestratedAgent(agentId, strategy) {
25
+ const normalized = normalizeRunStrategy(strategy);
26
+ if (normalized === RUN_STRATEGIES.ORCHESTRATED && agentId !== "pi") {
27
+ throw new Error(
28
+ `Strategy "orchestrated" requires agent "pi" (got "${agentId}").`
29
+ );
30
+ }
31
+ return normalized;
32
+ }
33
+
34
+ /** Fail closed when managed extension is missing or not a regular file. */
35
+ export async function assertManagedMinionExtension(homeDir) {
36
+ const extensionPath = resolveKairoMinionExtensionPath(homeDir);
37
+ let info;
38
+ try {
39
+ info = await stat(extensionPath);
40
+ } catch {
41
+ throw new Error(`Managed Kairo minion extension missing: ${extensionPath}`);
42
+ }
43
+ if (!info.isFile()) {
44
+ throw new Error(`Managed Kairo minion extension is not a regular file: ${extensionPath}`);
45
+ }
46
+ return extensionPath;
47
+ }
48
+
49
+ export function createRootRunLineage(runId) {
50
+ return createOrchLineage({ rootRunId: runId, parentRunId: null, depth: 0 });
51
+ }
52
+
53
+ /** Supervisor derives extension path from homeDir only — never from CLI/handoff. */
54
+ export function resolveOrchestratedExtensionPath(homeDir, strategy) {
55
+ if (normalizeRunStrategy(strategy) !== RUN_STRATEGIES.ORCHESTRATED) return null;
56
+ return resolveKairoMinionExtensionPath(homeDir);
57
+ }
58
+
59
+ export function buildOrchestratedRuntimeEnv({
60
+ homeDir, rootRunId, rootTaskId, cliVersion = null,
61
+ strategy = RUN_STRATEGIES.DIRECT, baseEnv = process.env
62
+ } = {}) {
63
+ const env = { ...baseEnv };
64
+ if (normalizeRunStrategy(strategy) !== RUN_STRATEGIES.ORCHESTRATED) return env;
65
+ env[ORCH_RUNTIME_ENV.HOME] = homeDir;
66
+ env[ORCH_RUNTIME_ENV.ROOT_RUN_ID] = rootRunId;
67
+ env[ORCH_RUNTIME_ENV.ROOT_TASK_ID] = rootTaskId;
68
+ env[ORCH_RUNTIME_ENV.CLI_VERSION] = cliVersion == null ? "" : String(cliVersion);
69
+ env[ORCH_RUNTIME_ENV.MODULE] = ORCH_MODULE_PATH;
70
+ return env;
71
+ }
@@ -18,6 +18,12 @@ import { consumeRunHandoff } from "./run-handoff.js";
18
18
  import { isRunCancelRequested } from "./run-cancel-signal.js";
19
19
  import { readSupervisorLock, touchSupervisorLock, writeSupervisorLock } from "./run-supervisor-lock.js";
20
20
  import { shouldPersistTranscript } from "./run-redact.js";
21
+ import {
22
+ buildOrchestratedRuntimeEnv,
23
+ normalizeRunStrategy,
24
+ resolveOrchestratedExtensionPath
25
+ } from "./run-strategy.js";
26
+ import { finalizeOrchState, RUN_STRATEGIES } from "./orchestration/index.js";
21
27
 
22
28
  async function shouldPreserveCancelledState(homeDir, runId) {
23
29
  const fresh = await readRunState(homeDir, runId);
@@ -66,12 +72,16 @@ export async function supervisePreparedRun({
66
72
  }
67
73
 
68
74
  const captureTranscript = handoff.captureTranscript === true;
75
+ const strategy = normalizeRunStrategy(handoff.strategy ?? metadata.strategy ?? "direct");
76
+ const extensionPath = resolveOrchestratedExtensionPath(homeDir, strategy);
69
77
  const launch = adapter.buildLaunch({
70
78
  task: handoff.task,
71
79
  cwd: handoff.cwd,
72
80
  model: handoff.model,
73
81
  permissions: handoff.permissions ?? [],
74
- profile: handoff.profile ?? null
82
+ profile: handoff.profile ?? null,
83
+ strategy,
84
+ extensionPath
75
85
  });
76
86
 
77
87
  metadata = {
@@ -90,7 +100,14 @@ export async function supervisePreparedRun({
90
100
 
91
101
  const child = spawnImpl(launch.command, launch.args, {
92
102
  cwd: launch.cwd,
93
- env: launch.env,
103
+ env: buildOrchestratedRuntimeEnv({
104
+ homeDir,
105
+ rootRunId: runId,
106
+ rootTaskId: metadata.lineage?.taskId,
107
+ cliVersion: metadata.cliVersion,
108
+ strategy,
109
+ baseEnv: launch.env ?? process.env
110
+ }),
94
111
  stdio: ["ignore", "pipe", "pipe"]
95
112
  });
96
113
  activeProcesses?.set(runId, child);
@@ -244,6 +261,9 @@ export async function supervisePreparedRun({
244
261
  type: failed ? "run.failed" : "run.completed",
245
262
  data: { exitCode }
246
263
  }), { captureTranscript: shouldPersistTranscript(captureTranscript) });
264
+ if (!failed && strategy === RUN_STRATEGIES.ORCHESTRATED) {
265
+ await finalizeOrchState(runId, { homeDir, recovered: false });
266
+ }
247
267
  resolve(metadata);
248
268
  });
249
269
  } catch (error) {
@@ -83,7 +83,9 @@ export function createRunMetadata({
83
83
  permissions = [],
84
84
  captureTranscript = false,
85
85
  cliVersion,
86
- profileSources = null
86
+ profileSources = null,
87
+ strategy = "direct",
88
+ lineage = null
87
89
  }) {
88
90
  const { taskDigest, taskLength } = createTaskFingerprint(task);
89
91
  const now = new Date().toISOString();
@@ -100,6 +102,8 @@ export function createRunMetadata({
100
102
  captureTranscript,
101
103
  cliVersion,
102
104
  profileSources,
105
+ strategy,
106
+ lineage,
103
107
  state: RUN_STATES.PENDING,
104
108
  pid: null,
105
109
  supervisorPid: null,