@adecore/agents 0.25.3 → 0.25.4

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.
package/dist/lineage.d.ts CHANGED
@@ -15,6 +15,9 @@ declare const LineageSchema: z.ZodObject<{
15
15
  }>>;
16
16
  createdAt: z.ZodNumber;
17
17
  endedAt: z.ZodOptional<z.ZodNumber>;
18
+ peer: z.ZodOptional<z.ZodObject<{
19
+ machineId: z.ZodString;
20
+ }, z.core.$strip>>;
18
21
  }, z.core.$strip>;
19
22
  type Lineage = z.infer<typeof LineageSchema>;
20
23
  export declare class AgentLineageStore {
@@ -33,6 +36,9 @@ export declare class AgentLineageStore {
33
36
  openedBy: string;
34
37
  }>;
35
38
  ceilingOf(nodeId: string): RuntimeMode | null;
39
+ peerOf(nodeId: string): {
40
+ machineId: string;
41
+ } | null;
36
42
  depthOf(nodeId: string): number;
37
43
  openedCount(callerId: string, include?: (nodeId: string) => boolean): number;
38
44
  madeBy(nodeId: string): string | null;
package/dist/lineage.js CHANGED
@@ -18,7 +18,9 @@ const LineageSchema = z.object({
18
18
  ceiling: RuntimeModeSchema.optional(),
19
19
  createdAt: z.number(),
20
20
  /* When stopping or deleting the node that opened it ended this one too; a cascade never ends a node twice. */
21
- endedAt: z.number().optional()
21
+ endedAt: z.number().optional(),
22
+ /* Set when the agent that opened this one runs on another machine; `openedBy` then names it there, not a node here. */
23
+ peer: z.object({ machineId: z.string().min(1) }).optional()
22
24
  });
23
25
  /* Opened by the agent it names, which is what every rule about children is about; a fork lives on its own. */
24
26
  function openedByAgent(entry) {
@@ -96,6 +98,10 @@ export class AgentLineageStore {
96
98
  const entry = this.opened.get(nodeId);
97
99
  return entry !== undefined && openedByAgent(entry) ? (entry.ceiling ?? null) : null;
98
100
  }
101
+ /* The machine whose agent opened this node, or null for a node opened on this one. */
102
+ peerOf(nodeId) {
103
+ return this.opened.get(nodeId)?.peer ?? null;
104
+ }
99
105
  /* How deep a node sits. A node nobody wrote down is one a person made, which is where a chain starts. */
100
106
  depthOf(nodeId) {
101
107
  return this.opened.get(nodeId)?.depth ?? 0;
@@ -34,6 +34,7 @@ export declare class TaskCoordinator {
34
34
  constructor(deps: TaskCoordinatorDeps);
35
35
  stop(): void;
36
36
  settled(): Promise<void>;
37
+ settleFromElsewhere(taskId: string, result: TaskResult, status?: 'done' | 'failed'): Promise<Task | null>;
37
38
  chatEvent(event: AgentEvent): void;
38
39
  agentEnded(childId: string, text: string): void;
39
40
  startFailed(childId: string, error: unknown): void;
@@ -41,6 +42,7 @@ export declare class TaskCoordinator {
41
42
  done(childId: string, text: string): Promise<Task | null>;
42
43
  outlasted(childId: string, commands: readonly string[], cause?: 'limit' | 'restart'): Promise<Task | null>;
43
44
  cancelled(tasks: readonly Task[]): void;
45
+ private localTask;
44
46
  private failed;
45
47
  private answers;
46
48
  private turnEnded;
@@ -75,6 +75,18 @@ export class TaskCoordinator {
75
75
  }
76
76
  await this.deps.tasks.settled();
77
77
  }
78
+ /*
79
+ * Settles a task whose child runs on another machine, which no chat here observes: the answer comes
80
+ * from there. Wakes the parent once however often it is told; null when the task is no such task or
81
+ * was not open any more.
82
+ */
83
+ async settleFromElsewhere(taskId, result, status = 'done') {
84
+ const task = this.deps.tasks.get(taskId);
85
+ if (task?.machine === undefined || task.status !== 'open') {
86
+ return null;
87
+ }
88
+ return this.settleNow(task, status, result);
89
+ }
78
90
  chatEvent(event) {
79
91
  if (this.stopped || event.event !== 'chat.event') {
80
92
  return;
@@ -105,7 +117,7 @@ export class TaskCoordinator {
105
117
  if (this.stopped) {
106
118
  return;
107
119
  }
108
- const task = this.deps.tasks.openFor(childId);
120
+ const task = this.localTask(childId);
109
121
  if (!task || !this.deps.placed(childId)) {
110
122
  return;
111
123
  }
@@ -120,7 +132,7 @@ export class TaskCoordinator {
120
132
  }
121
133
  /* `done` from the child itself, which wins over whatever else was about to settle it. */
122
134
  async done(childId, text) {
123
- const task = this.deps.tasks.openFor(childId);
135
+ const task = this.localTask(childId);
124
136
  if (!task) {
125
137
  return null;
126
138
  }
@@ -132,7 +144,7 @@ export class TaskCoordinator {
132
144
  * does when the CLI goes within one run.
133
145
  */
134
146
  async outlasted(childId, commands, cause = 'limit') {
135
- const task = this.deps.tasks.openFor(childId);
147
+ const task = this.localTask(childId);
136
148
  const items = this.deps.chatItems(childId);
137
149
  if (this.stopped || !task || items === null || this.delegating(childId)) {
138
150
  return null;
@@ -153,9 +165,14 @@ export class TaskCoordinator {
153
165
  this.recheck(task.parentId);
154
166
  }
155
167
  }
168
+ /* The open task a chat here works on; one whose child runs on another machine is settled from there. */
169
+ localTask(childId) {
170
+ const task = this.deps.tasks.openFor(childId);
171
+ return task?.machine === undefined ? task : undefined;
172
+ }
156
173
  /* An open task of this child that nothing can carry any more. */
157
174
  failed(childId, text) {
158
- const task = this.deps.tasks.openFor(childId);
175
+ const task = this.localTask(childId);
159
176
  if (!task || this.stopped) {
160
177
  return;
161
178
  }
@@ -179,7 +196,7 @@ export class TaskCoordinator {
179
196
  return own === -1 ? task.requiresTaskTurn !== true && turn.createdAt >= task.createdAt : own < items.findIndex((item) => item.id === turn.id);
180
197
  }
181
198
  turnEnded(chatId, turn) {
182
- const task = this.deps.tasks.openFor(chatId);
199
+ const task = this.localTask(chatId);
183
200
  const items = this.deps.chatItems(chatId);
184
201
  if (!task || items === null || !this.answers(items, turn, task) || this.delegating(chatId)) {
185
202
  return;
@@ -297,7 +314,7 @@ export class TaskCoordinator {
297
314
  }
298
315
  /* A child that took up work again is no longer waiting out a limit. */
299
316
  turnStarted(chatId) {
300
- const task = this.deps.tasks.openFor(chatId);
317
+ const task = this.localTask(chatId);
301
318
  if (task?.paused !== undefined) {
302
319
  this.hold(task, null);
303
320
  }
@@ -306,7 +323,7 @@ export class TaskCoordinator {
306
323
  void this.deps.tasks.pause(task.id, paused).catch((e) => console.error(`Pausing task ${task.id} failed:`, errorText(e)));
307
324
  }
308
325
  recheck(chatId) {
309
- const task = this.deps.tasks.openFor(chatId);
326
+ const task = this.localTask(chatId);
310
327
  const items = this.deps.chatItems(chatId);
311
328
  if (!task || items === null || this.delegating(chatId)) {
312
329
  return;
@@ -25,6 +25,7 @@ export declare class TaskStore {
25
25
  prompt: string;
26
26
  batchId?: string;
27
27
  requiresTaskTurn?: boolean;
28
+ machine?: string;
28
29
  }, now: number): Promise<Task>;
29
30
  all(): Task[];
30
31
  settled(): Promise<void>;
@@ -12,6 +12,7 @@ export interface TaskRecord {
12
12
  childId: string;
13
13
  title: string;
14
14
  prompt: string;
15
+ machine?: string;
15
16
  }
16
17
  export interface TaskVerbs {
17
18
  open(record: TaskRecord & {
@@ -56,7 +56,15 @@ export function wireTasks(deps) {
56
56
  }
57
57
  });
58
58
  const requests = chatRequests(chats);
59
- const waiting = new WaitingObserver({ openTask: (childId) => deps.tasks.openFor(childId), enqueue, now });
59
+ const waiting = new WaitingObserver({
60
+ // A child on another machine asks its questions there.
61
+ openTask: (childId) => {
62
+ const task = deps.tasks.openFor(childId);
63
+ return task?.machine === undefined ? task : undefined;
64
+ },
65
+ enqueue,
66
+ now
67
+ });
60
68
  chats.observe((event) => {
61
69
  coordinator.chatEvent(event);
62
70
  waiting.chatEvent(event);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/agents",
3
- "version": "0.25.3",
3
+ "version": "0.25.4",
4
4
  "description": "Node-compatible agent chat hosts, providers, accounts, usage and durable task coordination.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -81,8 +81,8 @@
81
81
  "test": "bun --conditions=source test"
82
82
  },
83
83
  "dependencies": {
84
- "@adecore/agent-contracts": "0.25.3",
85
- "@adecore/intelligent-ui": "0.25.3",
84
+ "@adecore/agent-contracts": "0.25.4",
85
+ "@adecore/intelligent-ui": "0.25.4",
86
86
  "zod": "^4.6.5"
87
87
  },
88
88
  "devDependencies": {
package/src/lineage.ts CHANGED
@@ -19,7 +19,9 @@ const LineageSchema = z.object({
19
19
  ceiling: RuntimeModeSchema.optional(),
20
20
  createdAt: z.number(),
21
21
  /* When stopping or deleting the node that opened it ended this one too; a cascade never ends a node twice. */
22
- endedAt: z.number().optional()
22
+ endedAt: z.number().optional(),
23
+ /* Set when the agent that opened this one runs on another machine; `openedBy` then names it there, not a node here. */
24
+ peer: z.object({ machineId: z.string().min(1) }).optional()
23
25
  });
24
26
 
25
27
  type Lineage = z.infer<typeof LineageSchema>;
@@ -115,6 +117,11 @@ export class AgentLineageStore {
115
117
  return entry !== undefined && openedByAgent(entry) ? (entry.ceiling ?? null) : null;
116
118
  }
117
119
 
120
+ /* The machine whose agent opened this node, or null for a node opened on this one. */
121
+ peerOf(nodeId: string): { machineId: string } | null {
122
+ return this.opened.get(nodeId)?.peer ?? null;
123
+ }
124
+
118
125
  /* How deep a node sits. A node nobody wrote down is one a person made, which is where a chain starts. */
119
126
  depthOf(nodeId: string): number {
120
127
  return this.opened.get(nodeId)?.depth ?? 0;
@@ -126,6 +126,19 @@ export class TaskCoordinator {
126
126
  await this.deps.tasks.settled();
127
127
  }
128
128
 
129
+ /*
130
+ * Settles a task whose child runs on another machine, which no chat here observes: the answer comes
131
+ * from there. Wakes the parent once however often it is told; null when the task is no such task or
132
+ * was not open any more.
133
+ */
134
+ async settleFromElsewhere(taskId: string, result: TaskResult, status: 'done' | 'failed' = 'done'): Promise<Task | null> {
135
+ const task = this.deps.tasks.get(taskId);
136
+ if (task?.machine === undefined || task.status !== 'open') {
137
+ return null;
138
+ }
139
+ return this.settleNow(task, status, result);
140
+ }
141
+
129
142
  chatEvent(event: AgentEvent): void {
130
143
  if (this.stopped || event.event !== 'chat.event') {
131
144
  return;
@@ -154,7 +167,7 @@ export class TaskCoordinator {
154
167
  if (this.stopped) {
155
168
  return;
156
169
  }
157
- const task = this.deps.tasks.openFor(childId);
170
+ const task = this.localTask(childId);
158
171
  if (!task || !this.deps.placed(childId)) {
159
172
  return;
160
173
  }
@@ -172,7 +185,7 @@ export class TaskCoordinator {
172
185
 
173
186
  /* `done` from the child itself, which wins over whatever else was about to settle it. */
174
187
  async done(childId: string, text: string): Promise<Task | null> {
175
- const task = this.deps.tasks.openFor(childId);
188
+ const task = this.localTask(childId);
176
189
  if (!task) {
177
190
  return null;
178
191
  }
@@ -185,7 +198,7 @@ export class TaskCoordinator {
185
198
  * does when the CLI goes within one run.
186
199
  */
187
200
  async outlasted(childId: string, commands: readonly string[], cause: 'limit' | 'restart' = 'limit'): Promise<Task | null> {
188
- const task = this.deps.tasks.openFor(childId);
201
+ const task = this.localTask(childId);
189
202
  const items = this.deps.chatItems(childId);
190
203
  if (this.stopped || !task || items === null || this.delegating(childId)) {
191
204
  return null;
@@ -208,9 +221,15 @@ export class TaskCoordinator {
208
221
  }
209
222
  }
210
223
 
224
+ /* The open task a chat here works on; one whose child runs on another machine is settled from there. */
225
+ private localTask(childId: string): Task | undefined {
226
+ const task = this.deps.tasks.openFor(childId);
227
+ return task?.machine === undefined ? task : undefined;
228
+ }
229
+
211
230
  /* An open task of this child that nothing can carry any more. */
212
231
  private failed(childId: string, text: string): void {
213
- const task = this.deps.tasks.openFor(childId);
232
+ const task = this.localTask(childId);
214
233
  if (!task || this.stopped) {
215
234
  return;
216
235
  }
@@ -236,7 +255,7 @@ export class TaskCoordinator {
236
255
  }
237
256
 
238
257
  private turnEnded(chatId: string, turn: ChatTurnItem): void {
239
- const task = this.deps.tasks.openFor(chatId);
258
+ const task = this.localTask(chatId);
240
259
  const items = this.deps.chatItems(chatId);
241
260
  if (!task || items === null || !this.answers(items, turn, task) || this.delegating(chatId)) {
242
261
  return;
@@ -360,7 +379,7 @@ export class TaskCoordinator {
360
379
 
361
380
  /* A child that took up work again is no longer waiting out a limit. */
362
381
  private turnStarted(chatId: string): void {
363
- const task = this.deps.tasks.openFor(chatId);
382
+ const task = this.localTask(chatId);
364
383
  if (task?.paused !== undefined) {
365
384
  this.hold(task, null);
366
385
  }
@@ -371,7 +390,7 @@ export class TaskCoordinator {
371
390
  }
372
391
 
373
392
  private recheck(chatId: string): void {
374
- const task = this.deps.tasks.openFor(chatId);
393
+ const task = this.localTask(chatId);
375
394
  const items = this.deps.chatItems(chatId);
376
395
  if (!task || items === null || this.delegating(chatId)) {
377
396
  return;
@@ -51,7 +51,16 @@ export class TaskStore {
51
51
  }
52
52
 
53
53
  async open(
54
- record: { projectId: string; parentId: string; childId: string; title: string; prompt: string; batchId?: string; requiresTaskTurn?: boolean },
54
+ record: {
55
+ projectId: string;
56
+ parentId: string;
57
+ childId: string;
58
+ title: string;
59
+ prompt: string;
60
+ batchId?: string;
61
+ requiresTaskTurn?: boolean;
62
+ machine?: string;
63
+ },
55
64
  now: number
56
65
  ): Promise<Task> {
57
66
  const task: Task = {
@@ -22,6 +22,8 @@ export interface TaskRecord {
22
22
  childId: string;
23
23
  title: string;
24
24
  prompt: string;
25
+ /* The machine the child runs on, when it is not this one. */
26
+ machine?: string;
25
27
  }
26
28
 
27
29
  /* What the verbs of a context CLI reach of the tasks. */
@@ -127,7 +129,15 @@ export function wireTasks(deps: TaskWiringDeps): TaskWiring {
127
129
  });
128
130
 
129
131
  const requests = chatRequests(chats);
130
- const waiting = new WaitingObserver({ openTask: (childId) => deps.tasks.openFor(childId), enqueue, now });
132
+ const waiting = new WaitingObserver({
133
+ // A child on another machine asks its questions there.
134
+ openTask: (childId) => {
135
+ const task = deps.tasks.openFor(childId);
136
+ return task?.machine === undefined ? task : undefined;
137
+ },
138
+ enqueue,
139
+ now
140
+ });
131
141
 
132
142
  chats.observe((event) => {
133
143
  coordinator.chatEvent(event);