@amaster.ai/dsh-a2a 0.1.34 → 0.1.36

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/task-store.js CHANGED
@@ -85,12 +85,12 @@ export class MemoryTaskStore {
85
85
  this.bucket(context).delete(taskId);
86
86
  }
87
87
  }
88
+ const TERMINAL_REOPEN_GUARD_MS = 24 * 60 * 60 * 1000;
88
89
  /** Saves task snapshots on state transitions while keeping active deltas in process. */
89
90
  export class SanitizedTaskStore {
90
91
  inner;
91
92
  lastState = new Map();
92
93
  writes = new Map();
93
- // Terminal task ids stay guarded until unload, like deleted-id tombstones.
94
94
  terminalStates = new Map();
95
95
  liveHistory = new Map();
96
96
  // ponytail: deleted ids stay tombstoned until plugin unload; use expiring ids if clear volume grows.
@@ -108,6 +108,7 @@ export class SanitizedTaskStore {
108
108
  await Promise.all(pending);
109
109
  } while ([...this.writes.values()].some((write) => !pending.includes(write)));
110
110
  this.liveHistory.clear();
111
+ this.terminalStates.clear();
111
112
  await this.inner.close?.();
112
113
  }
113
114
  /** Drop SDK saves that may arrive after a clear rejects an in-flight request. */
@@ -121,6 +122,7 @@ export class SanitizedTaskStore {
121
122
  await this.inner.delete(taskId, context);
122
123
  this.lastState.delete(taskId);
123
124
  this.writes.delete(taskId);
125
+ this.terminalStates.delete(taskId);
124
126
  }
