loki-mode 10.6.8 → 10.6.9
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 +14 -13
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/backlog.py +167 -36
- package/autonomy/lib/workspace.py +133 -39
- package/autonomy/lib/workspace_comment.py +91 -0
- package/autonomy/loki +85 -17
- package/bin/loki +3 -3
- package/completions/_loki +1 -0
- package/completions/loki.bash +1 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/api_start.py +26 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/WORKSPACES.md +11 -4
- package/docs/control-plane-container.md +8 -3
- package/docs/dashboard-guide.md +4 -0
- package/docs/exit-codes.md +1 -0
- package/docs/slack.md +12 -4
- package/docs/trackers.md +27 -0
- package/docs/v10/BOARD.md +64 -29
- package/docs/v10/CONTROL-PLANE.md +14 -7
- package/docs/v10/D51-PHASE-B.md +3 -1
- package/docs/v10/D61-SPEED.md +4 -0
- package/docs/v10/D63-CARDS.md +354 -0
- package/docs/v10/DECISIONS.md +39 -0
- package/docs/v10/PROGRESS.md +50 -0
- package/loki-ts/dist/loki.js +583 -564
- package/mcp/__init__.py +1 -1
- package/mcp/tests/test_v10_tools.py +56 -0
- package/mcp/v10_tools.py +13 -3
- package/package.json +1 -1
- package/packages/control-plane/dist/server.js +76 -6
- package/packages/control-plane/drizzle/0001_group_id.sql +3 -0
- package/packages/control-plane/drizzle/meta/_journal.json +8 -1
- package/packages/control-plane/src/db/schema.ts +3 -0
- package/packages/control-plane/src/server/answer.ts +1 -1
- package/packages/control-plane/src/server/app.ts +6 -2
- package/packages/control-plane/src/server/auth.ts +51 -0
- package/packages/control-plane/src/server/runs.ts +4 -3
- package/packages/control-plane/src/server/serve.ts +11 -1
- package/packages/control-plane/src/shipper/discover.ts +29 -0
- package/packages/control-plane/ui/dist/assets/{index-53E32yrO.js → index-Jt31nrK-.js} +24 -24
- package/packages/control-plane/ui/dist/index.html +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/web-app/dist/assets/{AdminPage-DN_pe8vo.js → AdminPage-CZKZLuWU.js} +1 -1
- package/web-app/dist/assets/{Avatar-BnQsr8z-.js → Avatar-Dcy9kO2E.js} +1 -1
- package/web-app/dist/assets/{Badge-DzERiZn2.js → Badge-DUotUSyx.js} +1 -1
- package/web-app/dist/assets/{Button-CaaoxQAt.js → Button-CFz_zfMZ.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-SXhnysJA.js → CockpitPage-BhRw_oiM.js} +1 -1
- package/web-app/dist/assets/{ComparePage-krxTCFJY.js → ComparePage-DfnsRTTC.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-BL96ESMc.js → ErrorBoundary-Dn0gUu3D.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-PUiGSpkB.js → EvidenceReceiptPanel-Co-xi282.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-C0hPs-P2.js → GitHubIssuesPanel-CoCpfexq.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-CLvqNUlu.js → GitHubPRsPanel-Dg0Eg6F_.js} +1 -1
- package/web-app/dist/assets/{HomePage-X-sq_Rc9.js → HomePage-BysxL4mV.js} +2 -2
- package/web-app/dist/assets/{LoginPage-DhQuwdua.js → LoginPage-CQ366p_d.js} +1 -1
- package/web-app/dist/assets/{MagicPage-CP2ODjfn.js → MagicPage-DD7YcbNq.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-C6SYiQES.js → MetricsPage-BYz49xQj.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-CCDxxYc-.js → NotFoundPage-CKs09MeO.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-yDF--8FQ.js → ProjectPage-Ci2BIA_D.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-BBLspdLn.js → ProjectsPage-ClwMH0eu.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-C_sWbQRw.js → SettingsPage-Cm0rCX-y.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-xCMuL0N8.js → ShowcasePage-CxvESt8X.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-DnJKGCx7.js → SystemSettingsPage-W_CGYPOY.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-BBXdhrSs.js → TeamsPage-feVvRvys.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-AZ1Lr1rn.js → TemplatesPage-C-pUWMmF.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-CtIm9tXc.js → TerminalOutput-j5ibjKY3.js} +1 -1
- package/web-app/dist/assets/{activity-Dk1FK25Q.js → activity-TTdVYMa_.js} +1 -1
- package/web-app/dist/assets/{bell-dhqXyzP4.js → bell-DeoTFwpv.js} +1 -1
- package/web-app/dist/assets/{bot-CExHd4Kf.js → bot-B5SghxqU.js} +1 -1
- package/web-app/dist/assets/{check-D51zaA1a.js → check-k8SzsdbE.js} +1 -1
- package/web-app/dist/assets/{chevron-left-DEwnQzH1.js → chevron-left-DHnEllFz.js} +1 -1
- package/web-app/dist/assets/{circle-alert-C6U90Msk.js → circle-alert-DW_SLdcB.js} +1 -1
- package/web-app/dist/assets/{clock-6mFbMzzH.js → clock-Bmoaxy09.js} +1 -1
- package/web-app/dist/assets/{cloud-BrZZtE7F.js → cloud-eg3tzXTh.js} +1 -1
- package/web-app/dist/assets/{code-xml-DTFzeepP.js → code-xml-VAfXihhD.js} +1 -1
- package/web-app/dist/assets/{database-B5rEiTre.js → database-CY3_-5aK.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-bQs4rlS_.js → dollar-sign-DYC0OLlg.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-Mo6e6qbf.js → file-code-corner-0X3SveKz.js} +1 -1
- package/web-app/dist/assets/{file-plus-DzHZ0jpP.js → file-plus-B_oh_ukl.js} +1 -1
- package/web-app/dist/assets/{globe-DYN9UhTa.js → globe-F0tLA8WJ.js} +1 -1
- package/web-app/dist/assets/{hammer-bB0hSRcJ.js → hammer-DrlvBmfW.js} +1 -1
- package/web-app/dist/assets/{index-CIH5CqZB.js → index-YihVaZAj.js} +3 -3
- package/web-app/dist/assets/{layers-qhFkY8Nu.js → layers-CbJfn5UC.js} +1 -1
- package/web-app/dist/assets/{loader-circle-BGBkaYH7.js → loader-circle-CpmOggou.js} +1 -1
- package/web-app/dist/assets/{lock-DIcsNFum.js → lock-BGJ6QWrI.js} +1 -1
- package/web-app/dist/assets/{package-BdIIbZhS.js → package-BTmH4Xto.js} +1 -1
- package/web-app/dist/assets/{plus-Co1Bv8zT.js → plus-mKQ7hz-8.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-CGF4ABWH.js → refresh-cw-vPetpLsm.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-BX10Q79h.js → rotate-ccw-DiJ8GFhj.js} +1 -1
- package/web-app/dist/assets/{scroll-text-CbXDlpGp.js → scroll-text-DpPQIlQR.js} +1 -1
- package/web-app/dist/assets/{server-CIhi6yUO.js → server-CiaV4drx.js} +1 -1
- package/web-app/dist/assets/{shield-alert-DJ5fB6Dg.js → shield-alert-BaaQqyco.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-BEft2T94.js → thumbs-up-2zNCKp0Q.js} +1 -1
- package/web-app/dist/assets/{trash-2-CWGUPn7Y.js → trash-2-CiVDx5Co.js} +1 -1
- package/web-app/dist/assets/{trending-down-CjqstvxQ.js → trending-down-tcxvw5BH.js} +1 -1
- package/web-app/dist/assets/{trending-up-CTyLjMzE.js → trending-up-C2bv2_v_.js} +1 -1
- package/web-app/dist/assets/{upload-BmRx-uv4.js → upload-DS4VPUAp.js} +1 -1
- package/web-app/dist/assets/{usePolling-ChHkSJye.js → usePolling-JggBcVln.js} +1 -1
- package/web-app/dist/assets/{user-ZXh_f35z.js → user-CvavvOxM.js} +1 -1
- package/web-app/dist/index.html +1 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Post the workspace integration result as one PR comment per repo PR (D51-B11).
|
|
2
|
+
|
|
3
|
+
No LLM. Uses the `gh` CLI with the caller's own environment, so GH_TOKEN or
|
|
4
|
+
GITHUB_TOKEN is read by gh itself and never placed in argv, the comment body
|
|
5
|
+
or any log line. Commenting is best effort: it never raises and never changes
|
|
6
|
+
the run's exit code. Opt out with LOKI_WORKSPACE_COMMENT=0.
|
|
7
|
+
"""
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import subprocess
|
|
11
|
+
import tempfile
|
|
12
|
+
|
|
13
|
+
GH_TIMEOUT_S = 60
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def verdict(ev):
|
|
17
|
+
"""Map integration evidence to a label. Only a clean exit 0 reads as PASSED."""
|
|
18
|
+
status = ev.get("status")
|
|
19
|
+
if "timeout" in str(ev.get("note") or "").lower():
|
|
20
|
+
return "TIMEOUT"
|
|
21
|
+
if status == "passed" and ev.get("exit_code") == 0:
|
|
22
|
+
return "PASSED"
|
|
23
|
+
if status == "not_configured":
|
|
24
|
+
return "NOT CONFIGURED"
|
|
25
|
+
return "FAILED"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def render(ws_name, run_id, repo, ev):
|
|
29
|
+
heads = ev.get("heads") or {}
|
|
30
|
+
lines = ["Loki workspace `%s` run `%s`" % (ws_name, run_id), "",
|
|
31
|
+
"Integration: **%s** (exit code %s)" % (verdict(ev), ev.get("exit_code")), "",
|
|
32
|
+
"| Repo | Outcome | Head SHA |", "| --- | --- | --- |"]
|
|
33
|
+
outcomes = ev.get("outcomes") or {}
|
|
34
|
+
for r in sorted(heads.keys() | outcomes.keys()):
|
|
35
|
+
out = outcomes.get(r, "unknown")
|
|
36
|
+
out = "ok" if out == "ok" else str(out).replace("|", "/")
|
|
37
|
+
mark = " (this PR)" if r == repo else ""
|
|
38
|
+
lines.append("| %s%s | %s | %s |" % (r, mark, out, heads.get(r) or "none"))
|
|
39
|
+
if ev.get("log_sha256"):
|
|
40
|
+
lines += ["", "Integration log sha256: `%s`" % ev["log_sha256"]]
|
|
41
|
+
lines += ["", "Integration evidence is not part of the Seal."]
|
|
42
|
+
return "\n".join(lines) + "\n"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _gh(args, cwd):
|
|
46
|
+
return subprocess.run(["gh"] + args, cwd=cwd, stdin=subprocess.DEVNULL, capture_output=True,
|
|
47
|
+
text=True, timeout=GH_TIMEOUT_S)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _branch(wt):
|
|
51
|
+
r = subprocess.run(["git", "-C", wt, "rev-parse", "--abbrev-ref", "HEAD"], capture_output=True, text=True,
|
|
52
|
+
timeout=GH_TIMEOUT_S)
|
|
53
|
+
b = r.stdout.strip()
|
|
54
|
+
return b if r.returncode == 0 and b and b != "HEAD" else None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def post_comments(ws_name, run_id, run_dir, worktrees, ev, say):
|
|
58
|
+
"""One comment per repo that has an open PR on its run branch. Never raises."""
|
|
59
|
+
if os.environ.get("LOKI_WORKSPACE_COMMENT") == "0":
|
|
60
|
+
return 0
|
|
61
|
+
posted = 0
|
|
62
|
+
try:
|
|
63
|
+
for repo, wt in sorted(worktrees.items()):
|
|
64
|
+
try:
|
|
65
|
+
branch = _branch(wt)
|
|
66
|
+
if not branch:
|
|
67
|
+
continue
|
|
68
|
+
r = _gh(["pr", "list", "--repo", repo, "--head", branch, "--state", "open",
|
|
69
|
+
"--json", "number", "--jq", ".[0].number"], wt)
|
|
70
|
+
num = r.stdout.strip()
|
|
71
|
+
if r.returncode != 0 or not re.fullmatch(r"\d+", num):
|
|
72
|
+
continue
|
|
73
|
+
fd, body = tempfile.mkstemp(prefix="comment-", suffix=".md", dir=run_dir) # mode 0600
|
|
74
|
+
try:
|
|
75
|
+
with os.fdopen(fd, "w") as f:
|
|
76
|
+
f.write(render(ws_name, run_id, repo, ev))
|
|
77
|
+
c = _gh(["pr", "comment", num, "--repo", repo, "--body-file", body], wt)
|
|
78
|
+
finally:
|
|
79
|
+
try:
|
|
80
|
+
os.unlink(body)
|
|
81
|
+
except OSError:
|
|
82
|
+
pass
|
|
83
|
+
if c.returncode == 0:
|
|
84
|
+
posted += 1
|
|
85
|
+
else:
|
|
86
|
+
say("workspace: comment on %s#%s failed (gh exit %d)" % (repo, num, c.returncode))
|
|
87
|
+
except (OSError, subprocess.SubprocessError, ValueError) as e:
|
|
88
|
+
say("workspace: comment for %s skipped (%s)" % (repo, type(e).__name__))
|
|
89
|
+
except Exception as e: # never fail the run because commenting failed
|
|
90
|
+
say("workspace: commenting skipped (%s)" % type(e).__name__)
|
|
91
|
+
return posted
|
package/autonomy/loki
CHANGED
|
@@ -1630,7 +1630,7 @@ show_help() {
|
|
|
1630
1630
|
echo " start [SPEC] Start a build (PRD file, GitHub/GitLab/Jira/Azure issue, or no arg)"
|
|
1631
1631
|
echo " plan <PRD-file> Dry-run analysis: complexity, cost, execution plan"
|
|
1632
1632
|
echo " backlog <owner/repo> --all|--label X|--issues N,N Run every matching open issue, N in parallel"
|
|
1633
|
-
echo " workspace list | run <name> <ref> Multi-repo runs from loki.yaml workspaces (LOKI_WORKSPACES=
|
|
1633
|
+
echo " workspace list | run <name> <ref> Multi-repo runs from loki.yaml workspaces (LOKI_WORKSPACES=0 disables)"
|
|
1634
1634
|
echo " quickstart [idea] Guided first build: setup, idea, template, plan, go"
|
|
1635
1635
|
echo ""
|
|
1636
1636
|
echo "Session:"
|
|
@@ -1783,7 +1783,7 @@ show_help() {
|
|
|
1783
1783
|
echo " syslog telemetry template test tour trigger trust trust-metrics"
|
|
1784
1784
|
echo " ultracode update verify version voice watch watchdog web welcome why"
|
|
1785
1785
|
echo " wiki worktree (wt)"
|
|
1786
|
-
echo " control
|
|
1786
|
+
echo " control"
|
|
1787
1787
|
echo ""
|
|
1788
1788
|
echo "Any command: loki <command> --help"
|
|
1789
1789
|
echo ""
|
|
@@ -1897,7 +1897,74 @@ _loki_dashboard_port_is_ours() {
|
|
|
1897
1897
|
return 1
|
|
1898
1898
|
}
|
|
1899
1899
|
|
|
1900
|
+
# C3: the Control Plane is the default UI. Prints a live loopback URL from
|
|
1901
|
+
# ~/.loki/control/instance.json (pid alive, http loopback only, /health says
|
|
1902
|
+
# service=loki-control), or starts `loki control serve` detached and waits for
|
|
1903
|
+
# it to publish one. Returns 1 when the caller should fall back to the old
|
|
1904
|
+
# dashboard (LOKI_CONTROL_DEFAULT=0, no bun, no python3, server did not start).
|
|
1905
|
+
_loki_control_live_url() {
|
|
1906
|
+
local inst="${HOME}/.loki/control/instance.json" fields pid url health
|
|
1907
|
+
[ -f "$inst" ] || return 1
|
|
1908
|
+
fields=$(python3 - "$inst" 2>/dev/null <<'PY'
|
|
1909
|
+
import json, sys, urllib.parse
|
|
1910
|
+
d = json.load(open(sys.argv[1]))
|
|
1911
|
+
pid, url = d.get("pid"), d.get("url")
|
|
1912
|
+
if not isinstance(pid, int) or isinstance(pid, bool) or not isinstance(url, str):
|
|
1913
|
+
sys.exit(1)
|
|
1914
|
+
u = urllib.parse.urlparse(url)
|
|
1915
|
+
if u.scheme != "http" or u.hostname not in ("127.0.0.1", "localhost", "::1"):
|
|
1916
|
+
sys.exit(1)
|
|
1917
|
+
if u.path not in ("", "/") or u.query or u.fragment or u.params or "@" in u.netloc or "?" in url or "#" in url:
|
|
1918
|
+
sys.exit(1)
|
|
1919
|
+
print(pid, url.rstrip("/"))
|
|
1920
|
+
PY
|
|
1921
|
+
) || return 1
|
|
1922
|
+
pid="${fields%% *}"; url="${fields#* }"
|
|
1923
|
+
kill -0 "$pid" 2>/dev/null || return 1
|
|
1924
|
+
health=$(curl -g -s --max-time 2 "${url}/health" 2>/dev/null) || return 1
|
|
1925
|
+
case "$health" in *'"service"'*'"loki-control"'*) ;; *) return 1 ;; esac
|
|
1926
|
+
printf '%s\n' "$url"
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
_loki_control_ui() {
|
|
1930
|
+
[ "${LOKI_CONTROL_DEFAULT:-1}" = "0" ] && return 1
|
|
1931
|
+
local url="" open_it=1 arg i
|
|
1932
|
+
for arg in "$@"; do [ "$arg" = "--no-open" ] && open_it=0; done
|
|
1933
|
+
if [ "${LOKI_HEADLESS:-0}" = "1" ] || [ "${LOKI_NO_BROWSER:-0}" = "1" ]; then open_it=0; fi
|
|
1934
|
+
command -v python3 >/dev/null 2>&1 || return 1
|
|
1935
|
+
url=$(_loki_control_live_url) || url=""
|
|
1936
|
+
if [ -z "$url" ]; then
|
|
1937
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
1938
|
+
echo "loki: the Control Plane needs bun (https://bun.sh); using the classic dashboard." >&2
|
|
1939
|
+
return 1
|
|
1940
|
+
fi
|
|
1941
|
+
local cli="$SKILL_DIR/loki-ts/dist/loki.js" cdir="${HOME}/.loki/control" spid
|
|
1942
|
+
[ -f "$cli" ] || cli="$SKILL_DIR/loki-ts/src/cli.ts"
|
|
1943
|
+
mkdir -p "$cdir" && chmod 700 "$cdir" 2>/dev/null || true
|
|
1944
|
+
LOKI_CONTROL=1 LOKI_NO_BROWSER=1 nohup bun "$cli" control serve >"$cdir/serve.log" 2>&1 </dev/null &
|
|
1945
|
+
spid=$!
|
|
1946
|
+
echo "$spid" > "$cdir/serve.pid" 2>/dev/null || true
|
|
1947
|
+
for i in $(seq 1 100); do
|
|
1948
|
+
url=$(_loki_control_live_url) && break
|
|
1949
|
+
url=""
|
|
1950
|
+
kill -0 "$spid" 2>/dev/null || break
|
|
1951
|
+
sleep 0.1
|
|
1952
|
+
done
|
|
1953
|
+
if [ -z "$url" ]; then
|
|
1954
|
+
echo "loki: the Control Plane did not start (see $cdir/serve.log); using the classic dashboard." >&2
|
|
1955
|
+
return 1
|
|
1956
|
+
fi
|
|
1957
|
+
fi
|
|
1958
|
+
if [ "$open_it" = "1" ]; then
|
|
1959
|
+
loki_open_url "$url" || echo "$url"
|
|
1960
|
+
else
|
|
1961
|
+
echo "$url"
|
|
1962
|
+
fi
|
|
1963
|
+
return 0
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1900
1966
|
cmd_ui() {
|
|
1967
|
+
_loki_control_ui "$@" && return 0
|
|
1901
1968
|
local port="" pid=""
|
|
1902
1969
|
if [ -f "$DASHBOARD_PID_FILE" ]; then
|
|
1903
1970
|
pid=$(cat "$DASHBOARD_PID_FILE" 2>/dev/null)
|
|
@@ -7325,16 +7392,15 @@ _loki_dashboard_bind_is_safe() {
|
|
|
7325
7392
|
cmd_dashboard() {
|
|
7326
7393
|
local subcommand="${1:-}"
|
|
7327
7394
|
|
|
7328
|
-
#
|
|
7329
|
-
#
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
""
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
|
|
7337
|
-
fi
|
|
7395
|
+
# The Control Plane is the default UI; LOKI_CONTROL_DEFAULT=0 restores the classic dashboard.
|
|
7396
|
+
# Only the bare forms are taken over; `start --port ...` and friends keep the classic server.
|
|
7397
|
+
case "$subcommand" in
|
|
7398
|
+
""|start|open)
|
|
7399
|
+
if [ $# -le 1 ] || { [ $# -eq 2 ] && [ "$2" = "--no-open" ]; }; then
|
|
7400
|
+
_loki_control_ui "${@:2}" && return 0
|
|
7401
|
+
fi
|
|
7402
|
+
;;
|
|
7403
|
+
esac
|
|
7338
7404
|
|
|
7339
7405
|
# If no subcommand provided, show help (backward compatible with old behavior)
|
|
7340
7406
|
if [ -z "$subcommand" ]; then
|
|
@@ -12921,10 +12987,10 @@ cmd_config_schema() {
|
|
|
12921
12987
|
loki_config_generate_schema
|
|
12922
12988
|
}
|
|
12923
12989
|
|
|
12924
|
-
# D65: multi-repo workspaces (D51 Phase B).
|
|
12990
|
+
# D65: multi-repo workspaces (D51 Phase B). On by default; LOKI_WORKSPACES=0 disables.
|
|
12925
12991
|
cmd_workspace() {
|
|
12926
|
-
if [ "${LOKI_WORKSPACES:-}"
|
|
12927
|
-
echo -e "${RED}loki workspace is disabled.${NC}
|
|
12992
|
+
if [ "${LOKI_WORKSPACES:-}" = "0" ]; then
|
|
12993
|
+
echo -e "${RED}loki workspace is disabled.${NC} Unset LOKI_WORKSPACES (it is set to 0)." >&2
|
|
12928
12994
|
return 2
|
|
12929
12995
|
fi
|
|
12930
12996
|
command -v python3 >/dev/null 2>&1 || { echo -e "${RED}loki workspace: python3 is required${NC}" >&2; return 2; }
|
|
@@ -18714,10 +18780,12 @@ with open(manifest_path, 'w') as f:
|
|
|
18714
18780
|
# usable.
|
|
18715
18781
|
# ---------------------------------------------------------------------------
|
|
18716
18782
|
cmd_verify() {
|
|
18717
|
-
# D48: --pubkey verifies a receipt against a given public key (v10 engine, no ~/.loki needed).
|
|
18783
|
+
# D48: --pubkey FILE or --pubkey=FILE (any position) verifies a receipt against a given public key (v10 engine, no ~/.loki needed).
|
|
18718
18784
|
local _pk_arg
|
|
18719
18785
|
for _pk_arg in "$@"; do
|
|
18720
|
-
|
|
18786
|
+
case "$_pk_arg" in
|
|
18787
|
+
--pubkey|--pubkey=*) LOKI_ENGINE=v10 exec "$_LOKI_SCRIPT_DIR/../bin/loki" verify "$@" ;;
|
|
18788
|
+
esac
|
|
18721
18789
|
done
|
|
18722
18790
|
# --fast: deterministic checks only, in milliseconds. Measured on this repo
|
|
18723
18791
|
# (1,932 tracked files): 298 ms cold, 87 ms warm, 19 ms diff-scoped, against
|
package/bin/loki
CHANGED
|
@@ -354,12 +354,12 @@ case "${LOKI_ENGINE:-$_loki_engine_default}" in
|
|
|
354
354
|
# `loki <ref>`; any other start call (PRD file, flags first, one word) stays legacy.
|
|
355
355
|
start)
|
|
356
356
|
case "${2:-}" in
|
|
357
|
-
*/*\#[0-9]*|http*://*/issues/*|http*://*/browse/*|*" "*) _e10=1; _e10_args=("${@:2}") ;;
|
|
357
|
+
*/*\#[0-9]*|http*://*/issues/*|http*://*/browse/*|jira:*|linear:*|https://linear.app/*/issue/*|*" "*) _e10=1; _e10_args=("${@:2}") ;;
|
|
358
358
|
*) _e10=0 ;;
|
|
359
359
|
esac
|
|
360
360
|
;;
|
|
361
361
|
# http*://*/issues/* also covers GitLab's /-/issues/ URLs.
|
|
362
|
-
*/*\#[0-9]*|http*://*/issues/*|http*://*/browse/*) _e10=1 ;;
|
|
362
|
+
*/*\#[0-9]*|http*://*/issues/*|http*://*/browse/*|jira:*|linear:*|https://linear.app/*/issue/*) _e10=1 ;;
|
|
363
363
|
*" "*) _e10=1 ;;
|
|
364
364
|
# ponytail: a one-word argument stays on the legacy CLI. The telemetry
|
|
365
365
|
# allowlist is not a command registry (estimate, intent, outcomes are
|
|
@@ -567,7 +567,7 @@ fi
|
|
|
567
567
|
# Two-token routes (provider show/list, memory list/index) match on the first
|
|
568
568
|
# token only; the Bun dispatcher handles subcommand routing internally.
|
|
569
569
|
case "${1:-}" in
|
|
570
|
-
version|--version|-v|status|stats|doctor|provider|memory|rollback|internal|kpis|trust|proof|receipt|wiki|crash|control|contract)
|
|
570
|
+
version|--version|-v|status|stats|doctor|provider|memory|rollback|internal|kpis|trust|proof|receipt|wiki|crash|control|contract|slack|answer)
|
|
571
571
|
# v7.5.2: rollback added (wires loki-ts/src/commands/rollback.ts).
|
|
572
572
|
# v7.5.3: internal added for autonomy/run.sh phase1-hooks calls.
|
|
573
573
|
# v7.5.28: kpis added (Phase K MVP: read-only KPI snapshot).
|
package/completions/_loki
CHANGED
|
@@ -206,6 +206,7 @@ function _loki_commands {
|
|
|
206
206
|
'crash:Crash report management'
|
|
207
207
|
'otel:OpenTelemetry management'
|
|
208
208
|
'wiki:Wiki commands'
|
|
209
|
+
'answer:Resume a BLOCKED run with an answer'
|
|
209
210
|
'control:Control plane (preview, LOKI_CONTROL=1)'
|
|
210
211
|
'workspace:Multi-project workspaces (preview, LOKI_WORKSPACES=1)'
|
|
211
212
|
'backlog:Run every matching open issue, N in parallel'
|
package/completions/loki.bash
CHANGED
|
@@ -5,7 +5,7 @@ _loki_completion() {
|
|
|
5
5
|
_init_completion || return
|
|
6
6
|
|
|
7
7
|
# Main subcommands (must match autonomy/loki main case statement)
|
|
8
|
-
local main_commands="start backlog quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue intent config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt outcomes explain plan report cost estimate kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash control workspace open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help keys"
|
|
8
|
+
local main_commands="start backlog quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue intent config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt outcomes explain plan report cost estimate kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash control workspace open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help keys answer"
|
|
9
9
|
|
|
10
10
|
# 1. If we are on the first argument (subcommand)
|
|
11
11
|
if [[ $cword -eq 1 ]]; then
|
package/dashboard/__init__.py
CHANGED
package/dashboard/api_start.py
CHANGED
|
@@ -21,6 +21,7 @@ import os
|
|
|
21
21
|
import re
|
|
22
22
|
import shutil
|
|
23
23
|
import subprocess
|
|
24
|
+
import sys
|
|
24
25
|
import threading
|
|
25
26
|
import time
|
|
26
27
|
import urllib.error
|
|
@@ -156,6 +157,25 @@ def _project_dir() -> str:
|
|
|
156
157
|
return os.environ.get("LOKI_PROJECT_DIR") or os.getcwd()
|
|
157
158
|
|
|
158
159
|
|
|
160
|
+
def _autonomy_lib():
|
|
161
|
+
lib = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "autonomy", "lib")
|
|
162
|
+
if lib not in sys.path:
|
|
163
|
+
sys.path.insert(0, lib)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _workspaces_enabled() -> bool:
|
|
167
|
+
"""Same rule as `loki workspace`: on by default, LOKI_WORKSPACES=0 disables."""
|
|
168
|
+
_autonomy_lib()
|
|
169
|
+
import workspace
|
|
170
|
+
return workspace.enabled()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _worktree_prep():
|
|
174
|
+
_autonomy_lib()
|
|
175
|
+
import worktree_prep
|
|
176
|
+
return worktree_prep
|
|
177
|
+
|
|
178
|
+
|
|
159
179
|
def _prepare_workdir(repo: str, number: int, token: str) -> str:
|
|
160
180
|
"""One git worktree per issue, off the repo's default branch."""
|
|
161
181
|
env = dict(os.environ, GH_TOKEN=token, GIT_TERMINAL_PROMPT="0")
|
|
@@ -180,7 +200,12 @@ def _prepare_workdir(repo: str, number: int, token: str) -> str:
|
|
|
180
200
|
stamp = time.strftime("%Y%m%d%H%M%S")
|
|
181
201
|
wt = _home() / "worktrees" / slug / ("issue-%d-%s" % (number, stamp))
|
|
182
202
|
wt.parent.mkdir(parents=True, exist_ok=True)
|
|
183
|
-
|
|
203
|
+
branch = "loki/backlog-%d-%s" % (number, stamp)
|
|
204
|
+
if _workspaces_enabled():
|
|
205
|
+
# Shared worktree prep (D51-B06): same per-base lock and dep copy as backlog and workspace runs.
|
|
206
|
+
_worktree_prep().prepare_worktree(base, str(wt), branch, base=start)
|
|
207
|
+
return str(wt)
|
|
208
|
+
_git("worktree", "add", "-b", branch, str(wt), start, cwd=base)
|
|
184
209
|
return str(wt)
|
|
185
210
|
|
|
186
211
|
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v10.6.
|
|
5
|
+
**Version:** v10.6.9
|
|
6
6
|
|
|
7
7
|
**Engine note:** `loki "<task>"`, `loki owner/repo#N` and `loki quick` run the Loki 10 engine. `loki start` still routes to the legacy engine, which is being removed (planned work resumes 2026-10-07); prefer `loki owner/repo#N`. See [docs/v10/GUIDE.md](v10/GUIDE.md).
|
|
8
8
|
|
package/docs/WORKSPACES.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Workspaces: one issue across several repos
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
On by default. Set `LOKI_WORKSPACES=0` to disable. Design: `docs/v10/D51-PHASE-B.md`.
|
|
4
4
|
|
|
5
5
|
Define a group of repos once in `loki.yaml`:
|
|
6
6
|
|
|
@@ -13,17 +13,24 @@ workspaces:
|
|
|
13
13
|
integration: {command: "make e2e", timeout_s: 900}
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
Repos with no pending `after` run in parallel, at most `concurrency` at a time (default 2; set `LOKI_WORKSPACE_CONCURRENCY` to change it).
|
|
17
|
+
|
|
16
18
|
Then:
|
|
17
19
|
|
|
18
20
|
```bash
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
loki workspace list
|
|
22
|
+
loki workspace show shop
|
|
23
|
+
loki workspace run shop acme/api#12
|
|
24
|
+
loki workspace status # every recorded run, one row per repo
|
|
25
|
+
loki workspace status <run-id>
|
|
21
26
|
```
|
|
22
27
|
|
|
23
28
|
- Each repo gets its own git worktree on a `loki/ws-<run-id>-N` branch and its own engine run. Your checkouts are not touched.
|
|
24
29
|
- `after` orders repos: a repo starts once its predecessors succeed. If a predecessor fails, its dependents are SKIPPED.
|
|
25
|
-
- A failing repo does not stop the others (continue and report). The command exits 0 only when every repo succeeded and the integration step did not fail
|
|
30
|
+
- A failing repo does not stop the others (continue and report). The command exits 0 only when every repo succeeded and the integration step did not fail. It exits 3 when the only non-ok outcomes are budget stops (a child exited 3, or a dependent was skipped behind one), and 1 for any other failure. Ctrl-C (SIGINT) stops the recorded child process groups like SIGTERM.
|
|
26
31
|
- The repo that owns the issue ref gets the ref; other repos get a task text naming the ref and the sibling worktree paths as read-only context.
|
|
27
32
|
- After all repos finish, the `integration` command runs once from the run directory with `LOKI_WS_DIR_<OWNER>_<REPO>` pointing at each worktree. A timeout counts as failed. With no integration configured the status is `not_configured`.
|
|
28
33
|
- Evidence is written to `.loki/workspaces/<name>/<run-id>/integration.json`: head SHA per repo, exit code, log sha256. It is not part of the Seal.
|
|
29
34
|
- Without `path`, a repo is cloned to `~/.loki/repos/owner__name`.
|
|
35
|
+
- After the integration step, a PR comment is posted on each repo PR that exists for its run branch, showing the integration status (PASSED, FAILED, TIMEOUT or NOT CONFIGURED) and every head SHA. It uses your own `gh` login or GH_TOKEN, skips silently when there is no PR, never fails the run, and is disabled with `LOKI_WORKSPACE_COMMENT=0`.
|
|
36
|
+
- Backlog (`loki backlog`) and dashboard starts now share the workspace worktree prep: a per-repo lock, a clean start point and copied dependencies. `LOKI_WORKSPACES=0` restores the previous worktree creation (D51-B05, D51-B06).
|
|
@@ -4,14 +4,17 @@ The Control Plane (run ingest API plus UI) ships as its own small image built
|
|
|
4
4
|
from `Dockerfile.control-plane`. It serves on port 47821, stores its SQLite
|
|
5
5
|
database under `/data`, runs as a non-root user and contains no secrets.
|
|
6
6
|
|
|
7
|
-
The
|
|
8
|
-
|
|
7
|
+
The image binds 0.0.0.0, so `LOKI_CONTROL_TOKEN` is required at run time. Without
|
|
8
|
+
it the server exits 2 before listening. Every `/v1/*` call then needs
|
|
9
|
+
`Authorization: Bearer <token>`. The token is never baked into the image. Set
|
|
10
|
+
`LOKI_CONTROL_ALLOW_INSECURE_BIND=1` only on a trusted private network; the
|
|
11
|
+
server prints a warning when you do.
|
|
9
12
|
|
|
10
13
|
## Docker
|
|
11
14
|
|
|
12
15
|
```bash
|
|
13
16
|
docker build -f Dockerfile.control-plane -t loki-control-plane .
|
|
14
|
-
docker run -d -p 47821:47821 -v loki-control:/data loki-control-plane
|
|
17
|
+
docker run -d -p 47821:47821 -e LOKI_CONTROL_TOKEN="$(openssl rand -hex 32)" -v loki-control:/data loki-control-plane
|
|
15
18
|
LOKI_CONTROL_URL=http://localhost:47821 loki control status
|
|
16
19
|
```
|
|
17
20
|
|
|
@@ -42,4 +45,6 @@ EFS volume, secrets from Secrets Manager ARNs).
|
|
|
42
45
|
| --- | --- | --- |
|
|
43
46
|
| `PORT` | 47821 | listen port |
|
|
44
47
|
| `LOKI_CONTROL_HOST` | 0.0.0.0 | bind address (outside the image: 127.0.0.1) |
|
|
48
|
+
| `LOKI_CONTROL_TOKEN` | none (required) | bearer token for `/v1/*`; required on a non-loopback bind |
|
|
49
|
+
| `LOKI_CONTROL_ALLOW_INSECURE_BIND` | unset | `1` accepts a tokenless non-loopback bind (not recommended) |
|
|
45
50
|
| `LOKI_CONTROL_DB` | /data/control.db | SQLite path |
|
package/docs/dashboard-guide.md
CHANGED
|
@@ -482,3 +482,7 @@ Useful for:
|
|
|
482
482
|
- [Agent Types](../references/agent-types.md) - Agent role definitions
|
|
483
483
|
- [Quality Control](../references/quality-control.md) - Quality gates system
|
|
484
484
|
- [Memory System](../references/memory-system.md) - Memory architecture
|
|
485
|
+
|
|
486
|
+
## Control Plane by default
|
|
487
|
+
|
|
488
|
+
Bare `loki` and `loki dashboard` (also `start` or `open` with no other flags) open the Control Plane. If `~/.loki/control/instance.json` names a live loopback server whose `/health` reports `service=loki-control`, that URL is reused; otherwise `loki control serve` is started detached and its PID is recorded in `~/.loki/control/serve.pid`. With `LOKI_HEADLESS=1`, `LOKI_NO_BROWSER=1` or `--no-open` the URL is printed and no browser is opened. Without bun, Loki prints one line naming bun and uses the classic dashboard. Set `LOKI_CONTROL_DEFAULT=0` to always use the classic dashboard. Multi-repo `loki workspace` is on by default; `LOKI_WORKSPACES=0` disables it.
|
package/docs/exit-codes.md
CHANGED
|
@@ -104,6 +104,7 @@ never the local JWKS. Exits: 0 VERIFIED, 1 TAMPERED, 2 UNCHECKED (kid does not
|
|
|
104
104
|
match the key, or the key file is unusable), 3 UNSIGNED (a receipt with no
|
|
105
105
|
signature is refused even with `--allow-unsigned`, because the caller asked for a
|
|
106
106
|
signature check), 4 run outcome not verified (checked before UNSIGNED).
|
|
107
|
+
`--pubkey` may come before or after the run-id and may be written `--pubkey=FILE`; `--pubkey` with no file or an empty `--pubkey=`, a repeated `--pubkey`, an unknown option, or more than one run-id exits 2.
|
|
107
108
|
|
|
108
109
|
An early draft spec listed `1=BLOCKED, 2=CONCERNS`. That ordering was rejected:
|
|
109
110
|
it is not used anywhere, it has no consumers, and it inverts the
|
package/docs/slack.md
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
# Two-way Slack
|
|
1
|
+
# Two-way Slack
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
On by default. Set `LOKI_SLACK_INBOUND=0` to disable the inbound handler (`loki slack serve` then exits 2).
|
|
4
|
+
|
|
5
|
+
Without `SLACK_BOT_TOKEN` or `SLACK_SIGNING_SECRET`, `loki slack serve` prints one line naming both and exits 2 without binding a port.
|
|
4
6
|
|
|
5
7
|
```bash
|
|
6
|
-
export LOKI_SLACK_INBOUND=1
|
|
7
8
|
export SLACK_BOT_TOKEN=... # from env only, never stored by Loki
|
|
8
9
|
export SLACK_SIGNING_SECRET=...
|
|
9
10
|
loki slack serve --port 3000 # binds 127.0.0.1 unless --host is given
|
|
@@ -13,10 +14,17 @@ Point the Slack Events API request URL at this server through a tunnel or revers
|
|
|
13
14
|
|
|
14
15
|
## What it does
|
|
15
16
|
|
|
16
|
-
- `@loki <issue ref or task text>` in a channel starts a Loki 10 run (
|
|
17
|
+
- `@loki <issue ref or task text>` in a channel starts a Loki 10 run (it launches `bin/loki "<task>"`; the text must be at least two words and must not start with `-`, otherwise the bot replies asking you to describe the task, so a lone word like `reset` can never reach a legacy subcommand) and replies in the thread with the run id.
|
|
17
18
|
- When the run ends BLOCKED, the question is posted in the same thread. A reply in that thread starts a follow-up run with your answer added to the original task (v10 has no in-place resume, BLOCKED is terminal).
|
|
19
|
+
- Runs are launched through the repo `bin/loki` with the two Slack secrets removed from the child environment and `LOKI_NO_BROWSER=1` set. A failed Slack post logs one redacted line to stderr.
|
|
18
20
|
- Every request is verified with the Slack signature (HMAC sha256 over `v0:timestamp:body`, constant-time compare, timestamps older than 5 minutes are rejected). Retried events are deduplicated.
|
|
19
21
|
|
|
22
|
+
## Who can start a run
|
|
23
|
+
|
|
24
|
+
Set `LOKI_SLACK_ALLOWED_USERS` to a comma-separated list of Slack user IDs (for example `U012ABC,U034DEF`). A mention or a BLOCKED-thread answer from any other user is rejected with a short thread reply and nothing is spawned. When it is unset, anyone who can mention the bot can start a run, and `loki slack serve` prints one startup line saying so.
|
|
25
|
+
|
|
26
|
+
Limits: task text over 64 KB is rejected with "task too long" and no run starts. If a run cannot be launched, the thread gets a generic message and the detail (which may include local paths) is logged to stderr only. `--port` must be a whole number (an empty value exits 2).
|
|
27
|
+
|
|
20
28
|
## App manifest scopes
|
|
21
29
|
|
|
22
30
|
Bot token scopes: `app_mentions:read`, `chat:write`, `channels:history` (and `groups:history` for private channels, `im:history` for DMs).
|
package/docs/trackers.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Jira and Linear intake
|
|
2
|
+
|
|
3
|
+
Loki 10 can start a run from a Jira or Linear issue. This is intake only: the issue is fetched once, before any model runs, and normalized into the same `issue.json` GitHub intake writes (with a `source` field of `jira` or `linear`). Loki does not write back to the tracker or sync status.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
loki jira:PROJ-123
|
|
7
|
+
loki linear:ENG-42
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Credentials (environment only)
|
|
11
|
+
|
|
12
|
+
- Jira: `JIRA_EMAIL`, `JIRA_API_TOKEN` and `JIRA_BASE_URL` (for example `https://acme.atlassian.net`). `JIRA_BASE_URL` may be omitted when you pass an `atlassian.net` browse URL.
|
|
13
|
+
- Linear: `LINEAR_API_KEY`.
|
|
14
|
+
|
|
15
|
+
A missing variable stops the run with an error that names it. A Linear HTTP 401, or a GraphQL response with errors and no data, also names `LINEAR_API_KEY`.
|
|
16
|
+
|
|
17
|
+
## Self-hosted Jira
|
|
18
|
+
|
|
19
|
+
`<JIRA_BASE_URL>/browse/KEY` is accepted as a ref only when its origin equals the origin of `JIRA_BASE_URL`. A browse URL on any other host is not treated as a tracker ref.
|
|
20
|
+
|
|
21
|
+
## Kill switch
|
|
22
|
+
|
|
23
|
+
`LOKI_TRACKER_INTAKE=0` disables Jira and Linear refs entirely; they are then treated as ordinary text.
|
|
24
|
+
|
|
25
|
+
## Safety
|
|
26
|
+
|
|
27
|
+
Issue text is untrusted input. It is passed to the run as data and never grants tool authority.
|