@alisio/core 0.5.2 → 0.7.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.
Files changed (204) hide show
  1. package/dist/analysis/janitor.d.ts +48 -0
  2. package/dist/analysis/janitor.js +170 -18
  3. package/dist/analysis/jobs.d.ts +11 -2
  4. package/dist/analysis/jobs.js +13 -0
  5. package/dist/analysis/oci.d.ts +11 -2
  6. package/dist/analysis/oci.js +20 -4
  7. package/dist/analysis/python/sources.js +2 -1
  8. package/dist/analysis/rerun.d.ts +1 -1
  9. package/dist/analysis/status.d.ts +3 -1
  10. package/dist/analysis/status.js +3 -0
  11. package/dist/application.d.ts +28 -2
  12. package/dist/application.js +189 -4
  13. package/dist/changelog/data.js +42 -0
  14. package/dist/config.d.ts +110 -2
  15. package/dist/config.js +188 -6
  16. package/dist/core/call-internals.d.ts +22 -0
  17. package/dist/core/call-internals.js +9 -0
  18. package/dist/core/contracts.d.ts +2 -2
  19. package/dist/core/runner.js +111 -38
  20. package/dist/eval/canonical.d.ts +14 -0
  21. package/dist/eval/canonical.js +40 -0
  22. package/dist/eval/cassette.d.ts +180 -0
  23. package/dist/eval/cassette.js +424 -0
  24. package/dist/eval/compare.d.ts +147 -0
  25. package/dist/eval/compare.js +436 -0
  26. package/dist/eval/engine.d.ts +144 -0
  27. package/dist/eval/engine.js +728 -0
  28. package/dist/eval/export.d.ts +40 -0
  29. package/dist/eval/export.js +90 -0
  30. package/dist/eval/fingerprint.d.ts +88 -0
  31. package/dist/eval/fingerprint.js +186 -0
  32. package/dist/eval/normalize.d.ts +68 -0
  33. package/dist/eval/normalize.js +138 -0
  34. package/dist/eval/paths.d.ts +11 -0
  35. package/dist/eval/paths.js +50 -0
  36. package/dist/eval/plan.d.ts +26 -0
  37. package/dist/eval/plan.js +44 -0
  38. package/dist/eval/report.d.ts +53 -0
  39. package/dist/eval/report.js +258 -0
  40. package/dist/eval/run-suite.d.ts +68 -0
  41. package/dist/eval/run-suite.js +94 -0
  42. package/dist/eval/secrets.d.ts +12 -0
  43. package/dist/eval/secrets.js +45 -0
  44. package/dist/eval/stats.d.ts +17 -0
  45. package/dist/eval/stats.js +50 -0
  46. package/dist/eval/suite.d.ts +20 -0
  47. package/dist/eval/suite.js +165 -0
  48. package/dist/eval/variants.d.ts +35 -0
  49. package/dist/eval/variants.js +137 -0
  50. package/dist/eval/verifiers.d.ts +49 -0
  51. package/dist/eval/verifiers.js +264 -0
  52. package/dist/forge/broker.d.ts +51 -0
  53. package/dist/forge/broker.js +244 -0
  54. package/dist/forge/budget.d.ts +27 -0
  55. package/dist/forge/budget.js +60 -0
  56. package/dist/forge/catalog-view.d.ts +24 -0
  57. package/dist/forge/catalog-view.js +43 -0
  58. package/dist/forge/errors.d.ts +26 -0
  59. package/dist/forge/errors.js +49 -0
  60. package/dist/forge/guides/javascript.d.ts +25 -0
  61. package/dist/forge/guides/javascript.js +90 -0
  62. package/dist/forge/guides/provider.d.ts +42 -0
  63. package/dist/forge/guides/provider.js +96 -0
  64. package/dist/forge/guides/python-environment.d.ts +15 -0
  65. package/dist/forge/guides/python-environment.js +81 -0
  66. package/dist/forge/guides/python.d.ts +38 -0
  67. package/dist/forge/guides/python.js +170 -0
  68. package/dist/forge/javascript/engine.d.ts +44 -0
  69. package/dist/forge/javascript/engine.js +229 -0
  70. package/dist/forge/javascript/hosts/factory.d.ts +25 -0
  71. package/dist/forge/javascript/hosts/factory.js +21 -0
  72. package/dist/forge/javascript/hosts/framing.d.ts +17 -0
  73. package/dist/forge/javascript/hosts/framing.js +49 -0
  74. package/dist/forge/javascript/hosts/isolate-main.d.ts +9 -0
  75. package/dist/forge/javascript/hosts/isolate-main.js +63 -0
  76. package/dist/forge/javascript/hosts/process-entry.d.ts +1 -0
  77. package/dist/forge/javascript/hosts/process-entry.js +3 -0
  78. package/dist/forge/javascript/hosts/process-host.d.ts +33 -0
  79. package/dist/forge/javascript/hosts/process-host.js +160 -0
  80. package/dist/forge/javascript/hosts/worker-entry.d.ts +1 -0
  81. package/dist/forge/javascript/hosts/worker-entry.js +3 -0
  82. package/dist/forge/javascript/hosts/worker-host.d.ts +27 -0
  83. package/dist/forge/javascript/hosts/worker-host.js +116 -0
  84. package/dist/forge/javascript/prelude.d.ts +2 -0
  85. package/dist/forge/javascript/prelude.js +333 -0
  86. package/dist/forge/javascript/probe.d.ts +2 -0
  87. package/dist/forge/javascript/probe.js +53 -0
  88. package/dist/forge/javascript/runtime-manifest.d.ts +14 -0
  89. package/dist/forge/javascript/runtime-manifest.js +14 -0
  90. package/dist/forge/javascript/vm-runner.d.ts +21 -0
  91. package/dist/forge/javascript/vm-runner.js +301 -0
  92. package/dist/forge/javascript/wasm-asset.d.ts +8 -0
  93. package/dist/forge/javascript/wasm-asset.js +8 -0
  94. package/dist/forge/javascript/wasm.d.ts +15 -0
  95. package/dist/forge/javascript/wasm.js +54 -0
  96. package/dist/forge/journal.d.ts +105 -0
  97. package/dist/forge/journal.js +174 -0
  98. package/dist/forge/kernel.d.ts +76 -0
  99. package/dist/forge/kernel.js +339 -0
  100. package/dist/forge/limits.d.ts +38 -0
  101. package/dist/forge/limits.js +73 -0
  102. package/dist/forge/nested.d.ts +96 -0
  103. package/dist/forge/nested.js +214 -0
  104. package/dist/forge/ports.d.ts +120 -0
  105. package/dist/forge/ports.js +1 -0
  106. package/dist/forge/progress.d.ts +24 -0
  107. package/dist/forge/progress.js +76 -0
  108. package/dist/forge/protocol.d.ts +110 -0
  109. package/dist/forge/protocol.js +171 -0
  110. package/dist/forge/python/backend-selector.d.ts +38 -0
  111. package/dist/forge/python/backend-selector.js +111 -0
  112. package/dist/forge/python/backend.d.ts +89 -0
  113. package/dist/forge/python/backend.js +1 -0
  114. package/dist/forge/python/engine.d.ts +84 -0
  115. package/dist/forge/python/engine.js +279 -0
  116. package/dist/forge/python/frame-parser.d.ts +24 -0
  117. package/dist/forge/python/frame-parser.js +107 -0
  118. package/dist/forge/python/frame-transport.d.ts +40 -0
  119. package/dist/forge/python/frame-transport.js +101 -0
  120. package/dist/forge/python/import-scan.d.ts +22 -0
  121. package/dist/forge/python/import-scan.js +133 -0
  122. package/dist/forge/python/managed-backend.d.ts +30 -0
  123. package/dist/forge/python/managed-backend.js +111 -0
  124. package/dist/forge/python/oci-backend.d.ts +27 -0
  125. package/dist/forge/python/oci-backend.js +84 -0
  126. package/dist/forge/python/pyodide-backend.d.ts +40 -0
  127. package/dist/forge/python/pyodide-backend.js +289 -0
  128. package/dist/forge/python/pyodide-install.d.ts +41 -0
  129. package/dist/forge/python/pyodide-install.js +167 -0
  130. package/dist/forge/python/pyodide-manifest.d.ts +42 -0
  131. package/dist/forge/python/pyodide-manifest.js +376 -0
  132. package/dist/forge/python/pyodide-runner-source.d.ts +2 -0
  133. package/dist/forge/python/pyodide-runner-source.js +3 -0
  134. package/dist/forge/python/rss.d.ts +2 -0
  135. package/dist/forge/python/rss.js +33 -0
  136. package/dist/forge/python/scratch-copy.d.ts +6 -0
  137. package/dist/forge/python/scratch-copy.js +61 -0
  138. package/dist/forge/python/settle.d.ts +6 -0
  139. package/dist/forge/python/settle.js +21 -0
  140. package/dist/forge/python/status.d.ts +8 -0
  141. package/dist/forge/python/status.js +15 -0
  142. package/dist/forge/python/wasm-node.d.ts +59 -0
  143. package/dist/forge/python/wasm-node.js +220 -0
  144. package/dist/forge/python/wiring.d.ts +39 -0
  145. package/dist/forge/python/wiring.js +47 -0
  146. package/dist/forge/registry.d.ts +26 -0
  147. package/dist/forge/registry.js +83 -0
  148. package/dist/forge/result.d.ts +58 -0
  149. package/dist/forge/result.js +86 -0
  150. package/dist/forge/routing-rule.d.ts +19 -0
  151. package/dist/forge/routing-rule.js +31 -0
  152. package/dist/forge/runtime.d.ts +64 -0
  153. package/dist/forge/runtime.js +88 -0
  154. package/dist/forge/scratch-files.d.ts +36 -0
  155. package/dist/forge/scratch-files.js +94 -0
  156. package/dist/forge/scratch-provider.d.ts +41 -0
  157. package/dist/forge/scratch-provider.js +79 -0
  158. package/dist/forge/scratch-quota.d.ts +8 -0
  159. package/dist/forge/scratch-quota.js +25 -0
  160. package/dist/forge/selector.d.ts +35 -0
  161. package/dist/forge/selector.js +51 -0
  162. package/dist/forge/session-dir.d.ts +59 -0
  163. package/dist/forge/session-dir.js +239 -0
  164. package/dist/forge/session-path.d.ts +31 -0
  165. package/dist/forge/session-path.js +61 -0
  166. package/dist/forge/slots.d.ts +16 -0
  167. package/dist/forge/slots.js +47 -0
  168. package/dist/forge/state-delta.d.ts +54 -0
  169. package/dist/forge/state-delta.js +99 -0
  170. package/dist/forge/state-machine.d.ts +20 -0
  171. package/dist/forge/state-machine.js +40 -0
  172. package/dist/forge/state-store.d.ts +25 -0
  173. package/dist/forge/state-store.js +50 -0
  174. package/dist/forge/task-log.d.ts +9 -0
  175. package/dist/forge/task-log.js +42 -0
  176. package/dist/forge/verdict.d.ts +20 -0
  177. package/dist/forge/verdict.js +28 -0
  178. package/dist/forge-isolate.d.ts +6 -0
  179. package/dist/forge-isolate.js +6 -0
  180. package/dist/index.d.ts +23 -1
  181. package/dist/index.js +23 -1
  182. package/dist/permissions/decide.d.ts +42 -0
  183. package/dist/permissions/decide.js +24 -0
  184. package/dist/plan/exit-plan.js +2 -1
  185. package/dist/resources/context.d.ts +2 -1
  186. package/dist/resources/context.js +2 -1
  187. package/dist/runtime/access.d.ts +34 -0
  188. package/dist/runtime/access.js +80 -0
  189. package/dist/runtime/ids.d.ts +21 -0
  190. package/dist/runtime/ids.js +41 -1
  191. package/dist/runtime/process.d.ts +2 -1
  192. package/dist/runtime/process.js +5 -5
  193. package/dist/runtime/store.d.ts +11 -0
  194. package/dist/runtime/store.js +106 -2
  195. package/dist/tools/analysis.d.ts +25 -2
  196. package/dist/tools/analysis.js +315 -267
  197. package/dist/tools/execute.js +6 -7
  198. package/dist/tools/forge-guide.d.ts +6 -0
  199. package/dist/tools/forge-guide.js +60 -0
  200. package/dist/tools/js-run.d.ts +19 -0
  201. package/dist/tools/js-run.js +126 -0
  202. package/dist/tools/standard.d.ts +7 -1
  203. package/dist/tools/standard.js +44 -43
  204. package/package.json +7 -2
