@adecore/agents 0.25.5 → 0.25.7

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.
@@ -108,6 +108,7 @@ export declare class ChatSession {
108
108
  private readonly stopping;
109
109
  private readonly orphans;
110
110
  private pendingPreambles;
111
+ private pendingHints;
111
112
  private submission;
112
113
  private readonly steering;
113
114
  private readonly capturedUiAccess;
@@ -79,6 +79,8 @@ export class ChatSession {
79
79
  // Background subagents a load found running whose transcript did not show an end yet; no CLI here will report them.
80
80
  orphans = new Set();
81
81
  pendingPreambles;
82
+ // Said once like the preambles, but never drawn as a note; kept in memory only, since a lost hint costs nothing.
83
+ pendingHints = [];
82
84
  submission = null;
83
85
  steering = new Set();
84
86
  capturedUiAccess;
@@ -95,8 +97,13 @@ export class ChatSession {
95
97
  clock: options.clock ?? systemClock,
96
98
  ...(options.ui ? { host: options.ui } : {}),
97
99
  emit: (event) => options.emit(event),
98
- repair: (note) => {
99
- if (!this.pendingPreambles.includes(note)) {
100
+ repair: (note, audience) => {
101
+ if (audience === 'agent') {
102
+ if (!this.pendingHints.includes(note)) {
103
+ this.pendingHints = [...this.pendingHints, note];
104
+ }
105
+ }
106
+ else if (!this.pendingPreambles.includes(note)) {
100
107
  this.pendingPreambles = [...this.pendingPreambles, note];
101
108
  }
102
109
  }
@@ -647,6 +654,7 @@ export class ChatSession {
647
654
  this.options.promptNotes?.reset();
648
655
  // A cleared chat starts a new conversation, which what was waiting to be said no longer describes.
649
656
  this.pendingPreambles = [];
657
+ this.pendingHints = [];
650
658
  this.submission = null;
651
659
  this.staleResults = 0;
652
660
  this.sendAfterStop = false;
@@ -1430,9 +1438,11 @@ export class ChatSession {
1430
1438
  const preambles = this.preambles;
1431
1439
  this.pendingPreambles = [];
1432
1440
  this.submission = null;
1441
+ const hints = this.pendingHints;
1442
+ this.pendingHints = [];
1433
1443
  const read = [...preambles, ...host.shown];
1434
1444
  const joined = (parts) => (parts.length === 0 ? null : parts.join('\n\n'));
1435
- return { preamble: joined([...read, ...host.heard]), note: joined(read) };
1445
+ return { preamble: joined([...read, ...host.heard, ...hints]), note: joined(read) };
1436
1446
  }
1437
1447
  submit(turnId, input) {
1438
1448
  this.pendingPreambles = this.preambles;
@@ -12,7 +12,7 @@ export interface UiRepliesOptions {
12
12
  clock: OutboxClock;
13
13
  host?: UiReplyHost;
14
14
  emit(event: ChatEvent): void;
15
- repair(note: string): void;
15
+ repair(note: string, audience: 'person' | 'agent'): void;
16
16
  }
17
17
  export declare class UiReplies {
18
18
  private readonly options;
@@ -25,6 +25,7 @@ export declare class UiReplies {
25
25
  previews(): ChatEvent[];
26
26
  previewItems(items: ChatItem[]): ChatItem[];
27
27
  pass(event: ChatEvent): void;
28
+ private repair;
28
29
  clear(): void;
29
30
  private unscannedText;
30
31
  private forgetScan;
@@ -89,15 +89,22 @@ export class UiReplies {
89
89
  .map((block) => ({ ...block, revision: createHash('sha256').update(JSON.stringify(block)).digest('hex') }));
90
90
  this.streams.delete(item.id);
91
91
  this.compiled.delete(item.id);
92
- const diagnostics = [...new Set(ui.flatMap((block) => block.diagnostics.map((diagnostic) => diagnostic.message)))].slice(0, 6);
93
- if (diagnostics.length > 0) {
94
- this.options.repair(`Your last ${this.options.fenceLanguage} block had these diagnostics: ${diagnostics.join(' ')} Use the catalog's supported props and components.`.slice(0, 1200));
95
- }
92
+ this.repair(ui.flatMap((block) => block.diagnostics));
96
93
  const current = thread.get(item.id);
97
94
  const completed = { ...item, ui, ...(current?.kind === 'assistant' && current.uiQueries ? { uiQueries: current.uiQueries } : {}) };
98
95
  emit(thread.upsert(completed));
99
96
  this.options.host?.observe(completed, true);
100
97
  }
98
+ repair(diagnostics) {
99
+ const say = (found, audience) => {
100
+ const messages = [...new Set(found.map((diagnostic) => diagnostic.message))].slice(0, 6);
101
+ if (messages.length > 0) {
102
+ this.options.repair(`Your last ${this.options.fenceLanguage} block had these diagnostics: ${messages.join(' ')} Use the catalog's supported props and components.`.slice(0, 1200), audience);
103
+ }
104
+ };
105
+ say(diagnostics.filter((diagnostic) => diagnostic.severity !== 'warning'), 'person');
106
+ say(diagnostics.filter((diagnostic) => diagnostic.severity === 'warning'), 'agent');
107
+ }
101
108
  clear() {
102
109
  for (const stream of this.streams.values()) {
103
110
  stream.dispose();
@@ -143,10 +143,14 @@ export class TaskStore {
143
143
  const ids = this.ofParent(parentId).flatMap((task) => (task.status === 'open' && task.batchId !== undefined ? [task.batchId] : []));
144
144
  return [...new Set(ids)];
145
145
  }
146
- /* A parent nobody can wake any more: its settled tasks stop waiting for it. */
146
+ /*
147
+ * A parent nobody can wake any more: its settled tasks stop waiting for it. Each record is read again
148
+ * just before its write, since a task may settle while an earlier write of this loop waits.
149
+ */
147
150
  async dropWake(parentId) {
148
- for (const task of this.ofParent(parentId)) {
149
- if (task.wake === 'pending') {
151
+ for (const { id } of this.ofParent(parentId)) {
152
+ const task = this.tasks.get(id);
153
+ if (task && task.wake === 'pending') {
150
154
  await this.write({ ...task, wake: 'none' });
151
155
  }
152
156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/agents",
3
- "version": "0.25.5",
3
+ "version": "0.25.7",
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.5",
85
- "@adecore/intelligent-ui": "0.25.5",
84
+ "@adecore/agent-contracts": "0.25.7",
85
+ "@adecore/intelligent-ui": "0.25.7",
86
86
  "zod": "^4.6.5"
87
87
  },
88
88
  "devDependencies": {
@@ -205,6 +205,8 @@ export class ChatSession {
205
205
  // Background subagents a load found running whose transcript did not show an end yet; no CLI here will report them.
206
206
  private readonly orphans = new Set<string>();
207
207
  private pendingPreambles: readonly string[];
208
+ // Said once like the preambles, but never drawn as a note; kept in memory only, since a lost hint costs nothing.
209
+ private pendingHints: readonly string[] = [];
208
210
  private submission: { turnId: string; input: TurnInput } | null = null;
209
211
  private readonly steering = new Set<string>();
210
212
  private readonly capturedUiAccess: Map<string, unknown>;
@@ -222,8 +224,12 @@ export class ChatSession {
222
224
  clock: options.clock ?? systemClock,
223
225
  ...(options.ui ? { host: options.ui } : {}),
224
226
  emit: (event) => options.emit(event),
225
- repair: (note) => {
226
- if (!this.pendingPreambles.includes(note)) {
227
+ repair: (note, audience) => {
228
+ if (audience === 'agent') {
229
+ if (!this.pendingHints.includes(note)) {
230
+ this.pendingHints = [...this.pendingHints, note];
231
+ }
232
+ } else if (!this.pendingPreambles.includes(note)) {
227
233
  this.pendingPreambles = [...this.pendingPreambles, note];
228
234
  }
229
235
  }
@@ -828,6 +834,7 @@ export class ChatSession {
828
834
  this.options.promptNotes?.reset();
829
835
  // A cleared chat starts a new conversation, which what was waiting to be said no longer describes.
830
836
  this.pendingPreambles = [];
837
+ this.pendingHints = [];
831
838
  this.submission = null;
832
839
  this.staleResults = 0;
833
840
  this.sendAfterStop = false;
@@ -1658,9 +1665,11 @@ export class ChatSession {
1658
1665
  const preambles = this.preambles;
1659
1666
  this.pendingPreambles = [];
1660
1667
  this.submission = null;
1668
+ const hints = this.pendingHints;
1669
+ this.pendingHints = [];
1661
1670
  const read = [...preambles, ...host.shown];
1662
1671
  const joined = (parts: string[]): string | null => (parts.length === 0 ? null : parts.join('\n\n'));
1663
- return { preamble: joined([...read, ...host.heard]), note: joined(read) };
1672
+ return { preamble: joined([...read, ...host.heard, ...hints]), note: joined(read) };
1664
1673
  }
1665
1674
 
1666
1675
  private submit(turnId: string, input: TurnInput): void {
@@ -1,6 +1,6 @@
1
1
  import { createHash } from 'node:crypto';
2
2
  import { GeneratedImageInputSchema, type ChatAssistantItem, type ChatEvent, type ChatItem } from '@adecore/agent-contracts';
3
- import { uiHasFence, uiMayReferenceHost } from '@adecore/intelligent-ui';
3
+ import { uiHasFence, uiMayReferenceHost, type UiDiagnostic } from '@adecore/intelligent-ui';
4
4
  import type { UiCompileOptions } from '@adecore/intelligent-ui/compiler';
5
5
  import { UiStream, type UiStreamPreview } from '@adecore/intelligent-ui/stream';
6
6
  import type { OutboxClock } from '../outbox/outbox-worker.ts';
@@ -21,8 +21,12 @@ export interface UiRepliesOptions {
21
21
  clock: OutboxClock;
22
22
  host?: UiReplyHost;
23
23
  emit(event: ChatEvent): void;
24
- // What the agent hears in front of its next prompt about a block that compiled with diagnostics.
25
- repair(note: string): void;
24
+ /*
25
+ * What the agent hears in front of its next prompt about a block that compiled with diagnostics.
26
+ * `agent` is for a warning: the person sees the block, not the note. `person` is for an error,
27
+ * which the thread shows as a note.
28
+ */
29
+ repair(note: string, audience: 'person' | 'agent'): void;
26
30
  }
27
31
 
28
32
  /*
@@ -114,21 +118,36 @@ export class UiReplies {
114
118
  .map((block) => ({ ...block, revision: createHash('sha256').update(JSON.stringify(block)).digest('hex') }));
115
119
  this.streams.delete(item.id);
116
120
  this.compiled.delete(item.id);
117
- const diagnostics = [...new Set(ui.flatMap((block) => block.diagnostics.map((diagnostic) => diagnostic.message)))].slice(0, 6);
118
- if (diagnostics.length > 0) {
119
- this.options.repair(
120
- `Your last ${this.options.fenceLanguage} block had these diagnostics: ${diagnostics.join(' ')} Use the catalog's supported props and components.`.slice(
121
- 0,
122
- 1200
123
- )
124
- );
125
- }
121
+ this.repair(ui.flatMap((block) => block.diagnostics));
126
122
  const current = thread.get(item.id);
127
123
  const completed = { ...item, ui, ...(current?.kind === 'assistant' && current.uiQueries ? { uiQueries: current.uiQueries } : {}) };
128
124
  emit(thread.upsert(completed));
129
125
  this.options.host?.observe(completed, true);
130
126
  }
131
127
 
128
+ private repair(diagnostics: UiDiagnostic[]): void {
129
+ const say = (found: UiDiagnostic[], audience: 'person' | 'agent') => {
130
+ const messages = [...new Set(found.map((diagnostic) => diagnostic.message))].slice(0, 6);
131
+ if (messages.length > 0) {
132
+ this.options.repair(
133
+ `Your last ${this.options.fenceLanguage} block had these diagnostics: ${messages.join(' ')} Use the catalog's supported props and components.`.slice(
134
+ 0,
135
+ 1200
136
+ ),
137
+ audience
138
+ );
139
+ }
140
+ };
141
+ say(
142
+ diagnostics.filter((diagnostic) => diagnostic.severity !== 'warning'),
143
+ 'person'
144
+ );
145
+ say(
146
+ diagnostics.filter((diagnostic) => diagnostic.severity === 'warning'),
147
+ 'agent'
148
+ );
149
+ }
150
+
132
151
  clear(): void {
133
152
  for (const stream of this.streams.values()) {
134
153
  stream.dispose();
@@ -187,10 +187,14 @@ export class TaskStore {
187
187
  return [...new Set(ids)];
188
188
  }
189
189
 
190
- /* A parent nobody can wake any more: its settled tasks stop waiting for it. */
190
+ /*
191
+ * A parent nobody can wake any more: its settled tasks stop waiting for it. Each record is read again
192
+ * just before its write, since a task may settle while an earlier write of this loop waits.
193
+ */
191
194
  async dropWake(parentId: string): Promise<void> {
192
- for (const task of this.ofParent(parentId)) {
193
- if (task.wake === 'pending') {
195
+ for (const { id } of this.ofParent(parentId)) {
196
+ const task = this.tasks.get(id);
197
+ if (task && task.wake === 'pending') {
194
198
  await this.write({ ...task, wake: 'none' });
195
199
  }
196
200
  }