@alisio/core 0.1.0-alpha.20 → 0.1.0-alpha.22

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 (104) hide show
  1. package/dist/agents/active.d.ts +19 -0
  2. package/dist/agents/active.js +41 -0
  3. package/dist/agents/capabilities.d.ts +26 -0
  4. package/dist/agents/capabilities.js +120 -0
  5. package/dist/agents/create-agent-skill.d.ts +11 -0
  6. package/dist/agents/create-agent-skill.js +60 -0
  7. package/dist/agents/definitions.d.ts +88 -0
  8. package/dist/agents/definitions.js +404 -0
  9. package/dist/agents/draft.d.ts +41 -0
  10. package/dist/agents/draft.js +133 -0
  11. package/dist/agents/service.d.ts +57 -0
  12. package/dist/agents/service.js +86 -0
  13. package/dist/agents/templates.d.ts +8 -0
  14. package/dist/agents/templates.js +102 -0
  15. package/dist/analysis/capabilities.d.ts +48 -0
  16. package/dist/analysis/capabilities.js +90 -0
  17. package/dist/analysis/data/csv.d.ts +46 -0
  18. package/dist/analysis/data/csv.js +240 -0
  19. package/dist/analysis/data/datasets.d.ts +132 -0
  20. package/dist/analysis/data/datasets.js +441 -0
  21. package/dist/analysis/data/describe.d.ts +27 -0
  22. package/dist/analysis/data/describe.js +103 -0
  23. package/dist/analysis/data/engine-client.d.ts +58 -0
  24. package/dist/analysis/data/engine-client.js +304 -0
  25. package/dist/analysis/data/engine-entry.d.ts +1 -0
  26. package/dist/analysis/data/engine-entry.js +59 -0
  27. package/dist/analysis/data/engine-source.d.ts +4 -0
  28. package/dist/analysis/data/engine-source.js +6 -0
  29. package/dist/analysis/data/engine-types.d.ts +86 -0
  30. package/dist/analysis/data/engine-types.js +7 -0
  31. package/dist/analysis/data/infer.d.ts +42 -0
  32. package/dist/analysis/data/infer.js +118 -0
  33. package/dist/analysis/data/ingest-worker.d.ts +17 -0
  34. package/dist/analysis/data/ingest-worker.js +365 -0
  35. package/dist/analysis/data/json.d.ts +19 -0
  36. package/dist/analysis/data/json.js +44 -0
  37. package/dist/analysis/data/query-worker.d.ts +12 -0
  38. package/dist/analysis/data/query-worker.js +75 -0
  39. package/dist/analysis/data/rows.d.ts +39 -0
  40. package/dist/analysis/data/rows.js +120 -0
  41. package/dist/analysis/data/sql-guard.d.ts +20 -0
  42. package/dist/analysis/data/sql-guard.js +129 -0
  43. package/dist/analysis/data/stats.d.ts +18 -0
  44. package/dist/analysis/data/stats.js +77 -0
  45. package/dist/analysis/install-hints.d.ts +33 -0
  46. package/dist/analysis/install-hints.js +144 -0
  47. package/dist/analysis/janitor.d.ts +60 -0
  48. package/dist/analysis/janitor.js +264 -0
  49. package/dist/analysis/jobs.d.ts +94 -0
  50. package/dist/analysis/jobs.js +165 -0
  51. package/dist/analysis/oci.d.ts +129 -0
  52. package/dist/analysis/oci.js +330 -0
  53. package/dist/analysis/python/sources.d.ts +2 -0
  54. package/dist/analysis/python/sources.js +10 -0
  55. package/dist/analysis/requirements.d.ts +32 -0
  56. package/dist/analysis/requirements.js +73 -0
  57. package/dist/analysis/rerun.d.ts +40 -0
  58. package/dist/analysis/rerun.js +136 -0
  59. package/dist/analysis/runtime-manager.d.ts +146 -0
  60. package/dist/analysis/runtime-manager.js +472 -0
  61. package/dist/analysis/status.d.ts +21 -0
  62. package/dist/analysis/status.js +52 -0
  63. package/dist/application.d.ts +48 -0
  64. package/dist/application.js +228 -0
  65. package/dist/artifacts/kinds.d.ts +35 -0
  66. package/dist/artifacts/kinds.js +177 -0
  67. package/dist/artifacts/publisher.d.ts +7 -0
  68. package/dist/artifacts/publisher.js +29 -0
  69. package/dist/artifacts/store.d.ts +119 -0
  70. package/dist/artifacts/store.js +581 -0
  71. package/dist/commands/builtins.js +13 -0
  72. package/dist/commands/catalog.js +13 -1
  73. package/dist/config.d.ts +102 -0
  74. package/dist/config.js +167 -6
  75. package/dist/core/contracts.d.ts +69 -1
  76. package/dist/core/runner.d.ts +34 -4
  77. package/dist/core/runner.js +388 -130
  78. package/dist/index.d.ts +30 -5
  79. package/dist/index.js +29 -4
  80. package/dist/mcp/rich.js +4 -0
  81. package/dist/plugins/host.js +14 -6
  82. package/dist/runtime/blobs.d.ts +22 -0
  83. package/dist/runtime/blobs.js +84 -1
  84. package/dist/runtime/ids.d.ts +8 -0
  85. package/dist/runtime/ids.js +32 -0
  86. package/dist/runtime/paths.d.ts +5 -0
  87. package/dist/runtime/paths.js +6 -0
  88. package/dist/runtime/process.d.ts +12 -0
  89. package/dist/runtime/process.js +64 -4
  90. package/dist/runtime/sqlite.d.ts +2 -0
  91. package/dist/runtime/sqlite.js +18 -0
  92. package/dist/runtime/store.d.ts +5 -0
  93. package/dist/runtime/store.js +75 -0
  94. package/dist/runtime/zip.d.ts +24 -0
  95. package/dist/runtime/zip.js +126 -0
  96. package/dist/sessions/children.js +4 -0
  97. package/dist/tools/analysis.d.ts +37 -0
  98. package/dist/tools/analysis.js +572 -0
  99. package/dist/tools/artifacts.d.ts +14 -0
  100. package/dist/tools/artifacts.js +143 -0
  101. package/dist/tools/data.d.ts +7 -0
  102. package/dist/tools/data.js +160 -0
  103. package/dist/tools/execute.js +6 -2
  104. package/package.json +2 -2
