aiur-cli 0.0.9-nightly.5a0d430 → 0.0.9-nightly.fac02a9
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/bin/aiur.js +37 -17
- package/libexec/aiur-engine.sh +175 -39
- package/libexec/aiur-epic.sh +41 -0
- package/libexec/aiur-queue.sh +93 -0
- package/package.json +6 -4
package/bin/aiur.js
CHANGED
|
@@ -165,8 +165,8 @@ function ensureTmux() {
|
|
|
165
165
|
// True only when the pinned opencode is on PATH; a wrong (e.g. newer,
|
|
166
166
|
// unverified) version must be replaced with the pin, so presence alone
|
|
167
167
|
// isn't enough.
|
|
168
|
-
function opencodeMatchesPin() {
|
|
169
|
-
const result = safeSpawn(
|
|
168
|
+
function opencodeMatchesPin(command = "opencode") {
|
|
169
|
+
const result = safeSpawn(command, ["--version"], { encoding: "utf8" });
|
|
170
170
|
return !result.error && result.status === 0 && (result.stdout || "").trim() === opencodeVersion();
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -185,40 +185,60 @@ function installOpencode() {
|
|
|
185
185
|
return !result.error && result.status === 0;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
// npm's global bin may be shadowed by another OpenCode on the incoming PATH.
|
|
189
|
+
// Verify the actual installed executable before making it the release's choice.
|
|
190
|
+
function selectGlobalOpencode() {
|
|
191
|
+
const result = safeSpawn("npm", ["prefix", "-g"], { encoding: "utf8" });
|
|
192
|
+
if (result.error || result.status !== 0) return false;
|
|
193
|
+
const prefix = (result.stdout || "").trim();
|
|
194
|
+
if (!path.isAbsolute(prefix)) return false;
|
|
195
|
+
const bin = path.join(prefix, "bin");
|
|
196
|
+
if (!opencodeMatchesPin(path.join(bin, "opencode"))) return false;
|
|
197
|
+
process.env.PATH = `${bin}${path.delimiter}${process.env.PATH || ""}`;
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
188
201
|
// opencode powers the interactive "take the wheel" panes. Non-fatal: everything
|
|
189
202
|
// else works without it.
|
|
190
203
|
function ensureOpencode() {
|
|
191
|
-
if (opencodeMatchesPin()) return;
|
|
204
|
+
if (opencodeMatchesPin() || selectGlobalOpencode()) return;
|
|
192
205
|
if (process.env.AIUR_SKIP_OPENCODE_INSTALL !== "1") {
|
|
193
206
|
process.stderr.write(
|
|
194
207
|
`aiur: provisioning opencode@${opencodeVersion()} (one-time, for the interactive 'take the wheel' panes)…\n`,
|
|
195
208
|
);
|
|
196
209
|
installOpencode();
|
|
197
|
-
if (opencodeMatchesPin()) return;
|
|
210
|
+
if (opencodeMatchesPin() || selectGlobalOpencode()) return;
|
|
198
211
|
}
|
|
199
212
|
process.stderr.write(
|
|
200
|
-
|
|
213
|
+
`aiur: opencode@${opencodeVersion()} is unavailable. The interactive 'take the wheel' ` +
|
|
201
214
|
"feature needs it; everything else works without it. " +
|
|
202
|
-
|
|
215
|
+
`Install with: npm install -g opencode-ai@${opencodeVersion()}, ` +
|
|
216
|
+
"and put npm's global bin ahead of other opencode binaries on PATH.\n",
|
|
203
217
|
);
|
|
204
218
|
}
|
|
205
219
|
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
220
|
+
// Match the shared engine's first-argument dispatch: only session launch paths
|
|
221
|
+
// need interactive tools. Unknown commands still reach the engine's usage error.
|
|
222
|
+
function requiresInteractiveTools(argv) {
|
|
223
|
+
const command = argv[0] || "";
|
|
224
|
+
if (["", "run", "restart", "--bg"].includes(command)) return true;
|
|
225
|
+
const nonLaunchCommands = new Set([
|
|
226
|
+
"__identity", "help", "-h", "-help", "--h", "--help", "--version", "--todo", "--only",
|
|
227
|
+
"init", "findings", "ask", "asks", "status", "usage", "agents", "commands", "units",
|
|
228
|
+
"build-orders", "queue", "analytics", "github-cost", "github-usage", "alerts", "watch", "set",
|
|
229
|
+
"upgrade", "pause", "resume", "reset-budget", "message", "cleanup-stale", "stop",
|
|
230
|
+
"executor-answer", "executor-escalate", "executor-moot", "executor-listen", "executor-wait",
|
|
231
|
+
"executor-emit", "executor-subscribe", "executor-unsubscribe", "executor-subscriptions",
|
|
232
|
+
"executor-roster", "executor-fast-forward", "executor-claim", "executor-release", "executor-revoke",
|
|
233
|
+
]);
|
|
234
|
+
if (nonLaunchCommands.has(command)) return false;
|
|
235
|
+
return command.startsWith("-") || fs.existsSync(command);
|
|
216
236
|
}
|
|
217
237
|
|
|
218
238
|
function main() {
|
|
219
239
|
const releaseDir = resolveReleaseDir();
|
|
220
240
|
|
|
221
|
-
if (
|
|
241
|
+
if (requiresInteractiveTools(process.argv.slice(2))) {
|
|
222
242
|
ensureTmux();
|
|
223
243
|
ensureOpencode();
|
|
224
244
|
}
|
package/libexec/aiur-engine.sh
CHANGED
|
@@ -20,9 +20,7 @@
|
|
|
20
20
|
#
|
|
21
21
|
# The release is self-contained (bundled ERTS), so it runs without mise/Elixir on
|
|
22
22
|
# PATH. Dev's build-if-stale step lives in the aiurdev shim, not here.
|
|
23
|
-
|
|
24
23
|
set -euo pipefail
|
|
25
|
-
|
|
26
24
|
# Raise the soft open-file limit toward the hard maximum. High agent concurrency
|
|
27
25
|
# spawns many tmux/opencode/git subprocesses + sockets; on hosts with a low
|
|
28
26
|
# default (macOS ships 256) that exhausts file descriptors (:emfile) and crashes
|
|
@@ -34,7 +32,6 @@ elif [ -n "${__aiur_hard_nofile}" ]; then
|
|
|
34
32
|
ulimit -Sn "${__aiur_hard_nofile}" 2>/dev/null || true
|
|
35
33
|
fi
|
|
36
34
|
unset __aiur_hard_nofile
|
|
37
|
-
|
|
38
35
|
# Export the effective soft limit after the best-effort raise. The BEAM uses
|
|
39
36
|
# this inherited value for FD-headroom admission on hosts without procfs,
|
|
40
37
|
# avoiding a runtime `ulimit` subprocess precisely when descriptors are scarce.
|
|
@@ -45,7 +42,6 @@ else
|
|
|
45
42
|
unset AIUR_NOFILE_SOFT_LIMIT
|
|
46
43
|
fi
|
|
47
44
|
unset __aiur_soft_nofile
|
|
48
|
-
|
|
49
45
|
# Preserve the shell that initiated the run as a best-effort Executor root.
|
|
50
46
|
# An explicit positive override wins (service managers may know a better root);
|
|
51
47
|
# otherwise the engine's parent is the nearest identity available before tmux
|
|
@@ -57,12 +53,10 @@ if ! [[ "${AIUR_OPERATOR_PID:-}" =~ ^[1-9][0-9]*$ ]]; then
|
|
|
57
53
|
unset AIUR_OPERATOR_PID
|
|
58
54
|
fi
|
|
59
55
|
fi
|
|
60
|
-
|
|
61
56
|
die() {
|
|
62
57
|
echo "❌ $*" >&2
|
|
63
58
|
exit 1
|
|
64
59
|
}
|
|
65
|
-
|
|
66
60
|
legacy_config_path() {
|
|
67
61
|
while [ "$#" -gt 0 ]; do
|
|
68
62
|
case "$1" in
|
|
@@ -203,7 +197,6 @@ warn_if_cli_behind_release_checkout() {
|
|
|
203
197
|
fi
|
|
204
198
|
}
|
|
205
199
|
|
|
206
|
-
# --- distribution identity (per-instance: keyed by the aiur project root) -----
|
|
207
200
|
|
|
208
201
|
# The aiur project root used to key this instance. AIUR_REPO_ROOT (set by the dev
|
|
209
202
|
# shim) wins. Otherwise walk up from $PWD to the first dir holding a REPO-LOCAL
|
|
@@ -311,7 +304,6 @@ aiur_print_identity() {
|
|
|
311
304
|
printf 'AIUR_COOKIE_FILE=%s\n' "$AIUR_COOKIE_FILE"
|
|
312
305
|
}
|
|
313
306
|
|
|
314
|
-
# --- BEAM distribution (cookie + named node) ---------------------------------
|
|
315
307
|
|
|
316
308
|
ensure_bg_state_dir() {
|
|
317
309
|
aiur_resolve_identity
|
|
@@ -368,7 +360,6 @@ prepare_distribution() {
|
|
|
368
360
|
export AIUR_ERLANG_COOKIE="$RELEASE_COOKIE"
|
|
369
361
|
}
|
|
370
362
|
|
|
371
|
-
# --- release resolution ------------------------------------------------------
|
|
372
363
|
|
|
373
364
|
release_dir=""
|
|
374
365
|
vsn_dir=""
|
|
@@ -405,7 +396,6 @@ resolve_release() {
|
|
|
405
396
|
fi
|
|
406
397
|
}
|
|
407
398
|
|
|
408
|
-
# --- argv round-trip (System.argv is empty under `elixir --eval`) -------------
|
|
409
399
|
|
|
410
400
|
argv_file=""
|
|
411
401
|
init_argv_file() {
|
|
@@ -450,6 +440,9 @@ usage() {
|
|
|
450
440
|
Usage: aiur [--interactive] [--no-dashboard] [--executor] [--pause] [--max-agents <n>] [--logs-root <path>] [--port <port>] [--host <host>] [config-path]
|
|
451
441
|
aiur run [--bg] [--no-dashboard] [--executor] [--debug] explicit launch form (foreground unless --bg)
|
|
452
442
|
aiur init [--force] scaffold .aiur/config (interactive setup wizard)
|
|
443
|
+
aiur login <harness> <name> [--dir <path>] sign in to a supported backend account
|
|
444
|
+
aiur accounts [<harness>] [--json] list registered accounts
|
|
445
|
+
aiur logout <harness> <name> [--purge] remove a backend account
|
|
453
446
|
aiur --bg [--no-dashboard] [--executor] [--debug] start detached; dashboard on unless suppressed
|
|
454
447
|
aiur stop stop the running session
|
|
455
448
|
aiur restart [--no-build] [run flags] stop, refresh the build, start again (detached)
|
|
@@ -460,18 +453,25 @@ Usage: aiur [--interactive] [--no-dashboard] [--executor] [--pause] [--max-agent
|
|
|
460
453
|
aiur executor-escalate <decision-id> --expected-version <n> --reason <text> [--executor-id <id>]
|
|
461
454
|
aiur executor-moot <decision-id> --expected-version <n> --reason-class <class> [--reason <text>] [--executor-id <id>]
|
|
462
455
|
aiur units [--scope live|unfinished|all|none] [--condition active|alert|paused|queued|finished]... [--format auto|table|records] [--json]
|
|
456
|
+
aiur queue show [--queue NAME] [--json] read queue; add/remove/reorder/hold/release/recover/clear steer it
|
|
463
457
|
aiur build-orders [<root>] [--json] show the Build Order catalog or one root
|
|
458
|
+
aiur epic set <epic> <ids...> [--as <who>] [--source cli|backfill-agent] [--json]
|
|
459
|
+
aiur epic clear <ids...> [--as <who>] [--json]
|
|
460
|
+
aiur epic show [<ids...>] [--json]
|
|
461
|
+
aiur epic list [--json]
|
|
464
462
|
aiur analytics [--range run|full] [--since <ISO-8601>] [--until <ISO-8601>] [--build-order <id>] [--json]
|
|
465
463
|
aiur github-cost [--budget graphql|core|all] [--format auto|table|records] [--json] rank GitHub API spend by call site
|
|
466
464
|
aiur github-usage [--json] per-actor (daemon vs agent) GitHub usage and ceilings
|
|
467
465
|
aiur alerts [--needs-attention] show structured alert feed
|
|
468
466
|
aiur watch [--full|--changes] [--interval <secs>] server-side status board
|
|
469
|
-
aiur
|
|
467
|
+
aiur listen [--topic <pattern> | --ticket <id>] stream events as JSON lines
|
|
468
|
+
aiur executor-listen [--topic <pattern>] deprecated alias for listen
|
|
470
469
|
aiur executor-wait [--timeout <seconds>] [--json] block until Executor work arrives
|
|
471
470
|
aiur executor-fast-forward <wake-id> [--as <id>] acknowledge an externally covered wake prefix
|
|
472
471
|
aiur executor-emit <topic> --payload <json> publish an Executor event
|
|
473
472
|
aiur executor-subscribe|executor-unsubscribe <pattern>
|
|
474
473
|
aiur executor-subscriptions list persistent Executor bindings
|
|
474
|
+
aiur workspace-recover <ticket-identifier> <generation> release a held workspace after verified provider exit
|
|
475
475
|
aiur executor-roster [--json] list Executor consumers with their liveness evidence
|
|
476
476
|
aiur executor-claim [--as <id>] claim the wake stream, or refuse and name the live owner
|
|
477
477
|
aiur executor-release [--as <id>] give up this consumer's claim
|
|
@@ -488,14 +488,13 @@ Usage: aiur [--interactive] [--no-dashboard] [--executor] [--pause] [--max-agent
|
|
|
488
488
|
aiur ask <title> [--body <text>|--body-file <path>] [--urgency low|normal|high] [--blocking]
|
|
489
489
|
aiur ask --done <id> [--note <text>] create or resolve an operator request
|
|
490
490
|
aiur asks [--open|--all] [--json] inspect current-repository operator requests
|
|
491
|
+
aiur doctor [--repair] check mise shims; repair only with consent
|
|
491
492
|
aiur cleanup-stale [--dry-run] list/reap stale manual-smoke leftovers
|
|
492
493
|
aiur --version
|
|
493
|
-
|
|
494
494
|
Bare aiur: start or attach to this directory's interactive session.
|
|
495
495
|
EOF
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
-
# --- one-shot: --version (no tmux) -------------------------------------------
|
|
499
498
|
|
|
500
499
|
run_version() {
|
|
501
500
|
resolve_release
|
|
@@ -511,7 +510,6 @@ run_version() {
|
|
|
511
510
|
exec "${release_cmd[@]}"
|
|
512
511
|
}
|
|
513
512
|
|
|
514
|
-
# --- one-shot: init (interactive wizard, distribution-free, no tmux) ----------
|
|
515
513
|
|
|
516
514
|
run_init() {
|
|
517
515
|
resolve_release
|
|
@@ -522,6 +520,47 @@ run_init() {
|
|
|
522
520
|
exec "${release_cmd[@]}"
|
|
523
521
|
}
|
|
524
522
|
|
|
523
|
+
run_local_cli() {
|
|
524
|
+
resolve_release
|
|
525
|
+
build_init_cmd
|
|
526
|
+
init_argv_file
|
|
527
|
+
write_argv "$@"
|
|
528
|
+
export AIUR_ARGV_FILE="$argv_file"
|
|
529
|
+
exec "${release_cmd[@]}"
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
run_claude_login() {
|
|
533
|
+
resolve_release
|
|
534
|
+
local profile_dir
|
|
535
|
+
build_init_cmd
|
|
536
|
+
init_argv_file
|
|
537
|
+
write_argv "__login_prepare" "${@:2}"
|
|
538
|
+
export AIUR_ARGV_FILE="$argv_file"
|
|
539
|
+
profile_dir="$("${release_cmd[@]}")" || return $?
|
|
540
|
+
[ -n "$profile_dir" ] || { echo "aiur: account profile directory is unavailable" >&2; return 1; }
|
|
541
|
+
exec env "CLAUDE_CONFIG_DIR=$profile_dir" claude
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
run_account_login() {
|
|
545
|
+
local harness="$2"
|
|
546
|
+
case "$harness" in
|
|
547
|
+
kimi|deepseek|openrouter) run_local_cli "$@"; return $? ;;
|
|
548
|
+
esac
|
|
549
|
+
resolve_release
|
|
550
|
+
local profile_dir
|
|
551
|
+
build_init_cmd
|
|
552
|
+
init_argv_file
|
|
553
|
+
write_argv "__login_prepare" "$harness" "${@:3}"
|
|
554
|
+
export AIUR_ARGV_FILE="$argv_file"
|
|
555
|
+
profile_dir="$("${release_cmd[@]}")" || return $?
|
|
556
|
+
[ -n "$profile_dir" ] || { echo "aiur: account profile directory is unavailable" >&2; return 1; }
|
|
557
|
+
case "$harness" in
|
|
558
|
+
claude) exec env "CLAUDE_CONFIG_DIR=$profile_dir" claude ;;
|
|
559
|
+
codex) exec env "CODEX_HOME=$profile_dir" codex ;;
|
|
560
|
+
*) echo "aiur: unsupported account harness: $harness" >&2; return 64 ;;
|
|
561
|
+
esac
|
|
562
|
+
}
|
|
563
|
+
|
|
525
564
|
# --- one-shot: --todo (control RPC; requires a running daemon) ----------------
|
|
526
565
|
|
|
527
566
|
parsed_todo_only=0
|
|
@@ -723,22 +762,10 @@ scrub_run_only_env() {
|
|
|
723
762
|
}
|
|
724
763
|
|
|
725
764
|
run_argv=()
|
|
726
|
-
# Default dashboard bind host.
|
|
727
|
-
#
|
|
728
|
-
# fall back to loopback when Tailscale is absent, or when dashboard credentials
|
|
729
|
-
# are unset (a non-loopback bind requires them, so we stay on loopback rather
|
|
730
|
-
# than refuse to start). The BEAM applies this below an explicit `server.host`,
|
|
731
|
-
# while an explicit `--host` remains the highest-precedence value.
|
|
765
|
+
# Default dashboard bind host. Remote access is an explicit operator choice.
|
|
766
|
+
# The BEAM applies this below `server.host`; `--host` has highest precedence.
|
|
732
767
|
default_dashboard_host() {
|
|
733
|
-
|
|
734
|
-
if command -v tailscale >/dev/null 2>&1; then
|
|
735
|
-
ip="$(tailscale ip -4 2>/dev/null | grep -m1 -E '^100\.[0-9]+\.[0-9]+\.[0-9]+$' || true)"
|
|
736
|
-
fi
|
|
737
|
-
if [ -n "$ip" ] && [ -n "${AIUR_DASHBOARD_USERNAME:-}" ] && [ -n "${AIUR_DASHBOARD_PASSWORD:-}" ]; then
|
|
738
|
-
printf '%s' "$ip"
|
|
739
|
-
else
|
|
740
|
-
printf '127.0.0.1'
|
|
741
|
-
fi
|
|
768
|
+
printf '%s' "${AIUR_DEFAULT_DASHBOARD_HOST:-127.0.0.1}"
|
|
742
769
|
}
|
|
743
770
|
|
|
744
771
|
build_run_argv() {
|
|
@@ -2620,8 +2647,8 @@ parse_issue_targets() {
|
|
|
2620
2647
|
|
|
2621
2648
|
[ "${#parsed_targets[@]}" -gt 0 ]
|
|
2622
2649
|
}
|
|
2623
|
-
|
|
2624
2650
|
cmd_status() {
|
|
2651
|
+
[ ! -f "$engine_dir/aiur-mise-doctor" ] || bash "$engine_dir/aiur-mise-doctor" --check || true
|
|
2625
2652
|
[ "$#" -eq 0 ] || die "status does not accept arguments"
|
|
2626
2653
|
run_control_rpc "Aiur.AgentControlCLI.status()"
|
|
2627
2654
|
}
|
|
@@ -2633,6 +2660,44 @@ cmd_usage() {
|
|
|
2633
2660
|
run_control_rpc "Aiur.AgentControlCLI.usage()"
|
|
2634
2661
|
}
|
|
2635
2662
|
|
|
2663
|
+
cmd_accounts() {
|
|
2664
|
+
local json_arg=false harness="" arg encoded expression
|
|
2665
|
+
for arg in "$@"; do
|
|
2666
|
+
case "$arg" in
|
|
2667
|
+
--json)
|
|
2668
|
+
[ "$json_arg" = false ] || die "accounts accepts --json only once"
|
|
2669
|
+
json_arg=true
|
|
2670
|
+
;;
|
|
2671
|
+
--all)
|
|
2672
|
+
;;
|
|
2673
|
+
-*)
|
|
2674
|
+
die "accounts accepts an optional harness and --json"
|
|
2675
|
+
;;
|
|
2676
|
+
*)
|
|
2677
|
+
[ -z "$harness" ] || die "accounts accepts only one harness"
|
|
2678
|
+
harness="$arg"
|
|
2679
|
+
;;
|
|
2680
|
+
esac
|
|
2681
|
+
done
|
|
2682
|
+
|
|
2683
|
+
resolve_release || return $?
|
|
2684
|
+
prepare_distribution || die "distribution setup failed; cannot contact aiur"
|
|
2685
|
+
resolve_control_identity_from_records
|
|
2686
|
+
if [ "$(probe_node_liveness)" = "down" ]; then
|
|
2687
|
+
# The local one-shot CLI renders the identity and marks usage unavailable.
|
|
2688
|
+
# It never makes a provider request.
|
|
2689
|
+
run_local_cli accounts "$@"
|
|
2690
|
+
else
|
|
2691
|
+
if [ -n "$harness" ]; then
|
|
2692
|
+
encoded="$(printf '%s' "$harness" | base64 | tr -d '\n')"
|
|
2693
|
+
expression="Aiur.AgentControlCLI.accounts($json_arg, Base.decode64!(\"$encoded\"))"
|
|
2694
|
+
else
|
|
2695
|
+
expression="Aiur.AgentControlCLI.accounts($json_arg)"
|
|
2696
|
+
fi
|
|
2697
|
+
run_control_rpc "$expression"
|
|
2698
|
+
fi
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2636
2701
|
cmd_pause_resume() {
|
|
2637
2702
|
local command="$1"
|
|
2638
2703
|
shift
|
|
@@ -2680,6 +2745,16 @@ cmd_reset_budget() {
|
|
|
2680
2745
|
run_control_rpc "$expression"
|
|
2681
2746
|
}
|
|
2682
2747
|
|
|
2748
|
+
# Requires the operator to name the exact ticket and generation shown by
|
|
2749
|
+
# status. The daemon independently verifies the recorded boot proof.
|
|
2750
|
+
cmd_workspace_recover() {
|
|
2751
|
+
[ "$#" -eq 2 ] || { echo "aiur: workspace-recover expects a ticket identifier and generation (e.g. aiur workspace-recover ENG-123 7)" >&2; exit 64; }
|
|
2752
|
+
local ticket="$1" generation="$2" encoded
|
|
2753
|
+
[[ "$generation" =~ ^[1-9][0-9]*$ ]] || { echo "aiur: workspace-recover generation must be a positive integer" >&2; exit 64; }
|
|
2754
|
+
encoded="$(printf '%s' "$ticket" | base64 | tr -d '\n')"
|
|
2755
|
+
run_control_rpc "Aiur.AgentControlCLI.recover_workspace(Base.decode64!(\"$encoded\"), $generation)"
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2683
2758
|
# `aiur message <issue> <text>` — deliver Executor text to one running agent.
|
|
2684
2759
|
# The text is base64-encoded for the RPC hop so arbitrary content (quotes,
|
|
2685
2760
|
# backslashes, `#{}`, newlines) survives without Elixir-string escaping.
|
|
@@ -3161,21 +3236,61 @@ cmd_watch() {
|
|
|
3161
3236
|
fi
|
|
3162
3237
|
}
|
|
3163
3238
|
|
|
3164
|
-
|
|
3165
|
-
|
|
3239
|
+
listen_clock() { printf '%s' "$SECONDS"; }
|
|
3240
|
+
|
|
3241
|
+
cmd_listen() {
|
|
3242
|
+
local topic="executor.#" ticket="" arg
|
|
3166
3243
|
while [ "$#" -gt 0 ]; do
|
|
3167
3244
|
arg="$1"
|
|
3168
3245
|
case "$arg" in
|
|
3169
3246
|
--topic) shift; topic="${1:-}" ;;
|
|
3170
3247
|
--topic=*) topic="${arg#--topic=}" ;;
|
|
3171
|
-
|
|
3248
|
+
--ticket) shift; ticket="${1:-}" ;;
|
|
3249
|
+
--ticket=*) ticket="${arg#--ticket=}" ;;
|
|
3250
|
+
*) echo "aiur: listen accepts --topic <pattern> or --ticket <id>" >&2; exit 64 ;;
|
|
3172
3251
|
esac
|
|
3173
|
-
[ -n "$topic" ] || { echo "aiur: executor-listen requires a topic" >&2; exit 64; }
|
|
3174
3252
|
shift
|
|
3175
3253
|
done
|
|
3254
|
+
if [ -n "$ticket" ]; then
|
|
3255
|
+
[[ "$ticket" =~ ^[0-9]+$ ]] || { echo "aiur: listen --ticket expects a numeric ticket id" >&2; exit 64; }
|
|
3256
|
+
[ "$topic" = "executor.#" ] || { echo "aiur: listen cannot combine --ticket and --topic" >&2; exit 64; }
|
|
3257
|
+
topic="ticket.${ticket}.#"
|
|
3258
|
+
fi
|
|
3259
|
+
[ -n "$topic" ] || { echo "aiur: listen requires a topic" >&2; exit 64; }
|
|
3176
3260
|
local encoded
|
|
3177
3261
|
encoded="$(printf '%s' "$topic" | base64 | tr -d '\n')"
|
|
3178
|
-
|
|
3262
|
+
run_control_rpc "Aiur.AgentControlCLI.executor_listen_validate(Base.decode64!(\"$encoded\"))" || return $?
|
|
3263
|
+
# A stream that stayed up 30s had a live connection, so losing it starts a
|
|
3264
|
+
# new outage. Each outage gets a bounded backoff totalling ~10 minutes, long
|
|
3265
|
+
# enough to outlast a normal `aiur restart`.
|
|
3266
|
+
# ponytail: attempt duration stands in for "connected"; a wedged daemon whose
|
|
3267
|
+
# RPC hangs 30s+ before failing keeps the listener retrying past the budget.
|
|
3268
|
+
# Upgrade to a listener-ready signal if that case shows up in practice.
|
|
3269
|
+
local waited=0 delay=2 started status
|
|
3270
|
+
while :; do
|
|
3271
|
+
started="$(listen_clock)"
|
|
3272
|
+
status=0
|
|
3273
|
+
# Subshell: a `die` inside (e.g. the release dir missing mid-rebuild during
|
|
3274
|
+
# `aiurdev restart`) fails this attempt with exit 1 instead of the listener.
|
|
3275
|
+
(run_control_stream "Aiur.AgentControlCLI.executor_listen(topic: Base.decode64!(\"$encoded\"))") || status=$?
|
|
3276
|
+
[ "$status" -eq 0 ] && return 0
|
|
3277
|
+
if [ "$status" -ne 1 ] || [ "${AIUR_LISTEN_RECONNECT:-1}" -ne 1 ]; then
|
|
3278
|
+
echo "aiur: listen stopped after streaming control RPC failure (exit ${status}); restart the command after correcting the daemon error" >&2
|
|
3279
|
+
return "$status"
|
|
3280
|
+
fi
|
|
3281
|
+
if [ $(($(listen_clock) - started)) -ge 30 ]; then
|
|
3282
|
+
waited=0
|
|
3283
|
+
delay=2
|
|
3284
|
+
fi
|
|
3285
|
+
if [ "$waited" -ge 600 ]; then
|
|
3286
|
+
echo "aiur: listen could not reconnect within ${waited} seconds; daemon may be unavailable" >&2
|
|
3287
|
+
return 1
|
|
3288
|
+
fi
|
|
3289
|
+
echo "aiur: listen lost the daemon stream (exit ${status}); reconnecting in ${delay} seconds" >&2
|
|
3290
|
+
sleep "$delay"
|
|
3291
|
+
waited=$((waited + delay))
|
|
3292
|
+
delay=$((delay * 2 > 60 ? 60 : delay * 2))
|
|
3293
|
+
done
|
|
3179
3294
|
}
|
|
3180
3295
|
|
|
3181
3296
|
cmd_executor_wait() {
|
|
@@ -4061,8 +4176,8 @@ cmd_upgrade() {
|
|
|
4061
4176
|
}
|
|
4062
4177
|
|
|
4063
4178
|
# --- dispatch ----------------------------------------------------------------
|
|
4064
|
-
|
|
4065
4179
|
dispatch_run() {
|
|
4180
|
+
[ ! -f "$engine_dir/aiur-mise-doctor" ] || bash "$engine_dir/aiur-mise-doctor" --check || true
|
|
4066
4181
|
local mode="foreground" arg
|
|
4067
4182
|
local args=()
|
|
4068
4183
|
|
|
@@ -4080,7 +4195,6 @@ dispatch_run() {
|
|
|
4080
4195
|
# `set -u` — happens for a bare `--bg` run. Guard the expansion.
|
|
4081
4196
|
run_session "$mode" "${args[@]+"${args[@]}"}"
|
|
4082
4197
|
}
|
|
4083
|
-
|
|
4084
4198
|
aiur_engine_main() {
|
|
4085
4199
|
local cmd="${1:-}"
|
|
4086
4200
|
# Names the running subcommand in control-RPC diagnostics so a failure says
|
|
@@ -4106,6 +4220,16 @@ aiur_engine_main() {
|
|
|
4106
4220
|
init)
|
|
4107
4221
|
run_init "$@"
|
|
4108
4222
|
;;
|
|
4223
|
+
login)
|
|
4224
|
+
run_account_login "$@"
|
|
4225
|
+
;;
|
|
4226
|
+
accounts)
|
|
4227
|
+
shift
|
|
4228
|
+
cmd_accounts "$@"
|
|
4229
|
+
;;
|
|
4230
|
+
logout)
|
|
4231
|
+
run_local_cli "$@"
|
|
4232
|
+
;;
|
|
4109
4233
|
findings)
|
|
4110
4234
|
run_findings "$@"
|
|
4111
4235
|
;;
|
|
@@ -4119,6 +4243,7 @@ aiur_engine_main() {
|
|
|
4119
4243
|
shift
|
|
4120
4244
|
dispatch_run "$@"
|
|
4121
4245
|
;;
|
|
4246
|
+
doctor) shift; exec bash "$engine_dir/aiur-mise-doctor" "$@" ;;
|
|
4122
4247
|
status)
|
|
4123
4248
|
shift
|
|
4124
4249
|
cmd_status "$@"
|
|
@@ -4151,6 +4276,11 @@ aiur_engine_main() {
|
|
|
4151
4276
|
shift
|
|
4152
4277
|
cmd_units "$@"
|
|
4153
4278
|
;;
|
|
4279
|
+
queue)
|
|
4280
|
+
shift
|
|
4281
|
+
cmd_queue "$@"
|
|
4282
|
+
;;
|
|
4283
|
+
epic) shift; cmd_epic "$@" ;;
|
|
4154
4284
|
build-orders)
|
|
4155
4285
|
shift
|
|
4156
4286
|
cmd_build_orders "$@"
|
|
@@ -4175,9 +4305,9 @@ aiur_engine_main() {
|
|
|
4175
4305
|
shift
|
|
4176
4306
|
cmd_watch "$@"
|
|
4177
4307
|
;;
|
|
4178
|
-
executor-listen)
|
|
4308
|
+
listen|executor-listen)
|
|
4179
4309
|
shift
|
|
4180
|
-
|
|
4310
|
+
cmd_listen "$@"
|
|
4181
4311
|
;;
|
|
4182
4312
|
executor-wait)
|
|
4183
4313
|
shift
|
|
@@ -4231,6 +4361,10 @@ aiur_engine_main() {
|
|
|
4231
4361
|
shift
|
|
4232
4362
|
cmd_reset_budget "$@"
|
|
4233
4363
|
;;
|
|
4364
|
+
workspace-recover)
|
|
4365
|
+
shift
|
|
4366
|
+
cmd_workspace_recover "$@"
|
|
4367
|
+
;;
|
|
4234
4368
|
message)
|
|
4235
4369
|
shift
|
|
4236
4370
|
cmd_message "$@"
|
|
@@ -4267,6 +4401,8 @@ aiur_engine_main() {
|
|
|
4267
4401
|
esac
|
|
4268
4402
|
}
|
|
4269
4403
|
|
|
4404
|
+
source "$(dirname "${BASH_SOURCE[0]}")/aiur-queue.sh"
|
|
4405
|
+
source "$(dirname "${BASH_SOURCE[0]}")/aiur-epic.sh"
|
|
4270
4406
|
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
|
|
4271
4407
|
aiur_engine_main "$@"
|
|
4272
4408
|
fi
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Shared launcher epic command parser. Sourced by aiur-engine.sh.
|
|
2
|
+
cmd_epic() {
|
|
3
|
+
local action="${1:-}" epic="" who="${USER:-}" source="cli" json=0 ids="" count=0 arg n opts
|
|
4
|
+
[ "$#" -gt 0 ] && shift
|
|
5
|
+
case "$action" in
|
|
6
|
+
set) [ "$#" -gt 0 ] || { echo 'aiur: epic set requires an epic' >&2; exit 64; }; epic="$1"; shift
|
|
7
|
+
[[ "$epic" =~ ^[a-z0-9][a-z0-9_-]{0,63}$ ]] || { echo 'aiur: invalid epic key' >&2; exit 64; } ;;
|
|
8
|
+
clear|show|list) ;;
|
|
9
|
+
*) echo 'aiur: epic requires set, clear, show or list' >&2; exit 64 ;;
|
|
10
|
+
esac
|
|
11
|
+
while [ "$#" -gt 0 ]; do
|
|
12
|
+
arg="$1"
|
|
13
|
+
case "$arg" in
|
|
14
|
+
--json) json=1 ;;
|
|
15
|
+
--as)
|
|
16
|
+
[[ "$action" == set || "$action" == clear ]] && [ "$#" -ge 2 ] || { echo 'aiur: invalid --as' >&2; exit 64; }
|
|
17
|
+
who="$2"; shift ;;
|
|
18
|
+
--source)
|
|
19
|
+
[ "$action" = set ] && [ "$#" -ge 2 ] || { echo 'aiur: invalid --source' >&2; exit 64; }
|
|
20
|
+
source="$2"; shift
|
|
21
|
+
[[ "$source" == cli || "$source" == backfill-agent ]] || { echo 'aiur: invalid epic source' >&2; exit 64; } ;;
|
|
22
|
+
-*) echo 'aiur: unknown epic option' >&2; exit 64 ;;
|
|
23
|
+
*)
|
|
24
|
+
[ "$action" != list ] && [[ "$arg" =~ ^#?[1-9][0-9]{0,9}$ ]] || { echo 'aiur: invalid epic ticket id' >&2; exit 64; }
|
|
25
|
+
n="${arg#\#}"; [ -n "$ids" ] && ids="$ids, "; ids="$ids$n"; count=$((count + 1)) ;;
|
|
26
|
+
esac
|
|
27
|
+
shift
|
|
28
|
+
done
|
|
29
|
+
[ "$count" -le 200 ] || { echo 'aiur: epic accepts at most 200 ids' >&2; exit 64; }
|
|
30
|
+
opts="action: :$action"
|
|
31
|
+
if [[ "$action" == set || "$action" == clear ]]; then
|
|
32
|
+
[ "$count" -gt 0 ] && [[ "$who" =~ ^[A-Za-z0-9._-]{1,64}$ ]] || { echo 'aiur: epic write requires ids and a valid --as or USER' >&2; exit 64; }
|
|
33
|
+
[ "$action" = set ] && opts="$opts, epic: Base.decode64!(\"$(encode_control_value "$epic")\")"
|
|
34
|
+
opts="$opts, ids: [$ids], who: Base.decode64!(\"$(encode_control_value "$who")\")"
|
|
35
|
+
[ "$action" = set ] && { if [ "$source" = cli ]; then opts="$opts, source: :cli"; else opts="$opts, source: :\"backfill-agent\""; fi; }
|
|
36
|
+
elif [ "$count" -gt 0 ]; then
|
|
37
|
+
opts="$opts, ids: [$ids]"
|
|
38
|
+
fi
|
|
39
|
+
[ "$json" -eq 1 ] && opts="$opts, json: true"
|
|
40
|
+
run_control_rpc "Aiur.AgentControlCLI.epic([$opts])"
|
|
41
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Queue commands shared by the installed CLI and aiurdev.
|
|
2
|
+
queue_usage_error() { echo "aiur: queue $*" >&2; exit 64; }
|
|
3
|
+
queue_string() { printf 'Base.decode64!("%s")' "$(printf '%s' "$1" | base64 | tr -d '\n')"; }
|
|
4
|
+
queue_workspace_guard() {
|
|
5
|
+
if [ -n "${AIUR_AGENT_WORKSPACE:-}" ] || [[ "$PWD" == */aiur-workspaces/* ]] ||
|
|
6
|
+
[[ "${AIUR_PROJECT_ROOT:-}" == */aiur-workspaces/* ]] || [[ "${AIUR_REPO_ROOT:-}" == */aiur-workspaces/* ]]; then
|
|
7
|
+
echo 'aiur queue changes are blocked inside agent workspaces' >&2
|
|
8
|
+
exit 64
|
|
9
|
+
fi
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
cmd_queue() {
|
|
13
|
+
local verb="${1:-}" json=0 queue="" after="" at="" to="" root="" opts="" id status=0 force=0 remove_markers=0 yes=0
|
|
14
|
+
local ids=()
|
|
15
|
+
case "$verb" in
|
|
16
|
+
show) ;;
|
|
17
|
+
add|remove|reorder|hold|release|recover|clear) queue_workspace_guard ;;
|
|
18
|
+
*) queue_usage_error 'expects show, add, remove, reorder, hold, release, recover, or clear' ;;
|
|
19
|
+
esac
|
|
20
|
+
shift
|
|
21
|
+
while [ "$#" -gt 0 ]; do
|
|
22
|
+
case "$1" in
|
|
23
|
+
--force) [ "$verb" = recover ] && [ "$force" -eq 0 ] || queue_usage_error '--force requires recover'; force=1 ;;
|
|
24
|
+
--remove-markers) [ "$verb" = clear ] && [ "$remove_markers" -eq 0 ] || queue_usage_error '--remove-markers requires clear'; remove_markers=1 ;;
|
|
25
|
+
--yes) [ "$verb" = clear ] && [ "$yes" -eq 0 ] || queue_usage_error '--yes requires clear'; yes=1 ;;
|
|
26
|
+
--json) [ "$verb" = show ] || queue_usage_error '--json requires show'; json=1 ;;
|
|
27
|
+
--queue)
|
|
28
|
+
[ "$#" -ge 2 ] && [ -n "$2" ] && [[ "$2" != -* ]] || queue_usage_error '--queue requires a name'
|
|
29
|
+
case "$verb" in show|add|hold|release) ;; *) queue_usage_error "--queue is not valid for $verb" ;; esac
|
|
30
|
+
[ -z "$queue" ] || queue_usage_error 'duplicate --queue'
|
|
31
|
+
queue="$2"; shift ;;
|
|
32
|
+
--after)
|
|
33
|
+
[ "$verb" = add ] && [ "$#" -ge 2 ] && [[ "$2" =~ ^[1-9][0-9]*$ ]] && [ -z "$after" ] || queue_usage_error '--after requires add and a positive ticket ID'
|
|
34
|
+
after="$2"; shift ;;
|
|
35
|
+
--at)
|
|
36
|
+
[ "$verb" = add ] && [ "$#" -ge 2 ] && [[ "$2" =~ ^(0|[1-9][0-9]*)$ ]] && [ -z "$at" ] || queue_usage_error '--at requires add and a non-negative position'
|
|
37
|
+
at="$2"; shift ;;
|
|
38
|
+
--to)
|
|
39
|
+
[ "$verb" = reorder ] && [ "$#" -ge 2 ] && [[ "$2" =~ ^(0|[1-9][0-9]*)$ ]] && [ -z "$to" ] || queue_usage_error '--to requires reorder and a non-negative position'
|
|
40
|
+
to="$2"; shift ;;
|
|
41
|
+
--build-order)
|
|
42
|
+
[ "$verb" = add ] && [ "$#" -ge 2 ] && [[ "$2" =~ ^[1-9][0-9]*$ ]] && [ -z "$root" ] || queue_usage_error '--build-order requires add and a positive root ID'
|
|
43
|
+
root="$2"; shift ;;
|
|
44
|
+
-*) queue_usage_error "received an unknown argument: $1" ;;
|
|
45
|
+
*)
|
|
46
|
+
[[ "$1" =~ ^[1-9][0-9]*$ ]] || queue_usage_error "invalid ticket ID: $1"
|
|
47
|
+
for id in "${ids[@]}"; do [ "$id" != "$1" ] || queue_usage_error "duplicate ticket ID: $1"; done
|
|
48
|
+
ids+=("$1") ;;
|
|
49
|
+
esac
|
|
50
|
+
shift
|
|
51
|
+
done
|
|
52
|
+
case "$verb" in
|
|
53
|
+
show) [ "${#ids[@]}" -eq 0 ] || queue_usage_error 'show does not take ticket IDs' ;;
|
|
54
|
+
add)
|
|
55
|
+
if [ -n "$root" ]; then
|
|
56
|
+
[ "${#ids[@]}" -eq 0 ] && [ -z "$after$at" ] || queue_usage_error '--build-order cannot be combined with ticket IDs, --after, or --at'
|
|
57
|
+
else
|
|
58
|
+
[ "${#ids[@]}" -gt 0 ] || queue_usage_error 'add requires ticket IDs or --build-order'
|
|
59
|
+
fi ;;
|
|
60
|
+
recover) [ "${#ids[@]}" -eq 0 ] || queue_usage_error 'recover does not take ticket IDs' ;;
|
|
61
|
+
clear)
|
|
62
|
+
[ "${#ids[@]}" -eq 0 ] && [ "$remove_markers" -eq 1 ] || queue_usage_error 'clear requires --remove-markers and no ticket IDs'
|
|
63
|
+
[ "$yes" -eq 1 ] || { echo 'aiur: queue clear requires --yes; removes all queue membership and markers, keeping todo' >&2; exit 1; } ;;
|
|
64
|
+
remove) [ "${#ids[@]}" -gt 0 ] || queue_usage_error 'remove requires ticket IDs' ;;
|
|
65
|
+
reorder) [ "${#ids[@]}" -eq 1 ] && [ -n "$to" ] || queue_usage_error 'reorder requires one ticket ID and --to POS' ;;
|
|
66
|
+
hold|release)
|
|
67
|
+
{ [ "${#ids[@]}" -eq 1 ] && [ -z "$queue" ]; } ||
|
|
68
|
+
{ [ "${#ids[@]}" -eq 0 ] && [ -n "$queue" ]; } || queue_usage_error "$verb requires one ticket ID or --queue NAME" ;;
|
|
69
|
+
esac
|
|
70
|
+
if [ "$verb" != show ]; then
|
|
71
|
+
opts="verb: :$verb, caller_agent_workspace: $(queue_string "${AIUR_AGENT_WORKSPACE:-}")"
|
|
72
|
+
fi
|
|
73
|
+
[ "$force" -eq 1 ] && opts="$opts, force: true"
|
|
74
|
+
[ "$remove_markers" -eq 1 ] && opts="$opts, remove_markers: true"
|
|
75
|
+
[ "$yes" -eq 1 ] && opts="$opts, yes: true"
|
|
76
|
+
[ "$json" -eq 1 ] && opts="json: true"
|
|
77
|
+
[ -n "$queue" ] && opts="${opts:+$opts, }queue: $(queue_string "$queue")"
|
|
78
|
+
[ -n "$root" ] && opts="$opts, build_order: $root"
|
|
79
|
+
[ -n "$after" ] && opts="$opts, after: \"$after\""
|
|
80
|
+
[ -n "$at" ] && opts="$opts, at: $at"
|
|
81
|
+
[ -n "$to" ] && opts="$opts, to: $to"
|
|
82
|
+
if [ "${#ids[@]}" -gt 0 ]; then
|
|
83
|
+
opts="$opts, ids: ["
|
|
84
|
+
for id in "${ids[@]}"; do opts="$opts\"$id\","; done
|
|
85
|
+
opts="${opts%,}]"
|
|
86
|
+
fi
|
|
87
|
+
if [ "$verb" = add ] || [ "$verb" = remove ]; then
|
|
88
|
+
AIUR_CONTROL_RPC_TIMEOUT_SECONDS="$(todo_rpc_seconds "${#ids[@]}" 0)"
|
|
89
|
+
fi
|
|
90
|
+
AIUR_CONTROL_COMMAND="queue $verb" run_control_rpc "Aiur.AgentControlCLI.queue([$opts])" || status=$?
|
|
91
|
+
if [ "$status" -eq 124 ]; then echo 'aiur: queue outcome unknown; run aiur queue show before retrying' >&2; fi
|
|
92
|
+
return "$status"
|
|
93
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aiur-cli",
|
|
3
|
-
"version": "0.0.9-nightly.
|
|
3
|
+
"version": "0.0.9-nightly.fac02a9",
|
|
4
4
|
"description": "AI Unit Runtime for Executors.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/aiur-team/aiur",
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"bin/aiur.js",
|
|
20
20
|
"libexec/aiur-engine.sh",
|
|
21
|
+
"libexec/aiur-queue.sh",
|
|
22
|
+
"libexec/aiur-epic.sh",
|
|
21
23
|
"libexec/aiur-pane-ctrlc",
|
|
22
24
|
"scripts/postinstall.mjs",
|
|
23
25
|
"share/aiur.tmux.conf"
|
|
@@ -35,8 +37,8 @@
|
|
|
35
37
|
],
|
|
36
38
|
"opencodeVersion": "1.17.10",
|
|
37
39
|
"optionalDependencies": {
|
|
38
|
-
"aiur-cli-linux-x64": "0.0.9-nightly.
|
|
39
|
-
"aiur-cli-linux-arm64": "0.0.9-nightly.
|
|
40
|
-
"aiur-cli-darwin-arm64": "0.0.9-nightly.
|
|
40
|
+
"aiur-cli-linux-x64": "0.0.9-nightly.fac02a9",
|
|
41
|
+
"aiur-cli-linux-arm64": "0.0.9-nightly.fac02a9",
|
|
42
|
+
"aiur-cli-darwin-arm64": "0.0.9-nightly.fac02a9"
|
|
41
43
|
}
|
|
42
44
|
}
|