125
127
  checkpoint(task, context) {
126
128
  return this.save(task, context, true);
@@ -129,8 +131,11 @@ export class SanitizedTaskStore {
129
131
  if (this.deleted.has(task.id))
130
132
  return;
131
133
  const state = task.status?.state;
134
+ const now = Date.now();
132
135
  const terminal = this.terminalStates.get(task.id);
133
- if (terminal !== undefined && state !== terminal)
136
+ if (terminal && terminal.expiresAt <= now)
137
+ this.terminalStates.delete(task.id);
138
+ else if (terminal && state !== terminal.state)
134
139
  return;
135
140
  if (state !== undefined &&
136
141
  [
@@ -138,8 +143,21 @@ export class SanitizedTaskStore {
138
143
  TaskState.TASK_STATE_CANCELED,
139
144
  TaskState.TASK_STATE_FAILED,
140
145
  TaskState.TASK_STATE_REJECTED,
141
- ].includes(state))
142
- this.terminalStates.set(task.id, state);
146
+ ].includes(state)) {
147
+ // Map order matches expiry order: only the expired prefix needs scanning.
148
+ for (const [id, guard] of this.terminalStates) {
149
+ if (guard.expiresAt > now)
150
+ break;
151
+ this.terminalStates.delete(id);
152
+ }
153
+ // Re-registering an id refreshes both its deadline and insertion order.
154
+ this.terminalStates.delete(task.id);
155
+ this.terminalStates.set(task.id, {
156
+ state,
157
+ expiresAt: now + TERMINAL_REOPEN_GUARD_MS,
158
+ });
159
+ }
160
+ // shortcut: WORKING deltas clone and concatenate per event; use chunk accumulators if long streams make this hot path measurable.
143
161
  const history = structuredClone(task.history ?? []);
144
162
  const current = task.status?.message;
145
163
  if (current?.role === Role.ROLE_USER)
@@ -0,0 +1,10 @@
1
+ export type AdmissionRelease = (onCurrent?: () => void) => void;
2
+ export interface Admission {
3
+ ready: Promise<void>;
4
+ release: AdmissionRelease;
5
+ }
6
+ /** Small keyed FIFO gate. Cleanup runs only when the releasing tail is still current. */
7
+ export declare class AdmissionGate {
8
+ private readonly tails;
9
+ acquire(key: string): Admission;
10
+ }
@@ -26,6 +26,10 @@ export interface BridgeOptions {
26
26
  cwd: string;
27
27
  /** Upload root for persisted file parts; absent = the OS temp dir. */
28
28
  uploadsDir?: string;
29
+ /** Allow every private URL host in a trusted intranet. */
30
+ downloadAllowPrivate?: boolean;
31
+ /** Private URL file hosts explicitly allowed by the deployment. */
32
+ downloadAllowedHosts?: readonly string[];
29
33
  /** Optional provider/model overrides for created agents. */
30
34
  agentOptions?: {
31
35
  provider?: string;
@@ -42,6 +46,8 @@ export interface TaskEntry {
42
46
  taskId: string;
43
47
  sessionId: SessionId;
44
48
  handle: AgentHandle;
49
+ /** An existing native producer retains agent lifecycle ownership. */
50
+ borrowed?: boolean;
45
51
  translator: SessionTranslator;
46
52
  /** The in-flight execute()'s bus; null between turns. */
47
53
  bus: ExecutionEventBus | null;
@@ -69,6 +75,7 @@ export declare class A2aBridge {
69
75
  private readonly bySession;
70
76
  private readonly clearing;
71
77
  private readonly creating;
78
+ private readonly admissions;
72
79
  private readonly executing;
73
80
  private readonly asked;
74
81
  private readonly approvals;
@@ -88,6 +95,7 @@ export declare class A2aBridge {
88
95
  freshTask: boolean;
89
96
  }>;
90
97
  private ensureTaskInner;
98
+ private createAgent;
91
99
  /** A2A parts → dsh content blocks; throws only when no part is usable. */
92
100
  buildContent(message: Message, contextId: string): Promise<ContentBlock[]>;
93
101
  isClearing(contextId: string): boolean;
@@ -34,7 +34,13 @@ interface Materialization {
34
34
  contextId: string;
35
35
  materializer: A2aFileMaterializer;
36
36
  }
37
- export declare function buildMessageContent(parts: readonly Part[], attachments: AttachmentStore | undefined, dir: string, materialization?: Materialization): Promise<ContentBlock[]>;
37
+ export interface UrlDownloadOptions {
38
+ /** Allow every private/loopback host in a trusted intranet; HTTP(S), size and time limits still apply. */
39
+ allowPrivate?: boolean;
40
+ /** Private/loopback hosts are refused unless explicitly named here. */
41
+ allowedHosts?: readonly string[];
42
+ }
43
+ export declare function buildMessageContent(parts: readonly Part[], attachments: AttachmentStore | undefined, dir: string, materialization?: Materialization, downloads?: UrlDownloadOptions): Promise<ContentBlock[]>;
38
44
  /** The directory non-image file parts persist into without a materializer. */
39
45
  export declare function uploadsDir(root?: string): string;
40
46
  export {};
@@ -22,6 +22,8 @@ import type { A2aBridge } from './bridge.js';
22
22
  export declare class DshAgentExecutor implements AgentExecutor {
23
23
  private readonly bridge;
24
24
  private readonly suppressTask?;
25
+ private readonly admissions;
26
+ private readonly admissionFailures;
25
27
  constructor(bridge: A2aBridge, suppressTask?: ((taskId: string) => void) | undefined);
26
28
  execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void>;
27
29
  private executeInner;
@@ -0,0 +1,5 @@
1
+ /** JSON-RPC method extension; standard task methods stay with the official SDK. */
2
+ import { type AgentCard } from '@a2a-js/sdk';
3
+ import type { RequestHandler } from 'express';
4
+ import { type NativeInbox } from './inbox.js';
5
+ export declare function inboxRpcHandler(inbox: NativeInbox, card: AgentCard): RequestHandler;
@@ -0,0 +1,25 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { Session } from '@deepseek-ai/dsh-session';
3
+ export interface InboxChange {
4
+ contextId: string;
5
+ version: number;
6
+ }
7
+ declare module '@deepseek-ai/cordis' {
8
+ interface Events {
9
+ 'a2a/inbox-checkpointed'(change: InboxChange): void;
10
+ }
11
+ }
12
+ /** Invalidation only: DSH owns the queue, sequence and durability barrier. */
13
+ export declare class InboxChanges {
14
+ private readonly ctx;
15
+ private readonly states;
16
+ private readonly listeners;
17
+ private readonly disposed;
18
+ private readonly detach;
19
+ private stopped;
20
+ constructor(ctx: Context, managed: (contextId: string) => boolean, enabled?: boolean);
21
+ watch(session: Session, listener: (change: InboxChange) => void, closed: () => void): () => void;
22
+ checkpoint(session: Session): Promise<InboxChange>;
23
+ close(): void;
24
+ private closeSession;
25
+ }
@@ -6,10 +6,11 @@ import type { GoalMessageSource } from '@deepseek-ai/dsh-goal';
6
6
  import type { ContentBlock } from '@deepseek-ai/dsh-llm/types';
7
7
  import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
8
8
  import type { A2aBridge } from './bridge.js';
9
+ import { InboxChanges } from './inbox-changes.js';
9
10
  import type { A2aTurnContext } from './index.js';
10
11
  import type { A2aServer } from './server.js';
11
12
  import { type ManagedTaskStore } from './task-store.js';
12
- export declare const INBOX_EXTENSION = "https://github.com/TGYD-helige/dsh-plugins/blob/main/docs/protocol/inbox-v1.md";
13
+ export declare const INBOX_EXTENSION = "https://github.com/TGYD-helige/dsh-plugins/blob/main/packages/dsh-a2a/README.md";
13
14
  export interface A2aInboxEvent {
14
15
  phase: string;
15
16
  contextId: string;
@@ -45,6 +46,13 @@ declare module '@deepseek-ai/dsh-llm/types' {
45
46
  goal: GoalMessageSource;
46
47
  }
47
48
  }
49
+ declare module '@deepseek-ai/dsh-session/types' {
50
+ interface SessionEventMap {
51
+ 'agent/inbox/paused': {
52
+ paused: boolean;
53
+ };
54
+ }
55
+ }
48
56
  declare module '@deepseek-ai/cordis' {
49
57
  interface Events {
50
58
  /** Native inbox lifecycle; content consumers must honor their capture settings. */
@@ -55,6 +63,12 @@ declare module '@deepseek-ai/cordis' {
55
63
  }
56
64
  /** Same-process queue control; wire messages and task subscriptions remain standard A2A. */
57
65
  export interface A2aInbox {
66
+ get(contextId: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
67
+ update(contextId: string, id: string, message: Message, page?: InboxPageOptions): Promise<InboxSnapshot>;
68
+ move(contextId: string, id: string, index: number, page?: InboxPageOptions): Promise<InboxSnapshot>;
69
+ remove(contextId: string, id: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
70
+ clearPending(contextId: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
71
+ executePending(contextId: string, id?: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
58
72
  resolve(contextId: string): Promise<Agent>;
59
73
  content(message: Message, contextId: string): Promise<ContentBlock[]>;
60
74
  /** Admit a same-process host follow-up under an existing task's durable identity. */
@@ -64,14 +78,42 @@ export interface A2aInbox {
64
78
  }): Promise<void>;
65
79
  /** Transfer one unclaimed independent input into the active task. */
66
80
  promote(contextId: string, messageId: string, taskId: string): Promise<void>;
81
+ /** Explicitly resume terminal retained input under a fresh task identity. */
82
+ resume(contextId: string, taskId: string): Promise<string>;
83
+ /** Pause admission and the current driver while retaining pending input. */
84
+ pause(contextId: string): Promise<void>;
85
+ /** Wake retained work in queue order without creating a replacement task. */
86
+ wake(contextId: string): Promise<string[]>;
67
87
  /** Standard cancellation is terminal and removes only this task's pending input. */
68
88
  cancel(taskId: string): Promise<string | undefined>;
69
89
  }
90
+ export interface InboxSnapshot {
91
+ contextId: string;
92
+ version: number;
93
+ nextTurn: Array<{
94
+ id: string;
95
+ message: unknown;
96
+ editable: boolean;
97
+ }>;
98
+ nextStep: Array<{
99
+ id: string;
100
+ message: unknown;
101
+ editable: boolean;
102
+ }>;
103
+ totalSize: number;
104
+ nextPageToken: string;
105
+ }
106
+ export interface InboxPageOptions {
107
+ pageSize?: number;
108
+ pageToken?: string;
109
+ }
110
+ export declare function validateInboxPage(page?: InboxPageOptions): void;
70
111
  export declare class NativeInbox implements A2aInbox {
71
112
  private readonly ctx;
72
113
  private readonly bridge;
73
114
  private readonly store;
74
115
  private readonly retainPending;
116
+ readonly changes: InboxChanges;
75
117
  private readonly contexts;
76
118
  private readonly resolving;
77
119
  private stopping;
@@ -79,10 +121,20 @@ export declare class NativeInbox implements A2aInbox {
79
121
  private readonly preadmitted;
80
122
  server?: A2aServer;
81
123
  private readonly persistence?;
82
- constructor(ctx: Context, bridge: A2aBridge, store: ManagedTaskStore, retainPending?: boolean);
124
+ constructor(ctx: Context, bridge: A2aBridge, store: ManagedTaskStore, retainPending?: boolean, notifications?: boolean);
83
125
  private unloading;
84
126
  content(message: Message, contextId: string): Promise<ContentBlock[]>;
85
127
  resolve(contextId: string): Promise<Agent>;
128
+ get(contextId: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
129
+ private editQueue;
130
+ private queued;
131
+ update(contextId: string, id: string, message: Message, page?: InboxPageOptions): Promise<InboxSnapshot>;
132
+ move(contextId: string, id: string, index: number, page?: InboxPageOptions): Promise<InboxSnapshot>;
133
+ private reorder;
134
+ private removed;
135
+ remove(contextId: string, id: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
136
+ clearPending(contextId: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
137
+ executePending(contextId: string, id?: string, page?: InboxPageOptions): Promise<InboxSnapshot>;
86
138
  enqueue(taskId: string, message: Message, options?: {
87
139
  target?: InboxTarget;
88
140
  wakeup?: boolean;
@@ -90,6 +142,7 @@ export declare class NativeInbox implements A2aInbox {
90
142
  private moveInput;
91
143
  promote(contextId: string, messageId: string, taskId: string): Promise<void>;
92
144
  private promoteInput;
145
+ resume(contextId: string, taskId: string): Promise<string>;
93
146
  /** Repair only an incomplete ownership move, never an ordinary DSH claim. */
94
147
  private recoverPromotions;
95
148
  private recoverRetained;
@@ -122,6 +175,9 @@ export declare class NativeInbox implements A2aInbox {
122
175
  private discard;
123
176
  cancel(taskId: string): Promise<string | undefined>;
124
177
  private cancelTask;
178
+ private requeueClaimed;
179
+ pause(contextId: string): Promise<void>;
180
+ wake(contextId: string): Promise<string[]>;
125
181
  clear(contextId: string): Promise<void>;
126
182
  onDisposed(session: Session): void;
127
183
  shutdown(): Promise<void>;