@songsid/agend 2.2.0-beta.6 → 2.2.0-beta.7

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 (79) hide show
  1. package/dist/agent-cli-instructions.md +6 -2
  2. package/dist/agent-cli.js +9 -5
  3. package/dist/agent-cli.js.map +1 -1
  4. package/dist/backend/muse.d.ts +26 -0
  5. package/dist/backend/muse.js +131 -0
  6. package/dist/backend/muse.js.map +1 -1
  7. package/dist/channel/adapters/discord.js +12 -1
  8. package/dist/channel/adapters/discord.js.map +1 -1
  9. package/dist/channel/adapters/telegram.js +6 -0
  10. package/dist/channel/adapters/telegram.js.map +1 -1
  11. package/dist/channel/mcp-tools.js +3 -3
  12. package/dist/channel/mcp-tools.js.map +1 -1
  13. package/dist/channel/types.d.ts +3 -0
  14. package/dist/cross-instance-envelope.js +2 -0
  15. package/dist/cross-instance-envelope.js.map +1 -1
  16. package/dist/daemon.d.ts +6 -0
  17. package/dist/daemon.js +124 -54
  18. package/dist/daemon.js.map +1 -1
  19. package/dist/delivery-outbox.d.ts +27 -0
  20. package/dist/delivery-outbox.js +175 -6
  21. package/dist/delivery-outbox.js.map +1 -1
  22. package/dist/fleet-manager.d.ts +28 -1
  23. package/dist/fleet-manager.js +203 -40
  24. package/dist/fleet-manager.js.map +1 -1
  25. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +14 -1
  26. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +4 -0
  27. package/dist/human-delivery-receipts.d.ts +83 -0
  28. package/dist/human-delivery-receipts.js +194 -0
  29. package/dist/human-delivery-receipts.js.map +1 -0
  30. package/dist/instructions.d.ts +1 -1
  31. package/dist/instructions.js +1 -1
  32. package/dist/instructions.js.map +1 -1
  33. package/dist/locale.js +8 -0
  34. package/dist/locale.js.map +1 -1
  35. package/dist/outbound-handlers.d.ts +3 -0
  36. package/dist/outbound-handlers.js +20 -4
  37. package/dist/outbound-handlers.js.map +1 -1
  38. package/dist/outbound-schemas.d.ts +15 -13
  39. package/dist/outbound-schemas.js +5 -0
  40. package/dist/outbound-schemas.js.map +1 -1
  41. package/dist/public-link-progress.d.ts +1 -1
  42. package/dist/public-link-progress.js +8 -2
  43. package/dist/public-link-progress.js.map +1 -1
  44. package/dist/public-preview.d.ts +2 -1
  45. package/dist/public-preview.js +8 -5
  46. package/dist/public-preview.js.map +1 -1
  47. package/dist/public-web-link.d.ts +4 -1
  48. package/dist/public-web-link.js +27 -10
  49. package/dist/public-web-link.js.map +1 -1
  50. package/dist/reply-buttons-controller.d.ts +3 -0
  51. package/dist/reply-buttons-controller.js +3 -2
  52. package/dist/reply-buttons-controller.js.map +1 -1
  53. package/dist/topic-commands.d.ts +3 -2
  54. package/dist/topic-commands.js +14 -4
  55. package/dist/topic-commands.js.map +1 -1
  56. package/dist/tunnel/cloudflared.d.ts +7 -15
  57. package/dist/tunnel/cloudflared.js +36 -1
  58. package/dist/tunnel/cloudflared.js.map +1 -1
  59. package/dist/tunnel/types.d.ts +5 -1
  60. package/dist/tunnel/types.js.map +1 -1
  61. package/dist/ui/chat-strings.js +8 -2
  62. package/dist/ui/chat-thread.js +24 -4
  63. package/dist/ui/chat-time.js +80 -0
  64. package/dist/ui/preview-panel.js +7 -1
  65. package/dist/ui/preview.js +5 -1
  66. package/dist/ui/shared/app.css +4 -1
  67. package/dist/web-api.d.ts +2 -0
  68. package/dist/web-api.js +8 -1
  69. package/dist/web-api.js.map +1 -1
  70. package/dist/web-commands.d.ts +2 -2
  71. package/dist/web-commands.js +3 -2
  72. package/dist/web-commands.js.map +1 -1
  73. package/dist/web-reply-files.d.ts +45 -0
  74. package/dist/web-reply-files.js +223 -0
  75. package/dist/web-reply-files.js.map +1 -0
  76. package/dist/web-upload.d.ts +17 -4
  77. package/dist/web-upload.js +64 -13
  78. package/dist/web-upload.js.map +1 -1
  79. package/package.json +1 -1
