@alisio/alisio-code 0.1.0-alpha.26 → 0.1.0-alpha.28
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/main.js +17 -1
- package/dist/tui/components.js +15 -3
- package/dist/tui/settings.d.ts +2 -1
- package/dist/tui/settings.js +5 -4
- package/dist/tui/state.d.ts +25 -0
- package/dist/tui/state.js +98 -2
- package/package.json +5 -5
package/dist/main.js
CHANGED
|
@@ -127,6 +127,14 @@ async function run(cmd, prompt, sessionId) {
|
|
|
127
127
|
process.stdout.write(`${JSON.stringify(event)}\n`);
|
|
128
128
|
else if (event.type === "text_delta")
|
|
129
129
|
process.stdout.write(String(event.data.delta));
|
|
130
|
+
else if (event.type === "request_retry") {
|
|
131
|
+
const retry = event.data;
|
|
132
|
+
process.stderr.write(`\nThe model did not respond; retrying (${retry.attempt}/${retry.of})…\n`);
|
|
133
|
+
}
|
|
134
|
+
else if (event.type === "truncation_recovery") {
|
|
135
|
+
const recovery = event.data;
|
|
136
|
+
process.stderr.write(`\nThe response was cut off by the output limit; retrying in smaller steps (${recovery.attempt}/${recovery.of})…\n`);
|
|
137
|
+
}
|
|
130
138
|
else if (event.type === "tool_started")
|
|
131
139
|
process.stderr.write(`\n→ ${event.data.name}\n`);
|
|
132
140
|
else if (event.type === "artifact_published")
|
|
@@ -314,7 +322,7 @@ program
|
|
|
314
322
|
console.log('To generate AGENTS.md for this project, run /init inside alisio (or: alisio run "/init" --allow-write).');
|
|
315
323
|
});
|
|
316
324
|
program.command("doctor").action(async (_opts, cmd) => {
|
|
317
|
-
const { findWorkspace, loadConfigWithProvenance, overridesSavedProviderProfile, ProviderSettingsStore, RIPGREP_INSTALL_HINT, which, } = await import("@alisio/core");
|
|
325
|
+
const { DEFAULT_MAX_OUTPUT_TOKENS, findWorkspace, loadConfigWithProvenance, MAX_AUTO_OUTPUT_TOKENS, overridesSavedProviderProfile, ProviderSettingsStore, RIPGREP_INSTALL_HINT, which, } = await import("@alisio/core");
|
|
318
326
|
const o = options(cmd);
|
|
319
327
|
const workspace = await findWorkspace(o.cwd ?? process.cwd());
|
|
320
328
|
const { config, provenance } = await loadConfigWithProvenance(workspace, {
|
|
@@ -337,6 +345,14 @@ program.command("doctor").action(async (_opts, cmd) => {
|
|
|
337
345
|
workspace,
|
|
338
346
|
git: (await which("git")) ?? null,
|
|
339
347
|
ripgrep: (await which("rg")) ?? null,
|
|
348
|
+
// The per-request output budget is resolved when a run starts, from the model's catalog;
|
|
349
|
+
// doctor does not contact the provider, so it reports what the configuration decides.
|
|
350
|
+
outputTokens: config.limits.maxOutputTokens !== undefined
|
|
351
|
+
? { limit: config.limits.maxOutputTokens, source: "set by you (limits.maxOutputTokens)" }
|
|
352
|
+
: {
|
|
353
|
+
limit: null,
|
|
354
|
+
source: `the model catalog's declared limit (capped at ${MAX_AUTO_OUTPUT_TOKENS}), else ${DEFAULT_MAX_OUTPUT_TOKENS}`,
|
|
355
|
+
},
|
|
340
356
|
provider: {
|
|
341
357
|
id: useSaved ? saved.profile.provider : "openai-compatible",
|
|
342
358
|
baseURL: useSaved ? saved.profile.values.baseURL : config.provider.baseURL,
|
package/dist/tui/components.js
CHANGED
|
@@ -6,7 +6,7 @@ import { attachmentCaption, MAX_ATTACHMENTS_PER_MESSAGE, } from "./attachments.j
|
|
|
6
6
|
import { btwCurrent, btwHints, btwPosition, btwUsageLine, btwWindow, reduceBtw, } from "./btw.js";
|
|
7
7
|
import { branchDisplay } from "./git-branch.js";
|
|
8
8
|
import { initialQuestionState, reduceQuestions, } from "./questions.js";
|
|
9
|
-
import { contextLevel, contextPercent, editSummary, entryKeyOf, fitIdentityParts, fitSegments, foldCandidateOf, formatContext, formatDuration, formatTokens, groupIdentity, groupToolEntries, humanizeToolName, isUiBlock, previewLinesFor, previewRows, REASONING_EXPAND_MAX_LINES, reasoningDurationMs, } from "./state.js";
|
|
9
|
+
import { contextLevel, contextPercent, editSummary, entryKeyOf, fitIdentityParts, fitSegments, foldCandidateOf, formatContext, formatDuration, formatTokens, groupIdentity, groupToolEntries, humanizeToolName, isUiBlock, previewLinesFor, previewRows, quietFor, REASONING_EXPAND_MAX_LINES, reasoningDurationMs, runPhase, STALLED_MS, } from "./state.js";
|
|
10
10
|
import { highlightCode, imageTheme, levelColor, markdownDefaultTextStyle, markdownTheme, markdownTransform, sanitizeCode, style, } from "./theme.js";
|
|
11
11
|
export const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
12
12
|
/** Shared animation clock advanced by the app while work is running. */
|
|
@@ -437,10 +437,14 @@ export class Footer {
|
|
|
437
437
|
const bar = pct === undefined
|
|
438
438
|
? style.gray("░".repeat(cells))
|
|
439
439
|
: levelColor(level)("█".repeat(filled)) + style.gray("░".repeat(cells - filled));
|
|
440
|
+
const quiet = v.streaming ? quietFor(v, clock.now) : 0;
|
|
441
|
+
const quietNote = quiet
|
|
442
|
+
? ` · no response for ${formatDuration(quiet)}${quiet >= STALLED_MS ? " (stuck?)" : ""}`
|
|
443
|
+
: "";
|
|
440
444
|
const state = v.compacting
|
|
441
445
|
? `${SPINNER[clock.frame % SPINNER.length]} compacting`
|
|
442
446
|
: v.streaming
|
|
443
|
-
? `${SPINNER[clock.frame % SPINNER.length]}
|
|
447
|
+
? `${SPINNER[clock.frame % SPINNER.length]} ${runPhase(v).label} ${formatDuration(clock.now - (v.runStartedAt ?? clock.now))}${quietNote}`
|
|
444
448
|
: "● idle";
|
|
445
449
|
const s = v.stats;
|
|
446
450
|
const segments = [
|
|
@@ -449,7 +453,15 @@ export class Footer {
|
|
|
449
453
|
priority: 10,
|
|
450
454
|
paint: pct === undefined ? style.gray : levelColor(level),
|
|
451
455
|
},
|
|
452
|
-
{
|
|
456
|
+
{
|
|
457
|
+
text: state,
|
|
458
|
+
priority: 9,
|
|
459
|
+
paint: quiet >= STALLED_MS
|
|
460
|
+
? style.yellow
|
|
461
|
+
: v.streaming || v.compacting
|
|
462
|
+
? style.cyan
|
|
463
|
+
: style.green,
|
|
464
|
+
},
|
|
453
465
|
{
|
|
454
466
|
text: `↑${formatTokens(s.input)} ↓${formatTokens(s.output)}${s.cached ? ` ⚡${formatTokens(s.cached)}` : ""}`,
|
|
455
467
|
priority: 6,
|
package/dist/tui/settings.d.ts
CHANGED
|
@@ -50,7 +50,8 @@ export interface SettingsConfigView {
|
|
|
50
50
|
};
|
|
51
51
|
limits: {
|
|
52
52
|
maxTurns: number;
|
|
53
|
-
|
|
53
|
+
/** Only present when the user set it; unset means "the model's declared limit, else 16384". */
|
|
54
|
+
maxOutputTokens?: number | undefined;
|
|
54
55
|
maxContextChars: number;
|
|
55
56
|
timeoutMs: number;
|
|
56
57
|
};
|
package/dist/tui/settings.js
CHANGED
|
@@ -12,7 +12,7 @@ export const WEBSEARCH_PROVIDERS = [
|
|
|
12
12
|
export const defaultConfig = {
|
|
13
13
|
compaction: { auto: true, threshold: 0.85, keepTurns: 2, maxOutputTokens: 16_000 },
|
|
14
14
|
context: { claudeMdFallback: false, maxBytes: 32 * 1024 },
|
|
15
|
-
limits: { maxTurns: 100,
|
|
15
|
+
limits: { maxTurns: 100, maxContextChars: 800_000, timeoutMs: 300_000 },
|
|
16
16
|
tui: { paddingX: 1, contentPaddingX: 2, skillSlashCommands: true },
|
|
17
17
|
mcp: { allow: false },
|
|
18
18
|
websearch: { provider: undefined },
|
|
@@ -125,9 +125,10 @@ export const SETTINGS_DEFINITIONS = [
|
|
|
125
125
|
label: "Agent max output tokens",
|
|
126
126
|
category: "Limits",
|
|
127
127
|
valueType: "number",
|
|
128
|
-
values: [1_024, 2_048, 4_096, 8_192, 16_384],
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
values: [1_024, 2_048, 4_096, 8_192, 16_384, 32_768, 65_536],
|
|
129
|
+
// Unset: each request uses the model's declared limit (capped at 65536), else 16384.
|
|
130
|
+
read: (config) => config.limits.maxOutputTokens ?? 16_384,
|
|
131
|
+
description: "Per-call output token budget for agent turns. While you leave it unchanged, each request uses the output limit the model's catalog declares (capped at 65536), or 16384 when it declares none; a value set here always wins. A cut response shows a notice suggesting a higher value. Applied from the next run.",
|
|
131
132
|
},
|
|
132
133
|
{
|
|
133
134
|
id: "limits.maxContextChars",
|
package/dist/tui/state.d.ts
CHANGED
|
@@ -343,6 +343,16 @@ export interface ViewState {
|
|
|
343
343
|
items: TranscriptItem[];
|
|
344
344
|
streaming: boolean;
|
|
345
345
|
compacting: boolean;
|
|
346
|
+
/** The runner is sending a silent request again (`request_retry`); cleared by any output. */
|
|
347
|
+
retry?: {
|
|
348
|
+
attempt: number;
|
|
349
|
+
of: number;
|
|
350
|
+
};
|
|
351
|
+
/** The response was cut by the output limit and is requested again (`truncation_recovery`). */
|
|
352
|
+
recovery?: {
|
|
353
|
+
attempt: number;
|
|
354
|
+
of: number;
|
|
355
|
+
};
|
|
346
356
|
model: string;
|
|
347
357
|
/** Context size in tokens: provider-reported, or estimated (`~`). */
|
|
348
358
|
context?: {
|
|
@@ -350,6 +360,8 @@ export interface ViewState {
|
|
|
350
360
|
estimated: boolean;
|
|
351
361
|
};
|
|
352
362
|
runStartedAt?: number;
|
|
363
|
+
/** Epoch ms of the latest event of the live run (any type); undefined when no run is live. */
|
|
364
|
+
lastEventAt?: number;
|
|
353
365
|
stats: Stats;
|
|
354
366
|
}
|
|
355
367
|
export declare function initialViewState(model: string, now?: number): ViewState;
|
|
@@ -360,6 +372,19 @@ export declare function reasoningDurationMs(item: {
|
|
|
360
372
|
reasoningDoneAt?: number;
|
|
361
373
|
}): number | undefined;
|
|
362
374
|
export declare function reduceEvent(state: ViewState, event: RunEvent): ViewState;
|
|
375
|
+
/** No event for this long: the footer says how long the run has been quiet. */
|
|
376
|
+
export declare const QUIET_MS = 15000;
|
|
377
|
+
/** No event for this long: the footer marks the run as possibly stuck. */
|
|
378
|
+
export declare const STALLED_MS = 60000;
|
|
379
|
+
export interface RunPhase {
|
|
380
|
+
label: string;
|
|
381
|
+
/** Silence is expected here (the user's turn, or a sub-agent working in its own session). */
|
|
382
|
+
silentByDesign: boolean;
|
|
383
|
+
}
|
|
384
|
+
/** What the live run is doing, from the transcript items and flags the events already update. */
|
|
385
|
+
export declare function runPhase(view: ViewState): RunPhase;
|
|
386
|
+
/** Milliseconds of silence worth reporting (0 when the run is busy, or silent by design). */
|
|
387
|
+
export declare function quietFor(view: ViewState, now: number): number;
|
|
363
388
|
export declare function lastAssistantText(items: TranscriptItem[]): string | undefined;
|
|
364
389
|
/**
|
|
365
390
|
* True when a printable key should copy the last assistant response instead of typing: bound to
|
package/dist/tui/state.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Pure presentation logic for the TUI: formatting, command parsing and the reduction of
|
|
3
3
|
* versioned runner events into a view model. No terminal or pi-tui imports here.
|
|
4
4
|
*/
|
|
5
|
-
import { CommandCatalog } from "@alisio/core";
|
|
5
|
+
import { CommandCatalog, describeOutputLimitSource } from "@alisio/core";
|
|
6
6
|
const trimZero = (value) => value.replace(/\.0$/, "");
|
|
7
7
|
export function formatTokens(n) {
|
|
8
8
|
if (!Number.isFinite(n) || n < 0)
|
|
@@ -577,6 +577,96 @@ function updateTool(state, id, update) {
|
|
|
577
577
|
return { ...state, items };
|
|
578
578
|
}
|
|
579
579
|
export function reduceEvent(state, event) {
|
|
580
|
+
const next = reduceEventItems(state, event);
|
|
581
|
+
const terminal = event.type === "run_completed" ||
|
|
582
|
+
event.type === "run_failed" ||
|
|
583
|
+
event.type === "run_cancelled" ||
|
|
584
|
+
event.type === "run_turns_exceeded";
|
|
585
|
+
if (terminal) {
|
|
586
|
+
const { lastEventAt: _ended, retry: _retry, recovery: _recovery, ...rest } = next;
|
|
587
|
+
return rest;
|
|
588
|
+
}
|
|
589
|
+
return { ...next, lastEventAt: Date.parse(event.timestamp) || Date.now() };
|
|
590
|
+
}
|
|
591
|
+
/** No event for this long: the footer says how long the run has been quiet. */
|
|
592
|
+
export const QUIET_MS = 15_000;
|
|
593
|
+
/** No event for this long: the footer marks the run as possibly stuck. */
|
|
594
|
+
export const STALLED_MS = 60_000;
|
|
595
|
+
/** A short, safe label from tool arguments: a title or a file name, never commands or URLs. */
|
|
596
|
+
function shortArg(args) {
|
|
597
|
+
const input = parseArgs(args);
|
|
598
|
+
if (!input)
|
|
599
|
+
return "";
|
|
600
|
+
const first = Array.isArray(input.inputs) ? input.inputs[0] : {};
|
|
601
|
+
const value = [input.title, input.fileName, input.path, input.file_path, first?.path].find((v) => typeof v === "string" && v.trim() !== "");
|
|
602
|
+
if (!value)
|
|
603
|
+
return "";
|
|
604
|
+
return truncatePlain(oneLine(value.replace(/\\/g, "/").split("/").filter(Boolean).pop() ?? ""), 40);
|
|
605
|
+
}
|
|
606
|
+
/** What the live run is doing, from the transcript items and flags the events already update. */
|
|
607
|
+
export function runPhase(view) {
|
|
608
|
+
if (view.compacting)
|
|
609
|
+
return { label: "compacting context", silentByDesign: false };
|
|
610
|
+
if (view.retry)
|
|
611
|
+
return {
|
|
612
|
+
label: `the model did not respond; retrying (${view.retry.attempt}/${view.retry.of})`,
|
|
613
|
+
silentByDesign: false,
|
|
614
|
+
};
|
|
615
|
+
if (view.recovery)
|
|
616
|
+
return {
|
|
617
|
+
label: `the response was cut off; retrying in smaller steps (${view.recovery.attempt}/${view.recovery.of})`,
|
|
618
|
+
silentByDesign: false,
|
|
619
|
+
};
|
|
620
|
+
const tool = view.items.findLast((i) => i.kind === "tool" && i.status !== "ok" && i.status !== "error");
|
|
621
|
+
if (tool?.kind === "tool") {
|
|
622
|
+
const name = tool.name.replace(/^p_[0-9a-f]{10}_/, "");
|
|
623
|
+
const arg = shortArg(tool.args);
|
|
624
|
+
const detail = arg ? ` (${arg})` : "";
|
|
625
|
+
if (tool.status === "approval")
|
|
626
|
+
return {
|
|
627
|
+
label: `waiting for your approval: ${humanizeToolName(name)}`,
|
|
628
|
+
silentByDesign: true,
|
|
629
|
+
};
|
|
630
|
+
if (name === "ask_user_question")
|
|
631
|
+
return { label: "waiting for your answer", silentByDesign: true };
|
|
632
|
+
if (name === "task" || name === "task_wait")
|
|
633
|
+
return { label: "a sub-agent is working", silentByDesign: true };
|
|
634
|
+
if (name === "python_run")
|
|
635
|
+
return { label: `running Python${detail}`, silentByDesign: false };
|
|
636
|
+
if (name === "data_inspect" || name === "data_query")
|
|
637
|
+
return { label: `reading your data${detail}`, silentByDesign: false };
|
|
638
|
+
if (name === "artifact_create")
|
|
639
|
+
return { label: `publishing the artifact${detail}`, silentByDesign: false };
|
|
640
|
+
return { label: `running ${humanizeToolName(name)}${detail}`, silentByDesign: false };
|
|
641
|
+
}
|
|
642
|
+
const last = view.items.at(-1);
|
|
643
|
+
if (last?.kind === "assistant" && !last.done)
|
|
644
|
+
return {
|
|
645
|
+
label: last.text ? "writing the answer" : "thinking",
|
|
646
|
+
silentByDesign: false,
|
|
647
|
+
};
|
|
648
|
+
return { label: "waiting for the model", silentByDesign: false };
|
|
649
|
+
}
|
|
650
|
+
/** Milliseconds of silence worth reporting (0 when the run is busy, or silent by design). */
|
|
651
|
+
export function quietFor(view, now) {
|
|
652
|
+
if (view.lastEventAt === undefined || runPhase(view).silentByDesign)
|
|
653
|
+
return 0;
|
|
654
|
+
const idle = Math.max(0, now - view.lastEventAt);
|
|
655
|
+
return idle >= QUIET_MS ? idle : 0;
|
|
656
|
+
}
|
|
657
|
+
/** What the retried request produced (or the end of the turn) ends the retry label. */
|
|
658
|
+
const RETRY_ENDERS = new Set([
|
|
659
|
+
"text_delta",
|
|
660
|
+
"reasoning_delta",
|
|
661
|
+
"turn_completed",
|
|
662
|
+
"tool_started",
|
|
663
|
+
"compaction_started",
|
|
664
|
+
]);
|
|
665
|
+
function reduceEventItems(state, event) {
|
|
666
|
+
if ((state.retry || state.recovery) && RETRY_ENDERS.has(event.type)) {
|
|
667
|
+
const { retry: _retry, recovery: _recovery, ...rest } = state;
|
|
668
|
+
return reduceEventItems(rest, event);
|
|
669
|
+
}
|
|
580
670
|
const d = (event.data ?? {});
|
|
581
671
|
const at = Date.parse(event.timestamp);
|
|
582
672
|
switch (event.type) {
|
|
@@ -679,6 +769,10 @@ export function reduceEvent(state, event) {
|
|
|
679
769
|
}
|
|
680
770
|
return ended;
|
|
681
771
|
}
|
|
772
|
+
case "truncation_recovery":
|
|
773
|
+
return { ...state, recovery: { attempt: Number(d.attempt) || 1, of: Number(d.of) || 1 } };
|
|
774
|
+
case "request_retry":
|
|
775
|
+
return { ...state, retry: { attempt: Number(d.attempt) || 1, of: Number(d.of) || 1 } };
|
|
682
776
|
case "compaction_started":
|
|
683
777
|
return { ...state, compacting: true };
|
|
684
778
|
case "compaction_completed": {
|
|
@@ -720,7 +814,9 @@ export function reduceEvent(state, event) {
|
|
|
720
814
|
case "response_truncated":
|
|
721
815
|
return addItem(state, {
|
|
722
816
|
kind: "notice",
|
|
723
|
-
text:
|
|
817
|
+
text: `Response cut by max output tokens${typeof d.maxOutputTokens === "number"
|
|
818
|
+
? ` (${d.maxOutputTokens} tokens, ${describeOutputLimitSource(d.source)})`
|
|
819
|
+
: ""} — the answer may be incomplete. Raise limits.maxOutputTokens (/settings → Agent max output tokens) to allow longer answers.`,
|
|
724
820
|
});
|
|
725
821
|
case "model_changed":
|
|
726
822
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alisio/alisio-code",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.28",
|
|
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,12 +45,12 @@
|
|
|
45
45
|
"alisio": "./dist/main.js"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@alisio/core": "0.1.0-alpha.
|
|
48
|
+
"@alisio/core": "0.1.0-alpha.24",
|
|
49
49
|
"@alisio/plugin-memory": "0.1.0-alpha.9",
|
|
50
|
-
"@alisio/plugin-openai-compatible": "0.1.0-alpha.
|
|
50
|
+
"@alisio/plugin-openai-compatible": "0.1.0-alpha.10",
|
|
51
51
|
"@alisio/plugin-subagents": "0.1.0-alpha.11",
|
|
52
|
-
"@alisio/sdk": "0.1.0-alpha.
|
|
53
|
-
"@alisio/server": "0.1.0-alpha.
|
|
52
|
+
"@alisio/sdk": "0.1.0-alpha.20",
|
|
53
|
+
"@alisio/server": "0.1.0-alpha.8",
|
|
54
54
|
"@earendil-works/pi-tui": "0.87.1",
|
|
55
55
|
"commander": "15.0.0"
|
|
56
56
|
},
|