agent-devkit 0.0.4 → 0.1.5
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 +42 -2
- package/package.json +1 -1
- package/runtime/README.md +50 -2
- package/runtime/agent +29 -13
- package/runtime/agents/README.md +3 -0
- package/runtime/agents/github-pr-reviewer/AGENTS.md +10 -0
- package/runtime/agents/github-pr-reviewer/README.md +7 -0
- package/runtime/agents/github-pr-reviewer/agent.yaml +33 -0
- package/runtime/agents/github-pr-reviewer/capabilities/create-review-automation/capability.yaml +20 -0
- package/runtime/agents/github-pr-reviewer/capabilities/create-review-automation/decision-rules.md +8 -0
- package/runtime/agents/github-pr-reviewer/capabilities/create-review-automation/workflow.md +9 -0
- package/runtime/agents/github-pr-reviewer/capabilities/inspect-pr/capability.yaml +20 -0
- package/runtime/agents/github-pr-reviewer/capabilities/inspect-pr/decision-rules.md +7 -0
- package/runtime/agents/github-pr-reviewer/capabilities/inspect-pr/workflow.md +8 -0
- package/runtime/agents/github-pr-reviewer/capabilities/list-review-requests/capability.yaml +20 -0
- package/runtime/agents/github-pr-reviewer/capabilities/list-review-requests/decision-rules.md +7 -0
- package/runtime/agents/github-pr-reviewer/capabilities/list-review-requests/workflow.md +8 -0
- package/runtime/agents/github-pr-reviewer/capabilities/review-pr-diff/capability.yaml +20 -0
- package/runtime/agents/github-pr-reviewer/capabilities/review-pr-diff/decision-rules.md +9 -0
- package/runtime/agents/github-pr-reviewer/capabilities/review-pr-diff/workflow.md +10 -0
- package/runtime/agents/github-pr-reviewer/infra/README.md +7 -0
- package/runtime/agents/github-pr-reviewer/knowledge/context.md +7 -0
- package/runtime/agents/github-pr-reviewer/knowledge/system.md +17 -0
- package/runtime/agents/github-pr-reviewer/templates/pr-automation-output.md +10 -0
- package/runtime/agents/github-pr-reviewer/templates/pr-inspection-output.md +9 -0
- package/runtime/agents/github-pr-reviewer/templates/pr-list-output.md +9 -0
- package/runtime/agents/github-pr-reviewer/templates/pr-review-output.md +17 -0
- package/runtime/cli/README.md +37 -1
- package/runtime/cli/aikit/__init__.py +1 -1
- package/runtime/cli/aikit/aliases.py +196 -0
- package/runtime/cli/aikit/app_home.py +78 -0
- package/runtime/cli/aikit/audit.py +344 -0
- package/runtime/cli/aikit/calendar.py +163 -0
- package/runtime/cli/aikit/configuration_orchestrator.py +291 -0
- package/runtime/cli/aikit/decision_store.py +158 -0
- package/runtime/cli/aikit/diagnostics.py +9 -0
- package/runtime/cli/aikit/fallback.py +48 -2
- package/runtime/cli/aikit/github_pr.py +254 -0
- package/runtime/cli/aikit/identity.py +75 -0
- package/runtime/cli/aikit/llm.py +249 -48
- package/runtime/cli/aikit/main.py +1240 -32
- package/runtime/cli/aikit/memory.py +148 -8
- package/runtime/cli/aikit/model_router.py +70 -0
- package/runtime/cli/aikit/notifications.py +9 -0
- package/runtime/cli/aikit/ollama.py +237 -0
- package/runtime/cli/aikit/permissions.py +345 -0
- package/runtime/cli/aikit/personality.py +123 -0
- package/runtime/cli/aikit/provider_wizard.py +19 -0
- package/runtime/cli/aikit/providers.py +4 -5
- package/runtime/cli/aikit/review_gate.py +40 -0
- package/runtime/cli/aikit/scheduler.py +18 -0
- package/runtime/cli/aikit/sessions.py +396 -0
- package/runtime/cli/aikit/setup_wizard.py +12 -0
- package/runtime/cli/aikit/tasks.py +351 -0
- package/runtime/cli/aikit/toolchain.py +274 -0
- package/runtime/providers/calendar.yaml +28 -0
- package/runtime/providers/github.yaml +42 -0
- package/runtime/providers/local-notification.yaml +19 -0
- package/runtime/providers/local-scheduler.yaml +24 -0
- package/runtime/vendor/skills/napkin/napkin.md +13 -3
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"""Persistent conversation session helpers for Agent DevKit."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
import uuid
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from cli.aikit.app_home import app_path, ensure_app_home, sessions_home
|
|
13
|
+
from cli.aikit.memory import redact_secrets
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
SESSION_ID_PATTERN = re.compile(r"^[a-zA-Z0-9][a-zA-Z0-9_.-]*$")
|
|
17
|
+
SESSION_STATE_VERSION = 1
|
|
18
|
+
RECENT_CONTEXT_EXCHANGES = 6
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def now_iso() -> str:
|
|
22
|
+
return datetime.now(timezone.utc).isoformat()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def active_session_path() -> Path:
|
|
26
|
+
return app_path("state", "active-session.json")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def ensure_sessions_home() -> Path:
|
|
30
|
+
ensure_app_home()
|
|
31
|
+
home = sessions_home()
|
|
32
|
+
home.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
return home
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def create_session(
|
|
37
|
+
*,
|
|
38
|
+
prompt: str | None = None,
|
|
39
|
+
project: str | None = None,
|
|
40
|
+
backend: str | None = None,
|
|
41
|
+
set_active: bool = True,
|
|
42
|
+
) -> dict[str, Any]:
|
|
43
|
+
home = ensure_sessions_home()
|
|
44
|
+
created_at = now_iso()
|
|
45
|
+
session_id = f"sess_{datetime.now(timezone.utc).strftime('%Y%m%d%H%M%S')}_{uuid.uuid4().hex[:8]}"
|
|
46
|
+
path = home / session_id
|
|
47
|
+
path.mkdir(parents=True, exist_ok=False)
|
|
48
|
+
|
|
49
|
+
state = {
|
|
50
|
+
"version": SESSION_STATE_VERSION,
|
|
51
|
+
"id": session_id,
|
|
52
|
+
"title": title_from_prompt(prompt),
|
|
53
|
+
"status": "active",
|
|
54
|
+
"created_at": created_at,
|
|
55
|
+
"updated_at": created_at,
|
|
56
|
+
"project": project,
|
|
57
|
+
"backend": backend,
|
|
58
|
+
"message_count": 0,
|
|
59
|
+
"exchange_count": 0,
|
|
60
|
+
"token_estimate": 0,
|
|
61
|
+
"path": str(path),
|
|
62
|
+
"summary_path": str(path / "summary.md"),
|
|
63
|
+
"messages_path": str(path / "messages.jsonl"),
|
|
64
|
+
}
|
|
65
|
+
write_state(path, state)
|
|
66
|
+
(path / "messages.jsonl").write_text("", encoding="utf-8")
|
|
67
|
+
write_summary(path, state, latest_prompt=prompt, latest_response=None)
|
|
68
|
+
write_session_markdown(path, state)
|
|
69
|
+
if set_active:
|
|
70
|
+
set_active_session(session_id)
|
|
71
|
+
return public_session(state, active=set_active)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_or_create_session(
|
|
75
|
+
*,
|
|
76
|
+
session_id: str | None = None,
|
|
77
|
+
force_new: bool = False,
|
|
78
|
+
prompt: str | None = None,
|
|
79
|
+
project: str | None = None,
|
|
80
|
+
backend: str | None = None,
|
|
81
|
+
) -> dict[str, Any]:
|
|
82
|
+
if session_id and force_new:
|
|
83
|
+
raise ValueError("--session and --new-session cannot be used together")
|
|
84
|
+
if session_id:
|
|
85
|
+
state = load_session(session_id)
|
|
86
|
+
set_active_session(state["id"])
|
|
87
|
+
return public_session(state, active=True)
|
|
88
|
+
if force_new:
|
|
89
|
+
return create_session(prompt=prompt, project=project, backend=backend, set_active=True)
|
|
90
|
+
|
|
91
|
+
active_id = get_active_session_id()
|
|
92
|
+
if active_id:
|
|
93
|
+
try:
|
|
94
|
+
state = load_session(active_id)
|
|
95
|
+
if project and state.get("project") and state.get("project") != project:
|
|
96
|
+
return create_session(prompt=prompt, project=project, backend=backend, set_active=True)
|
|
97
|
+
if project and not state.get("project"):
|
|
98
|
+
state["project"] = project
|
|
99
|
+
write_state(session_path_from_id(state["id"]), state)
|
|
100
|
+
return public_session(state, active=True)
|
|
101
|
+
except ValueError:
|
|
102
|
+
clear_active_session()
|
|
103
|
+
return create_session(prompt=prompt, project=project, backend=backend, set_active=True)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def list_sessions() -> dict[str, Any]:
|
|
107
|
+
home = ensure_sessions_home()
|
|
108
|
+
active_id = get_active_session_id()
|
|
109
|
+
items: list[dict[str, Any]] = []
|
|
110
|
+
for state_path in sorted(home.glob("*/state.json")):
|
|
111
|
+
try:
|
|
112
|
+
state = read_json(state_path)
|
|
113
|
+
except ValueError:
|
|
114
|
+
continue
|
|
115
|
+
if not isinstance(state, dict):
|
|
116
|
+
continue
|
|
117
|
+
items.append(public_session(state, active=state.get("id") == active_id))
|
|
118
|
+
items.sort(key=lambda item: str(item.get("updated_at") or ""), reverse=True)
|
|
119
|
+
return {
|
|
120
|
+
"kind": "sessions",
|
|
121
|
+
"status": "ok",
|
|
122
|
+
"home": str(home),
|
|
123
|
+
"active_session_id": active_id,
|
|
124
|
+
"items": items,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def show_session(session_id: str) -> dict[str, Any]:
|
|
129
|
+
state = load_session(session_id)
|
|
130
|
+
return {
|
|
131
|
+
"kind": "session",
|
|
132
|
+
"status": "ok",
|
|
133
|
+
"session": public_session(state, active=state["id"] == get_active_session_id()),
|
|
134
|
+
"summary": read_text(Path(state["summary_path"])),
|
|
135
|
+
"recent_messages": recent_exchanges(state["id"], limit=RECENT_CONTEXT_EXCHANGES),
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def resume_session(session_id: str) -> dict[str, Any]:
|
|
140
|
+
state = load_session(session_id)
|
|
141
|
+
set_active_session(state["id"])
|
|
142
|
+
return {
|
|
143
|
+
"kind": "session",
|
|
144
|
+
"status": "resumed",
|
|
145
|
+
"session": public_session(state, active=True),
|
|
146
|
+
"summary": read_text(Path(state["summary_path"])),
|
|
147
|
+
"recent_messages": recent_exchanges(state["id"], limit=RECENT_CONTEXT_EXCHANGES),
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def record_exchange(
|
|
152
|
+
session_id: str,
|
|
153
|
+
*,
|
|
154
|
+
prompt: str,
|
|
155
|
+
result: dict[str, Any],
|
|
156
|
+
backend: str | None = None,
|
|
157
|
+
) -> dict[str, Any]:
|
|
158
|
+
state = load_session(session_id)
|
|
159
|
+
path = session_path_from_id(state["id"])
|
|
160
|
+
response = str(result.get("response") or result.get("message") or "")
|
|
161
|
+
token_delta = estimate_tokens(prompt) + estimate_tokens(response)
|
|
162
|
+
exchange = {
|
|
163
|
+
"type": "exchange",
|
|
164
|
+
"created_at": now_iso(),
|
|
165
|
+
"prompt": redact_secrets(prompt),
|
|
166
|
+
"response": redact_secrets(response),
|
|
167
|
+
"status": result.get("status"),
|
|
168
|
+
"ok": result.get("ok"),
|
|
169
|
+
"backend": result.get("llm_backend") or backend,
|
|
170
|
+
"requires_llm": result.get("requires_llm"),
|
|
171
|
+
"token_estimate": token_delta,
|
|
172
|
+
}
|
|
173
|
+
with (path / "messages.jsonl").open("a", encoding="utf-8") as file:
|
|
174
|
+
json.dump(exchange, file, ensure_ascii=False, sort_keys=True)
|
|
175
|
+
file.write("\n")
|
|
176
|
+
|
|
177
|
+
state["updated_at"] = exchange["created_at"]
|
|
178
|
+
state["message_count"] = int(state.get("message_count") or 0) + 2
|
|
179
|
+
state["exchange_count"] = int(state.get("exchange_count") or 0) + 1
|
|
180
|
+
state["token_estimate"] = int(state.get("token_estimate") or 0) + token_delta
|
|
181
|
+
if backend and not state.get("backend"):
|
|
182
|
+
state["backend"] = backend
|
|
183
|
+
if state.get("title") == "Untitled session":
|
|
184
|
+
state["title"] = title_from_prompt(prompt)
|
|
185
|
+
write_state(path, state)
|
|
186
|
+
write_summary(path, state, latest_prompt=prompt, latest_response=response)
|
|
187
|
+
write_session_markdown(path, state)
|
|
188
|
+
set_active_session(state["id"])
|
|
189
|
+
return public_session(state, active=True)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def build_contextual_prompt(session_id: str, prompt: str) -> str:
|
|
193
|
+
exchanges = recent_exchanges(session_id, limit=RECENT_CONTEXT_EXCHANGES)
|
|
194
|
+
if not exchanges:
|
|
195
|
+
return prompt
|
|
196
|
+
lines = [
|
|
197
|
+
"Contexto recente da sessao atual do Agent DevKit:",
|
|
198
|
+
]
|
|
199
|
+
for item in exchanges:
|
|
200
|
+
previous_prompt = compact_line(str(item.get("prompt") or ""))
|
|
201
|
+
previous_response = compact_line(str(item.get("response") or ""))
|
|
202
|
+
if previous_prompt:
|
|
203
|
+
lines.append(f"- Usuario: {previous_prompt}")
|
|
204
|
+
if previous_response:
|
|
205
|
+
lines.append(f" Assistente: {previous_response}")
|
|
206
|
+
lines.extend(["", "Pedido atual do usuario:", prompt])
|
|
207
|
+
return "\n".join(lines)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def load_session(session_id: str) -> dict[str, Any]:
|
|
211
|
+
resolved_id = resolve_session_id(session_id)
|
|
212
|
+
path = session_path_from_id(resolved_id)
|
|
213
|
+
state_path = path / "state.json"
|
|
214
|
+
if not state_path.exists():
|
|
215
|
+
raise ValueError(f"session not found: {session_id}")
|
|
216
|
+
state = read_json(state_path)
|
|
217
|
+
if not isinstance(state, dict):
|
|
218
|
+
raise ValueError(f"invalid session state: {session_id}")
|
|
219
|
+
state.setdefault("id", resolved_id)
|
|
220
|
+
state.setdefault("path", str(path))
|
|
221
|
+
state.setdefault("summary_path", str(path / "summary.md"))
|
|
222
|
+
state.setdefault("messages_path", str(path / "messages.jsonl"))
|
|
223
|
+
return state
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def resolve_session_id(session_id: str) -> str:
|
|
227
|
+
if not session_id:
|
|
228
|
+
raise ValueError("session id is required")
|
|
229
|
+
if not SESSION_ID_PATTERN.match(session_id):
|
|
230
|
+
raise ValueError(f"invalid session id: {session_id}")
|
|
231
|
+
home = ensure_sessions_home()
|
|
232
|
+
direct = home / session_id
|
|
233
|
+
if direct.exists():
|
|
234
|
+
return session_id
|
|
235
|
+
matches = [path.name for path in home.iterdir() if path.is_dir() and path.name.startswith(session_id)]
|
|
236
|
+
if len(matches) == 1:
|
|
237
|
+
return matches[0]
|
|
238
|
+
if len(matches) > 1:
|
|
239
|
+
raise ValueError(f"ambiguous session id prefix: {session_id}")
|
|
240
|
+
raise ValueError(f"session not found: {session_id}")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def session_path_from_id(session_id: str) -> Path:
|
|
244
|
+
return ensure_sessions_home() / session_id
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def set_active_session(session_id: str) -> None:
|
|
248
|
+
ensure_app_home()
|
|
249
|
+
path = active_session_path()
|
|
250
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
251
|
+
payload = {"version": SESSION_STATE_VERSION, "session_id": session_id, "updated_at": now_iso()}
|
|
252
|
+
with path.open("w", encoding="utf-8") as file:
|
|
253
|
+
json.dump(payload, file, ensure_ascii=False, indent=2)
|
|
254
|
+
file.write("\n")
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def get_active_session_id() -> str | None:
|
|
258
|
+
path = active_session_path()
|
|
259
|
+
if not path.exists():
|
|
260
|
+
return None
|
|
261
|
+
try:
|
|
262
|
+
data = read_json(path)
|
|
263
|
+
except ValueError:
|
|
264
|
+
return None
|
|
265
|
+
if not isinstance(data, dict):
|
|
266
|
+
return None
|
|
267
|
+
session_id = data.get("session_id")
|
|
268
|
+
if not isinstance(session_id, str) or not SESSION_ID_PATTERN.match(session_id):
|
|
269
|
+
return None
|
|
270
|
+
if not (sessions_home() / session_id / "state.json").exists():
|
|
271
|
+
return None
|
|
272
|
+
return session_id
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def clear_active_session() -> None:
|
|
276
|
+
path = active_session_path()
|
|
277
|
+
if path.exists():
|
|
278
|
+
path.unlink()
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def public_session(state: dict[str, Any], *, active: bool = False) -> dict[str, Any]:
|
|
282
|
+
return {
|
|
283
|
+
"id": state.get("id"),
|
|
284
|
+
"title": state.get("title") or "Untitled session",
|
|
285
|
+
"status": state.get("status") or "active",
|
|
286
|
+
"created_at": state.get("created_at"),
|
|
287
|
+
"updated_at": state.get("updated_at"),
|
|
288
|
+
"project": state.get("project"),
|
|
289
|
+
"backend": state.get("backend"),
|
|
290
|
+
"message_count": int(state.get("message_count") or 0),
|
|
291
|
+
"exchange_count": int(state.get("exchange_count") or 0),
|
|
292
|
+
"token_estimate": int(state.get("token_estimate") or 0),
|
|
293
|
+
"path": state.get("path"),
|
|
294
|
+
"active": active,
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def recent_exchanges(session_id: str, *, limit: int) -> list[dict[str, Any]]:
|
|
299
|
+
path = session_path_from_id(resolve_session_id(session_id)) / "messages.jsonl"
|
|
300
|
+
if not path.exists():
|
|
301
|
+
return []
|
|
302
|
+
items: list[dict[str, Any]] = []
|
|
303
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
304
|
+
if not line.strip():
|
|
305
|
+
continue
|
|
306
|
+
try:
|
|
307
|
+
item = json.loads(line)
|
|
308
|
+
except json.JSONDecodeError:
|
|
309
|
+
continue
|
|
310
|
+
if isinstance(item, dict):
|
|
311
|
+
items.append(item)
|
|
312
|
+
return items[-limit:]
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def title_from_prompt(prompt: str | None) -> str:
|
|
316
|
+
if not prompt:
|
|
317
|
+
return "Untitled session"
|
|
318
|
+
text = compact_line(redact_secrets(prompt))
|
|
319
|
+
words = text.split()
|
|
320
|
+
if not words:
|
|
321
|
+
return "Untitled session"
|
|
322
|
+
title = " ".join(words[:8])
|
|
323
|
+
return title[:80]
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def estimate_tokens(text: str | None) -> int:
|
|
327
|
+
if not text:
|
|
328
|
+
return 0
|
|
329
|
+
compact = " ".join(str(text).split())
|
|
330
|
+
return max(1, (len(compact) + 3) // 4)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def compact_line(value: str, *, limit: int = 600) -> str:
|
|
334
|
+
text = " ".join(value.split())
|
|
335
|
+
if len(text) <= limit:
|
|
336
|
+
return text
|
|
337
|
+
return text[: limit - 3].rstrip() + "..."
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def write_state(path: Path, state: dict[str, Any]) -> None:
|
|
341
|
+
with (path / "state.json").open("w", encoding="utf-8") as file:
|
|
342
|
+
json.dump(state, file, ensure_ascii=False, indent=2, sort_keys=True)
|
|
343
|
+
file.write("\n")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def write_summary(path: Path, state: dict[str, Any], *, latest_prompt: str | None, latest_response: str | None) -> None:
|
|
347
|
+
lines = [
|
|
348
|
+
f"# {state.get('title') or 'Untitled session'}",
|
|
349
|
+
"",
|
|
350
|
+
f"- Session: {state.get('id')}",
|
|
351
|
+
f"- Status: {state.get('status') or 'active'}",
|
|
352
|
+
f"- Created: {state.get('created_at') or '-'}",
|
|
353
|
+
f"- Updated: {state.get('updated_at') or '-'}",
|
|
354
|
+
f"- Project: {state.get('project') or '-'}",
|
|
355
|
+
f"- Backend: {state.get('backend') or '-'}",
|
|
356
|
+
f"- Exchanges: {state.get('exchange_count') or 0}",
|
|
357
|
+
f"- Token estimate: {state.get('token_estimate') or 0}",
|
|
358
|
+
"",
|
|
359
|
+
"## Latest",
|
|
360
|
+
"",
|
|
361
|
+
f"- Prompt: {compact_line(redact_secrets(latest_prompt or '-'))}",
|
|
362
|
+
f"- Response: {compact_line(redact_secrets(latest_response or '-'))}",
|
|
363
|
+
"",
|
|
364
|
+
]
|
|
365
|
+
(path / "summary.md").write_text("\n".join(lines), encoding="utf-8")
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def write_session_markdown(path: Path, state: dict[str, Any]) -> None:
|
|
369
|
+
lines = [
|
|
370
|
+
f"# {state.get('title') or 'Untitled session'}",
|
|
371
|
+
"",
|
|
372
|
+
"This folder stores a local Agent DevKit conversation session.",
|
|
373
|
+
"",
|
|
374
|
+
"## Files",
|
|
375
|
+
"",
|
|
376
|
+
"- `state.json`: machine-readable metadata.",
|
|
377
|
+
"- `summary.md`: human-readable rolling summary.",
|
|
378
|
+
"- `messages.jsonl`: append-only redacted exchanges.",
|
|
379
|
+
"",
|
|
380
|
+
]
|
|
381
|
+
(path / "session.md").write_text("\n".join(lines), encoding="utf-8")
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def read_json(path: Path) -> Any:
|
|
385
|
+
try:
|
|
386
|
+
with path.open(encoding="utf-8") as file:
|
|
387
|
+
return json.load(file)
|
|
388
|
+
except (OSError, json.JSONDecodeError) as exc:
|
|
389
|
+
raise ValueError(f"cannot read JSON file: {path}") from exc
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def read_text(path: Path) -> str:
|
|
393
|
+
try:
|
|
394
|
+
return path.read_text(encoding="utf-8")
|
|
395
|
+
except OSError:
|
|
396
|
+
return ""
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Setup wizard orchestration for Agent DevKit."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from cli.aikit.toolchain import setup_plan
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def setup_wizard(root: Path, *, dry_run: bool = False, yes: bool = False) -> dict[str, Any]:
|
|
12
|
+
return setup_plan(root, dry_run=dry_run, yes=yes)
|