loki-mode 11.39.3 → 11.40.0
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 +5 -5
- package/VERSION +1 -1
- package/autonomy/route.sh +81 -21
- package/autonomy/trigger-server.py +62 -25
- package/bin/loki +1 -0
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-REFERENCE.md +1 -1
- package/docs/DEPLOYMENT.md +5 -3
- package/docs/INSTALLATION.md +1 -1
- package/docs/KNOWN-ISSUES.md +1 -1
- package/docs/exit-codes.md +0 -3
- package/docs/merge-queue.md +1 -1
- package/docs/sentry-intake.md +6 -6
- package/loki-ts/dist/loki.js +824 -824
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/web-app/dist/assets/{AdminPage-D4t9eA5j.js → AdminPage-p-RKJjMN.js} +1 -1
- package/web-app/dist/assets/{Avatar-BMvmVYy3.js → Avatar-BjoGcPik.js} +1 -1
- package/web-app/dist/assets/{Badge-j7ETCF1w.js → Badge-B1mLd-79.js} +1 -1
- package/web-app/dist/assets/{Button-Blavkzv4.js → Button-BjhRKQbb.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-V6Ji2ITd.js → CockpitPage-CkSNSL32.js} +1 -1
- package/web-app/dist/assets/{ComparePage-CBqk17CY.js → ComparePage-B0Aw7I9x.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-C-E6orzu.js → ErrorBoundary-BcU3oAZ3.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-rxNYJ9N-.js → EvidenceReceiptPanel-24LwFgFb.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-F31m3Aj_.js → GitHubIssuesPanel-CGv0HKPi.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-DHyZJE4y.js → GitHubPRsPanel-BsdNy2C5.js} +1 -1
- package/web-app/dist/assets/{HomePage-BysZHe4l.js → HomePage-DvVbWUZA.js} +2 -2
- package/web-app/dist/assets/{LoginPage-B1rXH3EO.js → LoginPage-BNrAxlRi.js} +1 -1
- package/web-app/dist/assets/{MagicPage-D_GRCJpO.js → MagicPage-D2QZjEAZ.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-DLp1k4s-.js → MetricsPage-BOYkm2zk.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-L1YG0wb5.js → NotFoundPage-BD1VPbHa.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-BFMWZp9h.js → ProjectPage-BdpZdNHa.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-DexfnMf5.js → ProjectsPage-CLuLEdxd.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-D0U8Jy7j.js → SettingsPage-DRJULFEa.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-vSg42BLH.js → ShowcasePage-C99iNlrf.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-kJor0y66.js → SystemSettingsPage-D3MbpQIy.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-qxjTX0fH.js → TeamsPage-CiGXZLru.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-Dfntjiqk.js → TemplatesPage-ggdmWggo.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-D6xtYrBW.js → TerminalOutput-Bv6B7E5y.js} +1 -1
- package/web-app/dist/assets/{activity-s480OdXm.js → activity-DjGsvCmy.js} +1 -1
- package/web-app/dist/assets/{bell-Dh2lHrTZ.js → bell-CGnEzRGV.js} +1 -1
- package/web-app/dist/assets/{bot-Dblq8THo.js → bot-CgGaaPS2.js} +1 -1
- package/web-app/dist/assets/{check-B3kJ3BBC.js → check-BcnYM4Ag.js} +1 -1
- package/web-app/dist/assets/{chevron-left-t17IGfgp.js → chevron-left-DEzx71XG.js} +1 -1
- package/web-app/dist/assets/{circle-alert-DY9mDnl2.js → circle-alert-CxHpfqnB.js} +1 -1
- package/web-app/dist/assets/{clock-j7h1XCY-.js → clock-CTMbchNi.js} +1 -1
- package/web-app/dist/assets/{cloud-DohoSLBp.js → cloud-COPPnmqG.js} +1 -1
- package/web-app/dist/assets/{code-xml-DTGXMKpD.js → code-xml-C7wRH4hG.js} +1 -1
- package/web-app/dist/assets/{database-CQH7dZj9.js → database-Ck1WjxAG.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-Dw7zuu-l.js → dollar-sign-CHpFeeAJ.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-CICoYfYw.js → file-code-corner-Bvz4JZkM.js} +1 -1
- package/web-app/dist/assets/{file-plus-DkdX4EaI.js → file-plus-DgOe1XY8.js} +1 -1
- package/web-app/dist/assets/{globe-C7dg3KOC.js → globe-Bm3qr09s.js} +1 -1
- package/web-app/dist/assets/{hammer-D1uweQjC.js → hammer-BlUi98hw.js} +1 -1
- package/web-app/dist/assets/{index-D5LyAuYp.js → index-4BrDzHz3.js} +3 -3
- package/web-app/dist/assets/{layers-BpNwSWsz.js → layers-Cu_pNTwy.js} +1 -1
- package/web-app/dist/assets/{loader-circle-D_nWlzcC.js → loader-circle-ClaYSJIg.js} +1 -1
- package/web-app/dist/assets/{lock-tSL-496a.js → lock-DzFMEjL0.js} +1 -1
- package/web-app/dist/assets/{package-Dl2lz12B.js → package-LfzNPC_L.js} +1 -1
- package/web-app/dist/assets/{plus-2KTRN0bG.js → plus-DLntn3jw.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-BRun0THB.js → refresh-cw-DdUJIS6x.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-Cs6_HAAN.js → rotate-ccw-D878TXBs.js} +1 -1
- package/web-app/dist/assets/{scroll-text-CwMqWnrv.js → scroll-text-BfS7Hxli.js} +1 -1
- package/web-app/dist/assets/{server-B74yQ8bd.js → server-Dz4fXHHH.js} +1 -1
- package/web-app/dist/assets/{shield-alert-BtlkYO24.js → shield-alert-2oLnQ56Z.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-DPAu2ToS.js → thumbs-up-DV9yRc0e.js} +1 -1
- package/web-app/dist/assets/{trash-2-CRTghV6p.js → trash-2-ATUWTDSk.js} +1 -1
- package/web-app/dist/assets/{trending-down-B3I7DqUM.js → trending-down-Cgn1GMMT.js} +1 -1
- package/web-app/dist/assets/{trending-up-C0wXKjbk.js → trending-up-CSF6Y_u6.js} +1 -1
- package/web-app/dist/assets/{upload-Ca7BK4wj.js → upload-jOidrbCO.js} +1 -1
- package/web-app/dist/assets/{usePolling-geBPWeod.js → usePolling-Iulf494p.js} +1 -1
- package/web-app/dist/assets/{user-DOlmlt1y.js → user-Cez6NU6B.js} +1 -1
- package/web-app/dist/index.html +1 -1
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Runs an autonomous software factory that knows what it is supposed to deliver and proves it did, with a signed receipt. Use when the user says Loki Mode or asks to build, fix or verify software autonomously.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v11.
|
|
6
|
+
# Loki Mode v11.40.0
|
|
7
7
|
|
|
8
8
|
**Current entry points (Loki 10 engine):** `loki "<task>"`, `loki owner/repo#N` and `loki quick "<task>"` run the Loki 10 engine and end with a signed receipt (`loki verify`). `loki backlog owner/repo --all` runs many issues in parallel. Guide: `docs/v10/GUIDE.md`. The skill instructions below, and `loki start`, describe the previous bash loop. PRD-file starts (`loki start ./prd.md`) and the opencode provider still run on it until FC38-SWEEP ports them; the legacy-engine command and the LOKI_ENGINE switch remain removed. Prefer `loki owner/repo#N` for issues.
|
|
9
9
|
|
|
@@ -259,9 +259,9 @@ loki start --provider codex ./prd.json # GPT-5.3 Codex, degraded mode
|
|
|
259
259
|
loki start --provider cline ./prd.md # Cline CLI, degraded mode
|
|
260
260
|
loki start --provider aider ./prd.md # Aider (18+ providers), degraded mode
|
|
261
261
|
|
|
262
|
-
# Parallel
|
|
263
|
-
loki start ./prd.md --parallel
|
|
264
|
-
loki start 123 --ship
|
|
262
|
+
# Parallel and auto-merge (legacy loop only; the default engine refuses these flags)
|
|
263
|
+
loki start ./prd.md --parallel --engine legacy
|
|
264
|
+
loki start 123 --ship --engine legacy # Issue -> PR -> auto-merge (default engine: 'loki start 123' opens the PR, then 'loki merge-queue')
|
|
265
265
|
|
|
266
266
|
# Run any loki command inside the published Docker image, zero config (v7.45.0).
|
|
267
267
|
# Bind-mounts the current folder to /workspace so .loki state, resume, and
|
|
@@ -476,4 +476,4 @@ Implemented, planned and deprecated items, with the version each landed in: `ski
|
|
|
476
476
|
|
|
477
477
|
---
|
|
478
478
|
|
|
479
|
-
**v11.
|
|
479
|
+
**v11.40.0 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
11.
|
|
1
|
+
11.40.0
|
package/autonomy/route.sh
CHANGED
|
@@ -4,20 +4,49 @@
|
|
|
4
4
|
# order and input form never change the engine:
|
|
5
5
|
# engine10 is the default for a PRD file, an issue ref, a multi-word task and a
|
|
6
6
|
# one-word brief, whether the flags come first or last.
|
|
7
|
-
# The legacy engine runs only on --engine legacy / LOKI_ENGINE=legacy, or
|
|
8
|
-
#
|
|
7
|
+
# The legacy engine runs only on --engine legacy / LOKI_ENGINE=legacy, or a size or
|
|
8
|
+
# env-provider fallback. A flag never changes the engine: engine10 maps it or refuses it.
|
|
9
9
|
# Pure functions: no exec, no side effects except R_* globals and the printers.
|
|
10
10
|
# Bash 3.2 compatible (no associative arrays, no ${var,,}).
|
|
11
11
|
|
|
12
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 "
|
|
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 --max-duration --log-level --compliance --cost --remote --issue --bmad-project --openspec --mirofish-docker --destination --domain --env-file --health-url --health-match "
|
|
14
14
|
# Flags engine10 honors as-is.
|
|
15
|
-
_ROUTE_E10_FLAGS=" --no-pr --deep --json --verbose --provider --max-cost --spec --attempts "
|
|
15
|
+
_ROUTE_E10_FLAGS=" --no-pr --deep --json --verbose --quiet --resume --provider --max-cost --spec --attempts "
|
|
16
|
+
# The most a run cap can reach (capCeilingS(true) in loki-ts/src/util/run_cap.ts: the subscription ceiling).
|
|
17
|
+
_ROUTE_CAP_CEILING_S=3600
|
|
16
18
|
# Accepted and dropped (no-ops on a non-interactive run).
|
|
17
|
-
|
|
19
|
+
# --pr is a no-op because engine10 opens a PR by default (--no-pr turns it off).
|
|
20
|
+
_ROUTE_NOOP_FLAGS=" --yes -y --no-plan --no-mirofish --no-dashboard --pr "
|
|
21
|
+
|
|
22
|
+
# _route_refusal_hint FLAG: the engine10 replacement for a legacy-only flag (no engine10 equivalent exists).
|
|
23
|
+
_route_refusal_hint() {
|
|
24
|
+
case "$1" in
|
|
25
|
+
--ship) echo "engine10 opens the PR itself and does not auto-merge; merge it with 'loki merge-queue', or pass --engine legacy to keep --ship" ;;
|
|
26
|
+
--worktree|-w) echo "engine10 runs each attempt in its own isolated worktree already; drop the flag, or pass --engine legacy for a git-worktree run" ;;
|
|
27
|
+
--detach|-d) echo "run it in the background yourself ('nohup loki start ... &') or queue it with 'loki queue run', or pass --engine legacy for --detach" ;;
|
|
28
|
+
--parallel) echo "use 'loki queue run --parallel K' or 'loki issues run --parallel K' for parallel runs, or pass --engine legacy for --parallel" ;;
|
|
29
|
+
*) echo "drop it, or pass --engine legacy to run the legacy loop with it" ;;
|
|
30
|
+
esac
|
|
31
|
+
}
|
|
18
32
|
|
|
19
33
|
_route_has() { case "$1" in *" $2 "*) return 0 ;; esac; return 1; }
|
|
20
34
|
|
|
35
|
+
# _route_duration_s VALUE: seconds from 90, 90s, 90m or 2h; empty output when unusable or zero.
|
|
36
|
+
_route_duration_s() {
|
|
37
|
+
local v="$1" n unit
|
|
38
|
+
case "$v" in
|
|
39
|
+
[0-9]*[0-9]|[0-9]) n="$v"; unit=s ;;
|
|
40
|
+
[0-9]*[smh]) n="${v%?}"; unit="${v#"$n"}" ;;
|
|
41
|
+
*) return 0 ;;
|
|
42
|
+
esac
|
|
43
|
+
case "$n" in *[!0-9]*|"") return 0 ;; esac
|
|
44
|
+
n=$((10#$n))
|
|
45
|
+
[ "$n" -gt 0 ] || return 0
|
|
46
|
+
case "$unit" in m) n=$((n * 60)) ;; h) n=$((n * 3600)) ;; esac
|
|
47
|
+
printf '%s' "$n"
|
|
48
|
+
}
|
|
49
|
+
|
|
21
50
|
_route_json_str() {
|
|
22
51
|
printf '%s' "$1" | tr '\n\t\r' ' ' | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g'
|
|
23
52
|
}
|
|
@@ -45,9 +74,9 @@ loki_route_resolve() {
|
|
|
45
74
|
local verb="$1"; shift
|
|
46
75
|
R_DECIDED=0; R_E10=0; R_HARD=0; R_ENGINE=""; R_REASON=""; R_INPUT_TYPE=""; R_INPUT=""
|
|
47
76
|
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=(); R_ADAPTER=""; R_CAPDIFF=()
|
|
77
|
+
R_E10_ARGS=(); R_CLEAN=(); R_ADAPTER=""; R_CAPDIFF=(); R_E10_CAP_S=""; R_DRY=0; R_MAXCOST=""
|
|
49
78
|
|
|
50
|
-
local a engine_req="" help=0 legacy_flag="" session_model="" provider_flag=""
|
|
79
|
+
local a engine_req="" dry=0 help=0 legacy_flag="" session_model="" provider_flag="" max_duration="" cap_err="" input_flag=""
|
|
51
80
|
local -a positionals=() passthru=()
|
|
52
81
|
local i=0 n=$#
|
|
53
82
|
local -a argv=("$@")
|
|
@@ -73,15 +102,21 @@ loki_route_resolve() {
|
|
|
73
102
|
fi
|
|
74
103
|
fi
|
|
75
104
|
case "$name" in
|
|
76
|
-
--prd) positionals+=("$val"); continue ;;
|
|
77
|
-
--brief) positionals+=("$val"); continue ;;
|
|
105
|
+
--prd|--brief) input_flag="$name"; positionals+=("$val"); continue ;;
|
|
78
106
|
--provider) provider_flag="$val"; passthru+=(--provider "$val"); continue ;;
|
|
79
107
|
--session-model) session_model="$val"; continue ;;
|
|
80
|
-
--budget|--budget-limit|--max-cost) passthru+=(--max-cost "$val"); continue ;;
|
|
108
|
+
--budget|--budget-limit|--max-cost) passthru+=(--max-cost "$val"); R_MAXCOST="$val"; continue ;;
|
|
109
|
+
--dry-run) dry=1; continue ;;
|
|
81
110
|
--json) R_JSON=1; passthru+=(--json); continue ;;
|
|
82
111
|
-v) passthru+=(--verbose); continue ;;
|
|
83
112
|
--attempts) passthru+=(--attempts "$val"); continue ;;
|
|
84
113
|
--spec) passthru+=(--spec "$val"); continue ;;
|
|
114
|
+
--max-duration)
|
|
115
|
+
max_duration="$(_route_duration_s "$val")"
|
|
116
|
+
if [ -z "$max_duration" ] && [ -z "$cap_err" ]; then
|
|
117
|
+
cap_err="loki: --max-duration '$val' is not a duration (use seconds, or 90m / 2h)"
|
|
118
|
+
fi
|
|
119
|
+
continue ;;
|
|
85
120
|
esac
|
|
86
121
|
if _route_has "$_ROUTE_NOOP_FLAGS" "$name"; then continue; fi
|
|
87
122
|
if _route_has "$_ROUTE_E10_FLAGS" "$name"; then passthru+=("$name"); continue; fi
|
|
@@ -131,10 +166,10 @@ loki_route_resolve() {
|
|
|
131
166
|
task|issue) ;;
|
|
132
167
|
*) R_INPUT_TYPE=""; return 0 ;;
|
|
133
168
|
esac
|
|
134
|
-
if [ -n "$legacy_flag" ] && [ -z "$engine_req" ]; then R_INPUT_TYPE=""; return 0; fi
|
|
135
169
|
fi
|
|
136
170
|
|
|
137
171
|
R_DECIDED=1
|
|
172
|
+
if [ -n "$cap_err" ]; then R_ERR="$cap_err"; return 0; fi
|
|
138
173
|
if [ "$R_INPUT_TYPE" = "missing-prd" ]; then
|
|
139
174
|
R_ERR="loki: PRD file not found: ${positionals[0]}"
|
|
140
175
|
return 0
|
|
@@ -177,19 +212,39 @@ loki_route_resolve() {
|
|
|
177
212
|
if [ -n "$task" ]; then R_E10_ARGS+=("$task"); fi
|
|
178
213
|
R_E10_ARGS+=("${passthru[@]+"${passthru[@]}"}")
|
|
179
214
|
|
|
215
|
+
# --prd and --brief name the input themselves; a second input would have glued its words into the task.
|
|
216
|
+
if [ -n "$input_flag" ] && [ "$npos" -gt 1 ]; then
|
|
217
|
+
R_ERR="loki: $input_flag cannot be combined with another input (${positionals[*]}); give one input"
|
|
218
|
+
return 0
|
|
219
|
+
fi
|
|
220
|
+
# A flag never changes the engine (#236): an unmapped one is refused, not routed to legacy.
|
|
221
|
+
if [ -n "$legacy_flag" ] && [ "$engine_req" != "legacy" ]; then
|
|
222
|
+
R_ERR="loki: flag $legacy_flag is not supported by the Loki 10 engine; $(_route_refusal_hint "$legacy_flag")"
|
|
223
|
+
return 0
|
|
224
|
+
fi
|
|
225
|
+
# Wall-clock cap: LOKI_E10_CAP_S seconds, bounded by the run-cap ceiling.
|
|
226
|
+
if [ -n "$max_duration" ] && [ "$engine_req" != "legacy" ]; then
|
|
227
|
+
R_E10_CAP_S="$max_duration"
|
|
228
|
+
if [ "$R_E10_CAP_S" -gt "$_ROUTE_CAP_CEILING_S" ]; then
|
|
229
|
+
R_E10_CAP_S="$_ROUTE_CAP_CEILING_S"
|
|
230
|
+
R_NOTICE="loki: --max-duration $max_duration s is above the engine10 run-cap ceiling; using ${_ROUTE_CAP_CEILING_S}s."
|
|
231
|
+
fi
|
|
232
|
+
fi
|
|
233
|
+
if [ -n "$provider_flag" ] && [ "$engine_req" != "legacy" ]; then
|
|
234
|
+
case "$provider_flag" in
|
|
235
|
+
claude|codex|cline|aider|opencode) ;;
|
|
236
|
+
*)
|
|
237
|
+
R_ERR="loki: --provider $provider_flag has no Loki 10 engine invoker (use claude, codex, cline, aider or opencode, or pass --engine legacy)"
|
|
238
|
+
return 0 ;;
|
|
239
|
+
esac
|
|
240
|
+
fi
|
|
241
|
+
# --dry-run (engine10 has no preview of its own for these inputs): print the route, then exit 0 without a run.
|
|
242
|
+
if [ "$dry" = "1" ] && [ "$engine_req" != "legacy" ]; then R_DRY=1; fi
|
|
180
243
|
local why=""
|
|
181
244
|
if [ "$engine_req" = "legacy" ]; then
|
|
182
245
|
R_ENGINE=legacy; R_REASON="explicit: --engine legacy or LOKI_ENGINE=legacy"
|
|
183
246
|
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
247
|
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
248
|
else
|
|
194
249
|
case "$R_PROVIDER" in
|
|
195
250
|
claude|codex|cline|aider|opencode) ;;
|
|
@@ -245,9 +300,9 @@ loki_route_print() {
|
|
|
245
300
|
for d in ${R_CAPDIFF[@]+"${R_CAPDIFF[@]}"}; do
|
|
246
301
|
diffs="${diffs:+$diffs,}\"$(_route_json_str "$d")\""
|
|
247
302
|
done
|
|
248
|
-
printf '{"engine":"%s","adapter":"%s","provider":"%s","model":"%s","input_type":"%s","input":"%s","reason":"%s","capability_differences":[%s]}\n' \
|
|
303
|
+
printf '{"engine":"%s","adapter":"%s","provider":"%s","model":"%s","input_type":"%s","input":"%s","reason":"%s","capability_differences":[%s],"run_cap_s":%s}\n' \
|
|
249
304
|
"$(_route_json_str "$R_ENGINE")" "$(_route_json_str "$R_ADAPTER")" "$(_route_json_str "$R_PROVIDER")" "$(_route_json_str "$R_MODEL")" \
|
|
250
|
-
"$(_route_json_str "$R_INPUT_TYPE")" "$(_route_json_str "$shown")" "$(_route_json_str "$R_REASON")" "$diffs"
|
|
305
|
+
"$(_route_json_str "$R_INPUT_TYPE")" "$(_route_json_str "$shown")" "$(_route_json_str "$R_REASON")" "$diffs" "${R_E10_CAP_S:-null}"
|
|
251
306
|
else
|
|
252
307
|
printf 'engine: %s\nadapter: %s\nprovider: %s\nmodel: %s\ninput: %s (%s)\nreason: %s\n' \
|
|
253
308
|
"$R_ENGINE" "$R_ADAPTER" "$R_PROVIDER" "$R_MODEL" "$R_INPUT_TYPE" "$shown" "$R_REASON"
|
|
@@ -296,6 +351,11 @@ loki_route_dispatch() {
|
|
|
296
351
|
if [ "$R_DECIDED" != "1" ]; then return 0; fi
|
|
297
352
|
if [ -n "$R_ERR" ]; then echo "$R_ERR" >&2; return 8; fi
|
|
298
353
|
if [ "$R_EXPLAIN" = "1" ]; then loki_route_print; return 7; fi
|
|
354
|
+
if [ "$R_DRY" = "1" ]; then
|
|
355
|
+
loki_route_print
|
|
356
|
+
printf 'max-cost: %s\nrun-cap: %ss\ndry-run: nothing was started\n' "${R_MAXCOST:-default (engine10 resolves its own)}" "${R_E10_CAP_S:-$_ROUTE_CAP_CEILING_S}"
|
|
357
|
+
return 7
|
|
358
|
+
fi
|
|
299
359
|
if [ -n "$R_NOTICE" ]; then echo "$R_NOTICE" >&2; fi
|
|
300
360
|
R_VERB="$verb"
|
|
301
361
|
return 0
|
|
@@ -29,7 +29,7 @@ every /jobs request is rejected with 503 and an audit log line.
|
|
|
29
29
|
Self-healing entry point: POST /sentry accepts a Sentry integration webhook
|
|
30
30
|
authenticated by Sentry's own HMAC (sentry-hook-signature) under a THIRD,
|
|
31
31
|
separate secret (LOKI_SENTRY_WEBHOOK_SECRET or SENTRY_CLIENT_SECRET). A valid
|
|
32
|
-
alert enqueues `loki start "<normalized brief>" --pr
|
|
32
|
+
alert enqueues `loki start "<normalized brief>" --pr`: the output is a
|
|
33
33
|
receipt-gated PR only, never a deploy. No secret means the route answers 503.
|
|
34
34
|
|
|
35
35
|
Usage:
|
|
@@ -268,9 +268,9 @@ def constant_time_equals(a, b):
|
|
|
268
268
|
|
|
269
269
|
|
|
270
270
|
# How long to wait for a dispatched `loki start` to finish before we stop
|
|
271
|
-
# waiting on it. The
|
|
272
|
-
#
|
|
273
|
-
#
|
|
271
|
+
# waiting on it. The start runs in the foreground on the Loki 10 engine; this bound only
|
|
272
|
+
# stops the worker thread waiting on a long run. The child keeps running and
|
|
273
|
+
# is reaped in the background after we stop waiting.
|
|
274
274
|
DISPATCH_WAIT_SECONDS = 30
|
|
275
275
|
|
|
276
276
|
# Defaults for the bounded worker queue.
|
|
@@ -445,6 +445,32 @@ def send_notification(message):
|
|
|
445
445
|
pass
|
|
446
446
|
|
|
447
447
|
|
|
448
|
+
def _dispatch_log_paths():
|
|
449
|
+
"""Fresh stdout and stderr log paths for one dispatch under .loki/triggers."""
|
|
450
|
+
paths = []
|
|
451
|
+
for suffix in (".out", ".err"):
|
|
452
|
+
fd, path = tempfile.mkstemp(prefix="dispatch-", suffix=suffix, dir=str(get_loki_dir()))
|
|
453
|
+
os.close(fd)
|
|
454
|
+
paths.append(path)
|
|
455
|
+
return paths
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def _read_bytes(path):
|
|
459
|
+
try:
|
|
460
|
+
with open(path, "rb") as f:
|
|
461
|
+
return f.read()
|
|
462
|
+
except OSError:
|
|
463
|
+
return b""
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def _remove_quietly(*paths):
|
|
467
|
+
for path in paths:
|
|
468
|
+
try:
|
|
469
|
+
os.unlink(path)
|
|
470
|
+
except OSError:
|
|
471
|
+
pass
|
|
472
|
+
|
|
473
|
+
|
|
448
474
|
def _reap_child(proc):
|
|
449
475
|
"""Wait on a child process so it is always reaped (no zombie).
|
|
450
476
|
|
|
@@ -514,29 +540,34 @@ def run_loki_outcome(args, dry_run=False, stdout_sink=None, extra_env=None):
|
|
|
514
540
|
logging.info("[DRY-RUN] Would run: %s", " ".join(cmd))
|
|
515
541
|
return OUTCOME_PASSED
|
|
516
542
|
logging.info("Running: %s", " ".join(cmd))
|
|
543
|
+
# Child output goes to per-run files, never a pipe: past the wait window
|
|
544
|
+
# nothing reads a pipe, so a child writing more than the ~64KB pipe buffer
|
|
545
|
+
# would block forever. The files double as the run log for a detached child.
|
|
546
|
+
out_path, err_path = _dispatch_log_paths()
|
|
517
547
|
try:
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
548
|
+
with open(out_path, "wb") as out_f, open(err_path, "wb") as err_f:
|
|
549
|
+
proc = subprocess.Popen(
|
|
550
|
+
cmd,
|
|
551
|
+
stdout=out_f,
|
|
552
|
+
stderr=err_f,
|
|
553
|
+
env=_scoped_child_env(extra_env),
|
|
554
|
+
)
|
|
524
555
|
except (FileNotFoundError, OSError) as e:
|
|
525
556
|
logging.error("Failed to launch %s: %s", " ".join(cmd), e)
|
|
557
|
+
_remove_quietly(out_path, err_path)
|
|
526
558
|
return OUTCOME_FAILED
|
|
527
559
|
|
|
528
560
|
try:
|
|
529
|
-
|
|
530
|
-
if stdout_sink is not None:
|
|
531
|
-
stdout_sink.append((stdout or b"").decode("utf-8", errors="replace"))
|
|
561
|
+
proc.wait(timeout=DISPATCH_WAIT_SECONDS)
|
|
532
562
|
except subprocess.TimeoutExpired:
|
|
533
563
|
# Detached past the wait window. The reaper collects the child, but we
|
|
534
564
|
# never see its exit code, so the outcome is genuinely UNKNOWN -- NOT a
|
|
535
565
|
# pass. Reporting success here is exactly the defect this replaces.
|
|
536
566
|
logging.info(
|
|
537
|
-
"Dispatch pid=%d still running after %ds; reaping in background",
|
|
567
|
+
"Dispatch pid=%d still running after %ds; reaping in background, output in %s",
|
|
538
568
|
proc.pid,
|
|
539
569
|
DISPATCH_WAIT_SECONDS,
|
|
570
|
+
out_path,
|
|
540
571
|
)
|
|
541
572
|
threading.Thread(
|
|
542
573
|
target=_reap_child,
|
|
@@ -546,6 +577,12 @@ def run_loki_outcome(args, dry_run=False, stdout_sink=None, extra_env=None):
|
|
|
546
577
|
).start()
|
|
547
578
|
return OUTCOME_UNKNOWN
|
|
548
579
|
|
|
580
|
+
stdout = _read_bytes(out_path)
|
|
581
|
+
stderr = _read_bytes(err_path)
|
|
582
|
+
_remove_quietly(out_path, err_path)
|
|
583
|
+
if stdout_sink is not None:
|
|
584
|
+
stdout_sink.append(stdout.decode("utf-8", errors="replace"))
|
|
585
|
+
|
|
549
586
|
if proc.returncode == 0:
|
|
550
587
|
logging.info("Dispatch pid=%d completed (exit 0)", proc.pid)
|
|
551
588
|
return OUTCOME_PASSED
|
|
@@ -778,7 +815,7 @@ def tracker_comment_steer(kind, payload, is_live, dry_run=False):
|
|
|
778
815
|
|
|
779
816
|
|
|
780
817
|
def handle_issues_event(payload, dry_run=False):
|
|
781
|
-
"""Handle issues event: opened -> loki start <issue-ref> --pr
|
|
818
|
+
"""Handle issues event: opened -> loki start <issue-ref> --pr."""
|
|
782
819
|
action = payload.get("action", "")
|
|
783
820
|
if action != "opened":
|
|
784
821
|
return None, "skipped (action=%s)" % action
|
|
@@ -801,7 +838,7 @@ def handle_issues_event(payload, dry_run=False):
|
|
|
801
838
|
ref = str(issue_number)
|
|
802
839
|
if repo_full_name:
|
|
803
840
|
ref = "%s#%s" % (repo_full_name, issue_number)
|
|
804
|
-
args = ["start", ref, "--pr"
|
|
841
|
+
args = ["start", ref, "--pr"]
|
|
805
842
|
summary = "issue #%s opened in %s" % (issue_number, repo_full_name)
|
|
806
843
|
success = run_loki_command(args, dry_run=dry_run)
|
|
807
844
|
status = "fired" if success else "error"
|
|
@@ -811,7 +848,7 @@ def handle_issues_event(payload, dry_run=False):
|
|
|
811
848
|
|
|
812
849
|
|
|
813
850
|
def handle_pull_request_event(payload, dry_run=False):
|
|
814
|
-
"""Handle pull_request event: synchronize -> loki start <pr-ref
|
|
851
|
+
"""Handle pull_request event: synchronize -> loki start <pr-ref>."""
|
|
815
852
|
action = payload.get("action", "")
|
|
816
853
|
if action != "synchronize":
|
|
817
854
|
return None, "skipped (action=%s)" % action
|
|
@@ -828,7 +865,7 @@ def handle_pull_request_event(payload, dry_run=False):
|
|
|
828
865
|
ref = str(pr_number)
|
|
829
866
|
if repo_full_name:
|
|
830
867
|
ref = "%s#%s" % (repo_full_name, pr_number)
|
|
831
|
-
args = ["start", ref
|
|
868
|
+
args = ["start", ref]
|
|
832
869
|
summary = "PR #%s synchronized in %s" % (pr_number, repo_full_name)
|
|
833
870
|
success = run_loki_command(args, dry_run=dry_run)
|
|
834
871
|
status = "fired" if success else "error"
|
|
@@ -852,7 +889,7 @@ def handle_workflow_run_event(payload, dry_run=False):
|
|
|
852
889
|
summary = "workflow '%s' failed in %s" % (wf_name, repo_full_name)
|
|
853
890
|
# CI-failure context: re-run the current spec in the working directory so
|
|
854
891
|
# the agent can repair the failing build. No issue/PR ref to attach here.
|
|
855
|
-
args = ["start"
|
|
892
|
+
args = ["start"]
|
|
856
893
|
success = run_loki_command(args, dry_run=dry_run)
|
|
857
894
|
status = "fired" if success else "error"
|
|
858
895
|
if success:
|
|
@@ -862,14 +899,14 @@ def handle_workflow_run_event(payload, dry_run=False):
|
|
|
862
899
|
|
|
863
900
|
def brief_start_args(brief):
|
|
864
901
|
"""argv for a delegated PR run. `--brief` forces brief mode so the text is
|
|
865
|
-
never reclassified as a PRD path or issue URL, and `--pr
|
|
902
|
+
never reclassified as a PRD path or issue URL, and `--pr` are only
|
|
866
903
|
honored by `loki start` in brief mode because cmd_start routes them through
|
|
867
904
|
the worktree + PR path."""
|
|
868
|
-
return ["start", "--brief", brief, "--pr"
|
|
905
|
+
return ["start", "--brief", brief, "--pr"]
|
|
869
906
|
|
|
870
907
|
|
|
871
908
|
def handle_job_event(payload, dry_run=False):
|
|
872
|
-
"""Handle a remotely-submitted job: loki start <spec
|
|
909
|
+
"""Handle a remotely-submitted job: loki start <spec>.
|
|
873
910
|
|
|
874
911
|
Runs on the same worker pool as the webhook handlers. The spec was already
|
|
875
912
|
validated by valid_spec() at admission; it is re-checked here so this
|
|
@@ -890,7 +927,7 @@ def handle_job_event(payload, dry_run=False):
|
|
|
890
927
|
elif payload.get("pr") is True:
|
|
891
928
|
args = brief_start_args(spec.strip())
|
|
892
929
|
else:
|
|
893
|
-
args = ["start", spec.strip()
|
|
930
|
+
args = ["start", spec.strip()]
|
|
894
931
|
summary = "job %s: %s" % (payload.get("job_id", "?"), spec.strip())
|
|
895
932
|
# A remote submitter gates CI on this, so report the BUILD's outcome, not
|
|
896
933
|
# merely that a build was launched. "fired" (launched) and "passed" must
|
|
@@ -1338,7 +1375,7 @@ def build_sentry_brief(norm, payload=None):
|
|
|
1338
1375
|
|
|
1339
1376
|
|
|
1340
1377
|
def handle_sentry_event(payload, dry_run=False):
|
|
1341
|
-
"""Handle a verified Sentry alert: loki start <brief> --pr
|
|
1378
|
+
"""Handle a verified Sentry alert: loki start <brief> --pr.
|
|
1342
1379
|
|
|
1343
1380
|
PR only. Never passes a deploy argument: a fix still has to earn a VERIFIED
|
|
1344
1381
|
receipt, and shipping it stays a separate, gated human step.
|
|
@@ -1535,7 +1572,7 @@ class Dispatcher:
|
|
|
1535
1572
|
borrowed-receipt failure this design exists to prevent.
|
|
1536
1573
|
|
|
1537
1574
|
Attribution is deliberately NOT resolved when the dispatch returns.
|
|
1538
|
-
`loki start
|
|
1575
|
+
`loki start` can outlast the dispatch wait window, and the
|
|
1539
1576
|
build runs for minutes and writes its receipt at the very end -- so a
|
|
1540
1577
|
window closed at dispatch-return would always see an unchanged pointer
|
|
1541
1578
|
and every real build would 404. Resolution happens lazily in
|
package/bin/loki
CHANGED
|
@@ -635,6 +635,7 @@ if [ -f "$REPO_ROOT/autonomy/route.sh" ] && { [ "$_LOKI_START_ATTEMPTS" != "1" ]
|
|
|
635
635
|
case "$R_SESSION_MODEL" in small) R_SESSION_MODEL=fast ;; medium) R_SESSION_MODEL=development ;; high) R_SESSION_MODEL=planning ;; esac
|
|
636
636
|
export LOKI_SESSION_MODEL="$R_SESSION_MODEL"
|
|
637
637
|
fi
|
|
638
|
+
if [ "$R_E10" = "1" ] && [ -n "${R_E10_CAP_S:-}" ]; then export LOKI_E10_CAP_S="$R_E10_CAP_S"; fi
|
|
638
639
|
if [ "$R_E10" != "1" ]; then
|
|
639
640
|
set -- "$([ "$R_VERB" = "bare" ] && echo start || echo "$R_VERB")" ${R_CLEAN[@]+"${R_CLEAN[@]}"}
|
|
640
641
|
fi
|
package/dashboard/__init__.py
CHANGED
package/docs/CLI-REFERENCE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CLI reference
|
|
2
2
|
|
|
3
|
-
Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.
|
|
3
|
+
Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.40.0. Do not edit by hand.
|
|
4
4
|
Hidden and legacy-only commands are omitted; see `docs/v10/CLI-MODERN.md` for the full inventory.
|
|
5
5
|
|
|
6
6
|
| Command | Description |
|
package/docs/DEPLOYMENT.md
CHANGED
|
@@ -119,9 +119,11 @@ logged and ignored.
|
|
|
119
119
|
|
|
120
120
|
| Event | Action | What runs |
|
|
121
121
|
|---|---|---|
|
|
122
|
-
| `issues` | `opened` | `loki start owner/repo#N --pr
|
|
123
|
-
| `pull_request` | `synchronize` | `loki start owner/repo#N
|
|
124
|
-
| `workflow_run` | `completed` with `conclusion=failure` | `loki start
|
|
122
|
+
| `issues` | `opened` | `loki start owner/repo#N --pr` |
|
|
123
|
+
| `pull_request` | `synchronize` | `loki start owner/repo#N` |
|
|
124
|
+
| `workflow_run` | `completed` with `conclusion=failure` | `loki start` |
|
|
125
|
+
|
|
126
|
+
The trigger server runs each of these starts in the foreground on the Loki 10 engine (Wall and VERIFIED apply). A run longer than the dispatch wait window is reaped in the background and its job status stays unknown until it exits.
|
|
125
127
|
|
|
126
128
|
Note what is **not** there. Labelling an issue does nothing. Commenting on an
|
|
127
129
|
issue does nothing. Opening a pull request does nothing -- for PRs it is
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Loki Mode, by [Autonomi](https://www.autonomi.dev/), turns a spec or an issue into a verified pull request with a receipt you can check. This page covers installing it, a first run, and the options around it.
|
|
4
4
|
|
|
5
|
-
**Version:** v11.
|
|
5
|
+
**Version:** v11.40.0
|
|
6
6
|
|
|
7
7
|
**Engine note:** `loki start` is the primary command. `loki start`, `loki "<task>"`, `loki owner/repo#N` and `loki quick` all run the Loki 10 engine, whatever the flag order or input form (PRD file, issue ref, task or one-word brief). The previous loop runs only on `--engine legacy`, `LOKI_ENGINE=legacy`, or for one call when a flag Loki 10 cannot honor is present. See the [user guide](v10/GUIDE.md) and the [5-minute quickstart](QUICKSTART.md).
|
|
8
8
|
|
package/docs/KNOWN-ISSUES.md
CHANGED
|
@@ -4,7 +4,7 @@ Issues in the current release that are known and not yet fixed. Report new ones
|
|
|
4
4
|
|
|
5
5
|
## Which engine runs
|
|
6
6
|
|
|
7
|
-
From 11.4.0, `loki start`, `loki "<task>"`, `loki owner/repo#N` and `loki quick` run the Loki 10 engine whatever the flag order or input form. The previous loop runs only on `--engine legacy`, `LOKI_ENGINE=legacy`, for a provider with no Loki 10 invoker, for a PRD over 100000 bytes, or
|
|
7
|
+
From 11.4.0, `loki start`, `loki "<task>"`, `loki owner/repo#N` and `loki quick` run the Loki 10 engine whatever the flag order or input form. The previous loop runs only on `--engine legacy`, `LOKI_ENGINE=legacy`, for a provider with no Loki 10 invoker, for a PRD over 100000 bytes, or never because of a flag: a flag Loki 10 cannot honor (`--simple`, `--sandbox`, `--ship`, `--worktree`, `--detach`, `--parallel`, ...) is refused with exit 2 and names its replacement, and `--engine legacy` keeps it. `--pr` is accepted (Loki 10 opens the PR by default), `--quiet` and `--resume` are honored, and `--dry-run` prints the route, max-cost and run cap without starting. Check with `loki route start <args> --json`: it reports `engine`, `adapter`, `reason` and `capability_differences`. The full table is in the Routing contract section of `docs/exit-codes.md`.
|
|
8
8
|
|
|
9
9
|
## Exit codes
|
|
10
10
|
|
package/docs/exit-codes.md
CHANGED
|
@@ -455,13 +455,10 @@ The table below is rendered from live `loki route --json` output by `tests/test-
|
|
|
455
455
|
| `start p.md --provider cline` | engine10 | engine10:cline | 1 listed | default: prd input runs on engine10 regardless of flag order |
|
|
456
456
|
| `start p.md --provider aider` | engine10 | engine10:aider | 1 listed | default: prd input runs on engine10 regardless of flag order |
|
|
457
457
|
| `start p.md --provider opencode` | engine10 | engine10:opencode | 1 listed | default: prd input runs on engine10 regardless of flag order |
|
|
458
|
-
| `start p.md --provider gemini` | legacy | legacy-bash:gemini | 2 listed | provider gemini has no engine10 invoker (use claude, codex, cline, aider or opencode) |
|
|
459
458
|
| `start p.md --engine legacy` | legacy | legacy-bash:claude | 1 listed | explicit: --engine legacy or LOKI_ENGINE=legacy |
|
|
460
459
|
| `start --engine legacy p.md` | legacy | legacy-bash:claude | 1 listed | explicit: --engine legacy or LOKI_ENGINE=legacy |
|
|
461
460
|
| `LOKI_ENGINE=legacy start p.md` | legacy | legacy-bash:claude | 1 listed | explicit: --engine legacy or LOKI_ENGINE=legacy |
|
|
462
461
|
| `start p.md --engine engine10` | engine10 | engine10:claude | none | explicit: --engine engine10 |
|
|
463
462
|
| `LOKI_ENGINE=engine10 start p.md` | engine10 | engine10:claude | none | explicit: --engine engine10 |
|
|
464
|
-
| `start p.md --parallel` | legacy | legacy-bash:claude | 2 listed | flag --parallel is not supported by engine10 |
|
|
465
|
-
| `start --parallel p.md` | legacy | legacy-bash:claude | 2 listed | flag --parallel is not supported by engine10 |
|
|
466
463
|
| `start big.md` | legacy | legacy-bash:claude | 1 listed | PRD is larger than 100000 bytes (engine10 takes the task as one argument) |
|
|
467
464
|
<!-- route-table:end -->
|
package/docs/merge-queue.md
CHANGED
|
@@ -28,5 +28,5 @@ tail to a bounded repair session on the PR branch (LOKI_PR_REPAIR_CMD, default a
|
|
|
28
28
|
argument), pushes, and re-checks, up to LOKI_PR_REPAIR_MAX attempts (default 2).
|
|
29
29
|
Green goes through the same merge gate as `loki merge pr`. When attempts run out
|
|
30
30
|
the PR is returned to draft, nothing merges, and the last failure log is printed.
|
|
31
|
-
`loki start --ship` calls this after it opens the PR; `/merge watch <pr>` runs it
|
|
31
|
+
`loki start --ship --engine legacy` calls this after it opens the PR; `/merge watch <pr>` runs it
|
|
32
32
|
from `loki chat`.
|
package/docs/sentry-intake.md
CHANGED
|
@@ -18,7 +18,7 @@ The token is sent only to the Sentry host through a curl config on stdin, never
|
|
|
18
18
|
|
|
19
19
|
1. Set the client secret: `LOKI_SENTRY_WEBHOOK_SECRET` (or `SENTRY_CLIENT_SECRET`). It must differ from the GitHub webhook secret and the API token. With no secret the route answers 503 and nothing is enqueued.
|
|
20
20
|
2. Start the server: `python3 autonomy/trigger-server.py --port 8080` and point the Sentry alert webhook at `/sentry`.
|
|
21
|
-
3. A valid alert is normalized with `normalize_sentry_json` and enqueues `loki start "<brief>" --pr
|
|
21
|
+
3. A valid alert is normalized with `normalize_sentry_json` and enqueues `loki start "<brief>" --pr`. A bad signature gets 401.
|
|
22
22
|
|
|
23
23
|
The brief also carries the event's release and environment, the newest `.loki/deploys` record (`last deploy <sha7> <finished_at> health=<status>`) and, when the culprit file resolves inside the project, its last `git log -1` author. All of it is sanitized to one line and capped; it adds no authority.
|
|
24
24
|
|
|
@@ -35,30 +35,30 @@ Slack (`POST /slack`):
|
|
|
35
35
|
|
|
36
36
|
1. Set `SLACK_SIGNING_SECRET`. Point a `/loki` slash command (and Events URL verification) at `/slack`.
|
|
37
37
|
2. The signature is `v0=` + hex HMAC-SHA256 over `v0:<X-Slack-Request-Timestamp>:<raw body>`, window 300s, the same contract as `loki-ts/src/contrib/slack_inbound.ts`. Bad, stale or missing signature: 401, nothing queued.
|
|
38
|
-
3. The command text is the spec (one line, not starting with `-`) and runs as `loki start <text
|
|
38
|
+
3. The command text is the spec (one line, not starting with `-`) and runs as `loki start <text>`. Slack gets an ephemeral reply carrying the job id.
|
|
39
39
|
4. On completion the summary is posted to the request's `response_url`, only if it starts with `https://hooks.slack.com/`. Any other host is never contacted.
|
|
40
40
|
|
|
41
41
|
Linear (`POST /linear`):
|
|
42
42
|
|
|
43
43
|
1. Set `LINEAR_WEBHOOK_SECRET`. Point a Linear issue webhook at `/linear`.
|
|
44
44
|
2. `Linear-Signature` is the hex HMAC-SHA256 of the raw body, and the signed `webhookTimestamp` must be within 300s. Otherwise 401.
|
|
45
|
-
3. A created issue enqueues `loki start linear:<identifier
|
|
45
|
+
3. A created issue enqueues `loki start linear:<identifier>` and answers 202 with the job id. Other events are skipped.
|
|
46
46
|
4. If `LINEAR_API_KEY` is set, a comment with the result is posted on the issue through `https://api.linear.app/graphql`. Without it nothing is posted.
|
|
47
47
|
|
|
48
48
|
Jira (`POST /jira`):
|
|
49
49
|
|
|
50
50
|
1. Set `JIRA_WEBHOOK_SECRET` (the secret on a Jira Cloud webhook) and point the webhook at `/jira`. Set `JIRA_ASSIGNEES` (comma-separated accountId, email or display name) and/or `JIRA_LABELS`. With neither set, every event is skipped.
|
|
51
51
|
2. `X-Hub-Signature` is `sha256=` + hex HMAC-SHA256 of the raw body. Bad or missing signature: 401. Jira signs no timestamp and the `X-Atlassian-Webhook-Identifier` header is not covered by the HMAC, so replay is bounded two ways: a repeated delivery id, or a repeated signed body (sha256 of the raw body, so changing or dropping the header does not help), answers `duplicate` and queues nothing. A replay of a body Jira never sent twice still needs the secret to forge.
|
|
52
|
-
3. `jira:issue_created`, or `jira:issue_updated` whose changelog touches assignee or labels, runs only when the issue is assigned to a configured user or has a configured label. The key, summary and description are folded into a one-line brief (1500 bytes max; an Atlassian Document Format description is flattened to its text) and run as `loki start --brief "<brief>" --pr
|
|
52
|
+
3. `jira:issue_created`, or `jira:issue_updated` whose changelog touches assignee or labels, runs only when the issue is assigned to a configured user or has a configured label. The key, summary and description are folded into a one-line brief (1500 bytes max; an Atlassian Document Format description is flattened to its text) and run as `loki start --brief "<brief>" --pr`. Answers 202 with the job id.
|
|
53
53
|
|
|
54
54
|
Teams (`POST /teams`):
|
|
55
55
|
|
|
56
56
|
1. Set `TEAMS_SECURITY_TOKEN` (the base64 token Teams shows when you create the outgoing webhook) and point the webhook at `/teams`.
|
|
57
57
|
2. `Authorization: HMAC <base64>` is the base64 HMAC-SHA256 of the raw body keyed by the base64-decoded token. Bad signature: 401. The activity `timestamp` must be within 300s; a stale or missing one is refused with 401.
|
|
58
|
-
3. The message text (with `<at>` mentions and tags removed) is the brief and runs as `loki start --brief "<brief>" --pr
|
|
58
|
+
3. The message text (with `<at>` mentions and tags removed) is the brief and runs as `loki start --brief "<brief>" --pr`. Teams gets a synchronous reply whose text starts with "Queued" and ends with the job id. A repeated activity id is not queued twice.
|
|
59
59
|
|
|
60
60
|
Jira completion comments: set `JIRA_BASE_URL` (https only), `JIRA_EMAIL` and `JIRA_API_TOKEN`; the result is then posted as a comment on the issue that triggered the run. Without all three nothing is posted, and `JIRA_API_TOKEN` never reaches the `loki start` child environment.
|
|
61
61
|
|
|
62
|
-
Jira and Teams runs are receipt-gated PR runs only: the server always passes `--pr` and never a deploy argument, so shipping stays a separate gated step. `--brief` forces brief mode, so text that ends in `.md` or contains an issue URL is never reclassified, and `loki start --brief ... --pr
|
|
62
|
+
Jira and Teams runs are receipt-gated PR runs only: the server always passes `--pr` and never a deploy argument, so shipping stays a separate gated step. `--brief` forces brief mode, so text that ends in `.md` or contains an issue URL is never reclassified, and `loki start --brief ... --pr` runs on the Loki 10 engine, which opens a PR carrying the Evidence Receipt (the same machinery as `loki start <issue>`). Sentry alerts use the same form. The server never passes `--detach` and never sets `LOKI_ENGINE`, so every webhook start gets the Wall and VERIFIED. Neither secret reaches the `loki start` child environment, and both must differ from every other credential. A missing secret answers 503 naming the variable.
|
|
63
63
|
|
|
64
64
|
Reply targets are stored server side keyed by job id, captured only after verification. Notifier errors are logged and never fail the job. Poll status with `GET /jobs/<id>`.
|