loki-mode 11.69.2 → 11.70.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 +2 -2
- package/VERSION +1 -1
- package/agents/managed_registry.py +2 -1
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-REFERENCE.md +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/dev/architecture-reference.md +19 -0
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/mcp/requirements.txt +3 -2
- package/memory/managed_memory/client.py +100 -20
- package/memory/managed_memory/fakes.py +42 -11
- package/memory/managed_memory/retrieve.py +3 -3
- package/memory/managed_memory/shadow_write.py +13 -9
- package/package.json +2 -2
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/managed.py +169 -18
- package/web-app/dist/assets/{AdminPage-X_Lb_tse.js → AdminPage-BYgBOrp7.js} +1 -1
- package/web-app/dist/assets/{Avatar-DWLB-5FR.js → Avatar--XJgyp7S.js} +1 -1
- package/web-app/dist/assets/{Badge-DXWXpuGv.js → Badge-CA9ZmmNt.js} +1 -1
- package/web-app/dist/assets/{Button-D1rzroG6.js → Button-iIDr0YM0.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-BMAIYuNu.js → CockpitPage-CCXd-vNb.js} +1 -1
- package/web-app/dist/assets/{ComparePage-cmRPQPxS.js → ComparePage-DMIapaYr.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-D5duP0WF.js → ErrorBoundary-CzuiJS34.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-DJPpS89i.js → EvidenceReceiptPanel-DCM8vWSn.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-DIWbrnDC.js → GitHubIssuesPanel-DcF3DDfw.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-CnCdjZB7.js → GitHubPRsPanel-mZO41gW_.js} +1 -1
- package/web-app/dist/assets/{HomePage-BjsCEgBs.js → HomePage-BIjWzEji.js} +2 -2
- package/web-app/dist/assets/{LoginPage-DKQ30F0-.js → LoginPage-DICi5kVZ.js} +1 -1
- package/web-app/dist/assets/{MagicPage-BsHP7coH.js → MagicPage-DKsXY1db.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-C6ciVEXD.js → MetricsPage-BFg1O9ty.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-hs1rD4X8.js → NotFoundPage-DqQUk-56.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-DTMswQ9G.js → ProjectPage-DM4IM6wN.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-B8tOFK8t.js → ProjectsPage-CzGe32i1.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-CyVW4etQ.js → SettingsPage-N5VnZNhG.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-DCzecCSj.js → ShowcasePage-aRH6Ze3C.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-BZaySZXZ.js → SystemSettingsPage-CY79fI0n.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-BqhwsW27.js → TeamsPage-0knktxOM.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-CltJdd3y.js → TemplatesPage-BCa9OiVY.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-VUIl1ErR.js → TerminalOutput-ibAXZT5_.js} +1 -1
- package/web-app/dist/assets/{activity-C9S7XY1A.js → activity-p22XUeoO.js} +1 -1
- package/web-app/dist/assets/{bell-DqzDSAr9.js → bell-Bcok_OOB.js} +1 -1
- package/web-app/dist/assets/{bot-8aFIm6xW.js → bot-GTdHTaoo.js} +1 -1
- package/web-app/dist/assets/{check-NCzDlct-.js → check-D2njzm4d.js} +1 -1
- package/web-app/dist/assets/{chevron-left-zskO6MDq.js → chevron-left-DdYAWi6b.js} +1 -1
- package/web-app/dist/assets/{circle-alert-D-hlVfCN.js → circle-alert-Bz1PKYGL.js} +1 -1
- package/web-app/dist/assets/{clock-oxd-JmiY.js → clock-CKqxy6VD.js} +1 -1
- package/web-app/dist/assets/{cloud-D3hGXv86.js → cloud-CORAtv96.js} +1 -1
- package/web-app/dist/assets/{code-xml-BNnbduZh.js → code-xml-DPEMWBpp.js} +1 -1
- package/web-app/dist/assets/{database-B4HDscwC.js → database-Cn2cboI6.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-DUlUol0M.js → dollar-sign-BZq5PiyR.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-ChEYKX9D.js → file-code-corner-oZdv9GWA.js} +1 -1
- package/web-app/dist/assets/{file-plus-ap66HUzm.js → file-plus-Bf-trjX8.js} +1 -1
- package/web-app/dist/assets/{globe-oYSYd3_L.js → globe-Cz464A2c.js} +1 -1
- package/web-app/dist/assets/{hammer-DiD3zET3.js → hammer-BYnhzku7.js} +1 -1
- package/web-app/dist/assets/{index-D9CnTCxt.js → index-CeC0H88y.js} +3 -3
- package/web-app/dist/assets/{layers-DDYhqcVZ.js → layers-tiKAh05w.js} +1 -1
- package/web-app/dist/assets/{loader-circle-C6ehieO7.js → loader-circle-aqYJwV71.js} +1 -1
- package/web-app/dist/assets/{lock-Dwvyti7y.js → lock--FTx90Il.js} +1 -1
- package/web-app/dist/assets/{package-BjBFIfYf.js → package-BxjrLRVZ.js} +1 -1
- package/web-app/dist/assets/{plus-8QJuNj8E.js → plus-DhnjqUjT.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-BETfXQWk.js → refresh-cw-DGtkiauK.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-Dz_sOG-S.js → rotate-ccw-hlcCtv5T.js} +1 -1
- package/web-app/dist/assets/{scroll-text-CKh6F_pG.js → scroll-text-FWLg-h_Z.js} +1 -1
- package/web-app/dist/assets/{server-BiGpEeM5.js → server-DYonzqCF.js} +1 -1
- package/web-app/dist/assets/{shield-alert-BbYZdXVA.js → shield-alert-DlpNXb9G.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-CiatW53x.js → thumbs-up-C0StPWU8.js} +1 -1
- package/web-app/dist/assets/{trash-2-7RSs5VJI.js → trash-2-pM7Ap3BX.js} +1 -1
- package/web-app/dist/assets/{trending-down-C7R_QV_z.js → trending-down-FqnOAsjZ.js} +1 -1
- package/web-app/dist/assets/{trending-up-D-nWGnPt.js → trending-up-C3o8sYhj.js} +1 -1
- package/web-app/dist/assets/{upload-2RLfY87V.js → upload-CBvBOxsH.js} +1 -1
- package/web-app/dist/assets/{usePolling-Dh8WabdI.js → usePolling-BrHWY5Q-.js} +1 -1
- package/web-app/dist/assets/{user-DuYN8D1j.js → user-DLFfE0Si.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.70.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
|
|
|
@@ -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.70.0 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
11.
|
|
1
|
+
11.70.0
|
|
@@ -166,7 +166,7 @@ def _call_create_agent(pool_name: str) -> str:
|
|
|
166
166
|
translate to a fallback-capable flow.
|
|
167
167
|
"""
|
|
168
168
|
# Deferred import to keep the module SDK-free at import time.
|
|
169
|
-
from providers.managed import _get_client # local import of shared client
|
|
169
|
+
from providers.managed import _get_client, agent_model # local import of shared client
|
|
170
170
|
|
|
171
171
|
client = _get_client()
|
|
172
172
|
beta = getattr(client, "beta", None)
|
|
@@ -183,6 +183,7 @@ def _call_create_agent(pool_name: str) -> str:
|
|
|
183
183
|
try:
|
|
184
184
|
created = agents_ns.create(
|
|
185
185
|
name=display_name,
|
|
186
|
+
model=agent_model(),
|
|
186
187
|
system=persona,
|
|
187
188
|
metadata={"loki_pool": pool_name},
|
|
188
189
|
)
|
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.70.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/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.70.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
|
|
|
@@ -114,3 +114,22 @@ extract_learnings_from_session (autonomy/run.sh) mirrors learnings and decisions
|
|
|
114
114
|
- Lessons: every sealed run appends a receipt fact (`LOKI_AUTO_LEARN_PR`, default on) outside the receipt body; `implement.ts` injects lessons and relevant learnings inside the untrusted-data fence; `loki memory brief --task T` prints the same block.
|
|
115
115
|
|
|
116
116
|
- Wall publish (FC-VERIFIED-PR-WITHOUT-ITS-TEST): on a VERIFIED seal only, `publishWall` (`util/check_result.ts`, called from `stages/seal.ts`) makes ONE child commit (trailer `Loki-Wall-Publish: <runId>`) of the SEALED Wall bytes (`runDir/wall`, hash-checked against `wall.files[].sha256`) that verify ran and passed (`publishableWall`). Before writing, the realpath of each Wall file's parent directory must be inside the repo, and an existing file or symlink is removed first. The child commit runs with hooks and signing OFF (the default `git()` call, no `userHooks`), so a pre-commit hook cannot add files or rewrite the Wall bytes. Afterwards the commit must touch exactly the published paths and each committed blob must equal the blob hash of the sealed bytes (computed in process, never read from the worktree); on any mismatch the child is rolled back (`update-ref` HEAD to the product head plus `read-tree`), nothing is published and a NOT PROVEN note is added. The receipt binds the PRODUCT head (head_sha, tree, diff_sha256 are computed before the child commit). A re-seal binds `HEAD^` only when HEAD's message has the exact trailer line for this run, HEAD touches only paths of this run's `wall.files`, and every blob sha256 matches (`wallChild`); a forged trailer is not enough. `util/seal_drift.ts` accepts a moved HEAD after head_sha as benign only when every commit after it is such a Wall publish commit (linear, trailer line for the receipt's run_id, only `wall.files` paths, blob sha256 equal to the sealed one); any commit after head_sha that carries this run's trailer but fails that check is head drift (UNCHECKED). Later commits without the trailer stay under the TEN-GATES-16 sealed-path overlap rule. The PR body gains an "Acceptance tests" section. Commit stage and `backstopCommit` still drop Wall files. `LOKI_COMMIT_WALL=0` restores the old behavior (nothing published).
|
|
117
|
+
|
|
118
|
+
### Python anthropic SDK (RQ-105: pinned `anthropic==2.2.0`)
|
|
119
|
+
|
|
120
|
+
`mcp/requirements.txt` pins the Python SDK exactly (was `>=0.40,<1.0.0`; the resolver landed on 0.125.0). Requires Python >=3.10 (Ubuntu 24.04 image ships 3.12). The SDK runs on `httpx2`; no repo code passes `httpx` objects to it. v2.0.0 changes (API version 2026-10-21): `temperature`/`top_p`/`top_k` removed from Messages (no call site uses them), Admin API field renames (unused). Python-side call sites: `memory/managed_memory/client.py` (the only importer) and `providers/managed.py` (lazy import, `tests/managed/test_registry_lazy.py` contract unchanged).
|
|
121
|
+
|
|
122
|
+
| Capability (SDK release notes 0.40 to 2.2.0) | Status | Evidence / reason |
|
|
123
|
+
| --- | --- | --- |
|
|
124
|
+
| Client construction `anthropic.Anthropic(api_key, timeout, default_headers)` | WIRED | `memory/managed_memory/client.py:76`, `providers/managed.py:223`; unchanged in 2.x |
|
|
125
|
+
| Managed Agents memory stores (`beta.memory_stores`) | WIRED | `memory/managed_memory/client.py:127`; `create()` has no `scope` in 2.x, scope now sent as `metadata` |
|
|
126
|
+
| Memories nested at `beta.memory_stores.memories` (`memory_store_id`) | WIRED | `memory/managed_memory/client.py:96`; legacy `beta.memories` kept for fakes/preview |
|
|
127
|
+
| Memory paths | WIRED | 2.x paths are absolute and unique per store: `client.py:157` `_abs` plus leading "/" logical paths (`shadow_write.py` verdict/pattern/episode, `retrieve.py` list prefixes); wire test `RealSdkWireTests` asserts the request path |
|
|
128
|
+
| Memories list view and paging | WIRED | `client.py:242` `memories_list` sends `view="full"` (default basic view returns null content), follows `next_page` (20 per page) and drops `memory_prefix` rows; wire test pages 45 rows over 3 requests |
|
|
129
|
+
| Memory create precondition | NOT ADOPTED | 2.x `memories.create` has no precondition; an existing path answers 409 (`client.py:161`, status kept visible) |
|
|
130
|
+
| Memory update with `content_sha256` precondition | WIRED | `client.py:198` `memory_update`; the shadow-write 409 path (`shadow_write.py:197`) re-reads, merges, then updates with the hash of the content currently stored, never the hash of the new content |
|
|
131
|
+
| Memory versions (`memory_stores.memory_versions`) | NOT ADOPTED | `redact` path still reads the legacy shape; separate slice, behind ManagedDisabled fallback |
|
|
132
|
+
| Managed Agents sessions, environments, multiagent coordinator | WIRED | `providers/managed.py` `_RealMultiagentSession.run`: coordinator agent (`multiagent={"type":"coordinator"}`) + cached environment, then `sessions.create(agent=, environment_id=, initial_events=)`; ids cached in `.loki/managed/agent_ids.json`; contract test binds kwargs to the real SDK signatures. Fails safe (ManagedUnavailable) on timeout, terminated status, or any reply set that is not exactly one reply per roster agent; replies are the coordinator's `agent.thread_message_received` events, attributed via `from_session_thread_id` and `sessions.threads.list` to thread.agent.id (agent threads only); the session is ready only when every roster agent has replied; coordinator cache key includes the model, resolved from the model catalog (non-Claude env overrides ignored). WIRED, live reply shape NOT PROVEN (no key) |
|
|
133
|
+
| Vaults, deployments, dreams, webhooks | NOT ADOPTED | no Loki consumer; research preview |
|
|
134
|
+
| Tool runner (`beta.messages.tool_runner`), compaction, structured outputs, adaptive thinking, advisor tool, citations, files API, web search / code execution, prompt cache TTL | NOT ADOPTED | Loki drives models through provider CLIs and the TS Agent SDK; the Python SDK is only the managed-agents bridge |
|
|
135
|
+
| Bedrock / Vertex clients | NOT ADOPTED | no use |
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var ch0=Object.create;var{getPrototypeOf:dh0,defineProperty:pK,getOwnPropertyNames:Mn,getOwnPropertyDescriptor:lh0}=Object,On=Object.prototype.hasOwnProperty;function An(Z){return this[Z]}var ah0,ih0,oh0=(Z,$,J)=>{var X=Z!=null&&typeof Z==="object";if(X){var Y=$?ah0??=new WeakMap:ih0??=new WeakMap,Q=Y.get(Z);if(Q)return Q}J=Z!=null?ch0(dh0(Z)):{};let z=$||!Z||!Z.__esModule?pK(J,"default",{value:Z,enumerable:!0}):J;for(let W of Mn(Z))if(!On.call(z,W))pK(z,W,{get:An.bind(Z,W),enumerable:!0});if(X)Y.set(Z,z);return z},nh0=(Z)=>{var $=(Nn??=new WeakMap).get(Z),J;if($)return $;if($=pK({},"__esModule",{value:!0}),Z&&typeof Z==="object"||typeof Z==="function"){for(var X of Mn(Z))if(!On.call($,X))pK($,X,{get:An.bind(Z,X),enumerable:!(J=lh0(Z,X))||J.enumerable})}return Nn.set(Z,$),$},Nn,qM=(Z,$)=>()=>($||Z(($={exports:{}}).exports,$),$.exports);var sh0=(Z)=>Z;function rh0(Z,$){this[Z]=sh0.bind(null,$)}var j0=(Z,$)=>{for(var J in $)pK(Z,J,{get:$[J],enumerable:!0,configurable:!0,set:rh0.bind($,J)})};var S=(Z,$)=>()=>(Z&&($=Z(Z=0)),$);var e9=import.meta.require;var jn={};j0(jn,{lokiDirFor:()=>BM,lokiDir:()=>D0,homeLokiDir:()=>T2,findRepoRootForVersion:()=>cK,REPO_ROOT:()=>F0});import{resolve as wZ,dirname as aE}from"path";import{fileURLToPath as th0}from"url";import{existsSync as UM}from"fs";import{homedir as eh0}from"os";function Zf0(){let Z=Ln;for(let $=0;$<6;$++){if(UM(wZ(Z,"VERSION"))&&UM(wZ(Z,"autonomy/run.sh")))return Z;let J=aE(Z);if(J===Z)break;Z=J}return wZ(Ln,"..","..","..")}function cK(Z){let $=Z;for(let J=0;J<6;J++){if(UM(wZ($,"VERSION"))&&UM(wZ($,"autonomy/run.sh")))return $;let X=aE($);if(X===$)break;$=X}return wZ(Z,"..","..","..")}function BM(Z){return process.env.LOKI_DIR??wZ(Z,".loki")}function D0(){return BM(process.cwd())}function T2(){return wZ(eh0(),".loki")}var Ln,F0;var s0=S(()=>{Ln=aE(th0(import.meta.url));F0=Zf0()});import{readFileSync as $f0}from"fs";import{resolve as Jf0,dirname as Xf0}from"path";import{fileURLToPath as Yf0}from"url";function O6(){if(f1!==null)return f1;let Z="11.
|
|
2
|
+
var ch0=Object.create;var{getPrototypeOf:dh0,defineProperty:pK,getOwnPropertyNames:Mn,getOwnPropertyDescriptor:lh0}=Object,On=Object.prototype.hasOwnProperty;function An(Z){return this[Z]}var ah0,ih0,oh0=(Z,$,J)=>{var X=Z!=null&&typeof Z==="object";if(X){var Y=$?ah0??=new WeakMap:ih0??=new WeakMap,Q=Y.get(Z);if(Q)return Q}J=Z!=null?ch0(dh0(Z)):{};let z=$||!Z||!Z.__esModule?pK(J,"default",{value:Z,enumerable:!0}):J;for(let W of Mn(Z))if(!On.call(z,W))pK(z,W,{get:An.bind(Z,W),enumerable:!0});if(X)Y.set(Z,z);return z},nh0=(Z)=>{var $=(Nn??=new WeakMap).get(Z),J;if($)return $;if($=pK({},"__esModule",{value:!0}),Z&&typeof Z==="object"||typeof Z==="function"){for(var X of Mn(Z))if(!On.call($,X))pK($,X,{get:An.bind(Z,X),enumerable:!(J=lh0(Z,X))||J.enumerable})}return Nn.set(Z,$),$},Nn,qM=(Z,$)=>()=>($||Z(($={exports:{}}).exports,$),$.exports);var sh0=(Z)=>Z;function rh0(Z,$){this[Z]=sh0.bind(null,$)}var j0=(Z,$)=>{for(var J in $)pK(Z,J,{get:$[J],enumerable:!0,configurable:!0,set:rh0.bind($,J)})};var S=(Z,$)=>()=>(Z&&($=Z(Z=0)),$);var e9=import.meta.require;var jn={};j0(jn,{lokiDirFor:()=>BM,lokiDir:()=>D0,homeLokiDir:()=>T2,findRepoRootForVersion:()=>cK,REPO_ROOT:()=>F0});import{resolve as wZ,dirname as aE}from"path";import{fileURLToPath as th0}from"url";import{existsSync as UM}from"fs";import{homedir as eh0}from"os";function Zf0(){let Z=Ln;for(let $=0;$<6;$++){if(UM(wZ(Z,"VERSION"))&&UM(wZ(Z,"autonomy/run.sh")))return Z;let J=aE(Z);if(J===Z)break;Z=J}return wZ(Ln,"..","..","..")}function cK(Z){let $=Z;for(let J=0;J<6;J++){if(UM(wZ($,"VERSION"))&&UM(wZ($,"autonomy/run.sh")))return $;let X=aE($);if(X===$)break;$=X}return wZ(Z,"..","..","..")}function BM(Z){return process.env.LOKI_DIR??wZ(Z,".loki")}function D0(){return BM(process.cwd())}function T2(){return wZ(eh0(),".loki")}var Ln,F0;var s0=S(()=>{Ln=aE(th0(import.meta.url));F0=Zf0()});import{readFileSync as $f0}from"fs";import{resolve as Jf0,dirname as Xf0}from"path";import{fileURLToPath as Yf0}from"url";function O6(){if(f1!==null)return f1;let Z="11.70.0";if(typeof Z==="string"&&Z.length>0)return f1=Z,f1;try{let $=Xf0(Yf0(import.meta.url)),J=cK($);f1=$f0(Jf0(J,"VERSION"),"utf-8").trim()}catch{f1="unknown"}return f1}var f1=null;var _Z=S(()=>{s0()});import{mkdirSync as oE,readFileSync as NM,realpathSync as Qf0,statSync as zf0,writeFileSync as nE}from"fs";import{dirname as MY,join as iE,resolve as Cn}from"path";function Hf0(){return Cn(T2(),"cache","update-check.json")}function xZ(Z){let $=/^(\d+)\.(\d+)\.(\d+)$/.exec(Z.trim());if(!$)return null;return[Number($[1]),Number($[2]),Number($[3])]}function dK(Z,$){let J=xZ(Z),X=xZ($);if(!J||!X)return!1;let[Y,Q,z]=J,[W,K,V]=X;if(Y!==W)return Y>W;if(Q!==K)return Q>K;return z>V}function Fn(){let Z=(process.env.LOKI_NO_UPDATE_CHECK??"").trim().toLowerCase();if(Z==="1"||Z==="true"||Z==="yes")return!0;if(process.env.CI)return!0;if(!process.stdout.isTTY)return!0;return!1}function In(Z){try{let $=NM(Z,"utf-8"),J=JSON.parse($);if(typeof J.checkedAt==="number"&&typeof J.latest==="string"&&xZ(J.latest)!==null)return{checkedAt:J.checkedAt,latest:J.latest}}catch{}return null}function qf0(Z){try{let $=JSON.parse(NM(Z,"utf-8")).failedAt;if(typeof $==="number")return $}catch{}return null}function Uf0(Z,$){let J=In(Z);try{oE(MY(Z),{recursive:!0}),nE(Z,JSON.stringify({...J??{},failedAt:$}),"utf-8")}catch{}}function Bf0(Z,$){try{oE(MY(Z),{recursive:!0}),nE(Z,JSON.stringify($),"utf-8")}catch{}}async function Nf0(){try{let Z=await fetch(Wf0,{signal:AbortSignal.timeout(Vf0),headers:{accept:"application/json"}});if(!Z.ok)return null;let $=await Z.json();if(typeof $.version==="string"&&xZ($.version)!==null)return $.version}catch{}return null}async function Mf0(Z=Date.now(),$=Nf0,J=Hf0()){let X=In(J);if(X&&Z-X.checkedAt<Kf0)return X.latest;let Y=qf0(J);if(Y!==null&&Z-Y>=0&&Z-Y<Gf0)return null;let Q=await $();if(Q===null)return Uf0(J,Z),null;return Bf0(J,{checkedAt:Z,latest:Q}),Q}function Of0(Z,$=process.env){if(xZ(Z)===null)return null;let X=$.PATH;if(!X)return null;let Y=new Set;for(let Q of X.split(":")){if(!Q)continue;let z=iE(Q,"loki"),W;try{zf0(z),W=Qf0(z)}catch{continue}if(Y.has(W))continue;Y.add(W);try{let K=iE(MY(MY(W)),"package.json"),V=JSON.parse(NM(K,"utf8")).version;if(typeof V!=="string")continue;if(dK(V,Z))return{path:z,version:V}}catch{continue}}return null}function Af0(){return Cn(T2(),"cache","last-seen-version.json")}function Lf0(Z,$={}){try{if(Fn())return!1;if(xZ(Z)===null)return!1;let J=$.seenFile??Af0(),X=null;try{let Q=JSON.parse(NM(J,"utf-8")).version;if(typeof Q==="string"&&xZ(Q)!==null)X=Q}catch{}if(X!==null&&!dK(Z,X))return!1;try{oE(MY(J),{recursive:!0}),nE(J,JSON.stringify({version:Z}),"utf-8")}catch{return!1}if(X===null)return!1;return($.write??((Q)=>process.stderr.write(Q)))(`Loki Mode upgraded ${X} -> ${Z}. What is new: https://github.com/asklokesh/loki-mode/releases/tag/v${Z}
|
|
3
3
|
`),!0}catch{return!1}}function jf0(Z){let $=Z.replace(/\\/g,"/");if($.includes("/Cellar/"))return"brew upgrade loki-mode";if(/\/pnpm\//.test($)||$.includes("/.pnpm/"))return"pnpm add -g loki-mode (or npm i -g loki-mode)";if($.includes("/yarn/global/")||$.includes("/.yarn/"))return"yarn global add loki-mode (or npm i -g loki-mode)";if($.includes("/.bun/"))return"bun install -g loki-mode (or npm i -g loki-mode)";if($.includes("/lib/node_modules/"))return"npm i -g loki-mode (or bun install -g loki-mode)";if($.includes("/homebrew/"))return"brew upgrade loki-mode";return"bun install -g loki-mode (or npm i -g loki-mode)"}async function Tn(Z,$={}){try{if(Fn())return;if(xZ(Z)===null)return;let J=$.write??((Q)=>process.stderr.write(Q));Lf0(Z,{write:J,...$.cacheFile?{seenFile:iE(MY($.cacheFile),"last-seen-version.json")}:{}});let X=Of0(Z,$.env??process.env);if(X!==null){J(`Loki Mode ${X.version} is installed but not the one running (you are running ${Z}).
|
|
4
4
|
Another loki earlier on PATH is winning. Newer copy: ${X.path}
|
|
5
5
|
Run \`which -a loki\` to see the order, then remove or re-point the earlier entry.
|
|
@@ -3307,4 +3307,4 @@ Unknown commands exit 2 with a suggestion; run 'loki help' for the full list.
|
|
|
3307
3307
|
`);{let{suggestCommand:X}=await Promise.resolve().then(() => (W7(),Ys)),Y=$===void 0?void 0:X($);if(Y)process.stderr.write(`Did you mean 'loki ${Y}'?
|
|
3308
3308
|
`)}return process.stderr.write(vh0),2}}AT0();var hh0=()=>globalThis[Symbol.for("loki.ownsStopSignals")]===!0;process.on("SIGINT",()=>{if(!hh0())process.exit(130)});process.on("SIGTERM",()=>{if(!hh0())process.exit(143)});var TM9=await IM9(Bun.argv.slice(2));process.exit(TM9);
|
|
3309
3309
|
|
|
3310
|
-
//# debugId=
|
|
3310
|
+
//# debugId=AF862D650A323CA44BB592AEDC2434A2
|
package/mcp/__init__.py
CHANGED
package/mcp/requirements.txt
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
# Phase N (v7.5.27): added upper bounds to cap risk of a future major-version
|
|
3
3
|
# break installing on a fresh `pip install`. Tested locally against the upper
|
|
4
4
|
# end of the installed range on this Mac:
|
|
5
|
-
# mcp 1.27.0, chromadb 1.5.1, anthropic 0
|
|
5
|
+
# mcp 1.27.0, chromadb 1.5.1, anthropic 2.2.0
|
|
6
6
|
# Update upper bound when a new tested major drops.
|
|
7
|
+
# anthropic is pinned exactly: 2.x requires Python >=3.10 and API version 2026-10-21.
|
|
7
8
|
mcp>=1.19.0,<2.0.0
|
|
8
9
|
chromadb>=1.5.9,<2.0.0
|
|
9
|
-
anthropic
|
|
10
|
+
anthropic==2.2.0
|
|
@@ -93,6 +93,19 @@ class ManagedClient:
|
|
|
93
93
|
raise ManagedDisabled("anthropic SDK missing `beta` namespace")
|
|
94
94
|
return beta
|
|
95
95
|
|
|
96
|
+
def _memories(self):
|
|
97
|
+
"""Return (namespace, id_kwarg) for the memories resource.
|
|
98
|
+
|
|
99
|
+
SDK 2.x nests it at `beta.memory_stores.memories` and names the store
|
|
100
|
+
argument `memory_store_id`. Older/preview shapes (and test fakes) expose
|
|
101
|
+
`beta.memories` with `store_id`.
|
|
102
|
+
"""
|
|
103
|
+
beta = self._beta()
|
|
104
|
+
nested = getattr(getattr(beta, "memory_stores", None), "memories", None)
|
|
105
|
+
if nested is not None:
|
|
106
|
+
return nested, "memory_store_id"
|
|
107
|
+
return getattr(beta, "memories", None), "store_id"
|
|
108
|
+
|
|
96
109
|
# ---------- stores --------------------------------------------------
|
|
97
110
|
|
|
98
111
|
def stores_list(self) -> List[Dict[str, Any]]:
|
|
@@ -123,7 +136,15 @@ class ManagedClient:
|
|
|
123
136
|
if stores is None or not hasattr(stores, "create"):
|
|
124
137
|
raise ManagedDisabled("memory_stores.create not available in SDK")
|
|
125
138
|
try:
|
|
126
|
-
|
|
139
|
+
if hasattr(stores, "memories"):
|
|
140
|
+
# SDK 2.x create() has no `scope` argument; carry it as metadata.
|
|
141
|
+
created = stores.create(
|
|
142
|
+
name=name, description=description, metadata={"scope": scope}
|
|
143
|
+
)
|
|
144
|
+
else:
|
|
145
|
+
created = stores.create(
|
|
146
|
+
name=name, description=description, scope=scope
|
|
147
|
+
)
|
|
127
148
|
except ManagedDisabled:
|
|
128
149
|
raise
|
|
129
150
|
except Exception as e:
|
|
@@ -132,6 +153,11 @@ class ManagedClient:
|
|
|
132
153
|
|
|
133
154
|
# ---------- memories ------------------------------------------------
|
|
134
155
|
|
|
156
|
+
@staticmethod
|
|
157
|
+
def _abs(path: str) -> str:
|
|
158
|
+
"""Memory paths are absolute in SDK 2.x: they must start with "/"."""
|
|
159
|
+
return path if path.startswith("/") else "/" + path
|
|
160
|
+
|
|
135
161
|
def memory_create(
|
|
136
162
|
self,
|
|
137
163
|
store_id: str,
|
|
@@ -142,37 +168,71 @@ class ManagedClient:
|
|
|
142
168
|
"""
|
|
143
169
|
Create a memory entry at `path` in `store_id`.
|
|
144
170
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
171
|
+
SDK 2.x paths are unique per store and create() has no precondition:
|
|
172
|
+
creating an existing path raises a 409-shaped error, which callers
|
|
173
|
+
handle by re-reading, merging, and calling memory_update with the
|
|
174
|
+
stored content hash. The legacy shape (and test fakes) still accept
|
|
175
|
+
`sha256_precondition` as an optimistic concurrency hint.
|
|
149
176
|
"""
|
|
150
|
-
|
|
151
|
-
memories = getattr(beta, "memories", None)
|
|
177
|
+
memories, id_kw = self._memories()
|
|
152
178
|
if memories is None or not hasattr(memories, "create"):
|
|
153
179
|
raise ManagedDisabled("memories.create not available in SDK")
|
|
154
180
|
kwargs: Dict[str, Any] = {
|
|
155
|
-
|
|
156
|
-
"path": path,
|
|
181
|
+
id_kw: store_id,
|
|
182
|
+
"path": self._abs(path),
|
|
157
183
|
"content": content,
|
|
158
184
|
}
|
|
159
|
-
if sha256_precondition:
|
|
185
|
+
if sha256_precondition and id_kw == "store_id":
|
|
160
186
|
kwargs["if_match_sha256"] = sha256_precondition
|
|
161
187
|
try:
|
|
162
188
|
created = memories.create(**kwargs)
|
|
163
189
|
except ManagedDisabled:
|
|
164
190
|
raise
|
|
165
191
|
except Exception as e:
|
|
192
|
+
if getattr(e, "status_code", None) == 409:
|
|
193
|
+
# Keep the 409 visible so the caller can merge and update.
|
|
194
|
+
raise
|
|
166
195
|
raise ManagedDisabled(f"memory_create failed: {e!s}") from e
|
|
167
196
|
return self._to_dict(created)
|
|
168
197
|
|
|
198
|
+
def memory_update(
|
|
199
|
+
self,
|
|
200
|
+
store_id: str,
|
|
201
|
+
memory_id: str,
|
|
202
|
+
content: str,
|
|
203
|
+
sha256_precondition: str,
|
|
204
|
+
) -> Dict[str, Any]:
|
|
205
|
+
"""
|
|
206
|
+
Overwrite an existing memory. `sha256_precondition` must be the
|
|
207
|
+
content_sha256 of the content currently stored (not of the new
|
|
208
|
+
content); the server answers 409 if the memory changed since.
|
|
209
|
+
"""
|
|
210
|
+
memories, id_kw = self._memories()
|
|
211
|
+
if memories is None or not hasattr(memories, "update"):
|
|
212
|
+
raise ManagedDisabled("memories.update not available in SDK")
|
|
213
|
+
try:
|
|
214
|
+
updated = memories.update(
|
|
215
|
+
memory_id,
|
|
216
|
+
**{id_kw: store_id},
|
|
217
|
+
content=content,
|
|
218
|
+
precondition={
|
|
219
|
+
"type": "content_sha256",
|
|
220
|
+
"content_sha256": sha256_precondition,
|
|
221
|
+
},
|
|
222
|
+
)
|
|
223
|
+
except ManagedDisabled:
|
|
224
|
+
raise
|
|
225
|
+
except Exception as e:
|
|
226
|
+
raise ManagedDisabled(f"memory_update failed: {e!s}") from e
|
|
227
|
+
return self._to_dict(updated)
|
|
228
|
+
|
|
169
229
|
def memory_read(self, store_id: str, memory_id: str) -> Dict[str, Any]:
|
|
170
|
-
|
|
171
|
-
memories = getattr(beta, "memories", None)
|
|
230
|
+
memories, id_kw = self._memories()
|
|
172
231
|
if memories is None or not hasattr(memories, "retrieve"):
|
|
173
232
|
raise ManagedDisabled("memories.retrieve not available in SDK")
|
|
174
233
|
try:
|
|
175
|
-
|
|
234
|
+
extra = {"view": "full"} if id_kw == "memory_store_id" else {}
|
|
235
|
+
got = memories.retrieve(**{id_kw: store_id}, memory_id=memory_id, **extra)
|
|
176
236
|
except ManagedDisabled:
|
|
177
237
|
raise
|
|
178
238
|
except Exception as e:
|
|
@@ -182,21 +242,41 @@ class ManagedClient:
|
|
|
182
242
|
def memories_list(
|
|
183
243
|
self, store_id: str, path_prefix: Optional[str] = None
|
|
184
244
|
) -> List[Dict[str, Any]]:
|
|
185
|
-
|
|
186
|
-
memories
|
|
245
|
+
"""
|
|
246
|
+
List memories under `path_prefix`, content included.
|
|
247
|
+
|
|
248
|
+
SDK 2.x defaults to the basic view (content null) and pages at most
|
|
249
|
+
20 items, so request view="full" and follow the cursor until it ends.
|
|
250
|
+
Prefix rows (type "memory_prefix") are not memories and are dropped.
|
|
251
|
+
"""
|
|
252
|
+
memories, id_kw = self._memories()
|
|
187
253
|
if memories is None or not hasattr(memories, "list"):
|
|
188
254
|
raise ManagedDisabled("memories.list not available in SDK")
|
|
189
|
-
kwargs: Dict[str, Any] = {
|
|
255
|
+
kwargs: Dict[str, Any] = {id_kw: store_id}
|
|
190
256
|
if path_prefix:
|
|
191
|
-
kwargs["path_prefix"] = path_prefix
|
|
257
|
+
kwargs["path_prefix"] = self._abs(path_prefix)
|
|
258
|
+
paged = id_kw == "memory_store_id"
|
|
259
|
+
if paged:
|
|
260
|
+
kwargs["view"] = "full"
|
|
261
|
+
rows: List[Dict[str, Any]] = []
|
|
262
|
+
seen_pages = set()
|
|
192
263
|
try:
|
|
193
|
-
|
|
264
|
+
while True:
|
|
265
|
+
result = memories.list(**kwargs)
|
|
266
|
+
data = getattr(result, "data", result)
|
|
267
|
+
rows.extend(self._to_dict(x) for x in (data or []))
|
|
268
|
+
nxt = getattr(result, "next_page", None) if paged else None
|
|
269
|
+
if not nxt:
|
|
270
|
+
break
|
|
271
|
+
if nxt in seen_pages:
|
|
272
|
+
raise ManagedDisabled("memories.list cursor did not advance")
|
|
273
|
+
seen_pages.add(nxt)
|
|
274
|
+
kwargs["page"] = nxt
|
|
194
275
|
except ManagedDisabled:
|
|
195
276
|
raise
|
|
196
277
|
except Exception as e:
|
|
197
278
|
raise ManagedDisabled(f"memories_list failed: {e!s}") from e
|
|
198
|
-
|
|
199
|
-
return [self._to_dict(x) for x in (data or [])]
|
|
279
|
+
return [r for r in rows if r.get("type", "memory") == "memory"]
|
|
200
280
|
|
|
201
281
|
# ---------- internal ------------------------------------------------
|
|
202
282
|
|
|
@@ -15,7 +15,7 @@ from typing import Any, Dict, List, Optional
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class _Conflict(Exception):
|
|
18
|
-
"""Simulated 409
|
|
18
|
+
"""Simulated 409: existing path on create, or stale sha on update."""
|
|
19
19
|
|
|
20
20
|
status_code = 409
|
|
21
21
|
|
|
@@ -69,28 +69,59 @@ class FakeManagedClient:
|
|
|
69
69
|
"sha256_precondition": sha256_precondition,
|
|
70
70
|
}
|
|
71
71
|
)
|
|
72
|
+
# SDK 2.x semantics: paths are unique per store and absolute, and
|
|
73
|
+
# create() has no precondition, so an existing path is a 409.
|
|
72
74
|
key = (store_id, path)
|
|
75
|
+
if not path.startswith("/"):
|
|
76
|
+
raise ValueError(f"memory path must start with '/': {path}")
|
|
77
|
+
if key in self.memories:
|
|
78
|
+
raise _Conflict(f"path already exists: {path}")
|
|
73
79
|
sha = hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
74
|
-
existing = self.memories.get(key)
|
|
75
|
-
if existing is not None and sha256_precondition is not None:
|
|
76
|
-
if existing["sha"] != sha256_precondition:
|
|
77
|
-
# Simulated 409: caller must re-read + merge + retry.
|
|
78
|
-
raise _Conflict(
|
|
79
|
-
f"sha256 mismatch for {path}: "
|
|
80
|
-
f"have={existing['sha']}, want={sha256_precondition}"
|
|
81
|
-
)
|
|
82
|
-
version = (existing.get("version", 0) + 1) if existing else 1
|
|
83
80
|
entry = {
|
|
84
81
|
"id": f"mem_{abs(hash(key)) % 10**8:08d}",
|
|
82
|
+
"type": "memory",
|
|
85
83
|
"store_id": store_id,
|
|
86
84
|
"path": path,
|
|
87
85
|
"content": content,
|
|
88
86
|
"sha": sha,
|
|
89
|
-
"
|
|
87
|
+
"content_sha256": sha,
|
|
88
|
+
"version": 1,
|
|
90
89
|
}
|
|
91
90
|
self.memories[key] = entry
|
|
92
91
|
return entry
|
|
93
92
|
|
|
93
|
+
def memory_update(
|
|
94
|
+
self,
|
|
95
|
+
store_id: str,
|
|
96
|
+
memory_id: str,
|
|
97
|
+
content: str,
|
|
98
|
+
sha256_precondition: str,
|
|
99
|
+
) -> Dict[str, Any]:
|
|
100
|
+
self.calls.append(
|
|
101
|
+
{
|
|
102
|
+
"op": "memory_update",
|
|
103
|
+
"store_id": store_id,
|
|
104
|
+
"memory_id": memory_id,
|
|
105
|
+
"sha256_precondition": sha256_precondition,
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
for key, entry in self.memories.items():
|
|
109
|
+
if key[0] == store_id and entry["id"] == memory_id:
|
|
110
|
+
if entry["sha"] != sha256_precondition:
|
|
111
|
+
raise _Conflict(
|
|
112
|
+
f"content_sha256 mismatch for {entry['path']}: "
|
|
113
|
+
f"have={entry['sha']}, want={sha256_precondition}"
|
|
114
|
+
)
|
|
115
|
+
sha = hashlib.sha256(content.encode("utf-8")).hexdigest()
|
|
116
|
+
entry.update(
|
|
117
|
+
content=content,
|
|
118
|
+
sha=sha,
|
|
119
|
+
content_sha256=sha,
|
|
120
|
+
version=entry["version"] + 1,
|
|
121
|
+
)
|
|
122
|
+
return entry
|
|
123
|
+
raise KeyError(memory_id)
|
|
124
|
+
|
|
94
125
|
def memory_read(self, store_id: str, memory_id: str) -> Dict[str, Any]:
|
|
95
126
|
self.calls.append({"op": "memory_read", "memory_id": memory_id})
|
|
96
127
|
for entry in self.memories.values():
|
|
@@ -105,7 +105,7 @@ def retrieve_related_verdicts(
|
|
|
105
105
|
store_id = store.get("id") or store.get("store_id")
|
|
106
106
|
if not store_id:
|
|
107
107
|
return []
|
|
108
|
-
entries = client.memories_list(store_id=store_id, path_prefix="verdicts/")
|
|
108
|
+
entries = client.memories_list(store_id=store_id, path_prefix="/verdicts/")
|
|
109
109
|
except Exception as e:
|
|
110
110
|
_warn_once(f"memories_list failed ({e}); returning empty")
|
|
111
111
|
emit_managed_event(
|
|
@@ -192,7 +192,7 @@ def hydrate_patterns(
|
|
|
192
192
|
store_id = store.get("id") or store.get("store_id")
|
|
193
193
|
if not store_id:
|
|
194
194
|
return 0
|
|
195
|
-
entries = client.memories_list(store_id=store_id, path_prefix="patterns/")
|
|
195
|
+
entries = client.memories_list(store_id=store_id, path_prefix="/patterns/")
|
|
196
196
|
except Exception as e:
|
|
197
197
|
emit_managed_event(
|
|
198
198
|
"managed_agents_fallback",
|
|
@@ -329,7 +329,7 @@ def hydrate_skills(
|
|
|
329
329
|
store_id = store.get("id") or store.get("store_id")
|
|
330
330
|
if not store_id:
|
|
331
331
|
return 0
|
|
332
|
-
entries = client.memories_list(store_id=store_id, path_prefix="skills/")
|
|
332
|
+
entries = client.memories_list(store_id=store_id, path_prefix="/skills/")
|
|
333
333
|
except Exception as e:
|
|
334
334
|
emit_managed_event(
|
|
335
335
|
"managed_agents_fallback",
|
|
@@ -137,7 +137,6 @@ def _shadow_write_blob(
|
|
|
137
137
|
store_id=store_id,
|
|
138
138
|
path=logical_path,
|
|
139
139
|
content=content,
|
|
140
|
-
sha256_precondition=sha,
|
|
141
140
|
)
|
|
142
141
|
emit_managed_event(
|
|
143
142
|
"managed_memory_shadow_write",
|
|
@@ -170,6 +169,11 @@ def _shadow_write_blob(
|
|
|
170
169
|
else None
|
|
171
170
|
)
|
|
172
171
|
if existing_entry and existing_sha:
|
|
172
|
+
if existing_entry.get("content") is None:
|
|
173
|
+
# Merging into unseen content would overwrite it.
|
|
174
|
+
raise RuntimeError(
|
|
175
|
+
f"stored content for {logical_path} not returned"
|
|
176
|
+
)
|
|
173
177
|
# Simple merge: if existing content already contains a
|
|
174
178
|
# "versions" list, append; else seed it. We do NOT attempt
|
|
175
179
|
# semantic merging in Phase 1.
|
|
@@ -190,9 +194,9 @@ def _shadow_write_blob(
|
|
|
190
194
|
new_sha = compute_sha(merged_content)
|
|
191
195
|
# Precondition is the sha of the CURRENT remote state that
|
|
192
196
|
# we just read. The new sha is what the server will store.
|
|
193
|
-
client.
|
|
197
|
+
client.memory_update(
|
|
194
198
|
store_id=store_id,
|
|
195
|
-
|
|
199
|
+
memory_id=existing_entry["id"],
|
|
196
200
|
content=merged_content,
|
|
197
201
|
sha256_precondition=existing_sha,
|
|
198
202
|
)
|
|
@@ -247,7 +251,7 @@ def shadow_write_verdict(verdict_json_path: str) -> bool:
|
|
|
247
251
|
"""
|
|
248
252
|
Read a council verdict JSON from disk and shadow-write it.
|
|
249
253
|
|
|
250
|
-
The logical remote path is
|
|
254
|
+
The logical remote path is `/verdicts/<iteration>.json`. If the file is
|
|
251
255
|
missing or unreadable, the call is a no-op. Never raises.
|
|
252
256
|
"""
|
|
253
257
|
if not is_enabled():
|
|
@@ -260,14 +264,14 @@ def shadow_write_verdict(verdict_json_path: str) -> bool:
|
|
|
260
264
|
)
|
|
261
265
|
return False
|
|
262
266
|
iteration = payload.get("iteration") or payload.get("round") or "unknown"
|
|
263
|
-
logical = f"verdicts/iteration-{iteration}.json"
|
|
267
|
+
logical = f"/verdicts/iteration-{iteration}.json"
|
|
264
268
|
return _shadow_write_blob(logical, payload, kind="verdict")
|
|
265
269
|
|
|
266
270
|
|
|
267
271
|
def shadow_write_pattern(pattern: Dict[str, Any]) -> bool:
|
|
268
272
|
"""
|
|
269
273
|
Shadow-write a SemanticPattern-shaped dict. Logical path is
|
|
270
|
-
|
|
274
|
+
`/patterns/<pattern_id>.json`. Never raises.
|
|
271
275
|
"""
|
|
272
276
|
if not is_enabled():
|
|
273
277
|
return False
|
|
@@ -278,14 +282,14 @@ def shadow_write_pattern(pattern: Dict[str, Any]) -> bool:
|
|
|
278
282
|
)
|
|
279
283
|
return False
|
|
280
284
|
pid = pattern.get("pattern_id") or pattern.get("id") or "unknown"
|
|
281
|
-
logical = f"patterns/{pid}.json"
|
|
285
|
+
logical = f"/patterns/{pid}.json"
|
|
282
286
|
return _shadow_write_blob(logical, pattern, kind="pattern")
|
|
283
287
|
|
|
284
288
|
|
|
285
289
|
def shadow_write_episode(episode_path: str) -> bool:
|
|
286
290
|
"""
|
|
287
291
|
Shadow-write a high-importance episode trace JSON. Logical path is
|
|
288
|
-
|
|
292
|
+
`/episodes/<episode_id>.json`. Called from autonomy/run.sh's
|
|
289
293
|
auto_capture_episode ONLY when importance >= 0.6. Never raises.
|
|
290
294
|
"""
|
|
291
295
|
if not is_enabled():
|
|
@@ -298,7 +302,7 @@ def shadow_write_episode(episode_path: str) -> bool:
|
|
|
298
302
|
)
|
|
299
303
|
return False
|
|
300
304
|
eid = payload.get("id") or payload.get("task_id") or Path(episode_path).stem
|
|
301
|
-
logical = f"episodes/{eid}.json"
|
|
305
|
+
logical = f"/episodes/{eid}.json"
|
|
302
306
|
return _shadow_write_blob(logical, payload, kind="episode")
|
|
303
307
|
|
|
304
308
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "11.
|
|
4
|
+
"version": "11.70.0",
|
|
5
5
|
"description": "An autonomous software factory that knows what it is supposed to deliver, and proves it did.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -160,5 +160,5 @@
|
|
|
160
160
|
"dependencies": {
|
|
161
161
|
"@resvg/resvg-wasm": "^2.6.2"
|
|
162
162
|
},
|
|
163
|
-
"gitHead": "
|
|
163
|
+
"gitHead": "6d57e1f3614ac584eaf7cfd78c80c6965481cd4e"
|
|
164
164
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "11.
|
|
5
|
+
"version": "11.70.0",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|