@akanjs/cli 3.1.0-rc.2 → 3.1.0-rc.21

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 (103) hide show
  1. package/.build-stamp +1 -1
  2. package/{CodeAgent-q50k4s45.js → CodeAgent-gzfc5t52.js} +2 -2
  3. package/{CodeAgentRpcHost-p7h6n2hp.js → CodeAgentRpcHost-9g27hmer.js} +1 -1
  4. package/{CodeTui-62g35ztk.js → CodeTui-bpsjs8wz.js} +47 -4
  5. package/TunnelShare-tfrnftgr.js +14 -0
  6. package/{agent.command-adfd5299.js → agent.command-67ysh3n6.js} +5 -5
  7. package/akanCode.js +7 -5
  8. package/{application.command-hj3gv1rx.js → application.command-q0tqw5pc.js} +38 -11
  9. package/{applicationBuildRunner-by10r9gf.js → applicationBuildRunner-wsszr305.js} +16 -9
  10. package/buildBatch.proc.js +7 -7
  11. package/{cloud.command-k2fhaape.js → cloud.command-e7tkxyx6.js} +17 -16
  12. package/cloud.script-krzh3d7k.js +24 -0
  13. package/{code.command-kt5zgcc2.js → code.command-ttk8tje2.js} +25 -12
  14. package/commandManifest.json +1 -1
  15. package/{context.command-8fgn42jw.js → context.command-bdsk2z2r.js} +17 -16
  16. package/context.runner-bxzv0gm0.js +25 -0
  17. package/{dependencyScanner-06z2dmrr.js → dependencyScanner-3afj2vt1.js} +1 -1
  18. package/{guideline.command-3yxxr2mm.js → guideline.command-man90n70.js} +2 -2
  19. package/guidelines/agentRule/agentRule.instruction.md +25 -10
  20. package/guidelines/conventions/conventions.instruction.md +486 -1084
  21. package/guidelines/cssRule/cssRule.instruction.md +16 -1
  22. package/guidelines/fieldRule/fieldRule.instruction.md +74 -0
  23. package/guidelines/framework/framework.instruction.md +4 -7
  24. package/guidelines/mcpRule/mcpRule.instruction.md +16 -9
  25. package/guidelines/modelConstant/modelConstant.instruction.md +2 -1
  26. package/guidelines/modelStore/modelStore.instruction.md +16 -0
  27. package/guidelines/pageRule/pageRule.instruction.md +80 -0
  28. package/guidelines/queryRule/queryRule.instruction.md +47 -12
  29. package/guidelines/runtimeRule/runtimeRule.instruction.md +89 -2
  30. package/guidelines/ssrRule/ssrRule.instruction.md +13 -0
  31. package/guidelines/transportRule/transportRule.instruction.md +107 -8
  32. package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +4 -4
  33. package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +70 -7
  34. package/incrementalBuilder.proc.js +4 -4
  35. package/{index-rhbabmja.js → index-033wne2j.js} +260 -259
  36. package/{index-xpdgxsj5.js → index-1r5a8bzx.js} +2216 -2216
  37. package/{index-jwqtchha.js → index-27y8t17p.js} +1 -1
  38. package/{index-wa5jbxj1.js → index-2mrn1mrg.js} +1 -0
  39. package/{index-t7nws9mx.js → index-2w8j2fnv.js} +661 -448
  40. package/{index-0909wckt.js → index-35m6bzm7.js} +238 -236
  41. package/{index-sqfdmb5b.js → index-4393nb4b.js} +4 -4
  42. package/{index-sby8fe1p.js → index-48edp6q4.js} +1 -1
  43. package/{index-g0vwzy9m.js → index-4v43h00g.js} +20 -21
  44. package/{index-azh29dc8.js → index-54wc2phm.js} +19 -14
  45. package/{index-k9rrten6.js → index-5nd1wtkv.js} +92 -94
  46. package/index-62md4dwy.js +260 -0
  47. package/{index-hnby7p9n.js → index-75n5q4ph.js} +7 -7
  48. package/{index-fjsv10hd.js → index-844q7f8z.js} +7 -7
  49. package/{index-tv8fjaba.js → index-8jehkey8.js} +4 -4
  50. package/{index-bea2gwc6.js → index-9mt4smfy.js} +2 -2
  51. package/{index-d2242ksy.js → index-at7bz0xq.js} +3 -0
  52. package/{index-3hx1z96c.js → index-bqbf654f.js} +1 -1
  53. package/{index-8zdwpxef.js → index-cae9m5jz.js} +119 -81
  54. package/{index-tz5evhdr.js → index-ebarbgba.js} +6 -6
  55. package/{index-2etqq75t.js → index-g6ax17x4.js} +1114 -1082
  56. package/{index-h85cxfkg.js → index-g9ks2k8c.js} +2 -2
  57. package/{index-nq045px5.js → index-h3r3nr9y.js} +356 -307
  58. package/{index-20cebtth.js → index-h6czey01.js} +79 -24
  59. package/{index-6726qr1m.js → index-jrmzcp66.js} +57 -58
  60. package/{index-q958tqqg.js → index-m9jj544v.js} +1 -1
  61. package/{index-8935h3n1.js → index-mdjc93e1.js} +1 -1
  62. package/{index-rk9sdys3.js → index-nv79n88g.js} +5 -5
  63. package/{index-kc8z134d.js → index-nx64dz92.js} +38 -15
  64. package/{index-z507nzhg.js → index-p2r64bpa.js} +4 -4
  65. package/{index-nq2ja5e0.js → index-pg1t468s.js} +3 -3
  66. package/{index-3reaaa1h.js → index-rc51f9ht.js} +29 -27
  67. package/{index-e9prnmyt.js → index-ta1dychm.js} +3 -3
  68. package/{index-2ahj896a.js → index-tvehn2sp.js} +1 -1
  69. package/{index-31ce9vhg.js → index-vkb9m4z7.js} +80 -38
  70. package/{index-58vvz603.js → index-xsd96yzw.js} +1 -1
  71. package/{index-pyxn1b53.js → index-zv5s8x58.js} +13 -13
  72. package/index.js +22 -22
  73. package/{library.command-8tw59xzj.js → library.command-6amzsrb8.js} +3 -3
  74. package/{localRegistry.command-tf3dmznd.js → localRegistry.command-yqc0nyhh.js} +15 -14
  75. package/{module.command-9vm8hffy.js → module.command-10x6jv6c.js} +5 -5
  76. package/{package.command-erbxe2tz.js → package.command-pwfvecjf.js} +4 -4
  77. package/package.json +21 -21
  78. package/{page.command-jpavcj0p.js → page.command-ahhgx9y1.js} +3 -3
  79. package/{pipeline.command-b8rzvjvv.js → pipeline.command-1ptr3e9y.js} +3 -3
  80. package/{primitive.command-sm32chzk.js → primitive.command-g5xwv6tj.js} +6 -6
  81. package/{projectTypecheck-jamtx95x.js → projectTypecheck-fx8x7cat.js} +1 -1
  82. package/{quality.command-pkd17n6y.js → quality.command-fw9ck061.js} +4 -4
  83. package/{repair.command-8y4mq6hs.js → repair.command-g8kecj96.js} +5 -5
  84. package/{scalar.command-q8khmyx5.js → scalar.command-fprap51w.js} +4 -4
  85. package/skills/akan-code/SKILL.md +4 -0
  86. package/skills/akan-store/SKILL.md +2 -1
  87. package/{subspace.command-51n0ykbx.js → subspace.command-sc0t527y.js} +11 -10
  88. package/templates/appSample/lib/task/Task.Util.tsx +7 -7
  89. package/templates/appSample/lib/task/Task.Zone.tsx +1 -1
  90. package/templates/appSample/ui/QuantityControl.tsx +2 -2
  91. package/templates/crudSinglePage/page.tsx +1 -1
  92. package/templates/lib/cnst.ts +0 -1
  93. package/templates/migration/__migrationId__.ts +23 -0
  94. package/templates/server.ts +8 -1
  95. package/templates/workspaceRoot/.gitignore.template +23 -52
  96. package/templates/workspaceRoot/biome.json.template +1 -1
  97. package/{tunnel.command-7820vtxq.js → tunnel.command-mqnsqbwf.js} +8 -7
  98. package/{workflow.command-n5sv4wqz.js → workflow.command-j0jgkr13.js} +10 -10
  99. package/{workspace.command-zdy60jh8.js → workspace.command-1r9hvvw0.js} +29 -28
  100. package/TunnelShare-f1egrktx.js +0 -13
  101. package/cloud.script-z8p5dz7e.js +0 -23
  102. package/context.runner-hnh0rg7m.js +0 -24
  103. package/index-n68jx79w.js +0 -120
@@ -3,8 +3,9 @@ import {
3
3
  SubagentPool2,
4
4
  akanCodePaths2,
5
5
  McpServerConfig,
6
- McpSignIn
7
- } from "./index-8zdwpxef.js";
6
+ McpSignIn,
7
+ AkanCodeCompaction2
8
+ } from "./index-cae9m5jz.js";
8
9
  import {
9
10
  stripAnsi
10
11
  } from "./index-7md2g9vw.js";
@@ -15,21 +16,318 @@ import {
15
16
  akanCodeModelWarnings2
16
17
  } from "./index-c6eggbee.js";
17
18
 
