@loopingai/core 0.3.1 → 0.5.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 (110) hide show
  1. package/README.md +83 -28
  2. package/dist/a2a/caller.d.ts +24 -0
  3. package/dist/a2a/caller.d.ts.map +1 -0
  4. package/dist/a2a/caller.js +34 -0
  5. package/dist/a2a/caller.js.map +1 -0
  6. package/dist/a2a/card.d.ts +7 -2
  7. package/dist/a2a/card.d.ts.map +1 -1
  8. package/dist/a2a/card.js +16 -7
  9. package/dist/a2a/card.js.map +1 -1
  10. package/dist/a2a/index.d.ts +15 -0
  11. package/dist/a2a/index.d.ts.map +1 -1
  12. package/dist/a2a/index.js +15 -0
  13. package/dist/a2a/index.js.map +1 -1
  14. package/dist/a2a/notify.d.ts +27 -5
  15. package/dist/a2a/notify.d.ts.map +1 -1
  16. package/dist/a2a/notify.js +18 -8
  17. package/dist/a2a/notify.js.map +1 -1
  18. package/dist/a2a/push.d.ts +71 -0
  19. package/dist/a2a/push.d.ts.map +1 -0
  20. package/dist/a2a/push.js +54 -0
  21. package/dist/a2a/push.js.map +1 -0
  22. package/dist/a2a/verify.d.ts +28 -36
  23. package/dist/a2a/verify.d.ts.map +1 -1
  24. package/dist/a2a/verify.js +20 -25
  25. package/dist/a2a/verify.js.map +1 -1
  26. package/dist/agent/index.d.ts +16 -5
  27. package/dist/agent/index.d.ts.map +1 -1
  28. package/dist/agent/index.js +16 -5
  29. package/dist/agent/index.js.map +1 -1
  30. package/dist/config.d.ts +49 -7
  31. package/dist/config.d.ts.map +1 -1
  32. package/dist/config.js +25 -3
  33. package/dist/config.js.map +1 -1
  34. package/dist/contract/recipe.d.ts +14 -2
  35. package/dist/contract/recipe.d.ts.map +1 -1
  36. package/dist/contract/validation.d.ts +22 -18
  37. package/dist/contract/validation.d.ts.map +1 -1
  38. package/dist/contract/validation.js +19 -10
  39. package/dist/contract/validation.js.map +1 -1
  40. package/dist/db/models/tasks.d.ts +19 -5
  41. package/dist/db/models/tasks.d.ts.map +1 -1
  42. package/dist/db/models/tasks.js +38 -8
  43. package/dist/db/models/tasks.js.map +1 -1
  44. package/dist/host/agent.d.ts +217 -0
  45. package/dist/host/agent.d.ts.map +1 -0
  46. package/dist/host/agent.js +304 -0
  47. package/dist/host/agent.js.map +1 -0
  48. package/dist/host/index.d.ts +21 -0
  49. package/dist/host/index.d.ts.map +1 -0
  50. package/dist/host/index.js +20 -0
  51. package/dist/host/index.js.map +1 -0
  52. package/dist/host/plugin-host.d.ts +43 -0
  53. package/dist/host/plugin-host.d.ts.map +1 -0
  54. package/dist/host/plugin-host.js +2 -0
  55. package/dist/host/plugin-host.js.map +1 -0
  56. package/dist/round/agent.d.ts +267 -0
  57. package/dist/round/agent.d.ts.map +1 -0
  58. package/dist/round/agent.js +691 -0
  59. package/dist/round/agent.js.map +1 -0
  60. package/dist/round/index.d.ts +25 -0
  61. package/dist/round/index.d.ts.map +1 -0
  62. package/dist/round/index.js +24 -0
  63. package/dist/round/index.js.map +1 -0
  64. package/dist/round/policy.d.ts +99 -0
  65. package/dist/round/policy.d.ts.map +1 -0
  66. package/dist/round/policy.js +2 -0
  67. package/dist/round/policy.js.map +1 -0
  68. package/dist/round/subagent.d.ts +67 -0
  69. package/dist/round/subagent.d.ts.map +1 -0
  70. package/dist/round/subagent.js +89 -0
  71. package/dist/round/subagent.js.map +1 -0
  72. package/dist/round/turn.d.ts +231 -0
  73. package/dist/round/turn.d.ts.map +1 -0
  74. package/dist/round/turn.js +497 -0
  75. package/dist/round/turn.js.map +1 -0
  76. package/dist/round/workflow.d.ts +109 -0
  77. package/dist/round/workflow.d.ts.map +1 -0
  78. package/dist/round/workflow.js +316 -0
  79. package/dist/round/workflow.js.map +1 -0
  80. package/dist/runtime/index.d.ts +5 -1
  81. package/dist/runtime/index.d.ts.map +1 -1
  82. package/dist/runtime/index.js +2 -6
  83. package/dist/runtime/index.js.map +1 -1
  84. package/dist/subagent/fingerprint.d.ts.map +1 -1
  85. package/dist/subagent/fingerprint.js +7 -2
  86. package/dist/subagent/fingerprint.js.map +1 -1
  87. package/dist/testing/auth.d.ts.map +1 -1
  88. package/dist/testing/auth.js +10 -7
  89. package/dist/testing/auth.js.map +1 -1
  90. package/dist/testing/fixtures.d.ts +17 -0
  91. package/dist/testing/fixtures.d.ts.map +1 -1
  92. package/dist/testing/fixtures.js +17 -0
  93. package/dist/testing/fixtures.js.map +1 -1
  94. package/dist/testing/harness.d.ts +98 -0
  95. package/dist/testing/harness.d.ts.map +1 -0
  96. package/dist/testing/harness.js +139 -0
  97. package/dist/testing/harness.js.map +1 -0
  98. package/dist/testing/index.d.ts +2 -1
  99. package/dist/testing/index.d.ts.map +1 -1
  100. package/dist/testing/index.js +2 -1
  101. package/dist/testing/index.js.map +1 -1
  102. package/dist/worker/define-agent.d.ts +124 -0
  103. package/dist/worker/define-agent.d.ts.map +1 -0
  104. package/dist/worker/define-agent.js +21 -0
  105. package/dist/worker/define-agent.js.map +1 -0
  106. package/dist/worker/index.d.ts +29 -4
  107. package/dist/worker/index.d.ts.map +1 -1
  108. package/dist/worker/index.js +70 -12
  109. package/dist/worker/index.js.map +1 -1
  110. package/package.json +12 -3
