@adecore/agents 0.45.1 → 0.46.1

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.
@@ -408,6 +408,7 @@ export class ChatCore {
408
408
  items: session.previewItems(page.items),
409
409
  pending: session.previewItems(session.thread.pending()),
410
410
  subagents: session.thread.subagentsBefore(page.history.start),
411
+ workflows: session.thread.workflowsBefore(page.history.start),
411
412
  outline: session.thread.outlineBefore(page.history.start),
412
413
  seq: log.seq
413
414
  };
@@ -52,6 +52,7 @@ export declare class ThreadProjector {
52
52
  private settleSubagent;
53
53
  private settleBackgroundSubagent;
54
54
  private settleLaunchedCall;
55
+ private ended;
55
56
  private startBackground;
56
57
  private withdraw;
57
58
  private finishTurn;
@@ -199,9 +199,17 @@ export class ThreadProjector {
199
199
  const tool = this.thread.get(this.itemId(generation, event.ref));
200
200
  if (tool?.kind === 'tool') {
201
201
  const name = event.workflow.name ?? tool.workflow?.name ?? null;
202
+ // A late frame after the call settled must not wipe the end it noted.
203
+ const finishedAt = event.workflow.finishedAt ?? tool.workflow?.finishedAt;
202
204
  events.push(this.thread.upsert({
203
205
  ...tool,
204
- workflow: { ...event.workflow, name, taskId: event.workflow.taskId ?? tool.workflow?.taskId, lastProgressAt: this.now() }
206
+ workflow: {
207
+ ...event.workflow,
208
+ name,
209
+ taskId: event.workflow.taskId ?? tool.workflow?.taskId,
210
+ lastProgressAt: this.now(),
211
+ ...(finishedAt === undefined ? {} : { finishedAt })
212
+ }
205
213
  }));
206
214
  }
207
215
  break;
@@ -415,7 +423,7 @@ export class ThreadProjector {
415
423
  events.push(this.thread.upsert({ ...item, state: 'cancelled' }));
416
424
  }
417
425
  else if (item.kind === 'tool' && item.state === 'running' && (processGone || !this.backgroundCalls.has(item.id))) {
418
- events.push(this.thread.upsert({ ...item, state: 'error' }));
426
+ events.push(this.thread.upsert(this.ended({ ...item, state: 'error' })));
419
427
  }
420
428
  else if (item.kind === 'subagent' && item.status === 'running' && (processGone || !this.outlivesTurn(item))) {
421
429
  events.push(this.thread.upsert({ ...item, status: 'failed', finishedAt: this.now() }));
@@ -553,7 +561,7 @@ export class ThreadProjector {
553
561
  if (event.changes) {
554
562
  settled.changes = event.changes;
555
563
  }
556
- events.push(this.thread.upsert(settled));
564
+ events.push(this.thread.upsert(this.ended(settled)));
557
565
  }
558
566
  patchProgress(generation, event, events) {
559
567
  const tool = this.thread.get(this.itemId(generation, event.ref));
@@ -803,7 +811,11 @@ export class ThreadProjector {
803
811
  const output = summary === '' ? call.output : [call.output, summary].filter((part) => part !== null && part !== '').join('\n\n');
804
812
  const settled = { ...call, output, state: event.ok ? 'done' : 'error' };
805
813
  delete settled.progress;
806
- events.push(this.thread.upsert(settled));
814
+ events.push(this.thread.upsert(this.ended(settled)));
815
+ }
816
+ /* A settling Workflow call notes when, since a tool item has no end of its own to tell a finished workflow's age by. */
817
+ ended(tool) {
818
+ return tool.workflow === undefined ? tool : { ...tool, workflow: { ...tool.workflow, finishedAt: this.now() } };
807
819
  }
808
820
  /* A shell or a monitor, told apart by the call that started it when the CLI's frame does not say. */
809
821
  startBackground(generation, event, events) {
@@ -1,4 +1,4 @@
1
- import { type AgentStatus, type ChatApprovalItem, type ChatEvent, type ChatHistoryResult, type ChatInfo, type ChatItem, type ChatQuestionItem, type ChatSubagentItem } from '@adecore/agent-contracts';
1
+ import { type AgentStatus, type ChatApprovalItem, type ChatEvent, type ChatHistoryResult, type ChatInfo, type ChatItem, type ChatQuestionItem, type ChatSubagentItem, type ChatToolItem } from '@adecore/agent-contracts';
2
2
  export declare class ChatThread {
3
3
  info: ChatInfo;
4
4
  private readonly items;
@@ -30,6 +30,7 @@ export declare class ChatThread {
30
30
  private syncRequests;
31
31
  history(limit?: number, cursor?: string): ChatHistoryResult;
32
32
  subagentsBefore(start: number): ChatSubagentItem[];
33
+ workflowsBefore(start: number): ChatToolItem[];
33
34
  outlineBefore(start: number, marked?: ReadonlySet<string>): ChatItem[];
34
35
  snapshot(): {
35
36
  info: ChatInfo;
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { activeSubagents, outlineItems } from '@adecore/agent-contracts';
2
+ import { activeSubagents, activeWorkflows, outlineItems } from '@adecore/agent-contracts';
3
3
  import { appendToolOutput } from '@adecore/agent-contracts/text';
4
4
  import { ChatError } from './errors.js';
5
5
  import { requestSummaries } from './request-summary.js';
@@ -185,6 +185,10 @@ export class ChatThread {
185
185
  subagentsBefore(start) {
186
186
  return activeSubagents(this.list()).filter((item) => this.indices.get(item.id) < start);
187
187
  }
188
+ /* The active Workflow calls a page that begins at `start` leaves out. */
189
+ workflowsBefore(start) {
190
+ return activeWorkflows(this.list()).filter((item) => this.indices.get(item.id) < start);
191
+ }
188
192
  /* The outline of what a page that begins at `start` leaves out, with the replies in `marked` that a bookmark is on. */
189
193
  outlineBefore(start, marked) {
190
194
  return outlineItems(this.order.slice(0, start).map((id) => this.items.get(id)), marked);
@@ -42,11 +42,11 @@ export function usageRootsOf(service) {
42
42
  return folder === null || folder === '' ? [] : [{ id, kind, folder }];
43
43
  }));
44
44
  }
45
- /* The plans the limits monitor reads: the default account of every CLI, and every other one that is on and signed in. */
45
+ /* The plans the limits monitor reads, of the accounts that are on: the default account of every CLI, and every other one that is signed in. */
46
46
  export function limitAccountsOf(service, nameOf, baseEnv) {
47
47
  return {
48
48
  list: () => machineAccounts(service.snapshot(), nameOf)
49
- .filter((account) => account.isDefault || (account.enabled && account.signedIn))
49
+ .filter((account) => account.enabled && (account.isDefault || account.signedIn))
50
50
  .map((account) => ({ ...usageAccount(account), isDefault: account.isDefault })),
51
51
  // A plan read is no start: the default account goes unnamed, so turning it off does not fail its read.
52
52
  envFor: (kind, id) => definedEnv(service.envFor(kind, storedAccount(kind, id), baseEnv)),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/agents",
3
- "version": "0.45.1",
3
+ "version": "0.46.1",
4
4
  "description": "Node-compatible agent chat hosts, providers, accounts, usage and durable task coordination.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -93,8 +93,8 @@
93
93
  "test": "bun --conditions=source test"
94
94
  },
95
95
  "dependencies": {
96
- "@adecore/agent-contracts": "0.45.1",
97
- "@adecore/intelligent-ui": "0.45.1",
96
+ "@adecore/agent-contracts": "0.46.1",
97
+ "@adecore/intelligent-ui": "0.46.1",
98
98
  "zod": "^4.6.7"
99
99
  },
100
100
  "devDependencies": {
@@ -663,6 +663,7 @@ export class ChatCore {
663
663
  items: session.previewItems(page.items),
664
664
  pending: session.previewItems(session.thread.pending()),
665
665
  subagents: session.thread.subagentsBefore(page.history.start),
666
+ workflows: session.thread.workflowsBefore(page.history.start),
666
667
  outline: session.thread.outlineBefore(page.history.start),
667
668
  seq: log.seq
668
669
  };
@@ -261,11 +261,19 @@ export class ThreadProjector {
261
261
 
262
262
  if (tool?.kind === 'tool') {
263
263
  const name = event.workflow.name ?? tool.workflow?.name ?? null;
264
+ // A late frame after the call settled must not wipe the end it noted.
265
+ const finishedAt = event.workflow.finishedAt ?? tool.workflow?.finishedAt;
264
266
 
265
267
  events.push(
266
268
  this.thread.upsert({
267
269
  ...tool,
268
- workflow: { ...event.workflow, name, taskId: event.workflow.taskId ?? tool.workflow?.taskId, lastProgressAt: this.now() }
270
+ workflow: {
271
+ ...event.workflow,
272
+ name,
273
+ taskId: event.workflow.taskId ?? tool.workflow?.taskId,
274
+ lastProgressAt: this.now(),
275
+ ...(finishedAt === undefined ? {} : { finishedAt })
276
+ }
269
277
  })
270
278
  );
271
279
  }
@@ -532,7 +540,7 @@ export class ThreadProjector {
532
540
  } else if (item.kind === 'question' && item.state === 'pending' && dropRequests) {
533
541
  events.push(this.thread.upsert({ ...item, state: 'cancelled' }));
534
542
  } else if (item.kind === 'tool' && item.state === 'running' && (processGone || !this.backgroundCalls.has(item.id))) {
535
- events.push(this.thread.upsert({ ...item, state: 'error' }));
543
+ events.push(this.thread.upsert(this.ended({ ...item, state: 'error' })));
536
544
  } else if (item.kind === 'subagent' && item.status === 'running' && (processGone || !this.outlivesTurn(item))) {
537
545
  events.push(this.thread.upsert({ ...item, status: 'failed', finishedAt: this.now() }));
538
546
  }
@@ -711,7 +719,7 @@ export class ThreadProjector {
711
719
  settled.changes = event.changes;
712
720
  }
713
721
 
714
- events.push(this.thread.upsert(settled));
722
+ events.push(this.thread.upsert(this.ended(settled)));
715
723
  }
716
724
 
717
725
  private patchProgress(generation: number, event: Extract<BackendEvent, { type: 'tool.progress' }>, events: ChatEvent[]): void {
@@ -1030,7 +1038,12 @@ export class ThreadProjector {
1030
1038
  const output = summary === '' ? call.output : [call.output, summary].filter((part) => part !== null && part !== '').join('\n\n');
1031
1039
  const settled: ChatToolItem = { ...call, output, state: event.ok ? 'done' : 'error' };
1032
1040
  delete settled.progress;
1033
- events.push(this.thread.upsert(settled));
1041
+ events.push(this.thread.upsert(this.ended(settled)));
1042
+ }
1043
+
1044
+ /* A settling Workflow call notes when, since a tool item has no end of its own to tell a finished workflow's age by. */
1045
+ private ended(tool: ChatToolItem): ChatToolItem {
1046
+ return tool.workflow === undefined ? tool : { ...tool, workflow: { ...tool.workflow, finishedAt: this.now() } };
1034
1047
  }
1035
1048
 
1036
1049
  /* A shell or a monitor, told apart by the call that started it when the CLI's frame does not say. */
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import {
3
3
  activeSubagents,
4
+ activeWorkflows,
4
5
  outlineItems,
5
6
  type AgentStatus,
6
7
  type ChatApprovalItem,
@@ -9,7 +10,8 @@ import {
9
10
  type ChatInfo,
10
11
  type ChatItem,
11
12
  type ChatQuestionItem,
12
- type ChatSubagentItem
13
+ type ChatSubagentItem,
14
+ type ChatToolItem
13
15
  } from '@adecore/agent-contracts';
14
16
  import { appendToolOutput } from '@adecore/agent-contracts/text';
15
17
  import { ChatError } from './errors.ts';
@@ -247,6 +249,11 @@ export class ChatThread {
247
249
  return activeSubagents(this.list()).filter((item) => this.indices.get(item.id)! < start);
248
250
  }
249
251
 
252
+ /* The active Workflow calls a page that begins at `start` leaves out. */
253
+ workflowsBefore(start: number): ChatToolItem[] {
254
+ return activeWorkflows(this.list()).filter((item) => this.indices.get(item.id)! < start);
255
+ }
256
+
250
257
  /* The outline of what a page that begins at `start` leaves out, with the replies in `marked` that a bookmark is on. */
251
258
  outlineBefore(start: number, marked?: ReadonlySet<string>): ChatItem[] {
252
259
  return outlineItems(
@@ -62,7 +62,7 @@ export function usageRootsOf(service: ProviderAccountsService): UsageRootPath[]
62
62
  );
63
63
  }
64
64
 
65
- /* The plans the limits monitor reads: the default account of every CLI, and every other one that is on and signed in. */
65
+ /* The plans the limits monitor reads, of the accounts that are on: the default account of every CLI, and every other one that is signed in. */
66
66
  export function limitAccountsOf(
67
67
  service: ProviderAccountsService,
68
68
  nameOf: (kind: UsageProvider) => string,
@@ -71,7 +71,7 @@ export function limitAccountsOf(
71
71
  return {
72
72
  list: () =>
73
73
  machineAccounts(service.snapshot(), nameOf)
74
- .filter((account) => account.isDefault || (account.enabled && account.signedIn))
74
+ .filter((account) => account.enabled && (account.isDefault || account.signedIn))
75
75
  .map((account): LimitAccount => ({ ...usageAccount(account), isDefault: account.isDefault })),
76
76
  // A plan read is no start: the default account goes unnamed, so turning it off does not fail its read.
77
77
  envFor: (kind, id) => definedEnv(service.envFor(kind, storedAccount(kind, id), baseEnv)),