@akanjs/cli 3.1.0-rc.11 → 3.1.0-rc.13

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 CHANGED
@@ -1 +1 @@
1
- 171ce01d24fb85578a6a0d8092afa7851ebc872608d4b93f3fcee8e760e82352
1
+ 7bcd4fe279524ed2759e02783eb078a66b8ee0e6acee19a817c2f751ecba695a
@@ -1,8 +1,8 @@
1
1
  // @bun
2
2
  import {
3
3
  CodeAgent2
4
- } from "./index-8fp3b47h.js";
5
- import"./index-20bxxyvn.js";
4
+ } from "./index-1djcjcre.js";
5
+ import"./index-dye8pnxx.js";
6
6
  import"./index-7md2g9vw.js";
7
7
  export {
8
8
  CodeAgent2 as CodeAgent
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  CodeAgentRpcHost2
4
- } from "./index-d2242ksy.js";
4
+ } from "./index-at7bz0xq.js";
5
5
  export {
6
6
  CodeAgentRpcHost2 as CodeAgentRpcHost
7
7
  };
@@ -3,8 +3,9 @@ import {
3
3
  SubagentPool2,
4
4
  McpServerConfig,
5
5
  McpTokenStore,
6
- McpSignIn
7
- } from "./index-20bxxyvn.js";
6
+ McpSignIn,
7
+ AkanCodeCompaction2
8
+ } from "./index-dye8pnxx.js";
8
9
  import {
9
10
  CodeTuiLines2,
10
11
  CodeTuiAgents2,
@@ -14,7 +15,7 @@ import {
14
15
  CodeTuiFiles2,
15
16
  CodeTuiMarkdown2,
16
17
  CodeTuiParts2
17
- } from "./index-wa5jbxj1.js";
18
+ } from "./index-2mrn1mrg.js";
18
19
  import {
19
20
  openBrowser
20
21
  } from "./index-9dp031by.js";
@@ -345,6 +346,34 @@ var CodeTuiApp = ({ actions }) => {
345
346
  }, undefined, true, undefined, this);
346
347
  };
347
348
 
349
+ // pkgs/@akanjs/cli/code/CodeTuiAutoCompact.ts
350
+ class CodeTuiAutoCompact {
351
+ static parse(argument) {
352
+ const percent = Number(argument.trim().replace(/%$/, ""));
353
+ return argument.trim() && AkanCodeCompaction2.isStep(percent) ? percent : undefined;
354
+ }
355
+ static describe(info) {
356
+ const percent = info?.autoCompactAt;
357
+ const label = `autocompact ${percent ? `${percent}%` : "default"}`;
358
+ const window = info?.contextTokens;
359
+ if (!window)
360
+ return label;
361
+ const threshold = AkanCodeCompaction2.thresholdOf(window, percent);
362
+ const k = (tokens) => `${Math.round(tokens / 1000)}k`;
363
+ const capped = percent && threshold < Math.floor(window * percent / 100) ? `, the latest that still leaves ${k(window - threshold)} for the reply` : "";
364
+ return `${label} \xB7 compacts above ${k(threshold)} of ${k(window)}${capped}`;
365
+ }
366
+ static usage(info) {
367
+ return [
368
+ "Usage: /autocompact <percent>",
369
+ "",
370
+ `now: ${CodeTuiAutoCompact.describe(info)}`,
371
+ `${AkanCodeCompaction2.steps.join(" \xB7 ")} \u2014 100 is the default`
372
+ ].join(`
373
+ `);
374
+ }
375
+ }
376
+
348
377
  // pkgs/@akanjs/cli/code/CodeTuiMcp.ts
