@dynamicagents/core 0.9.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 (168) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +522 -0
  3. package/dist/a2a/agent-stub.d.ts +60 -0
  4. package/dist/a2a/agent-stub.js +1 -0
  5. package/dist/a2a/caller-token.d.ts +44 -0
  6. package/dist/a2a/caller-token.js +61 -0
  7. package/dist/a2a/caller.d.ts +23 -0
  8. package/dist/a2a/caller.js +33 -0
  9. package/dist/a2a/card.d.ts +158 -0
  10. package/dist/a2a/card.js +163 -0
  11. package/dist/a2a/context.d.ts +19 -0
  12. package/dist/a2a/context.js +61 -0
  13. package/dist/a2a/deliver.d.ts +159 -0
  14. package/dist/a2a/deliver.js +185 -0
  15. package/dist/a2a/executor.d.ts +84 -0
  16. package/dist/a2a/executor.js +106 -0
  17. package/dist/a2a/index.d.ts +35 -0
  18. package/dist/a2a/index.js +34 -0
  19. package/dist/a2a/notify.d.ts +106 -0
  20. package/dist/a2a/notify.js +180 -0
  21. package/dist/a2a/parts.d.ts +45 -0
  22. package/dist/a2a/parts.js +94 -0
  23. package/dist/a2a/push.d.ts +70 -0
  24. package/dist/a2a/push.js +53 -0
  25. package/dist/a2a/self-origin.d.ts +91 -0
  26. package/dist/a2a/self-origin.js +114 -0
  27. package/dist/a2a/task-store.d.ts +44 -0
  28. package/dist/a2a/task-store.js +99 -0
  29. package/dist/a2a/task.d.ts +85 -0
  30. package/dist/a2a/task.js +15 -0
  31. package/dist/a2a/verify.d.ts +80 -0
  32. package/dist/a2a/verify.js +143 -0
  33. package/dist/agent/budget.d.ts +46 -0
  34. package/dist/agent/budget.js +42 -0
  35. package/dist/agent/control.d.ts +109 -0
  36. package/dist/agent/control.js +115 -0
  37. package/dist/agent/errors.d.ts +85 -0
  38. package/dist/agent/errors.js +64 -0
  39. package/dist/agent/final-reply.d.ts +49 -0
  40. package/dist/agent/final-reply.js +68 -0
  41. package/dist/agent/history.d.ts +97 -0
  42. package/dist/agent/history.js +133 -0
  43. package/dist/agent/index.d.ts +29 -0
  44. package/dist/agent/index.js +29 -0
  45. package/dist/agent/inference.d.ts +110 -0
  46. package/dist/agent/inference.js +120 -0
  47. package/dist/agent/model.d.ts +90 -0
  48. package/dist/agent/model.js +1 -0
  49. package/dist/agent/session.d.ts +100 -0
  50. package/dist/agent/session.js +82 -0
  51. package/dist/agent/workers-ai/index.d.ts +23 -0
  52. package/dist/agent/workers-ai/index.js +23 -0
  53. package/dist/agent/workers-ai/runtime.d.ts +42 -0
  54. package/dist/agent/workers-ai/runtime.js +63 -0
  55. package/dist/alarm/index.d.ts +77 -0
  56. package/dist/alarm/index.js +116 -0
  57. package/dist/config.d.ts +202 -0
  58. package/dist/config.js +135 -0
  59. package/dist/contract/index.d.ts +9 -0
  60. package/dist/contract/index.js +8 -0
  61. package/dist/contract/plugin.d.ts +324 -0
  62. package/dist/contract/plugin.js +114 -0
  63. package/dist/contract/recipe.d.ts +180 -0
  64. package/dist/contract/recipe.js +1 -0
  65. package/dist/contract/validation.d.ts +91 -0
  66. package/dist/contract/validation.js +84 -0
  67. package/dist/db/db.d.ts +147 -0
  68. package/dist/db/db.js +90 -0
  69. package/dist/db/index.d.ts +8 -0
  70. package/dist/db/index.js +8 -0
  71. package/dist/db/migrations/index.d.ts +20 -0
  72. package/dist/db/migrations/index.js +60 -0
  73. package/dist/db/models/subtasks.d.ts +100 -0
  74. package/dist/db/models/subtasks.js +241 -0
  75. package/dist/db/models/tasks.d.ts +118 -0
  76. package/dist/db/models/tasks.js +274 -0
  77. package/dist/db/schema.d.ts +468 -0
  78. package/dist/db/schema.js +88 -0
  79. package/dist/env.d.ts +53 -0
  80. package/dist/env.js +47 -0
  81. package/dist/host/agent.d.ts +305 -0
  82. package/dist/host/agent.js +400 -0
  83. package/dist/host/index.d.ts +20 -0
  84. package/dist/host/index.js +19 -0
  85. package/dist/host/plugin-host.d.ts +42 -0
  86. package/dist/host/plugin-host.js +1 -0
  87. package/dist/index.d.ts +19 -0
  88. package/dist/index.js +17 -0
  89. package/dist/job/index.d.ts +21 -0
  90. package/dist/job/index.js +21 -0
  91. package/dist/job/lifecycle.d.ts +176 -0
  92. package/dist/job/lifecycle.js +230 -0
  93. package/dist/job/state.d.ts +92 -0
  94. package/dist/job/state.js +40 -0
  95. package/dist/platform.d.ts +138 -0
  96. package/dist/platform.js +140 -0
  97. package/dist/round/agent.d.ts +271 -0
  98. package/dist/round/agent.js +678 -0
  99. package/dist/round/index.d.ts +25 -0
  100. package/dist/round/index.js +23 -0
  101. package/dist/round/policy.d.ts +98 -0
  102. package/dist/round/policy.js +1 -0
  103. package/dist/round/subagent.d.ts +87 -0
  104. package/dist/round/subagent.js +108 -0
  105. package/dist/round/turn.d.ts +249 -0
  106. package/dist/round/turn.js +564 -0
  107. package/dist/round/workflow.d.ts +147 -0
  108. package/dist/round/workflow.js +387 -0
  109. package/dist/runtime/index.d.ts +127 -0
  110. package/dist/runtime/index.js +186 -0
  111. package/dist/runtime/tool-families.d.ts +32 -0
  112. package/dist/runtime/tool-families.js +61 -0
  113. package/dist/subagent/fingerprint.d.ts +37 -0
  114. package/dist/subagent/fingerprint.js +92 -0
  115. package/dist/subagent/index.d.ts +169 -0
  116. package/dist/subagent/index.js +330 -0
  117. package/dist/subagent/prompt.d.ts +32 -0
  118. package/dist/subagent/prompt.js +37 -0
  119. package/dist/subagent/run.d.ts +157 -0
  120. package/dist/subagent/run.js +540 -0
  121. package/dist/subagent/workspace.d.ts +85 -0
  122. package/dist/subagent/workspace.js +127 -0
  123. package/dist/subtasks/catalog.d.ts +40 -0
  124. package/dist/subtasks/catalog.js +36 -0
  125. package/dist/subtasks/decomposition.d.ts +85 -0
  126. package/dist/subtasks/decomposition.js +156 -0
  127. package/dist/subtasks/delegate.d.ts +120 -0
  128. package/dist/subtasks/delegate.js +131 -0
  129. package/dist/subtasks/index.d.ts +9 -0
  130. package/dist/subtasks/index.js +9 -0
  131. package/dist/subtasks/subtask-types.d.ts +91 -0
  132. package/dist/subtasks/subtask-types.js +103 -0
  133. package/dist/subtasks/types.d.ts +295 -0
  134. package/dist/subtasks/types.js +15 -0
  135. package/dist/testing/auth.d.ts +34 -0
  136. package/dist/testing/auth.js +35 -0
  137. package/dist/testing/do.d.ts +29 -0
  138. package/dist/testing/do.js +25 -0
  139. package/dist/testing/fake-session.d.ts +26 -0
  140. package/dist/testing/fake-session.js +37 -0
  141. package/dist/testing/fixtures.d.ts +64 -0
  142. package/dist/testing/fixtures.js +104 -0
  143. package/dist/testing/harness.d.ts +97 -0
  144. package/dist/testing/harness.js +138 -0
  145. package/dist/testing/index.d.ts +31 -0
  146. package/dist/testing/index.js +35 -0
  147. package/dist/testing/mock-model.d.ts +77 -0
  148. package/dist/testing/mock-model.js +136 -0
  149. package/dist/testing/node.d.ts +56 -0
  150. package/dist/testing/node.js +56 -0
  151. package/dist/testing/vcr-global-setup.d.ts +12 -0
  152. package/dist/testing/vcr-global-setup.js +15 -0
  153. package/dist/testing/vcr-shared.d.ts +38 -0
  154. package/dist/testing/vcr-shared.js +33 -0
  155. package/dist/testing/vcr-spec.d.ts +25 -0
  156. package/dist/testing/vcr-spec.js +124 -0
  157. package/dist/testing/vcr-store.d.ts +86 -0
  158. package/dist/testing/vcr-store.js +191 -0
  159. package/dist/testing/vcr.d.ts +117 -0
  160. package/dist/testing/vcr.js +275 -0
  161. package/dist/worker/define-agent.d.ts +123 -0
  162. package/dist/worker/define-agent.js +20 -0
  163. package/dist/worker/index.d.ts +218 -0
  164. package/dist/worker/index.js +369 -0
  165. package/eslint-rules/index.js +31 -0
  166. package/eslint-rules/no-deprecated-object-properties.js +81 -0
  167. package/package.json +178 -0
  168. package/scripts/generate-keys.mjs +48 -0
