@alisio/core 0.4.2 → 0.4.4
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/analysis/dashboard/hints.js +13 -7
- package/dist/changelog/data.js +31 -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/dist/decisions/activation.d.ts +2 -2
- package/dist/decisions/activation.js +12 -3
- package/dist/plugins/host.js +2 -2
- package/package.json +2 -2
|
@@ -64,14 +64,17 @@ export const PURPOSE_WORDS = [
|
|
|
64
64
|
/** Goal words that name a measure, with the column words that satisfy them. */
|
|
65
65
|
export const MEASURE_SYNONYMS = [
|
|
66
66
|
{
|
|
67
|
-
goal: ["sales", "ventas", "venta", "ingreso", "revenue"],
|
|
68
|
-
column: words("sales sale ventas venta ingresos ingreso revenue revenues amount amounts monto montos importe importes"),
|
|
67
|
+
goal: ["sales", "ventas", "venta", "ingreso", "revenue", "facturacion"],
|
|
68
|
+
column: words("sales sale ventas venta ingresos ingreso revenue revenues facturacion amount amounts monto montos importe importes"),
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
|
-
goal: ["profit", "rentabilidad", "utilidad", "ganancia", "margin", "margen"],
|
|
72
|
-
column: words("profit profits rentabilidad utilidad utilidades ganancia ganancias margin margen"),
|
|
71
|
+
goal: ["profit", "rentabilidad", "utilidad", "ganancia", "beneficio", "margin", "margen"],
|
|
72
|
+
column: words("profit profits rentabilidad utilidad utilidades ganancia ganancias beneficio beneficios margin margen"),
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
goal: ["cost", "costo", "coste", "gasto"],
|
|
76
|
+
column: words("cost costs costo costos coste costes gasto gastos"),
|
|
73
77
|
},
|
|
74
|
-
{ goal: ["cost", "costo", "gasto"], column: words("cost costs costo costos gasto gastos") },
|
|
75
78
|
{
|
|
76
79
|
goal: ["quantity", "cantidad", "unidades", "units"],
|
|
77
80
|
column: words("quantity qty cantidad units unidades"),
|
|
@@ -83,11 +86,14 @@ export function columnTokens(name, label) {
|
|
|
83
86
|
}
|
|
84
87
|
/** Dimension words that mean the same in English and Spanish (spec 6.2, goal-aware planning). */
|
|
85
88
|
export const DIMENSION_SYNONYMS = [
|
|
89
|
+
// qualifiers of a measure: a goal that says "ventas netas" names net_sales, never gross_sales
|
|
90
|
+
words("net neto neta netos netas"),
|
|
91
|
+
words("gross bruto bruta brutos brutas"),
|
|
86
92
|
words("category categories categoria categorias"),
|
|
87
93
|
words("status estado"),
|
|
88
94
|
words("seller salesperson salesman rep vendedor vendedora vendedores"),
|
|
89
95
|
words("type tipo"),
|
|
90
|
-
words("region regiones"),
|
|
96
|
+
words("region regiones regions"),
|
|
91
97
|
words("channel canal"),
|
|
92
98
|
words("segment segmento"),
|
|
93
99
|
words("country pais"),
|
|
@@ -95,7 +101,7 @@ export const DIMENSION_SYNONYMS = [
|
|
|
95
101
|
words("product producto"),
|
|
96
102
|
words("customer client cliente"),
|
|
97
103
|
words("payment pago"),
|
|
98
|
-
words("discount descuento"),
|
|
104
|
+
words("discount descuento descuentos"),
|
|
99
105
|
words("date fecha"),
|
|
100
106
|
words("month mes"),
|
|
101
107
|
words("year ano anio"),
|
package/dist/changelog/data.js
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
export const CHANGELOG_DATA = {
|
|
3
3
|
schema: 1,
|
|
4
4
|
entries: [
|
|
5
|
+
{
|
|
6
|
+
version: "0.4.4",
|
|
7
|
+
date: "2026-10-04",
|
|
8
|
+
sections: [
|
|
9
|
+
{
|
|
10
|
+
title: "Fixed",
|
|
11
|
+
items: [
|
|
12
|
+
'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.',
|
|
13
|
+
'The web shows "Compacting context…" during a manual `/compact` or an automatic one, and blocks sending (keeping your draft) until it finishes.',
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
version: "0.4.3",
|
|
20
|
+
date: "2026-10-04",
|
|
21
|
+
sections: [
|
|
22
|
+
{
|
|
23
|
+
title: "Added",
|
|
24
|
+
items: [
|
|
25
|
+
'When you run the setup command of a decision provider plugin and it asks to become the decision provider, the question now starts on "Yes (recommended)". The web question panel also starts a single-choice question on its recommended option, as the terminal already did.',
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
title: "Fixed",
|
|
30
|
+
items: [
|
|
31
|
+
'Smart Dashboard now understands Spanish net and gross phrasing: asking for "ventas netas" or "ingresos netos" charts the net sales column instead of falling back to gross sales.',
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
5
36
|
{
|
|
6
37
|
version: "0.4.2",
|
|
7
38
|
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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AskQuestionsRequest, AskQuestionsResult, DecisionActivationResult } from "@alisio/sdk";
|
|
1
|
+
import type { AskQuestionsRequest, AskQuestionsResult, DecisionActivationOptions, DecisionActivationResult } from "@alisio/sdk";
|
|
2
2
|
import type { DecisionRegistry } from "./registry.ts";
|
|
3
3
|
import type { DecisionsConfig } from "./service.ts";
|
|
4
4
|
export interface DecisionActivationDeps {
|
|
@@ -15,4 +15,4 @@ export interface DecisionActivationDeps {
|
|
|
15
15
|
* A plugin asks the host to activate one of ITS providers. The host never overwrites a configured
|
|
16
16
|
* provider, asks the user exactly once, and only then persists. Never throws.
|
|
17
17
|
*/
|
|
18
|
-
export declare function requestDecisionActivation(owner: string, providerId: unknown, deps: DecisionActivationDeps): Promise<DecisionActivationResult>;
|
|
18
|
+
export declare function requestDecisionActivation(owner: string, providerId: unknown, deps: DecisionActivationDeps, options?: DecisionActivationOptions): Promise<DecisionActivationResult>;
|
|
@@ -3,7 +3,7 @@ const QUESTION_ID = "activate";
|
|
|
3
3
|
* A plugin asks the host to activate one of ITS providers. The host never overwrites a configured
|
|
4
4
|
* provider, asks the user exactly once, and only then persists. Never throws.
|
|
5
5
|
*/
|
|
6
|
-
export async function requestDecisionActivation(owner, providerId, deps) {
|
|
6
|
+
export async function requestDecisionActivation(owner, providerId, deps, options) {
|
|
7
7
|
if (typeof providerId !== "string" || deps.registry.get(providerId)?.owner !== owner)
|
|
8
8
|
return {
|
|
9
9
|
status: "unavailable",
|
|
@@ -18,6 +18,10 @@ export async function requestDecisionActivation(owner, providerId, deps) {
|
|
|
18
18
|
return { status: "other_provider_active", active: config.provider };
|
|
19
19
|
if (!deps.interactive())
|
|
20
20
|
return { status: "needs_confirmation" };
|
|
21
|
+
// Only an explicit `recommend: true` moves the pre-selection to Yes; anything else is ignored.
|
|
22
|
+
const recommendYes = typeof options === "object" &&
|
|
23
|
+
options !== null &&
|
|
24
|
+
options.recommend === true;
|
|
21
25
|
let answer;
|
|
22
26
|
try {
|
|
23
27
|
answer = await deps.ask({
|
|
@@ -28,12 +32,17 @@ export async function requestDecisionActivation(owner, providerId, deps) {
|
|
|
28
32
|
header: "Decision provider",
|
|
29
33
|
question: `Plugin ${owner} wants to become the decision provider. Dashboards will send your request goal and column names, never values, to it. Activate it?`,
|
|
30
34
|
options: [
|
|
31
|
-
{
|
|
35
|
+
{
|
|
36
|
+
value: "yes",
|
|
37
|
+
label: "Yes",
|
|
38
|
+
description: "Save it in your global configuration.",
|
|
39
|
+
...(recommendYes ? { recommended: true } : {}),
|
|
40
|
+
},
|
|
32
41
|
{
|
|
33
42
|
value: "no",
|
|
34
43
|
label: "No",
|
|
35
44
|
description: "Keep decisions unchanged.",
|
|
36
|
-
recommended: true,
|
|
45
|
+
...(recommendYes ? {} : { recommended: true }),
|
|
37
46
|
},
|
|
38
47
|
],
|
|
39
48
|
},
|
package/dist/plugins/host.js
CHANGED
|
@@ -439,13 +439,13 @@ export class PluginHost {
|
|
|
439
439
|
...(activation
|
|
440
440
|
? {
|
|
441
441
|
// Only providers THIS plugin registered; the user confirms once.
|
|
442
|
-
activate: (providerId) => requestDecisionActivation(plugin.id, providerId, {
|
|
442
|
+
activate: (providerId, options) => requestDecisionActivation(plugin.id, providerId, {
|
|
443
443
|
registry,
|
|
444
444
|
config: activation.config,
|
|
445
445
|
interactive: () => !!this.uiImpl,
|
|
446
446
|
ask: (request) => this.uiImpl ? this.uiImpl.askQuestions(request) : Promise.resolve({}),
|
|
447
447
|
persist: activation.persist,
|
|
448
|
-
}),
|
|
448
|
+
}, options),
|
|
449
449
|
}
|
|
450
450
|
: {}),
|
|
451
451
|
}))(this.decisionsImpl),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alisio/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
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.4",
|
|
45
45
|
"@mixmark-io/domino": "2.2.0",
|
|
46
46
|
"@modelcontextprotocol/client": "2.1.0",
|
|
47
47
|
"ajv": "8.20.0",
|