@@ -0,0 +1,304 @@
1
+ import { Agent } from "agents";
2
+ import { TaskState } from "@a2a-js/sdk";
3
+ import { createAgentRuntime } from "../runtime/index.js";
4
+ import { resolveConfig } from "../config.js";
5
+ import { AgentDB, stateOf } from "../db/index.js";
6
+ import { callerContext } from "../a2a/caller.js";
7
+ import { createPushChannel } from "../a2a/push.js";
8
+ import { buildAgentSession } from "../agent/session.js";
9
+ import { createModelRuntime } from "../agent/model.js";
10
+ /**
11
+ * The Durable Object body every Looping agent has, whatever loop it runs.
12
+ *
13
+ * ## Why this is core's and not the app's
14
+ *
15
+ * It was the app's, in the starter, and it was written twice — once for the
16
+ * delegating round agent, once for the single-turn proactive one. The two copies
17
+ * were identical for ~180 lines: the memoized runtime/db/models getters, the
18
+ * `onStart` that must await migrations before the SDK dispatches any RPC, the
19
+ * cron registration guard, the session with its displacement fan-out, the
20
+ * `identityKey` timing, and the task RPC surface.
21
+ *
22
+ * They did not stay identical. The second copy dropped `markWorking`'s
23
+ * cancellation verdict on the floor and probed with a separate `getTask` before
24
+ * writing a terminal Task — so a canceled task still burned a model call, and the
25
+ * gateway could still receive a `completed` callback for a task the caller had
26
+ * abandoned. Both are lifecycle invariants, both were documented in the first
27
+ * copy, and neither is visible to a type checker or a linter.
28
+ *
29
+ * That is the argument for this class. **None of it is policy.** How a turn is
30
+ * shaped, what ends it, what the model is told — all of that stays with the
31
+ * agent, and an agent that wants a different loop simply does not extend
32
+ * {@link file://../round/agent.ts RoundAgentBase}. What is here is the part where
33
+ * being different is only ever a bug.
34
+ *
35
+ * ## The three seams
36
+ *
37
+ * ```ts
38
+ * export class MyAgent extends LoopingAgent<Env> {
39
+ * protected agentConfig() { return MY_CONFIG; }
40
+ * protected agentPlugins(host: PluginHost<Env>) { return plugins(host); }
41
+ * protected agentSoul(capabilities: string) { return soulPrompt(capabilities); }
42
+ * }
43
+ * ```
44
+ *
45
+ * One Durable Object instance per verified caller (keyed by the gateway JWT's
46
+ * `identity.key`), each owning **one continuous Session** — durable history plus
47
+ * a self-edited `memory` block, backed by `this.sql`. All of a caller's turns, in
48
+ * any channel or thread, accumulate into that one conversation.
49
+ */
50
+ export class LoopingAgent extends Agent {
51
+ session;
52
+ _runtime;
53
+ _models;
54
+ _pair;
55
+ _db;
56
+ /**
57
+ * The verified caller this instance belongs to, set on the first turn.
58
+ *
59
+ * `onStart` runs before any request, so it is not known when `agentPlugins()`
60
+ * is built — which is why anything per-caller takes a thunk. The DO is keyed
61
+ * 1:1 by this value, so it is constant once set.
62
+ */
63
+ identityKey;
64
+ /**
65
+ * Test-only model injection. A **field**, not a constructor argument or an RPC
66
+ * parameter, so it never appears on the generated DO stub: production callers
67
+ * cannot reach it, and no model configuration crosses the RPC boundary.
68
+ */
69
+ modelsOverride;
70
+ // --- assembly -------------------------------------------------------------
71
+ /**
72
+ * Everything that would otherwise be a module-level constant, resolved once
73
+ * per DO instance from this agent's config and its installed plugins.
74
+ *
75
+ * Resolving a registry at *import* time is the one thing the package split
76
+ * exists to prevent: it freezes the registry before `env` exists (which on
77
+ * Workers is always), defeats tree-shaking, and makes per-agent plugin
78
+ * selection impossible.
79
+ */
80
+ get runtime() {
81
+ return (this._runtime ??= createAgentRuntime({
82
+ config: this.agentConfig(),
83
+ plugins: this.agentPlugins(this.pluginHost()),
84
+ // Opt in to verifying every plugin's declared bindings exist. Fails at DO
85
+ // start with a sentence naming the plugin, rather than at the first tool
86
+ // call inside a request someone is waiting on.
87
+ env: this.env
88
+ }));
89
+ }
90
+ /** The resolved config. */
91
+ get config() {
92
+ return this.runtime.config;
93
+ }
94
+ /** The agent's database (drizzle + migrations), built once per DO instance. */
95
+ get db() {
96
+ return (this._db ??= new AgentDB(this.ctx.storage, {
97
+ maxSubtasks: this.config.maxSubtasks,
98
+ // Plugin-owned tables, applied after core's own migrations. A store that
99
+ // throws fails DO start rather than being skipped — a plugin whose tables
100
+ // are missing would otherwise fail at its first tool call.
101
+ stores: this.runtime.stores
102
+ }));
103
+ }
104
+ /** The model runtime for this instance, built lazily over the `AI` binding. */
105
+ get models() {
106
+ return (this._models ??= createModelRuntime({
107
+ ai: this.env.AI,
108
+ config: this.config.model
109
+ }));
110
+ }
111
+ /**
112
+ * What this agent's plugins are handed. Built from
113
+ * {@link resolvedModelIds} rather than `this.config`, which would be a cycle —
114
+ * building the runtime is what needs these.
115
+ */
116
+ pluginHost() {
117
+ const model = this.resolvedModelIds();
118
+ return {
119
+ env: this.env,
120
+ storage: this.ctx.storage,
121
+ // A thunk, not a value — see `identityKey`.
122
+ callerKey: () => this.requireIdentityKey(),
123
+ aiGatewayId: model.aiGatewayId
124
+ };
125
+ }
126
+ /**
127
+ * The model settings a locally-declared recipe runs on, resolved *before* the
128
+ * runtime exists.
129
+ *
130
+ * Deliberately not `this.config` — that would be a cycle. `resolveConfig` is
131
+ * cheap and pure and fills in core's baseline, so this is the same result the
132
+ * runtime lands on; that matters because every recipe runs on exactly this
133
+ * pair — `RecipePolicy` carries it and `validateRecipe` stamps it on.
134
+ */
135
+ resolvedModelIds() {
136
+ return resolveConfig(this.agentConfig()).model;
137
+ }
138
+ async onStart() {
139
+ // Await migrations before the SDK dispatches any RPC — eliminates the race
140
+ // between schema creation and first query on cold start / hibernation wake-up.
141
+ await this.db.ensureReady();
142
+ // Register the weekly cleanup cron once per DO instance (idempotent guard).
143
+ const existing = await this.listSchedules({ type: "cron" });
144
+ if (!existing.some((s) => s.callback === "cleanupOldTasks")) {
145
+ await this.schedule("0 1 * * 0", "cleanupOldTasks", {});
146
+ }
147
+ }
148
+ /**
149
+ * Cron handler: delete task rows older than 30 days. Runs Sunday 01:00 UTC.
150
+ *
151
+ * A plugin's own tables are its business — core's journal does not reach them,
152
+ * and neither does this sweep. A subclass with more durable state of its own
153
+ * overrides {@link cleanupAgentState}.
154
+ */
155
+ async cleanupOldTasks(_payload, _schedule) {
156
+ this.db.tasks.cleanup();
157
+ this.cleanupAgentState();
158
+ }
159
+ /** Extra durable state to age out alongside the task rows. Default: none. */
160
+ cleanupAgentState() { }
161
+ /**
162
+ * The main agent's primary/fallback pair. With `metadata` it builds a fresh
163
+ * pair carrying that AI Gateway correlation tag (so a gateway log ties the call
164
+ * to its task and round); without it — the Session's own compaction model — it
165
+ * reuses a memoized default. A test `modelsOverride` always wins.
166
+ */
167
+ modelPair(metadata) {
168
+ if (this.modelsOverride)
169
+ return this.modelsOverride;
170
+ if (!metadata)
171
+ return (this._pair ??= this.models.createModelPair());
172
+ return this.models.createModelPair({ metadata });
173
+ }
174
+ /**
175
+ * The one continuous Session for this caller (rebuilt from `this.sql` after
176
+ * eviction). Memoized — `identity` is constant for the DO's life, since the DO
177
+ * is keyed 1:1 by `identity.key`.
178
+ *
179
+ * `onMessagesDisplaced` is the whole integration for anything that wants the
180
+ * messages a compaction folds away: core performs the compaction, so core
181
+ * announces the loss, and the runtime fans it out to every plugin that asked.
182
+ */
183
+ getSession(identity) {
184
+ this.identityKey ??= identity.key ?? undefined;
185
+ const { session, model } = this.config;
186
+ return (this.session ??= buildAgentSession(this, this.modelPair().primary(), {
187
+ soul: () => this.agentSoul(this.runtime.renderCapabilities()),
188
+ memoryDescription: session.memoryDescription,
189
+ memoryMaxTokens: session.memoryMaxTokens,
190
+ compactAfterTokens: session.compactAfterTokens,
191
+ compactTailTokens: session.compactTailTokens,
192
+ maxOutputTokens: model.maxOutputTokens,
193
+ onMessagesDisplaced: this.runtime.onMessagesDisplaced
194
+ }));
195
+ }
196
+ /** The caller key, which is present on every path that can reach a plugin. */
197
+ requireIdentityKey() {
198
+ if (!this.identityKey) {
199
+ throw new Error("identity.key is required for per-caller isolation");
200
+ }
201
+ return this.identityKey;
202
+ }
203
+ /** The gateway callback channel for one turn. See {@link PushChannel}. */
204
+ push(context) {
205
+ return createPushChannel(this.env.A2A_SIGNING_KEY, context);
206
+ }
207
+ /**
208
+ * The per-request system-prompt suffix describing the verified caller.
209
+ *
210
+ * A rendering of a protocol fact rather than prompt copy, so core supplies one
211
+ * — see {@link callerContext}. Override it to name what a workspace id means in
212
+ * your deployment; do not use it to say who the *user* is, which this is not.
213
+ */
214
+ callerContext(identity) {
215
+ return callerContext(identity);
216
+ }
217
+ // --- Async task state (accept + notify) ----------------------------------
218
+ //
219
+ // A thin RPC surface over `AgentDB`'s `tasks` table. Native RPC methods — the
220
+ // DO is never a network-reachable server — called by the Workflow, which
221
+ // cannot touch this SQLite directly.
222
+ //
223
+ // The Task-returning methods return `PlainTask`: the SDK `Task` narrowed to
224
+ // what survives Cloudflare's RPC types. Returning the raw SDK `Task` breaks
225
+ // the generated DO-stub types (under v1.0 it blows past TypeScript's
226
+ // instantiation-depth limit).
227
+ async beginTask(input) {
228
+ return this.db.tasks.begin(input);
229
+ }
230
+ async getTask(taskId) {
231
+ return this.db.tasks.get(taskId);
232
+ }
233
+ async listTasks(query) {
234
+ return this.db.tasks.list(query);
235
+ }
236
+ /**
237
+ * Persist a Task, returning **whether the guarded write applied**.
238
+ *
239
+ * That boolean is the cancellation check, and a caller must key its callback on
240
+ * it: `AgentDB` refuses to write a terminal state over a `canceled` row and
241
+ * does that read and write in one synchronous pass inside the DO. Probing with
242
+ * {@link getTask} first and saving second leaves a window — between the two
243
+ * calls, and again between the save and the notify — in which a cancel lands
244
+ * and the gateway still receives a `completed` callback.
245
+ *
246
+ * A `canceled` state routes to {@link markCanceled} instead of a plain write,
247
+ * so a `tasks/cancel` arriving through the a2a-js TaskStore and one arriving
248
+ * through {@link cancelTask} converge on the same interruption path.
249
+ */
250
+ async saveTask(task) {
251
+ if (stateOf(task) === TaskState.TASK_STATE_CANCELED) {
252
+ return (await this.markCanceled(task.id, task)) !== null;
253
+ }
254
+ return this.db.tasks.save(task);
255
+ }
256
+ /**
257
+ * Move the Task to `working`. Returns `"canceled"` when the caller cancelled
258
+ * first — read it and stop, rather than probing with a separate
259
+ * {@link getTask}, which reopens the gap between asking and acting.
260
+ *
261
+ * Anything else is `"ok"`, including an unknown row and a row already `working`
262
+ * (a replayed step): only an actual cancellation stops the pipeline.
263
+ */
264
+ async markWorking(taskId) {
265
+ return this.db.tasks.markWorking(taskId);
266
+ }
267
+ async cancelTask(taskId) {
268
+ return this.markCanceled(taskId);
269
+ }
270
+ /**
271
+ * The one place a Task becomes canceled: flip the row — terminal, so every
272
+ * non-canceled write is refused afterwards — then interrupt whatever is still
273
+ * running for it.
274
+ *
275
+ * `task` is supplied when the caller already built the canceled Task (the
276
+ * a2a-js cancel branch attaches its own status message); otherwise the row's
277
+ * own guarded flip produces it. Both paths are guarded against the same race:
278
+ * a task that already reached `completed`/`failed` refuses the write, and its
279
+ * verdict — not a `get` read straight after, which would return that
280
+ * unchanged terminal row and be mistaken for a successful cancellation — is
281
+ * what decides whether {@link onTaskCanceled} runs at all.
282
+ */
283
+ async markCanceled(taskId, task) {
284
+ const canceled = task
285
+ ? this.db.tasks.save(task) && this.db.tasks.get(taskId)
286
+ : this.db.tasks.cancel(taskId);
287
+ if (!canceled)
288
+ return null;
289
+ await this.onTaskCanceled(taskId);
290
+ return canceled;
291
+ }
292
+ /**
293
+ * Interrupt work still in flight for a task that has just been canceled.
294
+ *
295
+ * Default: nothing, which is right for an agent whose turn is a single
296
+ * inference — the row is terminal and the next guarded write refuses. An agent
297
+ * with children overrides this to abort them.
298
+ *
299
+ * **Must be best-effort.** Cancellation has already been recorded by the time
300
+ * this runs, and it must not fail because cleanup did.
301
+ */
302
+ async onTaskCanceled(_taskId) { }
303
+ }
304
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/host/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAiB,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAa,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAqB,MAAM,qBAAqB,CAAC;AAE5E,OAAO,EACL,aAAa,EAGd,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAElD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGjD,OAAO,EACL,iBAAiB,EAGlB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAoB,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EACL,kBAAkB,EAInB,MAAM,mBAAmB,CAAC;AAG3B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,MAAM,OAAgB,YAIpB,SAAQ,KAAW;IACX,OAAO,CAAe;IACtB,QAAQ,CAAgB;IACxB,OAAO,CAAgB;IACvB,KAAK,CAAa;IAClB,GAAG,CAAW;IAEtB;;;;;;OAMG;IACK,WAAW,CAAU;IAE7B;;;;OAIG;IACH,cAAc,CAAa;IAgB3B,6EAA6E;IAE7E;;;;;;;;OAQG;IACH,IAAc,OAAO;QACnB,OAAO,CAAC,IAAI,CAAC,QAAQ,KAAK,kBAAkB,CAAC;YAC3C,MAAM,EAAE,IAAI,CAAC,WAAW,EAAE;YAC1B,OAAO,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YAC7C,0EAA0E;YAC1E,yEAAyE;YACzE,+CAA+C;YAC/C,GAAG,EAAE,IAAI,CAAC,GAAG;SACd,CAAC,CAAC,CAAC;IACN,CAAC;IAED,2BAA2B;IAC3B,IAAc,MAAM;QAClB,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC;IAC7B,CAAC;IAED,+EAA+E;IAC/E,IAAc,EAAE;QACd,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE;YACjD,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,WAAW;YACpC,yEAAyE;YACzE,0EAA0E;YAC1E,2DAA2D;YAC3D,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM;SAC5B,CAAC,CAAC,CAAC;IACN,CAAC;IAED,+EAA+E;IAC/E,IAAc,MAAM;QAClB,OAAO,CAAC,IAAI,CAAC,OAAO,KAAK,kBAAkB,CAAC;YAC1C,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE;YACf,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,KAAK;SAC1B,CAAC,CAAC,CAAC;IACN,CAAC;IAED;;;;OAIG;IACO,UAAU;QAClB,MAAM,KAAK,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;QACtC,OAAO;YACL,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO;YACzB,4CAA4C;YAC5C,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE;YAC1C,WAAW,EAAE,KAAK,CAAC,WAAW;SAC/B,CAAC;IACJ,CAAC;IAED;;;;;;;;OAQG;IACK,gBAAgB;QACtB,OAAO,aAAa,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,OAAO;QACX,2EAA2E;QAC3E,+EAA+E;QAC/E,MAAM,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC;QAC5B,4EAA4E;QAC5E,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAC5D,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,iBAAiB,CAAC,EAAE,CAAC;YAC5D,MAAM,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,iBAAiB,EAAE,EAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,eAAe,CACnB,QAA+B,EAC/B,SAAmB;QAEnB,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;QACxB,IAAI,CAAC,iBAAiB,EAAE,CAAC;IAC3B,CAAC;IAED,6EAA6E;IACnE,iBAAiB,KAAU,CAAC;IAEtC;;;;;OAKG;IACO,SAAS,CAAC,QAA0B;QAC5C,IAAI,IAAI,CAAC,cAAc;YAAE,OAAO,IAAI,CAAC,cAAc,CAAC;QACpD,IAAI,CAAC,QAAQ;YAAE,OAAO,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,MAAM,CAAC,eAAe,EAAE,CAAC,CAAC;QACrE,OAAO,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;IACnD,CAAC;IAED;;;;;;;;OAQG;IACH,UAAU,CAAC,QAAyB;QAClC,IAAI,CAAC,WAAW,KAAK,QAAQ,CAAC,GAAG,IAAI,SAAS,CAAC;QAC/C,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,OAAO,KAAK,iBAAiB,CACxC,IAAI,EACJ,IAAI,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,EAC1B;YACE,IAAI,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC;YAC7D,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;YAC5C,eAAe,EAAE,OAAO,CAAC,eAAe;YACxC,kBAAkB,EAAE,OAAO,CAAC,kBAAkB;YAC9C,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;YAC5C,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,mBAAmB,EAAE,IAAI,CAAC,OAAO,CAAC,mBAAmB;SACtD,CACF,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IACpE,kBAAkB;QAC1B,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACvE,CAAC;QACD,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,0EAA0E;IAChE,IAAI,CAAC,OAAwB;QACrC,OAAO,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;IAC9D,CAAC;IAED;;;;;;OAMG;IACO,aAAa,CAAC,QAAyB;QAC/C,OAAO,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjC,CAAC;IAED,4EAA4E;IAC5E,EAAE;IACF,8EAA8E;IAC9E,yEAAyE;IACzE,qCAAqC;IACrC,EAAE;IACF,4EAA4E;IAC5E,4EAA4E;IAC5E,qEAAqE;IACrE,8BAA8B;IAE9B,KAAK,CAAC,SAAS,CAAC,KAIf;QACC,OAAO,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,MAAc;QAC1B,OAAO,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,SAAS,CACb,KAAoB;QAEpB,OAAO,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,KAAK,CAAC,QAAQ,CAAC,IAAU;QACvB,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC,mBAAmB,EAAE,CAAC;YACpD,OAAO,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC;QAC3D,CAAC;QACD,OAAO,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,WAAW,CAAC,MAAc;QAC9B,OAAO,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,MAAc;QAC7B,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;IAED;;;;;;;;;;;;OAYG;IACK,KAAK,CAAC,YAAY,CACxB,MAAc,EACd,IAAW;QAEX,MAAM,QAAQ,GAAG,IAAI;YACnB,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;YACvD,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC3B,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QAClC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED;;;;;;;;;OASG;IACO,KAAK,CAAC,cAAc,CAAC,OAAe,IAAkB,CAAC;CAClE"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * `@loopingai/core/host` — the Durable Object half of an agent.
3
+ *
4
+ * {@link LoopingAgent} is the DO body every Looping agent has whatever loop it
5
+ * runs: the runtime and database built once per instance, the one continuous
6
+ * Session per verified caller, the gateway callback channel, and the task
7
+ * lifecycle RPC surface a Workflow drives. {@link PluginHost} is what it hands
8
+ * the plugins.
9
+ *
10
+ * Its own subpath rather than part of `/agent` on purpose. `/agent` is the
11
+ * primitives a *loop* is built from — session, model pair, budget, control tools
12
+ * — and a loop module should be able to import those without pulling a Durable
13
+ * Object base class and drizzle into its graph.
14
+ *
15
+ * Core still ships **no loop and no prompt copy**. Nothing here decides how a
16
+ * turn is shaped or what ends it; see `@loopingai/core/round` for the delegating
17
+ * round loop, which is opt-in and takes its prompt copy from the agent.
18
+ */
19
+ export { LoopingAgent } from "./agent.js";
20
+ export type { PluginHost } from "./plugin-host.js";
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/host/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `@loopingai/core/host` — the Durable Object half of an agent.
3
+ *
4
+ * {@link LoopingAgent} is the DO body every Looping agent has whatever loop it
5
+ * runs: the runtime and database built once per instance, the one continuous
6
+ * Session per verified caller, the gateway callback channel, and the task
7
+ * lifecycle RPC surface a Workflow drives. {@link PluginHost} is what it hands
8
+ * the plugins.
9
+ *
10
+ * Its own subpath rather than part of `/agent` on purpose. `/agent` is the
11
+ * primitives a *loop* is built from — session, model pair, budget, control tools
12
+ * — and a loop module should be able to import those without pulling a Durable
13
+ * Object base class and drizzle into its graph.
14
+ *
15
+ * Core still ships **no loop and no prompt copy**. Nothing here decides how a
16
+ * turn is shaped or what ends it; see `@loopingai/core/round` for the delegating
17
+ * round loop, which is opt-in and takes its prompt copy from the agent.
18
+ */
19
+ export { LoopingAgent } from "./agent.js";
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/host/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Everything a plugin may need from its host, resolved once per Durable Object
3
+ * instance.
4
+ *
5
+ * This is **contract, not application code**, which is why it lives in core. A
6
+ * published plugin's config is written against this shape — `arcAgi` takes
7
+ * `storage`, `recall` takes `callerKey` — so a plugin that could not name the
8
+ * type was writing its signature against a structural guess at an interface
9
+ * declared in an app it has never seen.
10
+ *
11
+ * Not just `env`, and each field is load-bearing:
12
+ *
13
+ * - `env` — bindings and secrets. Typed `object` for the same reason
14
+ * {@link file://../runtime/index.ts CreateAgentRuntimeOptions.env} is: `Env` is
15
+ * the ambient interface `wrangler types` generates into a consumer's
16
+ * `worker-configuration.d.ts`, it has no index signature, and requiring a cast
17
+ * to pass one's own `this.env` is how a seam goes unused. A host narrows it.
18
+ * - `storage` — a plugin that owns tables needs the DO's storage to build a query
19
+ * handle over. `this.ctx.storage`.
20
+ * - `callerKey` — **a thunk, deliberately.** It derives from the verified
21
+ * caller's identity, which does not exist yet when `plugins()` runs at DO
22
+ * start. The DO is keyed 1:1 by that caller, so the value is constant once
23
+ * known; a thunk is what lets the host supply it late while every hook reads
24
+ * the same one. On a subagent facet there is no caller at all, and the honest
25
+ * encoding is a thunk that throws.
26
+ * - `aiGatewayId` — the **resolved** AI Gateway slug, so a plugin making its own
27
+ * model calls is correlated with the agent's. Resolved, not read off the
28
+ * overrides object: reaching into `MY_CONFIG.model?.aiGatewayId` at a call site
29
+ * silently yields `undefined` the moment that override is dropped in favour of
30
+ * core's baseline, and the plugin's calls quietly stop being correlated.
31
+ *
32
+ * Deliberately **not** here: the agent's model pair. See the note on the
33
+ * interface below.
34
+ */
35
+ export interface PluginHost<TEnv extends object = object> {
36
+ env: TEnv;
37
+ storage: DurableObjectStorage;
38
+ /** The verified caller. A thunk — it does not exist when `onStart` runs. */
39
+ callerKey: () => string;
40
+ /** `config.model.aiGatewayId`, already resolved over core's baseline. */
41
+ aiGatewayId: string;
42
+ }
43
+ //# sourceMappingURL=plugin-host.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-host.d.ts","sourceRoot":"","sources":["../../src/host/plugin-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,WAAW,UAAU,CAAC,IAAI,SAAS,MAAM,GAAG,MAAM;IACtD,GAAG,EAAE,IAAI,CAAC;IACV,OAAO,EAAE,oBAAoB,CAAC;IAC9B,4EAA4E;IAC5E,SAAS,EAAE,MAAM,MAAM,CAAC;IACxB,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;CAmBrB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=plugin-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin-host.js","sourceRoot":"","sources":["../../src/host/plugin-host.ts"],"names":[],"mappings":""}