@alisio/core 0.1.0-alpha.17 → 0.1.0-alpha.18
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/agents/active.d.ts +66 -0
- package/dist/agents/active.js +113 -0
- package/dist/application.d.ts +9 -3
- package/dist/application.js +22 -5
- package/dist/commands/builtins.d.ts +10 -0
- package/dist/commands/builtins.js +115 -0
- package/dist/commands/catalog.d.ts +83 -0
- package/dist/commands/catalog.js +268 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +27 -0
- package/dist/core/contracts.d.ts +109 -2
- package/dist/core/runner.d.ts +4 -0
- package/dist/core/runner.js +49 -12
- package/dist/index.d.ts +8 -4
- package/dist/index.js +7 -3
- package/dist/mcp/rich.js +68 -0
- package/dist/plugins/host.d.ts +4 -0
- package/dist/plugins/host.js +22 -8
- package/dist/providers/settings.d.ts +15 -0
- package/dist/providers/settings.js +38 -0
- package/dist/runtime/blobs.d.ts +29 -0
- package/dist/runtime/blobs.js +94 -0
- package/dist/runtime/diff.d.ts +23 -0
- package/dist/runtime/diff.js +154 -0
- package/dist/runtime/store.d.ts +69 -3
- package/dist/runtime/store.js +292 -11
- package/dist/tools/standard.js +59 -9
- package/package.json +2 -2
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { agentCatalogFromState, resolveActiveAgent } from "../agents/active.js";
|
|
2
|
+
import { BUILTIN_COMMANDS } from "./builtins.js";
|
|
3
|
+
export { BUILTIN_COMMANDS };
|
|
4
|
+
const builtinDescriptor = (c) => ({
|
|
5
|
+
name: c.name,
|
|
6
|
+
description: c.description,
|
|
7
|
+
...(c.aliases ? { aliases: [...c.aliases] } : {}),
|
|
8
|
+
...(c.argumentHint ? { argumentHint: c.argumentHint } : {}),
|
|
9
|
+
source: "builtin",
|
|
10
|
+
surfaces: [...c.surfaces],
|
|
11
|
+
execution: c.execution,
|
|
12
|
+
});
|
|
13
|
+
const ALL = ["tui", "web", "api"];
|
|
14
|
+
/** Root sessions of this workspace bound to the current provider, newest first (as the TUI). */
|
|
15
|
+
const workspaceSessions = (host) => host.store
|
|
16
|
+
.list()
|
|
17
|
+
.filter((s) => s.workspace === host.workspace && s.provider === host.provider.id);
|
|
18
|
+
const firstPrompt = (host, id) => {
|
|
19
|
+
const first = host.store.messages(id).find((m) => m.role === "user" && !m.summary);
|
|
20
|
+
return first?.role === "user" ? first.text.replace(/\s+/g, " ").slice(0, 60) : "";
|
|
21
|
+
};
|
|
22
|
+
const HANDLERS = {
|
|
23
|
+
async tools(_args, _ctx, host) {
|
|
24
|
+
const policy = host.runner.policy;
|
|
25
|
+
const rows = host.registry
|
|
26
|
+
.list()
|
|
27
|
+
.map((t) => {
|
|
28
|
+
const effect = t.effect ?? "external";
|
|
29
|
+
const state = effect === "read" || effect === "internal" || policy[effect]
|
|
30
|
+
? "enabled"
|
|
31
|
+
: host.runner.approvals && (effect === "write" || effect === "process")
|
|
32
|
+
? "ask"
|
|
33
|
+
: "disabled";
|
|
34
|
+
return `| \`${t.name}\` | ${effect} | ${state} |`;
|
|
35
|
+
})
|
|
36
|
+
.join("\n");
|
|
37
|
+
return {
|
|
38
|
+
text: `**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.`,
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
async sessions(_args, ctx, host) {
|
|
42
|
+
const list = workspaceSessions(host).slice(0, 20);
|
|
43
|
+
if (!list.length)
|
|
44
|
+
return { text: "No sessions in this workspace", tone: "notice" };
|
|
45
|
+
return {
|
|
46
|
+
text: [
|
|
47
|
+
"**Recent sessions** (use `/resume <id-prefix>`)",
|
|
48
|
+
"",
|
|
49
|
+
...list.map((s) => `- \`${s.id}\` ${s.id === ctx.sessionId ? "**(current)** " : ""}${s.model} · ${host.store.messages(s.id).length} msgs · ${firstPrompt(host, s.id) || "_empty_"}`),
|
|
50
|
+
].join("\n"),
|
|
51
|
+
data: list,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
async resume(args, _ctx, host) {
|
|
55
|
+
const target = args.trim();
|
|
56
|
+
if (!target)
|
|
57
|
+
throw new Error("Usage: /resume <id>");
|
|
58
|
+
const matches = workspaceSessions(host).filter((s) => s.id.startsWith(target));
|
|
59
|
+
if (matches.length !== 1)
|
|
60
|
+
throw new Error(matches.length
|
|
61
|
+
? `Ambiguous session prefix: ${target}`
|
|
62
|
+
: `No session in this workspace matches ${target}`);
|
|
63
|
+
const found = matches[0];
|
|
64
|
+
return { text: `Resumed session ${found.id}`, tone: "notice", sessionId: found.id };
|
|
65
|
+
},
|
|
66
|
+
async clear(_args, ctx, host) {
|
|
67
|
+
const model = host.store.get(ctx.sessionId).model;
|
|
68
|
+
const created = host.store.create(host.workspace, host.provider.id, model);
|
|
69
|
+
return { text: `New session ${created.id}`, tone: "notice", sessionId: created.id };
|
|
70
|
+
},
|
|
71
|
+
async compact(args, ctx, host) {
|
|
72
|
+
const result = await host.runner.compact(ctx.sessionId, {
|
|
73
|
+
...(args.trim() ? { focus: args.trim() } : {}),
|
|
74
|
+
...(ctx.signal ? { signal: ctx.signal } : {}),
|
|
75
|
+
});
|
|
76
|
+
return result
|
|
77
|
+
? {
|
|
78
|
+
text: `Compacted ${result.replaced} messages: ~${result.before} → ~${result.after} tokens`,
|
|
79
|
+
tone: "notice",
|
|
80
|
+
data: result,
|
|
81
|
+
}
|
|
82
|
+
: { text: "Not enough history to compact", tone: "notice" };
|
|
83
|
+
},
|
|
84
|
+
async model(args, ctx, host) {
|
|
85
|
+
const model = args.trim();
|
|
86
|
+
if (!model)
|
|
87
|
+
throw new Error("Usage: /model <model id>");
|
|
88
|
+
host.runner.setModel(ctx.sessionId, model);
|
|
89
|
+
return { text: `Model set to \`${model}\``, tone: "notice", data: { model } };
|
|
90
|
+
},
|
|
91
|
+
async effort(args, _ctx, host) {
|
|
92
|
+
const level = args.trim();
|
|
93
|
+
if (!level)
|
|
94
|
+
return {
|
|
95
|
+
text: `Reasoning effort: ${host.config?.agents.effort ?? "model default"}`,
|
|
96
|
+
tone: "notice",
|
|
97
|
+
};
|
|
98
|
+
if (!host.updateSetting)
|
|
99
|
+
throw new Error("Effort changes are unavailable here");
|
|
100
|
+
const value = level === "!clear" ? undefined : level;
|
|
101
|
+
await host.updateSetting("agents.effort", value);
|
|
102
|
+
return {
|
|
103
|
+
text: value ? `Reasoning effort set to ${value}` : "Reasoning effort cleared",
|
|
104
|
+
tone: "notice",
|
|
105
|
+
data: { effort: value },
|
|
106
|
+
};
|
|
107
|
+
},
|
|
108
|
+
async stats(_args, ctx, host) {
|
|
109
|
+
const session = host.store.get(ctx.sessionId);
|
|
110
|
+
const runs = host.store.runs?.(ctx.sessionId) ?? [];
|
|
111
|
+
const tokens = runs.reduce((sum, r) => ({
|
|
112
|
+
input: sum.input + (r.usage?.input ?? 0),
|
|
113
|
+
output: sum.output + (r.usage?.output ?? 0),
|
|
114
|
+
}), { input: 0, output: 0 });
|
|
115
|
+
return {
|
|
116
|
+
text: [
|
|
117
|
+
"**Session statistics**",
|
|
118
|
+
"",
|
|
119
|
+
`- Session: \`${session.id}\``,
|
|
120
|
+
`- Model: \`${session.model}\``,
|
|
121
|
+
`- Messages: ${host.store.messages(session.id).length}`,
|
|
122
|
+
`- Runs: ${runs.length}`,
|
|
123
|
+
`- Tokens: in ${tokens.input} · out ${tokens.output}`,
|
|
124
|
+
].join("\n"),
|
|
125
|
+
data: { session, runs: runs.length, tokens },
|
|
126
|
+
};
|
|
127
|
+
},
|
|
128
|
+
async skills(_args, _ctx, host) {
|
|
129
|
+
const skills = host.skillCatalog?.() ?? [];
|
|
130
|
+
if (!skills.length)
|
|
131
|
+
return { text: "No skills found", tone: "notice" };
|
|
132
|
+
const state = (s) => s.effective ? "enabled" : s.shadowedBy ? `shadowed by ${s.shadowedBy}` : "disabled";
|
|
133
|
+
return {
|
|
134
|
+
text: `**Skills**\n\n| Skill | Scope | State |\n| --- | --- | --- |\n${skills
|
|
135
|
+
.map((s) => `| \`${s.displayId}\` | ${s.scope} | ${state(s)} |`)
|
|
136
|
+
.join("\n")}`,
|
|
137
|
+
data: skills,
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
async plugins(_args, _ctx, host) {
|
|
141
|
+
const plugins = host.pluginCatalog?.() ?? [];
|
|
142
|
+
if (!plugins.length)
|
|
143
|
+
return { text: "No plugins loaded", tone: "notice" };
|
|
144
|
+
return {
|
|
145
|
+
text: `**Plugins**\n\n| Plugin | Status | Enabled |\n| --- | --- | --- |\n${plugins
|
|
146
|
+
.map((p) => `| \`${p.id}\` | ${p.status} | ${p.enabled ? "yes" : "no"} |`)
|
|
147
|
+
.join("\n")}`,
|
|
148
|
+
data: plugins,
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
async mcp(_args, _ctx, host) {
|
|
152
|
+
const servers = host.mcp?.list() ?? [];
|
|
153
|
+
if (!servers.length)
|
|
154
|
+
return { text: "No MCP servers configured", tone: "notice" };
|
|
155
|
+
return {
|
|
156
|
+
text: `**MCP servers**\n\n| Server | Status | Tools |\n| --- | --- | --- |\n${servers
|
|
157
|
+
.map((s) => `| \`${s.name}\` | ${s.status} | ${s.counts.tools} |`)
|
|
158
|
+
.join("\n")}`,
|
|
159
|
+
data: servers,
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
async agents(args, ctx, host) {
|
|
163
|
+
// With arguments, `/agents <verb>` belongs to the subagents plugin's own command (as in the
|
|
164
|
+
// TUI); without them it lists the active-agent catalog.
|
|
165
|
+
const plugin = args.trim() ? host.plugins?.commands.get("agents") : undefined;
|
|
166
|
+
if (plugin)
|
|
167
|
+
return { text: await plugin(args.trim(), { sessionId: ctx.sessionId }) };
|
|
168
|
+
let state;
|
|
169
|
+
try {
|
|
170
|
+
state = host.plugins?.pluginState?.("subagents", "mainAgents");
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
/* agent contributions are best-effort */
|
|
174
|
+
}
|
|
175
|
+
const agents = agentCatalogFromState(state);
|
|
176
|
+
const active = resolveActiveAgent(agents, host.config?.agents.active);
|
|
177
|
+
return {
|
|
178
|
+
text: [
|
|
179
|
+
"**Agents**",
|
|
180
|
+
"",
|
|
181
|
+
...agents.map((a) => `- **${a.name}**${a.id === active.id ? " (current)" : ""}${a.readOnly ? " · read-only" : ""} — ${a.description}`),
|
|
182
|
+
].join("\n"),
|
|
183
|
+
data: { agents, active: active.id },
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
export class CommandCatalog {
|
|
188
|
+
host;
|
|
189
|
+
constructor(host) {
|
|
190
|
+
this.host = host;
|
|
191
|
+
}
|
|
192
|
+
builtins() {
|
|
193
|
+
return BUILTIN_COMMANDS.map(builtinDescriptor);
|
|
194
|
+
}
|
|
195
|
+
/** Every descriptor (optionally for one surface). Precedence: builtin > plugin > prompt > skill. */
|
|
196
|
+
list(surface) {
|
|
197
|
+
const out = this.builtins();
|
|
198
|
+
const taken = new Set(out.flatMap((c) => [c.name, ...(c.aliases ?? [])]));
|
|
199
|
+
const push = (descriptor) => {
|
|
200
|
+
if (taken.has(descriptor.name))
|
|
201
|
+
return;
|
|
202
|
+
taken.add(descriptor.name);
|
|
203
|
+
out.push(descriptor);
|
|
204
|
+
};
|
|
205
|
+
const host = this.host;
|
|
206
|
+
if (host?.plugins)
|
|
207
|
+
for (const [name, info] of host.plugins.commandInfo)
|
|
208
|
+
push({
|
|
209
|
+
name,
|
|
210
|
+
description: info.description ?? `plugin ${info.plugin}`,
|
|
211
|
+
...(info.argumentHint ? { argumentHint: info.argumentHint } : {}),
|
|
212
|
+
source: "plugin",
|
|
213
|
+
owner: info.plugin,
|
|
214
|
+
surfaces: [...ALL],
|
|
215
|
+
execution: "core",
|
|
216
|
+
});
|
|
217
|
+
if (host?.prompts)
|
|
218
|
+
for (const template of host.prompts.templates.values())
|
|
219
|
+
push({
|
|
220
|
+
name: template.name,
|
|
221
|
+
description: template.description,
|
|
222
|
+
...(template.argumentHint ? { argumentHint: template.argumentHint } : {}),
|
|
223
|
+
source: "prompt",
|
|
224
|
+
owner: template.source,
|
|
225
|
+
surfaces: [...ALL],
|
|
226
|
+
execution: "surface",
|
|
227
|
+
});
|
|
228
|
+
for (const skill of host?.skillCatalog?.() ?? [])
|
|
229
|
+
if (skill.effective && skill.enabled)
|
|
230
|
+
push({
|
|
231
|
+
name: `skill:${skill.id}`,
|
|
232
|
+
description: skill.description,
|
|
233
|
+
source: "skill",
|
|
234
|
+
...(skill.owner ? { owner: skill.owner.id } : {}),
|
|
235
|
+
surfaces: [...ALL],
|
|
236
|
+
execution: "surface",
|
|
237
|
+
});
|
|
238
|
+
return surface ? out.filter((c) => c.surfaces.includes(surface)) : out;
|
|
239
|
+
}
|
|
240
|
+
/** A command by name or alias (built-in names and aliases are case-insensitive). */
|
|
241
|
+
resolve(name) {
|
|
242
|
+
const lower = name.toLowerCase();
|
|
243
|
+
const all = this.list();
|
|
244
|
+
return (all.find((c) => c.source === "builtin" && (c.name === lower || c.aliases?.includes(lower))) ??
|
|
245
|
+
all.find((c) => c.source !== "builtin" && c.name === name));
|
|
246
|
+
}
|
|
247
|
+
/** Runs an `execution: "core"` command. Surface commands and unknown names throw. */
|
|
248
|
+
async execute(name, args, ctx) {
|
|
249
|
+
const descriptor = this.resolve(name);
|
|
250
|
+
if (!descriptor)
|
|
251
|
+
throw new Error(`Unknown command /${name}. Type /help.`);
|
|
252
|
+
if (descriptor.execution !== "core")
|
|
253
|
+
throw new Error(`/${descriptor.name} is handled by each surface, not by the core catalog`);
|
|
254
|
+
const host = this.host;
|
|
255
|
+
if (!host)
|
|
256
|
+
throw new Error(`/${descriptor.name} needs an application host`);
|
|
257
|
+
if (descriptor.source === "plugin") {
|
|
258
|
+
const handler = host.plugins?.commands.get(descriptor.name);
|
|
259
|
+
if (!handler)
|
|
260
|
+
throw new Error(`Unknown plugin command: ${descriptor.name}`);
|
|
261
|
+
return { text: await handler(args, { sessionId: ctx.sessionId }) };
|
|
262
|
+
}
|
|
263
|
+
const handler = HANDLERS[descriptor.name];
|
|
264
|
+
if (!handler)
|
|
265
|
+
throw new Error(`/${descriptor.name} has no core handler`);
|
|
266
|
+
return handler(args, ctx, host);
|
|
267
|
+
}
|
|
268
|
+
}
|
package/dist/config.d.ts
CHANGED
|
@@ -476,6 +476,13 @@ declare const SETTABLE_KEYS: {
|
|
|
476
476
|
};
|
|
477
477
|
export type SettableSettingKey = keyof typeof SETTABLE_KEYS;
|
|
478
478
|
export declare function isSettableSettingKey(key: string): key is SettableSettingKey;
|
|
479
|
+
export interface SettableSettingInfo {
|
|
480
|
+
key: SettableSettingKey;
|
|
481
|
+
kind: "boolean" | "number" | "string" | "enum";
|
|
482
|
+
options?: string[];
|
|
483
|
+
}
|
|
484
|
+
/** Every settable key with its value kind (and enum options), for settings editors. */
|
|
485
|
+
export declare function settableSettings(): SettableSettingInfo[];
|
|
479
486
|
/**
|
|
480
487
|
* Atomically sets one user-facing setting in the global config file (`<config home>/config.json`)
|
|
481
488
|
* while preserving every unrelated JSON field. The value is validated against the same zod leaf
|
package/dist/config.js
CHANGED
|
@@ -551,6 +551,33 @@ const SETTABLE_KEYS = {
|
|
|
551
551
|
export function isSettableSettingKey(key) {
|
|
552
552
|
return Object.prototype.hasOwnProperty.call(SETTABLE_KEYS, key);
|
|
553
553
|
}
|
|
554
|
+
/** Unwraps optional/default/nullable wrappers down to the value schema. */
|
|
555
|
+
function settingLeaf(schema) {
|
|
556
|
+
let current = schema;
|
|
557
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
558
|
+
const type = current.def?.type;
|
|
559
|
+
if ((type === "optional" || type === "default" || type === "nullable") &&
|
|
560
|
+
current.def?.innerType)
|
|
561
|
+
current = current.def.innerType;
|
|
562
|
+
else
|
|
563
|
+
break;
|
|
564
|
+
}
|
|
565
|
+
return current;
|
|
566
|
+
}
|
|
567
|
+
/** Every settable key with its value kind (and enum options), for settings editors. */
|
|
568
|
+
export function settableSettings() {
|
|
569
|
+
return Object.keys(SETTABLE_KEYS).map((key) => {
|
|
570
|
+
const leaf = settingLeaf(SETTABLE_KEYS[key]);
|
|
571
|
+
const type = leaf.def?.type;
|
|
572
|
+
if (type === "enum")
|
|
573
|
+
return { key, kind: "enum", options: (leaf.options ?? []).map((o) => String(o)) };
|
|
574
|
+
if (type === "boolean")
|
|
575
|
+
return { key, kind: "boolean" };
|
|
576
|
+
if (type === "number" || type === "int")
|
|
577
|
+
return { key, kind: "number" };
|
|
578
|
+
return { key, kind: "string" };
|
|
579
|
+
});
|
|
580
|
+
}
|
|
554
581
|
/**
|
|
555
582
|
* Atomically sets one user-facing setting in the global config file (`<config home>/config.json`)
|
|
556
583
|
* while preserving every unrelated JSON field. The value is validated against the same zod leaf
|
package/dist/core/contracts.d.ts
CHANGED
|
@@ -18,6 +18,10 @@ export interface Session {
|
|
|
18
18
|
options?: Record<string, unknown>;
|
|
19
19
|
createdAt?: number;
|
|
20
20
|
updatedAt?: number;
|
|
21
|
+
/** Root sessions pinned in web clients (v4). */
|
|
22
|
+
pinned?: boolean;
|
|
23
|
+
/** When a root session was archived in web clients (v4); absent when not archived. */
|
|
24
|
+
archivedAt?: number;
|
|
21
25
|
}
|
|
22
26
|
export interface ChildSessionRecord {
|
|
23
27
|
id?: string;
|
|
@@ -30,6 +34,82 @@ export interface ChildSessionRecord {
|
|
|
30
34
|
depth: number;
|
|
31
35
|
options: Record<string, unknown>;
|
|
32
36
|
}
|
|
37
|
+
/** `queued` and `running` are the only non-terminal run states. */
|
|
38
|
+
export type RunStatus = "queued" | "running" | "completed" | "turns_exceeded" | "failed" | "cancelled" | "interrupted";
|
|
39
|
+
export type TerminalRunStatus = Exclude<RunStatus, "queued" | "running">;
|
|
40
|
+
/** One `AgentRunner.run` execution (v4 `runs` table). `id` equals `RunEvent.runId`. */
|
|
41
|
+
export interface RunRecord {
|
|
42
|
+
id: string;
|
|
43
|
+
session: string;
|
|
44
|
+
status: RunStatus;
|
|
45
|
+
/** Client idempotency key; absent for TUI/headless runs. */
|
|
46
|
+
requestId?: string;
|
|
47
|
+
correlationId?: string;
|
|
48
|
+
/** Process that executes (or executed) the run, for startup reconciliation. */
|
|
49
|
+
ownerPid?: number;
|
|
50
|
+
model?: string;
|
|
51
|
+
createdAt: number;
|
|
52
|
+
startedAt?: number;
|
|
53
|
+
endedAt?: number;
|
|
54
|
+
error?: string;
|
|
55
|
+
usage?: {
|
|
56
|
+
input: number;
|
|
57
|
+
output: number;
|
|
58
|
+
cachedInput?: number;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export interface BeginRunInput {
|
|
62
|
+
id: string;
|
|
63
|
+
session: string;
|
|
64
|
+
/** `queued` for scheduled runs, `running` (default) when execution starts now. */
|
|
65
|
+
status?: "queued" | "running";
|
|
66
|
+
requestId?: string;
|
|
67
|
+
correlationId?: string;
|
|
68
|
+
model?: string;
|
|
69
|
+
}
|
|
70
|
+
export interface EndRunInput {
|
|
71
|
+
status: TerminalRunStatus;
|
|
72
|
+
error?: string;
|
|
73
|
+
usage?: {
|
|
74
|
+
input: number;
|
|
75
|
+
output: number;
|
|
76
|
+
cachedInput?: number;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
export interface PageOptions {
|
|
80
|
+
/** Only rows with a sequence greater than this (ascending from there). */
|
|
81
|
+
after?: number;
|
|
82
|
+
/** Only rows with a sequence lower than this (the newest `limit` of them). */
|
|
83
|
+
before?: number;
|
|
84
|
+
limit?: number;
|
|
85
|
+
}
|
|
86
|
+
export interface MessagePage {
|
|
87
|
+
items: Array<{
|
|
88
|
+
seq: number;
|
|
89
|
+
message: Message;
|
|
90
|
+
compacted: boolean;
|
|
91
|
+
}>;
|
|
92
|
+
/** More rows exist beyond the page in the paging direction. */
|
|
93
|
+
hasMore: boolean;
|
|
94
|
+
}
|
|
95
|
+
export interface StoredEvent {
|
|
96
|
+
/** Global `events.seq` as a string (same value as `RunEvent.eventId`). */
|
|
97
|
+
eventId: string;
|
|
98
|
+
runId: string;
|
|
99
|
+
type: string;
|
|
100
|
+
data: unknown;
|
|
101
|
+
createdAt?: number;
|
|
102
|
+
correlationId?: string;
|
|
103
|
+
}
|
|
104
|
+
export interface EventPage {
|
|
105
|
+
items: StoredEvent[];
|
|
106
|
+
hasMore: boolean;
|
|
107
|
+
}
|
|
108
|
+
/** Optional tool-call metadata (v4 columns); stores may ignore it. */
|
|
109
|
+
export interface ToolCallMeta {
|
|
110
|
+
runId?: string;
|
|
111
|
+
effect?: string;
|
|
112
|
+
}
|
|
33
113
|
export interface SessionStore {
|
|
34
114
|
create(workspace: string, provider: string, model: string): Session;
|
|
35
115
|
get(id: string): Session;
|
|
@@ -64,12 +144,39 @@ export interface SessionStore {
|
|
|
64
144
|
overwrite(id: string, messages: Message[]): void;
|
|
65
145
|
acquire(id: string): void;
|
|
66
146
|
release(id: string): void;
|
|
67
|
-
beginCall(session: string, call: ToolCall): void;
|
|
147
|
+
beginCall(session: string, call: ToolCall, meta?: ToolCallMeta): void;
|
|
68
148
|
endCall(session: string, call: ToolCall, result: ToolResult): void;
|
|
69
149
|
/** The persisted result of a completed tool call, for replay of rich (ui/image) parts. */
|
|
70
150
|
callResult(session: string, callId: string): ToolResult | undefined;
|
|
71
151
|
reconcile(id: string, acknowledge?: boolean): void;
|
|
72
|
-
|
|
152
|
+
/**
|
|
153
|
+
* Persist a durable run event. Stores that assign a global, monotonic sequence return it; the
|
|
154
|
+
* runner exposes it as `RunEvent.eventId`. Returning nothing is allowed (no `eventId`).
|
|
155
|
+
*/
|
|
156
|
+
event(id: string, runId: string, type: string, data: unknown, meta?: {
|
|
157
|
+
correlationId?: string;
|
|
158
|
+
}): number | void;
|
|
159
|
+
/**
|
|
160
|
+
* Records a run, or moves an existing `queued` run with the same id to `running`. A run whose
|
|
161
|
+
* `(session, requestId)` already exists is returned as is (`created: false`): idempotency.
|
|
162
|
+
*/
|
|
163
|
+
beginRun?(input: BeginRunInput): {
|
|
164
|
+
run: RunRecord;
|
|
165
|
+
created: boolean;
|
|
166
|
+
};
|
|
167
|
+
/** Moves a non-terminal run to a terminal state; terminal runs are never overwritten. */
|
|
168
|
+
endRun?(id: string, input: EndRunInput): void;
|
|
169
|
+
runByRequest?(session: string, requestId: string): RunRecord | undefined;
|
|
170
|
+
/** Runs of a session, newest first. */
|
|
171
|
+
runs?(session: string, options?: {
|
|
172
|
+
limit?: number;
|
|
173
|
+
}): RunRecord[];
|
|
174
|
+
messagesPage?(session: string, options?: PageOptions & {
|
|
175
|
+
compacted?: boolean;
|
|
176
|
+
}): MessagePage;
|
|
177
|
+
eventsPage?(session: string, options?: PageOptions): EventPage;
|
|
178
|
+
/** Marks queued/running runs whose owner process is not alive as interrupted. */
|
|
179
|
+
interruptRuns?(): number;
|
|
73
180
|
}
|
|
74
181
|
export interface ContextSource {
|
|
75
182
|
/** System instructions; `sessionId` scopes lazily attached nested instructions. */
|
package/dist/core/runner.d.ts
CHANGED
|
@@ -93,6 +93,10 @@ export interface RunOptions {
|
|
|
93
93
|
timeoutMs?: number;
|
|
94
94
|
/** Reasoning effort level for this run; beats the runner-level default when set. */
|
|
95
95
|
reasoningEffort?: string;
|
|
96
|
+
/** Run id to use for this run's events (e.g. preassigned by an embedder); a UUID otherwise. */
|
|
97
|
+
runId?: string;
|
|
98
|
+
/** Copied to every event of this run as `RunEvent.correlationId` (e.g. an HTTP request id). */
|
|
99
|
+
correlationId?: string;
|
|
96
100
|
}
|
|
97
101
|
/**
|
|
98
102
|
* Default cumulative token budget for one run: several context windows (each turn re-sends the
|
package/dist/core/runner.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { textProjection, textResult, } from "@alisio/sdk";
|
|
1
|
+
import { isEphemeralRunEventType, textProjection, textResult, } from "@alisio/sdk";
|
|
2
2
|
import { checkpointInstructions, estimateTokens, MAX_TRUSTED_WINDOW, parseCheckpointOutput, planCompaction, reduceMessagesToBudget, shouldCompactContext, summarize, summaryMessage, } from "./compaction.js";
|
|
3
3
|
/**
|
|
4
4
|
* Default cumulative token budget for one run: several context windows (each turn re-sends the
|
|
@@ -49,8 +49,9 @@ export class AgentRunner {
|
|
|
49
49
|
...(compaction ? { compaction: { ...this.options.compaction, ...compaction } } : {}),
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
-
emitter(sessionId) {
|
|
53
|
-
const runId = crypto.randomUUID();
|
|
52
|
+
emitter(sessionId, run = {}) {
|
|
53
|
+
const runId = run.runId ?? crypto.randomUUID();
|
|
54
|
+
const correlationId = run.correlationId;
|
|
54
55
|
let seq = 0;
|
|
55
56
|
return (type, data) => {
|
|
56
57
|
const event = {
|
|
@@ -62,8 +63,15 @@ export class AgentRunner {
|
|
|
62
63
|
timestamp: new Date().toISOString(),
|
|
63
64
|
data,
|
|
64
65
|
};
|
|
65
|
-
if (type
|
|
66
|
-
|
|
66
|
+
if (!isEphemeralRunEventType(type)) {
|
|
67
|
+
const persisted = correlationId !== undefined
|
|
68
|
+
? this.options.store.event(sessionId, runId, type, data, { correlationId })
|
|
69
|
+
: this.options.store.event(sessionId, runId, type, data);
|
|
70
|
+
if (typeof persisted === "number")
|
|
71
|
+
event.eventId = String(persisted);
|
|
72
|
+
}
|
|
73
|
+
if (correlationId !== undefined)
|
|
74
|
+
event.correlationId = correlationId;
|
|
67
75
|
try {
|
|
68
76
|
this.options.onEvent?.(event);
|
|
69
77
|
}
|
|
@@ -280,7 +288,17 @@ export class AgentRunner {
|
|
|
280
288
|
}
|
|
281
289
|
async run(sessionId, prompt, signal, options = {}) {
|
|
282
290
|
const controller = this.claim(sessionId);
|
|
283
|
-
const
|
|
291
|
+
const runId = options.runId ?? crypto.randomUUID();
|
|
292
|
+
const o = this.options, emit = this.emitter(sessionId, { runId, correlationId: options.correlationId });
|
|
293
|
+
/** Run journal (v4 stores only): an auxiliary record that never changes the run outcome. */
|
|
294
|
+
const finish = (status, error) => {
|
|
295
|
+
try {
|
|
296
|
+
o.store.endRun?.(runId, { status, usage: usageTotal, ...(error ? { error } : {}) });
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
/* A journal write failure cannot invalidate an executed run. */
|
|
300
|
+
}
|
|
301
|
+
};
|
|
284
302
|
const limit = o.maxContextChars ?? 800_000;
|
|
285
303
|
let tokens = 0, lastText = "", acquired = false;
|
|
286
304
|
const usageTotal = { input: 0, output: 0 };
|
|
@@ -296,6 +314,13 @@ export class AgentRunner {
|
|
|
296
314
|
return options.instructions ? `${base}\n\n${options.instructions}` : base;
|
|
297
315
|
};
|
|
298
316
|
try {
|
|
317
|
+
o.store.beginRun?.({
|
|
318
|
+
id: runId,
|
|
319
|
+
session: sessionId,
|
|
320
|
+
status: "running",
|
|
321
|
+
model: o.store.get(sessionId).model,
|
|
322
|
+
...(options.correlationId !== undefined ? { correlationId: options.correlationId } : {}),
|
|
323
|
+
});
|
|
299
324
|
o.store.acquire(sessionId);
|
|
300
325
|
acquired = true;
|
|
301
326
|
o.store.reconcile(sessionId);
|
|
@@ -414,6 +439,8 @@ export class AgentRunner {
|
|
|
414
439
|
// display-only extension of the persisted transcript and never reach a model prompt
|
|
415
440
|
// (raw image bytes included). The store keeps the rich parts for TUI replay.
|
|
416
441
|
const providerMessages = messages.map((m) => m.role === "tool" ? { ...m, result: textProjection(m.result) } : m);
|
|
442
|
+
const requested = Date.now();
|
|
443
|
+
let firstDelta;
|
|
417
444
|
for await (const e of provider.stream({
|
|
418
445
|
instructions,
|
|
419
446
|
messages: providerMessages,
|
|
@@ -428,6 +455,8 @@ export class AgentRunner {
|
|
|
428
455
|
: {}),
|
|
429
456
|
})) {
|
|
430
457
|
combined.throwIfAborted();
|
|
458
|
+
if (e.type !== "completed")
|
|
459
|
+
firstDelta ??= Date.now();
|
|
431
460
|
if (e.type === "text_delta")
|
|
432
461
|
emit("text_delta", { delta: e.delta });
|
|
433
462
|
else if (e.type === "reasoning_delta")
|
|
@@ -445,6 +474,7 @@ export class AgentRunner {
|
|
|
445
474
|
}
|
|
446
475
|
if (!completion)
|
|
447
476
|
throw new Error("Provider stream ended without a completed response");
|
|
477
|
+
const durationMs = Date.now() - requested;
|
|
448
478
|
const ids = completion.calls.map((c) => c.id);
|
|
449
479
|
const previousIds = new Set(messages.flatMap((m) => (m.role === "assistant" ? m.calls.map((c) => c.id) : [])));
|
|
450
480
|
if (ids.some((id) => previousIds.has(id)))
|
|
@@ -466,6 +496,8 @@ export class AgentRunner {
|
|
|
466
496
|
calls: completion.calls.length,
|
|
467
497
|
model,
|
|
468
498
|
...(usage ? { usage } : {}),
|
|
499
|
+
durationMs,
|
|
500
|
+
...(firstDelta !== undefined ? { ttftMs: firstDelta - requested } : {}),
|
|
469
501
|
});
|
|
470
502
|
if (!completion.calls.length) {
|
|
471
503
|
const final = {
|
|
@@ -481,6 +513,7 @@ export class AgentRunner {
|
|
|
481
513
|
final.truncated = true;
|
|
482
514
|
}
|
|
483
515
|
emit("run_completed", final);
|
|
516
|
+
finish("completed");
|
|
484
517
|
return { sessionId, text: lastText, status: "completed", usage: usageTotal };
|
|
485
518
|
}
|
|
486
519
|
const prepared = completion.calls.map((call) => {
|
|
@@ -571,7 +604,7 @@ export class AgentRunner {
|
|
|
571
604
|
if (decision === "session")
|
|
572
605
|
policy[effect] = true;
|
|
573
606
|
}
|
|
574
|
-
o.store.beginCall(sessionId, call);
|
|
607
|
+
o.store.beginCall(sessionId, call, { runId, effect });
|
|
575
608
|
result = await p.tool.execute(p.input, {
|
|
576
609
|
signal: combined,
|
|
577
610
|
workspace,
|
|
@@ -594,8 +627,11 @@ export class AgentRunner {
|
|
|
594
627
|
}
|
|
595
628
|
: {}),
|
|
596
629
|
});
|
|
597
|
-
|
|
598
|
-
|
|
630
|
+
// The bound applies to what the model sees (the text projection): display-only
|
|
631
|
+
// ui/image parts are bounded by their producers and must not push text over it.
|
|
632
|
+
const projected = textProjection(result);
|
|
633
|
+
if (JSON.stringify(projected).length > 48_000)
|
|
634
|
+
result = textResult(`${JSON.stringify(projected).slice(0, 40_000)}\n[tool output truncated]`, result.isError);
|
|
599
635
|
if (pendingUpdate && effect === "read") {
|
|
600
636
|
result = {
|
|
601
637
|
...result,
|
|
@@ -664,14 +700,15 @@ export class AgentRunner {
|
|
|
664
700
|
// can simply prompt again to continue in the same session. The real hard stops remain the
|
|
665
701
|
// token budget (`maxTokens`) and the timeout; the turn count is a safety rail.
|
|
666
702
|
emit("run_turns_exceeded", { turns: maxTurns, maxTurns });
|
|
703
|
+
finish("turns_exceeded");
|
|
667
704
|
return { sessionId, text: lastText, status: "turns-exceeded", usage: usageTotal };
|
|
668
705
|
}
|
|
669
706
|
catch (error) {
|
|
670
707
|
// On cancellation report the abort reason, not the transport's secondary error.
|
|
671
708
|
const cause = combined.aborted ? (combined.reason ?? error) : error;
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
709
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
710
|
+
emit(combined.aborted ? "run_cancelled" : "run_failed", { error: message });
|
|
711
|
+
finish(combined.aborted ? "cancelled" : "failed", message);
|
|
675
712
|
throw error;
|
|
676
713
|
}
|
|
677
714
|
finally {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/** @alisio/core public API: embed the Alisio agent programmatically. */
|
|
2
2
|
export * from "@alisio/sdk";
|
|
3
|
+
export { type ActiveAgent, type AgentPickerItem, activeAgentCatalog, agentCatalogFromState, agentPickerItems, agentRunOptions, BUILTIN_AGENTS, DEFAULT_AGENT_ID, type MainCapableAgentRecord, mainAgentFromRecord, resolveActiveAgent, } from "./agents/active.ts";
|
|
3
4
|
export { type AppOptions, type BuiltinContext, type BuiltinPlugin, createApplication, enabledBuiltins, type PluginCatalogEntry, type PluginCatalogStatus, } from "./application.ts";
|
|
4
|
-
export {
|
|
5
|
+
export { BUILTIN_COMMANDS, type BuiltinCommand, CommandCatalog, type CommandExecutionContext, type CommandHost, type CommandResult, type CommandSurface, } from "./commands/catalog.ts";
|
|
6
|
+
export { type Config, type ConfigLoadResult, type ConfigProvenance, configFile, configHome, configSchema, isSettableSettingKey, type LoadedConfig, loadConfig, loadConfigWithProvenance, type McpServerSource, overridesSavedProviderProfile, type SettableSettingInfo, type SettableSettingKey, setConfigValue, setGlobalMcpAllow, setMcpServerEnabled, setProjectPluginEnabled, setProjectSkillEnabled, settableSettings, stateHome, } from "./config.ts";
|
|
5
7
|
export { type ContextBudget, checkpointInstructions, completeText, effectiveContextBudget, estimateTokens, MAX_TRUSTED_WINDOW, parseCheckpointOutput, planCompaction, renderCheckpoint, serializeForSummary, shouldCompact, shouldCompactContext, summarize, } from "./core/compaction.ts";
|
|
6
|
-
export type { ApprovalDecision, ApprovalHandler, ApprovalRequest, ContextSource, HookFailure, Policy, RunnerExtensions, Session, SessionStore, } from "./core/contracts.ts";
|
|
8
|
+
export type { ApprovalDecision, ApprovalHandler, ApprovalRequest, BeginRunInput, ContextSource, EndRunInput, EventPage, HookFailure, MessagePage, PageOptions, Policy, RunnerExtensions, RunRecord, RunStatus, Session, SessionStore, StoredEvent, TerminalRunStatus, ToolCallMeta, } from "./core/contracts.ts";
|
|
7
9
|
export { ToolRegistry } from "./core/registry.ts";
|
|
8
10
|
export { AgentRunner, type CompactionResult, defaultTokenBudget, type RunnerOptions, type RunnerSettingsPatch, type RunOptions, } from "./core/runner.ts";
|
|
9
11
|
export { type ExtensionConflict, ExtensionRegistry } from "./extensions/registry.ts";
|
|
@@ -13,14 +15,16 @@ export { discoverPlugins, PluginHost, type PluginHostOptions, pluginPrefix, } fr
|
|
|
13
15
|
export { type CliInstallOptions, cliInstall, INSTALL_TIMEOUT_MS, type InstallPluginInput, type InstallPluginResult, type InstallRunner, installedNpmPlugins, installPlugin, npmInstallTarget, type PluginSpec, parsePluginSpec, pluginTrustRemark, printInstallResult, registerPluginInstallTool, sanitizeNpmError, } from "./plugins/install.ts";
|
|
14
16
|
export { ActiveProvider, ProviderRegistry, UnconfiguredProvider } from "./providers/registry.ts";
|
|
15
17
|
export { availableProviderModels, type ProviderModelCatalog, resolveProviderModel, } from "./providers/routing.ts";
|
|
16
|
-
export { type ProviderProfile, type ProviderSettings, ProviderSettingsStore, } from "./providers/settings.ts";
|
|
18
|
+
export { maskSecret, type ProviderProfile, type ProviderSettings, ProviderSettingsStore, } from "./providers/settings.ts";
|
|
17
19
|
export { type InstructionFile, ProjectContext, type ProjectContextOptions, } from "./resources/context.ts";
|
|
18
20
|
export { expandSlashPrompt, loadPromptTemplates, type PromptDiagnostic, type PromptRequirement, type PromptSource, type PromptSourceKind, type PromptTemplate, parsePromptTemplate, promptSources, renderPromptTemplate, splitArguments, } from "./resources/prompts.ts";
|
|
19
21
|
export { type Skill, type SkillCatalogEntry, type SkillRoot, type SkillScope, Skills, skillRoots, } from "./resources/skills.ts";
|
|
20
22
|
export { type ExternalDirectoryDecision, type ExternalDirectoryHandler, type ExternalDirectoryRequest, PathAccess, type PathAccessOptions, type ResolvePathOptions, } from "./runtime/access.ts";
|
|
23
|
+
export { BlobStore, type BlobStoreOptions } from "./runtime/blobs.ts";
|
|
24
|
+
export { clipLines, unifiedPatch } from "./runtime/diff.ts";
|
|
21
25
|
export { exists, fileSize, readHead, readJson, readText, which } from "./runtime/fs.ts";
|
|
22
26
|
export { defaultGlobalRoots, isPathSpec, PLUGIN_KEYWORD, resolvePluginSpec, } from "./runtime/modules.ts";
|
|
23
|
-
export { findWorkspace, outsideRootsMessage, safePath } from "./runtime/paths.ts";
|
|
27
|
+
export { findWorkspace, inside, outsideRootsMessage, safePath } from "./runtime/paths.ts";
|
|
24
28
|
export { isMissingCommand, type ProcessResult, RIPGREP_INSTALL_HINT, runProcess, } from "./runtime/process.ts";
|
|
25
29
|
export { isSqliteExperimentalWarning, openDatabase } from "./runtime/sqlite.ts";
|
|
26
30
|
export { SQLiteStore } from "./runtime/store.ts";
|