@songsid/agend 2.2.0-beta.5 → 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 (127) 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/claude-carry.d.ts +22 -0
  5. package/dist/backend/claude-carry.js +170 -0
  6. package/dist/backend/claude-carry.js.map +1 -0
  7. package/dist/backend/claude-code.d.ts +16 -0
  8. package/dist/backend/claude-code.js +92 -2
  9. package/dist/backend/claude-code.js.map +1 -1
  10. package/dist/backend/credential-profile.d.ts +7 -0
  11. package/dist/backend/credential-profile.js +7 -1
  12. package/dist/backend/credential-profile.js.map +1 -1
  13. package/dist/backend/kiro.js +5 -2
  14. package/dist/backend/kiro.js.map +1 -1
  15. package/dist/backend/muse.d.ts +67 -0
  16. package/dist/backend/muse.js +370 -44
  17. package/dist/backend/muse.js.map +1 -1
  18. package/dist/backend/types.d.ts +7 -0
  19. package/dist/backend/types.js.map +1 -1
  20. package/dist/background-work-ended.d.ts +18 -0
  21. package/dist/background-work-ended.js +30 -0
  22. package/dist/background-work-ended.js.map +1 -0
  23. package/dist/channel/adapters/discord.js +12 -1
  24. package/dist/channel/adapters/discord.js.map +1 -1
  25. package/dist/channel/adapters/telegram.d.ts +4 -5
  26. package/dist/channel/adapters/telegram.js +39 -17
  27. package/dist/channel/adapters/telegram.js.map +1 -1
  28. package/dist/channel/mcp-tools.js +3 -3
  29. package/dist/channel/mcp-tools.js.map +1 -1
  30. package/dist/channel/message-edit-errors.d.ts +5 -0
  31. package/dist/channel/message-edit-errors.js +24 -0
  32. package/dist/channel/message-edit-errors.js.map +1 -0
  33. package/dist/channel/types.d.ts +3 -0
  34. package/dist/cli.js +25 -10
  35. package/dist/cli.js.map +1 -1
  36. package/dist/cross-instance-envelope.js +2 -0
  37. package/dist/cross-instance-envelope.js.map +1 -1
  38. package/dist/daemon.d.ts +6 -0
  39. package/dist/daemon.js +165 -54
  40. package/dist/daemon.js.map +1 -1
  41. package/dist/delivery-outbox.d.ts +27 -0
  42. package/dist/delivery-outbox.js +175 -6
  43. package/dist/delivery-outbox.js.map +1 -1
  44. package/dist/fleet-context.d.ts +4 -1
  45. package/dist/fleet-manager.d.ts +110 -6
  46. package/dist/fleet-manager.js +558 -100
  47. package/dist/fleet-manager.js.map +1 -1
  48. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +44 -9
  49. package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +14 -1
  50. package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +4 -0
  51. package/dist/human-delivery-receipts.d.ts +83 -0
  52. package/dist/human-delivery-receipts.js +194 -0
  53. package/dist/human-delivery-receipts.js.map +1 -0
  54. package/dist/instructions.d.ts +1 -1
  55. package/dist/instructions.js +1 -1
  56. package/dist/instructions.js.map +1 -1
  57. package/dist/locale.js +22 -2
  58. package/dist/locale.js.map +1 -1
  59. package/dist/needs-you-hub.js +2 -0
  60. package/dist/needs-you-hub.js.map +1 -1
  61. package/dist/needs-you-live.d.ts +6 -0
  62. package/dist/needs-you-live.js +23 -3
  63. package/dist/needs-you-live.js.map +1 -1
  64. package/dist/outbound-handlers.d.ts +10 -0
  65. package/dist/outbound-handlers.js +39 -14
  66. package/dist/outbound-handlers.js.map +1 -1
  67. package/dist/outbound-schemas.d.ts +15 -13
  68. package/dist/outbound-schemas.js +5 -0
  69. package/dist/outbound-schemas.js.map +1 -1
  70. package/dist/public-link-progress.d.ts +1 -1
  71. package/dist/public-link-progress.js +8 -2
  72. package/dist/public-link-progress.js.map +1 -1
  73. package/dist/public-preview.d.ts +2 -1
  74. package/dist/public-preview.js +8 -5
  75. package/dist/public-preview.js.map +1 -1
  76. package/dist/public-web-link.d.ts +4 -1
  77. package/dist/public-web-link.js +27 -10
  78. package/dist/public-web-link.js.map +1 -1
  79. package/dist/reply-buttons-controller.d.ts +3 -0
  80. package/dist/reply-buttons-controller.js +3 -2
  81. package/dist/reply-buttons-controller.js.map +1 -1
  82. package/dist/restart-progress.d.ts +4 -0
  83. package/dist/restart-progress.js +7 -0
  84. package/dist/restart-progress.js.map +1 -1
  85. package/dist/service-installer.d.ts +31 -0
  86. package/dist/service-installer.js +41 -2
  87. package/dist/service-installer.js.map +1 -1
  88. package/dist/topic-commands.d.ts +3 -2
  89. package/dist/topic-commands.js +17 -5
  90. package/dist/topic-commands.js.map +1 -1
  91. package/dist/tunnel/cloudflared.d.ts +7 -15
  92. package/dist/tunnel/cloudflared.js +36 -1
  93. package/dist/tunnel/cloudflared.js.map +1 -1
  94. package/dist/tunnel/types.d.ts +5 -1
  95. package/dist/tunnel/types.js.map +1 -1
  96. package/dist/ui/chat-strings.js +8 -2
  97. package/dist/ui/chat-thread.js +24 -4
  98. package/dist/ui/chat-time.js +80 -0
  99. package/dist/ui/preview-panel.js +7 -1
  100. package/dist/ui/preview.js +5 -1
  101. package/dist/ui/shared/app-stream.js +21 -4
  102. package/dist/ui/shared/app.css +9 -3
  103. package/dist/ui/shared/usage-dialog.js +10 -1
  104. package/dist/update-install.d.ts +6 -2
  105. package/dist/update-install.js +8 -2
  106. package/dist/update-install.js.map +1 -1
  107. package/dist/update-marker.d.ts +11 -0
  108. package/dist/update-marker.js +12 -0
  109. package/dist/update-marker.js.map +1 -1
  110. package/dist/web-api.d.ts +5 -1
  111. package/dist/web-api.js +8 -1
  112. package/dist/web-api.js.map +1 -1
  113. package/dist/web-commands.d.ts +2 -2
  114. package/dist/web-commands.js +3 -2
  115. package/dist/web-commands.js.map +1 -1
  116. package/dist/web-reply-files.d.ts +45 -0
  117. package/dist/web-reply-files.js +223 -0
  118. package/dist/web-reply-files.js.map +1 -0
  119. package/dist/web-server-drain.d.ts +10 -0
  120. package/dist/web-server-drain.js +50 -0
  121. package/dist/web-server-drain.js.map +1 -0
  122. package/dist/web-upload.d.ts +17 -4
  123. package/dist/web-upload.js +64 -13
  124. package/dist/web-upload.js.map +1 -1
  125. package/package.json +1 -1
  126. package/templates/launchd.plist.ejs +3 -1
  127. package/templates/systemd.service.ejs +2 -1