@@ -0,0 +1,404 @@
1
+ /**
2
+ * User agent definitions as portable Markdown files, shared with other harnesses:
3
+ *
4
+ * - project scope: `<workspace>/.agents/agents/<id>.md`
5
+ * - global scope: `~/.agents/agents/<id>.md`
6
+ *
7
+ * The file follows the common subagent convention (Claude Code / OpenCode): YAML frontmatter with
8
+ * `name` (the id slug), `description`, `model` and optional keys such as `tools`, and the system
9
+ * prompt as the Markdown body. Alisio-only settings live under one namespaced `alisio:` key that
10
+ * other tools ignore. Edits go through the YAML document model, so every key (and comment) another
11
+ * tool wrote round-trips unchanged; only the keys Alisio owns are set or removed.
12
+ *
13
+ * Pure file logic with portable `node:fs` only; no runtime-specific APIs.
14
+ */
15
+ import { mkdir, readdir, readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
16
+ import { basename, join } from "node:path";
17
+ import { Document, isMap, parseDocument } from "yaml";
18
+ /** Same identifier rule as the subagents plugin (and Claude Code agent names). */
19
+ export const AGENT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
20
+ const MAX_ID_LENGTH = 48;
21
+ /** Built-in agent names a user file must not shadow (main `build`/`plan`, subagents). */
22
+ export const RESERVED_AGENT_IDS = new Set(["build", "plan", "general", "explore"]);
23
+ export const AGENT_LIMITS = {
24
+ name: 80,
25
+ description: 300,
26
+ instructions: 24_000,
27
+ model: 300,
28
+ };
29
+ export const AGENT_REASONING_SUMMARIES = [
30
+ "auto",
31
+ "none",
32
+ "concise",
33
+ "detailed",
34
+ ];
35
+ export const AGENT_VERBOSITIES = ["low", "medium", "high"];
36
+ export const AGENT_TEXT_FORMATS = ["text", "json_object", "json_schema"];
37
+ const EFFORT = /^[a-z][a-z_-]{0,19}$/;
38
+ /** Frontmatter key of the Alisio-only settings (ignored by other harnesses). */
39
+ export const ALISIO_KEY = "alisio";
40
+ /** The directory of one scope. */
41
+ export function agentScopeDir(scope, roots) {
42
+ if (scope === "global")
43
+ return join(roots.home, ".agents", "agents");
44
+ if (!roots.workspace)
45
+ throw new Error("The project scope needs an open workspace");
46
+ return join(roots.workspace, ".agents", "agents");
47
+ }
48
+ const str = (value) => (typeof value === "string" ? value : undefined);
49
+ /**
50
+ * Validates and normalizes an untrusted definition body (web API, TUI, drafts). Unknown keys are
51
+ * dropped; strings are trimmed; empty optional settings are omitted.
52
+ */
53
+ export function validateAgentDefinitionInput(raw) {
54
+ const errors = [];
55
+ const body = (raw && typeof raw === "object" ? raw : {});
56
+ const name = str(body.name)?.trim() ?? "";
57
+ if (!name)
58
+ errors.push({ field: "name", message: "Name is required" });
59
+ else if (name.length > AGENT_LIMITS.name)
60
+ errors.push({ field: "name", message: `Name is limited to ${AGENT_LIMITS.name} characters` });
61
+ else if (!slugify(name))
62
+ errors.push({ field: "name", message: "Name needs at least one letter or digit" });
63
+ const description = str(body.description)?.trim() ?? "";
64
+ if (body.description !== undefined && typeof body.description !== "string")
65
+ errors.push({ field: "description", message: "Description must be text" });
66
+ if (description.length > AGENT_LIMITS.description)
67
+ errors.push({
68
+ field: "description",
69
+ message: `Description is limited to ${AGENT_LIMITS.description} characters`,
70
+ });
71
+ const instructions = str(body.instructions)?.trim() ?? "";
72
+ if (body.instructions !== undefined && typeof body.instructions !== "string")
73
+ errors.push({ field: "instructions", message: "Instructions must be text" });
74
+ if (instructions.length > AGENT_LIMITS.instructions)
75
+ errors.push({
76
+ field: "instructions",
77
+ message: `Instructions are limited to ${AGENT_LIMITS.instructions} characters`,
78
+ });
79
+ const model = str(body.model)?.trim() ?? "";
80
+ if (!model)
81
+ errors.push({ field: "model", message: "Model is required" });
82
+ else if (model.length > AGENT_LIMITS.model || /\s/.test(model))
83
+ errors.push({ field: "model", message: "Model must be a provider/model selector" });
84
+ const reasoningRaw = body.reasoning;
85
+ const reasoning = {};
86
+ if (reasoningRaw !== undefined && reasoningRaw !== null) {
87
+ if (typeof reasoningRaw !== "object")
88
+ errors.push({ field: "reasoning", message: "Reasoning must be an object" });
89
+ else {
90
+ const { effort, summary } = reasoningRaw;
91
+ if (effort !== undefined && effort !== null && effort !== "") {
92
+ if (typeof effort !== "string" || !EFFORT.test(effort))
93
+ errors.push({ field: "reasoning.effort", message: "Invalid reasoning effort level" });
94
+ else
95
+ reasoning.effort = effort;
96
+ }
97
+ if (summary !== undefined && summary !== null && summary !== "") {
98
+ if (!AGENT_REASONING_SUMMARIES.includes(summary))
99
+ errors.push({
100
+ field: "reasoning.summary",
101
+ message: `Summary must be one of ${AGENT_REASONING_SUMMARIES.join(", ")}`,
102
+ });
103
+ else
104
+ reasoning.summary = summary;
105
+ }
106
+ }
107
+ }
108
+ const textRaw = body.text;
109
+ const text = {};
110
+ if (textRaw !== undefined && textRaw !== null) {
111
+ if (typeof textRaw !== "object")
112
+ errors.push({ field: "text", message: "Text must be an object" });
113
+ else {
114
+ const { format, verbosity } = textRaw;
115
+ const type = format && typeof format === "object" ? format.type : undefined;
116
+ if (format !== undefined && format !== null) {
117
+ if (!AGENT_TEXT_FORMATS.includes(type))
118
+ errors.push({
119
+ field: "text.format.type",
120
+ message: `Text format must be one of ${AGENT_TEXT_FORMATS.join(", ")}`,
121
+ });
122
+ else
123
+ text.format = { type: type };
124
+ }
125
+ if (verbosity !== undefined && verbosity !== null && verbosity !== "") {
126
+ if (!AGENT_VERBOSITIES.includes(verbosity))
127
+ errors.push({
128
+ field: "text.verbosity",
129
+ message: `Verbosity must be one of ${AGENT_VERBOSITIES.join(", ")}`,
130
+ });
131
+ else
132
+ text.verbosity = verbosity;
133
+ }
134
+ }
135
+ }
136
+ if (errors.length)
137
+ return { ok: false, errors };
138
+ return {
139
+ ok: true,
140
+ value: {
141
+ name,
142
+ ...(description ? { description } : {}),
143
+ ...(instructions ? { instructions } : {}),
144
+ model,
145
+ ...(Object.keys(reasoning).length ? { reasoning } : {}),
146
+ ...(Object.keys(text).length ? { text } : {}),
147
+ },
148
+ };
149
+ }
150
+ /** Lowercase ASCII slug of a display name (diacritics folded), possibly empty. */
151
+ export function slugify(name) {
152
+ return name
153
+ .normalize("NFKD")
154
+ .replace(/[̀-ͯ]/g, "")
155
+ .toLowerCase()
156
+ .replace(/[^a-z0-9]+/g, "-")
157
+ .replace(/^-+|-+$/g, "")
158
+ .slice(0, MAX_ID_LENGTH)
159
+ .replace(/-+$/g, "");
160
+ }
161
+ /** A fresh id for `name`: its slug, suffixed (`-2`, `-3`…) past reserved and `taken` ids. */
162
+ export function agentIdFromName(name, taken) {
163
+ const base = slugify(name) || "agent";
164
+ const free = (id) => !taken.has(id) && !RESERVED_AGENT_IDS.has(id);
165
+ if (free(base))
166
+ return base;
167
+ for (let n = 2;; n++) {
168
+ const suffix = `-${n}`;
169
+ const id = `${base.slice(0, MAX_ID_LENGTH - suffix.length).replace(/-+$/g, "")}${suffix}`;
170
+ if (free(id))
171
+ return id;
172
+ }
173
+ }
174
+ /** The description written when none is given: the first sentence of the instructions. */
175
+ export function defaultDescription(name, instructions) {
176
+ const first = instructions
177
+ .split(/\n\s*\n/)[0]
178
+ ?.replace(/\s+/g, " ")
179
+ .trim();
180
+ const sentence = first?.match(/^.*?[.!?](?=\s|$)/)?.[0] ?? first ?? "";
181
+ const text = sentence || `${name} agent`;
182
+ return text.length > AGENT_LIMITS.description
183
+ ? `${text.slice(0, AGENT_LIMITS.description - 1).trimEnd()}…`
184
+ : text;
185
+ }
186
+ const FRONTMATTER = /^---\r?\n([\s\S]*?)\r?\n?---[ \t]*(?:\r?\n|$)([\s\S]*)$/;
187
+ /** Splits a Markdown agent file into its YAML document and body (no frontmatter: empty doc). */
188
+ export function parseAgentFile(text) {
189
+ const match = FRONTMATTER.exec(text);
190
+ if (!match)
191
+ return { doc: new Document({}), body: text.trim() };
192
+ const doc = parseDocument(match[1] ?? "");
193
+ if (doc.errors.length)
194
+ return undefined;
195
+ if (doc.contents !== null && !isMap(doc.contents))
196
+ return undefined;
197
+ if (doc.contents === null)
198
+ doc.contents = doc.createNode({});
199
+ // Bounded alias expansion (a hostile file cannot blow up `toJS`).
200
+ try {
201
+ doc.toJS({ maxAliasCount: 20 });
202
+ }
203
+ catch {
204
+ return undefined;
205
+ }
206
+ return { doc, body: (match[2] ?? "").trim() };
207
+ }
208
+ const iso = (ms) => new Date(ms).toISOString();
209
+ const time = (value) => {
210
+ if (value instanceof Date)
211
+ return value.getTime();
212
+ if (typeof value !== "string" && typeof value !== "number")
213
+ return undefined;
214
+ const ms = typeof value === "number" ? value : Date.parse(value);
215
+ return Number.isFinite(ms) ? ms : undefined;
216
+ };
217
+ /** The definition a parsed file describes (`id`/`scope`/`path` come from its location). */
218
+ export function definitionFromFile(parsed, meta) {
219
+ const front = (parsed.doc.toJS({ maxAliasCount: 20 }) ?? {});
220
+ const own = (front[ALISIO_KEY] && typeof front[ALISIO_KEY] === "object" ? front[ALISIO_KEY] : {});
221
+ const reasoningRaw = (own.reasoning && typeof own.reasoning === "object" ? own.reasoning : {});
222
+ const textRaw = (own.text && typeof own.text === "object" ? own.text : {});
223
+ const reasoning = {};
224
+ if (typeof reasoningRaw.effort === "string" && EFFORT.test(reasoningRaw.effort))
225
+ reasoning.effort = reasoningRaw.effort;
226
+ if (AGENT_REASONING_SUMMARIES.includes(reasoningRaw.summary))
227
+ reasoning.summary = reasoningRaw.summary;
228
+ const text = {};
229
+ const format = (textRaw.format && typeof textRaw.format === "object" ? textRaw.format : {});
230
+ if (AGENT_TEXT_FORMATS.includes(format.type))
231
+ text.format = { type: format.type };
232
+ if (AGENT_VERBOSITIES.includes(textRaw.verbosity))
233
+ text.verbosity = textRaw.verbosity;
234
+ const displayName = (typeof own.displayName === "string" && own.displayName.trim()) ||
235
+ (typeof front.name === "string" && front.name.trim()) ||
236
+ meta.id;
237
+ const updatedAt = time(own.updatedAt) ?? meta.mtimeMs;
238
+ const createdAt = time(own.createdAt) ?? meta.birthtimeMs ?? updatedAt;
239
+ return {
240
+ id: meta.id,
241
+ scope: meta.scope,
242
+ name: displayName,
243
+ description: typeof front.description === "string" ? front.description.trim() : "",
244
+ instructions: parsed.body,
245
+ model: typeof front.model === "string" ? front.model.trim() : "",
246
+ ...(Object.keys(reasoning).length ? { reasoning } : {}),
247
+ ...(Object.keys(text).length ? { text } : {}),
248
+ path: meta.path,
249
+ createdAt,
250
+ updatedAt,
251
+ };
252
+ }
253
+ /** Sets `path` to `value`, or removes it (and then-empty parent maps) when undefined. */
254
+ function put(doc, path, value) {
255
+ if (value !== undefined) {
256
+ doc.setIn(path, value);
257
+ return;
258
+ }
259
+ if (doc.hasIn(path))
260
+ doc.deleteIn(path);
261
+ for (let depth = path.length - 1; depth > 0; depth--) {
262
+ const parent = doc.getIn(path.slice(0, depth), true);
263
+ if (isMap(parent) && parent.items.length === 0)
264
+ doc.deleteIn(path.slice(0, depth));
265
+ }
266
+ }
267
+ /**
268
+ * Serializes `input` into the file's YAML document (mutating it) and returns the file text.
269
+ * Keys Alisio does not own are left exactly as they were.
270
+ */
271
+ export function renderAgentFile(doc, id, input, times) {
272
+ const instructions = input.instructions?.trim() ?? "";
273
+ doc.set("name", id);
274
+ doc.set("description", input.description?.trim() || defaultDescription(input.name, instructions));
275
+ doc.set("model", input.model);
276
+ // Main-capable (OpenCode `mode: all`) so it can be the ACTIVE agent and a subagent; an existing
277
+ // file keeps the mode its author chose.
278
+ if (times.created && !doc.has("mode"))
279
+ doc.set("mode", "all");
280
+ put(doc, [ALISIO_KEY, "displayName"], input.name === id ? undefined : input.name);
281
+ put(doc, [ALISIO_KEY, "reasoning", "effort"], input.reasoning?.effort);
282
+ put(doc, [ALISIO_KEY, "reasoning", "summary"], input.reasoning?.summary);
283
+ put(doc, [ALISIO_KEY, "text", "format", "type"], input.text?.format?.type);
284
+ put(doc, [ALISIO_KEY, "text", "verbosity"], input.text?.verbosity);
285
+ put(doc, [ALISIO_KEY, "createdAt"], iso(times.createdAt));
286
+ put(doc, [ALISIO_KEY, "updatedAt"], iso(times.updatedAt));
287
+ const front = doc.toString({ lineWidth: 0 }).trimEnd();
288
+ return `---\n${front}\n---\n${instructions ? `\n${instructions}\n` : ""}`;
289
+ }
290
+ export class AgentNotFoundError extends Error {
291
+ constructor(scope, id) {
292
+ super(`Agent ${id} not found in the ${scope} scope`);
293
+ }
294
+ }
295
+ /** Reads and writes the agent files of one scope directory. */
296
+ export class AgentScopeStore {
297
+ scope;
298
+ dir;
299
+ now;
300
+ constructor(scope, dir, now = Date.now) {
301
+ this.scope = scope;
302
+ this.dir = dir;
303
+ this.now = now;
304
+ }
305
+ pathOf(id) {
306
+ if (!AGENT_ID_PATTERN.test(id) || id.length > 64)
307
+ throw new AgentNotFoundError(this.scope, id);
308
+ return join(this.dir, `${id}.md`);
309
+ }
310
+ async read(id) {
311
+ const path = this.pathOf(id);
312
+ let text;
313
+ let stats;
314
+ try {
315
+ [text, stats] = await Promise.all([readFile(path, "utf8"), stat(path)]);
316
+ }
317
+ catch {
318
+ return undefined;
319
+ }
320
+ if (text.length > 64_000)
321
+ return undefined;
322
+ const parsed = parseAgentFile(text);
323
+ if (!parsed)
324
+ return undefined;
325
+ return {
326
+ parsed,
327
+ info: definitionFromFile(parsed, {
328
+ id,
329
+ scope: this.scope,
330
+ path,
331
+ mtimeMs: stats.mtimeMs,
332
+ ...(stats.birthtimeMs ? { birthtimeMs: stats.birthtimeMs } : {}),
333
+ }),
334
+ };
335
+ }
336
+ /** Every readable `<id>.md` of the scope (invalid names and unparseable files are skipped). */
337
+ async list() {
338
+ let names;
339
+ try {
340
+ names = (await readdir(this.dir)).filter((n) => n.endsWith(".md")).sort();
341
+ }
342
+ catch {
343
+ return [];
344
+ }
345
+ const out = [];
346
+ for (const name of names.slice(0, 200)) {
347
+ const id = basename(name, ".md");
348
+ if (!AGENT_ID_PATTERN.test(id))
349
+ continue;
350
+ const found = await this.read(id);
351
+ if (found)
352
+ out.push(found.info);
353
+ }
354
+ return out;
355
+ }
356
+ async get(id) {
357
+ try {
358
+ return (await this.read(id))?.info;
359
+ }
360
+ catch {
361
+ return undefined;
362
+ }
363
+ }
364
+ async create(input, extraTaken = []) {
365
+ const taken = new Set([...(await this.list()).map((a) => a.id), ...extraTaken]);
366
+ const id = agentIdFromName(input.name, taken);
367
+ const now = this.now();
368
+ const text = renderAgentFile(new Document({}), id, input, {
369
+ createdAt: now,
370
+ updatedAt: now,
371
+ created: true,
372
+ });
373
+ await this.write(id, text);
374
+ return (await this.get(id));
375
+ }
376
+ async update(id, input) {
377
+ const found = await this.read(id);
378
+ if (!found)
379
+ throw new AgentNotFoundError(this.scope, id);
380
+ const text = renderAgentFile(found.parsed.doc, id, input, {
381
+ createdAt: found.info.createdAt,
382
+ updatedAt: Math.max(this.now(), found.info.updatedAt + 1),
383
+ created: false,
384
+ });
385
+ await this.write(id, text);
386
+ return (await this.get(id));
387
+ }
388
+ async delete(id) {
389
+ try {
390
+ await unlink(this.pathOf(id));
391
+ return true;
392
+ }
393
+ catch {
394
+ return false;
395
+ }
396
+ }
397
+ async write(id, text) {
398
+ await mkdir(this.dir, { recursive: true });
399
+ const path = this.pathOf(id);
400
+ const temp = `${path}.${process.pid}.${Date.now()}.tmp`;
401
+ await writeFile(temp, text, "utf8");
402
+ await rename(temp, path);
403
+ }
404
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Assisted agent creation: one tool-less call of the ACTIVE model turns a short description into
3
+ * a draft definition (name, description, instructions, suggested settings) that the user reviews
4
+ * before saving. Nothing is persisted here.
5
+ */
6
+ import type { AgentDefinitionInput, AgentDraft, ModelProvider } from "@alisio/sdk";
7
+ export declare const AGENT_DRAFT_MAX_DESCRIPTION = 2000;
8
+ /** The reply contract appended after the authoring guidance. */
9
+ export declare const AGENT_DRAFT_INSTRUCTIONS: string;
10
+ /** Where the authoring guidance came from: a discovered skill, or the bundled one. */
11
+ export interface AgentAuthoringGuidance {
12
+ text: string;
13
+ source: string;
14
+ }
15
+ /**
16
+ * The authoring guidance: the first effective skill named like `AGENT_AUTHORING_SKILL_NAMES`
17
+ * (project/user/plugin skills), else the bundled `create-agent` skill.
18
+ */
19
+ export declare function agentAuthoringGuidance(skills?: {
20
+ items: {
21
+ has(name: string): boolean;
22
+ };
23
+ load(name: string): Promise<string>;
24
+ }): Promise<AgentAuthoringGuidance>;
25
+ /**
26
+ * A safe draft from a model answer: unknown settings are dropped, texts are clipped to the store
27
+ * limits. Throws when the answer has no usable JSON object or no instructions.
28
+ */
29
+ export declare function parseAgentDraft(answer: string, generatedBy: string, guidance?: string): AgentDraft;
30
+ /** The user turn: a fresh description, or a definition to refine with optional changes. */
31
+ export declare function agentDraftRequest(description: string, base?: Partial<Pick<AgentDefinitionInput, "name" | "description" | "instructions">>): string;
32
+ /**
33
+ * Drafts an agent with one tool-less call of `provider` (the ACTIVE model): from `description`,
34
+ * or by refining `base` (a template or the current form) with `description` as the changes.
35
+ */
36
+ export declare function generateAgentDraft(provider: ModelProvider, description: string, options?: {
37
+ signal?: AbortSignal;
38
+ maxTokens?: number;
39
+ guidance?: AgentAuthoringGuidance;
40
+ base?: Partial<Pick<AgentDefinitionInput, "name" | "description" | "instructions">>;
41
+ }): Promise<AgentDraft>;
@@ -0,0 +1,133 @@
1
+ import { completeText } from "../core/compaction.js";
2
+ import { AGENT_AUTHORING_SKILL_NAMES, CREATE_AGENT_SKILL, skillBody, } from "./create-agent-skill.js";
3
+ import { AGENT_LIMITS, AGENT_REASONING_SUMMARIES, AGENT_TEXT_FORMATS, AGENT_VERBOSITIES, defaultDescription, } from "./definitions.js";
4
+ export const AGENT_DRAFT_MAX_DESCRIPTION = 2_000;
5
+ /** The reply contract appended after the authoring guidance. */
6
+ export const AGENT_DRAFT_INSTRUCTIONS = [
7
+ "You design agents that run inside Alisio, a coding-agent harness with tools for reading and",
8
+ "editing files, searching code and running processes. Follow the authoring guidance above.",
9
+ "Reply with ONE JSON object and nothing else, using keys:",
10
+ '"name" (2-5 words, Title Case), "description" (one sentence, under 200 characters),',
11
+ '"instructions" (the complete system prompt in Markdown, under 6000 characters),',
12
+ '"reasoning" ({"effort": "low"|"medium"|"high", "summary": "auto"|"none"|"concise"|"detailed"})',
13
+ 'and "text" ({"format": {"type": "text"|"json_object"|"json_schema"}, "verbosity":',
14
+ '"low"|"medium"|"high"}).',
15
+ ].join(" ");
16
+ /**
17
+ * The authoring guidance: the first effective skill named like `AGENT_AUTHORING_SKILL_NAMES`
18
+ * (project/user/plugin skills), else the bundled `create-agent` skill.
19
+ */
20
+ export async function agentAuthoringGuidance(skills) {
21
+ for (const name of AGENT_AUTHORING_SKILL_NAMES) {
22
+ if (!skills?.items.has(name))
23
+ continue;
24
+ try {
25
+ const text = skillBody(await skills.load(name));
26
+ if (text)
27
+ return { text, source: `skill:${name}` };
28
+ }
29
+ catch {
30
+ /* fall back to the bundled guidance */
31
+ }
32
+ }
33
+ return { text: skillBody(CREATE_AGENT_SKILL), source: "bundled:create-agent" };
34
+ }
35
+ /** Extracts the first JSON object of a model answer (tolerating code fences and prose). */
36
+ function firstJsonObject(text) {
37
+ const fenced = /```(?:json)?\s*([\s\S]*?)```/i.exec(text)?.[1];
38
+ for (const candidate of [fenced, text]) {
39
+ if (!candidate)
40
+ continue;
41
+ const start = candidate.indexOf("{");
42
+ const end = candidate.lastIndexOf("}");
43
+ if (start < 0 || end <= start)
44
+ continue;
45
+ try {
46
+ const parsed = JSON.parse(candidate.slice(start, end + 1));
47
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
48
+ return parsed;
49
+ }
50
+ catch {
51
+ /* try the next candidate */
52
+ }
53
+ }
54
+ return undefined;
55
+ }
56
+ const clip = (value, max) => typeof value === "string" ? value.trim().slice(0, max) : "";
57
+ /**
58
+ * A safe draft from a model answer: unknown settings are dropped, texts are clipped to the store
59
+ * limits. Throws when the answer has no usable JSON object or no instructions.
60
+ */
61
+ export function parseAgentDraft(answer, generatedBy, guidance = "bundled:create-agent") {
62
+ const json = firstJsonObject(answer);
63
+ if (!json)
64
+ throw new Error("The model did not return a JSON agent draft; try again");
65
+ const instructions = clip(json.instructions, AGENT_LIMITS.instructions);
66
+ if (!instructions)
67
+ throw new Error("The model's draft has no instructions; try again");
68
+ const name = clip(json.name, AGENT_LIMITS.name) || "New agent";
69
+ const description = clip(json.description, AGENT_LIMITS.description) || defaultDescription(name, instructions);
70
+ const reasoningRaw = (json.reasoning && typeof json.reasoning === "object" ? json.reasoning : {});
71
+ const textRaw = (json.text && typeof json.text === "object" ? json.text : {});
72
+ const reasoning = {};
73
+ if (["low", "medium", "high"].includes(reasoningRaw.effort))
74
+ reasoning.effort = reasoningRaw.effort;
75
+ if (AGENT_REASONING_SUMMARIES.includes(reasoningRaw.summary))
76
+ reasoning.summary = reasoningRaw.summary;
77
+ const text = {};
78
+ const formatType = textRaw.format?.type;
79
+ if (AGENT_TEXT_FORMATS.includes(formatType))
80
+ text.format = { type: formatType };
81
+ if (AGENT_VERBOSITIES.includes(textRaw.verbosity))
82
+ text.verbosity = textRaw.verbosity;
83
+ return {
84
+ name,
85
+ description,
86
+ instructions,
87
+ ...(Object.keys(reasoning).length ? { reasoning } : {}),
88
+ ...(Object.keys(text).length ? { text } : {}),
89
+ generatedBy,
90
+ guidance,
91
+ };
92
+ }
93
+ /** The user turn: a fresh description, or a definition to refine with optional changes. */
94
+ export function agentDraftRequest(description, base) {
95
+ if (!base?.instructions?.trim())
96
+ return `Write a new agent from this description:\n${description.trim()}`;
97
+ return [
98
+ "Refine this existing agent definition. Keep what works, apply the requested changes and",
99
+ "complete any missing required section.",
100
+ "",
101
+ `Name: ${base.name ?? ""}`,
102
+ `Description: ${base.description ?? ""}`,
103
+ "Instructions:",
104
+ "<<<",
105
+ base.instructions.trim(),
106
+ ">>>",
107
+ "",
108
+ `Requested changes: ${description.trim() || "none; improve it to the quality bar"}`,
109
+ ].join("\n");
110
+ }
111
+ /**
112
+ * Drafts an agent with one tool-less call of `provider` (the ACTIVE model): from `description`,
113
+ * or by refining `base` (a template or the current form) with `description` as the changes.
114
+ */
115
+ export async function generateAgentDraft(provider, description, options = {}) {
116
+ const text = description.trim();
117
+ if (!text && !options.base?.instructions?.trim())
118
+ throw new Error("Describe the agent you want first");
119
+ if (text.length > AGENT_DRAFT_MAX_DESCRIPTION)
120
+ throw new Error(`Descriptions are limited to ${AGENT_DRAFT_MAX_DESCRIPTION} characters`);
121
+ const guidance = options.guidance ?? (await agentAuthoringGuidance());
122
+ const signal = AbortSignal.any([
123
+ AbortSignal.timeout(180_000),
124
+ ...(options.signal ? [options.signal] : []),
125
+ ]);
126
+ const answer = await completeText(provider, {
127
+ system: `${guidance.text}\n\n${AGENT_DRAFT_INSTRUCTIONS}`,
128
+ messages: [{ role: "user", text: agentDraftRequest(text, options.base) }],
129
+ maxTokens: options.maxTokens ?? 6_144,
130
+ signal,
131
+ });
132
+ return parseAgentDraft(answer, provider.model, guidance.source);
133
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * The agent definition service every surface (web server, TUI) uses: CRUD over the project and
3
+ * global `.agents/agents` scopes, the merged listing (a project agent overrides the global one
4
+ * with the same id) and the hot-reload of the runtime agent registry after each write.
5
+ *
6
+ * The runtime registry belongs to the subagents plugin (it discovers agent files at setup and
7
+ * publishes them through plugin state). After a write the service asks it to rediscover and then
8
+ * checks whether the saved file is actually loaded, so callers report `live: true` only when the
9
+ * change is usable by the next prompt; otherwise they must tell the user to restart.
10
+ */
11
+ import type { AgentDefinitionInfo, AgentDefinitionInput, AgentScope } from "@alisio/sdk";
12
+ /** The runtime registry the service refreshes (implemented by the application). */
13
+ export interface AgentRegistry {
14
+ /** Rediscovers agent files; false when no registry can reload (e.g. subagents plugin off). */
15
+ reload(): Promise<boolean>;
16
+ /** Absolute paths of the agent files currently loaded, or undefined when unknown. */
17
+ loadedPaths(): string[] | undefined;
18
+ }
19
+ export interface AgentChange<T> {
20
+ result: T;
21
+ /**
22
+ * The runtime registry reflects the change now (new prompts use it). False: the file is saved,
23
+ * but Alisio must restart (or the workspace must be trusted / the plugin enabled) to use it.
24
+ */
25
+ live: boolean;
26
+ }
27
+ export interface AgentDefinitionServiceOptions {
28
+ /** Workspace root; without it only the global scope exists. */
29
+ workspace?: string;
30
+ /** Home directory of the global scope (`os.homedir()`). */
31
+ home: string;
32
+ registry?: AgentRegistry;
33
+ now?: () => number;
34
+ }
35
+ /** Applies the project-over-global precedence and marks both sides of an override. */
36
+ export declare function mergeAgentScopes(project: AgentDefinitionInfo[], global: AgentDefinitionInfo[]): AgentDefinitionInfo[];
37
+ export declare class AgentDefinitionService {
38
+ private readonly options;
39
+ private readonly stores;
40
+ constructor(options: AgentDefinitionServiceOptions);
41
+ /** Scopes available here: `project` only with a workspace. */
42
+ get scopes(): AgentScope[];
43
+ /** The scope new agents default to: the project when a workspace is open. */
44
+ get defaultScope(): AgentScope;
45
+ dir(scope: AgentScope): string;
46
+ private store;
47
+ list(): Promise<AgentDefinitionInfo[]>;
48
+ get(scope: AgentScope, id: string): Promise<AgentDefinitionInfo | undefined>;
49
+ create(scope: AgentScope, input: AgentDefinitionInput): Promise<AgentChange<AgentDefinitionInfo>>;
50
+ update(scope: AgentScope, id: string, input: AgentDefinitionInput): Promise<AgentChange<AgentDefinitionInfo>>;
51
+ delete(scope: AgentScope, id: string): Promise<AgentChange<boolean>>;
52
+ /** Asks the runtime registry to rediscover agent files (false: nothing could reload). */
53
+ reload(): Promise<boolean>;
54
+ /** Whether the runtime registry has `path` loaded (false when unknown). */
55
+ isLoaded(path: string): boolean;
56
+ private settle;
57
+ }