@alisio/core 0.5.1 → 0.6.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 (169) 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 +38 -2
  12. package/dist/application.js +213 -4
  13. package/dist/changelog/data.js +64 -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/forge/broker.d.ts +51 -0
  21. package/dist/forge/broker.js +244 -0
  22. package/dist/forge/budget.d.ts +27 -0
  23. package/dist/forge/budget.js +60 -0
  24. package/dist/forge/catalog-view.d.ts +24 -0
  25. package/dist/forge/catalog-view.js +43 -0
  26. package/dist/forge/errors.d.ts +26 -0
  27. package/dist/forge/errors.js +49 -0
  28. package/dist/forge/guides/javascript.d.ts +25 -0
  29. package/dist/forge/guides/javascript.js +90 -0
  30. package/dist/forge/guides/provider.d.ts +42 -0
  31. package/dist/forge/guides/provider.js +96 -0
  32. package/dist/forge/guides/python-environment.d.ts +15 -0
  33. package/dist/forge/guides/python-environment.js +81 -0
  34. package/dist/forge/guides/python.d.ts +38 -0
  35. package/dist/forge/guides/python.js +170 -0
  36. package/dist/forge/javascript/engine.d.ts +44 -0
  37. package/dist/forge/javascript/engine.js +229 -0
  38. package/dist/forge/javascript/hosts/factory.d.ts +25 -0
  39. package/dist/forge/javascript/hosts/factory.js +21 -0
  40. package/dist/forge/javascript/hosts/framing.d.ts +17 -0
  41. package/dist/forge/javascript/hosts/framing.js +49 -0
  42. package/dist/forge/javascript/hosts/isolate-main.d.ts +9 -0
  43. package/dist/forge/javascript/hosts/isolate-main.js +63 -0
  44. package/dist/forge/javascript/hosts/process-entry.d.ts +1 -0
  45. package/dist/forge/javascript/hosts/process-entry.js +3 -0
  46. package/dist/forge/javascript/hosts/process-host.d.ts +33 -0
  47. package/dist/forge/javascript/hosts/process-host.js +160 -0
  48. package/dist/forge/javascript/hosts/worker-entry.d.ts +1 -0
  49. package/dist/forge/javascript/hosts/worker-entry.js +3 -0
  50. package/dist/forge/javascript/hosts/worker-host.d.ts +18 -0
  51. package/dist/forge/javascript/hosts/worker-host.js +101 -0
  52. package/dist/forge/javascript/prelude.d.ts +2 -0
  53. package/dist/forge/javascript/prelude.js +333 -0
  54. package/dist/forge/javascript/probe.d.ts +2 -0
  55. package/dist/forge/javascript/probe.js +53 -0
  56. package/dist/forge/javascript/runtime-manifest.d.ts +14 -0
  57. package/dist/forge/javascript/runtime-manifest.js +14 -0
  58. package/dist/forge/javascript/vm-runner.d.ts +21 -0
  59. package/dist/forge/javascript/vm-runner.js +301 -0
  60. package/dist/forge/javascript/wasm-asset.d.ts +8 -0
  61. package/dist/forge/javascript/wasm-asset.js +8 -0
  62. package/dist/forge/javascript/wasm.d.ts +15 -0
  63. package/dist/forge/javascript/wasm.js +54 -0
  64. package/dist/forge/journal.d.ts +105 -0
  65. package/dist/forge/journal.js +174 -0
  66. package/dist/forge/kernel.d.ts +76 -0
  67. package/dist/forge/kernel.js +338 -0
  68. package/dist/forge/limits.d.ts +38 -0
  69. package/dist/forge/limits.js +73 -0
  70. package/dist/forge/nested.d.ts +96 -0
  71. package/dist/forge/nested.js +214 -0
  72. package/dist/forge/ports.d.ts +120 -0
  73. package/dist/forge/ports.js +1 -0
  74. package/dist/forge/progress.d.ts +24 -0
  75. package/dist/forge/progress.js +76 -0
  76. package/dist/forge/protocol.d.ts +110 -0
  77. package/dist/forge/protocol.js +171 -0
  78. package/dist/forge/python/backend-selector.d.ts +38 -0
  79. package/dist/forge/python/backend-selector.js +111 -0
  80. package/dist/forge/python/backend.d.ts +89 -0
  81. package/dist/forge/python/backend.js +1 -0
  82. package/dist/forge/python/engine.d.ts +84 -0
  83. package/dist/forge/python/engine.js +279 -0
  84. package/dist/forge/python/frame-parser.d.ts +24 -0
  85. package/dist/forge/python/frame-parser.js +107 -0
  86. package/dist/forge/python/frame-transport.d.ts +40 -0
  87. package/dist/forge/python/frame-transport.js +101 -0
  88. package/dist/forge/python/import-scan.d.ts +22 -0
  89. package/dist/forge/python/import-scan.js +133 -0
  90. package/dist/forge/python/managed-backend.d.ts +30 -0
  91. package/dist/forge/python/managed-backend.js +111 -0
  92. package/dist/forge/python/oci-backend.d.ts +27 -0
  93. package/dist/forge/python/oci-backend.js +84 -0
  94. package/dist/forge/python/pyodide-backend.d.ts +40 -0
  95. package/dist/forge/python/pyodide-backend.js +289 -0
  96. package/dist/forge/python/pyodide-install.d.ts +41 -0
  97. package/dist/forge/python/pyodide-install.js +167 -0
  98. package/dist/forge/python/pyodide-manifest.d.ts +42 -0
  99. package/dist/forge/python/pyodide-manifest.js +376 -0
  100. package/dist/forge/python/pyodide-runner-source.d.ts +2 -0
  101. package/dist/forge/python/pyodide-runner-source.js +3 -0
  102. package/dist/forge/python/rss.d.ts +2 -0
  103. package/dist/forge/python/rss.js +33 -0
  104. package/dist/forge/python/scratch-copy.d.ts +6 -0
  105. package/dist/forge/python/scratch-copy.js +61 -0
  106. package/dist/forge/python/settle.d.ts +6 -0
  107. package/dist/forge/python/settle.js +21 -0
  108. package/dist/forge/python/status.d.ts +8 -0
  109. package/dist/forge/python/status.js +15 -0
  110. package/dist/forge/python/wasm-node.d.ts +59 -0
  111. package/dist/forge/python/wasm-node.js +220 -0
  112. package/dist/forge/python/wiring.d.ts +39 -0
  113. package/dist/forge/python/wiring.js +47 -0
  114. package/dist/forge/registry.d.ts +26 -0
  115. package/dist/forge/registry.js +83 -0
  116. package/dist/forge/result.d.ts +58 -0
  117. package/dist/forge/result.js +86 -0
  118. package/dist/forge/routing-rule.d.ts +19 -0
  119. package/dist/forge/routing-rule.js +31 -0
  120. package/dist/forge/runtime.d.ts +64 -0
  121. package/dist/forge/runtime.js +88 -0
  122. package/dist/forge/scratch-files.d.ts +36 -0
  123. package/dist/forge/scratch-files.js +94 -0
  124. package/dist/forge/scratch-provider.d.ts +41 -0
  125. package/dist/forge/scratch-provider.js +79 -0
  126. package/dist/forge/scratch-quota.d.ts +8 -0
  127. package/dist/forge/scratch-quota.js +25 -0
  128. package/dist/forge/selector.d.ts +35 -0
  129. package/dist/forge/selector.js +51 -0
  130. package/dist/forge/session-dir.d.ts +59 -0
  131. package/dist/forge/session-dir.js +239 -0
  132. package/dist/forge/session-path.d.ts +31 -0
  133. package/dist/forge/session-path.js +61 -0
  134. package/dist/forge/slots.d.ts +16 -0
  135. package/dist/forge/slots.js +47 -0
  136. package/dist/forge/state-delta.d.ts +54 -0
  137. package/dist/forge/state-delta.js +99 -0
  138. package/dist/forge/state-machine.d.ts +20 -0
  139. package/dist/forge/state-machine.js +40 -0
  140. package/dist/forge/state-store.d.ts +25 -0
  141. package/dist/forge/state-store.js +50 -0
  142. package/dist/forge/task-log.d.ts +9 -0
  143. package/dist/forge/task-log.js +42 -0
  144. package/dist/forge/verdict.d.ts +20 -0
  145. package/dist/forge/verdict.js +28 -0
  146. package/dist/forge-isolate.d.ts +6 -0
  147. package/dist/forge-isolate.js +6 -0
  148. package/dist/index.d.ts +7 -0
  149. package/dist/index.js +7 -0
  150. package/dist/permissions/decide.d.ts +42 -0
  151. package/dist/permissions/decide.js +24 -0
  152. package/dist/resources/context.d.ts +2 -1
  153. package/dist/resources/context.js +2 -1
  154. package/dist/runtime/access.d.ts +34 -0
  155. package/dist/runtime/access.js +80 -0
  156. package/dist/runtime/process.d.ts +2 -1
  157. package/dist/runtime/process.js +5 -5
  158. package/dist/runtime/store.d.ts +11 -0
  159. package/dist/runtime/store.js +103 -0
  160. package/dist/tools/analysis.d.ts +25 -2
  161. package/dist/tools/analysis.js +315 -267
  162. package/dist/tools/execute.js +6 -7
  163. package/dist/tools/forge-guide.d.ts +6 -0
  164. package/dist/tools/forge-guide.js +60 -0
  165. package/dist/tools/js-run.d.ts +19 -0
  166. package/dist/tools/js-run.js +126 -0
  167. package/dist/tools/standard.d.ts +7 -1
  168. package/dist/tools/standard.js +44 -43
  169. package/package.json +7 -2
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;
package/dist/config.js CHANGED
@@ -3,6 +3,7 @@ import { homedir } from "node:os";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { z } from "zod";
5
5
  import { DEFAULT_DECISIONS_CONFIG } from "./decisions/service.js";