@@ -8,6 +8,8 @@ export function renderCrossInstanceHandoffMetadata(meta) {
8
8
  rows.push(`${label}: ${value.trim()}`);
9
9
  };
10
10
  add("message_id", meta.message_id);
11
+ add("verification_id", meta.verification_id);
12
+ add("forwards", meta.forwards);
11
13
  add("correlation_id", meta.correlation_id);
12
14
  add("request_kind", meta.request_kind);
13
15
  add("task_summary", meta.task_summary);
@@ -1 +1 @@
1
- {"version":3,"file":"cross-instance-envelope.js","sourceRoot":"","sources":["../src/cross-instance-envelope.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cross-instance-envelope.js","sourceRoot":"","sources":["../src/cross-instance-envelope.ts"],"names":[],"mappings":"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"}
package/dist/daemon.d.ts CHANGED
@@ -1484,6 +1484,12 @@ export declare class Daemon extends EventEmitter {
1484
1484
  /** #1650: how this instance talks to the fleet — MCP tools, or agend-agent commands (agent_mode: cli). */
1485
1485
  private agentMode;
1486
1486
  private formatInboundMessage;
1487
+ /** Pure cached owner: no pane/process query on ingress. */
1488
+ captureHumanReceiptOwner(): string | null;
1489
+ /** The same formatter/sanitizer used by the actual write; snapshot only. */
1490
+ humanReceiptWire(content: string, meta: Record<string, string>, kind: string): string;
1491
+ private humanReceiptAttempt;
1492
+ private finishHumanReceipt;
1487
1493
  /**
1488
1494
  * Whether ONE delivery reached a verdict, carried by that delivery.
1489
1495
  *
package/dist/daemon.js CHANGED
@@ -9,6 +9,7 @@ import { rotateLogIfNeededAsync } from "./logger.js";
9
9
  import { coredumpFilterLaunchPrefix } from "./coredump-filter.js";
10
10
  import { mcpServerState } from "./mcp-liveness.js";
11
11
  import { clearPausedMarker, writePausedMarker } from "./pause-marker.js";
12
+ import { writeBackgroundWorkEndedMarker } from "./background-work-ended.js";
12
13
  import { TmuxManager, resolveTmuxLogicalSize } from "./tmux-manager.js";
13
14
  import { TranscriptMonitor } from "./transcript-monitor.js";
14
15
  import { createTranscriptSource } from "./transcript-sources.js";
@@ -32,6 +33,7 @@ import { HangDetector } from "./hang-detector.js";
32
33
  import { writeSecretFile } from "./secret-file.js";
33
34
  import { PaneWriteLock } from "./pane-write-lock.js";
34
35
  import { buildFleetInstructions } from "./instructions.js";
36
+ import { formatHumanInboundMessage, prepareDeliveryWire, steerInboundEnvelope, BTW_INBOUND_PREFIX, humanBodyHash, humanOwnerObjectId } from "./human-delivery-receipts.js";
35
37
  import { formatCrossInstanceInboundMessage, renderCrossInstanceHandoffMetadata, } from "./cross-instance-envelope.js";
36
38
  import { agendMessageInInput, bottomRowIsReady, inputAreaText, inputShowsPastedText, pastedTextSignature, pasteLeftInInput, strandedAgendMessageInInput } from "./pane-input-residue.js";
37
39
  import { TurnReplyGuard } from "./turn-reply-guard.js";
@@ -2002,6 +2004,7 @@ export class Daemon extends EventEmitter {
2002
2004
  }
2003
2005
  }
2004
2006
  deferDurableDelivery(meta, reason) {
2007
+ this.finishHumanReceipt(meta, { reached: false });
2005
2008
  const delivery = this.durableDeliveryAttempt(meta);
2006
2009
  if (delivery === null || delivery === false)
2007
2010
  return;
@@ -4232,6 +4235,13 @@ export class Daemon extends EventEmitter {
4232
4235
  if (!backend.quitBlockedByDialog(pane))
4233
4236
  return false;
4234
4237
  this.logger.warn({ reason }, "The CLI asked to confirm its exit (background work is running) — cancelling that prompt and stopping the process, not answering it");
4238
+ // #1716: the background work is about to be ended — record it where the
4239
+ // stop/restart reply and the instance topic notice can report it, so the
4240
+ // kill is never silent.
4241
+ try {
4242
+ writeBackgroundWorkEndedMarker(this.instanceDir, reason);
4243
+ }
4244
+ catch { /* reporting only */ }
4235
4245
  await this.tmux.sendSpecialKey("Escape").catch(() => false);
4236
4246
  return true;
4237
4247
  }
