@nelsonlongxiang/dsh-open-a2a-net 0.6.33 → 0.6.34

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.
Files changed (51) hide show
  1. package/README.md +5 -2
  2. package/README_zh.md +5 -2
  3. package/assets/nexusDist/assets/index-D2BGWOeU.js +9 -0
  4. package/assets/nexusDist/assets/index-z33lHtLw.css +1 -0
  5. package/assets/nexusDist/index.html +2 -2
  6. package/lib/a2a-client.js +9 -2
  7. package/lib/auth-observer.js +1 -1
  8. package/lib/canvas-store.js +44 -6
  9. package/lib/card.js +2 -1
  10. package/lib/cli-home.js +44 -0
  11. package/lib/client/A2aControl.js +1 -1
  12. package/lib/client.js +65 -65
  13. package/lib/client.js.map +1 -1
  14. package/lib/direct-forward.js +61 -0
  15. package/lib/faces/admin.js +41 -5
  16. package/lib/faces/wire.js +55 -16
  17. package/lib/index.js +110 -64
  18. package/lib/outbound-scope.js +25 -0
  19. package/lib/queue-guard.js +37 -0
  20. package/lib/skill/references/cli.md +5 -4
  21. package/lib/skill/references/pitfalls.md +1 -4
  22. package/lib/task-auth.js +52 -3
  23. package/lib/task-ledger.js +4 -1
  24. package/lib/tools/info.js +19 -3
  25. package/lib/types/a2a-client.d.ts +1 -1
  26. package/lib/types/auth-observer.d.ts +2 -2
  27. package/lib/types/canvas-store.d.ts +10 -1
  28. package/lib/types/cli-home.d.ts +24 -0
  29. package/lib/types/client/A2aControl.d.ts +1 -1
  30. package/lib/types/direct-forward.d.ts +32 -0
  31. package/lib/types/faces/admin.d.ts +8 -1
  32. package/lib/types/faces/wire.d.ts +12 -4
  33. package/lib/types/index.d.ts +4 -0
  34. package/lib/types/outbound-scope.d.ts +21 -0
  35. package/lib/types/queue-guard.d.ts +32 -0
  36. package/lib/types/runtime.d.ts +1 -1
  37. package/lib/types/task-auth.d.ts +17 -2
  38. package/lib/types/task-ledger.d.ts +6 -1
  39. package/lib/types/types.d.ts +8 -0
  40. package/nexus-stage/src/cli-home-picker.ts +42 -0
  41. package/nexus-stage/src/main.ts +9 -2
  42. package/nexus-stage/src/planning-card.ts +61 -0
  43. package/nexus-stage/src/planning-menus.ts +42 -7
  44. package/nexus-stage/src/planning-view.ts +14 -32
  45. package/nexus-stage/src/planning.css +17 -0
  46. package/nexus-stage/src/workbench.css +4 -0
  47. package/nexus-stage/src/workbench.ts +22 -5
  48. package/nexus-stage/src/world.ts +5 -1
  49. package/package.json +2 -1
  50. package/assets/nexusDist/assets/index-BACVZe0j.css +0 -1
  51. package/assets/nexusDist/assets/index-Bbxnpf2p.js +0 -9
