guardian-framework 0.1.155 → 0.1.157
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/dist/cli.js +1 -1
- package/dist/exports.js +1 -1
- package/package.json +1 -1
- package/templates/pi/extensions/architect.ts +21 -22
- package/templates/pi/preflight_report.json +1 -1
- package/templates/pi/scripts/epic-fanout.sh +128 -8
- package/templates/pi/scripts/epic-parallel.sh +3 -2
- package/templates/pi/scripts/epic-runner.mjs +26 -3
package/dist/cli.js
CHANGED
|
@@ -3206,7 +3206,7 @@ __export(exports_package, {
|
|
|
3206
3206
|
bin: () => bin,
|
|
3207
3207
|
author: () => author
|
|
3208
3208
|
});
|
|
3209
|
-
var name = "guardian-framework", version = "0.1.
|
|
3209
|
+
var name = "guardian-framework", version = "0.1.157", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
|
|
3210
3210
|
var init_package = __esm(() => {
|
|
3211
3211
|
exports = {
|
|
3212
3212
|
".": {
|
package/dist/exports.js
CHANGED
|
@@ -1991,7 +1991,7 @@ __export(exports_package, {
|
|
|
1991
1991
|
bin: () => bin,
|
|
1992
1992
|
author: () => author
|
|
1993
1993
|
});
|
|
1994
|
-
var name = "guardian-framework", version = "0.1.
|
|
1994
|
+
var name = "guardian-framework", version = "0.1.157", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
|
|
1995
1995
|
var init_package = __esm(() => {
|
|
1996
1996
|
exports = {
|
|
1997
1997
|
".": {
|
package/package.json
CHANGED
|
@@ -209,7 +209,11 @@ class EpicManager {
|
|
|
209
209
|
runScript(this.cwd, 'git commit -m "Initial Guardian scaffold"');
|
|
210
210
|
}
|
|
211
211
|
|
|
212
|
-
|
|
212
|
+
// GUARDIAN_LOCAL_ONLY=1 forces local-only: no remote issues are created.
|
|
213
|
+
// epic-fanout.sh sets this so an automated run cannot write to a forge
|
|
214
|
+
// without a human explicitly asking for it.
|
|
215
|
+
const localOnly = process.env.GUARDIAN_LOCAL_ONLY === "1";
|
|
216
|
+
const repoTool = localOnly ? "local" : readRepoTool(this.cwd);
|
|
213
217
|
const repository = readRepository(this.cwd);
|
|
214
218
|
const targetRepo = repository || slice.module;
|
|
215
219
|
let hasRemote = false;
|
|
@@ -880,16 +884,22 @@ export default function architectExtension(pi: ExtensionAPI) {
|
|
|
880
884
|
// roadmap order is used (implemented modules are skipped).
|
|
881
885
|
const modulesFlag = findFlag(tokens, "--modules");
|
|
882
886
|
const parallelMode = parallelFlag || useWorktree;
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
887
|
+
// In parallel mode fan-out owns the state: each spawned agent writes its
|
|
888
|
+
// own epic state INSIDE its worktree. Writing here would leave a stray
|
|
889
|
+
// .guardian-epic-state.json in the main checkout that makes
|
|
890
|
+
// `/architect status` report an epic that does not exist.
|
|
891
|
+
if (!parallelMode) {
|
|
892
|
+
try {
|
|
893
|
+
const epicStatePath = join(ctx.cwd, ".pi", ".guardian-epic-state.json");
|
|
894
|
+
const st = existsSync(epicStatePath)
|
|
895
|
+
? (JSON.parse(readFileSync(epicStatePath, "utf-8")) as Record<string, unknown>)
|
|
896
|
+
: {};
|
|
897
|
+
st.prStrategy = epicPrStrategy;
|
|
898
|
+
mkdirSync(join(ctx.cwd, ".pi"), { recursive: true });
|
|
899
|
+
writeFileSync(epicStatePath, JSON.stringify(st, null, 2), "utf-8");
|
|
900
|
+
} catch {
|
|
901
|
+
// ignore — strategy falls back to epic
|
|
902
|
+
}
|
|
893
903
|
}
|
|
894
904
|
const tddEnabled = tokens.includes("--tdd");
|
|
895
905
|
|
|
@@ -953,17 +963,6 @@ export default function architectExtension(pi: ExtensionAPI) {
|
|
|
953
963
|
ctx.ui.notify(`Fan-out failed:\n${out.trim().slice(-600)}`, "error");
|
|
954
964
|
return;
|
|
955
965
|
}
|
|
956
|
-
// Record orchestration on the state so later steps stay consistent.
|
|
957
|
-
try {
|
|
958
|
-
const p = join(ctx.cwd, ".pi/.guardian-epic-state.json");
|
|
959
|
-
const st = existsSync(p) ? (JSON.parse(readFileSync(p, "utf-8")) as Record<string, unknown>) : {};
|
|
960
|
-
st.prStrategy = epicPrStrategy;
|
|
961
|
-
st.parallel = true;
|
|
962
|
-
st.modules = list || "(roadmap order)";
|
|
963
|
-
writeFileSync(p, JSON.stringify(st, null, 2), "utf-8");
|
|
964
|
-
} catch {
|
|
965
|
-
// ignore
|
|
966
|
-
}
|
|
967
966
|
ctx.ui.notify(out.trim().split("\n").slice(-12).join("\n"), "info");
|
|
968
967
|
return;
|
|
969
968
|
}
|
|
@@ -13,9 +13,15 @@
|
|
|
13
13
|
#
|
|
14
14
|
# Usage:
|
|
15
15
|
# epic-fanout.sh [--modules a,b,c] [--tdd] [--max N]
|
|
16
|
-
# [--
|
|
16
|
+
# [--spawn] [--dry-run] [--assume-frozen] [--local]
|
|
17
17
|
# [--base main] [--timeout-ms N]
|
|
18
18
|
#
|
|
19
|
+
# SAFETY: nothing is spawned without --spawn. The default prepares worktrees
|
|
20
|
+
# and prints the exact commands, so you can inspect before any agent runs.
|
|
21
|
+
# Contracts must ALREADY be frozen in the registry; this script never freezes
|
|
22
|
+
# on your behalf (that made the gate meaningless and started agents on
|
|
23
|
+
# unreviewed contracts).
|
|
24
|
+
#
|
|
19
25
|
# Exit: 0 started/prepared · 2 blocked by a dependency · 1 error
|
|
20
26
|
# ============================================================================
|
|
21
27
|
set -uo pipefail
|
|
@@ -27,6 +33,9 @@ MAX=0
|
|
|
27
33
|
FORCE_FREEZE=0
|
|
28
34
|
DRY_RUN=0
|
|
29
35
|
NO_SPAWN=0
|
|
36
|
+
SPAWN=0
|
|
37
|
+
ASSUME_FROZEN=0
|
|
38
|
+
ALLOW_REMOTE=0
|
|
30
39
|
BASE="main"
|
|
31
40
|
TIMEOUT_MS=0
|
|
32
41
|
|
|
@@ -38,6 +47,9 @@ while [ $# -gt 0 ]; do
|
|
|
38
47
|
--force-freeze) FORCE_FREEZE=1; shift ;;
|
|
39
48
|
--dry-run) DRY_RUN=1; shift ;;
|
|
40
49
|
--no-spawn) NO_SPAWN=1; shift ;;
|
|
50
|
+
--spawn) SPAWN=1; shift ;;
|
|
51
|
+
--assume-frozen) ASSUME_FROZEN=1; shift ;;
|
|
52
|
+
--allow-remote-issues) ALLOW_REMOTE=1; shift ;;
|
|
41
53
|
--base) BASE="${2:-main}"; shift 2 ;;
|
|
42
54
|
--timeout-ms) TIMEOUT_MS="${2:-0}"; shift 2 ;;
|
|
43
55
|
-h | --help) sed -n '2,24p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
@@ -58,6 +70,16 @@ RUNNER="$ROOT/.pi/scripts/epic-runner.mjs"
|
|
|
58
70
|
[ -f "$RUNNER" ] || { echo "missing $RUNNER — run guardian update" >&2; exit 1; }
|
|
59
71
|
|
|
60
72
|
# The runner is a Node script; bun also executes it.
|
|
73
|
+
# The project directory is the one that owns .pi/ (this script lives in
|
|
74
|
+
# <project>/.pi/scripts). It is NOT necessarily the git top level: a monorepo
|
|
75
|
+
# often keeps the app in <repo>/backend with .pi/ inside it. Agents must run in
|
|
76
|
+
# the project dir, otherwise pi finds no extensions and /architect is not a
|
|
77
|
+
# command at all.
|
|
78
|
+
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
79
|
+
GIT_TOP="$(git -C "$PROJECT_DIR" rev-parse --show-toplevel 2>/dev/null || echo "$PROJECT_DIR")"
|
|
80
|
+
PROJECT_REL="${PROJECT_DIR#"$GIT_TOP"}"
|
|
81
|
+
PROJECT_REL="${PROJECT_REL#/}"
|
|
82
|
+
|
|
61
83
|
NODE_BIN="$(command -v node || command -v bun || true)"
|
|
62
84
|
if [ -z "$NODE_BIN" ]; then
|
|
63
85
|
echo "neither node nor bun found — required to spawn headless agents" >&2
|
|
@@ -158,6 +180,30 @@ while IFS= read -r modline; do
|
|
|
158
180
|
[ -n "$modline" ] && MODULES+=("$modline")
|
|
159
181
|
done <<<"$SELECTED"
|
|
160
182
|
|
|
183
|
+
# ── Batch-size guard ────────────────────────────────────────────────────────
|
|
184
|
+
# An unbounded automated batch is how six agents started at once. Spawning
|
|
185
|
+
# requires an explicit bound: --max N, or an explicit --modules list of <= 3.
|
|
186
|
+
if [ "$SPAWN" -eq 1 ] && [ "$MAX" -le 0 ]; then
|
|
187
|
+
COUNT=${#MODULES[@]}
|
|
188
|
+
if [ "$COUNT" -gt 3 ]; then
|
|
189
|
+
echo "⛔ Refusing to spawn ${COUNT} agents unbounded." >&2
|
|
190
|
+
echo " Add --max N (recommended 1-3) or pass an explicit --modules list of at most 3." >&2
|
|
191
|
+
echo " Nothing was created." >&2
|
|
192
|
+
exit 1
|
|
193
|
+
fi
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
# ── Pre-flight: the spawned agents inherit THIS project's .pi/ ──────────────
|
|
197
|
+
# Without extensions, /architect is not a command: the prompt falls through to
|
|
198
|
+
# the model, which then improvises (expensive and wrong). Fail before spawning.
|
|
199
|
+
if [ "$NO_SPAWN" -eq 0 ] && [ "$DRY_RUN" -eq 0 ]; then
|
|
200
|
+
if [ ! -d "$PROJECT_DIR/.pi/extensions" ]; then
|
|
201
|
+
echo "⛔ $PROJECT_DIR/.pi/extensions is missing — the agent would have no /architect command." >&2
|
|
202
|
+
echo " Run guardian update in $PROJECT_DIR, or use --no-spawn." >&2
|
|
203
|
+
exit 1
|
|
204
|
+
fi
|
|
205
|
+
fi
|
|
206
|
+
|
|
161
207
|
echo "============================================"
|
|
162
208
|
echo " Parallel epic fan-out"
|
|
163
209
|
echo "============================================"
|
|
@@ -346,6 +392,25 @@ if [ "$DEP_RC" -ne 0 ]; then
|
|
|
346
392
|
fi
|
|
347
393
|
|
|
348
394
|
if [ "$DRY_RUN" -eq 1 ]; then
|
|
395
|
+
echo "Contract freeze status (a real run refuses unfrozen modules):"
|
|
396
|
+
for m in ${MODULES[@]+"${MODULES[@]}"}; do
|
|
397
|
+
slug="$(echo "$m" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//')"
|
|
398
|
+
st="$(python3 - "$COMMON_ABS/guardian/epics.json" "$slug" <<'PY' 2>/dev/null
|
|
399
|
+
import json, re, sys
|
|
400
|
+
try:
|
|
401
|
+
d = json.load(open(sys.argv[1], encoding="utf-8"))
|
|
402
|
+
except Exception:
|
|
403
|
+
print("no"); raise SystemExit
|
|
404
|
+
want = sys.argv[2]
|
|
405
|
+
for mod, e in (d.get("epics") or {}).items():
|
|
406
|
+
if isinstance(e, dict) and re.sub(r"[^a-z0-9]+", "-", mod.lower()).strip("-") == want:
|
|
407
|
+
print("yes" if e.get("contractFrozen") else "no"); raise SystemExit
|
|
408
|
+
print("no")
|
|
409
|
+
PY
|
|
410
|
+
)"
|
|
411
|
+
[ "$st" = "yes" ] && echo " ✓ $m" || echo " ✗ $m (not frozen)"
|
|
412
|
+
done
|
|
413
|
+
echo ""
|
|
349
414
|
echo "Would do, per module:"
|
|
350
415
|
for m in ${MODULES[@]+"${MODULES[@]}"}; do
|
|
351
416
|
slug="$(echo "$m" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//')"
|
|
@@ -356,6 +421,45 @@ if [ "$DRY_RUN" -eq 1 ]; then
|
|
|
356
421
|
exit 0
|
|
357
422
|
fi
|
|
358
423
|
|
|
424
|
+
# ── Contract-freeze gate (the module itself) ────────────────────────────────
|
|
425
|
+
# Freeze is a human act: it asserts the module's public contracts are reviewed
|
|
426
|
+
# and merged. This script must never assert that for you — doing so is what let
|
|
427
|
+
# a fan-out start six agents on unreviewed contracts.
|
|
428
|
+
if [ "$ASSUME_FROZEN" -eq 0 ]; then
|
|
429
|
+
UNFROZEN="$(python3 - "$COMMON_ABS/guardian/epics.json" ${MODULES[@]+"${MODULES[@]}"} <<'PY'
|
|
430
|
+
import json, re, sys
|
|
431
|
+
|
|
432
|
+
path, batch = sys.argv[1], sys.argv[2:]
|
|
433
|
+
frozen = set()
|
|
434
|
+
try:
|
|
435
|
+
for mod, entry in (json.load(open(path, encoding="utf-8")).get("epics") or {}).items():
|
|
436
|
+
if isinstance(entry, dict) and entry.get("contractFrozen"):
|
|
437
|
+
frozen.add(re.sub(r"[^a-z0-9]+", "-", mod.lower()).strip("-"))
|
|
438
|
+
except Exception:
|
|
439
|
+
pass
|
|
440
|
+
|
|
441
|
+
missing = []
|
|
442
|
+
for m in batch:
|
|
443
|
+
slug = re.sub(r"[^a-z0-9]+", "-", m.lower()).strip("-")
|
|
444
|
+
if slug not in frozen:
|
|
445
|
+
missing.append(m)
|
|
446
|
+
print("\n".join(missing))
|
|
447
|
+
PY
|
|
448
|
+
)"
|
|
449
|
+
if [ -n "$UNFROZEN" ]; then
|
|
450
|
+
echo "⛔ Contracts are not frozen for:"
|
|
451
|
+
echo "$UNFROZEN" | sed 's/^/ - /'
|
|
452
|
+
echo ""
|
|
453
|
+
echo " Freeze is YOUR assertion that the module's public contracts are"
|
|
454
|
+
echo " reviewed and merged. Review the module doc, merge its contract"
|
|
455
|
+
echo " freeze, then record it:"
|
|
456
|
+
echo " bash .pi/scripts/epic-registry.sh freeze \"<Module>\" --frozen"
|
|
457
|
+
echo ""
|
|
458
|
+
echo " Bypass only if you are certain: --assume-frozen"
|
|
459
|
+
exit 2
|
|
460
|
+
fi
|
|
461
|
+
fi
|
|
462
|
+
|
|
359
463
|
# ── Freeze, create worktrees, spawn ─────────────────────────────────────────
|
|
360
464
|
mkdir -p "$LOGDIR"
|
|
361
465
|
STARTED=()
|
|
@@ -368,11 +472,11 @@ for m in ${MODULES[@]+"${MODULES[@]}"}; do
|
|
|
368
472
|
echo "────────────────────────────────────────────"
|
|
369
473
|
echo "▶ $m"
|
|
370
474
|
|
|
371
|
-
# 1. record the contract freeze (the gate below requires it)
|
|
372
|
-
bash "$REGISTRY" freeze "$m" --frozen >/dev/null 2>&1
|
|
373
475
|
|
|
374
476
|
# 2. worktree + epic branch (gated on the freeze we just recorded)
|
|
375
|
-
|
|
477
|
+
WT_ARGS=("$m" --base "$BASE")
|
|
478
|
+
[ "$ASSUME_FROZEN" -eq 1 ] && WT_ARGS+=(--assume-frozen)
|
|
479
|
+
WT_OUT="$(bash "$PARALLEL" "${WT_ARGS[@]}" 2>&1)"
|
|
376
480
|
WT_RC=$?
|
|
377
481
|
if [ "$WT_RC" -ne 0 ]; then
|
|
378
482
|
echo " ✗ worktree failed (exit $WT_RC)"
|
|
@@ -387,16 +491,27 @@ for m in ${MODULES[@]+"${MODULES[@]}"}; do
|
|
|
387
491
|
fi
|
|
388
492
|
echo " ✓ worktree $WT"
|
|
389
493
|
|
|
390
|
-
if [ "$NO_SPAWN" -eq 1 ]; then
|
|
391
|
-
echo " → run: cd \"$WT\" && /architect --epic \"$m\"$( [ "$TDD" -eq 1 ] && echo " --tdd")"
|
|
494
|
+
if [ "$SPAWN" -eq 0 ] || [ "$NO_SPAWN" -eq 1 ]; then
|
|
495
|
+
echo " → run: cd \"$WT${PROJECT_REL:+/$PROJECT_REL}\" && /architect --epic \"$m\"$( [ "$TDD" -eq 1 ] && echo " --tdd")"
|
|
392
496
|
STARTED+=("$m")
|
|
393
497
|
continue
|
|
394
498
|
fi
|
|
395
499
|
|
|
396
500
|
# 3. spawn one headless agent in the worktree (RPC mode dispatches the command)
|
|
501
|
+
# cwd must be the PROJECT dir inside the worktree (where .pi/ lives).
|
|
502
|
+
SPAWN_CWD="$WT"
|
|
503
|
+
[ -n "$PROJECT_REL" ] && SPAWN_CWD="$WT/$PROJECT_REL"
|
|
504
|
+
if [ ! -d "$SPAWN_CWD/.pi/extensions" ]; then
|
|
505
|
+
echo " ✗ $SPAWN_CWD/.pi/extensions missing — refusing to spawn a blind agent"
|
|
506
|
+
FAILED+=("$m")
|
|
507
|
+
continue
|
|
508
|
+
fi
|
|
509
|
+
# GUARDIAN_LOCAL_ONLY keeps the spawned epic from creating remote issues
|
|
510
|
+
# unless the caller explicitly allowed it (--allow-remote-issues).
|
|
511
|
+
export GUARDIAN_LOCAL_ONLY="$([ "$ALLOW_REMOTE" -eq 1 ] && echo 0 || echo 1)"
|
|
397
512
|
"$NODE_BIN" "$RUNNER" \
|
|
398
513
|
"/architect --epic \"$m\"$( [ "$TDD" -eq 1 ] && echo " --tdd")" \
|
|
399
|
-
--cwd "$
|
|
514
|
+
--cwd "$SPAWN_CWD" --log "$log" ${TIMEOUT_MS:+--timeout-ms "$TIMEOUT_MS"} \
|
|
400
515
|
>/dev/null 2>&1 &
|
|
401
516
|
PID=$!
|
|
402
517
|
bash "$REGISTRY" set "$m" "pid=$PID" "log=$log" "spawnedAt=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >/dev/null 2>&1
|
|
@@ -408,8 +523,13 @@ echo "────────────────────────
|
|
|
408
523
|
echo ""
|
|
409
524
|
echo "Started ${#STARTED[@]}/${#MODULES[@]} epic(s)."
|
|
410
525
|
[ "${#FAILED[@]}" -gt 0 ] && echo "Failed: ${FAILED[*]:-}"
|
|
526
|
+
if [ "$SPAWN" -eq 0 ] || [ "$NO_SPAWN" -eq 1 ]; then
|
|
527
|
+
echo ""
|
|
528
|
+
echo "Nothing was spawned (default). Worktrees are ready and empty."
|
|
529
|
+
echo "Re-run with --spawn to launch agents, or run the commands above yourself."
|
|
530
|
+
fi
|
|
411
531
|
|
|
412
|
-
if [ "${#STARTED[@]}" -gt 0 ]; then
|
|
532
|
+
if [ "${#STARTED[@]}" -gt 0 ] && [ "$SPAWN" -eq 1 ] && [ "$NO_SPAWN" -eq 0 ]; then
|
|
413
533
|
echo ""
|
|
414
534
|
echo "Monitor:"
|
|
415
535
|
echo " bash .pi/scripts/epic-registry.sh list"
|
|
@@ -13,9 +13,10 @@ set -uo pipefail
|
|
|
13
13
|
|
|
14
14
|
MODULE="${1:-}"
|
|
15
15
|
shift || true
|
|
16
|
-
BASE="main"; DIR=""
|
|
16
|
+
BASE="main"; DIR=""; ASSUME_FROZEN=0
|
|
17
17
|
while [ $# -gt 0 ]; do
|
|
18
18
|
case "$1" in
|
|
19
|
+
--assume-frozen) ASSUME_FROZEN=1; shift ;;
|
|
19
20
|
--base) BASE="${2:-main}"; shift 2 ;;
|
|
20
21
|
--dir) DIR="${2:-}"; shift 2 ;;
|
|
21
22
|
*) shift ;;
|
|
@@ -58,7 +59,7 @@ PY
|
|
|
58
59
|
)
|
|
59
60
|
fi
|
|
60
61
|
|
|
61
|
-
if [ "$FROZEN" != "true" ]; then
|
|
62
|
+
if [ "$FROZEN" != "true" ] && [ "$ASSUME_FROZEN" -eq 0 ]; then
|
|
62
63
|
# NOTE: block here (before worktree creation) and exit 2 — a non-zero,
|
|
63
64
|
# distinguishable code so callers/agents cannot mistake it for success.
|
|
64
65
|
echo "⛔ Blocked: contracts for '$MODULE' are not frozen (registry: $FROZEN)."
|
|
@@ -115,9 +115,32 @@ function handleLine(line) {
|
|
|
115
115
|
// The prompt was accepted by an extension command?
|
|
116
116
|
if (msg.type === "response" && msg.command === "prompt" && msg.success === true) {
|
|
117
117
|
const d = msg.data?.disposition;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
// "handled" = an extension command consumed it (what we need).
|
|
119
|
+
// "started"/"queued" = it went to the MODEL. For a /command that means
|
|
120
|
+
// the extension was not loaded (wrong cwd, missing .pi/extensions) — the
|
|
121
|
+
// model then improvises the workflow, which looks like success and burns
|
|
122
|
+
// tokens. Treat that as a hard error.
|
|
123
|
+
const isCommand = opts.command.startsWith("/");
|
|
124
|
+
if (d === "handled") {
|
|
125
|
+
handled = true;
|
|
126
|
+
armGrace();
|
|
127
|
+
} else if (isCommand) {
|
|
128
|
+
emit(
|
|
129
|
+
JSON.stringify({
|
|
130
|
+
type: "runner_error",
|
|
131
|
+
reason: "command_not_dispatched",
|
|
132
|
+
detail:
|
|
133
|
+
`'${opts.command.split(/\s+/)[0]}' was routed to the model ` +
|
|
134
|
+
`(disposition "${d}") — the extension is not loaded in ${opts.cwd}. ` +
|
|
135
|
+
"Check that .pi/extensions exists in that directory.",
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
138
|
+
failed = true;
|
|
139
|
+
finish(3);
|
|
140
|
+
} else {
|
|
141
|
+
handled = true;
|
|
142
|
+
armGrace();
|
|
143
|
+
}
|
|
121
144
|
}
|
|
122
145
|
if (msg.type === "response" && msg.command === "prompt" && msg.success === false) {
|
|
123
146
|
emit(JSON.stringify({ type: "runner_error", reason: "prompt rejected" }));
|