19
+ // pkgs/@akanjs/devkit/codeAgent/agent/AkanCodeServices.ts
20
+ import { existsSync as existsSync4, mkdirSync, mkdtempSync } from "fs";
21
+ import { tmpdir } from "os";
22
+ import path4 from "path";
23
+ import { ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
24
+
25
+ // pkgs/@akanjs/devkit/codeAgent/tools/AkanEnvKeys.ts
26
+ import { existsSync, readFileSync } from "fs";
27
+ import path from "path";
28
+
29
+ class AkanEnvKeys2 {
30
+ static async apply(runtime, workspaceRoot) {
31
+ const applied = [];
32
+ for (const [name, value] of AkanEnvKeys2.#entries(workspaceRoot)) {
33
+ const provider = AkanEnvKeys2.#providerOf(name);
34
+ if (!provider || !value)
35
+ continue;
36
+ await runtime.setRuntimeApiKey(provider, value);
37
+ applied.push(provider);
38
+ }
39
+ return applied;
40
+ }
41
+ static #entries(workspaceRoot) {
42
+ const fromProcess = Object.entries(process.env).filter(([, value]) => typeof value === "string");
43
+ const file = path.join(workspaceRoot, ".env");
44
+ if (!existsSync(file))
45
+ return fromProcess;
46
+ const fromFile = readFileSync(file, "utf8").split(`
47
+ `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
48
+ const index = line.indexOf("=");
49
+ return [
50
+ line.slice(0, index).trim(),
51
+ line.slice(index + 1).trim().replace(/^["']|["']$/g, "")
52
+ ];
53
+ });
54
+ return [...fromFile, ...fromProcess];
55
+ }
56
+ static #providerOf(name) {
57
+ const match = /^([A-Z0-9_]+)_API_KEY$/.exec(name);
58
+ return match?.[1]?.toLowerCase().replace(/_/g, "-");
59
+ }
60
+ }
61
+
62
+ // pkgs/@akanjs/devkit/codeAgent/agent/AkanContextFiles.ts
63
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
64
+ import path2 from "path";
65
+
66
+ class AkanContextFiles {
67
+ static #frontmatter = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
68
+ static extend(base, options) {
69
+ const seen = new Set(base.map((file) => file.path));
70
+ const contents = new Set(base.map((file) => file.content.trim()));
71
+ const added = [];
72
+ for (const file of [...AkanContextFiles.#claude(base), ...AkanContextFiles.#cursor(options)]) {
73
+ if (seen.has(file.path) || contents.has(file.content.trim()) || !file.content.trim())
74
+ continue;
75
+ seen.add(file.path);
76
+ contents.add(file.content.trim());
77
+ added.push(file);
78
+ }
79
+ return [...base, ...added];
80
+ }
81
+ static #claude(base) {
82
+ const files = [];
83
+ for (const file of base) {
84
+ if (!/^AGENTS\.(md|MD)$/.test(path2.basename(file.path)))
85
+ continue;
86
+ for (const name of ["CLAUDE.md", "CLAUDE.MD"]) {
87
+ const candidate = path2.join(path2.dirname(file.path), name);
88
+ const content = AkanContextFiles.#read(candidate);
89
+ if (content !== undefined)
90
+ files.push({ path: candidate, content });
91
+ }
92
+ }
93
+ return files;
94
+ }
95
+ static #cursor(options) {
96
+ const files = [];
97
+ const roots = [...new Set([options.workspaceRoot, options.cwd])];
98
+ for (const root of roots) {
99
+ const legacy = AkanContextFiles.#read(path2.join(root, ".cursorrules"));
100
+ if (legacy !== undefined)
101
+ files.push({ path: path2.join(root, ".cursorrules"), content: legacy });
102
+ for (const file of AkanContextFiles.#mdcFiles(path2.join(root, ".cursor", "rules"))) {
103
+ const raw = AkanContextFiles.#read(file);
104
+ if (raw === undefined)
105
+ continue;
106
+ const body = AkanContextFiles.#applied(raw);
107
+ if (body !== undefined)
108
+ files.push({ path: file, content: body });
109
+ }
110
+ }
111
+ return files;
112
+ }
113
+ static #applied(raw) {
114
+ const match = AkanContextFiles.#frontmatter.exec(raw);
115
+ if (!match)
116
+ return raw;
117
+ if (!/^\s*alwaysApply\s*:\s*true\s*$/m.test(match[1] ?? ""))
118
+ return;
119
+ return raw.slice(match[0].length);
120
+ }
121
+ static #mdcFiles(dir) {
122
+ if (!existsSync2(dir))
123
+ return [];
124
+ try {
125
+ return readdirSync(dir, { recursive: true, withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".mdc")).map((entry) => path2.join(entry.parentPath ?? dir, entry.name)).sort();
126
+ } catch {
127
+ return [];
128
+ }
129
+ }
130
+ static #read(file) {
131
+ try {
132
+ if (!existsSync2(file) || !statSync(file).isFile())
133
+ return;
134
+ return readFileSync2(file, "utf8").replace(/^\uFEFF/, "");
135
+ } catch {
136
+ return;
137
+ }
138
+ }
139
+ }
140
+
141
+ // pkgs/@akanjs/devkit/codeAgent/agent/akanSystemPrompt.ts
142
+ var akanSystemPrompt2 = (profile) => {
143
+ const lines = [
144
+ "You are the akan coding agent, running inside an Akan.js workspace.",
145
+ "",
146
+ "- Prefer an akan workflow to a direct source edit. `list_workflows` / `explain_workflow` / `plan_workflow`, then `apply_workflow({ planPath })`. Editing source by hand is the fallback, not the default.",
147
+ "- Read context with `inspect_akan_context` before reading source bodies; it answers most structural questions without spending the file.",
148
+ "- Fetch the guideline for an area before a deep pass on it: `get_guideline`.",
149
+ "- A question about `akan code` itself \u2014 its slash commands, MCP servers, sessions, models, keys, or where its files live \u2014 is answered by the `akan-code` skill. Read it rather than describing the tool from memory.",
150
+ "- Never hand-edit a generated file (`lib/cnst.ts`, `db.ts`, `st.ts`, any `*/index.ts` barrel). Fix the owning source and run `repair_generated` or `akan sync`."
151
+ ];
152
+ if (profile.tools.builtin.includes("write"))
153
+ lines.push("- After editing, call `akan_verify`. It runs sync, lint, typecheck and the SSR check over what the working tree changed. A turn that edits and does not verify is not finished.");
154
+ else
155
+ lines.push("- This session is read-only: there is no write, edit or bash tool. Report what should change instead of changing it.");
156
+ if (profile.tools.subagent)
157
+ lines.push('- Hand a self-contained search or read to `task` \u2014 `type: "explore"` reads and reports, `type: "code"` makes the change. Only its answer comes back, so give it everything it needs and ask for exactly what you want returned. Do it yourself when the reading is short or when you need the files in front of you.');
158
+ if (profile.ui.canPrompt)
159
+ lines.push("- Ask with `ask_user` when two readings of the request would lead to materially different work, and offer the options you can name. Do not ask for a choice that has an obvious default, and do not ask permission for work you were already asked to do \u2014 make the call, say the assumption, and carry on.");
160
+ if (profile.approval !== "never")
161
+ lines.push(`- Some tool calls need the user's approval (policy: ${profile.approval}). A refusal is an answer; do not retry it a different way.`);
162
+ lines.push("- Secrets are refused at the tool boundary. If you need a value from `.env`, ask the user for it rather than looking for another way to read the file.", "", "## How to answer", "", "- Answer in as few words as carry the answer. A short question gets one sentence with no heading.", "- Do not open with what you are about to do, and do not close with a summary of what you did. The tool calls are already on the user's screen.", "- Skip headings, bold labels and bullet lists unless the answer is genuinely a list. Prose is shorter.", "- State a file as `path:line`. Do not paste a file back that the user can open.", "- Say what you could not do, and why, in one line. Do not offer alternatives nobody asked for.");
163
+ return lines.join(`
164
+ `);
165
+ };
166
+
167
+ // pkgs/@akanjs/devkit/codeAgent/agent/CodeSessionIndex.ts
168
+ import { existsSync as existsSync3, readdirSync as readdirSync2, statSync as statSync3 } from "fs";
169
+ import path3 from "path";
170
+
171
+ // pkgs/@akanjs/devkit/codeAgent/agent/jsonLines.ts
172
+ import { closeSync, openSync, readFileSync as readFileSync3, readSync, statSync as statSync2 } from "fs";
173
+ var readHead = (file, maxBytes) => {
174
+ const fd = openSync(file, "r");
175
+ try {
176
+ const head = Buffer.alloc(maxBytes);
177
+ return head.subarray(0, readSync(fd, head, 0, maxBytes, 0)).toString("utf8");
178
+ } finally {
179
+ closeSync(fd);
180
+ }
181
+ };
182
+ var readJsonLines = (file, maxBytes) => {
183
+ try {
184
+ const text = maxBytes !== undefined && statSync2(file).size > maxBytes ? readHead(file, maxBytes) : readFileSync3(file, "utf8");
185
+ return text.split(`
186
+ `).filter((line) => !!line.trim());
187
+ } catch {
188
+ return [];
189
+ }
190
+ };
191
+ var parseJsonLine = (line) => {
192
+ try {
193
+ return JSON.parse(line);
194
+ } catch {
195
+ return;
196
+ }
197
+ };
198
+
199
+ // pkgs/@akanjs/devkit/codeAgent/agent/CodeSessionIndex.ts
200
+ class CodeSessionIndex2 {
201
+ static readLimit = 512 * 1024;
202
+ static list(dir, limit = 30) {
203
+ if (!existsSync3(dir))
204
+ return [];
205
+ const files = readdirSync2(dir).filter((entry) => entry.endsWith(".jsonl")).map((entry) => path3.join(dir, entry)).map((file) => ({ file, updatedAt: CodeSessionIndex2.#mtime(file) })).sort((left, right) => right.updatedAt - left.updatedAt).slice(0, limit);
206
+ return files.map((entry) => CodeSessionIndex2.#read(entry.file, entry.updatedAt)).filter((entry) => !!entry);
207
+ }
208
+ static fileOf(dir, id) {
209
+ if (!existsSync3(dir))
210
+ return;
211
+ const match = readdirSync2(dir).find((entry) => entry.endsWith(`${id}.jsonl`) || entry === `${id}.jsonl`);
212
+ return match ? path3.join(dir, match) : undefined;
213
+ }
214
+ static #mtime(file) {
215
+ try {
216
+ return statSync3(file).mtimeMs;
217
+ } catch {
218
+ return 0;
219
+ }
220
+ }
221
+ static #read(file, updatedAt) {
222
+ const lines = readJsonLines(file, CodeSessionIndex2.readLimit);
223
+ if (!lines.length)
224
+ return;
225
+ const entry = { id: "", file, name: undefined, opening: "", updatedAt, turns: 0 };
226
+ for (const line of lines) {
227
+ const record = parseJsonLine(line);
228
+ if (!record)
229
+ continue;
230
+ if (record.type === "session" && typeof record.id === "string")
231
+ entry.id = record.id;
232
+ if (record.type === "session_info" && typeof record.name === "string")
233
+ entry.name = record.name;
234
+ if (record.type !== "message")
235
+ continue;
236
+ const message = record.message;
237
+ if (message?.role !== "user")
238
+ continue;
239
+ entry.turns += 1;
240
+ if (!entry.opening)
241
+ entry.opening = CodeSessionIndex2.#text(message.content);
242
+ }
243
+ return entry.id ? entry : undefined;
244
+ }
245
+ static #text(content) {
246
+ const text = typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => typeof part === "object" && part && ("text" in part) ? String(part.text) : "").join(" ") : "";
247
+ return text.replace(/\s+/g, " ").trim();
248
+ }
249
+ }
250
+
251
+ // pkgs/@akanjs/devkit/codeAgent/agent/AkanCodeServices.ts
252
+ class AkanCodeServices2 {
253
+ static settings() {
254
+ return SettingsManager.inMemory({
255
+ enableInstallTelemetry: false,
256
+ enableAnalytics: false,
257
+ quietStartup: true,
258
+ compaction: { reserveTokens: AkanCodeCompaction2.reserveFloor }
259
+ });
260
+ }
261
+ static async runtime(workspaceRoot, proxy = null) {
262
+ mkdirSync(akanCodePaths2.globalDir(), { recursive: true, mode: 448 });
263
+ const runtime = await ModelRuntime.create({
264
+ authPath: proxy ? path4.join(mkdtempSync(path4.join(tmpdir(), "akan-code-proxy-")), "auth.json") : akanCodePaths2.authFile(),
265
+ modelsPath: akanCodePaths2.modelsFile()
266
+ });
267
+ if (proxy)
268
+ await proxy.apply(runtime);
269
+ else
270
+ await AkanEnvKeys2.apply(runtime, workspaceRoot);
271
+ return runtime;
272
+ }
273
+ static sessions(workspaceRoot, cwd, profile, resume) {
274
+ if (profile.session.store === "memory")
275
+ return SessionManager.inMemory(cwd);
276
+ const dir = akanCodePaths2.sessionsDir(workspaceRoot);
277
+ mkdirSync(dir, { recursive: true });
278
+ const manager = SessionManager.create(cwd, dir);
279
+ if (!resume)
280
+ return manager;
281
+ const file = CodeSessionIndex2.fileOf(dir, resume);
282
+ if (!file)
283
+ throw new Error(`No stored session ${resume} in ${dir}`);
284
+ manager.setSessionFile(file);
285
+ return manager;
286
+ }
287
+ static #skillPaths(profile, workspaceRoot) {
288
+ if (!profile.context.skills)
289
+ return [];
290
+ const dirs = [
291
+ akanCodePaths2.builtinSkillsDir(),
292
+ akanCodePaths2.globalSkillsDir(),
293
+ akanCodePaths2.skillsDir(workspaceRoot)
294
+ ];
295
+ return dirs.filter((dir) => !!dir && existsSync4(dir));
296
+ }
297
+ static resourceOptions(options) {
298
+ const present = (dir) => existsSync4(dir) ? [dir] : [];
299
+ return {
300
+ noExtensions: true,
301
+ noSkills: true,
302
+ noPromptTemplates: true,
303
+ noThemes: true,
304
+ noContextFiles: !options.profile.context.projectFiles,
305
+ agentsFilesOverride: ({ agentsFiles }) => ({
306
+ agentsFiles: options.profile.context.projectFiles ? AkanContextFiles.extend(agentsFiles, { workspaceRoot: options.workspaceRoot, cwd: options.cwd }) : agentsFiles
307
+ }),
308
+ appendSystemPrompt: [akanSystemPrompt2(options.profile)],
309
+ additionalExtensionPaths: present(akanCodePaths2.extensionsDir(options.workspaceRoot)),
310
+ additionalSkillPaths: AkanCodeServices2.#skillPaths(options.profile, options.workspaceRoot),
311
+ extensionFactories: options.extensions
312
+ };
313
+ }
314
+ }
315
+
18
316
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeAgent.ts
19
317
  import {
20
318
  createAgentSession,
21
319
  DefaultResourceLoader
22
320
  } from "@earendil-works/pi-coding-agent";