349
378
  class CodeTuiMcp {
350
379
  static namePattern = /^[a-zA-Z0-9_-]+$/;
@@ -897,7 +926,8 @@ class CodeTui {
897
926
  const context = this.#transcript.context;
898
927
  const window = info.contextTokens ? `${Math.round(info.contextTokens / 1000)}k` : "";
899
928
  const used = context?.max ? `${Math.round(context.used / context.max * 100)}% of ${window}` : "";
900
- return [info.model?.name ?? "no model", used || window && `${window} ctx`, info.profile, info.effort].filter((part) => !!part).join(" \xB7 ");
929
+ const compactsAt = info.autoCompactAt ? `compacts at ${info.autoCompactAt}%` : "";
930
+ return [info.model?.name ?? "no model", used || window && `${window} ctx`, compactsAt, info.profile, info.effort].filter((part) => !!part).join(" \xB7 ");
901
931
  }
902
932
  #status() {
903
933
  if (this.#transcript.compacting)
@@ -1248,6 +1278,8 @@ class CodeTui {
1248
1278
  return this.#say("screen cleared \xB7 the model still remembers \xB7 /compact shortens what it carries");
1249
1279
  case "compact":
1250
1280
  return void this.#agent.compact(argument || undefined).catch((error) => this.#fail(error));
1281
+ case "autocompact":
1282
+ return this.#setAutoCompact(argument);
1251
1283
  case "thinking":
1252
1284
  this.#thinking = !this.#thinking;
1253
1285
  return this.#say(this.#thinking ? "showing the model's reasoning" : "reasoning folded back to one line");
@@ -1410,6 +1442,17 @@ ${levels.join(" \xB7 ")}`);
1410
1442
  this.#fail(error);
1411
1443
  }
1412
1444
  }
1445
+ #setAutoCompact(argument) {
1446
+ const percent = CodeTuiAutoCompact.parse(argument);
1447
+ if (percent === undefined)
1448
+ return this.#open("autocompact", CodeTuiAutoCompact.usage(this.#transcript.info));
1449
+ try {
1450
+ this.#agent.setAutoCompactAt(percent);
1451
+ this.#say(CodeTuiAutoCompact.describe(this.#transcript.info));
1452
+ } catch (error) {
1453
+ this.#fail(error);
1454
+ }
1455
+ }
1413
1456
  #setModel(argument) {
1414
1457
  const wanted = argument.trim();
1415
1458
  const [provider, ...rest] = wanted.split("/");
package/akanCode.js CHANGED
@@ -28,7 +28,7 @@ import {
28
28
  CodeMailbox2,
29
29
  CodeSessionFork2,
30
30
  CodeAgent2
31
- } from "./index-8fp3b47h.js";
31
+ } from "./index-1djcjcre.js";
32
32
  import {
33
33
  akanCodeDefaultModel2,
34
34
  akanCodeModel2,
@@ -38,12 +38,13 @@ import {
38
38
  } from "./index-c6eggbee.js";
39
39
  import {
40
40
  SubagentPool2,
41
- akanCodePaths2
42
- } from "./index-20bxxyvn.js";
41
+ akanCodePaths2,
42
+ AkanCodeCompaction2
43
+ } from "./index-dye8pnxx.js";
43
44
  import"./index-7md2g9vw.js";
44
45
  import {
45
46
  CodeAgentRpcHost2
46
- } from "./index-d2242ksy.js";
47
+ } from "./index-at7bz0xq.js";
47
48
  import {
48
49
  CodeTuiLines2,
49
50
  CodeTuiAgents2,
@@ -53,9 +54,10 @@ import {
53
54
  CodeTuiFiles2,
54
55
  CodeTuiMarkdown2,
55
56
  CodeTuiParts2
56
- } from "./index-wa5jbxj1.js";
57
+ } from "./index-2mrn1mrg.js";
57
58
  export {
58
59
  AkanCli2 as AkanCli,
60
+ AkanCodeCompaction2 as AkanCodeCompaction,
59
61
  AkanCodePlugins2 as AkanCodePlugins,
60
62
  AkanCodeServices2 as AkanCodeServices,
61
63
  AkanEditScope2 as AkanEditScope,
@@ -19,8 +19,8 @@ class CodeRunner extends runner("code") {
19
19
  async serve(options, listen) {
20
20
  await import("./consoleToStderr-h5bryj94.js");
21
21
  const [{ CodeAgent }, { CodeAgentRpcHost }, { CodeAgentRpcListener }] = await Promise.all([
22
- import("./CodeAgent-rkjyxgec.js"),
23
- import("./CodeAgentRpcHost-p7h6n2hp.js"),
22
+ import("./CodeAgent-n3tzjm91.js"),
23
+ import("./CodeAgentRpcHost-9g27hmer.js"),
24
24
  import("./CodeAgentRpcListener-7p7z5dd7.js")
25
25
  ]);
26
26
  const address = listen ? CodeAgentRpcListener.parse(listen) : null;
@@ -34,21 +34,28 @@ class CodeRunner extends runner("code") {
34
34
  }
35
35
  async tui(options, seed) {
36
36
  const [{ CodeAgent }, { CodeTui }] = await Promise.all([
37
- import("./CodeAgent-rkjyxgec.js"),
38
- import("./CodeTui-h7pyj6xa.js")
37
+ import("./CodeAgent-n3tzjm91.js"),
38
+ import("./CodeTui-fnm9ft4d.js")
39
39
  ]);
40
40
  const agentOptions = await CodeRunner.#agentOptions(options, false);
41
41
  let resume = options.resume;
42
+ let autoCompactAt = agentOptions.autoCompactAt;
42
43
  let prompt = seed;
43
44
  let notice;
44
45
  for (;; ) {
45
- const agent = await CodeAgent.create({ ...agentOptions, mode: "tui", ...resume ? { resume } : {} });
46
+ const agent = await CodeAgent.create({
47
+ ...agentOptions,
48
+ mode: "tui",
49
+ ...resume ? { resume } : {},
50
+ ...autoCompactAt ? { autoCompactAt } : {}
51
+ });
46
52
  let next;
47
53
  try {
48
54
  next = await new CodeTui(agent, {
49
55
  thinking: options.thinking,
50
56
  ...notice ? { notice } : {}
51
57
  }).run(prompt);
58
+ autoCompactAt = agent.info.autoCompactAt;
52
59
  } finally {
53
60
  agent.dispose();
54
61
  }
@@ -60,7 +67,7 @@ class CodeRunner extends runner("code") {
60
67
  }
61
68
  }
62
69
  async run(prompt, options) {
63
- const { CodeAgent } = await import("./CodeAgent-rkjyxgec.js");
70
+ const { CodeAgent } = await import("./CodeAgent-n3tzjm91.js");
64
71
  const agent = await CodeAgent.create(await CodeRunner.#agentOptions(options, false));
65
72
  const printer = new CodeAgentStreamPrinter2({ json: options.json, thinking: options.thinking });
66
73
  agent.on((event) => printer.print(event));
@@ -80,7 +87,8 @@ class CodeRunner extends runner("code") {
80
87
  cwd: profile.paths.root,
81
88
  profile,
82
89
  apps: options.app ? [options.app] : await options.workspace.getApps(),
83
- model: CodeRunner.modelOf(options.model)
90
+ model: CodeRunner.modelOf(options.model),
91
+ ...options.compactAt !== undefined ? { autoCompactAt: options.compactAt } : {}
84
92
  };
85
93
  }
86
94
  static profileOf(options, { hostAttached }) {
@@ -116,6 +124,7 @@ class CodeScript extends script("code", [CodeRunner]) {
116
124
  ...options.app ? { app: options.app } : {},
117
125
  profile: options.profile,
118
126
  ...options.model ? { model: options.model } : {},
127
+ ...options.compactAt !== null ? { compactAt: options.compactAt } : {},
119
128
  json: options.json,
120
129
  thinking: options.thinking,
121
130
  ...options.resume ? { resume: options.resume } : {}
@@ -135,7 +144,11 @@ class CodeScript extends script("code", [CodeRunner]) {
135
144
 
136
145
  // pkgs/@akanjs/cli/code/code.command.ts
137
146
  class CodeCommand extends command("code", [CodeScript], ({ public: target }) => ({
138
- code: target({ desc: "Run the akan coding agent on a prompt" }).arg("prompt", String, { desc: "what the agent should do", nullable: true }).option("app", String, { desc: "narrow the agent to one app instead of the whole repo", nullable: true }).option("profile", String, { desc: "local, pod, review, or web", default: "local" }).option("model", String, { desc: "<provider>/<id>, e.g. deepseek/deepseek-flash", nullable: true }).option("json", Boolean, { desc: "print one event per line as JSON", default: false }).option("thinking", Boolean, { desc: "print the model's reasoning", default: false }).option("rpc", Boolean, { desc: "serve the agent over stdio for another process to drive", default: false }).option("rpcListen", String, {
147
+ code: target({ desc: "Run the akan coding agent on a prompt" }).arg("prompt", String, { desc: "what the agent should do", nullable: true }).option("app", String, { desc: "narrow the agent to one app instead of the whole repo", nullable: true }).option("profile", String, { desc: "local, pod, review, or web", default: "local" }).option("model", String, { desc: "<provider>/<id>, e.g. deepseek/deepseek-flash", nullable: true }).option("compactAt", Number, {
148
+ desc: "compact once the context window is this full, in percent: 10, 20, \u2026 100",
149
+ flag: "c",
150
+ nullable: true
151
+ }).option("json", Boolean, { desc: "print one event per line as JSON", default: false }).option("thinking", Boolean, { desc: "print the model's reasoning", default: false }).option("rpc", Boolean, { desc: "serve the agent over stdio for another process to drive", default: false }).option("rpcListen", String, {
139
152
  desc: "serve the RPC wire on unix:<path> or tcp:[host:]<port> instead of stdio",
140
153
  flag: "L",
141
154
  nullable: true
@@ -143,8 +156,8 @@ class CodeCommand extends command("code", [CodeScript], ({ public: target }) =>
143
156
  desc: "open the full-screen session even when a prompt is given",
144
157
  flag: "i",
145
158
  default: false
146
- }).with(Workspace).exec(async function(prompt, app, profile, model, json, thinking, rpc, rpcListen, resume, interactive, workspace) {
147
- const options = { app, profile, model, json, thinking, rpc, rpcListen, interactive, resume };
159
+ }).with(Workspace).exec(async function(prompt, app, profile, model, compactAt, json, thinking, rpc, rpcListen, resume, interactive, workspace) {
160
+ const options = { app, profile, model, compactAt, json, thinking, rpc, rpcListen, interactive, resume };
148
161
  await this.codeScript.run(workspace, prompt ?? "", options);
149
162
  })
150
163
  })) {
@@ -445,7 +445,9 @@ orchestration in `service`, client state in `store`; `Template` is forms, `Unit`
445
445
  - Import `Int`, `Float`, `ID`, `Any`, `Binary`, `Upload`, `ImageResult`, `enumOf`, `dayjs` from `akanjs/base`;
446
446
  `String` / `Boolean` / `Date` are globals. No `JSON` scalar (use `Any`), no `Number`, and `Binary` / `Upload` are
447
447
  never fields — a file is `field(File).optional()` / `field([File])`, uploaded by the generated
448
- `upload<Field>On<Model>`.
448
+ `upload<Field>On<Model>` (which `Field.Img` / `Files` and siblings use when handed the generated setter); it marks
449
+ the field in `<model>FormUploading` while the request runs and drops a result whose form was set whole meanwhile
450
+ (or, on a single field, re-picked) — gate a hand-written submit on it.
449
451
  - Defaults: a literal for scalars, a thunk for anything constructed (`default: () => dayjs()`). Arrays
450
452
  `field([T])`; optional `.optional()`.
451
453
  - **`field.hidden` / `field.secret` read `null` on the client while the type declares them** — guard with `??` or
@@ -64,6 +64,18 @@ Use this whenever generating constants or scalar constants. It defines the curre
64
64
  field as a relation to `File`: `image: field(File).optional()` for one, `images: field([File])` for many.
65
65
  - The store then auto-generates an `upload<Field>On<Model>(fileList)` action that calls the framework upload
66
66
  mutation and polls file status until it leaves `"uploading"` (`pkgs/akanjs/store/action.ts`).
67
+ - **An upload is scoped to the form that started it.** `Field.Img` / `Imgs` / `File` / `Files` handed the generated
68
+ `set<Field>On<Model>` upload through `upload<Field>On<Model>` (any other `onChange` keeps the plain slice upload).
69
+ While the request runs, `<model>FormUploading` holds a ticket under the field's name (`{}` when idle);
70
+ `check<Model>Submitable`, `Model.EditModal` and `Model.ViewEditModal` keep submit disabled, and
71
+ `create` / `update<Model>InForm` (so `submit<Model>`) save nothing. A written result runs `_postSet<Field>` like
72
+ any setter. The result is dropped when the form was set whole meanwhile (`new` / `edit` / `reset`, a save, a
73
+ draft restore, `set<Model>Form`, an edit shell's hydration — the store clears the key on every
74
+ `set({ <model>Form })` — or another record's id) or, on a single field, when a newer upload or a
75
+ `set<Field>On<Model>` superseded it; an array field inserts into the list as it is when the result lands. A form
76
+ replaced inside an immer updater (`set((state) => { state.<model>Form = … })`) is not seen, so its uploads stay
77
+ valid. A hand-written submit reads `Object.keys(st.use.<model>FormUploading()).length` rather than tracking uploads
78
+ itself.
67
79
  - Storage is wired through the `StorageAdaptor` DI role (default `BlobStorage`,
68
80
  `pkgs/akanjs/service/predefinedAdaptor/storage.adaptor.ts`); the reference implementation is the `file` module.
69
81
  Do not hand-roll data-URL fallbacks. `{ cascade: "removeRef" }` on the field removes the file with its owner
@@ -221,7 +221,9 @@ shape, so `cascade` never means "related" — it means one of exactly these:
221
221
  and an unmounted `removeRef` target or `removeWith` owner fails the boot rather than the first removal. An
222
222
  unmounted `refPath` candidate only warns, since that list spans optional modules by design.
223
223
  - **Nothing checks whether another document still references the same target.** `File` in particular is deduped
224
- by `origin`, so two parents can share one row; `removeRef` claims the field owns its target exclusively.
224
+ by `origin`, so two parents can share one row; `removeRef` claims the field owns its target exclusively. Removing
225
+ the first parent removes the shared row; the second parent's cascade later skips it, since a live-row lookup per
226
+ edge (200 ids at a time) drops a target that is already gone, and still runs the rest of its edges.
225
227
  - **A reference no cascade removes keeps pointing at the removed row, and a response reads it live-only**: a list
226
228
  leaves the target out, an optional field reads `null`, and a required one answers 404
227
229
  `base.error.relationNotFound` naming the model and the field path. Make a reference whose target can go first
@@ -3,8 +3,9 @@ import {
3
3
  SubagentPool2,
4
4
  akanCodePaths2,
5
5
  McpServerConfig,
6
- McpSignIn
7
- } from "./index-20bxxyvn.js";
6
+ McpSignIn,
7
+ AkanCodeCompaction2
8
+ } from "./index-dye8pnxx.js";
8
9
  import {
9
10
  stripAnsi
10
11
  } from "./index-7md2g9vw.js";
@@ -254,7 +255,7 @@ class AkanCodeServices2 {
254
255
  enableInstallTelemetry: false,
255
256
  enableAnalytics: false,
256
257
  quietStartup: true,
257
- compaction: { reserveTokens: 32768 }
258
+ compaction: { reserveTokens: AkanCodeCompaction2.reserveFloor }
258
259
  });
259
260
  }
260
261
  static async runtime(workspaceRoot, proxy = null) {
@@ -318,7 +319,7 @@ import {
318
319
  DefaultResourceLoader
319
320
  } from "@earendil-works/pi-coding-agent";
320
321
  import {
321
- codeAgentClip as codeAgentClip5,
322
+ codeAgentClip as codeAgentClip6,
322
323
  codeAgentLabelChars as codeAgentLabelChars2,
323
324
  codeAgentRenderAnswer,
324
325
  codeAgentSessionName as codeAgentSessionName2
@@ -1395,11 +1396,120 @@ class SessionToolPack2 {
1395
1396
  }
1396
1397
 
1397
1398
  // pkgs/@akanjs/devkit/codeAgent/tools/WebToolPack.ts
1398
- import { codeAgentClip as codeAgentClip3, codeAgentOutputChars as codeAgentOutputChars3 } from "akanjs/common";
1399
+ import { isIP } from "net";
1400
+ import { codeAgentClip as codeAgentClip4 } from "akanjs/common";
1399
1401
  import { Type as Type6 } from "typebox";
1400
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
1401
1507
  class WebToolPack2 {
1508
+ static pageChars = 1e4;
1509
+ static maxResults = 10;
1510
+ static #rememberedPages = 16;
1402
1511
  #options;
1512
+ #pages = new Map;
1403
1513
  constructor(options) {
1404
1514
  this.#options = options;
1405
1515
  }
@@ -1438,7 +1548,7 @@ class WebToolPack2 {
1438
1548
  const names = [];
1439
1549
  if (this.#options.profile.tools.web.fetch)
1440
1550
  names.push("web_fetch");
1441
- if (this.#options.profile.tools.web.search && WebToolPack2.#provider())
1551
+ if (this.#options.profile.tools.web.search && this.#searcher())
1442
1552
  names.push("web_search");
1443
1553
  return names;
1444
1554
  }
@@ -1450,58 +1560,124 @@ class WebToolPack2 {
1450
1560
  #register(pi) {
1451
1561
  if (this.#options.profile.tools.web.fetch)
1452
1562
  this.#registerFetch(pi);
1453
- const provider = WebToolPack2.#provider();
1454
- if (this.#options.profile.tools.web.search && provider)
1455
- this.#registerSearch(pi, provider);
1563
+ const searcher = this.#searcher();
1564
+ if (this.#options.profile.tools.web.search && searcher)
1565
+ this.#registerSearch(pi, searcher);
1456
1566
  }
1457
1567
  #registerFetch(pi) {
1458
1568
  const allowHosts = this.#options.profile.network.allowHosts;
1459
1569
  pi.registerTool({
1460
1570
  name: "web_fetch",
1461
1571
  label: "Fetch",
1462
- 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.`,
1463
1573
  promptSnippet: "web_fetch: fetch a URL and return its readable text",
1464
- parameters: Type6.Object({ url: Type6.String({ description: "Absolute http(s) URL." }) }),
1465
- 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) => {
1466
1579
  const refusal = WebToolPack2.#refuseHost(params.url, allowHosts);
1467
1580
  if (refusal)
1468
1581
  return { content: [{ type: "text", text: refusal }], details: undefined, isError: true };
1469
- const response = await fetch(params.url, { signal: AbortSignal.timeout(20000), redirect: "follow" });
1470
- 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);
1471
1587
  return {
1472
- content: [
1473
- { type: "text", text: `${response.status} ${params.url}
1474
-
1475
- ${codeAgentClip3(text, codeAgentOutputChars3)}` }
1476
- ],
1588
+ content: [{ type: "text", text: WebToolPack2.#window(params.url, page, offset) }],
1477
1589
  details: undefined,
1478
- isError: !response.ok
1590
+ isError: !page.ok
1479
1591
  };
1480
1592
  }
1481
1593
  });
1482
1594
  }
