auggy 0.3.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 (121) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +201 -0
  3. package/README.md +161 -0
  4. package/package.json +76 -0
  5. package/src/agent-card.ts +39 -0
  6. package/src/agent.ts +283 -0
  7. package/src/agentmail-client.ts +138 -0
  8. package/src/augments/bash/index.ts +463 -0
  9. package/src/augments/bash/skill/SKILL.md +156 -0
  10. package/src/augments/budgets/budget-store.ts +513 -0
  11. package/src/augments/budgets/index.ts +134 -0
  12. package/src/augments/budgets/preamble.ts +93 -0
  13. package/src/augments/budgets/types.ts +89 -0
  14. package/src/augments/file-memory/index.ts +71 -0
  15. package/src/augments/filesystem/index.ts +533 -0
  16. package/src/augments/filesystem/skill/SKILL.md +142 -0
  17. package/src/augments/filesystem/skill/references/mount-permissions.md +81 -0
  18. package/src/augments/layered-memory/extractor/buffer.ts +56 -0
  19. package/src/augments/layered-memory/extractor/frequency.ts +79 -0
  20. package/src/augments/layered-memory/extractor/inject-handler.ts +103 -0
  21. package/src/augments/layered-memory/extractor/parse.ts +75 -0
  22. package/src/augments/layered-memory/extractor/prompt.md +26 -0
  23. package/src/augments/layered-memory/index.ts +757 -0
  24. package/src/augments/layered-memory/skill/SKILL.md +153 -0
  25. package/src/augments/layered-memory/storage/migrations/README.md +16 -0
  26. package/src/augments/layered-memory/storage/migrations/supabase-add-fact-fields.sql +9 -0
  27. package/src/augments/layered-memory/storage/sqlite-store.ts +352 -0
  28. package/src/augments/layered-memory/storage/supabase-store.ts +263 -0
  29. package/src/augments/layered-memory/storage/types.ts +98 -0
  30. package/src/augments/link/index.ts +489 -0
  31. package/src/augments/link/translate.ts +261 -0
  32. package/src/augments/notify/adapters/agentmail.ts +70 -0
  33. package/src/augments/notify/adapters/telegram.ts +60 -0
  34. package/src/augments/notify/adapters/webhook.ts +55 -0
  35. package/src/augments/notify/index.ts +284 -0
  36. package/src/augments/notify/skill/SKILL.md +150 -0
  37. package/src/augments/org-context/index.ts +721 -0
  38. package/src/augments/org-context/skill/SKILL.md +96 -0
  39. package/src/augments/skills/index.ts +103 -0
  40. package/src/augments/supabase-memory/index.ts +151 -0
  41. package/src/augments/telegram-transport/index.ts +312 -0
  42. package/src/augments/telegram-transport/polling.ts +55 -0
  43. package/src/augments/telegram-transport/webhook.ts +56 -0
  44. package/src/augments/turn-control/index.ts +61 -0
  45. package/src/augments/turn-control/skill/SKILL.md +155 -0
  46. package/src/augments/visitor-auth/email-validation.ts +66 -0
  47. package/src/augments/visitor-auth/index.ts +779 -0
  48. package/src/augments/visitor-auth/rate-limiter.ts +90 -0
  49. package/src/augments/visitor-auth/skill/SKILL.md +55 -0
  50. package/src/augments/visitor-auth/storage/sqlite-store.ts +398 -0
  51. package/src/augments/visitor-auth/storage/types.ts +164 -0
  52. package/src/augments/visitor-auth/types.ts +123 -0
  53. package/src/augments/visitor-auth/verify-page.ts +179 -0
  54. package/src/augments/web-fetch/index.ts +331 -0
  55. package/src/augments/web-fetch/skill/SKILL.md +100 -0
  56. package/src/cli/agent-index.ts +289 -0
  57. package/src/cli/augment-catalog.ts +320 -0
  58. package/src/cli/augment-resolver.ts +597 -0
  59. package/src/cli/commands/add-skill.ts +194 -0
  60. package/src/cli/commands/add.ts +87 -0
  61. package/src/cli/commands/chat.ts +207 -0
  62. package/src/cli/commands/create.ts +462 -0
  63. package/src/cli/commands/dev.ts +139 -0
  64. package/src/cli/commands/eval.ts +180 -0
  65. package/src/cli/commands/ls.ts +66 -0
  66. package/src/cli/commands/remove.ts +95 -0
  67. package/src/cli/commands/restart.ts +40 -0
  68. package/src/cli/commands/start.ts +123 -0
  69. package/src/cli/commands/status.ts +104 -0
  70. package/src/cli/commands/stop.ts +84 -0
  71. package/src/cli/commands/visitors-revoke.ts +155 -0
  72. package/src/cli/commands/visitors.ts +101 -0
  73. package/src/cli/config-parser.ts +1034 -0
  74. package/src/cli/engine-resolver.ts +68 -0
  75. package/src/cli/index.ts +178 -0
  76. package/src/cli/model-picker.ts +89 -0
  77. package/src/cli/pid-registry.ts +146 -0
  78. package/src/cli/plist-generator.ts +117 -0
  79. package/src/cli/resolve-config.ts +56 -0
  80. package/src/cli/scaffold-skills.ts +158 -0
  81. package/src/cli/scaffold.ts +291 -0
  82. package/src/cli/skill-frontmatter.ts +51 -0
  83. package/src/cli/skill-validator.ts +151 -0
  84. package/src/cli/types.ts +228 -0
  85. package/src/cli/yaml-helpers.ts +66 -0
  86. package/src/engines/_shared/cost.ts +55 -0
  87. package/src/engines/_shared/schema-normalize.ts +75 -0
  88. package/src/engines/anthropic/pricing.ts +117 -0
  89. package/src/engines/anthropic.ts +483 -0
  90. package/src/engines/openai/pricing.ts +67 -0
  91. package/src/engines/openai.ts +446 -0
  92. package/src/engines/openrouter/pricing.ts +83 -0
  93. package/src/engines/openrouter.ts +185 -0
  94. package/src/helpers.ts +24 -0
  95. package/src/http.ts +387 -0
  96. package/src/index.ts +165 -0
  97. package/src/kernel/capability-table.ts +172 -0
  98. package/src/kernel/context-allocator.ts +161 -0
  99. package/src/kernel/history-manager.ts +198 -0
  100. package/src/kernel/lifecycle-manager.ts +106 -0
  101. package/src/kernel/output-validator.ts +35 -0
  102. package/src/kernel/preamble.ts +23 -0
  103. package/src/kernel/route-collector.ts +97 -0
  104. package/src/kernel/timeout.ts +21 -0
  105. package/src/kernel/tool-selector.ts +47 -0
  106. package/src/kernel/trace-emitter.ts +66 -0
  107. package/src/kernel/transport-queue.ts +147 -0
  108. package/src/kernel/turn-loop.ts +1148 -0
  109. package/src/memory/context-synthesis.ts +83 -0
  110. package/src/memory/memory-bus.ts +61 -0
  111. package/src/memory/registry.ts +80 -0
  112. package/src/memory/tools.ts +320 -0
  113. package/src/memory/types.ts +8 -0
  114. package/src/parts.ts +30 -0
  115. package/src/scaffold-templates/identity.md +31 -0
  116. package/src/telegram-client.ts +145 -0
  117. package/src/tokenizer.ts +14 -0
  118. package/src/transports/ag-ui-events.ts +253 -0
  119. package/src/transports/visitor-token.ts +82 -0
  120. package/src/transports/web-transport.ts +948 -0
  121. package/src/types.ts +1009 -0
