instar 1.3.1350 → 1.3.1352

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 (101) hide show
  1. package/dashboard/index.html +62 -16
  2. package/dashboard/pairing.js +229 -109
  3. package/dist/commands/server.d.ts.map +1 -1
  4. package/dist/commands/server.js +5 -1
  5. package/dist/commands/server.js.map +1 -1
  6. package/dist/core/ClaudeBinaryRetainer.d.ts +186 -0
  7. package/dist/core/ClaudeBinaryRetainer.d.ts.map +1 -0
  8. package/dist/core/ClaudeBinaryRetainer.js +585 -0
  9. package/dist/core/ClaudeBinaryRetainer.js.map +1 -0
  10. package/dist/core/Config.d.ts.map +1 -1
  11. package/dist/core/Config.js +5 -0
  12. package/dist/core/Config.js.map +1 -1
  13. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  14. package/dist/core/PostUpdateMigrator.js +53 -17
  15. package/dist/core/PostUpdateMigrator.js.map +1 -1
  16. package/dist/core/SessionManager.d.ts +26 -0
  17. package/dist/core/SessionManager.d.ts.map +1 -1
  18. package/dist/core/SessionManager.js +61 -5
  19. package/dist/core/SessionManager.js.map +1 -1
  20. package/dist/core/WriteDomainRegistry.d.ts.map +1 -1
  21. package/dist/core/WriteDomainRegistry.js +26 -0
  22. package/dist/core/WriteDomainRegistry.js.map +1 -1
  23. package/dist/core/types.d.ts +14 -0
  24. package/dist/core/types.d.ts.map +1 -1
  25. package/dist/core/types.js.map +1 -1
  26. package/dist/data/standards-guard-index.json +1 -1
  27. package/dist/data/standards-guard-index.meta.json +2 -2
  28. package/dist/data/standards-registry.meta.json +1 -1
  29. package/dist/lifeline/MessageQueue.d.ts.map +1 -1
  30. package/dist/lifeline/MessageQueue.js +5 -0
  31. package/dist/lifeline/MessageQueue.js.map +1 -1
  32. package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
  33. package/dist/lifeline/TelegramLifeline.js +8 -0
  34. package/dist/lifeline/TelegramLifeline.js.map +1 -1
  35. package/dist/messaging/TelegramAdapter.d.ts +13 -0
  36. package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
  37. package/dist/messaging/TelegramAdapter.js +51 -0
  38. package/dist/messaging/TelegramAdapter.js.map +1 -1
  39. package/dist/scaffold/templates.d.ts.map +1 -1
  40. package/dist/scaffold/templates.js +9 -5
  41. package/dist/scaffold/templates.js.map +1 -1
  42. package/dist/server/AgentServer.d.ts +2 -0
  43. package/dist/server/AgentServer.d.ts.map +1 -1
  44. package/dist/server/AgentServer.js +1 -0
  45. package/dist/server/AgentServer.js.map +1 -1
  46. package/dist/server/routes.d.ts +2 -0
  47. package/dist/server/routes.d.ts.map +1 -1
  48. package/dist/server/routes.js +236 -0
  49. package/dist/server/routes.js.map +1 -1
  50. package/dist/threadline/AgentTrustManager.d.ts +36 -1
  51. package/dist/threadline/AgentTrustManager.d.ts.map +1 -1
  52. package/dist/threadline/AgentTrustManager.js +111 -0
  53. package/dist/threadline/AgentTrustManager.js.map +1 -1
  54. package/dist/threadline/InboundMessageGate.d.ts +10 -0
  55. package/dist/threadline/InboundMessageGate.d.ts.map +1 -1
  56. package/dist/threadline/InboundMessageGate.js +39 -1
  57. package/dist/threadline/InboundMessageGate.js.map +1 -1
  58. package/dist/threadline/PairingInvite.d.ts +92 -0
  59. package/dist/threadline/PairingInvite.d.ts.map +1 -0
  60. package/dist/threadline/PairingInvite.js +174 -0
  61. package/dist/threadline/PairingInvite.js.map +1 -0
  62. package/dist/threadline/PairingInviteFlow.d.ts +141 -0
  63. package/dist/threadline/PairingInviteFlow.d.ts.map +1 -0
  64. package/dist/threadline/PairingInviteFlow.js +345 -0
  65. package/dist/threadline/PairingInviteFlow.js.map +1 -0
  66. package/dist/threadline/PairingInviteStore.d.ts +92 -0
  67. package/dist/threadline/PairingInviteStore.d.ts.map +1 -0
  68. package/dist/threadline/PairingInviteStore.js +163 -0
  69. package/dist/threadline/PairingInviteStore.js.map +1 -0
  70. package/dist/threadline/PairingInviteTelegram.d.ts +76 -0
  71. package/dist/threadline/PairingInviteTelegram.d.ts.map +1 -0
  72. package/dist/threadline/PairingInviteTelegram.js +141 -0
  73. package/dist/threadline/PairingInviteTelegram.js.map +1 -0
  74. package/dist/threadline/ThreadlineBootstrap.d.ts +14 -0
  75. package/dist/threadline/ThreadlineBootstrap.d.ts.map +1 -1
  76. package/dist/threadline/ThreadlineBootstrap.js +51 -1
  77. package/dist/threadline/ThreadlineBootstrap.js.map +1 -1
  78. package/dist/threadline/ThreadlineMCPServer.d.ts +13 -0
  79. package/dist/threadline/ThreadlineMCPServer.d.ts.map +1 -1
  80. package/dist/threadline/ThreadlineMCPServer.js +22 -7
  81. package/dist/threadline/ThreadlineMCPServer.js.map +1 -1
  82. package/dist/threadline/client/ThreadlineClient.d.ts +11 -0
  83. package/dist/threadline/client/ThreadlineClient.d.ts.map +1 -1
  84. package/dist/threadline/client/ThreadlineClient.js +24 -1
  85. package/dist/threadline/client/ThreadlineClient.js.map +1 -1
  86. package/dist/threadline/mcp-http-client.d.ts +14 -0
  87. package/dist/threadline/mcp-http-client.d.ts.map +1 -1
  88. package/dist/threadline/mcp-http-client.js +29 -0
  89. package/dist/threadline/mcp-http-client.js.map +1 -1
  90. package/dist/threadline/mcp-stdio-entry.js +3 -1
  91. package/dist/threadline/mcp-stdio-entry.js.map +1 -1
  92. package/package.json +1 -1
  93. package/src/data/builtin-manifest.json +66 -66
  94. package/src/data/standards-guard-index.json +1 -1
  95. package/src/data/standards-guard-index.meta.json +2 -2
  96. package/src/data/standards-registry.meta.json +1 -1
  97. package/src/scaffold/templates.ts +9 -5
  98. package/upgrades/1.3.1351.md +28 -0
  99. package/upgrades/1.3.1352.md +41 -0
  100. package/upgrades/side-effects/retained-claude-executable.md +116 -0
  101. package/upgrades/side-effects/threadline-pairing-invite-link.md +184 -0
