@alisio/alisio-code 0.5.2 → 0.7.0
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.d.ts +24 -1
- package/dist/analysis.js +100 -1
- package/dist/eval.d.ts +101 -0
- package/dist/eval.js +690 -0
- package/dist/forge.d.ts +13 -0
- package/dist/forge.js +46 -0
- package/dist/main.js +94 -4
- package/dist/tui/artifacts.d.ts +1 -1
- package/dist/tui/artifacts.js +5 -1
- package/dist/tui/components.js +5 -3
- package/dist/tui/state.d.ts +17 -1
- package/dist/tui/state.js +63 -3
- package/package.json +7 -7
package/dist/forge.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `alisio forge status`: what `GET /api/forge` will report, for the terminal. It builds the engine
|
|
3
|
+
* from the configuration alone (no application, no database): the WASM is checked against its
|
|
4
|
+
* pinned hash, the host files are looked for, and `--probe` runs a real isolate end to end.
|
|
5
|
+
*/
|
|
6
|
+
type Common = {
|
|
7
|
+
cwd?: string;
|
|
8
|
+
config?: string;
|
|
9
|
+
trustProject?: boolean;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare function printForgeStatus(o: Common, probe: boolean): Promise<void>;
|
|
13
|
+
export {};
|
package/dist/forge.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const mib = (bytes) => `${Math.round(bytes / (1024 * 1024))} MiB`;
|
|
2
|
+
export async function printForgeStatus(o, probe) {
|
|
3
|
+
const core = await import("@alisio/core");
|
|
4
|
+
const workspace = await core.findWorkspace(o.cwd ?? process.cwd());
|
|
5
|
+
const { config } = await core.loadConfigWithProvenance(workspace, {
|
|
6
|
+
file: o.config,
|
|
7
|
+
trustProject: o.trustProject,
|
|
8
|
+
});
|
|
9
|
+
const engine = core.forgeEngineFor(config.forge);
|
|
10
|
+
const { pythonBackendDescriptors } = await import("./analysis.js");
|
|
11
|
+
const python = await pythonBackendDescriptors(o);
|
|
12
|
+
const base = await core.describeForge(config.forge, engine);
|
|
13
|
+
const status = {
|
|
14
|
+
...base,
|
|
15
|
+
engines: [...base.engines, ...python],
|
|
16
|
+
python: { runtime: config.analysis.runtime, isolation: config.analysis.isolation },
|
|
17
|
+
};
|
|
18
|
+
const probed = probe ? await engine.probe() : undefined;
|
|
19
|
+
if (o.json) {
|
|
20
|
+
process.stdout.write(`${JSON.stringify({ ...status, ...(probed ? { probe: probed } : {}) })}\n`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const js = status.javascript;
|
|
24
|
+
const descriptor = status.engines[0];
|
|
25
|
+
const lines = [
|
|
26
|
+
`JavaScript ${js.enabled ? "enabled" : "disabled"} ${descriptor?.ready ? "ready" : "not ready"}`,
|
|
27
|
+
` engine ${descriptor?.id ?? "javascript-quickjs"} ${js.version}`,
|
|
28
|
+
` isolation ${descriptor?.isolation ?? "none"} (${js.host} host${js.hostSetting === "auto" ? "" : `, forced by forge.javascript.host=${js.hostSetting}`})`,
|
|
29
|
+
` wasm sha256 ${js.wasmSha256}${js.verified ? " verified" : ""}`,
|
|
30
|
+
];
|
|
31
|
+
if (!descriptor?.ready && descriptor?.reason)
|
|
32
|
+
lines.push(` reason ${descriptor.reason}`);
|
|
33
|
+
if (js.verification)
|
|
34
|
+
lines.push(` problem ${js.verification}`);
|
|
35
|
+
lines.push(` limits timeout ${js.limits.timeoutMs} ms, heap ${mib(js.limits.memoryBytes)}, stack ${Math.round(js.limits.stackBytes / 1024)} KiB, ${js.limits.maxToolCalls} calls (${js.limits.maxConcurrency} at once), ${js.limits.maxOutputChars} output chars`, ` state ${status.state.enabled ? "on" : "off"} scratchpad ${status.scratch.enabled ? "on" : "off"}`);
|
|
36
|
+
lines.push("", `Python analysis.runtime=${config.analysis.runtime} isolation=${config.analysis.isolation}`);
|
|
37
|
+
for (const backend of python)
|
|
38
|
+
lines.push(` ${backend.id.replace(/^python-/, "").padEnd(10)} ${backend.isolation.padEnd(13)} ${backend.ready ? "ready " : "not ready"} network ${backend.capabilities.networkDeniedByOS ? "denied by the OS" : "not blocked"}${backend.profiles?.length ? ` profiles ${backend.profiles.join(",")}` : ""}${backend.reason ? ` (${backend.reason})` : ""}`);
|
|
39
|
+
if (probed)
|
|
40
|
+
lines.push(probed.ok
|
|
41
|
+
? ` probe ok, a ${probed.host} isolate ran a script in ${probed.bootMs} ms`
|
|
42
|
+
: ` probe FAILED (${probed.host} host): ${probed.reason}`);
|
|
43
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
44
|
+
if (probed && !probed.ok)
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -419,6 +419,8 @@ program.command("doctor").action(async (_opts, cmd) => {
|
|
|
419
419
|
python: { mode: analysisInfo.status.mode, ...pythonInfo },
|
|
420
420
|
analysis: {
|
|
421
421
|
mode: analysisInfo.status.mode,
|
|
422
|
+
...(analysisInfo.status.isolation ? { isolation: analysisInfo.status.isolation } : {}),
|
|
423
|
+
...(analysisInfo.status.backends ? { backends: analysisInfo.status.backends } : {}),
|
|
422
424
|
oci: analysisInfo.status.oci,
|
|
423
425
|
limits: analysisInfo.status.limits,
|
|
424
426
|
retention: analysisInfo.status.retention,
|
|
@@ -447,13 +449,21 @@ analysis
|
|
|
447
449
|
});
|
|
448
450
|
analysis
|
|
449
451
|
.command("setup")
|
|
450
|
-
.description("Optional: install hash-locked Python extras (--extras)
|
|
451
|
-
"python_run never needs this for the standard library.")
|
|
452
|
+
.description("Optional: install hash-locked Python extras (--extras), pull the container image (--oci) or " +
|
|
453
|
+
"download the WebAssembly runtime (--wasm). python_run never needs this for the standard library.")
|
|
452
454
|
.option("--extras <set>", "analysis (pandas, numpy, matplotlib…) or science (adds scipy…)")
|
|
453
455
|
.option("--oci", "Pull the container image once and verify its digest (Docker or Podman)")
|
|
454
456
|
.option("--image <name>", "With --oci: the image to pull (default: analysis.oci.image)")
|
|
457
|
+
.option("--wasm", "Download the pinned Pyodide runtime for the wasm backend (needs network)")
|
|
458
|
+
.option("--profile <name>", "With --wasm: analysis (default) or science (adds scipy, scikit-learn…)")
|
|
459
|
+
.option("--yes", "With --wasm: do not ask for confirmation (required when not interactive)")
|
|
455
460
|
.action(async (flags, cmd) => {
|
|
456
|
-
const { setupExtras, setupOci } = await import("./analysis.js");
|
|
461
|
+
const { setupExtras, setupOci, setupWasm } = await import("./analysis.js");
|
|
462
|
+
if (flags.wasm)
|
|
463
|
+
return setupWasm(options(cmd), {
|
|
464
|
+
...(flags.profile ? { profile: flags.profile } : {}),
|
|
465
|
+
yes: !!flags.yes,
|
|
466
|
+
});
|
|
457
467
|
if (flags.oci)
|
|
458
468
|
return setupOci(options(cmd), flags.image);
|
|
459
469
|
return setupExtras(options(cmd), flags.extras);
|
|
@@ -466,6 +476,79 @@ analysis
|
|
|
466
476
|
const { sweep } = await import("./analysis.js");
|
|
467
477
|
await sweep(options(cmd), !!flags.force);
|
|
468
478
|
});
|
|
479
|
+
const forge = program
|
|
480
|
+
.command("forge")
|
|
481
|
+
.description("Forja: isolated code execution (js_run). Shows the engine and why it is or is not ready");
|
|
482
|
+
forge
|
|
483
|
+
.command("status")
|
|
484
|
+
.description("Show the engines: JavaScript (version, WASM hash, isolation, host, limits) and the Python backends")
|
|
485
|
+
.option("--probe", "Also start a real isolate and run a trivial script through the protocol")
|
|
486
|
+
.action(async (flags, cmd) => {
|
|
487
|
+
const { printForgeStatus } = await import("./forge.js");
|
|
488
|
+
await printForgeStatus(options(cmd), !!flags.probe);
|
|
489
|
+
});
|
|
490
|
+
const evalCommand = program
|
|
491
|
+
.command("eval")
|
|
492
|
+
.description("Reliability lab: run a task suite k times, report pass rates with Wilson intervals, compare reports or config variants, record cassettes and replay them without a model");
|
|
493
|
+
/** `alisio eval` entry points: lazy, and exit codes are returned, not thrown (0 ok, 1 error, 2 replay diverged, 3 incompatible). */
|
|
494
|
+
async function runEval(cmd, action) {
|
|
495
|
+
const api = await import("./eval.js");
|
|
496
|
+
const env = await api.realEvalEnv(async () => options(cmd), async () => cliDefaults());
|
|
497
|
+
process.exitCode = await action(api, env, cmd.optsWithGlobals());
|
|
498
|
+
}
|
|
499
|
+
const evalRunOptions = (command) => command
|
|
500
|
+
.option("--tasks <ids>", "Comma-separated task ids (default: every task of the suite)")
|
|
501
|
+
.option("--k <n>", "Repetitions per task (default 5; below 5 the report warns of low power)")
|
|
502
|
+
.option("--seed <n>", "Seed of the run order; each run gets seed + repetition")
|
|
503
|
+
.option("--mode <mode>", "live (default) or replay (serve the recorded cassettes: no provider, no network; measures the harness, not the model)")
|
|
504
|
+
.option("--cassette-dir <dir>", "Cassette directory (default: <suite>/cassettes; used by --mode replay and by record)")
|
|
505
|
+
.option("--keep", "Keep each run's temporary directory (it prints the paths)")
|
|
506
|
+
.option("--out <dir>", "Write the report files here (default: <state>/eval/<runId>/)")
|
|
507
|
+
.option("--suite-dir <dir>", "A suite, or a directory of suites; third-party code: needs --yes")
|
|
508
|
+
.option("--allow-suite-full", "Let a suite run tasks that ask for the full permission mode")
|
|
509
|
+
.option("--yes", "Confirm running a --suite-dir suite without asking (required if not interactive)")
|
|
510
|
+
.option("--input-rate <usd>", "USD per million input tokens, to estimate cost (needs --output-rate)")
|
|
511
|
+
.option("--output-rate <usd>", "USD per million output tokens (needs --input-rate)");
|
|
512
|
+
evalCommand
|
|
513
|
+
.command("list")
|
|
514
|
+
.description("List the available suites (bundled ones plus --suite-dir)")
|
|
515
|
+
.option("--suite-dir <dir>", "A suite, or a directory of suites")
|
|
516
|
+
.action((_flags, cmd) => runEval(cmd, (api, env, flags) => api.evalList(env, flags)));
|
|
517
|
+
evalRunOptions(evalCommand
|
|
518
|
+
.command("run")
|
|
519
|
+
.description("Run a suite k times (live, or --mode replay from cassettes) and write report.json, report.md and runs.jsonl; --json prints the run records as JSONL and the report last. Exit 0 when it completed (whatever passed), 1 on a usage or infrastructure error, 2 when a replay diverged from its cassette")
|
|
520
|
+
.argument("<suite>")).action((suite, _flags, cmd) => runEval(cmd, (api, env, flags) => api.evalRun(env, flags, suite)));
|
|
521
|
+
evalCommand
|
|
522
|
+
.command("record")
|
|
523
|
+
.description("Run each task of a suite ONCE live and write <suite>/cassettes/<taskId>.cassette.jsonl for `eval run --mode replay`. Refuses (exit 1) a cassette that holds a secret or is over 256 KB (2 MB per directory)")
|
|
524
|
+
.argument("<suite>")
|
|
525
|
+
.option("--tasks <ids>", "Comma-separated task ids (default: every task of the suite)")
|
|
526
|
+
.option("--seed <n>", "Seed of the run order")
|
|
527
|
+
.option("--keep", "Keep each run's temporary directory (it prints the paths)")
|
|
528
|
+
.option("--cassette-dir <dir>", "Write the cassettes here (default: <suite>/cassettes)")
|
|
529
|
+
.option("--suite-dir <dir>", "A suite, or a directory of suites; third-party code: needs --yes")
|
|
530
|
+
.option("--allow-suite-full", "Let a suite run tasks that ask for the full permission mode")
|
|
531
|
+
.option("--yes", "Confirm running a --suite-dir suite without asking (required if not interactive)")
|
|
532
|
+
.action((suite, _flags, cmd) => runEval(cmd, (api, env, flags) => api.evalRecord(env, flags, suite)));
|
|
533
|
+
evalCommand
|
|
534
|
+
.command("compare")
|
|
535
|
+
.description("Compare two reports of the same suite and version; exit 3 when the suites are incompatible")
|
|
536
|
+
.argument("<reportA>", "Baseline report.json (or the directory that holds it)")
|
|
537
|
+
.argument("<reportB>", "Candidate report.json (or the directory that holds it)")
|
|
538
|
+
.option("--md", "Print Markdown (the default; --json prints JSON instead)")
|
|
539
|
+
.action((a, b, _flags, cmd) => runEval(cmd, (api, env, flags) => api.evalCompare(env, flags, a, b)));
|
|
540
|
+
evalRunOptions(evalCommand
|
|
541
|
+
.command("variants")
|
|
542
|
+
.description('Run two or more config variants over the same tasks and seeds and compare them with the first. compare.json: {"suite":"id","variants":[{"id":"baseline","overrides":{}},{"id":"early","overrides":{"compaction.threshold":0.6}}]}')
|
|
543
|
+
.argument("<suite>")
|
|
544
|
+
.requiredOption("--variants <file>", "The compare.json file")).action((suite, _flags, cmd) => runEval(cmd, (api, env, flags) => api.evalVariants(env, flags, suite)));
|
|
545
|
+
evalCommand
|
|
546
|
+
.command("export")
|
|
547
|
+
.description("Write ONE shareable JSON bundle ({schemaVersion, report, runs, markdown}) of a stored run (<state>/eval/<runId>/) with absolute paths replaced by placeholders and endpoint hosts removed from provider ids. Refuses (exit 1) when a secret is found, naming the field; refuses to overwrite FILE without --force. Local only: nothing is uploaded. Scrubbing is pattern-based, not a guarantee")
|
|
548
|
+
.argument("<runId>", "Directory name of the run under <state>/eval/")
|
|
549
|
+
.requiredOption("--out <file>", "The bundle to write")
|
|
550
|
+
.option("--force", "Overwrite FILE if it exists")
|
|
551
|
+
.action((runId, _flags, cmd) => runEval(cmd, (api, env, flags) => api.evalExport(env, flags, runId)));
|
|
469
552
|
const trust = program.command("trust").description("Inspect or revoke per-directory project trust");
|
|
470
553
|
trust.command("list").action(async () => {
|
|
471
554
|
const { listTrust } = await import("@alisio/core");
|
|
@@ -665,7 +748,14 @@ program
|
|
|
665
748
|
await cliUpdateAll(common);
|
|
666
749
|
});
|
|
667
750
|
try {
|
|
668
|
-
|
|
751
|
+
// The compiled binary starts its own Forja process isolates with this hidden command (spec D-21).
|
|
752
|
+
// It is dispatched before any configuration or database is loaded and is not part of the CLI.
|
|
753
|
+
if (process.argv.slice(2)[0] === "__forge-isolate") {
|
|
754
|
+
const { runProcessIsolate } = await import("@alisio/core/forge-isolate");
|
|
755
|
+
await runProcessIsolate();
|
|
756
|
+
}
|
|
757
|
+
else
|
|
758
|
+
await program.parseAsync();
|
|
669
759
|
}
|
|
670
760
|
catch (error) {
|
|
671
761
|
console.error(error instanceof Error ? error.message : String(error));
|
package/dist/tui/artifacts.d.ts
CHANGED
|
@@ -51,4 +51,4 @@ export declare const installApprovalTitle: (install: InstallPreview) => string;
|
|
|
51
51
|
/** Body of the `analysis.install` approval: the packages, the download estimate and the network. */
|
|
52
52
|
export declare function installApprovalDetail(install: InstallPreview): string;
|
|
53
53
|
/** Title of a capability approval in the TUI picker. */
|
|
54
|
-
export declare const capabilityApprovalTitle: (runtime: "managed" | "oci" | undefined) => string;
|
|
54
|
+
export declare const capabilityApprovalTitle: (runtime: "auto" | "managed" | "oci" | "wasm" | undefined) => string;
|
package/dist/tui/artifacts.js
CHANGED
|
@@ -204,4 +204,8 @@ export function installApprovalDetail(install) {
|
|
|
204
204
|
/** Title of a capability approval in the TUI picker. */
|
|
205
205
|
export const capabilityApprovalTitle = (runtime) => runtime === "oci"
|
|
206
206
|
? "Run Python analysis (container · no network)?"
|
|
207
|
-
:
|
|
207
|
+
: runtime === "wasm"
|
|
208
|
+
? "Run Python analysis (wasm · restricted process, network not blocked)?"
|
|
209
|
+
: runtime === "auto"
|
|
210
|
+
? "Run Python analysis (most isolated backend ready; the host only if none · see the engine line)?"
|
|
211
|
+
: "Run Python analysis (managed · not sandboxed)?";
|
package/dist/tui/components.js
CHANGED
|
@@ -8,7 +8,7 @@ import { branchDisplay } from "./git-branch.js";
|
|
|
8
8
|
import { initialPlanReviewState, reducePlanReview, } from "./plan-review.js";
|
|
9
9
|
import { initialQuestionState, reduceQuestions, } from "./questions.js";
|
|
10
10
|
import { scrollWindow } from "./scroll.js";
|
|
11
|
-
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";
|
|
11
|
+
import { contextLevel, contextPercent, editSummary, entryKeyOf, fitIdentityParts, fitSegments, foldCandidateOf, forgeLabel, formatContext, formatDuration, formatTokens, groupIdentity, groupToolEntries, humanizeToolName, isUiBlock, previewLinesFor, previewRows, quietFor, REASONING_EXPAND_MAX_LINES, reasoningDurationMs, runPhase, STALLED_MS, } from "./state.js";
|
|
12
12
|
import { highlightCode, imageTheme, levelColor, markdownDefaultTextStyle, markdownTheme, markdownTransform, sanitizeCode, style, } from "./theme.js";
|
|
13
13
|
export const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
14
14
|
/** Shared animation clock advanced by the app while work is running. */
|
|
@@ -643,7 +643,9 @@ export class ToolBlock {
|
|
|
643
643
|
? style.gray(formatDuration(i.durationMs))
|
|
644
644
|
: "";
|
|
645
645
|
const progress = i.status === "running" && i.progress ? ` ${style.dim(`· ${i.progress}`)}` : "";
|
|
646
|
-
const
|
|
646
|
+
const forge = forgeLabel(i.forge);
|
|
647
|
+
const forgePart = forge ? ` ${style.dim(`· ${forge}`)}` : "";
|
|
648
|
+
const head = `${marker}${icon} ${style.bold(humanizeToolName(i.name))} ${style.gray(i.summary)}${forgePart}${progress}`;
|
|
647
649
|
const gap = width - visibleWidth(head) - visibleWidth(right) - 1;
|
|
648
650
|
const lines = [
|
|
649
651
|
gap > 0 ? `${head}${" ".repeat(gap + 1)}${right}` : truncateToWidth(head, width),
|
|
@@ -728,7 +730,7 @@ export class ToolGroupBlock {
|
|
|
728
730
|
: `${style.bold(member.humanName)} ${style.dim(`(${member.name})`)}`;
|
|
729
731
|
lines.push(` ${micon} ${name}${member.durationMs !== undefined
|
|
730
732
|
? ` ${style.gray(`· ${formatDuration(member.durationMs)}`)}`
|
|
731
|
-
: ""}${member.summary ? ` ${style.gray(member.summary)}` : ""}`);
|
|
733
|
+
: ""}${member.summary ? ` ${style.gray(member.summary)}` : ""}${member.forge ? ` ${style.dim(`· ${member.forge}`)}` : ""}`);
|
|
732
734
|
const rich = richBodyLines(member, width);
|
|
733
735
|
if (rich.lines.length) {
|
|
734
736
|
lines.push(...rich.lines.map((line) => ` ${line}`));
|
package/dist/tui/state.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Message, ModelInfo, RunEvent, ToolResult, TreeNode, UiBlock } from "@alisio/sdk";
|
|
1
|
+
import type { EngineId, Message, ModelInfo, RunEvent, ToolResult, TreeNode, UiBlock } from "@alisio/sdk";
|
|
2
2
|
export type Level = "ok" | "warn" | "danger";
|
|
3
3
|
export declare function formatTokens(n: number): string;
|
|
4
4
|
/** Prefixes every model with its owning provider so identical model ids are never ambiguous. */
|
|
@@ -317,6 +317,8 @@ export type TranscriptItem = {
|
|
|
317
317
|
toolKind?: ToolKind;
|
|
318
318
|
/** Last non-empty `tool_progress` line while the tool runs (cleared on completion). */
|
|
319
319
|
progress?: string;
|
|
320
|
+
/** What a Forja execution (`js_run`, `python_run`) reported; kept after completion. */
|
|
321
|
+
forge?: ForgeLine;
|
|
320
322
|
/** Exit code derived safely from the JSON preview when the tool reports one (run_process/shell/search_text). */
|
|
321
323
|
exitCode?: number;
|
|
322
324
|
} | {
|
|
@@ -377,6 +379,18 @@ export declare function reasoningDurationMs(item: {
|
|
|
377
379
|
reasoningStartedAt?: number;
|
|
378
380
|
reasoningDoneAt?: number;
|
|
379
381
|
}): number | undefined;
|
|
382
|
+
/** What the row of a Forja execution shows: nested calls, the script's own note and the engine. */
|
|
383
|
+
export interface ForgeLine {
|
|
384
|
+
engine: EngineId;
|
|
385
|
+
/** Highest nested call number seen. */
|
|
386
|
+
started: number;
|
|
387
|
+
/** Nested calls that ended (succeeded, failed or denied). */
|
|
388
|
+
finished: number;
|
|
389
|
+
/** The script's own `progress({ message, completed, total })`, bounded. */
|
|
390
|
+
note?: string;
|
|
391
|
+
}
|
|
392
|
+
/** `page 2 (2/9) · 2/3 calls · wasm`; empty when there is nothing to say yet. */
|
|
393
|
+
export declare function forgeLabel(forge: ForgeLine | undefined): string;
|
|
380
394
|
export declare function reduceEvent(state: ViewState, event: RunEvent): ViewState;
|
|
381
395
|
/** No event for this long: the footer says how long the run has been quiet. */
|
|
382
396
|
export declare const QUIET_MS = 15000;
|
|
@@ -460,6 +474,8 @@ export interface ToolItemView {
|
|
|
460
474
|
summary: string;
|
|
461
475
|
/** Last progress line while running. */
|
|
462
476
|
progress?: string;
|
|
477
|
+
/** `2/3 calls · wasm` of a Forja execution (running or finished). */
|
|
478
|
+
forge?: string;
|
|
463
479
|
status: ToolStatus;
|
|
464
480
|
durationMs?: number;
|
|
465
481
|
preview?: string;
|
package/dist/tui/state.js
CHANGED
|
@@ -580,6 +580,54 @@ function updateTool(state, id, update) {
|
|
|
580
580
|
items[index] = { ...item, ...update };
|
|
581
581
|
return { ...state, items };
|
|
582
582
|
}
|
|
583
|
+
const FORGE_NOTE_MAX = 40;
|
|
584
|
+
const isForgeProgress = (value) => !!value &&
|
|
585
|
+
typeof value === "object" &&
|
|
586
|
+
value.kind === "forge" &&
|
|
587
|
+
typeof value.engine === "string";
|
|
588
|
+
/** Folds one `ForgeProgress` into the line (terminal call events are never coalesced away). */
|
|
589
|
+
function foldForgeProgress(current, progress) {
|
|
590
|
+
const next = current
|
|
591
|
+
? { ...current, engine: progress.engine }
|
|
592
|
+
: { engine: progress.engine, started: 0, finished: 0 };
|
|
593
|
+
if (progress.call) {
|
|
594
|
+
next.started = Math.max(next.started, progress.call.callId);
|
|
595
|
+
if (progress.call.status !== "started")
|
|
596
|
+
next.finished += 1;
|
|
597
|
+
}
|
|
598
|
+
else if (progress.message || progress.total !== undefined) {
|
|
599
|
+
const fraction = progress.completed !== undefined && progress.total !== undefined
|
|
600
|
+
? ` (${progress.completed}/${progress.total})`
|
|
601
|
+
: "";
|
|
602
|
+
const message = (progress.message ?? "").slice(0, FORGE_NOTE_MAX);
|
|
603
|
+
next.note = `${message}${fraction}`.trim();
|
|
604
|
+
}
|
|
605
|
+
return next;
|
|
606
|
+
}
|
|
607
|
+
/** The isolation tag of a Python engine; nothing for JavaScript (it is always isolated). */
|
|
608
|
+
const FORGE_ENGINE_TAG = {
|
|
609
|
+
"python-oci": "oci",
|
|
610
|
+
"python-wasm": "wasm",
|
|
611
|
+
"python-managed": "host!",
|
|
612
|
+
};
|
|
613
|
+
/** `page 2 (2/9) · 2/3 calls · wasm`; empty when there is nothing to say yet. */
|
|
614
|
+
export function forgeLabel(forge) {
|
|
615
|
+
if (!forge)
|
|
616
|
+
return "";
|
|
617
|
+
return [
|
|
618
|
+
forge.note,
|
|
619
|
+
forge.started > 0 ? `${forge.finished}/${forge.started} calls` : undefined,
|
|
620
|
+
FORGE_ENGINE_TAG[forge.engine],
|
|
621
|
+
]
|
|
622
|
+
.filter(Boolean)
|
|
623
|
+
.join(" · ");
|
|
624
|
+
}
|
|
625
|
+
const ENGINE_LINE = /^engine: (python-oci|python-wasm|python-managed) \(/m;
|
|
626
|
+
/** The Python engine a result text names on its last line (PD11), for rows without progress. */
|
|
627
|
+
const engineOfResult = (text) => {
|
|
628
|
+
const engine = typeof text === "string" ? ENGINE_LINE.exec(text)?.[1] : undefined;
|
|
629
|
+
return engine ? { engine: engine, started: 0, finished: 0 } : undefined;
|
|
630
|
+
};
|
|
583
631
|
/** Longest progress line kept on a running tool row. */
|
|
584
632
|
const PROGRESS_LINE_MAX = 80;
|
|
585
633
|
/** The last non-empty line of a `tool_progress` payload, bounded; empty when it has none. */
|
|
@@ -733,6 +781,11 @@ function reduceEventItems(state, event) {
|
|
|
733
781
|
});
|
|
734
782
|
}
|
|
735
783
|
case "tool_progress": {
|
|
784
|
+
if (isForgeProgress(d.data)) {
|
|
785
|
+
const row = state.items.findLast((i) => i.kind === "tool" && i.id === d.id);
|
|
786
|
+
const before = row?.kind === "tool" ? row.forge : undefined;
|
|
787
|
+
return updateTool(state, d.id, { forge: foldForgeProgress(before, d.data) });
|
|
788
|
+
}
|
|
736
789
|
const line = lastProgressLine(d.data);
|
|
737
790
|
return line ? updateTool(state, d.id, { progress: line }) : state;
|
|
738
791
|
}
|
|
@@ -757,6 +810,9 @@ function reduceEventItems(state, event) {
|
|
|
757
810
|
status: d.isError ? "error" : "ok",
|
|
758
811
|
...(typeof d.durationMs === "number" ? { durationMs: d.durationMs } : {}),
|
|
759
812
|
...(typeof d.preview === "string" ? { preview: d.preview } : {}),
|
|
813
|
+
...(running?.kind === "tool" && !running.forge && engineOfResult(d.preview)
|
|
814
|
+
? { forge: engineOfResult(d.preview) }
|
|
815
|
+
: {}),
|
|
760
816
|
...exitCodePart(d.preview),
|
|
761
817
|
...(isUiBlock(d.ui) ? { ui: d.ui } : {}),
|
|
762
818
|
...(isImagePart(d.image) ? { image: d.image } : {}),
|
|
@@ -1186,6 +1242,7 @@ export function toolItemView(item) {
|
|
|
1186
1242
|
kind: item.toolKind ?? toolKindOf(item.name),
|
|
1187
1243
|
summary: item.summary,
|
|
1188
1244
|
...(item.progress ? { progress: item.progress } : {}),
|
|
1245
|
+
...(forgeLabel(item.forge) ? { forge: forgeLabel(item.forge) } : {}),
|
|
1189
1246
|
status: item.status,
|
|
1190
1247
|
...(item.durationMs !== undefined ? { durationMs: item.durationMs } : {}),
|
|
1191
1248
|
...(item.preview !== undefined ? { preview: item.preview } : {}),
|
|
@@ -1351,11 +1408,13 @@ export function itemsFromHistory(messages) {
|
|
|
1351
1408
|
items.push({ kind: "assistant", text: m.text, reasoning: "", done: true });
|
|
1352
1409
|
for (const c of m.calls) {
|
|
1353
1410
|
const r = results.get(c.id);
|
|
1354
|
-
const
|
|
1411
|
+
const text = r?.result.content
|
|
1355
1412
|
.filter((p) => p.type === "text")
|
|
1356
1413
|
.map((x) => x.text)
|
|
1357
|
-
.join("\n")
|
|
1358
|
-
|
|
1414
|
+
.join("\n");
|
|
1415
|
+
const preview = text?.slice(0, 2_000);
|
|
1416
|
+
// The engine line closes a python_run result (PD11); read it before the preview clips it.
|
|
1417
|
+
const forge = c.name === "python_run" ? engineOfResult(text) : undefined;
|
|
1359
1418
|
items.push({
|
|
1360
1419
|
kind: "tool",
|
|
1361
1420
|
id: c.id,
|
|
@@ -1365,6 +1424,7 @@ export function itemsFromHistory(messages) {
|
|
|
1365
1424
|
status: r?.result.isError ? "error" : "ok",
|
|
1366
1425
|
preview,
|
|
1367
1426
|
toolKind: toolKindOf(c.name),
|
|
1427
|
+
...(forge ? { forge } : {}),
|
|
1368
1428
|
...exitCodePart(preview),
|
|
1369
1429
|
...richPartsOf(r?.result, c.name),
|
|
1370
1430
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alisio/alisio-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
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.
|
|
49
|
-
"@alisio/plugin-memory": "0.
|
|
50
|
-
"@alisio/plugin-openai-compatible": "0.
|
|
51
|
-
"@alisio/plugin-subagents": "0.
|
|
52
|
-
"@alisio/sdk": "0.
|
|
53
|
-
"@alisio/server": "0.
|
|
48
|
+
"@alisio/core": "0.7.0",
|
|
49
|
+
"@alisio/plugin-memory": "0.7.0",
|
|
50
|
+
"@alisio/plugin-openai-compatible": "0.7.0",
|
|
51
|
+
"@alisio/plugin-subagents": "0.7.0",
|
|
52
|
+
"@alisio/sdk": "0.7.0",
|
|
53
|
+
"@alisio/server": "0.7.0",
|
|
54
54
|
"@earendil-works/pi-tui": "0.87.1",
|
|
55
55
|
"commander": "15.0.0"
|
|
56
56
|
},
|