@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.
- package/.build-stamp +1 -1
- package/{CodeAgent-q50k4s45.js → CodeAgent-gzfc5t52.js} +2 -2
- package/{CodeAgentRpcHost-p7h6n2hp.js → CodeAgentRpcHost-9g27hmer.js} +1 -1
- package/{CodeTui-62g35ztk.js → CodeTui-bpsjs8wz.js} +47 -4
- package/TunnelShare-tfrnftgr.js +14 -0
- package/{agent.command-adfd5299.js → agent.command-67ysh3n6.js} +5 -5
- package/akanCode.js +7 -5
- package/{application.command-hj3gv1rx.js → application.command-q0tqw5pc.js} +38 -11
- package/{applicationBuildRunner-by10r9gf.js → applicationBuildRunner-wsszr305.js} +16 -9
- package/buildBatch.proc.js +7 -7
- package/{cloud.command-k2fhaape.js → cloud.command-e7tkxyx6.js} +17 -16
- package/cloud.script-krzh3d7k.js +24 -0
- package/{code.command-kt5zgcc2.js → code.command-ttk8tje2.js} +25 -12
- package/commandManifest.json +1 -1
- package/{context.command-8fgn42jw.js → context.command-bdsk2z2r.js} +17 -16
- package/context.runner-bxzv0gm0.js +25 -0
- package/{dependencyScanner-06z2dmrr.js → dependencyScanner-3afj2vt1.js} +1 -1
- package/{guideline.command-3yxxr2mm.js → guideline.command-man90n70.js} +2 -2
- package/guidelines/agentRule/agentRule.instruction.md +25 -10
- package/guidelines/conventions/conventions.instruction.md +486 -1084
- package/guidelines/cssRule/cssRule.instruction.md +16 -1
- package/guidelines/fieldRule/fieldRule.instruction.md +74 -0
- package/guidelines/framework/framework.instruction.md +4 -7
- package/guidelines/mcpRule/mcpRule.instruction.md +16 -9
- package/guidelines/modelConstant/modelConstant.instruction.md +2 -1
- package/guidelines/modelStore/modelStore.instruction.md +16 -0
- package/guidelines/pageRule/pageRule.instruction.md +80 -0
- package/guidelines/queryRule/queryRule.instruction.md +47 -12
- package/guidelines/runtimeRule/runtimeRule.instruction.md +89 -2
- package/guidelines/ssrRule/ssrRule.instruction.md +13 -0
- package/guidelines/transportRule/transportRule.instruction.md +107 -8
- package/guidelines/workspaceOnboarding/workspaceOnboarding.instruction.md +4 -4
- package/guidelines/workspaceRecipes/workspaceRecipes.instruction.md +70 -7
- package/incrementalBuilder.proc.js +4 -4
- package/{index-rhbabmja.js → index-033wne2j.js} +260 -259
- package/{index-xpdgxsj5.js → index-1r5a8bzx.js} +2216 -2216
- package/{index-jwqtchha.js → index-27y8t17p.js} +1 -1
- package/{index-wa5jbxj1.js → index-2mrn1mrg.js} +1 -0
- package/{index-t7nws9mx.js → index-2w8j2fnv.js} +661 -448
- package/{index-0909wckt.js → index-35m6bzm7.js} +238 -236
- package/{index-sqfdmb5b.js → index-4393nb4b.js} +4 -4
- package/{index-sby8fe1p.js → index-48edp6q4.js} +1 -1
- package/{index-g0vwzy9m.js → index-4v43h00g.js} +20 -21
- package/{index-azh29dc8.js → index-54wc2phm.js} +19 -14
- package/{index-k9rrten6.js → index-5nd1wtkv.js} +92 -94
- package/index-62md4dwy.js +260 -0
- package/{index-hnby7p9n.js → index-75n5q4ph.js} +7 -7
- package/{index-fjsv10hd.js → index-844q7f8z.js} +7 -7
- package/{index-tv8fjaba.js → index-8jehkey8.js} +4 -4
- package/{index-bea2gwc6.js → index-9mt4smfy.js} +2 -2
- package/{index-d2242ksy.js → index-at7bz0xq.js} +3 -0
- package/{index-3hx1z96c.js → index-bqbf654f.js} +1 -1
- package/{index-8zdwpxef.js → index-cae9m5jz.js} +119 -81
- package/{index-tz5evhdr.js → index-ebarbgba.js} +6 -6
- package/{index-2etqq75t.js → index-g6ax17x4.js} +1114 -1082
- package/{index-h85cxfkg.js → index-g9ks2k8c.js} +2 -2
- package/{index-nq045px5.js → index-h3r3nr9y.js} +356 -307
- package/{index-20cebtth.js → index-h6czey01.js} +79 -24
- package/{index-6726qr1m.js → index-jrmzcp66.js} +57 -58
- package/{index-q958tqqg.js → index-m9jj544v.js} +1 -1
- package/{index-8935h3n1.js → index-mdjc93e1.js} +1 -1
- package/{index-rk9sdys3.js → index-nv79n88g.js} +5 -5
- package/{index-kc8z134d.js → index-nx64dz92.js} +38 -15
- package/{index-z507nzhg.js → index-p2r64bpa.js} +4 -4
- package/{index-nq2ja5e0.js → index-pg1t468s.js} +3 -3
- package/{index-3reaaa1h.js → index-rc51f9ht.js} +29 -27
- package/{index-e9prnmyt.js → index-ta1dychm.js} +3 -3
- package/{index-2ahj896a.js → index-tvehn2sp.js} +1 -1
- package/{index-31ce9vhg.js → index-vkb9m4z7.js} +80 -38
- package/{index-58vvz603.js → index-xsd96yzw.js} +1 -1
- package/{index-pyxn1b53.js → index-zv5s8x58.js} +13 -13
- package/index.js +22 -22
- package/{library.command-8tw59xzj.js → library.command-6amzsrb8.js} +3 -3
- package/{localRegistry.command-tf3dmznd.js → localRegistry.command-yqc0nyhh.js} +15 -14
- package/{module.command-9vm8hffy.js → module.command-10x6jv6c.js} +5 -5
- package/{package.command-erbxe2tz.js → package.command-pwfvecjf.js} +4 -4
- package/package.json +21 -21
- package/{page.command-jpavcj0p.js → page.command-ahhgx9y1.js} +3 -3
- package/{pipeline.command-b8rzvjvv.js → pipeline.command-1ptr3e9y.js} +3 -3
- package/{primitive.command-sm32chzk.js → primitive.command-g5xwv6tj.js} +6 -6
- package/{projectTypecheck-jamtx95x.js → projectTypecheck-fx8x7cat.js} +1 -1
- package/{quality.command-pkd17n6y.js → quality.command-fw9ck061.js} +4 -4
- package/{repair.command-8y4mq6hs.js → repair.command-g8kecj96.js} +5 -5
- package/{scalar.command-q8khmyx5.js → scalar.command-fprap51w.js} +4 -4
- package/skills/akan-code/SKILL.md +4 -0
- package/skills/akan-store/SKILL.md +2 -1
- package/{subspace.command-51n0ykbx.js → subspace.command-sc0t527y.js} +11 -10
- package/templates/appSample/lib/task/Task.Util.tsx +7 -7
- package/templates/appSample/lib/task/Task.Zone.tsx +1 -1
- package/templates/appSample/ui/QuantityControl.tsx +2 -2
- package/templates/crudSinglePage/page.tsx +1 -1
- package/templates/lib/cnst.ts +0 -1
- package/templates/migration/__migrationId__.ts +23 -0
- package/templates/server.ts +8 -1
- package/templates/workspaceRoot/.gitignore.template +23 -52
- package/templates/workspaceRoot/biome.json.template +1 -1
- package/{tunnel.command-7820vtxq.js → tunnel.command-mqnsqbwf.js} +8 -7
- package/{workflow.command-n5sv4wqz.js → workflow.command-j0jgkr13.js} +10 -10
- package/{workspace.command-zdy60jh8.js → workspace.command-1r9hvvw0.js} +29 -28
- package/TunnelShare-f1egrktx.js +0 -13
- package/cloud.script-z8p5dz7e.js +0 -23
- package/context.runner-hnh0rg7m.js +0 -24
- package/index-n68jx79w.js +0 -120
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
SubagentPool2,
|
|
4
4
|
akanCodePaths2,
|
|
5
5
|
McpServerConfig,
|
|
6
|
-
McpSignIn
|
|
7
|
-
|
|
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
|
|
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
|
|
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) =>
|
|
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) =>
|
|
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
|
|
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 =
|
|
130
|
-
if (!
|
|
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
|
|
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
|
|
439
|
+
import { existsSync as existsSync6 } from "fs";
|
|
142
440
|
import { homedir } from "os";
|
|
143
|
-
import
|
|
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",
|
|
479
|
+
for (const root of ["/Applications", path6.join(homedir(), "Applications")])
|
|
182
480
|
for (const bundle of PreviewChrome2.appBundles) {
|
|
183
|
-
const candidate =
|
|
184
|
-
if (
|
|
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
|
|
392
|
-
import
|
|
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
|
-
|
|
420
|
-
|
|
717
|
+
path7.join(import.meta.dir, "index.js"),
|
|
718
|
+
path7.join(import.meta.dir, "..", "..", "..", "cli", "index.ts")
|
|
421
719
|
];
|
|
422
|
-
return candidates.find((candidate) =>
|
|
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-
|
|
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-
|
|
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
|
|
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 {
|
|
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 &&
|
|
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
|
|
1157
|
-
if (this.#options.profile.tools.web.search &&
|
|
1158
|
-
this.#registerSearch(pi,
|
|
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:
|
|
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({
|
|
1168
|
-
|
|
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
|
|
1173
|
-
const
|
|
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: !
|
|
1590
|
+
isError: !page.ok
|
|
1182
1591
|
};
|
|
1183
1592
|
}
|
|
1184
1593
|
});
|
|
1185
1594
|
}
|
|
1186
|
-
#registerSearch(pi,
|
|
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 (${
|
|
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:
|
|
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
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
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
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
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(/ /g, " ").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/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
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
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
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
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
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
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
|
-
|
|
1470
|
-
|
|
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
|
-
|
|
1498
|
-
|
|
1499
|
-
static
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
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 #
|
|
1682
|
+
static #refuseHost(url, allowHosts) {
|
|
1683
|
+
let parsed;
|
|
1513
1684
|
try {
|
|
1514
|
-
|
|
1685
|
+
parsed = new URL(url);
|
|
1515
1686
|
} catch {
|
|
1516
|
-
return
|
|
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
|
-
|
|
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 #
|
|
1544
|
-
|
|
1545
|
-
|
|
1695
|
+
static #readable(html) {
|
|
1696
|
+
return html.replace(/<script[\s\S]*?<\/script[^<>]*>/gi, " ").replace(/<style[\s\S]*?<\/style[^<>]*>/gi, " ").replace(/<[^<>]+>/g, " ").replace(/ /g, " ").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/&/g, "&").replace(/[ \t]+/g, " ").replace(/\n{3,}/g, `
|
|
1697
|
+
|
|
1698
|
+
`).trim();
|
|
1546
1699
|
}
|
|
1547
1700
|
}
|
|
1548
1701
|
|
|
1549
|
-
// pkgs/@akanjs/devkit/codeAgent/
|
|
1550
|
-
class
|
|
1551
|
-
static
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
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
|
-
|
|
1560
|
-
|
|
1561
|
-
const
|
|
1562
|
-
|
|
1563
|
-
|
|
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
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
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 #
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
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
|
-
|
|
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
|
|
1596
|
-
const
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
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
|
|
1847
|
+
codeAgentClip as codeAgentClip5,
|
|
1672
1848
|
codeAgentLabelChars,
|
|
1673
|
-
codeAgentOutputChars as
|
|
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:
|
|
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:
|
|
1805
|
-
truncated: full.length >
|
|
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:
|
|
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 `(${
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
2904
|
+
summary: codeAgentClip6(summary, codeAgentLabelChars2),
|
|
2692
2905
|
policy: this.#profile.approval
|
|
2693
2906
|
};
|
|
2694
2907
|
this.#emit({ type: "approval", request });
|