@akanjs/cli 3.1.0-rc.10 → 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 +1 -1
- package/{CodeAgent-rkjyxgec.js → CodeAgent-fhrb6xyw.js} +2 -2
- package/{CodeAgentRpcHost-p7h6n2hp.js → CodeAgentRpcHost-9g27hmer.js} +1 -1
- package/{CodeTui-h7pyj6xa.js → CodeTui-ntnda5dn.js} +47 -4
- package/akanCode.js +7 -5
- package/{code.command-0ad6gw2a.js → code.command-3sxektnr.js} +23 -10
- package/guidelines/conventions/conventions.instruction.md +3 -1
- package/guidelines/fieldRule/fieldRule.instruction.md +12 -0
- package/guidelines/queryRule/queryRule.instruction.md +3 -1
- package/{index-wa5jbxj1.js → index-2mrn1mrg.js} +1 -0
- package/{index-d2242ksy.js → index-at7bz0xq.js} +3 -0
- package/{index-20bxxyvn.js → index-ht15pm08.js} +40 -2
- package/{index-8fp3b47h.js → index-zjgs0t7s.js} +39 -4
- package/index.js +1 -1
- package/package.json +2 -2
- package/skills/akan-code/SKILL.md +4 -0
- package/skills/akan-store/SKILL.md +2 -1
package/.build-stamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
45dc4be5e34b35b9eaac8daa987756f8464382b61a5afdc7f0d60d658b487b2f
|
|
@@ -3,8 +3,9 @@ import {
|
|
|
3
3
|
SubagentPool2,
|
|
4
4
|
McpServerConfig,
|
|
5
5
|
McpTokenStore,
|
|
6
|
-
McpSignIn
|
|
7
|
-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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
|
-
|
|
41
|
+
akanCodePaths2,
|
|
42
|
+
AkanCodeCompaction2
|
|
43
|
+
} from "./index-ht15pm08.js";
|
|
43
44
|
import"./index-7md2g9vw.js";
|
|
44
45
|
import {
|
|
45
46
|
CodeAgentRpcHost2
|
|
46
|
-
} from "./index-
|
|
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-
|
|
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-
|
|
23
|
-
import("./CodeAgentRpcHost-
|
|
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-
|
|
38
|
-
import("./CodeTui-
|
|
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({
|
|
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-
|
|
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("
|
|
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-
|
|
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
|
-
|
|
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:
|
|
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-
|
|
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.
|
|
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.
|
|
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`, `
|
|
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>`.
|