loki-mode 11.15.0 → 11.17.1
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/SKILL.md +3 -3
- package/VERSION +1 -1
- package/autonomy/grill.sh +3 -1
- package/autonomy/hooks/validate-bash.sh +51 -13
- package/autonomy/lib/chat.sh +111 -7
- package/autonomy/lib/claude-flags.sh +20 -0
- package/autonomy/lib/claude_permissions.py +40 -0
- package/autonomy/lib/done-recognition.sh +8 -4
- package/autonomy/lib/engine10-push.sh +30 -5
- package/autonomy/lib/prd-enrich.sh +4 -2
- package/autonomy/lib/proof-generator.py +5 -0
- package/autonomy/lib/proof-verify.py +109 -1
- package/autonomy/lib/trust_trajectory.py +13 -4
- package/autonomy/lib/voter-agents.sh +3 -1
- package/autonomy/loki +175 -26
- package/autonomy/quickstart.sh +21 -2
- package/autonomy/run.sh +16 -6
- package/autonomy/trigger-server.py +11 -5
- package/completions/loki.bash +2 -2
- package/dashboard/__init__.py +1 -1
- package/dashboard/api_start.py +2 -2
- package/dashboard/build_supervisor.py +2 -0
- package/docs/CLI-REFERENCE.md +2 -1
- package/docs/EVALUATING.md +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/v10/BOARD.md +25 -0
- package/docs/v10/CLI-MODERN.md +3 -3
- package/docs/v10/DECISIONS.md +1 -0
- package/docs/v10/ENGINE.md +1 -1
- package/docs/v10/FAILURE-CLASSES.md +18 -0
- package/docs/v10/PROGRESS.md +4 -0
- package/loki-ts/data/finding-schema.json +2 -1
- package/loki-ts/dist/loki.js +1135 -1114
- package/magic/core/debate.py +27 -1
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/packages/control-plane/dist/server.js +3761 -3454
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/claude.sh +6 -3
- package/providers/model_catalog.json +6 -0
- package/src/policies/claude_permissions.js +114 -0
- package/web-app/dist/assets/{AdminPage-bZlvH4TL.js → AdminPage-OVzN6eLm.js} +1 -1
- package/web-app/dist/assets/{Avatar-BZrBstQe.js → Avatar-hCCrxrCZ.js} +1 -1
- package/web-app/dist/assets/{Badge-Q8l8r4Vq.js → Badge-CDcpkhbJ.js} +1 -1
- package/web-app/dist/assets/{Button-B8Bt0A3S.js → Button-8mwWlhEu.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-CIYExrXa.js → CockpitPage-D8cf3u22.js} +1 -1
- package/web-app/dist/assets/{ComparePage-CBOmHdam.js → ComparePage-CIgkbDRY.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-BKwDPSnK.js → ErrorBoundary-99kJH9g2.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-KqzjphWl.js → EvidenceReceiptPanel-C9fj32eS.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-lF3GkHRv.js → GitHubIssuesPanel-De43YQ_C.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-BCzipPWD.js → GitHubPRsPanel-7g7ZTnkx.js} +1 -1
- package/web-app/dist/assets/{HomePage-CIdnkzaN.js → HomePage-CcNA24ny.js} +2 -2
- package/web-app/dist/assets/{LoginPage-D-178nTT.js → LoginPage-BYjlb-IK.js} +1 -1
- package/web-app/dist/assets/{MagicPage-BmHuzMT8.js → MagicPage-C0Dwt9Na.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-CPCiyDFn.js → MetricsPage-jDGmdcgg.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-B2T6n1gx.js → NotFoundPage-C_ZO8tLE.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-DQmpFhb0.js → ProjectPage-B5wnurfQ.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-BmugzFEd.js → ProjectsPage--EyKt5-D.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-ZB-RrmgM.js → SettingsPage-cB0lnfti.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-BAhwHt4_.js → ShowcasePage-Dl16FDkK.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-Bt7sf2Vu.js → SystemSettingsPage-BmAXd4V2.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-B-v5j_8q.js → TeamsPage-BFkWVOZe.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-bdqUL-LG.js → TemplatesPage-Bf2kmEUM.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-CRHEXBwS.js → TerminalOutput-BItv9Wa6.js} +1 -1
- package/web-app/dist/assets/{activity-C-ROkmXt.js → activity-Df2Awg6I.js} +1 -1
- package/web-app/dist/assets/{bell-DbiCBxQA.js → bell-D-1ztTsJ.js} +1 -1
- package/web-app/dist/assets/{bot-DsIgE_qr.js → bot-A4qgZsKi.js} +1 -1
- package/web-app/dist/assets/{check-CkAdmHWP.js → check-BPfFpF0c.js} +1 -1
- package/web-app/dist/assets/{chevron-left-CYTLivAo.js → chevron-left-CfSJvmDi.js} +1 -1
- package/web-app/dist/assets/{circle-alert-BVIcAsFJ.js → circle-alert-wx2d1dzb.js} +1 -1
- package/web-app/dist/assets/{clock-Dg3Rq0go.js → clock-pQMai6l-.js} +1 -1
- package/web-app/dist/assets/{cloud-CenrqHFl.js → cloud-DWL470r6.js} +1 -1
- package/web-app/dist/assets/{code-xml-C2mvfoug.js → code-xml-BaEQpQLZ.js} +1 -1
- package/web-app/dist/assets/{database-BKa0_eCb.js → database-h-N7WkQN.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-DfelUK1S.js → dollar-sign-B5oYiD27.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-CZKWoHVg.js → file-code-corner-fbTtTy0o.js} +1 -1
- package/web-app/dist/assets/{file-plus-CZ-m1zMX.js → file-plus-PaF6HMo8.js} +1 -1
- package/web-app/dist/assets/{globe-S0kv_GwC.js → globe-Ct-GlWID.js} +1 -1
- package/web-app/dist/assets/{hammer-BW1MDdu1.js → hammer-C1oZFX51.js} +1 -1
- package/web-app/dist/assets/{index-ACEYsTc2.js → index-C2egrRhz.js} +3 -3
- package/web-app/dist/assets/{layers-BrXHYZ-L.js → layers-Dt5ZK4v2.js} +1 -1
- package/web-app/dist/assets/{loader-circle-DOdhKFx0.js → loader-circle-DrpMziic.js} +1 -1
- package/web-app/dist/assets/{lock-BcFTyg8J.js → lock-DOez8MmK.js} +1 -1
- package/web-app/dist/assets/{package-D34cMVQG.js → package-ClGIkerr.js} +1 -1
- package/web-app/dist/assets/{plus-CEaMeBxL.js → plus-C004i9ua.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-D2Rzxr5U.js → refresh-cw-Ddfk-7Sy.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-C_-EjRLh.js → rotate-ccw-Bu1KgDjS.js} +1 -1
- package/web-app/dist/assets/{scroll-text-BSSCMI_c.js → scroll-text-3q1lW5pm.js} +1 -1
- package/web-app/dist/assets/{server-BKvLonWt.js → server-pd6x4HW1.js} +1 -1
- package/web-app/dist/assets/{shield-alert-DbbQbPdW.js → shield-alert-B9jGJepT.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-NoBD1fUR.js → thumbs-up-Cdcc9nQf.js} +1 -1
- package/web-app/dist/assets/{trash-2-BXz8rAyd.js → trash-2-foSojXHK.js} +1 -1
- package/web-app/dist/assets/{trending-down-D8u535Re.js → trending-down-CTTiqlJ1.js} +1 -1
- package/web-app/dist/assets/{trending-up-q8k3kgiy.js → trending-up-B_MgmRAx.js} +1 -1
- package/web-app/dist/assets/{upload-Cp7_zeBI.js → upload-g6BfALzG.js} +1 -1
- package/web-app/dist/assets/{usePolling-CTHVdcuX.js → usePolling-DrHwvzuh.js} +1 -1
- package/web-app/dist/assets/{user-DF7bfSf_.js → user-COp75m7l.js} +1 -1
- package/web-app/dist/index.html +1 -1
|
@@ -183,7 +183,7 @@ def _cost_value(proof):
|
|
|
183
183
|
|
|
184
184
|
|
|
185
185
|
def _intervention_detail(proof):
|
|
186
|
-
"""(by_type, waited_s) from proof.interventions_detail; None when absent."""
|
|
186
|
+
"""(by_type, waited_s, active_s) from proof.interventions_detail; None when absent."""
|
|
187
187
|
d = _obj(proof.get("interventions_detail"))
|
|
188
188
|
bt = _obj(d.get("by_type"))
|
|
189
189
|
out = {}
|
|
@@ -198,7 +198,9 @@ def _intervention_detail(proof):
|
|
|
198
198
|
any_key = True
|
|
199
199
|
w = d.get("waited_s")
|
|
200
200
|
ws = w if isinstance(w, (int, float)) and not isinstance(w, bool) and w >= 0 else None
|
|
201
|
-
|
|
201
|
+
a = d.get("active_s")
|
|
202
|
+
act = a if isinstance(a, int) and not isinstance(a, bool) and a >= 0 else None
|
|
203
|
+
return (out if any_key else None), ws, act
|
|
202
204
|
|
|
203
205
|
|
|
204
206
|
def _verdict_evidence(proof):
|
|
@@ -229,13 +231,14 @@ def _load_runs(loki_dir):
|
|
|
229
231
|
# Malformed / partial proof: skip, do not fail the trajectory.
|
|
230
232
|
continue
|
|
231
233
|
verdict, false_verified = _verdict_evidence(proof)
|
|
232
|
-
by_type, waited_s = _intervention_detail(proof)
|
|
234
|
+
by_type, waited_s, active_s = _intervention_detail(proof)
|
|
233
235
|
runs.append({
|
|
234
236
|
"verdict": verdict,
|
|
235
237
|
"false_verified": false_verified,
|
|
236
238
|
"cost_usd": _cost_value(proof),
|
|
237
239
|
"interventions_by_type": by_type,
|
|
238
240
|
"waited_s": waited_s,
|
|
241
|
+
"active_s": active_s,
|
|
239
242
|
"run_id": str(proof.get("run_id") or name),
|
|
240
243
|
"generated_at": proof.get("generated_at"),
|
|
241
244
|
"council_pass_rate": _council_pass_value(proof.get("council")),
|
|
@@ -343,6 +346,7 @@ def outcome_effort(series):
|
|
|
343
346
|
by_type = {k: sum(r["interventions_by_type"][k] for r in with_detail) for k in _IV_TYPES}
|
|
344
347
|
all_iv = bool(series) and all(r["interventions"] is not None for r in series)
|
|
345
348
|
all_waited = bool(series) and all(r["waited_s"] is not None for r in series)
|
|
349
|
+
all_active = bool(series) and all(r["active_s"] is not None for r in series)
|
|
346
350
|
return {
|
|
347
351
|
"runs": len(series),
|
|
348
352
|
"accepted": accepted,
|
|
@@ -356,6 +360,8 @@ def outcome_effort(series):
|
|
|
356
360
|
round(sum(r["interventions"] for r in series) / accepted, 4) if accepted and all_iv else None),
|
|
357
361
|
"waited_min_per_accepted": (
|
|
358
362
|
round(sum(r["waited_s"] for r in series) / 60.0 / accepted, 1) if accepted and all_waited else None),
|
|
363
|
+
"active_min_per_accepted": (
|
|
364
|
+
round(sum(r["active_s"] for r in series) / 60.0 / accepted, 1) if accepted and all_active else None),
|
|
359
365
|
}
|
|
360
366
|
|
|
361
367
|
|
|
@@ -376,7 +382,9 @@ def _fmt_outcome_effort(e):
|
|
|
376
382
|
lines.append(" Interventions per accepted: %s" % ("n/a" if ipa is None else ipa))
|
|
377
383
|
w = e.get("waited_min_per_accepted")
|
|
378
384
|
lines.append(" Minutes per accepted: %s" % (
|
|
379
|
-
"n/a" if w is None else "%s (waited, upper bound
|
|
385
|
+
"n/a" if w is None else "%s (waited, upper bound)" % w))
|
|
386
|
+
am = e.get("active_min_per_accepted")
|
|
387
|
+
lines.append(" Active minutes per accepted: %s" % ("n/a (not measured)" if am is None else am))
|
|
380
388
|
return lines
|
|
381
389
|
|
|
382
390
|
|
|
@@ -397,6 +405,7 @@ def compute_trajectory(loki_dir):
|
|
|
397
405
|
"cost_usd": r["cost_usd"],
|
|
398
406
|
"interventions_by_type": r["interventions_by_type"],
|
|
399
407
|
"waited_s": r["waited_s"],
|
|
408
|
+
"active_s": r["active_s"],
|
|
400
409
|
"verdict": r["verdict"],
|
|
401
410
|
"false_verified": r["false_verified"],
|
|
402
411
|
})
|
|
@@ -373,8 +373,10 @@ loki_council_dispatch_agents() {
|
|
|
373
373
|
# (completion-council.sh:2792). Fail-closed: a hung or timed-out council can
|
|
374
374
|
# never become a false COMPLETE; it always degrades to the heuristic path
|
|
375
375
|
# (which has its own timeout + conservative defaults).
|
|
376
|
+
_LOKI_PERM_ARGV=(--dangerously-skip-permissions)
|
|
377
|
+
! type _loki_claude_permission_argv >/dev/null 2>&1 || _loki_claude_permission_argv
|
|
376
378
|
response=$(timeout "${LOKI_COUNCIL_REVIEW_TIMEOUT:-600}" \
|
|
377
|
-
env CAVEMAN_DEFAULT_MODE=off claude
|
|
379
|
+
env CAVEMAN_DEFAULT_MODE=off claude "${_LOKI_PERM_ARGV[@]}" \
|
|
378
380
|
-p "$prompt" \
|
|
379
381
|
--agents "$agents_json" \
|
|
380
382
|
--json-schema "$schema_content" 2>"$stderr_log") || rc=$?
|
package/autonomy/loki
CHANGED
|
@@ -132,6 +132,34 @@ _loki_caveman_narration_level() {
|
|
|
132
132
|
fi
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
# P12-26b: permission flags for the migrate/doc/heal claude launches. Lazily sources
|
|
136
|
+
# claude-flags.sh (like the caveman wrapper above) and fills _LOKI_PERM_ARGV; unset
|
|
137
|
+
# LOKI_PERMISSION_POLICY keeps --dangerously-skip-permissions byte-identically.
|
|
138
|
+
_loki_perm_argv_for() {
|
|
139
|
+
_LOKI_PERM_ARGV=(--dangerously-skip-permissions)
|
|
140
|
+
# Unset policy: return before sourcing anything (claude-flags.sh exports caveman vars).
|
|
141
|
+
[ "${LOKI_PERMISSION_POLICY:-}" = "compiled" ] || return 0
|
|
142
|
+
_LOKI_PERM_ARGV=(--permission-mode plan)
|
|
143
|
+
local _pa_lib="${_LOKI_SCRIPT_DIR}/lib/claude-flags.sh" _pa_out _pa_a
|
|
144
|
+
if [ ! -f "$_pa_lib" ]; then
|
|
145
|
+
echo "[loki] WARNING: LOKI_PERMISSION_POLICY=compiled: helper missing; using most restrictive mode (plan)" >&2
|
|
146
|
+
return 0
|
|
147
|
+
fi
|
|
148
|
+
# Subshell so the helper's top-level exports never leak into this shell.
|
|
149
|
+
_pa_out="$(
|
|
150
|
+
LOKI_PROJECT_DIR="${1:-${LOKI_PROJECT_DIR:-$PWD}}"
|
|
151
|
+
# shellcheck source=autonomy/lib/claude-flags.sh
|
|
152
|
+
. "$_pa_lib" >/dev/null 2>&1 || exit 1
|
|
153
|
+
declare -F _loki_claude_permission_argv >/dev/null 2>&1 || exit 1
|
|
154
|
+
_loki_claude_permission_argv
|
|
155
|
+
printf '%s\n' "${_LOKI_PERM_ARGV[@]}"
|
|
156
|
+
)" || return 0
|
|
157
|
+
[ -n "$_pa_out" ] || return 0
|
|
158
|
+
_LOKI_PERM_ARGV=()
|
|
159
|
+
while IFS= read -r _pa_a; do _LOKI_PERM_ARGV+=("$_pa_a"); done <<< "$_pa_out"
|
|
160
|
+
return 0
|
|
161
|
+
}
|
|
162
|
+
|
|
135
163
|
# loki edit: one-pass small change fast path (functions-only, self-guarded).
|
|
136
164
|
if [ -f "$_LOKI_SCRIPT_DIR/edit.sh" ]; then
|
|
137
165
|
# shellcheck source=edit.sh
|
|
@@ -1797,7 +1825,7 @@ show_help_all() {
|
|
|
1797
1825
|
echo " stop Stop execution immediately"
|
|
1798
1826
|
echo " pause Pause after current session"
|
|
1799
1827
|
echo " resume Resume a paused or interrupted run (after Ctrl-C or a crash)"
|
|
1800
|
-
echo " steer \"<note>\" Nudge a running build (needs LOKI_PROMPT_INJECTION=
|
|
1828
|
+
echo " steer \"<note>\" Nudge a running build (Loki 10 engine; legacy loop needs LOKI_PROMPT_INJECTION=true)"
|
|
1801
1829
|
echo ""
|
|
1802
1830
|
echo "Verify / trust:"
|
|
1803
1831
|
echo " verify [base] Deterministic PR verification (CI-gate exit codes)"
|
|
@@ -1842,7 +1870,7 @@ show_help_all() {
|
|
|
1842
1870
|
echo ""
|
|
1843
1871
|
echo "Options for 'start':"
|
|
1844
1872
|
echo " --provider NAME AI provider: claude (default), codex, cline, aider, opencode"
|
|
1845
|
-
echo "
|
|
1873
|
+
echo " (model route: set LOKI_MODEL_ROUTE, e.g. ollama/qwen3-coder; there is no --model flag)"
|
|
1846
1874
|
echo " --simple Force simple complexity tier (3 phases)"
|
|
1847
1875
|
echo " --complex Force complex complexity tier (8 phases)"
|
|
1848
1876
|
echo " --no-dashboard Disable web dashboard"
|
|
@@ -2519,6 +2547,37 @@ _loki_start_handoff() {
|
|
|
2519
2547
|
}
|
|
2520
2548
|
|
|
2521
2549
|
# Start Loki Mode
|
|
2550
|
+
# Every value-taking cmd_start flag (its value is never brief text or a run id).
|
|
2551
|
+
# The one list: the --workspace brief scan and the --resume scan both use it.
|
|
2552
|
+
# It also names value flags that are not cmd_start's own, so a scan never
|
|
2553
|
+
# mistakes their value for a brief or run id. --model is honored nowhere (model
|
|
2554
|
+
# routing is LOKI_MODEL_ROUTE); --max-cost only via the engine10 rewrite.
|
|
2555
|
+
_loki_start_flag_takes_value() {
|
|
2556
|
+
case "$1" in
|
|
2557
|
+
--provider|--brief|--budget|--prd|--issue|--isolation|--aider-model|--aider-flags|--cline-model|--compliance|--max-duration|--log-level|--bmad-project|--openspec|--mirofish-rounds|--mirofish-timeout|--output|--config|--vars|--env-file|--remote|--attempts|--mirofish-docker|--spec|--model|--max-cost|--max-wait|--base-wait|--max-iterations|--max-retries|--budget-limit|--session-model|--completion-promise) return 0 ;;
|
|
2558
|
+
esac
|
|
2559
|
+
return 1
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
# Optional-value cmd_start flags (--mirofish [URL]). The one decision on whether
|
|
2563
|
+
# the NEXT token is the flag's value: non-empty and not another --flag. The
|
|
2564
|
+
# cmd_start --mirofish arm and both scans use it, so they cannot disagree.
|
|
2565
|
+
_loki_start_flag_optional_value() { [ "$1" = "--mirofish" ]; }
|
|
2566
|
+
_loki_start_optval_consumes() { [[ -n "${1:-}" && "${1:-}" != --* ]]; }
|
|
2567
|
+
|
|
2568
|
+
# One exit-code mapping for an engine10 resume, shared by cmd_resume and
|
|
2569
|
+
# `loki start --resume`. 0 and 4 (phase reported) -> 0, 5 (publish failed) -> 1.
|
|
2570
|
+
# Sets _LOKI_E10_RC and returns 0 when handled; any other code returns 1 with
|
|
2571
|
+
# _LOKI_E10_RC set to the raw code (cmd_resume falls through, start returns it).
|
|
2572
|
+
_loki_e10_resume_map() {
|
|
2573
|
+
case "$1" in
|
|
2574
|
+
0 | 4) _LOKI_E10_RC=0 ;;
|
|
2575
|
+
5) _LOKI_E10_RC=1 ;;
|
|
2576
|
+
*) _LOKI_E10_RC="$1"; return 1 ;;
|
|
2577
|
+
esac
|
|
2578
|
+
return 0
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2522
2581
|
cmd_start() {
|
|
2523
2582
|
# First-run welcome (once): open the branded welcome page on the very
|
|
2524
2583
|
# first start. Writes ~/.loki/.welcomed and never repeats; non-blocking,
|
|
@@ -2584,13 +2643,13 @@ cmd_start() {
|
|
|
2584
2643
|
local _ps_skip=0
|
|
2585
2644
|
for _ps_a in ${_ps_rest[@]+"${_ps_rest[@]}"}; do
|
|
2586
2645
|
if [ "$_ps_skip" = 1 ]; then _ps_skip=0; continue; fi
|
|
2646
|
+
if [ "$_ps_skip" = 3 ]; then _ps_skip=0; if _loki_start_optval_consumes "$_ps_a"; then continue; fi; fi
|
|
2587
2647
|
if [ "$_ps_skip" = 2 ]; then _ps_skip=0; _ps_brief+=("$_ps_a"); continue; fi
|
|
2588
2648
|
case "$_ps_a" in
|
|
2589
2649
|
--brief=*) _ps_brief+=("${_ps_a#*=}") ;;
|
|
2590
2650
|
--brief) _ps_skip=2 ;;
|
|
2591
2651
|
# Every value-taking cmd_start flag: its value is never brief text.
|
|
2592
|
-
|
|
2593
|
-
-*) ;;
|
|
2652
|
+
-*) if _loki_start_flag_takes_value "$_ps_a"; then _ps_skip=1; elif _loki_start_flag_optional_value "$_ps_a"; then _ps_skip=3; fi ;;
|
|
2594
2653
|
*) _ps_brief+=("$_ps_a") ;;
|
|
2595
2654
|
esac
|
|
2596
2655
|
done
|
|
@@ -2615,6 +2674,34 @@ cmd_start() {
|
|
|
2615
2674
|
cmd_deploy "${_ps_dargs[@]}"
|
|
2616
2675
|
return $?
|
|
2617
2676
|
fi
|
|
2677
|
+
# P12-07b: a bare `loki start --resume [run-id]` goes to the same engine10
|
|
2678
|
+
# resume path as `loki resume` (util/run_outputs.ts resumeMain), never into a
|
|
2679
|
+
# new build. Exit codes go through _loki_e10_resume_map (0 and 4 -> 0, 5 -> 1); any other code, such as 3 for nothing to resume, is returned as is.
|
|
2680
|
+
local _ps_resume=0 _ps_resume_args=() _ps_rskip=0
|
|
2681
|
+
for _ps_a in ${_ps_rest[@]+"${_ps_rest[@]}"}; do
|
|
2682
|
+
if [ "$_ps_rskip" = 1 ]; then _ps_rskip=0; continue; fi
|
|
2683
|
+
if [ "$_ps_rskip" = 3 ]; then _ps_rskip=0; if _loki_start_optval_consumes "$_ps_a"; then continue; fi; fi
|
|
2684
|
+
case "$_ps_a" in
|
|
2685
|
+
--resume) _ps_resume=1 ;;
|
|
2686
|
+
--*=*) ;;
|
|
2687
|
+
-*) if _loki_start_flag_takes_value "$_ps_a"; then _ps_rskip=1; elif _loki_start_flag_optional_value "$_ps_a"; then _ps_rskip=3; fi ;;
|
|
2688
|
+
*) _ps_resume_args+=("$_ps_a") ;;
|
|
2689
|
+
esac
|
|
2690
|
+
done
|
|
2691
|
+
if [ "$_ps_resume" = 1 ]; then
|
|
2692
|
+
local _ps_rrc=0
|
|
2693
|
+
for _ps_a in ${_ps_resume_args[@]+"${_ps_resume_args[@]}"}; do
|
|
2694
|
+
case "$_ps_a" in
|
|
2695
|
+
e10-[A-Za-z0-9-]*) ;;
|
|
2696
|
+
*)
|
|
2697
|
+
echo -e "${RED}--resume resumes a sealed run; it takes an optional run id, not a task. Run \`loki start <task>\` for a new run.${NC}" >&2
|
|
2698
|
+
return 2 ;;
|
|
2699
|
+
esac
|
|
2700
|
+
done
|
|
2701
|
+
( _loki_bun_delegate engine10 resume ${_ps_resume_args[@]+"${_ps_resume_args[@]}"} ) || _ps_rrc=$?
|
|
2702
|
+
_loki_e10_resume_map "$_ps_rrc" || true
|
|
2703
|
+
return "$_LOKI_E10_RC"
|
|
2704
|
+
fi
|
|
2618
2705
|
set -- ${_ps_rest[@]+"${_ps_rest[@]}"}
|
|
2619
2706
|
|
|
2620
2707
|
local args=()
|
|
@@ -2675,7 +2762,7 @@ cmd_start() {
|
|
|
2675
2762
|
echo " Token from LOKI_REMOTE_TOKEN / LOKI_REMOTE_TOKEN_FILE"
|
|
2676
2763
|
echo " (never an argument). Env: LOKI_REMOTE_URL."
|
|
2677
2764
|
echo " --provider NAME AI provider: claude (default), codex, cline, aider, opencode"
|
|
2678
|
-
echo "
|
|
2765
|
+
echo " (model route: set LOKI_MODEL_ROUTE, for example ollama/qwen3-coder; no --model flag)"
|
|
2679
2766
|
echo " --allow-haiku Enable Haiku model for the fast tier (default: disabled)"
|
|
2680
2767
|
echo " --simple Force simple complexity tier (3 phases)"
|
|
2681
2768
|
echo " --complex Force complex complexity tier (8 phases)"
|
|
@@ -2720,6 +2807,7 @@ cmd_start() {
|
|
|
2720
2807
|
echo " --pr Worktree + auto-create PR"
|
|
2721
2808
|
echo " --ship Worktree + PR + auto-merge"
|
|
2722
2809
|
echo " --detach, -d Run in background (implies --worktree)"
|
|
2810
|
+
echo " --resume [RUN_ID] Resume an interrupted run at the stage it stopped in"
|
|
2723
2811
|
echo ""
|
|
2724
2812
|
echo "Preview without spending (any input mode):"
|
|
2725
2813
|
echo " loki plan <PRD> Analyze a spec WITHOUT executing: complexity,"
|
|
@@ -3024,7 +3112,7 @@ cmd_start() {
|
|
|
3024
3112
|
shift
|
|
3025
3113
|
;;
|
|
3026
3114
|
--mirofish)
|
|
3027
|
-
if
|
|
3115
|
+
if _loki_start_optval_consumes "${2:-}"; then
|
|
3028
3116
|
mirofish_url="$2"
|
|
3029
3117
|
shift 2
|
|
3030
3118
|
else
|
|
@@ -5071,15 +5159,19 @@ cmd_pause() {
|
|
|
5071
5159
|
# RUN-25 iter 22 (Wave D #3): `loki steer "<note>"` writes an operator directive
|
|
5072
5160
|
# to .loki/HUMAN_INPUT.md. engine10 reads it before the next plan or implement
|
|
5073
5161
|
# session (default on; only LOKI_PROMPT_INJECTION=0/false opts out, steer.ts).
|
|
5074
|
-
# The legacy loop injects it only when LOKI_PROMPT_INJECTION=
|
|
5162
|
+
# The legacy loop injects it only when LOKI_PROMPT_INJECTION=true.
|
|
5075
5163
|
# Each steer is a numbered revision: `## Operator directive rev N (ts) actor=A source=S`.
|
|
5076
5164
|
# engine10 (util/steer.ts) archives the file and appends the revisions to
|
|
5077
5165
|
# .loki/intent-revisions.jsonl. Actor and source come from LOKI_STEER_ACTOR
|
|
5078
5166
|
# (default: git user.name, then $USER) and LOKI_STEER_SOURCE (cli, chat or an
|
|
5079
5167
|
# https comment URL; default cli). Both are reduced to a safe character set.
|
|
5080
5168
|
cmd_steer() {
|
|
5081
|
-
|
|
5082
|
-
|
|
5169
|
+
# "--" ends options so guidance beginning with "-" is data, not a flag
|
|
5170
|
+
local _eoo=0
|
|
5171
|
+
if [ "${1:-}" = "--" ]; then _eoo=1; shift; fi
|
|
5172
|
+
case "$_eoo:${1:-}" in
|
|
5173
|
+
1:?*) ;;
|
|
5174
|
+
*:--help|*:-h|*:help|*:"")
|
|
5083
5175
|
echo -e "${BOLD}Loki Mode -- steer the running build${NC}"
|
|
5084
5176
|
echo ""
|
|
5085
5177
|
echo "Usage: loki steer \"<guidance>\""
|
|
@@ -5087,11 +5179,13 @@ cmd_steer() {
|
|
|
5087
5179
|
echo "Writes your guidance to .loki/HUMAN_INPUT.md as a numbered revision"
|
|
5088
5180
|
echo "(rev N, actor, source). The Loki 10 engine applies it once, before the"
|
|
5089
5181
|
echo "next plan or implement session, and records it in .loki/intent-revisions.jsonl."
|
|
5182
|
+
echo "A steer that arrives after the Wall (the frozen acceptance tests) was authored"
|
|
5183
|
+
echo "makes Seal refuse the run; rerun the task with the new intent included."
|
|
5090
5184
|
echo ""
|
|
5091
5185
|
echo " loki steer \"focus on the auth flow, skip the docs for now\""
|
|
5092
5186
|
echo ""
|
|
5093
5187
|
echo "Opt out with LOKI_PROMPT_INJECTION=0. The legacy loop reads the note"
|
|
5094
|
-
echo "only when LOKI_PROMPT_INJECTION=
|
|
5188
|
+
echo "only when LOKI_PROMPT_INJECTION=true."
|
|
5095
5189
|
[ -z "${1:-}" ] && return 2
|
|
5096
5190
|
return 0
|
|
5097
5191
|
;;
|
|
@@ -5127,6 +5221,7 @@ cmd_steer() {
|
|
|
5127
5221
|
echo -e "${DIM}The Loki 10 engine applies it before the next plan or implement session.${NC}"
|
|
5128
5222
|
# Same text the issue comment ack posts (trigger-server.py steer_ack_text).
|
|
5129
5223
|
echo "Steer rev $rev received: applies at next plan/implement stage."
|
|
5224
|
+
echo "If the Wall is already frozen, Seal will refuse this run: rerun with the new intent."
|
|
5130
5225
|
;;
|
|
5131
5226
|
esac
|
|
5132
5227
|
return 0
|
|
@@ -5289,10 +5384,7 @@ cmd_resume() {
|
|
|
5289
5384
|
if ls "$LOKI_DIR"/runs/e10-*/events.jsonl >/dev/null 2>&1; then
|
|
5290
5385
|
local _e10_rc=0
|
|
5291
5386
|
( _loki_bun_delegate engine10 resume "$@" ) || _e10_rc=$?
|
|
5292
|
-
|
|
5293
|
-
0 | 4) return 0 ;;
|
|
5294
|
-
5) return 1 ;;
|
|
5295
|
-
esac
|
|
5387
|
+
if _loki_e10_resume_map "$_e10_rc"; then return "$_LOKI_E10_RC"; fi
|
|
5296
5388
|
fi
|
|
5297
5389
|
|
|
5298
5390
|
# Check if there is anything to resume before touching signal files
|
|
@@ -6290,7 +6382,7 @@ if _unc and os.path.exists(_unc):
|
|
|
6290
6382
|
except Exception:
|
|
6291
6383
|
pass
|
|
6292
6384
|
print(" Unstick it : give the loop a concrete next step:")
|
|
6293
|
-
print(" loki steer \"<what to try next>\" (needs LOKI_PROMPT_INJECTION=
|
|
6385
|
+
print(" loki steer \"<what to try next>\" (Loki 10 engine; legacy loop needs LOKI_PROMPT_INJECTION=true)")
|
|
6294
6386
|
print(" or narrow the spec / fix the blocker, then loki resume.")
|
|
6295
6387
|
|
|
6296
6388
|
# Surface the structured per-iteration error record, if one exists at
|
|
@@ -18014,9 +18106,10 @@ Tasks:
|
|
|
18014
18106
|
# pre-caveman. No-op when caveman is absent.
|
|
18015
18107
|
local _loki_mig_cm=""
|
|
18016
18108
|
_loki_mig_cm="$(_loki_caveman_narration_level)"
|
|
18109
|
+
_loki_perm_argv_for "$codebase_path"
|
|
18017
18110
|
case "$provider_name" in
|
|
18018
18111
|
claude)
|
|
18019
|
-
{ (cd "$codebase_path" && if [ -n "$_loki_mig_cm" ]; then CAVEMAN_DEFAULT_MODE="$_loki_mig_cm" claude
|
|
18112
|
+
{ (cd "$codebase_path" && if [ -n "$_loki_mig_cm" ]; then CAVEMAN_DEFAULT_MODE="$_loki_mig_cm" claude "${_LOKI_PERM_ARGV[@]}" -p "$phase_prompt" --output-format stream-json --verbose 2>&1; else claude "${_LOKI_PERM_ARGV[@]}" -p "$phase_prompt" --output-format stream-json --verbose 2>&1; fi) | \
|
|
18020
18113
|
while IFS= read -r line; do
|
|
18021
18114
|
# Extract text from stream-json
|
|
18022
18115
|
if echo "$line" | python3 -c "
|
|
@@ -18191,9 +18284,10 @@ IMPORTANT RULES:
|
|
|
18191
18284
|
# branch, byte-identical to pre-caveman (empty mode is NOT inert). No-op absent.
|
|
18192
18285
|
local _loki_doc_cm=""
|
|
18193
18286
|
_loki_doc_cm="$(_loki_caveman_narration_level)"
|
|
18287
|
+
_loki_perm_argv_for "$codebase_path"
|
|
18194
18288
|
case "$provider_name" in
|
|
18195
18289
|
claude)
|
|
18196
|
-
{ (cd "$codebase_path" && if [ -n "$_loki_doc_cm" ]; then CAVEMAN_DEFAULT_MODE="$_loki_doc_cm" claude
|
|
18290
|
+
{ (cd "$codebase_path" && if [ -n "$_loki_doc_cm" ]; then CAVEMAN_DEFAULT_MODE="$_loki_doc_cm" claude "${_LOKI_PERM_ARGV[@]}" -p "$doc_prompt" --output-format stream-json --verbose 2>&1; else claude "${_LOKI_PERM_ARGV[@]}" -p "$doc_prompt" --output-format stream-json --verbose 2>&1; fi) | \
|
|
18197
18291
|
while IFS= read -r line; do
|
|
18198
18292
|
if echo "$line" | python3 -c "
|
|
18199
18293
|
import sys, json
|
|
@@ -19256,7 +19350,8 @@ Begin healing now. Follow the RARV cycle for each action."
|
|
|
19256
19350
|
_loki_heal_cm="$(_loki_caveman_narration_level)"
|
|
19257
19351
|
case "$provider" in
|
|
19258
19352
|
claude)
|
|
19259
|
-
|
|
19353
|
+
_loki_perm_argv_for "$codebase_path"
|
|
19354
|
+
local run_args=("${_LOKI_PERM_ARGV[@]}" -p "$heal_prompt" --output-format stream-json --verbose)
|
|
19260
19355
|
(cd "$codebase_path" && if [ -n "$_loki_heal_cm" ]; then CAVEMAN_DEFAULT_MODE="$_loki_heal_cm" claude "${run_args[@]}" 2>&1; else claude "${run_args[@]}" 2>&1; fi) | \
|
|
19261
19356
|
while IFS= read -r line; do
|
|
19262
19357
|
if echo "$line" | python3 -c "
|
|
@@ -26031,9 +26126,11 @@ _pr_record_merge() {
|
|
|
26031
26126
|
|
|
26032
26127
|
# Shared merge gate. One PR (number or branch) merges only when it is not a
|
|
26033
26128
|
# draft, `gh pr checks` exits 0 after polling pending (rc=8), and, with
|
|
26034
|
-
# --require-verified,
|
|
26035
|
-
#
|
|
26036
|
-
#
|
|
26129
|
+
# --require-verified, every clean proof recorded for the PR's head commit
|
|
26130
|
+
# (facts.git.head_sha == headRefOid, dirty-tree proofs ignored) says VERIFIED; a stale or missing proof
|
|
26131
|
+
# refuses ("gate: head"). The merge passes --match-head-commit so a push after
|
|
26132
|
+
# the gate cannot land unjudged. On refusal it prints the exact blocking gate
|
|
26133
|
+
# ("gate: draft|head|verdict|checks") and leaves the PR open.
|
|
26037
26134
|
# Usage: _merge_pr_gated <pr> [--require-verified] [--delete-branch]
|
|
26038
26135
|
_merge_pr_gated() {
|
|
26039
26136
|
local pr="$1" need_verified=false margs=(--squash) rc d
|
|
@@ -26052,14 +26149,59 @@ _merge_pr_gated() {
|
|
|
26052
26149
|
_pr_record_merge "$pr" "blocked:draft" "draft"
|
|
26053
26150
|
return 1
|
|
26054
26151
|
fi
|
|
26152
|
+
# Pin the merge to the exact head the checks and proof are about to judge.
|
|
26153
|
+
local head=""
|
|
26154
|
+
head="$(gh pr view "$pr" --json headRefOid -q .headRefOid 2>/dev/null)" || head=""
|
|
26155
|
+
case "$head" in
|
|
26156
|
+
*[!0-9a-f]*) head="" ;;
|
|
26157
|
+
esac
|
|
26158
|
+
if [ "${#head}" != "40" ] && [ "${#head}" != "64" ]; then
|
|
26159
|
+
echo "$pr: not merged, gate: head (PR head commit is unreadable); PR left open" >&2
|
|
26160
|
+
_pr_record_merge "$pr" "blocked:head" "head"
|
|
26161
|
+
return 1
|
|
26162
|
+
fi
|
|
26055
26163
|
if $need_verified; then
|
|
26056
|
-
local pid="" headline=""
|
|
26164
|
+
local pid="" headline="" pj="" ph="" cands=() dirty_n=0 unknown_n=0
|
|
26165
|
+
# Resolve the proof recorded for THIS head: the run pointer first, then
|
|
26166
|
+
# any proof in .loki/proofs whose facts.git.head_sha equals the PR head.
|
|
26057
26167
|
[ -f .loki/state/last-proof-id.txt ] && pid="$(cat .loki/state/last-proof-id.txt 2>/dev/null)"
|
|
26058
|
-
|
|
26059
|
-
|
|
26168
|
+
[ -z "$pid" ] || cands+=(".loki/proofs/${pid}/proof.json")
|
|
26169
|
+
cands+=(.loki/proofs/*/proof.json)
|
|
26170
|
+
if command -v jq >/dev/null 2>&1; then
|
|
26171
|
+
# Every clean proof for this head must agree: an older VERIFIED
|
|
26172
|
+
# never overrides a newer NOT_PROVEN. A proof taken on a dirty tree
|
|
26173
|
+
# (facts.git.dirty anything but literal false, incl. missing/null; .loki/ is excluded from that flag) judged
|
|
26174
|
+
# bytes other than the commit and is ignored.
|
|
26175
|
+
local hl="" disagree=false
|
|
26176
|
+
for pj in "${cands[@]}"; do
|
|
26177
|
+
[ -f "$pj" ] || continue
|
|
26178
|
+
ph="$(jq -r '.facts.git.head_sha // empty' "$pj" 2>/dev/null)"
|
|
26179
|
+
[ "$ph" = "$head" ] || continue
|
|
26180
|
+
case "$(jq -r 'if .facts.git.dirty == false then "clean" elif .facts.git.dirty == true then "dirty" else "unknown" end' "$pj" 2>/dev/null)" in
|
|
26181
|
+
clean) ;;
|
|
26182
|
+
dirty) dirty_n=$((dirty_n + 1)); continue ;;
|
|
26183
|
+
*) unknown_n=$((unknown_n + 1)); continue ;;
|
|
26184
|
+
esac
|
|
26185
|
+
hl="$(jq -r '.honesty.headline // empty' "$pj" 2>/dev/null)"
|
|
26186
|
+
[ -n "$hl" ] || hl="UNREADABLE"
|
|
26187
|
+
if [ -z "$headline" ]; then headline="$hl"; elif [ "$headline" != "$hl" ]; then disagree=true; fi
|
|
26188
|
+
done
|
|
26189
|
+
if $disagree; then
|
|
26190
|
+
echo "$pr: not merged, gate: verdict (proofs for PR head ${head:0:12} disagree); PR left open" >&2
|
|
26191
|
+
_pr_record_merge "$pr" "blocked:verdict" "verdict"
|
|
26192
|
+
return 1
|
|
26193
|
+
fi
|
|
26194
|
+
fi
|
|
26195
|
+
if [ -z "$headline" ]; then
|
|
26196
|
+
echo "$pr: not merged, gate: head (no clean proof recorded for PR head ${head:0:12}; proof is stale, dirty ($dirty_n), dirty-unknown ($unknown_n) or missing); PR left open" >&2
|
|
26197
|
+
if [ "$unknown_n" -gt 0 ]; then
|
|
26198
|
+
echo "$pr: $unknown_n proof(s) for this head have an unknown dirty state (the proof predates the dirty flag, or git could not read the tree). Commit or stash local changes, then re-run 'loki start ...' on a clean tree for this head to record a clean proof." >&2
|
|
26199
|
+
fi
|
|
26200
|
+
_pr_record_merge "$pr" "blocked:head" "head"
|
|
26201
|
+
return 1
|
|
26060
26202
|
fi
|
|
26061
26203
|
if [ "$headline" != "VERIFIED" ]; then
|
|
26062
|
-
echo "$pr: not merged, gate: verdict (run verdict is '${headline
|
|
26204
|
+
echo "$pr: not merged, gate: verdict (run verdict is '${headline}', need VERIFIED); PR left open" >&2
|
|
26063
26205
|
_pr_record_merge "$pr" "blocked:verdict" "verdict"
|
|
26064
26206
|
return 1
|
|
26065
26207
|
fi
|
|
@@ -26074,7 +26216,14 @@ _merge_pr_gated() {
|
|
|
26074
26216
|
esac
|
|
26075
26217
|
return 1
|
|
26076
26218
|
fi
|
|
26077
|
-
if ! gh pr merge "$pr" "${margs[@]}" >/dev/null 2>&1; then
|
|
26219
|
+
if ! gh pr merge "$pr" "${margs[@]}" --match-head-commit "$head" >/dev/null 2>&1; then
|
|
26220
|
+
local head_now=""
|
|
26221
|
+
head_now="$(gh pr view "$pr" --json headRefOid -q .headRefOid 2>/dev/null)" || head_now=""
|
|
26222
|
+
if [ -n "$head_now" ] && [ "$head_now" != "$head" ]; then
|
|
26223
|
+
echo "$pr: not merged, gate: head (PR head moved from ${head:0:12} to ${head_now:0:12} during checks); PR left open" >&2
|
|
26224
|
+
_pr_record_merge "$pr" "blocked:head" "head" "passing"
|
|
26225
|
+
return 1
|
|
26226
|
+
fi
|
|
26078
26227
|
echo "$pr: not merged, gate: merge (gh pr merge failed); PR left open" >&2
|
|
26079
26228
|
_pr_record_merge "$pr" "blocked:merge" "merge" "passing"
|
|
26080
26229
|
return 1
|
package/autonomy/quickstart.sh
CHANGED
|
@@ -824,12 +824,31 @@ _qs_classify_invoke() {
|
|
|
824
824
|
local to=""
|
|
825
825
|
if command -v timeout >/dev/null 2>&1; then to="timeout"
|
|
826
826
|
elif command -v gtimeout >/dev/null 2>&1; then to="gtimeout"; fi
|
|
827
|
+
_LOKI_PERM_ARGV=(--dangerously-skip-permissions)
|
|
828
|
+
if [ "${LOKI_PERMISSION_POLICY:-}" = "compiled" ]; then
|
|
829
|
+
# P12-26b: nothing on this path sources claude-flags.sh. Compute in a subshell so its
|
|
830
|
+
# top-level exports (caveman vars) never leak; fail closed to plan when unavailable.
|
|
831
|
+
local _qs_flags _qs_out _qs_a
|
|
832
|
+
_qs_flags="$(cd "$(dirname "${BASH_SOURCE[0]}")" 2>/dev/null && pwd)/lib/claude-flags.sh"
|
|
833
|
+
_LOKI_PERM_ARGV=(--permission-mode plan)
|
|
834
|
+
_qs_out="$(
|
|
835
|
+
# shellcheck source=/dev/null
|
|
836
|
+
. "$_qs_flags" >/dev/null 2>&1 || exit 1
|
|
837
|
+
type _loki_claude_permission_argv >/dev/null 2>&1 || exit 1
|
|
838
|
+
_loki_claude_permission_argv
|
|
839
|
+
printf '%s\n' "${_LOKI_PERM_ARGV[@]}"
|
|
840
|
+
)" || _qs_out=""
|
|
841
|
+
if [ -n "$_qs_out" ]; then
|
|
842
|
+
_LOKI_PERM_ARGV=()
|
|
843
|
+
while IFS= read -r _qs_a; do _LOKI_PERM_ARGV+=("$_qs_a"); done <<< "$_qs_out"
|
|
844
|
+
fi
|
|
845
|
+
fi
|
|
827
846
|
if [ -n "$to" ]; then
|
|
828
847
|
out=$(CAVEMAN_DEFAULT_MODE=off "$to" "${LOKI_QUICKSTART_CLASSIFY_TIMEOUT}" \
|
|
829
|
-
claude
|
|
848
|
+
claude "${_LOKI_PERM_ARGV[@]}" -p "$prompt" 2>/dev/null) || rc=$?
|
|
830
849
|
else
|
|
831
850
|
out=$(CAVEMAN_DEFAULT_MODE=off \
|
|
832
|
-
claude
|
|
851
|
+
claude "${_LOKI_PERM_ARGV[@]}" -p "$prompt" 2>/dev/null) || rc=$?
|
|
833
852
|
fi
|
|
834
853
|
[ "$rc" -ne 0 ] && return 1
|
|
835
854
|
[ -z "$out" ] && return 1
|
package/autonomy/run.sh
CHANGED
|
@@ -6709,13 +6709,15 @@ spawn_worktree_session() {
|
|
|
6709
6709
|
# Expand the auto-flag array with the bash-3.2 empty-array guard
|
|
6710
6710
|
# (${arr[@]+...}) so a bare "${arr[@]}" under set -u does not
|
|
6711
6711
|
# abort with "unbound variable" when no flags were collected.
|
|
6712
|
+
_LOKI_PERM_ARGV=(--dangerously-skip-permissions)
|
|
6713
|
+
! type _loki_claude_permission_argv >/dev/null 2>&1 || _loki_claude_permission_argv
|
|
6712
6714
|
if [ -n "$_loki_wt_cm" ]; then
|
|
6713
|
-
CAVEMAN_DEFAULT_MODE="$_loki_wt_cm" claude
|
|
6715
|
+
CAVEMAN_DEFAULT_MODE="$_loki_wt_cm" claude "${_LOKI_PERM_ARGV[@]}" \
|
|
6714
6716
|
"${_LOKI_WT_AUTO_FLAGS[@]+"${_LOKI_WT_AUTO_FLAGS[@]}"}" \
|
|
6715
6717
|
-p "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
|
|
6716
6718
|
>> "$log_file" 2>&1 || _wt_exit=$?
|
|
6717
6719
|
else
|
|
6718
|
-
claude
|
|
6720
|
+
claude "${_LOKI_PERM_ARGV[@]}" \
|
|
6719
6721
|
"${_LOKI_WT_AUTO_FLAGS[@]+"${_LOKI_WT_AUTO_FLAGS[@]}"}" \
|
|
6720
6722
|
-p "Loki Mode: $task_prompt. Read .loki/CONTINUITY.md for context." \
|
|
6721
6723
|
>> "$log_file" 2>&1 || _wt_exit=$?
|
|
@@ -6950,7 +6952,9 @@ Output ONLY the resolved file content with no conflict markers. No explanations.
|
|
|
6950
6952
|
# variable (the shell, not the agent, writes the resolved file).
|
|
6951
6953
|
# It needs no hooks, LSP, CLAUDE.md auto-discovery, or MCP, so
|
|
6952
6954
|
# --bare is safe and cheaper. Gated + opt-out LOKI_BARE_SUBCALLS=0.
|
|
6953
|
-
|
|
6955
|
+
_LOKI_PERM_ARGV=(--dangerously-skip-permissions)
|
|
6956
|
+
! type _loki_claude_permission_argv >/dev/null 2>&1 || _loki_claude_permission_argv
|
|
6957
|
+
local _cr_argv=("${_LOKI_PERM_ARGV[@]}")
|
|
6954
6958
|
if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
|
|
6955
6959
|
_cr_argv+=("--bare")
|
|
6956
6960
|
fi
|
|
@@ -16282,7 +16286,9 @@ _dispatch_reviewer() {
|
|
|
16282
16286
|
# Guardrail, not a sandbox -- echo>/sed -i/etc. remain; the
|
|
16283
16287
|
# real net is commit-before-agent-wave. Opt out
|
|
16284
16288
|
# LOKI_REVIEW_TOOL_GUARD=0. See loki_review_guard_denylist.
|
|
16285
|
-
|
|
16289
|
+
_LOKI_PERM_ARGV=(--dangerously-skip-permissions)
|
|
16290
|
+
! type _loki_claude_permission_argv >/dev/null 2>&1 || _loki_claude_permission_argv
|
|
16291
|
+
local _rv_argv=("${_LOKI_PERM_ARGV[@]}")
|
|
16286
16292
|
# OPT-IN advisor pin (v7.104.0, LOKI_ADVISOR_MODEL): reviewers still
|
|
16287
16293
|
# pass NO --model by default (unchanged behavior: they run on the
|
|
16288
16294
|
# account default). When the operator/user sets LOKI_ADVISOR_MODEL to
|
|
@@ -21352,7 +21358,9 @@ ${_commits}"
|
|
|
21352
21358
|
# needed, so --bare is safe and cheaper. Opt out LOKI_BARE_SUBCALLS=0.
|
|
21353
21359
|
# Always at least --dangerously-skip-permissions, so the array is never
|
|
21354
21360
|
# empty (empty "${arr[@]}" under set -u errors on bash 3.2, stock macOS).
|
|
21355
|
-
|
|
21361
|
+
_LOKI_PERM_ARGV=(--dangerously-skip-permissions)
|
|
21362
|
+
! type _loki_claude_permission_argv >/dev/null 2>&1 || _loki_claude_permission_argv
|
|
21363
|
+
local _ic_argv=("${_LOKI_PERM_ARGV[@]}")
|
|
21356
21364
|
if type loki_subcall_bare_enabled >/dev/null 2>&1 && loki_subcall_bare_enabled; then
|
|
21357
21365
|
_ic_argv+=("--bare")
|
|
21358
21366
|
fi
|
|
@@ -25529,7 +25537,9 @@ except Exception as exc:
|
|
|
25529
25537
|
# present so the array is never empty (empty "${arr[@]}" under `set -u`
|
|
25530
25538
|
# is an error on bash 3.2, the stock macOS shell). The autonomy override
|
|
25531
25539
|
# is appended conditionally.
|
|
25532
|
-
|
|
25540
|
+
_LOKI_PERM_ARGV=(--dangerously-skip-permissions)
|
|
25541
|
+
! type _loki_claude_permission_argv >/dev/null 2>&1 || _loki_claude_permission_argv
|
|
25542
|
+
local _loki_claude_argv=("${_LOKI_PERM_ARGV[@]}" "--model" "$tier_param")
|
|
25533
25543
|
if [ "${LOKI_AUTONOMY_OVERRIDE:-on}" != "off" ] \
|
|
25534
25544
|
&& type _loki_autonomy_override_text >/dev/null 2>&1 \
|
|
25535
25545
|
&& type loki_claude_flag_supported >/dev/null 2>&1 \
|
|
@@ -494,16 +494,20 @@ CHILD_ENV_DENYLIST = (
|
|
|
494
494
|
)
|
|
495
495
|
|
|
496
496
|
|
|
497
|
-
def _scoped_child_env():
|
|
498
|
-
|
|
497
|
+
def _scoped_child_env(extra_env=None):
|
|
498
|
+
env = {k: v for k, v in os.environ.items() if k not in CHILD_ENV_DENYLIST}
|
|
499
|
+
# Caller values override inherited ones but can never restore a denied secret.
|
|
500
|
+
env.update({k: v for k, v in (extra_env or {}).items() if k not in CHILD_ENV_DENYLIST})
|
|
501
|
+
return env
|
|
499
502
|
|
|
500
503
|
|
|
501
|
-
def run_loki_outcome(args, dry_run=False, stdout_sink=None):
|
|
504
|
+
def run_loki_outcome(args, dry_run=False, stdout_sink=None, extra_env=None):
|
|
502
505
|
"""Run a loki command and report WHICH of the three outcomes occurred.
|
|
503
506
|
|
|
504
507
|
Returns one of OUTCOME_PASSED / OUTCOME_FAILED / OUTCOME_UNKNOWN. This is
|
|
505
508
|
the honest version of run_loki_command, which answers only "did it launch".
|
|
506
509
|
When stdout_sink is a list, the decoded stdout is appended to it on completion.
|
|
510
|
+
extra_env adds child env vars (e.g. LOKI_STEER_SOURCE) on top of the scoped env.
|
|
507
511
|
"""
|
|
508
512
|
cmd = ["loki"] + args
|
|
509
513
|
if dry_run:
|
|
@@ -515,7 +519,7 @@ def run_loki_outcome(args, dry_run=False, stdout_sink=None):
|
|
|
515
519
|
cmd,
|
|
516
520
|
stdout=subprocess.PIPE,
|
|
517
521
|
stderr=subprocess.PIPE,
|
|
518
|
-
env=_scoped_child_env(),
|
|
522
|
+
env=_scoped_child_env(extra_env),
|
|
519
523
|
)
|
|
520
524
|
except (FileNotFoundError, OSError) as e:
|
|
521
525
|
logging.error("Failed to launch %s: %s", " ".join(cmd), e)
|
|
@@ -703,7 +707,9 @@ def handle_issue_comment_event(payload, dry_run=False):
|
|
|
703
707
|
note = "GitHub comment from %s: %s" % (login or "unknown", body)
|
|
704
708
|
summary = "steer %s from comment" % ref
|
|
705
709
|
sink = []
|
|
706
|
-
|
|
710
|
+
# Provenance for the revision log: loki steer records actor= and source= per revision.
|
|
711
|
+
steer_env = {"LOKI_STEER_SOURCE": "github", "LOKI_STEER_ACTOR": login or "unknown"}
|
|
712
|
+
outcome = run_loki_outcome(["steer", note], dry_run=dry_run, stdout_sink=sink, extra_env=steer_env)
|
|
707
713
|
# Only a confirmed exit 0 proves the steer was written. A detached child
|
|
708
714
|
# (UNKNOWN) never reports its exit code, so acking would claim an unverified
|
|
709
715
|
# steer; report it as launched but post nothing.
|
package/completions/loki.bash
CHANGED
|
@@ -27,7 +27,7 @@ _loki_completion() {
|
|
|
27
27
|
|
|
28
28
|
# If the word starts with a dash, show flags
|
|
29
29
|
if [[ "$cur" == -* ]]; then
|
|
30
|
-
local flags="--provider --max-iterations --parallel --background --bg --simple --complex --github --no-dashboard --sandbox --skip-memory --spec --spec-first --yes --budget --attempts --model --isolation --max-cost --no-pr --explain-route --brief --worktree --pr --ship --detach --workspace --deploy --health-url --auto-rollback --help"
|
|
30
|
+
local flags="--provider --max-iterations --parallel --background --bg --simple --complex --github --no-dashboard --sandbox --skip-memory --spec --spec-first --yes --budget --attempts --model --isolation --max-cost --no-pr --explain-route --brief --worktree --pr --ship --detach --workspace --deploy --health-url --auto-rollback --resume --help"
|
|
31
31
|
COMPREPLY=( $(compgen -W "${flags}" -- "$cur") )
|
|
32
32
|
return 0
|
|
33
33
|
fi
|
|
@@ -149,7 +149,7 @@ _loki_completion() {
|
|
|
149
149
|
|
|
150
150
|
deploy)
|
|
151
151
|
if [[ "$cur" == -* ]]; then
|
|
152
|
-
COMPREPLY=( $(compgen -W "--execute --canary --health-url --auto-rollback --dir --no-clip --allow-unanchored --help" -- "$cur") )
|
|
152
|
+
COMPREPLY=( $(compgen -W "--execute --canary --health-url --health-match --auto-rollback --dir --no-clip --allow-unanchored --help" -- "$cur") )
|
|
153
153
|
return 0
|
|
154
154
|
fi
|
|
155
155
|
;;
|
package/dashboard/__init__.py
CHANGED
package/dashboard/api_start.py
CHANGED
|
@@ -12,7 +12,7 @@ argv.
|
|
|
12
12
|
|
|
13
13
|
ponytail: run state is in memory (a dashboard restart forgets the queue; the
|
|
14
14
|
runs themselves keep going and their worktrees keep the receipts). The
|
|
15
|
-
v10 engine has no resume-with-answer (--resume
|
|
15
|
+
v10 engine has no resume-with-answer (--resume only resumes a publish-phase run), so a BLOCKED
|
|
16
16
|
question is shown with the re-run command instead of an answer box.
|
|
17
17
|
"""
|
|
18
18
|
import json
|
|
@@ -472,7 +472,7 @@ def backlog_status():
|
|
|
472
472
|
for r in _RUNS.values():
|
|
473
473
|
row = {k: r.get(k) for k in ("number", "title", "status", "pr_url", "question", "detail")}
|
|
474
474
|
if r["status"] == "blocked":
|
|
475
|
-
row["resume_hint"] = ("The engine cannot resume with an answer (--resume
|
|
475
|
+
row["resume_hint"] = ("The engine cannot resume with an answer (--resume only finishes the publish phase of a sealed run, not a blocked one). "
|
|
476
476
|
"Answer on the issue, then re-run: loki %s#%d" % (repo, r["number"]))
|
|
477
477
|
out.append(row)
|
|
478
478
|
return {"repo": repo, "concurrency": _concurrency(), "issues": out}
|