@adecore/agents 0.45.0 → 0.46.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.
@@ -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
  };
@@ -764,7 +764,7 @@ export class ClaudeProtocol {
764
764
  const errors = Array.isArray(frame.errors) ? frame.errors.filter((error) => typeof error === 'string') : [];
765
765
  const lastUuid = this.lastUuid;
766
766
  this.lastUuid = null;
767
- const limit = failed ? this.limit() : null;
767
+ const limit = failed ? this.limit([str(frame.result), ...errors].join('\n')) : null;
768
768
  const replaceProcess = failed && this.apiError === 'authentication_failed';
769
769
  if (limit?.kind !== 'usage') {
770
770
  this.refusedResets.clear();
@@ -819,10 +819,10 @@ export class ClaudeProtocol {
819
819
  }
820
820
  /*
821
821
  * What stopped a failed turn, in the words of Claude Code 2.1.281: a window of the plan that refused
822
- * the request is a usage limit, and an API error of `rate_limit` without one (a 429 the plan did
823
- * not explain) or of `overloaded` is a model too busy to answer.
822
+ * the request or an explicit spent-plan message is a usage limit. A bare `rate_limit` (a 429
823
+ * the plan did not explain) or `overloaded` is a model too busy to answer.
824
824
  */
825
- limit() {
825
+ limit(message) {
826
826
  if (this.refusedWindows.size > 0) {
827
827
  let resetsAt = 0;
828
828
  for (const reset of this.refusedWindows.values()) {
@@ -833,6 +833,10 @@ export class ClaudeProtocol {
833
833
  }
834
834
  return { kind: 'usage', resetsAt };
835
835
  }
836
+ // The first refusal can name the spent plan before a rate_limit_event supplies its window.
837
+ if (this.apiError === 'rate_limit' && /you(?:'|’)ve hit your (?:5[- ]hour|weekly|session|usage) limit\b/i.test(message)) {
838
+ return { kind: 'usage' };
839
+ }
836
840
  if (this.apiError === 'rate_limit' || this.apiError === 'overloaded') {
837
841
  return { kind: 'overload' };
838
842
  }
@@ -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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/agents",
3
- "version": "0.45.0",
3
+ "version": "0.46.0",
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.0",
97
- "@adecore/intelligent-ui": "0.45.0",
96
+ "@adecore/agent-contracts": "0.46.0",
97
+ "@adecore/intelligent-ui": "0.46.0",
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
  };
@@ -949,7 +949,7 @@ export class ClaudeProtocol {
949
949
  const errors = Array.isArray(frame.errors) ? frame.errors.filter((error): error is string => typeof error === 'string') : [];
950
950
  const lastUuid = this.lastUuid;
951
951
  this.lastUuid = null;
952
- const limit = failed ? this.limit() : null;
952
+ const limit = failed ? this.limit([str(frame.result), ...errors].join('\n')) : null;
953
953
  const replaceProcess = failed && this.apiError === 'authentication_failed';
954
954
 
955
955
  if (limit?.kind !== 'usage') {
@@ -1014,10 +1014,10 @@ export class ClaudeProtocol {
1014
1014
 
1015
1015
  /*
1016
1016
  * What stopped a failed turn, in the words of Claude Code 2.1.281: a window of the plan that refused
1017
- * the request is a usage limit, and an API error of `rate_limit` without one (a 429 the plan did
1018
- * not explain) or of `overloaded` is a model too busy to answer.
1017
+ * the request or an explicit spent-plan message is a usage limit. A bare `rate_limit` (a 429
1018
+ * the plan did not explain) or `overloaded` is a model too busy to answer.
1019
1019
  */
1020
- private limit(): ChatTurnLimit | null {
1020
+ private limit(message: string): ChatTurnLimit | null {
1021
1021
  if (this.refusedWindows.size > 0) {
1022
1022
  let resetsAt = 0;
1023
1023
 
@@ -1032,6 +1032,11 @@ export class ClaudeProtocol {
1032
1032
  return { kind: 'usage', resetsAt };
1033
1033
  }
1034
1034
 
1035
+ // The first refusal can name the spent plan before a rate_limit_event supplies its window.
1036
+ if (this.apiError === 'rate_limit' && /you(?:'|’)ve hit your (?:5[- ]hour|weekly|session|usage) limit\b/i.test(message)) {
1037
+ return { kind: 'usage' };
1038
+ }
1039
+
1035
1040
  if (this.apiError === 'rate_limit' || this.apiError === 'overloaded') {
1036
1041
  return { kind: 'overload' };
1037
1042
  }
@@ -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(