1483
- #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.` : "";
1484
1597
  pi.registerTool({
1485
1598
  name: "web_search",
1486
1599
  label: "Search",
1487
- 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}`,
1488
1601
  promptSnippet: "web_search: search the web and return titles, urls and snippets",
1489
1602
  parameters: Type6.Object({
1490
1603
  query: Type6.String(),
1491
- 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}.` }))
1492
1605
  }),
1493
- execute: async (_id, params) => {
1494
- const key = process.env[provider.envKey] ?? "";
1495
- const results = await provider.search(key, params.query, params.count ?? 5);
1496
- return {
1497
- content: [{ type: "text", text: codeAgentClip3(results || "(no results)", codeAgentOutputChars3) }],
1498
- details: undefined
1499
- };
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
+ }
1500
1618
  }
1501
1619
  });
1502
1620
  }
1503
- static #provider() {
1504
- return WebToolPack2.providers.find((provider) => !!process.env[provider.envKey]);
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;
1630
+ }
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
+ }
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()) };
1647
+ }
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(`
1657
+ `);
1658
+ const total = page.text.length;
1659
+ if (offset && offset >= total)
1660
+ return `${head}
1661
+
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 ? `
1665
+
1666
+ [characters ${offset}\u2013${end} of ${total}; offset ${end} reads on]` : "";
1667
+ return `${head}
1668
+
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);
1505
1681
  }
