@alisio/alisio-code 0.1.0-alpha.4 → 0.1.0-alpha.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/tui/app.js CHANGED
@@ -183,8 +183,10 @@ export async function runTui(options) {
183
183
  path.unshift(cur.label);
184
184
  const siblings = nodes.filter((n) => n.parentId === node.parentId);
185
185
  const child = viewedState();
186
- const window = app.contextWindow(child?.model ?? view.model);
187
- const pct = child?.context && window ? ` · ctx ${Math.round((child.context.used / window) * 100)}%` : "";
186
+ const budget = app.contextBudget(child?.model ?? view.model);
187
+ const pct = child?.context && budget
188
+ ? ` · ctx ${Math.round((child.context.used / budget.total) * 100)}%`
189
+ : "";
188
190
  const tokens = child
189
191
  ? ` · ↑${formatTokens(child.stats.input)} ↓${formatTokens(child.stats.output)}`
190
192
  : "";
@@ -195,7 +197,7 @@ export async function runTui(options) {
195
197
  : panelState.focus === "view"
196
198
  ? viewHint()
197
199
  : undefined;
198
- const footer = new Footer(() => viewedState() ?? view, () => app.contextWindow((viewedState() ?? view).model), () => panelHint() ?? hint, () => [...app.plugins.status.values()].map((s) => s.text));
200
+ const footer = new Footer(() => viewedState() ?? view, () => app.contextBudget((viewedState() ?? view).model), () => panelHint() ?? hint, () => [...app.plugins.status.values()].map((s) => s.text));
199
201
  const treePanel = new TreePanel(() => {
200
202
  const entry = panelEntry();
201
203
  if (!entry)
@@ -866,7 +868,7 @@ export async function runTui(options) {
866
868
  };
867
869
  const statsReport = () => {
868
870
  const s = view.stats;
869
- const window = app.contextWindow(view.model);
871
+ const budget = app.contextBudget(view.model);
870
872
  const tools = Object.entries(s.tools)
871
873
  .map(([name, t]) => `| \`${name}\` | ${t.calls} | ${t.errors} |`)
872
874
  .join("\n");
@@ -878,7 +880,7 @@ export async function runTui(options) {
878
880
  `- Tokens: in ${formatTokens(s.input)} · out ${formatTokens(s.output)} · cached ${formatTokens(s.cached)}`,
879
881
  `- Runs: ${s.runs} · turns: ${s.turns}${s.lastRunMs !== undefined ? ` · last run ${formatDuration(s.lastRunMs)}` : ""}`,
880
882
  `- Duration: ${formatDuration(Date.now() - s.startedAt)}`,
881
- `- Context: ${formatContext(view.context?.used ?? 0, window, view.context?.estimated ?? true)}`,
883
+ `- Context: ${formatContext(view.context?.used ?? 0, budget?.total, view.context?.estimated ?? true, budget?.basis)}`,
882
884
  "",
883
885
  tools ? `| Tool | Calls | Errors |\n| --- | --- | --- |\n${tools}` : "No tool calls yet.",
884
886
  "",
@@ -2,7 +2,7 @@ import type { PanelNode } from "@alisio/sdk";
2
2
  import { type Component, Container } from "@earendil-works/pi-tui";
3
3
  import { type PendingAttachment } from "./attachments.ts";
4
4
  import { type QuestionPanelState, type QuestionSpec } from "./questions.ts";
5
- import { type TranscriptItem, type ViewState } from "./state.ts";
5
+ import { type ContextBudget, type TranscriptItem, type ViewState } from "./state.ts";
6
6
  export declare const SPINNER: string[];
7
7
  /** Shared animation clock advanced by the app while work is running. */
8
8
  export declare const clock: {
@@ -31,10 +31,10 @@ export declare class Header implements Component {
31
31
  }
32
32
  export declare class Footer implements Component {
33
33
  private view;
34
- private window;
34
+ private budget;
35
35
  private hint;
36
36
  private statuses;
37
- constructor(view: () => ViewState, window: () => number | undefined, hint: () => string | undefined, statuses?: () => string[]);
37
+ constructor(view: () => ViewState, budget: () => ContextBudget | undefined, hint: () => string | undefined, statuses?: () => string[]);
38
38
  invalidate(): void;
39
39
  render(width: number): string[];
40
40
  }
@@ -60,20 +60,20 @@ export class Header {
60
60
  }
61
61
  export class Footer {
62
62
  view;
63
- window;
63
+ budget;
64
64
  hint;
65
65
  statuses;
66
- constructor(view, window, hint, statuses = () => []) {
66
+ constructor(view, budget, hint, statuses = () => []) {
67
67
  this.view = view;
68
- this.window = window;
68
+ this.budget = budget;
69
69
  this.hint = hint;
70
70
  this.statuses = statuses;
71
71
  }
72
72
  invalidate() { }
73
73
  render(width) {
74
- const v = this.view(), total = this.window(), used = v.context?.used ?? 0, estimated = v.context?.estimated ?? true;
74
+ const v = this.view(), budget = this.budget(), total = budget?.total, used = v.context?.used ?? 0, estimated = v.context?.estimated ?? true;
75
75
  const pct = contextPercent(used, total);
76
- const level = contextLevel(pct ?? 0);
76
+ const level = contextLevel(pct ?? 0, budget?.compactionAt ?? 85);
77
77
  const cells = 10, filled = pct === undefined ? 0 : Math.min(cells, Math.round((pct / 100) * cells));
78
78
  const bar = pct === undefined
79
79
  ? style.gray("░".repeat(cells))
@@ -86,7 +86,7 @@ export class Footer {
86
86
  const s = v.stats;
87
87
  const segments = [
88
88
  {
89
- text: `ctx ${formatContext(used, total, estimated)}`,
89
+ text: `ctx ${formatContext(used, total, estimated, budget?.basis)}`,
90
90
  priority: 10,
91
91
  paint: pct === undefined ? style.gray : levelColor(level),
92
92
  },
@@ -84,9 +84,18 @@ export declare function configuredProviderModelItems(catalogs: ProviderCatalogVi
84
84
  provider: string;
85
85
  model: string;
86
86
  }): ConfiguredProviderModelItem[];
87
- export declare function contextLevel(pct: number): Level;
87
+ export declare function contextLevel(pct: number, compactionAt?: number): Level;
88
88
  export declare function contextPercent(used: number, total: number | undefined): number | undefined;
89
- export declare function formatContext(used: number, total: number | undefined, estimated: boolean): string;
89
+ /** The effective total the context bar measures against, and what it is derived from. */
90
+ export interface ContextBudget {
91
+ /** Effective total in tokens (model window, or the char budget converted to tokens). */
92
+ total: number;
93
+ /** Basis of the total: the model's context window, or the char-budget fallback. */
94
+ basis: "window" | "chars";
95
+ /** Percentage of `total` at which the engine auto-compacts; the bar turns red there. */
96
+ compactionAt: number;
97
+ }
98
+ export declare function formatContext(used: number, total: number | undefined, estimated: boolean, basis?: "window" | "chars"): string;
90
99
  export declare function formatDuration(ms: number): string;
91
100
  export declare const textWidth: (text: string) => number;
92
101
  export declare function truncatePlain(text: string, width: number): string;
package/dist/tui/state.js CHANGED
@@ -117,18 +117,23 @@ export function configuredProviderModelItems(catalogs, current) {
117
117
  }
118
118
  return items;
119
119
  }
120
- export function contextLevel(pct) {
121
- return pct < 60 ? "ok" : pct < 85 ? "warn" : "danger";
120
+ export function contextLevel(pct, compactionAt = 85) {
121
+ // Warning band starts a quarter below the auto-compaction point; danger is exactly there.
122
+ const warn = Math.max(0, compactionAt - 25);
123
+ return pct < warn ? "ok" : pct < compactionAt ? "warn" : "danger";
122
124
  }
123
125
  export function contextPercent(used, total) {
124
126
  return total && total > 0 ? (used / total) * 100 : undefined;
125
127
  }
126
- export function formatContext(used, total, estimated) {
128
+ export function formatContext(used, total, estimated, basis) {
127
129
  const prefix = `${estimated ? "~" : ""}${formatTokens(used)} / `;
128
130
  const pct = contextPercent(used, total);
129
- return pct === undefined || !total
130
- ? `${prefix}unknown`
131
- : `${prefix}${formatTokens(total)} (${Math.round(pct)}%)`;
131
+ if (pct === undefined || !total)
132
+ return `${prefix}unknown`;
133
+ // The `~` marks an estimate; the "char budget" suffix tells the user the bar is measured
134
+ // against the fallback (est. tokens from limits.maxContextChars), not a model window.
135
+ const basisSuffix = basis === "chars" ? " char budget" : "";
136
+ return `${prefix}${formatTokens(total)} (${Math.round(pct)}%)${basisSuffix}`;
132
137
  }
133
138
  export function formatDuration(ms) {
134
139
  if (ms < 1000)
@@ -461,6 +466,9 @@ export function reduceEvent(state, event) {
461
466
  const checkpoint = typeof d.summarizedTokens === "number" && typeof d.checkpointTokens === "number"
462
467
  ? ` · checkpoint ~${formatTokens(d.summarizedTokens)} → ~${formatTokens(d.checkpointTokens)} tokens`
463
468
  : "";
469
+ const partial = d.partial
470
+ ? " · partial: the summary was cut by max output tokens; consider raising compaction.maxOutputTokens"
471
+ : "";
464
472
  return addItem({
465
473
  ...state,
466
474
  compacting: false,
@@ -468,7 +476,7 @@ export function reduceEvent(state, event) {
468
476
  }, {
469
477
  kind: "notice",
470
478
  text: [
471
- `Context compacted (${String(d.reason ?? "manual")}): ${String(d.replaced ?? 0)} messages summarized, ~${formatTokens(Number(d.before ?? 0))} → ~${formatTokens(Number(d.after ?? 0))} tokens${checkpoint}`,
479
+ `Context compacted (${String(d.reason ?? "manual")}): ${String(d.replaced ?? 0)} messages summarized, ~${formatTokens(Number(d.before ?? 0))} → ~${formatTokens(Number(d.after ?? 0))} tokens${checkpoint}${partial}`,
472
480
  ...reports,
473
481
  ].join("\n"),
474
482
  });
@@ -487,6 +495,11 @@ export function reduceEvent(state, event) {
487
495
  return addItem({ ...state, compacting: false }, { kind: "notice", text: `Compaction skipped: ${String(d.detail ?? "nothing to compact")}` });
488
496
  case "compaction_failed":
489
497
  return addItem({ ...state, compacting: false }, { kind: "error", text: `Compaction failed: ${String(d.error ?? "unknown error")}` });
498
+ case "response_truncated":
499
+ return addItem(state, {
500
+ kind: "notice",
501
+ text: "Response cut by max output tokens — the answer may be incomplete. Raise limits.maxOutputTokens to allow longer answers.",
502
+ });
490
503
  case "model_changed":
491
504
  return {
492
505
  ...state,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alisio/alisio-code",
3
- "version": "0.1.0-alpha.4",
3
+ "version": "0.1.0-alpha.5",
4
4
  "description": "Alisio: an extensible, provider-agnostic coding-agent harness for your terminal. TUI, OpenAI-compatible providers, permissioned local tools, context compaction, persistent memory and a typed plugin SDK.",
5
5
  "author": "Gustavo Gutiérrez",
6
6
  "license": "MIT",
@@ -45,14 +45,14 @@
45
45
  "alisio": "./dist/main.js"
46
46
  },
47
47
  "dependencies": {
48
- "@alisio/core": "0.1.0-alpha.3",
49
- "@alisio/plugin-deepseek": "0.1.0-alpha.2",
50
- "@alisio/plugin-memory": "0.1.0-alpha.3",
51
- "@alisio/plugin-openai-compatible": "0.1.0-alpha.3",
52
- "@alisio/plugin-opencode": "0.1.0-alpha.2",
53
- "@alisio/plugin-opencode-go": "0.1.0-alpha.2",
54
- "@alisio/plugin-subagents": "0.1.0-alpha.3",
55
- "@alisio/sdk": "0.1.0-alpha.2",
48
+ "@alisio/core": "0.1.0-alpha.5",
49
+ "@alisio/plugin-deepseek": "0.1.0-alpha.5",
50
+ "@alisio/plugin-memory": "0.1.0-alpha.5",
51
+ "@alisio/plugin-openai-compatible": "0.1.0-alpha.5",
52
+ "@alisio/plugin-opencode": "0.1.0-alpha.5",
53
+ "@alisio/plugin-opencode-go": "0.1.0-alpha.5",
54
+ "@alisio/plugin-subagents": "0.1.0-alpha.5",
55
+ "@alisio/sdk": "0.1.0-alpha.5",
56
56
  "@earendil-works/pi-tui": "0.87.1",
57
57
  "commander": "15.0.0"
58
58
  },