@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
@@ -16,7 +16,7 @@ export interface RerunPlan {
16
16
  manifest: unknown[];
17
17
  extras: Array<"analysis" | "science">;
18
18
  /** The runtime of the original execution (a rerun uses the configured runtime now). */
19
- runtime: "managed" | "oci";
19
+ runtime: "managed" | "oci" | "wasm";
20
20
  }
21
21
  export type RerunPreparation = {
22
22
  ok: true;
@@ -3,7 +3,7 @@
3
3
  * Settings page: mode, interpreter and extras, container engine (probed only when it matters),
4
4
  * limits and retention. One shape (`AnalysisStatus` of the SDK) for every surface.
5
5
  */
6
- import type { AnalysisStatus } from "@alisio/sdk";
6
+ import type { AnalysisStatus, EngineDescriptor } from "@alisio/sdk";
7
7
  import type { Config } from "../config.ts";
8
8
  import type { AnalysisJanitor } from "./janitor.ts";
9
9
  import type { OciRuntime } from "./oci.ts";
@@ -14,6 +14,8 @@ export declare function analysisStatus(input: {
14
14
  /** Builds the container runtime of the current settings (probed on demand). */
15
15
  oci?: () => OciRuntime;
16
16
  janitor?: Pick<AnalysisJanitor, "lastSweep">;
17
+ /** The live state of the Python backends (isolation, readiness, profiles); omitted = no table. */
18
+ backends?: (signal?: AbortSignal) => Promise<EngineDescriptor[]>;
17
19
  /** python_run is registered (analysis enabled and not `--read-only`). */
18
20
  enabled: boolean;
19
21
  readOnly: boolean;
@@ -40,10 +40,13 @@ export async function analysisStatus(input) {
40
40
  oci.reason = status.reason;
41
41
  }
42
42
  const lastSweep = await input.janitor?.lastSweep();
43
+ const backends = input.backends ? await input.backends(input.signal) : undefined;
43
44
  return {
44
45
  enabled: input.enabled,
45
46
  readOnly: input.readOnly,
46
47
  mode: config.runtime,
48
+ isolation: config.isolation,
49
+ ...(backends ? { backends } : {}),
47
50
  python,
48
51
  oci,
49
52
  limits: { timeoutMs: config.limits.timeoutMs },
@@ -2,7 +2,7 @@ import type { AvailableProviderModel, ModelInfo, ModelProvider, Plugin, PluginCa
2
2
  import { AgentDefinitionService } from "./agents/service.ts";
3
3
  import { CapabilityGrants } from "./analysis/capabilities.ts";
4
4
  import { DatasetService } from "./analysis/data/datasets.ts";
5
- import { AnalysisJanitor } from "./analysis/janitor.ts";
5
+ import { AnalysisJanitor, ScratchJanitor } from "./analysis/janitor.ts";
6
6
  import { AnalysisJobs } from "./analysis/jobs.ts";
7
7
  import { OciRuntime } from "./analysis/oci.ts";
8
8
  import { AnalysisRerun } from "./analysis/rerun.ts";
@@ -15,6 +15,8 @@ import type { ApprovalHandler } from "./core/contracts.ts";
15
15
  import { ToolRegistry } from "./core/registry.ts";
16
16
  import { AgentRunner } from "./core/runner.ts";
17
17
  import { DecisionMetrics, DecisionRegistry, DecisionService } from "./decisions/index.ts";
18
+ import { SqliteExecutionJournal } from "./forge/journal.ts";
19
+ import { SqliteStateStore } from "./forge/state-store.ts";
18
20
  import { GoalService } from "./goal/service.ts";
19
21
  import { HerdrBridge } from "./integrations/herdr.ts";
20
22
  import { McpConnector } from "./mcp/connector.ts";
@@ -188,12 +190,36 @@ export declare function createApplication(options?: AppOptions): Promise<{
188
190
  jobs: AnalysisJobs;
189
191
  /** Container runtime of the current configuration (status, pull); mode `managed` ignores it. */
190
192
  oci: {
191
- mode: () => "managed" | "oci";
193
+ mode: () => "auto" | "managed" | "oci" | "wasm";
192
194
  runtime: () => OciRuntime;
193
195
  };
196
+ /** The live state of every Python backend (isolation, ready, profiles, reason). */
197
+ backends: (signal?: AbortSignal) => Promise<import("@alisio/sdk").EngineDescriptor[]>;
194
198
  rerun: AnalysisRerun;
195
199
  janitor: AnalysisJanitor;
196
200
  };
201
+ /**
202
+ * Forja services for the engines (Phase 3b): the execution journal, the cross-execution
203
+ * state, the scratchpad of a session (undefined while the JavaScript engine is off) and the
204
+ * retention sweep of the volatile session folders.
205
+ */
206
+ forge: {
207
+ journal: SqliteExecutionJournal;
208
+ state: SqliteStateStore;
209
+ /** The scratchpad, created on first use; under the same gate as the write root. */
210
+ scratchpad: ((session: string) => Promise<string | undefined>) | undefined;
211
+ /**
212
+ * What the HTTP surface sees (L2): `available` follows the gate of the write root (engine
213
+ * verified, not `--read-only`); the folder is only looked up, never created.
214
+ */
215
+ scratchpadView: ((session: string) => Promise<{
216
+ available: boolean;
217
+ path?: string;
218
+ }>) | undefined;
219
+ scratchJanitor: ScratchJanitor;
220
+ /** The JavaScript engine: descriptor and effective limits (`alisio forge status`), probe. */
221
+ runtime: import("./forge/runtime.ts").ForgeRuntime;
222
+ };
197
223
  /** Keys a project layer set that only the user layer decides (ignored on load). */
198
224
  configDiagnostics: string[];
199
225
  /** Tabular datasets of every session (ingestion, read-only queries, pages). */
@@ -4,7 +4,7 @@ import { dirname, join, resolve } from "node:path";
4
4
  import { AgentDefinitionService } from "./agents/service.js";
5
5
  import { CapabilityGrants } from "./analysis/capabilities.js";
6
6
  import { DatasetService } from "./analysis/data/datasets.js";
7
- import { AnalysisJanitor } from "./analysis/janitor.js";
7
+ import { AnalysisJanitor, ScratchJanitor } from "./analysis/janitor.js";
8
8
  import { AnalysisJobs } from "./analysis/jobs.js";
9
9
  import { OciRuntime } from "./analysis/oci.js";
10
10
  import { AnalysisRerun } from "./analysis/rerun.js";
@@ -21,6 +21,12 @@ import { DEFAULT_MAX_OUTPUT_TOKENS } from "./core/output-limit.js";
21
21
  import { ToolRegistry } from "./core/registry.js";
22
22
  import { AgentRunner } from "./core/runner.js";
23
23
  import { bindDecisions, DecisionMetrics, DecisionRegistry, DecisionService, } from "./decisions/index.js";
24
+ import { reconcileInterruptedExecutions, SqliteExecutionJournal } from "./forge/journal.js";
25
+ import { describePythonBackends } from "./forge/python/status.js";
26
+ import { createPythonBackends } from "./forge/python/wiring.js";
27
+ import { createForgeRuntime } from "./forge/runtime.js";
28
+ import { createScratchpadLookup, createScratchpadProvider, scratchSweepRoots, } from "./forge/scratch-provider.js";
29
+ import { SqliteStateStore } from "./forge/state-store.js";
24
30
  import { GoalService } from "./goal/service.js";
25
31
  import { GoalStore } from "./goal/store.js";
26
32
  import { HerdrBridge } from "./integrations/herdr.js";
@@ -35,7 +41,7 @@ import { Skills, skillRoots } from "./resources/skills.js";
35
41
  import { PathAccess } from "./runtime/access.js";
36
42
  import { BlobStore } from "./runtime/blobs.js";
37
43
  import { isPathSpec } from "./runtime/modules.js";
38
- import { findWorkspace } from "./runtime/paths.js";
44
+ import { findWorkspace, workspaceKey } from "./runtime/paths.js";
39
45
  import { SQLiteStore } from "./runtime/store.js";
40
46
  import { ChildSessions } from "./sessions/children.js";
41
47
  import { SideQuestions } from "./sessions/side-questions.js";
@@ -100,6 +106,24 @@ export async function createApplication(options = {}) {
100
106
  }
101
107
  });
102
108
  const askExternalDirectory = options.approveExternalDirectory;
109
+ const customStore = !!options.db;
110
+ const scratchStateRoot = options.db && options.db !== ":memory:" ? dirname(options.db) : stateHome();
111
+ // Forja's scratchpad (spec §9.4): an authorized write root only while the JavaScript engine is
112
+ // enabled, so a build with Forja off behaves exactly as before. `store` is read lazily, on the
113
+ // first path outside the workspace.
114
+ const scratchOptions = {
115
+ rootOf: (session) => store.rootOf(session),
116
+ workspaceOf: (rootSessionId) => store.get(rootSessionId).workspace,
117
+ workspaceKey,
118
+ stateHome: scratchStateRoot,
119
+ configRoot: () => config.forge.scratch.root,
120
+ };
121
+ const scratchEnabled = config.forge.javascript.enabled && config.forge.scratch.enabled;
122
+ const scratchpad = scratchEnabled ? createScratchpadProvider(scratchOptions) : undefined;
123
+ const scratchpadLookup = scratchEnabled ? createScratchpadLookup(scratchOptions) : undefined;
124
+ // The scratchpad is a free write root only once the engine verified itself (`jsReady`, known after
125
+ // `createForgeRuntime` below): a failed self-check leaves it an ordinary directory (L9).
126
+ let scratchReady = false;
103
127
  const pathAccess = new PathAccess({
104
128
  workspace,
105
129
  extraRoots: [...config.additionalDirectories, ...addDirs],
@@ -109,8 +133,20 @@ export async function createApplication(options = {}) {
109
133
  }
110
134
  : {}),
111
135
  readOnly: !!options.readOnly,
136
+ ...(scratchpad
137
+ ? {
138
+ scratchpad: (session) => scratchReady ? scratchpad(session) : Promise.resolve(undefined),
139
+ scratchMaxBytes: () => config.forge.scratch.maxBytes,
140
+ }
141
+ : {}),
112
142
  });
113
143
  const store = new SQLiteStore(options.db ?? join(stateHome(), "sessions.sqlite"));
144
+ // Executions of a process that is gone cannot be running: close them (nothing is relaunched).
145
+ reconcileInterruptedExecutions(store.db);
146
+ const forgeJournal = new SqliteExecutionJournal(store.db);
147
+ const forgeState = new SqliteStateStore(store.db, {
148
+ maxTotalBytes: () => config.forge.state.maxTotalBytes,
149
+ });
114
150
  // Content-addressed attachment bytes next to the session database (no I/O until `put`).
115
151
  const blobs = new BlobStore({
116
152
  root: options.db && options.db !== ":memory:"
@@ -352,6 +388,14 @@ export async function createApplication(options = {}) {
352
388
  }));
353
389
  context.extras.push(async () => skills.catalog(), ...plugins.contexts);
354
390
  const { registerStandard } = await import("./tools/standard.js");
391
+ // Forja's JavaScript engine verifies itself here (the WASM against its pinned hash). Enabled
392
+ // means `js_run` replaces `execute` for good: a failed check leaves neither (no fallback).
393
+ const forgeRuntime = await createForgeRuntime({
394
+ forge: () => config.forge,
395
+ journal: forgeJournal,
396
+ state: forgeState,
397
+ });
398
+ scratchReady = forgeRuntime.jsReady;
355
399
  // `runner` is referenced lazily (constructed just below): safe because these accessors only
356
400
  // run once the tool is actually called, well after the runner exists.
357
401
  registerStandard(registry, workspace, skills, context, plugins.ui, {
@@ -359,7 +403,27 @@ export async function createApplication(options = {}) {
359
403
  }, {
360
404
  config: config.websearch,
361
405
  resolveExtension: () => plugins.extensions.resolve("websearch"),
362
- }, pathAccess);
406
+ }, pathAccess, { execute: !config.forge.javascript.enabled });
407
+ // `--read-only` has no scratchpad root, so `codePath` is unavailable there.
408
+ const sessionScratchpad = options.readOnly ? async () => undefined : scratchpad;
409
+ if (forgeRuntime.jsReady) {
410
+ const { registerJsRun } = await import("./tools/js-run.js");
411
+ const { buildRoutingRule } = await import("./forge/routing-rule.js");
412
+ registerJsRun(registry, {
413
+ kernel: forgeRuntime.kernel,
414
+ rootOf: (session) => store.rootOf(session),
415
+ scratchpad: async (session) => sessionScratchpad?.(session),
416
+ policy: () => runner.policy,
417
+ });
418
+ // Layer 0 of the context (spec §8.1): present only because `js_run` is, per session.
419
+ context.extras.push(async (sessionId) => buildRoutingRule({
420
+ javascript: true,
421
+ python: registry.list().some((tool) => tool.name === "python_run"),
422
+ // Registered later (after `python_run`), so it is looked up when the prompt is built.
423
+ guide: registry.list().some((tool) => tool.name === "forge_guide"),
424
+ scratchpad: sessionId ? await sessionScratchpad?.(sessionId) : undefined,
425
+ }));
426
+ }
363
427
  // Python analysis and artifacts live next to the session database (like attachment blobs).
364
428
  const artifacts = new ArtifactStore({
365
429
  root: stateRoot,
@@ -395,6 +459,13 @@ export async function createApplication(options = {}) {
395
459
  return ociRuntime.runtime;
396
460
  },
397
461
  };
462
+ // The Python backends as surfaces report them (`GET /api/analysis`, `GET /api/forge`); the tool
463
+ // builds its own for running. Read live: the settings can change between calls.
464
+ const pythonBackends = createPythonBackends({
465
+ runtime: analysisRuntime,
466
+ oci,
467
+ wasm: { stateRoot, settings: () => config.analysis.wasm },
468
+ });
398
469
  // Tabular datasets (one SQLite file each); XLSX needs the discovered Python.
399
470
  const datasets = new DatasetService({
400
471
  root: stateRoot,
@@ -416,6 +487,28 @@ export async function createApplication(options = {}) {
416
487
  ? { initialDelayMs: options.analysisSweepDelayMs }
417
488
  : {}),
418
489
  });
490
+ // Volatile Forja session folders: same schedule as the analysis sweep, gone with their session.
491
+ const scratchJanitor = new ScratchJanitor({
492
+ stateRoot,
493
+ roots: () => scratchSweepRoots({
494
+ customStore,
495
+ stateHome: scratchStateRoot,
496
+ configRoot: config.forge.scratch.root,
497
+ }),
498
+ sessionExists: (rootSessionId) => {
499
+ try {
500
+ store.get(rootSessionId);
501
+ return true;
502
+ }
503
+ catch {
504
+ return false;
505
+ }
506
+ },
507
+ retentionDays: () => config.forge.scratch.retentionDays,
508
+ ...(options.analysisSweepDelayMs !== undefined
509
+ ? { initialDelayMs: options.analysisSweepDelayMs }
510
+ : {}),
511
+ });
419
512
  // Background tasks: a service over the shared database (state change = compare-and-set), logs
420
513
  // under `<state>/tasks/`. Tasks of a dead owner process (a previous run) become `lost` now.
421
514
  const tasks = new BackgroundTasks({
@@ -473,6 +566,15 @@ export async function createApplication(options = {}) {
473
566
  runtime: analysisRuntime,
474
567
  datasets,
475
568
  oci,
569
+ // Read live: the policy and the Pyodide settings are global-only and may change.
570
+ isolation: () => config.analysis.isolation,
571
+ wasm: { stateRoot, settings: () => config.analysis.wasm },
572
+ // Python runs through the same kernel as `js_run`: one journal, one state store.
573
+ forge: { kernel: forgeRuntime.kernel, engines: forgeRuntime.engines },
574
+ // `codePath` and the read-only exposure exist only where `js_run` has its scratchpad too.
575
+ ...(scratchReady && sessionScratchpad
576
+ ? { scratchpad: async (session) => sessionScratchpad(session) }
577
+ : {}),
476
578
  rerun: analysisRerun,
477
579
  rootOf: (id) => store.rootOf(id),
478
580
  // Read live: a changed `analysis.limits.*` setting applies to the next call.
@@ -485,6 +587,8 @@ export async function createApplication(options = {}) {
485
587
  },
486
588
  },
487
589
  startup: { candidates: candidates.length > 0, extras: active?.extras ?? [] },
590
+ // Error results point at `forge_guide` only while that tool exists (spec §8.4).
591
+ guideHints: () => registry.list().some((tool) => tool.name === "forge_guide"),
488
592
  });
489
593
  const { registerArtifactTools } = await import("./tools/artifacts.js");
490
594
  registerArtifactTools(registry, { store: artifacts, rootOf: (id) => store.rootOf(id) });
@@ -496,6 +600,42 @@ export async function createApplication(options = {}) {
496
600
  context.extras.push(async () => DASHBOARD_PROMPT_RULE);
497
601
  }
498
602
  }
603
+ // Layer 2 of the context (spec §8.3): `forge_guide`. Registered only with Forja on and a ready
604
+ // engine for at least one language; the `python.*` topics need `python_run`.
605
+ if (config.forge.guides.enabled && config.forge.javascript.enabled) {
606
+ const [{ GuideProvider }, { registerForgeGuide }, { probePythonEnvironment }] = await Promise.all([
607
+ import("./forge/guides/provider.js"),
608
+ import("./tools/forge-guide.js"),
609
+ import("./forge/guides/python-environment.js"),
610
+ ]);
611
+ const hasPython = registry.list().some((tool) => tool.name === "python_run");
612
+ registerForgeGuide(registry, new GuideProvider({
613
+ ...(forgeRuntime.jsReady
614
+ ? {
615
+ javascript: async () => {
616
+ const status = await forgeRuntime.status();
617
+ return {
618
+ version: status.javascript.version,
619
+ isolation: status.engines[0]?.isolation ?? "wasm-isolate",
620
+ host: status.javascript.host,
621
+ limits: status.javascript.limits,
622
+ };
623
+ },
624
+ }
625
+ : {}),
626
+ ...(hasPython
627
+ ? {
628
+ python: () => probePythonEnvironment({
629
+ backends: pythonBackends,
630
+ runtime: () => config.analysis.runtime,
631
+ isolation: () => config.analysis.isolation,
632
+ managedExtras: async () => (await analysisRuntime.activeRuntime())?.extras ?? [],
633
+ smartDashboard: () => config.analysis.smartDashboard,
634
+ }),
635
+ }
636
+ : {}),
637
+ }));
638
+ }
499
639
  // `exit_plan` (opt-in: only the plan agent's run sees it): the plan review hand-over.
500
640
  const { registerExitPlan } = await import("./plan/exit-plan.js");
501
641
  registerExitPlan(registry, {
@@ -957,8 +1097,10 @@ export async function createApplication(options = {}) {
957
1097
  if (analysisEnabled && options.db !== ":memory:")
958
1098
  janitor.start();
959
1099
  // Retention is maintenance that deletes files: never under --read-only, never for `:memory:`.
960
- if (options.db !== ":memory:" && !options.readOnly)
1100
+ if (options.db !== ":memory:" && !options.readOnly) {
961
1101
  taskJanitor.start();
1102
+ scratchJanitor.start();
1103
+ }
962
1104
  return {
963
1105
  workspace,
964
1106
  pathAccess,
@@ -983,9 +1125,39 @@ export async function createApplication(options = {}) {
983
1125
  jobs: analysisJobs,
984
1126
  /** Container runtime of the current configuration (status, pull); mode `managed` ignores it. */
985
1127
  oci,
1128
+ /** The live state of every Python backend (isolation, ready, profiles, reason). */
1129
+ backends: (signal) => describePythonBackends(pythonBackends, signal),
986
1130
  rerun: analysisRerun,
987
1131
  janitor,
988
1132
  },
1133
+ /**
1134
+ * Forja services for the engines (Phase 3b): the execution journal, the cross-execution
1135
+ * state, the scratchpad of a session (undefined while the JavaScript engine is off) and the
1136
+ * retention sweep of the volatile session folders.
1137
+ */
1138
+ forge: {
1139
+ journal: forgeJournal,
1140
+ state: forgeState,
1141
+ /** The scratchpad, created on first use; under the same gate as the write root. */
1142
+ scratchpad: scratchpad
1143
+ ? (session) => scratchReady && !options.readOnly ? scratchpad(session) : Promise.resolve(undefined)
1144
+ : undefined,
1145
+ /**
1146
+ * What the HTTP surface sees (L2): `available` follows the gate of the write root (engine
1147
+ * verified, not `--read-only`); the folder is only looked up, never created.
1148
+ */
1149
+ scratchpadView: scratchpadLookup
1150
+ ? async (session) => {
1151
+ if (!scratchReady || options.readOnly)
1152
+ return { available: false };
1153
+ const path = await scratchpadLookup(session);
1154
+ return path ? { available: true, path } : { available: true };
1155
+ }
1156
+ : undefined,
1157
+ scratchJanitor,
1158
+ /** The JavaScript engine: descriptor and effective limits (`alisio forge status`), probe. */
1159
+ runtime: forgeRuntime,
1160
+ },
989
1161
  /** Keys a project layer set that only the user layer decides (ignored on load). */
990
1162
  configDiagnostics: configProvenance.ignored ?? [],
991
1163
  /** Tabular datasets of every session (ingestion, read-only queries, pages). */
@@ -1306,6 +1478,18 @@ export async function createApplication(options = {}) {
1306
1478
  config.tasks = { ...config.tasks, [leaf]: Number(value) };
1307
1479
  break;
1308
1480
  }
1481
+ case "forge.javascript.enabled":
1482
+ // The tool set (`js_run` or `execute`) is chosen when the application starts: the
1483
+ // change applies to the next application (the web recycles the workspace application).
1484
+ config.forge = {
1485
+ ...config.forge,
1486
+ javascript: { ...config.forge.javascript, enabled: value === true },
1487
+ };
1488
+ break;
1489
+ case "forge.guides.enabled":
1490
+ // Like `forge.javascript.enabled`: `forge_guide` is registered at start.
1491
+ config.forge = { ...config.forge, guides: { enabled: value === true } };
1492
+ break;
1309
1493
  case "web.iconTheme":
1310
1494
  // Read live by the icon-theme routes on the next request; `"none"` clears the theme.
1311
1495
  config.web = { ...config.web, iconTheme: String(value) };
@@ -1492,6 +1676,7 @@ export async function createApplication(options = {}) {
1492
1676
  try {
1493
1677
  janitor.stop();
1494
1678
  taskJanitor.stop();
1679
+ scratchJanitor.stop();
1495
1680
  datasets?.close();
1496
1681
  // Tasks first in the same stage: they end with Alisio (process groups killed).
1497
1682
  await capped(Promise.allSettled([herdr.close(), mcp.close(), tasks.close()]));
@@ -2,6 +2,48 @@
2
2
  export const CHANGELOG_DATA = {
3
3
  schema: 1,
4
4
  entries: [
5
+ {
6
+ version: "0.7.0",
7
+ date: "2026-10-14",
8
+ sections: [
9
+ {
10
+ title: "Added",
11
+ items: [
12
+ "`alisio eval list|run|record|compare|variants|export`: run evaluation suites against any configured provider, compare variants and export the results.",
13
+ "Reliability Lab suites with deterministic verifiers (files, commands, tool sequences), so a pass or fail never depends on a model judging itself.",
14
+ "`pass@k` and `pass^k` metrics with Wilson confidence intervals, so a comparison tells you whether a difference is real or noise.",
15
+ "Cassettes: `alisio eval record` captures provider responses and later runs replay them offline, without network or cost.",
16
+ "Bundled suites `smoke-coding`, `tool-sequences` and `long-context`, available from a source checkout.",
17
+ ],
18
+ },
19
+ ],
20
+ },
21
+ {
22
+ version: "0.6.0",
23
+ date: "2026-10-12",
24
+ sections: [
25
+ {
26
+ title: "Added",
27
+ items: [
28
+ "`js_run`: the model writes a short JavaScript script that composes tools, runs them in parallel and returns a small result. It runs in an isolated QuickJS-WebAssembly VM with limits for time, memory, calls and output; every nested call passes your permissions and a script cannot ask for approval.",
29
+ "`forge_guide`: a short guide for `js_run` and `python_run`, generated from the live limits, tools and Python backend, so the model learns the real environment on demand (`forge.guides.enabled`).",
30
+ "`python_run` backends `auto`, `managed`, `oci` and `wasm`, with an isolation policy (`analysis.isolation`: `preferred`, `required` or `strict`). The new `wasm` backend runs Pyodide in a restricted Node process with no Docker (`alisio analysis setup --wasm`); the result names the engine and why it was chosen. `alisio forge status` shows every engine and why one is not ready.",
31
+ "A per-session scratchpad where the model keeps scripts and intermediate files and runs them with `codePath` (`forge.scratch.*`: 7 days of retention and a 256 MiB quota by default). Python sees it read only through `ALISIO_SCRATCH_DIR`.",
32
+ "Web UI: a Forja settings page, a `js_run` card with the code, the nested calls and a progress bar, an isolation badge and the reason on every `python_run`, and a Scratch tab to preview, download and empty the scratchpad. The terminal shows nested-call counts and the Python backend.",
33
+ "Database migrations v9 and v10 (the execution journal and the shared script state) and `alisio_runtime` 4 with `alisio_runtime.forge`; they apply automatically.",
34
+ ],
35
+ },
36
+ {
37
+ title: "Changed",
38
+ items: [
39
+ "`analysis.runtime` defaults to `auto` (before: `managed`): Alisio uses the most isolated backend that is ready (container, then WebAssembly, then your Python). Set `managed` to keep the old behavior; an explicit value is honored or fails, never replaced.",
40
+ "While `forge.javascript.enabled` is on (the default), `execute` is not registered: `js_run` takes its place. Set it to `false` to get `execute` and the previous prompt back; saved sessions with `execute` calls still open.",
41
+ "The script's own progress now reaches the web UI next to the nested-call events instead of being replaced by them.",
42
+ "Isolation has limits you should know: the `wasm` backend does not block the network on Node 22 and 24, its memory limit is soft and a hostile script can reach the runner's JavaScript; `managed` is not a sandbox. The Forja documentation page lists them.",
43
+ ],
44
+ },
45
+ ],
46
+ },
5
47
  {
6
48
  version: "0.5.2",
7
49
  date: "2026-10-08",
package/dist/config.d.ts CHANGED
@@ -196,9 +196,22 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
196
196
  enabled: z.ZodDefault<z.ZodBoolean>;
197
197
  smartDashboard: z.ZodDefault<z.ZodBoolean>;
198
198
  runtime: z.ZodDefault<z.ZodEnum<{
199
+ auto: "auto";
199
200
  managed: "managed";
200
201
  oci: "oci";
202
+ wasm: "wasm";
201
203
  }>>;
204
+ isolation: z.ZodDefault<z.ZodEnum<{
205
+ preferred: "preferred";
206
+ required: "required";
207
+ strict: "strict";
208
+ }>>;
209
+ wasm: z.ZodDefault<z.ZodObject<{
210
+ memoryMb: z.ZodDefault<z.ZodNumber>;
211
+ startupMs: z.ZodDefault<z.ZodNumber>;
212
+ maxInputBytes: z.ZodDefault<z.ZodNumber>;
213
+ nodePath: z.ZodDefault<z.ZodNullable<z.ZodString>>;
214
+ }, z.core.$strict>>;
202
215
  oci: z.ZodDefault<z.ZodObject<{
203
216
  engine: z.ZodDefault<z.ZodEnum<{
204
217
  docker: "docker";
@@ -255,6 +268,35 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
255
268
  active: z.ZodDefault<z.ZodString>;
256
269
  effort: z.ZodOptional<z.ZodString>;
257
270
  }, z.core.$strict>>;
271
+ forge: z.ZodDefault<z.ZodObject<{
272
+ javascript: z.ZodDefault<z.ZodObject<{
273
+ enabled: z.ZodDefault<z.ZodBoolean>;
274
+ host: z.ZodDefault<z.ZodEnum<{
275
+ auto: "auto";
276
+ process: "process";
277
+ worker: "worker";
278
+ }>>;
279
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
280
+ memoryBytes: z.ZodDefault<z.ZodNumber>;
281
+ stackBytes: z.ZodDefault<z.ZodNumber>;
282
+ maxToolCalls: z.ZodDefault<z.ZodNumber>;
283
+ maxConcurrency: z.ZodDefault<z.ZodNumber>;
284
+ maxOutputChars: z.ZodDefault<z.ZodNumber>;
285
+ }, z.core.$strict>>;
286
+ state: z.ZodDefault<z.ZodObject<{
287
+ enabled: z.ZodDefault<z.ZodBoolean>;
288
+ maxTotalBytes: z.ZodDefault<z.ZodNumber>;
289
+ }, z.core.$strict>>;
290
+ scratch: z.ZodDefault<z.ZodObject<{
291
+ enabled: z.ZodDefault<z.ZodBoolean>;
292
+ root: z.ZodDefault<z.ZodNullable<z.ZodString>>;
293
+ retentionDays: z.ZodDefault<z.ZodNumber>;
294
+ maxBytes: z.ZodDefault<z.ZodNumber>;
295
+ }, z.core.$strict>>;
296
+ guides: z.ZodDefault<z.ZodObject<{
297
+ enabled: z.ZodDefault<z.ZodBoolean>;
298
+ }, z.core.$strict>>;
299
+ }, z.core.$strict>>;
258
300
  web: z.ZodDefault<z.ZodObject<{
259
301
  iconTheme: z.ZodDefault<z.ZodString>;
260
302
  }, z.core.$strict>>;
@@ -325,7 +367,14 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
325
367
  analysis: {
326
368
  enabled: boolean;
327
369
  smartDashboard: boolean;
328
- runtime: "managed" | "oci";
370
+ runtime: "auto" | "managed" | "oci" | "wasm";
371
+ isolation: "preferred" | "required" | "strict";
372
+ wasm: {
373
+ memoryMb: number;
374
+ startupMs: number;
375
+ maxInputBytes: number;
376
+ nodePath: string | null;
377
+ };
329
378
  oci: {
330
379
  engine: "docker" | "podman";
331
380
  image?: string | undefined;
@@ -379,6 +428,31 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
379
428
  active: string;
380
429
  effort?: string | undefined;
381
430
  };
431
+ forge: {
432
+ javascript: {
433
+ enabled: boolean;
434
+ host: "auto" | "process" | "worker";
435
+ timeoutMs: number;
436
+ memoryBytes: number;
437
+ stackBytes: number;
438
+ maxToolCalls: number;
439
+ maxConcurrency: number;
440
+ maxOutputChars: number;
441
+ };
442
+ state: {
443
+ enabled: boolean;
444
+ maxTotalBytes: number;
445
+ };
446
+ scratch: {
447
+ enabled: boolean;
448
+ root: string | null;
449
+ retentionDays: number;
450
+ maxBytes: number;
451
+ };
452
+ guides: {
453
+ enabled: boolean;
454
+ };
455
+ };
382
456
  web: {
383
457
  iconTheme: string;
384
458
  };
@@ -532,7 +606,14 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
532
606
  analysis: {
533
607
  enabled: boolean;
534
608
  smartDashboard: boolean;
535
- runtime: "managed" | "oci";
609
+ runtime: "auto" | "managed" | "oci" | "wasm";
610
+ isolation: "preferred" | "required" | "strict";
611
+ wasm: {
612
+ memoryMb: number;
613
+ startupMs: number;
614
+ maxInputBytes: number;
615
+ nodePath: string | null;
616
+ };
536
617
  oci: {
537
618
  engine: "docker" | "podman";
538
619
  image?: string | undefined;
@@ -586,6 +667,31 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
586
667
  active: string;
587
668
  effort?: string | undefined;
588
669
  };
670
+ forge: {
671
+ javascript: {
672
+ enabled: boolean;
673
+ host: "auto" | "process" | "worker";
674
+ timeoutMs: number;
675
+ memoryBytes: number;
676
+ stackBytes: number;
677
+ maxToolCalls: number;
678
+ maxConcurrency: number;
679
+ maxOutputChars: number;
680
+ };
681
+ state: {
682
+ enabled: boolean;
683
+ maxTotalBytes: number;
684
+ };
685
+ scratch: {
686
+ enabled: boolean;
687
+ root: string | null;
688
+ retentionDays: number;
689
+ maxBytes: number;
690
+ };
691
+ guides: {
692
+ enabled: boolean;
693
+ };
694
+ };
589
695
  web: {
590
696
  iconTheme: string;
591
697
  };
@@ -705,6 +811,8 @@ declare const SETTABLE_KEYS: {
705
811
  readonly "plan.diagrams": z.ZodDefault<z.ZodBoolean>;
706
812
  readonly "plan.maxDiagrams": z.ZodDefault<z.ZodNumber>;
707
813
  readonly "web.iconTheme": z.ZodDefault<z.ZodString>;
814
+ readonly "forge.javascript.enabled": z.ZodDefault<z.ZodBoolean>;
815
+ readonly "forge.guides.enabled": z.ZodDefault<z.ZodBoolean>;
708
816
  };
709
817
  export type SettableSettingKey = keyof typeof SETTABLE_KEYS;
710
818
  export declare function isSettableSettingKey(key: string): key is SettableSettingKey;