23
321
  import {
24
- codeAgentClip as codeAgentClip5,
322
+ codeAgentClip as codeAgentClip6,
25
323
  codeAgentLabelChars as codeAgentLabelChars2,
26
324
  codeAgentRenderAnswer,
27
325
  codeAgentSessionName as codeAgentSessionName2
28
326
  } from "akanjs/common";
29
327
 
30
328
  // pkgs/@akanjs/devkit/codeAgent/feedback/DevLogFeedback.ts
31
- import { existsSync, statSync } from "fs";
32
- import path from "path";
329
+ import { existsSync as existsSync5, statSync as statSync4 } from "fs";
330
+ import path5 from "path";
33
331
 
34
332
  // pkgs/@akanjs/devkit/codeAgent/tools/AkanEditScope.ts
35
333
  var empty = { paths: [], apps: [], libs: [], needsSync: false, touchesTsx: false };
@@ -85,7 +383,7 @@ class DevLogFeedback2 {
85
383
  this.#baseline = await AkanEditScope2.baseline(this.#options.cwd);
86
384
  }
87
385
  async observe() {
88
- const running = this.#options.apps.filter((app) => existsSync(this.#pathOf(app)));
386
+ const running = this.#options.apps.filter((app) => existsSync5(this.#pathOf(app)));
89
387
  if (!running.length)
90
388
  return;
91
389
  if (!(await AkanEditScope2.since(this.#options.cwd, this.#baseline)).paths.length)
@@ -106,7 +404,7 @@ ${lines.join(`
106
404
  return [
107
405
  "The dev server logged errors after your edits:",
108
406
  ...reports,
109
- `Full log: ${running.map((app) => path.relative(this.#options.workspaceRoot, this.#pathOf(app))).join(", ")}`
407
+ `Full log: ${running.map((app) => path5.relative(this.#options.workspaceRoot, this.#pathOf(app))).join(", ")}`
110
408
  ].join(`
111
409
 
112
410
  `);
@@ -123,24 +421,24 @@ ${lines.join(`
123
421
  `).filter((line) => errorPattern.test(line) && !noisePattern.test(line)).slice(-this.#options.maxLines);
124
422
  }
125
423
  #pathOf(app) {
126
- return path.join(this.#options.workspaceRoot, "local", "apps", app, "runtime", "dev.log");
424
+ return path5.join(this.#options.workspaceRoot, "local", "apps", app, "runtime", "dev.log");
127
425
  }
128
426
  static async previewUrl(workspaceRoot, app) {
129
- const file = path.join(workspaceRoot, "local", "apps", app, "runtime", "dev.log");
130
- if (!existsSync(file))
427
+ const file = path5.join(workspaceRoot, "local", "apps", app, "runtime", "dev.log");
428
+ if (!existsSync5(file))
131
429
  return;
132
430
  const matches = [...(await Bun.file(file).text()).matchAll(/ready \(pid=\d+\) \u2014 (https?:\/\/\S+)/g)];
133
431
  return matches.at(-1)?.[1];
134
432
  }
135
433
  static #sizeOf(file) {
136
- return existsSync(file) ? statSync(file).size : 0;
434
+ return existsSync5(file) ? statSync4(file).size : 0;
137
435
  }
138
436
  }
139
437
 
140
438
  // pkgs/@akanjs/devkit/codeAgent/feedback/PreviewChrome.ts
141
- import { existsSync as existsSync2 } from "fs";
439
+ import { existsSync as existsSync6 } from "fs";
142
440
  import { homedir } from "os";
143
- import path2 from "path";
441
+ import path6 from "path";
144
442
 
145
443
  class PreviewChrome2 {
146
444
  static pathEnvKey = "BUN_CHROME_PATH";
@@ -178,10 +476,10 @@ class PreviewChrome2 {
178
476
  }
179
477
  if (process.platform !== "darwin")
180
478
  return;
181
- for (const root of ["/Applications", path2.join(homedir(), "Applications")])
479
+ for (const root of ["/Applications", path6.join(homedir(), "Applications")])
182
480
  for (const bundle of PreviewChrome2.appBundles) {
183
- const candidate = path2.join(root, bundle);
184
- if (existsSync2(candidate))
481
+ const candidate = path6.join(root, bundle);
482
+ if (existsSync6(candidate))
185
483
  return candidate;
186
484
  }
187
485
  return;
@@ -388,8 +686,8 @@ class TurnFeedback2 {
388
686
  }
389
687
 
390
688
  // pkgs/@akanjs/devkit/codeAgent/tools/AkanCli.ts
391
- import { existsSync as existsSync3 } from "fs";
392
- import path3 from "path";
689
+ import { existsSync as existsSync7 } from "fs";
690
+ import path7 from "path";
393
691
  class AkanCli2 {
394
692
  #cwd;
395
693
  #entry;
@@ -416,10 +714,10 @@ class AkanCli2 {
416
714
  }
417
715
  static #resolveEntry() {
418
716
  const candidates = [
419
- path3.join(import.meta.dir, "index.js"),
420
- path3.join(import.meta.dir, "..", "..", "..", "cli", "index.ts")
717
+ path7.join(import.meta.dir, "index.js"),
718
+ path7.join(import.meta.dir, "..", "..", "..", "cli", "index.ts")
421
719
  ];
422
- return candidates.find((candidate) => existsSync3(candidate)) ?? Bun.main;
720
+ return candidates.find((candidate) => existsSync7(candidate)) ?? Bun.main;
423
721
  }
424
722
  }
425
723
 
@@ -511,7 +809,7 @@ class AkanToolPack2 {
511
809
  return { name: "akan-tools", factory: async (pi) => await this.#register(pi) };
512
810
  }
513
811
  async names() {
514
- const { ContextRunner } = await import("./context.runner-hnh0rg7m.js");
812
+ const { ContextRunner } = await import("./context.runner-bxzv0gm0.js");
515
813
  return [
516
814
  ...new ContextRunner().listMcpTools(this.#options.mode).map((tool) => tool.name),
517
815
  AkanToolPack2.verifyToolName
@@ -519,7 +817,7 @@ class AkanToolPack2 {
519
817
  }
520
818
  static verifyToolName = "akan_verify";
521
819
  async#register(pi) {
522
- const { ContextRunner } = await import("./context.runner-hnh0rg7m.js");
820
+ const { ContextRunner } = await import("./context.runner-bxzv0gm0.js");
523
821
  const runner = new ContextRunner;
524
822
  const guidelineNames = await AkanToolPack2.#guidelineNames();
525
823
  for (const tool of runner.listMcpTools(this.#options.mode, { guidelineNames })) {
@@ -1027,7 +1325,7 @@ class McpToolPack2 {
1027
1325
  }
1028
1326
 
1029
1327
  // pkgs/@akanjs/devkit/codeAgent/tools/SessionToolPack.ts
1030
- import { SessionManager } from "@earendil-works/pi-coding-agent";
1328
+ import { SessionManager as SessionManager2 } from "@earendil-works/pi-coding-agent";
1031
1329
  import { codeAgentClip as codeAgentClip2, codeAgentOutputChars as codeAgentOutputChars2 } from "akanjs/common";
1032
1330
  import { Type as Type5 } from "typebox";
1033
1331
  var snippetChars = 240;
@@ -1093,16 +1391,125 @@ class SessionToolPack2 {
1093
1391
  return codeAgentClip2(session.allMessagesText, codeAgentOutputChars2 * 2);
1094
1392
  }
1095
1393
  async#list() {
1096
- return await SessionManager.list(this.#options.cwd, akanCodePaths2.sessionsDir(this.#options.workspaceRoot));
1394
+ return await SessionManager2.list(this.#options.cwd, akanCodePaths2.sessionsDir(this.#options.workspaceRoot));
1097
1395
  }
1098
1396
  }
1099
1397
 
1100
1398
  // pkgs/@akanjs/devkit/codeAgent/tools/WebToolPack.ts
1101
- import { codeAgentClip as codeAgentClip3, codeAgentOutputChars as codeAgentOutputChars3 } from "akanjs/common";
1399
+ import { isIP } from "net";
1400
+ import { codeAgentClip as codeAgentClip4 } from "akanjs/common";
1102
1401
  import { Type as Type6 } from "typebox";
1103
1402
 
1403
+ // pkgs/@akanjs/devkit/codeAgent/tools/DeepSeekWeb.ts
1404
+ import { codeAgentClip as codeAgentClip3 } from "akanjs/common";
1405
+
1406
+ class DeepSeekWeb {
1407
+ static baseUrl = "https://api.deepseek.com/v1/tools";
1408
+ static maxQueryChars = 128;
1409
+ static summaryChars = 600;
1410
+ static #retryable = {
1411
+ search: [408, 429, 500, 502, 503, 504],
1412
+ fetch: [429]
1413
+ };
1414
+ static #attempts = 3;
1415
+ #key;
1416
+ #baseUrl;
1417
+ constructor(key, baseUrl = DeepSeekWeb.baseUrl) {
1418
+ this.#key = key;
1419
+ this.#baseUrl = baseUrl;
1420
+ }
1421
+ static async fromRuntime(runtime, proxied) {
1422
+ if (proxied)
1423
+ return;
1424
+ const key = (await runtime.getAuth("deepseek").catch(() => {
1425
+ return;
1426
+ }))?.auth.apiKey;
1427
+ return key ? new DeepSeekWeb(key) : undefined;
1428
+ }
1429
+ async search(query, count, signal) {
1430
+ if ([...query].length > DeepSeekWeb.maxQueryChars)
1431
+ throw new Error(`DeepSeek search takes at most ${DeepSeekWeb.maxQueryChars} characters; shorten the query.`);
1432
+ const body = await this.#call("search", { query, count }, 20000, signal);
1433
+ const byUrl = new Map;
1434
+ for (const result of body.results ?? []) {
1435
+ const url = DeepSeekWeb.#cleanUrl(result.url);
1436
+ if (!byUrl.has(url))
1437
+ byUrl.set(url, { ...result, url });
1438
+ }
1439
+ return [...byUrl.values()].map((result) => {
1440
+ const date = result.date ? ` \xB7 ${result.date.slice(0, 10)}` : "";
1441
+ const summary = DeepSeekWeb.unescape(result.summary || result.snippet || "").replace(/\s+/g, " ").trim();
1442
+ return `- ${result.title}
1443
+ ${result.url}${date}
1444
+ ${codeAgentClip3(summary, DeepSeekWeb.summaryChars)}`;
1445
+ }).join(`
1446
+ `);
1447
+ }
1448
+ async fetch(url, signal) {
1449
+ const page = await this.#call("fetch", { url }, 30000, signal);
1450
+ return { title: page.title ?? "", content: DeepSeekWeb.unescape(page.content ?? "") };
1451
+ }
1452
+ static unescape(markdown) {
1453
+ const escaped = markdown.match(/\\[!-/:-@[-`{-~]/g)?.length ?? 0;
1454
+ const punctuation = markdown.match(/[!-/:-@[-`{-~]/g)?.length ?? 0;
1455
+ if (!escaped || escaped < (punctuation - escaped) / 2)
1456
+ return markdown;
1457
+ return markdown.replace(/\\([!-/:-@[-`{-~])/g, "$1");
1458
+ }
1459
+ async#call(tool, body, timeoutMs, signal) {
1460
+ for (let attempt = 1;; attempt++) {
1461
+ const response = await fetch(`${this.#baseUrl}/${tool}`, {
1462
+ method: "POST",
1463
+ headers: { authorization: `Bearer ${this.#key}`, "content-type": "application/json" },
1464
+ body: JSON.stringify(body),
1465
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs)
1466
+ });
1467
+ if (response.ok)
1468
+ return await response.json();
1469
+ if (!DeepSeekWeb.#retryable[tool].includes(response.status) || attempt >= DeepSeekWeb.#attempts) {
1470
+ const error = await response.json().catch(() => {
1471
+ return;
1472
+ });
1473
+ throw new Error(`DeepSeek ${tool} answered ${response.status}: ${error?.error?.message ?? response.statusText}`);
1474
+ }
1475
+ await DeepSeekWeb.#wait(DeepSeekWeb.#delayOf(response, attempt), signal);
1476
+ }
1477
+ }
1478
+ static #delayOf(response, attempt) {
1479
+ const retryAfter = Number(response.headers.get("retry-after"));
1480
+ return Number.isFinite(retryAfter) && retryAfter > 0 ? Math.min(retryAfter, 5) * 1000 : 1000 * 2 ** (attempt - 1);
1481
+ }
1482
+ static #wait(ms, signal) {
1483
+ return new Promise((resolve) => {
1484
+ const timer = setTimeout(resolve, ms);
1485
+ signal?.addEventListener("abort", () => {
1486
+ clearTimeout(timer);
1487
+ resolve();
1488
+ }, { once: true });
1489
+ });
1490
+ }
1491
+ static #cleanUrl(url) {
1492
+ try {
1493
+ const parsed = new URL(url);
1494
+ for (const name of [...parsed.searchParams.keys()])
1495
+ if (name.startsWith("utm_"))
1496
+ parsed.searchParams.delete(name);
1497
+ if (/^#\d+$/.test(parsed.hash))
1498
+ parsed.hash = "";
1499
+ return parsed.toString();
1500
+ } catch {
1501
+ return url;
1502
+ }
1503
+ }
1504
+ }
1505
+
1506
+ // pkgs/@akanjs/devkit/codeAgent/tools/WebToolPack.ts
1104
1507
  class WebToolPack2 {
1508
+ static pageChars = 1e4;
1509
+ static maxResults = 10;
1510
+ static #rememberedPages = 16;
1105
1511
  #options;
1512
+ #pages = new Map;
1106
1513
  constructor(options) {
1107
1514
  this.#options = options;
1108
1515
  }
@@ -1141,7 +1548,7 @@ class WebToolPack2 {
1141
1548
  const names = [];
1142
1549
  if (this.#options.profile.tools.web.fetch)
1143
1550
  names.push("web_fetch");
1144
- if (this.#options.profile.tools.web.search && WebToolPack2.#provider())
1551
+ if (this.#options.profile.tools.web.search && this.#searcher())
1145
1552
  names.push("web_search");
1146
1553
  return names;
1147
1554
  }
@@ -1153,461 +1560,230 @@ class WebToolPack2 {
1153
1560
  #register(pi) {
1154
1561
  if (this.#options.profile.tools.web.fetch)
1155
1562
  this.#registerFetch(pi);
1156
- const provider = WebToolPack2.#provider();
1157
- if (this.#options.profile.tools.web.search && provider)
1158
- this.#registerSearch(pi, provider);
1563
+ const searcher = this.#searcher();
1564
+ if (this.#options.profile.tools.web.search && searcher)
1565
+ this.#registerSearch(pi, searcher);
1159
1566
  }
1160
1567
  #registerFetch(pi) {
1161
1568
  const allowHosts = this.#options.profile.network.allowHosts;
1162
1569
  pi.registerTool({
1163
1570
  name: "web_fetch",
1164
1571
  label: "Fetch",
1165
- description: "Fetch a URL and return its readable text. HTML is stripped to text; the result is truncated.",
1572
+ description: `Fetch a URL and return its readable text, ${WebToolPack2.pageChars} characters at a time. A longer page ends with the offset that continues it.`,
1166
1573
  promptSnippet: "web_fetch: fetch a URL and return its readable text",
1167
- parameters: Type6.Object({ url: Type6.String({ description: "Absolute http(s) URL." }) }),
1168
- execute: async (_id, params) => {
1574
+ parameters: Type6.Object({
1575
+ url: Type6.String({ description: "Absolute http(s) URL." }),
1576
+ offset: Type6.Optional(Type6.Number({ description: "Character offset a previous call named, to read on." }))
1577
+ }),
1578
+ execute: async (_id, params, signal) => {
1169
1579
  const refusal = WebToolPack2.#refuseHost(params.url, allowHosts);
1170
1580
  if (refusal)
1171
1581
  return { content: [{ type: "text", text: refusal }], details: undefined, isError: true };
1172
- const response = await fetch(params.url, { signal: AbortSignal.timeout(20000), redirect: "follow" });
1173
- const text = WebToolPack2.#readable(await response.text());
1582
+ const offset = Math.max(0, Math.floor(params.offset ?? 0));
1583
+ const cached = offset ? this.#pages.get(params.url) : undefined;
1584
+ const page = cached ?? await this.#read(params.url, signal);
1585
+ if (page.ok)
1586
+ this.#remember(params.url, page);
1174
1587
  return {
1175
- content: [
1176
- { type: "text", text: `${response.status} ${params.url}
1177
-
1178
- ${codeAgentClip3(text, codeAgentOutputChars3)}` }
1179
- ],
1588
+ content: [{ type: "text", text: WebToolPack2.#window(params.url, page, offset) }],
1180
1589
  details: undefined,
1181
- isError: !response.ok
1590
+ isError: !page.ok
1182
1591
  };
1183
1592
  }
1184
1593
  });
1185
1594
  }
1186
- #registerSearch(pi, provider) {
1595
+ #registerSearch(pi, searcher) {
1596
+ const hint = searcher.name === "deepseek" ? ` Keep the query within ${DeepSeekWeb.maxQueryChars} characters; site:, -site: and quoted phrases work.` : "";
1187
1597
  pi.registerTool({
1188
1598
  name: "web_search",
1189
1599
  label: "Search",
1190
- description: `Search the web (${provider.name}) and return the top results as title, url and snippet.`,
1600
+ description: `Search the web (${searcher.name}) and return the top results as title, url and summary.${hint}`,
1191
1601
  promptSnippet: "web_search: search the web and return titles, urls and snippets",
1192
1602
  parameters: Type6.Object({
1193
1603
  query: Type6.String(),
1194
- count: Type6.Optional(Type6.Number({ description: "How many results, default 5." }))
1604
+ count: Type6.Optional(Type6.Number({ description: `How many results, default 5, at most ${WebToolPack2.maxResults}.` }))
1195
1605
  }),
1196
- execute: async (_id, params) => {
1197
- const key = process.env[provider.envKey] ?? "";
1198
- const results = await provider.search(key, params.query, params.count ?? 5);
1199
- return {
1200
- content: [{ type: "text", text: codeAgentClip3(results || "(no results)", codeAgentOutputChars3) }],
1201
- details: undefined
1202
- };
1606
+ execute: async (_id, params, signal) => {
1607
+ const count = Math.min(Math.max(Math.round(params.count ?? 5), 1), WebToolPack2.maxResults);
1608
+ try {
1609
+ const results = await searcher.search(params.query, count, signal);
1610
+ return {
1611
+ content: [{ type: "text", text: codeAgentClip4(results || "(no results)", WebToolPack2.pageChars) }],
1612
+ details: undefined
1613
+ };
1614
+ } catch (error) {
1615
+ const message = error instanceof Error ? error.message : String(error);
1616
+ return { content: [{ type: "text", text: message }], details: undefined, isError: true };
1617
+ }
1203
1618
  }
1204
1619
  });
1205
1620
  }
1206
- static #provider() {
1207
- return WebToolPack2.providers.find((provider) => !!process.env[provider.envKey]);
1208
- }
1209
- static #refuseHost(url, allowHosts) {
1210
- let parsed;
1211
- try {
1212
- parsed = new URL(url);
1213
- } catch {
1214
- return `"${url}" is not an absolute URL.`;
1215
- }
1216
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
1217
- return "Only http and https URLs can be fetched.";
1218
- if (allowHosts?.length && !allowHosts.includes(parsed.hostname))
1219
- return `This profile only allows ${allowHosts.join(", ")}.`;
1220
- return;
1221
- }
1222
- static #readable(html) {
1223
- return html.replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<[^>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/[ \t]+/g, " ").replace(/\n{3,}/g, `
1224
-
1225
- `).trim();
1226
- }
1227
- }
1228
-
1229
- // pkgs/@akanjs/devkit/codeAgent/tools/AkanCodePlugins.ts
1230
- class AkanCodePlugins2 {
1231
- static async build(options) {
1232
- const mcp = await McpToolPack2.connect({
1233
- workspaceRoot: options.workspace.workspaceRoot,
1234
- profile: options.profile,
1235
- ...options.onNotice ? { onNotice: options.onNotice } : {}
1236
- });
1237
- const dispose = () => mcp?.close();
1238
- const mcpStatus = mcp?.status ?? [];
1239
- const web = new WebToolPack2({ profile: options.profile });
1240
- const sessions = new SessionToolPack2({
1241
- workspaceRoot: options.workspace.workspaceRoot,
1242
- cwd: options.cwd,
1243
- profile: options.profile,
1244
- currentSessionId: options.currentSessionId
1245
- });
1246
- const mail = new MailToolPack2({ profile: options.profile, mailbox: options.mailbox });
1247
- const onAsk = options.onAsk;
1248
- const ask = onAsk ? new AskToolPack({ profile: options.profile, ask: onAsk }) : undefined;
1249
- const shared = {
1250
- extensions: [mcp?.extension(), web.extension(), sessions.extension(), mail.extension(), ask?.extension()].filter((entry) => !!entry),
1251
- toolNames: [
1252
- ...mcp?.toolNames ?? [],
1253
- ...web.names(),
1254
- ...sessions.names(),
1255
- ...mail.names(),
1256
- ...ask?.names() ?? []
1257
- ]
1258
- };
1259
- if (!options.profile.tools.akan)
1260
- return { ...shared, mcp: mcpStatus, dispose };
1261
- const readOnly = !options.profile.tools.builtin.includes("write");
1262
- const pack = new AkanToolPack2({
1263
- workspace: options.workspace,
1264
- cwd: options.cwd,
1265
- mode: readOnly ? "readonly" : "apply"
1266
- });
1267
- const extensions = [pack.extension()];
1268
- const subagent = SubagentPool2.extensionFor({
1269
- workspace: options.workspace,
1270
- cwd: options.cwd,
1271
- parent: options.profile,
1272
- depth: options.depth,
1273
- ...options.subagentSpend ? { spend: options.subagentSpend } : {},
1274
- ...options.onNotice ? { onNotice: options.onNotice } : {},
1275
- ...options.onAgents ? { onAgents: options.onAgents } : {}
1276
- });
1277
- if (subagent)
1278
- extensions.push(subagent);
1279
- const sources = await AkanCodePlugins2.#sources(options, readOnly);
1280
- if (sources.length)
1281
- extensions.push(new TurnFeedback2(sources, {
1282
- budget: options.profile.limits.feedback,
1283
- ...options.onNotice ? { onNotice: options.onNotice } : {}
1284
- }).extension());
1285
- return {
1286
- extensions: [...extensions, ...shared.extensions],
1287
- toolNames: [...await pack.names(), ...subagent ? [SubagentPool2.toolName] : [], ...shared.toolNames],
1288
- mcp: mcpStatus,
1289
- dispose
1290
- };
1291
- }
1292
- static async#sources(options, readOnly) {
1293
- const sources = [
1294
- new VerifyFeedback2({ cwd: options.cwd, readOnly }),
1295
- new DevLogFeedback2({ workspaceRoot: options.workspace.workspaceRoot, cwd: options.cwd, apps: options.apps })
1296
- ];
1297
- const previewUrl = await AkanCodePlugins2.#previewUrl(options);
1298
- if (!previewUrl)
1299
- return sources;
1300
- const reason = PreviewView2.unavailableReason();
1301
- if (reason)
1302
- options.onNotice?.(reason);
1303
- else
1304
- sources.push(new PreviewFeedback2({ cwd: options.cwd, previewUrl, canSeeImages: options.canSeeImages }));
1305
- return sources;
1306
- }
1307
- static async#previewUrl(options) {
1308
- for (const app of options.apps) {
1309
- const url = await DevLogFeedback2.previewUrl(options.workspace.workspaceRoot, app);
1310
- if (url)
1311
- return url;
1312
- }
1313
- return;
1314
- }
1315
- }
1316
-
1317
- // pkgs/@akanjs/devkit/codeAgent/agent/AkanCodeServices.ts
1318
- import { existsSync as existsSync7, mkdirSync, mkdtempSync } from "fs";
1319
- import { tmpdir } from "os";
1320
- import path7 from "path";
1321
- import { ModelRuntime, SessionManager as SessionManager2, SettingsManager } from "@earendil-works/pi-coding-agent";
1322
-
1323
- // pkgs/@akanjs/devkit/codeAgent/tools/AkanEnvKeys.ts
1324
- import { existsSync as existsSync4, readFileSync } from "fs";
1325
- import path4 from "path";
1326
-
1327
- class AkanEnvKeys2 {
1328
- static async apply(runtime, workspaceRoot) {
1329
- const applied = [];
1330
- for (const [name, value] of AkanEnvKeys2.#entries(workspaceRoot)) {
1331
- const provider = AkanEnvKeys2.#providerOf(name);
1332
- if (!provider || !value)
1333
- continue;
1334
- await runtime.setRuntimeApiKey(provider, value);
1335
- applied.push(provider);
1336
- }
1337
- return applied;
1338
- }
1339
- static #entries(workspaceRoot) {
1340
- const fromProcess = Object.entries(process.env).filter(([, value]) => typeof value === "string");
1341
- const file = path4.join(workspaceRoot, ".env");
1342
- if (!existsSync4(file))
1343
- return fromProcess;
1344
- const fromFile = readFileSync(file, "utf8").split(`
1345
- `).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && line.includes("=")).map((line) => {
1346
- const index = line.indexOf("=");
1347
- return [
1348
- line.slice(0, index).trim(),
1349
- line.slice(index + 1).trim().replace(/^["']|["']$/g, "")
1350
- ];
1351
- });
1352
- return [...fromFile, ...fromProcess];
1353
- }
1354
- static #providerOf(name) {
1355
- const match = /^([A-Z0-9_]+)_API_KEY$/.exec(name);
1356
- return match?.[1]?.toLowerCase().replace(/_/g, "-");
1357
- }
1358
- }
1359
-
1360
- // pkgs/@akanjs/devkit/codeAgent/agent/AkanContextFiles.ts
1361
- import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
1362
- import path5 from "path";
1363
-
1364
- class AkanContextFiles {
1365
- static #frontmatter = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
1366
- static extend(base, options) {
1367
- const seen = new Set(base.map((file) => file.path));
1368
- const contents = new Set(base.map((file) => file.content.trim()));
1369
- const added = [];
1370
- for (const file of [...AkanContextFiles.#claude(base), ...AkanContextFiles.#cursor(options)]) {
1371
- if (seen.has(file.path) || contents.has(file.content.trim()) || !file.content.trim())
1372
- continue;
1373
- seen.add(file.path);
1374
- contents.add(file.content.trim());
1375
- added.push(file);
1376
- }
1377
- return [...base, ...added];
1378
- }
1379
- static #claude(base) {
1380
- const files = [];
1381
- for (const file of base) {
1382
- if (!/^AGENTS\.(md|MD)$/.test(path5.basename(file.path)))
1383
- continue;
1384
- for (const name of ["CLAUDE.md", "CLAUDE.MD"]) {
1385
- const candidate = path5.join(path5.dirname(file.path), name);
1386
- const content = AkanContextFiles.#read(candidate);
1387
- if (content !== undefined)
1388
- files.push({ path: candidate, content });
1389
- }
1390
- }
1391
- return files;
1392
- }
1393
- static #cursor(options) {
1394
- const files = [];
1395
- const roots = [...new Set([options.workspaceRoot, options.cwd])];
1396
- for (const root of roots) {
1397
- const legacy = AkanContextFiles.#read(path5.join(root, ".cursorrules"));
1398
- if (legacy !== undefined)
1399
- files.push({ path: path5.join(root, ".cursorrules"), content: legacy });
1400
- for (const file of AkanContextFiles.#mdcFiles(path5.join(root, ".cursor", "rules"))) {
1401
- const raw = AkanContextFiles.#read(file);
1402
- if (raw === undefined)
1403
- continue;
1404
- const body = AkanContextFiles.#applied(raw);
1405
- if (body !== undefined)
1406
- files.push({ path: file, content: body });
1407
- }
1408
- }
1409
- return files;
1410
- }
1411
- static #applied(raw) {
1412
- const match = AkanContextFiles.#frontmatter.exec(raw);
1413
- if (!match)
1414
- return raw;
1415
- if (!/^\s*alwaysApply\s*:\s*true\s*$/m.test(match[1] ?? ""))
1416
- return;
1417
- return raw.slice(match[0].length);
1418
- }
1419
- static #mdcFiles(dir) {
1420
- if (!existsSync5(dir))
1421
- return [];
1422
- try {
1423
- return readdirSync(dir, { recursive: true, withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".mdc")).map((entry) => path5.join(entry.parentPath ?? dir, entry.name)).sort();
1424
- } catch {
1425
- return [];
1426
- }
1621
+ #searcher() {
1622
+ const provider = WebToolPack2.providers.find((entry) => !!process.env[entry.envKey]);
1623
+ if (provider)
1624
+ return {
1625
+ name: provider.name,
1626
+ search: (query, count) => provider.search(process.env[provider.envKey] ?? "", query, count)
1627
+ };
1628
+ const deepseek = this.#options.deepseek;
1629
+ return deepseek ? { name: "deepseek", search: (query, count, signal) => deepseek.search(query, count, signal) } : undefined;
1427
1630
  }
1428
- static #read(file) {
1429
- try {
1430
- if (!existsSync5(file) || !statSync2(file).isFile())
1431
- return;
1432
- return readFileSync2(file, "utf8").replace(/^\uFEFF/, "");
1433
- } catch {
1434
- return;
1631
+ async#read(url, signal) {
1632
+ const deepseek = this.#options.deepseek;
1633
+ if (deepseek && !WebToolPack2.#isLocal(new URL(url).hostname)) {
1634
+ try {
1635
+ const page = await deepseek.fetch(url, signal);
1636
+ return { status: 200, ok: true, title: page.title, text: page.content };
1637
+ } catch (error) {
1638
+ if (signal?.aborted)
1639
+ throw error;
1640
+ }
1435
1641
  }
1642
+ const response = await fetch(url, {
1643
+ signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(20000)]) : AbortSignal.timeout(20000),
1644
+ redirect: "follow"
1645
+ });
1646
+ return { status: response.status, ok: response.ok, title: "", text: WebToolPack2.#readable(await response.text()) };
1436
1647
  }
1437
- }
1438
-
1439
- // pkgs/@akanjs/devkit/codeAgent/agent/akanSystemPrompt.ts
1440
- var akanSystemPrompt2 = (profile) => {
1441
- const lines = [
1442
- "You are the akan coding agent, running inside an Akan.js workspace.",
1443
- "",
1444
- "- Prefer an akan workflow to a direct source edit. `list_workflows` / `explain_workflow` / `plan_workflow`, then `apply_workflow({ planPath })`. Editing source by hand is the fallback, not the default.",
1445
- "- Read context with `inspect_akan_context` before reading source bodies; it answers most structural questions without spending the file.",
1446
- "- Fetch the guideline for an area before a deep pass on it: `get_guideline`.",
1447
- "- A question about `akan code` itself \u2014 its slash commands, MCP servers, sessions, models, keys, or where its files live \u2014 is answered by the `akan-code` skill. Read it rather than describing the tool from memory.",
1448
- "- Never hand-edit a generated file (`lib/cnst.ts`, `db.ts`, `st.ts`, any `*/index.ts` barrel). Fix the owning source and run `repair_generated` or `akan sync`."
1449
- ];
1450
- if (profile.tools.builtin.includes("write"))
1451
- lines.push("- After editing, call `akan_verify`. It runs sync, lint, typecheck and the SSR check over what the working tree changed. A turn that edits and does not verify is not finished.");
1452
- else
1453
- lines.push("- This session is read-only: there is no write, edit or bash tool. Report what should change instead of changing it.");
1454
- if (profile.tools.subagent)
1455
- lines.push('- Hand a self-contained search or read to `task` \u2014 `type: "explore"` reads and reports, `type: "code"` makes the change. Only its answer comes back, so give it everything it needs and ask for exactly what you want returned. Do it yourself when the reading is short or when you need the files in front of you.');
1456
- if (profile.ui.canPrompt)
1457
- lines.push("- Ask with `ask_user` when two readings of the request would lead to materially different work, and offer the options you can name. Do not ask for a choice that has an obvious default, and do not ask permission for work you were already asked to do \u2014 make the call, say the assumption, and carry on.");
1458
- if (profile.approval !== "never")
1459
- lines.push(`- Some tool calls need the user's approval (policy: ${profile.approval}). A refusal is an answer; do not retry it a different way.`);
1460
- lines.push("- Secrets are refused at the tool boundary. If you need a value from `.env`, ask the user for it rather than looking for another way to read the file.", "", "## How to answer", "", "- Answer in as few words as carry the answer. A short question gets one sentence with no heading.", "- Do not open with what you are about to do, and do not close with a summary of what you did. The tool calls are already on the user's screen.", "- Skip headings, bold labels and bullet lists unless the answer is genuinely a list. Prose is shorter.", "- State a file as `path:line`. Do not paste a file back that the user can open.", "- Say what you could not do, and why, in one line. Do not offer alternatives nobody asked for.");
1461
- return lines.join(`
1648
+ #remember(url, page) {
1649
+ this.#pages.delete(url);
1650
+ this.#pages.set(url, page);
1651
+ const oldest = this.#pages.keys().next().value;
1652
+ if (this.#pages.size > WebToolPack2.#rememberedPages && oldest)
1653
+ this.#pages.delete(oldest);
1654
+ }
1655
+ static #window(url, page, offset) {
1656
+ const head = [`${page.status} ${url}`, page.title].filter(Boolean).join(`
1462
1657
  `);
1463
- };
1658
+ const total = page.text.length;
1659
+ if (offset && offset >= total)
1660
+ return `${head}
1464
1661
 
1465
- // pkgs/@akanjs/devkit/codeAgent/agent/CodeSessionIndex.ts
1466
- import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
1467
- import path6 from "path";
1662
+ Offset ${offset} is past the end; the page has ${total} characters.`;
1663
+ const end = Math.min(offset + WebToolPack2.pageChars, total);
1664
+ const rest = end < total ? `
1468
1665
 
1469
- // pkgs/@akanjs/devkit/codeAgent/agent/jsonLines.ts
1470
- import { closeSync, openSync, readFileSync as readFileSync3, readSync, statSync as statSync3 } from "fs";
1471
- var readHead = (file, maxBytes) => {
1472
- const fd = openSync(file, "r");
1473
- try {
1474
- const head = Buffer.alloc(maxBytes);
1475
- return head.subarray(0, readSync(fd, head, 0, maxBytes, 0)).toString("utf8");
1476
- } finally {
1477
- closeSync(fd);
1478
- }
1479
- };
1480
- var readJsonLines = (file, maxBytes) => {
1481
- try {
1482
- const text = maxBytes !== undefined && statSync3(file).size > maxBytes ? readHead(file, maxBytes) : readFileSync3(file, "utf8");
1483
- return text.split(`
1484
- `).filter((line) => !!line.trim());
1485
- } catch {
1486
- return [];
1487
- }
1488
- };
1489
- var parseJsonLine = (line) => {
1490
- try {
1491
- return JSON.parse(line);
1492
- } catch {
1493
- return;
1494
- }
1495
- };
1666
+ [characters ${offset}\u2013${end} of ${total}; offset ${end} reads on]` : "";
1667
+ return `${head}
1496
1668
 
1497
- // pkgs/@akanjs/devkit/codeAgent/agent/CodeSessionIndex.ts
1498
- class CodeSessionIndex2 {
1499
- static readLimit = 512 * 1024;
1500
- static list(dir, limit = 30) {
1501
- if (!existsSync6(dir))
1502
- return [];
1503
- const files = readdirSync2(dir).filter((entry) => entry.endsWith(".jsonl")).map((entry) => path6.join(dir, entry)).map((file) => ({ file, updatedAt: CodeSessionIndex2.#mtime(file) })).sort((left, right) => right.updatedAt - left.updatedAt).slice(0, limit);
1504
- return files.map((entry) => CodeSessionIndex2.#read(entry.file, entry.updatedAt)).filter((entry) => !!entry);
1505
- }
1506
- static fileOf(dir, id) {
1507
- if (!existsSync6(dir))
1508
- return;
1509
- const match = readdirSync2(dir).find((entry) => entry.endsWith(`${id}.jsonl`) || entry === `${id}.jsonl`);
1510
- return match ? path6.join(dir, match) : undefined;
1669
+ ${page.text.slice(offset, end)}${rest}`;
1670
+ }
1671
+ static #isLocal(hostname) {
1672
+ const host = hostname.replace(/^\[|\]$/g, "").toLowerCase();
1673
+ const family = isIP(host);
1674
+ if (family === 6)
1675
+ return host === "::1" || /^(f[cd]|fe[89ab])/.test(host);
1676
+ if (family === 4) {
1677
+ const [a = 0, b = 0] = host.split(".").map(Number);
1678
+ return a === 0 || a === 10 || a === 127 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168;
1679
+ }
1680
+ return !host.includes(".") || /\.(localhost|local|internal|lan|home\.arpa)$/.test(host);
1511
1681
  }
1512
- static #mtime(file) {
1682
+ static #refuseHost(url, allowHosts) {
1683
+ let parsed;
1513
1684
  try {
1514
- return statSync4(file).mtimeMs;
1685
+ parsed = new URL(url);
1515
1686
  } catch {
1516
- return 0;
1517
- }
1518
- }
1519
- static #read(file, updatedAt) {
1520
- const lines = readJsonLines(file, CodeSessionIndex2.readLimit);
1521
- if (!lines.length)
1522
- return;
1523
- const entry = { id: "", file, name: undefined, opening: "", updatedAt, turns: 0 };
1524
- for (const line of lines) {
1525
- const record = parseJsonLine(line);
1526
- if (!record)
1527
- continue;
1528
- if (record.type === "session" && typeof record.id === "string")
1529
- entry.id = record.id;
1530
- if (record.type === "session_info" && typeof record.name === "string")
1531
- entry.name = record.name;
1532
- if (record.type !== "message")
1533
- continue;
1534
- const message = record.message;
1535
- if (message?.role !== "user")
1536
- continue;
1537
- entry.turns += 1;
1538
- if (!entry.opening)
1539
- entry.opening = CodeSessionIndex2.#text(message.content);
1687
+ return `"${url}" is not an absolute URL.`;
1540
1688
  }
1541
- return entry.id ? entry : undefined;
1689
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
1690
+ return "Only http and https URLs can be fetched.";
1691
+ if (allowHosts?.length && !allowHosts.includes(parsed.hostname))
1692
+ return `This profile only allows ${allowHosts.join(", ")}.`;
1693
+ return;
1542
1694
  }
1543
- static #text(content) {
1544
- const text = typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => typeof part === "object" && part && ("text" in part) ? String(part.text) : "").join(" ") : "";
1545
- return text.replace(/\s+/g, " ").trim();
1695
+ static #readable(html) {
1696
+ return html.replace(/<script[\s\S]*?<\/script[^<>]*>/gi, " ").replace(/<style[\s\S]*?<\/style[^<>]*>/gi, " ").replace(/<[^<>]+>/g, " ").replace(/&nbsp;/g, " ").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&amp;/g, "&").replace(/[ \t]+/g, " ").replace(/\n{3,}/g, `
1697
+
1698
+ `).trim();
1546
1699
  }
1547
1700
  }
1548
1701
 
1549
- // pkgs/@akanjs/devkit/codeAgent/agent/AkanCodeServices.ts
1550
- class AkanCodeServices2 {
1551
- static settings() {
1552
- return SettingsManager.inMemory({
1553
- enableInstallTelemetry: false,
1554
- enableAnalytics: false,
1555
- quietStartup: true,
1556
- compaction: { reserveTokens: 32768 }
1702
+ // pkgs/@akanjs/devkit/codeAgent/tools/AkanCodePlugins.ts
1703
+ class AkanCodePlugins2 {
1704
+ static async build(options) {
1705
+ const mcp = await McpToolPack2.connect({
1706
+ workspaceRoot: options.workspace.workspaceRoot,
1707
+ profile: options.profile,
1708
+ ...options.onNotice ? { onNotice: options.onNotice } : {}
1557
1709
  });
1558
- }
1559
- static async runtime(workspaceRoot, proxy = null) {
1560
- mkdirSync(akanCodePaths2.globalDir(), { recursive: true, mode: 448 });
1561
- const runtime = await ModelRuntime.create({
1562
- authPath: proxy ? path7.join(mkdtempSync(path7.join(tmpdir(), "akan-code-proxy-")), "auth.json") : akanCodePaths2.authFile(),
1563
- modelsPath: akanCodePaths2.modelsFile()
1710
+ const dispose = () => mcp?.close();
1711
+ const mcpStatus = mcp?.status ?? [];
1712
+ const web = new WebToolPack2({ profile: options.profile, deepseek: options.deepseek });
1713
+ const sessions = new SessionToolPack2({
1714
+ workspaceRoot: options.workspace.workspaceRoot,
1715
+ cwd: options.cwd,
1716
+ profile: options.profile,
1717
+ currentSessionId: options.currentSessionId
1564
1718
  });
1565
- if (proxy)
1566
- await proxy.apply(runtime);
1567
- else
1568
- await AkanEnvKeys2.apply(runtime, workspaceRoot);
1569
- return runtime;
1570
- }
1571
- static sessions(workspaceRoot, cwd, profile, resume) {
1572
- if (profile.session.store === "memory")
1573
- return SessionManager2.inMemory(cwd);
1574
- const dir = akanCodePaths2.sessionsDir(workspaceRoot);
1575
- mkdirSync(dir, { recursive: true });
1576
- const manager = SessionManager2.create(cwd, dir);
1577
- if (!resume)
1578
- return manager;
1579
- const file = CodeSessionIndex2.fileOf(dir, resume);
1580
- if (!file)
1581
- throw new Error(`No stored session ${resume} in ${dir}`);
1582
- manager.setSessionFile(file);
1583
- return manager;
1719
+ const mail = new MailToolPack2({ profile: options.profile, mailbox: options.mailbox });
1720
+ const onAsk = options.onAsk;
1721
+ const ask = onAsk ? new AskToolPack({ profile: options.profile, ask: onAsk }) : undefined;
1722
+ const shared = {
1723
+ extensions: [mcp?.extension(), web.extension(), sessions.extension(), mail.extension(), ask?.extension()].filter((entry) => !!entry),
1724
+ toolNames: [
1725
+ ...mcp?.toolNames ?? [],
1726
+ ...web.names(),
1727
+ ...sessions.names(),
1728
+ ...mail.names(),
1729
+ ...ask?.names() ?? []
1730
+ ]
1731
+ };
1732
+ if (!options.profile.tools.akan)
1733
+ return { ...shared, mcp: mcpStatus, dispose };
1734
+ const readOnly = !options.profile.tools.builtin.includes("write");
1735
+ const pack = new AkanToolPack2({
1736
+ workspace: options.workspace,
1737
+ cwd: options.cwd,
1738
+ mode: readOnly ? "readonly" : "apply"
1739
+ });
1740
+ const extensions = [pack.extension()];
1741
+ const subagent = SubagentPool2.extensionFor({
1742
+ workspace: options.workspace,
1743
+ cwd: options.cwd,
1744
+ parent: options.profile,
1745
+ depth: options.depth,
1746
+ ...options.subagentSpend ? { spend: options.subagentSpend } : {},
1747
+ ...options.onNotice ? { onNotice: options.onNotice } : {},
1748
+ ...options.onAgents ? { onAgents: options.onAgents } : {}
1749
+ });
1750
+ if (subagent)
1751
+ extensions.push(subagent);
1752
+ const sources = await AkanCodePlugins2.#sources(options, readOnly);
1753
+ if (sources.length)
1754
+ extensions.push(new TurnFeedback2(sources, {
1755
+ budget: options.profile.limits.feedback,
1756
+ ...options.onNotice ? { onNotice: options.onNotice } : {}
1757
+ }).extension());
1758
+ return {
1759
+ extensions: [...extensions, ...shared.extensions],
1760
+ toolNames: [...await pack.names(), ...subagent ? [SubagentPool2.toolName] : [], ...shared.toolNames],
1761
+ mcp: mcpStatus,
1762
+ dispose
1763
+ };
1584
1764
  }
1585
- static #skillPaths(profile, workspaceRoot) {
1586
- if (!profile.context.skills)
1587
- return [];
1588
- const dirs = [
1589
- akanCodePaths2.builtinSkillsDir(),
1590
- akanCodePaths2.globalSkillsDir(),
1591
- akanCodePaths2.skillsDir(workspaceRoot)
1765
+ static async#sources(options, readOnly) {
1766
+ const sources = [
1767
+ new VerifyFeedback2({ cwd: options.cwd, readOnly }),
1768
+ new DevLogFeedback2({ workspaceRoot: options.workspace.workspaceRoot, cwd: options.cwd, apps: options.apps })
1592
1769
  ];
1593
- return dirs.filter((dir) => !!dir && existsSync7(dir));
1770
+ const previewUrl = await AkanCodePlugins2.#previewUrl(options);
1771
+ if (!previewUrl)
1772
+ return sources;
1773
+ const reason = PreviewView2.unavailableReason();
1774
+ if (reason)
1775
+ options.onNotice?.(reason);
1776
+ else
1777
+ sources.push(new PreviewFeedback2({ cwd: options.cwd, previewUrl, canSeeImages: options.canSeeImages }));
1778
+ return sources;
1594
1779
  }
1595
- static resourceOptions(options) {
1596
- const present = (dir) => existsSync7(dir) ? [dir] : [];
1597
- return {
1598
- noExtensions: true,
1599
- noSkills: true,
1600
- noPromptTemplates: true,
1601
- noThemes: true,
1602
- noContextFiles: !options.profile.context.projectFiles,
1603
- agentsFilesOverride: ({ agentsFiles }) => ({
1604
- agentsFiles: options.profile.context.projectFiles ? AkanContextFiles.extend(agentsFiles, { workspaceRoot: options.workspaceRoot, cwd: options.cwd }) : agentsFiles
1605
- }),
1606
- appendSystemPrompt: [akanSystemPrompt2(options.profile)],
1607
- additionalExtensionPaths: present(akanCodePaths2.extensionsDir(options.workspaceRoot)),
1608
- additionalSkillPaths: AkanCodeServices2.#skillPaths(options.profile, options.workspaceRoot),
1609
- extensionFactories: options.extensions
1610
- };
1780
+ static async#previewUrl(options) {
1781
+ for (const app of options.apps) {
1782
+ const url = await DevLogFeedback2.previewUrl(options.workspace.workspaceRoot, app);
1783
+ if (url)
1784
+ return url;
1785
+ }
1786
+ return;
1611
1787
  }
1612
1788
  }
1613
1789
 
@@ -1668,9 +1844,9 @@ class CodeAgentAsks2 {
1668
1844
 
1669
1845
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeAgentEventMapper.ts
1670
1846
  import {
1671
- codeAgentClip as codeAgentClip4,
1847
+ codeAgentClip as codeAgentClip5,
1672
1848
  codeAgentLabelChars,
1673
- codeAgentOutputChars as codeAgentOutputChars4
1849
+ codeAgentOutputChars as codeAgentOutputChars3
1674
1850
  } from "akanjs/common";
1675
1851
 
1676
1852
  // pkgs/@akanjs/devkit/codeAgent/agent/stringArg.ts
@@ -1730,7 +1906,7 @@ class CodeAgentEventMapper2 {
1730
1906
  type: "tool_progress",
1731
1907
  turnId: this.#turnId,
1732
1908
  toolCallId: event.toolCallId,
1733
- text: codeAgentClip4(CodeAgentEventMapper2.#renderToolText(event.partialResult), codeAgentLabelChars)
1909
+ text: codeAgentClip5(CodeAgentEventMapper2.#renderToolText(event.partialResult), codeAgentLabelChars)
1734
1910
  }
1735
1911
  ];
1736
1912
  case "tool_execution_end":
@@ -1801,8 +1977,8 @@ class CodeAgentEventMapper2 {
1801
1977
  turnId: this.#turnId,
1802
1978
  tool,
1803
1979
  outcome: reason ? "blocked" : event.isError ? "error" : "ok",
1804
- output: codeAgentClip4(full, codeAgentOutputChars4),
1805
- truncated: full.length > codeAgentOutputChars4
1980
+ output: codeAgentClip5(full, codeAgentOutputChars3),
1981
+ truncated: full.length > codeAgentOutputChars3
1806
1982
  };
1807
1983
  }
1808
1984
  noteOutcome(reason) {
@@ -1831,7 +2007,7 @@ class CodeAgentEventMapper2 {
1831
2007
  name,
1832
2008
  op: toolOps[name] ?? "other",
1833
2009
  ...path ? { path } : {},
1834
- title: codeAgentClip4(`${name}${CodeAgentEventMapper2.#renderArgs(args)}`, codeAgentLabelChars)
2010
+ title: codeAgentClip5(`${name}${CodeAgentEventMapper2.#renderArgs(args)}`, codeAgentLabelChars)
1835
2011
  };
1836
2012
  }
1837
2013
  #mapDelta(event) {
@@ -1858,7 +2034,7 @@ class CodeAgentEventMapper2 {
1858
2034
  if (!entries.length)
1859
2035
  return "";
1860
2036
  const rendered = entries.map(([key, value]) => `${key}=${CodeAgentEventMapper2.#renderArgValue(value)}`).join(" ");
1861
- return `(${codeAgentClip4(rendered, 120)})`;
2037
+ return `(${codeAgentClip5(rendered, 120)})`;
1862
2038
  }
1863
2039
  static #renderArgValue(value) {
1864
2040
  if (Array.isArray(value))
@@ -1867,7 +2043,7 @@ class CodeAgentEventMapper2 {
1867
2043
  const keys = Object.keys(value).length;
1868
2044
  return `{${keys} key${keys === 1 ? "" : "s"}}`;
1869
2045
  }
1870
- return codeAgentClip4(String(value), 60);
2046
+ return codeAgentClip5(String(value), 60);
1871
2047
  }
1872
2048
  static #renderContent(content) {
1873
2049
  if (typeof content === "string")
@@ -1964,6 +2140,7 @@ class CodeAgentGate2 {
1964
2140
 
1965
2141
  // pkgs/@akanjs/devkit/codeAgent/agent/CodeAgentProxy.ts
1966
2142
  import { readFile } from "fs/promises";
2143
+ import { trimTrailingSlashes } from "akanjs/common";
1967
2144
  class CodeAgentProxy {
1968
2145
  static placeholderKey = "akan-code-proxy";
1969
2146
  baseUrl;
@@ -1971,7 +2148,7 @@ class CodeAgentProxy {
1971
2148
  #tokenFile;
1972
2149
  #token;
1973
2150
  constructor({ baseUrl, providers, tokenFile, token }) {
1974
- this.baseUrl = baseUrl.replace(/\/+$/, "");
2151
+ this.baseUrl = trimTrailingSlashes(baseUrl);
1975
2152
  this.providers = providers;
1976
2153
  this.#tokenFile = tokenFile;
1977
2154
  this.#token = token;
@@ -2307,6 +2484,7 @@ class CodeAgent2 {
2307
2484
  #replay = [];
2308
2485
  #replayFrom = 0;
2309
2486
  #lastContextTokens;
2487
+ #autoCompactAt;
2310
2488
  constructor(profile) {
2311
2489
  this.#profile = profile;
2312
2490
  this.#gate = new CodeAgentGate2(profile);
@@ -2315,8 +2493,10 @@ class CodeAgent2 {
2315
2493
  const workspaceRoot = options.workspace.workspaceRoot;
2316
2494
  const cwd = options.cwd ?? workspaceRoot;
2317
2495
  const agent = new CodeAgent2(options.profile);
2496
+ const autoCompactAt = AkanCodeCompaction2.normalize(options.autoCompactAt);
2318
2497
  const settingsManager = AkanCodeServices2.settings();
2319
- const modelRuntime = await AkanCodeServices2.runtime(workspaceRoot, CodeAgentProxy.fromEnv(options.profile));
2498
+ const proxy = CodeAgentProxy.fromEnv(options.profile);
2499
+ const modelRuntime = await AkanCodeServices2.runtime(workspaceRoot, proxy);
2320
2500
  const model = await akanCodeModel2(modelRuntime, options.model);
2321
2501
  const compaction = settingsManager.getCompactionSettings(model);
2322
2502
  const modelWarnings = akanCodeModelWarnings2(model, compaction.reserveTokens + compaction.keepRecentTokens);
@@ -2326,6 +2506,7 @@ class CodeAgent2 {
2326
2506
  profile: options.profile,
2327
2507
  apps: options.apps ?? [],
2328
2508
  canSeeImages: akanCodeModelSupportsImages2(model),
2509
+ deepseek: await DeepSeekWeb.fromRuntime(modelRuntime, !!proxy),
2329
2510
  depth: options.depth ?? 0,
2330
2511
  ...options.subagentSpend ? { subagentSpend: options.subagentSpend } : {},
2331
2512
  currentSessionId: () => agent.sessionId,
@@ -2364,10 +2545,11 @@ class CodeAgent2 {
2364
2545
  if (options.profile.session.store !== "memory")
2365
2546
  agent.#sessionDir = akanCodePaths2.sessionsDir(workspaceRoot);
2366
2547
  await agent.#attach(session, options.mode ?? "print");
2548
+ const autoCompactWarning = agent.#fitAutoCompact(autoCompactAt);
2367
2549
  agent.#suspended = new CodeAgentSuspended(CodeAgentSuspended.fileOf(agent.#sessionDir, agent.sessionId));
2368
2550
  if (agent.#sessionDir && options.profile.session.store === "file")
2369
2551
  agent.#openMail(akanCodePaths2.mailDir(workspaceRoot), cwd);
2370
- for (const message of modelWarnings)
2552
+ for (const message of [...modelWarnings, ...autoCompactWarning])
2371
2553
  agent.#emit({ type: "notice", level: "warning", message });
2372
2554
  return agent;
2373
2555
  }
@@ -2432,6 +2614,7 @@ ${mail.text}`).catch((error) => this.#emit({ type: "notice", level: "error", mes
2432
2614
  tools: session.getActiveToolNames(),
2433
2615
  contextTokens: model?.contextWindow,
2434
2616
  effort: session.supportsThinking() ? session.thinkingLevel : undefined,
2617
+ autoCompactAt: this.#autoCompactAt,
2435
2618
  name: session.sessionName,
2436
2619
  interaction: this.#profile.interaction
2437
2620
  };
@@ -2580,8 +2763,38 @@ Continue the task.`);
2580
2763
  if (!model)
2581
2764
  throw new Error(`Unknown model: ${ref.provider}/${ref.id}`);
2582
2765
  await session.setModel(model);
2766
+ for (const message of this.#fitAutoCompact(this.#autoCompactAt))
2767
+ this.#emit({ type: "notice", level: "warning", message });
2768
+ this.#emit({ type: "session", info: this.info });
2769
+ }
2770
+ setAutoCompactAt(percent) {
2771
+ const wanted = AkanCodeCompaction2.normalize(percent);
2772
+ const problem = this.#autoCompactProblem(wanted);
2773
+ if (problem)
2774
+ throw new Error(problem);
2775
+ this.#autoCompactAt = wanted;
2776
+ this.#applyAutoCompact();
2583
2777
  this.#emit({ type: "session", info: this.info });
2584
2778
  }
2779
+ #fitAutoCompact(percent) {
2780
+ const problem = this.#autoCompactProblem(percent);
2781
+ this.#autoCompactAt = problem ? undefined : percent;
2782
+ this.#applyAutoCompact();
2783
+ return problem ? [`${problem} Autocompact is back to the default.`] : [];
2784
+ }
2785
+ #autoCompactProblem(percent) {
2786
+ const session = this.#require();
2787
+ const model = session.model;
2788
+ if (!model || !percent)
2789
+ return;
2790
+ const { keepRecentTokens } = session.settingsManager.getCompactionSettings(model);
2791
+ return AkanCodeCompaction2.problemOf(model, percent, keepRecentTokens);
2792
+ }
2793
+ #applyAutoCompact() {
2794
+ const session = this.#require();
2795
+ if (session.model)
2796
+ AkanCodeCompaction2.apply(session.settingsManager, session.model, this.#autoCompactAt);
2797
+ }
2585
2798
  async waitForIdle() {
2586
2799
  await this.#require().waitForIdle();
2587
2800
  }
@@ -2688,7 +2901,7 @@ Continue the task.`);
2688
2901
  approvalId,
2689
2902
  toolCallId,
2690
2903
  name,
2691
- summary: codeAgentClip5(summary, codeAgentLabelChars2),
2904
+ summary: codeAgentClip6(summary, codeAgentLabelChars2),
2692
2905
  policy: this.#profile.approval
2693
2906
  };
2694
2907
  this.#emit({ type: "approval", request });