@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
package/lib/tools/info.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* a getter so the deps object can be built before the const is declared.
|
|
8
8
|
* @module src/tools/info
|
|
9
9
|
*/
|
|
10
|
+
import { reminderView } from "../reconciliation-state.js";
|
|
10
11
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
12
|
+
import { splitQualifiedMember } from "../canvas-member.js";
|
|
11
13
|
import { peerDirectoryRows } from "../peer-directory.js";
|
|
12
14
|
import { canvasMemberships, canvasOwners } from "../canvas-discovery.js";
|
|
13
15
|
import { peerContentMeta } from "../node-self.js";
|
|
@@ -60,7 +62,7 @@ export function createInfoTools(deps) {
|
|
|
60
62
|
return id !== undefined && joinedSessions.has(id);
|
|
61
63
|
});
|
|
62
64
|
}
|
|
63
|
-
return joined.has(row.team) || (row.teams ?? []).some(team => joined.has(team));
|
|
65
|
+
return joined.has(row.team) || [...joined].some(ref => splitQualifiedMember(ref)?.handle === row.team && canvasStore.ownerKeyOf(ref) === card.publicKey) || (row.teams ?? []).some(team => joined.has(team));
|
|
64
66
|
});
|
|
65
67
|
/** Human duration for a ledger row: how long a task has been owed or took. */
|
|
66
68
|
const describeAge = (ms) => {
|
|
@@ -163,6 +165,7 @@ export function createInfoTools(deps) {
|
|
|
163
165
|
roster: [...teamMemberships.teamsOf(agentId)],
|
|
164
166
|
canvas: [...canvasTeamsOfSession(agentId)],
|
|
165
167
|
canvasDetails,
|
|
168
|
+
canvasMigration: (() => { const s = deps.canvasMigrationState(); return Object.fromEntries(['canvasLegacyBareMembers', 'epoch', 'legacyBareMembers', 'legacyInferredMembers', 'legacyInferenceResolutions', 'legacyCollisionWarnings', 'suspendedMembersCount'].map(key => [key, s[key]])); })(),
|
|
166
169
|
},
|
|
167
170
|
allowlist: {
|
|
168
171
|
patterns,
|
|
@@ -212,6 +215,7 @@ export function createInfoTools(deps) {
|
|
|
212
215
|
additionalProperties: false,
|
|
213
216
|
properties: {
|
|
214
217
|
team: { type: 'string', required: true },
|
|
218
|
+
memberRef: { type: 'string', description: 'Owner-qualified session selector, bound to the verified publishing host key; not an independent authority.' },
|
|
215
219
|
session: { type: 'string', required: true },
|
|
216
220
|
name: { type: 'string', required: true },
|
|
217
221
|
seatName: { type: 'string', description: 'Human-owned stable display label, never a route identity.' },
|
|
@@ -263,7 +267,8 @@ export function createInfoTools(deps) {
|
|
|
263
267
|
const id = resolveLocal(ref);
|
|
264
268
|
return id !== undefined && joinedSessions.has(id);
|
|
265
269
|
});
|
|
266
|
-
|
|
270
|
+
const card = row.via ? cardCache.get(row.via)?.card : undefined;
|
|
271
|
+
return joined.has(row.team) || [...joined].some(ref => splitQualifiedMember(ref)?.handle === row.team && canvasStore.ownerKeyOf(ref) === card?.publicKey) || (row.teams ?? []).some(name => joined.has(name));
|
|
267
272
|
});
|
|
268
273
|
const hiddenRemote = remoteBefore - teams.filter(row => row.local !== true).length;
|
|
269
274
|
// gh#39 (owner ruling: the listing ADVERTISES targets — an exposed
|
|
@@ -371,6 +376,10 @@ export function createInfoTools(deps) {
|
|
|
371
376
|
},
|
|
372
377
|
},
|
|
373
378
|
},
|
|
379
|
+
deliveryBookkeeping: { type: 'object', additionalProperties: false, required: true, properties: { orphaned: { type: 'number', required: true }, failures: { type: 'number', required: true }, lastError: { type: 'object', additionalProperties: false, properties: { at: { type: 'number', required: true }, code: { type: 'string', required: true } } } } },
|
|
380
|
+
ledgerPersistence: { type: 'object', additionalProperties: false, required: true, properties: { ok: { type: 'boolean', required: true }, lastError: { type: 'object', additionalProperties: false, properties: { at: { type: 'number', required: true }, code: { type: 'string', required: true } } } } },
|
|
381
|
+
nudgeEscalations: { type: 'object', additionalProperties: false, required: true, properties: { queued: { type: 'number', required: true }, unavailable: { type: 'number', required: true }, failed: { type: 'number', required: true }, lastAt: { type: 'number' }, lastError: { type: 'string' } } },
|
|
382
|
+
reconciliationReminders: { type: 'object', additionalProperties: false, required: true, properties: { observed: { type: 'number', required: true }, repeatedlyUnreconciled: { type: 'number', required: true } } },
|
|
374
383
|
idempotency: {
|
|
375
384
|
type: 'object',
|
|
376
385
|
additionalProperties: false,
|
|
@@ -390,6 +399,9 @@ export function createInfoTools(deps) {
|
|
|
390
399
|
render: (_args, value) => [{
|
|
391
400
|
type: 'text',
|
|
392
401
|
text: [
|
|
402
|
+
`Delivery bookkeeping: orphaned=${value.deliveryBookkeeping.orphaned} failures=${value.deliveryBookkeeping.failures}${value.deliveryBookkeeping.lastError ? " code=" + value.deliveryBookkeeping.lastError.code : ""}`,
|
|
403
|
+
`Ledger persistence: ${value.ledgerPersistence.ok ? 'ok' : '!!! FAILED !!!'}${value.ledgerPersistence.lastError ? ' at=' + value.ledgerPersistence.lastError.at + ' code=' + value.ledgerPersistence.lastError.code : ''}`,
|
|
404
|
+
`Nudge escalations: queued=${value.nudgeEscalations.queued} unavailable=${value.nudgeEscalations.unavailable} failed=${value.nudgeEscalations.failed}${value.nudgeEscalations.lastAt === undefined ? '' : ' lastAt=' + value.nudgeEscalations.lastAt}${value.nudgeEscalations.lastError ? ' code=' + value.nudgeEscalations.lastError : ''}`,
|
|
393
405
|
...(value.inFlight.length === 0 ? [] : ['In flight:']),
|
|
394
406
|
...value.inFlight.map((route) => ` → ${route.team} via ${route.peer === 'local' ? 'this host' : route.peer}`),
|
|
395
407
|
`Peers (${String(value.peers.length)}):`,
|
|
@@ -411,6 +423,10 @@ export function createInfoTools(deps) {
|
|
|
411
423
|
inFlight: [...inFlightRoutes.values()].map(route => ({ team: route.team, peer: route.peer, startedAt: route.startedAt })),
|
|
412
424
|
activity: recentActivity.slice(),
|
|
413
425
|
idempotency: idempotencyStore.stats(),
|
|
426
|
+
deliveryBookkeeping: taskLedger.deliveryBookkeepingState(),
|
|
427
|
+
ledgerPersistence: taskLedger.persistenceState(),
|
|
428
|
+
nudgeEscalations: deps.nudgeEscalations?.() ?? { queued: 0, unavailable: 0, failed: 0 },
|
|
429
|
+
reconciliationReminders: reminderSummary(),
|
|
414
430
|
};
|
|
415
431
|
}
|
|
416
432
|
// S5 scoped plane: the tracked fleet narrows to peers whose verified
|
|
@@ -451,11 +467,19 @@ export function createInfoTools(deps) {
|
|
|
451
467
|
peers,
|
|
452
468
|
inFlight: [...inFlightRoutes.values()].map(route => ({ team: route.team, peer: route.peer, startedAt: route.startedAt })).filter(inScope),
|
|
453
469
|
activity: recentActivity.filter(inScope),
|
|
470
|
+
deliveryBookkeeping: taskLedger.deliveryBookkeepingState(),
|
|
471
|
+
ledgerPersistence: taskLedger.persistenceState(),
|
|
472
|
+
nudgeEscalations: deps.nudgeEscalations?.() ?? { queued: 0, unavailable: 0, failed: 0 },
|
|
454
473
|
idempotency: idempotencyStore.stats(),
|
|
474
|
+
reconciliationReminders: reminderSummary(),
|
|
455
475
|
};
|
|
456
476
|
},
|
|
457
477
|
}));
|
|
458
478
|
}
|
|
479
|
+
function reminderSummary() {
|
|
480
|
+
const seen = taskLedger.list().filter(row => row.status === 'pending').map(row => reminderView(taskLedger.reminderState(row.taskId)));
|
|
481
|
+
return { observed: seen.filter(v => v.reminderAttempts !== undefined).length, repeatedlyUnreconciled: seen.filter(v => v.repeatedlyUnreconciled).length };
|
|
482
|
+
}
|
|
459
483
|
function registerTasks() {
|
|
460
484
|
ctx.tools.register(defineTool({
|
|
461
485
|
name: 'a2a_tasks',
|
|
@@ -488,6 +512,9 @@ export function createInfoTools(deps) {
|
|
|
488
512
|
status: { type: 'string', required: true },
|
|
489
513
|
deadAt: { type: 'number' },
|
|
490
514
|
answeredAt: { type: 'number' },
|
|
515
|
+
receiptDelivery: { type: 'string', enum: ['receipt-undelivered'] },
|
|
516
|
+
consumed: { type: 'boolean' }, deliveryObservation: { type: 'string', enum: ['consumed', 'delivered-unconsumed'] },
|
|
517
|
+
reminderAttempts: { type: 'number' }, reminderDelivered: { type: 'number' }, reminderLastAt: { type: 'number' }, reminderNextAt: { type: 'number' }, reminderResult: { type: 'string' }, reminderDiagnostic: { type: 'string', enum: ['metadata-reset'] }, repeatedlyUnreconciled: { type: 'boolean' },
|
|
491
518
|
direction: { type: 'string', enum: ['inbound', 'outbound'] },
|
|
492
519
|
receiptExpected: { type: 'boolean' },
|
|
493
520
|
forwarded: { type: 'boolean' },
|
|
@@ -516,6 +543,7 @@ export function createInfoTools(deps) {
|
|
|
516
543
|
team: { type: 'string', required: true },
|
|
517
544
|
startedAt: { type: 'number', required: true },
|
|
518
545
|
resolvedAt: { type: 'number', required: true },
|
|
546
|
+
withdrawalResult: { type: 'string', enum: ['removed', 'claimed-steered', 'withdraw-failed-steered', 'no-agent'] },
|
|
519
547
|
summary: { type: 'string' },
|
|
520
548
|
brief: { type: 'string' },
|
|
521
549
|
forwarded: { type: 'boolean' },
|
|
@@ -552,7 +580,7 @@ export function createInfoTools(deps) {
|
|
|
552
580
|
...settleable.map((task) => {
|
|
553
581
|
const context = task.contextId === undefined || task.contextId === '' ? '' : `, follow-up context ${task.contextId}`;
|
|
554
582
|
const brief = task.brief === undefined || task.brief === '' ? '' : ` — "${task.brief}"`;
|
|
555
|
-
return ` - ${task.taskId}${brief} → ${task.team} (via ${task.peer === 'local' ? 'this host' : task.peer}), waiting ${describeAge(Date.now() - task.startedAt)}${context}`;
|
|
583
|
+
return ` - ${task.taskId}${brief} → ${task.team} (via ${task.peer === 'local' ? 'this host' : task.peer}), waiting ${describeAge(Date.now() - task.startedAt)}${context}${task.receiptDelivery ? ' — ' + task.receiptDelivery : ''}${task.reminderAttempts === undefined ? '' : ` — reminders ${task.reminderDelivered ?? 0}/${task.reminderAttempts}, next ${task.reminderNextAt ?? 0}${task.repeatedlyUnreconciled ? ' (多次提醒未对账)' : ''}`}`;
|
|
556
584
|
}),
|
|
557
585
|
...(staleCount > 0 ? [`Delivered conversationally without a formal receipt (${String(staleCount)}) — not debts; they auto-dead-letter at the stale TTL.`] : []),
|
|
558
586
|
];
|
|
@@ -566,7 +594,7 @@ export function createInfoTools(deps) {
|
|
|
566
594
|
...(value.archivedTotal > 0 ? [`Archived (${String(value.archivedTotal)}), most recent first:`] : []),
|
|
567
595
|
...value.archive.map((entry) => {
|
|
568
596
|
const brief = entry.brief === undefined || entry.brief === '' ? '' : ` — "${entry.brief}"`;
|
|
569
|
-
return ` - ${entry.taskId}${brief} → ${entry.team}: after ${describeAge(entry.resolvedAt - entry.startedAt)}, ${entry.summary === undefined || entry.summary === '' ? 'resolved' : entry.summary}`;
|
|
597
|
+
return ` - ${entry.taskId}${brief} → ${entry.team}: after ${describeAge(entry.resolvedAt - entry.startedAt)}, ${entry.summary === undefined || entry.summary === '' ? 'resolved' : entry.summary}${entry.withdrawalResult ? `, withdrawal: ${entry.withdrawalResult}` : ''}`;
|
|
570
598
|
}),
|
|
571
599
|
...(value.archivedTotal > value.archive.length ? [` (+${String(value.archivedTotal - value.archive.length)} older not shown)`] : []),
|
|
572
600
|
].join('\n'),
|
|
@@ -581,12 +609,14 @@ export function createInfoTools(deps) {
|
|
|
581
609
|
ok: true,
|
|
582
610
|
// Project the declared fields only: ledger rows also carry internal audit facts (the #114 auth record,
|
|
583
611
|
// #139 peerKey/peerHost) the output schema does not admit — spreading the row failed every call (0.6.32+).
|
|
584
|
-
tasks: taskLedger.list().map(({ taskId, team, peer, startedAt, contextId, brief, status, deadAt, answeredAt, direction, receiptExpected, forwarded, deliveredTo, reason, sender }) => ({
|
|
585
|
-
taskId, team, peer, startedAt, status,
|
|
612
|
+
tasks: taskLedger.list().map(({ taskId, team, peer, startedAt, contextId, brief, status, deadAt, answeredAt, receiptDelivery, consumed, direction, receiptExpected, forwarded, deliveredTo, reason, sender }) => ({
|
|
613
|
+
taskId, team, peer, startedAt, status, ...reminderView(taskLedger.reminderState(taskId)),
|
|
586
614
|
...(contextId !== undefined ? { contextId } : {}),
|
|
587
615
|
...(brief !== undefined ? { brief } : {}),
|
|
588
616
|
...(deadAt !== undefined ? { deadAt } : {}),
|
|
589
617
|
...(answeredAt !== undefined ? { answeredAt } : {}),
|
|
618
|
+
...(receiptDelivery ? { receiptDelivery } : {}),
|
|
619
|
+
...(consumed !== undefined ? { consumed, deliveryObservation: consumed ? 'consumed' : 'delivered-unconsumed' } : {}),
|
|
590
620
|
...(direction !== undefined ? { direction } : {}),
|
|
591
621
|
...(receiptExpected !== undefined ? { receiptExpected } : {}),
|
|
592
622
|
...(forwarded !== undefined ? { forwarded } : {}),
|
|
@@ -594,7 +624,7 @@ export function createInfoTools(deps) {
|
|
|
594
624
|
...(reason !== undefined ? { reason } : {}),
|
|
595
625
|
...(sender !== undefined ? { sender } : {}),
|
|
596
626
|
})),
|
|
597
|
-
archive: archiveAll.slice(0, 5).map(({ taskId, team, startedAt, resolvedAt, summary, brief, sender }) => ({ taskId, team, startedAt, resolvedAt, ...(summary !== undefined && summary !== '' ? { summary } : {}), ...(brief !== undefined ? { brief } : {}), ...(sender !== undefined ? { sender } : {}) })),
|
|
627
|
+
archive: archiveAll.slice(0, 5).map(({ taskId, team, startedAt, resolvedAt, summary, brief, withdrawalResult, sender }) => ({ ...(withdrawalResult ? { withdrawalResult } : {}), taskId, team, startedAt, resolvedAt, ...(summary !== undefined && summary !== '' ? { summary } : {}), ...(brief !== undefined ? { brief } : {}), ...(sender !== undefined ? { sender } : {}) })),
|
|
598
628
|
archivedTotal: archiveAll.length,
|
|
599
629
|
};
|
|
600
630
|
},
|
|
@@ -119,7 +119,7 @@ export declare class A2aClient {
|
|
|
119
119
|
* @param callerSession - caller label overriding the node's label (the calling session's node label).
|
|
120
120
|
* @returns the canonical route result (success or explicit failure).
|
|
121
121
|
*/
|
|
122
|
-
routeDirect(baseUrl: string, team: string, message: string, contextId?: string, signal?: AbortSignal, callerSession?: string, asyncMode?: boolean, taskIdFromCaller?: string, callbackAddress?: string, guard?: string): Promise<A2aRouteResult>;
|
|
122
|
+
routeDirect(baseUrl: string, team: string, message: string, contextId?: string, signal?: AbortSignal, callerSession?: string, asyncMode?: boolean, taskIdFromCaller?: string, callbackAddress?: string, guard?: string, cancelTaskId?: string): Promise<A2aRouteResult>;
|
|
123
123
|
/**
|
|
124
124
|
* Query a peer's `/a2a/query` outcome surface (W7 slice 2): read-only
|
|
125
125
|
* lookup of one claimed task's settled outcome. The fingerprint is the
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const ADMITTED_BOOKKEEPING_NOTE = "\u5DF2\u9001\u8FBE\u3001\u63A5\u6536\u65B9\u8BB0\u8D26\u5931\u8D25\uFF0C\u8BF7\u5BF9\u8D26\uFF0C\u4E0D\u8981\u91CD\u6D3E\u3002Delivered, but receiver bookkeeping failed; reconcile the original task, do not redispatch.";
|
|
2
|
+
export declare class AdmittedBookkeepingError extends Error {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
constructor(error: unknown);
|
|
5
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface QualifiedMember {
|
|
2
|
+
handle: string;
|
|
3
|
+
fingerprint: string;
|
|
4
|
+
}
|
|
5
|
+
/** CLI handles keep their existing home/deposit rules and are never parsed as session members. */
|
|
6
|
+
export declare function splitQualifiedMember(ref: string): QualifiedMember | undefined;
|
|
7
|
+
export declare function validMemberOwner(ref: string, key: string): boolean;
|
|
8
|
+
export declare function qualifiedSessionMember(handle: string, key: string): string | undefined;
|
|
9
|
+
export type MemberProvenance = 'local-id' | 'confirmed-local' | 'confirmed-remote' | 'unknown' | 'cli' | 'conflict';
|
|
10
|
+
export declare function memberProvenance(ref: string, ownerKey: string | undefined, ownKey: string): MemberProvenance;
|
|
@@ -17,7 +17,21 @@
|
|
|
17
17
|
* @module @nelsonlongxiang/dsh-open-a2a-net/canvas-store
|
|
18
18
|
*/
|
|
19
19
|
/** On-disk shape: ordered team entries, each an ordered member id list. */
|
|
20
|
+
export interface SuspendedCanvasMember {
|
|
21
|
+
readonly team: string;
|
|
22
|
+
readonly member: string;
|
|
23
|
+
readonly localSessionId: string;
|
|
24
|
+
readonly reason: 'leave' | 'archive' | 'remove';
|
|
25
|
+
readonly at: string;
|
|
26
|
+
readonly token: string;
|
|
27
|
+
}
|
|
28
|
+
/** A bare session handle is not a CLI identity or a qualified owner selector. */
|
|
29
|
+
export declare function legacyBareMember(id: string): boolean;
|
|
20
30
|
export interface CanvasSnapshot {
|
|
31
|
+
readonly formatVersion?: number;
|
|
32
|
+
readonly revision?: number;
|
|
33
|
+
readonly memberOwners?: Readonly<Record<string, string>>;
|
|
34
|
+
readonly suspendedMembers?: readonly SuspendedCanvasMember[];
|
|
21
35
|
readonly teams: ReadonlyArray<{
|
|
22
36
|
readonly name: string;
|
|
23
37
|
readonly members: readonly string[];
|
|
@@ -52,16 +66,28 @@ export declare function validRemoteMemberRef(id: string): boolean;
|
|
|
52
66
|
* registry knowledge: callers validate join state and resolve members.
|
|
53
67
|
*/
|
|
54
68
|
export declare class CanvasStore {
|
|
69
|
+
private readonly options;
|
|
55
70
|
/** Team entries in creation order; members in add order (routing priority). */
|
|
56
71
|
private teams;
|
|
57
72
|
private cliHome;
|
|
58
73
|
private cliHomeCleared;
|
|
74
|
+
private suspended;
|
|
75
|
+
private revision;
|
|
76
|
+
private committed;
|
|
77
|
+
private memberOwners;
|
|
78
|
+
private disk;
|
|
79
|
+
private readOnlyReason;
|
|
80
|
+
private lastWriteError;
|
|
81
|
+
private failedSuspension;
|
|
59
82
|
private readonly path;
|
|
60
83
|
private readonly listeners;
|
|
61
84
|
/**
|
|
62
85
|
* @param path - the snapshot file; an empty path keeps the store in memory.
|
|
63
86
|
*/
|
|
64
|
-
constructor(path: string
|
|
87
|
+
constructor(path: string, options?: {
|
|
88
|
+
legacyBareMembers?: boolean;
|
|
89
|
+
ownPublicKey?: string;
|
|
90
|
+
});
|
|
65
91
|
/**
|
|
66
92
|
* Observe changes: the listener runs after every write that changed the
|
|
67
93
|
* store (never on restore). A throwing listener cannot break the write.
|
|
@@ -73,13 +99,41 @@ export declare class CanvasStore {
|
|
|
73
99
|
private restore;
|
|
74
100
|
/** Persist the current snapshot (no-op without a path), then tell the listeners. */
|
|
75
101
|
private persist;
|
|
102
|
+
private serialize;
|
|
76
103
|
private write;
|
|
104
|
+
private replaceFile;
|
|
105
|
+
private lockInfo;
|
|
77
106
|
/** Every team name, in creation order. */
|
|
78
107
|
list(): readonly string[];
|
|
79
108
|
/** Whether the named team exists. */
|
|
80
109
|
hasTeam(name: string): boolean;
|
|
81
110
|
/** The named team's member ids in routing-priority order (a copy). */
|
|
82
111
|
membersOf(name: string): readonly string[];
|
|
112
|
+
/** Inspection only: never use raw rows for routing, authorization or counts. */
|
|
113
|
+
rawMembersOf(name: string): readonly string[];
|
|
114
|
+
isSuspended(name: string, member: string): boolean;
|
|
115
|
+
suspensionOf(name: string, member: string): readonly SuspendedCanvasMember[];
|
|
116
|
+
state(): {
|
|
117
|
+
revision: number;
|
|
118
|
+
suspendedMembers: readonly SuspendedCanvasMember[];
|
|
119
|
+
error?: string;
|
|
120
|
+
lock?: unknown;
|
|
121
|
+
directoryFsync: string;
|
|
122
|
+
};
|
|
123
|
+
ownerKeyOf(member: string): string | undefined;
|
|
124
|
+
private ownerConflict;
|
|
125
|
+
/** Owner control only: CAS the complete slot and preserve suspension plus routing priority. */
|
|
126
|
+
confirmMember(team: string, member: string, replacement: string, ownerKey: string | undefined, revision: unknown, expectedOwner: unknown): {
|
|
127
|
+
ok: boolean;
|
|
128
|
+
error?: string;
|
|
129
|
+
};
|
|
130
|
+
/** Durably revoke canvas consent BEFORE join/agent/roster state is removed. */
|
|
131
|
+
revokeLocal(id: string, ownQualified: string | undefined, inferredBare: string | undefined, reason: SuspendedCanvasMember['reason'], team?: string): boolean;
|
|
132
|
+
/** Owner-only CAS. The caller supplies the exact slot and the complete current suspension records. */
|
|
133
|
+
regulateSuspended(action: 'restore' | 'delete', team: string, member: string, revision: unknown, expected: unknown): {
|
|
134
|
+
ok: boolean;
|
|
135
|
+
error?: string;
|
|
136
|
+
};
|
|
83
137
|
/** Frozen, operator-linked CLI home; undefined means unknown or ambiguous. */
|
|
84
138
|
cliHomeOf(handle: string): string | undefined;
|
|
85
139
|
pinnedCliHomes(): ReadonlyArray<readonly [string, string]>;
|
|
@@ -114,6 +168,7 @@ export declare class CanvasStore {
|
|
|
114
168
|
*/
|
|
115
169
|
addMember(name: string, id: string, home?: string, options?: {
|
|
116
170
|
replaceHome?: boolean;
|
|
171
|
+
ownerKey?: string;
|
|
117
172
|
}): boolean;
|
|
118
173
|
/**
|
|
119
174
|
* Remove one member from one team (a no-op when absent).
|
package/lib/types/config.d.ts
CHANGED
|
@@ -77,6 +77,8 @@ export interface Config {
|
|
|
77
77
|
* closed, it signs and accepts the audience-bound form only.
|
|
78
78
|
*/
|
|
79
79
|
readonly originClaimUnboundWindow: boolean;
|
|
80
|
+
/** #335: preserve unsuspended legacy bare member inference until the owner closes the window. */
|
|
81
|
+
readonly canvasLegacyBareMembers: boolean;
|
|
80
82
|
/**
|
|
81
83
|
* Extra origins this host answers to as an OriginClaim audience, beyond
|
|
82
84
|
* its card URL and the address a request arrives on (port-mapped or
|
|
@@ -21,6 +21,7 @@ import type { OriginClaimTarget } from '../origin-claim.ts';
|
|
|
21
21
|
export type RemoteRowsCache = {
|
|
22
22
|
at: number;
|
|
23
23
|
rows: {
|
|
24
|
+
memberRef?: string;
|
|
24
25
|
team: string;
|
|
25
26
|
name: string;
|
|
26
27
|
origin?: string;
|
|
@@ -62,6 +63,14 @@ export interface AdminFaceDeps extends Pick<CanvasNotifyDeps, 'verifyOriginClaim
|
|
|
62
63
|
readonly cliDeposits: CliDepositStore;
|
|
63
64
|
readonly groupStore: GroupStore;
|
|
64
65
|
readonly canvasStore: CanvasStore;
|
|
66
|
+
readonly canvasMemberForWrite: (ref: string, peer?: unknown) => Promise<{
|
|
67
|
+
ok: true;
|
|
68
|
+
member: string;
|
|
69
|
+
ownerKey?: string;
|
|
70
|
+
} | {
|
|
71
|
+
ok: false;
|
|
72
|
+
error: string;
|
|
73
|
+
}>;
|
|
65
74
|
readonly cliHomeForAdd: (handle: string, requested?: unknown) => Promise<CliHomeDecision>;
|
|
66
75
|
readonly cliHomeOptions: (handle: string) => Promise<{
|
|
67
76
|
homes: string[];
|
|
@@ -70,6 +79,8 @@ export interface AdminFaceDeps extends Pick<CanvasNotifyDeps, 'verifyOriginClaim
|
|
|
70
79
|
}>;
|
|
71
80
|
readonly layoutStore: LayoutStore;
|
|
72
81
|
readonly taskLedger: TaskLedger;
|
|
82
|
+
readonly reminderDiagnostics?: () => unknown;
|
|
83
|
+
readonly nudgeEscalations?: () => import('../nudge-observability.ts').NudgeEscalations;
|
|
73
84
|
readonly enforceRefused?: {
|
|
74
85
|
reason: string;
|
|
75
86
|
blockers: readonly string[];
|
|
@@ -126,6 +137,8 @@ export interface AdminFaceDeps extends Pick<CanvasNotifyDeps, 'verifyOriginClaim
|
|
|
126
137
|
readonly sessionTeamOf: (agent: Agent) => string;
|
|
127
138
|
/** gh#131: the `<zone>/<id8>` handle a canvas may hold for this local session, if it reads as this session. */
|
|
128
139
|
readonly ownCanvasHandleOf: (id: string) => string | undefined;
|
|
140
|
+
readonly revokeCanvasMember: (id: string, reason: 'leave' | 'archive' | 'remove', name?: string) => boolean;
|
|
141
|
+
readonly canvasMigrationState: () => Record<string, unknown>;
|
|
129
142
|
/** gh#131: the local session a remote-shaped member ref reads as, if unambiguous. */
|
|
130
143
|
readonly localSessionOfHandle: (ref: string) => string | undefined;
|
|
131
144
|
/** gh#152: the session's canvas teams read through the local lens — a handle-stored membership of this session counts. */
|
|
@@ -150,6 +163,14 @@ export interface AdminFaceDeps extends Pick<CanvasNotifyDeps, 'verifyOriginClaim
|
|
|
150
163
|
count: number;
|
|
151
164
|
lastAt: number;
|
|
152
165
|
}>;
|
|
166
|
+
readonly claimRejectsInboundCount: number;
|
|
167
|
+
readonly claimRejectsRecent: ReadonlyArray<{
|
|
168
|
+
caller: string;
|
|
169
|
+
callerUrl: string;
|
|
170
|
+
reason: string;
|
|
171
|
+
count: number;
|
|
172
|
+
lastAt: number;
|
|
173
|
+
}>;
|
|
153
174
|
readonly ownCardUrl: string;
|
|
154
175
|
/** #282 S2: this host's canvas owner fingerprint. */
|
|
155
176
|
readonly ownCanvasFingerprint: string;
|
|
@@ -26,7 +26,7 @@ export interface FileFaceDeps {
|
|
|
26
26
|
readonly ctx: Context;
|
|
27
27
|
readonly config: Pick<Config, 'apiKey' | 'team'>;
|
|
28
28
|
readonly blobStore: BlobStore;
|
|
29
|
-
readonly canvasStore: Pick<CanvasStore, 'membersOf'
|
|
29
|
+
readonly canvasStore: Pick<CanvasStore, 'membersOf'> & Partial<Pick<CanvasStore, 'ownerKeyOf'>>;
|
|
30
30
|
readonly verifyOriginClaim: (req: IncomingMessage, options?: {
|
|
31
31
|
readonly cli?: boolean;
|
|
32
32
|
}) => Promise<OriginClaimResult>;
|
|
@@ -22,6 +22,7 @@ import type { IdempotencyStore } from '../idempotency-store.ts';
|
|
|
22
22
|
import type { JoinedSessions } from '../joined-store.ts';
|
|
23
23
|
import { type TaskLedger } from '../task-ledger.ts';
|
|
24
24
|
import { type Admission, type RemoteCliProof, type SettlementRefusal, type Settler } from '../task-auth.ts';
|
|
25
|
+
import { type OriginClaimVerdict } from '../origin-claim.ts';
|
|
25
26
|
import type { Config } from '../index.ts';
|
|
26
27
|
/**
|
|
27
28
|
* #139 (design §4.4): add `settlement: {status:'refused', reason, task_id}`
|
|
@@ -46,6 +47,7 @@ export type ColdWake = {
|
|
|
46
47
|
export type RouteOutcome = {
|
|
47
48
|
ok: true;
|
|
48
49
|
reply: string;
|
|
50
|
+
receiverBookkeeping?: 'failed';
|
|
49
51
|
status?: string;
|
|
50
52
|
bridge?: 'native-teams';
|
|
51
53
|
placeholder?: boolean;
|
|
@@ -105,18 +107,7 @@ export type NativeRoundOutcome = {
|
|
|
105
107
|
readonly aborted?: boolean;
|
|
106
108
|
};
|
|
107
109
|
/** What verifyOriginClaim answers (see index.ts; reason widens to string here — the faces only interpolate it). */
|
|
108
|
-
export type OriginClaimResult =
|
|
109
|
-
ok: true;
|
|
110
|
-
caller: string;
|
|
111
|
-
callerUrl: string;
|
|
112
|
-
card: A2aPeerCard;
|
|
113
|
-
bound: boolean;
|
|
114
|
-
cli?: true;
|
|
115
|
-
bodyBound?: true;
|
|
116
|
-
} | {
|
|
117
|
-
ok: false;
|
|
118
|
-
reason: string;
|
|
119
|
-
};
|
|
110
|
+
export type OriginClaimResult = OriginClaimVerdict;
|
|
120
111
|
/** The closure captures the wire faces read (grows with each extracted face). */
|
|
121
112
|
export interface WireDeps {
|
|
122
113
|
readonly ctx: Context;
|
|
@@ -124,6 +115,14 @@ export interface WireDeps {
|
|
|
124
115
|
readonly config: Config;
|
|
125
116
|
readonly cliDeposits: CliDepositStore;
|
|
126
117
|
readonly canvasStore: CanvasStore;
|
|
118
|
+
readonly canvasMemberForWrite: (ref: string, peer?: unknown, editorCard?: A2aPeerCard) => Promise<{
|
|
119
|
+
ok: true;
|
|
120
|
+
member: string;
|
|
121
|
+
ownerKey?: string;
|
|
122
|
+
} | {
|
|
123
|
+
ok: false;
|
|
124
|
+
error: string;
|
|
125
|
+
}>;
|
|
127
126
|
readonly canvasNameReserved: (name: string) => boolean;
|
|
128
127
|
readonly sessionNodes: Map<string, Agent>;
|
|
129
128
|
readonly joinedSessions: JoinedSessions;
|
|
@@ -143,6 +142,9 @@ export interface WireDeps {
|
|
|
143
142
|
readonly forwardReceiptBasis: (taskId: string, caller: string, cliHandle: string, claim: Extract<OriginClaimResult, {
|
|
144
143
|
ok: true;
|
|
145
144
|
}>) => string | undefined;
|
|
145
|
+
readonly receiptReplayAllowed: (message: string, caller: string, team: string, claim: Extract<OriginClaimResult, {
|
|
146
|
+
ok: true;
|
|
147
|
+
}>) => boolean;
|
|
146
148
|
readonly callerLinkedToThisHost: (caller: string, callerUrl: string, callerCard: A2aPeerCard | undefined) => boolean;
|
|
147
149
|
/** This host's own card URL (read when a request lands; set once the web server settles). */
|
|
148
150
|
readonly ownCardUrl: string;
|
|
@@ -152,7 +154,7 @@ export interface WireDeps {
|
|
|
152
154
|
readonly peerKeyAdmits: (req: IncomingMessage, rawBody?: Uint8Array) => Promise<boolean>;
|
|
153
155
|
readonly a2aCallerRefusal: (caller: string, targetTeam?: string) => string | undefined;
|
|
154
156
|
/** #114 S2: the same decision with the rule and basis that admitted the caller. */
|
|
155
|
-
readonly a2aCallerAdmission: (caller: string, targetTeam?: string, cliProof?: RemoteCliProof) => Admission;
|
|
157
|
+
readonly a2aCallerAdmission: (caller: string, targetTeam?: string, cliProof?: RemoteCliProof, signerKey?: string) => Admission;
|
|
156
158
|
/** #114 S2: this host's allowlist version right now (design §2.3). */
|
|
157
159
|
readonly allowlistVersionNow: () => string;
|
|
158
160
|
/** #114 S6: record the version if it moved and re-judge affected tasks (observe only). */
|
|
@@ -192,6 +194,11 @@ export interface WireDeps {
|
|
|
192
194
|
readonly sessionTeamOf: (agent: Agent) => string;
|
|
193
195
|
/** gh#131: the local session one of this host's own `<zone>/<id8>` handles names, if exactly one. */
|
|
194
196
|
readonly localSessionOfHandle: (ref: string) => string | undefined;
|
|
197
|
+
readonly cancelQueuedTask: (taskId: string, message: string, sender: string, original?: import('../task-ledger.ts').TaskRecord) => Promise<{
|
|
198
|
+
removed: boolean;
|
|
199
|
+
steered: boolean;
|
|
200
|
+
}>;
|
|
201
|
+
readonly followupRelay: (agent: Agent, text: string, sender?: string, localFrom?: string, taskId?: string, callerKey?: string, taskExpected?: boolean) => void;
|
|
195
202
|
readonly steerRelay: (agent: Agent, text: string, sender?: string) => void;
|
|
196
203
|
readonly fromClause: (from: string) => string;
|
|
197
204
|
readonly sanitizeFromClause: (from: string) => string;
|
|
@@ -210,7 +217,7 @@ export interface WireDeps {
|
|
|
210
217
|
readonly nativeTeamsRound: (prepared: Extract<NativePrepared, {
|
|
211
218
|
ok: true;
|
|
212
219
|
}>, team: string, caller: string, message: string, taskId: string | undefined, proofClause: string, external?: AbortSignal, proofHeader?: (team: string, taskId: string, from: string, proofClause: string) => string) => Promise<NativeRoundOutcome>;
|
|
213
|
-
readonly routeIntoAgent: (team: string, message: string, caller: string, taskId?: string, proofClause?: string, directHeader?: (from: string) => string, localFrom?: string, onSteered?: (target: Agent, floor: number) => void) => Promise<RouteOutcome>;
|
|
220
|
+
readonly routeIntoAgent: (team: string, message: string, caller: string, taskId?: string, proofClause?: string, directHeader?: (from: string) => string, localFrom?: string, onSteered?: (target: Agent, floor: number) => void, callerKey?: string) => Promise<RouteOutcome>;
|
|
214
221
|
}
|
|
215
222
|
/**
|
|
216
223
|
* #187: what a verified claim may vouch for at settlement. The signing key always (the card was
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** #356 sourced follow-up admission after the existing A2A authorization/queue gates. */
|
|
2
|
+
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
3
|
+
import { MessageId } from '@deepseek-ai/dsh-llm';
|
|
4
|
+
import type { TaskLedger } from './task-ledger.ts';
|
|
5
|
+
export interface FollowupDeps {
|
|
6
|
+
ledger: TaskLedger;
|
|
7
|
+
taint: (target: Agent, localFrom?: string) => void;
|
|
8
|
+
warn: (code: string) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare class FollowupDelivery {
|
|
11
|
+
private readonly d;
|
|
12
|
+
constructor(d: FollowupDeps);
|
|
13
|
+
deliver(agent: Agent, text: string, sender?: string, localFrom?: string, taskId?: string, callerKey?: string, taskExpected?: boolean): void;
|
|
14
|
+
/** Only called after the existing cancel authorization has identified the original task. */
|
|
15
|
+
withdraw(taskId: string, controller: {
|
|
16
|
+
updateQueue: (request: {
|
|
17
|
+
sessionId: Agent['id'];
|
|
18
|
+
itemId: MessageId;
|
|
19
|
+
action: {
|
|
20
|
+
kind: 'remove';
|
|
21
|
+
};
|
|
22
|
+
}) => Promise<unknown>;
|
|
23
|
+
} | undefined, resolve: (id: string) => Agent | undefined): Promise<'removed' | 'claimed' | 'legacy'>;
|
|
24
|
+
claimed(agent: Agent, messageId: string): void;
|
|
25
|
+
}
|
|
@@ -142,6 +142,8 @@ export declare class IdempotencyStore {
|
|
|
142
142
|
* Unknown or already-settled ids answer `false`. Never throws.
|
|
143
143
|
* @returns whether the outcome was recorded.
|
|
144
144
|
*/
|
|
145
|
+
/** Release only an unchanged, uncompleted receipt attempt whose settlement did not commit. */
|
|
146
|
+
releaseUncompleted(taskId: string, fingerprint: string): void;
|
|
145
147
|
recordOutcome(taskId: string, outcome: TaskOutcome, settledAt?: number): boolean;
|
|
146
148
|
/**
|
|
147
149
|
* Read-only outcome lookup for the `/a2a/query` surface (W7 slice 2).
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Bounded completion gate for inbound request verification. */
|
|
2
|
+
import type { IncomingMessage } from 'node:http';
|
|
3
|
+
import type { Admission } from './task-auth.ts';
|
|
4
|
+
import { type OriginClaimVerdict } from './origin-claim.ts';
|
|
5
|
+
export declare const WIRE_ERROR_INBOUND_VERIFICATION = -32011;
|
|
6
|
+
export declare function hasOriginClaimFields(req: Pick<IncomingMessage, 'headers'>): boolean;
|
|
7
|
+
export declare function completeInboundVerification(work: () => Promise<OriginClaimVerdict>, timeoutMs?: number): Promise<OriginClaimVerdict>;
|
|
8
|
+
export declare function verifiedInboundAdmission(claim: OriginClaimVerdict, present: boolean, requireBound: boolean, admit: (signerKey: string | undefined) => Admission): {
|
|
9
|
+
ok: true;
|
|
10
|
+
admission: Admission;
|
|
11
|
+
} | {
|
|
12
|
+
ok: false;
|
|
13
|
+
reason: string;
|
|
14
|
+
};
|
package/lib/types/index.d.ts
CHANGED
|
@@ -30,6 +30,11 @@ declare module '@deepseek-ai/dsh-llm' {
|
|
|
30
30
|
* ui-chat's relay context body renders "来自 X" + the full text from
|
|
31
31
|
* this field; without it the row falls back to an opaque body. */
|
|
32
32
|
senderSessionId?: string;
|
|
33
|
+
/** Audited A2A task identity and clean same-host lineage, never a human principal. */
|
|
34
|
+
taskId?: string;
|
|
35
|
+
localSender?: string;
|
|
36
|
+
/** Host-built node reminder only; never copied from an inbound peer source. */
|
|
37
|
+
nodeReminder?: true;
|
|
33
38
|
};
|
|
34
39
|
}
|
|
35
40
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Observation only; no scheduling, delivery or ledger authority. */
|
|
2
|
+
export interface NudgeEscalations {
|
|
3
|
+
queued: number;
|
|
4
|
+
unavailable: number;
|
|
5
|
+
failed: number;
|
|
6
|
+
lastAt?: number;
|
|
7
|
+
lastError?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function safeObservationCode(error: unknown, fallback: string): string;
|
|
10
|
+
export declare class NudgeObservability {
|
|
11
|
+
private readonly warn;
|
|
12
|
+
private readonly now;
|
|
13
|
+
private value;
|
|
14
|
+
constructor(warn: (message: string) => void, now?: () => number);
|
|
15
|
+
snapshot(): NudgeEscalations;
|
|
16
|
+
note(result: 'queued' | 'unavailable' | 'failed', team: string, ref: string, error?: unknown): void;
|
|
17
|
+
}
|
|
@@ -28,6 +28,8 @@ import type { CardCacheEntry } from './runtime.ts';
|
|
|
28
28
|
import type { A2aPeerCard } from './types.ts';
|
|
29
29
|
/** ts skew tolerance (protocol constant, §7). */
|
|
30
30
|
export declare const ORIGIN_CLAIM_SKEW_MS: number;
|
|
31
|
+
/** Local verification work budget, including response body reads. */
|
|
32
|
+
export declare const ORIGIN_CLAIM_VERIFY_MS = 5000;
|
|
31
33
|
/** Domain tag of the bound form: a v1 signature can never verify as v2. */
|
|
32
34
|
export declare const ORIGIN_CLAIM_BOUND_TAG = "a2a-origin-claim/2";
|
|
33
35
|
export declare const ORIGIN_CLAIM_BODY_TAG = "a2a-origin-claim/3";
|
|
@@ -39,7 +41,7 @@ export interface OriginClaimTarget {
|
|
|
39
41
|
readonly method: string;
|
|
40
42
|
readonly body?: string | Uint8Array;
|
|
41
43
|
}
|
|
42
|
-
export type OriginClaimRefusal = 'absent' | 'bad-caller-shape' | 'bad-ts' | 'no-card' | 'bad-sig' | 'not-derivable' | 'wrong-audience' | 'unbound-closed' | 'body-mismatch';
|
|
44
|
+
export type OriginClaimRefusal = 'absent' | 'bad-caller-shape' | 'bad-ts' | 'no-card' | 'bad-sig' | 'not-derivable' | 'wrong-audience' | 'unbound-closed' | 'body-mismatch' | 'verification-timeout';
|
|
43
45
|
/** Only the canonical origin root can claim that origin's identity. */
|
|
44
46
|
export declare function isRootOriginUrl(value: string): boolean;
|
|
45
47
|
/**
|
|
@@ -127,6 +129,10 @@ export declare function verifyOriginClaim(req: IncomingMessage, d: {
|
|
|
127
129
|
/** A loopback arrival is an audience of its own (default); off when originClaimUrl declares a tunnel or proxy. */
|
|
128
130
|
readonly loopbackArrival?: boolean;
|
|
129
131
|
readonly now: number;
|
|
132
|
+
/** Internal deterministic test seam; production uses ORIGIN_CLAIM_VERIFY_MS. */
|
|
133
|
+
readonly timeoutMs?: number;
|
|
134
|
+
/** Request admission requires a current card read, not a discovery cache assertion. */
|
|
135
|
+
readonly freshCard?: boolean;
|
|
130
136
|
/** Exact received bytes, supplied by the bounded body reader. Missing bytes cannot verify Sig3. */
|
|
131
137
|
readonly rawBody?: Uint8Array;
|
|
132
138
|
/** On the faces that admit CLI identities: the deposit a claim URL names. */
|
|
@@ -7,6 +7,7 @@ export interface OutboundScopeRecords {
|
|
|
7
7
|
canvasNames(): readonly string[];
|
|
8
8
|
canvasMembers(name: string): readonly string[];
|
|
9
9
|
canvasTeam(name: string): string;
|
|
10
|
+
memberHandle?(ref: string): string;
|
|
10
11
|
peerOrgs(): Iterable<readonly {
|
|
11
12
|
team?: unknown;
|
|
12
13
|
members?: unknown;
|
|
@@ -165,9 +165,13 @@ export declare class PeerStore {
|
|
|
165
165
|
* re-seeded from config on every boot, and scores rebuild on contact.
|
|
166
166
|
*/
|
|
167
167
|
private dirty;
|
|
168
|
+
private closed;
|
|
168
169
|
private writeChain;
|
|
169
170
|
private pendingTimer;
|
|
170
171
|
private persist;
|
|
172
|
+
private writePending;
|
|
173
|
+
/** Final teardown: drain once, then late peer responses cannot recreate this home. */
|
|
174
|
+
close(): Promise<void>;
|
|
171
175
|
/**
|
|
172
176
|
* Await the last scheduled write (tests and teardown). Resolves when the
|
|
173
177
|
* debounced write chain has settled with the current in-memory state.
|
|
@@ -17,6 +17,13 @@ export declare const WIRE_ERROR_QUEUE_FULL = -32009;
|
|
|
17
17
|
export declare const DEFAULT_TARGET_QUEUE_CAP = 3;
|
|
18
18
|
/** The ledger fields the count reads. */
|
|
19
19
|
export interface QueueRow {
|
|
20
|
+
readonly taskId?: string;
|
|
21
|
+
readonly auth?: {
|
|
22
|
+
readonly caller: string;
|
|
23
|
+
};
|
|
24
|
+
readonly sender?: {
|
|
25
|
+
readonly id: string;
|
|
26
|
+
};
|
|
20
27
|
readonly team: string;
|
|
21
28
|
readonly peer: string;
|
|
22
29
|
readonly status: string;
|
|
@@ -30,6 +37,6 @@ export declare function queuedFor(rows: readonly QueueRow[], team: string): numb
|
|
|
30
37
|
/** The refusal text when `team`'s queue is full, else undefined. `cap <= 0` disables the guard. */
|
|
31
38
|
export declare function queueFullRefusal(rows: readonly QueueRow[], team: string, message: string, cap: number, mailbox: boolean, ownTask: (taskId: string) => boolean): string | undefined;
|
|
32
39
|
/** #282 S1: the one next step for a full target queue. */
|
|
33
|
-
export declare const QUEUE_FULL_NEXT = "wait for a receipt from the target (a2a-net inbox / a2a_tasks), then send again with a new task id; do not resend in a loop";
|
|
40
|
+
export declare const QUEUE_FULL_NEXT = "wait for a receipt from the target after it reconciles its outstanding tasks (a2a-net inbox / a2a_tasks), then send again with a new task id; do not resend in a loop";
|
|
34
41
|
/** Wire-face adapter in the flood guard's shape: a refused fresh key is FAILED, not stranded pending. */
|
|
35
|
-
export declare function createQueueGuard(rows: () => readonly QueueRow[], cap: () => number, ownTask: (taskId: string) => boolean, outcomes: Pick<IdempotencyStore, 'recordOutcome'>, recordActivity: (dir: 'in' | 'out', team: string, peer: string, ok: boolean) => void): (caller: string, message: string, team: string, taskId: string, mailbox: boolean, res: ServerResponse) => boolean;
|
|
42
|
+
export declare function createQueueGuard(rows: () => readonly QueueRow[], cap: () => number, ownTask: (taskId: string) => boolean, outcomes: Pick<IdempotencyStore, 'recordOutcome'>, recordActivity: (dir: 'in' | 'out', team: string, peer: string, ok: boolean) => void, reminder?: (taskId: string) => Record<string, unknown>): (caller: string, message: string, team: string, taskId: string, mailbox: boolean, res: ServerResponse) => boolean;
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
export interface ReceiptLadderDeps {
|
|
12
12
|
/** Try delivering the receipt text to one routable team. */
|
|
13
13
|
deliver: (team: string) => Promise<unknown>;
|
|
14
|
+
/** Transient caller failures never enter the receipt-bearing owner tier. */
|
|
15
|
+
transient?: (error: unknown) => boolean;
|
|
14
16
|
/** This host's process/team mailbox (tier two). */
|
|
15
17
|
ownerTeam: string;
|
|
16
18
|
/** Observability hook: `stage` distinguishes `to <team>` / `to owner <team>` / `both lanes`. */
|