@alisio/alisio-code 0.1.0-alpha.9 → 0.1.1
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/README.md +95 -16
- package/dist/analysis.d.ts +27 -0
- package/dist/analysis.js +145 -0
- package/dist/banner.js +2 -1
- package/dist/builtin.d.ts +3 -1
- package/dist/builtin.js +0 -24
- package/dist/main.js +133 -6
- package/dist/serve.d.ts +15 -0
- package/dist/serve.js +99 -0
- package/dist/tui/agent-manager.d.ts +105 -0
- package/dist/tui/agent-manager.js +269 -0
- package/dist/tui/agents.d.ts +5 -0
- package/dist/tui/agents.js +5 -0
- package/dist/tui/app.d.ts +42 -1
- package/dist/tui/app.js +1895 -97
- package/dist/tui/artifact-preview.d.ts +52 -0
- package/dist/tui/artifact-preview.js +212 -0
- package/dist/tui/artifacts.d.ts +54 -0
- package/dist/tui/artifacts.js +207 -0
- package/dist/tui/btw.d.ts +57 -0
- package/dist/tui/btw.js +76 -0
- package/dist/tui/components.d.ts +174 -12
- package/dist/tui/components.js +962 -66
- package/dist/tui/connect-input.d.ts +2 -0
- package/dist/tui/connect-input.js +1 -1
- package/dist/tui/exit.d.ts +10 -0
- package/dist/tui/exit.js +15 -0
- package/dist/tui/git-branch.d.ts +48 -0
- package/dist/tui/git-branch.js +99 -0
- package/dist/tui/goal.d.ts +75 -0
- package/dist/tui/goal.js +171 -0
- package/dist/tui/modes.d.ts +71 -0
- package/dist/tui/modes.js +90 -0
- package/dist/tui/open-path.d.ts +38 -0
- package/dist/tui/open-path.js +80 -0
- package/dist/tui/plan-review.d.ts +48 -0
- package/dist/tui/plan-review.js +76 -0
- package/dist/tui/queue.d.ts +2 -0
- package/dist/tui/queue.js +5 -1
- package/dist/tui/scroll.d.ts +8 -0
- package/dist/tui/scroll.js +12 -0
- package/dist/tui/settings-menu.d.ts +40 -0
- package/dist/tui/settings-menu.js +132 -0
- package/dist/tui/settings.d.ts +194 -0
- package/dist/tui/settings.js +482 -0
- package/dist/tui/state.d.ts +343 -7
- package/dist/tui/state.js +823 -54
- package/dist/tui/tasks.d.ts +123 -0
- package/dist/tui/tasks.js +369 -0
- package/dist/tui/theme.d.ts +42 -1
- package/dist/tui/theme.js +516 -1
- package/dist/tui/viewer-state.d.ts +7 -0
- package/dist/tui/viewer-state.js +36 -0
- package/dist/version.d.ts +7 -5
- package/dist/version.js +44 -8
- package/package.json +7 -9
package/dist/serve.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `alisio serve`: loaded only by the serve action, and it loads `@alisio/server` only then, so
|
|
3
|
+
* `alisio`, `alisio run` and the TUI never load the HTTP server (spec §3.4, T-12).
|
|
4
|
+
*/
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
/** The operating system's URL opener for `platform`, as `[command, ...args]`. */
|
|
7
|
+
export function browserCommand(platform, url) {
|
|
8
|
+
if (platform === "darwin")
|
|
9
|
+
return ["open", url];
|
|
10
|
+
if (platform === "win32")
|
|
11
|
+
return ["cmd", "/c", "start", '""', url];
|
|
12
|
+
return ["xdg-open", url];
|
|
13
|
+
}
|
|
14
|
+
/** Opens `url` in the default browser; failures are reported, never fatal. */
|
|
15
|
+
export function openBrowser(url) {
|
|
16
|
+
const [command = "", ...args] = browserCommand(process.platform, url);
|
|
17
|
+
try {
|
|
18
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
19
|
+
child.on("error", () => process.stderr.write("Could not open a browser; open the URL above manually.\n"));
|
|
20
|
+
child.unref();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
process.stderr.write("Could not open a browser; open the URL above manually.\n");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Parsed option keys that are not `AppOptions` (serve flags and UI-only globals). */
|
|
27
|
+
const SERVE_ONLY_KEYS = new Set([
|
|
28
|
+
"port",
|
|
29
|
+
"host",
|
|
30
|
+
"allowRemote",
|
|
31
|
+
"open",
|
|
32
|
+
"maxWorkspaces",
|
|
33
|
+
"maxRuns",
|
|
34
|
+
"json",
|
|
35
|
+
"quiet",
|
|
36
|
+
"banner",
|
|
37
|
+
"tui",
|
|
38
|
+
"baseUrl",
|
|
39
|
+
"herdr",
|
|
40
|
+
"disablePlugin",
|
|
41
|
+
"addDir",
|
|
42
|
+
"agents",
|
|
43
|
+
]);
|
|
44
|
+
function positiveInt(value, flag, min = 1) {
|
|
45
|
+
const parsed = Number(value);
|
|
46
|
+
if (!Number.isInteger(parsed) || parsed < min)
|
|
47
|
+
throw new Error(`${flag} expects an integer >= ${min}, got "${value}"`);
|
|
48
|
+
return parsed;
|
|
49
|
+
}
|
|
50
|
+
/** Starts the server, prints the launch URL and stops cleanly on SIGINT/SIGTERM. */
|
|
51
|
+
export async function serve(flags, app, version) {
|
|
52
|
+
const port = positiveInt(flags.port, "--port", 0);
|
|
53
|
+
if (port > 65_535)
|
|
54
|
+
throw new Error(`--port expects a value up to 65535, got "${flags.port}"`);
|
|
55
|
+
const { startServer, isLoopbackHost } = await import("@alisio/server");
|
|
56
|
+
if (!isLoopbackHost(flags.host) && !flags.allowRemote)
|
|
57
|
+
throw new Error(`--host ${flags.host} is not a loopback address: add --allow-remote to expose the server on the network (no TLS; prefer an SSH tunnel).`);
|
|
58
|
+
// Global CLI options minus the serve flags and the UI-only ones: the rest are app options.
|
|
59
|
+
const { cwd, onEvent: _onEvent, approve: _approve, approveExternalDirectory: _approveDirectory, ...rest } = app;
|
|
60
|
+
const base = Object.fromEntries(Object.entries(rest).filter(([key]) => !SERVE_ONLY_KEYS.has(key)));
|
|
61
|
+
let server;
|
|
62
|
+
try {
|
|
63
|
+
server = await startServer({
|
|
64
|
+
host: flags.host,
|
|
65
|
+
port,
|
|
66
|
+
allowRemote: !!flags.allowRemote,
|
|
67
|
+
version,
|
|
68
|
+
maxOpenWorkspaces: positiveInt(flags.maxWorkspaces, "--max-workspaces"),
|
|
69
|
+
maxConcurrentRuns: positiveInt(flags.maxRuns, "--max-runs"),
|
|
70
|
+
defaultWorkspace: cwd ?? process.cwd(),
|
|
71
|
+
app: base,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error.code === "EADDRINUSE")
|
|
76
|
+
throw new Error(`Port ${port} is already in use: choose another with --port <n> (0 picks a free one).`);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
process.stdout.write(`Alisio web UI: ${server.launchUrl}\n`);
|
|
80
|
+
if (!isLoopbackHost(flags.host))
|
|
81
|
+
process.stderr.write(`Warning: listening on ${flags.host} (--allow-remote). Anyone who can reach this address and obtains the token can drive the agent; traffic is not encrypted.\n`);
|
|
82
|
+
if (!server.webInstalled)
|
|
83
|
+
process.stderr.write("The web UI assets are not installed in this build; only the HTTP API is served.\n");
|
|
84
|
+
process.stderr.write("Press Ctrl+C to stop.\n");
|
|
85
|
+
if (flags.open)
|
|
86
|
+
openBrowser(server.launchUrl);
|
|
87
|
+
let stopping = false;
|
|
88
|
+
const stop = () => {
|
|
89
|
+
if (stopping)
|
|
90
|
+
process.exit(130);
|
|
91
|
+
stopping = true;
|
|
92
|
+
process.stderr.write("Stopping Alisio server…\n");
|
|
93
|
+
// Safety net: shutdown is bounded internally; never hang the terminal.
|
|
94
|
+
setTimeout(() => process.exit(1), 8_000).unref();
|
|
95
|
+
void server.close().finally(() => process.exit(0));
|
|
96
|
+
};
|
|
97
|
+
process.on("SIGINT", stop);
|
|
98
|
+
process.on("SIGTERM", stop);
|
|
99
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure logic of the TUI agent manager (`/agents`): argument parsing, the agent picker rows and
|
|
3
|
+
* their filter, and the editor form with capability-driven options. Rendering and I/O live in
|
|
4
|
+
* `app.ts`; agent files go through the core `AgentDefinitionService` shared with the web server.
|
|
5
|
+
*/
|
|
6
|
+
import type { AgentDefinitionInfo, AgentDefinitionInput, AgentDraft, AgentModelCapabilities, AgentReasoningSummary, AgentScope, AgentTemplateInfo } from "@alisio/core";
|
|
7
|
+
import { type ActiveAgent } from "@alisio/core";
|
|
8
|
+
/** Verbs the TUI owns; any other `/agents <verb>` goes to the subagents plugin's command. */
|
|
9
|
+
export type AgentsCommand = {
|
|
10
|
+
kind: "picker";
|
|
11
|
+
} | {
|
|
12
|
+
kind: "new";
|
|
13
|
+
description?: string;
|
|
14
|
+
} | {
|
|
15
|
+
kind: "templates";
|
|
16
|
+
} | {
|
|
17
|
+
kind: "manage";
|
|
18
|
+
} | {
|
|
19
|
+
kind: "edit";
|
|
20
|
+
id: string;
|
|
21
|
+
scope?: AgentScope;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "delete";
|
|
24
|
+
id: string;
|
|
25
|
+
scope?: AgentScope;
|
|
26
|
+
} | {
|
|
27
|
+
kind: "plugin";
|
|
28
|
+
args: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* `/agents` → picker; `/agents new [description]` (assisted when a description is given),
|
|
32
|
+
* `/agents templates`, `/agents manage`, `/agents edit|delete <id> [project|global]`. Anything
|
|
33
|
+
* else (`list`, `defs`, `reload`, `open`, `cancel`…) belongs to the subagents plugin.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseAgentsArgs(args: string): AgentsCommand;
|
|
36
|
+
/** "Project" / "Global" for agent files, "Built-in" / "Plugin" / "User" otherwise. */
|
|
37
|
+
export declare function agentBadge(agent: Pick<ActiveAgent, "source"> & {
|
|
38
|
+
path?: string;
|
|
39
|
+
}, workspace: string, home: string): string;
|
|
40
|
+
export interface PickerRow {
|
|
41
|
+
value: string;
|
|
42
|
+
label: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
}
|
|
45
|
+
/** Action rows shown above the agents in the `/agents` picker. */
|
|
46
|
+
export declare const AGENT_PICKER_ACTIONS: PickerRow[];
|
|
47
|
+
/** Picker rows: name, badge and markers in the label; description below. */
|
|
48
|
+
export declare function agentPickerRows(agents: Array<ActiveAgent & {
|
|
49
|
+
path?: string;
|
|
50
|
+
}>, activeId: string, roots: {
|
|
51
|
+
workspace: string;
|
|
52
|
+
home: string;
|
|
53
|
+
}): PickerRow[];
|
|
54
|
+
/** Case-insensitive substring match of every query word on the label and description. */
|
|
55
|
+
export declare function filterAgentRows<T extends {
|
|
56
|
+
label: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
value: string;
|
|
59
|
+
}>(rows: ReadonlyArray<T>, query: string): T[];
|
|
60
|
+
/** The editable state of one agent in the TUI editor. */
|
|
61
|
+
export interface AgentForm {
|
|
62
|
+
scope: AgentScope;
|
|
63
|
+
/** Set when editing a saved agent. */
|
|
64
|
+
id?: string;
|
|
65
|
+
name: string;
|
|
66
|
+
description: string;
|
|
67
|
+
instructions: string;
|
|
68
|
+
model: string;
|
|
69
|
+
reasoning: {
|
|
70
|
+
effort?: string;
|
|
71
|
+
summary?: AgentReasoningSummary;
|
|
72
|
+
};
|
|
73
|
+
text: {
|
|
74
|
+
format?: "text" | "json_object" | "json_schema";
|
|
75
|
+
verbosity?: "low" | "medium" | "high";
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export declare function emptyAgentForm(scope: AgentScope, model: string): AgentForm;
|
|
79
|
+
export declare function formFromTemplate(template: AgentTemplateInfo, scope: AgentScope, model: string): AgentForm;
|
|
80
|
+
export declare function formFromDefinition(definition: AgentDefinitionInfo): AgentForm;
|
|
81
|
+
/** Applies a model draft to the form (keeps scope, id and model). */
|
|
82
|
+
export declare function applyDraft(form: AgentForm, draft: AgentDraft): AgentForm;
|
|
83
|
+
/** The API/service body of a form, fitted to the model's capabilities. */
|
|
84
|
+
export declare function formToInput(form: AgentForm, capabilities?: AgentModelCapabilities): AgentDefinitionInput;
|
|
85
|
+
/** Validation errors of a form (empty when it can be saved). */
|
|
86
|
+
export declare function formErrors(form: AgentForm): string[];
|
|
87
|
+
/** One-line editing of multi-line text: newlines shown and typed as `\n`. */
|
|
88
|
+
export declare const encodeMultiline: (text: string) => string;
|
|
89
|
+
export declare const decodeMultiline: (text: string) => string;
|
|
90
|
+
export type AgentField = "scope" | "name" | "description" | "instructions" | "model" | "effort" | "summary" | "verbosity" | "format" | "refine" | "save" | "cancel";
|
|
91
|
+
/**
|
|
92
|
+
* Editor rows. Settings the model does not support are shown as "not supported by this model"
|
|
93
|
+
* (their value is ignored on save); `dirty` enables the Save row.
|
|
94
|
+
*/
|
|
95
|
+
export declare function agentEditorRows(form: AgentForm, capabilities: AgentModelCapabilities, options: {
|
|
96
|
+
creating: boolean;
|
|
97
|
+
scopes: AgentScope[];
|
|
98
|
+
dirty: boolean;
|
|
99
|
+
}): Array<PickerRow & {
|
|
100
|
+
value: AgentField;
|
|
101
|
+
}>;
|
|
102
|
+
/** Choice rows of a select field, honoring the model capabilities. */
|
|
103
|
+
export declare function agentFieldChoices(field: "effort" | "summary" | "verbosity" | "format", capabilities: AgentModelCapabilities): PickerRow[];
|
|
104
|
+
/** Whether a field can be edited for the model (unsupported settings are read-only). */
|
|
105
|
+
export declare function agentFieldSupported(field: "effort" | "summary" | "verbosity", capabilities: AgentModelCapabilities): boolean;
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { AGENT_REASONING_SUMMARIES, AGENT_TEXT_FORMATS, AGENT_VERBOSITIES, DEFAULT_AGENT_ID, fitAgentSettings, validateAgentDefinitionInput, } from "@alisio/core";
|
|
2
|
+
/**
|
|
3
|
+
* `/agents` → picker; `/agents new [description]` (assisted when a description is given),
|
|
4
|
+
* `/agents templates`, `/agents manage`, `/agents edit|delete <id> [project|global]`. Anything
|
|
5
|
+
* else (`list`, `defs`, `reload`, `open`, `cancel`…) belongs to the subagents plugin.
|
|
6
|
+
*/
|
|
7
|
+
export function parseAgentsArgs(args) {
|
|
8
|
+
const text = args.trim();
|
|
9
|
+
if (!text)
|
|
10
|
+
return { kind: "picker" };
|
|
11
|
+
const [verb = "", ...rest] = text.split(/\s+/);
|
|
12
|
+
const tail = text.slice(verb.length).trim();
|
|
13
|
+
const scope = (value) => value === "project" || value === "global" ? value : undefined;
|
|
14
|
+
switch (verb.toLowerCase()) {
|
|
15
|
+
case "new":
|
|
16
|
+
case "create":
|
|
17
|
+
return tail ? { kind: "new", description: tail } : { kind: "new" };
|
|
18
|
+
case "templates":
|
|
19
|
+
case "template":
|
|
20
|
+
return { kind: "templates" };
|
|
21
|
+
case "manage":
|
|
22
|
+
return { kind: "manage" };
|
|
23
|
+
case "edit":
|
|
24
|
+
case "delete":
|
|
25
|
+
case "rm": {
|
|
26
|
+
const [id, where] = rest;
|
|
27
|
+
if (!id)
|
|
28
|
+
return { kind: "manage" };
|
|
29
|
+
const found = scope(where);
|
|
30
|
+
return {
|
|
31
|
+
kind: verb.toLowerCase() === "edit" ? "edit" : "delete",
|
|
32
|
+
id,
|
|
33
|
+
...(found ? { scope: found } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
default:
|
|
37
|
+
return { kind: "plugin", args: text };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** "Project" / "Global" for agent files, "Built-in" / "Plugin" / "User" otherwise. */
|
|
41
|
+
export function agentBadge(agent, workspace, home) {
|
|
42
|
+
if (agent.source === "builtin")
|
|
43
|
+
return "Built-in";
|
|
44
|
+
if (agent.source === "plugin")
|
|
45
|
+
return "Plugin";
|
|
46
|
+
const path = agent.path ?? "";
|
|
47
|
+
if (path.startsWith(`${workspace}/`) || path.startsWith(`${workspace}\\`))
|
|
48
|
+
return "Project";
|
|
49
|
+
if (path.startsWith(`${home}/`) || path.startsWith(`${home}\\`))
|
|
50
|
+
return "Global";
|
|
51
|
+
return "User";
|
|
52
|
+
}
|
|
53
|
+
/** Action rows shown above the agents in the `/agents` picker. */
|
|
54
|
+
export const AGENT_PICKER_ACTIONS = [
|
|
55
|
+
{
|
|
56
|
+
value: "__agents:new",
|
|
57
|
+
label: "+ Create agent…",
|
|
58
|
+
description: "Blank, from a template or written by Alisio",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
value: "__agents:manage",
|
|
62
|
+
label: "✎ Manage saved agents…",
|
|
63
|
+
description: "Edit or delete project and global agents",
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
/** Picker rows: name, badge and markers in the label; description below. */
|
|
67
|
+
export function agentPickerRows(agents, activeId, roots) {
|
|
68
|
+
return agents.map((agent) => ({
|
|
69
|
+
value: agent.id,
|
|
70
|
+
label: [
|
|
71
|
+
agent.name,
|
|
72
|
+
`[${agentBadge(agent, roots.workspace, roots.home)}]`,
|
|
73
|
+
agent.id === activeId ? "(current)" : undefined,
|
|
74
|
+
agent.id === DEFAULT_AGENT_ID ? "(default)" : undefined,
|
|
75
|
+
agent.readOnly ? "read-only" : undefined,
|
|
76
|
+
]
|
|
77
|
+
.filter(Boolean)
|
|
78
|
+
.join(" "),
|
|
79
|
+
description: [agent.description, agent.model ? `model: ${agent.model}` : undefined]
|
|
80
|
+
.filter(Boolean)
|
|
81
|
+
.join(" · "),
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
/** Case-insensitive substring match of every query word on the label and description. */
|
|
85
|
+
export function filterAgentRows(rows, query) {
|
|
86
|
+
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
87
|
+
if (!words.length)
|
|
88
|
+
return [...rows];
|
|
89
|
+
return rows.filter((row) => {
|
|
90
|
+
const haystack = `${row.value} ${row.label} ${row.description ?? ""}`.toLowerCase();
|
|
91
|
+
return words.every((word) => haystack.includes(word));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
export function emptyAgentForm(scope, model) {
|
|
95
|
+
return {
|
|
96
|
+
scope,
|
|
97
|
+
name: "New agent",
|
|
98
|
+
description: "",
|
|
99
|
+
instructions: "",
|
|
100
|
+
model,
|
|
101
|
+
reasoning: {},
|
|
102
|
+
text: {},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
const settingsOf = (source) => ({
|
|
106
|
+
reasoning: {
|
|
107
|
+
...(source.reasoning?.effort ? { effort: source.reasoning.effort } : {}),
|
|
108
|
+
...(source.reasoning?.summary ? { summary: source.reasoning.summary } : {}),
|
|
109
|
+
},
|
|
110
|
+
text: {
|
|
111
|
+
...(source.text?.format?.type ? { format: source.text.format.type } : {}),
|
|
112
|
+
...(source.text?.verbosity ? { verbosity: source.text.verbosity } : {}),
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
export function formFromTemplate(template, scope, model) {
|
|
116
|
+
return {
|
|
117
|
+
scope,
|
|
118
|
+
name: template.name,
|
|
119
|
+
description: template.description,
|
|
120
|
+
instructions: template.instructions,
|
|
121
|
+
model,
|
|
122
|
+
...settingsOf(template),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export function formFromDefinition(definition) {
|
|
126
|
+
return {
|
|
127
|
+
scope: definition.scope,
|
|
128
|
+
id: definition.id,
|
|
129
|
+
name: definition.name,
|
|
130
|
+
description: definition.description,
|
|
131
|
+
instructions: definition.instructions,
|
|
132
|
+
model: definition.model,
|
|
133
|
+
...settingsOf(definition),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/** Applies a model draft to the form (keeps scope, id and model). */
|
|
137
|
+
export function applyDraft(form, draft) {
|
|
138
|
+
return {
|
|
139
|
+
...form,
|
|
140
|
+
name: draft.name,
|
|
141
|
+
description: draft.description,
|
|
142
|
+
instructions: draft.instructions,
|
|
143
|
+
...settingsOf(draft),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/** The API/service body of a form, fitted to the model's capabilities. */
|
|
147
|
+
export function formToInput(form, capabilities) {
|
|
148
|
+
const input = {
|
|
149
|
+
name: form.name.trim(),
|
|
150
|
+
...(form.description.trim() ? { description: form.description.trim() } : {}),
|
|
151
|
+
...(form.instructions.trim() ? { instructions: form.instructions.trim() } : {}),
|
|
152
|
+
model: form.model.trim(),
|
|
153
|
+
...(form.reasoning.effort || form.reasoning.summary
|
|
154
|
+
? {
|
|
155
|
+
reasoning: {
|
|
156
|
+
...(form.reasoning.effort ? { effort: form.reasoning.effort } : {}),
|
|
157
|
+
...(form.reasoning.summary ? { summary: form.reasoning.summary } : {}),
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
: {}),
|
|
161
|
+
...(form.text.format || form.text.verbosity
|
|
162
|
+
? {
|
|
163
|
+
text: {
|
|
164
|
+
...(form.text.format ? { format: { type: form.text.format } } : {}),
|
|
165
|
+
...(form.text.verbosity ? { verbosity: form.text.verbosity } : {}),
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
: {}),
|
|
169
|
+
};
|
|
170
|
+
return capabilities ? fitAgentSettings(input, capabilities) : input;
|
|
171
|
+
}
|
|
172
|
+
/** Validation errors of a form (empty when it can be saved). */
|
|
173
|
+
export function formErrors(form) {
|
|
174
|
+
const result = validateAgentDefinitionInput(formToInput(form));
|
|
175
|
+
return result.ok ? [] : result.errors.map((e) => e.message);
|
|
176
|
+
}
|
|
177
|
+
/** One-line editing of multi-line text: newlines shown and typed as `\n`. */
|
|
178
|
+
export const encodeMultiline = (text) => text.replace(/\\/g, "\\\\").replace(/\n/g, "\\n");
|
|
179
|
+
export const decodeMultiline = (text) => text.replace(/\\(\\|n)/g, (_, c) => (c === "n" ? "\n" : "\\"));
|
|
180
|
+
const clip = (text, max = 60) => {
|
|
181
|
+
const line = text.replace(/\s+/g, " ").trim();
|
|
182
|
+
return line.length > max ? `${line.slice(0, max - 1)}…` : line;
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Editor rows. Settings the model does not support are shown as "not supported by this model"
|
|
186
|
+
* (their value is ignored on save); `dirty` enables the Save row.
|
|
187
|
+
*/
|
|
188
|
+
export function agentEditorRows(form, capabilities, options) {
|
|
189
|
+
const unsupported = "not supported by this model";
|
|
190
|
+
const rows = [];
|
|
191
|
+
if (options.creating && options.scopes.length > 1)
|
|
192
|
+
rows.push({
|
|
193
|
+
value: "scope",
|
|
194
|
+
label: `Scope: ${form.scope === "project" ? "Project" : "Global"}`,
|
|
195
|
+
description: "Project: .agents/agents in this workspace · Global: ~/.agents/agents",
|
|
196
|
+
});
|
|
197
|
+
rows.push({ value: "name", label: `Name: ${form.name || "(empty)"}` }, {
|
|
198
|
+
value: "description",
|
|
199
|
+
label: `Description: ${clip(form.description) || "(derived from instructions)"}`,
|
|
200
|
+
}, {
|
|
201
|
+
value: "instructions",
|
|
202
|
+
label: `Instructions: ${clip(form.instructions) || "(empty)"}`,
|
|
203
|
+
description: `${form.instructions.length} characters`,
|
|
204
|
+
}, { value: "model", label: `Model: ${form.model || "(choose one)"}` }, {
|
|
205
|
+
value: "effort",
|
|
206
|
+
label: `Reasoning effort: ${capabilities.reasoning ? (form.reasoning.effort ?? "model default") : "—"}`,
|
|
207
|
+
...(capabilities.reasoning ? {} : { description: unsupported }),
|
|
208
|
+
}, {
|
|
209
|
+
value: "summary",
|
|
210
|
+
label: `Reasoning summary: ${capabilities.summary ? (form.reasoning.summary ?? "auto") : "—"}`,
|
|
211
|
+
...(capabilities.summary ? {} : { description: unsupported }),
|
|
212
|
+
}, {
|
|
213
|
+
value: "verbosity",
|
|
214
|
+
label: `Verbosity: ${capabilities.verbosity ? (form.text.verbosity ?? "default") : "—"}`,
|
|
215
|
+
...(capabilities.verbosity ? {} : { description: unsupported }),
|
|
216
|
+
}, { value: "format", label: `Text format: ${form.text.format ?? "text"}` }, {
|
|
217
|
+
value: "refine",
|
|
218
|
+
label: "✦ Refine with Alisio…",
|
|
219
|
+
description: "The active model rewrites the instructions to the authoring standards",
|
|
220
|
+
});
|
|
221
|
+
const errors = formErrors(form);
|
|
222
|
+
rows.push({
|
|
223
|
+
value: "save",
|
|
224
|
+
label: options.dirty && !errors.length
|
|
225
|
+
? "✓ Save agent definition"
|
|
226
|
+
: "✓ Save agent definition (nothing to save)",
|
|
227
|
+
...(errors.length
|
|
228
|
+
? { description: errors.join("; ") }
|
|
229
|
+
: options.dirty
|
|
230
|
+
? {}
|
|
231
|
+
: { description: "No unsaved changes" }),
|
|
232
|
+
});
|
|
233
|
+
rows.push({ value: "cancel", label: "✗ Close editor" });
|
|
234
|
+
return rows;
|
|
235
|
+
}
|
|
236
|
+
/** Choice rows of a select field, honoring the model capabilities. */
|
|
237
|
+
export function agentFieldChoices(field, capabilities) {
|
|
238
|
+
const levels = field === "effort"
|
|
239
|
+
? capabilities.effortLevels
|
|
240
|
+
: field === "summary"
|
|
241
|
+
? AGENT_REASONING_SUMMARIES
|
|
242
|
+
: field === "verbosity"
|
|
243
|
+
? AGENT_VERBOSITIES
|
|
244
|
+
: AGENT_TEXT_FORMATS.filter((format) => capabilities.textFormats.includes(format));
|
|
245
|
+
return [
|
|
246
|
+
...levels.map((value) => ({
|
|
247
|
+
value,
|
|
248
|
+
label: field === "format"
|
|
249
|
+
? value === "json_object"
|
|
250
|
+
? "JSON"
|
|
251
|
+
: value === "json_schema"
|
|
252
|
+
? "JSON Schema"
|
|
253
|
+
: "Text"
|
|
254
|
+
: value,
|
|
255
|
+
...(field === "effort" && value === capabilities.defaultEffort
|
|
256
|
+
? { description: "model default" }
|
|
257
|
+
: {}),
|
|
258
|
+
})),
|
|
259
|
+
...(field === "format" ? [] : [{ value: "!clear", label: "(use the default)" }]),
|
|
260
|
+
];
|
|
261
|
+
}
|
|
262
|
+
/** Whether a field can be edited for the model (unsupported settings are read-only). */
|
|
263
|
+
export function agentFieldSupported(field, capabilities) {
|
|
264
|
+
return field === "effort"
|
|
265
|
+
? capabilities.reasoning
|
|
266
|
+
: field === "summary"
|
|
267
|
+
? capabilities.summary
|
|
268
|
+
: capabilities.verbosity;
|
|
269
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ACTIVE (main-session) agent logic lives in `@alisio/core` (`agents/active.ts`) so every
|
|
3
|
+
* surface (TUI, headless, server) resolves agents the same way. Re-exported for compatibility.
|
|
4
|
+
*/
|
|
5
|
+
export { type ActiveAgent, type AgentPickerItem, activeAgentCatalog, agentCatalogFromState, agentPickerItems, agentRunOptions, BUILTIN_AGENTS, DEFAULT_AGENT_ID, type MainCapableAgentRecord, mainAgentFromRecord, resolveActiveAgent, } from "@alisio/core";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ACTIVE (main-session) agent logic lives in `@alisio/core` (`agents/active.ts`) so every
|
|
3
|
+
* surface (TUI, headless, server) resolves agents the same way. Re-exported for compatibility.
|
|
4
|
+
*/
|
|
5
|
+
export { activeAgentCatalog, agentCatalogFromState, agentPickerItems, agentRunOptions, BUILTIN_AGENTS, DEFAULT_AGENT_ID, mainAgentFromRecord, resolveActiveAgent, } from "@alisio/core";
|
package/dist/tui/app.d.ts
CHANGED
|
@@ -1,4 +1,45 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type AppOptions } from "@alisio/core";
|
|
2
|
+
import { type Component, type SelectItem } from "@earendil-works/pi-tui";
|
|
3
|
+
/**
|
|
4
|
+
* Inline selection list with type-to-filter, rendered above the editor.
|
|
5
|
+
*
|
|
6
|
+
* Grouped item builders (pluginCatalogItems) interleave their output with non-selectable
|
|
7
|
+
* `__group:` header items. Headers stay in the SelectList so they render, scroll and truncate
|
|
8
|
+
* like rows, but selection is owned here: navigation jumps over headers via skipGroupHeaders
|
|
9
|
+
* (wrapping both directions), Enter never fires onSelect for a header, and the whole display
|
|
10
|
+
* sequence is re-derived on every filter keystroke so headers of fully-filtered groups vanish.
|
|
11
|
+
*/
|
|
12
|
+
export declare class Picker implements Component {
|
|
13
|
+
private title;
|
|
14
|
+
private readonly onSelectItem;
|
|
15
|
+
private readonly onCancelPick;
|
|
16
|
+
private filterable;
|
|
17
|
+
private detail?;
|
|
18
|
+
/** Custom filter (default: value prefix, keeping group headers). */
|
|
19
|
+
private readonly match?;
|
|
20
|
+
private list;
|
|
21
|
+
private filter;
|
|
22
|
+
/** Full item sequence (group headers + rows) before any filtering. */
|
|
23
|
+
private readonly allItems;
|
|
24
|
+
/** Display sequence: filtered rows plus the headers of groups that still match. */
|
|
25
|
+
private items;
|
|
26
|
+
/** Selection index within `items`; always a real row, never a group header. */
|
|
27
|
+
private index;
|
|
28
|
+
/** Picker height target: counted on real rows only, so headers never stretch the list. */
|
|
29
|
+
private readonly maxVisible;
|
|
30
|
+
constructor(title: string, items: SelectItem[], onSelectItem: (item: SelectItem) => void, onCancelPick: () => void, filterable?: boolean, detail?: string | undefined,
|
|
31
|
+
/** Custom filter (default: value prefix, keeping group headers). */
|
|
32
|
+
match?: ((items: SelectItem[], filter: string) => SelectItem[]) | undefined);
|
|
33
|
+
/** Copies items, dimming group-header labels (style helpers stay plain under NO_COLOR). */
|
|
34
|
+
private static displayItems;
|
|
35
|
+
/** (Re)builds the SelectList over the filtered display sequence; selection starts on a row. */
|
|
36
|
+
private resetList;
|
|
37
|
+
invalidate(): void;
|
|
38
|
+
/** The currently selected item — always a real row, never a group header. */
|
|
39
|
+
getSelectedItem(): SelectItem | null;
|
|
40
|
+
handleInput(data: string): void;
|
|
41
|
+
render(width: number): string[];
|
|
42
|
+
}
|
|
2
43
|
export interface TuiOptions extends AppOptions {
|
|
3
44
|
session?: string;
|
|
4
45
|
quiet?: boolean;
|