@alisio/alisio-code 0.1.0-alpha.26 → 0.1.0-alpha.27

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 CHANGED
@@ -127,6 +127,10 @@ 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
+ }
130
134
  else if (event.type === "tool_started")
131
135
  process.stderr.write(`\n→ ${event.data.name}\n`);
132
136
  else if (event.type === "artifact_published")
@@ -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]} streaming ${formatDuration(clock.now - (v.runStartedAt ?? clock.now))}`
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
- { text: state, priority: 9, paint: v.streaming || v.compacting ? style.cyan : style.green },
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,
@@ -343,6 +343,11 @@ 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
+ };
346
351
  model: string;
347
352
  /** Context size in tokens: provider-reported, or estimated (`~`). */
348
353
  context?: {
@@ -350,6 +355,8 @@ export interface ViewState {
350
355
  estimated: boolean;
351
356
  };
352
357
  runStartedAt?: number;
358
+ /** Epoch ms of the latest event of the live run (any type); undefined when no run is live. */
359
+ lastEventAt?: number;
353
360
  stats: Stats;
354
361
  }
355
362
  export declare function initialViewState(model: string, now?: number): ViewState;
@@ -360,6 +367,19 @@ export declare function reasoningDurationMs(item: {
360
367
  reasoningDoneAt?: number;
361
368
  }): number | undefined;
362
369
  export declare function reduceEvent(state: ViewState, event: RunEvent): ViewState;
370
+ /** No event for this long: the footer says how long the run has been quiet. */
371
+ export declare const QUIET_MS = 15000;
372
+ /** No event for this long: the footer marks the run as possibly stuck. */
373
+ export declare const STALLED_MS = 60000;
374
+ export interface RunPhase {
375
+ label: string;
376
+ /** Silence is expected here (the user's turn, or a sub-agent working in its own session). */
377
+ silentByDesign: boolean;
378
+ }
379
+ /** What the live run is doing, from the transcript items and flags the events already update. */
380
+ export declare function runPhase(view: ViewState): RunPhase;
381
+ /** Milliseconds of silence worth reporting (0 when the run is busy, or silent by design). */
382
+ export declare function quietFor(view: ViewState, now: number): number;
363
383
  export declare function lastAssistantText(items: TranscriptItem[]): string | undefined;
364
384
  /**
365
385
  * True when a printable key should copy the last assistant response instead of typing: bound to
package/dist/tui/state.js CHANGED
@@ -577,6 +577,91 @@ 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, ...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
+ const tool = view.items.findLast((i) => i.kind === "tool" && i.status !== "ok" && i.status !== "error");
616
+ if (tool?.kind === "tool") {
617
+ const name = tool.name.replace(/^p_[0-9a-f]{10}_/, "");
618
+ const arg = shortArg(tool.args);
619
+ const detail = arg ? ` (${arg})` : "";
620
+ if (tool.status === "approval")
621
+ return {
622
+ label: `waiting for your approval: ${humanizeToolName(name)}`,
623
+ silentByDesign: true,
624
+ };
625
+ if (name === "ask_user_question")
626
+ return { label: "waiting for your answer", silentByDesign: true };
627
+ if (name === "task" || name === "task_wait")
628
+ return { label: "a sub-agent is working", silentByDesign: true };
629
+ if (name === "python_run")
630
+ return { label: `running Python${detail}`, silentByDesign: false };
631
+ if (name === "data_inspect" || name === "data_query")
632
+ return { label: `reading your data${detail}`, silentByDesign: false };
633
+ if (name === "artifact_create")
634
+ return { label: `publishing the artifact${detail}`, silentByDesign: false };
635
+ return { label: `running ${humanizeToolName(name)}${detail}`, silentByDesign: false };
636
+ }
637
+ const last = view.items.at(-1);
638
+ if (last?.kind === "assistant" && !last.done)
639
+ return {
640
+ label: last.text ? "writing the answer" : "thinking",
641
+ silentByDesign: false,
642
+ };
643
+ return { label: "waiting for the model", silentByDesign: false };
644
+ }
645
+ /** Milliseconds of silence worth reporting (0 when the run is busy, or silent by design). */
646
+ export function quietFor(view, now) {
647
+ if (view.lastEventAt === undefined || runPhase(view).silentByDesign)
648
+ return 0;
649
+ const idle = Math.max(0, now - view.lastEventAt);
650
+ return idle >= QUIET_MS ? idle : 0;
651
+ }
652
+ /** What the retried request produced (or the end of the turn) ends the retry label. */
653
+ const RETRY_ENDERS = new Set([
654
+ "text_delta",
655
+ "reasoning_delta",
656
+ "turn_completed",
657
+ "tool_started",
658
+ "compaction_started",
659
+ ]);
660
+ function reduceEventItems(state, event) {
661
+ if (state.retry && RETRY_ENDERS.has(event.type)) {
662
+ const { retry: _retry, ...rest } = state;
663
+ return reduceEventItems(rest, event);
664
+ }
580
665
  const d = (event.data ?? {});
581
666
  const at = Date.parse(event.timestamp);
582
667
  switch (event.type) {
@@ -679,6 +764,8 @@ export function reduceEvent(state, event) {
679
764
  }
680
765
  return ended;
681
766
  }
767
+ case "request_retry":
768
+ return { ...state, retry: { attempt: Number(d.attempt) || 1, of: Number(d.of) || 1 } };
682
769
  case "compaction_started":
683
770
  return { ...state, compacting: true };
684
771
  case "compaction_completed": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alisio/alisio-code",
3
- "version": "0.1.0-alpha.26",
3
+ "version": "0.1.0-alpha.27",
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.22",
48
+ "@alisio/core": "0.1.0-alpha.23",
49
49
  "@alisio/plugin-memory": "0.1.0-alpha.9",
50
50
  "@alisio/plugin-openai-compatible": "0.1.0-alpha.9",
51
51
  "@alisio/plugin-subagents": "0.1.0-alpha.11",
52
- "@alisio/sdk": "0.1.0-alpha.18",
53
- "@alisio/server": "0.1.0-alpha.6",
52
+ "@alisio/sdk": "0.1.0-alpha.19",
53
+ "@alisio/server": "0.1.0-alpha.7",
54
54
  "@earendil-works/pi-tui": "0.87.1",
55
55
  "commander": "15.0.0"
56
56
  },