package/src/types.ts ADDED
@@ -0,0 +1,1009 @@
1
+ import type { z } from "zod";
2
+ import type { CostResult } from "./engines/_shared/cost";
3
+ export type { CostResult } from "./engines/_shared/cost";
4
+
5
+ // === Context Types (spec §3) ===
6
+
7
+ export type ContextPlacement = "system" | "preamble" | "assistant-preamble";
8
+ export type ContextProvenance = "identity" | "memory" | "retrieval" | "augment";
9
+ export type ContextPriority = "required" | "high" | "normal" | "low" | "evictable";
10
+ export type EvictionPolicy = "never" | "summarize" | "drop";
11
+ export type ContextOrigin = "operator" | "system" | "agent" | "peer-derived";
12
+
13
+ export interface ContextBlock {
14
+ source: string;
15
+ content: string;
16
+ placement: ContextPlacement;
17
+ provenance: ContextProvenance;
18
+ priority: ContextPriority;
19
+ eviction: EvictionPolicy;
20
+ origin: ContextOrigin;
21
+ ttl?: "turn" | "session" | "persistent";
22
+ visibility?: "public" | "pipeline-only";
23
+ tokenCount?: number;
24
+ }
25
+
26
+ // === A2A-compatible content types (spec §3, A2A-shaped) ===
27
+
28
+ export type Part =
29
+ | { kind: "text"; text: string }
30
+ | { kind: "file"; uri: string; mimeType?: string; name?: string }
31
+ | { kind: "data"; data: Record<string, unknown> };
32
+
33
+ // === Task lifecycle (A2A-shaped, v1 uses "completed" | "failed" | "canceled") ===
34
+
35
+ export type TaskState =
36
+ | "working"
37
+ | "input-required"
38
+ | "auth-required"
39
+ | "completed"
40
+ | "failed"
41
+ | "canceled"
42
+ | "rejected";
43
+
44
+ // === Memory Provider Contract ===
45
+
46
+ export interface MemoryDefaults {
47
+ mutable: boolean;
48
+ origin: ContextOrigin;
49
+ priority: ContextPriority;
50
+ placement: ContextPlacement;
51
+ eviction: EvictionPolicy;
52
+ ttl?: "turn" | "session" | "persistent";
53
+ }
54
+
55
+ /**
56
+ * Provenance origin of a memory entry. Canonical here; storage types alias
57
+ * via MemoryOrigin so the runtime contract and the storage column type
58
+ * stay in lockstep.
59
+ *
60
+ * - "operator" — written by the operator (config-mounted entries, identity)
61
+ * - "peer-derived" — explicit `memory_write` calls from the model on behalf
62
+ * of a peer's request ("save this for me")
63
+ * - "agent-derived" — written by background extraction (auto-save, ADR-018
64
+ * Phase 2; populated by PR β). Paraphrases, not verbatim.
65
+ * - "agent" — direct agent-side writes (rare; reserved for system-internal
66
+ * writes that shouldn't carry "peer-derived" trust)
67
+ */
68
+ export type MemoryOrigin = "operator" | "peer-derived" | "agent-derived" | "agent";
69
+
70
+ export interface MemoryEntry {
71
+ label: string;
72
+ content: string;
73
+ metadata?: Record<string, unknown>;
74
+ // Provenance — providers that don't track this omit these fields
75
+ peerId?: string;
76
+ trustLevel?: TrustLevel;
77
+ createdAt?: number;
78
+ supersededBy?: string;
79
+ retentionClass?: "operational" | "lesson";
80
+ isVerbatim?: boolean;
81
+ origin?: MemoryOrigin;
82
+ }
83
+
84
+ export interface MemoryQueryOpts {
85
+ peerId?: string;
86
+ }
87
+
88
+ export interface MemoryWriteOpts {
89
+ peerId?: string;
90
+ trustLevel?: TrustLevel;
91
+ }
92
+
93
+ export interface StaticMemoryProvider {
94
+ owns: { kind: "static"; labels: string[] };
95
+ defaults: MemoryDefaults;
96
+ read: (label: string) => Promise<MemoryEntry | null>;
97
+ write?: (label: string, content: string) => Promise<void>;
98
+ }
99
+
100
+ export interface NamespaceMemoryProvider {
101
+ owns: { kind: "namespace"; prefix: string };
102
+ defaults: MemoryDefaults;
103
+ search: (query: string, opts?: MemoryQueryOpts) => Promise<MemoryEntry[]>;
104
+ write?: (label: string, content: string, opts?: MemoryWriteOpts) => Promise<void>;
105
+ read?: (label: string) => Promise<MemoryEntry | null>;
106
+ list?: () => Promise<string[]>;
107
+ forget?: (peerId: string) => Promise<number>;
108
+ }
109
+
110
+ export type MemoryProviderSpec = StaticMemoryProvider | NamespaceMemoryProvider;
111
+
112
+ // === Tool Types (spec §3) ===
113
+
114
+ export type ToolCategory = "memory" | "search" | "communication" | "meta" | (string & {});
115
+
116
+ export interface ToolExecuteContext {
117
+ turnId: string;
118
+ peer: PeerIdentity | null;
119
+ threadId: string;
120
+ }
121
+
122
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
123
+ export interface Tool<TInput = any> {
124
+ name: string;
125
+ description: string;
126
+ category: ToolCategory;
127
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
+ input: z.ZodType<TInput, any, any>;
129
+ inputJsonSchema?: Record<string, unknown>;
130
+ execute: (input: TInput, context?: ToolExecuteContext) => Promise<string | ToolResult>;
131
+ }
132
+
133
+ /**
134
+ * Structured tool return shape. Augments may return either a plain string
135
+ * (back-compat with all existing tools) OR a ToolResult that lets the tool
136
+ * influence turn lifecycle.
137
+ *
138
+ * The narrowed `terminate.status` union is a type-level guarantee that
139
+ * augments cannot spoof kernel-controlled states (failed/canceled/rejected/
140
+ * auth-required). Those remain owned by the kernel.
141
+ */
142
+ export interface ToolResult {
143
+ /** What the model sees as the tool's output. Replaces the plain-string return. */
144
+ content: string;
145
+ /** Optional turn-termination directive. */
146
+ terminate?: {
147
+ status: Extract<TaskState, "input-required" | "completed">;
148
+ message?: string;
149
+ };
150
+ }
151
+
152
+ // === Peer Identity (spec §4) ===
153
+
154
+ export type PeerKind = "human" | "agent" | "system" | "anonymous";
155
+
156
+ /**
157
+ * Trust level for an inbound peer. Determines what they CAN do
158
+ * (capability gating).
159
+ *
160
+ * creator — the deployer of this specific agent. Bypasses budgets.
161
+ * agent — a machine the creator has admitted (shared-secret).
162
+ * public — everyone else (anonymous or recognized via visitor token).
163
+ *
164
+ * v0 ships these three. "person" (verified human, post-OAuth/SSO) is
165
+ * the post-v0 fourth level — reserved in design, not enabled in code.
166
+ */
167
+ export type TrustLevel = "creator" | "agent" | "public";
168
+
169
+ export interface PeerIdentity {
170
+ id: string;
171
+ kind: PeerKind;
172
+ trustLevel: TrustLevel;
173
+ /**
174
+ * Substate within the `public` trust level. Set by the transport at
175
+ * identity resolution. Differentiates first-contact anonymous visitors
176
+ * from those holding a valid agent-issued visitor token.
177
+ *
178
+ * - "anonymous": no token, ephemeral peer.id (anon-<threadId>).
179
+ * Memory writes attach to ephemeral identity.
180
+ * - "recognized": HMAC-verified visitor token, durable peer.id (vis_*).
181
+ * Memory writes attach to durable identity.
182
+ *
183
+ * Present iff trustLevel === "public". Other trust levels MUST omit it.
184
+ */
185
+ publicSubstate?: "anonymous" | "recognized";
186
+ sourceAugment: string;
187
+ displayName?: string;
188
+ orgId?: string;
189
+ }
190
+
191
+ // === Turn Types (spec §4) ===
192
+
193
+ export type TurnTriggerType = "message" | "scheduled" | "event" | "continuation" | "internal";
194
+
195
+ export interface InboundMessage {
196
+ parts: Part[];
197
+ sourceAugment: string;
198
+ peer: PeerIdentity | null;
199
+ timestamp: number;
200
+ contextId?: string;
201
+ taskId?: string;
202
+ metadata?: Record<string, unknown>;
203
+ }
204
+
205
+ export interface TurnTrigger {
206
+ type: TurnTriggerType;
207
+ turnId: string;
208
+ threadId?: string;
209
+ contextId?: string;
210
+ taskId?: string;
211
+ timestamp: number;
212
+ source?: string;
213
+ peer?: PeerIdentity | null;
214
+ payload: InboundMessage | Record<string, unknown>;
215
+ }
216
+
217
+ export interface TurnState {
218
+ turnId: string;
219
+ threadId: string;
220
+ trigger: TurnTrigger;
221
+ peer: PeerIdentity | null;
222
+ toolCallsSoFar: number;
223
+ turnStartedAt: number;
224
+ metadata: Record<string, unknown>;
225
+ }
226
+
227
+ export interface OutboundMessage {
228
+ parts: Part[];
229
+ targetAugment?: string;
230
+ targetPeer?: string;
231
+ contextId?: string;
232
+ taskId?: string;
233
+ metadata?: Record<string, unknown>;
234
+ }
235
+
236
+ export interface ToolCallRecord {
237
+ name: string;
238
+ input: unknown;
239
+ output: string;
240
+ durationMs: number;
241
+ }
242
+
243
+ export interface TurnTrace {
244
+ turnId: string;
245
+ threadId: string;
246
+ timestamp: number;
247
+ duration: number;
248
+ trigger: {
249
+ type: string;
250
+ sourceAugment?: string;
251
+ peerKind?: string;
252
+ trustLevel?: string;
253
+ };
254
+ contextAssembly: {
255
+ augmentBlocks: {
256
+ source: string;
257
+ tokens: number;
258
+ included: boolean;
259
+ evicted: boolean;
260
+ }[];
261
+ preambleTokens: number;
262
+ toolSchemaTokens: number;
263
+ historyTokens: number;
264
+ totalTokens: number;
265
+ budgetUsed: number;
266
+ };
267
+ toolSelection: {
268
+ totalTools: number;
269
+ phase1Used: boolean;
270
+ selectedCategories?: string[];
271
+ mountedTools: string[];
272
+ withheldTools: string[];
273
+ };
274
+ inferenceSteps: {
275
+ model: string;
276
+ inputTokens: number;
277
+ outputTokens: number;
278
+ durationMs: number;
279
+ toolCalls: {
280
+ name: string;
281
+ augment: string;
282
+ durationMs: number;
283
+ approved: boolean;
284
+ }[];
285
+ cost: CostResult;
286
+ }[];
287
+ capabilityChecks: {
288
+ tool: string;
289
+ result: "allowed" | "needs-approval" | "denied";
290
+ }[];
291
+ approvals?: {
292
+ tool: string;
293
+ outcome: "approved" | "denied" | "timeout";
294
+ waitMs: number;
295
+ }[];
296
+ outputValidation?: {
297
+ flagged: boolean;
298
+ reasons: string[];
299
+ };
300
+ }
301
+
302
+ export interface TurnResult {
303
+ turnId: string;
304
+ success: boolean;
305
+ status: TaskState;
306
+ response?: OutboundMessage;
307
+ responses?: OutboundMessage[];
308
+ errorResponse?: string;
309
+ toolCalls: ToolCallRecord[];
310
+ trace: TurnTrace;
311
+ error?: { message: string; source: string };
312
+ /**
313
+ * When status is "rejected", this names the class of rejection so
314
+ * the transport can map to the right HTTP status:
315
+ * - "cap-denied" → HTTP 429 (over budget cap)
316
+ * - "admission-state-failed" → HTTP 5xx (confirm-phase failure)
317
+ * - "engine-error" → HTTP 5xx (engine call threw)
318
+ * - other strings → HTTP 5xx fallback
319
+ *
320
+ * Optional for backward compatibility — older callers and existing
321
+ * rejection sites may not yet set this.
322
+ */
323
+ errorClass?: string;
324
+ }
325
+
326
+ // === Transcript + Scheduler (ADR-027) ===
327
+
328
+ /**
329
+ * Snapshot of a completed turn, captured by the kernel and exposed via
330
+ * SchedulerContext.getCompletedTranscript() for background-work augments.
331
+ *
332
+ * Returned by history-manager's kernel-internal getTranscript(turnId).
333
+ * Returns null when the turn was already compacted before retrieval.
334
+ */
335
+ export interface Transcript {
336
+ turnId: string;
337
+ threadId: string;
338
+ peer: PeerIdentity | null;
339
+ parts: Part[];
340
+ toolCalls: ToolCallRecord[];
341
+ startedAt: number;
342
+ endedAt: number;
343
+ }
344
+
345
+ /**
346
+ * Context handed to `Augment.scheduleAfterTurn` (ADR-027). Exposes the
347
+ * narrow surface needed for post-turn background work:
348
+ *
349
+ * - inject(trigger): admit a follow-up turn through the normal turn
350
+ * loop. The follow-up gets its own turnId, runs admission/budgets,
351
+ * fires lifecycle hooks, and surfaces in cost accounting like any
352
+ * other turn.
353
+ * - getCompletedTranscript(): retrieve the just-completed turn's
354
+ * transcript snapshot. Closure-bound to the just-completed turnId;
355
+ * no turnId argument by design (per ADR-027 Decision 3 — arbitrary
356
+ * turnId reads stay kernel-internal at v1.0). Returns null when the
357
+ * turn was already compacted before the hook ran.
358
+ */
359
+ export interface SchedulerContext {
360
+ inject(trigger: TurnTrigger): Promise<TurnResult>;
361
+ getCompletedTranscript(): Promise<Transcript | null>;
362
+ }
363
+
364
+ /**
365
+ * Context handed to `Augment.handleInternalTurn` (ADR-027 Decision 5).
366
+ * Exposes the kernel-resolved threadId + peer for the internal turn so
367
+ * the handler can propagate them when writing to memory or recording
368
+ * side-effects.
369
+ */
370
+ export interface InternalTurnContext {
371
+ threadId: string;
372
+ peer: PeerIdentity | null;
373
+ }
374
+
375
+ // === Kernel Events (internal — emitted by turn loop, consumed by transports) ===
376
+
377
+ export type KernelEvent =
378
+ | {
379
+ kind: "run_started";
380
+ turnId: string;
381
+ threadId: string;
382
+ contextId?: string;
383
+ taskId?: string;
384
+ }
385
+ | {
386
+ kind: "tool_call_started";
387
+ turnId: string;
388
+ toolCallId: string;
389
+ toolName: string;
390
+ augmentName: string;
391
+ }
392
+ | {
393
+ kind: "tool_call_args";
394
+ turnId: string;
395
+ toolCallId: string;
396
+ args: Record<string, unknown>;
397
+ }
398
+ | {
399
+ kind: "tool_call_result";
400
+ turnId: string;
401
+ toolCallId: string;
402
+ output: string;
403
+ isError: boolean;
404
+ }
405
+ | {
406
+ kind: "text_message";
407
+ turnId: string;
408
+ messageId: string;
409
+ role: "assistant";
410
+ text: string;
411
+ }
412
+ | {
413
+ kind: "text_message_start";
414
+ turnId: string;
415
+ messageId: string;
416
+ role: "assistant";
417
+ }
418
+ | {
419
+ kind: "text_message_delta";
420
+ turnId: string;
421
+ messageId: string;
422
+ delta: string;
423
+ }
424
+ | {
425
+ kind: "text_message_end";
426
+ turnId: string;
427
+ messageId: string;
428
+ }
429
+ | {
430
+ kind: "run_finished";
431
+ turnId: string;
432
+ status: TaskState;
433
+ /**
434
+ * Optional human-visible message that explains the terminal status.
435
+ * Today this is populated only when a tool's `ToolResult.terminate`
436
+ * carries a `message` (e.g. the prompt from `request_input`). The
437
+ * AG-UI translator forwards it as `RUN_FINISHED.result.message`.
438
+ */
439
+ message?: string;
440
+ }
441
+ | {
442
+ kind: "run_error";
443
+ turnId: string;
444
+ message: string;
445
+ source: string;
446
+ };
447
+
448
+ export type KernelEventHandler = (event: KernelEvent) => void;
449
+
450
+ // === Message / History (spec §4) ===
451
+
452
+ export type MessageRole = "user" | "assistant" | "tool_use" | "tool_result";
453
+
454
+ export interface Message {
455
+ id: string;
456
+ role: MessageRole;
457
+ peerId?: string;
458
+ toolCallId?: string;
459
+ content: string;
460
+ timestamp: number;
461
+ tokenCount: number;
462
+ }
463
+
464
+ // === Model Interface (spec §12) ===
465
+
466
+ export interface ToolDefinition {
467
+ name: string;
468
+ description: string;
469
+ inputSchema: Record<string, unknown>;
470
+ }
471
+
472
+ export interface AssembledPrompt {
473
+ systemBlocks: string[];
474
+ contextBlocks: string[];
475
+ assistantPreamble?: string[];
476
+ messages: Message[];
477
+ tools: ToolDefinition[];
478
+ toolChoice?: "auto" | "any" | { name: string };
479
+ totalTokens: number;
480
+ evictions: { source: string; priority: ContextPriority; reason: string }[];
481
+ }
482
+
483
+ export interface ModelResponse {
484
+ content: string;
485
+ toolCalls?: { name: string; arguments: Record<string, unknown> }[];
486
+ inputTokens: number;
487
+ outputTokens: number;
488
+ cacheCreationTokens?: number; // Anthropic-specific: tokens written to prompt cache
489
+ cacheReadTokens?: number; // Anthropic-specific: tokens read from prompt cache
490
+ finishReason: "end_turn" | "tool_use" | "max_tokens";
491
+ costUsd?: number; // populated by adapter when pricing is known; undefined otherwise
492
+ unpricedReason?: string; // set when costUsd is absent, describes why pricing was unavailable
493
+ }
494
+
495
+ export type ModelDelta = { kind: "text_delta"; text: string };
496
+
497
+ export interface ModelClient {
498
+ complete(
499
+ prompt: AssembledPrompt,
500
+ opts?: { onDelta?: (delta: ModelDelta) => void },
501
+ ): Promise<ModelResponse>;
502
+ countTokens(text: string): number;
503
+ maxContextTokens: number;
504
+ }
505
+
506
+ // === Storage (spec §5.5) ===
507
+
508
+ export interface Storage {
509
+ get(key: string): Promise<string | null>;
510
+ put(key: string, value: string): Promise<void>;
511
+ delete(key: string): Promise<void>;
512
+ list(prefix: string): Promise<string[]>;
513
+ }
514
+
515
+ // === Agent Card (A2A-shaped, used for discovery) ===
516
+
517
+ export interface AgentCardProvider {
518
+ name: string;
519
+ version?: string;
520
+ contact?: string;
521
+ }
522
+
523
+ export interface AgentCardCapabilities {
524
+ streaming: boolean;
525
+ pushNotifications: boolean;
526
+ memory: boolean;
527
+ transport: boolean;
528
+ }
529
+
530
+ export interface AgentCardSkill {
531
+ name: string;
532
+ description: string;
533
+ category: string;
534
+ }
535
+
536
+ export interface AgentCard {
537
+ provider: AgentCardProvider;
538
+ purpose?: string;
539
+ capabilities: AgentCardCapabilities;
540
+ skills: AgentCardSkill[];
541
+ interfaces: string[];
542
+ extensions: Record<string, unknown>;
543
+ }
544
+
545
+ // === Transport (spec §3) ===
546
+
547
+ export interface TransportKernel {
548
+ handleInbound(
549
+ trigger: TurnTrigger,
550
+ options?: { onEvent?: KernelEventHandler },
551
+ ): Promise<TurnResult>;
552
+ onOutbound(callback: (peer: PeerIdentity, message: OutboundMessage) => Promise<void>): void;
553
+ getAgentCard(): AgentCard;
554
+ /**
555
+ * Cross-augment HTTP routes collected at `agent.start()` after
556
+ * `lifecycle.boot()`. Returns a frozen array — transports MUST NOT mutate.
557
+ * Transports that don't speak HTTP simply ignore this method.
558
+ */
559
+ getAugmentRoutes(): readonly AugmentHttpRoute[];
560
+ }
561
+
562
+ export interface TransportSpec {
563
+ /**
564
+ * Called once at agent boot. The kernel handle is captured for handleInbound
565
+ * dispatch; the augmentName is the operator-chosen runtime name (e.g. "telegram"),
566
+ * which the transport SHOULD use as trigger.source so kernel-emitted outbound
567
+ * messages route back through the agent's outboundHandlers map (keyed by aug.name).
568
+ */
569
+ register(kernel: TransportKernel, augmentName: string): Promise<void>;
570
+ identify(raw: unknown): PeerIdentity | null;
571
+ concurrency?: number;
572
+ maxQueueDepth?: number;
573
+ rateLimitPerPeer?: { maxPerMinute: number };
574
+ }
575
+
576
+ // === Augment HTTP routes (PR γ.1) ===
577
+
578
+ /**
579
+ * HTTP methods supported by augment-registered routes.
580
+ *
581
+ * v1 limits to GET + POST. PUT/DELETE/PATCH deferred — no current consumer
582
+ * needs them, and a smaller surface reduces audit cost. Add on demand.
583
+ */
584
+ export type HttpMethod = "GET" | "POST";
585
+
586
+ /**
587
+ * Authentication mode for an augment-registered HTTP route.
588
+ *
589
+ * - `"bearer"` — the route inherits webTransport's bearer-token check (same
590
+ * token that gates `/agent/run`). Recommended default for any route that
591
+ * represents creator-driven action.
592
+ * - `"none"` — the route accepts any caller. Opt-in only; required for
593
+ * public callbacks like email magic-link clicks (PR γ.2 visitorAuth) where
594
+ * the visitor can't supply a bearer token. Boot logs a warning per
595
+ * `auth: "none"` route so operators can't miss them.
596
+ */
597
+ export type AugmentHttpRouteAuth = "bearer" | "none";
598
+
599
+ /**
600
+ * One HTTP route registered by an augment. Routes are collected at
601
+ * `agent.start()` AFTER `lifecycle.boot()` succeeds (so `onBoot`-populated
602
+ * route lists are visible) and BEFORE any transport binds a port. Path
603
+ * collisions (vs built-in paths or across augments) throw at `agent.start()`,
604
+ * never silently override.
605
+ */
606
+ export interface AugmentHttpRoute {
607
+ method: HttpMethod;
608
+ /**
609
+ * Exact-match path. v1 does not support patterns or parameters.
610
+ * Must start with `/`. Reserved paths (cannot be registered):
611
+ * - "/"
612
+ * - "/agent/run"
613
+ * - "/health"
614
+ * - "/.well-known/agent-card.json"
615
+ * Convention: scope under `/<augment-name>/...` to make collisions unlikely.
616
+ */
617
+ path: string;
618
+ /** Auth mode is required — no implicit default; forces deliberate choice. */
619
+ auth: AugmentHttpRouteAuth;
620
+ /**
621
+ * Optional per-route handler timeout in milliseconds. Default 30_000.
622
+ * Times out → 504. Independent from Bun.serve's connection idleTimeout.
623
+ */
624
+ timeoutMs?: number;
625
+ /**
626
+ * Optional max body bytes the dispatcher will accept. Default 1_048_576 (1 MB).
627
+ * Over cap → 413 before the handler runs. Enforced by counting actual bytes
628
+ * read from req.body (not trusting content-length, which is bypassable via
629
+ * chunked encoding or omission).
630
+ */
631
+ maxBodyBytes?: number;
632
+ /**
633
+ * Optional sliding-window rate limit per route (NOT per peer — auth-none
634
+ * routes have no peer). Returns 429 with `Retry-After` when triggered.
635
+ */
636
+ rateLimit?: {
637
+ maxPerMinute: number;
638
+ };
639
+ /**
640
+ * The handler. Receives the raw Request and an options bag carrying an
641
+ * AbortSignal that fires on timeout. Handlers SHOULD listen for the
642
+ * signal and short-circuit side-effecting work to avoid duplicate effects
643
+ * after a 504. Errors thrown are caught by the dispatcher and surfaced
644
+ * as 500 with an opaque body; the actual error is logged with the route
645
+ * path for triage.
646
+ */
647
+ handler: (req: Request, opts: { signal: AbortSignal }) => Promise<Response>;
648
+ }
649
+
650
+ // === Turn Gate (2PC admission) ===
651
+
652
+ /**
653
+ * Pre-dispatch admission gate. Augments declaring `turnGate` participate
654
+ * in two-phase commit (2PC) admission: the augment opens a transaction,
655
+ * stages writes inside it, returns a ticket the kernel uses to confirm
656
+ * (commit) or rollback (discard).
657
+ *
658
+ * The kernel pairs every prepare with exactly one of confirm or rollback.
659
+ * Storage transactions enforce atomicity; the augment cannot leak partial
660
+ * state because the writes only escape into the live state when the
661
+ * kernel signals confirm.
662
+ *
663
+ * v0 NOTE: This contract is FIRST-PARTY ONLY. The kernel cannot
664
+ * mechanically prevent third-party augments from violating the
665
+ * prepare-then-confirm contract (e.g. by writing outside the transaction).
666
+ * v0 ships with the budgets augment as the sole turn-gate; third-party
667
+ * turn-gate augments are out of scope until the contract has more
668
+ * real-world miles.
669
+ */
670
+ export interface TurnGateProvider {
671
+ /**
672
+ * Stage admission writes inside an open transaction. Returns a ticket
673
+ * the kernel uses to confirm or rollback. The augment evaluates caps
674
+ * using whatever reads it needs, stages reservation rows / rate-limit
675
+ * ticks / etc inside the transaction, and returns either:
676
+ * - { decision: { allow: false, reason }, confirm, rollback }
677
+ * where confirm is a no-op and rollback closes the read transaction.
678
+ * - { decision: { allow: true }, confirm, rollback }
679
+ * where confirm commits the staged writes and rollback discards them.
680
+ *
681
+ * The kernel pairs every prepare with exactly one of confirm/rollback.
682
+ */
683
+ prepare(args: {
684
+ turnId: string;
685
+ peer: PeerIdentity | null;
686
+ threadId: string;
687
+ trigger: TurnTrigger;
688
+ }): Promise<TurnGateTicket>;
689
+
690
+ /**
691
+ * Post-response cost commit. Receives the cost result; the augment
692
+ * uses this to debit USD totals or mark reservations completed.
693
+ * The CostResult discriminated union forces unpriced-aware handling.
694
+ *
695
+ * Optional. Errors here are logged but do not fail the turn — the
696
+ * response already exists.
697
+ */
698
+ commit?(args: {
699
+ turnId: string;
700
+ peer: PeerIdentity | null;
701
+ threadId: string;
702
+ cost: CostResult;
703
+ }): Promise<void>;
704
+ }
705
+
706
+ export interface TurnGateTicket {
707
+ decision: { allow: true } | { allow: false; reason: string };
708
+ /** Commit the staged writes. Idempotent — calling twice is a no-op. */
709
+ confirm(): Promise<void>;
710
+ /** Discard the staged writes. Idempotent — calling twice is a no-op. */
711
+ rollback(): Promise<void>;
712
+ }
713
+
714
+ // === Augment (spec §3) ===
715
+
716
+ export type AugmentCapability = "transport" | "context" | "tools" | "lifecycle";
717
+
718
+ export interface AugmentConstraints {
719
+ maxToolCallsPerTurn?: number;
720
+ requiresHumanApproval?: string[];
721
+ approvalPolicy?: "block-and-queue" | "skip" | "fail";
722
+ neverExpose?: string[];
723
+ contextTimeoutMs?: number;
724
+ toolTimeoutMs?: number;
725
+ /**
726
+ * Per-trust-level structural constraints. Applied additively on top of the
727
+ * top-level `neverExpose` / `requiresHumanApproval` fields. Top-level rules
728
+ * apply to every peer; per-level rules apply only to the specific level.
729
+ *
730
+ * Example: hide `fs_remove` from public peers but keep it visible to
731
+ * agent and creator:
732
+ * perTrustLevel: { public: { neverExpose: ["fs_remove"] } }
733
+ *
734
+ * Null peer (internal/scheduled triggers) is treated as "creator" trust.
735
+ */
736
+ perTrustLevel?: Partial<
737
+ Record<
738
+ TrustLevel,
739
+ {
740
+ neverExpose?: string[];
741
+ requiresHumanApproval?: string[];
742
+ }
743
+ >
744
+ >;
745
+ }
746
+
747
+ export interface Augment {
748
+ name: string;
749
+ version?: string;
750
+ required?: boolean;
751
+ capabilities?: AugmentCapability[];
752
+ context?: (turn: TurnState, priorContext?: ContextBlock[]) => Promise<ContextBlock[] | string>;
753
+ receivesPriorContext?: boolean;
754
+ tools?: Tool[];
755
+ transport?: TransportSpec;
756
+ /**
757
+ * HTTP routes the augment serves on any HTTP-capable transport (today: webTransport).
758
+ * Collected at `agent.start()` after `onBoot` runs; immutable thereafter.
759
+ * See `AugmentHttpRoute` for the contract.
760
+ */
761
+ httpRoutes?: AugmentHttpRoute[];
762
+ memory?: MemoryProviderSpec;
763
+ constraints?: AugmentConstraints;
764
+ onBoot?: () => Promise<void>;
765
+ onShutdown?: () => Promise<void>;
766
+ onTurnStart?: (turn: TurnState) => Promise<void>;
767
+ onTurnEnd?: (turn: TurnResult) => Promise<void>;
768
+ onIdle?: () => Promise<void>;
769
+ /**
770
+ * ADR-027: post-turn background-work hook. Fires after `onTurnEnd` for
771
+ * the just-completed user-facing turn. Receives a `SchedulerContext`
772
+ * with `inject` (admit a follow-up turn through the normal turn loop)
773
+ * and `getCompletedTranscript` (retrieve the just-completed turn's
774
+ * transcript snapshot).
775
+ *
776
+ * Errors thrown from this hook are caught and logged; they NEVER block
777
+ * the user-facing turn or affect the response delivered to the peer.
778
+ * Background work is best-effort by design.
779
+ *
780
+ * Multiple augments registering the hook execute sequentially in
781
+ * declaration order (ADR-027 Decision 2).
782
+ */
783
+ scheduleAfterTurn?: (result: TurnResult, ctx: SchedulerContext) => Promise<void>;
784
+ /**
785
+ * ADR-027 Decision 5: internal-trigger handler dispatch. When the
786
+ * kernel admits a turn whose `trigger.type === "internal"`, the
787
+ * turn-loop walks the augment list in declaration order and calls
788
+ * each augment's `handleInternalTurn` (if present) with the trigger.
789
+ * Augments that do not recognize the trigger MUST return null —
790
+ * dispatch then continues to the next augment. The first augment to
791
+ * return a non-null TurnResult owns the turn; the standard
792
+ * model-engine + tool-execution path is bypassed and the returned
793
+ * result is the turn's outcome.
794
+ *
795
+ * Augments use trigger.source as the authoritative routing key
796
+ * (e.g. `"layered-memory.autoSave"`). Augments emitting and consuming
797
+ * triggers SHOULD use a dotted prefix matching their augment name to
798
+ * avoid cross-augment collisions.
799
+ *
800
+ * The handler runs WITHIN the admitted turn — turn-gate prepare /
801
+ * confirm, onTurnStart, onTurnEnd, scheduleAfterTurn, and history
802
+ * recording all fire as for any standard turn. The handler is
803
+ * responsible for any LLM call its work needs; cost flows through
804
+ * `runCostCommit` via the standard trace pipeline (push priced
805
+ * inference steps onto `TurnResult.trace.inferenceSteps[]`).
806
+ *
807
+ * Augment authors MUST guard against re-entry — a handler should
808
+ * never synthesize a trigger that re-routes back to itself during
809
+ * the same execution.
810
+ *
811
+ * **Throw contract — load-bearing for budget accuracy.** Handlers
812
+ * MUST NOT throw with side effects. Failure modes (engine error,
813
+ * malformed response, transient network failure) MUST be caught
814
+ * inside the handler and returned as a failed TurnResult that still
815
+ * carries any priced inference steps in `trace.inferenceSteps[]`.
816
+ *
817
+ * If a handler throws after incurring LLM spend, the kernel's
818
+ * catch-block will commit a turn with no recorded cost — budgets
819
+ * sees zero spend for a turn that actually burned money. The kernel
820
+ * logs a structured warning (`[kernel] handleInternalTurn for
821
+ * augment "X" threw; cost may be undercounted ...`) so a misbehaving
822
+ * handler is operator-visible, but the cap-accuracy invariant
823
+ * depends on handlers honoring this contract.
824
+ */
825
+ handleInternalTurn?: (
826
+ trigger: TurnTrigger,
827
+ ctx: InternalTurnContext,
828
+ ) => Promise<TurnResult | null>;
829
+ /**
830
+ * Pre-dispatch admission gate. Kernel calls prepare/confirm/rollback
831
+ * before executing the turn. See TurnGateProvider for the 2PC contract.
832
+ *
833
+ * v0 NOTE: First-party only. The budgets augment is the sole shipped
834
+ * implementation. Third-party turn-gate augments are out of scope
835
+ * until the contract has more real-world miles.
836
+ */
837
+ turnGate?: TurnGateProvider;
838
+ }
839
+
840
+ // === Agent Config (spec §8) ===
841
+
842
+ export type CompactionStrategy = "summarize" | "truncate" | "sliding-window";
843
+
844
+ export interface AgentConfig {
845
+ name: string;
846
+ purpose?: string;
847
+ model: string;
848
+ augments: Augment[];
849
+ operators?: string[];
850
+ contextBudget?: {
851
+ historyPercent?: number;
852
+ toolSchemaPercent?: number;
853
+ };
854
+ compactionStrategy?: CompactionStrategy;
855
+ /** Max inference loop iterations per turn. Default 10. */
856
+ maxInferenceLoops?: number;
857
+ /** Tool-choice policy sent to the model. "auto" (default) lets the model
858
+ * decide; "any" forces a tool call; { name } forces a specific tool. */
859
+ toolChoice?: "auto" | "any" | { name: string };
860
+ }
861
+
862
+ export interface AgentHealth {
863
+ status: "healthy" | "degraded" | "unhealthy";
864
+ agent: string;
865
+ uptime: number;
866
+ augments: Record<string, { status: "ok" | "degraded" | "failed"; error?: string }>;
867
+ model: { reachable: boolean };
868
+ }
869
+
870
+ export interface AgentHandle {
871
+ start(): Promise<void>;
872
+ stop(): Promise<void>;
873
+ ready(): Promise<void>;
874
+ health(): AgentHealth;
875
+ card(): AgentCard;
876
+ inject(trigger: TurnTrigger): Promise<TurnResult>;
877
+ }
878
+
879
+ // === Notify augment ===
880
+
881
+ export type NotifyAdapterKind = "webhook" | "telegram" | "agentmail";
882
+
883
+ export interface WebhookNotifyDestination {
884
+ name: string;
885
+ transport: "webhook";
886
+ url: string;
887
+ headers?: Record<string, string>;
888
+ /** Optional per-destination rate limit. Falls back to the augment-level global cap when absent. */
889
+ rateLimit?: {
890
+ maxPerHour?: number;
891
+ cooldownMs?: number;
892
+ };
893
+ }
894
+
895
+ export interface TelegramNotifyDestination {
896
+ name: string;
897
+ transport: "telegram";
898
+ botToken: string;
899
+ chatId: number | string;
900
+ parseMode?: "Markdown" | "HTML" | "MarkdownV2";
901
+ /** Optional per-destination rate limit. Falls back to the augment-level global cap when absent. */
902
+ rateLimit?: {
903
+ maxPerHour?: number;
904
+ cooldownMs?: number;
905
+ };
906
+ }
907
+
908
+ export interface AgentMailNotifyDestination {
909
+ name: string;
910
+ transport: "agentmail";
911
+ /** AgentMail API key (Bearer token, prefix `am_`). Resolve via env interpolation in agent.yaml. */
912
+ apiKey: string;
913
+ /** AgentMail inbox ID this notification is sent FROM. */
914
+ inboxId: string;
915
+ /** Recipient email address(es). String or array; adapter normalizes to array. */
916
+ to: string | string[];
917
+ /** Optional subject prefix prepended to the notify summary. e.g. "[Auggy] ". */
918
+ subjectPrefix?: string;
919
+ /** Optional labels applied to the sent message in AgentMail. */
920
+ labels?: string[];
921
+ /** Override the AgentMail API base URL (testing/sandbox). Default: https://api.agentmail.to/v0 */
922
+ apiBaseUrl?: string;
923
+ /** Optional per-destination rate limit. Falls back to the augment-level global cap when absent. */
924
+ rateLimit?: {
925
+ maxPerHour?: number;
926
+ cooldownMs?: number;
927
+ };
928
+ }
929
+
930
+ export type NotifyDestination =
931
+ | WebhookNotifyDestination
932
+ | TelegramNotifyDestination
933
+ | AgentMailNotifyDestination;
934
+
935
+ export interface NotifyRateLimitOptions {
936
+ enabled?: boolean;
937
+ cooldownMs?: number;
938
+ globalMaxPerHour?: number;
939
+ dedupWindowMs?: number;
940
+ dedupThreshold?: number;
941
+ perPeerCooldownMs?: number;
942
+ }
943
+
944
+ export interface NotifyAugmentOptions {
945
+ destinations: NotifyDestination[];
946
+ rateLimit?: NotifyRateLimitOptions;
947
+ }
948
+
949
+ export interface NotifyPayload {
950
+ summary: string;
951
+ reason?: string;
952
+ visitor?: string;
953
+ }
954
+
955
+ export interface NotifyDeliveryResult {
956
+ status: "sent" | "failed";
957
+ detail?: string;
958
+ }
959
+
960
+ export interface NotifyAdapter {
961
+ deliver(destination: NotifyDestination, payload: NotifyPayload): Promise<NotifyDeliveryResult>;
962
+ }
963
+
964
+ // ---------------------------------------------------------------------------
965
+ // Telegram transport
966
+ // ---------------------------------------------------------------------------
967
+
968
+ export type TelegramInboundMode = "polling" | "webhook";
969
+
970
+ export interface TelegramPollingOptions {
971
+ timeoutSec?: number;
972
+ }
973
+
974
+ export interface TelegramWebhookOptions {
975
+ publicUrl: string;
976
+ port?: number;
977
+ secretToken: string;
978
+ allowedUpdates?: string[];
979
+ }
980
+
981
+ export interface TelegramAdmittedAgent {
982
+ id: string;
983
+ telegramUserId: number;
984
+ }
985
+
986
+ export type TelegramAnonymousIdentityMode = "ephemeral" | "durable";
987
+
988
+ export interface TelegramAuthOptions {
989
+ creatorUserIds?: number[];
990
+ admittedAgents?: TelegramAdmittedAgent[];
991
+ recognizedUserIds?: number[];
992
+ /**
993
+ * peer.id durability for anonymous Telegram peers. Default "ephemeral"
994
+ * matches web's anonymous-ephemeral semantics — peer.id is `tg_anon_<threadId>`,
995
+ * memory dies with thread. "durable" uses `tg_user_<userId>` for cross-session
996
+ * recall; operators opt into this consciously.
997
+ */
998
+ anonymousIdentityMode?: TelegramAnonymousIdentityMode;
999
+ }
1000
+
1001
+ export interface TelegramTransportOptions {
1002
+ botToken: string;
1003
+ inbound: {
1004
+ mode: TelegramInboundMode;
1005
+ polling?: TelegramPollingOptions;
1006
+ webhook?: TelegramWebhookOptions;
1007
+ };
1008
+ auth: TelegramAuthOptions;
1009
+ }