@kal-elsam/kairo-runtime 0.2.1 → 0.2.3

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 (37) hide show
  1. package/README.md +13 -2
  2. package/package.json +1 -1
  3. package/scripts/runtime-mvp-smoke.sh +152 -0
  4. package/src/cli.js +97 -9
  5. package/src/global/brand/index.js +10 -0
  6. package/src/global/dashboard-guidance.js +66 -0
  7. package/src/global/initial-experience.js +34 -0
  8. package/src/global/ink/orchestrator-app.js +372 -82
  9. package/src/global/ink/orchestrator-state.js +196 -65
  10. package/src/global/ink/run-orchestrator-ink.js +2 -0
  11. package/src/global/ink/run-setup-ink.js +2 -0
  12. package/src/global/ink/setup-app.js +8 -4
  13. package/src/global/ink/setup-state.js +24 -2
  14. package/src/global/orchestrator.js +46 -42
  15. package/src/global/paths.js +13 -0
  16. package/src/global/profile.js +17 -2
  17. package/src/global/runtime/execution-adapters/claude.js +65 -0
  18. package/src/global/runtime/execution-adapters/codex.js +78 -0
  19. package/src/global/runtime/execution-adapters/create-execution-adapter.js +92 -0
  20. package/src/global/runtime/execution-adapters/cursor.js +104 -0
  21. package/src/global/runtime/execution-adapters/index.js +36 -0
  22. package/src/global/runtime/execution-adapters/opencode.js +38 -0
  23. package/src/global/runtime/run-cancel-signal.js +29 -0
  24. package/src/global/runtime/run-cli.js +221 -0
  25. package/src/global/runtime/run-events.js +144 -0
  26. package/src/global/runtime/run-handoff.js +71 -0
  27. package/src/global/runtime/run-liveness.js +28 -0
  28. package/src/global/runtime/run-manager.js +271 -0
  29. package/src/global/runtime/run-profile.js +93 -0
  30. package/src/global/runtime/run-redact.js +66 -0
  31. package/src/global/runtime/run-starting.js +13 -0
  32. package/src/global/runtime/run-store.js +159 -0
  33. package/src/global/runtime/run-supervisor-lock.js +37 -0
  34. package/src/global/runtime/run-supervisor-worker.js +12 -0
  35. package/src/global/runtime/run-supervisor.js +289 -0
  36. package/src/global/runtime/run-types.js +117 -0
  37. package/src/global/setup.js +13 -5
