@kal-elsam/kairo-runtime 0.2.1 → 0.2.2
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/package.json +1 -1
- package/scripts/runtime-mvp-smoke.sh +152 -0
- package/src/cli.js +77 -4
- package/src/global/ink/orchestrator-app.js +355 -81
- package/src/global/ink/orchestrator-state.js +196 -65
- package/src/global/orchestrator.js +8 -39
- package/src/global/paths.js +13 -0
- package/src/global/profile.js +17 -2
- package/src/global/runtime/execution-adapters/claude.js +65 -0
- package/src/global/runtime/execution-adapters/codex.js +78 -0
- package/src/global/runtime/execution-adapters/create-execution-adapter.js +92 -0
- package/src/global/runtime/execution-adapters/cursor.js +104 -0
- package/src/global/runtime/execution-adapters/index.js +36 -0
- package/src/global/runtime/execution-adapters/opencode.js +38 -0
- package/src/global/runtime/run-cancel-signal.js +29 -0
- package/src/global/runtime/run-cli.js +221 -0
- package/src/global/runtime/run-events.js +144 -0
- package/src/global/runtime/run-handoff.js +71 -0
- package/src/global/runtime/run-liveness.js +28 -0
- package/src/global/runtime/run-manager.js +271 -0
- package/src/global/runtime/run-profile.js +93 -0
- package/src/global/runtime/run-redact.js +66 -0
- package/src/global/runtime/run-starting.js +13 -0
- package/src/global/runtime/run-store.js +159 -0
- package/src/global/runtime/run-supervisor-lock.js +37 -0
- package/src/global/runtime/run-supervisor-worker.js +12 -0
- package/src/global/runtime/run-supervisor.js +289 -0
- package/src/global/runtime/run-types.js +117 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kal-elsam/kairo-runtime",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Kairo Runtime — local agent operating system for Codex, Cursor, Claude, Gemini, Copilot, Engram, and Graphify.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://github.com/Kal-elSam/harness#readme",
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
KAIRO=(node "$ROOT/bin/kairo.js")
|
|
6
|
+
WORKSPACE="${SMOKE_WORKSPACE:-$ROOT}"
|
|
7
|
+
HOME_DIR="${HARNESS_HOME:-$(mktemp -d /tmp/kairo-runtime-smoke-XXXXXX)}"
|
|
8
|
+
export HARNESS_HOME="$HOME_DIR"
|
|
9
|
+
export HARNESS_INK=0
|
|
10
|
+
|
|
11
|
+
PROMPT_A="List the files in the current directory and summarize in one sentence."
|
|
12
|
+
PROMPT_B="Reply with exactly the single word OK and nothing else."
|
|
13
|
+
PROMPT_SECRET="SMOKE_SECRET_PROMPT_$(date +%s)_do_not_persist"
|
|
14
|
+
SMOKE_MODEL="${SMOKE_MODEL:-}"
|
|
15
|
+
|
|
16
|
+
MODEL_ARGS=()
|
|
17
|
+
if [[ -n "$SMOKE_MODEL" ]]; then
|
|
18
|
+
MODEL_ARGS=(--model "$SMOKE_MODEL")
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
log() { printf '[smoke] %s\n' "$*"; }
|
|
22
|
+
|
|
23
|
+
assert_no_prompt() {
|
|
24
|
+
local dir=$1 prompt=$2
|
|
25
|
+
if grep -qF "$prompt" "$dir/state.json" 2>/dev/null; then
|
|
26
|
+
echo "FAIL: prompt found in state.json for $dir" >&2
|
|
27
|
+
return 1
|
|
28
|
+
fi
|
|
29
|
+
if grep -qF "$prompt" "$dir/events.jsonl" 2>/dev/null; then
|
|
30
|
+
echo "FAIL: prompt found in events.jsonl for $dir" >&2
|
|
31
|
+
return 1
|
|
32
|
+
fi
|
|
33
|
+
if [[ -f "$dir/handoff.json" ]]; then
|
|
34
|
+
echo "FAIL: handoff.json still present for $dir" >&2
|
|
35
|
+
return 1
|
|
36
|
+
fi
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
log "HARNESS_HOME=$HOME_DIR"
|
|
40
|
+
log "WORKSPACE=$WORKSPACE"
|
|
41
|
+
if [[ -n "$SMOKE_MODEL" ]]; then
|
|
42
|
+
log "SMOKE_MODEL=$SMOKE_MODEL"
|
|
43
|
+
else
|
|
44
|
+
log "SMOKE_MODEL=(not set — Case 2 may fail if Codex default model is incompatible)"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# --- Case 1: detached run + cross-process list/show/stop ---
|
|
48
|
+
log "=== Case 1: --no-wait + cross-process supervision ==="
|
|
49
|
+
START_A=$(python3 -c 'import time; print(int(time.time()*1000))')
|
|
50
|
+
OUT_A=$("${KAIRO[@]}" run --agent codex --task "$PROMPT_A" --cwd "$WORKSPACE" "${MODEL_ARGS[@]}" --no-wait 2>&1)
|
|
51
|
+
END_A=$(python3 -c 'import time; print(int(time.time()*1000))')
|
|
52
|
+
DUR_A=$((END_A - START_A))
|
|
53
|
+
|
|
54
|
+
RUN_ID=$(printf '%s\n' "$OUT_A" | sed -n 's/.*\(run_[a-z0-9_]*\).*/\1/p' | head -1)
|
|
55
|
+
if [[ -z "$RUN_ID" ]]; then
|
|
56
|
+
echo "FAIL: could not parse runId from output:" >&2
|
|
57
|
+
echo "$OUT_A" >&2
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
log "Case 1 runId=$RUN_ID parent_return_ms=$DUR_A"
|
|
62
|
+
if (( DUR_A > 5000 )); then
|
|
63
|
+
echo "FAIL: --no-wait took ${DUR_A}ms (>5000ms)" >&2
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
sleep 1
|
|
68
|
+
LIST_B=$("${KAIRO[@]}" runs list --cwd "$WORKSPACE" 2>&1)
|
|
69
|
+
if printf '%s\n' "$LIST_B" | grep -q interrupted; then
|
|
70
|
+
echo "FAIL: runs list shows interrupted" >&2
|
|
71
|
+
echo "$LIST_B" >&2
|
|
72
|
+
exit 1
|
|
73
|
+
fi
|
|
74
|
+
if ! printf '%s\n' "$LIST_B" | grep -q "$RUN_ID"; then
|
|
75
|
+
echo "FAIL: run not listed" >&2
|
|
76
|
+
echo "$LIST_B" >&2
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
if ! printf '%s\n' "$LIST_B" | grep -Eq "running|starting"; then
|
|
80
|
+
echo "FAIL: run not active in list" >&2
|
|
81
|
+
echo "$LIST_B" >&2
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
SHOW_B=$("${KAIRO[@]}" runs show "$RUN_ID" --cwd "$WORKSPACE" 2>&1)
|
|
86
|
+
if printf '%s\n' "$SHOW_B" | grep -q interrupted; then
|
|
87
|
+
echo "FAIL: runs show reports interrupted" >&2
|
|
88
|
+
echo "$SHOW_B" >&2
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
START_STOP=$(python3 -c 'import time; print(int(time.time()*1000))')
|
|
93
|
+
STOP_B=$("${KAIRO[@]}" runs stop "$RUN_ID" --cwd "$WORKSPACE" 2>&1)
|
|
94
|
+
END_STOP=$(python3 -c 'import time; print(int(time.time()*1000))')
|
|
95
|
+
DUR_STOP=$((END_STOP - START_STOP))
|
|
96
|
+
|
|
97
|
+
sleep 1
|
|
98
|
+
FINAL_A=$("${KAIRO[@]}" runs show "$RUN_ID" --json --cwd "$WORKSPACE")
|
|
99
|
+
STATE_A=$(printf '%s\n' "$FINAL_A" | node -e 'let s="";process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{const j=JSON.parse(s);console.log(j.metadata.state)})')
|
|
100
|
+
if [[ "$STATE_A" != "cancelled" ]]; then
|
|
101
|
+
echo "FAIL: expected cancelled, got $STATE_A" >&2
|
|
102
|
+
exit 1
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
RUN_DIR_A="$HOME_DIR/.harness/runs/$RUN_ID"
|
|
106
|
+
assert_no_prompt "$RUN_DIR_A" "$PROMPT_A"
|
|
107
|
+
log "Case 1 PASS state=$STATE_A stop_ms=$DUR_STOP"
|
|
108
|
+
|
|
109
|
+
# --- Case 2: normal completion (must be completed) ---
|
|
110
|
+
log "=== Case 2: wait for normal completion ==="
|
|
111
|
+
START_B=$(python3 -c 'import time; print(int(time.time()*1000))')
|
|
112
|
+
OUT_B=$("${KAIRO[@]}" run --agent codex --task "$PROMPT_B" --cwd "$WORKSPACE" --permissions yolo "${MODEL_ARGS[@]}" 2>&1)
|
|
113
|
+
END_B=$(python3 -c 'import time; print(int(time.time()*1000))')
|
|
114
|
+
DUR_B=$((END_B - START_B))
|
|
115
|
+
|
|
116
|
+
RUN_ID_B=$(printf '%s\n' "$OUT_B" | sed -n 's/.*\(run_[a-z0-9_]*\).*/\1/p' | head -1)
|
|
117
|
+
FINAL_B=$("${KAIRO[@]}" runs show "$RUN_ID_B" --json --cwd "$WORKSPACE")
|
|
118
|
+
STATE_B=$(printf '%s\n' "$FINAL_B" | node -e 'let s="";process.stdin.on("data",d=>s+=d);process.stdin.on("end",()=>{const j=JSON.parse(s);console.log(j.metadata.state)})')
|
|
119
|
+
|
|
120
|
+
if [[ "$STATE_B" != "completed" ]]; then
|
|
121
|
+
echo "FAIL: expected completed, got $STATE_B" >&2
|
|
122
|
+
echo "$OUT_B" >&2
|
|
123
|
+
if [[ -z "$SMOKE_MODEL" ]]; then
|
|
124
|
+
echo "Hint: set SMOKE_MODEL to a Codex model compatible with your account/CLI version." >&2
|
|
125
|
+
fi
|
|
126
|
+
exit 1
|
|
127
|
+
fi
|
|
128
|
+
|
|
129
|
+
RUN_DIR_B="$HOME_DIR/.harness/runs/$RUN_ID_B"
|
|
130
|
+
assert_no_prompt "$RUN_DIR_B" "$PROMPT_B"
|
|
131
|
+
log "Case 2 PASS runId=$RUN_ID_B state=$STATE_B duration_ms=$DUR_B"
|
|
132
|
+
|
|
133
|
+
# --- Case 3: privacy spot-check with unique secret prompt ---
|
|
134
|
+
log "=== Case 3: privacy secret prompt ==="
|
|
135
|
+
START_C=$(python3 -c 'import time; print(int(time.time()*1000))')
|
|
136
|
+
OUT_C=$("${KAIRO[@]}" run --agent codex --task "$PROMPT_SECRET" --cwd "$WORKSPACE" --permissions yolo "${MODEL_ARGS[@]}" --no-wait 2>&1)
|
|
137
|
+
RUN_ID_C=$(printf '%s\n' "$OUT_C" | sed -n 's/.*\(run_[a-z0-9_]*\).*/\1/p' | head -1)
|
|
138
|
+
sleep 2
|
|
139
|
+
"${KAIRO[@]}" runs stop "$RUN_ID_C" --cwd "$WORKSPACE" >/dev/null 2>&1 || true
|
|
140
|
+
sleep 1
|
|
141
|
+
RUN_DIR_C="$HOME_DIR/.harness/runs/$RUN_ID_C"
|
|
142
|
+
assert_no_prompt "$RUN_DIR_C" "$PROMPT_SECRET"
|
|
143
|
+
END_C=$(python3 -c 'import time; print(int(time.time()*1000))')
|
|
144
|
+
DUR_C=$((END_C - START_C))
|
|
145
|
+
log "Case 3 PASS runId=$RUN_ID_C duration_ms=$DUR_C"
|
|
146
|
+
|
|
147
|
+
printf '\n=== SMOKE SUMMARY ===\n'
|
|
148
|
+
printf 'Case 1 (detach+stop): runId=%s state=%s parent_ms=%s stop_ms=%s\n' "$RUN_ID" "$STATE_A" "$DUR_A" "$DUR_STOP"
|
|
149
|
+
printf 'Case 2 (complete): runId=%s state=%s duration_ms=%s model=%s\n' "$RUN_ID_B" "$STATE_B" "$DUR_B" "${SMOKE_MODEL:-default}"
|
|
150
|
+
printf 'Case 3 (privacy): runId=%s duration_ms=%s\n' "$RUN_ID_C" "$DUR_C"
|
|
151
|
+
printf 'HARNESS_HOME=%s\n' "$HOME_DIR"
|
|
152
|
+
printf 'ALL PASS\n'
|
package/src/cli.js
CHANGED
|
@@ -32,6 +32,7 @@ import { resolveHomeDir } from "./global/paths.js";
|
|
|
32
32
|
import { runWorkspaceDetect, runWorkspaceDoctor, runWorkspaceInit, runWorkspaceUpdate } from "./workspace-cli.js";
|
|
33
33
|
import { runOrchestratorDiagnostics, runOrchestratorShell } from "./global/orchestrator.js";
|
|
34
34
|
import { runIntelligenceCli } from "./global/intelligence-cli.js";
|
|
35
|
+
import { runGlobalRun, runGlobalRuns } from "./global/runtime/run-cli.js";
|
|
35
36
|
import {
|
|
36
37
|
LEGACY_PACKAGE_NAME,
|
|
37
38
|
PACKAGE_NAME,
|
|
@@ -86,6 +87,12 @@ export async function runCli(argv) {
|
|
|
86
87
|
json: optionsWithPolicy.json
|
|
87
88
|
});
|
|
88
89
|
return;
|
|
90
|
+
case "run":
|
|
91
|
+
await runGlobalRun(optionsWithPolicy, packageManifest);
|
|
92
|
+
return;
|
|
93
|
+
case "runs":
|
|
94
|
+
await runGlobalRuns(optionsWithPolicy, packageManifest);
|
|
95
|
+
return;
|
|
89
96
|
case "intelligence":
|
|
90
97
|
await runIntelligenceCli(optionsWithPolicy, packageManifest);
|
|
91
98
|
return;
|
|
@@ -333,6 +340,17 @@ export function parseArgs(argv) {
|
|
|
333
340
|
intelligenceTask: null,
|
|
334
341
|
intelligencePrompt: null,
|
|
335
342
|
intelligencePaths: [],
|
|
343
|
+
runsAction: null,
|
|
344
|
+
runId: null,
|
|
345
|
+
agent: null,
|
|
346
|
+
task: null,
|
|
347
|
+
model: null,
|
|
348
|
+
permissions: null,
|
|
349
|
+
captureTranscript: false,
|
|
350
|
+
follow: false,
|
|
351
|
+
wait: true,
|
|
352
|
+
activeOnly: false,
|
|
353
|
+
timeoutMs: null,
|
|
336
354
|
includePrivate: false,
|
|
337
355
|
cloudConsent: false
|
|
338
356
|
};
|
|
@@ -349,6 +367,10 @@ export function parseArgs(argv) {
|
|
|
349
367
|
parseHistoryAction(args, options);
|
|
350
368
|
}
|
|
351
369
|
|
|
370
|
+
if (command === "runs") {
|
|
371
|
+
parseRunsAction(args, options);
|
|
372
|
+
}
|
|
373
|
+
|
|
352
374
|
if (command === "intelligence") {
|
|
353
375
|
parseIntelligenceAction(args, options);
|
|
354
376
|
}
|
|
@@ -414,12 +436,27 @@ export function parseArgs(argv) {
|
|
|
414
436
|
else if (arg === "--out") options.outPath = resolve(args[++index]);
|
|
415
437
|
else if (arg.startsWith("--out=")) options.outPath = resolve(arg.slice("--out=".length));
|
|
416
438
|
else if (arg === "--simple") options.simple = true;
|
|
417
|
-
else if (arg === "--task"
|
|
418
|
-
|
|
439
|
+
else if (arg === "--task" || arg.startsWith("--task=")) {
|
|
440
|
+
const taskValue = arg.startsWith("--task=") ? arg.slice("--task=".length) : args[++index];
|
|
441
|
+
if (command === "run") options.task = taskValue;
|
|
442
|
+
else options.intelligenceTask = taskValue;
|
|
443
|
+
}
|
|
419
444
|
else if (arg === "--prompt") options.intelligencePrompt = args[++index];
|
|
420
445
|
else if (arg.startsWith("--prompt=")) options.intelligencePrompt = arg.slice("--prompt=".length);
|
|
421
446
|
else if (arg === "--paths") options.intelligencePaths = parsePathList(args[++index]);
|
|
422
447
|
else if (arg.startsWith("--paths=")) options.intelligencePaths = parsePathList(arg.slice("--paths=".length));
|
|
448
|
+
else if (arg === "--agent") options.agent = args[++index];
|
|
449
|
+
else if (arg.startsWith("--agent=")) options.agent = arg.slice("--agent=".length);
|
|
450
|
+
else if (arg === "--model") options.model = args[++index];
|
|
451
|
+
else if (arg.startsWith("--model=")) options.model = arg.slice("--model=".length);
|
|
452
|
+
else if (arg === "--permissions") options.permissions = parsePathList(args[++index]);
|
|
453
|
+
else if (arg.startsWith("--permissions=")) options.permissions = parsePathList(arg.slice("--permissions=".length));
|
|
454
|
+
else if (arg === "--capture-transcript") options.captureTranscript = true;
|
|
455
|
+
else if (arg === "--follow") options.follow = true;
|
|
456
|
+
else if (arg === "--no-wait") options.wait = false;
|
|
457
|
+
else if (arg === "--active-only") options.activeOnly = true;
|
|
458
|
+
else if (arg === "--timeout") options.timeoutMs = parsePositiveInt(args[++index], "timeout") * 1000;
|
|
459
|
+
else if (arg.startsWith("--timeout=")) options.timeoutMs = parsePositiveInt(arg.slice("--timeout=".length), "timeout") * 1000;
|
|
423
460
|
else if (arg === "--include-private") options.includePrivate = true;
|
|
424
461
|
else if (arg === "--cloud-consent") options.cloudConsent = true;
|
|
425
462
|
else if (arg === "--help" || arg === "-h") options.help = true;
|
|
@@ -427,6 +464,10 @@ export function parseArgs(argv) {
|
|
|
427
464
|
else throw new Error(`Unknown option "${arg}".`);
|
|
428
465
|
}
|
|
429
466
|
|
|
467
|
+
if (command === "run" && !options.task && args.length > 0) {
|
|
468
|
+
options.task = args.join(" ").trim();
|
|
469
|
+
}
|
|
470
|
+
|
|
430
471
|
return { command, options };
|
|
431
472
|
}
|
|
432
473
|
|
|
@@ -512,6 +553,30 @@ function parseHistoryAction(args, options) {
|
|
|
512
553
|
throw new Error(`Unknown history action "${action}". Use last or omit for the full log.`);
|
|
513
554
|
}
|
|
514
555
|
|
|
556
|
+
function parseRunsAction(args, options) {
|
|
557
|
+
const action = args[0];
|
|
558
|
+
|
|
559
|
+
if (!action || action.startsWith("-")) {
|
|
560
|
+
options.runsAction = "list";
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (!new Set(["list", "show", "stop"]).has(action)) {
|
|
565
|
+
throw new Error(`Unknown runs action "${action}". Use list, show, or stop.`);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
args.shift();
|
|
569
|
+
options.runsAction = action;
|
|
570
|
+
|
|
571
|
+
if (action === "show" || action === "stop") {
|
|
572
|
+
const runId = args[0];
|
|
573
|
+
if (!runId || runId.startsWith("-")) {
|
|
574
|
+
throw new Error(`Missing run id. Use: ${formatCliCommand(`runs ${action} <runId>`)}`);
|
|
575
|
+
}
|
|
576
|
+
options.runId = args.shift();
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
515
580
|
function parseIntelligenceAction(args, options) {
|
|
516
581
|
const action = args[0];
|
|
517
582
|
if (!action || action.startsWith("-")) {
|
|
@@ -559,6 +624,8 @@ function normalizeCommand(command) {
|
|
|
559
624
|
if (command === "install" || command === "i") return "install";
|
|
560
625
|
if (command === "shell") return "shell";
|
|
561
626
|
if (command === "orchestrator") return "orchestrator";
|
|
627
|
+
if (command === "run") return "run";
|
|
628
|
+
if (command === "runs") return "runs";
|
|
562
629
|
if (command === "intelligence" || command === "intel") return "intelligence";
|
|
563
630
|
if (command === "setup") return "setup";
|
|
564
631
|
if (command === "status") return "status";
|
|
@@ -619,7 +686,11 @@ Usage:
|
|
|
619
686
|
${cli} Interactive orchestrator shell (TTY)
|
|
620
687
|
${cli} --dry-run Setup dry-run (scriptable)
|
|
621
688
|
${cli} --version
|
|
622
|
-
${cli} shell
|
|
689
|
+
${cli} shell Operations dashboard (TTY)
|
|
690
|
+
${cli} run --agent <id> --task "..." [--model <name>] [--cwd <dir>] [--permissions force] [--capture-transcript] [--follow] [--no-wait] [--json]
|
|
691
|
+
${cli} runs list [--json] [--limit <n>] [--active-only]
|
|
692
|
+
${cli} runs show <runId> [--json] [--limit <n>] [--follow]
|
|
693
|
+
${cli} runs stop <runId> [--json]
|
|
623
694
|
${cli} orchestrator [--json] Read-only agent capability diagnostics
|
|
624
695
|
${cli} intelligence [status|models|context|route|ask] [--json]
|
|
625
696
|
${cli} intelligence ask --prompt "..." [--cloud-consent] [--yes] [--paths a,b]
|
|
@@ -656,7 +727,9 @@ Scopes:
|
|
|
656
727
|
Explicit --scope=workspace only.
|
|
657
728
|
|
|
658
729
|
Commands:
|
|
659
|
-
shell
|
|
730
|
+
shell Operations dashboard (TTY). Bare ${cli} opens this in TTY sessions.
|
|
731
|
+
run Launch a managed agent run with local audit trail.
|
|
732
|
+
runs List, inspect, or cancel agent runs under ~/.harness/runs/.
|
|
660
733
|
orchestrator Read-only capability registry diagnostics (--json supported).
|
|
661
734
|
intelligence Harness Engineering layer: backends, context packs, routing, budgets.
|
|
662
735
|
Local-first (Ollama). Cloud (OpenRouter/free) only with --cloud-consent.
|