@@ -4564,6 +4574,39 @@ export class Daemon extends EventEmitter {
4564
4574
  this.pauseRequested = false;
4565
4575
  return;
4566
4576
  }
4577
+ // #1717: an idle pause is AgEnD-initiated and silent — it must not kill
4578
+ // the user's running background work the way an explicit stop may. Defer
4579
+ // this cycle while the backend reports background work; the next idle
4580
+ // tick retries once the task ends. A pane-capture failure proceeds (the
4581
+ // quit-menu handler below stays the backstop), never wedges unpaused.
4582
+ if (this.backend?.backgroundWorkRunning && this.tmux) {
4583
+ const tmux = this.tmux;
4584
+ const windowId = tmux.getWindowId?.();
4585
+ const generation = this.spawnGeneration;
4586
+ let pane = null;
4587
+ try {
4588
+ pane = await tmux.capturePane();
4589
+ }
4590
+ catch { /* capture failed — pause as before, after revalidation */ }
4591
+ // #1730 P2-2: the await yields — the pane read is only valid for the
4592
+ // world it was taken from. Revalidate owner, phase, state and queue
4593
+ // before acting on it; anything that moved defers this cycle.
4594
+ if (this.tmux !== tmux
4595
+ || this.tmux?.getWindowId?.() !== windowId
4596
+ || this.spawnGeneration !== generation
4597
+ || this.getPauseWakeState() !== "active"
4598
+ || !(this.instanceState === "idle" || (allowStuck && this.instanceState === "stuck"))
4599
+ || this.pasteQueueDepth > 0) {
4600
+ this.logger.info("Deferring auto-pause: pause preconditions changed during the pane read");
4601
+ this.pauseRequested = false;
4602
+ return;
4603
+ }
4604
+ if (pane !== null && this.backend?.backgroundWorkRunning?.(pane)) {
4605
+ this.logger.info("Deferring auto-pause: background work is still running");
4606
+ this.pauseRequested = false;
4607
+ return;
4608
+ }
4609
+ }
4567
4610
  this.pauseWakeState = "pausing";
