raga-cli 0.8.0 → 0.10.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +33 -5
  3. package/bundled-skills/README.md +2 -2
  4. package/bundled-skills/bundle.json +75 -9
  5. package/bundled-skills/docs/guide/01-setup.md +1 -1
  6. package/bundled-skills/skills/bend/SKILL.md +58 -0
  7. package/bundled-skills/skills/bend/bend_tool.py +144 -0
  8. package/bundled-skills/skills/peak/SKILL.md +2 -2
  9. package/bundled-skills/skills/peak/references/peak-setup/SKILL.md +1 -1
  10. package/bundled-skills/skills/peak/references/raga-runtime/SKILL.md +20 -6
  11. package/bundled-skills/skills/peak/references/raga-runtime/raga_runtime.py +24 -6
  12. package/bundled-skills/skills/raga-artifacts/SKILL.md +117 -0
  13. package/bundled-skills/skills/raga-artifacts/artifact.mjs +259 -0
  14. package/bundled-skills/skills/raga-artifacts/artifact_tool.py +55 -0
  15. package/bundled-skills/skills/raga-artifacts/diagrams.md +641 -0
  16. package/bundled-skills/skills/raga-artifacts/templates/doc.html +124 -0
  17. package/bundled-skills/skills/raga-artifacts/templates/slides.html +138 -0
  18. package/dist/bundle/chunks/agent-attach-ZCS2QGIU.js +9 -0
  19. package/dist/bundle/chunks/anthropic.js +1 -8
  20. package/dist/bundle/chunks/chunk-4OPO7UJ6.js +4 -0
  21. package/dist/bundle/chunks/{chunk-IOPE2TJ4.js → chunk-A4342OQJ.js} +16 -16
  22. package/dist/bundle/chunks/{chunk-YMZUNB25.js → chunk-ABE5B2U7.js} +1 -1
  23. package/dist/bundle/chunks/{chunk-P2QCWBFM.js → chunk-D2SEW6VC.js} +1 -1
  24. package/dist/bundle/chunks/chunk-FTQDEUXY.js +242 -0
  25. package/dist/bundle/chunks/chunk-HRI5E2Y3.js +2 -0
  26. package/dist/bundle/chunks/{chunk-UDT647VB.js → chunk-JNHUOSVT.js} +2 -9
  27. package/dist/bundle/chunks/chunk-K22YTJFL.js +3 -0
  28. package/dist/bundle/chunks/chunk-MXJNFS34.js +47 -0
  29. package/dist/bundle/chunks/{chunk-J22MF7YW.js → chunk-NXGZMUQX.js} +3 -48
  30. package/dist/bundle/chunks/{chunk-3EGDWOKT.js → chunk-NYFFOHME.js} +1 -1
  31. package/dist/bundle/chunks/chunk-P2EB6CEF.js +6 -0
  32. package/dist/bundle/chunks/{chunk-KNY3PXQI.js → chunk-SK25JXS4.js} +1 -1
  33. package/dist/bundle/chunks/chunk-VEV3PRRP.js +1358 -0
  34. package/dist/bundle/chunks/chunk-XBBS7V4D.js +2 -0
  35. package/dist/bundle/chunks/{cli-4P6FVVLT.js → cli-XZ774I3U.js} +1 -1
  36. package/dist/bundle/chunks/{easter-egg-3d-65VIC4G2.js → easter-egg-3d-XWC5XWQO.js} +1 -1
  37. package/dist/bundle/chunks/{execute-3H5WKMA3.js → execute-7BWE4ZV3.js} +1 -1
  38. package/dist/bundle/chunks/openai-chatgpt.js +1 -8
  39. package/dist/bundle/chunks/openai-codex.js +1 -8
  40. package/dist/bundle/chunks/openrouter.js +1 -8
  41. package/dist/bundle/chunks/radius.js +1 -8
  42. package/dist/bundle/chunks/runtime-K76CFDG5.js +2 -0
  43. package/dist/bundle/chunks/virtual-modules-YAFG26WW.js +2 -0
  44. package/dist/bundle/cli-runtime.js +1 -1
  45. package/dist/bundle/index.js +1 -1
  46. package/dist/bundle/raga-runtime.js +1 -1
  47. package/dist/bundle/rpc-entry.js +1 -1
  48. package/docs/cli.md +8 -0
  49. package/docs/compaction.md +26 -0
  50. package/docs/sdk.md +2 -1
  51. package/docs/settings.md +1 -0
  52. package/package.json +1 -1
  53. package/python/raga_rlm/__init__.py +30 -6
  54. package/python/raga_rlm/mlx_models.py +265 -0
  55. package/schemas/keybindings.schema.json +84 -0
  56. package/schemas/settings.schema.json +74 -1
  57. package/schemas/theme.schema.json +2 -2
  58. package/dist/bundle/chunks/chunk-AI2NMHE7.js +0 -1567
  59. package/dist/bundle/chunks/chunk-C4IJNE7U.js +0 -2
  60. package/dist/bundle/chunks/chunk-RFPFZTK2.js +0 -7
  61. package/dist/bundle/chunks/chunk-RP2WKNT7.js +0 -4
  62. package/dist/bundle/chunks/runtime-435R6IKY.js +0 -2
  63. package/dist/bundle/chunks/virtual-modules-U3JBUDP7.js +0 -2
