@zenera/neo 1.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.
Files changed (109) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +178 -0
  3. package/dist/agent.d.ts +66 -0
  4. package/dist/agent.js +97 -0
  5. package/dist/architecture.d.ts +74 -0
  6. package/dist/architecture.js +105 -0
  7. package/dist/embedding.d.ts +60 -0
  8. package/dist/embedding.js +21 -0
  9. package/dist/embeddings/gemini.d.ts +24 -0
  10. package/dist/embeddings/gemini.js +71 -0
  11. package/dist/embeddings/index.d.ts +4 -0
  12. package/dist/embeddings/index.js +4 -0
  13. package/dist/embeddings/openai.d.ts +18 -0
  14. package/dist/embeddings/openai.js +27 -0
  15. package/dist/embeddings/openrouter.d.ts +21 -0
  16. package/dist/embeddings/openrouter.js +54 -0
  17. package/dist/events.d.ts +144 -0
  18. package/dist/events.js +110 -0
  19. package/dist/ids.d.ts +14 -0
  20. package/dist/ids.js +33 -0
  21. package/dist/index.d.ts +25 -0
  22. package/dist/index.js +25 -0
  23. package/dist/inspect/client.d.ts +2 -0
  24. package/dist/inspect/client.js +1326 -0
  25. package/dist/inspect/css.d.ts +2 -0
  26. package/dist/inspect/css.js +164 -0
  27. package/dist/inspect/index.d.ts +9 -0
  28. package/dist/inspect/index.js +12 -0
  29. package/dist/inspect/page.d.ts +3 -0
  30. package/dist/inspect/page.js +93 -0
  31. package/dist/inspect/report.d.ts +54 -0
  32. package/dist/inspect/report.js +62 -0
  33. package/dist/kernel.d.ts +178 -0
  34. package/dist/kernel.js +929 -0
  35. package/dist/memory-stores/factory.d.ts +15 -0
  36. package/dist/memory-stores/factory.js +29 -0
  37. package/dist/memory-stores/file.d.ts +37 -0
  38. package/dist/memory-stores/file.js +174 -0
  39. package/dist/memory-stores/in-memory.d.ts +17 -0
  40. package/dist/memory-stores/in-memory.js +80 -0
  41. package/dist/memory-stores/index.d.ts +4 -0
  42. package/dist/memory-stores/index.js +5 -0
  43. package/dist/memory.d.ts +115 -0
  44. package/dist/memory.js +213 -0
  45. package/dist/model.d.ts +25 -0
  46. package/dist/model.js +2 -0
  47. package/dist/models/anthropic.d.ts +36 -0
  48. package/dist/models/anthropic.js +293 -0
  49. package/dist/models/factory.d.ts +202 -0
  50. package/dist/models/factory.js +533 -0
  51. package/dist/models/gemini.d.ts +45 -0
  52. package/dist/models/gemini.js +368 -0
  53. package/dist/models/index.d.ts +7 -0
  54. package/dist/models/index.js +7 -0
  55. package/dist/models/openai-chat.d.ts +19 -0
  56. package/dist/models/openai-chat.js +203 -0
  57. package/dist/models/openai-responses.d.ts +31 -0
  58. package/dist/models/openai-responses.js +258 -0
  59. package/dist/models/openrouter.d.ts +44 -0
  60. package/dist/models/openrouter.js +230 -0
  61. package/dist/payload-stores/factory.d.ts +15 -0
  62. package/dist/payload-stores/factory.js +30 -0
  63. package/dist/payload-stores/file.d.ts +29 -0
  64. package/dist/payload-stores/file.js +112 -0
  65. package/dist/payload-stores/in-memory.d.ts +17 -0
  66. package/dist/payload-stores/in-memory.js +40 -0
  67. package/dist/payload-stores/index.d.ts +4 -0
  68. package/dist/payload-stores/index.js +5 -0
  69. package/dist/payload.d.ts +89 -0
  70. package/dist/payload.js +181 -0
  71. package/dist/project/config.d.ts +302 -0
  72. package/dist/project/config.js +290 -0
  73. package/dist/project/index.d.ts +4 -0
  74. package/dist/project/index.js +4 -0
  75. package/dist/project/load.d.ts +142 -0
  76. package/dist/project/load.js +430 -0
  77. package/dist/project/refs.d.ts +23 -0
  78. package/dist/project/refs.js +70 -0
  79. package/dist/prompt.d.ts +66 -0
  80. package/dist/prompt.js +62 -0
  81. package/dist/runner.d.ts +103 -0
  82. package/dist/runner.js +537 -0
  83. package/dist/services.d.ts +23 -0
  84. package/dist/services.js +44 -0
  85. package/dist/skill-providers/factory.d.ts +16 -0
  86. package/dist/skill-providers/factory.js +26 -0
  87. package/dist/skill-providers/file.d.ts +44 -0
  88. package/dist/skill-providers/file.js +209 -0
  89. package/dist/skill-providers/index.d.ts +4 -0
  90. package/dist/skill-providers/index.js +5 -0
  91. package/dist/skill-providers/static.d.ts +13 -0
  92. package/dist/skill-providers/static.js +47 -0
  93. package/dist/skills.d.ts +102 -0
  94. package/dist/skills.js +232 -0
  95. package/dist/state.d.ts +86 -0
  96. package/dist/state.js +49 -0
  97. package/dist/tools/exa.d.ts +23 -0
  98. package/dist/tools/exa.js +389 -0
  99. package/dist/tools/index.d.ts +4 -0
  100. package/dist/tools/index.js +6 -0
  101. package/dist/tools/sandbox.d.ts +172 -0
  102. package/dist/tools/sandbox.js +602 -0
  103. package/dist/tools/workspace.d.ts +53 -0
  104. package/dist/tools/workspace.js +1034 -0
  105. package/dist/trajectory.d.ts +213 -0
  106. package/dist/trajectory.js +232 -0
  107. package/dist/types.d.ts +195 -0
  108. package/dist/types.js +134 -0
  109. package/package.json +98 -0