package/dist/daemon.js CHANGED
@@ -33,6 +33,7 @@ import { HangDetector } from "./hang-detector.js";
33
33
  import { writeSecretFile } from "./secret-file.js";
34
34
  import { PaneWriteLock } from "./pane-write-lock.js";
35
35
  import { buildFleetInstructions } from "./instructions.js";
36
+ import { formatHumanInboundMessage, prepareDeliveryWire, steerInboundEnvelope, BTW_INBOUND_PREFIX, humanBodyHash, humanOwnerObjectId } from "./human-delivery-receipts.js";
36
37
  import { formatCrossInstanceInboundMessage, renderCrossInstanceHandoffMetadata, } from "./cross-instance-envelope.js";
37
38
  import { agendMessageInInput, bottomRowIsReady, inputAreaText, inputShowsPastedText, pastedTextSignature, pasteLeftInInput, strandedAgendMessageInInput } from "./pane-input-residue.js";
38
39
  import { TurnReplyGuard } from "./turn-reply-guard.js";
@@ -2003,6 +2004,7 @@ export class Daemon extends EventEmitter {
2003
2004
  }
2004
2005
  }
2005
2006
  deferDurableDelivery(meta, reason) {
2007
+ this.finishHumanReceipt(meta, { reached: false });
2006
2008
  const delivery = this.durableDeliveryAttempt(meta);
2007
2009
  if (delivery === null || delivery === false)
2008
2010
  return;
@@ -6322,11 +6324,7 @@ export class Daemon extends EventEmitter {
6322
6324
  * is a durable delivery) must LEAD the persisted user message — restart reconciliation recognises a delivery only by a
6323
6325
  * marker at the start of the body (#1197) — so the banner goes right after it, not in front of it.
6324
6326
  */
6325
- static steerEnvelope(inbound) {
6326
- const banner = "[STEERING — mid-task course correction. Fold this into the CURRENT work if one is active.]\n";
6327
- const marker = inbound.match(/^\[agend-delivery-id:[^\]\r\n]+\]\r?\n/);
6328
- return marker ? marker[0] + banner + inbound.slice(marker[0].length) : banner + inbound;
6329
- }
6327
+ static steerEnvelope(inbound) { return steerInboundEnvelope(inbound); }
6330
6328
  /**
6331
6329
  * The one place an inbound message grows its metadata wrapper ([user:]/
6332
6330
  * [from:] prefix, pending reactions, handoff metadata, reply instructions).
@@ -6337,35 +6335,65 @@ export class Daemon extends EventEmitter {
6337
6335
  /** #1650: how this instance talks to the fleet — MCP tools, or agend-agent commands (agent_mode: cli). */
6338
6336
  agentMode() { return this.config.agent_mode === "cli" ? "cli" : "mcp"; }
6339
6337
  formatInboundMessage(content, meta) {
6340
- const user = meta.user || "unknown";
6341
- const fromInstance = meta.from_instance;
6342
- let formatted;
6343
- if (fromInstance) {
6344
- formatted = formatCrossInstanceInboundMessage(content, meta, this.agentMode());
6338
+ if (!meta.from_instance)
6339
+ return formatHumanInboundMessage(content, meta, this.agentMode());
6340
+ let formatted = formatCrossInstanceInboundMessage(content, meta, this.agentMode());
6341
+ if (meta.reply_to_text)
6342
+ formatted += '\n(reply_to: "' + meta.reply_to_text + '")';
6343
+ if (meta.delivery_id)
6344
+ formatted = "[agend-delivery-id:" + meta.delivery_id + "]\n" + formatted;
6345
+ return formatted;
6346
+ }
6347
+ /** Pure cached owner: no pane/process query on ingress. */
6348
+ captureHumanReceiptOwner() {
6349
+ try {
6350
+ if (this.deliveryWritesStopping || this.spawning || !this.tmux)
6351
+ return null;
6352
+ const o = this.interactionOwner();
6353
+ const window = this.tmux.getWindowId();
6354
+ if (!window || !o.bootId || ![o.spawnGeneration, o.launchAttempt, o.launchFenceEpoch, this.deliveryEpoch]
6355
+ .every(n => Number.isSafeInteger(n) && n >= 0))
6356
+ return null;
6357
+ return JSON.stringify([o.bootId, o.spawnGeneration, o.launchAttempt, o.launchFenceEpoch,
6358
+ this.deliveryEpoch, humanOwnerObjectId(this.tmux), window]);
6345
6359
  }
6346
- else {
6347
- const via = meta.source ? ` via ${meta.source}` : "";
6348
- const idTag = meta.user_id ? `, id:${meta.user_id}` : "";
6349
- formatted = `[user:${user}${via}${idTag}] ${content}`;
6350
- // Reactions queued since the last real message (#432). One leading line of
6351
- // context, present only when something is pending — a reaction no longer
6352
- // costs a turn of its own.
6353
- if (meta.pending_reactions) {
6354
- formatted = `[Recent reactions: ${meta.pending_reactions}]\n${formatted}`;
6355
- }
6356
- formatted += renderHandoffMetadata(meta);
6357
- // #1650: a CLI-mode agent has no reply tool — it answers with the agend-agent command.
6358
- formatted += this.agentMode() === "cli"
6359
- ? '\n(Reply with agend-agent reply "…" — do NOT respond with direct text)'
6360
- : "\n(Reply using the reply tool — do NOT respond with direct text)";
6361
- }
6362
- if (meta.reply_to_text) {
6363
- formatted += `\n(reply_to: "${meta.reply_to_text}")`;
6364
- }
6365
- if (meta.delivery_id) {
6366
- formatted = `[agend-delivery-id:${meta.delivery_id}]\n${formatted}`;
6360
+ catch {
6361
+ return null;
6362
+ }
6363
+ }
6364
+ /** The same formatter/sanitizer used by the actual write; snapshot only. */
6365
+ humanReceiptWire(content, meta, kind) {
6366
+ let wire = kind === "raw_paste" ? content
6367
+ : !meta.from_instance && content.startsWith("/raw ") && kind === "fleet_inbound"
6368
+ ? content.slice(5) : this.formatInboundMessage(content, meta);
6369
+ if (kind === "steer")
6370
+ wire = Daemon.steerEnvelope(wire);
6371
+ else if (kind === "btw")
6372
+ wire = BTW_INBOUND_PREFIX + wire;
6373
+ return prepareDeliveryWire(wire, kind === "raw_paste");
6374
+ }
6375
+ humanReceiptAttempt(meta, wire) {
6376
+ const id = meta.human_receipt_id;
6377
+ const daemonOwner = meta.human_daemon_owner;
6378
+ if (!id || !/^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/.test(id) || meta.verification_id !== "hmsg-" + id
6379
+ || !daemonOwner || !meta.human_owner_key || !/^[a-f0-9]{64}$/.test(meta.human_wire_sha256 ?? "")
6380
+ || humanBodyHash(prepareDeliveryWire(wire)) !== meta.human_wire_sha256)
6381
+ return undefined;
6382
+ return { receiptId: id, verificationId: meta.verification_id, expectedWireHash: meta.human_wire_sha256,
6383
+ ownerKey: meta.human_owner_key, current: () => this.captureHumanReceiptOwner() === daemonOwner };
6384
+ }
6385
+ finishHumanReceipt(meta, verdict) {
6386
+ if (!meta.human_receipt_id || !meta.human_owner_key)
6387
+ return;
6388
+ const attempt = verdict.humanReceipt;
6389
+ const proof = attempt?.current() && !["unverified", "output-edge", "unverifiable"].includes(verdict.proof ?? "")
6390
+ ? attempt.proof ?? null : null;
6391
+ try {
6392
+ this.emit("human_receipt_result", { receipt_id: meta.human_receipt_id, owner_key: meta.human_owner_key, proof });
6393
+ }
6394
+ catch (err) {
6395
+ this.logger.error({ err }, "Could not publish human delivery evidence");
6367
6396
  }
6368
- return formatted;
6369
6397
  }
6370
6398
  /**
6371
6399
  * Whether ONE delivery reached a verdict, carried by that delivery.
@@ -6458,12 +6486,13 @@ export class Daemon extends EventEmitter {
6458
6486
  this.deferDurableDelivery(meta, "delivery epoch changed while waking for steer");
6459
6487
  return;
6460
6488
  }
6461
- const verdict = { reached: false };
6489
+ const verdict = { reached: false, humanReceipt: this.humanReceiptAttempt(meta, formatted) };
6462
6490
  try {
6463
6491
  if (await this.deliverMessage(formatted, status, {
6464
6492
  steer: true,
6465
6493
  deliveryEpoch,
6466
6494
  submissionId: meta.message_id,
6495
+ ...(meta.verification_id ? { humanVerificationId: meta.verification_id } : {}),
6467
6496
  verdict,
6468
6497
  durableAttempt: durableAttempt || undefined,
6469
6498
  })) {
@@ -6494,6 +6523,9 @@ export class Daemon extends EventEmitter {
6494
6523
  this.retryDurableDeliveryBeforeBegin(durableAttempt, err.message);
6495
6524
  throw err;
6496
6525
  }
6526
+ finally {
6527
+ this.finishHumanReceipt(meta, verdict);
6528
+ }
6497
6529
  }).catch(err => {
6498
6530
  this.deferDurableDelivery(meta, `target wake/steer failed before pane submission: ${err.message}`);
6499
6531
  this.logger.warn({ err: err.message }, "steer delivery error");
@@ -6509,24 +6541,37 @@ export class Daemon extends EventEmitter {
6509
6541
  * so channel delivery intentionally uses a normal wrapped inbound instead.
6510
6542
  */
6511
6543
  btwMessage(content, meta, deliveryEpoch = this.deliveryEpoch) {
6512
- if (!this.isDeliveryEpochCurrent(deliveryEpoch))
6544
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch)) {
6545
+ this.finishHumanReceipt(meta, { reached: false });
6513
6546
  return;
6547
+ }
6514
6548
  this.updateLastChat(meta.chat_id, meta.thread_id, meta.adapter_id);
6515
6549
  this.pendingWork.recordInbound();
6516
6550
  this.recordRecentUserMessage(content, meta);
6517
- const formatted = "[BTW — side question from the user. Answer it separately without changing or interrupting the CURRENT work.]\n"
6518
- + this.formatInboundMessage(content, meta);
6551
+ const formatted = BTW_INBOUND_PREFIX + this.formatInboundMessage(content, meta);
6519
6552
  const status = channelStatus(meta);
6520
6553
  this.steerLock = this.steerLock.then(async () => {
6521
- if (!this.isDeliveryEpochCurrent(deliveryEpoch))
6554
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch)) {
6555
+ this.finishHumanReceipt(meta, { reached: false });
6522
6556
  return;
6557
+ }
6523
6558
  await this.wake();
6524
- if (!this.isDeliveryEpochCurrent(deliveryEpoch))
6559
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch)) {
6560
+ this.finishHumanReceipt(meta, { reached: false });
6525
6561
  return;
6526
- if (await this.deliverMessage(formatted, status, { steer: true, deliveryEpoch, submissionId: meta.message_id })) {
6527
- this.markTurnStarted(meta, formatted, deliveryEpoch);
6562
+ }
6563
+ const verdict = { reached: false, humanReceipt: this.humanReceiptAttempt(meta, formatted) };
6564
+ try {
6565
+ if (await this.deliverMessage(formatted, status, { steer: true, deliveryEpoch, submissionId: meta.message_id,
6566
+ ...(meta.verification_id ? { humanVerificationId: meta.verification_id } : {}), verdict })) {
6567
+ this.markTurnStarted(meta, formatted, deliveryEpoch);
6568
+ }
6569
+ }
6570
+ finally {
6571
+ this.finishHumanReceipt(meta, verdict);
6528
6572
  }
6529
6573
  }).catch(err => {
6574
+ this.finishHumanReceipt(meta, { reached: false });
6530
6575
  this.logger.warn({ err: err.message }, "btw delivery error");
6531
6576
  });
