@alisio/core 0.1.0-alpha.8 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -7
- package/dist/agents/active.d.ts +99 -0
- package/dist/agents/active.js +218 -0
- package/dist/agents/capabilities.d.ts +26 -0
- package/dist/agents/capabilities.js +120 -0
- package/dist/agents/create-agent-skill.d.ts +11 -0
- package/dist/agents/create-agent-skill.js +60 -0
- package/dist/agents/definitions.d.ts +88 -0
- package/dist/agents/definitions.js +404 -0
- package/dist/agents/draft.d.ts +41 -0
- package/dist/agents/draft.js +133 -0
- package/dist/agents/service.d.ts +57 -0
- package/dist/agents/service.js +86 -0
- package/dist/agents/templates.d.ts +8 -0
- package/dist/agents/templates.js +102 -0
- package/dist/analysis/capabilities.d.ts +48 -0
- package/dist/analysis/capabilities.js +90 -0
- package/dist/analysis/data/csv.d.ts +46 -0
- package/dist/analysis/data/csv.js +240 -0
- package/dist/analysis/data/datasets.d.ts +132 -0
- package/dist/analysis/data/datasets.js +441 -0
- package/dist/analysis/data/describe.d.ts +27 -0
- package/dist/analysis/data/describe.js +103 -0
- package/dist/analysis/data/engine-client.d.ts +58 -0
- package/dist/analysis/data/engine-client.js +304 -0
- package/dist/analysis/data/engine-entry.d.ts +1 -0
- package/dist/analysis/data/engine-entry.js +59 -0
- package/dist/analysis/data/engine-source.d.ts +4 -0
- package/dist/analysis/data/engine-source.js +6 -0
- package/dist/analysis/data/engine-types.d.ts +86 -0
- package/dist/analysis/data/engine-types.js +7 -0
- package/dist/analysis/data/infer.d.ts +42 -0
- package/dist/analysis/data/infer.js +118 -0
- package/dist/analysis/data/ingest-worker.d.ts +17 -0
- package/dist/analysis/data/ingest-worker.js +365 -0
- package/dist/analysis/data/json.d.ts +19 -0
- package/dist/analysis/data/json.js +44 -0
- package/dist/analysis/data/query-worker.d.ts +12 -0
- package/dist/analysis/data/query-worker.js +75 -0
- package/dist/analysis/data/rows.d.ts +39 -0
- package/dist/analysis/data/rows.js +120 -0
- package/dist/analysis/data/sql-guard.d.ts +20 -0
- package/dist/analysis/data/sql-guard.js +129 -0
- package/dist/analysis/data/stats.d.ts +18 -0
- package/dist/analysis/data/stats.js +77 -0
- package/dist/analysis/install-hints.d.ts +33 -0
- package/dist/analysis/install-hints.js +144 -0
- package/dist/analysis/janitor.d.ts +60 -0
- package/dist/analysis/janitor.js +264 -0
- package/dist/analysis/jobs.d.ts +94 -0
- package/dist/analysis/jobs.js +165 -0
- package/dist/analysis/oci.d.ts +129 -0
- package/dist/analysis/oci.js +330 -0
- package/dist/analysis/python/sources.d.ts +2 -0
- package/dist/analysis/python/sources.js +10 -0
- package/dist/analysis/requirements.d.ts +32 -0
- package/dist/analysis/requirements.js +73 -0
- package/dist/analysis/rerun.d.ts +40 -0
- package/dist/analysis/rerun.js +136 -0
- package/dist/analysis/runtime-manager.d.ts +146 -0
- package/dist/analysis/runtime-manager.js +472 -0
- package/dist/analysis/status.d.ts +21 -0
- package/dist/analysis/status.js +52 -0
- package/dist/application.d.ts +118 -3
- package/dist/application.js +510 -19
- package/dist/artifacts/kinds.d.ts +35 -0
- package/dist/artifacts/kinds.js +177 -0
- package/dist/artifacts/publisher.d.ts +7 -0
- package/dist/artifacts/publisher.js +29 -0
- package/dist/artifacts/store.d.ts +119 -0
- package/dist/artifacts/store.js +581 -0
- package/dist/background/janitor.d.ts +22 -0
- package/dist/background/janitor.js +62 -0
- package/dist/background/mirror.d.ts +14 -0
- package/dist/background/mirror.js +41 -0
- package/dist/background/notify.d.ts +58 -0
- package/dist/background/notify.js +172 -0
- package/dist/background/output.d.ts +46 -0
- package/dist/background/output.js +179 -0
- package/dist/background/service.d.ts +137 -0
- package/dist/background/service.js +410 -0
- package/dist/background/store.d.ts +103 -0
- package/dist/background/store.js +195 -0
- package/dist/changelog/data.d.ts +6 -0
- package/dist/changelog/data.js +293 -0
- package/dist/changelog/index.d.ts +5 -0
- package/dist/changelog/index.js +7 -0
- package/dist/changelog/parse.d.ts +53 -0
- package/dist/changelog/parse.js +160 -0
- package/dist/commands/builtins.d.ts +10 -0
- package/dist/commands/builtins.js +161 -0
- package/dist/commands/catalog.d.ts +86 -0
- package/dist/commands/catalog.js +304 -0
- package/dist/config.d.ts +252 -5
- package/dist/config.js +514 -13
- package/dist/core/compaction.js +13 -2
- package/dist/core/contracts.d.ts +180 -3
- package/dist/core/opt-in.d.ts +12 -0
- package/dist/core/opt-in.js +12 -0
- package/dist/core/output-limit.d.ts +34 -0
- package/dist/core/output-limit.js +39 -0
- package/dist/core/run-stats.d.ts +16 -0
- package/dist/core/run-stats.js +8 -0
- package/dist/core/runner.d.ts +152 -4
- package/dist/core/runner.js +792 -138
- package/dist/core/timeout.d.ts +15 -0
- package/dist/core/timeout.js +61 -0
- package/dist/core/truncation.d.ts +30 -0
- package/dist/core/truncation.js +83 -0
- package/dist/goal/blockers.d.ts +45 -0
- package/dist/goal/blockers.js +51 -0
- package/dist/goal/budget.d.ts +24 -0
- package/dist/goal/budget.js +53 -0
- package/dist/goal/command.d.ts +36 -0
- package/dist/goal/command.js +98 -0
- package/dist/goal/format.d.ts +19 -0
- package/dist/goal/format.js +102 -0
- package/dist/goal/host.d.ts +29 -0
- package/dist/goal/host.js +42 -0
- package/dist/goal/index.d.ts +9 -0
- package/dist/goal/index.js +9 -0
- package/dist/goal/machine.d.ts +186 -0
- package/dist/goal/machine.js +344 -0
- package/dist/goal/prompts.d.ts +10 -0
- package/dist/goal/prompts.js +75 -0
- package/dist/goal/service.d.ts +113 -0
- package/dist/goal/service.js +273 -0
- package/dist/goal/store.d.ts +59 -0
- package/dist/goal/store.js +184 -0
- package/dist/index.d.ts +51 -6
- package/dist/index.js +49 -4
- package/dist/mcp/connector.js +38 -6
- package/dist/mcp/rich.d.ts +41 -0
- package/dist/mcp/rich.js +362 -0
- package/dist/permissions/modes.d.ts +72 -0
- package/dist/permissions/modes.js +98 -0
- package/dist/plan/exit-plan.d.ts +25 -0
- package/dist/plan/exit-plan.js +130 -0
- package/dist/plan/state.d.ts +102 -0
- package/dist/plan/state.js +165 -0
- package/dist/plugins/host.d.ts +39 -1
- package/dist/plugins/host.js +150 -9
- package/dist/providers/settings.d.ts +15 -0
- package/dist/providers/settings.js +38 -0
- package/dist/reload.d.ts +75 -0
- package/dist/reload.js +204 -0
- package/dist/resources/context.d.ts +5 -0
- package/dist/resources/context.js +13 -1
- package/dist/runtime/access.d.ts +62 -0
- package/dist/runtime/access.js +115 -0
- package/dist/runtime/blobs.d.ts +51 -0
- package/dist/runtime/blobs.js +177 -0
- package/dist/runtime/diff.d.ts +23 -0
- package/dist/runtime/diff.js +154 -0
- package/dist/runtime/ids.d.ts +8 -0
- package/dist/runtime/ids.js +32 -0
- package/dist/runtime/paths.d.ts +11 -0
- package/dist/runtime/paths.js +17 -1
- package/dist/runtime/process.d.ts +22 -0
- package/dist/runtime/process.js +100 -15
- package/dist/runtime/sqlite.d.ts +2 -0
- package/dist/runtime/sqlite.js +18 -0
- package/dist/runtime/store.d.ts +91 -3
- package/dist/runtime/store.js +465 -11
- package/dist/runtime/zip.d.ts +24 -0
- package/dist/runtime/zip.js +126 -0
- package/dist/sessions/children.js +17 -1
- package/dist/sessions/side-questions.d.ts +57 -0
- package/dist/sessions/side-questions.js +161 -0
- package/dist/startup/screen.js +6 -5
- package/dist/tools/analysis.d.ts +37 -0
- package/dist/tools/analysis.js +573 -0
- package/dist/tools/artifacts.d.ts +14 -0
- package/dist/tools/artifacts.js +143 -0
- package/dist/tools/background.d.ts +7 -0
- package/dist/tools/background.js +165 -0
- package/dist/tools/data.d.ts +7 -0
- package/dist/tools/data.js +160 -0
- package/dist/tools/execute.d.ts +5 -0
- package/dist/tools/execute.js +15 -3
- package/dist/tools/goal.d.ts +19 -0
- package/dist/tools/goal.js +126 -0
- package/dist/tools/search.d.ts +17 -3
- package/dist/tools/search.js +153 -0
- package/dist/tools/standard.d.ts +4 -1
- package/dist/tools/standard.js +99 -18
- package/dist/version.d.ts +5 -3
- package/dist/version.js +35 -6
- package/package.json +2 -2
package/dist/config.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chmod, mkdir, open, readFile, rename, unlink } from "node:fs/promises";
|
|
1
|
+
import { chmod, mkdir, open, readFile, realpath, rename, unlink } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -75,12 +75,25 @@ 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),
|
|
81
88
|
provider: providerSchema.default(() => providerSchema.parse({})),
|
|
82
89
|
plugins: z.array(z.string()).default([]),
|
|
83
90
|
skills: z.array(z.string()).default([]),
|
|
91
|
+
/**
|
|
92
|
+
* Extra directories the mediated path policy may touch outside the workspace. Resolved
|
|
93
|
+
* relative to the defining configuration file and canonicalized on load. Additive across
|
|
94
|
+
* layers like `plugins`/`skills`: a lower layer can only ADD, never clear the global list.
|
|
95
|
+
*/
|
|
96
|
+
additionalDirectories: z.array(z.string().trim().min(1)).default([]),
|
|
84
97
|
/** Project-local enable/disable overrides for effective non-plugin skills. */
|
|
85
98
|
skillOverrides: z.record(z.string(), z.object({ enabled: z.boolean() }).strict()).default({}),
|
|
86
99
|
mcp: z
|
|
@@ -140,7 +153,15 @@ const configObjectSchema = z
|
|
|
140
153
|
websearch: z
|
|
141
154
|
.object({
|
|
142
155
|
provider: z
|
|
143
|
-
.enum([
|
|
156
|
+
.enum([
|
|
157
|
+
"searxng",
|
|
158
|
+
"duckduckgo-instant",
|
|
159
|
+
"duckduckgo-html",
|
|
160
|
+
"tavily",
|
|
161
|
+
"brave",
|
|
162
|
+
"serpapi",
|
|
163
|
+
"native",
|
|
164
|
+
])
|
|
144
165
|
.optional(),
|
|
145
166
|
searxngUrl: z.string().optional(),
|
|
146
167
|
apiKeyEnv: z.string().optional(),
|
|
@@ -151,19 +172,260 @@ const configObjectSchema = z
|
|
|
151
172
|
.default(() => ({ nativeToolType: "web_search" })),
|
|
152
173
|
limits: z
|
|
153
174
|
.object({
|
|
154
|
-
maxTurns: z.number().int().min(1).max(100).default(
|
|
155
|
-
timeoutMs: z.number().int().min(100).default(
|
|
156
|
-
|
|
157
|
-
|
|
175
|
+
maxTurns: z.number().int().min(1).max(100).default(100),
|
|
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),
|
|
190
|
+
/**
|
|
191
|
+
* How many times a turn is requested again after the model's response was cut off by
|
|
192
|
+
* `maxOutputTokens` before it was usable (no text and no tool call, or a tool call with
|
|
193
|
+
* incomplete arguments). The truncated calls are never executed. 0 disables the
|
|
194
|
+
* recovery. Never counts as a turn.
|
|
195
|
+
*/
|
|
196
|
+
truncationRecoveries: z.number().int().min(0).max(5).default(2),
|
|
197
|
+
maxContextChars: z.number().int().positive().default(800000),
|
|
198
|
+
/**
|
|
199
|
+
* Per-call output token budget for one agent turn. Deliberately WITHOUT a schema default:
|
|
200
|
+
* when unset, the budget of each request is the model catalog's declared maximum (capped
|
|
201
|
+
* at 65536) or 16384 (see `core/output-limit.ts`), so "set by the user" stays
|
|
202
|
+
* distinguishable from "defaulted". A value set here always wins. 4096 starves
|
|
203
|
+
* reasoning-heavy models, which can spend the whole budget on `reasoning_content`
|
|
204
|
+
* before any usable text arrives.
|
|
205
|
+
*/
|
|
206
|
+
maxOutputTokens: z.number().int().positive().optional(),
|
|
158
207
|
/** Cumulative tokens per run; default is proportional to the context window. */
|
|
159
208
|
maxTokens: z.number().int().positive().optional(),
|
|
160
209
|
})
|
|
161
210
|
.default(() => ({
|
|
162
|
-
maxTurns:
|
|
163
|
-
timeoutMs:
|
|
164
|
-
|
|
165
|
-
|
|
211
|
+
maxTurns: 100,
|
|
212
|
+
timeoutMs: 600000,
|
|
213
|
+
firstTokenTimeoutMs: 90000,
|
|
214
|
+
firstTokenRetries: 1,
|
|
215
|
+
truncationRecoveries: 2,
|
|
216
|
+
maxContextChars: 800000,
|
|
166
217
|
})),
|
|
218
|
+
/**
|
|
219
|
+
* Python analysis and downloadable artifacts. `enabled: false` registers none of the
|
|
220
|
+
* analysis tools. The interpreter is discovered automatically (or pinned with `--python`).
|
|
221
|
+
*/
|
|
222
|
+
analysis: z
|
|
223
|
+
.object({
|
|
224
|
+
enabled: z.boolean().default(true),
|
|
225
|
+
/**
|
|
226
|
+
* Where `python_run` executes. `managed` is the discovered Python (not a sandbox); `oci`
|
|
227
|
+
* is a Docker or Podman container started from a pinned image. GLOBAL ONLY: a project
|
|
228
|
+
* layer cannot choose the runtime or the image (it is ignored with a diagnostic).
|
|
229
|
+
*/
|
|
230
|
+
runtime: z.enum(["managed", "oci"]).default("managed"),
|
|
231
|
+
/** Container runtime settings (global only). Used when `runtime` is `oci`. */
|
|
232
|
+
oci: z
|
|
233
|
+
.object({
|
|
234
|
+
engine: z.enum(["docker", "podman"]).default("docker"),
|
|
235
|
+
/** `name@sha256:<digest>`: an image without a digest is rejected at load time. */
|
|
236
|
+
image: z
|
|
237
|
+
.string()
|
|
238
|
+
.regex(OCI_IMAGE_PATTERN, "analysis.oci.image must be pinned by digest: <name>@sha256:<64 hex characters>")
|
|
239
|
+
.optional(),
|
|
240
|
+
memoryMb: z.number().int().min(256).max(65_536).default(2048),
|
|
241
|
+
cpus: z.number().min(0.5).max(64).default(2),
|
|
242
|
+
})
|
|
243
|
+
.strict()
|
|
244
|
+
.default(() => ({ engine: "docker", memoryMb: 2048, cpus: 2 })),
|
|
245
|
+
limits: z
|
|
246
|
+
.object({
|
|
247
|
+
/** Default and maximum run time of one python_run call. */
|
|
248
|
+
timeoutMs: z.number().int().min(1000).max(900_000).default(120_000),
|
|
249
|
+
/** Files published by one execution. */
|
|
250
|
+
maxFiles: z.number().int().min(1).max(1000).default(200),
|
|
251
|
+
maxFileBytes: z
|
|
252
|
+
.number()
|
|
253
|
+
.int()
|
|
254
|
+
.positive()
|
|
255
|
+
.default(100 * 1024 * 1024),
|
|
256
|
+
/** Total bytes published by one execution. */
|
|
257
|
+
maxOutputBytes: z
|
|
258
|
+
.number()
|
|
259
|
+
.int()
|
|
260
|
+
.positive()
|
|
261
|
+
.default(500 * 1024 * 1024),
|
|
262
|
+
/** Size cap of each stdout/stderr log of a job. */
|
|
263
|
+
maxLogBytes: z
|
|
264
|
+
.number()
|
|
265
|
+
.int()
|
|
266
|
+
.positive()
|
|
267
|
+
.default(10 * 1024 * 1024),
|
|
268
|
+
})
|
|
269
|
+
.strict()
|
|
270
|
+
.default(() => ({
|
|
271
|
+
timeoutMs: 120_000,
|
|
272
|
+
maxFiles: 200,
|
|
273
|
+
maxFileBytes: 100 * 1024 * 1024,
|
|
274
|
+
maxOutputBytes: 500 * 1024 * 1024,
|
|
275
|
+
maxLogBytes: 10 * 1024 * 1024,
|
|
276
|
+
})),
|
|
277
|
+
/** Tabular data (CSV, TSV, JSON, JSONL, XLSX) ingested into one SQLite file per dataset. */
|
|
278
|
+
data: z
|
|
279
|
+
.object({
|
|
280
|
+
/** Largest file ingested (an upload or a workspace file). */
|
|
281
|
+
maxUploadBytes: z
|
|
282
|
+
.number()
|
|
283
|
+
.int()
|
|
284
|
+
.positive()
|
|
285
|
+
.default(200 * 1024 * 1024),
|
|
286
|
+
/** Rows per sheet; a larger file stops the ingestion and leaves no dataset. */
|
|
287
|
+
maxRows: z.number().int().positive().default(5_000_000),
|
|
288
|
+
/** Time limit of one `data_query` statement; the engine process is killed past it. */
|
|
289
|
+
queryTimeoutMs: z.number().int().min(100).max(60_000).default(5000),
|
|
290
|
+
/** Sorting and filtering are disabled above this many rows (the file has no indexes). */
|
|
291
|
+
maxInteractiveRows: z.number().int().positive().default(1_000_000),
|
|
292
|
+
})
|
|
293
|
+
.strict()
|
|
294
|
+
.default(() => ({
|
|
295
|
+
maxUploadBytes: 200 * 1024 * 1024,
|
|
296
|
+
maxRows: 5_000_000,
|
|
297
|
+
queryTimeoutMs: 5000,
|
|
298
|
+
maxInteractiveRows: 1_000_000,
|
|
299
|
+
})),
|
|
300
|
+
/**
|
|
301
|
+
* Retention of analysis data (`0` disables the automatic deletion of that class). Global
|
|
302
|
+
* only: one sweep covers every workspace, so a project layer cannot shorten it.
|
|
303
|
+
*/
|
|
304
|
+
retention: z
|
|
305
|
+
.object({
|
|
306
|
+
/** Logs and staging of a job; its script and inputs once no artifact is `ready`. */
|
|
307
|
+
jobsDays: z.number().int().min(0).max(3650).default(30),
|
|
308
|
+
/** The scratch folder (`work/`) of a job. */
|
|
309
|
+
intermediateDays: z.number().int().min(0).max(3650).default(7),
|
|
310
|
+
/** Artifacts older than this become `expired` (folder deleted, row kept). 0 = keep. */
|
|
311
|
+
artifactsDays: z.number().int().min(0).max(3650).default(0),
|
|
312
|
+
})
|
|
313
|
+
.strict()
|
|
314
|
+
.default(() => ({ jobsDays: 30, intermediateDays: 7, artifactsDays: 0 })),
|
|
315
|
+
})
|
|
316
|
+
.strict()
|
|
317
|
+
.default(() => ({
|
|
318
|
+
enabled: true,
|
|
319
|
+
runtime: "managed",
|
|
320
|
+
oci: { engine: "docker", memoryMb: 2048, cpus: 2 },
|
|
321
|
+
retention: { jobsDays: 30, intermediateDays: 7, artifactsDays: 0 },
|
|
322
|
+
limits: {
|
|
323
|
+
timeoutMs: 120_000,
|
|
324
|
+
maxFiles: 200,
|
|
325
|
+
maxFileBytes: 100 * 1024 * 1024,
|
|
326
|
+
maxOutputBytes: 500 * 1024 * 1024,
|
|
327
|
+
maxLogBytes: 10 * 1024 * 1024,
|
|
328
|
+
},
|
|
329
|
+
data: {
|
|
330
|
+
maxUploadBytes: 200 * 1024 * 1024,
|
|
331
|
+
maxRows: 5_000_000,
|
|
332
|
+
queryTimeoutMs: 5000,
|
|
333
|
+
maxInteractiveRows: 1_000_000,
|
|
334
|
+
},
|
|
335
|
+
})),
|
|
336
|
+
/**
|
|
337
|
+
* Background tasks (`bg_run`, `bg_list`, `bg_output`, `bg_stop`). They are ordinary child
|
|
338
|
+
* processes of Alisio (not detached, not sandboxed): they end when Alisio ends. `enabled:
|
|
339
|
+
* false` registers none of the tools (applies from the next start). `retentionDays` is
|
|
340
|
+
* global-only: one sweep covers every workspace, so a project layer cannot shorten it.
|
|
341
|
+
*/
|
|
342
|
+
tasks: z
|
|
343
|
+
.object({
|
|
344
|
+
enabled: z.boolean().default(true),
|
|
345
|
+
/** Live (queued, running or stopping) tasks per root session. */
|
|
346
|
+
maxPerSession: z.number().int().min(1).max(32).default(4),
|
|
347
|
+
/** Watchdog: a task still running after this long is stopped and fails with `timeout`. */
|
|
348
|
+
maxRunMs: z.number().int().min(1000).max(86_400_000).default(3_600_000),
|
|
349
|
+
/** Size limit of one task log (the head is kept, a short tail is added when it ends). */
|
|
350
|
+
maxOutputBytes: z.number().int().min(65_536).max(104_857_600).default(2_097_152),
|
|
351
|
+
/** Finished task rows and their logs are deleted after this many days (0 keeps them). */
|
|
352
|
+
retentionDays: z.number().int().min(0).max(3650).default(7),
|
|
353
|
+
})
|
|
354
|
+
.strict()
|
|
355
|
+
.default(() => ({
|
|
356
|
+
enabled: true,
|
|
357
|
+
maxPerSession: 4,
|
|
358
|
+
maxRunMs: 3_600_000,
|
|
359
|
+
maxOutputBytes: 2_097_152,
|
|
360
|
+
retentionDays: 7,
|
|
361
|
+
})),
|
|
362
|
+
/**
|
|
363
|
+
* `/goal`: a session objective the runtime keeps working on, with hard caps. The token budget
|
|
364
|
+
* is NOT a setting (none unless the user gives one with `budget=`); without it only the turn
|
|
365
|
+
* and time caps stop a goal. `maxTurns` counts runs (a goal turn is one run); `maxMinutes` is
|
|
366
|
+
* the time spent inside runs. The breakers pause a goal that makes no progress.
|
|
367
|
+
*/
|
|
368
|
+
goal: z
|
|
369
|
+
.object({
|
|
370
|
+
enabled: z.boolean().default(true),
|
|
371
|
+
maxTurns: z.number().int().min(1).max(1000).default(50),
|
|
372
|
+
maxMinutes: z.number().int().min(1).max(1440).default(120),
|
|
373
|
+
/** Pause after this many consecutive identical replies (observed, nudged, then paused). */
|
|
374
|
+
repeatedReplyLimit: z.number().int().min(2).max(20).default(3),
|
|
375
|
+
/** Pause after this many consecutive turns without a tool call. */
|
|
376
|
+
noToolTurnsLimit: z.number().int().min(2).max(20).default(3),
|
|
377
|
+
/** Consecutive turns in which the model must report the same blocker before it stops. */
|
|
378
|
+
blockedRepeats: z.number().int().min(1).max(10).default(2),
|
|
379
|
+
})
|
|
380
|
+
.strict()
|
|
381
|
+
.default(() => ({
|
|
382
|
+
enabled: true,
|
|
383
|
+
maxTurns: 50,
|
|
384
|
+
maxMinutes: 120,
|
|
385
|
+
repeatedReplyLimit: 3,
|
|
386
|
+
noToolTurnsLimit: 3,
|
|
387
|
+
blockedRepeats: 2,
|
|
388
|
+
})),
|
|
389
|
+
tui: z
|
|
390
|
+
.object({
|
|
391
|
+
/** Horizontal padding (columns) around the editor input box. Editor-only. */
|
|
392
|
+
paddingX: z.number().int().min(0).max(4).default(1),
|
|
393
|
+
/**
|
|
394
|
+
* Horizontal inset (columns) applied to each side of the transcript content, so
|
|
395
|
+
* agent-written rows keep breathing room from the window borders. Independent of
|
|
396
|
+
* `paddingX` (which styles only the editor input box). Clamped on narrow terminals so the
|
|
397
|
+
* content column never collapses below a usable width.
|
|
398
|
+
*/
|
|
399
|
+
contentPaddingX: z.number().int().min(0).max(12).default(2),
|
|
400
|
+
/**
|
|
401
|
+
* Offer effective skills as first-class `skill:<id>` editor slash-autocomplete entries.
|
|
402
|
+
* Off hides those entries; the `/skills` manager and its argument completion stay available.
|
|
403
|
+
*/
|
|
404
|
+
skillSlashCommands: z.boolean().default(true),
|
|
405
|
+
})
|
|
406
|
+
.strict()
|
|
407
|
+
.default(() => ({ paddingX: 1, contentPaddingX: 2, skillSlashCommands: true })),
|
|
408
|
+
/**
|
|
409
|
+
* The ACTIVE agent drives the main session: its system prompt is appended to every prompt and
|
|
410
|
+
* its optional model selector is offered when the agent is chosen (`/agents`). `build` is the
|
|
411
|
+
* built-in default (full-power, no added persona); `plan` is the built-in read-only planner.
|
|
412
|
+
* `effort` holds a reasoning effort level chosen with `/effort` for a model that advertises
|
|
413
|
+
* `effort.supportedLevels`; it is validated against the ACTIVE model on use, falling back to
|
|
414
|
+
* the model's default level when unsupported.
|
|
415
|
+
*/
|
|
416
|
+
agents: z
|
|
417
|
+
.object({
|
|
418
|
+
active: z
|
|
419
|
+
.string()
|
|
420
|
+
.trim()
|
|
421
|
+
.min(1)
|
|
422
|
+
.max(100)
|
|
423
|
+
.regex(/^[a-zA-Z0-9][a-zA-Z0-9._:-]*$/, "invalid agent name")
|
|
424
|
+
.default("build"),
|
|
425
|
+
effort: z.string().trim().min(1).max(50).optional(),
|
|
426
|
+
})
|
|
427
|
+
.strict()
|
|
428
|
+
.default(() => ({ active: "build" })),
|
|
167
429
|
})
|
|
168
430
|
.strict()
|
|
169
431
|
.superRefine((config, context) => {
|
|
@@ -192,6 +454,26 @@ function isUsableLegacyModel(model) {
|
|
|
192
454
|
const normalized = model.trim();
|
|
193
455
|
return normalized !== "" && normalized !== LEGACY_PROVIDER_MODEL_PLACEHOLDER;
|
|
194
456
|
}
|
|
457
|
+
/**
|
|
458
|
+
* Appends the entries of `selected` after the `global` list, dropping exact duplicates so the
|
|
459
|
+
* global entries keep their order and the selected layer only ever ADDS to the collection. An
|
|
460
|
+
* empty `selected` list therefore leaves the global list intact. Entries were already resolved
|
|
461
|
+
* per layer by `parseLayer`, so deduplication happens on the resolved strings.
|
|
462
|
+
*/
|
|
463
|
+
function mergeUnique(global, selected) {
|
|
464
|
+
const seen = new Set(global);
|
|
465
|
+
const merged = [...global];
|
|
466
|
+
for (const entry of selected)
|
|
467
|
+
if (!seen.has(entry)) {
|
|
468
|
+
seen.add(entry);
|
|
469
|
+
merged.push(entry);
|
|
470
|
+
}
|
|
471
|
+
return merged;
|
|
472
|
+
}
|
|
473
|
+
/** Absolute, canonical directory when it exists; a missing path keeps its resolved absolute form. */
|
|
474
|
+
async function canonicalDirectory(path) {
|
|
475
|
+
return realpath(path).catch(() => resolve(path));
|
|
476
|
+
}
|
|
195
477
|
/**
|
|
196
478
|
* Whether legacy provider selection should take priority over a saved plugin profile for this
|
|
197
479
|
* run: an explicit endpoint override, or a trusted project/explicit layer whose parsed root
|
|
@@ -224,6 +506,9 @@ export async function loadConfigWithProvenance(workspace, options = {}) {
|
|
|
224
506
|
const raw = (await readJson(file)) ?? {};
|
|
225
507
|
const config = configSchema.parse(raw);
|
|
226
508
|
config.skills = config.skills.map((p) => resolve(file, "..", p));
|
|
509
|
+
// Extra tool roots are paths relative to the config file; canonicalize so containment checks
|
|
510
|
+
// compare real directories (a symlinked root never escapes its declared location).
|
|
511
|
+
config.additionalDirectories = await Promise.all(config.additionalDirectories.map((p) => canonicalDirectory(resolve(file, "..", p))));
|
|
227
512
|
// Plugin entries are paths (relative to the config file) or npm package names.
|
|
228
513
|
config.plugins = config.plugins.map((p) => (isPathSpec(p) ? resolve(file, "..", p) : p));
|
|
229
514
|
for (const server of Object.values(config.mcp.servers))
|
|
@@ -247,7 +532,17 @@ export async function loadConfigWithProvenance(workspace, options = {}) {
|
|
|
247
532
|
form: canonical && name in canonical ? "canonical" : "alias",
|
|
248
533
|
},
|
|
249
534
|
]));
|
|
250
|
-
|
|
535
|
+
const analysisRaw = object.analysis && typeof object.analysis === "object" && !Array.isArray(object.analysis)
|
|
536
|
+
? object.analysis
|
|
537
|
+
: {};
|
|
538
|
+
const tasksRaw = object.tasks && typeof object.tasks === "object" && !Array.isArray(object.tasks)
|
|
539
|
+
? object.tasks
|
|
540
|
+
: {};
|
|
541
|
+
const ignored = [
|
|
542
|
+
...GLOBAL_ONLY_ANALYSIS.filter((key) => key in analysisRaw).map((key) => `analysis.${key}`),
|
|
543
|
+
...("retentionDays" in tasksRaw ? ["tasks.retentionDays"] : []),
|
|
544
|
+
];
|
|
545
|
+
return { config, keys: new Set(Object.keys(object)), sources, ignored, file };
|
|
251
546
|
};
|
|
252
547
|
const global = await parseLayer(globalFile, "global");
|
|
253
548
|
// Skill activation is deliberately project-local; never carry a similarly named global field
|
|
@@ -258,15 +553,72 @@ export async function loadConfigWithProvenance(workspace, options = {}) {
|
|
|
258
553
|
// Parsed `provider.model` of the selected layer itself (before env/CLI overrides), used to
|
|
259
554
|
// decide whether its legacy root `provider` is usable against a saved `/connect` profile.
|
|
260
555
|
let selectedLegacyModel = "";
|
|
556
|
+
let ignoredKeys = [];
|
|
261
557
|
if (selectedFile && selectedFile !== globalFile) {
|
|
262
558
|
const selected = await parseLayer(selectedFile, options.file ? "explicit" : "project");
|
|
559
|
+
ignoredKeys = selected.ignored;
|
|
263
560
|
selectedLegacyModel = selected.config.provider.model;
|
|
264
561
|
const overlaid = { ...config };
|
|
265
562
|
for (const key of selected.keys) {
|
|
266
563
|
if (key === "mcp" || key === "mcpServers")
|
|
267
564
|
continue;
|
|
268
|
-
if (key in selected.config)
|
|
269
|
-
|
|
565
|
+
if (!(key in selected.config))
|
|
566
|
+
continue;
|
|
567
|
+
// Additive collections: the selected layer ADDS to the global list (global entries
|
|
568
|
+
// first, exact duplicates dropped); an empty selected list never clears the global one.
|
|
569
|
+
if (key === "plugins") {
|
|
570
|
+
overlaid.plugins = mergeUnique(overlaid.plugins, selected.config.plugins);
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
if (key === "skills") {
|
|
574
|
+
overlaid.skills = mergeUnique(overlaid.skills, selected.config.skills);
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (key === "additionalDirectories") {
|
|
578
|
+
overlaid.additionalDirectories = mergeUnique(overlaid.additionalDirectories, selected.config.additionalDirectories);
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
// Additive records: merge by key, the selected layer wins per key.
|
|
582
|
+
if (key === "pluginOverrides") {
|
|
583
|
+
overlaid.pluginOverrides = {
|
|
584
|
+
...overlaid.pluginOverrides,
|
|
585
|
+
...selected.config.pluginOverrides,
|
|
586
|
+
};
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
if (key === "skillOverrides") {
|
|
590
|
+
// Global skill overrides were reset above (skill activation is project-local); the
|
|
591
|
+
// selected overrides still merge into that empty record and win per key.
|
|
592
|
+
overlaid.skillOverrides = { ...overlaid.skillOverrides, ...selected.config.skillOverrides };
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (key === "builtinPlugins") {
|
|
596
|
+
// Per-plugin options merge by plugin id: the selected entry wins per id, so
|
|
597
|
+
// `{id: {enabled: false}}` in the selected layer still disables that built-in while
|
|
598
|
+
// every other global entry stays.
|
|
599
|
+
overlaid.builtinPlugins = { ...overlaid.builtinPlugins, ...selected.config.builtinPlugins };
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (key === "analysis") {
|
|
603
|
+
// `runtime`, `oci` and `retention` are global-only (a repository must not pick the binary
|
|
604
|
+
// that runs scripts, nor shorten the retention of every workspace's data).
|
|
605
|
+
overlaid.analysis = {
|
|
606
|
+
...selected.config.analysis,
|
|
607
|
+
runtime: global.config.analysis.runtime,
|
|
608
|
+
oci: global.config.analysis.oci,
|
|
609
|
+
retention: global.config.analysis.retention,
|
|
610
|
+
};
|
|
611
|
+
continue;
|
|
612
|
+
}
|
|
613
|
+
if (key === "tasks") {
|
|
614
|
+
// `retentionDays` is global-only (one sweep deletes the data of every workspace).
|
|
615
|
+
overlaid.tasks = {
|
|
616
|
+
...selected.config.tasks,
|
|
617
|
+
retentionDays: global.config.tasks.retentionDays,
|
|
618
|
+
};
|
|
619
|
+
continue;
|
|
620
|
+
}
|
|
621
|
+
overlaid[key] = selected.config[key];
|
|
270
622
|
}
|
|
271
623
|
if (selected.keys.has("mcp") || selected.keys.has("mcpServers"))
|
|
272
624
|
// Only servers merge upward; `mcp.allow` is a global/user preference and is deliberately
|
|
@@ -304,6 +656,7 @@ export async function loadConfigWithProvenance(workspace, options = {}) {
|
|
|
304
656
|
},
|
|
305
657
|
}
|
|
306
658
|
: {}),
|
|
659
|
+
...(ignoredKeys.length ? { ignored: ignoredKeys } : {}),
|
|
307
660
|
},
|
|
308
661
|
};
|
|
309
662
|
}
|
|
@@ -398,6 +751,154 @@ export async function setGlobalMcpAllow(input) {
|
|
|
398
751
|
}
|
|
399
752
|
return file;
|
|
400
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* The user-facing settings writable through the atomic `setConfigValue` writer, with their zod
|
|
756
|
+
* leaf validators derived from the config schema. Only these keys are accepted; every other
|
|
757
|
+
* config path stays out of reach of the settings menu (unknown keys are rejected, never created).
|
|
758
|
+
*/
|
|
759
|
+
const SETTABLE_SECTIONS = {
|
|
760
|
+
compaction: configObjectSchema.shape.compaction.removeDefault(),
|
|
761
|
+
context: configObjectSchema.shape.context.removeDefault(),
|
|
762
|
+
limits: configObjectSchema.shape.limits.removeDefault(),
|
|
763
|
+
pluginHooks: configObjectSchema.shape.pluginHooks.removeDefault(),
|
|
764
|
+
tui: configObjectSchema.shape.tui.removeDefault(),
|
|
765
|
+
websearch: configObjectSchema.shape.websearch.removeDefault(),
|
|
766
|
+
agents: configObjectSchema.shape.agents.removeDefault(),
|
|
767
|
+
analysis: configObjectSchema.shape.analysis.removeDefault(),
|
|
768
|
+
tasks: configObjectSchema.shape.tasks.removeDefault(),
|
|
769
|
+
goal: configObjectSchema.shape.goal.removeDefault(),
|
|
770
|
+
};
|
|
771
|
+
const ANALYSIS_LIMITS = SETTABLE_SECTIONS.analysis.shape.limits.removeDefault();
|
|
772
|
+
const ANALYSIS_RETENTION = SETTABLE_SECTIONS.analysis.shape.retention.removeDefault();
|
|
773
|
+
const SETTABLE_KEYS = {
|
|
774
|
+
"compaction.auto": SETTABLE_SECTIONS.compaction.shape.auto,
|
|
775
|
+
"compaction.threshold": SETTABLE_SECTIONS.compaction.shape.threshold,
|
|
776
|
+
"compaction.keepTurns": SETTABLE_SECTIONS.compaction.shape.keepTurns,
|
|
777
|
+
"compaction.maxOutputTokens": SETTABLE_SECTIONS.compaction.shape.maxOutputTokens,
|
|
778
|
+
"context.claudeMdFallback": SETTABLE_SECTIONS.context.shape.claudeMdFallback,
|
|
779
|
+
"context.maxBytes": SETTABLE_SECTIONS.context.shape.maxBytes,
|
|
780
|
+
"limits.maxTurns": SETTABLE_SECTIONS.limits.shape.maxTurns,
|
|
781
|
+
"limits.maxOutputTokens": SETTABLE_SECTIONS.limits.shape.maxOutputTokens,
|
|
782
|
+
"limits.maxContextChars": SETTABLE_SECTIONS.limits.shape.maxContextChars,
|
|
783
|
+
"limits.timeoutMs": SETTABLE_SECTIONS.limits.shape.timeoutMs,
|
|
784
|
+
"limits.firstTokenTimeoutMs": SETTABLE_SECTIONS.limits.shape.firstTokenTimeoutMs,
|
|
785
|
+
"limits.firstTokenRetries": SETTABLE_SECTIONS.limits.shape.firstTokenRetries,
|
|
786
|
+
"limits.truncationRecoveries": SETTABLE_SECTIONS.limits.shape.truncationRecoveries,
|
|
787
|
+
"pluginHooks.timeoutMs": SETTABLE_SECTIONS.pluginHooks.shape.timeoutMs,
|
|
788
|
+
"tui.paddingX": SETTABLE_SECTIONS.tui.shape.paddingX,
|
|
789
|
+
"tui.contentPaddingX": SETTABLE_SECTIONS.tui.shape.contentPaddingX,
|
|
790
|
+
"tui.skillSlashCommands": SETTABLE_SECTIONS.tui.shape.skillSlashCommands,
|
|
791
|
+
"websearch.provider": SETTABLE_SECTIONS.websearch.shape.provider,
|
|
792
|
+
"agents.active": SETTABLE_SECTIONS.agents.shape.active,
|
|
793
|
+
"agents.effort": SETTABLE_SECTIONS.agents.shape.effort,
|
|
794
|
+
"analysis.enabled": SETTABLE_SECTIONS.analysis.shape.enabled,
|
|
795
|
+
"analysis.limits.timeoutMs": ANALYSIS_LIMITS.shape.timeoutMs,
|
|
796
|
+
"analysis.retention.jobsDays": ANALYSIS_RETENTION.shape.jobsDays,
|
|
797
|
+
"analysis.retention.intermediateDays": ANALYSIS_RETENTION.shape.intermediateDays,
|
|
798
|
+
"analysis.retention.artifactsDays": ANALYSIS_RETENTION.shape.artifactsDays,
|
|
799
|
+
"tasks.enabled": SETTABLE_SECTIONS.tasks.shape.enabled,
|
|
800
|
+
"tasks.maxPerSession": SETTABLE_SECTIONS.tasks.shape.maxPerSession,
|
|
801
|
+
"tasks.maxRunMs": SETTABLE_SECTIONS.tasks.shape.maxRunMs,
|
|
802
|
+
"tasks.maxOutputBytes": SETTABLE_SECTIONS.tasks.shape.maxOutputBytes,
|
|
803
|
+
"tasks.retentionDays": SETTABLE_SECTIONS.tasks.shape.retentionDays,
|
|
804
|
+
"goal.enabled": SETTABLE_SECTIONS.goal.shape.enabled,
|
|
805
|
+
"goal.maxTurns": SETTABLE_SECTIONS.goal.shape.maxTurns,
|
|
806
|
+
"goal.maxMinutes": SETTABLE_SECTIONS.goal.shape.maxMinutes,
|
|
807
|
+
"goal.repeatedReplyLimit": SETTABLE_SECTIONS.goal.shape.repeatedReplyLimit,
|
|
808
|
+
"goal.noToolTurnsLimit": SETTABLE_SECTIONS.goal.shape.noToolTurnsLimit,
|
|
809
|
+
"goal.blockedRepeats": SETTABLE_SECTIONS.goal.shape.blockedRepeats,
|
|
810
|
+
};
|
|
811
|
+
export function isSettableSettingKey(key) {
|
|
812
|
+
return Object.prototype.hasOwnProperty.call(SETTABLE_KEYS, key);
|
|
813
|
+
}
|
|
814
|
+
/** Unwraps optional/default/nullable wrappers down to the value schema. */
|
|
815
|
+
function settingLeaf(schema) {
|
|
816
|
+
let current = schema;
|
|
817
|
+
for (let depth = 0; depth < 8; depth++) {
|
|
818
|
+
const type = current.def?.type;
|
|
819
|
+
if ((type === "optional" || type === "default" || type === "nullable") &&
|
|
820
|
+
current.def?.innerType)
|
|
821
|
+
current = current.def.innerType;
|
|
822
|
+
else
|
|
823
|
+
break;
|
|
824
|
+
}
|
|
825
|
+
return current;
|
|
826
|
+
}
|
|
827
|
+
/** Every settable key with its value kind (and enum options), for settings editors. */
|
|
828
|
+
export function settableSettings() {
|
|
829
|
+
return Object.keys(SETTABLE_KEYS).map((key) => {
|
|
830
|
+
const leaf = settingLeaf(SETTABLE_KEYS[key]);
|
|
831
|
+
const type = leaf.def?.type;
|
|
832
|
+
if (type === "enum")
|
|
833
|
+
return { key, kind: "enum", options: (leaf.options ?? []).map((o) => String(o)) };
|
|
834
|
+
if (type === "boolean")
|
|
835
|
+
return { key, kind: "boolean" };
|
|
836
|
+
if (type === "number" || type === "int")
|
|
837
|
+
return { key, kind: "number" };
|
|
838
|
+
return { key, kind: "string" };
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Atomically sets one user-facing setting in the global config file (`<config home>/config.json`)
|
|
843
|
+
* while preserving every unrelated JSON field. The value is validated against the same zod leaf
|
|
844
|
+
* used by `configSchema`, so a write can never produce a config the loader would reject. The
|
|
845
|
+
* caller is responsible for applying the change to the running process (see `application.ts`).
|
|
846
|
+
*/
|
|
847
|
+
export async function setConfigValue(input) {
|
|
848
|
+
const validator = SETTABLE_KEYS[input.key];
|
|
849
|
+
if (!validator)
|
|
850
|
+
throw new Error(`Unknown setting key: ${input.key}`);
|
|
851
|
+
const parsed = validator.safeParse(input.value);
|
|
852
|
+
if (!parsed.success) {
|
|
853
|
+
const detail = parsed.error.issues[0];
|
|
854
|
+
throw new Error(detail
|
|
855
|
+
? `Invalid value for ${input.key}: ${detail.path.join(".") || "value"} ${detail.message}`
|
|
856
|
+
: `Invalid value for ${input.key}`);
|
|
857
|
+
}
|
|
858
|
+
const file = join(configHome(), "config.json");
|
|
859
|
+
let raw = {};
|
|
860
|
+
if (await exists(file)) {
|
|
861
|
+
const existing = JSON.parse(await readFile(file, "utf8"));
|
|
862
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing))
|
|
863
|
+
throw new Error("Global Alisio configuration must be a JSON object");
|
|
864
|
+
raw = existing;
|
|
865
|
+
}
|
|
866
|
+
// Keys are `section.leaf` or `section.group.leaf`: every object on the path keeps its other fields.
|
|
867
|
+
const path = input.key.split(".");
|
|
868
|
+
const setPath = (target, depth) => {
|
|
869
|
+
const name = path[depth];
|
|
870
|
+
if (depth === path.length - 1)
|
|
871
|
+
return { ...target, [name]: parsed.data };
|
|
872
|
+
const child = target[name];
|
|
873
|
+
return {
|
|
874
|
+
...target,
|
|
875
|
+
[name]: setPath(child && typeof child === "object" && !Array.isArray(child)
|
|
876
|
+
? child
|
|
877
|
+
: {}, depth + 1),
|
|
878
|
+
};
|
|
879
|
+
};
|
|
880
|
+
raw = setPath(raw, 0);
|
|
881
|
+
raw.schemaVersion ??= 1;
|
|
882
|
+
await mkdir(dirname(file), { recursive: true, mode: 0o700 });
|
|
883
|
+
const temporary = `${file}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
884
|
+
const handle = await open(temporary, "wx", 0o600);
|
|
885
|
+
try {
|
|
886
|
+
await handle.writeFile(`${JSON.stringify(raw, null, 2)}\n`, "utf8");
|
|
887
|
+
await handle.sync();
|
|
888
|
+
}
|
|
889
|
+
finally {
|
|
890
|
+
await handle.close();
|
|
891
|
+
}
|
|
892
|
+
try {
|
|
893
|
+
await rename(temporary, file);
|
|
894
|
+
await chmod(file, 0o600);
|
|
895
|
+
}
|
|
896
|
+
catch (error) {
|
|
897
|
+
await unlink(temporary).catch(() => { });
|
|
898
|
+
throw error;
|
|
899
|
+
}
|
|
900
|
+
return file;
|
|
901
|
+
}
|
|
401
902
|
/** Atomically updates one project plugin override while preserving every unrelated JSON field. */
|
|
402
903
|
export async function setProjectPluginEnabled(input) {
|
|
403
904
|
const file = join(input.workspace, ".alisio", "config.json");
|
package/dist/core/compaction.js
CHANGED
|
@@ -116,6 +116,10 @@ function clampMessage(message, maxToolResultChars, maxTextChars) {
|
|
|
116
116
|
let used = 0;
|
|
117
117
|
let changed = false;
|
|
118
118
|
const content = message.result.content.map((part) => {
|
|
119
|
+
// Only the text projection is clipped: ui/image parts are display-only transcript data and
|
|
120
|
+
// are never truncated (raw bytes never reach the model, which only sees the projection).
|
|
121
|
+
if (part.type !== "text")
|
|
122
|
+
return part;
|
|
119
123
|
const room = maxToolResultChars - used;
|
|
120
124
|
const text = room <= 0
|
|
121
125
|
? TRUNCATION_MARKER
|
|
@@ -213,7 +217,8 @@ export function reduceMessagesToBudget(messages, targetChars, options = {}) {
|
|
|
213
217
|
: m.role === "assistant"
|
|
214
218
|
? m.text.length > textCap
|
|
215
219
|
: m.role === "tool" &&
|
|
216
|
-
m.result.content.reduce((a, p) => a + p.text.length, 0) >
|
|
220
|
+
m.result.content.reduce((a, p) => a + (p.type === "text" ? p.text.length : 0), 0) >
|
|
221
|
+
toolCap;
|
|
217
222
|
if (over) {
|
|
218
223
|
best = i;
|
|
219
224
|
bestSize = current;
|
|
@@ -260,7 +265,12 @@ export function serializeForSummary(messages, maxItemChars = 4_000) {
|
|
|
260
265
|
lines.push(`TOOL CALL ${c.id} ${c.name}: ${clip(c.arguments, maxItemChars)}`);
|
|
261
266
|
}
|
|
262
267
|
else {
|
|
263
|
-
|
|
268
|
+
// Only the text projection is summarized: ui/image parts are display data and never
|
|
269
|
+
// reach the summarizer (raw image bytes in particular stay out of the checkpoint).
|
|
270
|
+
const text = m.result.content
|
|
271
|
+
.filter((p) => p.type === "text")
|
|
272
|
+
.map((c) => c.text)
|
|
273
|
+
.join("\n");
|
|
264
274
|
lines.push(`TOOL RESULT ${m.callId}${m.result.isError ? " (error)" : ""}: ${clip(text, maxItemChars)}`);
|
|
265
275
|
}
|
|
266
276
|
}
|
|
@@ -381,6 +391,7 @@ async function completeTextWithMeta(provider, request) {
|
|
|
381
391
|
signal: request.signal,
|
|
382
392
|
...(request.model ? { model: request.model } : {}),
|
|
383
393
|
...(request.sessionId ? { sessionId: request.sessionId } : {}),
|
|
394
|
+
...(request.reasoningEffort ? { reasoningEffort: request.reasoningEffort } : {}),
|
|
384
395
|
})) {
|
|
385
396
|
request.signal.throwIfAborted();
|
|
386
397
|
if (event.type === "completed") {
|