@@ -0,0 +1,61 @@
1
+ /** H-side one-hop forwarding. Never resolves or dispatches local candidates again. */
2
+ import { DEFAULT_HOP_BUDGET, guardHeader, mintGuard } from "./recursion-guard.js";
3
+ import { briefOf } from "./task-ledger.js";
4
+ import { parseReceipt } from "./receipt.js";
5
+ export function createDirectForward(d) {
6
+ return async (request) => {
7
+ const refuse = (why) => ({ ok: false, code: -32000, task_id: request.taskId, error: 'not forwarded: ' + why });
8
+ if (!d.enabled())
9
+ return refuse('forwardDirect is off');
10
+ if (d.isolated())
11
+ return refuse('host is isolated');
12
+ if (request.guarded)
13
+ return refuse('an inbound X-A2A-Guard is present; already forwarded');
14
+ if (!request.localCliProof || !d.deposited(request.caller))
15
+ return refuse('caller is not a proven online local CLI deposit');
16
+ const scope = await d.scope(request.caller, request.team);
17
+ if (!scope.ok)
18
+ return refuse('target is outside sender scope: ' + scope.error);
19
+ const failures = [];
20
+ const directory = await d.candidates(request.team, failures, request.caller);
21
+ const guard = guardHeader({ ...mintGuard(), hopBudget: DEFAULT_HOP_BUDGET - 1 });
22
+ for (const url of directory.urls) {
23
+ if (!d.deposited(request.caller))
24
+ return refuse('local CLI deposit expired before dispatch');
25
+ if (request.team.includes('/canvas/')) {
26
+ const card = await directory.card(url);
27
+ const org = card?.canvasOrg?.find(row => row.team === request.team);
28
+ const receivers = org?.members.filter(member => member !== request.caller) ?? [];
29
+ if (receivers.length === 0 || receivers.some(member => !d.memberLinked(member))) {
30
+ failures.push(url + ': forwarding a canvas requires a direct operator link to every receiving member for its receipt return path');
31
+ continue;
32
+ }
33
+ }
34
+ const answer = await d.dispatch(url, request, guard);
35
+ const result = !answer.ok && answer.ownBudgetExhausted === true ? { ...answer, code: -32005 } : answer;
36
+ d.activity(request.team, url, result.ok);
37
+ if (result.ok || result.code === -32005) {
38
+ // A receipt owes no receipt; native bridge rounds have no A2A receipt contract.
39
+ // Match routeTrunk/trackOwedTask rather than manufacturing an unpayable debt.
40
+ if (parseReceipt(request.message) !== null)
41
+ return { ...result, receipt: 'none' };
42
+ if (result.ok && result.bridge !== undefined)
43
+ return { ...result, receipt: 'none' };
44
+ const card = await directory.card(url);
45
+ const birth = { forwarded: true, ...(card?.publicKey ? { peerKey: card.publicKey } : {}), ...(card?.session ? { peerHost: card.session } : {}), deliveredTo: result.ok ? result.deliveredTo ?? request.team : request.team };
46
+ // Exactly one outbound row. A completed sync response is receiptless bookkeeping.
47
+ if (result.ok && (result.task_status === 'TASK_STATE_COMPLETED' || result.receipt === 'none'))
48
+ d.ledger.trackReceiptless(request.taskId, request.team, url, request.contextId, briefOf(request.message), { kind: 'cli', id: request.caller }, birth);
49
+ else
50
+ d.ledger.track(request.taskId, request.team, url, request.contextId, briefOf(request.message), { kind: 'cli', id: request.caller }, birth);
51
+ return result;
52
+ }
53
+ // The shared dispatcher's sync recovery query may prove a definitive failed
54
+ // outcome; like routeTrunk, that is an eligible miss, not an uncertain -32005.
55
+ failures.push(url + ': ' + result.error);
56
+ if (result.code === -32002 || result.code === -32003)
57
+ return { ...result, task_id: request.taskId };
58
+ }
59
+ return { ok: false, code: -32004, task_id: request.taskId, error: 'forwarding to "' + request.team + '" failed: ' + (failures.join('; ') || 'no eligible peer candidate') };
60
+ };
61
+ }
@@ -20,11 +20,13 @@ import { join } from 'node:path';
20
20
  import { fileURLToPath } from 'node:url';
21
21
  import { canvasNameReserved as reservedCanvasName, isRemoteMemberRef, validRemoteMemberRef } from "../canvas-store.js";
22
22
  import { refreshPeerCard, registerCanvasNotify } from "./canvas-notify.js";
23
+ import { CLI_HANDLE_RE } from "../cli-deposit.js";
23
24
  import { applyOwnerSettings } from "../owner-settings.js";
24
25
  import { rosterOp } from "../roster-op.js";
25
26
  import { resolveStageMount } from "../stage-mount.js";
26
27
  import { ARCHIVE_CAP, archivedEndedBy } from "../task-ledger.js";
27
28
  import { MAX_LAYOUT_BODY_BYTES } from "../transport-caps.js";
