@amaster.ai/dsh-a2a 0.1.8 → 0.1.9

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/lib/bridge.js CHANGED
@@ -170,10 +170,10 @@ export class A2aBridge {
170
170
  }
171
171
  }
172
172
  /** Queue one user-message turn on the task's agent and await its `turn/end`. */
173
- async runTurn(entry, content, bus) {
173
+ async runTurn(entry, content, bus, a2aMessageId, requestHeaders) {
174
174
  if (this.clearing.has(entry.sessionId))
175
175
  throw new Error('Context is being cleared.');
176
- const running = this.runTurnInner(entry, content, bus);
176
+ const running = this.runTurnInner(entry, content, bus, a2aMessageId, requestHeaders);
177
177
  entry.running.add(running);
178
178
  try {
179
179
  await running;
@@ -182,7 +182,7 @@ export class A2aBridge {
182
182
  entry.running.delete(running);
183
183
  }
184
184
  }
185
- async runTurnInner(entry, content, bus) {
185
+ async runTurnInner(entry, content, bus, a2aMessageId, requestHeaders) {
186
186
  let resolveSettled;
187
187
  const settled = new Promise((resolve) => {
188
188
  resolveSettled = resolve;
@@ -190,26 +190,45 @@ export class A2aBridge {
190
190
  entry.settled.push(resolveSettled);
191
191
  entry.bus = bus;
192
192
  try {
193
- const result = entry.handle.agent.followup(createUserMessage({
194
- content,
195
- source: { kind: 'user' },
196
- }));
197
- // followup() is typed void; guard a mistyped async impl anyway — an
198
- // unhandled rejection would crash the host process, and no turn/end
199
- // would ever settle this waiter.
200
- void Promise.resolve(result).catch((error) => {
201
- const message = error instanceof Error ? error.message : String(error);
202
- console.error('[dsh-a2a] followup failed:', error);
203
- if (entry.bus === bus) {
204
- entry.bus.publish(terminalStatusUpdate(entry.taskId, entry.sessionId, 'failed', message));
205
- }
206
- resolveSettled();
193
+ const message = createUserMessage({ content, source: { kind: 'user' } });
194
+ // followup() inserts synchronously, then may claim the message before
195
+ // returning. Observe that insertion so the mapping is both real and early.
196
+ const stopListening = this.ctx.on('agent/inbox/inserted', ({ agent, message: inserted }) => {
197
+ if (agent !== entry.handle.agent || inserted.id !== message.id)
198
+ return;
199
+ stopListening();
200
+ void this.ctx
201
+ .parallel('a2a/message-admitted', {
202
+ contextId: entry.sessionId,
203
+ taskId: entry.taskId,
204
+ a2aMessageId,
205
+ dshMessageId: message.id,
206
+ requestHeaders,
207
+ })
208
+ .catch((error) => {
209
+ console.error('[dsh-a2a] message-admitted listener failed:', error);
210
+ });
207
211
  });
208
- // Never rejects: waiters only resolve. A synchronous followup() throw is
209
- // the one failure path — it means no turn ever started, so pull this
210
- // waiter back out of the FIFO before it can be settled by the NEXT
211
- // turn's turn/end (which would resolve one execute() too early).
212
- await settled;
212
+ try {
213
+ const result = entry.handle.agent.followup(message);
214
+ // followup() is typed void; guard a mistyped async impl anyway — an
215
+ // unhandled rejection would crash the host process, and no turn/end
216
+ // would ever settle this waiter.
217
+ void Promise.resolve(result).catch((error) => {
218
+ const message = error instanceof Error ? error.message : String(error);
219
+ console.error('[dsh-a2a] followup failed:', error);
220
+ if (entry.bus === bus) {
221
+ entry.bus.publish(terminalStatusUpdate(entry.taskId, entry.sessionId, 'failed', message));
222
+ }
223
+ resolveSettled();
224
+ });
225
+ // Never rejects: waiters only resolve. A synchronous followup() throw
226
+ // means no turn started, so pull its waiter from the FIFO below.
227
+ await settled;
228
+ }
229
+ finally {
230
+ stopListening();
231
+ }
213
232
  }
214
233
  catch (error) {
215
234
  const index = entry.settled.indexOf(resolveSettled);
package/lib/executor.js CHANGED
@@ -18,7 +18,7 @@
18
18
  * `cancelTask` is an arrow property because the SDK destructures it.
19
19
  */
20
20
  import { TaskState } from '@a2a-js/sdk';
21
- import { AgentEvent } from '@a2a-js/sdk/server';
21
+ import { AgentEvent, STATE_HEADERS_KEY } from '@a2a-js/sdk/server';
22
22
  import { agentTextMessage, terminalStatusUpdate } from './translator.js';
23
23
  export class DshAgentExecutor {
24
24
  bridge;
@@ -48,7 +48,8 @@ export class DshAgentExecutor {
48
48
  // stored task (history/artifacts preserved).
49
49
  taskEvent(taskId, contextId, TaskState.TASK_STATE_WORKING));
50
50
  anchored = true;
51
- await this.bridge.runTurn(entry, content, eventBus);
51
+ const requestHeaders = requestContext.context.state.get(STATE_HEADERS_KEY);
52
+ await this.bridge.runTurn(entry, content, eventBus, userMessage.messageId, requestHeaders);
52
53
  }
53
54
  catch (error) {
54
55
  if (this.bridge.isClearing(contextId))
package/lib/index.js CHANGED
@@ -20,6 +20,8 @@ import { startA2aServer } from './server.js';
20
20
  import { GcsTaskStore } from './stores/gcs.js';
21
21
  import { RedisTaskStore } from './stores/redis.js';
22
22
  import { MemoryTaskStore, SanitizedTaskStore } from './task-store.js';
23
+ /** Event emitted when one A2A user message enters the dsh inbox. */
24
+ export const A2A_MESSAGE_ADMITTED_EVENT = 'a2a/message-admitted';
23
25
  export const name = 'dsh-a2a';
24
26
  /** The bridge creates and owns agents through the registry service. */
25
27
  export const inject = ['agents'];
@@ -78,7 +78,7 @@ export declare class A2aBridge {
78
78
  /** Include content preparation in the work a context clear must drain. */
79
79
  trackExecution(contextId: string, run: () => Promise<void>): Promise<void>;
80
80
  /** Queue one user-message turn on the task's agent and await its `turn/end`. */
81
- runTurn(entry: TaskEntry, content: ContentBlock[], bus: ExecutionEventBus): Promise<void>;
81
+ runTurn(entry: TaskEntry, content: ContentBlock[], bus: ExecutionEventBus, a2aMessageId: string, requestHeaders: unknown): Promise<void>;
82
82
  private runTurnInner;
83
83
  /** Cancel and release the live binding before the caller deletes stored shells. */
84
84
  clearContext(contextId: string, clearStored?: (liveIds: string[]) => Promise<string[]>): Promise<string[]>;
@@ -14,6 +14,20 @@
14
14
  import type { Context } from '@deepseek-ai/cordis';
15
15
  import Schema from '@deepseek-ai/schemastery';
16
16
  export type { A2aFileMaterializer } from './content.js';
17
+ /** Event emitted when one A2A user message enters the dsh inbox. */
18
+ export declare const A2A_MESSAGE_ADMITTED_EVENT: "a2a/message-admitted";
19
+ declare module '@deepseek-ai/cordis' {
20
+ interface Events {
21
+ /** Link a protocol message to the dsh inbox message created for its turn. */
22
+ 'a2a/message-admitted'(detail: {
23
+ contextId: string;
24
+ taskId: string;
25
+ a2aMessageId: string;
26
+ dshMessageId: string;
27
+ requestHeaders: unknown;
28
+ }): void;
29
+ }
30
+ }
17
31
  export declare const name = "dsh-a2a";
18
32
  /** The bridge creates and owns agents through the registry service. */
19
33
  export declare const inject: string[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amaster.ai/dsh-a2a",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "A2A protocol (JSON-RPC + SSE) server plugin for DeepSeek Harness: expose dsh agents as A2A agents.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",