@loopingai/core 0.3.1 → 0.4.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 (100) 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/db/models/tasks.d.ts +19 -5
  35. package/dist/db/models/tasks.d.ts.map +1 -1
  36. package/dist/db/models/tasks.js +38 -8
  37. package/dist/db/models/tasks.js.map +1 -1
  38. package/dist/host/agent.d.ts +217 -0
  39. package/dist/host/agent.d.ts.map +1 -0
  40. package/dist/host/agent.js +306 -0
  41. package/dist/host/agent.js.map +1 -0
  42. package/dist/host/index.d.ts +21 -0
  43. package/dist/host/index.d.ts.map +1 -0
  44. package/dist/host/index.js +20 -0
  45. package/dist/host/index.js.map +1 -0
  46. package/dist/host/plugin-host.d.ts +47 -0
  47. package/dist/host/plugin-host.d.ts.map +1 -0
  48. package/dist/host/plugin-host.js +2 -0
  49. package/dist/host/plugin-host.js.map +1 -0
  50. package/dist/round/agent.d.ts +267 -0
  51. package/dist/round/agent.d.ts.map +1 -0
  52. package/dist/round/agent.js +691 -0
  53. package/dist/round/agent.js.map +1 -0
  54. package/dist/round/index.d.ts +25 -0
  55. package/dist/round/index.d.ts.map +1 -0
  56. package/dist/round/index.js +24 -0
  57. package/dist/round/index.js.map +1 -0
  58. package/dist/round/policy.d.ts +99 -0
  59. package/dist/round/policy.d.ts.map +1 -0
  60. package/dist/round/policy.js +2 -0
  61. package/dist/round/policy.js.map +1 -0
  62. package/dist/round/subagent.d.ts +67 -0
  63. package/dist/round/subagent.d.ts.map +1 -0
  64. package/dist/round/subagent.js +91 -0
  65. package/dist/round/subagent.js.map +1 -0
  66. package/dist/round/turn.d.ts +231 -0
  67. package/dist/round/turn.d.ts.map +1 -0
  68. package/dist/round/turn.js +497 -0
  69. package/dist/round/turn.js.map +1 -0
  70. package/dist/round/workflow.d.ts +109 -0
  71. package/dist/round/workflow.d.ts.map +1 -0
  72. package/dist/round/workflow.js +316 -0
  73. package/dist/round/workflow.js.map +1 -0
  74. package/dist/runtime/index.d.ts +5 -1
  75. package/dist/runtime/index.d.ts.map +1 -1
  76. package/dist/runtime/index.js.map +1 -1
  77. package/dist/testing/auth.d.ts.map +1 -1
  78. package/dist/testing/auth.js +10 -7
  79. package/dist/testing/auth.js.map +1 -1
  80. package/dist/testing/fixtures.d.ts +17 -0
  81. package/dist/testing/fixtures.d.ts.map +1 -1
  82. package/dist/testing/fixtures.js +17 -0
  83. package/dist/testing/fixtures.js.map +1 -1
  84. package/dist/testing/harness.d.ts +98 -0
  85. package/dist/testing/harness.d.ts.map +1 -0
  86. package/dist/testing/harness.js +139 -0
  87. package/dist/testing/harness.js.map +1 -0
  88. package/dist/testing/index.d.ts +2 -1
  89. package/dist/testing/index.d.ts.map +1 -1
  90. package/dist/testing/index.js +2 -1
  91. package/dist/testing/index.js.map +1 -1
  92. package/dist/worker/define-agent.d.ts +124 -0
  93. package/dist/worker/define-agent.d.ts.map +1 -0
  94. package/dist/worker/define-agent.js +21 -0
  95. package/dist/worker/define-agent.js.map +1 -0
  96. package/dist/worker/index.d.ts +29 -4
  97. package/dist/worker/index.d.ts.map +1 -1
  98. package/dist/worker/index.js +70 -12
  99. package/dist/worker/index.js.map +1 -1
  100. package/package.json +12 -3
