@alisio/core 0.1.0-alpha.9 → 0.1.1

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 (187) hide show
  1. package/README.md +52 -7
  2. package/dist/agents/active.d.ts +99 -0
  3. package/dist/agents/active.js +218 -0
  4. package/dist/agents/capabilities.d.ts +26 -0
  5. package/dist/agents/capabilities.js +120 -0
  6. package/dist/agents/create-agent-skill.d.ts +11 -0
  7. package/dist/agents/create-agent-skill.js +60 -0
  8. package/dist/agents/definitions.d.ts +88 -0
  9. package/dist/agents/definitions.js +404 -0
  10. package/dist/agents/draft.d.ts +41 -0
  11. package/dist/agents/draft.js +133 -0
  12. package/dist/agents/service.d.ts +57 -0
  13. package/dist/agents/service.js +86 -0
  14. package/dist/agents/templates.d.ts +8 -0
  15. package/dist/agents/templates.js +102 -0
  16. package/dist/analysis/capabilities.d.ts +48 -0
  17. package/dist/analysis/capabilities.js +90 -0
  18. package/dist/analysis/data/csv.d.ts +46 -0
  19. package/dist/analysis/data/csv.js +240 -0
  20. package/dist/analysis/data/datasets.d.ts +132 -0
  21. package/dist/analysis/data/datasets.js +441 -0
  22. package/dist/analysis/data/describe.d.ts +27 -0
  23. package/dist/analysis/data/describe.js +103 -0
  24. package/dist/analysis/data/engine-client.d.ts +58 -0
  25. package/dist/analysis/data/engine-client.js +304 -0
  26. package/dist/analysis/data/engine-entry.d.ts +1 -0
  27. package/dist/analysis/data/engine-entry.js +59 -0
  28. package/dist/analysis/data/engine-source.d.ts +4 -0
  29. package/dist/analysis/data/engine-source.js +6 -0
  30. package/dist/analysis/data/engine-types.d.ts +86 -0
  31. package/dist/analysis/data/engine-types.js +7 -0
  32. package/dist/analysis/data/infer.d.ts +42 -0
  33. package/dist/analysis/data/infer.js +118 -0
  34. package/dist/analysis/data/ingest-worker.d.ts +17 -0
  35. package/dist/analysis/data/ingest-worker.js +365 -0
  36. package/dist/analysis/data/json.d.ts +19 -0
  37. package/dist/analysis/data/json.js +44 -0
  38. package/dist/analysis/data/query-worker.d.ts +12 -0
  39. package/dist/analysis/data/query-worker.js +75 -0
  40. package/dist/analysis/data/rows.d.ts +39 -0
  41. package/dist/analysis/data/rows.js +120 -0
  42. package/dist/analysis/data/sql-guard.d.ts +20 -0
  43. package/dist/analysis/data/sql-guard.js +129 -0
  44. package/dist/analysis/data/stats.d.ts +18 -0
  45. package/dist/analysis/data/stats.js +77 -0
  46. package/dist/analysis/install-hints.d.ts +33 -0
  47. package/dist/analysis/install-hints.js +144 -0
  48. package/dist/analysis/janitor.d.ts +60 -0
  49. package/dist/analysis/janitor.js +264 -0
  50. package/dist/analysis/jobs.d.ts +94 -0
  51. package/dist/analysis/jobs.js +165 -0
  52. package/dist/analysis/oci.d.ts +129 -0
  53. package/dist/analysis/oci.js +330 -0
  54. package/dist/analysis/python/sources.d.ts +2 -0
  55. package/dist/analysis/python/sources.js +10 -0
  56. package/dist/analysis/requirements.d.ts +32 -0
  57. package/dist/analysis/requirements.js +73 -0
  58. package/dist/analysis/rerun.d.ts +40 -0
  59. package/dist/analysis/rerun.js +136 -0
  60. package/dist/analysis/runtime-manager.d.ts +146 -0
  61. package/dist/analysis/runtime-manager.js +472 -0
  62. package/dist/analysis/status.d.ts +21 -0
  63. package/dist/analysis/status.js +52 -0
  64. package/dist/application.d.ts +105 -3
  65. package/dist/application.js +446 -18
  66. package/dist/artifacts/kinds.d.ts +35 -0
  67. package/dist/artifacts/kinds.js +177 -0
  68. package/dist/artifacts/publisher.d.ts +7 -0
  69. package/dist/artifacts/publisher.js +29 -0
  70. package/dist/artifacts/store.d.ts +119 -0
  71. package/dist/artifacts/store.js +581 -0
  72. package/dist/background/janitor.d.ts +22 -0
  73. package/dist/background/janitor.js +62 -0
  74. package/dist/background/mirror.d.ts +14 -0
  75. package/dist/background/mirror.js +41 -0
  76. package/dist/background/notify.d.ts +58 -0
  77. package/dist/background/notify.js +172 -0
  78. package/dist/background/output.d.ts +46 -0
  79. package/dist/background/output.js +179 -0
  80. package/dist/background/service.d.ts +137 -0
  81. package/dist/background/service.js +410 -0
  82. package/dist/background/store.d.ts +103 -0
  83. package/dist/background/store.js +195 -0
  84. package/dist/changelog/data.d.ts +6 -0
  85. package/dist/changelog/data.js +306 -0
  86. package/dist/changelog/index.d.ts +5 -0
  87. package/dist/changelog/index.js +7 -0
  88. package/dist/changelog/parse.d.ts +53 -0
  89. package/dist/changelog/parse.js +160 -0
  90. package/dist/commands/builtins.d.ts +10 -0
  91. package/dist/commands/builtins.js +161 -0
  92. package/dist/commands/catalog.d.ts +86 -0
  93. package/dist/commands/catalog.js +304 -0
  94. package/dist/config.d.ts +206 -6
  95. package/dist/config.js +426 -20
  96. package/dist/core/compaction.js +13 -2
  97. package/dist/core/contracts.d.ts +180 -3
  98. package/dist/core/opt-in.d.ts +12 -0
  99. package/dist/core/opt-in.js +12 -0
  100. package/dist/core/output-limit.d.ts +34 -0
  101. package/dist/core/output-limit.js +39 -0
  102. package/dist/core/run-stats.d.ts +16 -0
  103. package/dist/core/run-stats.js +8 -0
  104. package/dist/core/runner.d.ts +134 -4
  105. package/dist/core/runner.js +780 -138
  106. package/dist/core/timeout.d.ts +15 -0
  107. package/dist/core/timeout.js +61 -0
  108. package/dist/core/truncation.d.ts +30 -0
  109. package/dist/core/truncation.js +83 -0
  110. package/dist/goal/blockers.d.ts +45 -0
  111. package/dist/goal/blockers.js +51 -0
  112. package/dist/goal/budget.d.ts +24 -0
  113. package/dist/goal/budget.js +53 -0
  114. package/dist/goal/command.d.ts +36 -0
  115. package/dist/goal/command.js +98 -0
  116. package/dist/goal/format.d.ts +19 -0
  117. package/dist/goal/format.js +102 -0
  118. package/dist/goal/host.d.ts +29 -0
  119. package/dist/goal/host.js +42 -0
  120. package/dist/goal/index.d.ts +9 -0
  121. package/dist/goal/index.js +9 -0
  122. package/dist/goal/machine.d.ts +186 -0
  123. package/dist/goal/machine.js +344 -0
  124. package/dist/goal/prompts.d.ts +10 -0
  125. package/dist/goal/prompts.js +75 -0
  126. package/dist/goal/service.d.ts +113 -0
  127. package/dist/goal/service.js +273 -0
  128. package/dist/goal/store.d.ts +59 -0
  129. package/dist/goal/store.js +184 -0
  130. package/dist/index.d.ts +50 -5
  131. package/dist/index.js +49 -4
  132. package/dist/mcp/connector.js +38 -6
  133. package/dist/mcp/rich.d.ts +41 -0
  134. package/dist/mcp/rich.js +362 -0
  135. package/dist/permissions/modes.d.ts +72 -0
  136. package/dist/permissions/modes.js +98 -0
  137. package/dist/plan/exit-plan.d.ts +25 -0
  138. package/dist/plan/exit-plan.js +130 -0
  139. package/dist/plan/state.d.ts +102 -0
  140. package/dist/plan/state.js +165 -0
  141. package/dist/plugins/host.d.ts +34 -1
  142. package/dist/plugins/host.js +143 -9
  143. package/dist/providers/settings.d.ts +15 -0
  144. package/dist/providers/settings.js +38 -0
  145. package/dist/reload.d.ts +75 -0
  146. package/dist/reload.js +204 -0
  147. package/dist/resources/context.js +6 -1
  148. package/dist/runtime/access.d.ts +62 -0
  149. package/dist/runtime/access.js +115 -0
  150. package/dist/runtime/blobs.d.ts +51 -0
  151. package/dist/runtime/blobs.js +177 -0
  152. package/dist/runtime/diff.d.ts +23 -0
  153. package/dist/runtime/diff.js +154 -0
  154. package/dist/runtime/ids.d.ts +8 -0
  155. package/dist/runtime/ids.js +32 -0
  156. package/dist/runtime/paths.d.ts +11 -0
  157. package/dist/runtime/paths.js +17 -1
  158. package/dist/runtime/process.d.ts +22 -0
  159. package/dist/runtime/process.js +100 -15
  160. package/dist/runtime/sqlite.d.ts +2 -0
  161. package/dist/runtime/sqlite.js +18 -0
  162. package/dist/runtime/store.d.ts +91 -3
  163. package/dist/runtime/store.js +465 -11
  164. package/dist/runtime/zip.d.ts +24 -0
  165. package/dist/runtime/zip.js +126 -0
  166. package/dist/sessions/children.js +17 -1
  167. package/dist/sessions/side-questions.d.ts +57 -0
  168. package/dist/sessions/side-questions.js +161 -0
  169. package/dist/tools/analysis.d.ts +37 -0
  170. package/dist/tools/analysis.js +573 -0
  171. package/dist/tools/artifacts.d.ts +14 -0
  172. package/dist/tools/artifacts.js +143 -0
  173. package/dist/tools/background.d.ts +7 -0
  174. package/dist/tools/background.js +165 -0
  175. package/dist/tools/data.d.ts +7 -0
  176. package/dist/tools/data.js +160 -0
  177. package/dist/tools/execute.d.ts +5 -0
  178. package/dist/tools/execute.js +15 -3
  179. package/dist/tools/goal.d.ts +19 -0
  180. package/dist/tools/goal.js +126 -0
  181. package/dist/tools/search.d.ts +17 -3
  182. package/dist/tools/search.js +153 -0
  183. package/dist/tools/standard.d.ts +4 -1
  184. package/dist/tools/standard.js +99 -18
  185. package/dist/version.d.ts +5 -3
  186. package/dist/version.js +35 -6
  187. package/package.json +2 -2