6
+ import { DEFAULT_FORGE_LIMITS, MAX_FORGE_LIMITS } from "./forge/limits.js";
6
7
  import { exists, readJson } from "./runtime/fs.js";
7
8
  import { isPathSpec } from "./runtime/modules.js";
8
9
  const providerSchema = z
@@ -82,7 +83,42 @@ const serversSchema = (value) => z.record(z.string(), value).superRefine((server
82
83
  */
83
84
  export const OCI_IMAGE_PATTERN = /^[a-zA-Z0-9][^\s@]*@sha256:[a-f0-9]{64}$/;
84
85
  /** Analysis keys only the user (global) layer decides; a project layer cannot set them. */
85
- const GLOBAL_ONLY_ANALYSIS = ["runtime", "oci", "retention"];
86
+ const GLOBAL_ONLY_ANALYSIS = ["runtime", "isolation", "oci", "wasm", "retention"];
87
+ /**
88
+ * Forja keys only the user (global) layer decides (spec §10). A project may still lower
89
+ * `timeoutMs`, `maxToolCalls`, `maxConcurrency` and `maxOutputChars` and switch a feature off.
90
+ */
91
+ const GLOBAL_ONLY_FORGE = [
92
+ "javascript.host",
93
+ "javascript.memoryBytes",
94
+ "javascript.stackBytes",
95
+ "state.maxTotalBytes",
96
+ "scratch",
97
+ ];
98
+ /** The limits a project layer may lower: the same list `mergeLimits` honours. */
99
+ const FORGE_PROJECT_LOWERABLE = [
100
+ "timeoutMs",
101
+ "maxToolCalls",
102
+ "maxConcurrency",
103
+ "maxOutputChars",
104
+ ];
105
+ /** A Forja limit: a positive integer up to its hard global maximum, with the spec default. */
106
+ const forgeLimit = (key) => z.number().int().min(1).max(MAX_FORGE_LIMITS[key]).default(DEFAULT_FORGE_LIMITS[key]);
107
+ const FORGE_DEFAULTS = {
108
+ javascript: {
109
+ enabled: true,
110
+ host: "auto",
111
+ timeoutMs: DEFAULT_FORGE_LIMITS.timeoutMs,
112
+ memoryBytes: DEFAULT_FORGE_LIMITS.memoryBytes,
113
+ stackBytes: DEFAULT_FORGE_LIMITS.stackBytes,
114
+ maxToolCalls: DEFAULT_FORGE_LIMITS.maxToolCalls,
115
+ maxConcurrency: DEFAULT_FORGE_LIMITS.maxConcurrency,
116
+ maxOutputChars: DEFAULT_FORGE_LIMITS.maxOutputChars,
117
+ },
118
+ state: { enabled: true, maxTotalBytes: 512 * 1024 },
119
+ scratch: { enabled: true, root: null, retentionDays: 7, maxBytes: 256 * 1024 * 1024 },
120
+ guides: { enabled: true },
121
+ };
86
122
  /** Decision Intelligence keys only the user (global) layer decides: the provider and telemetry. */
87
123
  const GLOBAL_ONLY_DECISIONS = ["provider", "telemetry"];
88
124
  /** Web UI keys only the user (global) layer decides: the server picks which theme it serves. */
@@ -289,11 +325,43 @@ const configObjectSchema = z
289
325
  */
290
326
  smartDashboard: z.boolean().default(true),
291
327
  /**
292
- * Where `python_run` executes. `managed` is the discovered Python (not a sandbox); `oci`
293
- * is a Docker or Podman container started from a pinned image. GLOBAL ONLY: a project
328
+ * Where `python_run` executes. `auto` (default) picks the most isolated backend that is
329
+ * ready: `oci`, then `wasm`, then `managed` (the last only under `isolation: preferred`).
330
+ * `managed` is the discovered Python (not a sandbox); `oci` is a Docker or Podman
331
+ * container started from a pinned image; `wasm` is Pyodide in a restricted Node child
332
+ * process. An explicit value is honored or fails, never replaced. GLOBAL ONLY: a project
294
333
  * layer cannot choose the runtime or the image (it is ignored with a diagnostic).
295
334
  */
296
- runtime: z.enum(["managed", "oci"]).default("managed"),
335
+ runtime: z.enum(["auto", "managed", "oci", "wasm"]).default("auto"),
336
+ /**
337
+ * What `auto` may use: `preferred` falls back to the host when no isolated backend is
338
+ * ready, `required` never runs on the host, `strict` admits only backends whose network
339
+ * the OS denies (global only).
340
+ */
341
+ isolation: z.enum(["preferred", "required", "strict"]).default("preferred"),
342
+ /** Pyodide backend settings (global only). Read by `wasm`, and by `auto` when it is ready. */
343
+ wasm: z
344
+ .object({
345
+ /** Soft limit: the parent samples the child's RSS and kills it above this. */
346
+ memoryMb: z.number().int().min(256).max(65_536).default(3072),
347
+ /** Margin for loading Pyodide, added to the run's own deadline and reported apart. */
348
+ startupMs: z.number().int().min(1000).max(300_000).default(30_000),
349
+ /** Inputs above this many bytes keep `auto` away from wasm. */
350
+ maxInputBytes: z
351
+ .number()
352
+ .int()
353
+ .positive()
354
+ .default(100 * 1024 * 1024),
355
+ /** A `node` >= 22.22 / 24.13 / 25.3 for the child (needed under Bun; the running Node otherwise). */
356
+ nodePath: z.string().min(1).nullable().default(null),
357
+ })
358
+ .strict()
359
+ .default(() => ({
360
+ memoryMb: 3072,
361
+ startupMs: 30_000,
362
+ maxInputBytes: 100 * 1024 * 1024,
363
+ nodePath: null,
364
+ })),
297
365
  /** Container runtime settings (global only). Used when `runtime` is `oci`. */
298
366
  oci: z
299
367
  .object({
@@ -383,7 +451,14 @@ const configObjectSchema = z
383
451
  .default(() => ({
384
452
  enabled: true,
385
453
  smartDashboard: true,
386
- runtime: "managed",
454
+ runtime: "auto",
455
+ isolation: "preferred",
456
+ wasm: {
457
+ memoryMb: 3072,
458
+ startupMs: 30_000,
459
+ maxInputBytes: 100 * 1024 * 1024,
460
+ nodePath: null,
461
+ },
387
462
  oci: { engine: "docker", memoryMb: 2048, cpus: 2 },
388
463
  retention: { jobsDays: 30, intermediateDays: 7, artifactsDays: 0 },
389
464
  limits: {
@@ -505,6 +580,58 @@ const configObjectSchema = z
505
580
  })
506
581
  .strict()
507
582
  .default(() => ({ active: "build" })),
583
+ /**
584
+ * Forja: isolated code execution (spec §10). `javascript.enabled` is `true` since the
585
+ * JavaScript engine passed its gate (Phase 3b): `js_run` replaces `execute`; `false` brings
586
+ * `execute` and the previous system prompt back. `javascript.host`, `memoryBytes`, `stackBytes`,
587
+ * `state.maxTotalBytes` and `scratch.*` are GLOBAL ONLY; a project may only lower
588
+ * `timeoutMs`, `maxToolCalls`, `maxConcurrency` and `maxOutputChars` and switch a feature off.
589
+ */
590
+ forge: z
591
+ .object({
592
+ javascript: z
593
+ .object({
594
+ enabled: z.boolean().default(FORGE_DEFAULTS.javascript.enabled),
595
+ host: z.enum(["auto", "worker", "process"]).default("auto"),
596
+ timeoutMs: forgeLimit("timeoutMs"),
597
+ memoryBytes: forgeLimit("memoryBytes"),
598
+ stackBytes: forgeLimit("stackBytes"),
599
+ maxToolCalls: forgeLimit("maxToolCalls"),
600
+ maxConcurrency: forgeLimit("maxConcurrency"),
601
+ maxOutputChars: forgeLimit("maxOutputChars"),
602
+ })
603
+ .strict()
604
+ .default(() => ({ ...FORGE_DEFAULTS.javascript })),
605
+ state: z
606
+ .object({
607
+ enabled: z.boolean().default(true),
608
+ maxTotalBytes: z
609
+ .number()
610
+ .int()
611
+ .min(1)
612
+ .max(8 * 1024 * 1024)
613
+ .default(FORGE_DEFAULTS.state.maxTotalBytes),
614
+ })
615
+ .strict()
616
+ .default(() => ({ ...FORGE_DEFAULTS.state })),
617
+ scratch: z
618
+ .object({
619
+ enabled: z.boolean().default(true),
620
+ /** Base of the volatile session directories; `ALISIO_TMPDIR` wins over it. */
621
+ root: z.string().min(1).nullable().default(null),
622
+ /** Days without activity before a session folder goes; `0` disables the cleanup. */
623
+ retentionDays: z.number().int().min(0).max(3650).default(7),
624
+ maxBytes: z.number().int().min(1).default(FORGE_DEFAULTS.scratch.maxBytes),
625
+ })
626
+ .strict()
627
+ .default(() => ({ ...FORGE_DEFAULTS.scratch })),
628
+ guides: z
629
+ .object({ enabled: z.boolean().default(true) })
630
+ .strict()
631
+ .default(() => ({ ...FORGE_DEFAULTS.guides })),
632
+ })
633
+ .strict()
634
+ .default(() => structuredClone(FORGE_DEFAULTS)),
508
635
  /**
509
636
  * Web UI preferences. `iconTheme` names the active `icon-theme` extension point provider
510
637
  * (`"none"` keeps the built-in inline icons). GLOBAL ONLY: the server decides which theme its
@@ -572,6 +699,44 @@ async function canonicalDirectory(path) {
572
699
  export function overridesSavedProviderProfile(provenance, hasEndpointOverride) {
573
700
  return hasEndpointOverride || !!provenance.selectedLayer?.hasLegacyProvider;
574
701
  }
702
+ /** Whether a dotted path (`javascript.host`) is written in a raw `forge` block. */
703
+ function forgeHas(raw, path) {
704
+ let current = raw;
705
+ for (const part of path.split(".")) {
706
+ if (!current || typeof current !== "object" || !(part in current))
707
+ return false;
708
+ current = current[part];
709
+ }
710
+ return true;
711
+ }
712
+ /**
713
+ * The project layer over the global one for `forge`: global-only keys always come from the global
714
+ * layer, a lowerable limit takes the smaller value when the project wrote it, and a feature switch
715
+ * the project wrote can only turn the feature off (a defaulted value never changes the global one).
716
+ */
717
+ function overlayForge(global, project, raw) {
718
+ const javascript = { ...global.javascript };
719
+ for (const key of FORGE_PROJECT_LOWERABLE)
720
+ if (forgeHas(raw, `javascript.${key}`))
721
+ javascript[key] = Math.min(global.javascript[key], project.javascript[key]);
722
+ if (forgeHas(raw, "javascript.enabled"))
723
+ javascript.enabled = global.javascript.enabled && project.javascript.enabled;
724
+ return {
725
+ javascript,
726
+ state: {
727
+ ...global.state,
728
+ enabled: forgeHas(raw, "state.enabled")
729
+ ? global.state.enabled && project.state.enabled
730
+ : global.state.enabled,
731
+ },
732
+ scratch: global.scratch,
733
+ guides: {
734
+ enabled: forgeHas(raw, "guides.enabled")
735
+ ? global.guides.enabled && project.guides.enabled
736
+ : global.guides.enabled,
737
+ },
738
+ };
739
+ }
575
740
  export const configHome = () => process.env.ALISIO_CONFIG_HOME ??
576
741
  join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "alisio");
577
742
  export const stateHome = () => process.env.ALISIO_STATE_HOME ??
@@ -625,6 +790,9 @@ export async function loadConfigWithProvenance(workspace, options = {}) {
625
790
  const analysisRaw = object.analysis && typeof object.analysis === "object" && !Array.isArray(object.analysis)
626
791
  ? object.analysis
627
792
  : {};
793
+ const forgeRaw = object.forge && typeof object.forge === "object" && !Array.isArray(object.forge)
794
+ ? object.forge
795
+ : {};
628
796
  const tasksRaw = object.tasks && typeof object.tasks === "object" && !Array.isArray(object.tasks)
629
797
  ? object.tasks
630
798
  : {};
@@ -641,6 +809,7 @@ export async function loadConfigWithProvenance(workspace, options = {}) {
641
809
  : {};
642
810
  const ignored = [
643
811
  ...GLOBAL_ONLY_ANALYSIS.filter((key) => key in analysisRaw).map((key) => `analysis.${key}`),
812
+ ...GLOBAL_ONLY_FORGE.filter((path) => forgeHas(forgeRaw, path)).map((path) => `forge.${path}`),
644
813
  ...GLOBAL_ONLY_DECISIONS.filter((key) => key in decisionsRaw).map((key) => `decisions.${key}`),
645
814
  ...GLOBAL_ONLY_WEB.filter((key) => key in webRaw).map((key) => `web.${key}`),
646
815
  ...Object.entries(overridesRaw)
@@ -653,6 +822,8 @@ export async function loadConfigWithProvenance(workspace, options = {}) {
653
822
  keys: new Set(Object.keys(object)),
654
823
  /** Raw keys of the layer's `analysis` block (a defaulted key is not an expressed one). */
655
824
  analysisKeys: new Set(Object.keys(analysisRaw)),
825
+ /** Raw `forge` block of the layer, to tell a written key from a defaulted one. */
826
+ forgeRaw,
656
827
  sources,
657
828
  ignored,
658
829
  file,
@@ -723,11 +894,13 @@ export async function loadConfigWithProvenance(workspace, options = {}) {
723
894
  continue;
724
895
  }
725
896
  if (key === "analysis") {
726
- // `runtime`, `oci` and `retention` are global-only (a repository must not pick the binary
897
+ // `runtime`, `isolation`, `oci`, `wasm` and `retention` are global-only (a repository must not pick the binary
727
898
  // that runs scripts, nor shorten the retention of every workspace's data).
728
899
  overlaid.analysis = {
729
900
  ...selected.config.analysis,
730
901
  runtime: global.config.analysis.runtime,
902
+ isolation: global.config.analysis.isolation,
903
+ wasm: global.config.analysis.wasm,
731
904
  oci: global.config.analysis.oci,
732
905
  retention: global.config.analysis.retention,
733
906
  // A defaulted `true` must not undo the global `false`: only a key the layer wrote wins.
@@ -737,6 +910,10 @@ export async function loadConfigWithProvenance(workspace, options = {}) {
737
910
  };
738
911
  continue;
739
912
  }
913
+ if (key === "forge") {
914
+ overlaid.forge = overlayForge(global.config.forge, selected.config.forge, selected.forgeRaw);
915
+ continue;
916
+ }
740
917
  if (key === "decisions") {
741
918
  // `provider` and `telemetry` are global-only (a repository cannot pick the decision
742
919
  // engine nor its telemetry policy); the rest follows the selected layer.
@@ -912,7 +1089,10 @@ const SETTABLE_SECTIONS = {
912
1089
  goal: configObjectSchema.shape.goal.removeDefault(),
913
1090
  plan: configObjectSchema.shape.plan.removeDefault(),
914
1091
  web: configObjectSchema.shape.web.removeDefault(),
1092
+ forge: configObjectSchema.shape.forge.removeDefault(),
915
1093
  };
1094
+ const FORGE_JAVASCRIPT = SETTABLE_SECTIONS.forge.shape.javascript.removeDefault();
1095
+ const FORGE_GUIDES = SETTABLE_SECTIONS.forge.shape.guides.removeDefault();
916
1096
  const ANALYSIS_LIMITS = SETTABLE_SECTIONS.analysis.shape.limits.removeDefault();
917
1097
  const ANALYSIS_RETENTION = SETTABLE_SECTIONS.analysis.shape.retention.removeDefault();
918
1098
  const SETTABLE_KEYS = {
@@ -962,6 +1142,8 @@ const SETTABLE_KEYS = {
962
1142
  "plan.diagrams": SETTABLE_SECTIONS.plan.shape.diagrams,
963
1143
  "plan.maxDiagrams": SETTABLE_SECTIONS.plan.shape.maxDiagrams,
964
1144
  "web.iconTheme": SETTABLE_SECTIONS.web.shape.iconTheme,
1145
+ "forge.javascript.enabled": FORGE_JAVASCRIPT.shape.enabled,
1146
+ "forge.guides.enabled": FORGE_GUIDES.shape.enabled,
965
1147
  };
966
1148
  export function isSettableSettingKey(key) {
967
1149
  return Object.prototype.hasOwnProperty.call(SETTABLE_KEYS, key);
@@ -0,0 +1,22 @@
1
+ /**
2
+ * What only the core's own tools may do from inside a call (spec D-23, D-24): flag the running
3
+ * call as `effectful` and reach the run's nested-call dispatcher. It is NOT part of the SDK
4
+ * `ToolContext`: the runner binds it to the context object of a call in a module-private
5
+ * `WeakMap`, so a plugin tool, which sees only the SDK type, has no property to find and no module
6
+ * to import.
7
+ */
8
+ import type { ToolContext } from "@alisio/sdk";
9
+ import type { NestedCallDispatcher } from "../forge/nested.ts";
10
+ export interface CallInternals {
11
+ /**
12
+ * Flags the running call: it may have left effects, so a cancellation must leave it pending for
13
+ * recovery even though its own effect is `read`. Idempotent.
14
+ */
15
+ markEffectful(): void;
16
+ /** The dispatcher for tool calls made from inside this call (built on first use). */
17
+ nested(): NestedCallDispatcher;
18
+ }
19
+ /** The runner binds the internals of a call to the context it hands the tool. */
20
+ export declare function bindCallInternals(context: ToolContext, internals: CallInternals): void;
21
+ /** The internals of the call this context belongs to; undefined for any other context. */
22
+ export declare function callInternals(context: ToolContext): CallInternals | undefined;
@@ -0,0 +1,9 @@
1
+ const bound = new WeakMap();
2
+ /** The runner binds the internals of a call to the context it hands the tool. */
3
+ export function bindCallInternals(context, internals) {
4
+ bound.set(context, internals);
5
+ }
6
+ /** The internals of the call this context belongs to; undefined for any other context. */
7
+ export function callInternals(context) {
8
+ return bound.get(context);
9
+ }
@@ -209,7 +209,7 @@ export interface ApprovalRequest {
209
209
  /** Capability approvals: the first 40 lines of the script. */
210
210
  preview?: string;
211
211
  /** Capability approvals: how the code runs (`managed` is not sandboxed). */
212
- runtime?: "managed" | "oci";
212
+ runtime?: "auto" | "managed" | "oci" | "wasm";
213
213
  /** `analysis.install`: the packages, the size estimate and the network need (once or deny). */
214
214
  install?: InstallPreview;
215
215
  }
@@ -233,7 +233,7 @@ export interface CapabilityGate {
233
233
  /** Recorded as the source of interactive decisions (`tui` or `web`). */
234
234
  source?: "tui" | "web";
235
235
  /** Runtime mode shown with `analysis.run` approvals (read live: the configuration can change). */
236
- runtime?: "managed" | "oci";
236
+ runtime?: "auto" | "managed" | "oci" | "wasm";
237
237
  /**
238
238
  * The script to show in an approval whose input does not carry it (a rerun runs a saved script).
239
239
  * Absent or undefined: the preview is taken from `input.code`.