@alisio/core 0.4.3 → 0.4.5
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/dist/application.js +16 -0
- package/dist/changelog/data.js +26 -0
- package/dist/core/contracts.d.ts +2 -2
- package/dist/core/runner.d.ts +2 -0
- package/dist/core/runner.js +62 -10
- package/package.json +2 -2
package/dist/application.js
CHANGED
|
@@ -321,6 +321,22 @@ export async function createApplication(options = {}) {
|
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* Plugin resources (`api.resources.agents`) register during activation, but the built-in
|
|
326
|
+
* subagents plugin discovers agent files in its own setup, which runs BEFORE external plugins
|
|
327
|
+
* (config, command line, plugin directories) are activated. Re-run its discovery once every
|
|
328
|
+
* plugin has registered, so the first `/api/agents` (web selector and Agents page, TUI cycle)
|
|
329
|
+
* already lists the plugin primary agents instead of waiting for a manual `/agents reload`.
|
|
330
|
+
*/
|
|
331
|
+
const rediscoverAgents = plugins.commands.get("agents");
|
|
332
|
+
if (rediscoverAgents) {
|
|
333
|
+
try {
|
|
334
|
+
await rediscoverAgents("reload");
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
/* keeps the built-ins: agent discovery is best-effort */
|
|
338
|
+
}
|
|
339
|
+
}
|
|
324
340
|
// Every plugin has registered its providers: start the configured one in the background
|
|
325
341
|
// (`activate` is bounded, never fatal and never awaited here).
|
|
326
342
|
decisionService.syncActive();
|
package/dist/changelog/data.js
CHANGED
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
export const CHANGELOG_DATA = {
|
|
3
3
|
schema: 1,
|
|
4
4
|
entries: [
|
|
5
|
+
{
|
|
6
|
+
version: "0.4.5",
|
|
7
|
+
date: "2026-10-07",
|
|
8
|
+
sections: [
|
|
9
|
+
{
|
|
10
|
+
title: "Fixed",
|
|
11
|
+
items: [
|
|
12
|
+
"The chat agent selector, the Agents page and Shift+Tab list a plugin's `primary` agents on the first open, instead of only the built-ins until you visited the Agents window and came back.",
|
|
13
|
+
'Choosing "Full access" applies to the session that is already running: it stops asking for approvals from the next tool call, not only on the following turn.',
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
version: "0.4.4",
|
|
20
|
+
date: "2026-10-04",
|
|
21
|
+
sections: [
|
|
22
|
+
{
|
|
23
|
+
title: "Fixed",
|
|
24
|
+
items: [
|
|
25
|
+
'Automatic compaction now also triggers at 85 % of `limits.maxContextChars`, so a long conversation with a large-window model is compacted before the run fails instead of stopping with "Context budget exceeded" until you ran `/compact` yourself. A last-chance compaction runs once before a run fails, and the final error now says whether compaction ran, was skipped, failed or is disabled.',
|
|
26
|
+
'The web shows "Compacting context…" during a manual `/compact` or an automatic one, and blocks sending (keeping your draft) until it finishes.',
|
|
27
|
+
],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
},
|
|
5
31
|
{
|
|
6
32
|
version: "0.4.3",
|
|
7
33
|
date: "2026-10-04",
|
package/dist/core/contracts.d.ts
CHANGED
|
@@ -302,7 +302,7 @@ export interface HookFailure {
|
|
|
302
302
|
export interface RunnerExtensions {
|
|
303
303
|
beforeCompact(input: {
|
|
304
304
|
sessionId: string;
|
|
305
|
-
reason: "manual" | "auto";
|
|
305
|
+
reason: "manual" | "auto" | "budget";
|
|
306
306
|
messages: readonly Message[];
|
|
307
307
|
focus?: string;
|
|
308
308
|
}): Promise<{
|
|
@@ -316,7 +316,7 @@ export interface RunnerExtensions {
|
|
|
316
316
|
}>;
|
|
317
317
|
afterCompact(input: {
|
|
318
318
|
sessionId: string;
|
|
319
|
-
reason: "manual" | "auto";
|
|
319
|
+
reason: "manual" | "auto" | "budget";
|
|
320
320
|
messages: readonly Message[];
|
|
321
321
|
focus?: string;
|
|
322
322
|
model: string;
|
package/dist/core/runner.d.ts
CHANGED
|
@@ -189,6 +189,8 @@ export declare class AgentRunner {
|
|
|
189
189
|
private inbox;
|
|
190
190
|
/** Last provider-reported context size per session and the history length it covered. */
|
|
191
191
|
private reported;
|
|
192
|
+
/** Conversation size in characters right after the last automatic compaction, per session. */
|
|
193
|
+
private compactedChars;
|
|
192
194
|
/** Where human waits are opened; the clocks of the active runs listen to it. */
|
|
193
195
|
readonly humanWaits: HumanWaits;
|
|
194
196
|
constructor(options: RunnerOptions);
|
package/dist/core/runner.js
CHANGED
|
@@ -41,6 +41,13 @@ const MAX_INJECT_CHARS = 24_000;
|
|
|
41
41
|
* fails with an actionable error instead of silently losing the whole history.
|
|
42
42
|
*/
|
|
43
43
|
const MIN_MESSAGE_BUDGET_FLOOR = 4_000;
|
|
44
|
+
/**
|
|
45
|
+
* Fraction of the character limit the conversation must grow beyond its post-compaction size
|
|
46
|
+
* before the CHARACTER-based auto-compaction trigger may fire again. When the kept tail alone
|
|
47
|
+
* sits at or above the trigger, summarizing gains nothing, so without this margin every turn
|
|
48
|
+
* would pay for a summarizer call. The hard-limit last-chance compaction ignores it.
|
|
49
|
+
*/
|
|
50
|
+
const RECOMPACT_GROWTH_MARGIN = 0.1;
|
|
44
51
|
const allowed = (policy, effect) => effect === "read" ||
|
|
45
52
|
effect === "internal" ||
|
|
46
53
|
((effect === "write" || effect === "process" || effect === "external") && !!policy[effect]);
|
|
@@ -62,6 +69,8 @@ export class AgentRunner {
|
|
|
62
69
|
inbox = new Map();
|
|
63
70
|
/** Last provider-reported context size per session and the history length it covered. */
|
|
64
71
|
reported = new Map();
|
|
72
|
+
/** Conversation size in characters right after the last automatic compaction, per session. */
|
|
73
|
+
compactedChars = new Map();
|
|
65
74
|
/** Where human waits are opened; the clocks of the active runs listen to it. */
|
|
66
75
|
humanWaits;
|
|
67
76
|
constructor(options) {
|
|
@@ -907,11 +916,24 @@ export class AgentRunner {
|
|
|
907
916
|
// unknown (or absurdly large, see MAX_TRUSTED_WINDOW) window falls back to the char
|
|
908
917
|
// estimate (`chars / 4 >= maxContextChars / 4`). `maxContextChars` below stays as the
|
|
909
918
|
// post-compaction hard limit.
|
|
919
|
+
// The character budget is an independent trigger: `maxContextChars` is a hard limit for
|
|
920
|
+
// every model, so a large-window model must compact at `limit * threshold` characters
|
|
921
|
+
// too instead of reaching the hard check with compaction never having run.
|
|
922
|
+
const threshold = compaction.threshold ?? 0.85;
|
|
923
|
+
// What automatic compaction did this turn (at most ONE attempt per turn).
|
|
924
|
+
let attempt = "none";
|
|
925
|
+
let attemptError = "";
|
|
910
926
|
if (compaction.auto !== false &&
|
|
911
|
-
shouldCompactContext(used(), chars(), o.contextWindow?.(model), limit,
|
|
912
|
-
|
|
927
|
+
(shouldCompactContext(used(), chars(), o.contextWindow?.(model), limit, threshold) ||
|
|
928
|
+
(charsConversation() >= limit * threshold &&
|
|
929
|
+
charsConversation() >=
|
|
930
|
+
(this.compactedChars.get(sessionId) ?? 0) + limit * RECOMPACT_GROWTH_MARGIN))) {
|
|
931
|
+
const result = await this.compactLocked(sessionId, emit, model, combined, "auto");
|
|
932
|
+
attempt = result ? "ran" : "skipped";
|
|
913
933
|
instructions = await withPersona();
|
|
914
934
|
messages = o.store.messages(sessionId);
|
|
935
|
+
if (result)
|
|
936
|
+
this.compactedChars.set(sessionId, charsConversation());
|
|
915
937
|
}
|
|
916
938
|
// A compaction that still leaves the session over the hard limit is reduced in place
|
|
917
939
|
// instead of failing outright: retained content is clipped against a TOTAL character
|
|
@@ -925,15 +947,45 @@ export class AgentRunner {
|
|
|
925
947
|
// prompts. Only if even the reduction floor cannot fit (instructions alone crowd out the
|
|
926
948
|
// limit) the run fails with an actionable error.
|
|
927
949
|
if (charsConversation() > limit) {
|
|
928
|
-
const
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
950
|
+
const reduceInPlace = () => {
|
|
951
|
+
const targetChars = Math.max(MIN_MESSAGE_BUDGET_FLOOR, limit - instructions.length);
|
|
952
|
+
const reduction = reduceMessagesToBudget(messages, targetChars);
|
|
953
|
+
if (reduction.truncated > 0) {
|
|
954
|
+
o.store.overwrite(sessionId, reduction.messages);
|
|
955
|
+
messages = reduction.messages;
|
|
956
|
+
emit("context_reduced", { messages: reduction.truncated });
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
reduceInPlace();
|
|
960
|
+
// Last chance: clipping alone was not enough and no compaction was attempted this
|
|
961
|
+
// turn, so summarize history once (reason "budget") and reduce again before failing.
|
|
962
|
+
if (charsConversation() > limit && compaction.auto !== false && attempt === "none") {
|
|
963
|
+
try {
|
|
964
|
+
const result = await this.compactLocked(sessionId, emit, model, combined, "budget");
|
|
965
|
+
attempt = result ? "ran" : "skipped";
|
|
966
|
+
}
|
|
967
|
+
catch (error) {
|
|
968
|
+
combined.throwIfAborted();
|
|
969
|
+
attempt = "failed";
|
|
970
|
+
attemptError = error instanceof Error ? error.message : String(error);
|
|
971
|
+
}
|
|
972
|
+
instructions = await withPersona();
|
|
973
|
+
messages = o.store.messages(sessionId);
|
|
974
|
+
if (charsConversation() > limit)
|
|
975
|
+
reduceInPlace();
|
|
976
|
+
if (attempt === "ran")
|
|
977
|
+
this.compactedChars.set(sessionId, charsConversation());
|
|
978
|
+
}
|
|
979
|
+
if (charsConversation() > limit) {
|
|
980
|
+
const what = compaction.auto === false
|
|
981
|
+
? "Automatic compaction is disabled (compaction.auto is false)."
|
|
982
|
+
: attempt === "ran"
|
|
983
|
+
? "Automatic compaction ran but could not reduce it enough."
|
|
984
|
+
: attempt === "failed"
|
|
985
|
+
? `Automatic compaction failed (${attemptError}).`
|
|
986
|
+
: "Automatic compaction was skipped because there is no safe boundary to summarize (the conversation is a single long turn or too short).";
|
|
987
|
+
throw new Error(`Context budget exceeded (approximately ${charsConversation()} characters of conversation; limit ${limit}). ${what} Run /compact, trim large tool outputs, start a new session, or disable unneeded MCP servers with /plugins.`);
|
|
934
988
|
}
|
|
935
|
-
if (charsConversation() > limit)
|
|
936
|
-
throw new Error(`Context budget exceeded and compaction could not reduce it (approximately ${charsConversation()} characters of conversation; limit ${limit}). Run /compact, trim large tool outputs, start a new session, or disable unneeded MCP servers with /plugins.`);
|
|
937
989
|
}
|
|
938
990
|
let completion;
|
|
939
991
|
let truncated = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alisio/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "Embeddable provider-agnostic Alisio agent core: tool loop, context compaction, local tools, MCP, plugin host and SQLite session store. Runs on Node >=22.16 and Bun.",
|
|
5
5
|
"author": "Gustavo Gutiérrez",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"node": ">=22.16"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@alisio/sdk": "0.4.
|
|
44
|
+
"@alisio/sdk": "0.4.5",
|
|
45
45
|
"@mixmark-io/domino": "2.2.0",
|
|
46
46
|
"@modelcontextprotocol/client": "2.1.0",
|
|
47
47
|
"ajv": "8.20.0",
|