@alisio/core 0.1.0-alpha.8 → 0.1.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/README.md +52 -7
- package/dist/agents/active.d.ts +99 -0
- package/dist/agents/active.js +218 -0
- package/dist/agents/capabilities.d.ts +26 -0
- package/dist/agents/capabilities.js +120 -0
- package/dist/agents/create-agent-skill.d.ts +11 -0
- package/dist/agents/create-agent-skill.js +60 -0
- package/dist/agents/definitions.d.ts +88 -0
- package/dist/agents/definitions.js +404 -0
- package/dist/agents/draft.d.ts +41 -0
- package/dist/agents/draft.js +133 -0
- package/dist/agents/service.d.ts +57 -0
- package/dist/agents/service.js +86 -0
- package/dist/agents/templates.d.ts +8 -0
- package/dist/agents/templates.js +102 -0
- package/dist/analysis/capabilities.d.ts +48 -0
- package/dist/analysis/capabilities.js +90 -0
- package/dist/analysis/data/csv.d.ts +46 -0
- package/dist/analysis/data/csv.js +240 -0
- package/dist/analysis/data/datasets.d.ts +132 -0
- package/dist/analysis/data/datasets.js +441 -0
- package/dist/analysis/data/describe.d.ts +27 -0
- package/dist/analysis/data/describe.js +103 -0
- package/dist/analysis/data/engine-client.d.ts +58 -0
- package/dist/analysis/data/engine-client.js +304 -0
- package/dist/analysis/data/engine-entry.d.ts +1 -0
- package/dist/analysis/data/engine-entry.js +59 -0
- package/dist/analysis/data/engine-source.d.ts +4 -0
- package/dist/analysis/data/engine-source.js +6 -0
- package/dist/analysis/data/engine-types.d.ts +86 -0
- package/dist/analysis/data/engine-types.js +7 -0
- package/dist/analysis/data/infer.d.ts +42 -0
- package/dist/analysis/data/infer.js +118 -0
- package/dist/analysis/data/ingest-worker.d.ts +17 -0
- package/dist/analysis/data/ingest-worker.js +365 -0
- package/dist/analysis/data/json.d.ts +19 -0
- package/dist/analysis/data/json.js +44 -0
- package/dist/analysis/data/query-worker.d.ts +12 -0
- package/dist/analysis/data/query-worker.js +75 -0
- package/dist/analysis/data/rows.d.ts +39 -0
- package/dist/analysis/data/rows.js +120 -0
- package/dist/analysis/data/sql-guard.d.ts +20 -0
- package/dist/analysis/data/sql-guard.js +129 -0
- package/dist/analysis/data/stats.d.ts +18 -0
- package/dist/analysis/data/stats.js +77 -0
- package/dist/analysis/install-hints.d.ts +33 -0
- package/dist/analysis/install-hints.js +144 -0
- package/dist/analysis/janitor.d.ts +60 -0
- package/dist/analysis/janitor.js +264 -0
- package/dist/analysis/jobs.d.ts +94 -0
- package/dist/analysis/jobs.js +165 -0
- package/dist/analysis/oci.d.ts +129 -0
- package/dist/analysis/oci.js +330 -0
- package/dist/analysis/python/sources.d.ts +2 -0
- package/dist/analysis/python/sources.js +10 -0
- package/dist/analysis/requirements.d.ts +32 -0
- package/dist/analysis/requirements.js +73 -0
- package/dist/analysis/rerun.d.ts +40 -0
- package/dist/analysis/rerun.js +136 -0
- package/dist/analysis/runtime-manager.d.ts +146 -0
- package/dist/analysis/runtime-manager.js +472 -0
- package/dist/analysis/status.d.ts +21 -0
- package/dist/analysis/status.js +52 -0
- package/dist/application.d.ts +118 -3
- package/dist/application.js +510 -19
- package/dist/artifacts/kinds.d.ts +35 -0
- package/dist/artifacts/kinds.js +177 -0
- package/dist/artifacts/publisher.d.ts +7 -0
- package/dist/artifacts/publisher.js +29 -0
- package/dist/artifacts/store.d.ts +119 -0
- package/dist/artifacts/store.js +581 -0
- package/dist/background/janitor.d.ts +22 -0
- package/dist/background/janitor.js +62 -0
- package/dist/background/mirror.d.ts +14 -0
- package/dist/background/mirror.js +41 -0
- package/dist/background/notify.d.ts +58 -0
- package/dist/background/notify.js +172 -0
- package/dist/background/output.d.ts +46 -0
- package/dist/background/output.js +179 -0
- package/dist/background/service.d.ts +137 -0
- package/dist/background/service.js +410 -0
- package/dist/background/store.d.ts +103 -0
- package/dist/background/store.js +195 -0
- package/dist/changelog/data.d.ts +6 -0
- package/dist/changelog/data.js +293 -0
- package/dist/changelog/index.d.ts +5 -0
- package/dist/changelog/index.js +7 -0
- package/dist/changelog/parse.d.ts +53 -0
- package/dist/changelog/parse.js +160 -0
- package/dist/commands/builtins.d.ts +10 -0
- package/dist/commands/builtins.js +161 -0
- package/dist/commands/catalog.d.ts +86 -0
- package/dist/commands/catalog.js +304 -0
- package/dist/config.d.ts +252 -5
- package/dist/config.js +514 -13
- package/dist/core/compaction.js +13 -2
- package/dist/core/contracts.d.ts +180 -3
- package/dist/core/opt-in.d.ts +12 -0
- package/dist/core/opt-in.js +12 -0
- package/dist/core/output-limit.d.ts +34 -0
- package/dist/core/output-limit.js +39 -0
- package/dist/core/run-stats.d.ts +16 -0
- package/dist/core/run-stats.js +8 -0
- package/dist/core/runner.d.ts +152 -4
- package/dist/core/runner.js +792 -138
- package/dist/core/timeout.d.ts +15 -0
- package/dist/core/timeout.js +61 -0
- package/dist/core/truncation.d.ts +30 -0
- package/dist/core/truncation.js +83 -0
- package/dist/goal/blockers.d.ts +45 -0
- package/dist/goal/blockers.js +51 -0
- package/dist/goal/budget.d.ts +24 -0
- package/dist/goal/budget.js +53 -0
- package/dist/goal/command.d.ts +36 -0
- package/dist/goal/command.js +98 -0
- package/dist/goal/format.d.ts +19 -0
- package/dist/goal/format.js +102 -0
- package/dist/goal/host.d.ts +29 -0
- package/dist/goal/host.js +42 -0
- package/dist/goal/index.d.ts +9 -0
- package/dist/goal/index.js +9 -0
- package/dist/goal/machine.d.ts +186 -0
- package/dist/goal/machine.js +344 -0
- package/dist/goal/prompts.d.ts +10 -0
- package/dist/goal/prompts.js +75 -0
- package/dist/goal/service.d.ts +113 -0
- package/dist/goal/service.js +273 -0
- package/dist/goal/store.d.ts +59 -0
- package/dist/goal/store.js +184 -0
- package/dist/index.d.ts +51 -6
- package/dist/index.js +49 -4
- package/dist/mcp/connector.js +38 -6
- package/dist/mcp/rich.d.ts +41 -0
- package/dist/mcp/rich.js +362 -0
- package/dist/permissions/modes.d.ts +72 -0
- package/dist/permissions/modes.js +98 -0
- package/dist/plan/exit-plan.d.ts +25 -0
- package/dist/plan/exit-plan.js +130 -0
- package/dist/plan/state.d.ts +102 -0
- package/dist/plan/state.js +165 -0
- package/dist/plugins/host.d.ts +39 -1
- package/dist/plugins/host.js +150 -9
- package/dist/providers/settings.d.ts +15 -0
- package/dist/providers/settings.js +38 -0
- package/dist/reload.d.ts +75 -0
- package/dist/reload.js +204 -0
- package/dist/resources/context.d.ts +5 -0
- package/dist/resources/context.js +13 -1
- package/dist/runtime/access.d.ts +62 -0
- package/dist/runtime/access.js +115 -0
- package/dist/runtime/blobs.d.ts +51 -0
- package/dist/runtime/blobs.js +177 -0
- package/dist/runtime/diff.d.ts +23 -0
- package/dist/runtime/diff.js +154 -0
- package/dist/runtime/ids.d.ts +8 -0
- package/dist/runtime/ids.js +32 -0
- package/dist/runtime/paths.d.ts +11 -0
- package/dist/runtime/paths.js +17 -1
- package/dist/runtime/process.d.ts +22 -0
- package/dist/runtime/process.js +100 -15
- package/dist/runtime/sqlite.d.ts +2 -0
- package/dist/runtime/sqlite.js +18 -0
- package/dist/runtime/store.d.ts +91 -3
- package/dist/runtime/store.js +465 -11
- package/dist/runtime/zip.d.ts +24 -0
- package/dist/runtime/zip.js +126 -0
- package/dist/sessions/children.js +17 -1
- package/dist/sessions/side-questions.d.ts +57 -0
- package/dist/sessions/side-questions.js +161 -0
- package/dist/startup/screen.js +6 -5
- package/dist/tools/analysis.d.ts +37 -0
- package/dist/tools/analysis.js +573 -0
- package/dist/tools/artifacts.d.ts +14 -0
- package/dist/tools/artifacts.js +143 -0
- package/dist/tools/background.d.ts +7 -0
- package/dist/tools/background.js +165 -0
- package/dist/tools/data.d.ts +7 -0
- package/dist/tools/data.js +160 -0
- package/dist/tools/execute.d.ts +5 -0
- package/dist/tools/execute.js +15 -3
- package/dist/tools/goal.d.ts +19 -0
- package/dist/tools/goal.js +126 -0
- package/dist/tools/search.d.ts +17 -3
- package/dist/tools/search.js +153 -0
- package/dist/tools/standard.d.ts +4 -1
- package/dist/tools/standard.js +99 -18
- package/dist/version.d.ts +5 -3
- package/dist/version.js +35 -6
- package/package.json +2 -2
package/dist/mcp/rich.js
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP → Alisio result mapping ("Nivel 0" rich tool results).
|
|
3
|
+
*
|
|
4
|
+
* MCP protocol types never leak into the SDK: this adapter is the single place that folds an
|
|
5
|
+
* MCP `CallToolResult` / `ReadResourceResult` into the Alisio-owned `ToolResult` content union
|
|
6
|
+
* (text, image, ui blocks). Mapping is heuristic and conservative:
|
|
7
|
+
*
|
|
8
|
+
* - `text` parts stay text parts; an image part becomes `{type:"image"}` (base64 data + mime).
|
|
9
|
+
* - `structuredContent` (or a JSON-parseable text part) is folded into a `ui` block when it
|
|
10
|
+
* matches a verified shape — `{columns,rows}` / `{headers,rows}` tables, all-scalar
|
|
11
|
+
* key-value objects, or `{nodes:[{label,children,meta}]}` trees. Anything else stays text
|
|
12
|
+
* (the previous flattening).
|
|
13
|
+
* - A canonical text projection of every ui block / image is ALWAYS appended as a text part so
|
|
14
|
+
* providers, compaction and headless output only see text.
|
|
15
|
+
* - Unknown call results (no contents, nothing recognized) keep the previous
|
|
16
|
+
* `JSON.stringify(result)` behavior verbatim.
|
|
17
|
+
*/
|
|
18
|
+
import { textResult } from "@alisio/sdk";
|
|
19
|
+
import { stripAnsi } from "../startup/text.js";
|
|
20
|
+
const scalar = (value) => typeof value === "string"
|
|
21
|
+
? value
|
|
22
|
+
: typeof value === "number" || typeof value === "boolean" || value === null
|
|
23
|
+
? String(value)
|
|
24
|
+
: undefined;
|
|
25
|
+
/** Cell rendering: scalars verbatim, nested values compact-JSON'd (never raw bytes). */
|
|
26
|
+
const cellText = (value) => typeof value === "string"
|
|
27
|
+
? value
|
|
28
|
+
: value === null || value === undefined
|
|
29
|
+
? ""
|
|
30
|
+
: typeof value === "object"
|
|
31
|
+
? JSON.stringify(value)
|
|
32
|
+
: String(value);
|
|
33
|
+
function tableShape(value) {
|
|
34
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
35
|
+
return undefined;
|
|
36
|
+
const record = value;
|
|
37
|
+
const columnsRaw = Array.isArray(record.columns)
|
|
38
|
+
? record.columns
|
|
39
|
+
: Array.isArray(record.headers)
|
|
40
|
+
? record.headers
|
|
41
|
+
: undefined;
|
|
42
|
+
const rowsRaw = Array.isArray(record.rows) ? record.rows : undefined;
|
|
43
|
+
if (!columnsRaw || !rowsRaw)
|
|
44
|
+
return undefined;
|
|
45
|
+
const columns = [];
|
|
46
|
+
for (const column of columnsRaw) {
|
|
47
|
+
const text = scalar(column);
|
|
48
|
+
if (text === undefined)
|
|
49
|
+
return undefined;
|
|
50
|
+
columns.push(text);
|
|
51
|
+
}
|
|
52
|
+
const rows = [];
|
|
53
|
+
for (const row of rowsRaw) {
|
|
54
|
+
if (!row || typeof row !== "object")
|
|
55
|
+
return undefined;
|
|
56
|
+
if (Array.isArray(row)) {
|
|
57
|
+
rows.push(row.map(cellText));
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
// Object rows are keyed by the declared columns (`{columns, rows:[{a:1,b:2}]}`).
|
|
61
|
+
const named = row;
|
|
62
|
+
rows.push(columns.map((column) => cellText(named[column])));
|
|
63
|
+
}
|
|
64
|
+
if (!columns.length || !rows.length)
|
|
65
|
+
return undefined;
|
|
66
|
+
return { kind: "table", columns, rows };
|
|
67
|
+
}
|
|
68
|
+
function treeNode(value) {
|
|
69
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
70
|
+
return undefined;
|
|
71
|
+
const record = value;
|
|
72
|
+
const label = scalar(record.label) ?? scalar(record.name);
|
|
73
|
+
if (label === undefined)
|
|
74
|
+
return undefined;
|
|
75
|
+
const meta = scalar(record.meta) ?? scalar(record.value) ?? scalar(record.detail);
|
|
76
|
+
const node = { label };
|
|
77
|
+
if (meta !== undefined && meta !== label)
|
|
78
|
+
node.meta = meta;
|
|
79
|
+
const children = Array.isArray(record.children) ? record.children : undefined;
|
|
80
|
+
if (children) {
|
|
81
|
+
const mapped = [];
|
|
82
|
+
for (const child of children) {
|
|
83
|
+
const next = treeNode(child);
|
|
84
|
+
if (!next)
|
|
85
|
+
return undefined;
|
|
86
|
+
mapped.push(next);
|
|
87
|
+
}
|
|
88
|
+
node.children = mapped;
|
|
89
|
+
}
|
|
90
|
+
return node;
|
|
91
|
+
}
|
|
92
|
+
function treeShape(value) {
|
|
93
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
94
|
+
return undefined;
|
|
95
|
+
const record = value;
|
|
96
|
+
const nodes = Array.isArray(record.nodes)
|
|
97
|
+
? record.nodes
|
|
98
|
+
: record.tree !== undefined && typeof record.tree === "object" && !Array.isArray(record.tree)
|
|
99
|
+
? [record.tree]
|
|
100
|
+
: undefined;
|
|
101
|
+
if (!nodes)
|
|
102
|
+
return undefined;
|
|
103
|
+
const mapped = [];
|
|
104
|
+
for (const node of nodes) {
|
|
105
|
+
const next = treeNode(node);
|
|
106
|
+
if (!next)
|
|
107
|
+
return undefined;
|
|
108
|
+
mapped.push(next);
|
|
109
|
+
}
|
|
110
|
+
return mapped.length ? { kind: "tree", nodes: mapped } : undefined;
|
|
111
|
+
}
|
|
112
|
+
function keyValueShape(value) {
|
|
113
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
114
|
+
return undefined;
|
|
115
|
+
const record = value;
|
|
116
|
+
const entries = Object.entries(record);
|
|
117
|
+
if (!entries.length || entries.some(([, v]) => typeof v === "object" && v !== null))
|
|
118
|
+
return undefined;
|
|
119
|
+
return { kind: "key-value", entries: entries.map(([key, v]) => [key, String(v)]) };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Folds a candidate value into a UiBlock when it matches one of the verified shapes:
|
|
123
|
+
* `{columns,rows}` / `{headers,rows}` tables, `{nodes:[...]}` trees, or flat scalar objects.
|
|
124
|
+
*/
|
|
125
|
+
export function blockFromUnknown(value) {
|
|
126
|
+
return tableShape(value) ?? treeShape(value) ?? keyValueShape(value);
|
|
127
|
+
}
|
|
128
|
+
/** Tries a JSON-parseable text part: recognized shapes become ui blocks, everything else stays text. */
|
|
129
|
+
export function blockFromJsonText(text) {
|
|
130
|
+
const trimmed = text.trim();
|
|
131
|
+
if (!trimmed.startsWith("{"))
|
|
132
|
+
return undefined;
|
|
133
|
+
try {
|
|
134
|
+
return blockFromUnknown(JSON.parse(trimmed));
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** Compact plain-text rendering of a ui block, used as the canonical model-visible projection. */
|
|
141
|
+
export function renderUiBlockText(block) {
|
|
142
|
+
switch (block.kind) {
|
|
143
|
+
case "table": {
|
|
144
|
+
const head = `| ${block.columns.join(" | ")} |`;
|
|
145
|
+
const sep = `| ${block.columns.map(() => "---").join(" | ")} |`;
|
|
146
|
+
const rows = block.rows.map((row) => `| ${row.join(" | ")} |`);
|
|
147
|
+
return [block.caption ? `[table: ${block.caption}]` : "", head, sep, ...rows]
|
|
148
|
+
.filter((line) => line !== "")
|
|
149
|
+
.join("\n");
|
|
150
|
+
}
|
|
151
|
+
case "key-value":
|
|
152
|
+
return [
|
|
153
|
+
block.caption ? `[key-value: ${block.caption}]` : "",
|
|
154
|
+
...block.entries.map(([key, value]) => `${key}: ${value}`),
|
|
155
|
+
]
|
|
156
|
+
.filter((line) => line !== "")
|
|
157
|
+
.join("\n");
|
|
158
|
+
case "tree": {
|
|
159
|
+
const lines = [];
|
|
160
|
+
const walk = (nodes, prefix) => {
|
|
161
|
+
nodes.forEach((node, index) => {
|
|
162
|
+
const last = index === nodes.length - 1;
|
|
163
|
+
lines.push(`${prefix}${last ? "└─ " : "├─ "}${node.label}${node.meta ? ` (${node.meta})` : ""}`);
|
|
164
|
+
if (node.children?.length)
|
|
165
|
+
walk(node.children, `${prefix}${last ? " " : "│ "}`);
|
|
166
|
+
});
|
|
167
|
+
};
|
|
168
|
+
walk(block.nodes, "");
|
|
169
|
+
return lines.join("\n");
|
|
170
|
+
}
|
|
171
|
+
case "code": {
|
|
172
|
+
const fence = "```";
|
|
173
|
+
const lang = block.lang ? `${block.lang}\n` : "\n";
|
|
174
|
+
const body = block.code.endsWith("\n") ? block.code : `${block.code}\n`;
|
|
175
|
+
return [block.caption ? `[code: ${block.caption}]` : "", `${fence}${lang}${body}${fence}`]
|
|
176
|
+
.filter((line) => line !== "")
|
|
177
|
+
.join("\n");
|
|
178
|
+
}
|
|
179
|
+
case "markdown":
|
|
180
|
+
return block.text;
|
|
181
|
+
case "diff": {
|
|
182
|
+
const label = [block.caption, block.path].filter(Boolean).join(" · ");
|
|
183
|
+
const head = label ? `[diff: ${label}]` : "[diff]";
|
|
184
|
+
if (block.patch !== undefined)
|
|
185
|
+
return `${head}\n${fenced(block.patch, "diff")}`;
|
|
186
|
+
return [
|
|
187
|
+
head,
|
|
188
|
+
`before:\n${fenced(block.before ?? "", block.lang)}`,
|
|
189
|
+
`after:\n${fenced(block.after ?? "", block.lang)}`,
|
|
190
|
+
].join("\n");
|
|
191
|
+
}
|
|
192
|
+
case "terminal": {
|
|
193
|
+
const status = [
|
|
194
|
+
block.exitCode !== undefined ? `exit ${block.exitCode}` : "",
|
|
195
|
+
block.durationMs !== undefined ? `${block.durationMs}ms` : "",
|
|
196
|
+
block.truncated ? "output truncated" : "",
|
|
197
|
+
]
|
|
198
|
+
.filter(Boolean)
|
|
199
|
+
.join(" · ");
|
|
200
|
+
return [
|
|
201
|
+
block.command ? `$ ${block.command}` : "[terminal]",
|
|
202
|
+
fenced(stripAnsi(block.output)),
|
|
203
|
+
status,
|
|
204
|
+
]
|
|
205
|
+
.filter((line) => line !== "")
|
|
206
|
+
.join("\n");
|
|
207
|
+
}
|
|
208
|
+
case "mermaid":
|
|
209
|
+
return [block.title ? `[mermaid: ${block.title}]` : "", fenced(block.source, "mermaid")]
|
|
210
|
+
.filter((line) => line !== "")
|
|
211
|
+
.join("\n");
|
|
212
|
+
case "math":
|
|
213
|
+
return block.display ? `$$\n${block.latex}\n$$` : `$${block.latex}$`;
|
|
214
|
+
case "json":
|
|
215
|
+
return [
|
|
216
|
+
block.caption ? `[json: ${block.caption}]` : "",
|
|
217
|
+
fenced(jsonText(block.value), "json"),
|
|
218
|
+
]
|
|
219
|
+
.filter((line) => line !== "")
|
|
220
|
+
.join("\n");
|
|
221
|
+
case "test-results": {
|
|
222
|
+
const cases = block.suites.flatMap((suite) => suite.cases.map((c) => `${c.status.toUpperCase()} ${suite.name} › ${c.name}${c.error ? `: ${c.error}` : ""}`));
|
|
223
|
+
return [`[test-results${block.framework ? `: ${block.framework}` : ""}]`, ...cases].join("\n");
|
|
224
|
+
}
|
|
225
|
+
case "progress":
|
|
226
|
+
return [
|
|
227
|
+
block.title ? `[progress: ${block.title}]` : "[progress]",
|
|
228
|
+
...block.steps.map((step) => `- [${step.status}] ${step.label}${step.detail ? ` (${step.detail})` : ""}`),
|
|
229
|
+
].join("\n");
|
|
230
|
+
case "artifact": {
|
|
231
|
+
const a = block.artifact;
|
|
232
|
+
return `[artifact: ${a.fileName} · ${a.kind} · ${a.bytes} bytes · ${a.id}${a.partial ? " · partial" : ""}${a.status !== "ready" ? ` · ${a.status}` : ""}]`;
|
|
233
|
+
}
|
|
234
|
+
default: {
|
|
235
|
+
// A kind this build does not know (e.g. persisted by a newer Alisio): keep it readable.
|
|
236
|
+
const kind = String(block.kind ?? "unknown");
|
|
237
|
+
return `[ui block: ${kind}]\n${fenced(jsonText(block), "json")}`;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function fenced(text, lang) {
|
|
242
|
+
const body = text.endsWith("\n") ? text : `${text}\n`;
|
|
243
|
+
return `\`\`\`${lang ?? ""}\n${body}\`\`\``;
|
|
244
|
+
}
|
|
245
|
+
/** `JSON.stringify` that never throws and never returns `undefined`. */
|
|
246
|
+
function jsonText(value) {
|
|
247
|
+
try {
|
|
248
|
+
return JSON.stringify(value, null, 2) ?? String(value);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
return String(value);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const base64Bytes = (data) => Math.floor((data.length * 3) / 4);
|
|
255
|
+
const projectionText = (parts) => parts
|
|
256
|
+
.map((part) => part.type === "ui"
|
|
257
|
+
? renderUiBlockText(part.block)
|
|
258
|
+
: `[image: ${part.mimeType} (${base64Bytes(part.data)} bytes)]`)
|
|
259
|
+
.join("\n");
|
|
260
|
+
/**
|
|
261
|
+
* Maps an MCP `CallToolResult` into an Alisio `ToolResult`. Text/image parts map directly;
|
|
262
|
+
* verified table/key-value/tree shapes (from `structuredContent` or JSON text parts) become
|
|
263
|
+
* `ui` blocks; a canonical text projection is always appended when non-text parts exist, and
|
|
264
|
+
* unrecognized results keep the previous `JSON.stringify` flattening verbatim.
|
|
265
|
+
*/
|
|
266
|
+
export function mapMcpCallResult(result) {
|
|
267
|
+
const raw = (result ?? {});
|
|
268
|
+
const parts = Array.isArray(raw.content) ? raw.content : [];
|
|
269
|
+
if (!parts.length && raw.structuredContent === undefined)
|
|
270
|
+
return textResult(JSON.stringify(result), raw.isError === true);
|
|
271
|
+
const content = [];
|
|
272
|
+
const rich = [];
|
|
273
|
+
const keepSourceOrder = (part) => {
|
|
274
|
+
rich.push(part);
|
|
275
|
+
content.push(part);
|
|
276
|
+
};
|
|
277
|
+
for (const part of parts) {
|
|
278
|
+
if (!part || typeof part !== "object")
|
|
279
|
+
continue;
|
|
280
|
+
const record = part;
|
|
281
|
+
const type = record.type;
|
|
282
|
+
if (type === "text") {
|
|
283
|
+
const text = typeof record.text === "string" ? record.text : JSON.stringify(record.text ?? "");
|
|
284
|
+
const block = blockFromJsonText(text);
|
|
285
|
+
if (block)
|
|
286
|
+
keepSourceOrder({ type: "ui", block });
|
|
287
|
+
else
|
|
288
|
+
content.push({ type: "text", text });
|
|
289
|
+
}
|
|
290
|
+
else if (type === "image") {
|
|
291
|
+
const data = typeof record.data === "string" ? record.data : "";
|
|
292
|
+
if (data)
|
|
293
|
+
keepSourceOrder({
|
|
294
|
+
type: "image",
|
|
295
|
+
mimeType: typeof record.mimeType === "string" ? record.mimeType : "application/octet-stream",
|
|
296
|
+
data,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
// Audio / embedded resource / unknown part: never forward raw bytes; a marker keeps the model informed.
|
|
301
|
+
content.push({ type: "text", text: `[mcp part: ${type}]` });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const structured = blockFromUnknown(raw.structuredContent);
|
|
305
|
+
if (structured)
|
|
306
|
+
keepSourceOrder({ type: "ui", block: structured });
|
|
307
|
+
if (rich.length)
|
|
308
|
+
content.push({ type: "text", text: projectionText(rich) });
|
|
309
|
+
if (!content.length)
|
|
310
|
+
return textResult(JSON.stringify(result), raw.isError === true);
|
|
311
|
+
return { content, ...(raw.isError === true ? { isError: true } : {}) };
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Maps an MCP `ReadResourceResult` / `ListResourcesResult` into an Alisio `ToolResult`.
|
|
315
|
+
* Text contents map to text parts; image blobs become `{type:"image"}` parts; non-image blobs
|
|
316
|
+
* become a text marker (never raw base64 in the model prompt). Results without contents (a
|
|
317
|
+
* resource listing, for example) keep the previous `JSON.stringify` flattening.
|
|
318
|
+
*/
|
|
319
|
+
export function mapMcpResourceResult(result) {
|
|
320
|
+
const raw = (result ?? {});
|
|
321
|
+
const contents = Array.isArray(raw.contents) ? raw.contents : [];
|
|
322
|
+
if (!contents.length)
|
|
323
|
+
return textResult(JSON.stringify(result), typeof result === "object" &&
|
|
324
|
+
result !== null &&
|
|
325
|
+
result.isError === true);
|
|
326
|
+
const content = [];
|
|
327
|
+
const rich = [];
|
|
328
|
+
for (const item of contents) {
|
|
329
|
+
if (!item || typeof item !== "object")
|
|
330
|
+
continue;
|
|
331
|
+
const record = item;
|
|
332
|
+
const mimeType = typeof record.mimeType === "string" ? record.mimeType : "application/octet-stream";
|
|
333
|
+
if (typeof record.text === "string") {
|
|
334
|
+
const block = blockFromJsonText(record.text);
|
|
335
|
+
if (block) {
|
|
336
|
+
rich.push({ type: "ui", block });
|
|
337
|
+
content.push({ type: "ui", block });
|
|
338
|
+
}
|
|
339
|
+
else
|
|
340
|
+
content.push({ type: "text", text: record.text });
|
|
341
|
+
}
|
|
342
|
+
else if (typeof record.blob === "string") {
|
|
343
|
+
if (mimeType.startsWith("image/")) {
|
|
344
|
+
const image = { type: "image", mimeType, data: record.blob };
|
|
345
|
+
rich.push(image);
|
|
346
|
+
content.push(image);
|
|
347
|
+
}
|
|
348
|
+
else
|
|
349
|
+
content.push({
|
|
350
|
+
type: "text",
|
|
351
|
+
text: `[resource blob: ${mimeType} (${base64Bytes(record.blob)} bytes)]`,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (rich.length)
|
|
356
|
+
content.push({ type: "text", text: projectionText(rich) });
|
|
357
|
+
if (!content.length)
|
|
358
|
+
return textResult(JSON.stringify(result), typeof result === "object" &&
|
|
359
|
+
result !== null &&
|
|
360
|
+
result.isError === true);
|
|
361
|
+
return { content };
|
|
362
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission modes (`ask`, `auto`, `full`): the ONE table that maps a mode to the runner policy,
|
|
3
|
+
* shared by the TUI (`/permission`) and the web presets (`host/presets.ts`). Pure: no I/O.
|
|
4
|
+
*
|
|
5
|
+
* A mode only decides which EFFECTS run without asking. It is not a sandbox and it does not change
|
|
6
|
+
* path confinement (`PathAccess` still asks per external directory) or the `analysis` pre-grant.
|
|
7
|
+
*/
|
|
8
|
+
import type { PermissionMode, PermissionPresetId } from "@alisio/sdk";
|
|
9
|
+
import type { Policy } from "../core/contracts.ts";
|
|
10
|
+
export type { PermissionMode };
|
|
11
|
+
export declare const PERMISSION_MODES: readonly PermissionMode[];
|
|
12
|
+
/** The effects a mode lets run without asking. `analysis` is never part of a mode. */
|
|
13
|
+
export type ModePolicy = Pick<Policy, "write" | "process" | "external">;
|
|
14
|
+
export interface PermissionModeSpec {
|
|
15
|
+
/** Effects allowed without asking; the rest ask (or are denied when `approvals` is false). */
|
|
16
|
+
policy: ModePolicy;
|
|
17
|
+
/** Whether the effects that are not allowed ask for approval. */
|
|
18
|
+
approvals: boolean;
|
|
19
|
+
/** The web preset that implements this mode. */
|
|
20
|
+
preset: Exclude<PermissionPresetId, "read-only">;
|
|
21
|
+
label: string;
|
|
22
|
+
summary: string;
|
|
23
|
+
}
|
|
24
|
+
/** The single mapping table (decision P3). Auto = fixed rules, no model classifier. */
|
|
25
|
+
export declare const PERMISSION_MODE_TABLE: Readonly<Record<PermissionMode, PermissionModeSpec>>;
|
|
26
|
+
/** The read-only preset: nothing runs and nothing can be approved. Not selectable as a mode. */
|
|
27
|
+
export declare const READ_ONLY_POLICY: ModePolicy;
|
|
28
|
+
export declare const FULL_ACCESS_WARNING = "Full access: writes, commands and network calls run without asking. This is not a sandbox: anything the agent runs has your user's permissions.";
|
|
29
|
+
export declare const PERMISSION_MODES_LOCKED = "Permission modes are locked: Alisio was started with --read-only.";
|
|
30
|
+
export declare const PERMISSION_USAGE = "Usage: /permission [ask|auto|full|status]";
|
|
31
|
+
/** The mode a web preset implements; `read-only` has none. */
|
|
32
|
+
export declare function presetToMode(preset: PermissionPresetId): PermissionMode | undefined;
|
|
33
|
+
/** The web preset of a mode. */
|
|
34
|
+
export declare function modeToPreset(mode: PermissionMode): PermissionPresetId;
|
|
35
|
+
/** `ask`, `auto` or `full` (also the preset spellings `full-access` and `workspace-write`). */
|
|
36
|
+
export declare function parsePermissionMode(text: string): PermissionMode | undefined;
|
|
37
|
+
export type PermissionCommand = {
|
|
38
|
+
type: "menu";
|
|
39
|
+
} | {
|
|
40
|
+
type: "status";
|
|
41
|
+
} | {
|
|
42
|
+
type: "set";
|
|
43
|
+
mode: PermissionMode;
|
|
44
|
+
} | {
|
|
45
|
+
type: "invalid";
|
|
46
|
+
input: string;
|
|
47
|
+
};
|
|
48
|
+
/** Parses the arguments of `/permission` (`ask|auto|full|status`; none opens the menu). */
|
|
49
|
+
export declare function parsePermissionCommand(args: string): PermissionCommand;
|
|
50
|
+
/** What a TUI shows for its mode: a real mode, a flag combination that matches none, or locked. */
|
|
51
|
+
export type PermissionModeLabel = PermissionMode | "custom" | "locked";
|
|
52
|
+
export interface ModeFlags {
|
|
53
|
+
allowWrite?: boolean;
|
|
54
|
+
allowProcess?: boolean;
|
|
55
|
+
allowExternal?: boolean;
|
|
56
|
+
readOnly?: boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The initial mode label of a TUI process from its launch flags: `--read-only` is locked, no flags
|
|
60
|
+
* is `ask`, `--allow-write` alone is `auto`, the three allow flags are `full`, anything else is
|
|
61
|
+
* `custom`. It only LABELS the state: no policy is applied at startup.
|
|
62
|
+
*/
|
|
63
|
+
export declare function modeFromFlags(flags: ModeFlags): PermissionModeLabel;
|
|
64
|
+
export type EffectState = "on" | "ask" | "off";
|
|
65
|
+
/** How one effect behaves under a policy: runs, asks, or is denied. */
|
|
66
|
+
export declare function effectState(allowed: boolean, approvals: boolean): EffectState;
|
|
67
|
+
/** Markdown status of the permission state of a session (shared wording for TUI and API callers). */
|
|
68
|
+
export declare function describePermissionStatus(input: {
|
|
69
|
+
mode: PermissionModeLabel;
|
|
70
|
+
policy: Readonly<Policy>;
|
|
71
|
+
approvals: boolean;
|
|
72
|
+
}): string;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export const PERMISSION_MODES = ["ask", "auto", "full"];
|
|
2
|
+
/** The single mapping table (decision P3). Auto = fixed rules, no model classifier. */
|
|
3
|
+
export const PERMISSION_MODE_TABLE = {
|
|
4
|
+
ask: {
|
|
5
|
+
policy: { write: false, process: false, external: false },
|
|
6
|
+
approvals: true,
|
|
7
|
+
preset: "ask",
|
|
8
|
+
label: "Ask",
|
|
9
|
+
summary: "Every write, command and network call asks first.",
|
|
10
|
+
},
|
|
11
|
+
auto: {
|
|
12
|
+
policy: { write: true, process: false, external: false },
|
|
13
|
+
approvals: true,
|
|
14
|
+
preset: "workspace-write",
|
|
15
|
+
label: "Auto",
|
|
16
|
+
summary: "File edits inside the workspace run without asking; commands, network calls and external directories still ask.",
|
|
17
|
+
},
|
|
18
|
+
full: {
|
|
19
|
+
policy: { write: true, process: true, external: true },
|
|
20
|
+
approvals: true,
|
|
21
|
+
preset: "full-access",
|
|
22
|
+
label: "Full access",
|
|
23
|
+
summary: "Writes, commands and network calls run without asking.",
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
/** The read-only preset: nothing runs and nothing can be approved. Not selectable as a mode. */
|
|
27
|
+
export const READ_ONLY_POLICY = { write: false, process: false, external: false };
|
|
28
|
+
export const FULL_ACCESS_WARNING = "Full access: writes, commands and network calls run without asking. This is not a sandbox: anything the agent runs has your user's permissions.";
|
|
29
|
+
export const PERMISSION_MODES_LOCKED = "Permission modes are locked: Alisio was started with --read-only.";
|
|
30
|
+
export const PERMISSION_USAGE = "Usage: /permission [ask|auto|full|status]";
|
|
31
|
+
/** The mode a web preset implements; `read-only` has none. */
|
|
32
|
+
export function presetToMode(preset) {
|
|
33
|
+
return PERMISSION_MODES.find((mode) => PERMISSION_MODE_TABLE[mode].preset === preset);
|
|
34
|
+
}
|
|
35
|
+
/** The web preset of a mode. */
|
|
36
|
+
export function modeToPreset(mode) {
|
|
37
|
+
return PERMISSION_MODE_TABLE[mode].preset;
|
|
38
|
+
}
|
|
39
|
+
/** `ask`, `auto` or `full` (also the preset spellings `full-access` and `workspace-write`). */
|
|
40
|
+
export function parsePermissionMode(text) {
|
|
41
|
+
const value = text.trim().toLowerCase();
|
|
42
|
+
if (value === "full-access")
|
|
43
|
+
return "full";
|
|
44
|
+
if (value === "workspace-write")
|
|
45
|
+
return "auto";
|
|
46
|
+
return PERMISSION_MODES.find((mode) => mode === value);
|
|
47
|
+
}
|
|
48
|
+
/** Parses the arguments of `/permission` (`ask|auto|full|status`; none opens the menu). */
|
|
49
|
+
export function parsePermissionCommand(args) {
|
|
50
|
+
const text = args.trim().toLowerCase();
|
|
51
|
+
if (!text)
|
|
52
|
+
return { type: "menu" };
|
|
53
|
+
if (text === "status")
|
|
54
|
+
return { type: "status" };
|
|
55
|
+
const mode = parsePermissionMode(text);
|
|
56
|
+
return mode ? { type: "set", mode } : { type: "invalid", input: args.trim() };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The initial mode label of a TUI process from its launch flags: `--read-only` is locked, no flags
|
|
60
|
+
* is `ask`, `--allow-write` alone is `auto`, the three allow flags are `full`, anything else is
|
|
61
|
+
* `custom`. It only LABELS the state: no policy is applied at startup.
|
|
62
|
+
*/
|
|
63
|
+
export function modeFromFlags(flags) {
|
|
64
|
+
if (flags.readOnly)
|
|
65
|
+
return "locked";
|
|
66
|
+
const { allowWrite: write, allowProcess: process, allowExternal: external } = flags;
|
|
67
|
+
if (!write && !process && !external)
|
|
68
|
+
return "ask";
|
|
69
|
+
if (write && !process && !external)
|
|
70
|
+
return "auto";
|
|
71
|
+
if (write && process && external)
|
|
72
|
+
return "full";
|
|
73
|
+
return "custom";
|
|
74
|
+
}
|
|
75
|
+
/** How one effect behaves under a policy: runs, asks, or is denied. */
|
|
76
|
+
export function effectState(allowed, approvals) {
|
|
77
|
+
return allowed ? "on" : approvals ? "ask" : "off";
|
|
78
|
+
}
|
|
79
|
+
/** Markdown status of the permission state of a session (shared wording for TUI and API callers). */
|
|
80
|
+
export function describePermissionStatus(input) {
|
|
81
|
+
const { mode, policy, approvals } = input;
|
|
82
|
+
const row = (label, allowed) => `- ${label}: ${effectState(allowed, approvals)}`;
|
|
83
|
+
const head = mode === "locked"
|
|
84
|
+
? "Permission mode: **locked** (read-only)"
|
|
85
|
+
: mode === "custom"
|
|
86
|
+
? "Permission mode: **custom** (launch flags that match no mode)"
|
|
87
|
+
: `Permission mode: **${mode}** — ${PERMISSION_MODE_TABLE[mode].summary}`;
|
|
88
|
+
return [
|
|
89
|
+
head,
|
|
90
|
+
"",
|
|
91
|
+
row("write", policy.write),
|
|
92
|
+
row("process", policy.process),
|
|
93
|
+
row("external (network, MCP, plugin tools)", policy.external),
|
|
94
|
+
...(policy.analysis ? ["- Python analysis: pre-allowed (--allow-analysis)"] : []),
|
|
95
|
+
"",
|
|
96
|
+
"`on` runs without asking, `ask` prompts first, `off` is denied. Paths outside the workspace ask per directory. Modes are not a sandbox.",
|
|
97
|
+
].join("\n");
|
|
98
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `exit_plan`: how the plan agent hands over its plan. The tool publishes the plan as a Markdown
|
|
3
|
+
* artifact (`plan.md`, one revision per call), asks the user for a decision through the same
|
|
4
|
+
* question infrastructure as `ask_user_question` and ALWAYS returns a tool result (approved,
|
|
5
|
+
* skipped or feedback), so a session never keeps a dangling call. It does not switch agents or
|
|
6
|
+
* start anything: an approved plan is recorded (`plan/state.ts`) and the host starts the
|
|
7
|
+
* implementation turn once the plan run has ended.
|
|
8
|
+
*
|
|
9
|
+
* The tool is opt-in (`core/opt-in.ts`): only the plan agent's run lists it. Its effect is
|
|
10
|
+
* `read`, so it is allowed under every policy; the plan agent stays read-only because its run
|
|
11
|
+
* policy has no write/process/external effect, whatever the permission mode.
|
|
12
|
+
*/
|
|
13
|
+
import type { AskQuestionsRequest, AskQuestionsResult } from "@alisio/sdk";
|
|
14
|
+
import type { ToolRegistry } from "../core/registry.ts";
|
|
15
|
+
import { type PlanStore } from "./state.ts";
|
|
16
|
+
export interface ExitPlanDeps {
|
|
17
|
+
store: PlanStore;
|
|
18
|
+
ui: {
|
|
19
|
+
interactive(): boolean;
|
|
20
|
+
askQuestions(request: AskQuestionsRequest): Promise<AskQuestionsResult>;
|
|
21
|
+
};
|
|
22
|
+
/** `--read-only`: nothing can be implemented afterwards, so there is no decision to take. */
|
|
23
|
+
readOnly?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function registerExitPlan(registry: ToolRegistry, deps: ExitPlanDeps): void;
|