1506
1682
  static #refuseHost(url, allowHosts) {
1507
1683
  let parsed;
@@ -1533,7 +1709,7 @@ class AkanCodePlugins2 {
1533
1709
  });
1534
1710
  const dispose = () => mcp?.close();
1535
1711
  const mcpStatus = mcp?.status ?? [];
1536
- const web = new WebToolPack2({ profile: options.profile });
1712
+ const web = new WebToolPack2({ profile: options.profile, deepseek: options.deepseek });
1537
1713
  const sessions = new SessionToolPack2({
1538
1714
  workspaceRoot: options.workspace.workspaceRoot,
1539
1715
  cwd: options.cwd,
@@ -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")
@@ -2308,6 +2484,7 @@ class CodeAgent2 {
2308
2484
  #replay = [];
2309
2485
  #replayFrom = 0;
2310
2486
  #lastContextTokens;
2487
+ #autoCompactAt;
2311
2488
  constructor(profile) {
2312
2489
  this.#profile = profile;
2313
2490
  this.#gate = new CodeAgentGate2(profile);
@@ -2316,8 +2493,10 @@ class CodeAgent2 {
2316
2493
  const workspaceRoot = options.workspace.workspaceRoot;
2317
2494
  const cwd = options.cwd ?? workspaceRoot;
2318
2495
  const agent = new CodeAgent2(options.profile);
2496
+ const autoCompactAt = AkanCodeCompaction2.normalize(options.autoCompactAt);
2319
2497
  const settingsManager = AkanCodeServices2.settings();
2320
- 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);
2321
2500
  const model = await akanCodeModel2(modelRuntime, options.model);
2322
2501
  const compaction = settingsManager.getCompactionSettings(model);
2323
2502
  const modelWarnings = akanCodeModelWarnings2(model, compaction.reserveTokens + compaction.keepRecentTokens);
@@ -2327,6 +2506,7 @@ class CodeAgent2 {
2327
2506
  profile: options.profile,
2328
2507
  apps: options.apps ?? [],
2329
2508
  canSeeImages: akanCodeModelSupportsImages2(model),
2509
+ deepseek: await DeepSeekWeb.fromRuntime(modelRuntime, !!proxy),
2330
2510
  depth: options.depth ?? 0,
2331
2511
  ...options.subagentSpend ? { subagentSpend: options.subagentSpend } : {},
2332
2512
  currentSessionId: () => agent.sessionId,
@@ -2365,10 +2545,11 @@ class CodeAgent2 {
2365
2545
  if (options.profile.session.store !== "memory")
2366
2546
  agent.#sessionDir = akanCodePaths2.sessionsDir(workspaceRoot);
2367
2547
  await agent.#attach(session, options.mode ?? "print");
2548
+ const autoCompactWarning = agent.#fitAutoCompact(autoCompactAt);
2368
2549
  agent.#suspended = new CodeAgentSuspended(CodeAgentSuspended.fileOf(agent.#sessionDir, agent.sessionId));
2369
2550
  if (agent.#sessionDir && options.profile.session.store === "file")
2370
2551
  agent.#openMail(akanCodePaths2.mailDir(workspaceRoot), cwd);
2371
- for (const message of modelWarnings)
2552
+ for (const message of [...modelWarnings, ...autoCompactWarning])
2372
2553
  agent.#emit({ type: "notice", level: "warning", message });
2373
2554
  return agent;
2374
2555
  }
@@ -2433,6 +2614,7 @@ ${mail.text}`).catch((error) => this.#emit({ type: "notice", level: "error", mes
2433
2614
  tools: session.getActiveToolNames(),
2434
2615
  contextTokens: model?.contextWindow,
2435
2616
  effort: session.supportsThinking() ? session.thinkingLevel : undefined,
2617
+ autoCompactAt: this.#autoCompactAt,
2436
2618
  name: session.sessionName,
2437
2619
  interaction: this.#profile.interaction
2438
2620
  };
@@ -2581,8 +2763,38 @@ Continue the task.`);
2581
2763
  if (!model)
2582
2764
  throw new Error(`Unknown model: ${ref.provider}/${ref.id}`);
2583
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();
2584
2777
  this.#emit({ type: "session", info: this.info });
2585
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
+ }
2586
2798
  async waitForIdle() {
2587
2799
  await this.#require().waitForIdle();
2588
2800
  }
@@ -2689,7 +2901,7 @@ Continue the task.`);
2689
2901
  approvalId,
2690
2902
  toolCallId,
2691
2903
  name,
2692
- summary: codeAgentClip5(summary, codeAgentLabelChars2),
2904
+ summary: codeAgentClip6(summary, codeAgentLabelChars2),
2693
2905
  policy: this.#profile.approval
2694
2906
  };
2695
2907
  this.#emit({ type: "approval", request });
@@ -250,6 +250,7 @@ class CodeTuiCommands2 {
250
250
  { name: "effort", desc: "how hard the model thinks before it answers", arg: "<level>" },
251
251
  { name: "thinking", desc: "show the model's reasoning, or fold it away again" },
252
252
  { name: "compact", desc: "summarise the conversation to free the window", arg: "[notes]" },
253
+ { name: "autocompact", desc: "how full the window gets before it is summarised", arg: "[<percent>]" },
253
254
  { name: "abort", desc: "stop the running turn" },
254
255
  { name: "clear", desc: "wipe the screen; the model keeps the conversation" },
255
256
  { name: "name", desc: "name this session", arg: "<text>" },
@@ -77,6 +77,9 @@ class CodeAgentRpcHost2 {
77
77
  case "set_model":
78
78
  await this.#agent.setModel({ provider: command.provider, id: command.modelId });
79
79
  return await this.#agent.state();
80
+ case "set_auto_compact":
81
+ this.#agent.setAutoCompactAt(command.percent);
82
+ return await this.#agent.state();
80
83
  case "get_state":
81
84
  return await this.#agent.state(command.sinceSeq === undefined ? {} : { sinceSeq: command.sinceSeq });
82
85
  case "shutdown":
@@ -3,6 +3,44 @@ import {
3
3
  ConfigLock
4
4
  } from "./index-bqbf654f.js";
5
5
 
6
+ // pkgs/@akanjs/devkit/codeAgent/agent/AkanCodeCompaction.ts
7
+ class AkanCodeCompaction2 {
8
+ static reserveFloor = 32768;
9
+ static steps = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
10
+ static isStep(percent) {
11
+ return AkanCodeCompaction2.steps.some((step) => step === percent);
12
+ }
13
+ static normalize(percent) {
14
+ if (percent === undefined)
15
+ return;
16
+ if (!AkanCodeCompaction2.isStep(percent))
17
+ throw new Error(`Autocompact takes a percent in steps of 10: ${AkanCodeCompaction2.steps.join(", ")}.`);
18
+ return percent === 100 ? undefined : percent;
19
+ }
20
+ static thresholdOf(contextWindow, percent) {
21
+ const share = percent ? Math.floor(contextWindow * percent / 100) : contextWindow;
22
+ return Math.min(share, contextWindow - AkanCodeCompaction2.reserveFloor);
23
+ }
24
+ static apply(settings, model, percent) {
25
+ const threshold = AkanCodeCompaction2.thresholdOf(model.contextWindow, percent);
26
+ settings.applyOverrides({
27
+ compaction: {
28
+ modelOverrides: { [`${model.provider}/${model.id}`]: { reserveTokens: model.contextWindow - threshold } }
29
+ }
30
+ });
31
+ }
32
+ static problemOf(model, percent, keepRecentTokens) {
33
+ const name = `${model.provider}/${model.id}`;
34
+ if (model.contextWindow <= 0)
35
+ return `${name} declares no context window, so it has no share to compact at.`;
36
+ const threshold = AkanCodeCompaction2.thresholdOf(model.contextWindow, percent);
37
+ if (threshold >= keepRecentTokens)
38
+ return;
39
+ const k = (tokens) => `${Math.round(tokens / 1000)}k`;
40
+ return `Compacting at ${percent}% of the ${k(model.contextWindow)} window of ${name} starts at ${k(threshold)}, under the ${k(keepRecentTokens)} of recent turns every compaction keeps, so every turn would compact again.`;
41
+ }
42
+ }
43
+
6
44
  // pkgs/@akanjs/devkit/codeAgent/agent/akanCodePaths.ts
7
45
  import { existsSync } from "fs";
8
46
  import { homedir } from "os";
@@ -141,7 +179,7 @@ class SubagentPool2 {
141
179
  }
142
180
  }
143
181
  async#run(id, kind, prompt, signal) {
144
- const { CodeAgent } = await import("./CodeAgent-rkjyxgec.js");
182
+ const { CodeAgent } = await import("./CodeAgent-n3tzjm91.js");
145
183
  const budget = this.#options.parent.tools.subagent;
146
184
  const child = SubagentPool2.childOf(this.#options.parent, kind);
147
185
  const agent = await CodeAgent.create({
@@ -697,4 +735,4 @@ ${stored.refreshToken}`;
697
735
  }
698
736
  }
699
737
 
700
- export { SubagentPool2, akanCodePaths2, McpServerConfig, McpTokenStore, McpSignIn };
738
+ export { SubagentPool2, akanCodePaths2, McpServerConfig, McpTokenStore, McpSignIn, AkanCodeCompaction2 };
package/index.js CHANGED
@@ -15,7 +15,7 @@ import path from "path";
15
15
  var commandModules = {
16
16
  workspace: async () => (await import("./workspace.command-6qfgq4b4.js")).WorkspaceCommand,
17
17
  agent: async () => (await import("./agent.command-3qxf4xcj.js")).AgentCommand,
18
- code: async () => (await import("./code.command-0ad6gw2a.js")).CodeCommand,
18
+ code: async () => (await import("./code.command-130bdmhe.js")).CodeCommand,
19
19
  application: async () => (await import("./application.command-m1kcd7jf.js")).ApplicationCommand,
20
20
  library: async () => (await import("./library.command-erp2f56n.js")).LibraryCommand,
21
21
  localRegistry: async () => (await import("./localRegistry.command-y176p558.js")).LocalRegistryCommand,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/cli",
3
- "version": "3.1.0-rc.11",
3
+ "version": "3.1.0-rc.13",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -39,7 +39,7 @@
39
39
  "@earendil-works/pi-tui": "1.3.0",
40
40
  "@inquirer/prompts": "^8.7.3",
41
41
  "@tailwindcss/node": "^4.3.3",
42
- "akanjs": "3.1.0-rc.11",
42
+ "akanjs": "3.1.0-rc.13",
43
43
  "chalk": "^6.0.1",
44
44
  "commander": "^15.0.0",
45
45
  "dayjs": "^1.11.23",
@@ -78,6 +78,9 @@ Stored under `.akan/code/sessions` — project history, so a second checkout of
78
78
  - `/peers` lists other `akan code` sessions running in this workspace; `/msg <peer> <text>` hands one a
79
79
  message.
80
80
  - `/clear` wipes the screen and keeps the conversation; `/compact [notes]` shortens what the model carries.
81
+ - `/autocompact <percent>` compacts on its own once the window is that full, in steps of 10 (`/autocompact 60`);
82
+ `100` is the default, which waits until 32k remain for the reply. `akan code --compact-at 60` (`-c`) starts
83
+ with it, and a share under the 20k every compaction keeps is refused.
81
84
 
82
85
  ## Model, effort, keys
83
86
 
@@ -119,6 +122,7 @@ akan code -i "<prompt>" open the session with the prompt already sent
119
122
  --app <name> narrow the agent to one app
120
123
  --profile local|pod|review|web what it may do (default local)
121
124
  --model <provider>/<id> --thinking, --json, --rpc, --resume <id>
125
+ --compact-at <percent> compact once the window is this full (10, 20, … 100)
122
126
  ```
123
127
 
124
128
  `review` reads and never writes, loads no project context files, and keeps no session on disk. `pod` has
@@ -26,7 +26,8 @@ For a model `comment` (`<Slice>` is a signal slice suffix, e.g. `InPublic`):
26
26
 
27
27
  ## Generated state — `st.use.*`
28
28
 
29
- `comment`, `commentLoading`, `commentForm`, `commentFormLoading`, `commentSubmit`, `commentModal`,
29
+ `comment`, `commentLoading`, `commentForm`, `commentFormLoading`, `commentFormUploading` (uploads in flight, per
30
+ `File` field — non-empty means do not submit), `commentSubmit`, `commentModal`,
30
31
  `commentViewAt`, `commentOperation`; per slice `commentList<Slice>`, `commentListLoading<Slice>`,
31
32
  `commentInitList<Slice>`, `commentSelection<Slice>`, `commentInsight<Slice>`, `pageOfComment<Slice>`,
32
33
  `lastPageOfComment<Slice>`, `hasMoreOfComment<Slice>`, `limitOfComment<Slice>`, `sortOfComment<Slice>`.