@@ -21,11 +21,9 @@
21
21
  */
22
22
  import { createContext, Script } from "node:vm";
23
23
  import { OPT_IN_TOOLS } from "../core/opt-in.js";
24
+ import { decideEffect, effectOf } from "../permissions/decide.js";
24
25
  export const EXECUTE_TIMEOUT_MS = 10_000;
25
26
  export const MAX_NESTED_CALLS = 20;
26
- const allowedEffect = (policy, effect) => effect === "read" ||
27
- effect === "internal" ||
28
- ((effect === "write" || effect === "process" || effect === "external") && !!policy[effect]);
29
27
  export async function runExecute(code, deps) {
30
28
  let calls = 0;
31
29
  const controller = new AbortController();
@@ -47,10 +45,11 @@ export async function runExecute(code, deps) {
47
45
  // Opt-in tools (`exit_plan`) belong to the run that lists them, never to a nested call.
48
46
  if (OPT_IN_TOOLS.has(name))
49
47
  throw new Error(`callTool: "${name}" is not available from execute`);
50
- const effect = tool.effect ?? "external";
51
- // A capability tool is reachable only when already allowed (never through a persisted grant).
52
- if (!allowedEffect(deps.policy, effect) &&
53
- !(tool.capability === "analysis.run" && deps.policy.analysis))
48
+ const effect = effectOf(tool);
49
+ // A capability tool is reachable only when already allowed (never through a persisted grant),
50
+ // and a nested call never opens an approval: anything but an allow is a denial.
51
+ if (decideEffect(deps.policy, tool, { approvals: false, capabilityGranted: false }).kind !==
52
+ "allow")
54
53
  throw new Error(`callTool: capability denied for "${name}" (effect: ${effect}); execute cannot request ` +
55
54
  "new approvals, only use capabilities already allowed for this session");
56
55
  const rawInput = input && typeof input === "object" ? input : {};
@@ -0,0 +1,6 @@
1
+ import type { ToolRegistry } from "../core/registry.ts";
2
+ import { type GuideProvider } from "../forge/guides/provider.ts";
3
+ /** Hard bound of the description (spec §8.2 budget). */
4
+ export declare const FORGE_GUIDE_DESCRIPTION_MAX_CHARS = 300;
5
+ export declare function forgeGuideDescription(topics: readonly string[]): string;
6
+ export declare function registerForgeGuide(registry: ToolRegistry, provider: GuideProvider): void;
@@ -0,0 +1,60 @@
1
+ import { callInternals } from "../core/call-internals.js";
2
+ import { UnknownGuideTopicError } from "../forge/guides/provider.js";
3
+ import { effectOf } from "../permissions/decide.js";
4
+ /** Hard bound of the description (spec §8.2 budget). */
5
+ export const FORGE_GUIDE_DESCRIPTION_MAX_CHARS = 300;
6
+ export function forgeGuideDescription(topics) {
7
+ const js = topics.some((topic) => topic.startsWith("js."));
8
+ const py = topics.some((topic) => topic.startsWith("python."));
9
+ const about = [js ? "js_run" : "", py ? "python_run" : ""].filter(Boolean).join(" and ");
10
+ return (`Live guidance for ${about}: limits, tools, backends, packages. Call it before writing ` +
11
+ "non-trivial code. Send ifNoneMatch=etag to skip unchanged content.");
12
+ }
13
+ const fail = (text) => ({ content: [{ type: "text", text }], isError: true });
14
+ export function registerForgeGuide(registry, provider) {
15
+ const topics = provider.topics();
16
+ if (topics.length === 0)
17
+ return;
18
+ const tool = {
19
+ name: "forge_guide",
20
+ effect: "read",
21
+ concurrent: true,
22
+ description: forgeGuideDescription(topics),
23
+ inputSchema: {
24
+ type: "object",
25
+ additionalProperties: false,
26
+ required: ["topic"],
27
+ properties: {
28
+ topic: { type: "string", enum: [...topics] },
29
+ ifNoneMatch: { type: "string", maxLength: 64 },
30
+ },
31
+ },
32
+ async execute(input, context) {
33
+ const topic = String(input.topic);
34
+ const catalog = topic === "js.tools"
35
+ ? callInternals(context)
36
+ ?.nested()
37
+ .catalog()
38
+ .map((candidate) => ({ name: candidate.name, effect: effectOf(candidate) }))
39
+ : undefined;
40
+ try {
41
+ const answer = await provider.get({
42
+ topic,
43
+ ...(typeof input.ifNoneMatch === "string" ? { ifNoneMatch: input.ifNoneMatch } : {}),
44
+ catalog,
45
+ });
46
+ if (answer.unchanged)
47
+ return { content: [{ type: "text", text: JSON.stringify(answer) }] };
48
+ return {
49
+ content: [{ type: "text", text: `etag: ${answer.etag}\n\n${answer.content}` }],
50
+ };
51
+ }
52
+ catch (error) {
53
+ if (error instanceof UnknownGuideTopicError)
54
+ return fail(error.message);
55
+ throw error;
56
+ }
57
+ },
58
+ };
59
+ registry.register(tool);
60
+ }
@@ -0,0 +1,19 @@
1
+ import { type ToolResult } from "@alisio/sdk";
2
+ import type { Policy } from "../core/contracts.ts";
3
+ import type { ToolRegistry } from "../core/registry.ts";
4
+ import type { ExecutionKernel } from "../forge/kernel.ts";
5
+ import { type ForgeResult } from "../forge/result.ts";
6
+ export declare const JS_RUN_DESCRIPTION: string;
7
+ export declare const CODE_PATH_MAX_BYTES: number;
8
+ export interface JsRunDeps {
9
+ kernel: ExecutionKernel;
10
+ /** The root session of a session (its scratchpad, state and slots are keyed by it). */
11
+ rootOf: (session: string) => string;
12
+ /** The verified scratchpad of a session; undefined while there is none (or under --read-only). */
13
+ scratchpad: (session: string) => Promise<string | undefined>;
14
+ /** The run's live policy, to label the permission mode and the `--allow-*` flags. */
15
+ policy: () => Policy;
16
+ }
17
+ export declare function registerJsRun(registry: ToolRegistry, deps: JsRunDeps): void;
18
+ /** The ForgeResult as the model sees it. */
19
+ export declare function toToolResult(result: ForgeResult, maxChars: number): ToolResult;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * `js_run` (spec §6.1, D6, D-23, D-30): runs a JavaScript script in the isolated QuickJS-WASM
3
+ * engine. It is a thin adapter: it validates the input, reads `codePath` from the scratchpad and
4
+ * hands everything to the `ExecutionKernel`, then renders the `ForgeResult` for the model.
5
+ *
6
+ * Declared `read` and `concurrent: false` on purpose. The script has no effects of its own; every
7
+ * effect happens in a nested call that passes the run's gate with ITS effect (D7). A run's
8
+ * parallel read batch skips this tool (D-23a), and the kernel allows one active execution per
9
+ * session. The nested-call port and the "left effects behind" flag come from the runner through
10
+ * `callInternals`, which plugin tools cannot reach.
11
+ */
12
+ import { readFile, stat } from "node:fs/promises";
13
+ import { dirname, extname, join } from "node:path";
14
+ import { callInternals } from "../core/call-internals.js";
15
+ import { ForgeError, isForgeError } from "../forge/errors.js";
16
+ import { MAX_FORGE_LIMITS } from "../forge/limits.js";
17
+ import { renderForgeResult } from "../forge/result.js";
18
+ import { resolveScratchPath } from "../forge/session-dir.js";
19
+ export const JS_RUN_DESCRIPTION = "Run JavaScript in an isolated QuickJS-WASM VM to combine tools and shape results (tools.call, " +
20
+ "catalog, parallel, state; `return` a small JSON value). No imports, network or files; nested " +
21
+ "calls use this session's permissions and never ask. Pass `code` or `codePath` (.js in the " +
22
+ "scratchpad). See forge_guide.";
23
+ export const CODE_PATH_MAX_BYTES = 64 * 1024;
24
+ const modeOf = (policy) => policy.write && policy.process && policy.external ? "full" : policy.write ? "auto" : "ask";
25
+ const invalid = (message) => ({
26
+ content: [{ type: "text", text: `INVALID_INPUT: ${message}` }],
27
+ isError: true,
28
+ });
29
+ /** The script's source from `code` or `codePath` (exactly one), or a reason it cannot be had. */
30
+ async function sourceOf(input, deps, session) {
31
+ const hasCode = input.code !== undefined;
32
+ const hasPath = input.codePath !== undefined;
33
+ if (hasCode === hasPath)
34
+ throw new ForgeError("INVALID_INPUT", "pass exactly one of `code` or `codePath`");
35
+ if (hasCode) {
36
+ if (typeof input.code !== "string")
37
+ throw new ForgeError("INVALID_INPUT", "`code` must be a string");
38
+ return input.code;
39
+ }
40
+ if (typeof input.codePath !== "string" || input.codePath === "")
41
+ throw new ForgeError("INVALID_INPUT", "`codePath` must be a non-empty string");
42
+ const pad = await deps.scratchpad(session);
43
+ if (!pad)
44
+ throw new ForgeError("INVALID_INPUT", "`codePath` needs the session scratchpad, which is not available");
45
+ const path = await resolveScratchPath(pad, input.codePath);
46
+ if (![".js", ".mjs"].includes(extname(path)))
47
+ throw new ForgeError("INVALID_INPUT", "`codePath` must be a .js file inside the scratchpad");
48
+ const info = await stat(path).catch(() => undefined);
49
+ if (!info?.isFile())
50
+ throw new ForgeError("INVALID_INPUT", `\`codePath\` does not name a file: ${input.codePath}`);
51
+ if (info.size > CODE_PATH_MAX_BYTES)
52
+ throw new ForgeError("INVALID_INPUT", `\`codePath\` file is larger than ${CODE_PATH_MAX_BYTES} bytes`);
53
+ return readFile(path, "utf8");
54
+ }
55
+ export function registerJsRun(registry, deps) {
56
+ const tool = {
57
+ name: "js_run",
58
+ effect: "read",
59
+ concurrent: false,
60
+ description: JS_RUN_DESCRIPTION,
61
+ inputSchema: {
62
+ type: "object",
63
+ additionalProperties: false,
64
+ properties: {
65
+ code: { type: "string", minLength: 1, maxLength: 65536 },
66
+ title: { type: "string", maxLength: 120 },
67
+ timeoutMs: { type: "integer", minimum: 100 },
68
+ codePath: { type: "string", maxLength: 1024 },
69
+ },
70
+ },
71
+ async execute(input, context) {
72
+ const internals = callInternals(context);
73
+ const session = context.session;
74
+ if (!internals || !session || !context.callId)
75
+ return invalid("js_run can only run inside an agent run");
76
+ let source;
77
+ try {
78
+ source = await sourceOf(input, deps, session);
79
+ }
80
+ catch (error) {
81
+ if (isForgeError(error))
82
+ return invalid(error.message);
83
+ throw error;
84
+ }
85
+ const policy = deps.policy();
86
+ const rootSessionId = deps.rootOf(session);
87
+ const pad = await deps.scratchpad(session).catch(() => undefined);
88
+ const outcome = await deps.kernel.execute({
89
+ source,
90
+ ...(typeof input.title === "string" ? { title: input.title } : {}),
91
+ ...(typeof input.timeoutMs === "number"
92
+ ? {
93
+ requestedLimits: { timeoutMs: Math.min(input.timeoutMs, MAX_FORGE_LIMITS.timeoutMs) },
94
+ }
95
+ : {}),
96
+ call: {
97
+ toolCallId: context.callId,
98
+ session,
99
+ rootSessionId,
100
+ workspace: context.workspace,
101
+ signal: context.signal,
102
+ permissionMode: modeOf(policy),
103
+ allowAnalysis: !!policy.analysis,
104
+ allowProcess: policy.process,
105
+ dispatcher: internals.nested(),
106
+ emit: (progress) => context.emit(progress),
107
+ ...(pad ? { tasksDir: join(dirname(pad), "tasks") } : {}),
108
+ },
109
+ });
110
+ // A cancelled run ends by throwing, like any tool interrupted by the user: the runner then keeps
111
+ // a call that left effects pending for recovery (D-23b) and closes one that only read.
112
+ if (outcome.result.status === "cancelled")
113
+ throw new Error("js_run was cancelled");
114
+ return toToolResult(outcome.result, outcome.maxOutputChars);
115
+ },
116
+ };
117
+ registry.register(tool);
118
+ }
119
+ /** The ForgeResult as the model sees it. */
120
+ export function toToolResult(result, maxChars) {
121
+ const rendered = renderForgeResult(result, maxChars);
122
+ return {
123
+ content: [{ type: "text", text: rendered.text }],
124
+ ...(result.status === "succeeded" ? {} : { isError: true }),
125
+ };
126
+ }
@@ -22,4 +22,10 @@ execCtx?: {
22
22
  } | undefined;
23
23
  },
24
24
  /** Mediated path policy (workspace plus declared extra directories and external approval). */
25
- pathAccess?: PathAccess): void;
25
+ pathAccess?: PathAccess, features?: {
26
+ /**
27
+ * Register the legacy `execute` tool (default true). Forja's `js_run` replaces it (spec D6):
28
+ * the host turns this off while the JavaScript engine is enabled, with no fallback to `node:vm`.
29
+ */
30
+ execute?: boolean;
31
+ }): void;
@@ -99,7 +99,7 @@ export function registerStandard(registry, workspace, skills, context, ui,
99
99
  /** Live policy accessor for `execute`'s nested tool calls (bound once the runner exists). */
100
100
  execCtx, websearchCtx,
101
101
  /** Mediated path policy (workspace plus declared extra directories and external approval). */
102
- pathAccess) {
102
+ pathAccess, features = {}) {
103
103
  const register = (tool) => registry.register(tool);
104
104
  /**
105
105
  * Resolves one tool path through the host-injected policy when present, then the captured
@@ -455,46 +455,47 @@ pathAccess) {
455
455
  }));
456
456
  },
457
457
  });
458
- register({
459
- name: "execute",
460
- effect: "process",
461
- description: "Run a short JavaScript snippet (Code Mode) that can call other tools via " +
462
- "`await callTool(name, input)` and return one final JSON-serializable value, so " +
463
- "intermediate tool results never re-enter your context — only the return value does. " +
464
- "The snippet runs in an isolated context with no filesystem, network, imports or timers " +
465
- "of its own; every nested callTool still goes through this session's normal permission " +
466
- `gate (an effect not already allowed is denied, never newly approved). Bounded to ` +
467
- `${EXECUTE_TIMEOUT_MS}ms wall-clock and ${MAX_NESTED_CALLS} nested tool calls. This is a ` +
468
- "correctness sandbox, not an OS security boundary. Prefer plain sequential tool calls " +
469
- "unless you are combining several read-only calls or post-processing their output.",
470
- inputSchema: objectSchema({ code: { type: "string", minLength: 1 } }, ["code"]),
471
- async execute(i, c) {
472
- const policy = execCtx?.policy() ?? { write: false, process: false, external: false };
473
- const result = await runExecute(String(i.code), {
474
- registry,
475
- policy,
476
- workspace: c.workspace,
477
- signal: c.signal,
478
- emit: c.emit,
479
- ...(c.session ? { session: c.session } : {}),
480
- ...(c.label ? { label: c.label } : {}),
481
- // Nested calls must never open a new external-directory prompt; only directories already
482
- // approved for this session (or inside the workspace) are reachable.
483
- ...(pathAccess
484
- ? {
485
- resolvePath: (path) => pathAccess.resolve(path, {
486
- workspace: c.workspace,
487
- ...(c.session ? { session: c.session } : {}),
488
- ...(c.label ? { label: c.label } : {}),
489
- signal: c.signal,
490
- interactive: false,
491
- }),
492
- }
493
- : c.resolvePath
494
- ? { resolvePath: c.resolvePath }
495
- : {}),
496
- });
497
- return textResult(JSON.stringify({ result: result ?? null }));
498
- },
499
- });
458
+ if (features.execute !== false)
459
+ register({
460
+ name: "execute",
461
+ effect: "process",
462
+ description: "Run a short JavaScript snippet (Code Mode) that can call other tools via " +
463
+ "`await callTool(name, input)` and return one final JSON-serializable value, so " +
464
+ "intermediate tool results never re-enter your context — only the return value does. " +
465
+ "The snippet runs in an isolated context with no filesystem, network, imports or timers " +
466
+ "of its own; every nested callTool still goes through this session's normal permission " +
467
+ `gate (an effect not already allowed is denied, never newly approved). Bounded to ` +
468
+ `${EXECUTE_TIMEOUT_MS}ms wall-clock and ${MAX_NESTED_CALLS} nested tool calls. This is a ` +
469
+ "correctness sandbox, not an OS security boundary. Prefer plain sequential tool calls " +
470
+ "unless you are combining several read-only calls or post-processing their output.",
471
+ inputSchema: objectSchema({ code: { type: "string", minLength: 1 } }, ["code"]),
472
+ async execute(i, c) {
473
+ const policy = execCtx?.policy() ?? { write: false, process: false, external: false };
474
+ const result = await runExecute(String(i.code), {
475
+ registry,
476
+ policy,
477
+ workspace: c.workspace,
478
+ signal: c.signal,
479
+ emit: c.emit,
480
+ ...(c.session ? { session: c.session } : {}),
481
+ ...(c.label ? { label: c.label } : {}),
482
+ // Nested calls must never open a new external-directory prompt; only directories already
483
+ // approved for this session (or inside the workspace) are reachable.
484
+ ...(pathAccess
485
+ ? {
486
+ resolvePath: (path) => pathAccess.resolve(path, {
487
+ workspace: c.workspace,
488
+ ...(c.session ? { session: c.session } : {}),
489
+ ...(c.label ? { label: c.label } : {}),
490
+ signal: c.signal,
491
+ interactive: false,
492
+ }),
493
+ }
494
+ : c.resolvePath
495
+ ? { resolvePath: c.resolvePath }
496
+ : {}),
497
+ });
498
+ return textResult(JSON.stringify({ result: result ?? null }));
499
+ },
500
+ });
500
501
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alisio/core",
3
- "version": "0.5.2",
3
+ "version": "0.7.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",
@@ -28,6 +28,10 @@
28
28
  ".": {
29
29
  "types": "./dist/index.d.ts",
30
30
  "import": "./dist/index.js"
31
+ },
32
+ "./forge-isolate": {
33
+ "types": "./dist/forge-isolate.d.ts",
34
+ "import": "./dist/forge-isolate.js"
31
35
  }
32
36
  },
33
37
  "types": "./dist/index.d.ts",
@@ -41,10 +45,11 @@
41
45
  "node": ">=22.16"
42
46
  },
43
47
  "dependencies": {
44
- "@alisio/sdk": "0.5.2",
48
+ "@alisio/sdk": "0.7.0",
45
49
  "@mixmark-io/domino": "2.2.0",
46
50
  "@modelcontextprotocol/client": "2.1.0",
47
51
  "ajv": "8.20.0",
52
+ "quickjs-wasi": "3.6.2",
48
53
  "turndown": "7.2.4",
49
54
  "yaml": "2.9.1",
50
55
  "zod": "4.6.5"