@@ -0,0 +1,274 @@
1
+ import { Task, TaskState } from "@a2a-js/sdk";
2
+ import { and, count, desc, eq, gte, lt } from "drizzle-orm";
3
+ import { taskStateLabel } from "../../a2a/task.js";
4
+ import { buildSubmittedTask } from "../../a2a/notify.js";
5
+ import { notifyTasks } from "../schema.js";
6
+ function nowIso() {
7
+ return new Date().toISOString();
8
+ }
9
+ /**
10
+ * A task's state, tolerating the `status`-less task the SDK's generated type
11
+ * permits (`TaskStatus | undefined`). Nothing we build omits it, so an
12
+ * unspecified state means the row came from somewhere unexpected — and it
13
+ * compares equal to none of the states the callers switch on.
14
+ */
15
+ export function stateOf(task) {
16
+ return task.status?.state ?? TaskState.TASK_STATE_UNSPECIFIED;
17
+ }
18
+ /**
19
+ * The states a task never leaves.
20
+ *
21
+ * `INPUT_REQUIRED` and `AUTH_REQUIRED` are deliberately absent: a turn parked on
22
+ * either is waiting, not finished, and will move again.
23
+ */
24
+ const TERMINAL_STATES = new Set([
25
+ TaskState.TASK_STATE_COMPLETED,
26
+ TaskState.TASK_STATE_FAILED,
27
+ TaskState.TASK_STATE_CANCELED,
28
+ TaskState.TASK_STATE_REJECTED
29
+ ]);
30
+ function isTerminal(state) {
31
+ return TERMINAL_STATES.has(state);
32
+ }
33
+ /**
34
+ * Query methods for the `notify_tasks` table (async A2A task state).
35
+ *
36
+ * Bound to a drizzle handle by {@link AgentDB} and reached as `db.tasks.*`.
37
+ * Migrations are owned by `AgentDB`, not this factory — it only issues queries.
38
+ *
39
+ * Rows hold the task in its **A2A wire form** (`Task.toJSON`), not the in-memory
40
+ * protobuf shape the SDK hands us. Those differ under v1.0 — enums are numbers
41
+ * in memory but `SCREAMING_SNAKE` strings on the wire, and `Part.content` is a
42
+ * `{ $case, value }` wrapper in memory but a bare named key on the wire — so a
43
+ * plain `JSON.stringify` would persist a shape that is neither valid A2A JSON
44
+ * nor stable across SDK versions. Encoding on write and decoding on read keeps
45
+ * the stored bytes the spec's own format, which is also exactly what
46
+ * `postNotification` puts on the wire.
47
+ */
48
+ export function makeTasks(db) {
49
+ // Every row was written by the builders in `a2a/notify`, which produce exactly
50
+ // the narrowed {@link PlainTask} shape, so the decode lands back on it.
51
+ const parse = (row) => Task.fromJSON(JSON.parse(row.taskJson));
52
+ const serialize = (task) => JSON.stringify(Task.toJSON(task));
53
+ const readOne = (taskId) => {
54
+ const row = db
55
+ .select()
56
+ .from(notifyTasks)
57
+ .where(eq(notifyTasks.taskId, taskId))
58
+ .get();
59
+ return row ? parse(row) : null;
60
+ };
61
+ const upsert = (task) => {
62
+ const now = Date.now();
63
+ // Denormalized so `ListTasks` can filter on it without decoding every blob.
64
+ const columns = {
65
+ contextId: task.contextId,
66
+ state: taskStateLabel(stateOf(task)),
67
+ taskJson: serialize(task),
68
+ updatedAt: now
69
+ };
70
+ db.insert(notifyTasks)
71
+ .values({ taskId: task.id, messageId: null, createdAt: now, ...columns })
72
+ .onConflictDoUpdate({ target: notifyTasks.taskId, set: columns })
73
+ .run();
74
+ };
75
+ return {
76
+ /**
77
+ * Accept a turn: return the `submitted` task for the given `messageId`,
78
+ * creating it on first sight. Idempotent on `messageId` (the gatekeeper's
79
+ * dedupe key, stable across dispatch retries).
80
+ */
81
+ begin(input) {
82
+ const existing = db
83
+ .select()
84
+ .from(notifyTasks)
85
+ .where(eq(notifyTasks.messageId, input.messageId))
86
+ .get();
87
+ if (existing)
88
+ return parse(existing);
89
+ const task = buildSubmittedTask(input.taskId, input.contextId);
90
+ const now = Date.now();
91
+ db.insert(notifyTasks)
92
+ .values({
93
+ taskId: task.id,
94
+ messageId: input.messageId,
95
+ contextId: task.contextId,
96
+ state: taskStateLabel(task.status.state),
97
+ taskJson: serialize(task),
98
+ createdAt: now,
99
+ updatedAt: now
100
+ })
101
+ .run();
102
+ return task;
103
+ },
104
+ /** Load a task by id (for `GetTask` via the Worker's `DurableTaskStore`). */
105
+ get(taskId) {
106
+ return readOne(taskId);
107
+ },
108
+ /**
109
+ * A page of this caller's tasks for `ListTasks`, newest first, with the
110
+ * total matching count so the store can decide whether a next page exists.
111
+ */
112
+ list(query) {
113
+ const filters = [];
114
+ if (query.contextId) {
115
+ filters.push(eq(notifyTasks.contextId, query.contextId));
116
+ }
117
+ if (query.state !== undefined) {
118
+ filters.push(eq(notifyTasks.state, taskStateLabel(query.state)));
119
+ }
120
+ if (query.updatedAfter !== undefined) {
121
+ filters.push(gte(notifyTasks.updatedAt, query.updatedAfter));
122
+ }
123
+ const where = filters.length > 0 ? and(...filters) : undefined;
124
+ const rows = db
125
+ .select()
126
+ .from(notifyTasks)
127
+ .where(where)
128
+ .orderBy(desc(notifyTasks.createdAt))
129
+ .limit(query.limit)
130
+ .offset(query.offset)
131
+ .all();
132
+ const total = db
133
+ .select({ value: count() })
134
+ .from(notifyTasks)
135
+ .where(where)
136
+ .get();
137
+ return {
138
+ tasks: rows.map((row) => project(parse(row), query)),
139
+ totalSize: total?.value ?? 0
140
+ };
141
+ },
142
+ /**
143
+ * Upsert a task by id, preserving the `message_id` set by {@link begin}.
144
+ * Returns whether the write applied.
145
+ *
146
+ * Guarded exactly like {@link markWorking}, and for the same reason: a
147
+ * `canceled` row is terminal, so nothing may write a non-canceled state over
148
+ * it. That closes the window between a workflow's terminal build and its
149
+ * callback — the read-check-write is synchronous here, so a `CancelTask`
150
+ * landing mid-delivery makes this return `false` and the notify never fires.
151
+ *
152
+ * The reverse direction is guarded too: writing `canceled` over an already
153
+ * `completed`/`failed` row is refused, mirroring {@link cancel}'s own source
154
+ * guard. Without it, a cancellation landing between the Workflow's `complete`
155
+ * and `notify` steps — separate, independently-retried steps — could flip
156
+ * storage to canceled while `deliver()` still posts the cached completed
157
+ * task it already built, the exact race this guard exists to close. Writing
158
+ * `canceled` onto a `submitted`/`working` row, or re-writing it onto an
159
+ * already-`canceled` one, stays allowed: that is how the a2a-js handler's own
160
+ * cancel branch records the cancellation.
161
+ *
162
+ * **And no terminal row may be replaced by a *different* terminal state.**
163
+ * The two rules above were written about cancellation and between them left
164
+ * `completed → failed` wide open, which is not hypothetical: a workflow whose
165
+ * `notify` step exhausts its retries throws *after* `complete` durably saved
166
+ * a completed Task, and an abandoned-task recovery above it would then write
167
+ * a generic failure over a real answer and post a callback contradicting it.
168
+ * A turn that succeeded would be recorded as having failed because a webhook
169
+ * was flaky.
170
+ *
171
+ * Same terminal state re-written is still allowed, and must be: a Workflow
172
+ * replay legitimately re-runs `complete` and saves what it already saved, and
173
+ * refusing that would suppress the callback that replay exists to send.
174
+ */
175
+ save(task) {
176
+ const existing = readOne(task.id);
177
+ if (existing === null) {
178
+ upsert(task);
179
+ return true;
180
+ }
181
+ const existingState = stateOf(existing);
182
+ const incomingState = stateOf(task);
183
+ if (existingState === TaskState.TASK_STATE_CANCELED &&
184
+ incomingState !== TaskState.TASK_STATE_CANCELED) {
185
+ return false;
186
+ }
187
+ if (incomingState === TaskState.TASK_STATE_CANCELED &&
188
+ existingState !== TaskState.TASK_STATE_SUBMITTED &&
189
+ existingState !== TaskState.TASK_STATE_WORKING &&
190
+ existingState !== TaskState.TASK_STATE_CANCELED) {
191
+ return false;
192
+ }
193
+ if (isTerminal(existingState) &&
194
+ isTerminal(incomingState) &&
195
+ incomingState !== existingState) {
196
+ return false;
197
+ }
198
+ upsert(task);
199
+ return true;
200
+ },
201
+ /**
202
+ * Move a task to `working` (a turn workflow's first step). Returns
203
+ * `"canceled"` when the row is already canceled, which is the caller's
204
+ * signal to stop the turn; every other outcome — unknown task, or a task
205
+ * already past `submitted` — is a no-op reported as `"ok"`, because a
206
+ * workflow replay legitimately re-runs this step.
207
+ */
208
+ markWorking(taskId) {
209
+ const task = readOne(taskId);
210
+ if (!task)
211
+ return "ok";
212
+ if (stateOf(task) === TaskState.TASK_STATE_CANCELED)
213
+ return "canceled";
214
+ if (stateOf(task) !== TaskState.TASK_STATE_SUBMITTED)
215
+ return "ok";
216
+ task.status = {
217
+ ...task.status,
218
+ state: TaskState.TASK_STATE_WORKING,
219
+ message: task.status?.message,
220
+ timestamp: nowIso()
221
+ };
222
+ upsert(task);
223
+ return "ok";
224
+ },
225
+ /**
226
+ * Flip the task to `canceled` and return it, or `null` if the row is not
227
+ * eligible — unknown, or already past `submitted`/`working`. Guarding the
228
+ * source state (not just the destination, as {@link save} does) matters
229
+ * because `complete`/`notify` are separate Workflow steps: without this, a
230
+ * cancellation landing between them would flip an already-`completed` or
231
+ * `failed` row to `canceled` right as `deliver()` posts the terminal
232
+ * callback it had already built, silently rewriting a delivered result.
233
+ * Terminal: once this lands, {@link save} refuses every non-canceled write,
234
+ * so no completed or failed callback can be built from this row afterwards.
235
+ */
236
+ cancel(taskId) {
237
+ const task = readOne(taskId);
238
+ if (!task)
239
+ return null;
240
+ const state = stateOf(task);
241
+ if (state !== TaskState.TASK_STATE_SUBMITTED &&
242
+ state !== TaskState.TASK_STATE_WORKING) {
243
+ return null;
244
+ }
245
+ task.status = {
246
+ ...task.status,
247
+ state: TaskState.TASK_STATE_CANCELED,
248
+ message: task.status?.message,
249
+ timestamp: nowIso()
250
+ };
251
+ upsert(task);
252
+ return task;
253
+ },
254
+ /** Delete all tasks older than 30 days (called by the maintenance cron). */
255
+ cleanup() {
256
+ const cutoff = Date.now() - 30 * 24 * 60 * 60 * 1000;
257
+ db.delete(notifyTasks).where(lt(notifyTasks.createdAt, cutoff)).run();
258
+ }
259
+ };
260
+ }
261
+ /**
262
+ * Apply the `ListTasks` response-shaping options to one stored task. Rows are
263
+ * JSON blobs, so the repeated fields are defaulted rather than assumed present.
264
+ */
265
+ function project(task, query) {
266
+ const history = task.history ?? [];
267
+ return {
268
+ ...task,
269
+ artifacts: query.includeArtifacts ? (task.artifacts ?? []) : [],
270
+ history: query.historyLength === undefined
271
+ ? history
272
+ : history.slice(Math.max(0, history.length - query.historyLength))
273
+ };
274
+ }