@agentproto/apps 0.7.0 → 0.8.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/dist/bin/sync.d.ts.map +1 -1
- package/dist/bin/sync.mjs +782 -2
- 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 +782 -3
- package/dist/index.mjs.map +1 -1
- package/dist/ops-panel/agents/manager.d.ts +16 -0
- package/dist/ops-panel/agents/manager.d.ts.map +1 -0
- package/dist/ops-panel/agents/watchdog.d.ts +23 -0
- package/dist/ops-panel/agents/watchdog.d.ts.map +1 -0
- package/dist/ops-panel/index.d.ts +19 -0
- package/dist/ops-panel/index.d.ts.map +1 -0
- package/dist/ops-panel/ui.d.ts +22 -0
- package/dist/ops-panel/ui.d.ts.map +1 -0
- package/dist/ops-panel.mjs +791 -0
- package/dist/ops-panel.mjs.map +1 -0
- package/package.json +6 -1
package/dist/bin/sync.mjs
CHANGED
|
@@ -1566,6 +1566,785 @@ var mediaViewer = defineApp({
|
|
|
1566
1566
|
tools: [...MEDIA_VIEWER_TOOLS]
|
|
1567
1567
|
}
|
|
1568
1568
|
});
|
|
1569
|
+
var MANAGER_LABEL = "Sessions Manager";
|
|
1570
|
+
var manager = {
|
|
1571
|
+
agent: defineAgent({
|
|
1572
|
+
schema: "agent/v1",
|
|
1573
|
+
id: "@agentproto/sessions-manager",
|
|
1574
|
+
description: "Durable coordinator over the daemon's sessions \u2014 routes questions to the right session, re-prompts stalled work, restarts dead sessions, reports status.",
|
|
1575
|
+
model: "claude-sonnet-5",
|
|
1576
|
+
boundaries: [
|
|
1577
|
+
"Never kill a session unless the human explicitly asks for that session by id/label",
|
|
1578
|
+
"Never merge PRs or bypass a repo's declared review flow from a supervised session",
|
|
1579
|
+
"Prefer re-prompting an existing session over spawning a duplicate for the same task"
|
|
1580
|
+
],
|
|
1581
|
+
tools: [
|
|
1582
|
+
"session_list",
|
|
1583
|
+
"conversation_read",
|
|
1584
|
+
"agent_prompt",
|
|
1585
|
+
"session_restart",
|
|
1586
|
+
"agent_start",
|
|
1587
|
+
"session_events_poll"
|
|
1588
|
+
]
|
|
1589
|
+
}),
|
|
1590
|
+
body: [
|
|
1591
|
+
"You are the manager of this agentproto daemon's sessions: the human talks to you, and you talk",
|
|
1592
|
+
"to the other sessions. You are durable \u2014 the same session across many turns \u2014 so keep a working",
|
|
1593
|
+
"memory of who is doing what and don't re-derive it from scratch every turn.",
|
|
1594
|
+
"",
|
|
1595
|
+
"What you do:",
|
|
1596
|
+
"- Locate: given a task or question, find the relevant session (`session_list`, labels, cwd,",
|
|
1597
|
+
" recency) and read its transcript (`conversation_read`) before answering \u2014 never guess status.",
|
|
1598
|
+
"- Drive: re-prompt an idle/parked session onto its task with `agent_prompt`; restart a",
|
|
1599
|
+
" killed/exited session with `session_restart` when its work is clearly unfinished.",
|
|
1600
|
+
"- Spawn: only when no existing session owns the task \u2014 check for duplicates first.",
|
|
1601
|
+
"- Report: answer with short, concrete status (who, doing what, since when, blocked on what).",
|
|
1602
|
+
"",
|
|
1603
|
+
"Boundaries:",
|
|
1604
|
+
"- Never kill a session unless the human explicitly asks for that session.",
|
|
1605
|
+
"- A session's own repo conventions (review flow, merge policy) always win \u2014 you coordinate,",
|
|
1606
|
+
" you don't override.",
|
|
1607
|
+
"- If a session awaits a human decision, surface the question to the human instead of answering",
|
|
1608
|
+
" in their place."
|
|
1609
|
+
].join("\n")
|
|
1610
|
+
};
|
|
1611
|
+
var WATCHDOG_LABEL = "Session Watchdog";
|
|
1612
|
+
var WATCHDOG_TICK_LABEL = "watchdog-tick";
|
|
1613
|
+
var WATCHDOG_TICK_SCHEDULE = "*/5 * * * *";
|
|
1614
|
+
var WATCHDOG_TICK_PROMPT = "Run your maintenance check now.";
|
|
1615
|
+
var watchdog = {
|
|
1616
|
+
agent: defineAgent({
|
|
1617
|
+
schema: "agent/v1",
|
|
1618
|
+
id: "@agentproto/session-watchdog",
|
|
1619
|
+
description: "Durable daemon-health watchdog \u2014 classifies sessions (needs-you, stalled, duplicated, abandonable), corrects flags, archives dead rows. Never kills or spawns.",
|
|
1620
|
+
model: "claude-sonnet-5",
|
|
1621
|
+
boundaries: [
|
|
1622
|
+
"NEVER kill a live session, message/interrupt another session's conversation, or spawn new sessions",
|
|
1623
|
+
"Housekeeping only on terminal-status sessions (exited/killed/error) \u2014 never touches a live one",
|
|
1624
|
+
"Report only what's NEW since the last tick \u2014 no padding, no repeats unless something got worse"
|
|
1625
|
+
],
|
|
1626
|
+
tools: [
|
|
1627
|
+
"session_list",
|
|
1628
|
+
"session_events_poll",
|
|
1629
|
+
"session_flag_status",
|
|
1630
|
+
"session_archive",
|
|
1631
|
+
"session_gc"
|
|
1632
|
+
]
|
|
1633
|
+
}),
|
|
1634
|
+
body: [
|
|
1635
|
+
"You are the Session Watchdog for this agentproto daemon. You run on a recurring cron tick",
|
|
1636
|
+
"(~every 5 minutes) as the SAME durable session, re-prompted each time \u2014 you retain memory of",
|
|
1637
|
+
"what you already flagged, so don't repeat an alert unless something got meaningfully worse or new.",
|
|
1638
|
+
"",
|
|
1639
|
+
"Each tick:",
|
|
1640
|
+
"1. Call `session_list` to see current session state (status, awaitingInput/awaitingQuestion,",
|
|
1641
|
+
" stalledSinceMs, busy, blockedOn, childrenBusy).",
|
|
1642
|
+
"2. Call `session_events_poll` for events since your last tick (stalled, exited, awaiting-input)",
|
|
1643
|
+
" to catch anything between ticks.",
|
|
1644
|
+
"3. Classify:",
|
|
1645
|
+
" - Needs you: `awaitingInput: true` with a real unanswered question, older than ~10 minutes.",
|
|
1646
|
+
" - Stalled: busy but silent well past normal turn latency, no legitimate blockedOn excuse.",
|
|
1647
|
+
" - Possibly duplicated: two or more live sessions share workspace/cwd and a highly similar",
|
|
1648
|
+
" recent prompt/label \u2014 use judgment, not exact string matching.",
|
|
1649
|
+
" - Looks done/abandonable: idle/parked with no plausible remaining purpose.",
|
|
1650
|
+
"4. Correct misclassifications when confident: if `session_flag_status` is available and you can",
|
|
1651
|
+
" tell a session is actually waiting on a human (or isn't, despite being flagged), call it with",
|
|
1652
|
+
" a clear `reason`. If that tool is unknown on this daemon build, skip silently.",
|
|
1653
|
+
"5. Autonomous housekeeping (reversible, so no need to ask): `session_archive` or `session_gc` on",
|
|
1654
|
+
" sessions already terminal-status (exited/killed/error) and old. Never touches a live session.",
|
|
1655
|
+
"6. NEVER: kill a live session, message/interrupt another session's conversation, or spawn new",
|
|
1656
|
+
" sessions. You only have these five tools on purpose \u2014 if you'd want another one, report the",
|
|
1657
|
+
" suggestion instead of working around it.",
|
|
1658
|
+
"7. Report only what's NEW since last tick \u2014 one line per finding. If nothing new, reply with a",
|
|
1659
|
+
` short "nothing new" \u2014 don't pad it.`,
|
|
1660
|
+
"",
|
|
1661
|
+
"You are not a general coding assistant across ticks \u2014 stay scoped to this watchdog role."
|
|
1662
|
+
].join("\n")
|
|
1663
|
+
};
|
|
1664
|
+
|
|
1665
|
+
// src/ops-panel/ui.ts
|
|
1666
|
+
var OPS_PANEL_TOOLS = [
|
|
1667
|
+
"daemon_health",
|
|
1668
|
+
"session_list",
|
|
1669
|
+
"agent_start",
|
|
1670
|
+
"agent_prompt",
|
|
1671
|
+
"agent_kill",
|
|
1672
|
+
"session_restart",
|
|
1673
|
+
"session_archive",
|
|
1674
|
+
"session_gc",
|
|
1675
|
+
"cron_list",
|
|
1676
|
+
"cron_create",
|
|
1677
|
+
"cron_delete",
|
|
1678
|
+
"cron_run",
|
|
1679
|
+
"worktree_gc"
|
|
1680
|
+
];
|
|
1681
|
+
var APP_ID3 = "@agentproto/ops-panel";
|
|
1682
|
+
var SPECIALS = [
|
|
1683
|
+
{
|
|
1684
|
+
key: "watchdog",
|
|
1685
|
+
label: WATCHDOG_LABEL,
|
|
1686
|
+
tagline: "Health ticks every 5 min \u2014 flags, archives, never kills.",
|
|
1687
|
+
prompt: watchdog.body ?? "",
|
|
1688
|
+
cron: {
|
|
1689
|
+
label: WATCHDOG_TICK_LABEL,
|
|
1690
|
+
schedule: WATCHDOG_TICK_SCHEDULE,
|
|
1691
|
+
prompt: WATCHDOG_TICK_PROMPT
|
|
1692
|
+
}
|
|
1693
|
+
},
|
|
1694
|
+
{
|
|
1695
|
+
key: "manager",
|
|
1696
|
+
label: MANAGER_LABEL,
|
|
1697
|
+
tagline: "You talk to it, it drives the other sessions.",
|
|
1698
|
+
prompt: manager.body ?? "",
|
|
1699
|
+
cron: null
|
|
1700
|
+
}
|
|
1701
|
+
];
|
|
1702
|
+
var OPS_PANEL_HTML = `<!DOCTYPE html>
|
|
1703
|
+
<html lang="en">
|
|
1704
|
+
<head>
|
|
1705
|
+
<meta charset="UTF-8">
|
|
1706
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1707
|
+
<title>Ops Panel</title>
|
|
1708
|
+
<style>
|
|
1709
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
1710
|
+
:root{
|
|
1711
|
+
--bg:#0d1117;--bg2:#161b22;--bg3:#21262d;--border:#30363d;
|
|
1712
|
+
--text:#e6edf3;--text2:#8b949e;--text3:#6e7681;
|
|
1713
|
+
--green:#3fb950;--yellow:#d29922;--red:#f85149;--blue:#58a6ff;--purple:#bc8cff;
|
|
1714
|
+
}
|
|
1715
|
+
html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:13px;background:var(--bg);color:var(--text)}
|
|
1716
|
+
#app{display:flex;flex-direction:column;height:100%;overflow:hidden}
|
|
1717
|
+
|
|
1718
|
+
#topbar{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--bg2);border-bottom:1px solid var(--border);flex-shrink:0}
|
|
1719
|
+
#topbar h1{font-size:13px;font-weight:700;letter-spacing:.02em}
|
|
1720
|
+
.chip{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:600;background:var(--bg3);border:1px solid var(--border);color:var(--text2);padding:3px 9px;border-radius:999px}
|
|
1721
|
+
.chip .d{width:7px;height:7px;border-radius:50%;background:var(--text3);flex:none}
|
|
1722
|
+
.chip.ok .d{background:var(--green)}
|
|
1723
|
+
.chip.bad .d{background:var(--red)}
|
|
1724
|
+
#ws-select{margin-left:auto;background:var(--bg3);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:11.5px;font-family:inherit}
|
|
1725
|
+
#refresh-btn{background:none;border:1px solid var(--border);cursor:pointer;color:var(--text2);font-size:13px;line-height:1;padding:4px 8px;border-radius:6px;font-family:inherit}
|
|
1726
|
+
#refresh-btn:hover{color:var(--text);background:var(--bg3)}
|
|
1727
|
+
|
|
1728
|
+
#body{flex:1;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:16px}
|
|
1729
|
+
.sect h2{font-size:11px;font-weight:700;color:var(--text2);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px;display:flex;align-items:center;gap:8px}
|
|
1730
|
+
.sect h2 .count{font-weight:600;color:var(--text3);text-transform:none;letter-spacing:0}
|
|
1731
|
+
|
|
1732
|
+
#specials{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:10px}
|
|
1733
|
+
.card{background:var(--bg2);border:1px solid var(--border);border-radius:10px;padding:12px}
|
|
1734
|
+
.card .ct{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:700}
|
|
1735
|
+
.card .tagline{font-size:11px;color:var(--text2);margin-top:3px}
|
|
1736
|
+
.card .meta{font-size:10.5px;color:var(--text3);margin-top:7px;display:flex;flex-wrap:wrap;gap:5px 10px}
|
|
1737
|
+
.card .cronline{font-size:10.5px;margin-top:7px;display:flex;align-items:center;gap:6px;color:var(--text2)}
|
|
1738
|
+
.card .actions{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}
|
|
1739
|
+
|
|
1740
|
+
.badge{display:inline-block;padding:1px 6px;border-radius:10px;font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.02em}
|
|
1741
|
+
.b-run{background:rgba(63,185,80,.15);color:var(--green)}
|
|
1742
|
+
.b-busy{background:rgba(63,185,80,.25);color:var(--green)}
|
|
1743
|
+
.b-wait{background:rgba(88,166,255,.15);color:var(--blue)}
|
|
1744
|
+
.b-idle{background:rgba(139,148,158,.12);color:var(--text2)}
|
|
1745
|
+
.b-end{background:rgba(139,148,158,.1);color:var(--text3)}
|
|
1746
|
+
.b-err{background:rgba(248,81,73,.15);color:var(--red)}
|
|
1747
|
+
.b-warn{background:rgba(210,153,34,.15);color:var(--yellow)}
|
|
1748
|
+
|
|
1749
|
+
button.act{background:var(--bg3);border:1px solid var(--border);color:var(--text);font-size:11px;font-weight:600;padding:4px 10px;border-radius:6px;cursor:pointer;font-family:inherit}
|
|
1750
|
+
button.act:hover{border-color:var(--text3)}
|
|
1751
|
+
button.act.primary{background:rgba(88,166,255,.15);border-color:rgba(88,166,255,.4);color:var(--blue)}
|
|
1752
|
+
button.act.danger{color:var(--red)}
|
|
1753
|
+
button.act.danger:hover{border-color:var(--red)}
|
|
1754
|
+
button.act.confirm{background:rgba(248,81,73,.2);border-color:var(--red);color:var(--red)}
|
|
1755
|
+
button.act:disabled{opacity:.45;cursor:default}
|
|
1756
|
+
|
|
1757
|
+
table{width:100%;border-collapse:collapse}
|
|
1758
|
+
th{font-size:10px;font-weight:700;color:var(--text3);text-transform:uppercase;letter-spacing:.04em;text-align:left;padding:4px 8px;border-bottom:1px solid var(--border)}
|
|
1759
|
+
td{font-size:11.5px;padding:6px 8px;border-bottom:1px solid var(--bg3);vertical-align:middle}
|
|
1760
|
+
tr:hover td{background:var(--bg2)}
|
|
1761
|
+
td .sn{font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:260px;display:block}
|
|
1762
|
+
td .sid{font-size:10px;color:var(--text3);font-family:Menlo,Monaco,monospace}
|
|
1763
|
+
.ws-tag{display:inline-block;font-size:10px;font-weight:600;background:var(--bg3);border:1px solid var(--border);border-radius:5px;padding:1px 6px;color:var(--text2)}
|
|
1764
|
+
td.actions{white-space:nowrap;text-align:right}
|
|
1765
|
+
td.actions button{margin-left:4px}
|
|
1766
|
+
|
|
1767
|
+
.hk{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:10px}
|
|
1768
|
+
.hk .card .nums{display:flex;gap:14px;margin-top:8px}
|
|
1769
|
+
.hk .num{display:flex;flex-direction:column}
|
|
1770
|
+
.hk .num b{font-size:17px;font-weight:700}
|
|
1771
|
+
.hk .num span{font-size:10px;color:var(--text3);text-transform:uppercase;letter-spacing:.04em}
|
|
1772
|
+
|
|
1773
|
+
#toast{position:fixed;bottom:14px;left:50%;transform:translateX(-50%);background:var(--bg3);border:1px solid var(--border);border-radius:8px;padding:8px 14px;font-size:11.5px;display:none;max-width:80%;z-index:10}
|
|
1774
|
+
#toast.err{border-color:var(--red);color:var(--red)}
|
|
1775
|
+
.empty{color:var(--text3);font-size:11.5px;padding:8px}
|
|
1776
|
+
</style>
|
|
1777
|
+
</head>
|
|
1778
|
+
<body>
|
|
1779
|
+
<div id="app">
|
|
1780
|
+
<div id="topbar">
|
|
1781
|
+
<h1>Agentproto Ops</h1>
|
|
1782
|
+
<span class="chip" id="daemon-chip"><span class="d"></span><span id="daemon-txt">daemon…</span></span>
|
|
1783
|
+
<select id="ws-select"><option value="">all workspaces</option></select>
|
|
1784
|
+
<button id="refresh-btn" title="Refresh" type="button">↻ refresh</button>
|
|
1785
|
+
</div>
|
|
1786
|
+
<div id="body">
|
|
1787
|
+
<div class="sect">
|
|
1788
|
+
<h2>Special sessions</h2>
|
|
1789
|
+
<div id="specials"></div>
|
|
1790
|
+
</div>
|
|
1791
|
+
<div class="sect">
|
|
1792
|
+
<h2>Sessions <span class="count" id="sess-count"></span></h2>
|
|
1793
|
+
<div id="sessions"></div>
|
|
1794
|
+
</div>
|
|
1795
|
+
<div class="sect">
|
|
1796
|
+
<h2>Housekeeping</h2>
|
|
1797
|
+
<div class="hk" id="housekeeping"></div>
|
|
1798
|
+
</div>
|
|
1799
|
+
<div class="sect">
|
|
1800
|
+
<h2>Crons <span class="count" id="cron-count"></span></h2>
|
|
1801
|
+
<div id="crons"></div>
|
|
1802
|
+
</div>
|
|
1803
|
+
</div>
|
|
1804
|
+
</div>
|
|
1805
|
+
<div id="toast"></div>
|
|
1806
|
+
<script>
|
|
1807
|
+
var callTool = null;
|
|
1808
|
+
var APP_ID = ${JSON.stringify(APP_ID3)};
|
|
1809
|
+
var SPECIALS = ${JSON.stringify(SPECIALS)};
|
|
1810
|
+
|
|
1811
|
+
// app_tool_call wraps its dispatch result at least once (its own text-result
|
|
1812
|
+
// body is the JSON-stringified inner MCP tool result); peel back through
|
|
1813
|
+
// nested {content:[{type:"text",text:...}]} shells until the payload stops
|
|
1814
|
+
// looking like one \u2014 same convention as session-viewer.
|
|
1815
|
+
function unwrapText(result) {
|
|
1816
|
+
var cur = result;
|
|
1817
|
+
for (var i = 0; i < 4; i++) {
|
|
1818
|
+
if (typeof cur === "string") {
|
|
1819
|
+
try { cur = JSON.parse(cur); continue; } catch (e) { return cur; }
|
|
1820
|
+
}
|
|
1821
|
+
if (cur && Array.isArray(cur.content) && cur.content[0] && typeof cur.content[0].text === "string") {
|
|
1822
|
+
cur = cur.content[0].text;
|
|
1823
|
+
continue;
|
|
1824
|
+
}
|
|
1825
|
+
break;
|
|
1826
|
+
}
|
|
1827
|
+
return cur;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
function callApp(tool, args) {
|
|
1831
|
+
return callTool("app_tool_call", { appId: APP_ID, tool: tool, args: args || {} }).then(unwrapText);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
function escHtml(s) {
|
|
1835
|
+
return String(s == null ? "" : s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
function fmtRelative(iso) {
|
|
1839
|
+
if (!iso) return "";
|
|
1840
|
+
var ms = Date.now() - new Date(iso).getTime();
|
|
1841
|
+
if (!isFinite(ms)) return "";
|
|
1842
|
+
var s = Math.round(ms / 1000);
|
|
1843
|
+
if (s < 5) return "just now";
|
|
1844
|
+
if (s < 60) return s + "s ago";
|
|
1845
|
+
var m = Math.round(s / 60);
|
|
1846
|
+
if (m < 60) return m + "m ago";
|
|
1847
|
+
var h = Math.round(m / 60);
|
|
1848
|
+
if (h < 24) return h + "h ago";
|
|
1849
|
+
return Math.round(h / 24) + "d ago";
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
var toastTimer = null;
|
|
1853
|
+
function toast(msg, isErr) {
|
|
1854
|
+
var el = document.getElementById("toast");
|
|
1855
|
+
el.textContent = msg;
|
|
1856
|
+
el.className = isErr ? "err" : "";
|
|
1857
|
+
el.style.display = "block";
|
|
1858
|
+
if (toastTimer) clearTimeout(toastTimer);
|
|
1859
|
+
toastTimer = setTimeout(function () { el.style.display = "none"; }, isErr ? 6000 : 3000);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
// Two-click confirm: first click arms the button, second within 4s fires.
|
|
1863
|
+
var armed = {};
|
|
1864
|
+
function confirmClick(key, btn, fn) {
|
|
1865
|
+
if (armed[key]) {
|
|
1866
|
+
clearTimeout(armed[key].t);
|
|
1867
|
+
delete armed[key];
|
|
1868
|
+
fn();
|
|
1869
|
+
return;
|
|
1870
|
+
}
|
|
1871
|
+
var prev = btn.textContent;
|
|
1872
|
+
btn.classList.add("confirm");
|
|
1873
|
+
btn.textContent = "sure?";
|
|
1874
|
+
armed[key] = { t: setTimeout(function () {
|
|
1875
|
+
delete armed[key];
|
|
1876
|
+
btn.classList.remove("confirm");
|
|
1877
|
+
btn.textContent = prev;
|
|
1878
|
+
}, 4000) };
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// ============================================================
|
|
1882
|
+
// state
|
|
1883
|
+
// ============================================================
|
|
1884
|
+
|
|
1885
|
+
var sessions = [];
|
|
1886
|
+
var crons = [];
|
|
1887
|
+
var wsFilter = "";
|
|
1888
|
+
var pollTimer = null;
|
|
1889
|
+
var busyActions = false;
|
|
1890
|
+
|
|
1891
|
+
function isLive(s) { return !!s && (s.status === "running" || s.status === "starting"); }
|
|
1892
|
+
|
|
1893
|
+
function displayBadge(s) {
|
|
1894
|
+
if (s.status === "running") {
|
|
1895
|
+
if (s.busy) return { label: "working", cls: "b-busy" };
|
|
1896
|
+
if (s.awaitingInput) return { label: "waiting", cls: "b-wait" };
|
|
1897
|
+
return { label: "idle", cls: "b-idle" };
|
|
1898
|
+
}
|
|
1899
|
+
if (s.status === "starting") return { label: "starting", cls: "b-wait" };
|
|
1900
|
+
if (s.status === "error" || s.status === "killed") return { label: s.status, cls: "b-err" };
|
|
1901
|
+
return { label: s.status || "exited", cls: "b-end" };
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
function labelOf(s) {
|
|
1905
|
+
return s.label || s.title || s.name || (s.command ? String(s.command).split("/").pop() : null) || s.id;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
function specialSessionFor(sp) {
|
|
1909
|
+
var best = null;
|
|
1910
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
1911
|
+
var s = sessions[i];
|
|
1912
|
+
if ((s.label || s.title) !== sp.label) continue;
|
|
1913
|
+
if (!best) { best = s; continue; }
|
|
1914
|
+
// Prefer a live one; among equals prefer the newest.
|
|
1915
|
+
if (isLive(s) && !isLive(best)) { best = s; continue; }
|
|
1916
|
+
if (isLive(s) === isLive(best) && String(s.startedAt) > String(best.startedAt)) best = s;
|
|
1917
|
+
}
|
|
1918
|
+
return best;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
function cronByLabel(label) {
|
|
1922
|
+
var hits = [];
|
|
1923
|
+
for (var i = 0; i < crons.length; i++) if (crons[i].label === label) hits.push(crons[i]);
|
|
1924
|
+
return hits;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
// ============================================================
|
|
1928
|
+
// data loading
|
|
1929
|
+
// ============================================================
|
|
1930
|
+
|
|
1931
|
+
function loadAll() {
|
|
1932
|
+
return Promise.all([
|
|
1933
|
+
callApp("session_list", { kind: "all" }).then(function (d) {
|
|
1934
|
+
sessions = (d && d.sessions) || (Array.isArray(d) ? d : []);
|
|
1935
|
+
}),
|
|
1936
|
+
callApp("cron_list", {}).then(function (d) {
|
|
1937
|
+
crons = Array.isArray(d) ? d : (d && d.jobs) || [];
|
|
1938
|
+
}).catch(function () { crons = []; }),
|
|
1939
|
+
]).then(function () {
|
|
1940
|
+
renderWorkspaces();
|
|
1941
|
+
renderSpecials();
|
|
1942
|
+
renderSessions();
|
|
1943
|
+
renderCrons();
|
|
1944
|
+
updateHousekeepingNums();
|
|
1945
|
+
});
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
function loadHealth() {
|
|
1949
|
+
return callApp("daemon_health", {}).then(function (d) {
|
|
1950
|
+
var chip = document.getElementById("daemon-chip");
|
|
1951
|
+
chip.className = "chip ok";
|
|
1952
|
+
var v = (d && (d.version || (d.build && d.build.sha) || d.status)) || "ok";
|
|
1953
|
+
document.getElementById("daemon-txt").textContent = "daemon " + String(v).slice(0, 16);
|
|
1954
|
+
}).catch(function (e) {
|
|
1955
|
+
var chip = document.getElementById("daemon-chip");
|
|
1956
|
+
chip.className = "chip bad";
|
|
1957
|
+
document.getElementById("daemon-txt").textContent = "daemon unreachable";
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// Housekeeping card is built ONCE (so the worktree plan text and armed
|
|
1962
|
+
// confirm buttons survive the 5s poll); only the counters re-render \u2014 see
|
|
1963
|
+
// updateHousekeepingNums. The worktree plan is a separate dry-run call, on
|
|
1964
|
+
// demand only (it walks git state \u2014 not something to hammer on every poll).
|
|
1965
|
+
function buildHousekeeping() {
|
|
1966
|
+
var el = document.getElementById("housekeeping");
|
|
1967
|
+
el.innerHTML =
|
|
1968
|
+
'<div class="card"><div class="ct">Sessions GC</div>'
|
|
1969
|
+
+ '<div class="nums" id="gc-nums"></div>'
|
|
1970
|
+
+ '<div class="actions">'
|
|
1971
|
+
+ '<button class="act" id="gc-archive">Archive >7d</button>'
|
|
1972
|
+
+ '<button class="act danger" id="gc-forget">Forget >30d</button>'
|
|
1973
|
+
+ '</div></div>'
|
|
1974
|
+
+ '<div class="card"><div class="ct">Worktrees</div>'
|
|
1975
|
+
+ '<div class="meta" id="wt-plan">No plan yet \u2014 run a dry run.</div>'
|
|
1976
|
+
+ '<div class="actions">'
|
|
1977
|
+
+ '<button class="act" id="wt-dry">Dry run</button>'
|
|
1978
|
+
+ '<button class="act danger" id="wt-apply" disabled>Reclaim now</button>'
|
|
1979
|
+
+ '</div></div>';
|
|
1980
|
+
|
|
1981
|
+
document.getElementById("gc-archive").addEventListener("click", function () {
|
|
1982
|
+
runAction("session_gc", { olderThanDays: 7 }, "GC: archived old terminal sessions");
|
|
1983
|
+
});
|
|
1984
|
+
var forgetBtn = document.getElementById("gc-forget");
|
|
1985
|
+
forgetBtn.addEventListener("click", function () {
|
|
1986
|
+
confirmClick("gc-forget", forgetBtn, function () {
|
|
1987
|
+
runAction("session_gc", { olderThanDays: 30, forget: true }, "GC: forgot >30d terminal sessions");
|
|
1988
|
+
});
|
|
1989
|
+
});
|
|
1990
|
+
document.getElementById("wt-dry").addEventListener("click", function () { worktreePlan(false); });
|
|
1991
|
+
var applyBtn = document.getElementById("wt-apply");
|
|
1992
|
+
applyBtn.addEventListener("click", function () {
|
|
1993
|
+
confirmClick("wt-apply", applyBtn, function () { worktreePlan(true); });
|
|
1994
|
+
});
|
|
1995
|
+
updateHousekeepingNums();
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
function updateHousekeepingNums() {
|
|
1999
|
+
var el = document.getElementById("gc-nums");
|
|
2000
|
+
if (!el) return;
|
|
2001
|
+
var terminal = 0, live = 0;
|
|
2002
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
2003
|
+
if (isLive(sessions[i])) live++;
|
|
2004
|
+
else terminal++;
|
|
2005
|
+
}
|
|
2006
|
+
el.innerHTML =
|
|
2007
|
+
'<div class="num"><b>' + live + '</b><span>live</span></div>'
|
|
2008
|
+
+ '<div class="num"><b>' + terminal + '</b><span>terminal</span></div>';
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
function worktreePlan(apply) {
|
|
2012
|
+
var el = document.getElementById("wt-plan");
|
|
2013
|
+
el.textContent = apply ? "reclaiming\\u2026" : "planning\\u2026";
|
|
2014
|
+
callApp("worktree_gc", apply ? { apply: true } : {}).then(function (d) {
|
|
2015
|
+
var entries = (d && (d.plan || d.entries || d.worktrees)) || [];
|
|
2016
|
+
var counts = { reclaim: 0, salvage: 0, hold: 0 };
|
|
2017
|
+
for (var i = 0; i < entries.length; i++) {
|
|
2018
|
+
var c = entries[i].classification || entries[i].class || entries[i].action;
|
|
2019
|
+
if (counts[c] != null) counts[c]++;
|
|
2020
|
+
}
|
|
2021
|
+
el.textContent = (apply ? "applied \u2014 " : "dry run \u2014 ")
|
|
2022
|
+
+ counts.reclaim + " reclaim / " + counts.salvage + " salvage / " + counts.hold + " hold"
|
|
2023
|
+
+ (entries.length === 0 ? " (no linked worktrees)" : "");
|
|
2024
|
+
document.getElementById("wt-apply").disabled = counts.reclaim === 0 || apply;
|
|
2025
|
+
if (apply) toast("Worktree GC applied");
|
|
2026
|
+
}).catch(function (e) {
|
|
2027
|
+
el.textContent = "worktree_gc: " + e.message;
|
|
2028
|
+
});
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
// ============================================================
|
|
2032
|
+
// rendering
|
|
2033
|
+
// ============================================================
|
|
2034
|
+
|
|
2035
|
+
function renderWorkspaces() {
|
|
2036
|
+
var sel = document.getElementById("ws-select");
|
|
2037
|
+
var seen = {};
|
|
2038
|
+
var slugs = [];
|
|
2039
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
2040
|
+
var w = sessions[i].workspaceSlug;
|
|
2041
|
+
if (w && !seen[w]) { seen[w] = true; slugs.push(w); }
|
|
2042
|
+
}
|
|
2043
|
+
slugs.sort();
|
|
2044
|
+
var cur = wsFilter;
|
|
2045
|
+
var html = '<option value="">all workspaces</option>';
|
|
2046
|
+
for (var j = 0; j < slugs.length; j++) {
|
|
2047
|
+
html += '<option value="' + escHtml(slugs[j]) + '"' + (slugs[j] === cur ? " selected" : "") + '>' + escHtml(slugs[j]) + "</option>";
|
|
2048
|
+
}
|
|
2049
|
+
sel.innerHTML = html;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
function renderSpecials() {
|
|
2053
|
+
var el = document.getElementById("specials");
|
|
2054
|
+
var html = "";
|
|
2055
|
+
for (var i = 0; i < SPECIALS.length; i++) {
|
|
2056
|
+
var sp = SPECIALS[i];
|
|
2057
|
+
var s = specialSessionFor(sp);
|
|
2058
|
+
var badge = s ? displayBadge(s) : { label: "not running", cls: "b-end" };
|
|
2059
|
+
var meta = "";
|
|
2060
|
+
if (s) {
|
|
2061
|
+
meta += "<span>" + escHtml(s.id) + "</span>";
|
|
2062
|
+
if (s.startedAt) meta += "<span>started " + escHtml(fmtRelative(s.startedAt)) + "</span>";
|
|
2063
|
+
if (s.costUsd != null) meta += "<span>$" + Number(s.costUsd).toFixed(2) + "</span>";
|
|
2064
|
+
}
|
|
2065
|
+
var cronline = "";
|
|
2066
|
+
if (sp.cron) {
|
|
2067
|
+
var jobs = cronByLabel(sp.cron.label);
|
|
2068
|
+
if (jobs.length === 0) {
|
|
2069
|
+
cronline = '<span class="badge b-end">no cron</span> tick not scheduled';
|
|
2070
|
+
} else {
|
|
2071
|
+
var job = jobs[0];
|
|
2072
|
+
var ok = !job.lastResult || job.lastResult.ok;
|
|
2073
|
+
var pointsAt = job.action && job.action.sessionId;
|
|
2074
|
+
var stale = s && isLive(s) && pointsAt && pointsAt !== s.id;
|
|
2075
|
+
cronline = '<span class="badge ' + (job.active ? (ok && !stale ? "b-run" : "b-warn") : "b-end") + '">'
|
|
2076
|
+
+ (job.active ? (ok && !stale ? "tick ok" : (stale ? "tick stale" : "tick failing")) : "tick off") + "</span> "
|
|
2077
|
+
+ escHtml(job.schedule) + (pointsAt ? " → " + escHtml(pointsAt) : "")
|
|
2078
|
+
+ (!ok && job.lastResult && job.lastResult.summary ? " · " + escHtml(String(job.lastResult.summary).slice(0, 60)) : "");
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
var actions = "";
|
|
2082
|
+
if (!s || !isLive(s)) {
|
|
2083
|
+
actions += '<button class="act primary" data-sp="' + sp.key + '" data-do="launch">Launch</button>';
|
|
2084
|
+
if (s) actions += '<button class="act" data-sp="' + sp.key + '" data-do="restart">Restart last</button>';
|
|
2085
|
+
} else {
|
|
2086
|
+
if (sp.cron) {
|
|
2087
|
+
actions += '<button class="act" data-sp="' + sp.key + '" data-do="tick">Tick now</button>';
|
|
2088
|
+
actions += '<button class="act" data-sp="' + sp.key + '" data-do="fixcron">Re-wire cron</button>';
|
|
2089
|
+
} else {
|
|
2090
|
+
actions += '<button class="act" data-sp="' + sp.key + '" data-do="tick">Ping</button>';
|
|
2091
|
+
}
|
|
2092
|
+
actions += '<button class="act danger" data-sp="' + sp.key + '" data-do="kill">Kill</button>';
|
|
2093
|
+
}
|
|
2094
|
+
html += '<div class="card">'
|
|
2095
|
+
+ '<div class="ct">' + escHtml(sp.label) + ' <span class="badge ' + badge.cls + '">' + escHtml(badge.label) + "</span></div>"
|
|
2096
|
+
+ '<div class="tagline">' + escHtml(sp.tagline) + "</div>"
|
|
2097
|
+
+ (meta ? '<div class="meta">' + meta + "</div>" : "")
|
|
2098
|
+
+ (cronline ? '<div class="cronline">' + cronline + "</div>" : "")
|
|
2099
|
+
+ '<div class="actions">' + actions + "</div>"
|
|
2100
|
+
+ "</div>";
|
|
2101
|
+
}
|
|
2102
|
+
el.innerHTML = html;
|
|
2103
|
+
var btns = el.querySelectorAll("button[data-sp]");
|
|
2104
|
+
for (var k = 0; k < btns.length; k++) {
|
|
2105
|
+
(function (btn) {
|
|
2106
|
+
btn.addEventListener("click", function () { specialAction(btn.getAttribute("data-sp"), btn.getAttribute("data-do"), btn); });
|
|
2107
|
+
})(btns[k]);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
function renderSessions() {
|
|
2112
|
+
var el = document.getElementById("sessions");
|
|
2113
|
+
var rows = [];
|
|
2114
|
+
for (var i = 0; i < sessions.length; i++) {
|
|
2115
|
+
var s = sessions[i];
|
|
2116
|
+
if (wsFilter && s.workspaceSlug !== wsFilter) continue;
|
|
2117
|
+
rows.push(s);
|
|
2118
|
+
}
|
|
2119
|
+
rows.sort(function (a, b) {
|
|
2120
|
+
var la = isLive(a) ? 1 : 0, lb = isLive(b) ? 1 : 0;
|
|
2121
|
+
if (la !== lb) return lb - la;
|
|
2122
|
+
return String(b.startedAt || "").localeCompare(String(a.startedAt || ""));
|
|
2123
|
+
});
|
|
2124
|
+
var liveCount = 0;
|
|
2125
|
+
for (var c = 0; c < rows.length; c++) if (isLive(rows[c])) liveCount++;
|
|
2126
|
+
document.getElementById("sess-count").textContent = liveCount + " live / " + rows.length + " shown";
|
|
2127
|
+
if (rows.length === 0) {
|
|
2128
|
+
el.innerHTML = '<div class="empty">No sessions' + (wsFilter ? " in " + escHtml(wsFilter) : "") + ".</div>";
|
|
2129
|
+
return;
|
|
2130
|
+
}
|
|
2131
|
+
var html = "<table><thead><tr><th>Session</th><th>Workspace</th><th>Status</th><th>Started</th><th>Cost</th><th></th></tr></thead><tbody>";
|
|
2132
|
+
var shown = rows.slice(0, 80);
|
|
2133
|
+
for (var j = 0; j < shown.length; j++) {
|
|
2134
|
+
var s2 = shown[j];
|
|
2135
|
+
var db = displayBadge(s2);
|
|
2136
|
+
var acts = "";
|
|
2137
|
+
if (isLive(s2)) {
|
|
2138
|
+
acts += '<button class="act danger" data-id="' + escHtml(s2.id) + '" data-do="kill">Kill</button>';
|
|
2139
|
+
} else {
|
|
2140
|
+
acts += '<button class="act" data-id="' + escHtml(s2.id) + '" data-do="restart">Restart</button>';
|
|
2141
|
+
if (!s2.archived) acts += '<button class="act" data-id="' + escHtml(s2.id) + '" data-do="archive">Archive</button>';
|
|
2142
|
+
}
|
|
2143
|
+
html += "<tr>"
|
|
2144
|
+
+ '<td><span class="sn" title="' + escHtml(labelOf(s2)) + '">' + escHtml(labelOf(s2)) + '</span><span class="sid">' + escHtml(s2.id) + "</span></td>"
|
|
2145
|
+
+ "<td>" + (s2.workspaceSlug ? '<span class="ws-tag">' + escHtml(s2.workspaceSlug) + "</span>" : "") + "</td>"
|
|
2146
|
+
+ '<td><span class="badge ' + db.cls + '">' + escHtml(db.label) + "</span></td>"
|
|
2147
|
+
+ "<td>" + escHtml(fmtRelative(s2.startedAt)) + "</td>"
|
|
2148
|
+
+ "<td>" + (s2.costUsd != null ? "$" + Number(s2.costUsd).toFixed(2) : "") + "</td>"
|
|
2149
|
+
+ '<td class="actions">' + acts + "</td>"
|
|
2150
|
+
+ "</tr>";
|
|
2151
|
+
}
|
|
2152
|
+
html += "</tbody></table>";
|
|
2153
|
+
if (rows.length > shown.length) html += '<div class="empty">… ' + (rows.length - shown.length) + " more (narrow with the workspace filter)</div>";
|
|
2154
|
+
el.innerHTML = html;
|
|
2155
|
+
var btns = el.querySelectorAll("button[data-id]");
|
|
2156
|
+
for (var k = 0; k < btns.length; k++) {
|
|
2157
|
+
(function (btn) {
|
|
2158
|
+
btn.addEventListener("click", function () { sessionAction(btn.getAttribute("data-id"), btn.getAttribute("data-do"), btn); });
|
|
2159
|
+
})(btns[k]);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
function renderCrons() {
|
|
2164
|
+
var el = document.getElementById("crons");
|
|
2165
|
+
document.getElementById("cron-count").textContent = crons.length ? crons.length + " jobs" : "";
|
|
2166
|
+
if (crons.length === 0) {
|
|
2167
|
+
el.innerHTML = '<div class="empty">No cron jobs.</div>';
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
var html = "<table><thead><tr><th>Label</th><th>Schedule</th><th>State</th><th>Last</th><th>Next</th><th></th></tr></thead><tbody>";
|
|
2171
|
+
for (var i = 0; i < crons.length; i++) {
|
|
2172
|
+
var c = crons[i];
|
|
2173
|
+
var last = c.lastResult == null ? "" : (c.lastResult.ok ? "ok" : "failed");
|
|
2174
|
+
var lastCls = c.lastResult == null ? "b-end" : (c.lastResult.ok ? "b-run" : "b-err");
|
|
2175
|
+
html += "<tr>"
|
|
2176
|
+
+ '<td><span class="sn">' + escHtml(c.label || c.id) + "</span></td>"
|
|
2177
|
+
+ "<td>" + escHtml(c.schedule) + "</td>"
|
|
2178
|
+
+ '<td><span class="badge ' + (c.active ? "b-run" : "b-end") + '">' + (c.active ? "active" : "off") + "</span></td>"
|
|
2179
|
+
+ "<td>" + (last ? '<span class="badge ' + lastCls + '" title="' + escHtml((c.lastResult && c.lastResult.summary) || "") + '">' + last + "</span>" : "") + "</td>"
|
|
2180
|
+
+ "<td>" + escHtml(c.nextRunAt ? fmtRelative(c.nextRunAt).replace(" ago", "") : "") + "</td>"
|
|
2181
|
+
+ '<td class="actions">'
|
|
2182
|
+
+ '<button class="act" data-cron="' + escHtml(c.id) + '" data-do="run">Run</button>'
|
|
2183
|
+
+ '<button class="act danger" data-cron="' + escHtml(c.id) + '" data-do="delete">Delete</button>'
|
|
2184
|
+
+ "</td></tr>";
|
|
2185
|
+
}
|
|
2186
|
+
html += "</tbody></table>";
|
|
2187
|
+
el.innerHTML = html;
|
|
2188
|
+
var btns = el.querySelectorAll("button[data-cron]");
|
|
2189
|
+
for (var k = 0; k < btns.length; k++) {
|
|
2190
|
+
(function (btn) {
|
|
2191
|
+
btn.addEventListener("click", function () { cronAction(btn.getAttribute("data-cron"), btn.getAttribute("data-do"), btn); });
|
|
2192
|
+
})(btns[k]);
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
// ============================================================
|
|
2197
|
+
// actions
|
|
2198
|
+
// ============================================================
|
|
2199
|
+
|
|
2200
|
+
function runAction(tool, args, okMsg) {
|
|
2201
|
+
if (busyActions) return Promise.resolve();
|
|
2202
|
+
busyActions = true;
|
|
2203
|
+
return callApp(tool, args).then(function (d) {
|
|
2204
|
+
busyActions = false;
|
|
2205
|
+
if (okMsg) toast(okMsg);
|
|
2206
|
+
return loadAll().then(function () { return d; });
|
|
2207
|
+
}).catch(function (e) {
|
|
2208
|
+
busyActions = false;
|
|
2209
|
+
toast(tool + ": " + e.message, true);
|
|
2210
|
+
throw e;
|
|
2211
|
+
});
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
function findSpecial(key) {
|
|
2215
|
+
for (var i = 0; i < SPECIALS.length; i++) if (SPECIALS[i].key === key) return SPECIALS[i];
|
|
2216
|
+
return null;
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
function rewireTickCron(sp, sessionId) {
|
|
2220
|
+
if (!sp.cron) return Promise.resolve();
|
|
2221
|
+
var stale = cronByLabel(sp.cron.label);
|
|
2222
|
+
var chain = Promise.resolve();
|
|
2223
|
+
for (var i = 0; i < stale.length; i++) {
|
|
2224
|
+
(function (jobId) {
|
|
2225
|
+
chain = chain.then(function () { return callApp("cron_delete", { jobId: jobId }); });
|
|
2226
|
+
})(stale[i].id);
|
|
2227
|
+
}
|
|
2228
|
+
return chain.then(function () {
|
|
2229
|
+
return callApp("cron_create", {
|
|
2230
|
+
schedule: sp.cron.schedule,
|
|
2231
|
+
recurring: true,
|
|
2232
|
+
label: sp.cron.label,
|
|
2233
|
+
action: { kind: "prompt-session", sessionId: sessionId, prompt: sp.cron.prompt },
|
|
2234
|
+
});
|
|
2235
|
+
});
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
function specialAction(key, what, btn) {
|
|
2239
|
+
var sp = findSpecial(key);
|
|
2240
|
+
if (!sp) return;
|
|
2241
|
+
var s = specialSessionFor(sp);
|
|
2242
|
+
if (what === "launch") {
|
|
2243
|
+
btn.disabled = true;
|
|
2244
|
+
callApp("agent_start", {
|
|
2245
|
+
adapter: "claude-code",
|
|
2246
|
+
label: sp.label,
|
|
2247
|
+
prompt: sp.prompt,
|
|
2248
|
+
keepAlive: true,
|
|
2249
|
+
}).then(function (d) {
|
|
2250
|
+
var id = d && d.id;
|
|
2251
|
+
toast(sp.label + " launched" + (id ? " (" + id + ")" : ""));
|
|
2252
|
+
return id && sp.cron ? rewireTickCron(sp, id) : null;
|
|
2253
|
+
}).then(function () { return loadAll(); })
|
|
2254
|
+
.catch(function (e) { toast("launch: " + e.message, true); btn.disabled = false; });
|
|
2255
|
+
return;
|
|
2256
|
+
}
|
|
2257
|
+
if (what === "restart" && s) {
|
|
2258
|
+
btn.disabled = true;
|
|
2259
|
+
callApp("session_restart", { idOrName: s.id }).then(function (d) {
|
|
2260
|
+
var id = (d && d.id) || s.id;
|
|
2261
|
+
toast(sp.label + " restarted");
|
|
2262
|
+
return sp.cron ? rewireTickCron(sp, id) : null;
|
|
2263
|
+
}).then(function () { return loadAll(); })
|
|
2264
|
+
.catch(function (e) { toast("restart: " + e.message, true); btn.disabled = false; });
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
if (what === "tick" && s) {
|
|
2268
|
+
var prompt = sp.cron ? sp.cron.prompt : "Status check: report where things stand, briefly.";
|
|
2269
|
+
runAction("agent_prompt", { sessionId: s.id, prompt: prompt }, sp.label + " prompted");
|
|
2270
|
+
return;
|
|
2271
|
+
}
|
|
2272
|
+
if (what === "fixcron" && s) {
|
|
2273
|
+
btn.disabled = true;
|
|
2274
|
+
rewireTickCron(sp, s.id).then(function () {
|
|
2275
|
+
toast("Cron re-wired to " + s.id);
|
|
2276
|
+
return loadAll();
|
|
2277
|
+
}).catch(function (e) { toast("cron: " + e.message, true); btn.disabled = false; });
|
|
2278
|
+
return;
|
|
2279
|
+
}
|
|
2280
|
+
if (what === "kill" && s) {
|
|
2281
|
+
confirmClick("kill-" + s.id, btn, function () {
|
|
2282
|
+
runAction("agent_kill", { sessionId: s.id }, sp.label + " killed");
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
function sessionAction(id, what, btn) {
|
|
2288
|
+
if (what === "kill") {
|
|
2289
|
+
confirmClick("kill-" + id, btn, function () {
|
|
2290
|
+
runAction("agent_kill", { sessionId: id }, "Killed " + id);
|
|
2291
|
+
});
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
if (what === "restart") { runAction("session_restart", { idOrName: id }, "Restarted " + id); return; }
|
|
2295
|
+
if (what === "archive") { runAction("session_archive", { idOrName: id }, "Archived " + id); }
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
function cronAction(jobId, what, btn) {
|
|
2299
|
+
if (what === "run") { runAction("cron_run", { jobId: jobId }, "Cron fired"); return; }
|
|
2300
|
+
if (what === "delete") {
|
|
2301
|
+
confirmClick("cron-" + jobId, btn, function () {
|
|
2302
|
+
runAction("cron_delete", { jobId: jobId }, "Cron deleted");
|
|
2303
|
+
});
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
// ============================================================
|
|
2308
|
+
// boot
|
|
2309
|
+
// ============================================================
|
|
2310
|
+
|
|
2311
|
+
document.getElementById("refresh-btn").addEventListener("click", function () { loadHealth(); loadAll(); });
|
|
2312
|
+
document.getElementById("ws-select").addEventListener("change", function (e) {
|
|
2313
|
+
wsFilter = e.target.value;
|
|
2314
|
+
renderSessions();
|
|
2315
|
+
});
|
|
2316
|
+
|
|
2317
|
+
window.McpApp.connect()
|
|
2318
|
+
.then(function (bridge) {
|
|
2319
|
+
callTool = bridge.callTool;
|
|
2320
|
+
buildHousekeeping();
|
|
2321
|
+
loadHealth();
|
|
2322
|
+
return loadAll();
|
|
2323
|
+
})
|
|
2324
|
+
.then(function () {
|
|
2325
|
+
pollTimer = setInterval(loadAll, 5000);
|
|
2326
|
+
})
|
|
2327
|
+
.catch(function (err) {
|
|
2328
|
+
document.getElementById("body").innerHTML =
|
|
2329
|
+
'<div class="empty">Standalone mode \\u2014 no host bridge (' + escHtml(err.message) + ")</div>";
|
|
2330
|
+
});
|
|
2331
|
+
</script>
|
|
2332
|
+
</body>
|
|
2333
|
+
</html>`;
|
|
2334
|
+
|
|
2335
|
+
// src/ops-panel/index.ts
|
|
2336
|
+
var opsPanel = defineApp({
|
|
2337
|
+
id: "@agentproto/ops-panel",
|
|
2338
|
+
name: "Ops Panel",
|
|
2339
|
+
version: "0.1.0",
|
|
2340
|
+
description: "Daemon ops cockpit \u2014 watchdog & manager sessions, workspace-filtered session list, GC and cron housekeeping.",
|
|
2341
|
+
agents: [watchdog, manager],
|
|
2342
|
+
ui: {
|
|
2343
|
+
html: OPS_PANEL_HTML,
|
|
2344
|
+
title: "Ops Panel",
|
|
2345
|
+
tools: [...OPS_PANEL_TOOLS]
|
|
2346
|
+
}
|
|
2347
|
+
});
|
|
1569
2348
|
var narrator = {
|
|
1570
2349
|
agent: defineAgent({
|
|
1571
2350
|
schema: "agent/v1",
|
|
@@ -1615,7 +2394,7 @@ var narrateSession = defineWorkflow({
|
|
|
1615
2394
|
|
|
1616
2395
|
// src/session-viewer/ui.ts
|
|
1617
2396
|
var SESSION_VIEWER_TOOLS = ["session_list", "conversation_read"];
|
|
1618
|
-
var
|
|
2397
|
+
var APP_ID4 = "@agentproto/session-viewer";
|
|
1619
2398
|
var SESSION_VIEWER_HTML = `<!DOCTYPE html>
|
|
1620
2399
|
<html lang="en">
|
|
1621
2400
|
<head>
|
|
@@ -1717,7 +2496,7 @@ html,body{height:100%;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sa
|
|
|
1717
2496
|
</div>
|
|
1718
2497
|
<script>
|
|
1719
2498
|
var callTool = null;
|
|
1720
|
-
var APP_ID = ${JSON.stringify(
|
|
2499
|
+
var APP_ID = ${JSON.stringify(APP_ID4)};
|
|
1721
2500
|
|
|
1722
2501
|
// app_tool_call wraps its dispatch result at least once (its own text-result
|
|
1723
2502
|
// body is the JSON-stringified inner MCP tool result); peel back through
|
|
@@ -2099,6 +2878,7 @@ var APPS = [
|
|
|
2099
2878
|
{ slug: "content-team", category: "team", handle: contentTeam },
|
|
2100
2879
|
{ slug: "mail-triage", category: "app", handle: mailTriage },
|
|
2101
2880
|
{ slug: "media-viewer", category: "app", handle: mediaViewer },
|
|
2881
|
+
{ slug: "ops-panel", category: "app", handle: opsPanel },
|
|
2102
2882
|
{ slug: "session-viewer", category: "app", handle: sessionViewer }
|
|
2103
2883
|
];
|
|
2104
2884
|
async function syncApps(baseDir) {
|