4568
4611
  this.healthCheckPaused = true;
4569
4612
  this.freezeRuntimeMonitors();
@@ -6281,11 +6324,7 @@ export class Daemon extends EventEmitter {
6281
6324
  * is a durable delivery) must LEAD the persisted user message — restart reconciliation recognises a delivery only by a
6282
6325
  * marker at the start of the body (#1197) — so the banner goes right after it, not in front of it.
6283
6326
  */
6284
- static steerEnvelope(inbound) {
6285
- const banner = "[STEERING — mid-task course correction. Fold this into the CURRENT work if one is active.]\n";
6286
- const marker = inbound.match(/^\[agend-delivery-id:[^\]\r\n]+\]\r?\n/);
6287
- return marker ? marker[0] + banner + inbound.slice(marker[0].length) : banner + inbound;
6288
- }
6327
+ static steerEnvelope(inbound) { return steerInboundEnvelope(inbound); }
6289
6328
  /**
6290
6329
  * The one place an inbound message grows its metadata wrapper ([user:]/
6291
6330
  * [from:] prefix, pending reactions, handoff metadata, reply instructions).
@@ -6296,35 +6335,65 @@ export class Daemon extends EventEmitter {
6296
6335
  /** #1650: how this instance talks to the fleet — MCP tools, or agend-agent commands (agent_mode: cli). */
6297
6336
  agentMode() { return this.config.agent_mode === "cli" ? "cli" : "mcp"; }
6298
6337
  formatInboundMessage(content, meta) {
6299
- const user = meta.user || "unknown";
6300
- const fromInstance = meta.from_instance;
6301
- let formatted;
6302
- if (fromInstance) {
6303
- 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]);
6304
6359
  }
6305
- else {
6306
- const via = meta.source ? ` via ${meta.source}` : "";
6307
- const idTag = meta.user_id ? `, id:${meta.user_id}` : "";
6308
- formatted = `[user:${user}${via}${idTag}] ${content}`;
6309
- // Reactions queued since the last real message (#432). One leading line of
6310
- // context, present only when something is pending — a reaction no longer
6311
- // costs a turn of its own.
6312
- if (meta.pending_reactions) {
6313
- formatted = `[Recent reactions: ${meta.pending_reactions}]\n${formatted}`;
6314
- }
6315
- formatted += renderHandoffMetadata(meta);
6316
- // #1650: a CLI-mode agent has no reply tool — it answers with the agend-agent command.
6317
- formatted += this.agentMode() === "cli"
6318
- ? '\n(Reply with agend-agent reply "…" — do NOT respond with direct text)'
6319
- : "\n(Reply using the reply tool — do NOT respond with direct text)";
6320
- }
6321
- if (meta.reply_to_text) {
6322
- formatted += `\n(reply_to: "${meta.reply_to_text}")`;
6323
- }
6324
- if (meta.delivery_id) {
6325
- 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");
6326
6396
  }