6532
6577
  }
@@ -6640,7 +6685,7 @@ export class Daemon extends EventEmitter {
6640
6685
  // A fresh delivery begins a fresh turn — its bubble must not inherit
6641
6686
  // the previous turn's tool list.
6642
6687
  this.resetToolProgress();
6643
- verdict = { reached: false };
6688
+ verdict = { reached: false, humanReceipt: this.humanReceiptAttempt(meta, formatted) };
6644
6689
  const durableAttempt = this.durableDeliveryAttempt(meta, content);
6645
6690
  if (durableAttempt === false) {
6646
6691
  this.deferDurableDelivery(meta, "invalid durable delivery attempt metadata");
@@ -6650,6 +6695,7 @@ export class Daemon extends EventEmitter {
6650
6695
  if (await this.deliverMessage(formatted, status, {
6651
6696
  deliveryEpoch,
6652
6697
  submissionId: meta.message_id,
6698
+ ...(meta.verification_id ? { humanVerificationId: meta.verification_id } : {}),
6653
6699
  verdict,
6654
6700
  durableAttempt: durableAttempt || undefined,
6655
6701
  })) {
@@ -6697,6 +6743,7 @@ export class Daemon extends EventEmitter {
6697
6743
  this.logger.warn({ err: err.message }, "Durable pane delivery deferred after exception");
6698
6744
  }
6699
6745
  finally {
6746
+ this.finishHumanReceipt(meta, verdict);
6700
6747
  this.pasteQueueDepth--;
6701
6748
  }
6702
6749
  }).catch(err => {
@@ -6733,12 +6780,7 @@ export class Daemon extends EventEmitter {
6733
6780
  && !this.isDeliveryEpochCurrent(opts.deliveryEpoch);
6734
6781
  if (cancelled() || this.stormWindow?.isStopped())
6735
6782
  return false;
6736
- // Sanitize unclosed code fences — they cause CLI to wait for closure on Enter
6737
- const fenceCount = (formatted.match(/```/g) || []).length;
6738
- if (!opts?.preserveExactBytes && fenceCount % 2 !== 0) {
6739
- // Odd number of fences = unclosed. Remove all code fences from the message.
6740
- formatted = formatted.replace(/```/g, "");
6741
- }
6783
+ formatted = prepareDeliveryWire(formatted, opts?.preserveExactBytes);
6742
6784
  // Direct paths such as /steer, /btw, /raw and schedules do not pass the
6743
6785
  // FleetManager idle gate. Hold them at the final common pane-write path so
6744
6786
  // a tmux storm cannot force any text into a half-booted CLI.
@@ -6911,7 +6953,7 @@ export class Daemon extends EventEmitter {
6911
6953
  return stale();
6912
6954
  if (residue !== "clear")
6913
6955
  return this.failDelivery(verdict, status, "input-residue", residue);
6914
- const written = await this.writeMessageToPane(formatted, windowId, handingOffToNativeQueue, status, opts?.submissionId, verdict, opts?.durableAttempt, opts?.steer === true, writeCurrent);
6956
+ const written = await this.writeMessageToPane(formatted, windowId, handingOffToNativeQueue, status, opts?.submissionId, verdict, opts?.durableAttempt, opts?.steer === true, writeCurrent, opts?.humanVerificationId);
6915
6957
  // #1690: still under the lock — whatever waits on it next must already see what this write started.
6916
6958
  if (verdict.paneWriteStarted)
6917
6959
  opts?.onPaneWritten?.();
@@ -7706,7 +7748,7 @@ export class Daemon extends EventEmitter {
7706
7748
  return false;
7707
7749
  const row = norm(shown);
7708
7750
  // The marker line holds nothing but the marker: the row is exactly it.
7709
- if (/^\[agend-delivery-id:[^\]\s]+\]$/.test(first))
7751
+ if (/^\[agend-(?:delivery|human)-id:[^\]\s]+\]$/.test(first))
7710
7752
  return row === first;
7711
7753
  return row === first || row.startsWith(`${first} `);
7712
7754
  }
@@ -8248,6 +8290,8 @@ export class Daemon extends EventEmitter {
8248
8290
  if (!current())
8249
8291
  return false;
8250
8292
  if (evidence === "submitted") {
8293
+ if (pending.signature.unique)
8294
+ pending.signature.humanPositive?.("unique-id-pane");
8251
8295
  if (status)
8252
8296
  this.emit("message_confirmed", status);
8253
8297
  return true;
@@ -8283,6 +8327,8 @@ export class Daemon extends EventEmitter {
8283
8327
  if (retry === "cancelled")
8284
8328
  return false;
8285
8329
  if (retry === "submitted") {
8330
+ if (pending.signature.unique)
8331
+ pending.signature.humanPositive?.("unique-id-pane");
8286
8332
  if (status)
8287
8333
  this.emit("message_confirmed", status);
8288
8334
  return true;
@@ -8423,9 +8469,24 @@ export class Daemon extends EventEmitter {
8423
8469
  verdict = { reached: false }, durableAttempt, steer = false,
8424
8470
  // #829: the caller's fence, asked once more after the last await before the
8425
8471
  // first side effect. Stale means unwritten: false, with no failure verdict.
8426
- stillCurrent) {
8427
- const signature = this.submissionSignature(formatted, submissionId);
8472
+ stillCurrent, humanVerificationId) {
8473
+ const signature = this.submissionSignature(formatted, humanVerificationId ?? submissionId, humanVerificationId !== undefined);
8428
8474
  const rawPaste = durableAttempt?.submissionMode === "raw_paste";
8475
+ const humanAttempt = verdict.humanReceipt;
8476
+ const humanMode = rawPaste ? "raw_paste" : steer ? "steer"
8477
+ : handingOffToNativeQueue ? "native_queue_handoff" : "idle_submit";
8478
+ if (humanAttempt && signature.unique && signature.value === "agend-human-id:" + humanAttempt.verificationId
8479
+ && humanBodyHash(formatted) === humanAttempt.expectedWireHash) {
8480
+ signature.humanPositive = tag => {
8481
+ if (!humanAttempt.current() || (stillCurrent && !stillCurrent()))
8482
+ return;
8483
+ humanAttempt.proof = {
8484
+ receipt_id: humanAttempt.receiptId, verification_id: humanAttempt.verificationId,
8485
+ wire_sha256: humanBodyHash(formatted), owner_key: humanAttempt.ownerKey,
8486
+ mode: humanMode, tag,
8487
+ };
8488
+ };
8489
+ }
8429
8490
  let windowId = initialWindowId;
8430
8491
  // The spawn this write belongs to: acceptance evidence is only read while it is still the current one (#1197).
8431
8492
  const spawnAtWrite = this.spawnGeneration;
@@ -8571,6 +8632,7 @@ export class Daemon extends EventEmitter {
8571
8632
  // positively hold typed text, and after it be the empty placeholder again. The empty placeholder here means the
8572
8633
  // paste never reached the box; an unreadable box (a failed or late capture, a dialog) proves nothing: no Enter.
8573
8634
  const steerComposerProof = handingOffToNativeQueue && steer && !!this.backend?.readSteerComposer && tmuxAtWrite !== null;
8635
+ let humanComposerOwned = false;
8574
8636
  let steerEnterDeadline = Infinity;
8575
8637
  if (steerComposerProof) {
8576
8638
  const deadline = performance.now() + STEER_PASTE_VISIBLE_MS;
@@ -8594,6 +8656,8 @@ export class Daemon extends EventEmitter {
8594
8656
  return this.failDelivery(verdict, status, "steer-paste", before === null ? "box-unread" : `box-still-${before.mode}`);
8595
8657
  if (!this.steerPasteIsOurs(before.pane, formatted))
8596
8658
  return this.failDelivery(verdict, status, "steer-paste", "box-not-this-paste");
8659
+ const shown = this.backend?.readSteerComposerText?.(before.pane)?.replace(/\s+/g, " ").trim();
8660
+ humanComposerOwned = !!humanAttempt && shown === "[agend-human-id:" + humanAttempt.verificationId + "]";
8597
8661
  // …and so is the Enter: the key is sent only while the reading is still within it.
8598
8662
  steerEnterDeadline = deadline;
8599
8663
  }
@@ -8665,6 +8729,9 @@ export class Daemon extends EventEmitter {
8665
8729
  if (after === "steer" || after === "queue" || after === "idle") {
8666
8730
  const probe = await withinMs(this.probeBlockingDialog(), deadline - performance.now());
8667
8731
  if (probe?.state === "clear" && performance.now() <= deadline && steerCurrent() && this.getWindowId() === windowId) {
8732
+ if (humanComposerOwned)
8733
+ signature.humanPositive?.(after === "steer" ? "composer-empty-steer"
8734
+ : after === "queue" ? "composer-empty-queue" : "composer-empty-idle");
8668
8735
  verdict.phase = "steer-accepted";
8669
8736
  verdict.proof = `steer-composer-${after}`;
8670
8737
  if (status)
@@ -9251,8 +9318,11 @@ export class Daemon extends EventEmitter {
9251
9318
  // the input row is proof in itself — which also means a momentary
9252
9319
  // failure to read the pane BEFORE pasting cannot turn a delivered
9253
9320
  // message into a re-paste.
9254
- if (signature.unique && after.payload > 0)
9321
+ if (signature.unique && after.payload > 0) {
9322
+ if (after.inputReadable)
9323
+ signature.humanPositive?.("unique-id-pane");
9255
9324
  return this.submittedProof();
9325
+ }
9256
9326
  if (unattributedResidue)
9257
9327
  return "unverifiable";
9258
9328
  // 3. Otherwise the evidence must be NEW relative to the pane as it was
@@ -9389,7 +9459,7 @@ export class Daemon extends EventEmitter {
9389
9459
  * paste that never landed. System pastes carry no envelope and fall back to
9390
9460
  * the body signature, which the baseline comparison still ties to this paste.
9391
9461
  */
9392
- submissionSignature(formatted, trustedId) {
9462
+ submissionSignature(formatted, trustedId, verification = false) {
9393
9463
  // The id must come from the message's own metadata, never from scanning the
9394
9464
  // rendered text: agents and users discuss message ids in the body all the
9395
9465
  // time ("check message_id: abc"), and a body-scanned value would match an
@@ -9398,7 +9468,7 @@ export class Daemon extends EventEmitter {
9398
9468
  // the handoff metadata identifies THIS delivery.
9399
9469
  const id = trustedId?.trim();
9400
9470
  if (id)
9401
- return { value: `message_id:${id}`, unique: true };
9471
+ return { value: (verification ? "agend-human-id:" : "message_id:") + id, unique: true };
9402
9472
  return { value: pastedTextSignature(formatted), unique: false };
9403
9473
  }
9404
9474
  /**