@alisio/alisio-code 0.1.0-alpha.19 → 0.1.0-alpha.20
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 +2 -0
- package/dist/tui/app.js +46 -4
- package/dist/tui/components.d.ts +27 -0
- package/dist/tui/components.js +45 -0
- package/dist/tui/settings.d.ts +1 -0
- package/dist/tui/settings.js +10 -1
- package/package.json +3 -3
package/dist/main.js
CHANGED
|
@@ -38,6 +38,7 @@ program
|
|
|
38
38
|
.option("--allow-external", "Allow network tools: webfetch, websearch and provider-native search")
|
|
39
39
|
.option("--allow-mcp", "Allow configured MCP servers and remote tool calls")
|
|
40
40
|
.option("--allow-agents", "Allow messaging neighboring agents through Herdr")
|
|
41
|
+
.option("--add-dir <paths...>", "Additional directories tools may read/write outside the workspace (repeatable)")
|
|
41
42
|
.option("--no-herdr", "Disable automatic Herdr lifecycle reports")
|
|
42
43
|
.option("--read-only", "Disable writes, arbitrary processes, network tools, executable plugins and MCP")
|
|
43
44
|
.option("--db <path>", "Session database")
|
|
@@ -54,6 +55,7 @@ const options = (cmd) => {
|
|
|
54
55
|
baseURL: o.baseUrl,
|
|
55
56
|
noHerdr: o.herdr === false,
|
|
56
57
|
disablePlugins: o.disablePlugin,
|
|
58
|
+
addDirs: o.addDir,
|
|
57
59
|
...(o.agents
|
|
58
60
|
? { pluginOptions: { subagents: { agents: parseAgents(String(o.agents)) } } }
|
|
59
61
|
: {}),
|
package/dist/tui/app.js
CHANGED
|
@@ -4,7 +4,7 @@ import { loadVersion } from "../version.js";
|
|
|
4
4
|
import { activeAgentCatalog, agentPickerItems, agentRunOptions, mainAgentFromRecord, resolveActiveAgent, } from "./agents.js";
|
|
5
5
|
import { MAX_ATTACHMENTS_PER_MESSAGE, MAX_IMAGE_BYTES, pasteImageFromClipboard, removeLastAttachment, toApiAttachment, } from "./attachments.js";
|
|
6
6
|
import { copyText, nodeSpawn } from "./clipboard.js";
|
|
7
|
-
import { AttachmentsBar, BannerBlock, clock, Footer, Header, QuestionPanel, Switch, TranscriptSync, TreePanel, } from "./components.js";
|
|
7
|
+
import { AttachmentsBar, BannerBlock, ContentInset, clock, contentInnerWidth, Footer, Header, QuestionPanel, Switch, TranscriptSync, TreePanel, } from "./components.js";
|
|
8
8
|
import { ConnectInputPrompt } from "./connect-input.js";
|
|
9
9
|
import { bounded, EXIT_PENDING_CAP_MS, EXIT_SESSION_END_CAP_MS } from "./exit.js";
|
|
10
10
|
import { BRANCH_REFRESH_MS, createBranchCache } from "./git-branch.js";
|
|
@@ -141,6 +141,7 @@ export async function runTui(options) {
|
|
|
141
141
|
const { BUILTIN_PROMPTS } = await import("../prompts/index.js");
|
|
142
142
|
let dispatch = () => { };
|
|
143
143
|
let approve = async () => "deny";
|
|
144
|
+
let approveExternalDirectory = async () => "deny";
|
|
144
145
|
const app = await createApplication({
|
|
145
146
|
builtins: BUILTIN_PLUGINS,
|
|
146
147
|
builtinPrompts: BUILTIN_PROMPTS,
|
|
@@ -148,6 +149,7 @@ export async function runTui(options) {
|
|
|
148
149
|
...options,
|
|
149
150
|
onEvent: (event) => dispatch(event),
|
|
150
151
|
approve: (request) => approve(request),
|
|
152
|
+
approveExternalDirectory: (request) => approveExternalDirectory(request),
|
|
151
153
|
});
|
|
152
154
|
let activeProvider = app.providerInfo;
|
|
153
155
|
let session = options.session ?? app.store.create(app.workspace, app.provider.id, app.provider.model).id;
|
|
@@ -390,14 +392,21 @@ export async function runTui(options) {
|
|
|
390
392
|
// working (copyOnSelect must never be disturbed). The region sits between the ScrollView and
|
|
391
393
|
// the content switch; content rows = visible rows + the scroll offset.
|
|
392
394
|
let transcriptScroll;
|
|
393
|
-
|
|
395
|
+
// Transcript content is inset from both borders in ONE place: the wrapper hands children a
|
|
396
|
+
// reduced width and prefixes every row, so agent text, copy hints, tool sub-lines, reasoning
|
|
397
|
+
// lines and group headers all share the same visual column. The config is read live, so a
|
|
398
|
+
// `/settings` change applies on the next render without rebuilding the view.
|
|
399
|
+
const contentInsetX = () => app.config.tui.contentPaddingX;
|
|
400
|
+
const transcriptRegion = new MouseRegion(new ContentInset(new Switch(() => (viewedState() ? childView.container : transcript)), contentInsetX), (event) => {
|
|
394
401
|
if (picker || event.type !== "click" || event.button !== "left")
|
|
395
402
|
return undefined;
|
|
396
403
|
const scroll = transcriptScroll;
|
|
397
404
|
if (!scroll)
|
|
398
405
|
return undefined;
|
|
399
406
|
const visible = visibleTranscript();
|
|
400
|
-
|
|
407
|
+
// entryAt re-renders each row to measure its height, so it must use the same reduced width
|
|
408
|
+
// the wrapper hands the rows during render.
|
|
409
|
+
const candidate = visible.sync.entryAt(event.y + scroll.scrollTop, contentInnerWidth(event.width, contentInsetX()));
|
|
401
410
|
if (!candidate)
|
|
402
411
|
return undefined;
|
|
403
412
|
toggleFold(candidate);
|
|
@@ -549,6 +558,36 @@ export async function runTui(options) {
|
|
|
549
558
|
], (item) => finish(item.value), () => finish("deny")));
|
|
550
559
|
}),
|
|
551
560
|
});
|
|
561
|
+
/**
|
|
562
|
+
* External-directory approval: a tool path outside the workspace and every declared extra root
|
|
563
|
+
* asks here before the call runs, scoped to the containing directory (not the individual file),
|
|
564
|
+
* so one approval covers that directory's subtree for the session. Same queue and the same
|
|
565
|
+
* allow-once / allow-session / deny choices as the capability approval above.
|
|
566
|
+
*/
|
|
567
|
+
approveExternalDirectory = (request) => interactiveQueue.submit({
|
|
568
|
+
sessionId: request.session,
|
|
569
|
+
label: request.label,
|
|
570
|
+
signal: request.signal,
|
|
571
|
+
onWithdrawn: () => "deny",
|
|
572
|
+
run: () => new Promise((resolve) => {
|
|
573
|
+
let settled = false;
|
|
574
|
+
const finish = (decision) => {
|
|
575
|
+
if (settled)
|
|
576
|
+
return;
|
|
577
|
+
settled = true;
|
|
578
|
+
request.signal.removeEventListener("abort", onAbort);
|
|
579
|
+
closePicker();
|
|
580
|
+
resolve(decision);
|
|
581
|
+
};
|
|
582
|
+
const onAbort = () => finish("deny");
|
|
583
|
+
request.signal.addEventListener("abort", onAbort, { once: true });
|
|
584
|
+
showPicker(new Picker(`${request.label ? `[${request.label}] ` : ""}Allow access outside the workspace to ${shortenPath(request.directory, homedir(), 72)}?`, [
|
|
585
|
+
{ value: "once", label: "Allow once" },
|
|
586
|
+
{ value: "session", label: "Always allow this directory in this session" },
|
|
587
|
+
{ value: "deny", label: "Deny" },
|
|
588
|
+
], (item) => finish(item.value), () => finish("deny")));
|
|
589
|
+
}),
|
|
590
|
+
});
|
|
552
591
|
/**
|
|
553
592
|
* `ask_user_question` / `/ask`: routed through the same `interactiveQueue` as `approve`/`select`,
|
|
554
593
|
* so a root or subagent question never races another prompt for the screen. A question withdrawn
|
|
@@ -1269,6 +1308,9 @@ export async function runTui(options) {
|
|
|
1269
1308
|
.then(() => {
|
|
1270
1309
|
if (row.id === "tui.paddingX")
|
|
1271
1310
|
editor.setPaddingX(Number(value));
|
|
1311
|
+
// The inset wrapper reads the live config; repaint so the new value shows immediately.
|
|
1312
|
+
if (row.id === "tui.contentPaddingX")
|
|
1313
|
+
tui.requestRender();
|
|
1272
1314
|
// Rebuild the slash provider: the toggle gates the skill:<id> entries immediately.
|
|
1273
1315
|
if (row.id === "tui.skillSlashCommands")
|
|
1274
1316
|
editor.setAutocompleteProvider(buildSlashCompletionProvider());
|
|
@@ -1353,7 +1395,7 @@ export async function runTui(options) {
|
|
|
1353
1395
|
return `| \`${t.name}\` | ${effect} | ${state} |`;
|
|
1354
1396
|
})
|
|
1355
1397
|
.join("\n");
|
|
1356
|
-
return `**Tools**\n\n| Tool | Effect | State |\n| --- | --- | --- |\n${rows}\n\n\`ask\` prompts before running (allow once / session / deny). Use --allow-write / --allow-process to pre-allow; --read-only disables them. \`internal\` tools (built-in plugins) only write Alisio's own state.`;
|
|
1398
|
+
return `**Tools**\n\n| Tool | Effect | State |\n| --- | --- | --- |\n${rows}\n\n\`ask\` prompts before running (allow once / session / deny). Use --allow-write / --allow-process to pre-allow; --read-only disables them. Paths outside the workspace ask per directory; pre-allow with --add-dir or the \`additionalDirectories\` config key. \`internal\` tools (built-in plugins) only write Alisio's own state.`;
|
|
1357
1399
|
};
|
|
1358
1400
|
const statsReport = () => {
|
|
1359
1401
|
const s = view.stats;
|
package/dist/tui/components.d.ts
CHANGED
|
@@ -129,6 +129,33 @@ export declare class InfoBlock implements Component {
|
|
|
129
129
|
invalidate(): void;
|
|
130
130
|
render(width: number): string[];
|
|
131
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Minimum usable inner width (cells) the transcript keeps no matter how narrow the terminal is or
|
|
134
|
+
* how large the configured inset grows. The effective inset is clamped so this floor is honored,
|
|
135
|
+
* which also keeps the inner width positive.
|
|
136
|
+
*/
|
|
137
|
+
export declare const MIN_CONTENT_WIDTH = 20;
|
|
138
|
+
/**
|
|
139
|
+
* Inset actually applied on each side: the configured value capped so the inner column stays at
|
|
140
|
+
* least `MIN_CONTENT_WIDTH` wide. On terminals narrower than that floor the inset degrades to 0
|
|
141
|
+
* rather than overflowing (the inner width then simply equals the terminal width).
|
|
142
|
+
*/
|
|
143
|
+
export declare function effectiveContentInset(width: number, inset: number): number;
|
|
144
|
+
/** Inner width handed to transcript children after the effective inset is applied. Always >= 1. */
|
|
145
|
+
export declare function contentInnerWidth(width: number, inset: number): number;
|
|
146
|
+
/**
|
|
147
|
+
* Applies the transcript's horizontal inset in ONE place: it hands children a reduced width and
|
|
148
|
+
* prefixes every produced line with the inset, so all transcript chrome (text, copy hint, tool
|
|
149
|
+
* sub-lines, reasoning lines, group headers) shares the same visual column. The reduced width is a
|
|
150
|
+
* deterministic function of the terminal width, so per-row caches keyed by width stay effective.
|
|
151
|
+
*/
|
|
152
|
+
export declare class ContentInset implements Component {
|
|
153
|
+
private readonly inner;
|
|
154
|
+
private readonly inset;
|
|
155
|
+
constructor(inner: Component, inset: () => number);
|
|
156
|
+
invalidate(): void;
|
|
157
|
+
render(width: number): string[];
|
|
158
|
+
}
|
|
132
159
|
export declare function componentFor(entry: TranscriptEntry): Component;
|
|
133
160
|
/**
|
|
134
161
|
* Keeps a container of transcript components in step with view-model items. Rows are keyed
|
package/dist/tui/components.js
CHANGED
|
@@ -588,6 +588,51 @@ export class InfoBlock {
|
|
|
588
588
|
return fit(["", ...inner.map((l) => `${style.gray("│")} ${l}`)], width);
|
|
589
589
|
}
|
|
590
590
|
}
|
|
591
|
+
/**
|
|
592
|
+
* Minimum usable inner width (cells) the transcript keeps no matter how narrow the terminal is or
|
|
593
|
+
* how large the configured inset grows. The effective inset is clamped so this floor is honored,
|
|
594
|
+
* which also keeps the inner width positive.
|
|
595
|
+
*/
|
|
596
|
+
export const MIN_CONTENT_WIDTH = 20;
|
|
597
|
+
/**
|
|
598
|
+
* Inset actually applied on each side: the configured value capped so the inner column stays at
|
|
599
|
+
* least `MIN_CONTENT_WIDTH` wide. On terminals narrower than that floor the inset degrades to 0
|
|
600
|
+
* rather than overflowing (the inner width then simply equals the terminal width).
|
|
601
|
+
*/
|
|
602
|
+
export function effectiveContentInset(width, inset) {
|
|
603
|
+
const requested = Math.max(0, Math.floor(inset));
|
|
604
|
+
const max = Math.max(0, Math.floor((Math.floor(width) - MIN_CONTENT_WIDTH) / 2));
|
|
605
|
+
return Math.min(requested, max);
|
|
606
|
+
}
|
|
607
|
+
/** Inner width handed to transcript children after the effective inset is applied. Always >= 1. */
|
|
608
|
+
export function contentInnerWidth(width, inset) {
|
|
609
|
+
return Math.max(1, Math.floor(width) - effectiveContentInset(width, inset) * 2);
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Applies the transcript's horizontal inset in ONE place: it hands children a reduced width and
|
|
613
|
+
* prefixes every produced line with the inset, so all transcript chrome (text, copy hint, tool
|
|
614
|
+
* sub-lines, reasoning lines, group headers) shares the same visual column. The reduced width is a
|
|
615
|
+
* deterministic function of the terminal width, so per-row caches keyed by width stay effective.
|
|
616
|
+
*/
|
|
617
|
+
export class ContentInset {
|
|
618
|
+
inner;
|
|
619
|
+
inset;
|
|
620
|
+
constructor(inner, inset) {
|
|
621
|
+
this.inner = inner;
|
|
622
|
+
this.inset = inset;
|
|
623
|
+
}
|
|
624
|
+
invalidate() {
|
|
625
|
+
this.inner.invalidate();
|
|
626
|
+
}
|
|
627
|
+
render(width) {
|
|
628
|
+
const inset = effectiveContentInset(width, this.inset());
|
|
629
|
+
const lines = this.inner.render(contentInnerWidth(width, inset));
|
|
630
|
+
if (!inset)
|
|
631
|
+
return lines;
|
|
632
|
+
const pad = " ".repeat(inset);
|
|
633
|
+
return lines.map((line) => `${pad}${line}`);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
591
636
|
export function componentFor(entry) {
|
|
592
637
|
if (entry.kind === "group")
|
|
593
638
|
return new ToolGroupBlock(entry.group);
|
package/dist/tui/settings.d.ts
CHANGED
package/dist/tui/settings.js
CHANGED
|
@@ -13,7 +13,7 @@ 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
15
|
limits: { maxTurns: 100, maxOutputTokens: 16_384, maxContextChars: 800_000, timeoutMs: 300_000 },
|
|
16
|
-
tui: { paddingX: 1, skillSlashCommands: true },
|
|
16
|
+
tui: { paddingX: 1, contentPaddingX: 2, skillSlashCommands: true },
|
|
17
17
|
mcp: { allow: false },
|
|
18
18
|
websearch: { provider: undefined },
|
|
19
19
|
pluginHooks: { timeoutMs: 15_000 },
|
|
@@ -161,6 +161,15 @@ export const SETTINGS_DEFINITIONS = [
|
|
|
161
161
|
read: (config) => config.tui.paddingX,
|
|
162
162
|
description: "Horizontal padding (columns) around the editor input box. Applied immediately to the current editor.",
|
|
163
163
|
},
|
|
164
|
+
{
|
|
165
|
+
id: "tui.contentPaddingX",
|
|
166
|
+
label: "Content inset",
|
|
167
|
+
category: "TUI",
|
|
168
|
+
valueType: "number",
|
|
169
|
+
values: [0, 1, 2, 3, 4, 6, 8, 12],
|
|
170
|
+
read: (config) => config.tui.contentPaddingX,
|
|
171
|
+
description: "Horizontal inset (columns) on each side of the transcript content, keeping agent-written rows clear of the window borders. Clamped on narrow terminals so the content column stays usable. Applied immediately.",
|
|
172
|
+
},
|
|
164
173
|
{
|
|
165
174
|
id: "tui.skillSlashCommands",
|
|
166
175
|
label: "Skill slash commands",
|
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.20",
|
|
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,11 +45,11 @@
|
|
|
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.17",
|
|
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.10",
|
|
52
|
-
"@alisio/sdk": "0.1.0-alpha.
|
|
52
|
+
"@alisio/sdk": "0.1.0-alpha.12",
|
|
53
53
|
"@earendil-works/pi-tui": "0.87.1",
|
|
54
54
|
"commander": "15.0.0"
|
|
55
55
|
},
|