@alisio/core 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application.d.ts +7 -0
- package/dist/application.js +71 -4
- package/dist/changelog/data.js +21 -0
- package/dist/commands/builtins.js +6 -0
- package/dist/commands/catalog.d.ts +8 -1
- package/dist/commands/catalog.js +62 -0
- package/dist/config.d.ts +37 -3
- package/dist/config.js +109 -7
- package/dist/core/contracts.d.ts +10 -1
- package/dist/core/runner.d.ts +3 -1
- package/dist/core/runner.js +2 -0
- package/dist/decisions/bound.d.ts +23 -0
- package/dist/decisions/bound.js +48 -0
- package/dist/decisions/breaker.d.ts +35 -0
- package/dist/decisions/breaker.js +61 -0
- package/dist/decisions/format.d.ts +15 -0
- package/dist/decisions/format.js +89 -0
- package/dist/decisions/index.d.ts +7 -0
- package/dist/decisions/index.js +7 -0
- package/dist/decisions/metrics.d.ts +45 -0
- package/dist/decisions/metrics.js +79 -0
- package/dist/decisions/registry.d.ts +22 -0
- package/dist/decisions/registry.js +64 -0
- package/dist/decisions/service.d.ts +137 -0
- package/dist/decisions/service.js +377 -0
- package/dist/decisions/validate.d.ts +26 -0
- package/dist/decisions/validate.js +182 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -0
- package/dist/plugins/host.d.ts +33 -1
- package/dist/plugins/host.js +94 -14
- package/package.json +2 -2
package/dist/plugins/host.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { AskQuestionsRequest, AskQuestionsResult, CommandContext, CommandOptions, CompletionRequest, Message, PanelProvider, Plugin, PluginAPI, PluginMetadata, RunEvent, SelectRequest, ViewContext } from "@alisio/sdk";
|
|
1
|
+
import type { AskQuestionsRequest, AskQuestionsResult, CommandContext, CommandOptions, CompletionRequest, JsonValue, Message, PanelProvider, Plugin, PluginAPI, PluginMetadata, RunEvent, SelectRequest, ViewContext } from "@alisio/sdk";
|
|
2
2
|
import type { HookFailure, RunnerExtensions } from "../core/contracts.ts";
|
|
3
3
|
import type { HumanWaits } from "../core/human-wait.ts";
|
|
4
4
|
import type { ToolRegistry } from "../core/registry.ts";
|
|
5
|
+
import type { DecisionRegistry } from "../decisions/registry.ts";
|
|
6
|
+
import type { DecisionService } from "../decisions/service.ts";
|
|
5
7
|
import { ExtensionRegistry } from "../extensions/registry.ts";
|
|
6
8
|
import type { ProviderRegistry } from "../providers/registry.ts";
|
|
7
9
|
export declare const pluginPrefix: (id: string) => string;
|
|
@@ -10,6 +12,16 @@ export interface PluginHostOptions {
|
|
|
10
12
|
hookTimeoutMs?: number;
|
|
11
13
|
/** Timeout for session-end hooks (for example an automatic summary). */
|
|
12
14
|
sessionEndTimeoutMs?: number;
|
|
15
|
+
/** Per-plugin limit for `dispose()` on close (default 2000 ms). */
|
|
16
|
+
disposeTimeoutMs?: number;
|
|
17
|
+
}
|
|
18
|
+
/** What the host needs to hand each plugin its `api.paths` and `api.options`. */
|
|
19
|
+
export interface PluginEnvironment {
|
|
20
|
+
/** Root of the application state (the one analysis and artifacts use). */
|
|
21
|
+
stateRoot: string;
|
|
22
|
+
configHome: string;
|
|
23
|
+
/** `pluginOverrides[id].options` (external) or `builtinPlugins.<id>` without `enabled`. */
|
|
24
|
+
options(id: string, builtin: boolean): Record<string, JsonValue>;
|
|
13
25
|
}
|
|
14
26
|
/** A failed data view: the host maps `code` to an API error (never echoing parameter values). */
|
|
15
27
|
export declare class ViewRunError extends Error {
|
|
@@ -132,6 +144,21 @@ export declare class PluginHost implements RunnerExtensions {
|
|
|
132
144
|
setCompleter(fn: (request: CompletionRequest & {
|
|
133
145
|
signal: AbortSignal;
|
|
134
146
|
}) => Promise<string>): void;
|
|
147
|
+
/**
|
|
148
|
+
* Binds Decision Intelligence. Plugins activated afterwards see `api.decisions`; without it the
|
|
149
|
+
* member is absent, exactly like on a core that predates the feature.
|
|
150
|
+
*/
|
|
151
|
+
setDecisions(decisions: {
|
|
152
|
+
registry: DecisionRegistry;
|
|
153
|
+
service: DecisionService;
|
|
154
|
+
}): void;
|
|
155
|
+
private decisionsImpl?;
|
|
156
|
+
/**
|
|
157
|
+
* Binds the directories and options plugins receive (`api.paths`, `api.options`). Plugins
|
|
158
|
+
* activated before this call, or on a host without it, see neither member.
|
|
159
|
+
*/
|
|
160
|
+
setPluginEnvironment(environment: PluginEnvironment): void;
|
|
161
|
+
private environment?;
|
|
135
162
|
/** Binds credential-free model discovery/resolution after provider startup is complete. */
|
|
136
163
|
setModels(models: PluginAPI["models"]): void;
|
|
137
164
|
/**
|
|
@@ -206,6 +233,11 @@ export declare class PluginHost implements RunnerExtensions {
|
|
|
206
233
|
/** Conservative ownership check used to protect a live session from plugin removal. */
|
|
207
234
|
ownsSessionResources(plugin: string): boolean;
|
|
208
235
|
emit(event: RunEvent): void;
|
|
236
|
+
/**
|
|
237
|
+
* Disposes every plugin in parallel, each bounded by `disposeTimeoutMs`: a slow or hung
|
|
238
|
+
* `dispose()` neither delays the others nor holds the close past its limit. Registrations are
|
|
239
|
+
* undone either way. Failures (including a timeout) are reported together afterwards.
|
|
240
|
+
*/
|
|
209
241
|
close(): Promise<void>;
|
|
210
242
|
}
|
|
211
243
|
export declare function discoverPlugins(root: string): Promise<string[]>;
|
package/dist/plugins/host.js
CHANGED
|
@@ -7,17 +7,26 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
10
|
+
import { mkdirSync } from "node:fs";
|
|
10
11
|
import { readdir, stat } from "node:fs/promises";
|
|
11
12
|
import { dirname, join, resolve } from "node:path";
|
|
12
13
|
import { pathToFileURL } from "node:url";
|
|
13
14
|
import Ajv from "ajv";
|
|
14
15
|
import { z } from "zod";
|
|
15
16
|
import { ExtensionRegistry } from "../extensions/registry.js";
|
|
16
|
-
import { readJson } from "../runtime/fs.js";
|
|
17
|
+
import { exists, readJson } from "../runtime/fs.js";
|
|
17
18
|
import { resolvePluginSpec } from "../runtime/modules.js";
|
|
18
19
|
import { inside } from "../runtime/paths.js";
|
|
19
20
|
import { openDatabase } from "../runtime/sqlite.js";
|
|
20
21
|
export const pluginPrefix = (id) => `p_${createHash("sha256").update(id).digest("hex").slice(0, 10)}`;
|
|
22
|
+
const deepFreeze = (value) => {
|
|
23
|
+
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
24
|
+
Object.freeze(value);
|
|
25
|
+
for (const child of Object.values(value))
|
|
26
|
+
deepFreeze(child);
|
|
27
|
+
}
|
|
28
|
+
return value;
|
|
29
|
+
};
|
|
21
30
|
const VIEW_ID = /^[a-z][a-z0-9-]{0,39}$/;
|
|
22
31
|
const VIEW_PARAM_TYPES = new Set(["string", "integer", "number", "boolean"]);
|
|
23
32
|
/** Query strings arrive as text: coerce them to the declared primitives and apply defaults. */
|
|
@@ -156,6 +165,22 @@ export class PluginHost {
|
|
|
156
165
|
setCompleter(fn) {
|
|
157
166
|
this.completer = fn;
|
|
158
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Binds Decision Intelligence. Plugins activated afterwards see `api.decisions`; without it the
|
|
170
|
+
* member is absent, exactly like on a core that predates the feature.
|
|
171
|
+
*/
|
|
172
|
+
setDecisions(decisions) {
|
|
173
|
+
this.decisionsImpl = decisions;
|
|
174
|
+
}
|
|
175
|
+
decisionsImpl;
|
|
176
|
+
/**
|
|
177
|
+
* Binds the directories and options plugins receive (`api.paths`, `api.options`). Plugins
|
|
178
|
+
* activated before this call, or on a host without it, see neither member.
|
|
179
|
+
*/
|
|
180
|
+
setPluginEnvironment(environment) {
|
|
181
|
+
this.environment = environment;
|
|
182
|
+
}
|
|
183
|
+
environment;
|
|
159
184
|
/** Binds credential-free model discovery/resolution after provider startup is complete. */
|
|
160
185
|
setModels(models) {
|
|
161
186
|
this.modelsImpl = models;
|
|
@@ -218,6 +243,7 @@ export class PluginHost {
|
|
|
218
243
|
"mcp",
|
|
219
244
|
"storage",
|
|
220
245
|
"ui",
|
|
246
|
+
"decisions",
|
|
221
247
|
]))
|
|
222
248
|
.optional(),
|
|
223
249
|
setup: z.function(),
|
|
@@ -376,6 +402,42 @@ export class PluginHost {
|
|
|
376
402
|
});
|
|
377
403
|
},
|
|
378
404
|
},
|
|
405
|
+
...(this.environment
|
|
406
|
+
? (({ stateRoot, configHome, options }) => {
|
|
407
|
+
// The id is validated (`^[a-z0-9][a-z0-9.-]{0,63}$`): it cannot be `.`/`..` or hold a
|
|
408
|
+
// separator, so these stay inside their roots. Created 0700 on first read.
|
|
409
|
+
const state = join(stateRoot, "plugins", plugin.id);
|
|
410
|
+
const directories = {
|
|
411
|
+
state,
|
|
412
|
+
config: join(configHome, "plugins", plugin.id),
|
|
413
|
+
cache: join(state, "cache"),
|
|
414
|
+
};
|
|
415
|
+
const paths = {};
|
|
416
|
+
for (const key of ["state", "config", "cache"])
|
|
417
|
+
Object.defineProperty(paths, key, {
|
|
418
|
+
enumerable: true,
|
|
419
|
+
get: () => {
|
|
420
|
+
mkdirSync(directories[key], { recursive: true, mode: 0o700 });
|
|
421
|
+
return directories[key];
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
return {
|
|
425
|
+
paths: Object.freeze(paths),
|
|
426
|
+
options: deepFreeze(structuredClone(options(plugin.id, builtin))),
|
|
427
|
+
};
|
|
428
|
+
})(this.environment)
|
|
429
|
+
: {}),
|
|
430
|
+
...(this.decisionsImpl
|
|
431
|
+
? {
|
|
432
|
+
decisions: (({ registry, service }) => ({
|
|
433
|
+
// Registering never activates: `decisions.provider` in the configuration does.
|
|
434
|
+
registerProvider: (provider) => track(registry.register(plugin.id, provider)),
|
|
435
|
+
available: () => service.available(),
|
|
436
|
+
activeProvider: () => service.activeProvider(),
|
|
437
|
+
tryDecide: (request, options) => service.tryDecide(request, options),
|
|
438
|
+
}))(this.decisionsImpl),
|
|
439
|
+
}
|
|
440
|
+
: {}),
|
|
379
441
|
compaction: {
|
|
380
442
|
register: (hooks) => {
|
|
381
443
|
const entry = { plugin: plugin.id, hooks };
|
|
@@ -690,23 +752,34 @@ export class PluginHost {
|
|
|
690
752
|
}
|
|
691
753
|
}
|
|
692
754
|
}
|
|
755
|
+
/**
|
|
756
|
+
* Disposes every plugin in parallel, each bounded by `disposeTimeoutMs`: a slow or hung
|
|
757
|
+
* `dispose()` neither delays the others nor holds the close past its limit. Registrations are
|
|
758
|
+
* undone either way. Failures (including a timeout) are reported together afterwards.
|
|
759
|
+
*/
|
|
693
760
|
async close() {
|
|
694
|
-
const
|
|
695
|
-
|
|
761
|
+
const limit = this.options.disposeTimeoutMs ?? 2000;
|
|
762
|
+
const entries = [...this.loaded.entries()].reverse();
|
|
763
|
+
this.loaded.clear();
|
|
764
|
+
this.externalCount = 0;
|
|
765
|
+
this.builtins.clear();
|
|
766
|
+
const results = await Promise.allSettled(entries.map(async ([id, { plugin, undo }]) => {
|
|
767
|
+
let timer;
|
|
696
768
|
try {
|
|
697
|
-
await
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
769
|
+
await Promise.race([
|
|
770
|
+
Promise.resolve().then(() => plugin.dispose?.()),
|
|
771
|
+
new Promise((_, reject) => {
|
|
772
|
+
timer = setTimeout(() => reject(new Error(`Plugin ${id} dispose timed out after ${limit}ms`)), limit);
|
|
773
|
+
}),
|
|
774
|
+
]);
|
|
701
775
|
}
|
|
702
776
|
finally {
|
|
777
|
+
clearTimeout(timer);
|
|
703
778
|
for (const fn of undo.reverse())
|
|
704
779
|
fn();
|
|
705
780
|
}
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
this.externalCount = 0;
|
|
709
|
-
this.builtins.clear();
|
|
781
|
+
}));
|
|
782
|
+
const errors = results.flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
710
783
|
if (errors.length)
|
|
711
784
|
throw new AggregateError(errors, "Plugin disposal failed");
|
|
712
785
|
}
|
|
@@ -721,7 +794,7 @@ export async function discoverPlugins(root) {
|
|
|
721
794
|
return [];
|
|
722
795
|
throw e;
|
|
723
796
|
}
|
|
724
|
-
|
|
797
|
+
const found = await Promise.all(entries
|
|
725
798
|
.filter(
|
|
726
799
|
// A root may hold an `npm install --prefix` layout (`node_modules`, package.json/lock);
|
|
727
800
|
// npm packages there are resolved through the configured `plugins` array instead.
|
|
@@ -729,6 +802,13 @@ export async function discoverPlugins(root) {
|
|
|
729
802
|
e.name !== "package.json" &&
|
|
730
803
|
e.name !== "package-lock.json" &&
|
|
731
804
|
((e.isFile() && /\.[cm]?[jt]s$/.test(e.name)) || e.isDirectory()))
|
|
732
|
-
.map((e) =>
|
|
733
|
-
.
|
|
805
|
+
.map(async (e) => {
|
|
806
|
+
const path = join(root, e.name);
|
|
807
|
+
// A directory is a plugin only with its manifest: `<config home>/plugins/<id>` is also
|
|
808
|
+
// where `api.paths.config` puts a plugin's own files, and those are not plugins.
|
|
809
|
+
if (e.isDirectory() && !(await exists(join(path, "alisio-plugin.json"))))
|
|
810
|
+
return undefined;
|
|
811
|
+
return path;
|
|
812
|
+
}));
|
|
813
|
+
return found.filter((path) => path !== undefined).sort();
|
|
734
814
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alisio/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Embeddable provider-agnostic Alisio agent core: tool loop, context compaction, local tools, MCP, plugin host and SQLite session store. Runs on Node >=22.16 and Bun.",
|
|
5
5
|
"author": "Gustavo Gutiérrez",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"node": ">=22.16"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
|
-
"@alisio/sdk": "0.
|
|
44
|
+
"@alisio/sdk": "0.3.0",
|
|
45
45
|
"@mixmark-io/domino": "2.2.0",
|
|
46
46
|
"@modelcontextprotocol/client": "2.1.0",
|
|
47
47
|
"ajv": "8.20.0",
|