@amaster.ai/dsh-a2a 0.1.24 → 0.1.26

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/index.js CHANGED
@@ -16,17 +16,20 @@ import { ServerCallContext } from '@a2a-js/sdk/server';
16
16
  import Schema from '@deepseek-ai/schemastery';
17
17
  import { A2aBridge } from './bridge.js';
18
18
  import { DshAgentExecutor } from './executor.js';
19
+ import { INBOX_EXTENSION, NativeInbox } from './inbox.js';
19
20
  import { startA2aServer } from './server.js';
20
21
  import { GcsTaskStore } from './stores/gcs.js';
21
22
  import { RedisTaskStore } from './stores/redis.js';
22
23
  import { MemoryTaskStore, SanitizedTaskStore } from './task-store.js';
23
24
  export { defaultApprovalCodec } from './approval.js';
25
+ export { INBOX_EXTENSION } from './inbox.js';
24
26
  /** Event emitted when one A2A user message enters the dsh inbox. */
25
27
  export const A2A_MESSAGE_ADMITTED_EVENT = 'a2a/message-admitted';
26
28
  export const name = 'dsh-a2a';
27
29
  /** The bridge creates and owns agents through the registry service. */
28
30
  export const inject = ['agents'];
29
31
  export const Config = Schema.object({
32
+ inbox: Schema.object({ enabled: Schema.boolean().default(false) }).description('Use durable native inbox execution (requires the upstream native inbox capability).'),
30
33
  enabled: Schema.boolean().default(false),
31
34
  host: Schema.string().default('127.0.0.1'),
32
35
  port: Schema.natural().default(41241),
@@ -108,7 +111,10 @@ export function apply(ctx, config) {
108
111
  subagentTools: config.subagentTools,
109
112
  });
110
113
  const store = createTaskStore(config);
114
+ const inbox = config.inbox?.enabled ? new NativeInbox(ctx, bridge, store) : undefined;
111
115
  await store.init?.();
116
+ if (inbox)
117
+ ctx.provide('a2aInbox', inbox);
112
118
  ctx.provide('a2aTasks', {
113
119
  async clearContext(contextId, tenant = '') {
114
120
  const call = new ServerCallContext({ tenant });
@@ -136,6 +142,16 @@ export function apply(ctx, config) {
136
142
  });
137
143
  const executor = new DshAgentExecutor(bridge, (taskId) => store.suppress(taskId));
138
144
  const server = await startA2aServer({
145
+ extensions: inbox
146
+ ? [
147
+ {
148
+ uri: INBOX_EXTENSION,
149
+ description: 'Native inbox steering and parked admission',
150
+ required: false,
151
+ params: undefined,
152
+ },
153
+ ]
154
+ : [],
139
155
  host: config.host,
140
156
  port: config.port,
141
157
  basePath: config.basePath,
@@ -148,16 +164,35 @@ export function apply(ctx, config) {
148
164
  },
149
165
  executor,
150
166
  taskStore: store,
167
+ beforeMessage: inbox
168
+ ? (message, context) => inbox.beforeMessage(message, context)
169
+ : undefined,
170
+ beforeTask: inbox ? (taskId, context) => inbox.beforeTask(taskId, context) : undefined,
151
171
  approvalGuard: (message) => bridge.reserveApprovalMessage(message),
152
172
  beforeWorkingTaskMessage: (message, headers) => bridge.beforeWorkingTaskMessage(message, headers),
153
173
  });
154
174
  serverPort = server.port;
175
+ if (inbox)
176
+ inbox.server = server;
155
177
  console.log(`[dsh-a2a] A2A endpoint: http://${config.host}:${server.port}${config.basePath}/ ` +
156
178
  `(agent card: /.well-known/agent-card.json)`);
157
179
  return async () => {
158
- await server.close();
159
- await bridge.dispose();
160
- await store.close?.();
180
+ const failures = [];
181
+ for (const cleanup of [
182
+ () => inbox?.shutdown(),
183
+ () => server.close(),
184
+ () => bridge.dispose(),
185
+ () => store.close?.(),
186
+ ]) {
187
+ try {
188
+ await cleanup();
189
+ }
190
+ catch (error) {
191
+ failures.push(error);
192
+ }
193
+ }
194
+ if (failures.length)
195
+ throw new AggregateError(failures, 'A2A shutdown failed.');
161
196
  };
162
197
  }
163
198
  }
@@ -62,7 +62,7 @@ export function legacyStreamHandler(requestHandler) {
62
62
  return;
63
63
  for await (const event of stream) {
64
64
  if (res.destroyed)
65
- break;
65
+ continue; // Keep the SDK consuming results after its client leaves.
66
66
  if (write(event))
67
67
  return;
68
68
  }
package/lib/server.js CHANGED
@@ -16,7 +16,7 @@ import { createServer } from 'node:http';
16
16
  import { A2A_PROTOCOL_VERSION, AGENT_CARD_PATH, TaskState, } from '@a2a-js/sdk';
17
17
  import { duplicateInterfacesForLegacy } from '@a2a-js/sdk/compat/v0_3';
18
18
  import { RequestMalformedError } from '@a2a-js/sdk/errors';
19
- import { DefaultExecutionEventBus, DefaultRequestHandler, ServerCallContext, STATE_HEADERS_KEY, } from '@a2a-js/sdk/server';
19
+ import { DefaultExecutionEventBus, DefaultRequestHandler, ExecutionEventQueue, ResultManager, ServerCallContext, STATE_HEADERS_KEY, } from '@a2a-js/sdk/server';
20
20
  import { agentCardHandler, jsonRpcHandler, UserBuilder } from '@a2a-js/sdk/server/express';
21
21
  import express from 'express';
22
22
  import { legacyStreamHandler } from './legacy-stream.js';
@@ -59,7 +59,7 @@ export async function startA2aServer(options) {
59
59
  capabilities: {
60
60
  streaming: true,
61
61
  pushNotifications: false,
62
- extensions: [],
62
+ extensions: options.extensions ?? [],
63
63
  extendedAgentCard: false,
64
64
  },
65
65
  securitySchemes: {},
@@ -189,22 +189,39 @@ export async function startA2aServer(options) {
189
189
  }
190
190
  };
191
191
  const requestHandler = new (class extends DefaultRequestHandler {
192
+ async beforeMessage(message, context) {
193
+ if (!message)
194
+ return;
195
+ try {
196
+ return await options.beforeMessage?.(message, context);
197
+ }
198
+ catch (error) {
199
+ throw new RequestMalformedError(error instanceof Error ? error.message : String(error));
200
+ }
201
+ }
192
202
  async sendMessage(...args) {
193
203
  const release = guardApproval(args[0].message);
204
+ let releaseAdmission;
194
205
  try {
195
206
  if (!release)
196
207
  await beforeWorkingTaskMessage(args[0].message, args[1]);
208
+ if (!release)
209
+ releaseAdmission = await this.beforeMessage(args[0].message, args[1]);
197
210
  return await approvalReply.run(Boolean(release), () => super.sendMessage(...args));
198
211
  }
199
212
  finally {
213
+ releaseAdmission?.();
200
214
  release?.();
201
215
  }
202
216
  }
203
217
  async *sendMessageStream(...args) {
204
218
  const release = guardApproval(args[0].message);
219
+ let releaseAdmission;
205
220
  try {
206
221
  if (!release)
207
222
  await beforeWorkingTaskMessage(args[0].message, args[1]);
223
+ if (!release)
224
+ releaseAdmission = await this.beforeMessage(args[0].message, args[1]);
208
225
  const stream = observeTask(super.sendMessageStream(...args));
209
226
  try {
210
227
  if (release) {
@@ -220,10 +237,16 @@ export async function startA2aServer(options) {
220
237
  }
221
238
  }
222
239
  finally {
240
+ releaseAdmission?.();
223
241
  release?.();
224
242
  }
225
243
  }
244
+ async cancelTask(...args) {
245
+ await options.beforeTask?.(args[0].id, args[1]);
246
+ return super.cancelTask(...args);
247
+ }
226
248
  async *resubscribe(...args) {
249
+ await options.beforeTask?.(args[0].id, args[1]);
227
250
  const task = taskBuses.get(args[0].id);
228
251
  if (!task) {
229
252
  yield* observeTask(super.resubscribe(...args));
@@ -445,6 +468,24 @@ export async function startA2aServer(options) {
445
468
  server.listen(options.port, options.host, () => resolve());
446
469
  });
447
470
  return {
471
+ restoreTask(task, context) {
472
+ const bus = busManager.createOrGetByTaskId(task.id);
473
+ const queue = new ExecutionEventQueue(bus);
474
+ const result = new ResultManager(options.taskStore, context);
475
+ void (async () => {
476
+ try {
477
+ for await (const event of queue.events())
478
+ await result.processEvent(event);
479
+ }
480
+ catch (error) {
481
+ console.error('[dsh-a2a] restored task persistence failed:', error);
482
+ }
483
+ finally {
484
+ queue.stop();
485
+ }
486
+ })();
487
+ return bus;
488
+ },
448
489
  port: server.address().port,
449
490
  close: () => new Promise((resolve) => {
450
491
  for (const stop of stopHeartbeats)
package/lib/task-store.js CHANGED
@@ -63,6 +63,8 @@ export class SanitizedTaskStore {
63
63
  inner;
64
64
  lastState = new Map();
65
65
  writes = new Map();
66
+ // Terminal task ids stay guarded until unload, like deleted-id tombstones.
67
+ terminalStates = new Map();
66
68
  liveHistory = new Map();
67
69
  // ponytail: deleted ids stay tombstoned until plugin unload; use expiring ids if clear volume grows.
68
70
  deleted = new Set();
@@ -73,6 +75,11 @@ export class SanitizedTaskStore {
73
75
  await this.inner.init?.();
74
76
  }
75
77
  async close() {
78
+ let pending;
79
+ do {
80
+ pending = [...this.writes.values()];
81
+ await Promise.all(pending);
82
+ } while ([...this.writes.values()].some((write) => !pending.includes(write)));
76
83
  this.liveHistory.clear();
77
84
  await this.inner.close?.();
78
85
  }
@@ -88,9 +95,24 @@ export class SanitizedTaskStore {
88
95
  this.lastState.delete(taskId);
89
96
  this.writes.delete(taskId);
90
97
  }
91
- async save(task, context) {
98
+ checkpoint(task, context) {
99
+ return this.save(task, context, true);
100
+ }
101
+ async save(task, context, checkpoint = false) {
92
102
  if (this.deleted.has(task.id))
93
103
  return;
104
+ const state = task.status?.state;
105
+ const terminal = this.terminalStates.get(task.id);
106
+ if (terminal !== undefined && state !== terminal)
107
+ return;
108
+ if (state !== undefined &&
109
+ [
110
+ TaskState.TASK_STATE_COMPLETED,
111
+ TaskState.TASK_STATE_CANCELED,
112
+ TaskState.TASK_STATE_FAILED,
113
+ TaskState.TASK_STATE_REJECTED,
114
+ ].includes(state))
115
+ this.terminalStates.set(task.id, state);
94
116
  const history = structuredClone(task.history ?? []);
95
117
  const current = task.status?.message;
96
118
  if (task.status?.state === TaskState.TASK_STATE_WORKING && current) {
@@ -120,23 +142,35 @@ export class SanitizedTaskStore {
120
142
  else
121
143
  history.push(final);
122
144
  }
123
- this.liveHistory.set(task.id, {
124
- scope: scopeOf(context),
125
- history,
126
- });
127
- const state = task.status?.state;
128
- if (state !== undefined && this.lastState.get(task.id) === state)
145
+ const previousHistory = this.liveHistory.get(task.id);
146
+ const live = { scope: scopeOf(context), history };
147
+ this.liveHistory.set(task.id, live);
148
+ if (!checkpoint && state !== undefined && this.lastState.get(task.id) === state)
129
149
  return;
130
150
  const write = (this.writes.get(task.id) ?? Promise.resolve()).then(() => this.inner.save(sanitizeTask({ ...task, history }), context));
131
- this.writes.set(task.id, write.catch(() => { }));
132
- await write;
151
+ const tail = write.catch(() => { });
152
+ this.writes.set(task.id, tail);
153
+ try {
154
+ await write;
155
+ }
156
+ catch (error) {
157
+ if (this.liveHistory.get(task.id) === live) {
158
+ if (previousHistory)
159
+ this.liveHistory.set(task.id, previousHistory);
160
+ else
161
+ this.liveHistory.delete(task.id);
162
+ }
163
+ throw error;
164
+ }
133
165
  if (state === TaskState.TASK_STATE_WORKING || state === TaskState.TASK_STATE_SUBMITTED) {
134
166
  this.lastState.set(task.id, state);
135
167
  }
136
168
  else {
137
- this.lastState.delete(task.id);
138
- this.writes.delete(task.id);
139
- this.liveHistory.delete(task.id);
169
+ if (this.writes.get(task.id) === tail) {
170
+ this.lastState.delete(task.id);
171
+ this.writes.delete(task.id);
172
+ this.liveHistory.delete(task.id);
173
+ }
140
174
  }
141
175
  }
142
176
  async load(taskId, context) {
@@ -19,6 +19,7 @@ import type { Context } from '@deepseek-ai/cordis';
19
19
  import type { AgentHandle } from '@deepseek-ai/dsh-agent';
20
20
  import type { ContentBlock } from '@deepseek-ai/dsh-llm/types';
21
21
  import { SessionId } from '@deepseek-ai/dsh-session/types';
22
+ import type { NativeInbox } from './inbox.js';
22
23
  import { SessionTranslator } from './translator.js';
23
24
  export interface BridgeOptions {
24
25
  /** Absolute working directory for spawned agents. */
@@ -63,6 +64,7 @@ export interface TaskEntry {
63
64
  export declare class A2aBridge {
64
65
  private readonly ctx;
65
66
  private readonly options;
67
+ inbox?: NativeInbox;
66
68
  private readonly tasks;
67
69
  private readonly bySession;
68
70
  private readonly clearing;
@@ -100,6 +102,8 @@ export declare class A2aBridge {
100
102
  };
101
103
  private findApproval;
102
104
  waitForTurn(entry: TaskEntry): Promise<void>;
105
+ /** The native inbox selects task ownership before assembling its first prompt. */
106
+ bindInboxTask(entry: TaskEntry, taskId: string, translator: SessionTranslator, bus: ExecutionEventBus, completion: Promise<void>, controller: AbortController): void;
103
107
  /** Inspect a message for a live task before the SDK starts its next request. */
104
108
  beforeWorkingTaskMessage(a2aMessage: Message, requestHeaders: unknown): Promise<void>;
105
109
  /** Include content preparation in the work a context clear must drain. */
@@ -0,0 +1,149 @@
1
+ import { Message } from '@a2a-js/sdk';
2
+ import { type ExecutionEventBus, type RequestContext, ServerCallContext } from '@a2a-js/sdk/server';
3
+ import type { Context } from '@deepseek-ai/cordis';
4
+ import type { Agent, AssistantStreamFrame, InboxTarget } from '@deepseek-ai/dsh-agent';
5
+ import type { ContentBlock, UserMessage } from '@deepseek-ai/dsh-llm/types';
6
+ import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
7
+ import type { A2aBridge } from './bridge.js';
8
+ import type { A2aTurnContext } from './index.js';
9
+ import type { A2aServer } from './server.js';
10
+ import type { ManagedTaskStore } from './task-store.js';
11
+ export declare const INBOX_EXTENSION = "https://github.com/TGYD-helige/dsh-plugins/blob/main/docs/protocol/inbox-v1.md";
12
+ export interface A2aInboxEvent {
13
+ phase: string;
14
+ contextId: string;
15
+ at: number;
16
+ pending: number;
17
+ target?: 'next-turn' | 'next-step';
18
+ input?: string;
19
+ position?: number;
20
+ paused?: boolean;
21
+ agentStatus?: string;
22
+ taskId?: string;
23
+ taskIds?: string[];
24
+ messageId?: string;
25
+ referenceTaskIds?: string[];
26
+ nativeMessageId?: string;
27
+ queuedAt?: number;
28
+ selectedAt?: number;
29
+ turn?: number;
30
+ step?: number;
31
+ }
32
+ interface InboxSource {
33
+ kind: 'a2a';
34
+ taskId: string;
35
+ /** JSON encoding removes protobuf optional undefined fields before durable admission. */
36
+ message: string;
37
+ target: InboxTarget;
38
+ queuedAt?: number;
39
+ }
40
+ declare module '@deepseek-ai/dsh-llm/types' {
41
+ interface MessageSourceMap {
42
+ a2a: InboxSource;
43
+ }
44
+ }
45
+ declare module '@deepseek-ai/dsh-session/types' {
46
+ interface SessionEventMap {
47
+ 'agent/inbox/paused': {
48
+ paused: boolean;
49
+ };
50
+ }
51
+ }
52
+ declare module '@deepseek-ai/cordis' {
53
+ interface Events {
54
+ /** Native inbox lifecycle; content consumers must honor their capture settings. */
55
+ 'a2a/inbox'(detail: A2aInboxEvent): void;
56
+ 'agent/before-assembly'(detail: {
57
+ agent: Agent;
58
+ messages: UserMessage[];
59
+ turn: number;
60
+ step: number;
61
+ signal: AbortSignal;
62
+ }): void | Promise<void>;
63
+ /** Selected input, before system-prompt assembly; restored input has no original headers. */
64
+ 'a2a/before-assembly'(detail: A2aTurnContext): void | Promise<void>;
65
+ }
66
+ }
67
+ /** Same-process queue control; wire messages and task subscriptions remain standard A2A. */
68
+ export interface A2aInbox {
69
+ resolve(contextId: string): Promise<Agent>;
70
+ content(message: Message, contextId: string): Promise<ContentBlock[]>;
71
+ /** Admit a same-process host follow-up under an existing task's durable identity. */
72
+ enqueue(taskId: string, message: Message, options?: {
73
+ target?: InboxTarget;
74
+ wakeup?: boolean;
75
+ }): Promise<void>;
76
+ pause(contextId: string): Promise<void>;
77
+ /** Cancellation is terminal; retained pending input stays paused until explicit resume. */
78
+ cancel(taskId: string, options?: {
79
+ retainInbox?: boolean;
80
+ }): Promise<string | undefined>;
81
+ /** Recovery does not override a persisted pause unless resume is explicitly true. */
82
+ wake(contextId: string, options?: {
83
+ resume?: boolean;
84
+ context?: ServerCallContext;
85
+ }): Promise<string[]>;
86
+ }
87
+ /** Fold native input mutations; a claimed input is recoverable only before user/message. */
88
+ export declare function recoverInbox(events: readonly SessionEvent[]): {
89
+ paused: boolean;
90
+ pending: UserMessage[];
91
+ committed: Set<string>;
92
+ };
93
+ export declare class NativeInbox implements A2aInbox {
94
+ private readonly ctx;
95
+ private readonly bridge;
96
+ private readonly store;
97
+ private readonly contexts;
98
+ private readonly resolving;
99
+ private stopping;
100
+ private shutdownPromise?;
101
+ private readonly waking;
102
+ private readonly preadmitted;
103
+ server?: A2aServer;
104
+ constructor(ctx: Context, bridge: A2aBridge, store: ManagedTaskStore);
105
+ content(message: Message, contextId: string): Promise<ContentBlock[]>;
106
+ resolve(contextId: string): Promise<Agent>;
107
+ enqueue(taskId: string, message: Message, options?: {
108
+ target?: InboxTarget;
109
+ wakeup?: boolean;
110
+ }): Promise<void>;
111
+ private context;
112
+ private observe;
113
+ private observeInput;
114
+ private flush;
115
+ private checkpoint;
116
+ private pending;
117
+ private task;
118
+ private controls;
119
+ /** Reject follow-up admission at the request boundary, without terminating accepted work. */
120
+ beforeMessage(message: Message, context: ServerCallContext): Promise<undefined | (() => void)>;
121
+ /** Reattach a cold task before the SDK decides whether cancellation has a live executor. */
122
+ beforeTask(taskId: string, context: ServerCallContext): Promise<void>;
123
+ private outcome;
124
+ private terminal;
125
+ private admit;
126
+ execute(request: RequestContext, bus: ExecutionEventBus): Promise<void>;
127
+ private restoreTask;
128
+ private drive;
129
+ private prepare;
130
+ private publish;
131
+ onSessionEvent(session: Session, event: SessionEvent): boolean;
132
+ private settle;
133
+ onAssistantStream(agent: Agent, frame: AssistantStreamFrame): boolean;
134
+ private fail;
135
+ pause(contextId: string): Promise<void>;
136
+ wake(contextId: string, options?: {
137
+ resume?: boolean;
138
+ context?: ServerCallContext;
139
+ }): Promise<string[]>;
140
+ private wakeInner;
141
+ cancel(taskId: string, options?: {
142
+ retainInbox?: boolean;
143
+ }): Promise<string | undefined>;
144
+ private cancelTask;
145
+ clear(contextId: string): Promise<void>;
146
+ onDisposed(session: Session): void;
147
+ shutdown(): Promise<void>;
148
+ }
149
+ export {};
@@ -19,6 +19,8 @@ import Schema from '@deepseek-ai/schemastery';
19
19
  export type { A2aApproval, A2aApprovalCodec, A2aApprovalReply } from './approval.js';
20
20
  export { defaultApprovalCodec } from './approval.js';
21
21
  export type { A2aFileMaterializer } from './content.js';
22
+ export type { A2aInbox, A2aInboxEvent } from './inbox.js';
23
+ export { INBOX_EXTENSION } from './inbox.js';
22
24
  export type { SubagentToolSource } from './translator.js';
23
25
  /** Event emitted when one A2A user message enters the dsh inbox. */
24
26
  export declare const A2A_MESSAGE_ADMITTED_EVENT: "a2a/message-admitted";
@@ -67,6 +69,11 @@ export declare const name = "dsh-a2a";
67
69
  /** The bridge creates and owns agents through the registry service. */
68
70
  export declare const inject: string[];
69
71
  export declare const Config: Schema<Schemastery.ObjectS<{
72
+ inbox: Schema<Schemastery.ObjectS<{
73
+ enabled: Schema<boolean, boolean>;
74
+ }>, Schemastery.ObjectT<{
75
+ enabled: Schema<boolean, boolean>;
76
+ }>>;
70
77
  enabled: Schema<boolean, boolean>;
71
78
  host: Schema<string, string>;
72
79
  port: Schema<number, number>;
@@ -144,6 +151,11 @@ export declare const Config: Schema<Schemastery.ObjectS<{
144
151
  keyFilename: Schema<string, string>;
145
152
  }>>;
146
153
  }>, Schemastery.ObjectT<{
154
+ inbox: Schema<Schemastery.ObjectS<{
155
+ enabled: Schema<boolean, boolean>;
156
+ }>, Schemastery.ObjectT<{
157
+ enabled: Schema<boolean, boolean>;
158
+ }>>;
147
159
  enabled: Schema<boolean, boolean>;
148
160
  host: Schema<string, string>;
149
161
  port: Schema<number, number>;
@@ -222,6 +234,9 @@ export declare const Config: Schema<Schemastery.ObjectS<{
222
234
  }>>;
223
235
  }>>;
224
236
  export interface A2aPluginConfig {
237
+ inbox?: {
238
+ enabled: boolean;
239
+ };
225
240
  enabled: boolean;
226
241
  host: string;
227
242
  port: number;
@@ -11,9 +11,10 @@
11
11
  * The SSE framing and error envelopes are the SDK's; this layer only binds
12
12
  * the server, builds the card, and optionally adds transport heartbeats.
13
13
  */
14
- import { type Message } from '@a2a-js/sdk';
15
- import { type AgentExecutor, type TaskStore } from '@a2a-js/sdk/server';
14
+ import { type AgentExtension, type Message, type Task } from '@a2a-js/sdk';
15
+ import { type AgentExecutor, DefaultExecutionEventBus, ServerCallContext, type TaskStore } from '@a2a-js/sdk/server';
16
16
  export interface A2aServerOptions {
17
+ extensions?: AgentExtension[];
17
18
  host: string;
18
19
  port: number;
19
20
  basePath: string;
@@ -32,8 +33,12 @@ export interface A2aServerOptions {
32
33
  };
33
34
  approvalGuard?: (message: Message) => undefined | (() => void);
34
35
  beforeWorkingTaskMessage?: (message: Message, requestHeaders: unknown) => Promise<void>;
36
+ beforeMessage?: (message: Message, context: ServerCallContext) => Promise<undefined | (() => void)>;
37
+ beforeTask?: (taskId: string, context: ServerCallContext) => Promise<void>;
35
38
  }
36
39
  export interface A2aServer {
40
+ /** Reattach persistence and task subscriptions without admitting another message. */
41
+ restoreTask(task: Task, context: ServerCallContext): DefaultExecutionEventBus;
37
42
  /** The bound port (differs from options.port when 0). */
38
43
  port: number;
39
44
  close(): Promise<void>;
@@ -21,6 +21,8 @@ import { type ServerCallContext, type TaskStore } from '@a2a-js/sdk/server';
21
21
  export declare function sanitizeTask(task: Task): Task;
22
22
  /** TaskStore with the optional lifecycle our backends add. */
23
23
  export interface ManagedTaskStore extends TaskStore {
24
+ /** Durable admission checkpoint even when task state has not changed. */
25
+ checkpoint?(task: Task, context: ServerCallContext): Promise<void>;
24
26
  init?(): Promise<void>;
25
27
  delete(taskId: string, context: ServerCallContext): Promise<void>;
26
28
  close?(): Promise<void>;
@@ -41,6 +43,7 @@ export declare class SanitizedTaskStore implements ManagedTaskStore {
41
43
  private readonly inner;
42
44
  private readonly lastState;
43
45
  private readonly writes;
46
+ private readonly terminalStates;
44
47
  private readonly liveHistory;
45
48
  private readonly deleted;
46
49
  constructor(inner: ManagedTaskStore);
@@ -49,7 +52,8 @@ export declare class SanitizedTaskStore implements ManagedTaskStore {
49
52
  /** Drop SDK saves that may arrive after a clear rejects an in-flight request. */
50
53
  suppress(taskId: string): void;
51
54
  delete(taskId: string, context: ServerCallContext): Promise<void>;
52
- save(task: Task, context: ServerCallContext): Promise<void>;
55
+ checkpoint(task: Task, context: ServerCallContext): Promise<void>;
56
+ save(task: Task, context: ServerCallContext, checkpoint?: boolean): Promise<void>;
53
57
  load(taskId: string, context: ServerCallContext): Promise<Task | undefined>;
54
58
  list(params: ListTasksRequest, context: ServerCallContext): Promise<ListTasksResponse>;
55
59
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amaster.ai/dsh-a2a",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
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",