@@ -0,0 +1,217 @@
1
+ import { Agent, type Schedule } from "agents";
2
+ import { type Task } from "@a2a-js/sdk";
3
+ import { type AgentRuntime } from "../runtime/index.js";
4
+ import type { AgentPlugin } from "../contract/plugin.js";
5
+ import { type CoreConfig, type CoreConfigOverrides } from "../config.js";
6
+ import type { A2ASecretsEnv, AiEnv } from "../env.js";
7
+ import { AgentDB } from "../db/index.js";
8
+ import type { GatewayIdentity } from "../a2a/verify.js";
9
+ import type { PlainTask } from "../a2a/task.js";
10
+ import type { TaskListQuery } from "../a2a/agent-stub.js";
11
+ import { type PushChannel, type TurnPushContext } from "../a2a/push.js";
12
+ import { type SessionLike } from "../agent/session.js";
13
+ import { type GatewayMetadata, type ModelPair, type ModelRuntime } from "../agent/model.js";
14
+ import type { PluginHost } from "./plugin-host.js";
15
+ /**
16
+ * The Durable Object body every Looping agent has, whatever loop it runs.
17
+ *
18
+ * ## Why this is core's and not the app's
19
+ *
20
+ * It was the app's, in the starter, and it was written twice — once for the
21
+ * delegating round agent, once for the single-turn proactive one. The two copies
22
+ * were identical for ~180 lines: the memoized runtime/db/models getters, the
23
+ * `onStart` that must await migrations before the SDK dispatches any RPC, the
24
+ * cron registration guard, the session with its displacement fan-out, the
25
+ * `identityKey` timing, and the task RPC surface.
26
+ *
27
+ * They did not stay identical. The second copy dropped `markWorking`'s
28
+ * cancellation verdict on the floor and probed with a separate `getTask` before
29
+ * writing a terminal Task — so a canceled task still burned a model call, and the
30
+ * gateway could still receive a `completed` callback for a task the caller had
31
+ * abandoned. Both are lifecycle invariants, both were documented in the first
32
+ * copy, and neither is visible to a type checker or a linter.
33
+ *
34
+ * That is the argument for this class. **None of it is policy.** How a turn is
35
+ * shaped, what ends it, what the model is told — all of that stays with the
36
+ * agent, and an agent that wants a different loop simply does not extend
37
+ * {@link file://../round/agent.ts RoundAgentBase}. What is here is the part where
38
+ * being different is only ever a bug.
39
+ *
40
+ * ## The three seams
41
+ *
42
+ * ```ts
43
+ * export class MyAgent extends LoopingAgent<Env> {
44
+ * protected agentConfig() { return MY_CONFIG; }
45
+ * protected agentPlugins(host: PluginHost<Env>) { return plugins(host); }
46
+ * protected agentSoul(capabilities: string) { return soulPrompt(capabilities); }
47
+ * }
48
+ * ```
49
+ *
50
+ * One Durable Object instance per verified caller (keyed by the gateway JWT's
51
+ * `identity.key`), each owning **one continuous Session** — durable history plus
52
+ * a self-edited `memory` block, backed by `this.sql`. All of a caller's turns, in
53
+ * any channel or thread, accumulate into that one conversation.
54
+ */
55
+ export declare abstract class LoopingAgent<TEnv extends Cloudflare.Env & AiEnv & A2ASecretsEnv = Cloudflare.Env & AiEnv & A2ASecretsEnv> extends Agent<TEnv> {
56
+ private session?;
57
+ private _runtime?;
58
+ private _models?;
59
+ private _pair?;
60
+ private _db?;
61
+ /**
62
+ * The verified caller this instance belongs to, set on the first turn.
63
+ *
64
+ * `onStart` runs before any request, so it is not known when `agentPlugins()`
65
+ * is built — which is why anything per-caller takes a thunk. The DO is keyed
66
+ * 1:1 by this value, so it is constant once set.
67
+ */
68
+ private identityKey?;
69
+ /**
70
+ * Test-only model injection. A **field**, not a constructor argument or an RPC
71
+ * parameter, so it never appears on the generated DO stub: production callers
72
+ * cannot reach it, and no model configuration crosses the RPC boundary.
73
+ */
74
+ modelsOverride?: ModelPair;
75
+ /** This agent's config overrides. Merged onto core's baseline once, at start. */
76
+ protected abstract agentConfig(): CoreConfigOverrides;
77
+ /** This agent's installed capabilities. Conventionally its `./plugins.ts`. */
78
+ protected abstract agentPlugins(host: PluginHost<TEnv>): AgentPlugin[];
79
+ /**
80
+ * This agent's identity, with the installed plugins' capability blocks already
81
+ * rendered in. Core ships no prompt copy — this is yours to write.
82
+ */
83
+ protected abstract agentSoul(capabilities: string): string;
84
+ /**
85
+ * Everything that would otherwise be a module-level constant, resolved once
86
+ * per DO instance from this agent's config and its installed plugins.
87
+ *
88
+ * Resolving a registry at *import* time is the one thing the package split
89
+ * exists to prevent: it freezes the registry before `env` exists (which on
90
+ * Workers is always), defeats tree-shaking, and makes per-agent plugin
91
+ * selection impossible.
92
+ */
93
+ protected get runtime(): AgentRuntime;
94
+ /** The resolved config. */
95
+ protected get config(): CoreConfig;
96
+ /** The agent's database (drizzle + migrations), built once per DO instance. */
97
+ protected get db(): AgentDB;
98
+ /** The model runtime for this instance, built lazily over the `AI` binding. */
99
+ protected get models(): ModelRuntime;
100
+ /**
101
+ * What this agent's plugins are handed. Built from
102
+ * {@link resolvedModelIds} rather than `this.config`, which would be a cycle —
103
+ * building the runtime is what needs these.
104
+ */
105
+ protected pluginHost(): PluginHost<TEnv>;
106
+ /**
107
+ * The model settings a locally-declared recipe runs on, resolved *before* the
108
+ * runtime exists.
109
+ *
110
+ * Deliberately not `this.config` — that would be a cycle. `resolveConfig` is
111
+ * cheap and pure and fills in core's defaults, so this is the same result the
112
+ * runtime lands on; that matters because a recipe's models are checked against
113
+ * `policy.modelAllowlist`, which is built from these very values.
114
+ */
115
+ private resolvedModelIds;
116
+ onStart(): Promise<void>;
117
+ /**
118
+ * Cron handler: delete task rows older than 30 days. Runs Sunday 01:00 UTC.
119
+ *
120
+ * A plugin's own tables are its business — core's journal does not reach them,
121
+ * and neither does this sweep. A subclass with more durable state of its own
122
+ * overrides {@link cleanupAgentState}.
123
+ */
124
+ cleanupOldTasks(_payload: Record<string, never>, _schedule: Schedule): Promise<void>;
125
+ /** Extra durable state to age out alongside the task rows. Default: none. */
126
+ protected cleanupAgentState(): void;
127
+ /**
128
+ * The main agent's primary/fallback pair. With `metadata` it builds a fresh
129
+ * pair carrying that AI Gateway correlation tag (so a gateway log ties the call
130
+ * to its task and round); without it — the Session's own compaction model — it
131
+ * reuses a memoized default. A test `modelsOverride` always wins.
132
+ */
133
+ protected modelPair(metadata?: GatewayMetadata): ModelPair;
134
+ /**
135
+ * The one continuous Session for this caller (rebuilt from `this.sql` after
136
+ * eviction). Memoized — `identity` is constant for the DO's life, since the DO
137
+ * is keyed 1:1 by `identity.key`.
138
+ *
139
+ * `onMessagesDisplaced` is the whole integration for anything that wants the
140
+ * messages a compaction folds away: core performs the compaction, so core
141
+ * announces the loss, and the runtime fans it out to every plugin that asked.
142
+ */
143
+ getSession(identity: GatewayIdentity): SessionLike;
144
+ /** The caller key, which is present on every path that can reach a plugin. */
145
+ protected requireIdentityKey(): string;
146
+ /** The gateway callback channel for one turn. See {@link PushChannel}. */
147
+ protected push(context: TurnPushContext): PushChannel;
148
+ /**
149
+ * The per-request system-prompt suffix describing the verified caller.
150
+ *
151
+ * A rendering of a protocol fact rather than prompt copy, so core supplies one
152
+ * — see {@link callerContext}. Override it to name what a workspace id means in
153
+ * your deployment; do not use it to say who the *user* is, which this is not.
154
+ */
155
+ protected callerContext(identity: GatewayIdentity): string;
156
+ beginTask(input: {
157
+ messageId: string;
158
+ taskId: string;
159
+ contextId: string;
160
+ }): Promise<PlainTask>;
161
+ getTask(taskId: string): Promise<PlainTask | null>;
162
+ listTasks(query: TaskListQuery): Promise<{
163
+ tasks: PlainTask[];
164
+ totalSize: number;
165
+ }>;
166
+ /**
167
+ * Persist a Task, returning **whether the guarded write applied**.
168
+ *
169
+ * That boolean is the cancellation check, and a caller must key its callback on
170
+ * it: `AgentDB` refuses to write a terminal state over a `canceled` row and
171
+ * does that read and write in one synchronous pass inside the DO. Probing with
172
+ * {@link getTask} first and saving second leaves a window — between the two
173
+ * calls, and again between the save and the notify — in which a cancel lands
174
+ * and the gateway still receives a `completed` callback.
175
+ *
176
+ * A `canceled` state routes to {@link markCanceled} instead of a plain write,
177
+ * so a `tasks/cancel` arriving through the a2a-js TaskStore and one arriving
178
+ * through {@link cancelTask} converge on the same interruption path.
179
+ */
180
+ saveTask(task: Task): Promise<boolean>;
181
+ /**
182
+ * Move the Task to `working`. Returns `"canceled"` when the caller cancelled
183
+ * first — read it and stop, rather than probing with a separate
184
+ * {@link getTask}, which reopens the gap between asking and acting.
185
+ *
186
+ * Anything else is `"ok"`, including an unknown row and a row already `working`
187
+ * (a replayed step): only an actual cancellation stops the pipeline.
188
+ */
189
+ markWorking(taskId: string): Promise<"ok" | "canceled">;
190
+ cancelTask(taskId: string): Promise<PlainTask | null>;
191
+ /**
192
+ * The one place a Task becomes canceled: flip the row — terminal, so every
193
+ * non-canceled write is refused afterwards — then interrupt whatever is still
194
+ * running for it.
195
+ *
196
+ * `task` is supplied when the caller already built the canceled Task (the
197
+ * a2a-js cancel branch attaches its own status message); otherwise the row's
198
+ * own guarded flip produces it. Both paths are guarded against the same race:
199
+ * a task that already reached `completed`/`failed` refuses the write, and its
200
+ * verdict — not a `get` read straight after, which would return that
201
+ * unchanged terminal row and be mistaken for a successful cancellation — is
202
+ * what decides whether {@link onTaskCanceled} runs at all.
203
+ */
204
+ private markCanceled;
205
+ /**
206
+ * Interrupt work still in flight for a task that has just been canceled.
207
+ *
208
+ * Default: nothing, which is right for an agent whose turn is a single
209
+ * inference — the row is terminal and the next guarded write refuses. An agent
210
+ * with children overrides this to abort them.
211
+ *
212
+ * **Must be best-effort.** Cancellation has already been recorded by the time
213
+ * this runs, and it must not fail because cleanup did.
214
+ */
215
+ protected onTaskCanceled(_taskId: string): Promise<void>;
216
+ }
217
+ //# sourceMappingURL=agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/host/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,mBAAmB,EACzB,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,OAAO,EAAW,MAAM,gBAAgB,CAAC;AAClD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAEL,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,8BAAsB,YAAY,CAChC,IAAI,SAAS,UAAU,CAAC,GAAG,GAAG,KAAK,GAAG,aAAa,GAAG,UAAU,CAAC,GAAG,GAClE,KAAK,GACL,aAAa,CACf,SAAQ,KAAK,CAAC,IAAI,CAAC;IACnB,OAAO,CAAC,OAAO,CAAC,CAAc;IAC9B,OAAO,CAAC,QAAQ,CAAC,CAAe;IAChC,OAAO,CAAC,OAAO,CAAC,CAAe;IAC/B,OAAO,CAAC,KAAK,CAAC,CAAY;IAC1B,OAAO,CAAC,GAAG,CAAC,CAAU;IAEtB;;;;;;OAMG;IACH,OAAO,CAAC,WAAW,CAAC,CAAS;IAE7B;;;;OAIG;IACH,cAAc,CAAC,EAAE,SAAS,CAAC;IAI3B,iFAAiF;IACjF,SAAS,CAAC,QAAQ,CAAC,WAAW,IAAI,mBAAmB;IAErD,8EAA8E;IAC9E,SAAS,CAAC,QAAQ,CAAC,YAAY,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,GAAG,WAAW,EAAE;IAEtE;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;IAI1D;;;;;;;;OAQG;IACH,SAAS,KAAK,OAAO,IAAI,YAAY,CASpC;IAED,2BAA2B;IAC3B,SAAS,KAAK,MAAM,IAAI,UAAU,CAEjC;IAED,+EAA+E;IAC/E,SAAS,KAAK,EAAE,IAAI,OAAO,CAQ1B;IAED,+EAA+E;IAC/E,SAAS,KAAK,MAAM,IAAI,YAAY,CAKnC;IAED;;;;OAIG;IACH,SAAS,CAAC,UAAU,IAAI,UAAU,CAAC,IAAI,CAAC;IAaxC;;;;;;;;OAQG;IACH,OAAO,CAAC,gBAAgB;IAIlB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAW9B;;;;;;OAMG;IACG,eAAe,CACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAC/B,SAAS,EAAE,QAAQ,GAClB,OAAO,CAAC,IAAI,CAAC;IAKhB,6EAA6E;IAC7E,SAAS,CAAC,iBAAiB,IAAI,IAAI;IAEnC;;;;;OAKG;IACH,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,eAAe,GAAG,SAAS;IAM1D;;;;;;;;OAQG;IACH,UAAU,CAAC,QAAQ,EAAE,eAAe,GAAG,WAAW;IAkBlD,8EAA8E;IAC9E,SAAS,CAAC,kBAAkB,IAAI,MAAM;IAOtC,0EAA0E;IAC1E,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,GAAG,WAAW;IAIrD;;;;;;OAMG;IACH,SAAS,CAAC,aAAa,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM;IAepD,SAAS,CAAC,KAAK,EAAE;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,SAAS,CAAC;IAIhB,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAIlD,SAAS,CACb,KAAK,EAAE,aAAa,GACnB,OAAO,CAAC;QAAE,KAAK,EAAE,SAAS,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAIrD;;;;;;;;;;;;;OAaG;IACG,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC;IAO5C;;;;;;;OAOG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,UAAU,CAAC;IAIvD,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC;IAI3D;;;;;;;;;;;;OAYG;YACW,YAAY;IAY1B;;;;;;;;;OASG;cACa,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAC/D"}
@@ -0,0 +1,306 @@
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
+ primaryModelId: model.chatModelId,
124
+ fallbackModelId: model.fallbackChatModelId,
125
+ aiGatewayId: model.aiGatewayId
126
+ };
127
+ }
128
+ /**
129
+ * The model settings a locally-declared recipe runs on, resolved *before* the
130
+ * runtime exists.
131
+ *
132
+ * Deliberately not `this.config` — that would be a cycle. `resolveConfig` is
133
+ * cheap and pure and fills in core's defaults, so this is the same result the
134
+ * runtime lands on; that matters because a recipe's models are checked against
135
+ * `policy.modelAllowlist`, which is built from these very values.
136
+ */
137
+ resolvedModelIds() {
138
+ return resolveConfig(this.agentConfig()).model;
139
+ }
140
+ async onStart() {
141
+ // Await migrations before the SDK dispatches any RPC — eliminates the race
142
+ // between schema creation and first query on cold start / hibernation wake-up.
143
+ await this.db.ensureReady();
144
+ // Register the weekly cleanup cron once per DO instance (idempotent guard).
145
+ const existing = await this.listSchedules({ type: "cron" });
146
+ if (!existing.some((s) => s.callback === "cleanupOldTasks")) {
147
+ await this.schedule("0 1 * * 0", "cleanupOldTasks", {});
148
+ }
149
+ }
150
+ /**
151
+ * Cron handler: delete task rows older than 30 days. Runs Sunday 01:00 UTC.
152
+ *
153
+ * A plugin's own tables are its business — core's journal does not reach them,
154
+ * and neither does this sweep. A subclass with more durable state of its own
155
+ * overrides {@link cleanupAgentState}.
156
+ */
157
+ async cleanupOldTasks(_payload, _schedule) {
158
+ this.db.tasks.cleanup();
159
+ this.cleanupAgentState();
160
+ }
161
+ /** Extra durable state to age out alongside the task rows. Default: none. */
162
+ cleanupAgentState() { }
163
+ /**
164
+ * The main agent's primary/fallback pair. With `metadata` it builds a fresh
165
+ * pair carrying that AI Gateway correlation tag (so a gateway log ties the call
166
+ * to its task and round); without it — the Session's own compaction model — it
167
+ * reuses a memoized default. A test `modelsOverride` always wins.
168
+ */
169
+ modelPair(metadata) {
170
+ if (this.modelsOverride)
171
+ return this.modelsOverride;
172
+ if (!metadata)
173
+ return (this._pair ??= this.models.createModelPair());
174
+ return this.models.createModelPair({ metadata });
175
+ }
176
+ /**
177
+ * The one continuous Session for this caller (rebuilt from `this.sql` after
178
+ * eviction). Memoized — `identity` is constant for the DO's life, since the DO
179
+ * is keyed 1:1 by `identity.key`.
180
+ *
181
+ * `onMessagesDisplaced` is the whole integration for anything that wants the
182
+ * messages a compaction folds away: core performs the compaction, so core
183
+ * announces the loss, and the runtime fans it out to every plugin that asked.
184
+ */
185
+ getSession(identity) {
186
+ this.identityKey ??= identity.key ?? undefined;
187
+ const { session, model } = this.config;
188
+ return (this.session ??= buildAgentSession(this, this.modelPair().primary(), {
189
+ soul: () => this.agentSoul(this.runtime.renderCapabilities()),
190
+ memoryDescription: session.memoryDescription,
191
+ memoryMaxTokens: session.memoryMaxTokens,
192
+ compactAfterTokens: session.compactAfterTokens,
193
+ compactTailTokens: session.compactTailTokens,
194
+ maxOutputTokens: model.maxOutputTokens,
195
+ onMessagesDisplaced: this.runtime.onMessagesDisplaced
196
+ }));
197
+ }
198
+ /** The caller key, which is present on every path that can reach a plugin. */
199
+ requireIdentityKey() {
200
+ if (!this.identityKey) {
201
+ throw new Error("identity.key is required for per-caller isolation");
202
+ }
203
+ return this.identityKey;
204
+ }
205
+ /** The gateway callback channel for one turn. See {@link PushChannel}. */
206
+ push(context) {
207
+ return createPushChannel(this.env.A2A_SIGNING_KEY, context);
208
+ }
209
+ /**
210
+ * The per-request system-prompt suffix describing the verified caller.
211
+ *
212
+ * A rendering of a protocol fact rather than prompt copy, so core supplies one
213
+ * — see {@link callerContext}. Override it to name what a workspace id means in
214
+ * your deployment; do not use it to say who the *user* is, which this is not.
215
+ */
216
+ callerContext(identity) {
217
+ return callerContext(identity);
218
+ }
219
+ // --- Async task state (accept + notify) ----------------------------------
220
+ //
221
+ // A thin RPC surface over `AgentDB`'s `tasks` table. Native RPC methods — the
222
+ // DO is never a network-reachable server — called by the Workflow, which
223
+ // cannot touch this SQLite directly.
224
+ //
225
+ // The Task-returning methods return `PlainTask`: the SDK `Task` narrowed to
226
+ // what survives Cloudflare's RPC types. Returning the raw SDK `Task` breaks
227
+ // the generated DO-stub types (under v1.0 it blows past TypeScript's
228
+ // instantiation-depth limit).
229
+ async beginTask(input) {
230
+ return this.db.tasks.begin(input);
231
+ }
232
+ async getTask(taskId) {
233
+ return this.db.tasks.get(taskId);
234
+ }
235
+ async listTasks(query) {
236
+ return this.db.tasks.list(query);
237
+ }
238
+ /**
239
+ * Persist a Task, returning **whether the guarded write applied**.
240
+ *
241
+ * That boolean is the cancellation check, and a caller must key its callback on
242
+ * it: `AgentDB` refuses to write a terminal state over a `canceled` row and
243
+ * does that read and write in one synchronous pass inside the DO. Probing with
244
+ * {@link getTask} first and saving second leaves a window — between the two
245
+ * calls, and again between the save and the notify — in which a cancel lands
246
+ * and the gateway still receives a `completed` callback.
247
+ *
248
+ * A `canceled` state routes to {@link markCanceled} instead of a plain write,
249
+ * so a `tasks/cancel` arriving through the a2a-js TaskStore and one arriving
250
+ * through {@link cancelTask} converge on the same interruption path.
251
+ */
252
+ async saveTask(task) {
253
+ if (stateOf(task) === TaskState.TASK_STATE_CANCELED) {
254
+ return (await this.markCanceled(task.id, task)) !== null;
255
+ }
256
+ return this.db.tasks.save(task);
257
+ }
258
+ /**
259
+ * Move the Task to `working`. Returns `"canceled"` when the caller cancelled
260
+ * first — read it and stop, rather than probing with a separate
261
+ * {@link getTask}, which reopens the gap between asking and acting.
262
+ *
263
+ * Anything else is `"ok"`, including an unknown row and a row already `working`
264
+ * (a replayed step): only an actual cancellation stops the pipeline.
265
+ */
266
+ async markWorking(taskId) {
267
+ return this.db.tasks.markWorking(taskId);
268
+ }
269
+ async cancelTask(taskId) {
270
+ return this.markCanceled(taskId);
271
+ }
272
+ /**
273
+ * The one place a Task becomes canceled: flip the row — terminal, so every
274
+ * non-canceled write is refused afterwards — then interrupt whatever is still
275
+ * running for it.
276
+ *
277
+ * `task` is supplied when the caller already built the canceled Task (the
278
+ * a2a-js cancel branch attaches its own status message); otherwise the row's
279
+ * own guarded flip produces it. Both paths are guarded against the same race:
280
+ * a task that already reached `completed`/`failed` refuses the write, and its
281
+ * verdict — not a `get` read straight after, which would return that
282
+ * unchanged terminal row and be mistaken for a successful cancellation — is
283
+ * what decides whether {@link onTaskCanceled} runs at all.
284
+ */
285
+ async markCanceled(taskId, task) {
286
+ const canceled = task
287
+ ? this.db.tasks.save(task) && this.db.tasks.get(taskId)
288
+ : this.db.tasks.cancel(taskId);
289
+ if (!canceled)
290
+ return null;
291
+ await this.onTaskCanceled(taskId);
292
+ return canceled;
293
+ }
294
+ /**
295
+ * Interrupt work still in flight for a task that has just been canceled.
296
+ *
297
+ * Default: nothing, which is right for an agent whose turn is a single
298
+ * inference — the row is terminal and the next guarded write refuses. An agent
299
+ * with children overrides this to abort them.
300
+ *
301
+ * **Must be best-effort.** Cancellation has already been recorded by the time
302
+ * this runs, and it must not fail because cleanup did.
303
+ */
304
+ async onTaskCanceled(_taskId) { }
305
+ }
306
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/host/agent.ts"],"names":[],"mappings":"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"}
@@ -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,47 @@
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
+ * - the model ids — what a locally-declared recipe runs on. A recipe may only
27
+ * name models in the host's allowlist, so a plugin that ships one is handed the
28
+ * host's pair rather than guessing.
29
+ * - `aiGatewayId` — the **resolved** AI Gateway slug, so a plugin making its own
30
+ * model calls is correlated with the agent's. Resolved, not read off the
31
+ * overrides object: reaching into `MY_CONFIG.model?.aiGatewayId` at a call site
32
+ * silently yields `undefined` the moment that override is dropped in favour of
33
+ * core's default, and the plugin's calls quietly stop being correlated.
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.chatModelId` — what a locally-declared recipe runs on. */
41
+ primaryModelId: string;
42
+ /** `config.model.fallbackChatModelId`. */
43
+ fallbackModelId: string;
44
+ /** `config.model.aiGatewayId`, already resolved over core's defaults. */
45
+ aiGatewayId: string;
46
+ }
47
+ //# 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,2EAA2E;IAC3E,cAAc,EAAE,MAAM,CAAC;IACvB,0CAA0C;IAC1C,eAAe,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;CACrB"}
@@ -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":""}