29
+ import { queuedFor } from "../queue-guard.js";
28
30
  /**
29
31
  * Register the admin faces on a settled web server.
30
32
  * @param webServer - the settled host web server.
@@ -54,6 +56,14 @@ export function registerAdminFaces(webServer, d) {
54
56
  // or not. (The prune above already took their join intents; this
55
57
  // covers never-joined live roots and any intent a restart left.)
56
58
  const isArchived = archivedSessionFilter();
59
+ const liveStatusOf = (agent) => {
60
+ try {
61
+ return agent.status === 'running' ? 'running' : agent.status === 'idle' ? 'idle' : undefined;
62
+ }
63
+ catch {
64
+ return undefined;
65
+ }
66
+ };
57
67
  const tFacts0 = Date.now();
58
68
  const sessions = [...liveRoots.values()]
59
69
  .filter(agent => isArchived?.(String(agent.id)) !== true)
@@ -68,6 +78,10 @@ export function registerAdminFaces(webServer, d) {
68
78
  ...nodeMetadataOf(agent),
69
79
  joined: sessionNodes.has(String(agent.id)),
70
80
  live: true,
81
+ // Owner 2026-11-03: whether the session is mid-turn right now, and how many
82
+ // receipt-owing tasks wait on it (the gh#224 queue count) — the board's 工作中/空闲.
83
+ status: liveStatusOf(agent),
84
+ queued: queuedFor(taskLedger.list(), sessionTeamOf(agent)),
71
85
  teams: [...teamMemberships.teamsOf(String(agent.id))],
72
86
  // #19 D3: the session's exposure tier (linked floor /
73
87
  // fleet opt-up) — readable on the state face.
@@ -138,7 +152,7 @@ export function registerAdminFaces(webServer, d) {
138
152
  rows.push({ id: localId, team: `${config.team}/${id8(localId)}`, joined: sessionNodes.has(localId) || joinedSessions.has(localId), live: sessionNodes.has(localId) });
139
153
  continue;
140
154
  }
141
- rows.push({ id, team: id, remote: true });
155
+ rows.push({ id, team: id, remote: true, ...(CLI_HANDLE_RE.test(id) ? { cliHome: canvasStore.cliHomeOf(id) ?? null, cliHeld: cliDeposits.directoryRows().some(row => row.team === id) } : {}) });
142
156
  continue;
143
157
  }
144
158
  if (seenLocal.has(id))
@@ -238,6 +252,7 @@ export function registerAdminFaces(webServer, d) {
238
252
  ...(row.brief !== undefined ? { brief: row.brief } : {}),
239
253
  ...(row.summary !== undefined && row.summary !== '' ? { summary: row.summary.slice(0, 160) } : {}),
240
254
  ...(row.sender !== undefined ? { sender: row.sender } : {}),
255
+ ...(row.forwarded === true ? { forwarded: true } : {}),
241
256
  ...(row.auth !== undefined ? { auth: row.auth } : {}),
242
257
  // #114 S4: who settled it and how they stood on the chain.
243
258
  ...(row.settledBy !== undefined ? { settledBy: row.settledBy } : {}),
@@ -688,10 +703,24 @@ export function registerAdminFaces(webServer, d) {
688
703
  kind: 'exact',
689
704
  path: '/__dsh_a2a/canvas',
690
705
  handler: controlRoute((req, res) => {
691
- readJsonBody(req, res, (body) => {
706
+ readJsonBody(req, res, async (body) => {
692
707
  const action = body.action;
693
708
  let payload;
694
- if (action === 'create') {
709
+ if (action === 'cli-home-options') {
710
+ const name = typeof body.name === 'string' ? body.name : '';
711
+ const id = typeof body.id === 'string' ? body.id : '';
712
+ if (!CLI_HANDLE_RE.test(id) || !canvasStore.membersOf(name).includes(id))
713
+ payload = JSON.stringify({ ok: false, error: 'an existing canvas CLI member is required' });
714
+ else {
715
+ try {
716
+ payload = JSON.stringify({ ok: true, ...await d.cliHomeOptions(id) });
717
+ }
718
+ catch {
719
+ payload = JSON.stringify({ ok: false, error: 'fresh home card lookup failed' });
720
+ }
721
+ }
722
+ }
723
+ else if (action === 'create') {
695
724
  const name = typeof body.name === 'string' ? body.name : '';
696
725
  const stored = canvasNameReserved(name) ? undefined : canvasStore.create(name);
697
726
  payload = JSON.stringify(stored === undefined ? { ok: false, error: 'invalid, roster-reserved name or team cap reached' } : { ok: true, name: stored, teams: canvasStore.list() });
@@ -717,8 +746,15 @@ export function registerAdminFaces(webServer, d) {
717
746
  payload = JSON.stringify({ ok: false, error: 'name and a member (joined session id, or a zone-qualified remote team handle like zone/a1b2c3d4) are required' });
718
747
  }
719
748
  else {
720
- canvasStore.create(name);
721
- payload = JSON.stringify({ ok: canvasStore.addMember(name, id), teams: canvasStore.list(), members: canvasStore.membersOf(name) });
749
+ const pin = await d.cliHomeForAdd(id, body.home).catch(() => ({ ok: false, error: 'linked home card lookup failed' }));
750
+ if (!pin.ok)
751
+ payload = JSON.stringify({ ok: false, error: pin.error });
752
+ else {
753
+ canvasStore.create(name);
754
+ const explicitHome = body.home !== undefined;
755
+ const home = explicitHome ? pin.home : canvasStore.cliHomeOf(id) ?? pin.home;
756
+ payload = JSON.stringify({ ok: canvasStore.addMember(name, id, home, { replaceHome: explicitHome }), teams: canvasStore.list(), members: canvasStore.membersOf(name) });
757
+ }
722
758
  }
723
759
  }
724
760
  else if (action === 'remove-member') {
package/lib/faces/wire.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { isRemoteMemberRef, validRemoteMemberRef } from "../canvas-store.js";
2
+ import { CLI_HANDLE_RE } from "../cli-deposit.js";
2
3
  import { TRANSCRIPT_DEFAULT_LIMIT, TRANSCRIPT_MAX_LIMIT } from "../session-transcript.js";
3
4
  import { refuseIfBudgetExhausted } from "../recursion-guard.js";
4
5
  import { peerPayloadFingerprint, WIRE_ERROR_IDEMPOTENCY_CONFLICT, WIRE_ERROR_REPLAY_REJECTED } from "../idempotency-store.js";
@@ -74,7 +75,7 @@ export function settlerClaimFields(claim, originOf) {
74
75
  * @param d - the captures listed on {@link WireDeps}.
75
76
  */