@@ -0,0 +1,88 @@
1
+ import type { AgentDefinitionInfo, AgentDefinitionInput, AgentReasoningSummary, AgentScope, AgentTextFormatType, AgentVerbosity } from "@alisio/sdk";
2
+ import { Document } from "yaml";
3
+ export type { AgentDefinitionInfo, AgentDefinitionInput, AgentScope };
4
+ /** Same identifier rule as the subagents plugin (and Claude Code agent names). */
5
+ export declare const AGENT_ID_PATTERN: RegExp;
6
+ /** Built-in agent names a user file must not shadow (main `build`/`plan`, subagents). */
7
+ export declare const RESERVED_AGENT_IDS: Set<string>;
8
+ export declare const AGENT_LIMITS: {
9
+ readonly name: 80;
10
+ readonly description: 300;
11
+ readonly instructions: 24000;
12
+ readonly model: 300;
13
+ };
14
+ export declare const AGENT_REASONING_SUMMARIES: AgentReasoningSummary[];
15
+ export declare const AGENT_VERBOSITIES: AgentVerbosity[];
16
+ export declare const AGENT_TEXT_FORMATS: AgentTextFormatType[];
17
+ /** Frontmatter key of the Alisio-only settings (ignored by other harnesses). */
18
+ export declare const ALISIO_KEY = "alisio";
19
+ /** The directory of one scope. */
20
+ export declare function agentScopeDir(scope: AgentScope, roots: {
21
+ workspace?: string;
22
+ home: string;
23
+ }): string;
24
+ export interface AgentFieldError {
25
+ field: string;
26
+ message: string;
27
+ }
28
+ /**
29
+ * Validates and normalizes an untrusted definition body (web API, TUI, drafts). Unknown keys are
30
+ * dropped; strings are trimmed; empty optional settings are omitted.
31
+ */
32
+ export declare function validateAgentDefinitionInput(raw: unknown): {
33
+ ok: true;
34
+ value: AgentDefinitionInput;
35
+ } | {
36
+ ok: false;
37
+ errors: AgentFieldError[];
38
+ };
39
+ /** Lowercase ASCII slug of a display name (diacritics folded), possibly empty. */
40
+ export declare function slugify(name: string): string;
41
+ /** A fresh id for `name`: its slug, suffixed (`-2`, `-3`…) past reserved and `taken` ids. */
42
+ export declare function agentIdFromName(name: string, taken: Set<string>): string;
43
+ /** The description written when none is given: the first sentence of the instructions. */
44
+ export declare function defaultDescription(name: string, instructions: string): string;
45
+ /** Splits a Markdown agent file into its YAML document and body (no frontmatter: empty doc). */
46
+ export declare function parseAgentFile(text: string): {
47
+ doc: Document;
48
+ body: string;
49
+ } | undefined;
50
+ /** The definition a parsed file describes (`id`/`scope`/`path` come from its location). */
51
+ export declare function definitionFromFile(parsed: {
52
+ doc: Document;
53
+ body: string;
54
+ }, meta: {
55
+ id: string;
56
+ scope: AgentScope;
57
+ path: string;
58
+ mtimeMs: number;
59
+ birthtimeMs?: number;
60
+ }): AgentDefinitionInfo;
61
+ /**
62
+ * Serializes `input` into the file's YAML document (mutating it) and returns the file text.
63
+ * Keys Alisio does not own are left exactly as they were.
64
+ */
65
+ export declare function renderAgentFile(doc: Document, id: string, input: AgentDefinitionInput, times: {
66
+ createdAt: number;
67
+ updatedAt: number;
68
+ created: boolean;
69
+ }): string;
70
+ export declare class AgentNotFoundError extends Error {
71
+ constructor(scope: AgentScope, id: string);
72
+ }
73
+ /** Reads and writes the agent files of one scope directory. */
74
+ export declare class AgentScopeStore {
75
+ readonly scope: AgentScope;
76
+ readonly dir: string;
77
+ private readonly now;
78
+ constructor(scope: AgentScope, dir: string, now?: () => number);
79
+ private pathOf;
80
+ private read;
81
+ /** Every readable `<id>.md` of the scope (invalid names and unparseable files are skipped). */
82
+ list(): Promise<AgentDefinitionInfo[]>;
83
+ get(id: string): Promise<AgentDefinitionInfo | undefined>;
84
+ create(input: AgentDefinitionInput, extraTaken?: Iterable<string>): Promise<AgentDefinitionInfo>;
85
+ update(id: string, input: AgentDefinitionInput): Promise<AgentDefinitionInfo>;
86
+ delete(id: string): Promise<boolean>;
87
+ private write;
88
+ }
@@ -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
+ }