@alisio/core 0.1.0-alpha.21 → 0.1.0-alpha.23

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 (91) hide show
  1. package/dist/analysis/capabilities.d.ts +48 -0
  2. package/dist/analysis/capabilities.js +90 -0
  3. package/dist/analysis/data/csv.d.ts +46 -0
  4. package/dist/analysis/data/csv.js +240 -0
  5. package/dist/analysis/data/datasets.d.ts +132 -0
  6. package/dist/analysis/data/datasets.js +441 -0
  7. package/dist/analysis/data/describe.d.ts +27 -0
  8. package/dist/analysis/data/describe.js +103 -0
  9. package/dist/analysis/data/engine-client.d.ts +58 -0
  10. package/dist/analysis/data/engine-client.js +304 -0
  11. package/dist/analysis/data/engine-entry.d.ts +1 -0
  12. package/dist/analysis/data/engine-entry.js +59 -0
  13. package/dist/analysis/data/engine-source.d.ts +4 -0
  14. package/dist/analysis/data/engine-source.js +6 -0
  15. package/dist/analysis/data/engine-types.d.ts +86 -0
  16. package/dist/analysis/data/engine-types.js +7 -0
  17. package/dist/analysis/data/infer.d.ts +42 -0
  18. package/dist/analysis/data/infer.js +118 -0
  19. package/dist/analysis/data/ingest-worker.d.ts +17 -0
  20. package/dist/analysis/data/ingest-worker.js +365 -0
  21. package/dist/analysis/data/json.d.ts +19 -0
  22. package/dist/analysis/data/json.js +44 -0
  23. package/dist/analysis/data/query-worker.d.ts +12 -0
  24. package/dist/analysis/data/query-worker.js +75 -0
  25. package/dist/analysis/data/rows.d.ts +39 -0
  26. package/dist/analysis/data/rows.js +120 -0
  27. package/dist/analysis/data/sql-guard.d.ts +20 -0
  28. package/dist/analysis/data/sql-guard.js +129 -0
  29. package/dist/analysis/data/stats.d.ts +18 -0
  30. package/dist/analysis/data/stats.js +77 -0
  31. package/dist/analysis/install-hints.d.ts +33 -0
  32. package/dist/analysis/install-hints.js +144 -0
  33. package/dist/analysis/janitor.d.ts +60 -0
  34. package/dist/analysis/janitor.js +264 -0
  35. package/dist/analysis/jobs.d.ts +94 -0
  36. package/dist/analysis/jobs.js +165 -0
  37. package/dist/analysis/oci.d.ts +129 -0
  38. package/dist/analysis/oci.js +330 -0
  39. package/dist/analysis/python/sources.d.ts +2 -0
  40. package/dist/analysis/python/sources.js +10 -0
  41. package/dist/analysis/requirements.d.ts +32 -0
  42. package/dist/analysis/requirements.js +73 -0
  43. package/dist/analysis/rerun.d.ts +40 -0
  44. package/dist/analysis/rerun.js +136 -0
  45. package/dist/analysis/runtime-manager.d.ts +146 -0
  46. package/dist/analysis/runtime-manager.js +472 -0
  47. package/dist/analysis/status.d.ts +21 -0
  48. package/dist/analysis/status.js +52 -0
  49. package/dist/application.d.ts +41 -0
  50. package/dist/application.js +197 -0
  51. package/dist/artifacts/kinds.d.ts +35 -0
  52. package/dist/artifacts/kinds.js +177 -0
  53. package/dist/artifacts/publisher.d.ts +7 -0
  54. package/dist/artifacts/publisher.js +29 -0
  55. package/dist/artifacts/store.d.ts +119 -0
  56. package/dist/artifacts/store.js +581 -0
  57. package/dist/commands/builtins.js +13 -0
  58. package/dist/config.d.ts +110 -0
  59. package/dist/config.js +186 -8
  60. package/dist/core/contracts.d.ts +69 -1
  61. package/dist/core/runner.d.ts +53 -4
  62. package/dist/core/runner.js +549 -161
  63. package/dist/core/timeout.d.ts +15 -0
  64. package/dist/core/timeout.js +61 -0
  65. package/dist/index.d.ts +23 -4
  66. package/dist/index.js +22 -3
  67. package/dist/mcp/rich.js +4 -0
  68. package/dist/plugins/host.js +14 -6
  69. package/dist/runtime/blobs.d.ts +22 -0
  70. package/dist/runtime/blobs.js +84 -1
  71. package/dist/runtime/ids.d.ts +8 -0
  72. package/dist/runtime/ids.js +32 -0
  73. package/dist/runtime/paths.d.ts +5 -0
  74. package/dist/runtime/paths.js +6 -0
  75. package/dist/runtime/process.d.ts +12 -0
  76. package/dist/runtime/process.js +64 -4
  77. package/dist/runtime/sqlite.d.ts +2 -0
  78. package/dist/runtime/sqlite.js +18 -0
  79. package/dist/runtime/store.d.ts +5 -0
  80. package/dist/runtime/store.js +75 -0
  81. package/dist/runtime/zip.d.ts +24 -0
  82. package/dist/runtime/zip.js +126 -0
  83. package/dist/sessions/children.js +4 -0
  84. package/dist/tools/analysis.d.ts +37 -0
  85. package/dist/tools/analysis.js +572 -0
  86. package/dist/tools/artifacts.d.ts +14 -0
  87. package/dist/tools/artifacts.js +143 -0
  88. package/dist/tools/data.d.ts +7 -0
  89. package/dist/tools/data.js +160 -0
  90. package/dist/tools/execute.js +6 -2
  91. package/package.json +2 -2