@@ -151,9 +151,17 @@ async def rlm_query(
151
151
  *,
152
152
  read_only: bool = False,
153
153
  details: bool = False,
154
+ role: str | None = None,
155
+ seat: int | None = None,
154
156
  ) -> str | dict[str, Any]:
155
- """Run a child. Instance writers get isolated checkouts; details includes the change artifact."""
156
- payload = _drop_none(task=task, context=context, model=model, max_turns=max_turns, read_only=read_only)
157
+ """Run a child. Instance writers get isolated checkouts; details includes the change artifact.
158
+
159
+ Without `model`, `role` (and `seat` for panel roles) picks the model and reasoning budget assigned to
160
+ that role: the user's /agent-setup choices, or the pinned revision's roles in an instance session.
161
+ """
162
+ payload = _drop_none(
163
+ task=task, context=context, model=model, max_turns=max_turns, read_only=read_only, role=role, seat=seat
164
+ )
157
165
  result = await host_request({"type": "rlm.query", **payload})
158
166
  return result if details else result["answer"]
159
167
 
@@ -206,19 +214,35 @@ class Context:
206
214
 
207
215
 
208
216
  class Memory:
209
- """Scoped retrieval and unreviewed observations. Entries never grant permissions."""
217
+ """Scoped retrieval and unreviewed observations. Entries never grant permissions.
210
218
 
211
- async def search(self, query: str, *, task_type: str | None = None, max_chars: int = 4000, limit: int = 5) -> dict[str, Any]:
219
+ Search returns reviewed guidance only. `include_unverified=True` also returns unreviewed and hypothesis
220
+ entries, such as observations, with their `trust` and `epistemicStatus` labels. An observation is bound to
221
+ this harness revision unless `portable=True`; its origin records the revision either way.
222
+ """
223
+
224
+ async def search(
225
+ self,
226
+ query: str,
227
+ *,
228
+ task_type: str | None = None,
229
+ max_chars: int = 4000,
230
+ limit: int = 5,
231
+ include_unverified: bool = False,
232
+ ) -> dict[str, Any]:
212
233
  return await host_request({
213
234
  "type": "memory.search",
214
235
  **_drop_none(query=query, task_type=task_type, max_chars=max_chars, limit=limit),
236
+ "include_unverified": include_unverified,
215
237
  })
216
238
 
217
239
  async def get(self, entry_id: str) -> dict[str, Any]:
218
240
  return await host_request({"type": "memory.get", "id": entry_id})
219
241
 
220
- async def observe(self, text: str, evidence: list[str]) -> dict[str, Any]:
221
- return await host_request({"type": "memory.observe", "text": text, "evidence": evidence})
242
+ async def observe(self, text: str, evidence: list[str], *, portable: bool = False) -> dict[str, Any]:
243
+ """Record an unreviewed hypothesis. Returns {"entry", "ingestion"}: the archived record and whether the
244
+ selected search backend accepted it."""
245
+ return await host_request({"type": "memory.observe", "text": text, "evidence": evidence, "portable": portable})
222
246
 
