@agentproto/apps 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/bin/sync.d.ts.map +1 -1
- package/dist/bin/sync.mjs +1288 -84
- package/dist/bin/sync.mjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +1287 -84
- package/dist/index.mjs.map +1 -1
- package/dist/mail-triage/agents/triager.d.ts.map +1 -1
- package/dist/mail-triage/ui.d.ts +14 -3
- package/dist/mail-triage/ui.d.ts.map +1 -1
- package/dist/mail-triage/workflows/triage-inbox.d.ts.map +1 -1
- package/dist/mail-triage.mjs +760 -83
- package/dist/mail-triage.mjs.map +1 -1
- package/dist/session-viewer/agents/narrator.d.ts +3 -0
- package/dist/session-viewer/agents/narrator.d.ts.map +1 -0
- package/dist/session-viewer/index.d.ts +18 -0
- package/dist/session-viewer/index.d.ts.map +1 -0
- package/dist/session-viewer/ui.d.ts +17 -0
- package/dist/session-viewer/ui.d.ts.map +1 -0
- package/dist/session-viewer/workflows/narrate-session.d.ts +2 -0
- package/dist/session-viewer/workflows/narrate-session.d.ts.map +1 -0
- package/dist/session-viewer.mjs +539 -0
- package/dist/session-viewer.mjs.map +1 -0
- package/package.json +8 -3
package/dist/bin/sync.mjs
CHANGED
|
@@ -199,7 +199,7 @@ var triager = {
|
|
|
199
199
|
],
|
|
200
200
|
workflows: [{ ref: "triage-inbox" }]
|
|
201
201
|
}),
|
|
202
|
-
body:
|
|
202
|
+
body: 'You start by calling mailbox_list to discover connected mailboxes. Pick one with `capabilities.triage: true` (triage requires the gmail.modify scope). Every subsequent mailbox tool requires the `mailbox` parameter (UUID or alias from this list).\n\nScan the mailbox for unread messages using mailbox_search with query="is:unread" and the mailbox from step 1. Categorize them (urgent, needs-reply, newsletter, notification, spam). Create one triage plan per bulk action with mailbox_triage_plan \u2014 its exact contract is `{ mailbox, criteria: { query } | { messageIds }, action: { type: "markRead"|"archive"|"trash"|"label", addLabelIds?, removeLabelIds? } }` \u2014 e.g. label each category, archive newsletters+notifications, keep urgent+needs-reply in inbox. Each plan returns a `plan_id`, a `count`, and a `sample`; it expires after 15 minutes. Show the plan before applying. Execute only after user confirmation, with mailbox_triage_apply `{ plan_id, confirm: true }` \u2014 that is the sole mutation path.'
|
|
203
203
|
};
|
|
204
204
|
var triageInbox = defineWorkflow({
|
|
205
205
|
id: "triage-inbox",
|
|
@@ -213,20 +213,33 @@ var triageInbox = defineWorkflow({
|
|
|
213
213
|
id: "triage",
|
|
214
214
|
kind: "agent",
|
|
215
215
|
agent: { ref: "@agentproto/triager" },
|
|
216
|
-
prompt:
|
|
216
|
+
prompt: 'Call mailbox_list to discover connected mailboxes, then scan the selected mailbox for unread messages using mailbox_search with query="is:unread". Categorize them (urgent, needs-reply, newsletter, notification, spam). Create a triage plan: label each category, archive newsletters+notifications, keep urgent+needs-reply in inbox. Show me the plan before applying.'
|
|
217
217
|
}
|
|
218
218
|
]
|
|
219
219
|
});
|
|
220
220
|
|
|
221
221
|
// src/mail-triage/ui.ts
|
|
222
|
+
var ALIAS_TOOL_NAMES = [
|
|
223
|
+
"mailbox_list",
|
|
224
|
+
"mailbox_search",
|
|
225
|
+
"mailbox_triage_plan",
|
|
226
|
+
"mailbox_triage_apply"
|
|
227
|
+
];
|
|
228
|
+
var DEFAULT_MCP_ALIASES = ["agentpush-prod", "agentpush"];
|
|
229
|
+
function envAliases() {
|
|
230
|
+
if (typeof process === "undefined" || !process.env) return null;
|
|
231
|
+
const raw = process.env["MAIL_TRIAGE_MCP_ALIASES"];
|
|
232
|
+
if (!raw) return null;
|
|
233
|
+
const list = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
234
|
+
return list.length > 0 ? list : null;
|
|
235
|
+
}
|
|
236
|
+
var MAIL_TRIAGE_MCP_ALIASES = envAliases() ?? [...DEFAULT_MCP_ALIASES];
|
|
222
237
|
var MAIL_TRIAGE_TOOLS = [
|
|
223
|
-
|
|
224
|
-
"imported:agentpush/mailbox_search",
|
|
225
|
-
"imported:agentpush/mailbox_triage_plan",
|
|
226
|
-
"imported:agentpush/mailbox_triage_apply",
|
|
238
|
+
...MAIL_TRIAGE_MCP_ALIASES.flatMap((alias) => ALIAS_TOOL_NAMES.map((t) => `imported:${alias}/${t}`)),
|
|
227
239
|
"app_run",
|
|
228
240
|
"app_status",
|
|
229
|
-
"agent_output"
|
|
241
|
+
"agent_output",
|
|
242
|
+
"app_list"
|
|
230
243
|
];
|
|
231
244
|
var APP_ID = "@agentproto/mail-triage";
|
|
232
245
|
var AGENT_ID = "@agentproto/triager";
|
|
@@ -246,7 +259,7 @@ var MAIL_TRIAGE_HTML = `<!DOCTYPE html>
|
|
|
246
259
|
}
|
|
247
260
|
html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:13px;background:var(--bg);color:var(--text)}
|
|
248
261
|
#app{display:flex;flex-direction:column;height:100%;overflow-y:auto}
|
|
249
|
-
#toolbar{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;background:var(--bg2);flex-shrink:0}
|
|
262
|
+
#toolbar{padding:10px 14px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px;background:var(--bg2);flex-shrink:0;flex-wrap:wrap}
|
|
250
263
|
#toolbar .title{font-size:13px;font-weight:600;flex:1}
|
|
251
264
|
select,input[type=text]{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:5px 8px;border-radius:4px;font-size:12px;font-family:inherit}
|
|
252
265
|
.abtn{background:var(--bg3);border:1px solid var(--border);color:var(--text);padding:5px 10px;border-radius:4px;cursor:pointer;font-size:12px;font-family:inherit}
|
|
@@ -254,37 +267,68 @@ select,input[type=text]{background:var(--bg3);border:1px solid var(--border);col
|
|
|
254
267
|
.abtn:disabled{opacity:.4;cursor:default}
|
|
255
268
|
.abtn.primary{background:var(--blue);border-color:var(--blue);color:#04101c}
|
|
256
269
|
.abtn.primary:hover{opacity:.9}
|
|
270
|
+
#caps{padding:6px 14px;display:flex;gap:6px;align-items:center;background:var(--bg2);border-bottom:1px solid var(--border);flex-shrink:0}
|
|
271
|
+
.badge{font-size:11px;padding:2px 8px;border-radius:10px;border:1px solid var(--border);color:var(--text2)}
|
|
272
|
+
.badge.on{color:var(--green);border-color:var(--green)}
|
|
273
|
+
.badge.off{opacity:.5;text-decoration:line-through}
|
|
257
274
|
#content{padding:14px;display:flex;flex-direction:column;gap:16px}
|
|
258
275
|
section{background:var(--bg2);border:1px solid var(--border);border-radius:6px;padding:12px}
|
|
259
|
-
section h3{font-size:11px;color:var(--text2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:10px}
|
|
276
|
+
section h3{font-size:11px;color:var(--text2);text-transform:uppercase;letter-spacing:.04em;margin-bottom:10px;display:flex;justify-content:space-between;align-items:center}
|
|
260
277
|
#summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px}
|
|
261
278
|
.tile{background:var(--bg3);border:1px solid var(--border);border-radius:6px;padding:10px;text-align:center}
|
|
262
279
|
.tile .count{font-size:22px;font-weight:700}
|
|
263
280
|
.tile .label{font-size:11px;color:var(--text2);text-transform:capitalize}
|
|
281
|
+
.tile.unread .count{color:var(--green)}
|
|
264
282
|
.tile.urgent .count{color:var(--red)}
|
|
265
283
|
.tile.needs-reply .count{color:var(--yellow)}
|
|
266
284
|
.tile.newsletter .count{color:var(--blue)}
|
|
267
285
|
.tile.notification .count{color:var(--purple)}
|
|
268
|
-
#plan-list,#search-results,#log{max-height:260px;overflow-y:auto;font-size:12px}
|
|
286
|
+
#plan-list,#search-results,#log-events,#runs-list,#connect-hint-list{max-height:260px;overflow-y:auto;font-size:12px}
|
|
269
287
|
.plan-item{display:flex;justify-content:space-between;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)}
|
|
270
288
|
.plan-item .subj{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
271
289
|
.plan-item .tag{color:var(--text2);font-size:11px;white-space:nowrap}
|
|
272
|
-
|
|
290
|
+
.runs-item{display:flex;gap:10px;align-items:center;padding:6px 0;border-bottom:1px solid var(--border);cursor:pointer}
|
|
291
|
+
.runs-item:hover{background:var(--bg3)}
|
|
292
|
+
.runs-item .rid{font-family:Menlo,Monaco,monospace;font-size:11px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
|
|
293
|
+
.runs-item .st{font-size:11px;white-space:nowrap}
|
|
294
|
+
.runs-item .st.running{color:var(--yellow)}
|
|
295
|
+
.runs-item .st.ended{color:var(--green)}
|
|
296
|
+
.runs-item .meta{color:var(--text2);font-size:11px;white-space:nowrap}
|
|
297
|
+
#log-tail{font-family:Menlo,Monaco,monospace;white-space:pre-wrap;word-break:break-word;background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:8px;max-height:260px;overflow-y:auto}
|
|
273
298
|
.searchbar{display:flex;gap:8px;margin-bottom:8px}
|
|
274
299
|
.searchbar input{flex:1}
|
|
275
300
|
.actions{display:flex;gap:8px;margin-top:10px}
|
|
276
301
|
#status{padding:6px 14px;font-size:11px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0}
|
|
277
302
|
.empty{color:var(--text2);padding:8px 0}
|
|
303
|
+
.mini{font-size:10px;text-transform:none;letter-spacing:0;cursor:pointer;color:var(--blue);background:none;border:none;font-family:inherit}
|
|
304
|
+
.caps-hint{font-size:11px;color:var(--yellow)}
|
|
305
|
+
.snippet{color:var(--text2);font-size:11px;margin-top:2px}
|
|
306
|
+
.mail-checkbox{accent-color:var(--blue);cursor:pointer;flex-shrink:0;margin-top:1px}
|
|
307
|
+
.mail-open{font-size:10px;cursor:pointer;color:var(--blue);background:none;border:none;font-family:inherit;white-space:nowrap;padding:0;flex-shrink:0}
|
|
308
|
+
.mail-open:hover{text-decoration:underline}
|
|
309
|
+
#selection-bar{display:none;margin-bottom:8px;gap:8px;align-items:center}
|
|
310
|
+
#selection-bar.show{display:flex}
|
|
278
311
|
</style>
|
|
279
312
|
</head>
|
|
280
313
|
<body>
|
|
281
314
|
<div id="app">
|
|
282
315
|
<div id="toolbar">
|
|
283
316
|
<span class="title">Mail Triage</span>
|
|
317
|
+
<select id="alias-select" style="display:none" title="MCP server"></select>
|
|
284
318
|
<select id="mailbox-select"></select>
|
|
285
319
|
<button class="abtn" id="refresh-btn">Refresh</button>
|
|
286
320
|
</div>
|
|
321
|
+
<div id="caps" style="display:none"></div>
|
|
287
322
|
<div id="content">
|
|
323
|
+
<section id="connect-hint" style="display:none">
|
|
324
|
+
<h3>Connect a mailbox</h3>
|
|
325
|
+
<div id="connect-hint-list"></div>
|
|
326
|
+
<div class="empty">Import/start an agentpush MCP server and connect a Gmail account in agentpush, then retry.</div>
|
|
327
|
+
<div class="actions">
|
|
328
|
+
<button class="abtn primary" id="connect-retry-btn">Retry</button>
|
|
329
|
+
</div>
|
|
330
|
+
</section>
|
|
331
|
+
|
|
288
332
|
<section>
|
|
289
333
|
<h3>Inbox summary</h3>
|
|
290
334
|
<div id="summary"></div>
|
|
@@ -296,16 +340,30 @@ section h3{font-size:11px;color:var(--text2);text-transform:uppercase;letter-spa
|
|
|
296
340
|
<input type="text" id="search-input" placeholder="e.g. is:unread">
|
|
297
341
|
<button class="abtn" id="search-btn">Search</button>
|
|
298
342
|
</div>
|
|
343
|
+
<div id="selection-bar">
|
|
344
|
+
<span id="sel-count" style="font-size:12px;color:var(--text2)">0 selected</span>
|
|
345
|
+
<button class="abtn primary" data-action="send-selection">Send selection to Claude</button>
|
|
346
|
+
</div>
|
|
299
347
|
<div id="search-results"><div class="empty">No search run yet.</div></div>
|
|
300
348
|
</section>
|
|
301
349
|
|
|
302
350
|
<section>
|
|
303
351
|
<h3>Triage plan</h3>
|
|
352
|
+
<div class="searchbar">
|
|
353
|
+
<input type="text" id="plan-query" placeholder="criteria query" value="is:unread category:promotions older_than:7d">
|
|
354
|
+
<select id="plan-action">
|
|
355
|
+
<option value="markRead">mark read</option>
|
|
356
|
+
<option value="archive">archive</option>
|
|
357
|
+
<option value="label">label…</option>
|
|
358
|
+
<option value="trash">trash</option>
|
|
359
|
+
</select>
|
|
360
|
+
<input type="text" id="plan-label" placeholder="label name" style="display:none">
|
|
361
|
+
</div>
|
|
304
362
|
<div class="actions">
|
|
305
|
-
<button class="abtn primary" id="triage-btn">
|
|
363
|
+
<button class="abtn primary" id="triage-btn">Build plan</button>
|
|
306
364
|
<button class="abtn" id="apply-btn" disabled>Apply</button>
|
|
307
365
|
</div>
|
|
308
|
-
<div id="plan-list" style="margin-top:10px"><div class="empty">No plan yet \u2014 click
|
|
366
|
+
<div id="plan-list" style="margin-top:10px"><div class="empty">No plan yet \u2014 click Build plan.</div></div>
|
|
309
367
|
</section>
|
|
310
368
|
|
|
311
369
|
<section>
|
|
@@ -313,7 +371,13 @@ section h3{font-size:11px;color:var(--text2);text-transform:uppercase;letter-spa
|
|
|
313
371
|
<div class="actions">
|
|
314
372
|
<button class="abtn" id="run-agent-btn">Run Triager Agent</button>
|
|
315
373
|
</div>
|
|
316
|
-
<div id="log" style="margin-top:10px"><div class="empty">Agent has not run yet.</div></div>
|
|
374
|
+
<div id="log-events" style="margin-top:10px"><div class="empty">Agent has not run yet.</div></div>
|
|
375
|
+
<div id="log-tail" style="margin-top:6px"></div>
|
|
376
|
+
</section>
|
|
377
|
+
|
|
378
|
+
<section>
|
|
379
|
+
<h3><span>Past runs</span><button class="mini" id="runs-refresh-btn">refresh</button></h3>
|
|
380
|
+
<div id="runs-list"><div class="empty">No runs yet.</div></div>
|
|
317
381
|
</section>
|
|
318
382
|
</div>
|
|
319
383
|
<div id="status">Connecting…</div>
|
|
@@ -322,9 +386,21 @@ section h3{font-size:11px;color:var(--text2);text-transform:uppercase;letter-spa
|
|
|
322
386
|
var APP_ID = ${JSON.stringify(APP_ID)};
|
|
323
387
|
var AGENT_ID = ${JSON.stringify(AGENT_ID)};
|
|
324
388
|
var CATEGORIES = ${JSON.stringify(CATEGORIES)};
|
|
389
|
+
var ALIASES = ${JSON.stringify(MAIL_TRIAGE_MCP_ALIASES)};
|
|
390
|
+
var POLL_TIMEOUT_MS = 10 * 60 * 1000;
|
|
325
391
|
var callTool = null;
|
|
392
|
+
var updateModelContext = null;
|
|
393
|
+
var openLink = null;
|
|
394
|
+
var onTeardown = null;
|
|
326
395
|
var currentPlan = null;
|
|
327
396
|
var pollHandle = null;
|
|
397
|
+
var planExpiryHandle = null;
|
|
398
|
+
var activeAlias = ALIASES[0];
|
|
399
|
+
var currentMailboxes = [];
|
|
400
|
+
var categoryCounts = null;
|
|
401
|
+
var unreadCount = null;
|
|
402
|
+
var aliasStates = {};
|
|
403
|
+
var selectedMessages = {};
|
|
328
404
|
|
|
329
405
|
function setStatus(msg) {
|
|
330
406
|
document.getElementById("status").textContent = msg;
|
|
@@ -354,7 +430,125 @@ function unwrapText(result) {
|
|
|
354
430
|
}
|
|
355
431
|
|
|
356
432
|
function callApp(tool, args) {
|
|
357
|
-
|
|
433
|
+
if (!callTool) {
|
|
434
|
+
return Promise.reject(new Error("Not connected to host bridge (standalone mode)."));
|
|
435
|
+
}
|
|
436
|
+
return callTool("app_tool_call", { appId: APP_ID, tool: tool, args: args || {} }).then(function (result) {
|
|
437
|
+
var value = unwrapText(result);
|
|
438
|
+
if (value && typeof value === "object" && !Array.isArray(value) && typeof value.error === "string") {
|
|
439
|
+
throw new Error(value.error);
|
|
440
|
+
}
|
|
441
|
+
return value;
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function aliasTool(name) {
|
|
446
|
+
return "imported:" + activeAlias + "/" + name;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Disables a button and swaps its label to an ellipsis while a call is in
|
|
450
|
+
// flight, so double-submit is impossible; restores the original label after.
|
|
451
|
+
function setBusy(btn, busy) {
|
|
452
|
+
if (busy) {
|
|
453
|
+
if (btn.getAttribute("data-label") === null) btn.setAttribute("data-label", btn.textContent);
|
|
454
|
+
btn.disabled = true;
|
|
455
|
+
btn.textContent = "\\u2026";
|
|
456
|
+
} else {
|
|
457
|
+
btn.disabled = false;
|
|
458
|
+
var label = btn.getAttribute("data-label");
|
|
459
|
+
if (label !== null) btn.textContent = label;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function isSelected(id) {
|
|
464
|
+
return selectedMessages[id] ? true : false;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function toggleSelection(id, item) {
|
|
468
|
+
if (selectedMessages[id]) {
|
|
469
|
+
delete selectedMessages[id];
|
|
470
|
+
} else {
|
|
471
|
+
selectedMessages[id] = item;
|
|
472
|
+
}
|
|
473
|
+
updateSelectionBar();
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function updateSelectionBar() {
|
|
477
|
+
var bar = document.getElementById("selection-bar");
|
|
478
|
+
var countEl = document.getElementById("sel-count");
|
|
479
|
+
var count = 0;
|
|
480
|
+
for (var k in selectedMessages) { if (selectedMessages.hasOwnProperty(k)) count++; }
|
|
481
|
+
countEl.textContent = count + " selected";
|
|
482
|
+
bar.className = count > 0 ? "show" : "";
|
|
483
|
+
// Sync checkboxes
|
|
484
|
+
var cbs = document.querySelectorAll("#search-results .mail-checkbox");
|
|
485
|
+
for (var i = 0; i < cbs.length; i++) {
|
|
486
|
+
var mid = cbs[i].getAttribute("data-message-id");
|
|
487
|
+
cbs[i].checked = mid ? isSelected(mid) : false;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function sendSelectionToClaude() {
|
|
492
|
+
if (!updateModelContext) {
|
|
493
|
+
setStatus("Host does not support updateModelContext.");
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
var items = [];
|
|
497
|
+
var names = [];
|
|
498
|
+
for (var k in selectedMessages) {
|
|
499
|
+
if (!selectedMessages.hasOwnProperty(k)) continue;
|
|
500
|
+
var msg = selectedMessages[k];
|
|
501
|
+
items.push({
|
|
502
|
+
id: msg.id || k,
|
|
503
|
+
threadId: msg.threadId || "",
|
|
504
|
+
from: msg.from || "",
|
|
505
|
+
subject: msg.subject || "",
|
|
506
|
+
timestamp: msg.internalDate || msg.date || ""
|
|
507
|
+
});
|
|
508
|
+
if (msg.subject) names.push(msg.subject);
|
|
509
|
+
}
|
|
510
|
+
if (items.length === 0) {
|
|
511
|
+
setStatus("No messages selected.");
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
var summary = items.length + " emails selected for triage: " + names.join(", ");
|
|
515
|
+
setStatus("Sending selection to Claude\\u2026");
|
|
516
|
+
updateModelContext({
|
|
517
|
+
content: [{ type: "text", text: summary }],
|
|
518
|
+
structuredContent: { selection: items }
|
|
519
|
+
}).then(function () {
|
|
520
|
+
setStatus("Selection sent to Claude (" + items.length + " emails).");
|
|
521
|
+
}).catch(function (err) {
|
|
522
|
+
setStatus("Failed to send selection: " + (err && err.message ? err.message : String(err)));
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function clearPlanExpiry() {
|
|
527
|
+
if (planExpiryHandle) {
|
|
528
|
+
clearTimeout(planExpiryHandle);
|
|
529
|
+
planExpiryHandle = null;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function onPlanExpired() {
|
|
534
|
+
planExpiryHandle = null;
|
|
535
|
+
currentPlan = null;
|
|
536
|
+
document.getElementById("apply-btn").disabled = true;
|
|
537
|
+
setStatus("Plan expired \\u2014 rebuild it.");
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Arms a timeout that invalidates the plan at its server-declared expires_at
|
|
541
|
+
// (plans expire after 15 minutes) so Apply never fires against a stale plan.
|
|
542
|
+
function armPlanExpiry(expiresAt) {
|
|
543
|
+
clearPlanExpiry();
|
|
544
|
+
if (!expiresAt) return;
|
|
545
|
+
var ms = new Date(expiresAt).getTime() - Date.now();
|
|
546
|
+
if (isNaN(ms)) return;
|
|
547
|
+
if (ms <= 0) {
|
|
548
|
+
onPlanExpired();
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
planExpiryHandle = setTimeout(onPlanExpired, ms);
|
|
358
552
|
}
|
|
359
553
|
|
|
360
554
|
function asList(value, keys) {
|
|
@@ -367,145 +561,593 @@ function asList(value, keys) {
|
|
|
367
561
|
return [];
|
|
368
562
|
}
|
|
369
563
|
|
|
370
|
-
function renderSummary(
|
|
564
|
+
function renderSummary() {
|
|
371
565
|
var el = document.getElementById("summary");
|
|
372
566
|
el.innerHTML = "";
|
|
373
|
-
|
|
567
|
+
var tiles = [{ key: "unread", count: unreadCount }].concat(
|
|
568
|
+
CATEGORIES.map(function (cat) {
|
|
569
|
+
return { key: cat, count: categoryCounts ? categoryCounts[cat] : undefined };
|
|
570
|
+
})
|
|
571
|
+
);
|
|
572
|
+
tiles.forEach(function (t) {
|
|
374
573
|
var tile = document.createElement("div");
|
|
375
|
-
tile.className = "tile " +
|
|
376
|
-
var count = counts && counts[cat] !== undefined ? counts[cat] : "\u2013";
|
|
574
|
+
tile.className = "tile " + t.key;
|
|
377
575
|
tile.innerHTML = '<div class="count"></div><div class="label"></div>';
|
|
378
|
-
tile.querySelector(".count").textContent = String(count);
|
|
379
|
-
tile.querySelector(".label").textContent =
|
|
576
|
+
tile.querySelector(".count").textContent = t.count === undefined || t.count === null ? "\\u2013" : String(t.count);
|
|
577
|
+
tile.querySelector(".label").textContent = t.key.replace("-", " ");
|
|
380
578
|
el.appendChild(tile);
|
|
381
579
|
});
|
|
382
580
|
}
|
|
383
581
|
|
|
384
|
-
function
|
|
385
|
-
|
|
386
|
-
return
|
|
582
|
+
function mailboxId(mb) {
|
|
583
|
+
if (typeof mb === "string") return mb;
|
|
584
|
+
return mb.id || mb.name || JSON.stringify(mb);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function mailboxText(mb) {
|
|
588
|
+
if (typeof mb === "string") return mb;
|
|
589
|
+
var name = mb.address || mb.label || mb.name || mb.id || JSON.stringify(mb);
|
|
590
|
+
var caps = mb.capabilities;
|
|
591
|
+
if (caps && typeof caps === "object") {
|
|
592
|
+
return name + (caps.triage ? " \\u2014 triage \\u2713" : " \\u2014 read-only");
|
|
593
|
+
}
|
|
594
|
+
return name;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function selectedMailbox() {
|
|
598
|
+
var id = document.getElementById("mailbox-select").value;
|
|
599
|
+
for (var i = 0; i < currentMailboxes.length; i++) {
|
|
600
|
+
if (mailboxId(currentMailboxes[i]) === id) return currentMailboxes[i];
|
|
601
|
+
}
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function renderCaps(mb) {
|
|
606
|
+
var el = document.getElementById("caps");
|
|
607
|
+
var caps = mb && typeof mb === "object" ? mb.capabilities : null;
|
|
608
|
+
if (!caps || typeof caps !== "object") {
|
|
609
|
+
el.style.display = "none";
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
el.style.display = "flex";
|
|
613
|
+
el.innerHTML = "";
|
|
614
|
+
["read", "send", "triage", "compose"].forEach(function (cap) {
|
|
615
|
+
var b = document.createElement("span");
|
|
616
|
+
b.className = "badge " + (caps[cap] ? "on" : "off");
|
|
617
|
+
b.textContent = cap;
|
|
618
|
+
el.appendChild(b);
|
|
619
|
+
});
|
|
620
|
+
if (!caps.triage) {
|
|
621
|
+
var hint = document.createElement("span");
|
|
622
|
+
hint.className = "caps-hint";
|
|
623
|
+
hint.textContent = "triage scope missing \\u2014 grant it via agentpush (mailbox_request_elevation)";
|
|
624
|
+
el.appendChild(hint);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Gmail category labels -> the dashboard's triage buckets. IMPORTANT wins,
|
|
629
|
+
// so an important promo counts as urgent, not newsletter.
|
|
630
|
+
function categoryOf(labelIds) {
|
|
631
|
+
var l = labelIds || [];
|
|
632
|
+
if (l.indexOf("IMPORTANT") !== -1) return "urgent";
|
|
633
|
+
if (l.indexOf("CATEGORY_PROMOTIONS") !== -1) return "newsletter";
|
|
634
|
+
if (l.indexOf("CATEGORY_UPDATES") !== -1 || l.indexOf("CATEGORY_SOCIAL") !== -1 || l.indexOf("CATEGORY_FORUMS") !== -1) return "notification";
|
|
635
|
+
return "needs-reply";
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function onMailboxChange() {
|
|
639
|
+
clearPlanExpiry();
|
|
640
|
+
var mb = selectedMailbox();
|
|
641
|
+
renderCaps(mb);
|
|
642
|
+
var caps = mb && typeof mb === "object" ? mb.capabilities : null;
|
|
643
|
+
var noTriage = !!(caps && typeof caps === "object" && !caps.triage);
|
|
644
|
+
var scopeHint = noTriage ? "Mailbox lacks triage capability \\u2014 grant it via agentpush (mailbox_request_elevation)" : "";
|
|
645
|
+
document.getElementById("triage-btn").disabled = noTriage;
|
|
646
|
+
document.getElementById("triage-btn").title = scopeHint;
|
|
647
|
+
document.getElementById("apply-btn").disabled = true;
|
|
648
|
+
document.getElementById("apply-btn").title = scopeHint;
|
|
649
|
+
currentPlan = null;
|
|
650
|
+
categoryCounts = null;
|
|
651
|
+
unreadCount = null;
|
|
652
|
+
renderSummary();
|
|
653
|
+
if (!mb) return;
|
|
654
|
+
callApp(aliasTool("mailbox_search"), { mailbox: mailboxId(mb), query: "is:unread", limit: 50 })
|
|
387
655
|
.then(function (result) {
|
|
388
|
-
var
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if (mailboxes.length === 0) {
|
|
392
|
-
var opt = document.createElement("option");
|
|
393
|
-
opt.textContent = "inbox";
|
|
394
|
-
opt.value = "inbox";
|
|
395
|
-
select.appendChild(opt);
|
|
656
|
+
var items = asList(result, ["items", "messages", "results"]);
|
|
657
|
+
if (result && typeof result === "object" && typeof result.resultSizeEstimate === "number") {
|
|
658
|
+
unreadCount = result.resultSizeEstimate;
|
|
396
659
|
} else {
|
|
397
|
-
|
|
398
|
-
var name = typeof mb === "string" ? mb : mb.name || mb.id || JSON.stringify(mb);
|
|
399
|
-
var opt = document.createElement("option");
|
|
400
|
-
opt.value = name;
|
|
401
|
-
opt.textContent = name;
|
|
402
|
-
select.appendChild(opt);
|
|
403
|
-
});
|
|
660
|
+
unreadCount = items.length;
|
|
404
661
|
}
|
|
405
|
-
|
|
662
|
+
// Category breakdown over the newest page of unread \u2014 a sample when
|
|
663
|
+
// the mailbox has more unread than one page.
|
|
664
|
+
var counts = {};
|
|
665
|
+
items.forEach(function (item) {
|
|
666
|
+
var cat = categoryOf(item && item.labelIds);
|
|
667
|
+
counts[cat] = (counts[cat] || 0) + 1;
|
|
668
|
+
});
|
|
669
|
+
categoryCounts = items.length > 0 ? counts : null;
|
|
670
|
+
renderSummary();
|
|
671
|
+
})
|
|
672
|
+
.catch(function (err) {
|
|
673
|
+
// KPI is best-effort: keep the dashes, but say why instead of silence.
|
|
674
|
+
setStatus("Inbox summary unavailable: " + err.message);
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function renderMailboxes(mailboxes) {
|
|
679
|
+
currentMailboxes = mailboxes;
|
|
680
|
+
var select = document.getElementById("mailbox-select");
|
|
681
|
+
select.innerHTML = "";
|
|
682
|
+
if (mailboxes.length === 0) {
|
|
683
|
+
var opt = document.createElement("option");
|
|
684
|
+
opt.textContent = "no mailboxes";
|
|
685
|
+
opt.value = "";
|
|
686
|
+
select.appendChild(opt);
|
|
687
|
+
renderCaps(null);
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
mailboxes.forEach(function (mb) {
|
|
691
|
+
var opt = document.createElement("option");
|
|
692
|
+
opt.value = mailboxId(mb);
|
|
693
|
+
opt.textContent = mailboxText(mb);
|
|
694
|
+
select.appendChild(opt);
|
|
695
|
+
});
|
|
696
|
+
onMailboxChange();
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function renderAliasSelect(liveAliases) {
|
|
700
|
+
var select = document.getElementById("alias-select");
|
|
701
|
+
select.innerHTML = "";
|
|
702
|
+
var shown = liveAliases.length > 0 ? liveAliases : ALIASES;
|
|
703
|
+
shown.forEach(function (alias) {
|
|
704
|
+
var opt = document.createElement("option");
|
|
705
|
+
opt.value = alias;
|
|
706
|
+
opt.textContent = alias;
|
|
707
|
+
select.appendChild(opt);
|
|
708
|
+
});
|
|
709
|
+
select.value = activeAlias;
|
|
710
|
+
select.style.display = shown.length > 1 ? "" : "none";
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function loadMailboxes() {
|
|
714
|
+
setStatus("Loading mailboxes from " + activeAlias + "\\u2026");
|
|
715
|
+
return callApp(aliasTool("mailbox_list"), {})
|
|
716
|
+
.then(function (result) {
|
|
717
|
+
renderMailboxes(asList(result, ["mailboxes", "items"]));
|
|
718
|
+
setStatus("Ready \\u2014 " + activeAlias);
|
|
406
719
|
})
|
|
407
720
|
.catch(function (err) {
|
|
408
721
|
setStatus("Error loading mailboxes: " + err.message);
|
|
409
722
|
});
|
|
410
723
|
}
|
|
411
724
|
|
|
412
|
-
function
|
|
725
|
+
function renderConnectHint() {
|
|
726
|
+
var el = document.getElementById("connect-hint");
|
|
727
|
+
var anyLive = false;
|
|
728
|
+
for (var i = 0; i < ALIASES.length; i++) {
|
|
729
|
+
if (aliasStates[ALIASES[i]] && aliasStates[ALIASES[i]].state === "live") anyLive = true;
|
|
730
|
+
}
|
|
731
|
+
if (anyLive) {
|
|
732
|
+
el.style.display = "none";
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
el.style.display = "";
|
|
736
|
+
var list = document.getElementById("connect-hint-list");
|
|
737
|
+
list.innerHTML = "";
|
|
738
|
+
ALIASES.forEach(function (alias) {
|
|
739
|
+
var st = aliasStates[alias];
|
|
740
|
+
var row = document.createElement("div");
|
|
741
|
+
row.className = "plan-item";
|
|
742
|
+
var text;
|
|
743
|
+
if (!st) text = alias + " \\u2014 not probed yet";
|
|
744
|
+
else if (st.state === "unreachable") text = alias + " \\u2014 unreachable: " + st.message;
|
|
745
|
+
else if (st.state === "empty") text = alias + " \\u2014 connected, no mailboxes";
|
|
746
|
+
else text = alias + " \\u2014 live";
|
|
747
|
+
row.textContent = text;
|
|
748
|
+
list.appendChild(row);
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function probeAliases() {
|
|
753
|
+
setStatus("Detecting mail servers\\u2026");
|
|
754
|
+
return Promise.all(
|
|
755
|
+
ALIASES.map(function (alias) {
|
|
756
|
+
return callApp("imported:" + alias + "/mailbox_list", {})
|
|
757
|
+
.then(function (result) {
|
|
758
|
+
var mailboxes = asList(result, ["mailboxes", "items"]);
|
|
759
|
+
aliasStates[alias] = { state: mailboxes.length > 0 ? "live" : "empty" };
|
|
760
|
+
return { alias: alias, mailboxes: mailboxes };
|
|
761
|
+
})
|
|
762
|
+
.catch(function (err) {
|
|
763
|
+
aliasStates[alias] = { state: "unreachable", message: err && err.message ? err.message : String(err) };
|
|
764
|
+
return { alias: alias, mailboxes: [] };
|
|
765
|
+
});
|
|
766
|
+
})
|
|
767
|
+
).then(function (results) {
|
|
768
|
+
var live = results.filter(function (r) { return r.mailboxes.length > 0; });
|
|
769
|
+
if (live.length > 0) {
|
|
770
|
+
activeAlias = live[0].alias;
|
|
771
|
+
renderAliasSelect(live.map(function (r) { return r.alias; }));
|
|
772
|
+
renderMailboxes(live[0].mailboxes);
|
|
773
|
+
renderConnectHint();
|
|
774
|
+
setStatus("Ready \\u2014 " + activeAlias + (live.length > 1 ? " (" + live.length + " servers available)" : ""));
|
|
775
|
+
} else {
|
|
776
|
+
renderAliasSelect([]);
|
|
777
|
+
renderMailboxes([]);
|
|
778
|
+
renderConnectHint();
|
|
779
|
+
setStatus("No mailboxes reachable \\u2014 see connection panel below.");
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function renderSearchResults(items, raw) {
|
|
413
785
|
var el = document.getElementById("search-results");
|
|
414
786
|
el.innerHTML = "";
|
|
787
|
+
if (raw && typeof raw === "object" && typeof raw.resultSizeEstimate === "number") {
|
|
788
|
+
var count = document.createElement("div");
|
|
789
|
+
count.className = "empty";
|
|
790
|
+
count.textContent = items.length + " shown \\u00b7 ~" + raw.resultSizeEstimate + " total";
|
|
791
|
+
el.appendChild(count);
|
|
792
|
+
}
|
|
415
793
|
if (items.length === 0) {
|
|
416
|
-
|
|
794
|
+
var none = document.createElement("div");
|
|
795
|
+
none.className = "empty";
|
|
796
|
+
none.textContent = "No results.";
|
|
797
|
+
el.appendChild(none);
|
|
417
798
|
return;
|
|
418
799
|
}
|
|
419
800
|
items.forEach(function (item) {
|
|
420
801
|
var row = document.createElement("div");
|
|
421
802
|
row.className = "plan-item";
|
|
803
|
+
row.style.flexDirection = "column";
|
|
804
|
+
row.style.alignItems = "stretch";
|
|
805
|
+
var msgId = item && (item.id || item.messageId || "");
|
|
806
|
+
var threadId = item && (item.threadId || "");
|
|
422
807
|
var subj = typeof item === "string" ? item : item.subject || item.title || JSON.stringify(item);
|
|
423
808
|
var from = item && item.from ? item.from : "";
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
809
|
+
var snippet = item && item.snippet ? item.snippet : "";
|
|
810
|
+
var top = document.createElement("div");
|
|
811
|
+
top.style.display = "flex";
|
|
812
|
+
top.style.alignItems = "flex-start";
|
|
813
|
+
top.style.gap = "6px";
|
|
814
|
+
var cb = document.createElement("input");
|
|
815
|
+
cb.type = "checkbox";
|
|
816
|
+
cb.className = "mail-checkbox";
|
|
817
|
+
cb.setAttribute("data-action", "toggle-select");
|
|
818
|
+
if (msgId) cb.setAttribute("data-message-id", msgId);
|
|
819
|
+
if (isSelected(msgId)) cb.checked = true;
|
|
820
|
+
top.appendChild(cb);
|
|
821
|
+
var subjSpan = document.createElement("span");
|
|
822
|
+
subjSpan.className = "subj";
|
|
823
|
+
subjSpan.style.flex = "1";
|
|
824
|
+
subjSpan.textContent = subj;
|
|
825
|
+
top.appendChild(subjSpan);
|
|
826
|
+
var tagSpan = document.createElement("span");
|
|
827
|
+
tagSpan.className = "tag";
|
|
828
|
+
tagSpan.textContent = from;
|
|
829
|
+
top.appendChild(tagSpan);
|
|
830
|
+
if (threadId) {
|
|
831
|
+
var openBtn = document.createElement("button");
|
|
832
|
+
openBtn.className = "mail-open";
|
|
833
|
+
openBtn.textContent = "Open";
|
|
834
|
+
openBtn.setAttribute("data-action", "open-gmail");
|
|
835
|
+
openBtn.setAttribute("data-thread-id", threadId);
|
|
836
|
+
top.appendChild(openBtn);
|
|
837
|
+
}
|
|
838
|
+
row.appendChild(top);
|
|
839
|
+
if (snippet) {
|
|
840
|
+
var sn = document.createElement("div");
|
|
841
|
+
sn.className = "snippet";
|
|
842
|
+
sn.textContent = snippet;
|
|
843
|
+
row.appendChild(sn);
|
|
844
|
+
}
|
|
427
845
|
el.appendChild(row);
|
|
428
846
|
});
|
|
429
847
|
}
|
|
430
848
|
|
|
849
|
+
// Renders a mailbox_triage_plan result: { plan_id, action, count, sample,
|
|
850
|
+
// truncated, expires_at }. The sample rows are search-shaped messages.
|
|
431
851
|
function renderPlan(plan) {
|
|
432
|
-
var items = asList(plan, ["actions", "items", "plan"]);
|
|
433
852
|
var el = document.getElementById("plan-list");
|
|
434
853
|
el.innerHTML = "";
|
|
435
|
-
if (
|
|
436
|
-
el.innerHTML = '<div class="empty">Plan
|
|
854
|
+
if (!plan || typeof plan !== "object" || !plan.plan_id) {
|
|
855
|
+
el.innerHTML = '<div class="empty">Plan failed \\u2014 unexpected response.</div>';
|
|
437
856
|
return;
|
|
438
857
|
}
|
|
439
|
-
var
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
858
|
+
var head = document.createElement("div");
|
|
859
|
+
head.className = "plan-item";
|
|
860
|
+
head.innerHTML = '<span class="subj"></span><span class="tag"></span>';
|
|
861
|
+
head.querySelector(".subj").textContent =
|
|
862
|
+
plan.count + " message" + (plan.count === 1 ? "" : "s") + " \\u2192 " + (plan.action && plan.action.type);
|
|
863
|
+
head.querySelector(".tag").textContent = plan.expires_at ? "expires " + fmtDate(plan.expires_at) : "";
|
|
864
|
+
el.appendChild(head);
|
|
865
|
+
var sample = asList(plan, ["sample"]);
|
|
866
|
+
sample.forEach(function (item) {
|
|
443
867
|
var row = document.createElement("div");
|
|
444
868
|
row.className = "plan-item";
|
|
445
|
-
var subj = (item && (item.subject || item.title)) || JSON.stringify(item);
|
|
446
|
-
var action = (item && (item.action || item.category)) || "";
|
|
447
869
|
row.innerHTML = '<span class="subj"></span><span class="tag"></span>';
|
|
448
|
-
row.querySelector(".subj").textContent =
|
|
449
|
-
row.querySelector(".tag").textContent =
|
|
870
|
+
row.querySelector(".subj").textContent = (item && (item.subject || item.title)) || JSON.stringify(item);
|
|
871
|
+
row.querySelector(".tag").textContent = (item && item.from) || "";
|
|
450
872
|
el.appendChild(row);
|
|
451
873
|
});
|
|
452
|
-
|
|
874
|
+
if (plan.truncated) {
|
|
875
|
+
var more = document.createElement("div");
|
|
876
|
+
more.className = "empty";
|
|
877
|
+
more.textContent = "\\u2026sample truncated \\u2014 " + plan.count + " total.";
|
|
878
|
+
el.appendChild(more);
|
|
879
|
+
}
|
|
453
880
|
}
|
|
454
881
|
|
|
455
|
-
|
|
882
|
+
function fmtDate(iso) {
|
|
883
|
+
if (!iso) return "";
|
|
884
|
+
var d = new Date(iso);
|
|
885
|
+
return isNaN(d.getTime()) ? String(iso) : d.toLocaleString();
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function renderRuns(runs) {
|
|
889
|
+
var el = document.getElementById("runs-list");
|
|
890
|
+
el.innerHTML = "";
|
|
891
|
+
if (runs.length === 0) {
|
|
892
|
+
el.innerHTML = '<div class="empty">No runs yet.</div>';
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
var shown = runs.slice(0, 20);
|
|
896
|
+
shown.forEach(function (run) {
|
|
897
|
+
var row = document.createElement("div");
|
|
898
|
+
row.className = "runs-item";
|
|
899
|
+
row.title = "Show sessions + output for " + run.appRunId;
|
|
900
|
+
row.innerHTML = '<span class="st"></span><span class="rid"></span><span class="meta"></span>';
|
|
901
|
+
var st = row.querySelector(".st");
|
|
902
|
+
st.textContent = run.status || "?";
|
|
903
|
+
st.className = "st " + (run.status || "");
|
|
904
|
+
row.querySelector(".rid").textContent = run.appRunId;
|
|
905
|
+
row.querySelector(".meta").textContent =
|
|
906
|
+
fmtDate(run.startedAt) + (run.endedAt ? " \\u2192 " + fmtDate(run.endedAt) : "") + " \\u00b7 " + (run.sessions || 0) + " session" + (run.sessions === 1 ? "" : "s");
|
|
907
|
+
row.addEventListener("click", function () { viewRun(run.appRunId); });
|
|
908
|
+
el.appendChild(row);
|
|
909
|
+
});
|
|
910
|
+
if (runs.length > 20) {
|
|
911
|
+
var more = document.createElement("div");
|
|
912
|
+
more.className = "empty";
|
|
913
|
+
more.textContent = "showing 20 of " + runs.length;
|
|
914
|
+
el.appendChild(more);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function loadRuns() {
|
|
919
|
+
return callApp("app_list", {})
|
|
920
|
+
.then(function (result) {
|
|
921
|
+
var apps = Array.isArray(result) ? result : [];
|
|
922
|
+
var mine = null;
|
|
923
|
+
for (var i = 0; i < apps.length; i++) {
|
|
924
|
+
if (apps[i] && apps[i].appId === APP_ID) { mine = apps[i]; break; }
|
|
925
|
+
}
|
|
926
|
+
var runs = (mine && Array.isArray(mine.runs) ? mine.runs.slice() : []).sort(function (a, b) {
|
|
927
|
+
return String(b.startedAt || "").localeCompare(String(a.startedAt || ""));
|
|
928
|
+
});
|
|
929
|
+
renderRuns(runs);
|
|
930
|
+
})
|
|
931
|
+
.catch(function (err) {
|
|
932
|
+
setStatus("Could not load runs: " + err.message);
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function viewRun(appRunId) {
|
|
937
|
+
setStatus("Loading run " + appRunId + "\\u2026");
|
|
938
|
+
callApp("app_status", { appRunId: appRunId })
|
|
939
|
+
.then(function (status) {
|
|
940
|
+
var sessions = (status && status.sessions) || [];
|
|
941
|
+
appendLog("\\u2500\\u2500 run " + appRunId + " (" + (status && status.status) + ") \\u2500\\u2500");
|
|
942
|
+
if (sessions.length === 0) {
|
|
943
|
+
appendLog("(no sessions)");
|
|
944
|
+
setStatus("Ready \\u2014 " + activeAlias);
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
sessions.forEach(function (s) {
|
|
948
|
+
appendLog("session " + s.sessionId + " (" + (s.agentId || "?") + ")");
|
|
949
|
+
});
|
|
950
|
+
return callApp("agent_output", { sessionId: sessions[0].sessionId, lastN: 200, clean: true })
|
|
951
|
+
.then(function (output) {
|
|
952
|
+
if (output) appendLog(typeof output === "string" ? output : JSON.stringify(output, null, 2));
|
|
953
|
+
setStatus("Ready \\u2014 " + activeAlias);
|
|
954
|
+
});
|
|
955
|
+
})
|
|
956
|
+
.catch(function (err) {
|
|
957
|
+
setStatus("Run status failed: " + err.message);
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Event delegation: search-results (checkboxes + open buttons)
|
|
962
|
+
document.getElementById("search-results").addEventListener("change", function (e) {
|
|
963
|
+
var target = e.target;
|
|
964
|
+
if (target.getAttribute("data-action") === "toggle-select") {
|
|
965
|
+
var mid = target.getAttribute("data-message-id");
|
|
966
|
+
if (mid) {
|
|
967
|
+
var item = null;
|
|
968
|
+
var el = target.parentNode;
|
|
969
|
+
while (el && el.parentNode && el !== document) {
|
|
970
|
+
if (el.tagName === "DIV" && el.className.indexOf("plan-item") !== -1) {
|
|
971
|
+
var subjEl = el.querySelector(".subj");
|
|
972
|
+
var tagEl = el.querySelector(".tag");
|
|
973
|
+
var subjText = subjEl ? subjEl.textContent : "";
|
|
974
|
+
var tagText = tagEl ? tagEl.textContent : "";
|
|
975
|
+
// Reconstruct a minimal item from the DOM
|
|
976
|
+
item = { id: mid, threadId: "", subject: subjText, from: tagText };
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
el = el.parentNode;
|
|
980
|
+
}
|
|
981
|
+
toggleSelection(mid, item || { id: mid });
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
});
|
|
985
|
+
document.getElementById("search-results").addEventListener("click", function (e) {
|
|
986
|
+
var target = e.target;
|
|
987
|
+
if (target.getAttribute("data-action") === "open-gmail") {
|
|
988
|
+
var tid = target.getAttribute("data-thread-id");
|
|
989
|
+
if (tid && openLink) {
|
|
990
|
+
openLink("https://mail.google.com/mail/u/0/#inbox/" + tid).catch(function (err) {
|
|
991
|
+
setStatus("Failed to open link: " + (err && err.message ? err.message : String(err)));
|
|
992
|
+
});
|
|
993
|
+
} else if (tid) {
|
|
994
|
+
setStatus("Host does not support openLink.");
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
// Event delegation: selection-bar (send-selection)
|
|
999
|
+
document.getElementById("selection-bar").addEventListener("click", function (e) {
|
|
1000
|
+
if (e.target.getAttribute("data-action") === "send-selection") {
|
|
1001
|
+
sendSelectionToClaude();
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
document.getElementById("refresh-btn").addEventListener("click", function () {
|
|
1006
|
+
probeAliases();
|
|
1007
|
+
loadRuns();
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
document.getElementById("connect-retry-btn").addEventListener("click", function () {
|
|
1011
|
+
probeAliases();
|
|
1012
|
+
loadRuns();
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
document.getElementById("alias-select").addEventListener("change", function () {
|
|
1016
|
+
activeAlias = this.value;
|
|
1017
|
+
clearPlanExpiry();
|
|
1018
|
+
currentPlan = null;
|
|
1019
|
+
document.getElementById("apply-btn").disabled = true;
|
|
1020
|
+
loadMailboxes();
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
document.getElementById("mailbox-select").addEventListener("change", onMailboxChange);
|
|
1024
|
+
|
|
1025
|
+
document.getElementById("runs-refresh-btn").addEventListener("click", loadRuns);
|
|
456
1026
|
|
|
457
1027
|
document.getElementById("search-btn").addEventListener("click", function () {
|
|
458
|
-
var
|
|
1028
|
+
var btn = this;
|
|
1029
|
+
var query = document.getElementById("search-input").value.trim() || "is:unread";
|
|
459
1030
|
var mailbox = document.getElementById("mailbox-select").value;
|
|
1031
|
+
setBusy(btn, true);
|
|
460
1032
|
setStatus("Searching\\u2026");
|
|
461
|
-
callApp("
|
|
1033
|
+
callApp(aliasTool("mailbox_search"), { mailbox: mailbox, query: query })
|
|
462
1034
|
.then(function (result) {
|
|
463
|
-
renderSearchResults(asList(result, ["
|
|
464
|
-
|
|
1035
|
+
renderSearchResults(asList(result, ["items", "messages", "results"]), result);
|
|
1036
|
+
setBusy(btn, false);
|
|
1037
|
+
setStatus("Ready \\u2014 " + activeAlias);
|
|
465
1038
|
})
|
|
466
1039
|
.catch(function (err) {
|
|
1040
|
+
setBusy(btn, false);
|
|
467
1041
|
setStatus("Search failed: " + err.message);
|
|
468
1042
|
});
|
|
469
1043
|
});
|
|
470
1044
|
|
|
1045
|
+
document.getElementById("plan-action").addEventListener("change", function () {
|
|
1046
|
+
document.getElementById("plan-label").style.display = this.value === "label" ? "" : "none";
|
|
1047
|
+
});
|
|
1048
|
+
|
|
471
1049
|
document.getElementById("triage-btn").addEventListener("click", function () {
|
|
1050
|
+
var btn = this;
|
|
472
1051
|
var mailbox = document.getElementById("mailbox-select").value;
|
|
1052
|
+
var query = document.getElementById("plan-query").value.trim() || "is:unread";
|
|
1053
|
+
var actionType = document.getElementById("plan-action").value;
|
|
1054
|
+
var action = { type: actionType };
|
|
1055
|
+
if (actionType === "label") {
|
|
1056
|
+
var label = document.getElementById("plan-label").value.trim();
|
|
1057
|
+
if (!label) {
|
|
1058
|
+
setStatus("Enter a label name for the label action.");
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
1061
|
+
action.addLabelIds = [label];
|
|
1062
|
+
}
|
|
1063
|
+
setBusy(btn, true);
|
|
473
1064
|
setStatus("Building triage plan\\u2026");
|
|
474
1065
|
document.getElementById("apply-btn").disabled = true;
|
|
475
|
-
|
|
1066
|
+
clearPlanExpiry();
|
|
1067
|
+
currentPlan = null;
|
|
1068
|
+
callApp(aliasTool("mailbox_triage_plan"), { mailbox: mailbox, criteria: { query: query }, action: action })
|
|
476
1069
|
.then(function (plan) {
|
|
477
|
-
|
|
1070
|
+
setBusy(btn, false);
|
|
1071
|
+
currentPlan = plan && plan.plan_id ? plan : null;
|
|
478
1072
|
renderPlan(plan);
|
|
479
|
-
|
|
480
|
-
|
|
1073
|
+
if (currentPlan && plan.count > 0) {
|
|
1074
|
+
document.getElementById("apply-btn").disabled = false;
|
|
1075
|
+
armPlanExpiry(plan.expires_at);
|
|
1076
|
+
setStatus("Plan ready (" + plan.count + " messages) \\u2014 review, then Apply.");
|
|
1077
|
+
} else if (currentPlan) {
|
|
1078
|
+
setStatus("Plan matches no messages \\u2014 nothing to apply.");
|
|
1079
|
+
} else {
|
|
1080
|
+
setStatus("Triage plan failed \\u2014 see plan panel.");
|
|
1081
|
+
}
|
|
481
1082
|
})
|
|
482
1083
|
.catch(function (err) {
|
|
483
|
-
|
|
1084
|
+
setBusy(btn, false);
|
|
1085
|
+
var msg = err && err.message ? err.message : String(err);
|
|
1086
|
+
if (msg.indexOf("mailbox_scope_missing") !== -1) {
|
|
1087
|
+
setStatus("Triage scope missing \\u2014 grant it via agentpush (mailbox_request_elevation).");
|
|
1088
|
+
} else {
|
|
1089
|
+
setStatus("Triage plan failed: " + msg);
|
|
1090
|
+
}
|
|
484
1091
|
});
|
|
485
1092
|
});
|
|
486
1093
|
|
|
487
1094
|
document.getElementById("apply-btn").addEventListener("click", function () {
|
|
488
|
-
|
|
1095
|
+
var btn = this;
|
|
1096
|
+
if (!currentPlan || !currentPlan.plan_id) return;
|
|
1097
|
+
setBusy(btn, true);
|
|
489
1098
|
setStatus("Applying triage plan\\u2026");
|
|
490
|
-
callApp("
|
|
491
|
-
.then(function () {
|
|
492
|
-
|
|
1099
|
+
callApp(aliasTool("mailbox_triage_apply"), { plan_id: currentPlan.plan_id, confirm: true })
|
|
1100
|
+
.then(function (result) {
|
|
1101
|
+
setBusy(btn, false);
|
|
1102
|
+
var count = currentPlan.count;
|
|
1103
|
+
var extra = [];
|
|
1104
|
+
if (result && typeof result === "object") {
|
|
1105
|
+
["applied", "modified", "count"].forEach(function (key) {
|
|
1106
|
+
if (typeof result[key] === "number") extra.push(key + ": " + result[key]);
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
setStatus(
|
|
1110
|
+
extra.length > 0
|
|
1111
|
+
? "Plan applied \\u2014 " + extra.join(", ") + "."
|
|
1112
|
+
: "Plan applied (" + count + " message" + (count === 1 ? "" : "s") + ")."
|
|
1113
|
+
);
|
|
493
1114
|
document.getElementById("apply-btn").disabled = true;
|
|
1115
|
+
clearPlanExpiry();
|
|
1116
|
+
currentPlan = null;
|
|
1117
|
+
onMailboxChange();
|
|
494
1118
|
})
|
|
495
1119
|
.catch(function (err) {
|
|
1120
|
+
setBusy(btn, false);
|
|
496
1121
|
setStatus("Apply failed: " + err.message);
|
|
497
1122
|
});
|
|
498
1123
|
});
|
|
499
1124
|
|
|
500
1125
|
function appendLog(text) {
|
|
501
|
-
var el = document.getElementById("log");
|
|
1126
|
+
var el = document.getElementById("log-events");
|
|
502
1127
|
if (el.querySelector(".empty")) el.innerHTML = "";
|
|
503
|
-
|
|
1128
|
+
var line = document.createElement("div");
|
|
1129
|
+
line.textContent = text;
|
|
1130
|
+
el.appendChild(line);
|
|
504
1131
|
el.scrollTop = el.scrollHeight;
|
|
505
1132
|
}
|
|
506
1133
|
|
|
507
|
-
|
|
508
|
-
|
|
1134
|
+
// Replaces the whole tail on every poll tick (the daemon returns the last
|
|
1135
|
+
// N lines, not a delta) \u2014 appending it instead would duplicate the log.
|
|
1136
|
+
function renderLogTail(text) {
|
|
1137
|
+
var el = document.getElementById("log-tail");
|
|
1138
|
+
el.textContent = text || "";
|
|
1139
|
+
el.scrollTop = el.scrollHeight;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function pollAgentRun(appRunId, startedAt) {
|
|
1143
|
+
if (pollHandle) {
|
|
1144
|
+
clearTimeout(pollHandle);
|
|
1145
|
+
pollHandle = null;
|
|
1146
|
+
}
|
|
1147
|
+
if (Date.now() - startedAt > POLL_TIMEOUT_MS) {
|
|
1148
|
+
setStatus("Still running \\u2014 check Past runs later.");
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
509
1151
|
callApp("app_status", { appRunId: appRunId })
|
|
510
1152
|
.then(function (status) {
|
|
511
1153
|
var sessions = (status && status.sessions) || [];
|
|
@@ -514,12 +1156,14 @@ function pollAgentRun(appRunId) {
|
|
|
514
1156
|
? callApp("agent_output", { sessionId: sessionId, lastN: 200, clean: true })
|
|
515
1157
|
: Promise.resolve("");
|
|
516
1158
|
return tail.then(function (output) {
|
|
517
|
-
|
|
1159
|
+
renderLogTail(typeof output === "string" ? output : output ? JSON.stringify(output, null, 2) : "");
|
|
518
1160
|
var done = status && (status.status === "ended" || status.status === "done" || status.endedAt);
|
|
519
1161
|
if (!done) {
|
|
520
|
-
pollHandle = setTimeout(function () { pollAgentRun(appRunId); }, 2000);
|
|
1162
|
+
pollHandle = setTimeout(function () { pollAgentRun(appRunId, startedAt); }, 2000);
|
|
521
1163
|
} else {
|
|
1164
|
+
pollHandle = null;
|
|
522
1165
|
setStatus("Agent run finished.");
|
|
1166
|
+
loadRuns();
|
|
523
1167
|
}
|
|
524
1168
|
});
|
|
525
1169
|
})
|
|
@@ -529,29 +1173,62 @@ function pollAgentRun(appRunId) {
|
|
|
529
1173
|
}
|
|
530
1174
|
|
|
531
1175
|
document.getElementById("run-agent-btn").addEventListener("click", function () {
|
|
532
|
-
|
|
1176
|
+
var btn = this;
|
|
1177
|
+
if (pollHandle) {
|
|
1178
|
+
clearTimeout(pollHandle);
|
|
1179
|
+
pollHandle = null;
|
|
1180
|
+
}
|
|
1181
|
+
document.getElementById("log-events").innerHTML = "";
|
|
1182
|
+
document.getElementById("log-tail").textContent = "";
|
|
1183
|
+
setBusy(btn, true);
|
|
533
1184
|
setStatus("Starting triager agent\\u2026");
|
|
534
1185
|
callApp("app_run", { appId: APP_ID, agents: [AGENT_ID], prompt: "Triage my inbox." })
|
|
535
1186
|
.then(function (run) {
|
|
1187
|
+
setBusy(btn, false);
|
|
1188
|
+
var sessions = (run && run.sessions) || [];
|
|
1189
|
+
var errors = (run && run.errors) || [];
|
|
1190
|
+
if (sessions.length === 0 && errors.length > 0) {
|
|
1191
|
+
appendLog(
|
|
1192
|
+
"Run failed to start: " +
|
|
1193
|
+
errors.map(function (e) { return (e.agentId || "?") + ": " + e.error; }).join("; ")
|
|
1194
|
+
);
|
|
1195
|
+
setStatus("Run failed to start.");
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
536
1198
|
appendLog("Started app run " + (run && run.appRunId));
|
|
537
|
-
|
|
1199
|
+
loadRuns();
|
|
1200
|
+
pollAgentRun(run.appRunId, Date.now());
|
|
538
1201
|
})
|
|
539
1202
|
.catch(function (err) {
|
|
1203
|
+
setBusy(btn, false);
|
|
540
1204
|
setStatus("Run failed: " + err.message);
|
|
541
1205
|
});
|
|
542
1206
|
});
|
|
543
1207
|
|
|
544
|
-
window.McpApp
|
|
1208
|
+
// window.McpApp is normally injected by the serving layer (postMessage bridge
|
|
1209
|
+
// on the MCP resource path, REST bridge on GET /apps/:id/ui). Guard anyway:
|
|
1210
|
+
// without it a missing bridge throws here and the panel dies silently on
|
|
1211
|
+
// "Connecting\u2026" instead of reporting itself.
|
|
1212
|
+
(window.McpApp ? window.McpApp.connect() : Promise.reject(new Error("window.McpApp bridge missing")))
|
|
545
1213
|
.then(function (bridge) {
|
|
546
1214
|
callTool = bridge.callTool;
|
|
547
|
-
|
|
1215
|
+
updateModelContext = bridge.updateModelContext;
|
|
1216
|
+
openLink = bridge.openLink;
|
|
1217
|
+
onTeardown = bridge.onTeardown;
|
|
1218
|
+
if (onTeardown) {
|
|
1219
|
+
onTeardown(function () {
|
|
1220
|
+
if (pollHandle) { clearTimeout(pollHandle); pollHandle = null; }
|
|
1221
|
+
if (planExpiryHandle) { clearTimeout(planExpiryHandle); planExpiryHandle = null; }
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
return Promise.all([probeAliases(), loadRuns()]);
|
|
548
1225
|
})
|
|
549
1226
|
.catch(function (err) {
|
|
550
1227
|
setStatus("Standalone mode \\u2014 no host bridge (" + err.message + ")");
|
|
551
|
-
renderSummary(
|
|
1228
|
+
renderSummary();
|
|
552
1229
|
});
|
|
553
1230
|
|
|
554
|
-
renderSummary(
|
|
1231
|
+
renderSummary();
|
|
555
1232
|
</script>
|
|
556
1233
|
</body>
|
|
557
1234
|
</html>`;
|
|
@@ -889,13 +1566,540 @@ var mediaViewer = defineApp({
|
|
|
889
1566
|
tools: [...MEDIA_VIEWER_TOOLS]
|
|
890
1567
|
}
|
|
891
1568
|
});
|
|
1569
|
+
var narrator = {
|
|
1570
|
+
agent: defineAgent({
|
|
1571
|
+
schema: "agent/v1",
|
|
1572
|
+
id: "@agentproto/session-narrator",
|
|
1573
|
+
description: "Reads a daemon session's transcript and narrates it in plain English \u2014 what happened, what was decided, what's left.",
|
|
1574
|
+
model: "claude-sonnet-5",
|
|
1575
|
+
boundaries: [
|
|
1576
|
+
"Read-only \u2014 never spawn, kill, resume, or write to any session",
|
|
1577
|
+
"Narrate only what the transcript actually shows, never speculate about intent"
|
|
1578
|
+
],
|
|
1579
|
+
tools: ["conversation_read", "session_list"],
|
|
1580
|
+
workflows: [{ ref: "narrate-session" }]
|
|
1581
|
+
}),
|
|
1582
|
+
body: [
|
|
1583
|
+
"You narrate a daemon session's conversation for a human who doesn't want to read the raw transcript.",
|
|
1584
|
+
"",
|
|
1585
|
+
'Given a session id or name, call `conversation_read` with `format: "json"` to get the',
|
|
1586
|
+
"structured turn list (`meta` + `messages`, each `{ role, text, toolCalls?, ts }`). If the",
|
|
1587
|
+
"session id is ambiguous or unknown, call `session_list` first to resolve it by label.",
|
|
1588
|
+
"",
|
|
1589
|
+
"Write a short plain-English narration:",
|
|
1590
|
+
"- What the user asked for.",
|
|
1591
|
+
"- What the assistant did, grouped into a few natural beats (not a line-by-line tool log).",
|
|
1592
|
+
"- Any open question, decision point, or unfinished step at the end of the transcript.",
|
|
1593
|
+
"",
|
|
1594
|
+
"Skip tool-call plumbing details unless they're the point (e.g. a failing command, a real",
|
|
1595
|
+
"decision). If `conversation.conversation` comes back null, say plainly that this session has",
|
|
1596
|
+
"no readable conversation (e.g. a bare shell PTY) instead of guessing."
|
|
1597
|
+
].join("\n")
|
|
1598
|
+
};
|
|
1599
|
+
var narrateSession = defineWorkflow({
|
|
1600
|
+
id: "narrate-session",
|
|
1601
|
+
name: "Narrate Session",
|
|
1602
|
+
description: "Read a daemon session's transcript and narrate it in plain English.",
|
|
1603
|
+
version: "0.1.0",
|
|
1604
|
+
inputs: {},
|
|
1605
|
+
outputs: {},
|
|
1606
|
+
steps: [
|
|
1607
|
+
{
|
|
1608
|
+
id: "narrate",
|
|
1609
|
+
kind: "agent",
|
|
1610
|
+
agent: { ref: "@agentproto/session-narrator" },
|
|
1611
|
+
prompt: "Narrate the session named in the run input (or the most recently started session if none was given): what the user asked for, what the assistant did, and what \u2014 if anything \u2014 is still open at the end of the transcript."
|
|
1612
|
+
}
|
|
1613
|
+
]
|
|
1614
|
+
});
|
|
1615
|
+
|
|
1616
|
+
// src/session-viewer/ui.ts
|
|
1617
|
+
var SESSION_VIEWER_TOOLS = ["session_list", "conversation_read"];
|
|
1618
|
+
var APP_ID3 = "@agentproto/session-viewer";
|
|
1619
|
+
var SESSION_VIEWER_HTML = `<!DOCTYPE html>
|
|
1620
|
+
<html lang="en">
|
|
1621
|
+
<head>
|
|
1622
|
+
<meta charset="UTF-8">
|
|
1623
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1624
|
+
<title>Session Viewer</title>
|
|
1625
|
+
<style>
|
|
1626
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
1627
|
+
:root{
|
|
1628
|
+
--bg:#0d1117;--bg2:#161b22;--bg3:#21262d;--border:#30363d;
|
|
1629
|
+
--text:#e6edf3;--text2:#8b949e;--text3:#6e7681;
|
|
1630
|
+
--green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;
|
|
1631
|
+
}
|
|
1632
|
+
html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:13px;background:var(--bg);color:var(--text)}
|
|
1633
|
+
#app{display:flex;height:100%;overflow:hidden}
|
|
1634
|
+
|
|
1635
|
+
/* sidebar */
|
|
1636
|
+
#sidebar{width:250px;min-width:190px;background:var(--bg2);border-right:1px solid var(--border);display:flex;flex-direction:column;flex-shrink:0}
|
|
1637
|
+
#sidebar-hdr{padding:10px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px}
|
|
1638
|
+
#sidebar-hdr h1{font-size:11px;font-weight:700;color:var(--text2);text-transform:uppercase;letter-spacing:.06em;flex:1}
|
|
1639
|
+
#archived-toggle{display:flex;align-items:center;gap:5px;font-size:10.5px;color:var(--text2);padding:6px 12px;border-bottom:1px solid var(--border);cursor:pointer;user-select:none}
|
|
1640
|
+
#archived-toggle input{cursor:pointer}
|
|
1641
|
+
#refresh-btn{background:none;border:none;cursor:pointer;color:var(--text2);font-size:14px;line-height:1;padding:2px 4px;border-radius:4px;font-family:inherit}
|
|
1642
|
+
#refresh-btn:hover{color:var(--text);background:var(--bg3)}
|
|
1643
|
+
#session-list{flex:1;overflow-y:auto;padding:4px 0}
|
|
1644
|
+
.si{padding:8px 12px;cursor:pointer;border-left:2px solid transparent}
|
|
1645
|
+
.si:hover{background:var(--bg3)}
|
|
1646
|
+
.si.active{background:var(--bg3);border-left-color:var(--blue)}
|
|
1647
|
+
.si .sn{font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
1648
|
+
.si .sm{font-size:10.5px;color:var(--text2);margin-top:3px;display:flex;gap:5px;align-items:center;flex-wrap:wrap}
|
|
1649
|
+
.badge{display:inline-block;padding:1px 6px;border-radius:10px;font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.02em}
|
|
1650
|
+
.b-run{background:rgba(63,185,80,.15);color:var(--green)}
|
|
1651
|
+
.b-wait{background:rgba(88,166,255,.15);color:var(--blue)}
|
|
1652
|
+
.b-idle{background:rgba(139,148,158,.12);color:var(--text2)}
|
|
1653
|
+
.b-end{background:rgba(139,148,158,.1);color:var(--text3)}
|
|
1654
|
+
.b-err{background:rgba(248,81,73,.15);color:var(--red)}
|
|
1655
|
+
#empty-sidebar{padding:14px 12px;color:var(--text2);font-size:11.5px}
|
|
1656
|
+
|
|
1657
|
+
/* main */
|
|
1658
|
+
#main{flex:1;display:flex;flex-direction:column;overflow:hidden;min-width:0}
|
|
1659
|
+
#header{padding:12px 18px;border-bottom:1px solid var(--border);background:var(--bg2);flex-shrink:0}
|
|
1660
|
+
#header .htitle{font-size:14px;font-weight:700;display:flex;align-items:center;gap:8px}
|
|
1661
|
+
.pulse{width:7px;height:7px;border-radius:50%;background:var(--green);flex:none;box-shadow:0 0 0 0 rgba(63,185,80,.4);animation:pulse 2s infinite}
|
|
1662
|
+
@keyframes pulse{70%{box-shadow:0 0 0 6px rgba(63,185,80,0)}100%{box-shadow:0 0 0 0 rgba(63,185,80,0)}}
|
|
1663
|
+
#header .hfacts{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
|
|
1664
|
+
.fact{font-size:10.5px;font-weight:600;background:var(--bg3);border:1px solid var(--border);color:var(--text2);padding:3px 9px;border-radius:999px}
|
|
1665
|
+
#empty-main,#loading-main,#error-main{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text2);gap:8px;text-align:center;padding:24px}
|
|
1666
|
+
#empty-main .ico,#loading-main .ico{font-size:30px}
|
|
1667
|
+
#error-main{color:var(--red)}
|
|
1668
|
+
|
|
1669
|
+
/* transcript */
|
|
1670
|
+
#transcript{flex:1;overflow-y:auto;padding:14px 18px;display:flex;flex-direction:column;gap:10px}
|
|
1671
|
+
.msg{display:flex;flex-direction:column;max-width:78%}
|
|
1672
|
+
.msg.role-user{align-self:flex-end;align-items:flex-end}
|
|
1673
|
+
.msg.role-assistant,.msg.role-tool{align-self:flex-start;align-items:flex-start}
|
|
1674
|
+
.msg.role-system{align-self:center;max-width:90%}
|
|
1675
|
+
.bubble{border-radius:10px;padding:9px 12px;font-size:13px;line-height:1.55;white-space:pre-wrap;word-break:break-word}
|
|
1676
|
+
.role-user .bubble{background:var(--blue);color:#04101c;border-bottom-right-radius:3px}
|
|
1677
|
+
.role-assistant .bubble{background:var(--bg2);border:1px solid var(--border);border-bottom-left-radius:3px}
|
|
1678
|
+
.role-system .bubble{background:transparent;border:1px dashed var(--border);color:var(--text2);font-size:11.5px;text-align:center}
|
|
1679
|
+
.mrow{display:flex;align-items:center;gap:6px;margin-bottom:3px}
|
|
1680
|
+
.mrole{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--text3)}
|
|
1681
|
+
.mtime{font-size:10px;color:var(--text3);font-variant-numeric:tabular-nums}
|
|
1682
|
+
|
|
1683
|
+
.chip{margin-top:6px;border:1px solid var(--border);border-radius:8px;background:var(--bg3);overflow:hidden;cursor:pointer;width:100%}
|
|
1684
|
+
.chip .ch{padding:6px 10px;font-size:11.5px;display:flex;align-items:center;gap:7px;color:var(--text2)}
|
|
1685
|
+
.chip .ch .ci{color:var(--yellow);flex:none}
|
|
1686
|
+
.chip.tool-result .ch .ci{color:var(--purple)}
|
|
1687
|
+
.chip .ch .cname{font-weight:600;color:var(--text)}
|
|
1688
|
+
.chip .ch .cprev{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text3)}
|
|
1689
|
+
.chip .ch .cchev{flex:none;transition:transform .12s;color:var(--text3);font-size:10px}
|
|
1690
|
+
.chip.open .ch .cchev{transform:rotate(90deg)}
|
|
1691
|
+
.chip pre{display:none;margin:0;border-top:1px solid var(--border);padding:9px 11px;font-family:Menlo,Monaco,'Courier New',monospace;font-size:11px;line-height:1.5;color:var(--text2);white-space:pre-wrap;word-break:break-word;max-height:320px;overflow:auto}
|
|
1692
|
+
.chip.open pre{display:block}
|
|
1693
|
+
.reasoning{margin-bottom:6px;font-size:11.5px;font-style:italic;color:var(--text2);border-left:2px solid var(--border);padding-left:8px}
|
|
1694
|
+
|
|
1695
|
+
#statusbar{padding:5px 18px;font-size:10.5px;color:var(--text2);border-top:1px solid var(--border);background:var(--bg2);flex-shrink:0;display:flex;justify-content:space-between}
|
|
1696
|
+
</style>
|
|
1697
|
+
</head>
|
|
1698
|
+
<body>
|
|
1699
|
+
<div id="app">
|
|
1700
|
+
<div id="sidebar">
|
|
1701
|
+
<div id="sidebar-hdr">
|
|
1702
|
+
<h1>Sessions</h1>
|
|
1703
|
+
<button id="refresh-btn" title="Refresh" type="button">↻</button>
|
|
1704
|
+
</div>
|
|
1705
|
+
<label id="archived-toggle"><input type="checkbox" id="archived-check"> show archived</label>
|
|
1706
|
+
<div id="session-list"></div>
|
|
1707
|
+
</div>
|
|
1708
|
+
<div id="main">
|
|
1709
|
+
<div id="header" style="display:none"></div>
|
|
1710
|
+
<div id="empty-main"><div class="ico">💬</div><div>Pick a session to read its conversation</div></div>
|
|
1711
|
+
<div id="transcript" style="display:none"></div>
|
|
1712
|
+
<div id="statusbar" style="display:none">
|
|
1713
|
+
<span id="status-left"></span>
|
|
1714
|
+
<span id="status-right"></span>
|
|
1715
|
+
</div>
|
|
1716
|
+
</div>
|
|
1717
|
+
</div>
|
|
1718
|
+
<script>
|
|
1719
|
+
var callTool = null;
|
|
1720
|
+
var APP_ID = ${JSON.stringify(APP_ID3)};
|
|
1721
|
+
|
|
1722
|
+
// app_tool_call wraps its dispatch result at least once (its own text-result
|
|
1723
|
+
// body is the JSON-stringified inner MCP tool result); peel back through
|
|
1724
|
+
// nested {content:[{type:"text",text:...}]} shells until the payload stops
|
|
1725
|
+
// looking like one \u2014 works whichever layer the host's bridge already
|
|
1726
|
+
// unwrapped. With conversation_read's format:"json" this resolves straight
|
|
1727
|
+
// to the { meta, messages } object; with session_list, straight to
|
|
1728
|
+
// { sessions: [...] }.
|
|
1729
|
+
function unwrapText(result) {
|
|
1730
|
+
var cur = result;
|
|
1731
|
+
for (var i = 0; i < 4; i++) {
|
|
1732
|
+
if (typeof cur === "string") {
|
|
1733
|
+
try { cur = JSON.parse(cur); continue; } catch (e) { return cur; }
|
|
1734
|
+
}
|
|
1735
|
+
if (cur && Array.isArray(cur.content) && cur.content[0] && typeof cur.content[0].text === "string") {
|
|
1736
|
+
cur = cur.content[0].text;
|
|
1737
|
+
continue;
|
|
1738
|
+
}
|
|
1739
|
+
break;
|
|
1740
|
+
}
|
|
1741
|
+
return cur;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
function callApp(tool, args) {
|
|
1745
|
+
return callTool("app_tool_call", { appId: APP_ID, tool: tool, args: args || {} }).then(unwrapText);
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
function escHtml(s) {
|
|
1749
|
+
return String(s == null ? "" : s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
function fmtTime(ts) {
|
|
1753
|
+
if (!ts) return "";
|
|
1754
|
+
try { return new Date(ts).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" }); }
|
|
1755
|
+
catch (e) { return ""; }
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
function fmtClock(iso) {
|
|
1759
|
+
if (!iso) return "";
|
|
1760
|
+
try { return new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); }
|
|
1761
|
+
catch (e) { return ""; }
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
function fmtRelative(iso) {
|
|
1765
|
+
if (!iso) return "";
|
|
1766
|
+
var ms = Date.now() - new Date(iso).getTime();
|
|
1767
|
+
if (!isFinite(ms)) return "";
|
|
1768
|
+
var s = Math.round(ms / 1000);
|
|
1769
|
+
if (s < 5) return "just now";
|
|
1770
|
+
if (s < 60) return s + "s ago";
|
|
1771
|
+
var m = Math.round(s / 60);
|
|
1772
|
+
if (m < 60) return m + "m ago";
|
|
1773
|
+
var h = Math.round(m / 60);
|
|
1774
|
+
if (h < 24) return h + "h ago";
|
|
1775
|
+
return Math.round(h / 24) + "d ago";
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
function fmtDuration(startIso, endIso) {
|
|
1779
|
+
var end = endIso ? new Date(endIso).getTime() : Date.now();
|
|
1780
|
+
var start = new Date(startIso).getTime();
|
|
1781
|
+
if (!isFinite(start) || !isFinite(end)) return "";
|
|
1782
|
+
var s = Math.max(0, Math.round((end - start) / 1000));
|
|
1783
|
+
if (s < 60) return s + "s";
|
|
1784
|
+
var m = Math.floor(s / 60);
|
|
1785
|
+
if (m < 60) return m + "m " + (s % 60) + "s";
|
|
1786
|
+
var h = Math.floor(m / 60);
|
|
1787
|
+
return h + "h " + (m % 60) + "m";
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
function prettyArgs(raw) {
|
|
1791
|
+
if (raw == null) return "";
|
|
1792
|
+
if (typeof raw !== "string") return JSON.stringify(raw, null, 2);
|
|
1793
|
+
try { return JSON.stringify(JSON.parse(raw), null, 2); } catch (e) { return raw; }
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
function firstLine(s, max) {
|
|
1797
|
+
var t = String(s == null ? "" : s).split("\\n")[0];
|
|
1798
|
+
return t.length > max ? t.slice(0, max) + "\\u2026" : t;
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
// ============================================================
|
|
1802
|
+
// state
|
|
1803
|
+
// ============================================================
|
|
1804
|
+
|
|
1805
|
+
var sessions = [];
|
|
1806
|
+
var activeId = null;
|
|
1807
|
+
var includeArchived = false;
|
|
1808
|
+
var currentMeta = null;
|
|
1809
|
+
var lastMessageCount = -1;
|
|
1810
|
+
var sidebarTimer = null;
|
|
1811
|
+
var convoTimer = null;
|
|
1812
|
+
|
|
1813
|
+
function findSession(id) {
|
|
1814
|
+
for (var i = 0; i < sessions.length; i++) if (sessions[i].id === id) return sessions[i];
|
|
1815
|
+
return null;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
function isLive(s) {
|
|
1819
|
+
return !!s && (s.status === "running" || s.status === "starting");
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
// ============================================================
|
|
1823
|
+
// sidebar
|
|
1824
|
+
// ============================================================
|
|
1825
|
+
|
|
1826
|
+
function displayBadge(s) {
|
|
1827
|
+
if (s.status === "running") {
|
|
1828
|
+
if (s.busy) return { label: "working", cls: "b-run" };
|
|
1829
|
+
if (s.awaitingInput) return { label: "waiting", cls: "b-wait" };
|
|
1830
|
+
return { label: "idle", cls: "b-idle" };
|
|
1831
|
+
}
|
|
1832
|
+
if (s.status === "starting") return { label: "starting", cls: "b-wait" };
|
|
1833
|
+
if (s.status === "error" || s.status === "killed") return { label: s.status, cls: "b-err" };
|
|
1834
|
+
return { label: s.status || "exited", cls: "b-end" };
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
function renderSidebar() {
|
|
1838
|
+
var el = document.getElementById("session-list");
|
|
1839
|
+
if (sessions.length === 0) {
|
|
1840
|
+
el.innerHTML = '<div id="empty-sidebar">No sessions yet.</div>';
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
var html = "";
|
|
1844
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
1845
|
+
var s = sessions[i];
|
|
1846
|
+
var label = s.label || s.title || s.name || (s.command ? String(s.command).split("/").pop() : null) || s.id.slice(0, 10);
|
|
1847
|
+
var db = displayBadge(s);
|
|
1848
|
+
var active = s.id === activeId ? " active" : "";
|
|
1849
|
+
html += '<div class="si' + active + '" data-id="' + escHtml(s.id) + '">'
|
|
1850
|
+
+ '<div class="sn">' + escHtml(label) + "</div>"
|
|
1851
|
+
+ '<div class="sm">'
|
|
1852
|
+
+ '<span class="badge ' + db.cls + '">' + escHtml(db.label) + "</span>"
|
|
1853
|
+
+ "<span>" + escHtml(s.kind || "") + "</span>"
|
|
1854
|
+
+ "<span>" + escHtml(fmtRelative(s.startedAt)) + "</span>"
|
|
1855
|
+
+ "</div></div>";
|
|
1856
|
+
}
|
|
1857
|
+
el.innerHTML = html;
|
|
1858
|
+
var rows = el.querySelectorAll(".si");
|
|
1859
|
+
for (var j = 0; j < rows.length; j++) {
|
|
1860
|
+
rows[j].addEventListener("click", (function (id) { return function () { selectSession(id); }; })(rows[j].getAttribute("data-id")));
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
function loadSessions() {
|
|
1865
|
+
return callApp("session_list", { kind: "all", includeArchived: includeArchived }).then(function (data) {
|
|
1866
|
+
sessions = (data && data.sessions) || [];
|
|
1867
|
+
renderSidebar();
|
|
1868
|
+
if (activeId) {
|
|
1869
|
+
var hdr = document.getElementById("header");
|
|
1870
|
+
if (hdr.style.display !== "none") renderHeader();
|
|
1871
|
+
}
|
|
1872
|
+
}).catch(function (e) {
|
|
1873
|
+
document.getElementById("status-left").textContent = "sessions: " + e.message;
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
// ============================================================
|
|
1878
|
+
// header
|
|
1879
|
+
// ============================================================
|
|
1880
|
+
|
|
1881
|
+
function renderHeader() {
|
|
1882
|
+
var s = findSession(activeId);
|
|
1883
|
+
var meta = currentMeta || {};
|
|
1884
|
+
var el = document.getElementById("header");
|
|
1885
|
+
el.style.display = "block";
|
|
1886
|
+
|
|
1887
|
+
var title = meta.title || (s && (s.label || s.title)) || activeId;
|
|
1888
|
+
var live = s && isLive(s);
|
|
1889
|
+
var pulse = live ? '<span class="pulse"></span>' : "";
|
|
1890
|
+
|
|
1891
|
+
var facts = [];
|
|
1892
|
+
if (meta.model || (s && s.model)) facts.push(escHtml(meta.model || (s && s.model)));
|
|
1893
|
+
if (s && s.adapterSlug) facts.push(escHtml(s.adapterSlug));
|
|
1894
|
+
if (s && s.kind) facts.push(escHtml(s.kind));
|
|
1895
|
+
if (s) { var db = displayBadge(s); facts.push(escHtml(db.label)); }
|
|
1896
|
+
if (s && s.cwd) facts.push('<span title="' + escHtml(s.cwd) + '">' + escHtml(s.cwd.length > 46 ? "\\u2026" + s.cwd.slice(-44) : s.cwd) + "</span>");
|
|
1897
|
+
var started = (meta.startedAt || (s && s.startedAt));
|
|
1898
|
+
if (started) facts.push("started " + escHtml(fmtClock(started)) + " (" + escHtml(fmtRelative(started)) + ")");
|
|
1899
|
+
var ended = meta.endedAt || (s && s.endedAt);
|
|
1900
|
+
if (ended) facts.push("ran " + escHtml(fmtDuration(started, ended)));
|
|
1901
|
+
else if (started) facts.push("running " + escHtml(fmtDuration(started)));
|
|
1902
|
+
if (meta.messageCount != null) facts.push(meta.messageCount + " messages");
|
|
1903
|
+
if (meta.toolCallCount != null) facts.push(meta.toolCallCount + " tool calls");
|
|
1904
|
+
if (meta.tokens && (meta.tokens.input != null || meta.tokens.output != null)) {
|
|
1905
|
+
facts.push("tokens " + (meta.tokens.input || 0) + "\\u2192" + (meta.tokens.output || 0));
|
|
1906
|
+
}
|
|
1907
|
+
if (meta.costUsd != null) facts.push("$" + Number(meta.costUsd).toFixed(4));
|
|
1908
|
+
|
|
1909
|
+
el.innerHTML = '<div class="htitle">' + pulse + escHtml(title) + "</div>"
|
|
1910
|
+
+ '<div class="hfacts">' + facts.map(function (f) { return '<span class="fact">' + f + "</span>"; }).join("") + "</div>";
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
// ============================================================
|
|
1914
|
+
// transcript
|
|
1915
|
+
// ============================================================
|
|
1916
|
+
|
|
1917
|
+
function renderToolCallChip(tc) {
|
|
1918
|
+
var args = prettyArgs(tc.args);
|
|
1919
|
+
return '<div class="chip tool-call"><div class="ch"><span class="ci">▸</span>'
|
|
1920
|
+
+ '<span class="cname">' + escHtml(tc.name || "tool") + "</span>"
|
|
1921
|
+
+ '<span class="cprev">' + escHtml(firstLine(args, 64)) + "</span>"
|
|
1922
|
+
+ '<span class="cchev">▸</span></div><pre>' + escHtml(args) + "</pre></div>";
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
function renderToolResultChip(m) {
|
|
1926
|
+
var text = m.text || "";
|
|
1927
|
+
return '<div class="chip tool-result"><div class="ch"><span class="ci">🔧</span>'
|
|
1928
|
+
+ '<span class="cname">' + escHtml(m.toolName || "result") + "</span>"
|
|
1929
|
+
+ '<span class="cprev">' + escHtml(firstLine(text, 64)) + "</span>"
|
|
1930
|
+
+ '<span class="cchev">▸</span></div><pre>' + escHtml(text) + "</pre></div>";
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
function renderMessage(m) {
|
|
1934
|
+
var time = m.ts ? '<span class="mtime">' + fmtTime(m.ts) + "</span>" : "";
|
|
1935
|
+
if (m.role === "system") {
|
|
1936
|
+
return '<div class="msg role-system"><div class="bubble">' + escHtml(m.text || "") + "</div></div>";
|
|
1937
|
+
}
|
|
1938
|
+
if (m.role === "tool") {
|
|
1939
|
+
return '<div class="msg role-tool">' + renderToolResultChip(m) + "</div>";
|
|
1940
|
+
}
|
|
1941
|
+
if (m.role === "user") {
|
|
1942
|
+
return '<div class="msg role-user"><div class="mrow"><span class="mrole">You</span>' + time + "</div>"
|
|
1943
|
+
+ '<div class="bubble">' + escHtml(m.text || "") + "</div></div>";
|
|
1944
|
+
}
|
|
1945
|
+
// assistant
|
|
1946
|
+
var inner = "";
|
|
1947
|
+
if (m.reasoning) inner += '<div class="reasoning">' + escHtml(m.reasoning) + "</div>";
|
|
1948
|
+
if (m.text) inner += escHtml(m.text);
|
|
1949
|
+
var body = inner ? '<div class="bubble">' + inner + "</div>" : "";
|
|
1950
|
+
var chips = (m.toolCalls || []).map(renderToolCallChip).join("");
|
|
1951
|
+
return '<div class="msg role-assistant"><div class="mrow"><span class="mrole">Assistant</span>' + time + "</div>"
|
|
1952
|
+
+ body + chips + "</div>";
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
function renderTranscript(messages) {
|
|
1956
|
+
var el = document.getElementById("transcript");
|
|
1957
|
+
var atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
|
|
1958
|
+
el.style.display = "flex";
|
|
1959
|
+
document.getElementById("empty-main").style.display = "none";
|
|
1960
|
+
var errEl = document.getElementById("error-main");
|
|
1961
|
+
if (errEl) errEl.style.display = "none";
|
|
1962
|
+
if (messages.length === 0) {
|
|
1963
|
+
el.innerHTML = '<div style="color:var(--text2);text-align:center;padding:20px">(empty conversation)</div>';
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
el.innerHTML = messages.map(renderMessage).join("");
|
|
1967
|
+
if (atBottom) el.scrollTop = el.scrollHeight;
|
|
1968
|
+
|
|
1969
|
+
var chips = el.querySelectorAll(".chip");
|
|
1970
|
+
for (var i = 0; i < chips.length; i++) {
|
|
1971
|
+
chips[i].querySelector(".ch").addEventListener("click", (function (chip) {
|
|
1972
|
+
return function () { chip.classList.toggle("open"); };
|
|
1973
|
+
})(chips[i]));
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
function renderNoConversation(reason) {
|
|
1978
|
+
document.getElementById("transcript").style.display = "none";
|
|
1979
|
+
document.getElementById("empty-main").style.display = "none";
|
|
1980
|
+
var el = document.getElementById("error-main");
|
|
1981
|
+
el.style.display = "flex";
|
|
1982
|
+
el.innerHTML = '<div class="ico">🚫</div><div>No readable conversation for this session'
|
|
1983
|
+
+ (reason ? '<br><span style="font-size:11px;color:var(--text3)">' + escHtml(reason) + "</span>" : "")
|
|
1984
|
+
+ "</div>";
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
// ============================================================
|
|
1988
|
+
// selection + polling
|
|
1989
|
+
// ============================================================
|
|
1990
|
+
|
|
1991
|
+
function clearConvoPoll() {
|
|
1992
|
+
if (convoTimer) { clearTimeout(convoTimer); convoTimer = null; }
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1995
|
+
function scheduleConvoPoll() {
|
|
1996
|
+
clearConvoPoll();
|
|
1997
|
+
var s = findSession(activeId);
|
|
1998
|
+
if (!isLive(s)) return;
|
|
1999
|
+
convoTimer = setTimeout(function () {
|
|
2000
|
+
loadConversation(activeId, { silent: true });
|
|
2001
|
+
}, 3500);
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
function loadConversation(id, opts) {
|
|
2005
|
+
opts = opts || {};
|
|
2006
|
+
var requestedId = id;
|
|
2007
|
+
if (!opts.silent) {
|
|
2008
|
+
document.getElementById("transcript").style.display = "none";
|
|
2009
|
+
document.getElementById("empty-main").style.display = "none";
|
|
2010
|
+
var errEl = document.getElementById("error-main");
|
|
2011
|
+
errEl.style.display = "flex";
|
|
2012
|
+
errEl.innerHTML = '<div class="ico">⌛</div><div>Loading\\u2026</div>';
|
|
2013
|
+
}
|
|
2014
|
+
document.getElementById("status-left").textContent = "loading\\u2026";
|
|
2015
|
+
return callApp("conversation_read", { idOrName: id, format: "json" }).then(function (data) {
|
|
2016
|
+
if (activeId !== requestedId) return; // switched away meanwhile
|
|
2017
|
+
if (!data || !Array.isArray(data.messages)) {
|
|
2018
|
+
currentMeta = null;
|
|
2019
|
+
renderHeader();
|
|
2020
|
+
renderNoConversation(data && data.reason);
|
|
2021
|
+
clearConvoPoll();
|
|
2022
|
+
document.getElementById("status-left").textContent = "no conversation";
|
|
2023
|
+
return;
|
|
2024
|
+
}
|
|
2025
|
+
currentMeta = data.meta || {};
|
|
2026
|
+
lastMessageCount = data.messages.length;
|
|
2027
|
+
renderHeader();
|
|
2028
|
+
renderTranscript(data.messages);
|
|
2029
|
+
document.getElementById("status-left").textContent = data.messages.length + " messages";
|
|
2030
|
+
document.getElementById("status-right").textContent = "updated " + fmtTime(Date.now());
|
|
2031
|
+
scheduleConvoPoll();
|
|
2032
|
+
}).catch(function (e) {
|
|
2033
|
+
if (activeId !== requestedId) return;
|
|
2034
|
+
document.getElementById("status-left").textContent = "error: " + e.message;
|
|
2035
|
+
if (!opts.silent) renderNoConversation(e.message);
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
function selectSession(id) {
|
|
2040
|
+
if (activeId === id) return;
|
|
2041
|
+
activeId = id;
|
|
2042
|
+
currentMeta = null;
|
|
2043
|
+
lastMessageCount = -1;
|
|
2044
|
+
clearConvoPoll();
|
|
2045
|
+
renderSidebar();
|
|
2046
|
+
document.getElementById("statusbar").style.display = "flex";
|
|
2047
|
+
renderHeader();
|
|
2048
|
+
loadConversation(id, {});
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
// ============================================================
|
|
2052
|
+
// boot
|
|
2053
|
+
// ============================================================
|
|
2054
|
+
|
|
2055
|
+
document.getElementById("refresh-btn").addEventListener("click", function () {
|
|
2056
|
+
loadSessions();
|
|
2057
|
+
if (activeId) loadConversation(activeId, {});
|
|
2058
|
+
});
|
|
2059
|
+
document.getElementById("archived-check").addEventListener("change", function (e) {
|
|
2060
|
+
includeArchived = !!e.target.checked;
|
|
2061
|
+
loadSessions();
|
|
2062
|
+
});
|
|
2063
|
+
|
|
2064
|
+
window.McpApp.connect()
|
|
2065
|
+
.then(function (bridge) {
|
|
2066
|
+
callTool = bridge.callTool;
|
|
2067
|
+
return loadSessions();
|
|
2068
|
+
})
|
|
2069
|
+
.then(function () {
|
|
2070
|
+
sidebarTimer = setInterval(loadSessions, 5000);
|
|
2071
|
+
if (!activeId && sessions.length > 0) selectSession(sessions[0].id);
|
|
2072
|
+
})
|
|
2073
|
+
.catch(function (err) {
|
|
2074
|
+
document.getElementById("empty-main").innerHTML =
|
|
2075
|
+
'<div class="ico">⚠</div><div>Standalone mode \\u2014 no host bridge (' + escHtml(err.message) + ")</div>";
|
|
2076
|
+
});
|
|
2077
|
+
</script>
|
|
2078
|
+
</body>
|
|
2079
|
+
</html>`;
|
|
2080
|
+
|
|
2081
|
+
// src/session-viewer/index.ts
|
|
2082
|
+
var sessionViewer = defineApp({
|
|
2083
|
+
id: "@agentproto/session-viewer",
|
|
2084
|
+
name: "Session Viewer",
|
|
2085
|
+
version: "0.1.0",
|
|
2086
|
+
description: "Read a daemon session as a clean conversation \u2014 turns, tool calls, timestamps.",
|
|
2087
|
+
agents: [narrator],
|
|
2088
|
+
workflows: [narrateSession],
|
|
2089
|
+
ui: {
|
|
2090
|
+
html: SESSION_VIEWER_HTML,
|
|
2091
|
+
title: "Session Viewer",
|
|
2092
|
+
tools: [...SESSION_VIEWER_TOOLS]
|
|
2093
|
+
}
|
|
2094
|
+
});
|
|
892
2095
|
|
|
893
2096
|
// src/bin/sync.ts
|
|
894
2097
|
var APPS = [
|
|
895
2098
|
{ slug: "code-team", category: "team", handle: codeTeam },
|
|
896
2099
|
{ slug: "content-team", category: "team", handle: contentTeam },
|
|
897
2100
|
{ slug: "mail-triage", category: "app", handle: mailTriage },
|
|
898
|
-
{ slug: "media-viewer", category: "app", handle: mediaViewer }
|
|
2101
|
+
{ slug: "media-viewer", category: "app", handle: mediaViewer },
|
|
2102
|
+
{ slug: "session-viewer", category: "app", handle: sessionViewer }
|
|
899
2103
|
];
|
|
900
2104
|
async function syncApps(baseDir) {
|
|
901
2105
|
const catalog = [];
|