@kal-elsam/kairo-runtime 0.7.0 → 0.8.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.
@@ -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,