223
247
 
224
248
  class Instance:
@@ -0,0 +1,265 @@
1
+ """Private local model manager protocol. Model specs arrive on stdin, never argv.
2
+
3
+ No mode installs uv or fetches executable model code. Only download uses network.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import argparse
8
+ import importlib
9
+ import importlib.metadata
10
+ import json
11
+ import os
12
+ from pathlib import Path, PurePosixPath
13
+ import platform
14
+ import re
15
+ import shutil
16
+ import subprocess
17
+ import sys
18
+ import time
19
+ import threading
20
+
21
+ DEPENDENCIES = {"mlx-lm": "0.32.2", "huggingface-hub": "2.4.0", "hf-xet": "1.7.1"}
22
+ OUTPUT_LOCK = threading.Lock()
23
+
24
+
25
+ class ModelError(Exception):
26
+ """A safe, user-facing diagnostic (no hub exceptions or credentials)."""
27
+
28
+
29
+ def emit(**fields: object) -> None:
30
+ with OUTPUT_LOCK:
31
+ print(json.dumps(fields), flush=True)
32
+
33
+
34
+ def validate(spec: dict) -> None:
35
+ if not isinstance(spec.get("id"), str) or not re.fullmatch(r"[\w.-]+/[\w.-]+", spec["id"]):
36
+ raise ModelError("Invalid Hugging Face model id")
37
+ if not isinstance(spec.get("revision"), str) or not re.fullmatch(r"[0-9a-fA-F]{40}", spec["revision"]):
38
+ raise ModelError("Model revision must be a pinned 40-character commit SHA")
39
+ files = spec.get("files")
40
+ if not isinstance(files, list) or not files or not all(isinstance(f, str) for f in files):
41
+ raise ModelError("An exact model file allowlist is required")
42
+ for name in files:
43
+ path = PurePosixPath(name)
44
+ if path.is_absolute() or ".." in path.parts or str(path) != name or any(c in name for c in "*?[]\\"):
45
+ raise ModelError("Model files must be exact relative paths, not patterns")
46
+ if spec.get("format") == "gguf":
47
+ if not all(f.endswith(".gguf") for f in files):
48
+ raise ModelError("GGUF selection must contain only exact GGUF files")
49
+ model_file = spec.get("modelFile", files[0])
50
+ if model_file not in files:
51
+ raise ModelError("GGUF modelFile must be in the selected files")
52
+ context = spec.get("contextWindow")
53
+ if type(context) is not int or not 1 <= context <= 16384:
54
+ raise ModelError("GGUF contextWindow must be between 1 and 16384")
55
+ for name in files:
56
+ shard = re.fullmatch(r"(.+)-(\d{5})-of-(\d{5})\.gguf", name)
57
+ if shard:
58
+ prefix, number, count = shard.groups()
59
+ total = int(count)
60
+ if not 1 <= int(number) <= total <= len(files) or any(
61
+ f"{prefix}-{i:05d}-of-{count}.gguf" not in files for i in range(1, total + 1)
62
+ ):
63
+ raise ModelError("GGUF selection must include all shards")
64
+ first = re.fullmatch(r"(.+)-(\d{5})-of-(\d{5})\.gguf", model_file)
65
+ if first and first[2] != "00001":
66
+ raise ModelError("GGUF modelFile must be the first shard")
67
+ return
68
+ if spec.get("format") is not None:
69
+ raise ModelError("Unsupported model format")
70
+ if "config.json" not in files or not any(f.endswith(".safetensors") for f in files):
71
+ raise ModelError("Model files must include config.json and safetensors weights")
72
+
73
+
74
+ def backend() -> str:
75
+ value = os.environ.get("RAGA_LOCAL_MODEL_BACKEND", "mlx")
76
+ if value not in ("mlx", "llama.cpp"):
77
+ raise ModelError("Unknown local model backend")
78
+ return value
79
+
80
+
81
+ def llama_server() -> str:
82
+ instructions = ("llama-server >= v0.8.0 / b11960 is required. Install or upgrade llama.cpp "
83
+ "and put llama-server on PATH, or set LLAMA_SERVER_PATH.")
84
+ binary = shutil.which(os.environ.get("LLAMA_SERVER_PATH", "llama-server"))
85
+ if binary is None:
86
+ raise ModelError(instructions)
87
+ try:
88
+ result = subprocess.run([binary, "--version"], capture_output=True, text=True, timeout=15, check=True)
89
+ except (OSError, subprocess.SubprocessError) as exc:
90
+ raise ModelError(instructions) from exc
91
+ text = result.stdout + result.stderr
92
+ build = re.search(r"\bversion:\s*b?(\d+)(?![\d.])\b", text, re.I) or re.search(r"\bb(\d+)\b", text, re.I)
93
+ release = re.search(r"(?:\bversion:\s*v?|\bv)(\d+)\.(\d+)\.(\d+)\b", text)
94
+ if not (int(build[1]) >= 11960 if build else
95
+ release and tuple(map(int, release.groups())) >= (0, 8, 0)):
96
+ raise ModelError(instructions)
97
+ return binary
98
+
99
+
100
+ def probe() -> None:
101
+ gguf = backend() == "llama.cpp"
102
+ if gguf:
103
+ if platform.system() not in ("Darwin", "Linux"):
104
+ raise ModelError("llama.cpp requires macOS or Linux (POSIX process groups)")
105
+ llama_server()
106
+ elif platform.system() != "Darwin" or platform.machine() != "arm64":
107
+ raise ModelError("MLX requires macOS on Apple Silicon (arm64)")
108
+ for name, version in DEPENDENCIES.items():
109
+ if gguf and name == "mlx-lm":
110
+ continue
111
+ if importlib.metadata.version(name) != version:
112
+ raise ModelError("Local model dependencies need installation or repair")
113
+ modules = ("huggingface_hub", "hf_xet") if gguf else ("mlx.core", "mlx_lm.server", "huggingface_hub", "hf_xet")
114
+ for module in modules:
115
+ importlib.import_module(module)
116
+
117
+
118
+ def cached_snapshot(spec: dict) -> Path | None:
119
+ """Check every selected file without network, including empty/broken blobs."""
120
+ from huggingface_hub import hf_hub_download, snapshot_download
121
+ try:
122
+ root = Path(snapshot_download(repo_id=spec["id"], revision=spec["revision"],
123
+ allow_patterns=spec["files"], local_files_only=True))
124
+ for name in spec["files"]:
125
+ path = Path(hf_hub_download(repo_id=spec["id"], revision=spec["revision"],
126
+ filename=name, local_files_only=True))
127
+ if not path.is_file() or path.stat().st_size == 0 or not (root / name).is_file() or (root / name).stat().st_size == 0:
128
+ return None
129
+ # A partial selection must not omit weight shards referenced by the index.
130
+ index = root / "model.safetensors.index.json"
131
+ if spec.get("format") != "gguf" and index.is_file():
132
+ shards = set(json.loads(index.read_text())["weight_map"].values())
133
+ if not shards.issubset(spec["files"]):
134
+ return None
135
+ return root
136
+ except (OSError, ValueError, KeyError):
137
+ return None
138
+ except Exception:
139
+ # Hub cache misses use library-specific exception classes. Never echo auth errors.
140
+ return None
141
+
142
+
143
+ def preflight(root: Path) -> None:
144
+ from mlx_lm.utils import _get_classes
145
+ config = json.loads((root / "config.json").read_text())
146
+ model_type = config.get("model_type", "")
147
+ if not isinstance(model_type, str) or not re.fullmatch(r"[a-zA-Z0-9_]+", model_type):
148
+ raise ModelError("Unsupported MLX model architecture")
149
+ architectures = config.get("architectures") or []
150
+ if isinstance(architectures, str):
151
+ architectures = [architectures]
152
+ if "lfm3" in model_type.lower().replace("_", "") or any(
153
+ "lfm3" in str(arch).lower().replace("_", "") for arch in architectures
154
+ ):
155
+ raise ModelError("Unsupported MLX architecture: LFM3. Official mlx-lm support is pending.")
156
+ try:
157
+ _get_classes(config)
158
+ except (ImportError, ValueError, KeyError) as exc:
159
+ raise ModelError(f"Unsupported MLX architecture: {model_type}. mlx-lm support may be pending (including LFM3).") from exc
160
+
161
+
162
+ def progress_class():
163
+ from huggingface_hub.utils import tqdm
164
+
165
+ class Progress(tqdm):
166
+ def __init__(self, *args, **kwargs):
167
+ self.last_report = 0.0
168
+ super().__init__(*args, **kwargs)
169
+
170
+ def display(self, msg=None, pos=None):
171
+ now = time.monotonic()
172
+ if now - self.last_report < 0.2:
173
+ return
174
+ self.last_report = now
175
+ if self.unit != "B" or not self.desc.startswith("Downloading"):
176
+ return
177
+ total = self.total or 0
178
+ rate = self.format_dict.get("rate") or 0
179
+ size = f"{self.n / 1048576:.1f} / {total / 1048576:.1f} MiB"
180
+ emit(message=f"Downloading {size} · {rate / 1048576:.1f} MiB/s")
181
+
182
+ return Progress
183
+
184
+
185
+ def download(spec: dict) -> None:
186
+ os.environ["HF_XET_HIGH_PERFORMANCE"] = "1"
187
+ from huggingface_hub import snapshot_download
188
+ import hf_xet # noqa: F401 -- ensure the pinned transfer backend is available
189
+ # Downloading is allowed even when mlx-lm cannot yet run the architecture (e.g. LFM3).
190
+ emit(message="Downloading selected model files via Hugging Face cache")
191
+ snapshot_download(repo_id=spec["id"], revision=spec["revision"], allow_patterns=spec["files"],
192
+ max_workers=8, tqdm_class=progress_class())
193
+ if cached_snapshot(spec) is None:
194
+ raise ModelError("Download incomplete: selected model files are missing or empty")
195
+ emit(message="Model download complete", percent=100)
196
+
197
+
198
+ def serve(spec: dict, port: int) -> None:
199
+ # Set before importing HF/transformers/mlx_lm: their constants are import-time.
200
+ os.environ["HF_HUB_OFFLINE"] = "1"
201
+ os.environ["TRANSFORMERS_OFFLINE"] = "1"
202
+ root = cached_snapshot(spec)
203
+ if root is None:
204
+ raise ModelError("Selected model files are not fully cached; download first")
205
+ root = root.resolve()
206
+ if backend() == "llama.cpp":
207
+ if spec.get("format") != "gguf":
208
+ raise ModelError("llama.cpp requires a GGUF model spec")
209
+ binary = llama_server()
210
+ model = root / spec.get("modelFile", spec["files"][0])
211
+ emit(modelPath=str(model))
212
+ os.execv(binary, [binary, "--model", str(model), "--host", "127.0.0.1", "--port", str(port),
213
+ "--alias", spec["id"], "--ctx-size", str(spec["contextWindow"]), "--jinja",
214
+ "--temp", "0.1", "--top-k", "50", "--repeat-penalty", "1.1"])
215
+ return
216
+ if spec.get("format") == "gguf":
217
+ raise ModelError("GGUF models require the llama.cpp backend")
218
+ preflight(root)
219
+ emit(modelPath=str(root))
220
+ from mlx_lm import server
221
+
222
+ class LocalModelProvider(server.ModelProvider):
223
+ def __init__(self, cli_args: argparse.Namespace) -> None:
224
+ # mlx-lm 0.32.2 otherwise loads in a background thread, and /models
225
+ # can advertise the path before weights/tokenizer have loaded.
226
+ cli_args.trust_remote_code = False
227
+ super().__init__(cli_args)
228
+ self._model_map[spec["id"]] = str(root)
229
+ self.load_default()
230
+
231
+ server.ModelProvider = LocalModelProvider
232
+ # No --trust-remote-code: pinned mlx-lm defaults to false for tokenizers.
233
+ sys.argv = ["mlx_lm.server", "--model", str(root), "--host", "127.0.0.1", "--port", str(port)]
234
+ server.main()
235
+
236
+
237
+ def main() -> int:
238
+ try:
239
+ mode = sys.argv[1]
240
+ if mode == "probe":
241
+ probe()
242
+ emit(installed=True)
243
+ return 0
244
+ spec = json.load(sys.stdin)
245
+ validate(spec)
246
+ if mode == "cache":
247
+ os.environ["HF_HUB_OFFLINE"] = "1"
248
+ emit(cached=cached_snapshot(spec) is not None)
249
+ elif mode == "download":
250
+ download(spec)
251
+ elif mode == "serve":
252
+ serve(spec, int(sys.argv[2]))
253
+ else:
254
+ raise ModelError("Unknown local model helper mode")
255
+ return 0
256
+ except ModelError as exc:
257
+ emit(error=str(exc))
258
+ except Exception as exc:
259
+ # Deliberately omit exception text: requests/hub errors can include credentials.
260
+ emit(error=f"Local model operation failed ({type(exc).__name__}); check installation, cache, and Hugging Face access")
261
+ return 1
262
+
263
+
264
+ if __name__ == "__main__":
265
+ raise SystemExit(main())
@@ -195,6 +195,90 @@
195
195
  "$ref": "#/$defs/KeybindingValue",
196
196
  "description": "Scroll viewport to bottom"
197
197
  },
198
+ "app.agents.details": {
199
+ "$ref": "#/$defs/KeybindingValue",
200
+ "description": "Agents dashboard: toggle the details panel"
201
+ },
202
+ "app.agents.tree": {
203
+ "$ref": "#/$defs/KeybindingValue",
204
+ "description": "Agents dashboard: toggle tree and flat views"
205
+ },
206
+ "app.agents.stop": {
207
+ "$ref": "#/$defs/KeybindingValue",
208
+ "description": "Agents dashboard: stop the selected subagent"
209
+ },
210
+ "app.agentSetup.inherit": {
211
+ "$ref": "#/$defs/KeybindingValue",
212
+ "description": "Agent setup: let the role inherit the parent model"
213
+ },
214
+ "app.agentSetup.addSeat": {
215
+ "$ref": "#/$defs/KeybindingValue",
216
+ "description": "Agent setup: add a seat to a panel role"
217
+ },
218
+ "app.agentSetup.removeSeat": {
219
+ "$ref": "#/$defs/KeybindingValue",
220
+ "description": "Agent setup: remove the selected panel seat"
221
+ },
222
+ "app.agentSetup.save": {
223
+ "$ref": "#/$defs/KeybindingValue",
224
+ "description": "Agent setup: validate and save role assignments"
225
+ },
226
+ "app.agents.first": {
227
+ "$ref": "#/$defs/KeybindingValue",
228
+ "description": "Agents dashboard: first subagent"
229
+ },
230
+ "app.agents.last": {
231
+ "$ref": "#/$defs/KeybindingValue",
232
+ "description": "Agents dashboard: last subagent"
233
+ },
234
+ "app.diff.commits": {
235
+ "$ref": "#/$defs/KeybindingValue",
236
+ "description": "Diff dashboard: browse commits"
237
+ },
238
+ "app.diff.working": {
239
+ "$ref": "#/$defs/KeybindingValue",
240
+ "description": "Diff dashboard: working-tree changes"
241
+ },
242
+ "app.diff.staged": {
243
+ "$ref": "#/$defs/KeybindingValue",
244
+ "description": "Diff dashboard: staged changes"
245
+ },
246
+ "app.diff.file": {
247
+ "$ref": "#/$defs/KeybindingValue",
248
+ "description": "Diff dashboard: toggle diff and full file"
249
+ },
250
+ "app.diff.zoom": {
251
+ "$ref": "#/$defs/KeybindingValue",
252
+ "description": "Diff dashboard: toggle fullscreen viewer"
253
+ },
254
+ "app.diff.refresh": {
255
+ "$ref": "#/$defs/KeybindingValue",
256
+ "description": "Diff dashboard: refresh"
257
+ },
258
+ "app.diff.nextCategory": {
259
+ "$ref": "#/$defs/KeybindingValue",
260
+ "description": "Diff dashboard: next category"
261
+ },
262
+ "app.diff.previousCategory": {
263
+ "$ref": "#/$defs/KeybindingValue",
264
+ "description": "Diff dashboard: previous category"
265
+ },
266
+ "app.diff.left": {
267
+ "$ref": "#/$defs/KeybindingValue",
268
+ "description": "Diff dashboard: scroll left"
269
+ },
270
+ "app.diff.right": {
271
+ "$ref": "#/$defs/KeybindingValue",
272
+ "description": "Diff dashboard: scroll right"
273
+ },
274
+ "app.diff.start": {
275
+ "$ref": "#/$defs/KeybindingValue",
276
+ "description": "Diff dashboard: first line"
277
+ },
278
+ "app.diff.end": {
279
+ "$ref": "#/$defs/KeybindingValue",
280
+ "description": "Diff dashboard: last line"
281
+ },
198
282
  "app.interrupt": {
199
283
  "$ref": "#/$defs/KeybindingValue",
200
284
  "description": "Cancel or abort"
@@ -104,6 +104,20 @@
104
104
  "type": "boolean",
105
105
  "default": true
106
106
  },
107
+ "strategy": {
108
+ "anyOf": [
109
+ {
110
+ "type": "string",
111
+ "const": "summary"
112
+ },
113
+ {
114
+ "type": "string",
115
+ "const": "archive"
116
+ }
117
+ ],
118
+ "description": "\"summary\" replaces earlier context with an LLM summary. \"archive\" writes the earlier conversation to a plain-text log, drops it from context without a model call, and tells the agent to search the log.",
119
+ "default": "summary"
120
+ },
107
121
  "reserveTokens": {
108
122
  "type": "integer",
109
123
  "minimum": 0,
@@ -663,13 +677,72 @@
663
677
  }
664
678
  }
665
679
  },
680
+ "agentRoles": {
681
+ "type": "object",
682
+ "properties": {
683
+ "budget": {
684
+ "anyOf": [
685
+ {
686
+ "type": "string",
687
+ "const": "unlimited"
688
+ },
689
+ {
690
+ "type": "string",
691
+ "const": "large"
692
+ },
693
+ {
694
+ "type": "string",
695
+ "const": "medium"
696
+ },
697
+ {
698
+ "type": "string",
699
+ "const": "small"
700
+ }
701
+ ],
702
+ "description": "Reasoning budget for children launched by role: unlimited = xhigh, large = high, medium = medium, small = low thinking. Applies only to roles with an assigned model."
703
+ },
704
+ "roles": {
705
+ "type": "object",
706
+ "patternProperties": {
707
+ "^.*$": {
708
+ "anyOf": [
709
+ {
710
+ "anyOf": [
711
+ {
712
+ "type": "string"
713
+ },
714
+ {
715
+ "type": "null"
716
+ }
717
+ ]
718
+ },
719
+ {
720
+ "type": "array",
721
+ "items": {
722
+ "anyOf": [
723
+ {
724
+ "type": "string"
725
+ },
726
+ {
727
+ "type": "null"
728
+ }
729
+ ]
730
+ }
731
+ }
732
+ ]
733
+ }
734
+ },
735
+ "description": "Model per subagent role, set with /agent-setup. A value is \"provider/id\", \"inherit\" or null (the parent's model). Panel roles take a list with one entry per seat. Instance sessions use their pinned agents/model-roles.json instead."
736
+ }
737
+ }
738
+ },
666
739
  "sessionDir": {
667
740
  "type": "string",
668
741
  "description": "Custom session storage directory, in the same format as the --session-dir CLI flag."
669
742
  },
670
743
  "httpProxy": {
671
744
  "type": "string",
672
- "description": "Proxy URL applied as HTTP_PROXY and HTTPS_PROXY for Pi-managed HTTP clients."
745
+ "description": "Proxy URL applied as HTTP_PROXY and HTTPS_PROXY for application-managed HTTP clients."
673
746
  },
674
747
  "httpIdleTimeoutMs": {
675
748
  "anyOf": [
@@ -343,8 +343,8 @@
343
343
  "additionalProperties": false
344
344
  }
345
345
  },
346
- "title": "Pi Coding Agent Theme",
347
- "description": "Theme schema for Pi coding agent",
346
+ "title": "Coding Agent Theme",
347
+ "description": "Theme schema for the coding agent",
348
348
  "additionalProperties": false,
349
349
  "$defs": {
350
350
  "ColorValue": {