@nelsonlongxiang/dsh-open-a2a-net 0.6.42 → 0.7.0
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/README.md +1 -0
- package/README_zh.md +1 -0
- package/assets/nexusDist/assets/index-CA_YZTWG.js +9 -0
- package/assets/nexusDist/index.html +1 -1
- package/cli/a2a.mjs +43 -7
- package/lib/a2a-client.js +10 -2
- package/lib/admitted-bookkeeping.js +7 -0
- package/lib/canvas-discovery.js +11 -1
- package/lib/canvas-member.js +43 -0
- package/lib/canvas-store.js +315 -17
- package/lib/client.js +62 -72
- package/lib/client.js.map +1 -1
- package/lib/config.js +1 -0
- package/lib/direct-forward.js +9 -7
- package/lib/faces/admin.js +150 -67
- package/lib/faces/files.js +2 -1
- package/lib/faces/wire.js +569 -469
- package/lib/followup-delivery.js +89 -0
- package/lib/idempotency-store.js +8 -0
- package/lib/inbound-verification.js +23 -0
- package/lib/index.js +534 -166
- package/lib/nudge-observability.js +34 -0
- package/lib/origin-claim.js +36 -9
- package/lib/outbound-scope.js +4 -2
- package/lib/peer-directory.js +15 -5
- package/lib/peer-store.js +31 -17
- package/lib/queue-guard.js +4 -3
- package/lib/receipt-ladder.js +1 -1
- package/lib/receipt-retry.js +58 -0
- package/lib/reconciliation-reminders.js +148 -0
- package/lib/reconciliation-state.js +27 -0
- package/lib/session-transcript.js +46 -18
- package/lib/skill/SKILL.md +5 -0
- package/lib/skill/references/cli.md +4 -1
- package/lib/skill/references/routing.md +31 -1
- package/lib/task-auth.js +3 -3
- package/lib/task-delivery.js +23 -0
- package/lib/task-ledger-persistence.js +51 -0
- package/lib/task-ledger-rows.js +5 -0
- package/lib/task-ledger.js +219 -20
- package/lib/tools/info.js +37 -7
- package/lib/types/a2a-client.d.ts +1 -1
- package/lib/types/admitted-bookkeeping.d.ts +5 -0
- package/lib/types/canvas-member.d.ts +10 -0
- package/lib/types/canvas-store.d.ts +56 -1
- package/lib/types/config.d.ts +2 -0
- package/lib/types/faces/admin.d.ts +21 -0
- package/lib/types/faces/files.d.ts +1 -1
- package/lib/types/faces/wire.d.ts +21 -14
- package/lib/types/followup-delivery.d.ts +25 -0
- package/lib/types/idempotency-store.d.ts +2 -0
- package/lib/types/inbound-verification.d.ts +14 -0
- package/lib/types/index.d.ts +5 -0
- package/lib/types/nudge-observability.d.ts +17 -0
- package/lib/types/origin-claim.d.ts +7 -1
- package/lib/types/outbound-scope.d.ts +1 -0
- package/lib/types/peer-store.d.ts +4 -0
- package/lib/types/queue-guard.d.ts +9 -2
- package/lib/types/receipt-ladder.d.ts +2 -0
- package/lib/types/receipt-retry.d.ts +22 -0
- package/lib/types/reconciliation-reminders.d.ts +41 -0
- package/lib/types/reconciliation-state.d.ts +32 -0
- package/lib/types/session-transcript.d.ts +6 -1
- package/lib/types/task-auth.d.ts +7 -0
- package/lib/types/task-delivery.d.ts +17 -0
- package/lib/types/task-ledger-persistence.d.ts +3 -0
- package/lib/types/task-ledger-rows.d.ts +19 -2
- package/lib/types/task-ledger.d.ts +48 -1
- package/lib/types/tools/info.d.ts +3 -0
- package/lib/types/types.d.ts +3 -0
- package/nexus-stage/src/federation.ts +1 -0
- package/nexus-stage/src/main.ts +17 -2
- package/nexus-stage/src/member-provenance-picker.ts +13 -0
- package/nexus-stage/src/planning-member-handles.ts +4 -1
- package/nexus-stage/src/planning-view.ts +3 -3
- package/nexus-stage/src/workbench.ts +51 -7
- package/nexus-stage/src/world.ts +1 -0
- package/package.json +3 -2
- package/assets/nexusDist/assets/index-NkNSeIx6.js +0 -9
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Host-owned bounded retry policy; never changes the receipt text. */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
export declare function receiptRetryWait(ctx: Pick<Context, 'effect'>, ms: number): Promise<boolean>;
|
|
4
|
+
export declare const RECEIPT_RETRYABLE: unique symbol;
|
|
5
|
+
export declare const RECEIPT_RETRY_DELAYS_MS: readonly [1000, 2000, 4000, 8000, 16000, 30000];
|
|
6
|
+
export declare class ReceiptDeliveryError extends Error {
|
|
7
|
+
readonly transient: boolean;
|
|
8
|
+
constructor(message: string, transient: boolean);
|
|
9
|
+
}
|
|
10
|
+
export declare function transientReceiptFailure(error: unknown): boolean;
|
|
11
|
+
export declare function receiptFailure(result: {
|
|
12
|
+
error: string;
|
|
13
|
+
code?: number;
|
|
14
|
+
[RECEIPT_RETRYABLE]?: true;
|
|
15
|
+
}): ReceiptDeliveryError;
|
|
16
|
+
export declare function retryReceiptDelivery(d: {
|
|
17
|
+
attempt: () => Promise<void>;
|
|
18
|
+
notify: () => Promise<void>;
|
|
19
|
+
pending: () => boolean;
|
|
20
|
+
wait: (ms: number) => Promise<boolean>;
|
|
21
|
+
log: (error: unknown) => void;
|
|
22
|
+
}, delays?: readonly number[]): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { TaskLedger, TaskRecord } from './task-ledger.ts';
|
|
2
|
+
export interface ReminderAgent {
|
|
3
|
+
id: unknown;
|
|
4
|
+
status: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ReminderDeps {
|
|
7
|
+
ledger: TaskLedger;
|
|
8
|
+
agents: () => readonly ReminderAgent[];
|
|
9
|
+
resolve: (row: TaskRecord) => ReminderAgent | undefined;
|
|
10
|
+
sender: (row: TaskRecord) => ReminderAgent | undefined;
|
|
11
|
+
followup: (agent: ReminderAgent, text: string) => void;
|
|
12
|
+
now?: () => number;
|
|
13
|
+
warn: (code: string) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare class ReconciliationReminders {
|
|
16
|
+
private readonly d;
|
|
17
|
+
private stopped;
|
|
18
|
+
private active;
|
|
19
|
+
private failedUntil;
|
|
20
|
+
private nudgeAt;
|
|
21
|
+
readonly diagnostics: {
|
|
22
|
+
lastCheckedAt: number;
|
|
23
|
+
unresolved: number;
|
|
24
|
+
lastError?: {
|
|
25
|
+
at: number;
|
|
26
|
+
code: string;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
constructor(d: ReminderDeps);
|
|
30
|
+
stop(): void;
|
|
31
|
+
/** Shared session gate with the existing unread nudge; no budget/consumption changes. */
|
|
32
|
+
nudgeAllowed(id: string): boolean;
|
|
33
|
+
noteNudge(id: string): void;
|
|
34
|
+
private now;
|
|
35
|
+
private idle;
|
|
36
|
+
private throttled;
|
|
37
|
+
scan(): void;
|
|
38
|
+
private report;
|
|
39
|
+
private remind;
|
|
40
|
+
private notify;
|
|
41
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** #351 reminder observations only: never a task lifecycle or acknowledgement. */
|
|
2
|
+
export declare const REMINDER_SCAN_MS = 60000;
|
|
3
|
+
export declare const REMINDER_MIN_MS = 120000;
|
|
4
|
+
export declare const REMINDER_LONG_MS: number;
|
|
5
|
+
export declare const REMINDER_WARNING_MS: number;
|
|
6
|
+
export interface ReminderState {
|
|
7
|
+
version: 1;
|
|
8
|
+
targetId: string;
|
|
9
|
+
attempts: number;
|
|
10
|
+
deliveredCount: number;
|
|
11
|
+
lastAttemptAt: number;
|
|
12
|
+
nextAt: number;
|
|
13
|
+
requestId: string;
|
|
14
|
+
lastResult: 'reserved' | 'delivered' | 'failed' | 'unknown';
|
|
15
|
+
lastDeliveredAt?: number;
|
|
16
|
+
noticeAttemptedAt?: number;
|
|
17
|
+
notifiedAt?: number;
|
|
18
|
+
escalationAt?: number;
|
|
19
|
+
diagnostic?: 'metadata-reset';
|
|
20
|
+
}
|
|
21
|
+
export declare function parseReminder(raw: unknown): ReminderState | undefined;
|
|
22
|
+
export declare function nextReminderDelay(attempts: number): number;
|
|
23
|
+
export interface ReminderView {
|
|
24
|
+
reminderAttempts: number;
|
|
25
|
+
reminderDelivered: number;
|
|
26
|
+
reminderLastAt: number;
|
|
27
|
+
reminderNextAt: number;
|
|
28
|
+
repeatedlyUnreconciled: boolean;
|
|
29
|
+
reminderResult: string;
|
|
30
|
+
reminderDiagnostic?: 'metadata-reset';
|
|
31
|
+
}
|
|
32
|
+
export declare function reminderView(state: ReminderState | undefined, now?: number): Partial<ReminderView>;
|
|
@@ -26,7 +26,12 @@ export declare function scanZstdFrames(buffer: Buffer): FrameRange[];
|
|
|
26
26
|
* artifacts are split into frames and decoded frame by frame (node's public
|
|
27
27
|
* decoder is single-frame).
|
|
28
28
|
*/
|
|
29
|
-
|
|
29
|
+
type BoundedZstdDecoder = (input: Uint8Array, options: {
|
|
30
|
+
maxOutputLength: number;
|
|
31
|
+
}) => Buffer;
|
|
32
|
+
/** Codec seam for tests; budgets may only tighten the shipped hard caps. */
|
|
33
|
+
export declare function createSessionLogDecoder(decoder?: BoundedZstdDecoder | undefined, outputCap?: number): (buffer: Buffer) => string;
|
|
34
|
+
export declare const decodeSessionLog: (buffer: Buffer) => string;
|
|
30
35
|
/**
|
|
31
36
|
* Extract the last `limit` human-visible turns from a decoded session log.
|
|
32
37
|
* User lines come from user/message data.content[].text; assistant lines from
|
package/lib/types/task-auth.d.ts
CHANGED
|
@@ -43,10 +43,17 @@ export interface AdmissionRecords {
|
|
|
43
43
|
readonly teams: readonly string[];
|
|
44
44
|
}[];
|
|
45
45
|
joined(): readonly string[];
|
|
46
|
+
/** Caller-only proof projection; targets still use the complete local registry. */
|
|
47
|
+
callerJoined?(): readonly string[];
|
|
48
|
+
callerMemberships?(): readonly {
|
|
49
|
+
readonly session: string;
|
|
50
|
+
readonly teams: readonly string[];
|
|
51
|
+
}[];
|
|
46
52
|
canvasNames(): readonly string[];
|
|
47
53
|
/** Canvas names the session sits in. */
|
|
48
54
|
canvasTeamsOf(sessionId: string): readonly string[];
|
|
49
55
|
canvasMembersOf(name: string): readonly string[];
|
|
56
|
+
callerCanvasMembersOf?(name: string): readonly string[];
|
|
50
57
|
canvasRemoteMembersOf(name: string): readonly string[];
|
|
51
58
|
/** The canvasOrg lists of the warm peer-card cache. */
|
|
52
59
|
peerOrgs(): Iterable<readonly PeerOrgEntry[]>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface TaskDelivery {
|
|
2
|
+
version: 1;
|
|
3
|
+
taskId: string;
|
|
4
|
+
messageId: string;
|
|
5
|
+
sessionId: string;
|
|
6
|
+
fingerprint: string;
|
|
7
|
+
callerKey?: string;
|
|
8
|
+
/** Distinguishes task births from receipt-only or synchronous receiptless occurrences. */
|
|
9
|
+
taskExpected?: true;
|
|
10
|
+
createdAt: number;
|
|
11
|
+
state: 'reserved' | 'enqueued' | 'claimed' | 'cancelled' | 'failed';
|
|
12
|
+
}
|
|
13
|
+
export declare const TASK_DELIVERY_CAP = 512;
|
|
14
|
+
/** Keep completed/cancelled occurrence identities through the server idempotency window. */
|
|
15
|
+
export declare const TASK_DELIVERY_RETENTION_MS: number;
|
|
16
|
+
export declare function deliveryFingerprint(sessionId: string, text: string, sender?: string, localFrom?: string): string;
|
|
17
|
+
export declare function parseTaskDelivery(raw: unknown): TaskDelivery | undefined;
|
|
@@ -105,6 +105,9 @@ export declare function withAuth(raw: unknown, entry: {
|
|
|
105
105
|
export declare function routingOf(row: Pick<TaskRecord, 'peerKey' | 'peerHost' | 'deliveredTo' | 'forwarded' | 'answeredAt' | 'receiverMembers'>): Pick<TaskRecord, 'peerKey' | 'peerHost' | 'deliveredTo' | 'forwarded' | 'answeredAt' | 'receiverMembers'>;
|
|
106
106
|
/** #139: what an outbound row is born with besides its sender. */
|
|
107
107
|
export interface OutboundBirth {
|
|
108
|
+
readonly consumed?: boolean;
|
|
109
|
+
/** Exact receiver selected at local delivery, never inferred from a later canvas roster. */
|
|
110
|
+
readonly receiverSessionId?: string;
|
|
108
111
|
readonly forwarded?: true;
|
|
109
112
|
/** Local-candidate admission, or a forwarded online CLI's verified wire admission. */
|
|
110
113
|
readonly auth?: TaskAuthorization;
|
|
@@ -115,7 +118,7 @@ export interface OutboundBirth {
|
|
|
115
118
|
readonly receiverMembers?: readonly string[];
|
|
116
119
|
}
|
|
117
120
|
/** The facts of one outbound birth that fit the row (see {@link withRouting} for the same shape on restore). */
|
|
118
|
-
export declare function birthFacts(peer: string, birth: OutboundBirth | undefined, team?: string, sender?: Sender, now?: number, warn?: (message: string) => void): Pick<TaskRecord, 'auth' | 'peerKey' | 'peerHost' | 'deliveredTo' | 'forwarded' | 'receiverMembers'>;
|
|
121
|
+
export declare function birthFacts(peer: string, birth: OutboundBirth | undefined, team?: string, sender?: Sender, now?: number, warn?: (message: string) => void): Pick<TaskRecord, 'auth' | 'peerKey' | 'peerHost' | 'deliveredTo' | 'forwarded' | 'receiverMembers' | 'receiverSessionId' | 'consumed'>;
|
|
119
122
|
/** Bound on a persisted routing fact; anything longer (or empty, or not a string) is dropped on restore. */
|
|
120
123
|
export declare const ROUTING_FACT_CAP = 256;
|
|
121
124
|
export declare function parseReceiverMembers(value: unknown, warn?: (message: string) => void): readonly string[] | undefined;
|
|
@@ -125,9 +128,14 @@ export declare function parseReceiverMembers(value: unknown, warn?: (message: st
|
|
|
125
128
|
* a remote outbound row; anything else is dropped (the row survives as one
|
|
126
129
|
* born before the fields, judged by current membership and origin).
|
|
127
130
|
*/
|
|
128
|
-
export declare function withRouting(entry: Record<string, unknown>, warn?: (message: string) => void): Pick<TaskRecord, 'peerKey' | 'peerHost' | 'deliveredTo' | 'forwarded' | 'answeredAt' | 'receiverMembers'>;
|
|
131
|
+
export declare function withRouting(entry: Record<string, unknown>, warn?: (message: string) => void): Pick<TaskRecord, 'peerKey' | 'peerHost' | 'deliveredTo' | 'forwarded' | 'answeredAt' | 'receiverMembers' | 'receiptDelivery'>;
|
|
129
132
|
/** One tracked outbound task still in the owed book (unsettled). */
|
|
130
133
|
export interface TaskRecord {
|
|
134
|
+
/** Local work answered, but successful receipt delivery is not confirmed; never a settlement. */
|
|
135
|
+
receiptDelivery?: 'receipt-undelivered';
|
|
136
|
+
/** Observation from exact inbox claim or peer response, never an acknowledgement. */
|
|
137
|
+
consumed?: boolean;
|
|
138
|
+
readonly receiverSessionId?: string;
|
|
131
139
|
readonly taskId: string;
|
|
132
140
|
readonly team: string;
|
|
133
141
|
readonly peer: string;
|
|
@@ -184,8 +192,13 @@ export interface TaskRecord {
|
|
|
184
192
|
*/
|
|
185
193
|
reason?: DeadReason;
|
|
186
194
|
}
|
|
195
|
+
export declare const WITHDRAWAL_RESULTS: readonly ["removed", "claimed-steered", "withdraw-failed-steered", "no-agent"];
|
|
196
|
+
export type WithdrawalResult = typeof WITHDRAWAL_RESULTS[number];
|
|
197
|
+
export declare function isWithdrawalResult(value: unknown): value is WithdrawalResult;
|
|
187
198
|
/** One settled task kept for audit, with its receipt-correlated outcome. */
|
|
188
199
|
export interface ArchivedRecord {
|
|
200
|
+
/** Cancellation notification observation; independent of the terminal task outcome. */
|
|
201
|
+
readonly withdrawalResult?: WithdrawalResult;
|
|
189
202
|
readonly taskId: string;
|
|
190
203
|
readonly team: string;
|
|
191
204
|
readonly peer: string;
|
|
@@ -298,6 +311,10 @@ export interface ReceiptResolvedInfo {
|
|
|
298
311
|
}
|
|
299
312
|
/** The persisted ledger document: the owed book plus its settled archive. */
|
|
300
313
|
export interface TaskLedgerSnapshot {
|
|
314
|
+
readonly taskDeliveryRestoreError?: true;
|
|
315
|
+
readonly taskDeliveries?: readonly import('./task-delivery.ts').TaskDelivery[];
|
|
316
|
+
readonly reconciliationSessions?: Record<string, number>;
|
|
317
|
+
readonly reconciliationReminders?: Record<string, import('./reconciliation-state.ts').ReminderState>;
|
|
301
318
|
readonly tasks: readonly TaskRecord[];
|
|
302
319
|
readonly archived?: readonly ArchivedRecord[];
|
|
303
320
|
/** #114 S4: the bounded ring of refused / would-be-refused settlements. */
|
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
* @module @nelsonlongxiang/dsh-open-a2a-net/task-ledger
|
|
20
20
|
*/
|
|
21
21
|
import type { TaskAuthorization } from './task-auth.ts';
|
|
22
|
+
import { type TaskDelivery } from './task-delivery.ts';
|
|
23
|
+
import { type ReminderState } from './reconciliation-state.ts';
|
|
22
24
|
import { type ConservationVerdict } from './ledger-conservation.ts';
|
|
23
25
|
import type { TaskRecord, ArchivedRecord, Sender, OutboundBirth, TaskLedgerOptions, ReceiptResolvedInfo, SettlementRefusalRecord, SettlementJudge, AbandonResult, CancelledResult } from './task-ledger-rows.ts';
|
|
24
26
|
export * from './task-ledger-rows.ts';
|
|
@@ -45,6 +47,49 @@ export declare class TaskLedger {
|
|
|
45
47
|
/** Injectable clock (option `now`). */
|
|
46
48
|
private readonly now;
|
|
47
49
|
private readonly warn;
|
|
50
|
+
/** Last successful commit, for rollback when a mutation cannot persist. */
|
|
51
|
+
private committed;
|
|
52
|
+
private deliveries;
|
|
53
|
+
private deliveryRestoreError;
|
|
54
|
+
markConsumed(taskId: string, consumed: boolean): void;
|
|
55
|
+
deliveryFor(taskId: string): TaskDelivery | undefined;
|
|
56
|
+
deliveryByMessage(sessionId: string, messageId: string): TaskDelivery | undefined;
|
|
57
|
+
reserveDelivery(record: TaskDelivery): TaskDelivery;
|
|
58
|
+
updateDelivery(taskId: string, state: TaskDelivery['state']): void;
|
|
59
|
+
private reminderRecoveryPending;
|
|
60
|
+
private reminders;
|
|
61
|
+
private reminderSessions;
|
|
62
|
+
reminderSessionAt(id: string): number | undefined;
|
|
63
|
+
reminderState(taskId: string): ReminderState | undefined;
|
|
64
|
+
/** Atomic observation update; no task status, answer, or conservation mutation. */
|
|
65
|
+
saveReminders(updates: readonly {
|
|
66
|
+
taskId: string;
|
|
67
|
+
state: ReminderState | undefined;
|
|
68
|
+
}[], session?: {
|
|
69
|
+
id: string;
|
|
70
|
+
at: number | undefined;
|
|
71
|
+
}): void;
|
|
72
|
+
private admittedFailure;
|
|
73
|
+
private admittedFailures;
|
|
74
|
+
noteAdmittedBookkeepingFailure(error: unknown): void;
|
|
75
|
+
orphanDelivery(taskId: string): boolean;
|
|
76
|
+
deliveryBookkeepingState(): {
|
|
77
|
+
orphaned: number;
|
|
78
|
+
failures: number;
|
|
79
|
+
lastError?: {
|
|
80
|
+
at: number;
|
|
81
|
+
code: string;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
private writeFailure;
|
|
85
|
+
/** Diagnostic-only; never implies that a failed mutation committed. No paths or payloads exposed. */
|
|
86
|
+
persistenceState(): {
|
|
87
|
+
ok: boolean;
|
|
88
|
+
lastError?: {
|
|
89
|
+
at: number;
|
|
90
|
+
code: string;
|
|
91
|
+
};
|
|
92
|
+
};
|
|
48
93
|
/**
|
|
49
94
|
* @param path - persistence file (`<dsh-home>/a2a/tasks.json`); empty = no persistence.
|
|
50
95
|
* @param options - TTL/capacity overrides; defaults come from the exported constants.
|
|
@@ -99,7 +144,7 @@ export declare class TaskLedger {
|
|
|
99
144
|
* @param from - the caller's receipt target (label or callback address).
|
|
100
145
|
* @param contextId - the delivery's conversation id, kept for follow-up routes (empty omits it).
|
|
101
146
|
*/
|
|
102
|
-
trackInbound(taskId: string, team: string, from: string, contextId?: string, receiptExpected?: boolean, brief?: string, auth?: TaskAuthorization, deliveredTo?: string): void;
|
|
147
|
+
trackInbound(taskId: string, team: string, from: string, contextId?: string, receiptExpected?: boolean, brief?: string, auth?: TaskAuthorization, deliveredTo?: string, receiverSessionId?: string, consumed?: boolean): void;
|
|
103
148
|
/** Record an answer only from the local member that actually received this row; never settle or change counters. */
|
|
104
149
|
markAnswered(taskId: string, target: string): boolean;
|
|
105
150
|
/**
|
|
@@ -138,6 +183,7 @@ export declare class TaskLedger {
|
|
|
138
183
|
* the route layer reads team/contextId from here for its best-effort
|
|
139
184
|
* steer, which is the documented boundary until origin-auth lands.
|
|
140
185
|
*/
|
|
186
|
+
recordWithdrawalResult(taskId: string, result: import('./task-ledger-rows.ts').WithdrawalResult): void;
|
|
141
187
|
cancel(taskId: string, reason?: string): CancelledResult;
|
|
142
188
|
/**
|
|
143
189
|
* Correlate one message against the ledger: a receipt (message starting
|
|
@@ -204,4 +250,5 @@ export declare class TaskLedger {
|
|
|
204
250
|
private restore;
|
|
205
251
|
/** Persist the ledger (no-op when no path was configured). */
|
|
206
252
|
private persist;
|
|
253
|
+
private snapshot;
|
|
207
254
|
}
|
|
@@ -14,6 +14,7 @@ import type { TaskLedger } from '../task-ledger.ts';
|
|
|
14
14
|
import type { TeamMembershipStore } from '../team-store.ts';
|
|
15
15
|
/** One directory row shared with listDirectoryTeams (which stays in index.ts). */
|
|
16
16
|
export type DirectoryTeamRow = {
|
|
17
|
+
memberRef?: string;
|
|
17
18
|
team: string;
|
|
18
19
|
session: string;
|
|
19
20
|
name: string;
|
|
@@ -41,9 +42,11 @@ export interface InfoToolsDeps {
|
|
|
41
42
|
readonly ownCardUrl: string;
|
|
42
43
|
readonly peerStore: PeerStore;
|
|
43
44
|
readonly taskLedger: TaskLedger;
|
|
45
|
+
readonly nudgeEscalations?: () => import('../nudge-observability.ts').NudgeEscalations;
|
|
44
46
|
readonly joinedSessions: JoinedSessions;
|
|
45
47
|
readonly teamMemberships: TeamMembershipStore;
|
|
46
48
|
readonly canvasStore: CanvasStore;
|
|
49
|
+
readonly canvasMigrationState: () => Record<string, unknown>;
|
|
47
50
|
readonly exposureStore: ExposureStore;
|
|
48
51
|
readonly idempotencyStore: IdempotencyStore;
|
|
49
52
|
readonly cardCache: Map<string, CardCacheEntry>;
|
package/lib/types/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Wire and configuration vocabulary for the A2A client extension.
|
|
3
3
|
* @module @nelsonlongxiang/dsh-open-a2a-net/types
|
|
4
4
|
*/
|
|
5
|
+
import type { RECEIPT_RETRYABLE } from './receipt-retry.ts';
|
|
5
6
|
/** One peer node's agent card — the decentralized discovery record. */
|
|
6
7
|
export interface A2aPeerCard {
|
|
7
8
|
/** Human-facing peer name. */
|
|
@@ -108,6 +109,7 @@ export interface ZoneRecord {
|
|
|
108
109
|
}
|
|
109
110
|
/** Canonical `a2a_route` success value. */
|
|
110
111
|
export interface A2aRouteOk {
|
|
112
|
+
readonly receiverBookkeeping?: 'failed';
|
|
111
113
|
/** Actual member face returned by a forwarded peer delivery. */
|
|
112
114
|
readonly deliveredTo?: string;
|
|
113
115
|
/**
|
|
@@ -150,6 +152,7 @@ export interface A2aRouteOk {
|
|
|
150
152
|
}
|
|
151
153
|
/** Canonical `a2a_route` failure value. */
|
|
152
154
|
export interface A2aRouteError {
|
|
155
|
+
readonly [RECEIPT_RETRYABLE]?: true;
|
|
153
156
|
/** A forwarded receipt never promises another receipt, including uncertain waits. */
|
|
154
157
|
readonly receipt?: 'none';
|
|
155
158
|
readonly ok: false;
|
package/nexus-stage/src/main.ts
CHANGED
|
@@ -299,14 +299,28 @@ const bench = createWorkbench({
|
|
|
299
299
|
return { ...body, ok: res.ok && body.ok === true }
|
|
300
300
|
} catch (error) { return { ok: false, error: String(error) } }
|
|
301
301
|
},
|
|
302
|
-
async
|
|
302
|
+
async confirmMember(name, id, replacement, revision, expectedOwner, peer) {
|
|
303
|
+
try {
|
|
304
|
+
const response=await controlFetch('/__dsh_a2a/canvas',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({action:'confirm-member',name,id,replacement,revision,expectedOwner,...(peer?{peer}:{})})})
|
|
305
|
+
const body=await response.json() as {ok?:boolean;error?:string}
|
|
306
|
+
return {ok:response.ok&&body.ok===true,error:body.error}
|
|
307
|
+
}catch(error){return{ok:false,error:String(error)}}
|
|
308
|
+
},
|
|
309
|
+
async regulateSuspended(action, name, id, revision, expected) {
|
|
310
|
+
try {
|
|
311
|
+
const response = await controlFetch('/__dsh_a2a/canvas', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ action, name, id, revision, expected }) })
|
|
312
|
+
const body = await response.json() as { ok?: boolean; error?: string }
|
|
313
|
+
return { ok: response.ok && body.ok === true, error: body.error }
|
|
314
|
+
} catch (error) { return { ok: false, error: String(error) } }
|
|
315
|
+
},
|
|
316
|
+
async canvasOp(action, name, id, home, peer) {
|
|
303
317
|
// Same control route the planning board's wire uses; the inspector's
|
|
304
318
|
// member writes are one id at a time, so no serial fan-out needed.
|
|
305
319
|
try {
|
|
306
320
|
const res = await controlFetch('/__dsh_a2a/canvas', {
|
|
307
321
|
method: 'POST',
|
|
308
322
|
headers: { 'content-type': 'application/json' },
|
|
309
|
-
body: JSON.stringify({ action, name, id, ...(home === undefined ? {} : { home }) }),
|
|
323
|
+
body: JSON.stringify({ action, name, id, ...(home === undefined ? {} : { home }), ...(peer === undefined ? {} : { peer }) }),
|
|
310
324
|
})
|
|
311
325
|
const body = (await res.json().catch(() => ({}))) as { ok?: boolean; error?: string }
|
|
312
326
|
if (res.ok && body.ok !== false) return true
|
|
@@ -479,6 +493,7 @@ async function poll(): Promise<void> {
|
|
|
479
493
|
.filter(r => r !== null && typeof r === 'object' && typeof (r as { team?: unknown }).team === 'string' && typeof (r as { via?: unknown }).via === 'string')
|
|
480
494
|
.map(r => ({
|
|
481
495
|
team: r.team,
|
|
496
|
+
memberRef:r.memberRef,
|
|
482
497
|
name: r.name,
|
|
483
498
|
via: r.via,
|
|
484
499
|
workspace: r.workspace,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Explicit slot provenance confirmation; never clears suspension or infers owner from a label. */
|
|
2
|
+
export interface ProvenanceChoice { replacement: string; label: string; peer?: string }
|
|
3
|
+
export function createMemberProvenancePicker(choices: readonly ProvenanceChoice[], confirm: (choice: ProvenanceChoice) => Promise<{ ok: boolean; error?: string }>): HTMLElement {
|
|
4
|
+
const root=document.createElement('form');root.className='wb-member-provenance'
|
|
5
|
+
const select=document.createElement('select');select.setAttribute('aria-label','确认成员来源')
|
|
6
|
+
select.appendChild(new Option('明确选择来源…',''))
|
|
7
|
+
choices.forEach((choice,index)=>select.appendChild(new Option(choice.label,String(index))))
|
|
8
|
+
const submit=document.createElement('button');submit.type='submit';submit.textContent='确认来源'
|
|
9
|
+
const hint=document.createElement('span');hint.textContent='确认不等于恢复;已暂停槽位仍保持暂停'
|
|
10
|
+
root.append(select,submit,hint)
|
|
11
|
+
root.addEventListener('submit',async event=>{event.preventDefault();if(select.value==='')return;const choice=choices[Number(select.value)];if(!choice)return;submit.disabled=true;const out=await confirm(choice);if(!out.ok){submit.disabled=false;hint.textContent=out.error??'来源无法验证,原槽位保留'}else hint.textContent='来源已确认;等待状态刷新(暂停状态不变)'})
|
|
12
|
+
return root
|
|
13
|
+
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { RemoteTeamRow } from './federation'
|
|
3
3
|
import type { SessionLite } from './world'
|
|
4
4
|
|
|
5
|
-
type HandleSession = Pick<SessionLite, 'id' | 'team' | 'joined'>
|
|
5
|
+
type HandleSession = Pick<SessionLite, 'id' | 'team' | 'joined' | 'memberRef'>
|
|
6
6
|
|
|
7
7
|
/** Map only unique joined id8 handles that no remote directory row already owns. */
|
|
8
8
|
export function joinedLocalIdsByHandle(sessions: ReadonlyArray<HandleSession>, publishedHandles: ReadonlySet<string>): Map<string, string> {
|
|
@@ -21,6 +21,9 @@ export function joinedLocalIdsByHandle(sessions: ReadonlyArray<HandleSession>, p
|
|
|
21
21
|
localIds.set(handle, session.id)
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
const ownerAliases=new Map<string,string[]>()
|
|
25
|
+
for(const session of sessions)if(session.joined===true && session.memberRef){const ids=ownerAliases.get(session.memberRef)??[];ids.push(session.id);ownerAliases.set(session.memberRef,ids)}
|
|
26
|
+
for(const [ref,ids]of ownerAliases)if(ids.length===1)localIds.set(ref,ids[0]!)
|
|
24
27
|
return localIds
|
|
25
28
|
}
|
|
26
29
|
|
|
@@ -1236,7 +1236,7 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
|
|
|
1236
1236
|
const host = peerHostOf(p.url)
|
|
1237
1237
|
// Owner report 2026-11-04: the host's rows now lead with its bare process team (e.g. 'dsh'), which the
|
|
1238
1238
|
// server reads as a local session id and refuses — take the first zone/id8 SESSION handle instead.
|
|
1239
|
-
const
|
|
1239
|
+
const first = lastRemoteTeams.get(host)?.find(row => row.team.includes('/')); const handle = first?.memberRef ?? first?.team
|
|
1240
1240
|
model.upsertNode({
|
|
1241
1241
|
id, x: base.x, y: base.y,
|
|
1242
1242
|
label: p.url, team: '', name: p.url, peerUrl: p.url,
|
|
@@ -1262,7 +1262,7 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
|
|
|
1262
1262
|
let y = hostNode?.y ?? 0
|
|
1263
1263
|
for (const row of rows) {
|
|
1264
1264
|
if (!row.team.includes('/')) continue
|
|
1265
|
-
const id = `member-${row.team.replace(/[^a-z0-9-]/gi, '-')}`
|
|
1265
|
+
const id = `member-${(row.memberRef ?? row.team).replace(/[^a-z0-9-]/gi, '-')}`
|
|
1266
1266
|
if (memberSeen.has(id)) continue
|
|
1267
1267
|
memberSeen.add(id)
|
|
1268
1268
|
const saved = layoutDoc?.nodes[id]
|
|
@@ -1272,7 +1272,7 @@ export function createPlanningView(deps: PlanningDeps): PlanningView {
|
|
|
1272
1272
|
model.upsertNode({
|
|
1273
1273
|
id, x: pos.x, y: pos.y,
|
|
1274
1274
|
label: row.team, team: row.team, name: row.name !== undefined && row.name !== '' ? row.name : row.team,
|
|
1275
|
-
remoteHandle: row.team,
|
|
1275
|
+
remoteHandle: row.memberRef ?? row.team,
|
|
1276
1276
|
live: true, remote: true, memberships: [],
|
|
1277
1277
|
})
|
|
1278
1278
|
}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
import { createChatDrawer, type ChatTarget } from './wb-chat'
|
|
22
22
|
import { createSeatNameEditor } from './seat-name-editor'
|
|
23
23
|
import { createCliHomePicker, type CliHomeOptions } from './cli-home-picker'
|
|
24
|
+
import { createMemberProvenancePicker, type ProvenanceChoice } from './member-provenance-picker'
|
|
24
25
|
import { createRailTabs } from './wb-rail'
|
|
25
26
|
import { DIRECTORY_FILTERS, learnLogTitle, loadLogTitles, renderDirectory, type DirFilter } from './wb-directory'
|
|
26
27
|
import { ledgerFacts, renderLedger as paintLedger } from './wb-ledger'
|
|
@@ -92,6 +93,7 @@ export interface PeerOrgRow {
|
|
|
92
93
|
|
|
93
94
|
/** One session row from /state `sessions` (live or cold joined). */
|
|
94
95
|
export interface SessionRow {
|
|
96
|
+
readonly memberRef?: string
|
|
95
97
|
readonly id: string
|
|
96
98
|
readonly label: string
|
|
97
99
|
readonly team: string
|
|
@@ -112,6 +114,7 @@ export interface SessionRow {
|
|
|
112
114
|
|
|
113
115
|
/** One remote team row from /state `remote`. */
|
|
114
116
|
export interface RemoteRow {
|
|
117
|
+
readonly memberRef?: string
|
|
115
118
|
readonly team: string
|
|
116
119
|
readonly unreachable?: boolean
|
|
117
120
|
readonly name?: string
|
|
@@ -128,6 +131,7 @@ export interface RemoteRow {
|
|
|
128
131
|
* handle (`zone/id8`) — remote members are routed by the directory, never
|
|
129
132
|
* woken locally (0.6.6 unified team model). */
|
|
130
133
|
export interface CanvasMemberRow {
|
|
134
|
+
readonly provenance?: 'legacy-inferred' | 'unknown'
|
|
131
135
|
readonly id: string
|
|
132
136
|
readonly team?: string
|
|
133
137
|
readonly joined?: boolean
|
|
@@ -160,7 +164,7 @@ export interface StateBody {
|
|
|
160
164
|
readonly zone?: string
|
|
161
165
|
readonly version?: string
|
|
162
166
|
readonly sessions?: readonly SessionRow[]
|
|
163
|
-
readonly canvas?: { teams?: readonly CanvasTeamRow[] }
|
|
167
|
+
readonly canvas?: { teams?: readonly CanvasTeamRow[]; revision?: number; memberSlots?: ReadonlyArray<{ name: string; member: string; ownerKey: string | null; provenance: string; suspended: boolean; compatibilityInference: boolean }>; retainedMembers?: ReadonlyArray<{ team: string; member: string; suspended: boolean; records: readonly unknown[] }> }
|
|
164
168
|
/** gh#20 W-A/W-C: the peer row carries the link badge source, the host
|
|
165
169
|
* rail's label/version/last-contact facts (absent on older hosts). */
|
|
166
170
|
readonly peers?: ReadonlyArray<{
|
|
@@ -219,8 +223,10 @@ export interface WorkbenchDeps {
|
|
|
219
223
|
abandon(taskId: string, reason: string): Promise<boolean>
|
|
220
224
|
/** Canvas team member change via the control route (0.6.6): `id` is a
|
|
221
225
|
* local session id or a zone-qualified remote handle. True on success. */
|
|
222
|
-
canvasOp(action: 'add-member' | 'remove-member', name: string, id: string, home?: string): Promise<boolean>
|
|
226
|
+
canvasOp(action: 'add-member' | 'remove-member', name: string, id: string, home?: string, peer?: string): Promise<boolean>
|
|
223
227
|
cliHomeOptions?(name: string, id: string): Promise<CliHomeOptions>
|
|
228
|
+
confirmMember?(name: string, id: string, replacement: string, revision: number, expectedOwner: string | null, peer?: string): Promise<{ ok: boolean; error?: string }>
|
|
229
|
+
regulateSuspended?(action: 'restore-suspended' | 'delete-suspended', name: string, id: string, revision: number, expected: readonly unknown[]): Promise<{ ok: boolean; error?: string }>
|
|
224
230
|
/** Quick dispatch (0.6.9): POST /__dsh_a2a/dispatch as a human gesture —
|
|
225
231
|
* one message to a routing team, async delivery, receipt lands in the
|
|
226
232
|
* ledger. Optional — without it the 快派 form hides. */
|
|
@@ -342,6 +348,7 @@ export function createWorkbench(deps: WorkbenchDeps): Workbench {
|
|
|
342
348
|
// by team name — same trick as draftForm so inputs survive the 3s poll).
|
|
343
349
|
let canvasForm: { name: string; form: HTMLFormElement; pick: HTMLSelectElement; manual: HTMLInputElement } | null = null
|
|
344
350
|
const cliHomeForms = new Map<string, HTMLFormElement>()
|
|
351
|
+
const provenanceForms = new Map<string, HTMLElement>()
|
|
345
352
|
// Quick-dispatch form (0.6.9), cached per target routing team the same
|
|
346
353
|
// draftForm way: a textarea's draft must survive the 3s poll remount.
|
|
347
354
|
let dispatchForm: { team: string; form: HTMLFormElement; text: HTMLTextAreaElement; note: HTMLElement } | null = null
|
|
@@ -981,7 +988,7 @@ export function createWorkbench(deps: WorkbenchDeps): Workbench {
|
|
|
981
988
|
for (const member of fresh.members) {
|
|
982
989
|
const row = el('div', 'wb-member-row')
|
|
983
990
|
const dot = member.cliHeld === true ? '本机持有,无需指定' : member.remote === true ? (member.cliHome === null ? '◇ 远端 · home 未确定' : '◇ 远端') : member.live === true ? '● 现场' : member.joined === true ? '○ 冷启' : '· 未入网'
|
|
984
|
-
row.appendChild(el('span', 'wb-member-dot')).textContent = dot
|
|
991
|
+
row.appendChild(el('span', 'wb-member-dot')).textContent = dot + (member.provenance ? ' · 来源未确认' + (member.provenance === 'legacy-inferred' ? '(兼容推断)' : '') : '')
|
|
985
992
|
const label = el('span', 'wb-member-label')
|
|
986
993
|
const session = (last?.sessions ?? []).find(s => s.id === member.id)
|
|
987
994
|
label.textContent = member.remote === true ? member.id : (session !== undefined ? displayName(session) : member.id)
|
|
@@ -1011,6 +1018,37 @@ export function createWorkbench(deps: WorkbenchDeps): Workbench {
|
|
|
1011
1018
|
card.appendChild(form)
|
|
1012
1019
|
}
|
|
1013
1020
|
}
|
|
1021
|
+
const retained = (last?.canvas?.retainedMembers ?? []).filter(r => r.team === fresh.name)
|
|
1022
|
+
for (const slot of retained) {
|
|
1023
|
+
const row = el('div', 'wb-member-row')
|
|
1024
|
+
row.appendChild(el('span', 'wb-member-dot')).textContent = slot.suspended ? '已暂停 · 不参与路由' : '来源未确认 · 窗口关闭'
|
|
1025
|
+
row.appendChild(el('span', 'wb-member-label')).textContent = slot.member
|
|
1026
|
+
if (slot.suspended && deps.regulateSuspended && last?.canvas?.revision !== undefined) {
|
|
1027
|
+
for (const action of ['restore-suspended', 'delete-suspended'] as const) {
|
|
1028
|
+
const button = el('button', 'wb-member-remove'); button.type = 'button'
|
|
1029
|
+
button.textContent = action === 'restore-suspended' ? '显式恢复' : '删除暂停位'
|
|
1030
|
+
const revision = last.canvas.revision
|
|
1031
|
+
button.addEventListener('click', async () => {
|
|
1032
|
+
button.disabled = true
|
|
1033
|
+
const result = await deps.regulateSuspended!(action, fresh.name, slot.member, revision, slot.records)
|
|
1034
|
+
if (!result.ok) { button.disabled = false; button.title = result.error ?? '状态已变,请刷新后重试' }
|
|
1035
|
+
})
|
|
1036
|
+
row.appendChild(button)
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
card.appendChild(row)
|
|
1040
|
+
}
|
|
1041
|
+
for (const slot of last?.canvas?.memberSlots ?? []) {
|
|
1042
|
+
if (slot.name !== fresh.name || slot.provenance !== 'unknown' || !deps.confirmMember || last?.canvas?.revision === undefined) continue
|
|
1043
|
+
const badge=el('div','wb-hint');badge.textContent=slot.member+' · 来源未确认'+(slot.compatibilityInference?' · 兼容推断中':'')+(slot.suspended?' · 已暂停':'');card.appendChild(badge)
|
|
1044
|
+
const choices: ProvenanceChoice[]=(last.sessions??[]).filter(s=>s.joined===true).map(s=>({replacement:s.id,label:displayName(s)+' — 本机完整会话'}))
|
|
1045
|
+
for(const remote of last.remote??[])if(remote.team.includes('/') && remote.via)choices.push({replacement:remote.team,label:displayName(remote)+' — '+remote.via,peer:remote.via})
|
|
1046
|
+
const revision=last.canvas.revision
|
|
1047
|
+
const formKey=JSON.stringify([fresh.name,slot.member,revision,slot.ownerKey,choices])
|
|
1048
|
+
let form=provenanceForms.get(formKey)
|
|
1049
|
+
if(!form){form=createMemberProvenancePicker(choices,choice=>deps.confirmMember!(fresh.name,slot.member,choice.replacement,revision,slot.ownerKey,choice.peer));provenanceForms.set(formKey,form)}
|
|
1050
|
+
card.appendChild(form)
|
|
1051
|
+
}
|
|
1014
1052
|
// Add-member form: static DOM re-mounted per poll (draftForm trick) —
|
|
1015
1053
|
// rebuilt only when the inspected team changes.
|
|
1016
1054
|
if (canvasForm?.name !== fresh.name) {
|
|
@@ -1032,10 +1070,11 @@ export function createWorkbench(deps: WorkbenchDeps): Workbench {
|
|
|
1032
1070
|
form.addEventListener('submit', async ev => {
|
|
1033
1071
|
ev.preventDefault()
|
|
1034
1072
|
const handle = manual.value.trim()
|
|
1035
|
-
const
|
|
1073
|
+
const selected=handle!==''?undefined:pick.selectedOptions[0]
|
|
1074
|
+
const id = handle !== '' ? handle : selected?.dataset.member ?? pick.value
|
|
1036
1075
|
if (id === '') return
|
|
1037
1076
|
submit.disabled = true
|
|
1038
|
-
const ok = await deps.canvasOp('add-member', fresh.name, id)
|
|
1077
|
+
const ok = await deps.canvasOp('add-member', fresh.name, id, undefined, selected?.dataset.peer)
|
|
1039
1078
|
submit.disabled = false
|
|
1040
1079
|
if (ok) manual.value = ''
|
|
1041
1080
|
})
|
|
@@ -1067,7 +1106,10 @@ export function createWorkbench(deps: WorkbenchDeps): Workbench {
|
|
|
1067
1106
|
pick.appendChild(new Option('远端会话队(目录卡片)…', '', true, false))
|
|
1068
1107
|
remoteLabeled = true
|
|
1069
1108
|
}
|
|
1070
|
-
|
|
1109
|
+
const option=new Option(`${displayName(remote)} — ${remote.via ?? '来源未确认'}`, JSON.stringify([remote.team,remote.via]))
|
|
1110
|
+
option.dataset.member=remote.team
|
|
1111
|
+
if(remote.via)option.dataset.peer=remote.via
|
|
1112
|
+
pick.appendChild(option)
|
|
1071
1113
|
}
|
|
1072
1114
|
if (!remoteLabeled) {
|
|
1073
1115
|
pick.appendChild(new Option('(暂无可拉入的远端会话队——见下方说明)', '', true, false))
|
|
@@ -1076,7 +1118,7 @@ export function createWorkbench(deps: WorkbenchDeps): Workbench {
|
|
|
1076
1118
|
card.appendChild(canvasForm.form)
|
|
1077
1119
|
const hint = el('div', 'wb-hint')
|
|
1078
1120
|
hint.textContent =
|
|
1079
|
-
'
|
|
1121
|
+
'成员由人类明确分配。本机写完整会话;远端必须通过已链接主机的有效名片核实并记录完整公钥来源。最小卡、离线或第三方无法验证时保留待确认,不能手输裸句柄绕过来源校验。来源确认不恢复暂停;默认兼容窗口不自动关闭。'
|
|
1080
1122
|
card.appendChild(hint)
|
|
1081
1123
|
// Quick dispatch targets the canvas ROUTING team (0.6.9): local tiers
|
|
1082
1124
|
// first, then the remote members in order — the seam's own priority.
|
|
@@ -1230,6 +1272,8 @@ export function createWorkbench(deps: WorkbenchDeps): Workbench {
|
|
|
1230
1272
|
function update(state: StateBody | null): void {
|
|
1231
1273
|
const unknown = new Set((state?.canvas?.teams ?? []).flatMap(team => team.members.filter(member => member.cliHome === null && member.cliHeld !== true).map(member => JSON.stringify([team.name, member.id]))))
|
|
1232
1274
|
for (const key of cliHomeForms.keys()) if (!unknown.has(key)) cliHomeForms.delete(key)
|
|
1275
|
+
const slots=state?.canvas?.memberSlots??[]
|
|
1276
|
+
for(const key of provenanceForms.keys()){const [name,member,revision]=JSON.parse(key) as [string,string,number];if(revision!==state?.canvas?.revision||!slots.some(slot=>slot.name===name&&slot.member===member&&slot.provenance==='unknown'))provenanceForms.delete(key)}
|
|
1233
1277
|
last = state
|
|
1234
1278
|
// 0.6.12: refresh the readable-log list opportunistically (single flight;
|
|
1235
1279
|
// the sidebar re-renders when rows land, mid-poll remount is idempotent).
|
package/nexus-stage/src/world.ts
CHANGED
|
@@ -154,6 +154,7 @@ export function planSeatClear(sid: string, occupied: ReadonlyArray<{ readonly x:
|
|
|
154
154
|
|
|
155
155
|
/** Minimal session row shape the model needs (topology.SessionRow satisfies it). */
|
|
156
156
|
export interface SessionLite {
|
|
157
|
+
memberRef?: string
|
|
157
158
|
id: string; label: string; team: string; name?: string; description?: string; joined: boolean; live?: boolean; teams?: readonly string[]
|
|
158
159
|
/** Live local sessions only: mid-turn ('running') or idle, and how many receipt-owing tasks wait on it. */
|
|
159
160
|
status?: 'running' | 'idle'; queued?: number
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nelsonlongxiang/dsh-open-a2a-net",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Open A2A network plugin for DeepSeek Harness: signed agent cards, decentralized peer/zone discovery, direct routing model tools, and joinable session nodes with a sidebar control",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -88,7 +88,8 @@
|
|
|
88
88
|
"verify:docs": "node scripts/doc-drift.mjs",
|
|
89
89
|
"reanchor:arch": "node scripts/reanchor-architecture.mjs",
|
|
90
90
|
"verify:nexus": "node scripts/verify-nexus-ci.mjs && npm --prefix nexus-stage run build && npm --prefix nexus-stage run test && git diff --exit-code -- assets/nexusDist/assets && git diff --ignore-cr-at-eol --exit-code -- assets/nexusDist/index.html",
|
|
91
|
-
"verify": "
|
|
91
|
+
"verify": "node scripts/verify-temp-clean.mjs",
|
|
92
|
+
"verify:core": "pnpm typecheck && pnpm verify:docs && pnpm build && pnpm test && pnpm test:build-layout && pnpm verify:nexus"
|
|
92
93
|
},
|
|
93
94
|
"peerDependencies": {
|
|
94
95
|
"@deepseek-ai/cordis": ">=4.0.1",
|