loki-mode 11.3.9 → 11.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -18
- package/SKILL.md +3 -3
- package/VERSION +1 -1
- package/agents/custom_agents.py +280 -0
- package/autonomy/council-v2.sh +22 -1
- package/autonomy/edit.sh +242 -0
- package/autonomy/lib/chat.sh +312 -0
- package/autonomy/lib/help-core.sh +164 -0
- package/autonomy/lib/json-output.sh +38 -0
- package/autonomy/lib/run-summary.sh +96 -0
- package/autonomy/loki +105 -10
- package/autonomy/route.sh +270 -0
- package/autonomy/run.sh +6 -0
- package/autonomy/setup-wizard.sh +161 -0
- package/autonomy/verify.sh +22 -2
- package/bin/loki +73 -18
- package/completions/_loki +55 -0
- package/completions/loki.bash +49 -3
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-CORE-VERBS.md +112 -0
- package/docs/CLI-REFERENCE.md +7 -1
- package/docs/COMPARE-OPENCODE.md +94 -0
- package/docs/COMPONENTS.md +1 -0
- package/docs/INSTALLATION.md +56 -12
- package/docs/KNOWN-ISSUES.md +2 -12
- package/docs/QUICKSTART.md +108 -0
- package/docs/environment-variables.md +9 -2
- package/docs/exit-codes.md +29 -12
- package/docs/v10/CLI-MODERN.md +10 -4
- package/docs/v10/GUIDE.md +270 -44
- package/docs/v10/OPENCODE-PARITY-PLAN.md +84 -0
- package/loki-ts/dist/loki.js +968 -933
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/packages/control-plane/dist/server.js +180 -80
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/claude.sh +16 -2
- package/providers/model_route_exec.sh +22 -0
- package/providers/model_routes.sh +188 -0
- package/skills/providers.md +13 -0
- package/templates/agents/planner.md +10 -0
- package/templates/agents/reviewer.md +10 -0
- package/templates/agents/tester.md +10 -0
- package/web-app/dist/assets/{AdminPage-QK-i_x-O.js → AdminPage-CYJ2BWRC.js} +1 -1
- package/web-app/dist/assets/{Avatar-DblkLNjB.js → Avatar-DdK4wynI.js} +1 -1
- package/web-app/dist/assets/{Badge-DIV2ny5H.js → Badge-CrX3MI7J.js} +1 -1
- package/web-app/dist/assets/{Button--rePJOlQ.js → Button-709-wwGM.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-BZlKxpGy.js → CockpitPage-BapdTInO.js} +1 -1
- package/web-app/dist/assets/{ComparePage-3_INSVux.js → ComparePage-DT99pGvA.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-DHG63CNr.js → ErrorBoundary-B0uJSys8.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-DFsBnLlO.js → EvidenceReceiptPanel-BtMFgElP.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-phPGah98.js → GitHubIssuesPanel-DjJuU941.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-T7KQqJmx.js → GitHubPRsPanel-HXfEX1BC.js} +1 -1
- package/web-app/dist/assets/{HomePage-DFjGUF5I.js → HomePage-Cqyves-S.js} +2 -2
- package/web-app/dist/assets/{LoginPage-CorUid_5.js → LoginPage-jUbtC7_Y.js} +1 -1
- package/web-app/dist/assets/{MagicPage-DofbSxKD.js → MagicPage-DHRsqjgR.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-qzrhhfLC.js → MetricsPage-B-pED05R.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-BAZR0mMb.js → NotFoundPage-CsbUs21R.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-BOSUzG9J.js → ProjectPage-BtlLEQwB.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-Ba0YLtzD.js → ProjectsPage-DaRbRjSp.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-Cr10Ue2K.js → SettingsPage-DALrtP3F.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-Czn655fe.js → ShowcasePage-CoPHSNP0.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-Dwjnw6Ob.js → SystemSettingsPage-kOuSN50O.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-D8nc69Be.js → TeamsPage-DGWK0LMp.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-DHsaCDGv.js → TemplatesPage-C-_10CwA.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-DBejni2a.js → TerminalOutput-Cz_vPbXL.js} +1 -1
- package/web-app/dist/assets/{activity-B2VsBl1C.js → activity-icIsXJVa.js} +1 -1
- package/web-app/dist/assets/{bell-CT77Fq2H.js → bell-DbPR2_4r.js} +1 -1
- package/web-app/dist/assets/{bot-DfaWgF4H.js → bot-DvjP3Yh5.js} +1 -1
- package/web-app/dist/assets/{check-D1yp7Ndv.js → check-D1OBxHeU.js} +1 -1
- package/web-app/dist/assets/{chevron-left-B6Pa8IOX.js → chevron-left-D09jDZ29.js} +1 -1
- package/web-app/dist/assets/{circle-alert-D5jrL_Y7.js → circle-alert-DyZMLOcC.js} +1 -1
- package/web-app/dist/assets/{clock-DIsY_fa_.js → clock-CdpdJTId.js} +1 -1
- package/web-app/dist/assets/{cloud-eGwLh1As.js → cloud-CoLTlmoj.js} +1 -1
- package/web-app/dist/assets/{code-xml-CGDIcZ3m.js → code-xml-DrkE2ZZY.js} +1 -1
- package/web-app/dist/assets/{database-CWpdaykI.js → database-CMQQuM2R.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-DZEuTCs6.js → dollar-sign-CuioDaOG.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-BHBW6m9v.js → file-code-corner-Cx9j9K1S.js} +1 -1
- package/web-app/dist/assets/{file-plus-D4h1jZSR.js → file-plus-CYD2zaaI.js} +1 -1
- package/web-app/dist/assets/{globe-CXdqKgwd.js → globe-6w-hnQkL.js} +1 -1
- package/web-app/dist/assets/{hammer-B3YuuYI_.js → hammer-BpqtautL.js} +1 -1
- package/web-app/dist/assets/{index-BLEnboSv.js → index-Dzs-9EgJ.js} +3 -3
- package/web-app/dist/assets/{layers-Dvp5nFtw.js → layers-D5hd7jb7.js} +1 -1
- package/web-app/dist/assets/{loader-circle-DodDmIZ3.js → loader-circle-C6Y9x_7H.js} +1 -1
- package/web-app/dist/assets/{lock-EiuaH1Ep.js → lock-D51L7rYh.js} +1 -1
- package/web-app/dist/assets/{package-Cx4m2hFg.js → package-BJF8hkP8.js} +1 -1
- package/web-app/dist/assets/{plus-D-Sd5rpH.js → plus-D5kUQ79n.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-C0w1cdmF.js → refresh-cw-BKdlCa7F.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-C_qk6MTT.js → rotate-ccw-_bd_YI1e.js} +1 -1
- package/web-app/dist/assets/{scroll-text-DSxiFtkc.js → scroll-text-DbkegIxF.js} +1 -1
- package/web-app/dist/assets/{server-NTJt7I8M.js → server-DAwqFIsq.js} +1 -1
- package/web-app/dist/assets/{shield-alert-HBxMQOvH.js → shield-alert-BuR1tPHu.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-DEgsHzSD.js → thumbs-up-BF6p3mRd.js} +1 -1
- package/web-app/dist/assets/{trash-2-px46FnTD.js → trash-2-BVpnkqqz.js} +1 -1
- package/web-app/dist/assets/{trending-down-CBosVGKa.js → trending-down-DpsDvRwW.js} +1 -1
- package/web-app/dist/assets/{trending-up-Dbd7zrQj.js → trending-up-mFogTjt3.js} +1 -1
- package/web-app/dist/assets/{upload-nhdFKAs1.js → upload-4T8zpBsi.js} +1 -1
- package/web-app/dist/assets/{usePolling-BgaGdinh.js → usePolling-DPXQv8WO.js} +1 -1
- package/web-app/dist/assets/{user-CzIihUy9.js → user-BYGIFaTm.js} +1 -1
- package/web-app/dist/index.html +1 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# One predictable execution path (OC-ROUTE).
|
|
3
|
+
# Sourced by bin/loki. Normalizes argv BEFORE the engine decision so argument
|
|
4
|
+
# order and input form never change the engine:
|
|
5
|
+
# engine10 is the default for a PRD file, an issue ref, a multi-word task and a
|
|
6
|
+
# one-word brief, whether the flags come first or last.
|
|
7
|
+
# The legacy engine runs only on --engine legacy / LOKI_ENGINE=legacy, or, for
|
|
8
|
+
# one specific flag engine10 cannot honor, for that call only (with a notice).
|
|
9
|
+
# Pure functions: no exec, no side effects except R_* globals and the printers.
|
|
10
|
+
# Bash 3.2 compatible (no associative arrays, no ${var,,}).
|
|
11
|
+
|
|
12
|
+
# Flags that take a separate value token.
|
|
13
|
+
_ROUTE_VALUE_FLAGS=" --provider --budget --budget-limit --max-cost --max-iterations --max-retries --session-model --completion-promise --base-wait --max-wait --prd --brief --attempts --spec --aider-model --aider-flags --cline-model --isolation --output --engine --mirofish-rounds --mirofish-timeout "
|
|
14
|
+
# Flags engine10 honors as-is.
|
|
15
|
+
_ROUTE_E10_FLAGS=" --no-pr --deep --json --verbose --provider --max-cost --spec --attempts "
|
|
16
|
+
# Accepted and dropped (no-ops on a non-interactive run).
|
|
17
|
+
_ROUTE_NOOP_FLAGS=" --yes -y --no-plan --no-mirofish --no-dashboard "
|
|
18
|
+
|
|
19
|
+
_route_has() { case "$1" in *" $2 "*) return 0 ;; esac; return 1; }
|
|
20
|
+
|
|
21
|
+
_route_json_str() {
|
|
22
|
+
printf '%s' "$1" | tr '\n\t\r' ' ' | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
_route_classify() {
|
|
26
|
+
# $1 = single positional. Echoes: issue | prd | task | brief | missing-prd
|
|
27
|
+
local p="$1"
|
|
28
|
+
case "$p" in
|
|
29
|
+
*/*\#[0-9]*|http*://*/issues/*|http*://*/browse/*|jira:*|linear:*|https://linear.app/*/issue/*) echo issue; return 0 ;;
|
|
30
|
+
esac
|
|
31
|
+
if [ -f "$p" ]; then echo prd; return 0; fi
|
|
32
|
+
case "$p" in
|
|
33
|
+
*" "*) echo task; return 0 ;;
|
|
34
|
+
*.md|*.markdown|*.txt|*.json|*.yaml|*.yml) echo missing-prd; return 0 ;;
|
|
35
|
+
esac
|
|
36
|
+
echo brief
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# loki_route_resolve VERB ARGS... VERB = start | quick | bare
|
|
40
|
+
# Sets: R_DECIDED R_E10 R_HARD R_ENGINE R_REASON R_INPUT_TYPE R_INPUT R_PROVIDER
|
|
41
|
+
# R_MODEL R_NOTICE R_ERR R_EXPLAIN R_JSON R_E10_ARGS[] R_CLEAN[] R_SESSION_MODEL R_VERB
|
|
42
|
+
# R_* are the result interface read by the sourcing caller.
|
|
43
|
+
# shellcheck disable=SC2034
|
|
44
|
+
loki_route_resolve() {
|
|
45
|
+
local verb="$1"; shift
|
|
46
|
+
R_DECIDED=0; R_E10=0; R_HARD=0; R_ENGINE=""; R_REASON=""; R_INPUT_TYPE=""; R_INPUT=""
|
|
47
|
+
R_PROVIDER=""; R_MODEL=""; R_NOTICE=""; R_ERR=""; R_EXPLAIN=0; R_JSON=0; R_SESSION_MODEL=""; R_VERB=""
|
|
48
|
+
R_E10_ARGS=(); R_CLEAN=()
|
|
49
|
+
|
|
50
|
+
local a engine_req="" help=0 legacy_flag="" session_model="" provider_flag=""
|
|
51
|
+
local -a positionals=() passthru=()
|
|
52
|
+
local i=0 n=$#
|
|
53
|
+
local -a argv=("$@")
|
|
54
|
+
while [ "$i" -lt "$n" ]; do
|
|
55
|
+
a="${argv[$i]}"
|
|
56
|
+
i=$((i + 1))
|
|
57
|
+
case "$a" in
|
|
58
|
+
--engine) engine_req="${argv[$i]:-}"; i=$((i + 1)); continue ;;
|
|
59
|
+
--engine=*) engine_req="${a#--engine=}"; continue ;;
|
|
60
|
+
--explain-route) R_EXPLAIN=1; continue ;;
|
|
61
|
+
--help|-h) help=1; R_CLEAN+=("$a"); continue ;;
|
|
62
|
+
--) R_CLEAN+=("$a"); while [ "$i" -lt "$n" ]; do positionals+=("${argv[$i]}"); R_CLEAN+=("${argv[$i]}"); i=$((i + 1)); done; break ;;
|
|
63
|
+
esac
|
|
64
|
+
R_CLEAN+=("$a")
|
|
65
|
+
case "$a" in
|
|
66
|
+
-*)
|
|
67
|
+
local name="${a%%=*}" val=""
|
|
68
|
+
if _route_has "$_ROUTE_VALUE_FLAGS" "$name"; then
|
|
69
|
+
if [ "$name" = "$a" ]; then
|
|
70
|
+
val="${argv[$i]:-}"; i=$((i + 1)); R_CLEAN+=("$val")
|
|
71
|
+
else
|
|
72
|
+
val="${a#*=}"
|
|
73
|
+
fi
|
|
74
|
+
fi
|
|
75
|
+
case "$name" in
|
|
76
|
+
--prd) positionals+=("$val"); continue ;;
|
|
77
|
+
--brief) positionals+=("$val"); continue ;;
|
|
78
|
+
--provider) provider_flag="$val"; passthru+=(--provider "$val"); continue ;;
|
|
79
|
+
--session-model) session_model="$val"; continue ;;
|
|
80
|
+
--budget|--budget-limit|--max-cost) passthru+=(--max-cost "$val"); continue ;;
|
|
81
|
+
--json) R_JSON=1; passthru+=(--json); continue ;;
|
|
82
|
+
-v) passthru+=(--verbose); continue ;;
|
|
83
|
+
--attempts) passthru+=(--attempts "$val"); continue ;;
|
|
84
|
+
--spec) passthru+=(--spec "$val"); continue ;;
|
|
85
|
+
esac
|
|
86
|
+
if _route_has "$_ROUTE_NOOP_FLAGS" "$name"; then continue; fi
|
|
87
|
+
if _route_has "$_ROUTE_E10_FLAGS" "$name"; then passthru+=("$name"); continue; fi
|
|
88
|
+
# Everything else is a flag engine10 does not honor.
|
|
89
|
+
if [ -z "$legacy_flag" ]; then legacy_flag="$name"; fi
|
|
90
|
+
;;
|
|
91
|
+
*) positionals+=("$a") ;;
|
|
92
|
+
esac
|
|
93
|
+
done
|
|
94
|
+
|
|
95
|
+
if [ "$help" = "1" ]; then return 0; fi
|
|
96
|
+
|
|
97
|
+
# Engine request: flag wins over env.
|
|
98
|
+
if [ -z "$engine_req" ]; then engine_req="${LOKI_ENGINE:-}"; fi
|
|
99
|
+
engine_req=$(printf '%s' "$engine_req" | tr '[:upper:]' '[:lower:]')
|
|
100
|
+
case "$engine_req" in
|
|
101
|
+
""|legacy) ;;
|
|
102
|
+
engine10|e10|v10|10) engine_req=engine10 ;;
|
|
103
|
+
*) R_ERR="loki: unknown engine '$engine_req' (use --engine legacy or --engine engine10)"; R_DECIDED=1; return 0 ;;
|
|
104
|
+
esac
|
|
105
|
+
|
|
106
|
+
# Input.
|
|
107
|
+
local npos=${#positionals[@]}
|
|
108
|
+
if [ "$npos" -eq 0 ]; then
|
|
109
|
+
R_INPUT_TYPE=none
|
|
110
|
+
local k=0 sp=0
|
|
111
|
+
while [ "$k" -lt "${#passthru[@]}" ]; do
|
|
112
|
+
if [ "${passthru[$k]}" = "--spec" ]; then sp=1; fi
|
|
113
|
+
k=$((k + 1))
|
|
114
|
+
done
|
|
115
|
+
# No positional and no --spec: not ours (bare `loki start` auto-detects a PRD).
|
|
116
|
+
if [ "$sp" = "0" ]; then R_INPUT_TYPE=""; return 0; fi
|
|
117
|
+
R_INPUT_TYPE=spec
|
|
118
|
+
R_INPUT="(from --spec)"
|
|
119
|
+
elif [ "$npos" -eq 1 ]; then
|
|
120
|
+
R_INPUT_TYPE=$(_route_classify "${positionals[0]}")
|
|
121
|
+
R_INPUT="${positionals[0]}"
|
|
122
|
+
else
|
|
123
|
+
R_INPUT_TYPE=task
|
|
124
|
+
R_INPUT="${positionals[*]}"
|
|
125
|
+
fi
|
|
126
|
+
|
|
127
|
+
# Bare flag-first form takes over only multi-word tasks and issue refs (a one-word
|
|
128
|
+
# first argument is the command namespace) and only when no unknown flag is present.
|
|
129
|
+
if [ "$verb" = "bare" ]; then
|
|
130
|
+
case "$R_INPUT_TYPE" in
|
|
131
|
+
task|issue) ;;
|
|
132
|
+
*) R_INPUT_TYPE=""; return 0 ;;
|
|
133
|
+
esac
|
|
134
|
+
if [ -n "$legacy_flag" ] && [ -z "$engine_req" ]; then R_INPUT_TYPE=""; return 0; fi
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
R_DECIDED=1
|
|
138
|
+
if [ "$R_INPUT_TYPE" = "missing-prd" ]; then
|
|
139
|
+
R_ERR="loki: PRD file not found: ${positionals[0]}"
|
|
140
|
+
return 0
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Provider and model (display only; engine10 resolves its own at run time).
|
|
144
|
+
R_PROVIDER="$provider_flag"
|
|
145
|
+
if [ -z "$R_PROVIDER" ]; then R_PROVIDER="${LOKI_PROVIDER:-}"; fi
|
|
146
|
+
if [ -z "$R_PROVIDER" ] && [ -f "${PWD}/.loki/state/provider" ]; then
|
|
147
|
+
R_PROVIDER="$(sed -n '1p' "${PWD}/.loki/state/provider" 2>/dev/null || true)"
|
|
148
|
+
fi
|
|
149
|
+
if [ -z "$R_PROVIDER" ]; then R_PROVIDER=claude; fi
|
|
150
|
+
if [ -n "$session_model" ]; then
|
|
151
|
+
R_MODEL="$session_model"
|
|
152
|
+
elif [ -n "${LOKI_SESSION_MODEL:-}" ]; then
|
|
153
|
+
R_MODEL="$LOKI_SESSION_MODEL"
|
|
154
|
+
elif [ -n "${LOKI_MODEL_ROUTE:-}" ]; then
|
|
155
|
+
R_MODEL="route: $LOKI_MODEL_ROUTE"
|
|
156
|
+
elif [ -n "${LOKI_MODEL_OVERRIDE:-}" ]; then
|
|
157
|
+
R_MODEL="$LOKI_MODEL_OVERRIDE"
|
|
158
|
+
elif [ "$R_PROVIDER" = "opencode" ] && [ -n "${LOKI_OPENCODE_MODEL:-}" ]; then
|
|
159
|
+
R_MODEL="$LOKI_OPENCODE_MODEL"
|
|
160
|
+
elif [ "$R_PROVIDER" = "claude" ]; then
|
|
161
|
+
R_MODEL="${LOKI_CLAUDE_MODEL_DEVELOPMENT:-${LOKI_MODEL_DEVELOPMENT:-claude (provider default)}}"
|
|
162
|
+
else
|
|
163
|
+
R_MODEL="$R_PROVIDER (model not recorded)"
|
|
164
|
+
fi
|
|
165
|
+
R_SESSION_MODEL="$session_model"
|
|
166
|
+
|
|
167
|
+
# Task text for engine10: a PRD file is read, anything else is the words.
|
|
168
|
+
local task
|
|
169
|
+
if [ "$R_INPUT_TYPE" = "prd" ]; then
|
|
170
|
+
task=$(cat "${positionals[0]}" 2>/dev/null || true)
|
|
171
|
+
elif [ "$R_INPUT_TYPE" = "spec" ]; then
|
|
172
|
+
task=""
|
|
173
|
+
else
|
|
174
|
+
task="${positionals[*]}"
|
|
175
|
+
fi
|
|
176
|
+
if [ "$verb" = "quick" ]; then R_E10_ARGS+=(--no-pr); fi
|
|
177
|
+
if [ -n "$task" ]; then R_E10_ARGS+=("$task"); fi
|
|
178
|
+
R_E10_ARGS+=("${passthru[@]+"${passthru[@]}"}")
|
|
179
|
+
|
|
180
|
+
local why=""
|
|
181
|
+
if [ "$engine_req" = "legacy" ]; then
|
|
182
|
+
R_ENGINE=legacy; R_REASON="explicit: --engine legacy or LOKI_ENGINE=legacy"
|
|
183
|
+
elif [ "$engine_req" = "engine10" ]; then
|
|
184
|
+
if [ -n "$legacy_flag" ]; then
|
|
185
|
+
R_ERR="loki: flag $legacy_flag is not supported by the engine10 engine; drop it or use --engine legacy"
|
|
186
|
+
return 0
|
|
187
|
+
fi
|
|
188
|
+
R_ENGINE=engine10; R_E10=1; R_HARD=1; R_REASON="explicit: --engine engine10"
|
|
189
|
+
elif [ -n "$legacy_flag" ]; then
|
|
190
|
+
R_ENGINE=legacy
|
|
191
|
+
R_REASON="flag $legacy_flag is not supported by engine10"
|
|
192
|
+
R_NOTICE="loki: routing this call to the legacy engine because of $legacy_flag (engine10 does not support it). Drop the flag to use engine10, or pass --engine legacy to silence this."
|
|
193
|
+
else
|
|
194
|
+
case "$R_PROVIDER" in
|
|
195
|
+
claude|codex|cline|aider|opencode) ;;
|
|
196
|
+
*) why="provider $R_PROVIDER has no engine10 invoker (use claude, codex, cline, aider or opencode)" ;;
|
|
197
|
+
esac
|
|
198
|
+
if [ -z "$why" ] && [ "$R_INPUT_TYPE" = "prd" ]; then
|
|
199
|
+
local sz
|
|
200
|
+
sz=$(wc -c < "${positionals[0]}" 2>/dev/null | tr -d ' ' || echo 0)
|
|
201
|
+
if [ "${sz:-0}" -gt 100000 ]; then why="PRD is larger than 100000 bytes (engine10 takes the task as one argument)"; fi
|
|
202
|
+
fi
|
|
203
|
+
if [ -n "$why" ]; then
|
|
204
|
+
R_ENGINE=legacy; R_REASON="$why"
|
|
205
|
+
R_NOTICE="loki: routing this call to the legacy engine: $why."
|
|
206
|
+
else
|
|
207
|
+
R_ENGINE=engine10; R_E10=1
|
|
208
|
+
R_REASON="default: $R_INPUT_TYPE input runs on engine10 regardless of flag order"
|
|
209
|
+
fi
|
|
210
|
+
fi
|
|
211
|
+
return 0
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# Print the route (human or --json). Returns 0.
|
|
215
|
+
loki_route_print() {
|
|
216
|
+
local shown="$R_INPUT"
|
|
217
|
+
if [ "${#shown}" -gt 72 ]; then shown="$(printf '%s' "$shown" | cut -c1-69)..."; fi
|
|
218
|
+
if [ "$R_JSON" = "1" ]; then
|
|
219
|
+
printf '{"engine":"%s","provider":"%s","model":"%s","input_type":"%s","input":"%s","reason":"%s"}\n' \
|
|
220
|
+
"$(_route_json_str "$R_ENGINE")" "$(_route_json_str "$R_PROVIDER")" "$(_route_json_str "$R_MODEL")" \
|
|
221
|
+
"$(_route_json_str "$R_INPUT_TYPE")" "$(_route_json_str "$shown")" "$(_route_json_str "$R_REASON")"
|
|
222
|
+
else
|
|
223
|
+
printf 'engine: %s\nprovider: %s\nmodel: %s\ninput: %s (%s)\nreason: %s\n' \
|
|
224
|
+
"$R_ENGINE" "$R_PROVIDER" "$R_MODEL" "$R_INPUT_TYPE" "$shown" "$R_REASON"
|
|
225
|
+
fi
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
# Entry used by bin/loki. Args: the full original argv. Sets R_* and returns:
|
|
229
|
+
# 0 = handled (caller reads R_DECIDED); 7 = `loki route` / --explain-route printed (caller exits 0)
|
|
230
|
+
# 8 = usage or input error already printed (caller exits 2)
|
|
231
|
+
# shellcheck disable=SC2034
|
|
232
|
+
loki_route_dispatch() {
|
|
233
|
+
R_DECIDED=0
|
|
234
|
+
local verb="" first="${1:-}"
|
|
235
|
+
case "$first" in
|
|
236
|
+
route)
|
|
237
|
+
shift
|
|
238
|
+
case "${1:-}" in start|quick) verb="$1"; shift ;; *) verb=start ;; esac
|
|
239
|
+
loki_route_resolve "$verb" "$@"
|
|
240
|
+
if [ -n "$R_ERR" ]; then echo "$R_ERR" >&2; return 8; fi
|
|
241
|
+
if [ "$R_DECIDED" != "1" ]; then
|
|
242
|
+
echo "loki: usage: loki route [start|quick] <task | prd.md | owner/repo#N> [flags] [--json]" >&2; return 8
|
|
243
|
+
fi
|
|
244
|
+
loki_route_print; return 7
|
|
245
|
+
;;
|
|
246
|
+
start|quick)
|
|
247
|
+
verb="$first"; shift
|
|
248
|
+
loki_route_resolve "$verb" "$@"
|
|
249
|
+
;;
|
|
250
|
+
-*)
|
|
251
|
+
loki_route_resolve bare "$@"
|
|
252
|
+
verb=bare
|
|
253
|
+
;;
|
|
254
|
+
*)
|
|
255
|
+
# Positional-first call: only LOKI_ENGINE needs the resolver here.
|
|
256
|
+
if [ -n "${LOKI_ENGINE:-}" ]; then
|
|
257
|
+
case "$first" in
|
|
258
|
+
*" "*|*/*\#[0-9]*|http*://*/issues/*)
|
|
259
|
+
loki_route_resolve bare "$@"; verb=bare ;;
|
|
260
|
+
esac
|
|
261
|
+
fi
|
|
262
|
+
;;
|
|
263
|
+
esac
|
|
264
|
+
if [ "$R_DECIDED" != "1" ]; then return 0; fi
|
|
265
|
+
if [ -n "$R_ERR" ]; then echo "$R_ERR" >&2; return 8; fi
|
|
266
|
+
if [ "$R_EXPLAIN" = "1" ]; then loki_route_print; return 7; fi
|
|
267
|
+
if [ -n "$R_NOTICE" ]; then echo "$R_NOTICE" >&2; fi
|
|
268
|
+
R_VERB="$verb"
|
|
269
|
+
return 0
|
|
270
|
+
}
|
package/autonomy/run.sh
CHANGED
|
@@ -29819,6 +29819,12 @@ except (json.JSONDecodeError, OSError): pass
|
|
|
29819
29819
|
_loki_surface_why_hint || true
|
|
29820
29820
|
fi
|
|
29821
29821
|
|
|
29822
|
+
# OC-OUTPUT: unified run summary (same format as engine10); best-effort, never changes the exit code.
|
|
29823
|
+
if [ -f "$SCRIPT_DIR/lib/run-summary.sh" ]; then
|
|
29824
|
+
# shellcheck source=lib/run-summary.sh
|
|
29825
|
+
source "$SCRIPT_DIR/lib/run-summary.sh" 2>/dev/null && loki_run_summary "$result" 2>/dev/null || true
|
|
29826
|
+
fi
|
|
29827
|
+
|
|
29822
29828
|
exit $result
|
|
29823
29829
|
}
|
|
29824
29830
|
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# setup-wizard.sh -- `loki setup`: the single first-run wizard.
|
|
3
|
+
# Sourced by autonomy/loki; relies on cmd_config_set and SKILL_DIR from that
|
|
4
|
+
# file. `loki welcome` (intro), `loki quickstart` (first build) and
|
|
5
|
+
# `loki doctor` (health) remain separate entry points.
|
|
6
|
+
#
|
|
7
|
+
# REQUIRED (doctor fails without them): node >= 18, python3 >= 3.8, jq, git, curl
|
|
8
|
+
# AT LEAST ONE of: a provider CLI (claude, codex, cline, aider, opencode) or the
|
|
9
|
+
# bundled Claude Agent SDK with credentials. Claude Code is the default.
|
|
10
|
+
# OPTIONAL: bun (bundled by npm), bash >= 4, codex/cline/aider/opencode, ollama.
|
|
11
|
+
|
|
12
|
+
_lsw_ask() {
|
|
13
|
+
# _lsw_ask PROMPT DEFAULT -> echoes the answer; DEFAULT when --yes or no TTY
|
|
14
|
+
local prompt="$1" def="$2" ans=""
|
|
15
|
+
if [ "$_LSW_YES" = "1" ] || [ ! -t 0 ]; then
|
|
16
|
+
printf '%s' "$def"
|
|
17
|
+
return 0
|
|
18
|
+
fi
|
|
19
|
+
printf '%s [%s]: ' "$prompt" "$def" >&2
|
|
20
|
+
read -r ans || ans=""
|
|
21
|
+
printf '%s' "${ans:-$def}"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
_lsw_confirm() {
|
|
25
|
+
# _lsw_confirm PROMPT -> 0 for yes; --yes accepts, no TTY declines
|
|
26
|
+
local ans=""
|
|
27
|
+
[ "$_LSW_YES" = "1" ] && return 0
|
|
28
|
+
[ ! -t 0 ] && return 1
|
|
29
|
+
printf '%s [y/N] ' "$1" >&2
|
|
30
|
+
read -r ans || ans=""
|
|
31
|
+
case "$ans" in y|Y|yes|YES) return 0 ;; *) return 1 ;; esac
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_lsw_tool() {
|
|
35
|
+
# _lsw_tool NAME LABEL INSTALL_HINT
|
|
36
|
+
if command -v "$1" >/dev/null 2>&1; then
|
|
37
|
+
printf ' [ OK ] %-10s %s\n' "$1" "$2"
|
|
38
|
+
return 0
|
|
39
|
+
fi
|
|
40
|
+
printf ' [MISS] %-10s %s. Install: %s\n' "$1" "$2" "$3"
|
|
41
|
+
[ "$2" = "REQUIRED" ] && _LSW_MISSING=$((_LSW_MISSING + 1))
|
|
42
|
+
return 1
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
_lsw_help() {
|
|
46
|
+
echo "loki setup - first-run wizard (tools, provider, model route, spend cap)"
|
|
47
|
+
echo ""
|
|
48
|
+
echo "Usage: loki setup [--yes] [--provider NAME] [--model ROUTE] [--budget USD] [--global] [--check]"
|
|
49
|
+
echo ""
|
|
50
|
+
echo " --yes, -y Non-interactive: accept defaults, never prompt"
|
|
51
|
+
echo " --provider NAME claude (default), codex, cline, aider, opencode"
|
|
52
|
+
echo " --model ROUTE claude (default), ollama/<model>, openrouter/<vendor>/<model>"
|
|
53
|
+
echo " --budget USD Spend cap in USD (default: none)"
|
|
54
|
+
echo " --global Write to ~/.config/loki-mode instead of .loki/config"
|
|
55
|
+
echo " --check Report only; change nothing"
|
|
56
|
+
echo ""
|
|
57
|
+
echo "Related: loki welcome (intro), loki quickstart (first build), loki doctor (health)."
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
cmd_setup() {
|
|
61
|
+
_LSW_YES=0
|
|
62
|
+
_LSW_MISSING=0
|
|
63
|
+
local provider="" model="" budget="" use_global="" check_only=0
|
|
64
|
+
while [ $# -gt 0 ]; do
|
|
65
|
+
case "$1" in
|
|
66
|
+
--yes|-y) _LSW_YES=1 ;;
|
|
67
|
+
--provider) provider="${2:-}"; shift ;;
|
|
68
|
+
--model) model="${2:-}"; shift ;;
|
|
69
|
+
--budget) budget="${2:-}"; shift ;;
|
|
70
|
+
--global) use_global="--global" ;;
|
|
71
|
+
--check) check_only=1 ;;
|
|
72
|
+
--help|-h) _lsw_help; return 0 ;;
|
|
73
|
+
*) echo "Unknown option: $1" >&2; _lsw_help >&2; return 2 ;;
|
|
74
|
+
esac
|
|
75
|
+
shift
|
|
76
|
+
done
|
|
77
|
+
[ -n "${CI:-}" ] && _LSW_YES=1
|
|
78
|
+
|
|
79
|
+
echo "Loki Mode setup"
|
|
80
|
+
echo ""
|
|
81
|
+
echo "1. Required tools"
|
|
82
|
+
_lsw_tool node REQUIRED "https://nodejs.org (Node 20+ recommended, 18 minimum)" || true
|
|
83
|
+
_lsw_tool python3 REQUIRED "brew install python3, or your package manager" || true
|
|
84
|
+
_lsw_tool jq REQUIRED "brew install jq, or your package manager" || true
|
|
85
|
+
_lsw_tool git REQUIRED "brew install git, or your package manager" || true
|
|
86
|
+
_lsw_tool curl REQUIRED "brew install curl, or your package manager" || true
|
|
87
|
+
_lsw_tool bun OPTIONAL "bundled by npm; else see https://bun.sh" || true
|
|
88
|
+
if [ "$_LSW_MISSING" -gt 0 ]; then
|
|
89
|
+
echo ""
|
|
90
|
+
echo "Install the missing REQUIRED tools above with your package manager (setup never installs system packages), then rerun: loki setup"
|
|
91
|
+
return 1
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
echo ""
|
|
95
|
+
echo "2. Provider CLI (Claude Code is the default and the only Tier-1 provider)"
|
|
96
|
+
local p
|
|
97
|
+
for p in codex cline aider opencode; do
|
|
98
|
+
_lsw_tool "$p" OPTIONAL "see 'loki doctor' for the command" || true
|
|
99
|
+
done
|
|
100
|
+
if command -v claude >/dev/null 2>&1; then
|
|
101
|
+
printf ' [ OK ] %-10s DEFAULT\n' claude
|
|
102
|
+
if claude auth status 2>/dev/null | grep -q '"loggedIn"[[:space:]]*:[[:space:]]*true'; then
|
|
103
|
+
echo " claude is logged in."
|
|
104
|
+
else
|
|
105
|
+
echo " claude is installed but not logged in. Run: claude auth login"
|
|
106
|
+
if [ "$check_only" = 0 ] && _lsw_confirm "Run 'claude auth login' now?"; then
|
|
107
|
+
claude auth login || true
|
|
108
|
+
fi
|
|
109
|
+
fi
|
|
110
|
+
else
|
|
111
|
+
echo " [MISS] claude DEFAULT. Install: npm install -g @anthropic-ai/claude-code (then: claude auth login)"
|
|
112
|
+
if [ "$check_only" = 0 ] && [ -f "${SKILL_DIR}/autonomy/provider-offer.sh" ]; then
|
|
113
|
+
if _lsw_confirm "Run that install command now?"; then
|
|
114
|
+
# shellcheck disable=SC1091
|
|
115
|
+
. "${SKILL_DIR}/autonomy/provider-offer.sh"
|
|
116
|
+
LOKI_ASSUME_YES="$_LSW_YES" _po_do_install || true
|
|
117
|
+
fi
|
|
118
|
+
fi
|
|
119
|
+
if ! command -v claude >/dev/null 2>&1; then
|
|
120
|
+
echo " Claude Code is not mandatory if another provider above is installed,"
|
|
121
|
+
echo " or if the bundled Claude Agent SDK has credentials."
|
|
122
|
+
fi
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
if [ "$check_only" = 1 ]; then
|
|
126
|
+
echo ""
|
|
127
|
+
echo "Check only: no config written."
|
|
128
|
+
return 0
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
echo ""
|
|
132
|
+
echo "3. Provider and model route"
|
|
133
|
+
[ -z "$provider" ] && provider="$(_lsw_ask "Provider (claude|codex|cline|aider|opencode)" claude)"
|
|
134
|
+
[ -z "$model" ] && model="$(_lsw_ask "Model route (claude | ollama/<model> | openrouter/<vendor>/<model>)" claude)"
|
|
135
|
+
echo ""
|
|
136
|
+
echo "4. Spend cap"
|
|
137
|
+
[ -z "$budget" ] && budget="$(_lsw_ask "Spend cap in USD (blank for none)" "")"
|
|
138
|
+
|
|
139
|
+
echo ""
|
|
140
|
+
echo "5. Writing config"
|
|
141
|
+
# shellcheck disable=SC2086
|
|
142
|
+
cmd_config_set $use_global provider "$provider" || return 1
|
|
143
|
+
if [ -n "$budget" ]; then
|
|
144
|
+
# shellcheck disable=SC2086
|
|
145
|
+
cmd_config_set $use_global budget "$budget" || return 1
|
|
146
|
+
fi
|
|
147
|
+
case "$model" in
|
|
148
|
+
""|claude) ;;
|
|
149
|
+
ollama/*)
|
|
150
|
+
echo " Model route: pass '--model $model' to loki start (Ollama must be running: ollama serve)."
|
|
151
|
+
;;
|
|
152
|
+
openrouter/*)
|
|
153
|
+
echo " Model route: pass '--model $model' and export OPENROUTER_API_KEY (setup never stores keys)."
|
|
154
|
+
;;
|
|
155
|
+
*) echo " Unrecognized route '$model'; using the provider default." ;;
|
|
156
|
+
esac
|
|
157
|
+
|
|
158
|
+
echo ""
|
|
159
|
+
echo "Done. Next: loki doctor (verify), loki quickstart (first build). Inspect: loki config list"
|
|
160
|
+
return 0
|
|
161
|
+
}
|
package/autonomy/verify.sh
CHANGED
|
@@ -38,6 +38,8 @@
|
|
|
38
38
|
# 1 CONCERNS
|
|
39
39
|
# 2 BLOCKED
|
|
40
40
|
# 3 verifier error (could not complete; never silently passes)
|
|
41
|
+
# 64 usage error (unknown option / unexpected argument)
|
|
42
|
+
# Not a git repo exits 2; an empty diff exits 3 (enforced from v11.4.0).
|
|
41
43
|
#
|
|
42
44
|
# EXIT-CODE ORDERING, RECONCILED. An early draft spec listed
|
|
43
45
|
# 0=VERIFIED, 1=BLOCKED, 2=CONCERNS, and this header used to say a human must
|
|
@@ -66,6 +68,7 @@ VERIFY_EXIT_VERIFIED=0
|
|
|
66
68
|
VERIFY_EXIT_CONCERNS=1
|
|
67
69
|
VERIFY_EXIT_BLOCKED=2
|
|
68
70
|
VERIFY_EXIT_ERROR=3
|
|
71
|
+
VERIFY_EXIT_USAGE=64
|
|
69
72
|
VERIFY_SCHEMA_VERSION="1.0"
|
|
70
73
|
|
|
71
74
|
# S-216: verify runs from the tree under review (tree="."), and a bare
|
|
@@ -355,8 +358,10 @@ verify_diff_base() {
|
|
|
355
358
|
VERIFY_DIFF_INS=0
|
|
356
359
|
VERIFY_DIFF_DEL=0
|
|
357
360
|
VERIFY_DIFF_NAMES=""
|
|
361
|
+
VERIFY_NOT_GIT=0
|
|
358
362
|
|
|
359
363
|
if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
364
|
+
VERIFY_NOT_GIT=1
|
|
360
365
|
_verify_log "not a git repository; diff base unresolved (inconclusive)"
|
|
361
366
|
return 0
|
|
362
367
|
fi
|
|
@@ -2187,6 +2192,16 @@ verify_compute_verdict() {
|
|
|
2187
2192
|
elif [ "$has_nonblock_finding" = "true" ] || [ "$inconclusive" = "true" ]; then
|
|
2188
2193
|
VERIFY_VERDICT="CONCERNS"
|
|
2189
2194
|
VERIFY_EXIT=$VERIFY_EXIT_CONCERNS
|
|
2195
|
+
# Exit contract (v11.4.0): the two inputs that give the verifier nothing
|
|
2196
|
+
# to check map to their own codes. The verdict word stays CONCERNS so
|
|
2197
|
+
# evidence.json is unchanged; only the process status is sharper.
|
|
2198
|
+
if [ "$has_nonblock_finding" != "true" ]; then
|
|
2199
|
+
if [ "${VERIFY_NOT_GIT:-0}" = "1" ]; then
|
|
2200
|
+
VERIFY_EXIT=$VERIFY_EXIT_BLOCKED
|
|
2201
|
+
elif [ "${VERIFY_DIFF_RESOLVED:-false}" = "true" ] && [ "${VERIFY_DIFF_FILES:-0}" -eq 0 ]; then
|
|
2202
|
+
VERIFY_EXIT=$VERIFY_EXIT_ERROR
|
|
2203
|
+
fi
|
|
2204
|
+
fi
|
|
2190
2205
|
else
|
|
2191
2206
|
VERIFY_VERDICT="VERIFIED"
|
|
2192
2207
|
VERIFY_EXIT=$VERIFY_EXIT_VERIFIED
|
|
@@ -2570,6 +2585,11 @@ EXIT CODES:
|
|
|
2570
2585
|
1 CONCERNS
|
|
2571
2586
|
2 BLOCKED
|
|
2572
2587
|
3 verifier error (could not complete; never silently passes)
|
|
2588
|
+
64 usage error (unknown option or unexpected argument)
|
|
2589
|
+
|
|
2590
|
+
Two inputs that leave nothing to check have their own codes: a directory
|
|
2591
|
+
that is not a git repository exits 2, an empty diff vs base exits 3. The
|
|
2592
|
+
VERDICT line still reads CONCERNS for both.
|
|
2573
2593
|
|
|
2574
2594
|
Severity rises with the code, so `[ $rc -ge 2 ]` means "at least blocked".
|
|
2575
2595
|
An early draft spec listed 1=BLOCKED, 2=CONCERNS; that ordering was
|
|
@@ -3238,10 +3258,10 @@ verify_main() {
|
|
|
3238
3258
|
--allow-unsigned) VERIFY_ALLOW_UNSIGNED=1; shift ;;
|
|
3239
3259
|
--) shift; break ;;
|
|
3240
3260
|
-*)
|
|
3241
|
-
_verify_err "unknown option: $1"; verify_help; return $
|
|
3261
|
+
_verify_err "unknown option: $1"; verify_help; return $VERIFY_EXIT_USAGE ;;
|
|
3242
3262
|
*)
|
|
3243
3263
|
if [ -z "$base_ref" ]; then base_ref="$1"; else
|
|
3244
|
-
_verify_err "unexpected argument: $1"; return $
|
|
3264
|
+
_verify_err "unexpected argument: $1"; return $VERIFY_EXIT_USAGE
|
|
3245
3265
|
fi
|
|
3246
3266
|
shift ;;
|
|
3247
3267
|
esac
|
package/bin/loki
CHANGED
|
@@ -245,6 +245,48 @@ if [ "${LOKI_PROVIDER:-}" = "gemini" ]; then
|
|
|
245
245
|
exit 1
|
|
246
246
|
fi
|
|
247
247
|
|
|
248
|
+
# OC-MODELS: `loki start|quick ... --model <id>` (also flag-first). One place maps the flag to
|
|
249
|
+
# env before ANY engine/route decision, so engine10, the Bun start command and the legacy
|
|
250
|
+
# loop all see the same thing. Route specs (openrouter|ollama|lmstudio|llamacpp|vllm|custom/<id>)
|
|
251
|
+
# set LOKI_MODEL_ROUTE (claude child only); tier names and Claude aliases become --session-model;
|
|
252
|
+
# any other id sets LOKI_OPENCODE_MODEL for opencode or LOKI_MODEL_OVERRIDE otherwise.
|
|
253
|
+
case "${1:-}" in
|
|
254
|
+
start|quick|--model|--model=*)
|
|
255
|
+
_lm_has=0
|
|
256
|
+
for _lm_a in "$@"; do case "$_lm_a" in --model|--model=*) _lm_has=1 ;; esac; done
|
|
257
|
+
if [ "$_lm_has" = "1" ]; then
|
|
258
|
+
_lm_out=(); _lm_model=""; _lm_prov=""; _lm_skip=0; _lm_pnext=0
|
|
259
|
+
for _lm_a in "$@"; do
|
|
260
|
+
if [ "$_lm_skip" = "1" ]; then case "$_lm_a" in -*) _lm_model=""; break ;; esac; _lm_model="$_lm_a"; _lm_skip=0; continue; fi
|
|
261
|
+
if [ "$_lm_pnext" = "1" ]; then _lm_prov="$_lm_a"; _lm_pnext=0; fi
|
|
262
|
+
case "$_lm_a" in
|
|
263
|
+
--model) _lm_skip=1; continue ;;
|
|
264
|
+
--model=*) _lm_model="${_lm_a#--model=}"; continue ;;
|
|
265
|
+
--provider) _lm_pnext=1 ;;
|
|
266
|
+
--provider=*) _lm_prov="${_lm_a#--provider=}" ;;
|
|
267
|
+
esac
|
|
268
|
+
_lm_out+=("$_lm_a")
|
|
269
|
+
done
|
|
270
|
+
if [ -n "$_lm_model" ]; then
|
|
271
|
+
[ -n "$_lm_prov" ] || _lm_prov="${LOKI_PROVIDER:-claude}"
|
|
272
|
+
case "$_lm_model" in
|
|
273
|
+
openrouter/*|ollama/*|lmstudio/*|llamacpp/*|vllm/*|custom/*) export LOKI_MODEL_ROUTE="$_lm_model" ;;
|
|
274
|
+
small|medium|high|opus|sonnet|haiku) _lm_out+=(--session-model "$_lm_model") ;;
|
|
275
|
+
*)
|
|
276
|
+
if [ "$_lm_prov" = "opencode" ]; then export LOKI_OPENCODE_MODEL="$_lm_model"
|
|
277
|
+
else export LOKI_MODEL_OVERRIDE="$_lm_model"; fi
|
|
278
|
+
;;
|
|
279
|
+
esac
|
|
280
|
+
else
|
|
281
|
+
echo "loki: --model requires a value (for example ollama/qwen3-coder or openrouter/<id>)." >&2
|
|
282
|
+
exit 2
|
|
283
|
+
fi
|
|
284
|
+
set -- ${_lm_out[@]+"${_lm_out[@]}"}
|
|
285
|
+
fi
|
|
286
|
+
unset _lm_has _lm_out _lm_model _lm_prov _lm_skip _lm_pnext _lm_a
|
|
287
|
+
;;
|
|
288
|
+
esac
|
|
289
|
+
|
|
248
290
|
# First-run funnel, stage C: the activation ATTEMPT (first `loki start`).
|
|
249
291
|
#
|
|
250
292
|
# WHY HERE. This shim runs for BOTH routes before any exec, so one emit at this
|
|
@@ -406,29 +448,16 @@ _loki_start_needs_bash() {
|
|
|
406
448
|
esac
|
|
407
449
|
done
|
|
408
450
|
unset _loki_iso_next
|
|
409
|
-
|
|
410
|
-
# The Bun RARV loop has not ported the opencode invoker yet. Keep the
|
|
411
|
-
# released, autonomous shell adapter reachable when SDK mode is enabled
|
|
412
|
-
# instead of accepting the selection and then failing after intake. CLI
|
|
413
|
-
# selection wins over env, which wins over the project-level saved choice.
|
|
414
|
-
local effective_provider="$explicit_provider"
|
|
415
|
-
if [ -z "$effective_provider" ] && [ -n "${LOKI_PROVIDER:-}" ]; then
|
|
416
|
-
effective_provider="$LOKI_PROVIDER"
|
|
417
|
-
fi
|
|
418
|
-
if [ -z "$effective_provider" ] && [ -f "${PWD}/.loki/state/provider" ]; then
|
|
419
|
-
effective_provider="$(sed -n '1p' "${PWD}/.loki/state/provider" 2>/dev/null || true)"
|
|
420
|
-
fi
|
|
421
|
-
[ "$effective_provider" = "opencode" ] && return 0
|
|
422
451
|
return 1
|
|
423
452
|
}
|
|
424
453
|
_loki_attempts_refuse() {
|
|
425
454
|
if _loki_start_needs_bash "$@"; then
|
|
426
|
-
echo "loki: 'loki start --attempts' runs only the Loki 10 engine; an option or provider in this command (for example --parallel, --github, --sandbox, --issue, --dry-run, --detach
|
|
455
|
+
echo "loki: 'loki start --attempts' runs only the Loki 10 engine; an option or provider in this command (for example --parallel, --github, --sandbox, --issue, --dry-run, or --detach) was only honored by the removed legacy loop. Remove it, or use a task string, an issue ref or a PRD path." >&2
|
|
427
456
|
exit 2
|
|
428
457
|
fi
|
|
429
458
|
case "${LOKI_PROVIDER:-claude}" in
|
|
430
|
-
claude|codex|cline|aider) ;;
|
|
431
|
-
*) echo "loki: the Loki 10 engine does not support LOKI_PROVIDER=${LOKI_PROVIDER:-}; use claude, codex, cline or
|
|
459
|
+
claude|codex|cline|aider|opencode) ;;
|
|
460
|
+
*) echo "loki: the Loki 10 engine does not support LOKI_PROVIDER=${LOKI_PROVIDER:-}; use claude, codex, cline, aider or opencode." >&2; exit 2 ;;
|
|
432
461
|
esac
|
|
433
462
|
}
|
|
434
463
|
|
|
@@ -482,6 +511,28 @@ if [ "${1:-}" = "start" ]; then
|
|
|
482
511
|
unset _loki_sp_a
|
|
483
512
|
fi
|
|
484
513
|
|
|
514
|
+
# OC-ROUTE: normalize flags before the engine decision so argument order and input form
|
|
515
|
+
# never change the engine (autonomy/route.sh; `loki route ...` and --explain-route print it).
|
|
516
|
+
_R_APPLIED=0
|
|
517
|
+
if [ -f "$REPO_ROOT/autonomy/route.sh" ] && { [ "$_LOKI_START_ATTEMPTS" != "1" ] || case " $* " in *" --explain-route "*) true ;; *) false ;; esac; }; then
|
|
518
|
+
# shellcheck source=/dev/null
|
|
519
|
+
. "$REPO_ROOT/autonomy/route.sh"
|
|
520
|
+
_r_rc=0
|
|
521
|
+
loki_route_dispatch "$@" || _r_rc=$?
|
|
522
|
+
case "$_r_rc" in 7) exit 0 ;; 8) exit 2 ;; esac
|
|
523
|
+
if [ "$R_DECIDED" = "1" ]; then
|
|
524
|
+
_R_APPLIED=1
|
|
525
|
+
if [ -n "$R_SESSION_MODEL" ]; then
|
|
526
|
+
case "$R_SESSION_MODEL" in small) R_SESSION_MODEL=fast ;; medium) R_SESSION_MODEL=development ;; high) R_SESSION_MODEL=planning ;; esac
|
|
527
|
+
export LOKI_SESSION_MODEL="$R_SESSION_MODEL"
|
|
528
|
+
fi
|
|
529
|
+
if [ "$R_E10" != "1" ]; then
|
|
530
|
+
set -- "$([ "$R_VERB" = "bare" ] && echo start || echo "$R_VERB")" ${R_CLEAN[@]+"${R_CLEAN[@]}"}
|
|
531
|
+
fi
|
|
532
|
+
fi
|
|
533
|
+
unset _r_rc
|
|
534
|
+
fi
|
|
535
|
+
|
|
485
536
|
# Loki 10 engine (D29, the only engine from 11.0.0). Routes `loki "<task>"`,
|
|
486
537
|
# `loki owner/repo#N`, `loki quick "<task>"`, `keys` and `verify --pubkey` to engine10.
|
|
487
538
|
# shellcheck disable=SC2194
|
|
@@ -566,14 +617,18 @@ case "v10" in
|
|
|
566
617
|
# issue refs reach v10.
|
|
567
618
|
*) _e10=0 ;;
|
|
568
619
|
esac
|
|
620
|
+
if [ "$_R_APPLIED" = "1" ]; then
|
|
621
|
+
_e10="$R_E10"; _e10_hard="$R_HARD"
|
|
622
|
+
if [ "$R_E10" = "1" ]; then _e10_args=("${R_E10_ARGS[@]}"); fi
|
|
623
|
+
fi
|
|
569
624
|
# Default (unset) mode never breaks a host: no bun, or a provider engine10 has no
|
|
570
625
|
# invoker for, falls through to the legacy engine. Explicit v10 keeps its hard error.
|
|
571
626
|
if [ "$_e10" = "1" ] && [ "$_e10_hard" = "0" ]; then
|
|
572
627
|
case "${LOKI_PROVIDER:-claude}" in
|
|
573
|
-
claude|codex|cline|aider) ;;
|
|
628
|
+
claude|codex|cline|aider|opencode) ;;
|
|
574
629
|
*)
|
|
575
630
|
_e10=0
|
|
576
|
-
echo "loki: using the legacy engine (the Loki 10 engine does not support LOKI_PROVIDER=${LOKI_PROVIDER:-}; use claude, codex, cline or
|
|
631
|
+
echo "loki: using the legacy engine (the Loki 10 engine does not support LOKI_PROVIDER=${LOKI_PROVIDER:-}; use claude, codex, cline, aider or opencode)." >&2
|
|
577
632
|
;;
|
|
578
633
|
esac
|
|
579
634
|
if [ "$_e10" = "1" ]; then
|