@@ -1055,6 +1055,14 @@
1055
1055
  .pair-word { display: flex; align-items: center; gap: 4px; font-size: 12px; color: var(--text-dim); }
1056
1056
  .pair-word input { flex: 1; min-width: 0; padding: 6px; font-family: ui-monospace, monospace; font-size: 15px; }
1057
1057
  .pair-meta { font-size: 11px; color: var(--text-dim); margin-top: 8px; }
1058
+ .pair-waiting { border-color: #1f6feb; }
1059
+ #pairNotice:empty { display: none; }
1060
+ .pair-pin { width: 160px; padding: 8px; font-size: 16px; }
1061
+ .pair-nav-badge { margin-left: 8px; padding: 1px 8px; border-radius: 10px; background: #1f6feb; color: #fff; font-size: 12px; font-weight: 600; }
1062
+ /* On a phone the header has no room left: the badge floats on the menu button's corner and takes no width. */
1063
+ @media (max-width: 640px) { .pair-nav-badge-word { display: none; } #navToggle { position: relative; } .pair-nav-badge { position: absolute; top: -7px; right: -7px; margin: 0; padding: 1px 6px; } }
1064
+ .pair-alarm { border-color: #f85149; background: rgba(248, 81, 73, 0.08); }
1065
+ .pair-invite-text { width: 100%; box-sizing: border-box; font-family: ui-monospace, monospace; font-size: 13px; padding: 8px; }
1058
1066
  @media (max-width: 640px) {
1059
1067
  .pair-own-words { columns: 2; }
1060
1068
  .pair-words { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@@ -3023,6 +3031,10 @@
3023
3031
  </div>
3024
3032
 
3025
3033
  <!-- Main app -->
3034
+ <div id="dashVersionBar" style="display:none;position:fixed;top:0;left:0;right:0;z-index:10000;gap:10px;align-items:center;justify-content:center;padding:8px 12px;background:#1f6feb;color:#fff;font-size:14px">
3035
+ The agent was updated to <span data-new-version></span>. Reload to get the new dashboard.
3036
+ <button onclick="location.reload()" style="padding:4px 12px">Reload</button>
3037
+ </div>
3026
3038
  <div class="app" id="app" style="display:none">
3027
3039
  <header class="header">
3028
3040
  <div class="header-left">
@@ -4173,6 +4185,30 @@
4173
4185
  Threadline is your agent's secure channel to other AI agents. The Telegram bridge mirrors those conversations into per-thread Telegram topics so you can watch every exchange. Quiet by default — flip the master switch below to opt in.
4174
4186
  </div>
4175
4187
 
4188
+ <!-- Pair with another agent — by invite (docs/specs/a2a-pairing-invite-link.md §8). First on the
4189
+ tab: decisions waiting for the operator sit at the top of this card. -->
4190
+ <div id="pairCard" class="mnd-card" style="border:1px solid var(--border);border-radius:8px;padding:16px">
4191
+ <strong>Pair with another agent</strong>
4192
+ <div class="mnd-hint">Pairing proves another agent is really who it says it is, before it can be sent anything secret.</div>
4193
+ <div id="pairNotice" class="mnd-notice"></div>
4194
+ <div id="pairPinRow" class="pair-row" style="display:none"><label for="pairPin" class="pair-step-title" style="margin:0">Your PIN, to approve (asked once):</label><input type="password" id="pairPin" class="mnd-pin pair-pin" inputmode="numeric" autocomplete="off" /></div>
4195
+ <div id="pairPending"></div>
4196
+ <div class="pair-step">
4197
+ <div class="pair-step-title">Invite an agent</div>
4198
+ <div class="pair-row"><label for="pairFor" class="mnd-dim">Who is it for?</label><select id="pairFor"><option value="">Someone new</option></select></div>
4199
+ <div class="pair-row"><select id="pairTtl"><option value="60">Valid for 1 hour</option><option value="1440">Valid for 24 hours</option></select>
4200
+ <button class="mnd-btn mnd-btn-primary" data-make-invite>Make an invite</button></div>
4201
+ <div id="pairInviteOut"></div>
4202
+ <div id="pairOpen"></div>
4203
+ </div>
4204
+ <div class="pair-step">
4205
+ <div class="pair-step-title">Got an invite? Paste it here</div>
4206
+ <textarea id="pairInviteIn" class="pair-invite-text" rows="3" placeholder="Paste the whole message you were sent" autocomplete="off" spellcheck="false"></textarea>
4207
+ <div class="pair-row"><button class="mnd-btn mnd-btn-primary" data-inspect-invite>Check it</button></div>
4208
+ </div>
4209
+ <div id="pairList"></div>
4210
+ </div>
4211
+
4176
4212
  <!-- Bridge settings card -->
4177
4213
  <div id="tlBridgeCard" style="border:1px solid var(--border);border-radius:8px;padding:16px;display:flex;flex-direction:column;gap:14px">
4178
4214
  <div style="font-weight:600">Telegram bridge settings</div>
@@ -4202,7 +4238,7 @@
4202
4238
  </span>
4203
4239
  </label>
4204
4240
 
4205
- <div style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
4241
+ <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px">
4206
4242
  <div>
4207
4243
  <div style="font-weight:600;font-size:13px;margin-bottom:6px">Allow-list</div>
4208
4244
  <div style="font-size:11px;color:var(--text-dim);margin-bottom:6px">Always auto-create topics for these remote agents (overrides "auto-create off").</div>
@@ -4226,21 +4262,10 @@
4226
4262
  <div id="tlBridgeError" style="color:var(--err, #c44);font-size:12px;display:none"></div>
4227
4263
  </div>
4228
4264
 
4229
- <!-- Verified pairing — the 12-word check (docs/specs/secure-a2a-verified-pairing.md §3.6) -->
4230
- <div id="pairCard" class="mnd-card" style="border:1px solid var(--border);border-radius:8px;padding:16px">
4231
- <div style="display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap">
4232
- <strong>Verified pairing</strong>
4233
- <button onclick="startPairingPanel()" class="mnd-btn mnd-btn-quiet" style="padding:6px 12px">Refresh</button>
4234
- </div>
4235
- <div class="mnd-hint">Confirm another agent is really who it says it is: compare 12 words with its operator before it can be sent credentials.</div>
4236
- <div id="pairNotice" class="mnd-notice"></div>
4237
- <div id="pairList"></div>
4238
- </div>
4239
-
4240
4265
  <!-- Conversation observability — threads list + message stream + search -->
4241
4266
  <div style="border:1px solid var(--border);border-radius:8px;padding:0;display:flex;flex-direction:column;min-height:480px">
4242
4267
  <!-- Toolbar -->
4243
- <div style="display:flex;gap:8px;align-items:center;padding:12px 16px;border-bottom:1px solid var(--border)">
4268
+ <div style="display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:12px 16px;border-bottom:1px solid var(--border)">
4244
4269
  <div style="font-weight:600">Conversations</div>
4245
4270
  <div style="flex:1"></div>
4246
4271
  <input type="search" id="tlObsSearchInput" placeholder="search messages…" style="padding:6px 8px;font-size:12px;width:220px" oninput="tlObsSearchDebounced(this.value)" />
@@ -4430,6 +4455,7 @@
4430
4455
  startWaPolling();
4431
4456
  startVitalSigns();
4432
4457
  startPoolSessionsPolling();
4458
+ if (typeof startPairingPanel === 'function') startPairingPanel();
4433
4459
  loadTopicProfileOptions();
4434
4460
  } else {
4435
4461
  showAuthError('Incorrect PIN');
@@ -4457,6 +4483,7 @@
4457
4483
  startWaPolling();
4458
4484
  startVitalSigns();
4459
4485
  startPoolSessionsPolling();
4486
+ if (typeof startPairingPanel === 'function') startPairingPanel();
4460
4487
  loadTopicProfileOptions();
4461
4488
  }
4462
4489
  })
@@ -8008,6 +8035,18 @@
8008
8035
  const resp = await fetch('/health', { headers: { 'Authorization': 'Bearer ' + token } });
8009
8036
  if (!resp.ok) return;
8010
8037
  const h = await resp.json();
8038
+ // a2a-pairing-invite-link §8.4: an updated server means this page may be stale. Offer a
8039
+ // reload instead of silently showing an old dashboard.
8040
+ if (h.version) {
8041
+ if (!window.__dashServerVersion) window.__dashServerVersion = h.version;
8042
+ else if (h.version !== window.__dashServerVersion) {
8043
+ const bar = document.getElementById('dashVersionBar');
8044
+ if (bar && bar.style.display === 'none') {
8045
+ bar.querySelector('[data-new-version]').textContent = h.version;
8046
+ bar.style.display = 'flex';
8047
+ }
8048
+ }
8049
+ }
8011
8050
 
8012
8051
  // Server status
8013
8052
  const serverDot = document.getElementById('vitalServerDot');
@@ -10443,8 +10482,9 @@
10443
10482
  } catch { host.innerHTML = '<div style="opacity:.7">Throughput data is temporarily unavailable.</div>'; }
10444
10483
  }
10445
10484
 
10446
- // ── Verified pairing panel (Threadline tab; secure-a2a-verified-pairing §3.6) ──
10447
- // Lazy-loaded like Mandates. The PIN typed here is sent once per action, never stored.
10485
+ // ── Pairing by invite (Threadline tab; a2a-pairing-invite-link §8) ──
10486
+ // Started at login (not only on the tab) so the tab can show waiting decisions.
10487
+ // The PIN is held in the module's memory after one accepted use; never stored.
10448
10488
  let __pairController = null;
10449
10489
  let __pairModule = null;
10450
10490
  async function startPairingPanel() {
@@ -10453,7 +10493,13 @@
10453
10493
  catch (e) { console.error('[pairing] module load failed', e); return; }
10454
10494
  }
10455
10495
  if (!__pairController) {
10456
- const els = { list: document.getElementById('pairList'), notice: document.getElementById('pairNotice') };
10496
+ const $ = (id) => document.getElementById(id);
10497
+ const els = {
10498
+ root: $('pairCard'), notice: $('pairNotice'), pending: $('pairPending'), open: $('pairOpen'), list: $('pairList'),
10499
+ pin: $('pairPin'), pinRow: $('pairPinRow'), ttl: $('pairTtl'), forPeer: $('pairFor'), inviteOut: $('pairInviteOut'), inviteIn: $('pairInviteIn'),
10500
+ tabButton: document.querySelector('.tab[data-tab="threadline"]'),
10501
+ navToggle: $('navToggle'),
10502
+ };
10457
10503
  const fetchImpl = (url, opts = {}) => fetch(url, {
10458
10504
  ...opts,
10459
10505
  headers: { 'Authorization': `Bearer ${token}`, ...(opts.headers || {}) },
@@ -1,86 +1,116 @@
1
1
  /**
2
- * pairing.js — the Verified Pairing panel on the dashboard's Threadline tab
3
- * (docs/specs/secure-a2a-verified-pairing.md §3.6, Mobile-Complete operator action).
2
+ * pairing.js — "Pair with another agent" on the dashboard's Threadline tab
3
+ * (docs/specs/a2a-pairing-invite-link.md §8; replaces the 12-word panel of
4
+ * secure-a2a-verified-pairing §3.6, which Justin rejected on 2026-10-13).
4
5
  *
5
- * The operator's surface for the 12-word check: list pairings; for a pending one,
6
- * enter the dashboard PIN to see THIS side's 12 words, then type the 12 words the
7
- * OTHER side shows and confirm (the server compares all 12 in order; any difference
8
- * is a denial). A denied pairing can be cleared with the PIN. The PIN is typed at
9
- * action time, sent once, and never stored (no localStorage, no module state).
6
+ * What the operator does: make an invite and send it however they like; or paste one
7
+ * they were sent and approve it; and, when someone accepts their invite, Finish (or say
8
+ * "Not them"). Nobody reads, compares or types words.
9
+ *
10
+ * The rules from Justin's test (spec §8):
11
+ * 1. Nothing the operator is looking at or typing into is re-rendered by a timer. Only the
12
+ * list sections (waiting decisions, open invites, paired agents) refresh, and only when
13
+ * their content actually changed; none of them holds an input.
14
+ * 2. The PIN is asked once per page: after one accepted use it is held in this module's
15
+ * memory (never storage) until the page is reloaded. A refused PIN is forgotten.
16
+ * 3. Decisions waiting for the operator come first, with a count on the Threadline tab AND on
17
+ * the header menu button (the tabs sit behind it, so a count only on the tab is invisible).
10
18
  *
11
19
  * Same shape as mandates.js: pure renderers + createController({ doc, els, fetchImpl }).
12
20
  */
13
21
 
14
- const REFRESH_MS = 30_000;
15
- const WORD_COUNT = 12;
22
+ const POLL_MS = 10_000;
16
23
 
17
24
  function esc(s) {
18
25
  return String(s ?? '').replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
19
26
  }
20
27
 
21
28
  const STATE_LABEL = {
22
- 'pending-verification': 'Waiting for you to compare the words',
23
- 'mutual-verified': 'Verified',
24
- 'identity-verified': 'Verified on another of my machines',
25
- 'verification-failed': 'Words did not match — blocked',
29
+ 'mutual-verified': 'Paired',
30
+ 'identity-verified': 'Paired (on another of my machines)',
31
+ 'invite-accepted': 'Waiting for the other side to finish',
32
+ 'pending-verification': 'Started with the old word check. Pair by invite instead.',
33
+ 'verification-failed': 'Blocked (the old word check did not match)',
34
+ };
35
+
36
+ const claimed = (agent, owner) =>
37
+ `the agent that calls itself <strong>${esc(agent || 'unknown')}</strong>${owner ? `, which says it belongs to <strong>${esc(owner)}</strong>` : ''}`;
38
+
39
+ const cap = (h) => h.charAt(0).toUpperCase() + h.slice(1);
40
+
41
+ const timeLeft = (exp, now = Date.now()) => {
42
+ const ms = Date.parse(exp) - now;
43
+ if (!(ms > 0)) return 'expired';
44
+ const min = Math.round(ms / 60_000);
45
+ return min >= 90 ? `${Math.round(min / 60)} h left` : `${Math.max(1, min)} min left`;
26
46
  };
27
47
 
28
- /** One pairing card. Primary action first; the fingerprint is support metadata. */
48
+ /** Invites waiting for this operator's Finish: used, with an acceptor, undecided, not leaked. */
49
+ export function finishPrompts(invites) {
50
+ return (invites || []).filter((i) => i.state === 'used' && i.acceptor && !i.decision && !i.secondAccept);
51
+ }
52
+
53
+ /** Leak alarms from the last 24 h. */
54
+ export function alarms(invites, now = Date.now()) {
55
+ return (invites || []).filter((i) => i.secondAccept && now - Date.parse(i.secondAccept.at) < 24 * 60 * 60_000);
56
+ }
57
+
58
+ /** How many decisions wait for the operator (the tab badge). */
59
+ export function pendingCount(data) {
60
+ return finishPrompts(data?.invites).length + (data?.pendingAccepts || []).length;
61
+ }
62
+
63
+ export function renderPending(data, now = Date.now()) {
64
+ const parts = [];
65
+ for (const a of alarms(data?.invites, now)) {
66
+ parts.push(`<div class="pair-card pair-alarm" data-alarm="${esc(a.iid)}">
67
+ <div><strong>Warning: your invite got out.</strong> Two agents used it, so I paused the first one: it cannot receive anything secret.
68
+ The second calls itself <strong>${esc(a.secondAccept.agent || 'unknown')}</strong>. Make a new invite and send it privately.</div></div>`);
69
+ }
70
+ for (const p of data?.pendingAccepts || []) {
71
+ parts.push(`<div class="pair-card pair-waiting" data-pending-accept="${esc(p.iid)}">
72
+ <div>Pair with ${claimed(p.claimedAgent, p.claimedOwner)}? Only approve if you know who sent you this invite.</div>
73
+ <div class="pair-meta">Check code (optional, to read to them): ${esc(p.checkCode)} · ${esc(timeLeft(p.exp, now))}</div>
74
+ <div class="pair-row"><button class="mnd-btn mnd-btn-primary" data-approve="${esc(p.iid)}">Approve</button>
75
+ <button class="mnd-btn mnd-btn-quiet" data-decline="${esc(p.iid)}">Decline</button></div></div>`);
76
+ }
77
+ for (const i of finishPrompts(data?.invites)) {
78
+ parts.push(`<div class="pair-card pair-waiting" data-finish-prompt="${esc(i.iid)}">
79
+ <div>${cap(claimed(i.acceptor.agent, i.acceptor.owner))} accepted your invite. Finish only if that is who you sent it to.</div>
80
+ <div class="pair-row"><button class="mnd-btn mnd-btn-primary" data-finish="${esc(i.iid)}">Finish</button>
81
+ <button class="mnd-btn mnd-btn-quiet mnd-btn-danger" data-not-them="${esc(i.iid)}">Not them</button></div></div>`);
82
+ }
83
+ return parts.join('');
84
+ }
85
+
86
+ export function renderOpenInvites(invites, now = Date.now()) {
87
+ const open = (invites || []).filter((i) => i.state === 'open' && Date.parse(i.exp) > now);
88
+ if (open.length === 0) return '';
89
+ return '<div class="pair-step-title">Invites not used yet</div>' + open.map((i) => `<div class="pair-row" data-open-invite="${esc(i.iid)}">
90
+ <span>Invite ${esc(i.iid.slice(0, 6))} · ${esc(timeLeft(i.exp, now))}</span>
91
+ <button class="mnd-btn mnd-btn-quiet" data-revoke="${esc(i.iid)}">Cancel it</button></div>`).join('');
92
+ }
93
+
94
+ /** One paired (or pairing) agent. No actions: invites replace the 12-word flow. */
29
95
  export function renderPairing(p) {
30
96
  const fp = esc(p.peerFp);
31
- const name = esc(p.peerName || 'Unknown agent');
32
- const state = p.state;
33
- const label = esc(STATE_LABEL[state] || state || 'Unknown');
34
- let actions = '';
35
- if (state === 'pending-verification') {
36
- const inputs = Array.from({ length: WORD_COUNT }, (_, i) =>
37
- `<label class="pair-word"><span>${i + 1}</span><input type="text" autocomplete="off" autocapitalize="off" spellcheck="false" data-their-word="${fp}" data-idx="${i}" aria-label="Their word ${i + 1}" /></label>`).join('');
38
- actions = `
39
- <div class="pair-step">
40
- <div class="pair-step-title">1. See the words on this side</div>
41
- <div class="pair-row"><input type="password" class="mnd-pin" data-show-pin="${fp}" placeholder="Your PIN" autocomplete="off" />
42
- <button class="mnd-btn mnd-btn-primary" data-show-words="${fp}">Show my words</button></div>
43
- <ol class="pair-own-words" data-own-words="${fp}"></ol>
44
- </div>
45
- <div class="pair-step">
46
- <div class="pair-step-title">2. Type the 12 words the OTHER agent's operator sees</div>
47
- <div class="pair-words">${inputs}</div>
48
- <div class="pair-row"><input type="password" class="mnd-pin" data-confirm-pin="${fp}" placeholder="Your PIN" autocomplete="off" />
49
- <button class="mnd-btn mnd-btn-primary" data-confirm="${fp}">Confirm they match</button></div>
50
- <div class="mnd-hint">Compare over a call or in person, not through the agents. Any wrong word blocks this pairing.</div>
51
- <button class="mnd-btn mnd-btn-quiet mnd-btn-danger" data-deny="${fp}">They don't match</button>
52
- </div>`;
53
- } else if (state === 'verification-failed') {
54
- actions = `
55
- <div class="pair-step">
56
- <div class="mnd-hint">Clear this only once you know why the words were different.</div>
57
- <div class="pair-row"><input type="password" class="mnd-pin" data-clear-pin="${fp}" placeholder="Your PIN" autocomplete="off" />
58
- <button class="mnd-btn mnd-btn-quiet" data-clear="${fp}">Clear and start again</button></div>
59
- </div>`;
60
- }
97
+ const label = esc(STATE_LABEL[p.state] || p.state || 'Unknown');
61
98
  return `<div class="pair-card" data-pairing="${fp}">
62
- <div class="pair-head"><strong>${name}</strong> <span class="pair-state pair-state-${esc(state)}">${label}</span></div>
63
- ${actions}
99
+ <div class="pair-head"><strong>${esc(p.peerName || 'Unknown agent')}</strong> <span class="pair-state pair-state-${esc(p.state)}">${label}</span></div>
64
100
  <div class="pair-meta">Agent ${fp.slice(0, 12)}…</div>
65
101
  </div>`;
66
102
  }
67
103
 
68
104
  export function renderPairings(list) {
69
- if (!Array.isArray(list) || list.length === 0) {
70
- return '<div class="mnd-hint">No pairings yet. An agent starts one with <code>threadline_pair</code> start.</div>';
71
- }
72
- return list.map(renderPairing).join('');
105
+ if (!Array.isArray(list) || list.length === 0) return '<div class="mnd-hint">No paired agents yet.</div>';
106
+ return '<div class="pair-step-title">Agents</div>' + list.map(renderPairing).join('');
73
107
  }
74
108
 
75
- /** Words typed into the 12 inputs for one pairing, in order. */
76
- export function collectTypedWords(root, fp) {
77
- const inputs = Array.from(root.querySelectorAll(`[data-their-word="${fp}"]`));
78
- inputs.sort((a, b) => Number(a.dataset.idx) - Number(b.dataset.idx));
79
- return inputs.map((i) => String(i.value || '').trim().toLowerCase());
80
- }
81
-
82
- export function createController({ doc, els, fetchImpl }) {
109
+ export function createController({ doc, els, fetchImpl, now = () => Date.now() }) {
83
110
  let timer = null;
111
+ let heldPin = ''; // §8.2: asked once per page, memory only
112
+ let data = { invites: [], pendingAccepts: [], pairings: [] };
113
+ const lastHtml = new Map(); // section → last rendered HTML (§8.1: re-render only on change)
84
114
 
85
115
  async function fetchJson(url, opts) {
86
116
  const res = await fetchImpl(url, opts);
@@ -88,6 +118,7 @@ export function createController({ doc, els, fetchImpl }) {
88
118
  try { body = await res.json(); } catch { body = null; }
89
119
  return { status: res.status, body };
90
120
  }
121
+ const post = (url, payload, method = 'POST') => fetchJson(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload ?? {}) });
91
122
 
92
123
  function note(msg, kind) {
93
124
  if (!els.notice) return;
@@ -95,70 +126,158 @@ export function createController({ doc, els, fetchImpl }) {
95
126
  els.notice.className = kind === 'error' ? 'mnd-notice mnd-notice-err' : kind === 'success' ? 'mnd-notice mnd-notice-ok' : 'mnd-notice';
96
127
  }
97
128
 
98
- // A timed refresh must not wipe words or a PIN the operator is typing.
99
- function operatorIsTyping() {
100
- const inputs = els.list ? Array.from(els.list.querySelectorAll('input')) : [];
101
- return inputs.some((i) => i.value);
129
+ function setSection(el, key, html) {
130
+ if (!el || lastHtml.get(key) === html) return;
131
+ lastHtml.set(key, html);
132
+ el.innerHTML = html;
102
133
  }
103
134
 
104
- async function load(opts = {}) {
105
- if (opts.timed && operatorIsTyping()) return;
135
+ // The PIN field sits right above the decisions and shows only while one waits and no PIN is
136
+ // held: a person meets it before the button that needs it, and never otherwise.
137
+ function showPinRow() {
138
+ if (els.pinRow) els.pinRow.style.display = !heldPin && pendingCount(data) > 0 ? '' : 'none';
139
+ }
140
+
141
+ function render() {
142
+ setSection(els.pending, 'pending', renderPending(data, now()));
143
+ setSection(els.open, 'open', renderOpenInvites(data.invites, now()));
144
+ setSection(els.list, 'list', renderPairings(data.pairings));
145
+ const n = pendingCount(data);
146
+ if (els.tabButton) {
147
+ const base = els.tabButton.dataset.baseLabel || (els.tabButton.dataset.baseLabel = els.tabButton.textContent);
148
+ els.tabButton.textContent = n > 0 ? `${base} (${n})` : base;
149
+ }
150
+ // The tabs sit behind the header menu button, so the count must also show THERE or nobody
151
+ // sees it (found walking the flow as a person, 2026-10-14).
152
+ if (els.navToggle) {
153
+ let badge = els.navToggle.querySelector('[data-pair-badge]');
154
+ if (!badge && n > 0) {
155
+ badge = doc.createElement('span');
156
+ badge.setAttribute('data-pair-badge', '');
157
+ badge.className = 'pair-nav-badge';
158
+ els.navToggle.appendChild(badge);
159
+ }
160
+ if (badge) {
161
+ // On a phone only the number shows (the word would push the header past the screen).
162
+ badge.innerHTML = n > 0 ? `${n}<span class="pair-nav-badge-word"> waiting</span>` : '';
163
+ badge.setAttribute('aria-label', n > 0 ? `${n} pairing decision${n === 1 ? '' : 's'} waiting` : '');
164
+ badge.style.display = n > 0 ? '' : 'none';
165
+ }
166
+ }
167
+ showPinRow();
168
+ }
169
+
170
+ async function load() {
106
171
  try {
107
- const { status, body } = await fetchJson('/threadline/pairing');
108
- if (status === 503) { els.list.innerHTML = '<div class="mnd-hint">Verified pairing is turned off on this agent.</div>'; return; }
109
- if (status !== 200) { note(`Could not load pairings: ${body?.error ?? `HTTP ${status}`}`, 'error'); return; }
110
- els.list.innerHTML = renderPairings(body?.pairings ?? []);
172
+ const [inv, pr] = await Promise.all([fetchJson('/threadline/pairing/invites'), fetchJson('/threadline/pairing')]);
173
+ if (inv.status === 503 || pr.status === 503) {
174
+ const off = /not enabled/.test(String(inv.body?.error ?? pr.body?.error ?? ''));
175
+ setSection(els.list, 'list', off
176
+ ? '<div class="mnd-hint">Pairing is turned off on this agent.</div>'
177
+ : '<div class="mnd-hint">Pairing runs on the machine that holds this agent\'s Threadline identity. Open the dashboard on that machine.</div>');
178
+ return;
179
+ }
180
+ if (inv.status !== 200) { note(`Could not load invites: ${inv.body?.error ?? `HTTP ${inv.status}`}`, 'error'); return; }
181
+ data = { invites: inv.body?.invites ?? [], pendingAccepts: inv.body?.pendingAccepts ?? [], pairings: pr.status === 200 ? (pr.body?.pairings ?? []) : data.pairings };
182
+ renderForChoices(inv.body?.knownPeers ?? []);
183
+ render();
111
184
  } catch (e) {
112
185
  note(`Could not reach the agent: ${e?.message ?? e}`, 'error');
113
186
  }
114
187
  }
115
188
 
116
- async function post(url, payload) {
117
- return fetchJson(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
189
+ /** The PIN for one action: the held one, or the field's (asked once). */
190
+ function pinForAction() {
191
+ if (heldPin) return heldPin;
192
+ const v = els.pin ? String(els.pin.value || '') : '';
193
+ return v;
194
+ }
195
+ /** After a PIN-gated call: keep an accepted PIN, forget a refused one. */
196
+ function settlePin(pin, status) {
197
+ if (status === 403 || status === 429) { heldPin = ''; if (els.pin) els.pin.value = ''; showPinRow(); return; }
198
+ if (status < 500 && status !== 401) { heldPin = pin; if (els.pin) els.pin.value = ''; showPinRow(); }
199
+ }
200
+
201
+ async function pinned(url, okMsg) {
202
+ const pin = pinForAction();
203
+ if (!pin) { note('Enter your PIN first (you will only be asked once).', 'error'); if (els.pin) els.pin.focus?.(); return; }
204
+ const { status, body } = await post(url, { pin });
205
+ settlePin(pin, status);
206
+ if (status === 403) { note('That PIN was not accepted.', 'error'); return; }
207
+ if (status !== 200) { note(`That didn't work: ${body?.error ?? `HTTP ${status}`}`, 'error'); return; }
208
+ note(okMsg, 'success');
209
+ await load();
210
+ }
211
+
212
+ // "Who is it for?": a known agent binds the invite to it (spec §4.1: the default whenever the
213
+ // inviter starts from a peer it already knows). Rebuilt only when the list changes, and the
214
+ // operator's current choice is kept.
215
+ function renderForChoices(peers) {
216
+ if (!els.forPeer) return;
217
+ const key = JSON.stringify(peers.map((p) => [p.fp, p.name]));
218
+ if (lastHtml.get('for') === key) return;
219
+ const keep = els.forPeer.value;
220
+ lastHtml.set('for', key);
221
+ const opt = (fpValue, label) => { const o = doc.createElement('option'); o.value = fpValue; o.textContent = label; return o; };
222
+ els.forPeer.replaceChildren(opt('', 'Someone new'), ...peers.map((p) => opt(p.fp, `${p.name || 'Unknown agent'} (${p.fp.slice(0, 6)}…)`)));
223
+ if (peers.some((p) => p.fp === keep)) els.forPeer.value = keep;
224
+ }
225
+
226
+ async function makeInvite() {
227
+ const ttlMinutes = els.ttl ? Number(els.ttl.value) || 60 : 60;
228
+ const bindFp = els.forPeer ? String(els.forPeer.value || '') : '';
229
+ const { status, body } = await post('/threadline/pairing/invites', bindFp ? { ttlMinutes, bindFp } : { ttlMinutes });
230
+ if (status !== 200 || !body?.message) { note(`Could not make an invite: ${body?.error ?? `HTTP ${status}`}`, 'error'); return; }
231
+ // Shown until the operator closes it; no timer touches this box.
232
+ els.inviteOut.innerHTML = `<div class="pair-step-title">Send this to the other person, privately, in any app. It works once.</div>
233
+ <textarea class="pair-invite-text" readonly rows="4" data-invite-text>${esc(body.message)}</textarea>
234
+ <div class="pair-row"><button class="mnd-btn mnd-btn-primary" data-copy-invite>Copy</button>
235
+ <button class="mnd-btn mnd-btn-quiet" data-close-invite>Done</button></div>
236
+ <div class="mnd-hint">Expires in ${esc(timeLeft(body.exp, now()))}. When they accept, you will see it at the top of this card.</div>`;
237
+ await load();
238
+ }
239
+
240
+ async function copyInvite() {
241
+ const ta = els.inviteOut.querySelector('[data-invite-text]');
242
+ if (!ta) return;
243
+ try {
244
+ await doc.defaultView.navigator.clipboard.writeText(ta.value);
245
+ note('Copied. Paste it to the other person.', 'success');
246
+ } catch {
247
+ ta.select?.();
248
+ note('Select the text and copy it yourself (your browser blocked copying).', 'info');
249
+ }
250
+ }
251
+
252
+ async function inspect() {
253
+ const text = els.inviteIn ? String(els.inviteIn.value || '') : '';
254
+ if (!text.includes('tlinv1:')) { note('Paste the whole invite you were sent.', 'error'); return; }
255
+ const { status, body } = await post('/threadline/pairing/invites/inspect', { invite: text });
256
+ if (status !== 200) { note(`I can't use that invite: ${body?.error ?? `HTTP ${status}`}`, 'error'); return; }
257
+ els.inviteIn.value = ''; // the secret leaves the page; the decision now waits at the top
258
+ note('Got it. Approve or decline it at the top of this card.', 'info');
259
+ await load();
118
260
  }
119
261
 
120
262
  async function onClick(ev) {
121
263
  const t = ev.target;
122
264
  if (!t || !t.dataset) return;
123
- const root = els.list;
124
- const take = (attr, fp) => { const el = root.querySelector(`[${attr}="${fp}"]`); const v = el ? el.value : ''; if (el) el.value = ''; return v; };
265
+ const d = t.dataset;
125
266
  try {
126
- if (t.dataset.showWords) {
127
- const fp = t.dataset.showWords;
128
- const pin = take('data-show-pin', fp);
129
- if (!pin) { note('Enter your PIN to see the words.', 'error'); return; }
130
- const res = await fetchImpl(`/threadline/pairing/${fp}`, { headers: { 'X-Dashboard-Pin': pin } });
131
- const body = await res.json().catch(() => null);
132
- const words = body?.pairing?.sasWords;
133
- if (!Array.isArray(words)) { note(res.status === 200 ? 'The PIN was not accepted, or this pairing is no longer waiting.' : `Refused: ${body?.error ?? `HTTP ${res.status}`}`, 'error'); return; }
134
- const ol = root.querySelector(`[data-own-words="${fp}"]`);
135
- if (ol) ol.innerHTML = words.map((w) => `<li>${esc(w)}</li>`).join('');
136
- note('These are the words on this side. Read them to the other operator and type theirs below.', 'info');
137
- } else if (t.dataset.confirm) {
138
- const fp = t.dataset.confirm;
139
- const words = collectTypedWords(root, fp);
140
- const pin = take('data-confirm-pin', fp);
141
- if (words.some((w) => !w)) { note(`Type all ${WORD_COUNT} of the other side's words first.`, 'error'); return; }
142
- if (!pin) { note('Enter your PIN to confirm.', 'error'); return; }
143
- const { status, body } = await post(`/threadline/pairing/${fp}/verify`, { words, pin });
144
- if (status !== 200) { note(`Not confirmed — the server refused: ${body?.error ?? `HTTP ${status}`}`, 'error'); return; }
145
- note(body?.state === 'mutual-verified' ? 'Verified. Every word matched.' : 'The words did not match. This pairing is now blocked.', body?.state === 'mutual-verified' ? 'success' : 'error');
146
- await load();
147
- } else if (t.dataset.deny) {
148
- const fp = t.dataset.deny;
149
- const pin = take('data-confirm-pin', fp);
150
- if (!pin) { note('Enter your PIN (in the field above) to record that the words did not match.', 'error'); return; }
151
- const { status, body } = await post(`/threadline/pairing/${fp}/verify`, { match: false, pin });
152
- if (status !== 200) { note(`Not recorded — the server refused: ${body?.error ?? `HTTP ${status}`}`, 'error'); return; }
153
- note('Recorded: the words did not match. This pairing is blocked.', 'success');
267
+ if (d.makeInvite !== undefined) await makeInvite();
268
+ else if (d.copyInvite !== undefined) await copyInvite();
269
+ else if (d.closeInvite !== undefined) els.inviteOut.innerHTML = '';
270
+ else if (d.inspectInvite !== undefined) await inspect();
271
+ else if (d.approve) await pinned(`/threadline/pairing/invites/${d.approve}/approve`, 'Approved. Waiting for the other side to finish.');
272
+ else if (d.finish) await pinned(`/threadline/pairing/invites/${d.finish}/finish`, 'Finished. You are paired.');
273
+ else if (d.notThem) await pinned(`/threadline/pairing/invites/${d.notThem}/not-them`, 'Not paired. That invite is used up.');
274
+ else if (d.decline) {
275
+ const { status, body } = await post(`/threadline/pairing/invites/${d.decline}/decline`, {});
276
+ note(status === 200 ? 'Declined. Nothing was paired.' : `That didn't work: ${body?.error ?? `HTTP ${status}`}`, status === 200 ? 'success' : 'error');
154
277
  await load();
155
- } else if (t.dataset.clear) {
156
- const fp = t.dataset.clear;
157
- const pin = take('data-clear-pin', fp);
158
- if (!pin) { note('Enter your PIN to clear this pairing.', 'error'); return; }
159
- const { status, body } = await post(`/threadline/pairing/${fp}/start`, { clearFailed: true, pin });
160
- if (status !== 200) { note(`Not cleared — the server refused: ${body?.error ?? `HTTP ${status}`}`, 'error'); return; }
161
- note('Cleared. Compare the words again.', 'success');
278
+ } else if (d.revoke) {
279
+ const { status, body } = await post(`/threadline/pairing/invites/${d.revoke}`, {}, 'DELETE');
280
+ note(status === 200 ? 'Cancelled. That invite no longer works.' : `That didn't work: ${body?.error ?? `HTTP ${status}`}`, status === 200 ? 'success' : 'error');
162
281
  await load();
163
282
  }
164
283
  } catch (e) {
@@ -168,12 +287,13 @@ export function createController({ doc, els, fetchImpl }) {
168
287
 
169
288
  return {
170
289
  start() {
171
- if (els.list && !els.list.__pairWired) { els.list.addEventListener('click', onClick); els.list.__pairWired = true; }
290
+ if (els.root && !els.root.__pairWired) { els.root.addEventListener('click', onClick); els.root.__pairWired = true; }
172
291
  load();
173
- if (!timer) timer = setInterval(() => load({ timed: true }), REFRESH_MS);
292
+ if (!timer) timer = setInterval(load, POLL_MS);
174
293
  },
175
294
  stop() { if (timer) { clearInterval(timer); timer = null; } },
176
295
  load,
177
296
  onClick,
297
+ hasPin: () => Boolean(heldPin),
178
298
  };
179
299
  }
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"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"}