6327
- return formatted;
6328
6397
  }
6329
6398
  /**
6330
6399
  * Whether ONE delivery reached a verdict, carried by that delivery.
@@ -6417,12 +6486,13 @@ export class Daemon extends EventEmitter {
6417
6486
  this.deferDurableDelivery(meta, "delivery epoch changed while waking for steer");
6418
6487
  return;
6419
6488
  }
6420
- const verdict = { reached: false };
6489
+ const verdict = { reached: false, humanReceipt: this.humanReceiptAttempt(meta, formatted) };
6421
6490
  try {
6422
6491
  if (await this.deliverMessage(formatted, status, {
6423
6492
  steer: true,
6424
6493
  deliveryEpoch,
6425
6494
  submissionId: meta.message_id,
6495
+ ...(meta.verification_id ? { humanVerificationId: meta.verification_id } : {}),
6426
6496
  verdict,
6427
6497
  durableAttempt: durableAttempt || undefined,
6428
6498
  })) {
@@ -6453,6 +6523,9 @@ export class Daemon extends EventEmitter {
6453
6523
  this.retryDurableDeliveryBeforeBegin(durableAttempt, err.message);
6454
6524
  throw err;
6455
6525
  }
6526
+ finally {
6527
+ this.finishHumanReceipt(meta, verdict);
6528
+ }
6456
6529
  }).catch(err => {
6457
6530
  this.deferDurableDelivery(meta, `target wake/steer failed before pane submission: ${err.message}`);
6458
6531
  this.logger.warn({ err: err.message }, "steer delivery error");
@@ -6468,24 +6541,37 @@ export class Daemon extends EventEmitter {
6468
6541
  * so channel delivery intentionally uses a normal wrapped inbound instead.
6469
6542
  */
6470
6543
  btwMessage(content, meta, deliveryEpoch = this.deliveryEpoch) {
6471
- if (!this.isDeliveryEpochCurrent(deliveryEpoch))
6544
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch)) {
6545
+ this.finishHumanReceipt(meta, { reached: false });
6472
6546
  return;
6547
+ }
6473
6548
  this.updateLastChat(meta.chat_id, meta.thread_id, meta.adapter_id);
6474
6549
  this.pendingWork.recordInbound();
6475
6550
  this.recordRecentUserMessage(content, meta);
6476
- const formatted = "[BTW — side question from the user. Answer it separately without changing or interrupting the CURRENT work.]\n"
6477
- + this.formatInboundMessage(content, meta);
6551
+ const formatted = BTW_INBOUND_PREFIX + this.formatInboundMessage(content, meta);
6478
6552
  const status = channelStatus(meta);
6479
6553
  this.steerLock = this.steerLock.then(async () => {
6480
- if (!this.isDeliveryEpochCurrent(deliveryEpoch))
6554
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch)) {
6555
+ this.finishHumanReceipt(meta, { reached: false });
6481
6556
  return;
6557
+ }
6482
6558
  await this.wake();
6483
- if (!this.isDeliveryEpochCurrent(deliveryEpoch))
6559
+ if (!this.isDeliveryEpochCurrent(deliveryEpoch)) {
6560
+ this.finishHumanReceipt(meta, { reached: false });
6484
6561
  return;
6485
- if (await this.deliverMessage(formatted, status, { steer: true, deliveryEpoch, submissionId: meta.message_id })) {
6486
- 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);
6487
6572
  }
6488
6573
  }).catch(err => {
6574
+ this.finishHumanReceipt(meta, { reached: false });
6489
6575
  this.logger.warn({ err: err.message }, "btw delivery error");
6490
6576
  });
6491
6577
  }