76
77
  export function registerWireFaces(webServer, d) {
77
- const { ctx, logger, config, cliDeposits, canvasStore, canvasNameReserved, sessionNodes, joinedSessions, idempotencyStore, verifyOriginClaim, callerLinkedToThisHost, recordActivity, secretEqual, a2aCallerRefusal, a2aCallerAdmission, allowlistVersionNow, observeAuthorization, scanSessionLogList, readSessionLogTail, taskLedger, refuseIfFlooded, settleAndAnnounce, session, resolveAgentForTeam, parseCanvasTeamName, canvasLiveAgent, wakeColdTeam, probeConsumption, sessionTeamOf, localSessionOfHandle, steerRelay, fromClause, sanitizeFromClause, armReceiptAutosend, eventCountOf, receiptExpectationOf, armAsyncNudge, liveAgent, callerIsAgent, selfRouteText, noWakeFaceText, archivedColdMatchText, noColdMatchText, nativeTeamsPrepare, nativeTeamsRound, routeIntoAgent, } = d;
78
+ const { ctx, logger, config, cliDeposits, canvasStore, canvasNameReserved, sessionNodes, joinedSessions, idempotencyStore, verifyOriginClaim, remoteCliProof, callerLinkedToThisHost, recordActivity, secretEqual, a2aCallerRefusal, a2aCallerAdmission, allowlistVersionNow, observeAuthorization, scanSessionLogList, readSessionLogTail, taskLedger, refuseIfFlooded, refuseIfQueueFull, settleAndAnnounce, session, resolveAgentForTeam, parseCanvasTeamName, canvasLiveAgent, wakeColdTeam, probeConsumption, sessionTeamOf, localSessionOfHandle, steerRelay, fromClause, sanitizeFromClause, armReceiptAutosend, eventCountOf, receiptExpectationOf, armAsyncNudge, liveAgent, callerIsAgent, selfRouteText, noWakeFaceText, archivedColdMatchText, noColdMatchText, nativeTeamsPrepare, nativeTeamsRound, routeIntoAgent, } = d;
78
79
  // gh#45 CLI 户口卡面 (peer face, unsigned card serve): the deposited
79
80
  // CLI's SIGNED card is served verbatim under its own base URL — an
80
81
  // OriginClaim verification of a CLI envelope fetches exactly here
@@ -168,11 +169,19 @@ export function registerWireFaces(webServer, d) {
168
169
  // gh#131: a handle is stored as sent, even one of this host's own — the
169
170
  // local tiers read it as the session it names at route time.
170
171
  const memberValid = isRemoteMemberRef(rawId) ? validRemoteMemberRef(rawId) : rawId !== '' && (sessionNodes.has(rawId) || joinedSessions.has(rawId));
171
- payload = !canvasStore.hasTeam(name) && canvasNameReserved(name)
172
- ? { ok: false, error: 'roster reserves this canvas name' }
173
- : name === '' || !memberValid
174
- ? { ok: false, error: 'name and a member (joined session id, or a zone-qualified remote team handle) are required' }
175
- : (canvasStore.create(name), { ok: canvasStore.addMember(name, rawId), members: canvasStore.membersOf(name) });
172
+ if (!canvasStore.hasTeam(name) && canvasNameReserved(name))
173
+ payload = { ok: false, error: 'roster reserves this canvas name' };
174
+ else if (name === '' || !memberValid)
175
+ payload = { ok: false, error: 'name and a member (joined session id, or a zone-qualified remote team handle) are required' };
176
+ else {
177
+ // A peer may compose references, never decide this operator's CLI home pin.
178
+ if (body.home !== undefined)
179
+ payload = { ok: false, error: 'CLI home pins may only be set by this host local control route' };
180
+ else {
181
+ canvasStore.create(name);
182
+ payload = { ok: canvasStore.addMember(name, rawId), members: canvasStore.membersOf(name) };
183
+ }
184
+ }
176
185
  }
177
186
  else if (op === 'remove-member') {
178
187
  // Either shape may be stored (an older edit kept the handle): remove both (review of PR #151 P2-1).
@@ -484,7 +493,12 @@ export function registerWireFaces(webServer, d) {
484
493
  // (0.6.15: the rule now lives in a2aCallerRefusal so the
485
494
  // read-only /a2a/log face admits callers by exactly the same
486
495
  // standard as message delivery.)
487
- const admission = a2aCallerAdmission(caller, team);
496
+ // F1: only a FOREIGN CLI handle needs a bound host claim and live home card
497
+ // before admission. Every other caller keeps the existing admission-first order.
498
+ const foreignCli = config.teamScopeRouting && CLI_HANDLE_RE.test(caller) && cliDeposits.get(caller) === undefined;
499
+ const earlyClaim = foreignCli ? await verifyOriginClaim(req, { cli: true }) : undefined;
500
+ const cliProof = foreignCli && earlyClaim?.ok ? await remoteCliProof(caller, earlyClaim).catch(() => undefined) : undefined;
501
+ const admission = a2aCallerAdmission(caller, team, cliProof);
488
502
  const callerRefusal = refusalOf(admission);
489
503
  if (callerRefusal !== undefined || !admission.ok) {
490
504
  recordActivity('in', team, caller === '' ? 'anonymous' : caller, false);
@@ -504,7 +518,7 @@ export function registerWireFaces(webServer, d) {
504
518
  // after the window closes it rejects like a failed signature —
505
519
  // N3 proves a NAME's linkability, never key possession.
506
520
  // gh#133: the delivery face is where a deposited CLI identity signs its routes.
507
- const claim = await verifyOriginClaim(req, { cli: true });
521
+ const claim = earlyClaim ?? await verifyOriginClaim(req, { cli: true });
508
522
  if (!claim.ok) {
509
523
  if (claim.reason !== 'absent' || config.originClaimWindow === false) {
510
524
  recordActivity('in', team, caller, false);
@@ -518,11 +532,12 @@ export function registerWireFaces(webServer, d) {
518
532
  // A verified claim proves the caller only when its signed zone covers it (review of PR #134 P1),
519
533
  // and the host face only when it was signed from this very host (round 2: a zone name is not an origin).
520
534
  const callerProof = !claim.ok ? 'legacy-window'
521
- : claim.cli === true ? (claimCovers(caller, claim.caller) ? 'cli-identity' : 'claim-mismatch')
522
- : claim.bound !== true ? 'unbound'
523
- : !isRootOriginUrl(claim.callerUrl) ? 'claim-mismatch'
524
- : admission.rule === 'host-face' && originOf(claim.callerUrl) !== originOf(d.ownCardUrl) ? 'claim-mismatch'
525
- : claimCovers(caller, claim.caller) ? 'origin-claim' : 'claim-mismatch';
535
+ : admission.rule === 'remote-ref-cli' ? 'origin-claim'
536
+ : claim.cli === true ? (claimCovers(caller, claim.caller) ? 'cli-identity' : 'claim-mismatch')
537
+ : claim.bound !== true ? 'unbound'
538
+ : !isRootOriginUrl(claim.callerUrl) ? 'claim-mismatch'
539
+ : admission.rule === 'host-face' && originOf(claim.callerUrl) !== originOf(d.ownCardUrl) ? 'claim-mismatch'
540
+ : claimCovers(caller, claim.caller) ? 'origin-claim' : 'claim-mismatch';
526
541
  const proofClause = callerProof === 'origin-claim' && claim.ok ? `已验证来源 ${originOf(claim.callerUrl)}`
527
542
  : callerProof === 'cli-identity' ? '未验证(cli-identity)' : `未验证(${callerProof})`;
528
543
  const directHeader = (from) => `[A2A direct] (task ${taskId}) ${proofClause} ${sanitizeFromClause(from)} (routed to ${team}) sent:`;
@@ -551,6 +566,8 @@ export function registerWireFaces(webServer, d) {
551
566
  }
552
567
  if (refuseIfFlooded(caller, message, team, taskId, res))
553
568
  return;
569
+ if (refuseIfQueueFull(caller, message, team, taskId, cliDeposits.get(team) !== undefined, res))
570
+ return;
554
571
  // The caller-side receipt correlation: a message shaped as a
555
572
  // receipt resolves the task it echoes, whichever wait semantics
556
573
  // carried it here. Correlation is bookkeeping only — the message
@@ -585,6 +602,17 @@ export function registerWireFaces(webServer, d) {
585
602
  // Inbound routing is foreground demand: boot prewarm yields for a
586
603
  // quiet window around it.
587
604
  d.touchWakeDemand();
605
+ const forwardMiss = async (original) => {
606
+ const result = await d.forwardDirect({ team, message, caller, taskId, contextId, noWait,
607
+ localCliProof: claim.ok && claim.cli === true && claim.bound && claim.caller === caller, guarded: req.headers['x-a2a-guard'] !== undefined }).catch((error) => ({ ok: false, error: `forwarding unavailable: ${String(error)}`, code: -32000, task_id: taskId }));
608
+ if (!result.ok && result.code !== -32005)
609
+ idempotencyStore.recordOutcome(taskId, { status: 'failed', error: original + ' — ' + result.error });
610
+ else if (result.ok && result.task_status === 'TASK_STATE_COMPLETED')
611
+ idempotencyStore.recordOutcome(taskId, { status: 'completed', reply: result.reply });
612
+ const payload = JSON.stringify(result.ok ? { routed: true, ...(result.task_status === 'TASK_STATE_DELIVERED' ? { delivered: true } : { result: { text: result.reply } }), team, task_id: taskId, context_id: contextId, task_status: result.task_status, consumed: result.consumed, bridge: result.bridge, receipt: result.receipt, forwarded: true } : { error: original + ' — ' + result.error, code: result.code ?? -32000, receipt: result.receipt, task_id: taskId, team, task_status: result.code === -32005 ? 'TASK_STATE_DELIVERED' : 'TASK_STATE_FAILED' });
613
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) });
614
+ res.end(payload);
615
+ };
588
616
  // wait:false delivers without the final-reply hold: steer resolves
589
617
  // synchronously inside routeIntoAgentFor, so by the time the agent
590
618
  // lookup settles the message is already in — answer delivered and
@@ -675,6 +703,7 @@ export function registerWireFaces(webServer, d) {
675
703
  caller,
676
704
  proof: callerProof,
677
705
  ...(claim.ok ? { claim: { caller: claim.caller, origin: originOf(claim.callerUrl) } } : {}),
706
+ ...(admission.rule === 'remote-ref-cli' && claim.ok ? { carrierUrl: originOf(claim.callerUrl) } : {}),
678
707
  ingress: 'wire',
679
708
  allowlistVersion: admittedAt.version,
680
709
  target: team,
@@ -702,6 +731,7 @@ export function registerWireFaces(webServer, d) {
702
731
  task_status: 'TASK_STATE_DELIVERED',
703
732
  artifacts: [],
704
733
  consumed: consumedProbe,
734
+ ...(req.headers['x-a2a-guard'] !== undefined ? { delivered_to: deliveredTo } : {}),
705
735
  receipt: receiptExpectation === 'process-label' || deliveredReceipt ? 'none' : 'routed',
706
736
  });
707
737
  res.writeHead(200, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) });
@@ -739,7 +769,7 @@ export function registerWireFaces(webServer, d) {
739
769
  // success. Past prepare, the round fires detached (noWait means
740
770
  // delivery, not settlement); its late outcome corrects the
741
771
  // activity ring and the log.
742
- const standardMiss = () => {
772
+ const standardMiss = async () => {
743
773
  if (team === config.team) {
744
774
  const live = liveAgent();
745
775
  if (live !== undefined) {
@@ -755,6 +785,10 @@ export function registerWireFaces(webServer, d) {
755
785
  return;
756
786
  }
757
787
  }
788
+ if (wake?.kind === 'no-match') {
789
+ await forwardMiss(noColdMatchText(team));
790
+ return;
791
+ }
758
792
  refuseNoWait(wake?.kind === 'no-face' ? noWakeFaceText(team) : wake?.kind === 'archived' ? archivedColdMatchText(team) : noColdMatchText(team), false);
759
793
  };
760
794
  const prepared = await nativeTeamsPrepare(team);
@@ -763,7 +797,7 @@ export function registerWireFaces(webServer, d) {
763
797
  refuseNoWait(prepared.error);
764
798
  return;
765
799
  }
766
- standardMiss();
800
+ await standardMiss();
767
801
  return;
768
802
  }
769
803
  recordActivity('in', team, caller, true);
@@ -791,7 +825,11 @@ export function registerWireFaces(webServer, d) {
791
825
  })();
792
826
  return;
793
827
  }
794
- void routeIntoAgent(team, message, caller, taskId, proofClause, directHeader).then((outcome) => {
828
+ void routeIntoAgent(team, message, caller, taskId, proofClause, directHeader).then(async (outcome) => {
829
+ if (!outcome.ok && outcome.localMiss) {
830
+ await forwardMiss(outcome.error);
831
+ return;
832
+ }
795
833
  recordActivity('in', team, caller, outcome.ok);
796
834
  // W7 slice-2 hook 1: record the settled outcome on the claim row.
797
835
  // Ruling: a non-COMPLETED status (TASK_STATE_DELIVERED — the
@@ -818,6 +856,7 @@ export function registerWireFaces(webServer, d) {
818
856
  team,
819
857
  session,
820
858
  result: { text: outcome.reply },
859
+ ...(req.headers['x-a2a-guard'] !== undefined ? ((target) => target === undefined ? {} : { delivered_to: sessionTeamOf(target) })(resolveAgentForTeam(team) ?? canvasLiveAgent(parseCanvasTeamName(team))) : {}),
821
860
  task_id: taskId,
822
861
  context_id: contextId,
823
862
  task_status: outcome.status ?? 'TASK_STATE_COMPLETED',