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

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
+ 45dc4be5e34b35b9eaac8daa987756f8464382b61a5afdc7f0d60d658b487b2f
@@ -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-zjgs0t7s.js";
5
+ import"./index-ht15pm08.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-ht15pm08.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-zjgs0t7s.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-ht15pm08.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-fhrb6xyw.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-fhrb6xyw.js"),
38
+ import("./CodeTui-ntnda5dn.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-fhrb6xyw.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
@@ -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-fhrb6xyw.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 };
@@ -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-ht15pm08.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) {
@@ -2308,6 +2309,7 @@ class CodeAgent2 {
2308
2309
  #replay = [];
2309
2310
  #replayFrom = 0;
2310
2311
  #lastContextTokens;
2312
+ #autoCompactAt;
2311
2313
  constructor(profile) {
2312
2314
  this.#profile = profile;
2313
2315
  this.#gate = new CodeAgentGate2(profile);
@@ -2316,6 +2318,7 @@ class CodeAgent2 {
2316
2318
  const workspaceRoot = options.workspace.workspaceRoot;
2317
2319
  const cwd = options.cwd ?? workspaceRoot;
2318
2320
  const agent = new CodeAgent2(options.profile);
2321
+ const autoCompactAt = AkanCodeCompaction2.normalize(options.autoCompactAt);
2319
2322
  const settingsManager = AkanCodeServices2.settings();
2320
2323
  const modelRuntime = await AkanCodeServices2.runtime(workspaceRoot, CodeAgentProxy.fromEnv(options.profile));
2321
2324
  const model = await akanCodeModel2(modelRuntime, options.model);
@@ -2365,10 +2368,11 @@ class CodeAgent2 {
2365
2368
  if (options.profile.session.store !== "memory")
2366
2369
  agent.#sessionDir = akanCodePaths2.sessionsDir(workspaceRoot);
2367
2370
  await agent.#attach(session, options.mode ?? "print");
2371
+ const autoCompactWarning = agent.#fitAutoCompact(autoCompactAt);
2368
2372
  agent.#suspended = new CodeAgentSuspended(CodeAgentSuspended.fileOf(agent.#sessionDir, agent.sessionId));
2369
2373
  if (agent.#sessionDir && options.profile.session.store === "file")
2370
2374
  agent.#openMail(akanCodePaths2.mailDir(workspaceRoot), cwd);
2371
- for (const message of modelWarnings)
2375
+ for (const message of [...modelWarnings, ...autoCompactWarning])
2372
2376
  agent.#emit({ type: "notice", level: "warning", message });
2373
2377
  return agent;
2374
2378
  }
@@ -2433,6 +2437,7 @@ ${mail.text}`).catch((error) => this.#emit({ type: "notice", level: "error", mes
2433
2437
  tools: session.getActiveToolNames(),
2434
2438
  contextTokens: model?.contextWindow,
2435
2439
  effort: session.supportsThinking() ? session.thinkingLevel : undefined,
2440
+ autoCompactAt: this.#autoCompactAt,
2436
2441
  name: session.sessionName,
2437
2442
  interaction: this.#profile.interaction
2438
2443
  };
@@ -2581,8 +2586,38 @@ Continue the task.`);
2581
2586
  if (!model)
2582
2587
  throw new Error(`Unknown model: ${ref.provider}/${ref.id}`);
2583
2588
  await session.setModel(model);
2589
+ for (const message of this.#fitAutoCompact(this.#autoCompactAt))
2590
+ this.#emit({ type: "notice", level: "warning", message });
2584
2591
  this.#emit({ type: "session", info: this.info });
2585
2592
  }
2593
+ setAutoCompactAt(percent) {
2594
+ const wanted = AkanCodeCompaction2.normalize(percent);
2595
+ const problem = this.#autoCompactProblem(wanted);
2596
+ if (problem)
2597
+ throw new Error(problem);
2598
+ this.#autoCompactAt = wanted;
2599
+ this.#applyAutoCompact();
2600
+ this.#emit({ type: "session", info: this.info });
2601
+ }
2602
+ #fitAutoCompact(percent) {
2603
+ const problem = this.#autoCompactProblem(percent);
2604
+ this.#autoCompactAt = problem ? undefined : percent;
2605
+ this.#applyAutoCompact();
2606
+ return problem ? [`${problem} Autocompact is back to the default.`] : [];
2607
+ }
2608
+ #autoCompactProblem(percent) {
2609
+ const session = this.#require();
2610
+ const model = session.model;
2611
+ if (!model || !percent)
2612
+ return;
2613
+ const { keepRecentTokens } = session.settingsManager.getCompactionSettings(model);
2614
+ return AkanCodeCompaction2.problemOf(model, percent, keepRecentTokens);
2615
+ }
2616
+ #applyAutoCompact() {
2617
+ const session = this.#require();
2618
+ if (session.model)
2619
+ AkanCodeCompaction2.apply(session.settingsManager, session.model, this.#autoCompactAt);
2620
+ }
2586
2621
  async waitForIdle() {
2587
2622
  await this.#require().waitForIdle();
2588
2623
  }
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-3sxektnr.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.12",
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.12",
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>`.