@@ -6599,7 +6685,7 @@ export class Daemon extends EventEmitter {
6599
6685
  // A fresh delivery begins a fresh turn — its bubble must not inherit
6600
6686
  // the previous turn's tool list.
6601
6687
  this.resetToolProgress();
6602
- verdict = { reached: false };
6688
+ verdict = { reached: false, humanReceipt: this.humanReceiptAttempt(meta, formatted) };
6603
6689
  const durableAttempt = this.durableDeliveryAttempt(meta, content);
6604
6690
  if (durableAttempt === false) {
6605
6691
  this.deferDurableDelivery(meta, "invalid durable delivery attempt metadata");
@@ -6609,6 +6695,7 @@ export class Daemon extends EventEmitter {
6609
6695
  if (await this.deliverMessage(formatted, status, {
6610
6696
  deliveryEpoch,
6611
6697
  submissionId: meta.message_id,
6698
+ ...(meta.verification_id ? { humanVerificationId: meta.verification_id } : {}),
6612
6699
  verdict,
6613
6700
  durableAttempt: durableAttempt || undefined,
6614
6701
  })) {
@@ -6656,6 +6743,7 @@ export class Daemon extends EventEmitter {
6656
6743
  this.logger.warn({ err: err.message }, "Durable pane delivery deferred after exception");
6657
6744
  }
6658
6745
  finally {
6746
+ this.finishHumanReceipt(meta, verdict);
6659
6747
  this.pasteQueueDepth--;
6660
6748
  }
6661
6749
  }).catch(err => {
@@ -6692,12 +6780,7 @@ export class Daemon extends EventEmitter {
6692
6780
  && !this.isDeliveryEpochCurrent(opts.deliveryEpoch);
6693
6781
  if (cancelled() || this.stormWindow?.isStopped())
6694
6782
  return false;
6695
- // Sanitize unclosed code fences — they cause CLI to wait for closure on Enter
6696
- const fenceCount = (formatted.match(/```/g) || []).length;
6697
- if (!opts?.preserveExactBytes && fenceCount % 2 !== 0) {
6698
- // Odd number of fences = unclosed. Remove all code fences from the message.
6699
- formatted = formatted.replace(/```/g, "");
6700
- }
6783
+ formatted = prepareDeliveryWire(formatted, opts?.preserveExactBytes);
6701
6784
  // Direct paths such as /steer, /btw, /raw and schedules do not pass the
6702
6785
  // FleetManager idle gate. Hold them at the final common pane-write path so
6703
6786
  // a tmux storm cannot force any text into a half-booted CLI.
@@ -6870,7 +6953,7 @@ export class Daemon extends EventEmitter {
6870
6953
  return stale();
6871
6954
  if (residue !== "clear")
6872
6955
  return this.failDelivery(verdict, status, "input-residue", residue);
6873
- 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);
6874
6957
  // #1690: still under the lock — whatever waits on it next must already see what this write started.
6875
6958
  if (verdict.paneWriteStarted)
6876
6959
  opts?.onPaneWritten?.();
@@ -7665,7 +7748,7 @@ export class Daemon extends EventEmitter {
7665
7748
  return false;
7666
7749
  const row = norm(shown);
7667
7750
  // The marker line holds nothing but the marker: the row is exactly it.
7668
- if (/^\[agend-delivery-id:[^\]\s]+\]$/.test(first))
7751
+ if (/^\[agend-(?:delivery|human)-id:[^\]\s]+\]$/.test(first))
7669
7752
  return row === first;
7670
7753
  return row === first || row.startsWith(`${first} `);
7671
7754
  }
@@ -8207,6 +8290,8 @@ export class Daemon extends EventEmitter {
8207
8290
  if (!current())
8208
8291
  return false;
8209
8292
  if (evidence === "submitted") {
8293
+ if (pending.signature.unique)
8294
+ pending.signature.humanPositive?.("unique-id-pane");
8210
8295
  if (status)
8211
8296
  this.emit("message_confirmed", status);
8212
8297
  return true;
@@ -8242,6 +8327,8 @@ export class Daemon extends EventEmitter {
8242
8327
  if (retry === "cancelled")
8243
8328
  return false;
8244
8329
  if (retry === "submitted") {
8330
+ if (pending.signature.unique)
8331
+ pending.signature.humanPositive?.("unique-id-pane");
8245
8332
  if (status)
8246
8333
  this.emit("message_confirmed", status);
8247
8334
  return true;
@@ -8382,9 +8469,24 @@ export class Daemon extends EventEmitter {
8382
8469
  verdict = { reached: false }, durableAttempt, steer = false,
8383
8470
  // #829: the caller's fence, asked once more after the last await before the
8384
8471
  // first side effect. Stale means unwritten: false, with no failure verdict.
8385
- stillCurrent) {
8386
- const signature = this.submissionSignature(formatted, submissionId);
8472
+ stillCurrent, humanVerificationId) {
8473
+ const signature = this.submissionSignature(formatted, humanVerificationId ?? submissionId, humanVerificationId !== undefined);
8387
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
+ }
8388
8490
  let windowId = initialWindowId;
8389
8491
  // The spawn this write belongs to: acceptance evidence is only read while it is still the current one (#1197).
8390
8492
  const spawnAtWrite = this.spawnGeneration;
@@ -8530,6 +8632,7 @@ export class Daemon extends EventEmitter {
8530
8632
  // positively hold typed text, and after it be the empty placeholder again. The empty placeholder here means the
8531
8633
  // paste never reached the box; an unreadable box (a failed or late capture, a dialog) proves nothing: no Enter.
8532
8634
  const steerComposerProof = handingOffToNativeQueue && steer && !!this.backend?.readSteerComposer && tmuxAtWrite !== null;
8635
+ let humanComposerOwned = false;
8533
8636
  let steerEnterDeadline = Infinity;
8534
8637
  if (steerComposerProof) {
8535
8638
  const deadline = performance.now() + STEER_PASTE_VISIBLE_MS;
@@ -8553,6 +8656,8 @@ export class Daemon extends EventEmitter {
8553
8656
  return this.failDelivery(verdict, status, "steer-paste", before === null ? "box-unread" : `box-still-${before.mode}`);
8554
8657
  if (!this.steerPasteIsOurs(before.pane, formatted))
8555
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 + "]";
8556
8661
  // …and so is the Enter: the key is sent only while the reading is still within it.
8557
8662
  steerEnterDeadline = deadline;
8558
8663
  }
@@ -8624,6 +8729,9 @@ export class Daemon extends EventEmitter {
8624
8729
  if (after === "steer" || after === "queue" || after === "idle") {
8625
8730
  const probe = await withinMs(this.probeBlockingDialog(), deadline - performance.now());
8626
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");
8627
8735
  verdict.phase = "steer-accepted";
8628
8736
  verdict.proof = `steer-composer-${after}`;
8629
8737
  if (status)
@@ -9210,8 +9318,11 @@ export class Daemon extends EventEmitter {
9210
9318
  // the input row is proof in itself — which also means a momentary
9211
9319
  // failure to read the pane BEFORE pasting cannot turn a delivered
9212
9320
  // message into a re-paste.
9213
- if (signature.unique && after.payload > 0)
9321
+ if (signature.unique && after.payload > 0) {
9322
+ if (after.inputReadable)
9323
+ signature.humanPositive?.("unique-id-pane");
9214
9324
  return this.submittedProof();
9325
+ }
9215
9326
  if (unattributedResidue)
9216
9327
  return "unverifiable";
9217
9328
  // 3. Otherwise the evidence must be NEW relative to the pane as it was
@@ -9348,7 +9459,7 @@ export class Daemon extends EventEmitter {
9348
9459
  * paste that never landed. System pastes carry no envelope and fall back to
9349
9460
  * the body signature, which the baseline comparison still ties to this paste.
9350
9461
  */
9351
- submissionSignature(formatted, trustedId) {
9462
+ submissionSignature(formatted, trustedId, verification = false) {
9352
9463
  // The id must come from the message's own metadata, never from scanning the
9353
9464
  // rendered text: agents and users discuss message ids in the body all the
9354
9465
  // time ("check message_id: abc"), and a body-scanned value would match an
@@ -9357,7 +9468,7 @@ export class Daemon extends EventEmitter {
9357
9468
  // the handoff metadata identifies THIS delivery.
9358
9469
  const id = trustedId?.trim();
9359
9470
  if (id)
9360
- return { value: `message_id:${id}`, unique: true };
9471
+ return { value: (verification ? "agend-human-id:" : "message_id:") + id, unique: true };
9361
9472
  return { value: pastedTextSignature(formatted), unique: false };
9362
9473
  }
9363
9474
  /**