@amaster.ai/dsh-a2a 0.1.26 → 0.1.28

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
@@ -18,6 +18,7 @@ import { A2aBridge } from './bridge.js';
18
18
  import { DshAgentExecutor } from './executor.js';
19
19
  import { INBOX_EXTENSION, NativeInbox } from './inbox.js';
20
20
  import { startA2aServer } from './server.js';
21
+ import { FilesystemTaskStore } from './stores/filesystem.js';
21
22
  import { GcsTaskStore } from './stores/gcs.js';
22
23
  import { RedisTaskStore } from './stores/redis.js';
23
24
  import { MemoryTaskStore, SanitizedTaskStore } from './task-store.js';
@@ -29,7 +30,7 @@ export const name = 'dsh-a2a';
29
30
  /** The bridge creates and owns agents through the registry service. */
30
31
  export const inject = ['agents'];
31
32
  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).'),
33
+ inbox: Schema.object({ enabled: Schema.boolean().default(false) }).description('Use the official DSH inbox for queued turns and steering.'),
33
34
  enabled: Schema.boolean().default(false),
34
35
  host: Schema.string().default('127.0.0.1'),
35
36
  port: Schema.natural().default(41241),
@@ -70,9 +71,11 @@ export const Config = Schema.object({
70
71
  */
71
72
  taskStore: Schema.union([
72
73
  Schema.const('memory'),
74
+ Schema.const('filesystem'),
73
75
  Schema.const('redis'),
74
76
  Schema.const('gcs'),
75
77
  ]).default('memory'),
78
+ filesystem: Schema.object({ root: Schema.string().default('') }),
76
79
  redis: Schema.object({
77
80
  url: Schema.string().role('secret').default('redis://127.0.0.1:6379'),
78
81
  keyPrefix: Schema.string().default('a2a'),
@@ -198,6 +201,8 @@ export function apply(ctx, config) {
198
201
  }
199
202
  function createTaskStore(config) {
200
203
  switch (config.taskStore) {
204
+ case 'filesystem':
205
+ return new SanitizedTaskStore(new FilesystemTaskStore({ root: config.filesystem?.root ?? '' }));
201
206
  case 'redis':
202
207
  return new SanitizedTaskStore(new RedisTaskStore(config.redis));
203
208
  case 'gcs':
@@ -0,0 +1,86 @@
1
+ /** Local task snapshots, retained alongside the DSH session log. */
2
+ import { createHash, randomUUID } from 'node:crypto';
3
+ import { mkdir, open, readdir, readFile, rename, rm } from 'node:fs/promises';
4
+ import { join, resolve } from 'node:path';
5
+ import { Task } from '@a2a-js/sdk';
6
+ import { listShells, taskScopeKey } from '../task-store.js';
7
+ export class FilesystemTaskStore {
8
+ root;
9
+ constructor(config) {
10
+ if (!config.root)
11
+ throw new Error('[dsh-a2a] filesystem.root is required.');
12
+ this.root = resolve(config.root);
13
+ }
14
+ directory(context) {
15
+ return join(this.root, taskScopeKey(context));
16
+ }
17
+ path(id, context) {
18
+ return join(this.directory(context), `${createHash('sha256').update(id).digest('hex')}.json`);
19
+ }
20
+ async save(task, context) {
21
+ const directory = this.directory(context);
22
+ await mkdir(directory, { recursive: true, mode: 0o700 });
23
+ const temporary = join(directory, `${randomUUID()}.tmp`);
24
+ try {
25
+ const file = await open(temporary, 'wx', 0o600);
26
+ try {
27
+ await file.writeFile(JSON.stringify(Task.toJSON(task)));
28
+ await file.sync();
29
+ }
30
+ finally {
31
+ await file.close();
32
+ }
33
+ await rename(temporary, this.path(task.id, context));
34
+ // Windows cannot open directory handles through fs.open.
35
+ if (process.platform !== 'win32') {
36
+ const dir = await open(directory, 'r');
37
+ try {
38
+ await dir.sync();
39
+ }
40
+ finally {
41
+ await dir.close();
42
+ }
43
+ }
44
+ }
45
+ finally {
46
+ await rm(temporary, { force: true });
47
+ }
48
+ }
49
+ async load(id, context) {
50
+ try {
51
+ return Task.fromJSON(JSON.parse(await readFile(this.path(id, context), 'utf8')));
52
+ }
53
+ catch (error) {
54
+ if (error.code === 'ENOENT')
55
+ return;
56
+ throw error;
57
+ }
58
+ }
59
+ async delete(id, context) {
60
+ await rm(this.path(id, context), { force: true });
61
+ }
62
+ async list(params, context) {
63
+ let files;
64
+ try {
65
+ files = await readdir(this.directory(context));
66
+ }
67
+ catch (error) {
68
+ if (error.code !== 'ENOENT')
69
+ throw error;
70
+ files = [];
71
+ }
72
+ const tasks = [];
73
+ for (const file of files) {
74
+ if (!/^[a-f0-9]{64}\.json$/.test(file))
75
+ continue;
76
+ try {
77
+ tasks.push(Task.fromJSON(JSON.parse(await readFile(join(this.directory(context), file), 'utf8'))));
78
+ }
79
+ catch (error) {
80
+ if (error.code !== 'ENOENT')
81
+ throw error;
82
+ }
83
+ }
84
+ return listShells(tasks, params);
85
+ }
86
+ }
@@ -102,7 +102,7 @@ export declare class A2aBridge {
102
102
  };
103
103
  private findApproval;
104
104
  waitForTurn(entry: TaskEntry): Promise<void>;
105
- /** The native inbox selects task ownership before assembling its first prompt. */
105
+ /** The official inbox claim selects the task that owns the next model step. */
106
106
  bindInboxTask(entry: TaskEntry, taskId: string, translator: SessionTranslator, bus: ExecutionEventBus, completion: Promise<void>, controller: AbortController): void;
107
107
  /** Inspect a message for a live task before the SDK starts its next request. */
108
108
  beforeWorkingTaskMessage(a2aMessage: Message, requestHeaders: unknown): Promise<void>;
@@ -2,7 +2,8 @@ import { Message } from '@a2a-js/sdk';
2
2
  import { type ExecutionEventBus, type RequestContext, ServerCallContext } from '@a2a-js/sdk/server';
3
3
  import type { Context } from '@deepseek-ai/cordis';
4
4
  import type { Agent, AssistantStreamFrame, InboxTarget } from '@deepseek-ai/dsh-agent';
5
- import type { ContentBlock, UserMessage } from '@deepseek-ai/dsh-llm/types';
5
+ import type { GoalMessageSource } from '@deepseek-ai/dsh-goal';
6
+ import type { ContentBlock } from '@deepseek-ai/dsh-llm/types';
6
7
  import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
7
8
  import type { A2aBridge } from './bridge.js';
8
9
  import type { A2aTurnContext } from './index.js';
@@ -17,7 +18,6 @@ export interface A2aInboxEvent {
17
18
  target?: 'next-turn' | 'next-step';
18
19
  input?: string;
19
20
  position?: number;
20
- paused?: boolean;
21
21
  agentStatus?: string;
22
22
  taskId?: string;
23
23
  taskIds?: string[];
@@ -36,32 +36,21 @@ interface InboxSource {
36
36
  message: string;
37
37
  target: InboxTarget;
38
38
  queuedAt?: number;
39
+ promotedFrom?: string;
40
+ promotionId?: string;
39
41
  }
40
42
  declare module '@deepseek-ai/dsh-llm/types' {
41
43
  interface MessageSourceMap {
42
44
  a2a: InboxSource;
43
- }
44
- }
45
- declare module '@deepseek-ai/dsh-session/types' {
46
- interface SessionEventMap {
47
- 'agent/inbox/paused': {
48
- paused: boolean;
49
- };
45
+ goal: GoalMessageSource;
50
46
  }
51
47
  }
52
48
  declare module '@deepseek-ai/cordis' {
53
49
  interface Events {
54
50
  /** Native inbox lifecycle; content consumers must honor their capture settings. */
55
51
  '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>;
52
+ /** Selected input at the official pre-step boundary, after system-prompt assembly. */
53
+ 'a2a/before-step'(detail: A2aTurnContext): void | Promise<void>;
65
54
  }
66
55
  }
67
56
  /** Same-process queue control; wire messages and task subscriptions remain standard A2A. */
@@ -73,23 +62,11 @@ export interface A2aInbox {
73
62
  target?: InboxTarget;
74
63
  wakeup?: boolean;
75
64
  }): 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[]>;
65
+ /** Transfer one unclaimed independent input into the active task. */
66
+ promote(contextId: string, messageId: string, taskId: string): Promise<void>;
67
+ /** Standard cancellation is terminal and removes only this task's pending input. */
68
+ cancel(taskId: string): Promise<string | undefined>;
86
69
  }
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
70
  export declare class NativeInbox implements A2aInbox {
94
71
  private readonly ctx;
95
72
  private readonly bridge;
@@ -98,7 +75,6 @@ export declare class NativeInbox implements A2aInbox {
98
75
  private readonly resolving;
99
76
  private stopping;
100
77
  private shutdownPromise?;
101
- private readonly waking;
102
78
  private readonly preadmitted;
103
79
  server?: A2aServer;
104
80
  constructor(ctx: Context, bridge: A2aBridge, store: ManagedTaskStore);
@@ -108,6 +84,11 @@ export declare class NativeInbox implements A2aInbox {
108
84
  target?: InboxTarget;
109
85
  wakeup?: boolean;
110
86
  }): Promise<void>;
87
+ private moveInput;
88
+ promote(contextId: string, messageId: string, taskId: string): Promise<void>;
89
+ private promoteInput;
90
+ /** Repair only an incomplete ownership move, never an ordinary DSH claim. */
91
+ private recoverPromotions;
111
92
  private context;
112
93
  private observe;
113
94
  private observeInput;
@@ -125,22 +106,17 @@ export declare class NativeInbox implements A2aInbox {
125
106
  private admit;
126
107
  execute(request: RequestContext, bus: ExecutionEventBus): Promise<void>;
127
108
  private restoreTask;
128
- private drive;
109
+ private select;
110
+ private protocolMessage;
111
+ private owner;
129
112
  private prepare;
130
113
  private publish;
131
114
  onSessionEvent(session: Session, event: SessionEvent): boolean;
132
115
  private settle;
133
116
  onAssistantStream(agent: Agent, frame: AssistantStreamFrame): boolean;
134
117
  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>;
118
+ private discard;
119
+ cancel(taskId: string): Promise<string | undefined>;
144
120
  private cancelTask;
145
121
  clear(contextId: string): Promise<void>;
146
122
  onDisposed(session: Session): void;
@@ -131,7 +131,12 @@ export declare const Config: Schema<Schemastery.ObjectS<{
131
131
  * A2A task snapshots, including protocol history. dsh-storage separately
132
132
  * owns the full conversation log. 'memory' loses tasks on restart.
133
133
  */
134
- taskStore: Schema<"memory" | "redis" | "gcs", "memory" | "redis" | "gcs">;
134
+ taskStore: Schema<"memory" | "filesystem" | "redis" | "gcs", "memory" | "filesystem" | "redis" | "gcs">;
135
+ filesystem: Schema<Schemastery.ObjectS<{
136
+ root: Schema<string, string>;
137
+ }>, Schemastery.ObjectT<{
138
+ root: Schema<string, string>;
139
+ }>>;
135
140
  redis: Schema<Schemastery.ObjectS<{
136
141
  url: Schema<string, string>;
137
142
  keyPrefix: Schema<string, string>;
@@ -213,7 +218,12 @@ export declare const Config: Schema<Schemastery.ObjectS<{
213
218
  * A2A task snapshots, including protocol history. dsh-storage separately
214
219
  * owns the full conversation log. 'memory' loses tasks on restart.
215
220
  */
216
- taskStore: Schema<"memory" | "redis" | "gcs", "memory" | "redis" | "gcs">;
221
+ taskStore: Schema<"memory" | "filesystem" | "redis" | "gcs", "memory" | "filesystem" | "redis" | "gcs">;
222
+ filesystem: Schema<Schemastery.ObjectS<{
223
+ root: Schema<string, string>;
224
+ }>, Schemastery.ObjectT<{
225
+ root: Schema<string, string>;
226
+ }>>;
217
227
  redis: Schema<Schemastery.ObjectS<{
218
228
  url: Schema<string, string>;
219
229
  keyPrefix: Schema<string, string>;
@@ -261,7 +271,10 @@ export interface A2aPluginConfig {
261
271
  version: string;
262
272
  publicUrl: string;
263
273
  };
264
- taskStore: 'memory' | 'redis' | 'gcs';
274
+ taskStore: 'memory' | 'filesystem' | 'redis' | 'gcs';
275
+ filesystem?: {
276
+ root: string;
277
+ };
265
278
  redis: {
266
279
  url: string;
267
280
  keyPrefix: string;
@@ -0,0 +1,15 @@
1
+ import { type ListTasksRequest, type ListTasksResponse, Task } from '@a2a-js/sdk';
2
+ import type { ServerCallContext } from '@a2a-js/sdk/server';
3
+ import { type ManagedTaskStore } from '../task-store.js';
4
+ export declare class FilesystemTaskStore implements ManagedTaskStore {
5
+ private readonly root;
6
+ constructor(config: {
7
+ root: string;
8
+ });
9
+ private directory;
10
+ private path;
11
+ save(task: Task, context: ServerCallContext): Promise<void>;
12
+ load(id: string, context: ServerCallContext): Promise<Task | undefined>;
13
+ delete(id: string, context: ServerCallContext): Promise<void>;
14
+ list(params: ListTasksRequest, context: ServerCallContext): Promise<ListTasksResponse>;
15
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amaster.ai/dsh-a2a",
3
- "version": "0.1.26",
3
+ "version": "0.1.28",
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",
@@ -38,14 +38,15 @@
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@deepseek-ai/cordis": "^4.0.1",
41
- "@deepseek-ai/dsh-agent": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1",
42
- "@deepseek-ai/dsh-attachment": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1",
43
- "@deepseek-ai/dsh-llm": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1",
44
- "@deepseek-ai/dsh-session": ">=0.2.0-rc.1",
45
- "@deepseek-ai/dsh-tools": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1",
46
- "@deepseek-ai/dsh-user-approval": ">=0.1.6-alpha.2 || >=0.1.7-alpha.1 || >=0.2.0-rc.1",
41
+ "@deepseek-ai/dsh-agent": ">=0.2.0-rc.2 || >=0.2.1-alpha.1",
42
+ "@deepseek-ai/dsh-attachment": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1 || >=0.2.1-rc.1",
43
+ "@deepseek-ai/dsh-llm": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1 || >=0.2.1-rc.1",
44
+ "@deepseek-ai/dsh-session": ">=0.2.0-rc.1 || >=0.2.1-rc.1",
45
+ "@deepseek-ai/dsh-tools": ">=0.1.5-rc.1 || >=0.1.6-alpha.1 || >=0.1.7-alpha.1 || >=0.2.0-rc.1 || >=0.2.1-rc.1",
46
+ "@deepseek-ai/dsh-user-approval": ">=0.1.6-alpha.2 || >=0.1.7-alpha.1 || >=0.2.0-rc.1 || >=0.2.1-rc.1",
47
47
  "@google-cloud/storage": "^7.0.0",
48
- "ioredis": "^5.0.0"
48
+ "ioredis": "^5.0.0",
49
+ "@deepseek-ai/dsh-goal": ">=0.2.0-rc.2 || >=0.2.1-alpha.1"
49
50
  },
50
51
  "peerDependenciesMeta": {
51
52
  "@deepseek-ai/dsh-attachment": {
@@ -59,24 +60,28 @@
59
60
  },
60
61
  "ioredis": {
61
62
  "optional": true
63
+ },
64
+ "@deepseek-ai/dsh-goal": {
65
+ "optional": true
62
66
  }
63
67
  },
64
68
  "devDependencies": {
65
69
  "@a2a-js/sdk-v03": "npm:@a2a-js/sdk@0.3.14",
66
- "@deepseek-ai/cordis": "^4.0.4",
67
- "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
68
- "@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
69
- "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
70
- "@deepseek-ai/dsh-session": "0.2.0-rc.2",
71
- "@deepseek-ai/dsh-session-projection": "0.2.0-rc.2",
72
- "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
73
- "@deepseek-ai/dsh-user-approval": "0.2.0-rc.2",
70
+ "@deepseek-ai/cordis": "4.0.5",
71
+ "@deepseek-ai/dsh-agent": "0.2.1-rc.2",
72
+ "@deepseek-ai/dsh-attachment": "0.2.1-rc.2",
73
+ "@deepseek-ai/dsh-llm": "0.2.1-rc.2",
74
+ "@deepseek-ai/dsh-session": "0.2.1-rc.2",
75
+ "@deepseek-ai/dsh-session-projection": "0.2.1-rc.2",
76
+ "@deepseek-ai/dsh-tools": "0.2.1-rc.2",
77
+ "@deepseek-ai/dsh-user-approval": "0.2.1-rc.2",
74
78
  "@google-cloud/storage": "^7.0.0",
75
79
  "@types/express": "^4.17.21",
76
80
  "@types/node": "^22.0.0",
77
81
  "ioredis": "^5.0.0",
78
82
  "typescript": "^5.6.3",
79
- "vitest": "^4.1.11"
83
+ "vitest": "^4.1.11",
84
+ "@deepseek-ai/dsh-goal": "0.2.1-rc.2"
80
85
  },
81
86
  "engines": {
82
87
  "node": "^20.19 || ^22.12 || >=24.0"