package/dist/runner.js ADDED
@@ -0,0 +1,537 @@
1
+ import { Agent, AgentRegistry } from "./agent.js";
2
+ import { describeArchitecture } from "./architecture.js";
3
+ import { EventQueue, RunStream, } from "./events.js";
4
+ import { systemClock } from "./ids.js";
5
+ import * as Kernel from "./kernel.js";
6
+ import { renderMemories } from "./memory.js";
7
+ import { PayloadResolver } from "./payload.js";
8
+ import { Services } from "./services.js";
9
+ import { activeSkillNames } from "./skills.js";
10
+ import { lastText } from "./state.js";
11
+ import { lastOfType, projectMessages, } from "./trajectory.js";
12
+ import { isToolReturn, stringify, zeroUsage, } from "./types.js";
13
+ const defaultJoinPolicy = {
14
+ summarize: (child, services) => lastText(child, services.payloads),
15
+ onBranchError: 'continue',
16
+ };
17
+ /** Used when no summarizer is configured: says what went, spends no tokens. */
18
+ const structuralSummarizer = {
19
+ summarize(nodes, reason) {
20
+ const counts = new Map();
21
+ for (const n of nodes) {
22
+ counts.set(n.type, (counts.get(n.type) ?? 0) + 1);
23
+ }
24
+ const what = [...counts].map(([type, n]) => `${n}\u00d7 ${type}`).join(', ');
25
+ return Promise.resolve(`[${reason}] ${nodes.length} earlier steps were dropped: ${what}.`);
26
+ },
27
+ };
28
+ const SUMMARY_INSTRUCTIONS = 'You compress an agent transcript. Rewrite the excerpt below as a short plain-text ' +
29
+ 'briefing that preserves every decision, fact and open question a successor would ' +
30
+ 'need. Do not add commentary and do not invent detail.';
31
+ /** A summarizer that asks a model, so the compaction node holds real prose. */
32
+ export function modelSummarizer(model, instructions = SUMMARY_INSTRUCTIONS) {
33
+ return {
34
+ async summarize(nodes, reason, services) {
35
+ // The summarizer reads exactly what the model read.
36
+ const { messages } = await projectMessages(nodes, services.payloads);
37
+ const req = {
38
+ system: instructions,
39
+ messages: [
40
+ ...messages,
41
+ {
42
+ role: 'user',
43
+ content: [{ type: 'text', text: `Summarize the above (${reason}).` }],
44
+ },
45
+ ],
46
+ tools: [],
47
+ toolChoice: 'auto',
48
+ };
49
+ const res = await model.generate(req);
50
+ return { text: res.text, usage: res.usage };
51
+ },
52
+ };
53
+ }
54
+ // ---------------------------------------------------------------------------
55
+ // Runner
56
+ // ---------------------------------------------------------------------------
57
+ /**
58
+ * The driver: it owns all nondeterminism and all I/O, and calls the kernel to
59
+ * advance the state. Every checkpoint event carries a complete snapshot, so a
60
+ * consumer can persist it and later `resume()` from exactly that point.
61
+ *
62
+ * There is no turn budget. A run ends on a final answer, an abort or an
63
+ * unrecoverable error.
64
+ */
65
+ export class AgentRunner {
66
+ registry = new AgentRegistry();
67
+ services;
68
+ #model;
69
+ #context;
70
+ #handoffPolicy;
71
+ #summarizer;
72
+ #joinPolicy;
73
+ #stream;
74
+ #recordRequests;
75
+ #clock;
76
+ constructor(opts = {}) {
77
+ this.#model = opts.model;
78
+ this.#context = opts.context;
79
+ this.services =
80
+ opts.services ??
81
+ new Services({
82
+ payloads: opts.payloads,
83
+ memory: opts.memory,
84
+ skills: opts.skills,
85
+ });
86
+ this.#handoffPolicy = opts.handoffPolicy;
87
+ this.#summarizer = opts.summarizer ?? structuralSummarizer;
88
+ this.#joinPolicy = opts.joinPolicy ?? defaultJoinPolicy;
89
+ this.#stream = opts.stream ?? true;
90
+ this.#recordRequests = opts.recordRequests ?? false;
91
+ this.#clock = opts.clock ?? systemClock;
92
+ }
93
+ /** Registers agents; chainable. */
94
+ add(...agents) {
95
+ this.registry.add(...agents);
96
+ return this;
97
+ }
98
+ /** Creates + registers + returns an agent in one call. */
99
+ agent(opts) {
100
+ return this.registry.agent(opts);
101
+ }
102
+ get(name) {
103
+ return this.registry.get(name);
104
+ }
105
+ /**
106
+ * The declared wiring as plain JSON: agents, their tools, skills, memory
107
+ * and hand-offs. It lives here rather than on the registry because three
108
+ * of those answers are the runner's: the fallback model, the skill
109
+ * catalogs behind `services`, and the context that resolves memory scopes.
110
+ */
111
+ describe() {
112
+ return describeArchitecture(this.registry, {
113
+ services: this.services,
114
+ defaultModel: this.#model,
115
+ context: this.#context,
116
+ });
117
+ }
118
+ /**
119
+ * Starts a new run. State creation is explicit inside, so the first event a
120
+ * consumer sees (`run_created`) already carries a persistable snapshot.
121
+ */
122
+ run(agent, input, opts = {}) {
123
+ // Generators are lazy: nothing executes until the stream is iterated or
124
+ // awaited, so building a RunStream costs nothing.
125
+ return new RunStream(this.#start(agent, input, opts));
126
+ }
127
+ /** Continues a persisted run from its own snapshot. */
128
+ resume(state, opts = {}) {
129
+ return new RunStream(this.#drive(state, opts));
130
+ }
131
+ /** Adds a follow-up message to a finished (or paused) run and continues it. */
132
+ send(state, input, opts = {}) {
133
+ return new RunStream(this.#sendAndDrive(state, input, opts));
134
+ }
135
+ async *#sendAndDrive(state, input, opts) {
136
+ const next = await Kernel.applyUserInput(state, input, this.#env(opts));
137
+ return yield* this.#drive(next, opts);
138
+ }
139
+ #env(opts) {
140
+ return {
141
+ services: this.services,
142
+ output: opts.output,
143
+ clock: this.#clock,
144
+ };
145
+ }
146
+ async *#start(agent, input, opts) {
147
+ const name = typeof agent === 'string' ? agent : agent.name;
148
+ this.registry.get(name);
149
+ const state = await Kernel.createState({
150
+ agent: name,
151
+ input,
152
+ context: (opts.context ?? this.#context),
153
+ output: opts.output,
154
+ maxForkDepth: opts.maxForkDepth,
155
+ }, this.#env(opts));
156
+ yield tag(state, undefined, { type: 'run_created', state });
157
+ return yield* this.#drive(state, opts);
158
+ }
159
+ /**
160
+ * The loop. `state` is reassigned rather than mutated: every kernel call
161
+ * returns a fresh object, so the snapshot inside an already-emitted event
162
+ * stays valid.
163
+ */
164
+ async *#drive(initial, opts) {
165
+ const env = this.#env(opts);
166
+ Kernel.checkOutputSchema(initial, env);
167
+ const branch = opts.branch;
168
+ const signal = opts.signal;
169
+ let state = initial;
170
+ for (;;) {
171
+ if (signal?.aborted) {
172
+ return yield* this.#finish(state, branch, 'aborted');
173
+ }
174
+ const action = Kernel.nextAction(state);
175
+ switch (action.kind) {
176
+ case 'done':
177
+ return yield* this.#finish(state, branch, 'final');
178
+ case 'llm': {
179
+ state = await Kernel.applySystemPrompt(state, this.registry, env);
180
+ state = await this.#preload(state, env);
181
+ state = await this.#autoRecall(state, env);
182
+ yield tag(state, branch, { type: 'before_llm_call', state });
183
+ const req = await Kernel.buildRequest(state, this.registry, env);
184
+ const digest = Kernel.requestDigest(req);
185
+ const model = this.#modelFor(state);
186
+ const deltas = new EventQueue();
187
+ const call = (this.#stream && model.stream
188
+ ? model.stream({ ...req, signal }, (d) => deltas.push(tag(state, branch, d)))
189
+ : model.generate({ ...req, signal })).finally(() => deltas.close());
190
+ // Deltas arrive in a callback, which a generator cannot
191
+ // yield from; draining the queue while the call is in
192
+ // flight bridges the two.
193
+ yield* deltas.drain();
194
+ const res = await call;
195
+ const before = state;
196
+ state = await Kernel.applyLlmResponse(state, res, model.id, env, {
197
+ digest,
198
+ request: this.#recordRequests ? req : undefined,
199
+ registry: this.registry,
200
+ });
201
+ const node = appended(before, state).find((n) => n.type === 'llm_call');
202
+ if (node) {
203
+ yield tag(state, branch, { type: 'after_llm_call', state, node });
204
+ }
205
+ break;
206
+ }
207
+ case 'tools': {
208
+ const tools = await Kernel.resolveTools(state, this.registry, env);
209
+ for (const call of action.calls) {
210
+ yield tag(state, branch, { type: 'before_tool_call', state, call });
211
+ const was = state;
212
+ const outcome = await this.#runTool(state, tools, call, opts);
213
+ state = await Kernel.applyToolResult(state, call.callId, outcome, env);
214
+ const added = appended(was, state);
215
+ const node = added.find((n) => n.type === 'tool_result');
216
+ if (node) {
217
+ yield tag(state, branch, { type: 'after_tool_call', state, node });
218
+ }
219
+ const handoff = added.find((n) => n.type === 'handoff');
220
+ if (handoff) {
221
+ state = await this.#compactHandoff(state, handoff, env);
222
+ yield tag(state, branch, {
223
+ type: 'handoff',
224
+ state,
225
+ from: handoff.from,
226
+ to: handoff.to,
227
+ });
228
+ }
229
+ }
230
+ break;
231
+ }
232
+ case 'fork': {
233
+ const fork = state.trajectory.find((n) => n.type === 'fork' && n.callId === action.forkId);
234
+ if (fork) {
235
+ yield tag(state, branch, { type: 'before_fork', state, node: fork });
236
+ }
237
+ const results = yield* this.#runBranches(state, action, opts);
238
+ const before = state;
239
+ state = await Kernel.applyJoin(state, action.forkId, results, env);
240
+ const node = appended(before, state).find((n) => n.type === 'join');
241
+ if (node) {
242
+ yield tag(state, branch, { type: 'after_join', state, node });
243
+ }
244
+ break;
245
+ }
246
+ }
247
+ }
248
+ }
249
+ /**
250
+ * Branches really run in parallel — each has its own model calls and its own
251
+ * tool executions. Their events are interleaved into the parent stream but
252
+ * stay demultiplexable through the `branch` tag, while their trajectories
253
+ * remain separate and ordered.
254
+ */
255
+ async *#runBranches(parent, action, opts) {
256
+ const env = this.#env(opts);
257
+ const queue = new EventQueue();
258
+ const siblings = new AbortController();
259
+ const signal = this.#joinPolicy.onBranchError === 'abort_siblings'
260
+ ? anySignal(opts.signal, siblings.signal)
261
+ : opts.signal;
262
+ const tasks = action.branches.map(async (plan) => {
263
+ const child = await Kernel.createChildState(parent, action.forkId, plan.name, env);
264
+ const ref = {
265
+ forkId: action.forkId,
266
+ name: plan.name,
267
+ runId: child.runId,
268
+ depth: child.spec.forkDepth,
269
+ };
270
+ queue.push(tag(parent, opts.branch, {
271
+ type: 'branch_started',
272
+ state: parent,
273
+ child: ref,
274
+ childState: child,
275
+ }));
276
+ // Branches are plain runs: tools, handoffs, memory, skills and even
277
+ // nested forks work inside them exactly as on the trunk.
278
+ const gen = this.#drive(child, {
279
+ ...opts,
280
+ output: undefined,
281
+ signal,
282
+ branch: ref,
283
+ });
284
+ let result;
285
+ let error;
286
+ try {
287
+ for (;;) {
288
+ const step = await gen.next();
289
+ if (step.done) {
290
+ result = step.value;
291
+ break;
292
+ }
293
+ queue.push(step.value);
294
+ }
295
+ }
296
+ catch (e) {
297
+ error = e instanceof Error ? e.message : String(e);
298
+ if (this.#joinPolicy.onBranchError === 'abort_siblings') {
299
+ siblings.abort();
300
+ }
301
+ }
302
+ const childState = result?.state ?? child;
303
+ const status = error
304
+ ? 'error'
305
+ : result?.stopReason === 'aborted'
306
+ ? 'aborted'
307
+ : 'ok';
308
+ queue.push(tag(parent, opts.branch, {
309
+ type: 'branch_finished',
310
+ state: parent,
311
+ child: ref,
312
+ childState,
313
+ status,
314
+ }));
315
+ return {
316
+ name: plan.name,
317
+ status,
318
+ output: status === 'ok'
319
+ ? await this.#joinPolicy.summarize(childState, this.services)
320
+ : (error ?? ''),
321
+ error,
322
+ usage: childState.usage ?? zeroUsage(),
323
+ childRunId: child.runId,
324
+ childState,
325
+ };
326
+ });
327
+ const all = Promise.all(tasks).finally(() => queue.close());
328
+ yield* queue.drain();
329
+ return await all;
330
+ }
331
+ /**
332
+ * Collapses what the outgoing agent left behind. The nodes stay in the
333
+ * trajectory; only the projection replaces them with the summary.
334
+ */
335
+ async #compactHandoff(state, handoff, env) {
336
+ const nodes = this.#handoffPolicy?.select(state, handoff);
337
+ if (!nodes?.length) {
338
+ return state;
339
+ }
340
+ const summary = await this.#summarizer.summarize(nodes, 'handoff_noise', this.services);
341
+ const { text, usage } = typeof summary === 'string' ? { text: summary, usage: undefined } : summary;
342
+ return Kernel.applyCompaction(state, { covers: nodes.map((n) => n.id), summary: text, reason: 'handoff_noise', usage }, env);
343
+ }
344
+ /**
345
+ * Activates the current agent's `preload` skills, if they are not active
346
+ * already.
347
+ *
348
+ * Preloading is not a second mechanism: it appends the same `load_skills`
349
+ * node the `skill_load` tool does, so a preloaded skill and a
350
+ * model-loaded one are indistinguishable downstream — same projection,
351
+ * same unlocked tools.
352
+ *
353
+ * Checked before every call rather than once at `createState`, because
354
+ * `activeSkillNames` is scoped to the *current* agent. That one placement
355
+ * buys four behaviours: loaded before the first call; skipped with no I/O
356
+ * on every later turn; applied afresh when a hand-off makes another agent
357
+ * current, without inheriting the outgoing agent's set; and re-applied if
358
+ * compaction drops the activation. Self-healing for the same reason the
359
+ * tool gate is — both read the projected trajectory rather than a snapshot
360
+ * taken once.
361
+ *
362
+ * The node lands at the head of the transcript and never moves, so it sits
363
+ * inside the cached prefix from turn 0 — strictly cheaper than the model
364
+ * reaching for `skill_load` after its first reply.
365
+ *
366
+ * A name the catalog does not know throws. That is a configuration error,
367
+ * and callers that build agents from a file are expected to have validated
368
+ * it at load time rather than discovering it mid-run.
369
+ */
370
+ async #preload(state, env) {
371
+ const binding = this.registry.get(state.agentName).skills;
372
+ if (!binding?.preload?.length) {
373
+ return state;
374
+ }
375
+ const active = activeSkillNames(state);
376
+ const missing = binding.preload.filter((n) => !active.has(n));
377
+ if (!missing.length) {
378
+ return state;
379
+ }
380
+ const provider = this.services.skillProvider(binding.provider);
381
+ const skills = await Promise.all(missing.map((n) => provider.load(n)));
382
+ return Kernel.applySkillLoad(state, binding.provider, skills, env);
383
+ }
384
+ /**
385
+ * Auto-recall runs after new user input and after a hand-off, not before
386
+ * every call: recalling on every turn costs tokens and defeats prompt
387
+ * caching for a marginal gain.
388
+ */
389
+ async #autoRecall(state, env) {
390
+ const agent = this.registry.get(state.agentName);
391
+ const bindings = agent
392
+ .memoryBindings(state.context)
393
+ .filter((b) => b.autoRecall && b.autoRecall.query !== 'none');
394
+ if (!bindings.length || !shouldRecall(state)) {
395
+ return state;
396
+ }
397
+ const input = Kernel.lastUserInput(state);
398
+ if (!input) {
399
+ return state;
400
+ }
401
+ const parts = await Promise.all(input.content.map((p) => p.type === 'text' ? this.services.payloads.get(p.text) : Promise.resolve('')));
402
+ const query = parts.filter(Boolean).join('\n');
403
+ if (!query) {
404
+ return state;
405
+ }
406
+ let next = state;
407
+ for (const b of bindings) {
408
+ const hits = await this.services
409
+ .memoryStore(b.store)
410
+ .search(b.scope, { text: query, limit: b.autoRecall?.limit ?? 5 });
411
+ if (!hits.length) {
412
+ continue;
413
+ }
414
+ next = await Kernel.applyMemoryEffect(next, {
415
+ kind: 'recall',
416
+ store: b.store,
417
+ scope: b.scope,
418
+ query: { text: query, limit: b.autoRecall?.limit ?? 5 },
419
+ hits: hits.map((h) => ({
420
+ id: h.record.id,
421
+ score: h.score,
422
+ revision: h.record.revision,
423
+ })),
424
+ content: renderMemories(hits),
425
+ }, env);
426
+ }
427
+ return next;
428
+ }
429
+ /**
430
+ * Failure-tolerant on purpose: an unknown tool, malformed arguments or a
431
+ * throwing implementation are all reported back to the model as ordinary
432
+ * tool output, so an otherwise recoverable run continues.
433
+ */
434
+ async #runTool(state, tools, call, opts) {
435
+ const started = Date.now();
436
+ const fail = (output) => ({
437
+ output,
438
+ isError: true,
439
+ durationMs: Date.now() - started,
440
+ });
441
+ const def = tools.find((t) => t.name === call.name);
442
+ if (!def) {
443
+ return fail(`error: unknown tool "${call.name}"`);
444
+ }
445
+ const raw = await this.services.payloads.get(call.args);
446
+ let args;
447
+ try {
448
+ args = JSON.parse(raw || '{}');
449
+ }
450
+ catch (e) {
451
+ return fail(`error: arguments are not valid JSON: ${String(e)}`);
452
+ }
453
+ try {
454
+ const out = await def.execute(args, {
455
+ ctx: state.context,
456
+ state,
457
+ agent: this.registry.get(state.agentName),
458
+ callId: call.callId,
459
+ services: this.services,
460
+ signal: opts.signal,
461
+ });
462
+ const effects = isToolReturn(out) ? out.effects : [];
463
+ const value = isToolReturn(out) ? out.output : out;
464
+ return {
465
+ output: stringify(value),
466
+ isError: false,
467
+ durationMs: Date.now() - started,
468
+ effects: effects.length ? effects : undefined,
469
+ };
470
+ }
471
+ catch (e) {
472
+ return fail(`error: ${e instanceof Error ? e.message : String(e)}`);
473
+ }
474
+ }
475
+ #modelFor(state) {
476
+ const model = this.registry.get(state.agentName).model ?? this.#model;
477
+ if (!model) {
478
+ throw new Error(`no model configured for agent "${state.agentName}"`);
479
+ }
480
+ return model;
481
+ }
482
+ async *#finish(state, branch, stopReason) {
483
+ const final = lastOfType(state.trajectory, 'final_output');
484
+ const output = (state.spec.outputSchema && final && 'parsed' in final
485
+ ? final.parsed
486
+ : await lastText(state, this.services.payloads));
487
+ const result = {
488
+ output,
489
+ agent: state.agentName,
490
+ state,
491
+ usage: state.usage,
492
+ stopReason,
493
+ };
494
+ yield tag(state, branch, {
495
+ type: 'run_finished',
496
+ state,
497
+ result: result,
498
+ });
499
+ return result;
500
+ }
501
+ }
502
+ // ---------------------------------------------------------------------------
503
+ // helpers
504
+ // ---------------------------------------------------------------------------
505
+ function tag(state, branch, event) {
506
+ return { runId: state.runId, agent: state.agentName, branch, ...event };
507
+ }
508
+ /**
509
+ * What a kernel call just appended. The trajectory is append-only and `apply*`
510
+ * only ever appends, so the tail is exactly the new nodes — no backwards scan.
511
+ */
512
+ function appended(before, after) {
513
+ return after.trajectory.slice(before.trajectory.length);
514
+ }
515
+ /** Recall after new user input or a hand-off, and never twice in a row. */
516
+ function shouldRecall(state) {
517
+ const nodes = state.trajectory;
518
+ for (let i = nodes.length - 1; i >= 0; i--) {
519
+ const n = nodes[i];
520
+ if (n.type === 'system_prompt') {
521
+ continue;
522
+ }
523
+ if (n.type === 'memory_recall') {
524
+ return false;
525
+ }
526
+ return n.type === 'handoff' || (n.type === 'user_input' && !n.synthetic);
527
+ }
528
+ return false;
529
+ }
530
+ function anySignal(...signals) {
531
+ const present = signals.filter((s) => Boolean(s));
532
+ if (!present.length) {
533
+ return undefined;
534
+ }
535
+ return present.length === 1 ? present[0] : AbortSignal.any(present);
536
+ }
537
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1,23 @@
1
+ import { PayloadResolver, type PayloadStore } from './payload.ts';
2
+ import type { MemoryStore } from './memory.ts';
3
+ import type { SkillProvider } from './skills.ts';
4
+ /**
5
+ * The I/O the kernel is not allowed to do itself: payload resolution, memory
6
+ * backends and skill catalogs. Held by the driver (runner or Temporal
7
+ * activity) and handed to the kernel and to tools explicitly, so nothing reads
8
+ * ambient state.
9
+ */
10
+ export declare class Services {
11
+ #private;
12
+ readonly payloads: PayloadResolver;
13
+ constructor(opts?: {
14
+ payloads?: PayloadResolver | PayloadStore;
15
+ memory?: MemoryStore[];
16
+ skills?: SkillProvider[];
17
+ });
18
+ memoryStore(id: string): MemoryStore;
19
+ skillProvider(id: string): SkillProvider;
20
+ hasMemory(id: string): boolean;
21
+ hasSkills(id: string): boolean;
22
+ }
23
+ //# sourceMappingURL=services.d.ts.map
@@ -0,0 +1,44 @@
1
+ import { PayloadResolver } from "./payload.js";
2
+ import { InMemoryPayloadStore } from "./payload-stores/in-memory.js";
3
+ /**
4
+ * The I/O the kernel is not allowed to do itself: payload resolution, memory
5
+ * backends and skill catalogs. Held by the driver (runner or Temporal
6
+ * activity) and handed to the kernel and to tools explicitly, so nothing reads
7
+ * ambient state.
8
+ */
9
+ export class Services {
10
+ payloads;
11
+ #memory = new Map();
12
+ #skills = new Map();
13
+ constructor(opts = {}) {
14
+ const p = opts.payloads ?? new InMemoryPayloadStore();
15
+ this.payloads = p instanceof PayloadResolver ? p : new PayloadResolver(p);
16
+ for (const m of opts.memory ?? []) {
17
+ this.#memory.set(m.id, m);
18
+ }
19
+ for (const s of opts.skills ?? []) {
20
+ this.#skills.set(s.id, s);
21
+ }
22
+ }
23
+ memoryStore(id) {
24
+ const s = this.#memory.get(id);
25
+ if (!s) {
26
+ throw new Error(`unknown memory store: ${id} (known: ${[...this.#memory.keys()]})`);
27
+ }
28
+ return s;
29
+ }
30
+ skillProvider(id) {
31
+ const p = this.#skills.get(id);
32
+ if (!p) {
33
+ throw new Error(`unknown skill provider: ${id} (known: ${[...this.#skills.keys()]})`);
34
+ }
35
+ return p;
36
+ }
37
+ hasMemory(id) {
38
+ return this.#memory.has(id);
39
+ }
40
+ hasSkills(id) {
41
+ return this.#skills.has(id);
42
+ }
43
+ }
44
+ //# sourceMappingURL=services.js.map
@@ -0,0 +1,16 @@
1
+ import type { Skill, SkillProvider } from '../skills.ts';
2
+ import { type FileSkillProviderOptions } from './file.ts';
3
+ export interface StaticSkillProviderSpec {
4
+ kind: 'static';
5
+ id?: string;
6
+ skills: Skill[];
7
+ }
8
+ export interface FileSkillProviderSpec extends FileSkillProviderOptions {
9
+ kind: 'file';
10
+ }
11
+ /** Grows a member per source (`http`, `git`, …). */
12
+ export type SkillProviderSpec = StaticSkillProviderSpec | FileSkillProviderSpec;
13
+ /** Shorthand: `file:./skills`. The static provider needs its skills inline. */
14
+ export type SkillProviderRef = SkillProviderSpec | string;
15
+ export declare function createSkillProvider(ref: SkillProviderRef): SkillProvider;
16
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1,26 @@
1
+ import { FileSkillProvider } from "./file.js";
2
+ import { StaticSkillProvider } from "./static.js";
3
+ export function createSkillProvider(ref) {
4
+ const spec = typeof ref === 'string' ? parseRef(ref) : ref;
5
+ switch (spec.kind) {
6
+ case 'static':
7
+ return new StaticSkillProvider(spec.skills, spec.id);
8
+ case 'file':
9
+ return new FileSkillProvider(spec);
10
+ default:
11
+ throw new TypeError(`unknown skill provider kind: ${spec.kind}`);
12
+ }
13
+ }
14
+ function parseRef(ref) {
15
+ const colon = ref.indexOf(':');
16
+ const scheme = colon < 0 ? ref : ref.slice(0, colon);
17
+ const rest = colon < 0 ? '' : ref.slice(colon + 1);
18
+ if (scheme === 'file') {
19
+ if (!rest) {
20
+ throw new TypeError(`missing directory in "${ref}" (expected file:<dir>)`);
21
+ }
22
+ return { kind: 'file', dir: rest };
23
+ }
24
+ throw new TypeError(`unknown skill provider ref: "${ref}"`);
25
+ }
26
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1,44 @@
1
+ import type { Skill, SkillProvider, SkillSummary } from '../skills.ts';
2
+ import { type AnyTool } from '../types.ts';
3
+ export interface FileSkillProviderOptions {
4
+ /** one or more root directories holding `<name>.md` files and/or `<name>/SKILL.md` folders */
5
+ dir: string | string[];
6
+ /** logical provider id agents bind to; defaults to `file` */
7
+ id?: string;
8
+ /**
9
+ * Tools a skill may unlock, by name. Instructions can live in a file but
10
+ * code cannot, so a markdown skill declares `tools: [a, b]` in its
11
+ * frontmatter and the names are resolved against this registry — by name,
12
+ * by `<group>:*`, or `*` for all of them.
13
+ */
14
+ tools?: AnyTool<any>[];
15
+ }
16
+ /**
17
+ * Reads skills from disk. Two layouts, both discovered in one scan:
18
+ *
19
+ * ```
20
+ * <dir>/budget_travel.md flat: frontmatter + body
21
+ * <dir>/budget_travel/SKILL.md folder: sibling files become `resources`
22
+ * ```
23
+ *
24
+ * Frontmatter is a small subset of YAML — `key: value`, plus `[a, b]` lists
25
+ * for `tags` and `tools`. `name` defaults to the file or folder name and
26
+ * `description` to the first line of the body, so the minimum viable skill is
27
+ * a markdown file with no frontmatter at all.
28
+ */
29
+ export declare class FileSkillProvider implements SkillProvider {
30
+ #private;
31
+ readonly id: string;
32
+ constructor(opts: FileSkillProviderOptions | string);
33
+ /** The first configured directory (kept for backwards compatibility). */
34
+ get dir(): string;
35
+ /** All configured directories. */
36
+ get dirs(): string[];
37
+ /** Drops the cached scan; call after editing skills on disk. */
38
+ refresh(): void;
39
+ list(): Promise<SkillSummary[]>;
40
+ search(query: string, limit?: number): Promise<SkillSummary[]>;
41
+ load(name: string, version?: string): Promise<Skill>;
42
+ tool(name: string): AnyTool<any> | undefined;
43
+ }
44
+ //# sourceMappingURL=file.d.ts.map