package/dist/config.d.ts CHANGED
@@ -12,6 +12,11 @@ declare const serverSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
12
12
  url: z.ZodString;
13
13
  bearerTokenEnv: z.ZodOptional<z.ZodString>;
14
14
  }, z.core.$strict>], "transport">;
15
+ /**
16
+ * A container image reference pinned by digest. Anchored at the start with a letter or digit so a
17
+ * value can never be read as an option by the container CLI.
18
+ */
19
+ export declare const OCI_IMAGE_PATTERN: RegExp;
15
20
  export declare const configSchema: z.ZodPipe<z.ZodObject<{
16
21
  schemaVersion: z.ZodDefault<z.ZodLiteral<1>>;
17
22
  provider: z.ZodDefault<z.ZodObject<{
@@ -170,10 +175,46 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
170
175
  limits: z.ZodDefault<z.ZodObject<{
171
176
  maxTurns: z.ZodDefault<z.ZodNumber>;
172
177
  timeoutMs: z.ZodDefault<z.ZodNumber>;
178
+ firstTokenTimeoutMs: z.ZodDefault<z.ZodNumber>;
179
+ firstTokenRetries: z.ZodDefault<z.ZodNumber>;
173
180
  maxContextChars: z.ZodDefault<z.ZodNumber>;
174
181
  maxOutputTokens: z.ZodDefault<z.ZodNumber>;
175
182
  maxTokens: z.ZodOptional<z.ZodNumber>;
176
183
  }, z.core.$strip>>;
184
+ analysis: z.ZodDefault<z.ZodObject<{
185
+ enabled: z.ZodDefault<z.ZodBoolean>;
186
+ runtime: z.ZodDefault<z.ZodEnum<{
187
+ managed: "managed";
188
+ oci: "oci";
189
+ }>>;
190
+ oci: z.ZodDefault<z.ZodObject<{
191
+ engine: z.ZodDefault<z.ZodEnum<{
192
+ docker: "docker";
193
+ podman: "podman";
194
+ }>>;
195
+ image: z.ZodOptional<z.ZodString>;
196
+ memoryMb: z.ZodDefault<z.ZodNumber>;
197
+ cpus: z.ZodDefault<z.ZodNumber>;
198
+ }, z.core.$strict>>;
199
+ limits: z.ZodDefault<z.ZodObject<{
200
+ timeoutMs: z.ZodDefault<z.ZodNumber>;
201
+ maxFiles: z.ZodDefault<z.ZodNumber>;
202
+ maxFileBytes: z.ZodDefault<z.ZodNumber>;
203
+ maxOutputBytes: z.ZodDefault<z.ZodNumber>;
204
+ maxLogBytes: z.ZodDefault<z.ZodNumber>;
205
+ }, z.core.$strict>>;
206
+ data: z.ZodDefault<z.ZodObject<{
207
+ maxUploadBytes: z.ZodDefault<z.ZodNumber>;
208
+ maxRows: z.ZodDefault<z.ZodNumber>;
209
+ queryTimeoutMs: z.ZodDefault<z.ZodNumber>;
210
+ maxInteractiveRows: z.ZodDefault<z.ZodNumber>;
211
+ }, z.core.$strict>>;
212
+ retention: z.ZodDefault<z.ZodObject<{
213
+ jobsDays: z.ZodDefault<z.ZodNumber>;
214
+ intermediateDays: z.ZodDefault<z.ZodNumber>;
215
+ artifactsDays: z.ZodDefault<z.ZodNumber>;
216
+ }, z.core.$strict>>;
217
+ }, z.core.$strict>>;
177
218
  tui: z.ZodDefault<z.ZodObject<{
178
219
  paddingX: z.ZodDefault<z.ZodNumber>;
179
220
  contentPaddingX: z.ZodDefault<z.ZodNumber>;
@@ -231,10 +272,40 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
231
272
  limits: {
232
273
  maxTurns: number;
233
274
  timeoutMs: number;
275
+ firstTokenTimeoutMs: number;
276
+ firstTokenRetries: number;
234
277
  maxContextChars: number;
235
278
  maxOutputTokens: number;
236
279
  maxTokens?: number | undefined;
237
280
  };
281
+ analysis: {
282
+ enabled: boolean;
283
+ runtime: "managed" | "oci";
284
+ oci: {
285
+ engine: "docker" | "podman";
286
+ image?: string | undefined;
287
+ memoryMb: number;
288
+ cpus: number;
289
+ };
290
+ limits: {
291
+ timeoutMs: number;
292
+ maxFiles: number;
293
+ maxFileBytes: number;
294
+ maxOutputBytes: number;
295
+ maxLogBytes: number;
296
+ };
297
+ data: {
298
+ maxUploadBytes: number;
299
+ maxRows: number;
300
+ queryTimeoutMs: number;
301
+ maxInteractiveRows: number;
302
+ };
303
+ retention: {
304
+ jobsDays: number;
305
+ intermediateDays: number;
306
+ artifactsDays: number;
307
+ };
308
+ };
238
309
  tui: {
239
310
  paddingX: number;
240
311
  contentPaddingX: number;
@@ -375,10 +446,40 @@ export declare const configSchema: z.ZodPipe<z.ZodObject<{
375
446
  limits: {
376
447
  maxTurns: number;
377
448
  timeoutMs: number;
449
+ firstTokenTimeoutMs: number;
450
+ firstTokenRetries: number;
378
451
  maxContextChars: number;
379
452
  maxOutputTokens: number;
380
453
  maxTokens?: number | undefined;
381
454
  };
455
+ analysis: {
456
+ enabled: boolean;
457
+ runtime: "managed" | "oci";
458
+ oci: {
459
+ engine: "docker" | "podman";
460
+ image?: string | undefined;
461
+ memoryMb: number;
462
+ cpus: number;
463
+ };
464
+ limits: {
465
+ timeoutMs: number;
466
+ maxFiles: number;
467
+ maxFileBytes: number;
468
+ maxOutputBytes: number;
469
+ maxLogBytes: number;
470
+ };
471
+ data: {
472
+ maxUploadBytes: number;
473
+ maxRows: number;
474
+ queryTimeoutMs: number;
475
+ maxInteractiveRows: number;
476
+ };
477
+ retention: {
478
+ jobsDays: number;
479
+ intermediateDays: number;
480
+ artifactsDays: number;
481
+ };
482
+ };
382
483
  tui: {
383
484
  paddingX: number;
384
485
  contentPaddingX: number;
@@ -405,6 +506,8 @@ export interface ConfigProvenance {
405
506
  kind: "project" | "explicit";
406
507
  hasLegacyProvider: boolean;
407
508
  };
509
+ /** Keys a project or explicit layer set that only the global layer may decide (ignored). */
510
+ ignored?: string[];
408
511
  }
409
512
  export interface ConfigLoadResult {
410
513
  config: LoadedConfig;
@@ -458,6 +561,8 @@ declare const SETTABLE_KEYS: {
458
561
  readonly "limits.maxOutputTokens": z.ZodDefault<z.ZodNumber>;
459
562
  readonly "limits.maxContextChars": z.ZodDefault<z.ZodNumber>;
460
563
  readonly "limits.timeoutMs": z.ZodDefault<z.ZodNumber>;
564
+ readonly "limits.firstTokenTimeoutMs": z.ZodDefault<z.ZodNumber>;
565
+ readonly "limits.firstTokenRetries": z.ZodDefault<z.ZodNumber>;
461
566
  readonly "pluginHooks.timeoutMs": z.ZodDefault<z.ZodNumber>;
462
567
  readonly "tui.paddingX": z.ZodDefault<z.ZodNumber>;
463
568
  readonly "tui.contentPaddingX": z.ZodDefault<z.ZodNumber>;
@@ -473,6 +578,11 @@ declare const SETTABLE_KEYS: {
473
578
  }>>;
474
579
  readonly "agents.active": z.ZodDefault<z.ZodString>;
475
580
  readonly "agents.effort": z.ZodOptional<z.ZodString>;
581
+ readonly "analysis.enabled": z.ZodDefault<z.ZodBoolean>;
582
+ readonly "analysis.limits.timeoutMs": z.ZodDefault<z.ZodNumber>;
583
+ readonly "analysis.retention.jobsDays": z.ZodDefault<z.ZodNumber>;
584
+ readonly "analysis.retention.intermediateDays": z.ZodDefault<z.ZodNumber>;
585
+ readonly "analysis.retention.artifactsDays": z.ZodDefault<z.ZodNumber>;
476
586
  };
477
587
  export type SettableSettingKey = keyof typeof SETTABLE_KEYS;
478
588
  export declare function isSettableSettingKey(key: string): key is SettableSettingKey;
package/dist/config.js CHANGED
@@ -75,6 +75,13 @@ const serversSchema = (value) => z.record(z.string(), value).superRefine((server
75
75
  message: "MCP server names may contain only letters, numbers, dot, underscore and dash",
76
76
  });
77
77
  });
78
+ /**
79
+ * A container image reference pinned by digest. Anchored at the start with a letter or digit so a
80
+ * value can never be read as an option by the container CLI.
81
+ */
82
+ export const OCI_IMAGE_PATTERN = /^[a-zA-Z0-9][^\s@]*@sha256:[a-f0-9]{64}$/;
83
+ /** Analysis keys only the user (global) layer decides; a project layer cannot set them. */
84
+ const GLOBAL_ONLY_ANALYSIS = ["runtime", "oci", "retention"];
78
85
  const configObjectSchema = z
79
86
  .object({
80
87
  schemaVersion: z.literal(1).default(1),
@@ -166,7 +173,20 @@ const configObjectSchema = z
166
173
  limits: z
167
174
  .object({
168
175
  maxTurns: z.number().int().min(1).max(100).default(100),
169
- timeoutMs: z.number().int().min(100).default(300000),
176
+ timeoutMs: z.number().int().min(100).default(600000),
177
+ /**
178
+ * Stop a model request that stays completely silent (no text, reasoning or tool-call
179
+ * delta) this long, so a queued or stuck provider is cut after 90 s instead of the
180
+ * whole-run `timeoutMs`. 0 disables it (models that do not stream their reasoning can stay
181
+ * silent for minutes: raise or disable it for those).
182
+ */
183
+ firstTokenTimeoutMs: z.number().int().min(0).default(90000),
184
+ /**
185
+ * How many times the SAME request is sent again after it stayed silent for
186
+ * `firstTokenTimeoutMs` (a stalled connection usually answers on the next try). 0
187
+ * disables the retry. Never counts as a turn.
188
+ */
189
+ firstTokenRetries: z.number().int().min(0).max(3).default(1),
170
190
  maxContextChars: z.number().int().positive().default(800000),
171
191
  /**
172
192
  * Per-call output token budget for one agent turn. 4096 starves reasoning-heavy models,
@@ -178,10 +198,130 @@ const configObjectSchema = z
178
198
  })
179
199
  .default(() => ({
180
200
  maxTurns: 100,
181
- timeoutMs: 300000,
201
+ timeoutMs: 600000,
202
+ firstTokenTimeoutMs: 90000,
203
+ firstTokenRetries: 1,
182
204
  maxContextChars: 800000,
183
205
  maxOutputTokens: 16384,
184
206
  })),
207
+ /**
208
+ * Python analysis and downloadable artifacts. `enabled: false` registers none of the
209
+ * analysis tools. The interpreter is discovered automatically (or pinned with `--python`).
210
+ */
211
+ analysis: z
212
+ .object({
213
+ enabled: z.boolean().default(true),
214
+ /**
215
+ * Where `python_run` executes. `managed` is the discovered Python (not a sandbox); `oci`
216
+ * is a Docker or Podman container started from a pinned image. GLOBAL ONLY: a project
217
+ * layer cannot choose the runtime or the image (it is ignored with a diagnostic).
218
+ */
219
+ runtime: z.enum(["managed", "oci"]).default("managed"),
220
+ /** Container runtime settings (global only). Used when `runtime` is `oci`. */
221
+ oci: z
222
+ .object({
223
+ engine: z.enum(["docker", "podman"]).default("docker"),
224
+ /** `name@sha256:<digest>`: an image without a digest is rejected at load time. */
225
+ image: z
226
+ .string()
227
+ .regex(OCI_IMAGE_PATTERN, "analysis.oci.image must be pinned by digest: <name>@sha256:<64 hex characters>")
228
+ .optional(),
229
+ memoryMb: z.number().int().min(256).max(65_536).default(2048),
230
+ cpus: z.number().min(0.5).max(64).default(2),
231
+ })
232
+ .strict()
233
+ .default(() => ({ engine: "docker", memoryMb: 2048, cpus: 2 })),
234
+ limits: z
235
+ .object({
236
+ /** Default and maximum run time of one python_run call. */
237
+ timeoutMs: z.number().int().min(1000).max(900_000).default(120_000),
238
+ /** Files published by one execution. */
239
+ maxFiles: z.number().int().min(1).max(1000).default(200),
240
+ maxFileBytes: z
241
+ .number()
242
+ .int()
243
+ .positive()
244
+ .default(100 * 1024 * 1024),
245
+ /** Total bytes published by one execution. */
246
+ maxOutputBytes: z
247
+ .number()
248
+ .int()
249
+ .positive()
250
+ .default(500 * 1024 * 1024),
251
+ /** Size cap of each stdout/stderr log of a job. */
252
+ maxLogBytes: z
253
+ .number()
254
+ .int()
255
+ .positive()
256
+ .default(10 * 1024 * 1024),
257
+ })
258
+ .strict()
259
+ .default(() => ({
260
+ timeoutMs: 120_000,
261
+ maxFiles: 200,
262
+ maxFileBytes: 100 * 1024 * 1024,
263
+ maxOutputBytes: 500 * 1024 * 1024,
264
+ maxLogBytes: 10 * 1024 * 1024,
265
+ })),
266
+ /** Tabular data (CSV, TSV, JSON, JSONL, XLSX) ingested into one SQLite file per dataset. */
267
+ data: z
268
+ .object({
269
+ /** Largest file ingested (an upload or a workspace file). */
270
+ maxUploadBytes: z
271
+ .number()
272
+ .int()
273
+ .positive()
274
+ .default(200 * 1024 * 1024),
275
+ /** Rows per sheet; a larger file stops the ingestion and leaves no dataset. */
276
+ maxRows: z.number().int().positive().default(5_000_000),
277
+ /** Time limit of one `data_query` statement; the engine process is killed past it. */
278
+ queryTimeoutMs: z.number().int().min(100).max(60_000).default(5000),
279
+ /** Sorting and filtering are disabled above this many rows (the file has no indexes). */
280
+ maxInteractiveRows: z.number().int().positive().default(1_000_000),
281
+ })
282
+ .strict()
283
+ .default(() => ({
284
+ maxUploadBytes: 200 * 1024 * 1024,
285
+ maxRows: 5_000_000,
286
+ queryTimeoutMs: 5000,
287
+ maxInteractiveRows: 1_000_000,
288
+ })),
289
+ /**
290
+ * Retention of analysis data (`0` disables the automatic deletion of that class). Global
291
+ * only: one sweep covers every workspace, so a project layer cannot shorten it.
292
+ */
293
+ retention: z
294
+ .object({
295
+ /** Logs and staging of a job; its script and inputs once no artifact is `ready`. */
296
+ jobsDays: z.number().int().min(0).max(3650).default(30),
297
+ /** The scratch folder (`work/`) of a job. */
298
+ intermediateDays: z.number().int().min(0).max(3650).default(7),
299
+ /** Artifacts older than this become `expired` (folder deleted, row kept). 0 = keep. */
300
+ artifactsDays: z.number().int().min(0).max(3650).default(0),
301
+ })
302
+ .strict()
303
+ .default(() => ({ jobsDays: 30, intermediateDays: 7, artifactsDays: 0 })),
304
+ })
305
+ .strict()
306
+ .default(() => ({
307
+ enabled: true,
308
+ runtime: "managed",
309
+ oci: { engine: "docker", memoryMb: 2048, cpus: 2 },
310
+ retention: { jobsDays: 30, intermediateDays: 7, artifactsDays: 0 },
311
+ limits: {
312
+ timeoutMs: 120_000,
313
+ maxFiles: 200,
314
+ maxFileBytes: 100 * 1024 * 1024,
315
+ maxOutputBytes: 500 * 1024 * 1024,
316
+ maxLogBytes: 10 * 1024 * 1024,
317
+ },
318
+ data: {
319
+ maxUploadBytes: 200 * 1024 * 1024,
320
+ maxRows: 5_000_000,
321
+ queryTimeoutMs: 5000,
322
+ maxInteractiveRows: 1_000_000,
323
+ },
324
+ })),
185
325
  tui: z
186
326
  .object({
187
327
  /** Horizontal padding (columns) around the editor input box. Editor-only. */
@@ -328,7 +468,11 @@ export async function loadConfigWithProvenance(workspace, options = {}) {
328
468
  form: canonical && name in canonical ? "canonical" : "alias",
329
469
  },
330
470
  ]));
331
- return { config, keys: new Set(Object.keys(object)), sources };
471
+ const analysisRaw = object.analysis && typeof object.analysis === "object" && !Array.isArray(object.analysis)
472
+ ? object.analysis
473
+ : {};
474
+ const ignored = GLOBAL_ONLY_ANALYSIS.filter((key) => key in analysisRaw).map((key) => `analysis.${key}`);
475
+ return { config, keys: new Set(Object.keys(object)), sources, ignored, file };
332
476
  };
333
477
  const global = await parseLayer(globalFile, "global");
334
478
  // Skill activation is deliberately project-local; never carry a similarly named global field
@@ -339,8 +483,10 @@ export async function loadConfigWithProvenance(workspace, options = {}) {
339
483
  // Parsed `provider.model` of the selected layer itself (before env/CLI overrides), used to
340
484
  // decide whether its legacy root `provider` is usable against a saved `/connect` profile.
341
485
  let selectedLegacyModel = "";
486
+ let ignoredKeys = [];
342
487
  if (selectedFile && selectedFile !== globalFile) {
343
488
  const selected = await parseLayer(selectedFile, options.file ? "explicit" : "project");
489
+ ignoredKeys = selected.ignored;
344
490
  selectedLegacyModel = selected.config.provider.model;
345
491
  const overlaid = { ...config };
346
492
  for (const key of selected.keys) {
@@ -383,6 +529,17 @@ export async function loadConfigWithProvenance(workspace, options = {}) {
383
529
  overlaid.builtinPlugins = { ...overlaid.builtinPlugins, ...selected.config.builtinPlugins };
384
530
  continue;
385
531
  }
532
+ if (key === "analysis") {
533
+ // `runtime`, `oci` and `retention` are global-only (a repository must not pick the binary
534
+ // that runs scripts, nor shorten the retention of every workspace's data).
535
+ overlaid.analysis = {
536
+ ...selected.config.analysis,
537
+ runtime: global.config.analysis.runtime,
538
+ oci: global.config.analysis.oci,
539
+ retention: global.config.analysis.retention,
540
+ };
541
+ continue;
542
+ }
386
543
  overlaid[key] = selected.config[key];
387
544
  }
388
545
  if (selected.keys.has("mcp") || selected.keys.has("mcpServers"))
@@ -421,6 +578,7 @@ export async function loadConfigWithProvenance(workspace, options = {}) {
421
578
  },
422
579
  }
423
580
  : {}),
581
+ ...(ignoredKeys.length ? { ignored: ignoredKeys } : {}),
424
582
  },
425
583
  };
426
584
  }
@@ -528,7 +686,10 @@ const SETTABLE_SECTIONS = {
528
686
  tui: configObjectSchema.shape.tui.removeDefault(),
529
687
  websearch: configObjectSchema.shape.websearch.removeDefault(),
530
688
  agents: configObjectSchema.shape.agents.removeDefault(),
689
+ analysis: configObjectSchema.shape.analysis.removeDefault(),
531
690
  };
691
+ const ANALYSIS_LIMITS = SETTABLE_SECTIONS.analysis.shape.limits.removeDefault();
692
+ const ANALYSIS_RETENTION = SETTABLE_SECTIONS.analysis.shape.retention.removeDefault();
532
693
  const SETTABLE_KEYS = {
533
694
  "compaction.auto": SETTABLE_SECTIONS.compaction.shape.auto,
534
695
  "compaction.threshold": SETTABLE_SECTIONS.compaction.shape.threshold,
@@ -540,6 +701,8 @@ const SETTABLE_KEYS = {
540
701
  "limits.maxOutputTokens": SETTABLE_SECTIONS.limits.shape.maxOutputTokens,
541
702
  "limits.maxContextChars": SETTABLE_SECTIONS.limits.shape.maxContextChars,
542
703
  "limits.timeoutMs": SETTABLE_SECTIONS.limits.shape.timeoutMs,
704
+ "limits.firstTokenTimeoutMs": SETTABLE_SECTIONS.limits.shape.firstTokenTimeoutMs,
705
+ "limits.firstTokenRetries": SETTABLE_SECTIONS.limits.shape.firstTokenRetries,
543
706
  "pluginHooks.timeoutMs": SETTABLE_SECTIONS.pluginHooks.shape.timeoutMs,
544
707
  "tui.paddingX": SETTABLE_SECTIONS.tui.shape.paddingX,
545
708
  "tui.contentPaddingX": SETTABLE_SECTIONS.tui.shape.contentPaddingX,
@@ -547,6 +710,11 @@ const SETTABLE_KEYS = {
547
710
  "websearch.provider": SETTABLE_SECTIONS.websearch.shape.provider,
548
711
  "agents.active": SETTABLE_SECTIONS.agents.shape.active,
549
712
  "agents.effort": SETTABLE_SECTIONS.agents.shape.effort,
713
+ "analysis.enabled": SETTABLE_SECTIONS.analysis.shape.enabled,
714
+ "analysis.limits.timeoutMs": ANALYSIS_LIMITS.shape.timeoutMs,
715
+ "analysis.retention.jobsDays": ANALYSIS_RETENTION.shape.jobsDays,
716
+ "analysis.retention.intermediateDays": ANALYSIS_RETENTION.shape.intermediateDays,
717
+ "analysis.retention.artifactsDays": ANALYSIS_RETENTION.shape.artifactsDays,
550
718
  };
551
719
  export function isSettableSettingKey(key) {
552
720
  return Object.prototype.hasOwnProperty.call(SETTABLE_KEYS, key);
@@ -603,11 +771,21 @@ export async function setConfigValue(input) {
603
771
  throw new Error("Global Alisio configuration must be a JSON object");
604
772
  raw = existing;
605
773
  }
606
- const [section, leaf] = input.key.split(".");
607
- const container = raw[section] && typeof raw[section] === "object" && !Array.isArray(raw[section])
608
- ? raw[section]
609
- : {};
610
- raw[section] = { ...container, [leaf]: parsed.data };
774
+ // Keys are `section.leaf` or `section.group.leaf`: every object on the path keeps its other fields.
775
+ const path = input.key.split(".");
776
+ const setPath = (target, depth) => {
777
+ const name = path[depth];
778
+ if (depth === path.length - 1)
779
+ return { ...target, [name]: parsed.data };
780
+ const child = target[name];
781
+ return {
782
+ ...target,
783
+ [name]: setPath(child && typeof child === "object" && !Array.isArray(child)
784
+ ? child
785
+ : {}, depth + 1),
786
+ };
787
+ };
788
+ raw = setPath(raw, 0);
611
789
  raw.schemaVersion ??= 1;
612
790
  await mkdir(dirname(file), { recursive: true, mode: 0o700 });
613
791
  const temporary = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
@@ -1,4 +1,4 @@
1
- import type { CompactionCheckpoint, Message, ToolCall, ToolResult } from "@alisio/sdk";
1
+ import type { AnalysisCapability, ArtifactPublisher, ArtifactRef, CompactionCheckpoint, InstallPreview, Message, ToolCall, ToolResult } from "@alisio/sdk";
2
2
  export interface Session {
3
3
  id: string;
4
4
  workspace: string;
@@ -188,6 +188,11 @@ export interface Policy {
188
188
  write: boolean;
189
189
  process: boolean;
190
190
  external: boolean;
191
+ /**
192
+ * Pre-allows the `analysis.run` capability only (`--allow-analysis`); never widens `process`.
193
+ * Absent means false.
194
+ */
195
+ analysis?: boolean;
191
196
  }
192
197
  export type ApprovalDecision = "once" | "session" | "deny";
193
198
  export interface ApprovalRequest {
@@ -199,7 +204,70 @@ export interface ApprovalRequest {
199
204
  effect: "write" | "process" | "external";
200
205
  input: Record<string, unknown>;
201
206
  signal: AbortSignal;
207
+ /** The approval is for this capability of the tool, not for the whole effect. */
208
+ capability?: AnalysisCapability;
209
+ /** Capability approvals: the first 40 lines of the script. */
210
+ preview?: string;
211
+ /** Capability approvals: how the code runs (`managed` is not sandboxed). */
212
+ runtime?: "managed" | "oci";
213
+ /** `analysis.install`: the packages, the size estimate and the network need (once or deny). */
214
+ install?: InstallPreview;
202
215
  }
216
+ /**
217
+ * Persisted capability grants as the runner sees them (implemented by `CapabilityGrants`). A
218
+ * `session` decision is stored for the ROOT session and survives restarts; `once` and `deny`
219
+ * decisions are audit rows only.
220
+ */
221
+ export interface CapabilityGate {
222
+ granted(capability: AnalysisCapability, sessionId: string): boolean;
223
+ record(input: {
224
+ capability: AnalysisCapability;
225
+ sessionId: string;
226
+ scope: "once" | "session";
227
+ decision: "allow" | "deny";
228
+ source: "tui" | "web" | "flag" | "headless-grant";
229
+ callId?: string;
230
+ runId?: string;
231
+ correlationId?: string;
232
+ }): void;
233
+ /** Recorded as the source of interactive decisions (`tui` or `web`). */
234
+ source?: "tui" | "web";
235
+ /** Runtime mode shown with `analysis.run` approvals (read live: the configuration can change). */
236
+ runtime?: "managed" | "oci";
237
+ /**
238
+ * The script to show in an approval whose input does not carry it (a rerun runs a saved script).
239
+ * Absent or undefined: the preview is taken from `input.code`.
240
+ */
241
+ preview?(input: Record<string, unknown>, sessionId: string): Promise<string | undefined>;
242
+ }
243
+ /** One artifact published during a tool call, with its local path (for `artifact_published`). */
244
+ export interface PublishedArtifactInfo {
245
+ artifact: ArtifactRef;
246
+ path: string;
247
+ warnings: string[];
248
+ executionId?: string;
249
+ }
250
+ /** The publisher core tools receive; `publishOutputs` publishes a whole staging folder at once. */
251
+ export interface CoreArtifactPublisher extends ArtifactPublisher {
252
+ publishOutputs(staging: string, options: {
253
+ executionId: string;
254
+ title?: string;
255
+ partial?: boolean;
256
+ provenance?: Record<string, unknown>;
257
+ }): Promise<PublishedArtifactInfo[]>;
258
+ /** Detailed variant of `publishText` (path and warnings). */
259
+ publishTextDetailed(input: {
260
+ fileName: string;
261
+ title?: string;
262
+ text: string;
263
+ }): Promise<PublishedArtifactInfo>;
264
+ }
265
+ /** Creates the publisher of one tool call; `announce` emits `artifact_published`. */
266
+ export type ArtifactPublisherFactory = (call: {
267
+ sessionId: string;
268
+ runId: string;
269
+ callId: string;
270
+ }, announce: (published: PublishedArtifactInfo) => void) => CoreArtifactPublisher | undefined;
203
271
  /** Optional interactive approval for write/process/external tools not pre-allowed by the Policy. */
204
272
  export type ApprovalHandler = (request: ApprovalRequest) => Promise<ApprovalDecision>;
205
273
  export interface HookFailure {
@@ -1,6 +1,6 @@
1
- import { type Attachment, type Message, type ModelProvider, type RunEvent, type ToolDefinition } from "@alisio/sdk";
1
+ import { type Attachment, type DatasetRef, type Message, type ModelProvider, type RunEvent, type ToolDefinition, type ToolResult } from "@alisio/sdk";
2
2
  import type { PathAccess } from "../runtime/access.ts";
3
- import type { ApprovalHandler, ContextSource, Policy, RunnerExtensions, SessionStore } from "./contracts.ts";
3
+ import type { ApprovalHandler, ArtifactPublisherFactory, CapabilityGate, ContextSource, Policy, RunnerExtensions, SessionStore } from "./contracts.ts";
4
4
  import type { ToolRegistry } from "./registry.ts";
5
5
  export interface CompactionSettings {
6
6
  /** Compact automatically when context usage crosses `threshold` of a known window. */
@@ -24,6 +24,10 @@ export interface RunnerOptions {
24
24
  workspace: string;
25
25
  maxTurns?: number;
26
26
  timeoutMs?: number;
27
+ /** See `RunnerSettingsPatch.firstTokenTimeoutMs`. */
28
+ firstTokenTimeoutMs?: number;
29
+ /** See `RunnerSettingsPatch.firstTokenRetries`. */
30
+ firstTokenRetries?: number;
27
31
  maxContextChars?: number;
28
32
  maxOutputTokens?: number;
29
33
  maxTokens?: number;
@@ -42,6 +46,10 @@ export interface RunnerOptions {
42
46
  pathAccess?: PathAccess;
43
47
  /** Generic hooks (implemented by the plugin host) for compaction and session start. */
44
48
  extensions?: RunnerExtensions;
49
+ /** Persisted capability grants (`analysis.run`); without it capabilities only follow `effect`. */
50
+ capabilities?: CapabilityGate;
51
+ /** Artifact publisher of each tool call (`ToolContext.artifacts`). */
52
+ artifacts?: ArtifactPublisherFactory;
45
53
  /**
46
54
  * Provider-native tool definitions (for example a hosted `web_search` tool) appended to every
47
55
  * request's `tools` alongside the registry's function tools. Opt-in, set once for the whole
@@ -66,6 +74,17 @@ export interface RunnerSettingsPatch {
66
74
  maxContextChars?: number;
67
75
  /** Per-run timeout in milliseconds; the next run honors it. */
68
76
  timeoutMs?: number;
77
+ /**
78
+ * Stop a model request that stays completely silent (no text, reasoning or tool-call delta)
79
+ * for this many milliseconds. `0` or unset disables it: only the run timeout applies.
80
+ */
81
+ firstTokenTimeoutMs?: number;
82
+ /**
83
+ * How many times the same request is sent again after a silent timeout (`firstTokenTimeoutMs`).
84
+ * `0` or unset disables it. A retry is not a turn; it only happens while nothing at all was
85
+ * received for the request.
86
+ */
87
+ firstTokenRetries?: number;
69
88
  }
70
89
  /**
71
90
  * Per-run overrides used by embedders and child sessions. Callers must only NARROW: `policy`
@@ -76,6 +95,8 @@ export interface RunOptions {
76
95
  display?: string;
77
96
  /** Images attached to this turn's user message, sent as vision content parts. */
78
97
  attachments?: Attachment[];
98
+ /** Datasets attached to this turn (UI chips); their text summary is part of the prompt. */
99
+ datasets?: DatasetRef[];
79
100
  /** Extra system instructions for this run (e.g. an agent persona). */
80
101
  instructions?: string;
81
102
  toolFilter?: (tool: ToolDefinition) => boolean;
@@ -91,6 +112,10 @@ export interface RunOptions {
91
112
  /** Per-call output token budget for this run; beats the runner-level budget when set. */
92
113
  maxOutputTokens?: number;
93
114
  timeoutMs?: number;
115
+ /** Silent-request limit for this run; beats the runner-level `firstTokenTimeoutMs`. */
116
+ firstTokenTimeoutMs?: number;
117
+ /** Silent-request retries for this run; beats the runner-level `firstTokenRetries`. */
118
+ firstTokenRetries?: number;
94
119
  /** Reasoning effort level for this run; beats the runner-level default when set. */
95
120
  reasoningEffort?: string;
96
121
  /** Run id to use for this run's events (e.g. preassigned by an embedder); a UUID otherwise. */
@@ -130,8 +155,11 @@ export declare class AgentRunner {
130
155
  */
131
156
  applySettings(patch: RunnerSettingsPatch): void;
132
157
  private emitter;
133
- /** Tools offered to the model under the current policy (and optional run narrowing). */
134
- availableTools(run?: RunOptions): ToolDefinition[];
158
+ /**
159
+ * Tools offered to the model under the current policy (and optional run narrowing). With a
160
+ * session, a capability tool is also offered when that session's root holds a persisted grant.
161
+ */
162
+ availableTools(run?: RunOptions, sessionId?: string): ToolDefinition[];
135
163
  /** Abort signal of the session's active run, if any (used to cascade cancellation). */
136
164
  signal(sessionId: string): AbortSignal | undefined;
137
165
  isRunning(sessionId: string): boolean;
@@ -182,6 +210,27 @@ export declare class AgentRunner {
182
210
  signal?: AbortSignal;
183
211
  }): Promise<CompactionResult | undefined>;
184
212
  private compactLocked;
213
+ /**
214
+ * Gates and executes one tool call: external-directory paths, the capability/effect gate with
215
+ * its approval, the call journal, artifact publishing and the result bound. Shared by model
216
+ * runs and `runToolCall`, so a call started from a UI passes exactly the same gates.
217
+ */
218
+ private executeCall;
219
+ /**
220
+ * Runs ONE tool call without a model (spec §20 `runToolCall`): an action the user started from
221
+ * a UI, such as copying an artifact into the workspace. It is a real run of the session
222
+ * (`run_started` → `tool_*` → `run_completed`, journaled) and the call passes exactly the gates
223
+ * of a model call (`write` approval, capabilities, paths). The transcript stays valid for every
224
+ * provider: a user note (shown as `display`), an assistant message with the call, the tool
225
+ * result and a short assistant summary.
226
+ */
227
+ runToolCall(sessionId: string, name: string, input: Record<string, unknown>, options?: RunOptions & {
228
+ signal?: AbortSignal;
229
+ }): Promise<{
230
+ runId: string;
231
+ status: "completed";
232
+ result: ToolResult;
233
+ }>;
185
234
  run(sessionId: string, prompt: string, signal?: AbortSignal, options?: RunOptions): Promise<{
186
235
  sessionId: string;
187
236
  text: string;