@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.
- package/README.md +13 -2
- package/package.json +1 -1
- package/scripts/runtime-mvp-smoke.sh +152 -0
- package/src/cli.js +97 -9
- package/src/global/brand/index.js +10 -0
- package/src/global/dashboard-guidance.js +66 -0
- package/src/global/initial-experience.js +34 -0
- package/src/global/ink/orchestrator-app.js +372 -82
- package/src/global/ink/orchestrator-state.js +196 -65
- package/src/global/ink/run-orchestrator-ink.js +2 -0
- package/src/global/ink/run-setup-ink.js +2 -0
- package/src/global/ink/setup-app.js +8 -4
- package/src/global/ink/setup-state.js +24 -2
- package/src/global/orchestrator.js +46 -42
- 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/src/global/setup.js +13 -5
package/README.md
CHANGED
|
@@ -24,13 +24,24 @@ commands) without depending on Pi as a runtime or adding a Pi adapter.
|
|
|
24
24
|
|
|
25
25
|
## Quick start
|
|
26
26
|
|
|
27
|
-
Recommended entry — run Kairo Runtime in your terminal
|
|
27
|
+
Recommended entry — run Kairo Runtime in your terminal:
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
npx @kal-elsam/kairo-runtime
|
|
31
|
+
# or, after a global install:
|
|
32
|
+
kairo
|
|
31
33
|
```
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
**First run** (no `~/.harness/state.json`): interactive onboarding → safe diagnosis →
|
|
36
|
+
setup with confirmation → operations dashboard.
|
|
37
|
+
|
|
38
|
+
**Later runs** (state present): operations dashboard with a stable purpose line and a
|
|
39
|
+
contextual next step (configure, enable intelligence, launch a run, or review problems).
|
|
40
|
+
|
|
41
|
+
Explicit commands and setup flags keep their current behavior (`kairo setup`,
|
|
42
|
+
`kairo --dry-run`, `kairo shell`, non-TTY scripts, etc.).
|
|
43
|
+
|
|
44
|
+
Preview setup without writing anything:
|
|
34
45
|
|
|
35
46
|
```bash
|
|
36
47
|
npx @kal-elsam/kairo-runtime --dry-run
|
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.3",
|
|
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,
|
|
@@ -41,6 +42,11 @@ import {
|
|
|
41
42
|
resolveSuggestedInvocation
|
|
42
43
|
} from "./global/brand/cli.js";
|
|
43
44
|
import { BRAND } from "./global/brand/index.js";
|
|
45
|
+
import {
|
|
46
|
+
INITIAL_EXPERIENCE,
|
|
47
|
+
hasConfiguredGlobalState,
|
|
48
|
+
resolveInitialExperience
|
|
49
|
+
} from "./global/initial-experience.js";
|
|
44
50
|
|
|
45
51
|
export { resolveSuggestedInvocation };
|
|
46
52
|
|
|
@@ -49,7 +55,7 @@ const packageRoot = resolve(__dirname, "..");
|
|
|
49
55
|
const SCOPES = new Set(["agent-global", "workspace"]);
|
|
50
56
|
|
|
51
57
|
export async function runCli(argv) {
|
|
52
|
-
const { command, options } = parseArgs(argv);
|
|
58
|
+
const { command, options, isImplicitCommand } = parseArgs(argv);
|
|
53
59
|
maybeWarnLegacyCli(process.argv, { json: options.json });
|
|
54
60
|
|
|
55
61
|
if (options.help || command === "help") {
|
|
@@ -68,14 +74,22 @@ export async function runCli(argv) {
|
|
|
68
74
|
const invoke = resolveSuggestedInvocation(packageManifest.name);
|
|
69
75
|
|
|
70
76
|
switch (command) {
|
|
71
|
-
case "shell":
|
|
77
|
+
case "shell": {
|
|
78
|
+
const homeDir = resolveHomeDir();
|
|
79
|
+
const resolvedMode = resolveInitialExperience({
|
|
80
|
+
interactive: optionsWithPolicy.interactive,
|
|
81
|
+
isImplicitCommand,
|
|
82
|
+
hasGlobalState: hasConfiguredGlobalState(homeDir)
|
|
83
|
+
});
|
|
72
84
|
await runOrchestratorShell({
|
|
73
85
|
packageRoot,
|
|
74
86
|
packageManifest,
|
|
75
87
|
workspaceRoot: optionsWithPolicy.cwd,
|
|
76
|
-
interactive: optionsWithPolicy.interactive
|
|
88
|
+
interactive: optionsWithPolicy.interactive,
|
|
89
|
+
initialMode: resolvedMode ?? INITIAL_EXPERIENCE.DASHBOARD
|
|
77
90
|
});
|
|
78
91
|
return;
|
|
92
|
+
}
|
|
79
93
|
case "orchestrator":
|
|
80
94
|
await runOrchestratorDiagnostics({
|
|
81
95
|
homeDir: resolveHomeDir(),
|
|
@@ -86,6 +100,12 @@ export async function runCli(argv) {
|
|
|
86
100
|
json: optionsWithPolicy.json
|
|
87
101
|
});
|
|
88
102
|
return;
|
|
103
|
+
case "run":
|
|
104
|
+
await runGlobalRun(optionsWithPolicy, packageManifest);
|
|
105
|
+
return;
|
|
106
|
+
case "runs":
|
|
107
|
+
await runGlobalRuns(optionsWithPolicy, packageManifest);
|
|
108
|
+
return;
|
|
89
109
|
case "intelligence":
|
|
90
110
|
await runIntelligenceCli(optionsWithPolicy, packageManifest);
|
|
91
111
|
return;
|
|
@@ -333,6 +353,17 @@ export function parseArgs(argv) {
|
|
|
333
353
|
intelligenceTask: null,
|
|
334
354
|
intelligencePrompt: null,
|
|
335
355
|
intelligencePaths: [],
|
|
356
|
+
runsAction: null,
|
|
357
|
+
runId: null,
|
|
358
|
+
agent: null,
|
|
359
|
+
task: null,
|
|
360
|
+
model: null,
|
|
361
|
+
permissions: null,
|
|
362
|
+
captureTranscript: false,
|
|
363
|
+
follow: false,
|
|
364
|
+
wait: true,
|
|
365
|
+
activeOnly: false,
|
|
366
|
+
timeoutMs: null,
|
|
336
367
|
includePrivate: false,
|
|
337
368
|
cloudConsent: false
|
|
338
369
|
};
|
|
@@ -349,6 +380,10 @@ export function parseArgs(argv) {
|
|
|
349
380
|
parseHistoryAction(args, options);
|
|
350
381
|
}
|
|
351
382
|
|
|
383
|
+
if (command === "runs") {
|
|
384
|
+
parseRunsAction(args, options);
|
|
385
|
+
}
|
|
386
|
+
|
|
352
387
|
if (command === "intelligence") {
|
|
353
388
|
parseIntelligenceAction(args, options);
|
|
354
389
|
}
|
|
@@ -414,12 +449,27 @@ export function parseArgs(argv) {
|
|
|
414
449
|
else if (arg === "--out") options.outPath = resolve(args[++index]);
|
|
415
450
|
else if (arg.startsWith("--out=")) options.outPath = resolve(arg.slice("--out=".length));
|
|
416
451
|
else if (arg === "--simple") options.simple = true;
|
|
417
|
-
else if (arg === "--task"
|
|
418
|
-
|
|
452
|
+
else if (arg === "--task" || arg.startsWith("--task=")) {
|
|
453
|
+
const taskValue = arg.startsWith("--task=") ? arg.slice("--task=".length) : args[++index];
|
|
454
|
+
if (command === "run") options.task = taskValue;
|
|
455
|
+
else options.intelligenceTask = taskValue;
|
|
456
|
+
}
|
|
419
457
|
else if (arg === "--prompt") options.intelligencePrompt = args[++index];
|
|
420
458
|
else if (arg.startsWith("--prompt=")) options.intelligencePrompt = arg.slice("--prompt=".length);
|
|
421
459
|
else if (arg === "--paths") options.intelligencePaths = parsePathList(args[++index]);
|
|
422
460
|
else if (arg.startsWith("--paths=")) options.intelligencePaths = parsePathList(arg.slice("--paths=".length));
|
|
461
|
+
else if (arg === "--agent") options.agent = args[++index];
|
|
462
|
+
else if (arg.startsWith("--agent=")) options.agent = arg.slice("--agent=".length);
|
|
463
|
+
else if (arg === "--model") options.model = args[++index];
|
|
464
|
+
else if (arg.startsWith("--model=")) options.model = arg.slice("--model=".length);
|
|
465
|
+
else if (arg === "--permissions") options.permissions = parsePathList(args[++index]);
|
|
466
|
+
else if (arg.startsWith("--permissions=")) options.permissions = parsePathList(arg.slice("--permissions=".length));
|
|
467
|
+
else if (arg === "--capture-transcript") options.captureTranscript = true;
|
|
468
|
+
else if (arg === "--follow") options.follow = true;
|
|
469
|
+
else if (arg === "--no-wait") options.wait = false;
|
|
470
|
+
else if (arg === "--active-only") options.activeOnly = true;
|
|
471
|
+
else if (arg === "--timeout") options.timeoutMs = parsePositiveInt(args[++index], "timeout") * 1000;
|
|
472
|
+
else if (arg.startsWith("--timeout=")) options.timeoutMs = parsePositiveInt(arg.slice("--timeout=".length), "timeout") * 1000;
|
|
423
473
|
else if (arg === "--include-private") options.includePrivate = true;
|
|
424
474
|
else if (arg === "--cloud-consent") options.cloudConsent = true;
|
|
425
475
|
else if (arg === "--help" || arg === "-h") options.help = true;
|
|
@@ -427,7 +477,11 @@ export function parseArgs(argv) {
|
|
|
427
477
|
else throw new Error(`Unknown option "${arg}".`);
|
|
428
478
|
}
|
|
429
479
|
|
|
430
|
-
|
|
480
|
+
if (command === "run" && !options.task && args.length > 0) {
|
|
481
|
+
options.task = args.join(" ").trim();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return { command, options, isImplicitCommand: implicitCommand };
|
|
431
485
|
}
|
|
432
486
|
|
|
433
487
|
function parseComponentsAction(args, options) {
|
|
@@ -512,6 +566,30 @@ function parseHistoryAction(args, options) {
|
|
|
512
566
|
throw new Error(`Unknown history action "${action}". Use last or omit for the full log.`);
|
|
513
567
|
}
|
|
514
568
|
|
|
569
|
+
function parseRunsAction(args, options) {
|
|
570
|
+
const action = args[0];
|
|
571
|
+
|
|
572
|
+
if (!action || action.startsWith("-")) {
|
|
573
|
+
options.runsAction = "list";
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
if (!new Set(["list", "show", "stop"]).has(action)) {
|
|
578
|
+
throw new Error(`Unknown runs action "${action}". Use list, show, or stop.`);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
args.shift();
|
|
582
|
+
options.runsAction = action;
|
|
583
|
+
|
|
584
|
+
if (action === "show" || action === "stop") {
|
|
585
|
+
const runId = args[0];
|
|
586
|
+
if (!runId || runId.startsWith("-")) {
|
|
587
|
+
throw new Error(`Missing run id. Use: ${formatCliCommand(`runs ${action} <runId>`)}`);
|
|
588
|
+
}
|
|
589
|
+
options.runId = args.shift();
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
515
593
|
function parseIntelligenceAction(args, options) {
|
|
516
594
|
const action = args[0];
|
|
517
595
|
if (!action || action.startsWith("-")) {
|
|
@@ -559,6 +637,8 @@ function normalizeCommand(command) {
|
|
|
559
637
|
if (command === "install" || command === "i") return "install";
|
|
560
638
|
if (command === "shell") return "shell";
|
|
561
639
|
if (command === "orchestrator") return "orchestrator";
|
|
640
|
+
if (command === "run") return "run";
|
|
641
|
+
if (command === "runs") return "runs";
|
|
562
642
|
if (command === "intelligence" || command === "intel") return "intelligence";
|
|
563
643
|
if (command === "setup") return "setup";
|
|
564
644
|
if (command === "status") return "status";
|
|
@@ -616,10 +696,15 @@ sections, components, backups, and drift repair under ~/.harness.
|
|
|
616
696
|
Bootstrap: see README.md (curl install.sh or npx ${PACKAGE_NAME}).
|
|
617
697
|
|
|
618
698
|
Usage:
|
|
619
|
-
${cli}
|
|
699
|
+
${cli} First run: onboarding → setup → dashboard (TTY).
|
|
700
|
+
Later: operations dashboard with next-step guidance.
|
|
620
701
|
${cli} --dry-run Setup dry-run (scriptable)
|
|
621
702
|
${cli} --version
|
|
622
|
-
${cli} shell
|
|
703
|
+
${cli} shell Operations dashboard (TTY)
|
|
704
|
+
${cli} run --agent <id> --task "..." [--model <name>] [--cwd <dir>] [--permissions force] [--capture-transcript] [--follow] [--no-wait] [--json]
|
|
705
|
+
${cli} runs list [--json] [--limit <n>] [--active-only]
|
|
706
|
+
${cli} runs show <runId> [--json] [--limit <n>] [--follow]
|
|
707
|
+
${cli} runs stop <runId> [--json]
|
|
623
708
|
${cli} orchestrator [--json] Read-only agent capability diagnostics
|
|
624
709
|
${cli} intelligence [status|models|context|route|ask] [--json]
|
|
625
710
|
${cli} intelligence ask --prompt "..." [--cloud-consent] [--yes] [--paths a,b]
|
|
@@ -656,7 +741,10 @@ Scopes:
|
|
|
656
741
|
Explicit --scope=workspace only.
|
|
657
742
|
|
|
658
743
|
Commands:
|
|
659
|
-
shell
|
|
744
|
+
shell Operations dashboard (TTY). Bare ${cli} opens onboarding when ~/.harness/state.json
|
|
745
|
+
is missing, otherwise the dashboard. Explicit ${cli} shell always opens the dashboard.
|
|
746
|
+
run Launch a managed agent run with local audit trail.
|
|
747
|
+
runs List, inspect, or cancel agent runs under ~/.harness/runs/.
|
|
660
748
|
orchestrator Read-only capability registry diagnostics (--json supported).
|
|
661
749
|
intelligence Harness Engineering layer: backends, context packs, routing, budgets.
|
|
662
750
|
Local-first (Ollama). Cloud (OpenRouter/free) only with --cloud-consent.
|
|
@@ -70,6 +70,16 @@ export const WIZARD_COPY = {
|
|
|
70
70
|
coreOnlyLabel: "Core only (no components)"
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
+
/** First-run framing reused by onboarding → setup. */
|
|
74
|
+
export const ONBOARDING_COPY = {
|
|
75
|
+
welcomeTitle: `Welcome to ${BRAND.displayName}`,
|
|
76
|
+
purpose:
|
|
77
|
+
`${BRAND.displayName} detects, configures, and coordinates the local agents you already use.`,
|
|
78
|
+
safety:
|
|
79
|
+
"Diagnosis is read-only. Nothing is modified until you confirm a plan.",
|
|
80
|
+
continueHint: "Press Enter to diagnose and configure · Esc to exit"
|
|
81
|
+
};
|
|
82
|
+
|
|
73
83
|
export function getAgentLabel(agentId) {
|
|
74
84
|
return AGENT_LABELS[agentId] ?? agentId;
|
|
75
85
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { formatCliCommand } from "./brand/cli.js";
|
|
2
|
+
|
|
3
|
+
export const DASHBOARD_PURPOSE =
|
|
4
|
+
"Detects, configures, and coordinates local AI agents — no changes without confirmation.";
|
|
5
|
+
|
|
6
|
+
export const NEXT_STEP_KINDS = {
|
|
7
|
+
CONFIGURE: "configure",
|
|
8
|
+
ENABLE_INTELLIGENCE: "enable_intelligence",
|
|
9
|
+
LAUNCH: "launch",
|
|
10
|
+
REVIEW: "review"
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function formatDashboardPurpose() {
|
|
14
|
+
return DASHBOARD_PURPOSE;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Contextual next step from existing diagnostics + dashboard snapshot.
|
|
19
|
+
* Priority: configure → review problems → enable intelligence → launch.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveDashboardRecommendation({
|
|
22
|
+
hasGlobalState = false,
|
|
23
|
+
diagnostics = null,
|
|
24
|
+
dashboard = null
|
|
25
|
+
} = {}) {
|
|
26
|
+
const summary = diagnostics?.diagnostics ?? { detected: 0, errors: 0 };
|
|
27
|
+
const intelligence = diagnostics?.intelligence?.summary;
|
|
28
|
+
const launchableCount = (dashboard?.providers ?? []).filter((entry) => entry.launchable).length;
|
|
29
|
+
const hasErrors = (summary.errors ?? 0) > 0;
|
|
30
|
+
const hasProblemRecommendation = (diagnostics?.recommendations ?? []).some((line) =>
|
|
31
|
+
/error|fix|drift|not detected|failed|problem/i.test(line)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
if (!hasGlobalState || (summary.detected ?? 0) === 0) {
|
|
35
|
+
return {
|
|
36
|
+
kind: NEXT_STEP_KINDS.CONFIGURE,
|
|
37
|
+
message: `Configure the local environment with ${formatCliCommand("setup")}.`
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (hasErrors || hasProblemRecommendation) {
|
|
42
|
+
return {
|
|
43
|
+
kind: NEXT_STEP_KINDS.REVIEW,
|
|
44
|
+
message: "Review diagnostics for problems before launching a run."
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!intelligence?.localAvailable && !intelligence?.cloudAuthenticated) {
|
|
49
|
+
return {
|
|
50
|
+
kind: NEXT_STEP_KINDS.ENABLE_INTELLIGENCE,
|
|
51
|
+
message: "Enable intelligence: start Ollama or set OPENROUTER_API_KEY, then retry."
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (launchableCount > 0) {
|
|
56
|
+
return {
|
|
57
|
+
kind: NEXT_STEP_KINDS.LAUNCH,
|
|
58
|
+
message: "Launch a supervised run from the menu or with kairo run."
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
kind: NEXT_STEP_KINDS.REVIEW,
|
|
64
|
+
message: "Review diagnostics for problems before launching a run."
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { harnessHomePaths } from "./paths.js";
|
|
3
|
+
|
|
4
|
+
export const INITIAL_EXPERIENCE = {
|
|
5
|
+
ONBOARDING: "onboarding",
|
|
6
|
+
DASHBOARD: "dashboard"
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* First-run marker is ~/.harness/state.json only.
|
|
11
|
+
* profile.json does not participate in this decision.
|
|
12
|
+
*/
|
|
13
|
+
export function hasConfiguredGlobalState(homeDir) {
|
|
14
|
+
return existsSync(harnessHomePaths(homeDir).statePath);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pure resolver for the interactive bare-entry experience.
|
|
19
|
+
* Returns null when CLI should keep existing non-onboarding paths
|
|
20
|
+
* (non-TTY, explicit commands, setup flags already routed elsewhere).
|
|
21
|
+
*/
|
|
22
|
+
export function resolveInitialExperience({
|
|
23
|
+
interactive = false,
|
|
24
|
+
isImplicitCommand = false,
|
|
25
|
+
hasGlobalState = false
|
|
26
|
+
} = {}) {
|
|
27
|
+
if (!interactive || !isImplicitCommand) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return hasGlobalState
|
|
32
|
+
? INITIAL_EXPERIENCE.DASHBOARD
|
|
33
|
+
: INITIAL_EXPERIENCE.ONBOARDING;
|
|
34
|
+
}
|