@frockbot/kernel-contracts 0.1.3 → 0.2.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.
@@ -0,0 +1,711 @@
1
+ // The Agent loop's public event vocabulary.
2
+ //
3
+ // This is the single inventory Packages use to discover loop extension
4
+ // points. In-process first-party listeners receive the richer Cordis call
5
+ // signatures declared beside the services that dispatch them; a Bot isolate
6
+ // receives only the structured-clonable payload DTO named here. In
7
+ // particular, no payload contains a live Agent, Session, Context, AbortSignal,
8
+ // storage handle, credential, or service binding.
9
+ //
10
+ // Ordering is part of the contract: a Bot-isolate host appends its listeners
11
+ // only after the first-party application has mounted. First-party policy
12
+ // therefore observes the original dispatch before Bot-authored policy, and a
13
+ // first-party listener may short-circuit without entering an isolate. A hook
14
+ // is registered only on the mounted Bot's Cordis root and is additionally
15
+ // fenced by botId and Composition generation, so it cannot reach another Bot
16
+ // or an in-flight Turn pinned to another generation.
17
+ import type {
18
+ PromptAssembly,
19
+ PromptAssemblyContext,
20
+ } from "./prompt-assembly.js";
21
+ import type {
22
+ ToolExecutionContext,
23
+ ToolExecutionResult,
24
+ ToolPreparation,
25
+ } from "./tool-execution.js";
26
+ import type {
27
+ LlmMessage,
28
+ LlmStreamEvent,
29
+ NormalizedModelRequest,
30
+ SessionEventEnvelope,
31
+ ToolCall,
32
+ ToolSchema,
33
+ TurnTypeV1,
34
+ } from "./types.js";
35
+ import { decodeNormalizedModelRequestV1 } from "./types.js";
36
+ import type { Session } from "./session.js";
37
+ import { decodeSkillRefsV1, type SkillRefV1 } from "./skills.js";
38
+
39
+ export type LoopEventDispatchModeV1 = "waterfall" | "serial" | "emit";
40
+
41
+ export type LoopAgentStatusV1 = "idle" | "running" | "disposed";
42
+
43
+ export interface LoopAgentSnapshotV1 {
44
+ botId: string;
45
+ agentId: string;
46
+ sessionId: string;
47
+ status: LoopAgentStatusV1;
48
+ }
49
+
50
+ /** The live in-process projection used only by first-party Cordis listeners. */
51
+ export interface LoopAgentRuntimeV1 {
52
+ readonly id: string;
53
+ readonly botId: string;
54
+ readonly session: Session;
55
+ readonly status: LoopAgentStatusV1;
56
+ }
57
+
58
+ export interface LoopStepSnapshotV1 extends LoopAgentSnapshotV1 {
59
+ compositionGenerationId: string;
60
+ turn: number;
61
+ step: number;
62
+ turnType: TurnTypeV1;
63
+ subagentRole?: string;
64
+ }
65
+
66
+ export interface LoopAgentInputV1 {
67
+ messageId: string;
68
+ text: string;
69
+ skills?: SkillRefV1[];
70
+ }
71
+
72
+ export type LoopPreStepDecisionV1 =
73
+ | { kind: "enter"; inputs: LoopAgentInputV1[] }
74
+ | { kind: "reject"; reason: string };
75
+
76
+ export type LoopRequestErrorActionV1 = { kind: "retry" } | { kind: "fail" };
77
+
78
+ export type LoopStepContinuationV1 = { kind: "continue" } | { kind: "stop" };
79
+
80
+ export interface LoopToolExecutionContextV1 {
81
+ botId: string;
82
+ agentId: string;
83
+ sessionId: string;
84
+ compositionGenerationId: string;
85
+ effectId: string;
86
+ toolCall?: ToolCall;
87
+ turnType: TurnTypeV1;
88
+ subagentRole?: string;
89
+ }
90
+
91
+ export function loopToolExecutionContextSnapshotV1(
92
+ context: ToolExecutionContext,
93
+ ): LoopToolExecutionContextV1 {
94
+ return structuredClone({
95
+ botId: context.botId,
96
+ agentId: context.agentId,
97
+ sessionId: context.sessionId,
98
+ compositionGenerationId: context.compositionGenerationId,
99
+ effectId: context.effectId,
100
+ ...(context.toolCall === undefined ? {} : { toolCall: context.toolCall }),
101
+ turnType: context.turnType,
102
+ ...(context.subagentRole === undefined
103
+ ? {}
104
+ : { subagentRole: context.subagentRole }),
105
+ });
106
+ }
107
+
108
+ /** The structured-clonable payload carried for each public event. */
109
+ export interface LoopEventPayloadMapV1 {
110
+ "agent/created": { agent: LoopAgentSnapshotV1 };
111
+ "agent/disposed": { agent: LoopAgentSnapshotV1 };
112
+ "agent/status": {
113
+ agent: LoopAgentSnapshotV1;
114
+ status: LoopAgentStatusV1;
115
+ };
116
+ "agent/inbox/inserted": {
117
+ agent: LoopAgentSnapshotV1;
118
+ input: LoopAgentInputV1;
119
+ };
120
+ "agent/inbox/claimed": {
121
+ agent: LoopAgentSnapshotV1;
122
+ inputs: LoopAgentInputV1[];
123
+ turn: number;
124
+ };
125
+ "agent/pre-step": {
126
+ step: LoopStepSnapshotV1;
127
+ inputs: LoopAgentInputV1[];
128
+ decision: LoopPreStepDecisionV1;
129
+ };
130
+ "system-prompt/assemble": {
131
+ context: PromptAssemblyContext;
132
+ assembly: PromptAssembly;
133
+ };
134
+ "agent/message-window": {
135
+ step: LoopStepSnapshotV1;
136
+ messages: LlmMessage[];
137
+ };
138
+ "agent/tool-exposure": {
139
+ step: LoopStepSnapshotV1;
140
+ tools: ToolSchema[];
141
+ };
142
+ "agent/request": {
143
+ step: LoopStepSnapshotV1;
144
+ request: NormalizedModelRequest;
145
+ };
146
+ "agent/request-error": {
147
+ step: LoopStepSnapshotV1;
148
+ error: { name: string; message: string };
149
+ action: LoopRequestErrorActionV1;
150
+ };
151
+ "llm/stream": { request: NormalizedModelRequest };
152
+ "tools/pre-execute": {
153
+ call: ToolCall;
154
+ context: LoopToolExecutionContextV1;
155
+ preparation: ToolPreparation;
156
+ };
157
+ "tools/execute": {
158
+ call: ToolCall;
159
+ context: LoopToolExecutionContextV1;
160
+ };
161
+ "tools/post-execute": {
162
+ call: ToolCall;
163
+ context: LoopToolExecutionContextV1;
164
+ result: ToolExecutionResult;
165
+ };
166
+ "tools/result": { call: ToolCall; result: ToolExecutionResult };
167
+ "agent/step-continuation": {
168
+ step: LoopStepSnapshotV1;
169
+ decision: LoopStepContinuationV1;
170
+ };
171
+ "agent/model-outcome-committed": {
172
+ agent: LoopAgentSnapshotV1;
173
+ requestId: string;
174
+ outcome: "completed" | "not-started";
175
+ };
176
+ "agent/turn-stopping": { agent: LoopAgentSnapshotV1; turn: number };
177
+ "agent/cancel-requested": {
178
+ agent: LoopAgentSnapshotV1;
179
+ reason: "user" | "shutdown";
180
+ };
181
+ "agent/error": {
182
+ agent: LoopAgentSnapshotV1;
183
+ error: { name: string; message: string };
184
+ };
185
+ "session/event": SessionEventEnvelope;
186
+ }
187
+
188
+ /** The value a waterfall listener may replace; observations return nothing. */
189
+ export interface LoopEventReturnMapV1 {
190
+ "agent/created": void;
191
+ "agent/disposed": void;
192
+ "agent/status": void;
193
+ "agent/inbox/inserted": void;
194
+ "agent/inbox/claimed": void;
195
+ "agent/pre-step": LoopPreStepDecisionV1;
196
+ "system-prompt/assemble": PromptAssembly;
197
+ "agent/message-window": LlmMessage[];
198
+ "agent/tool-exposure": ToolSchema[];
199
+ "agent/request": NormalizedModelRequest;
200
+ "agent/request-error": LoopRequestErrorActionV1;
201
+ "llm/stream": AsyncIterable<LlmStreamEvent>;
202
+ "tools/pre-execute": ToolPreparation;
203
+ "tools/execute": ToolExecutionResult;
204
+ "tools/post-execute": ToolExecutionResult;
205
+ "tools/result": void;
206
+ "agent/step-continuation": LoopStepContinuationV1;
207
+ "agent/model-outcome-committed": void;
208
+ "agent/turn-stopping": void;
209
+ "agent/cancel-requested": void;
210
+ "agent/error": void;
211
+ "session/event": void;
212
+ }
213
+
214
+ export type LoopEventNameV1 = keyof LoopEventPayloadMapV1;
215
+
216
+ /**
217
+ * Waterfalls safe to bridge into a Bot isolate. Operational wrappers that
218
+ * carry an AbortSignal, an async stream, or an effect body remain first-party:
219
+ * the isolate receives policy DTOs, never control of the durable skeleton.
220
+ */
221
+ export const BOT_ISOLATE_HOOK_EVENTS_V1 = [
222
+ "agent/pre-step",
223
+ "system-prompt/assemble",
224
+ "agent/message-window",
225
+ "agent/tool-exposure",
226
+ "tools/pre-execute",
227
+ "tools/post-execute",
228
+ "agent/step-continuation",
229
+ ] as const satisfies readonly LoopEventNameV1[];
230
+
231
+ export type BotIsolateHookEventNameV1 =
232
+ (typeof BOT_ISOLATE_HOOK_EVENTS_V1)[number];
233
+
234
+ export function isBotIsolateHookEventNameV1(
235
+ value: unknown,
236
+ ): value is BotIsolateHookEventNameV1 {
237
+ return BOT_ISOLATE_HOOK_EVENTS_V1.some((event) => event === value);
238
+ }
239
+
240
+ function hookRecord(value: unknown, label: string): Record<string, unknown> {
241
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
242
+ throw new Error(`${label} must be an object`);
243
+ }
244
+ return value as Record<string, unknown>;
245
+ }
246
+
247
+ function hookExactKeys(
248
+ value: Record<string, unknown>,
249
+ required: readonly string[],
250
+ optional: readonly string[],
251
+ label: string,
252
+ ): void {
253
+ const allowed = new Set([...required, ...optional]);
254
+ if (
255
+ !required.every((key) => Object.hasOwn(value, key)) ||
256
+ !Object.keys(value).every((key) => allowed.has(key))
257
+ ) {
258
+ throw new Error(`${label} has invalid fields`);
259
+ }
260
+ }
261
+
262
+ function hookString(
263
+ value: unknown,
264
+ label: string,
265
+ maximum = 1_000_000,
266
+ allowEmpty = false,
267
+ ): string {
268
+ if (
269
+ typeof value !== "string" ||
270
+ (!allowEmpty && value.length === 0) ||
271
+ value.length > maximum
272
+ ) {
273
+ throw new Error(`${label} must be a bounded string`);
274
+ }
275
+ return value;
276
+ }
277
+
278
+ function decodeHookCall(value: unknown, label: string): ToolCall {
279
+ const call = hookRecord(value, label);
280
+ hookExactKeys(call, ["id", "name", "input"], [], label);
281
+ // Reuse the normalized request decoder's exact ToolCall and JSON checks.
282
+ const decoded = decodeNormalizedModelRequestV1(
283
+ {
284
+ requestId: "hook-decode",
285
+ provider: "hook-decode",
286
+ model: "hook-decode",
287
+ system: "",
288
+ messages: [{ role: "assistant", content: "", toolCalls: [call] }],
289
+ tools: [],
290
+ },
291
+ label,
292
+ );
293
+ return (decoded.messages[0] as { toolCalls: ToolCall[] }).toolCalls[0]!;
294
+ }
295
+
296
+ function decodeHookResult(value: unknown, label: string): ToolExecutionResult {
297
+ const result = hookRecord(value, label);
298
+ hookExactKeys(
299
+ result,
300
+ ["content", "isError"],
301
+ ["endsTurn", "attachments"],
302
+ label,
303
+ );
304
+ if (typeof result.isError !== "boolean") {
305
+ throw new Error(`${label}.isError must be a boolean`);
306
+ }
307
+ if (result.endsTurn !== undefined && typeof result.endsTurn !== "boolean") {
308
+ throw new Error(`${label}.endsTurn must be a boolean`);
309
+ }
310
+ const decoded = decodeNormalizedModelRequestV1(
311
+ {
312
+ requestId: "hook-decode",
313
+ provider: "hook-decode",
314
+ model: "hook-decode",
315
+ system: "",
316
+ messages: [
317
+ {
318
+ role: "tool",
319
+ callId: "hook-decode",
320
+ name: "hook_decode",
321
+ content: result.content,
322
+ isError: result.isError,
323
+ ...(result.attachments === undefined
324
+ ? {}
325
+ : { attachments: result.attachments }),
326
+ },
327
+ ],
328
+ tools: [],
329
+ },
330
+ label,
331
+ );
332
+ const message = decoded.messages[0] as Extract<LlmMessage, { role: "tool" }>;
333
+ return {
334
+ content: hookString(message.content, `${label}.content`, 1_000_000, true),
335
+ isError: message.isError,
336
+ ...(result.endsTurn === undefined
337
+ ? {}
338
+ : { endsTurn: result.endsTurn as boolean }),
339
+ ...(message.attachments === undefined
340
+ ? {}
341
+ : { attachments: message.attachments }),
342
+ };
343
+ }
344
+
345
+ function decodeHookInputs(value: unknown, label: string): LoopAgentInputV1[] {
346
+ if (!Array.isArray(value) || value.length > 256) {
347
+ throw new Error(`${label} must be a bounded array`);
348
+ }
349
+ return value.map((input, index) => {
350
+ const itemLabel = `${label}[${index}]`;
351
+ const item = hookRecord(input, itemLabel);
352
+ hookExactKeys(item, ["messageId", "text"], ["skills"], itemLabel);
353
+ const skills = item.skills;
354
+ if (skills !== undefined && !Array.isArray(skills)) {
355
+ throw new Error(`${itemLabel}.skills must be an array`);
356
+ }
357
+ const decodedSkills =
358
+ skills === undefined
359
+ ? undefined
360
+ : decodeSkillRefsV1(skills, `${itemLabel}.skills`);
361
+ return {
362
+ messageId: hookString(item.messageId, `${itemLabel}.messageId`, 256),
363
+ text: hookString(item.text, `${itemLabel}.text`, 1_000_000, true),
364
+ ...(decodedSkills === undefined ? {} : { skills: decodedSkills }),
365
+ };
366
+ });
367
+ }
368
+
369
+ function sameHookCall(left: ToolCall, right: ToolCall): boolean {
370
+ return JSON.stringify(left) === JSON.stringify(right);
371
+ }
372
+
373
+ /** Exact, event-specific decoding for an untrusted isolate replacement. */
374
+ export function decodeBotIsolateHookReplacementV1<
375
+ Event extends BotIsolateHookEventNameV1,
376
+ >(
377
+ event: Event,
378
+ input: unknown,
379
+ original: LoopEventReturnMapV1[Event],
380
+ ): LoopEventReturnMapV1[Event] {
381
+ const label = `isolate hook ${event} replacement`;
382
+ let decoded: LoopEventReturnMapV1[BotIsolateHookEventNameV1];
383
+ switch (event) {
384
+ case "agent/pre-step": {
385
+ const decision = hookRecord(input, label);
386
+ if (decision.kind === "enter") {
387
+ hookExactKeys(decision, ["kind", "inputs"], [], label);
388
+ decoded = {
389
+ kind: "enter",
390
+ inputs: decodeHookInputs(decision.inputs, `${label}.inputs`),
391
+ };
392
+ break;
393
+ }
394
+ hookExactKeys(decision, ["kind", "reason"], [], label);
395
+ if (decision.kind !== "reject") {
396
+ throw new Error(`${label}.kind is invalid`);
397
+ }
398
+ decoded = {
399
+ kind: "reject",
400
+ reason: hookString(decision.reason, `${label}.reason`, 2_048),
401
+ };
402
+ break;
403
+ }
404
+ case "system-prompt/assemble": {
405
+ const assembly = hookRecord(input, label);
406
+ hookExactKeys(assembly, ["text", "sections"], [], label);
407
+ if (!Array.isArray(assembly.sections) || assembly.sections.length > 256) {
408
+ throw new Error(`${label}.sections must be a bounded array`);
409
+ }
410
+ decoded = {
411
+ text: hookString(assembly.text, `${label}.text`, 1_000_000, true),
412
+ sections: assembly.sections.map((section, index) => {
413
+ const sectionLabel = `${label}.sections[${index}]`;
414
+ const item = hookRecord(section, sectionLabel);
415
+ hookExactKeys(item, ["id", "text"], [], sectionLabel);
416
+ return {
417
+ id: hookString(item.id, `${sectionLabel}.id`, 256),
418
+ text: hookString(
419
+ item.text,
420
+ `${sectionLabel}.text`,
421
+ 1_000_000,
422
+ true,
423
+ ),
424
+ };
425
+ }),
426
+ };
427
+ break;
428
+ }
429
+ case "agent/message-window": {
430
+ decoded = decodeNormalizedModelRequestV1(
431
+ {
432
+ requestId: "hook-decode",
433
+ provider: "hook-decode",
434
+ model: "hook-decode",
435
+ system: "",
436
+ messages: input,
437
+ tools: [],
438
+ },
439
+ label,
440
+ ).messages;
441
+ break;
442
+ }
443
+ case "agent/tool-exposure": {
444
+ decoded = decodeNormalizedModelRequestV1(
445
+ {
446
+ requestId: "hook-decode",
447
+ provider: "hook-decode",
448
+ model: "hook-decode",
449
+ system: "",
450
+ messages: [],
451
+ tools: input,
452
+ },
453
+ label,
454
+ ).tools;
455
+ break;
456
+ }
457
+ case "tools/pre-execute": {
458
+ const prior = original as ToolPreparation;
459
+ const preparation = hookRecord(input, label);
460
+ if (preparation.kind === "ready") {
461
+ hookExactKeys(preparation, ["kind", "call", "idempotent"], [], label);
462
+ if (typeof preparation.idempotent !== "boolean") {
463
+ throw new Error(`${label}.idempotent must be a boolean`);
464
+ }
465
+ const ready = {
466
+ kind: "ready" as const,
467
+ call: decodeHookCall(preparation.call, `${label}.call`),
468
+ idempotent: preparation.idempotent,
469
+ };
470
+ if (
471
+ prior.kind === "denied" ||
472
+ !sameHookCall(ready.call, prior.call) ||
473
+ ready.idempotent !== prior.idempotent
474
+ ) {
475
+ throw new Error(`${label} cannot lift or alter prior preparation`);
476
+ }
477
+ decoded = ready;
478
+ break;
479
+ }
480
+ hookExactKeys(preparation, ["kind", "call", "result"], [], label);
481
+ if (preparation.kind !== "denied") {
482
+ throw new Error(`${label}.kind is invalid`);
483
+ }
484
+ const denied = {
485
+ kind: "denied" as const,
486
+ call: decodeHookCall(preparation.call, `${label}.call`),
487
+ result: decodeHookResult(preparation.result, `${label}.result`),
488
+ };
489
+ if (!sameHookCall(denied.call, prior.call)) {
490
+ throw new Error(`${label} cannot alter the tool call`);
491
+ }
492
+ decoded = denied;
493
+ break;
494
+ }
495
+ case "tools/post-execute":
496
+ decoded = decodeHookResult(input, label);
497
+ break;
498
+ case "agent/step-continuation": {
499
+ const decision = hookRecord(input, label);
500
+ hookExactKeys(decision, ["kind"], [], label);
501
+ if (decision.kind !== "continue" && decision.kind !== "stop") {
502
+ throw new Error(`${label}.kind is invalid`);
503
+ }
504
+ decoded = { kind: decision.kind };
505
+ break;
506
+ }
507
+ }
508
+ return decoded as LoopEventReturnMapV1[Event];
509
+ }
510
+
511
+ export interface LoopEventDefinitionV1 {
512
+ mode: LoopEventDispatchModeV1;
513
+ payload: string;
514
+ returns: string;
515
+ isolateHook: boolean;
516
+ }
517
+
518
+ /**
519
+ * The complete public loop-event table. `payload` and `returns` name the DTOs
520
+ * above so generated authoring help and architecture docs use one vocabulary.
521
+ */
522
+ export const LOOP_EVENTS_V1 = {
523
+ "agent/created": {
524
+ mode: "emit",
525
+ payload: "{ agent: LoopAgentSnapshotV1 }",
526
+ returns: "void",
527
+ isolateHook: false,
528
+ },
529
+ "agent/disposed": {
530
+ mode: "emit",
531
+ payload: "{ agent: LoopAgentSnapshotV1 }",
532
+ returns: "void",
533
+ isolateHook: false,
534
+ },
535
+ "agent/status": {
536
+ mode: "emit",
537
+ payload: "{ agent, status }",
538
+ returns: "void",
539
+ isolateHook: false,
540
+ },
541
+ "agent/inbox/inserted": {
542
+ mode: "emit",
543
+ payload: "{ agent, input }",
544
+ returns: "void",
545
+ isolateHook: false,
546
+ },
547
+ "agent/inbox/claimed": {
548
+ mode: "emit",
549
+ payload: "{ agent, inputs, turn }",
550
+ returns: "void",
551
+ isolateHook: false,
552
+ },
553
+ "agent/pre-step": {
554
+ mode: "waterfall",
555
+ payload: "{ step, inputs, decision }",
556
+ returns: "LoopPreStepDecisionV1",
557
+ isolateHook: true,
558
+ },
559
+ "system-prompt/assemble": {
560
+ mode: "waterfall",
561
+ payload: "{ context, assembly }",
562
+ returns: "PromptAssembly",
563
+ isolateHook: true,
564
+ },
565
+ "agent/message-window": {
566
+ mode: "waterfall",
567
+ payload: "{ step, messages }",
568
+ returns: "LlmMessage[]",
569
+ isolateHook: true,
570
+ },
571
+ "agent/tool-exposure": {
572
+ mode: "waterfall",
573
+ payload: "{ step, tools }",
574
+ returns: "ToolSchema[]",
575
+ isolateHook: true,
576
+ },
577
+ "agent/request": {
578
+ mode: "waterfall",
579
+ payload: "{ step, request }",
580
+ returns: "NormalizedModelRequest",
581
+ isolateHook: false,
582
+ },
583
+ "agent/request-error": {
584
+ mode: "waterfall",
585
+ payload: "{ step, error, action }",
586
+ returns: "LoopRequestErrorActionV1",
587
+ isolateHook: false,
588
+ },
589
+ "llm/stream": {
590
+ mode: "waterfall",
591
+ payload: "{ request }",
592
+ returns: "AsyncIterable<LlmStreamEvent>",
593
+ isolateHook: false,
594
+ },
595
+ "tools/pre-execute": {
596
+ mode: "waterfall",
597
+ payload: "{ call, context, preparation }",
598
+ returns: "ToolPreparation",
599
+ isolateHook: true,
600
+ },
601
+ "tools/execute": {
602
+ mode: "waterfall",
603
+ payload: "{ call, context }",
604
+ returns: "ToolExecutionResult",
605
+ isolateHook: false,
606
+ },
607
+ "tools/post-execute": {
608
+ mode: "waterfall",
609
+ payload: "{ call, context, result }",
610
+ returns: "ToolExecutionResult",
611
+ isolateHook: true,
612
+ },
613
+ "tools/result": {
614
+ mode: "emit",
615
+ payload: "{ call, result }",
616
+ returns: "void",
617
+ isolateHook: false,
618
+ },
619
+ "agent/step-continuation": {
620
+ mode: "waterfall",
621
+ payload: "{ step, decision }",
622
+ returns: "LoopStepContinuationV1",
623
+ isolateHook: true,
624
+ },
625
+ "agent/model-outcome-committed": {
626
+ mode: "serial",
627
+ payload: "{ agent, requestId, outcome }",
628
+ returns: "void",
629
+ isolateHook: false,
630
+ },
631
+ "agent/turn-stopping": {
632
+ mode: "serial",
633
+ payload: "{ agent, turn }",
634
+ returns: "void",
635
+ isolateHook: false,
636
+ },
637
+ "agent/cancel-requested": {
638
+ mode: "emit",
639
+ payload: "{ agent, reason }",
640
+ returns: "void",
641
+ isolateHook: false,
642
+ },
643
+ "agent/error": {
644
+ mode: "emit",
645
+ payload: "{ agent, error }",
646
+ returns: "void",
647
+ isolateHook: false,
648
+ },
649
+ "session/event": {
650
+ mode: "emit",
651
+ payload: "SessionEventEnvelope",
652
+ returns: "void",
653
+ isolateHook: false,
654
+ },
655
+ } as const satisfies Record<LoopEventNameV1, LoopEventDefinitionV1>;
656
+
657
+ declare module "cordis" {
658
+ interface Events {
659
+ "agent/created": (agent: LoopAgentRuntimeV1) => void;
660
+ "agent/disposed": (agent: LoopAgentRuntimeV1) => void;
661
+ "agent/status": (
662
+ agent: LoopAgentRuntimeV1,
663
+ status: LoopAgentStatusV1,
664
+ ) => void;
665
+ "agent/inbox/inserted": (
666
+ agent: LoopAgentRuntimeV1,
667
+ input: LoopAgentInputV1,
668
+ ) => void;
669
+ "agent/inbox/claimed": (
670
+ agent: LoopAgentRuntimeV1,
671
+ inputs: LoopAgentInputV1[],
672
+ turn: number,
673
+ ) => void;
674
+ "agent/pre-step": (
675
+ agent: LoopAgentRuntimeV1,
676
+ inputs: LoopAgentInputV1[],
677
+ turn: number,
678
+ step: number,
679
+ next: () => Promise<LoopPreStepDecisionV1>,
680
+ ) => Promise<LoopPreStepDecisionV1>;
681
+ "agent/message-window": (
682
+ agent: LoopAgentRuntimeV1,
683
+ messages: LlmMessage[],
684
+ turn: number,
685
+ step: number,
686
+ signal: AbortSignal,
687
+ next: () => Promise<LlmMessage[]>,
688
+ ) => Promise<LlmMessage[]>;
689
+ "agent/tool-exposure": (
690
+ agent: LoopAgentRuntimeV1,
691
+ tools: ToolSchema[],
692
+ turn: number,
693
+ step: number,
694
+ signal: AbortSignal,
695
+ next: () => Promise<ToolSchema[]>,
696
+ ) => Promise<ToolSchema[]>;
697
+ "agent/step-continuation": (
698
+ agent: LoopAgentRuntimeV1,
699
+ decision: LoopStepContinuationV1,
700
+ turn: number,
701
+ step: number,
702
+ signal: AbortSignal,
703
+ next: () => Promise<LoopStepContinuationV1>,
704
+ ) => Promise<LoopStepContinuationV1>;
705
+ "agent/cancel-requested": (
706
+ agent: LoopAgentRuntimeV1,
707
+ reason: "user" | "shutdown",
708
+ ) => void;
709
+ "agent/error": (agent: LoopAgentRuntimeV1, error: unknown) => void;
710
+ }
711
+ }