@@ -0,0 +1,144 @@
1
+ import { RUN_EVENT_TYPES } from "./run-types.js";
2
+ import { redactObject } from "./run-redact.js";
3
+
4
+ export function createRunEvent({
5
+ runId,
6
+ type,
7
+ source = "kairo",
8
+ data = {},
9
+ captureTranscript = false
10
+ }) {
11
+ return {
12
+ timestamp: new Date().toISOString(),
13
+ runId,
14
+ type,
15
+ source,
16
+ data: sanitizeEventData(data, { captureTranscript })
17
+ };
18
+ }
19
+
20
+ export function sanitizeEventData(data, { captureTranscript = false } = {}) {
21
+ const allowTranscript = captureTranscript === true;
22
+ return redactObject(data, { allowTranscript });
23
+ }
24
+
25
+ export function normalizeAdapterEvent(adapterId, raw, { captureTranscript = false } = {}) {
26
+ if (raw == null) return null;
27
+
28
+ if (typeof raw === "object" && raw.type && raw.runId) {
29
+ return {
30
+ ...raw,
31
+ data: sanitizeEventData(raw.data ?? {}, { captureTranscript })
32
+ };
33
+ }
34
+
35
+ if (typeof raw === "object" && raw.type) {
36
+ return mapStructuredEvent(adapterId, raw, { captureTranscript });
37
+ }
38
+
39
+ if (typeof raw === "string") {
40
+ return {
41
+ timestamp: new Date().toISOString(),
42
+ type: RUN_EVENT_TYPES.STDOUT,
43
+ source: adapterId,
44
+ data: sanitizeEventData({ line: raw }, { captureTranscript })
45
+ };
46
+ }
47
+
48
+ return null;
49
+ }
50
+
51
+ function mapStructuredEvent(adapterId, raw, { captureTranscript }) {
52
+ const base = {
53
+ timestamp: new Date().toISOString(),
54
+ source: adapterId,
55
+ data: sanitizeEventData(extractEventData(raw), { captureTranscript })
56
+ };
57
+
58
+ switch (raw.type) {
59
+ case "system":
60
+ return { ...base, type: RUN_EVENT_TYPES.SYSTEM };
61
+ case "assistant":
62
+ return { ...base, type: RUN_EVENT_TYPES.ASSISTANT };
63
+ case "tool_call":
64
+ return { ...base, type: RUN_EVENT_TYPES.TOOL_CALL };
65
+ case "tool_result":
66
+ return { ...base, type: RUN_EVENT_TYPES.TOOL_RESULT };
67
+ case "result":
68
+ return { ...base, type: RUN_EVENT_TYPES.RESULT };
69
+ case "token_usage":
70
+ case "usage":
71
+ return { ...base, type: RUN_EVENT_TYPES.TOKEN_USAGE };
72
+ case "diff":
73
+ case "diff_summary":
74
+ return { ...base, type: RUN_EVENT_TYPES.DIFF_SUMMARY };
75
+ default:
76
+ return {
77
+ ...base,
78
+ type: RUN_EVENT_TYPES.SYSTEM,
79
+ data: sanitizeEventData({ rawType: raw.type, payload: extractEventData(raw) }, { captureTranscript })
80
+ };
81
+ }
82
+ }
83
+
84
+ function extractEventData(raw) {
85
+ const { type: _type, ...rest } = raw;
86
+ return rest;
87
+ }
88
+
89
+ export function applyEventToMetadata(metadata, event) {
90
+ const next = { ...metadata, updatedAt: event.timestamp ?? new Date().toISOString() };
91
+
92
+ switch (event.type) {
93
+ case RUN_EVENT_TYPES.TOOL_CALL: {
94
+ const tool = event.data?.tool_name ?? event.data?.name ?? event.data?.tool ?? event.data?.toolName ?? "unknown";
95
+ if (!next.tools.includes(tool)) {
96
+ next.tools = [...next.tools, tool];
97
+ }
98
+ break;
99
+ }
100
+ case RUN_EVENT_TYPES.STDERR:
101
+ case RUN_EVENT_TYPES.STDOUT: {
102
+ const command = event.data?.command;
103
+ if (command && !next.commands.includes(command)) {
104
+ next.commands = [...next.commands, command];
105
+ }
106
+ break;
107
+ }
108
+ case RUN_EVENT_TYPES.TOKEN_USAGE: {
109
+ next.tokenUsage = {
110
+ input: event.data?.inputTokens ?? event.data?.input ?? next.tokenUsage?.input ?? null,
111
+ output: event.data?.outputTokens ?? event.data?.output ?? next.tokenUsage?.output ?? null,
112
+ total: event.data?.totalTokens ?? event.data?.total ?? next.tokenUsage?.total ?? null
113
+ };
114
+ if (event.data?.cost != null) {
115
+ next.cost = event.data.cost;
116
+ }
117
+ break;
118
+ }
119
+ case RUN_EVENT_TYPES.DIFF_SUMMARY: {
120
+ next.diffSummary = {
121
+ filesChanged: event.data?.filesChanged ?? event.data?.files ?? null,
122
+ insertions: event.data?.insertions ?? null,
123
+ deletions: event.data?.deletions ?? null
124
+ };
125
+ break;
126
+ }
127
+ default:
128
+ break;
129
+ }
130
+
131
+ return next;
132
+ }
133
+
134
+ export function transitionRunState(metadata, nextState, { exitCode = null, error = null } = {}) {
135
+ const now = new Date().toISOString();
136
+ return {
137
+ ...metadata,
138
+ state: nextState,
139
+ exitCode: exitCode ?? metadata.exitCode,
140
+ error: error ?? metadata.error,
141
+ updatedAt: now,
142
+ completedAt: now
143
+ };
144
+ }
@@ -0,0 +1,71 @@
1
+ import { existsSync } from "node:fs";
2
+ import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
3
+ import { runPaths } from "../paths.js";
4
+ import { getRunsDir, readRunState } from "./run-store.js";
5
+ import { isActiveRunState } from "./run-types.js";
6
+ import { isWithinStartingGrace } from "./run-starting.js";
7
+ import { readSupervisorLock } from "./run-supervisor-lock.js";
8
+
9
+ function handoffPath(homeDir, runId) {
10
+ return `${runPaths(homeDir, runId).runDir}/handoff.json`;
11
+ }
12
+
13
+ export async function writeRunHandoff(homeDir, runId, payload) {
14
+ const { runDir } = runPaths(homeDir, runId);
15
+ await mkdir(runDir, { recursive: true });
16
+ await writeFile(handoffPath(homeDir, runId), `${JSON.stringify(payload)}\n`, "utf8");
17
+ }
18
+
19
+ export async function consumeRunHandoff(homeDir, runId) {
20
+ const path = handoffPath(homeDir, runId);
21
+ if (!existsSync(path)) {
22
+ throw new Error(`Missing run handoff for "${runId}".`);
23
+ }
24
+
25
+ const payload = JSON.parse(await readFile(path, "utf8"));
26
+ await rm(path, { force: true });
27
+ return payload;
28
+ }
29
+
30
+ export async function deleteRunHandoff(homeDir, runId) {
31
+ await rm(handoffPath(homeDir, runId), { force: true });
32
+ }
33
+
34
+ export function hasRunHandoff(homeDir, runId) {
35
+ return existsSync(handoffPath(homeDir, runId));
36
+ }
37
+
38
+ export async function cleanupStaleHandoffs(homeDir, { exceptRunIds = [], isRunAliveImpl } = {}) {
39
+ const except = new Set(exceptRunIds);
40
+ const runsDir = getRunsDir(homeDir);
41
+ if (!existsSync(runsDir)) return [];
42
+
43
+ const cleaned = [];
44
+ const entries = await readdir(runsDir, { withFileTypes: true });
45
+
46
+ for (const entry of entries) {
47
+ if (!entry.isDirectory()) continue;
48
+
49
+ const runId = entry.name;
50
+ if (except.has(runId)) continue;
51
+ if (!hasRunHandoff(homeDir, runId)) continue;
52
+
53
+ const state = await readRunState(homeDir, runId);
54
+ const lock = await readSupervisorLock(homeDir, runId);
55
+
56
+ if (isWithinStartingGrace(state, lock)) {
57
+ continue;
58
+ }
59
+
60
+ if (isRunAliveImpl && state && await isRunAliveImpl(homeDir, state)) {
61
+ continue;
62
+ }
63
+
64
+ if (!state || !isActiveRunState(state.state)) {
65
+ await deleteRunHandoff(homeDir, runId);
66
+ cleaned.push(runId);
67
+ }
68
+ }
69
+
70
+ return cleaned;
71
+ }
@@ -0,0 +1,28 @@
1
+ export function isProcessAlive(pid) {
2
+ if (pid == null || pid <= 0) return false;
3
+
4
+ try {
5
+ process.kill(pid, 0);
6
+ return true;
7
+ } catch (error) {
8
+ return error && typeof error === "object" && error.code === "EPERM";
9
+ }
10
+ }
11
+
12
+ export async function isRunAlive(homeDir, run, { readSupervisorLockImpl } = {}) {
13
+ if (run.pid && isProcessAlive(run.pid)) {
14
+ return true;
15
+ }
16
+
17
+ if (readSupervisorLockImpl) {
18
+ const lock = await readSupervisorLockImpl(homeDir, run.runId);
19
+ if (lock?.agentPid && isProcessAlive(lock.agentPid)) {
20
+ return true;
21
+ }
22
+ if (lock?.supervisorPid && isProcessAlive(lock.supervisorPid)) {
23
+ return true;
24
+ }
25
+ }
26
+
27
+ return false;
28
+ }
@@ -0,0 +1,271 @@
1
+ import { spawn } from "node:child_process";
2
+ import { createRunId, createRunMetadata, RUN_STATES } from "./run-types.js";
3
+ import { createRunEvent, transitionRunState } from "./run-events.js";
4
+ import {
5
+ appendRunEvent,
6
+ appendRunStartedEvent,
7
+ createRunRecord,
8
+ readRunState,
9
+ reconcileActiveRuns,
10
+ writeRunState
11
+ } from "./run-store.js";
12
+ import { resolveExecutionAdapter } from "./execution-adapters/index.js";
13
+ import { resolveProfileAgents } from "../profile.js";
14
+ import { resolveRuntimeOptions } from "./run-profile.js";
15
+ import { cleanupStaleHandoffs, deleteRunHandoff, writeRunHandoff } from "./run-handoff.js";
16
+ import { isRunAlive } from "./run-liveness.js";
17
+ import { readSupervisorLock, writeSupervisorLock } from "./run-supervisor-lock.js";
18
+ import { writeCancelSignal } from "./run-cancel-signal.js";
19
+ import { isWithinStartingGrace } from "./run-starting.js";
20
+ import {
21
+ forkDetachedSupervisor,
22
+ readSupervisorLockForRun,
23
+ supervisePreparedRun
24
+ } from "./run-supervisor.js";
25
+
26
+ const activeProcesses = new Map();
27
+ const cancelledRuns = new Set();
28
+
29
+ export function getActiveProcess(runId) {
30
+ return activeProcesses.get(runId) ?? null;
31
+ }
32
+
33
+ export function listActiveRunIds() {
34
+ return [...activeProcesses.keys()];
35
+ }
36
+
37
+ async function isRunSupervisedAlive(homeDir, run) {
38
+ if (listActiveRunIds().includes(run.runId)) {
39
+ return true;
40
+ }
41
+
42
+ const lock = await readSupervisorLock(homeDir, run.runId);
43
+ if (isWithinStartingGrace(run, lock)) {
44
+ return true;
45
+ }
46
+
47
+ return isRunAlive(homeDir, run, { readSupervisorLockImpl: readSupervisorLockForRun });
48
+ }
49
+
50
+ export async function recoverRuns(homeDir) {
51
+ const interrupted = await reconcileActiveRuns(homeDir, {
52
+ isRunAliveImpl: isRunSupervisedAlive
53
+ });
54
+ await cleanupStaleHandoffs(homeDir, {
55
+ exceptRunIds: listActiveRunIds(),
56
+ isRunAliveImpl: isRunSupervisedAlive
57
+ });
58
+ return interrupted;
59
+ }
60
+
61
+ async function prepareRun({
62
+ homeDir,
63
+ agentId,
64
+ task,
65
+ cwd,
66
+ model = null,
67
+ permissions = [],
68
+ captureTranscript = false,
69
+ cliVersion,
70
+ profile = null
71
+ }) {
72
+ const adapter = resolveExecutionAdapter(agentId);
73
+ const availability = adapter.availability({ cwd });
74
+
75
+ if (!availability.available) {
76
+ throw new Error(availability.reason ?? `${adapter.label} is not available.`);
77
+ }
78
+
79
+ if (!availability.launchable) {
80
+ throw new Error(
81
+ availability.reason
82
+ ?? `${adapter.label} is not launchable for auditable Kairo runs in v1.`
83
+ );
84
+ }
85
+
86
+ const runId = createRunId();
87
+ const metadata = createRunMetadata({
88
+ runId,
89
+ agentId,
90
+ provider: adapter.label,
91
+ model,
92
+ task,
93
+ cwd,
94
+ permissions,
95
+ captureTranscript,
96
+ cliVersion,
97
+ profileSources: profile?.sources ?? null
98
+ });
99
+
100
+ await createRunRecord(homeDir, metadata);
101
+ await appendRunStartedEvent(homeDir, metadata);
102
+ await writeRunHandoff(homeDir, runId, {
103
+ agentId,
104
+ task,
105
+ cwd,
106
+ model,
107
+ permissions,
108
+ captureTranscript,
109
+ cliVersion,
110
+ profile: profile?.profile ?? null
111
+ });
112
+
113
+ return { runId, metadata };
114
+ }
115
+
116
+ export async function startRun({
117
+ homeDir,
118
+ agentId,
119
+ task,
120
+ cwd,
121
+ model = null,
122
+ permissions = [],
123
+ captureTranscript = false,
124
+ cliVersion,
125
+ profile = null,
126
+ follow = false,
127
+ timeoutMs = null,
128
+ wait = true,
129
+ spawnImpl = spawn,
130
+ forkDetachedSupervisorImpl = forkDetachedSupervisor
131
+ }) {
132
+ const { runId, metadata } = await prepareRun({
133
+ homeDir,
134
+ agentId,
135
+ task,
136
+ cwd,
137
+ model,
138
+ permissions,
139
+ captureTranscript,
140
+ cliVersion,
141
+ profile
142
+ });
143
+
144
+ if (!wait) {
145
+ const startingAt = new Date().toISOString();
146
+ await writeRunState(homeDir, {
147
+ ...metadata,
148
+ state: RUN_STATES.STARTING,
149
+ updatedAt: startingAt
150
+ });
151
+ await writeSupervisorLock(homeDir, runId, {
152
+ startingAt,
153
+ supervisorPid: null,
154
+ agentPid: null,
155
+ lastHeartbeat: startingAt
156
+ });
157
+
158
+ let supervisorPid;
159
+ try {
160
+ supervisorPid = forkDetachedSupervisorImpl({ homeDir, runId });
161
+ if (!supervisorPid) {
162
+ throw new Error("Failed to start detached supervisor.");
163
+ }
164
+ await writeSupervisorLock(homeDir, runId, {
165
+ startingAt,
166
+ supervisorPid,
167
+ agentPid: null,
168
+ startedAt: startingAt,
169
+ lastHeartbeat: startingAt
170
+ });
171
+ } catch (error) {
172
+ await deleteRunHandoff(homeDir, runId);
173
+ const failed = transitionRunState(
174
+ { ...metadata, state: RUN_STATES.STARTING },
175
+ RUN_STATES.FAILED,
176
+ { error: error instanceof Error ? error.message : String(error) }
177
+ );
178
+ await writeRunState(homeDir, failed);
179
+ throw error;
180
+ }
181
+
182
+ const detachedMetadata = {
183
+ ...metadata,
184
+ state: RUN_STATES.STARTING,
185
+ supervisorPid,
186
+ updatedAt: startingAt
187
+ };
188
+ await writeRunState(homeDir, detachedMetadata);
189
+ return {
190
+ runId,
191
+ metadata: detachedMetadata,
192
+ completion: null
193
+ };
194
+ }
195
+
196
+ const completion = supervisePreparedRun({
197
+ homeDir,
198
+ runId,
199
+ follow,
200
+ timeoutMs,
201
+ spawnImpl,
202
+ cancelledRuns,
203
+ activeProcesses
204
+ });
205
+
206
+ return {
207
+ runId,
208
+ metadata,
209
+ completion
210
+ };
211
+ }
212
+
213
+ export async function stopRun(homeDir, runId, { signal = "SIGTERM" } = {}) {
214
+ const state = await readRunState(homeDir, runId);
215
+ if (!state) {
216
+ throw new Error(`Run "${runId}" not found.`);
217
+ }
218
+
219
+ if (state.state === RUN_STATES.COMPLETED || state.state === RUN_STATES.FAILED || state.state === RUN_STATES.CANCELLED) {
220
+ return state;
221
+ }
222
+
223
+ const lock = await readSupervisorLock(homeDir, runId);
224
+ const child = activeProcesses.get(runId);
225
+
226
+ if (child) {
227
+ child.kill(signal);
228
+ } else {
229
+ const targets = [lock?.agentPid, state.pid, lock?.supervisorPid].filter(Boolean);
230
+ for (const pid of targets) {
231
+ try {
232
+ process.kill(pid, signal);
233
+ } catch {
234
+ // Process may already be gone.
235
+ }
236
+ }
237
+ }
238
+
239
+ await writeCancelSignal(homeDir, runId, {
240
+ requested: true,
241
+ signal,
242
+ requestedAt: new Date().toISOString()
243
+ });
244
+ await deleteRunHandoff(homeDir, runId);
245
+
246
+ const metadata = transitionRunState(state, RUN_STATES.CANCELLED, {
247
+ error: "Run cancelled by user."
248
+ });
249
+ cancelledRuns.add(runId);
250
+ await writeRunState(homeDir, metadata);
251
+ await appendRunEvent(homeDir, createRunEvent({
252
+ runId,
253
+ type: "run.cancelled",
254
+ data: { signal }
255
+ }));
256
+
257
+ activeProcesses.delete(runId);
258
+ return metadata;
259
+ }
260
+
261
+ export async function resolveRunAgent(profile, requestedAgent, detectedAgentIds = []) {
262
+ const agents = resolveProfileAgents(profile?.profile ?? profile, detectedAgentIds);
263
+ const runtime = resolveRuntimeOptions(profile, { agentId: requestedAgent });
264
+ const agentId = runtime.agentId ?? agents[0] ?? null;
265
+
266
+ if (!agentId) {
267
+ throw new Error("No agent specified and no default agent available.");
268
+ }
269
+
270
+ return { agentId, runtime };
271
+ }
@@ -0,0 +1,93 @@
1
+ import { resolveProfileAgents } from "../profile.js";
2
+ import { EXECUTION_ADAPTER_IDS } from "./execution-adapters/index.js";
3
+
4
+ export function resolveRuntimeOptions(profileResolved, overrides = {}) {
5
+ const profile = profileResolved?.profile ?? profileResolved ?? {};
6
+ const agentAliases = profile.agentAliases ?? {};
7
+ const modelAliases = profile.modelAliases ?? {};
8
+
9
+ let agentId = overrides.agentId ?? profile.defaultRuntimeAgent ?? null;
10
+ if (agentId && agentAliases[agentId]) {
11
+ agentId = agentAliases[agentId];
12
+ }
13
+
14
+ let model = overrides.model ?? profile.preferredModel ?? profile.defaultRuntimeModel ?? null;
15
+ if (model && modelAliases[model]) {
16
+ model = modelAliases[model];
17
+ }
18
+
19
+ const permissions = overrides.permissions
20
+ ?? profile.defaultPermissions
21
+ ?? [];
22
+
23
+ const captureTranscript = overrides.captureTranscript
24
+ ?? profile.captureTranscript
25
+ ?? false;
26
+
27
+ return {
28
+ agentId,
29
+ model,
30
+ permissions: Array.isArray(permissions) ? permissions : [],
31
+ captureTranscript: captureTranscript === true
32
+ };
33
+ }
34
+
35
+ export function validateRuntimeProfile(profile) {
36
+ const agentAliases = profile.agentAliases;
37
+ if (agentAliases != null) {
38
+ if (typeof agentAliases !== "object" || Array.isArray(agentAliases)) {
39
+ throw new Error("Profile agentAliases must be an object.");
40
+ }
41
+ for (const target of Object.values(agentAliases)) {
42
+ if (!EXECUTION_ADAPTER_IDS.includes(target)) {
43
+ throw new Error(`Unknown agent alias target "${target}".`);
44
+ }
45
+ }
46
+ }
47
+
48
+ const modelAliases = profile.modelAliases;
49
+ if (modelAliases != null) {
50
+ if (typeof modelAliases !== "object" || Array.isArray(modelAliases)) {
51
+ throw new Error("Profile modelAliases must be an object.");
52
+ }
53
+ }
54
+
55
+ const defaultPermissions = profile.defaultPermissions;
56
+ if (defaultPermissions != null && !Array.isArray(defaultPermissions)) {
57
+ throw new Error("Profile defaultPermissions must be an array.");
58
+ }
59
+
60
+ if (
61
+ profile.defaultRuntimeAgent != null
62
+ && !EXECUTION_ADAPTER_IDS.includes(profile.defaultRuntimeAgent)
63
+ && !Object.keys(agentAliases ?? {}).includes(profile.defaultRuntimeAgent)
64
+ ) {
65
+ throw new Error(`Unknown defaultRuntimeAgent "${profile.defaultRuntimeAgent}".`);
66
+ }
67
+
68
+ if (profile.captureTranscript != null && typeof profile.captureTranscript !== "boolean") {
69
+ throw new Error("Profile captureTranscript must be a boolean.");
70
+ }
71
+ }
72
+
73
+ export function resolveAgentFromProfile(profileResolved, requestedAgent) {
74
+ const runtime = resolveRuntimeOptions(profileResolved, { agentId: requestedAgent });
75
+ if (!runtime.agentId) {
76
+ throw new Error(`Unknown or missing agent. Use ${EXECUTION_ADAPTER_IDS.join(", ")}.`);
77
+ }
78
+ if (!EXECUTION_ADAPTER_IDS.includes(runtime.agentId)) {
79
+ throw new Error(`Agent "${runtime.agentId}" is not supported for Kairo runs.`);
80
+ }
81
+ return runtime;
82
+ }
83
+
84
+ export const RUNTIME_PROFILE_KEYS = new Set([
85
+ "agentAliases",
86
+ "modelAliases",
87
+ "defaultPermissions",
88
+ "defaultRuntimeAgent",
89
+ "defaultRuntimeModel",
90
+ "captureTranscript"
91
+ ]);
92
+
93
+ export const SUPPORTED_RUN_AGENTS = [...EXECUTION_ADAPTER_IDS];
@@ -0,0 +1,66 @@
1
+ import { isForbiddenSecretKey, normalizeProfileKey } from "../profile.js";
2
+
3
+ const REDACTED = "[REDACTED]";
4
+
5
+ const SECRET_VALUE_PATTERN = /^(sk-[A-Za-z0-9]|sk-or-|gh[pousr]_|xox[baprs]-|AKIA[0-9A-Z]{16}\b|Bearer\s+\S+|eyJ[A-Za-z0-9_-]+\.)|-----BEGIN [A-Z ]*PRIVATE KEY-----/i;
6
+
7
+ const SENSITIVE_ENV_KEYS = new Set([
8
+ "ANTHROPIC_API_KEY",
9
+ "OPENAI_API_KEY",
10
+ "CURSOR_API_KEY",
11
+ "OPENROUTER_API_KEY",
12
+ "GITHUB_TOKEN",
13
+ "GH_TOKEN",
14
+ "AWS_SECRET_ACCESS_KEY",
15
+ "AWS_ACCESS_KEY_ID"
16
+ ]);
17
+
18
+ export function redactString(value) {
19
+ if (typeof value !== "string" || value.length === 0) return value;
20
+ if (SECRET_VALUE_PATTERN.test(value)) return REDACTED;
21
+ return value;
22
+ }
23
+
24
+ export function redactObject(value, { allowTranscript = false } = {}) {
25
+ if (value == null) return value;
26
+ if (typeof value === "string") return redactString(value);
27
+ if (Array.isArray(value)) {
28
+ return value.map((item) => redactObject(item, { allowTranscript }));
29
+ }
30
+ if (typeof value !== "object") return value;
31
+
32
+ const result = {};
33
+ for (const [key, nested] of Object.entries(value)) {
34
+ const normalizedKey = normalizeProfileKey(key);
35
+
36
+ if (!allowTranscript && (normalizedKey === "prompt" || normalizedKey === "response" || normalizedKey === "content")) {
37
+ result[key] = REDACTED;
38
+ continue;
39
+ }
40
+
41
+ if (isForbiddenSecretKey(key) || SENSITIVE_ENV_KEYS.has(key)) {
42
+ result[key] = REDACTED;
43
+ continue;
44
+ }
45
+
46
+ result[key] = redactObject(nested, { allowTranscript });
47
+ }
48
+
49
+ return result;
50
+ }
51
+
52
+ export function redactEnv(env = process.env) {
53
+ const result = {};
54
+ for (const [key, value] of Object.entries(env)) {
55
+ if (SENSITIVE_ENV_KEYS.has(key) || isForbiddenSecretKey(key)) {
56
+ result[key] = REDACTED;
57
+ } else {
58
+ result[key] = value;
59
+ }
60
+ }
61
+ return result;
62
+ }
63
+
64
+ export function shouldPersistTranscript(captureTranscript) {
65
+ return captureTranscript === true;
66
+ }
@@ -0,0 +1,13 @@
1
+ import { RUN_STATES, STARTING_GRACE_MS } from "./run-types.js";
2
+
3
+ export function isWithinStartingGrace(run, lock, nowMs = Date.now()) {
4
+ if (run?.state !== RUN_STATES.STARTING && run?.state !== RUN_STATES.PENDING) {
5
+ return false;
6
+ }
7
+
8
+ const anchor = lock?.startingAt ?? lock?.startedAt ?? run?.startedAt;
9
+ if (!anchor) return false;
10
+
11
+ const elapsed = nowMs - new Date(anchor).getTime();
12
+ return elapsed >= 0 && elapsed < STARTING_GRACE_MS;
13
+ }