@dev-mainsequence/codex-plugin 0.4.2 → 0.7.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/.codex-plugin/plugin.json +3 -3
  2. package/.mcp.json +2 -1
  3. package/README.md +30 -4
  4. package/assets/mainsequence-mark.svg +453 -0
  5. package/build-info.json +3 -3
  6. package/dist/hosts/discovery.js +138 -24
  7. package/dist/hosts/messaging.js +63 -0
  8. package/dist/hosts/panel/panel.html +250 -125
  9. package/dist/hosts/workspace.js +107 -0
  10. package/dist/panel-server.js +115 -27
  11. package/dist/vendor/devtools/index.js +41 -1
  12. package/dist/vendor/devtools/package.json +1 -1
  13. package/dist/vendor/devtools/shared/actions.js +36 -7
  14. package/dist/vendor/devtools/shared/agentAssets.js +38 -0
  15. package/dist/vendor/devtools/shared/agentDelivery.js +477 -0
  16. package/dist/vendor/devtools/shared/agentIcon.js +43 -0
  17. package/dist/vendor/devtools/shared/agents.js +13 -6
  18. package/dist/vendor/devtools/shared/arguments.js +16 -9
  19. package/dist/vendor/devtools/shared/deployments.js +214 -0
  20. package/dist/vendor/devtools/shared/mcp.js +124 -25
  21. package/dist/vendor/devtools/shared/mcpTransport.js +64 -21
  22. package/dist/vendor/devtools/shared/organizationProjectWorkspace.js +8 -3
  23. package/dist/vendor/devtools/shared/projectTools.js +437 -0
  24. package/dist/vendor/devtools/shared/repositories.js +2 -2
  25. package/dist/vendor/devtools/shared/session.js +46 -15
  26. package/dist/vendor/devtools/shared/sharedTransport.js +506 -0
  27. package/dist/vendor/devtools/shared/stateDirectory.js +44 -0
  28. package/dist/vendor/devtools/shared/workingDirectory.js +13 -0
  29. package/dist/vendor/devtools/shared/workspaceSettings.js +96 -0
  30. package/docs/agents-tools-panel.md +523 -12
  31. package/docs/conversational-mcp.md +15 -2
  32. package/package.json +3 -3
  33. package/skills/mainsequence-mcp/SKILL.md +18 -3
  34. package/skills/ms/assets/mainsequence-mark.svg +453 -0
@@ -1,152 +1,277 @@
1
1
  <!doctype html>
2
2
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>MainSequence</title>
3
3
  <style>
4
- :root{color-scheme:light dark;font-family:var(--font-sans,system-ui,sans-serif);font-size:var(--font-text-md-size,14px);color:var(--color-text-primary,light-dark(#242424,#eee));background:var(--color-background-primary,light-dark(#fff,#212121))}*{box-sizing:border-box}body{margin:0}main{max-width:900px;margin:auto;padding:24px}header,.row{display:flex;align-items:center;gap:12px;flex-wrap:wrap}header{justify-content:space-between;margin-bottom:22px}h1{font-size:var(--font-heading-md-size,22px);font-weight:var(--font-weight-semibold,600);margin:0}h2{font-size:16px;margin:0 0 8px}p{line-height:1.55;margin:8px 0}.muted,small{color:var(--color-text-secondary,light-dark(#666,#aaa))}small{font-size:12px}button,input,select{font:inherit;color:inherit;border:1px solid var(--color-border-primary,light-dark(#d8d8d8,#555));border-radius:var(--border-radius-md,8px);background:var(--color-background-primary,light-dark(#fff,#212121));padding:9px 12px;min-height:38px}button{cursor:pointer}button:hover{background:var(--color-background-secondary,light-dark(#f4f4f4,#303030))}button:disabled{opacity:.5;cursor:default}button:focus-visible,input:focus-visible,select:focus-visible,summary:focus-visible{outline:2px solid var(--color-text-primary,light-dark(#242424,#eee));outline-offset:3px}.account{border:1px solid var(--color-border-secondary,light-dark(#e5e5e5,#444));border-radius:var(--border-radius-lg,12px);padding:16px;margin-bottom:20px}.account p:first-child{margin-top:0}.backend{font-size:12px;overflow-wrap:anywhere}label{display:block;font-size:12px;color:var(--color-text-secondary,light-dark(#666,#aaa));margin:14px 0 6px}select{width:100%}nav{display:flex;gap:6px;border-bottom:1px solid var(--color-border-secondary,light-dark(#e5e5e5,#444));margin-bottom:16px}nav button{border:0;background:transparent;border-radius:0;color:var(--color-text-secondary,light-dark(#666,#aaa));padding:10px 16px}nav button[aria-selected=true]{color:var(--color-text-primary,light-dark(#242424,#eee));border-bottom:2px solid currentColor}form{display:flex;gap:8px}input{min-width:0;flex:1}.notice{padding:12px 0;min-height:42px}.item{padding:18px 0;border-bottom:1px solid var(--color-border-secondary,light-dark(#e5e5e5,#444));overflow-wrap:anywhere}.item p{white-space:pre-wrap}.tag{font-size:11px;border:1px solid var(--color-border-secondary,light-dark(#ddd,#444));border-radius:var(--border-radius-full,999px);padding:3px 8px}.pager{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:20px 0}.empty{padding:30px 12px;text-align:center}.empty p{max-width:440px;margin:8px auto}details{margin-top:12px}summary{cursor:pointer}pre{white-space:pre-wrap;word-break:break-word;font-family:var(--font-mono,monospace);font-size:12px;padding:12px;background:var(--color-background-secondary,light-dark(#f5f5f5,#2c2c2c));border-radius:var(--border-radius-md,8px);max-height:320px;overflow:auto}footer{margin-top:24px;padding-top:12px;color:var(--color-text-secondary,light-dark(#666,#aaa));font-size:12px}[hidden]{display:none!important}@media(max-width:420px){main{padding:16px}header{gap:8px}form{flex-wrap:wrap}form button{width:100%}}@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto}}
5
- </style></head>
6
- <body><main>
7
- <header><div><h1>MainSequence</h1><small>Agents and Tools</small></div><button id="refresh" disabled>Refresh</button></header>
8
- <section class="account" aria-label="MainSequence account and environment">
9
- <p id="account">Connecting to Codex…</p><p class="backend muted" id="backend"></p><small id="session"></small>
10
- <label for="environment">Environment</label><select id="environment" disabled><option value="">Choose an environment</option></select>
11
- <p id="account-issue" class="muted" role="status"></p></section>
12
- <nav role="tablist" aria-label="MainSequence directories"><button id="agents-tab" role="tab" aria-selected="true" aria-controls="directory" tabindex="0">Agents</button><button id="tools-tab" role="tab" aria-selected="false" aria-controls="directory" tabindex="-1">Tools</button></nav>
13
- <section id="directory" role="tabpanel" aria-labelledby="agents-tab">
14
- <form id="search-form"><input id="search" type="search" aria-label="Search agents" placeholder="Search agents" maxlength="300" disabled><button id="search-button" disabled>Search</button></form>
15
- <p id="notice" class="notice muted" role="status" aria-live="polite">Loading account status…</p>
16
- <div id="results"></div><div id="pager" class="pager" hidden><button id="previous">Previous</button><small id="page"></small><button id="next">Next</button></div>
17
- </section><footer id="footer">Read-only discovery · Uses your existing MainSequence session</footer>
4
+ :root{--brand-image: none;color-scheme:light dark;font-family:var(--font-sans,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif);font-size:var(--font-text-sm-size,13px);line-height:1.5;color:var(--color-text-primary,light-dark(#242424,#eee));background:var(--color-background-primary,light-dark(#fff,#181818));--muted:var(--color-text-secondary,light-dark(#666,#aaa));--border:var(--color-border-secondary,light-dark(#e5e5e5,#353535));--surface:var(--color-background-secondary,light-dark(#f5f5f5,#242424));--ink:var(--color-text-primary,light-dark(#242424,#eee));--paper:var(--color-background-primary,light-dark(#fff,#181818))}*{box-sizing:border-box}body{margin:0}button,input,select,textarea{font:inherit;color:inherit}button{border:1px solid var(--border);border-radius:var(--border-radius-md,7px);background:transparent;padding:5px 10px;min-height:30px;cursor:pointer;white-space:nowrap}button:hover{background:var(--surface)}button:disabled{opacity:.45;cursor:default}button.primary,nav button[aria-selected=true]{background:var(--ink);color:var(--paper);border-color:transparent}button.primary:hover,nav button[aria-selected=true]:hover{opacity:.85}button.plain{border-color:transparent;padding:4px 6px}button.name{text-align:left;white-space:normal;padding:0;border:0;min-height:24px;font-weight:600;background:none}button.name:hover{text-decoration:underline;text-underline-offset:3px}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,summary:focus-visible{outline:2px solid var(--ink);outline-offset:3px}input,select,textarea{border:1px solid var(--border);border-radius:var(--border-radius-md,7px);background:transparent;padding:6px 9px;min-height:32px;min-width:0}select{max-width:100%}input{flex:1}textarea{width:100%;resize:vertical;min-height:75px;line-height:1.5}label,small,.caption{font-size:12px;color:var(--muted)}h1,h2,h3{overflow-wrap:anywhere}h1{font-size:15px;font-weight:600;letter-spacing:-.2px;margin:0}h2{font-size:15px;font-weight:600;margin:0}h3{font-size:13px;margin:0;font-weight:600}p{margin:4px 0;overflow-wrap:anywhere}.muted{color:var(--muted)}.stack{display:flex;flex-direction:column;gap:10px}.row{display:flex;align-items:center;gap:8px;min-width:0}.wrap{flex-wrap:wrap}.between{justify-content:space-between}.grow{flex:1;min-width:0}.brand{gap:8px}.brand-mark{width:23px;height:23px;background:var(--ink);mask:var(--brand-image) center/contain no-repeat;-webkit-mask:var(--brand-image) center/contain no-repeat}.account-row{gap:8px;flex-wrap:wrap;margin-top:10px}.account-row select{max-width:190px;font-size:12px;min-height:28px;padding:3px 7px}.account-name{font-size:12px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.account-name:before{content:'●';font-size:7px;vertical-align:2px;margin-right:6px}.shell{max-width:920px;margin:auto;min-height:100vh;display:flex;flex-direction:column}.header{position:sticky;top:0;z-index:2;background:var(--paper);padding:16px 16px 0}nav{display:flex;gap:3px;flex-wrap:wrap;padding:12px 0;border-bottom:1px solid var(--border)}nav button{border:0;padding:5px 10px;color:var(--muted)}.content{padding:16px;flex:1}.toolbar{display:flex;align-items:center;gap:8px;margin-bottom:10px}.toolbar input{width:100%}.notice:empty{display:none}.notice{margin:0 0 12px;color:var(--muted);font-size:12px}.item{padding:10px 0;border-bottom:1px solid var(--border);min-width:0}.item:last-child{border-bottom:0}.item .caption{margin-top:1px}.agent-icon{width:22px;height:22px;flex-shrink:0;object-fit:contain;align-self:flex-start;margin-top:2px}.agent-icon.mask{background:var(--ink);mask:var(--agent-image) center/contain no-repeat;-webkit-mask:var(--agent-image) center/contain no-repeat}.agent-icon.large{width:34px;height:34px}.agent-placeholder{font-size:22px;line-height:22px;color:var(--muted);text-align:center}.detail-heading{flex-wrap:wrap;margin:12px 0 16px}.detail-heading h2{font-size:18px}.detail-section{margin:18px 0}.detail-section h3{margin-bottom:8px}.facts{display:grid;grid-template-columns:auto 1fr;gap:5px 18px;margin:12px 0;font-size:12px}.facts dt{color:var(--muted)}.facts dd{margin:0;text-align:right;overflow-wrap:anywhere}.card{border:1px solid var(--border);border-radius:var(--border-radius-lg,10px);padding:12px;margin-bottom:12px}.card>h2,.card>h3{margin-bottom:9px}.empty{padding:26px 4px}.empty h2{margin-bottom:5px}.pager{display:flex;justify-content:space-between;align-items:center;margin-top:12px;font-size:12px}.pager button{border:0;color:var(--muted)}details{margin-top:8px}summary{cursor:pointer;color:var(--muted);font-size:12px}pre{white-space:pre-wrap;overflow-wrap:anywhere;font:12px/1.5 var(--font-mono,ui-monospace,monospace);padding:10px;background:var(--surface);border-radius:7px;max-height:280px;overflow:auto}code{font-family:var(--font-mono,ui-monospace,monospace);font-size:.92em}.markdown p{white-space:pre-wrap;margin:8px 0}.markdown ul{padding-left:20px;margin:8px 0}.markdown h3{margin-top:14px}.example{white-space:normal;text-align:left;width:100%;margin-top:6px}.composer{position:sticky;bottom:0;background:var(--paper);border-top:1px solid var(--border);padding:12px 16px;z-index:1}.composer .row{margin-bottom:8px}.composer>.row:last-child{flex-wrap:wrap;gap:6px}.composer>.row:last-child>.row{margin:0 0 0 auto}.composer textarea{border:0;background:var(--surface);margin-bottom:8px}.composer .caption{margin:2px 0 0}.selected-label{font-size:11px;color:var(--muted)}#delivery-notice{margin:0;font-size:12px}#delivery-notice:not(:empty){padding:8px 16px;border-top:1px solid var(--border)}.receipt{white-space:pre-wrap;overflow-wrap:anywhere;border-left:2px solid var(--border);padding-left:10px;margin-top:8px}.review{padding:12px;border:1px solid var(--ink);border-radius:8px;margin:12px 0}.result-label{font-size:12px;color:var(--muted)}footer{padding:0 16px 12px;color:var(--muted);font-size:11px}#deliveries{padding:0 16px 16px}#deliveries>summary{padding:8px 0}#account-issue:empty{display:none}#account-issue{font-size:12px;padding-top:8px}#confirmation:empty{display:none}[hidden]{display:none!important}@media(max-width:400px){.header{padding:12px 12px 0}.content{padding:12px}.composer{padding:12px}nav{gap:0}nav button{padding:5px 8px}.account-row select{max-width:160px}.toolbar{gap:4px}.toolbar button{padding:5px 7px}.facts{gap:5px 10px}}input[type=checkbox]{flex:none;width:16px;min-height:16px;height:16px;margin:0;accent-color:var(--ink)}@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto}}
5
+
6
+ :root{font-size:var(--font-text-sm-size,14px)}.header{padding:20px 20px 0}.content{padding:8px 20px 20px}.brand h1{font-size:16px}.brand-mark{width:24px;height:24px}.account-row{margin-top:12px;gap:8px}.account-row select,.account-name{font-size:13px}.account-row select{width:108px}.account-row{gap:6px}.utility{margin-left:auto;gap:4px}nav{border-bottom:0;gap:4px;padding:10px 0 0}.item{border:0;padding:9px 0}.item h3,button.name{font-size:14px}.caption,small{font-size:13px}.field{display:flex;flex-direction:column;gap:5px;margin:10px 0}.field input,.field select{width:100%}.selected{background:var(--surface)}.card .row{flex-wrap:wrap}.card{margin:10px 0 14px;padding:14px}.detail-heading{margin:8px 0}.detail-heading h2{font-size:16px}#results>h2{margin:10px 0}#results>h3{margin-top:16px}.toolbar{margin:0 0 6px}#footer{display:none}.shell{max-width:none}.account-name{overflow-wrap:anywhere;white-space:normal}.review p{white-space:pre-wrap}
7
+ .account-name[data-status=connected]:before{color:var(--color-text-success,light-dark(#16803d,#40c977))}.account-name[data-status=error]:before{color:var(--color-text-danger,light-dark(#b42318,#ff6764))}.account-row select{width:auto;min-width:132px;max-width:220px}
8
+ @media(max-width:400px){.header{padding:14px 12px 0}.content{padding:8px 12px 16px}.account-row{gap:4px}.utility{margin-left:0}.account-name{font-size:12px}.account-row select{max-width:180px}nav button{padding:5px 7px}.toolbar{flex-wrap:wrap}button{max-width:100%;white-space:normal}.row.between{flex-wrap:wrap}.card{padding:10px}}
9
+ </style></head><body>
10
+ <main class="shell">
11
+ <header class="header">
12
+ <div class="row between"><div class="row brand"><span id="brand-mark" class="brand-mark" aria-hidden="true"></span><h1>MainSequence</h1></div></div>
13
+ <div class="row account-row"><span id="account" class="account-name">Connecting…</span><select id="environment" aria-label="Environment" disabled><option value="">Choose environment</option></select><div class="row utility"><button id="activity-tab" class="plain muted" aria-label="Activity">Activity</button><button id="settings-tab" class="plain muted" aria-label="Settings">Settings</button></div></div>
14
+ <p id="account-issue" role="status" class="muted"></p>
15
+ <nav role="tablist" aria-label="MainSequence"><button id="home-tab" role="tab">Home</button><button id="code-tab" role="tab">Code</button><button id="projects-tab" role="tab">Projects</button><button id="agents-tab" role="tab" aria-selected="true">Agents</button><button id="tools-tab" role="tab">Tools</button></nav>
16
+ </header>
17
+ <section class="content" id="directory" role="tabpanel" aria-labelledby="agents-tab">
18
+ <div id="confirmation" role="region" aria-label="Review action"></div>
19
+ <div id="list-toolbar" class="toolbar"><form id="search-form" class="row grow"><input id="search" type="search" aria-label="Search agents" placeholder="Search agents" maxlength="300" disabled><button id="search-button" class="plain" disabled>Search</button></form><div id="code-filters" class="row" hidden><button id="code-all" class="plain" type="button">All</button><button id="code-local" class="plain" type="button">Local</button></div><button id="refresh" class="plain muted" disabled>Refresh</button></div>
20
+ <p id="notice" class="notice" role="status" aria-live="polite">Loading account status…</p><div id="results"></div>
21
+ <div id="pager" class="pager" hidden><button id="previous">Previous</button><small id="page"></small><button id="next">Next</button></div>
22
+ </section>
23
+ <details id="deliveries" hidden><summary>Messages and recovery</summary><p class="caption">Recover the saved request after Stop. A saved reply is returned without another send.</p><button id="refresh-deliveries" class="plain muted">Refresh messages</button><div id="delivery-results"></div></details>
24
+ <footer id="footer">Shared MainSequence session</footer>
25
+ <div id="delivery-notice" role="status" aria-live="polite" class="muted"></div>
26
+ <section id="composer" class="composer" hidden aria-label="Selected MainSequence agent">
27
+ <div class="row between"><div class="grow"><div class="selected-label">Talking to</div><h3 id="recipient"></h3></div><button id="switch-agent" class="plain muted">Switch agent</button><button id="close-composer" class="plain muted">End</button></div>
28
+ <label id="response-kind-field" class="row" hidden>Response <select id="response-kind" aria-label="Agent response mode"></select></label>
29
+ <textarea id="message" aria-label="Message to selected agent" maxlength="64000" placeholder="Message this agent…"></textarea>
30
+ <div class="row between"><small>Replies stay in this Codex chat</small><div class="row"><button id="draft-message" class="plain">Draft</button><button id="send-message" class="primary">Send in Codex</button></div></div>
31
+ </section>
18
32
  </main>
19
33
  <script type="module">
20
34
  const BUILD = /* MAINSEQUENCE_PANEL_BUILD */ null;
21
35
  const $ = id => document.getElementById(id), pending = new Map();
22
- let sequence = 0, generation = 0, connected = false, busy = false, snapshot = null, catalog = 'agents', offset = 0, currentPage = null;
36
+ const sections = ['home', 'code', 'projects', 'agents', 'tools', 'activity', 'settings'];
37
+ let preparationGeneration=0, recipientKinds=[];
38
+ let recipient = null, draftRequest = null, messageBusy = false, deliveryGeneration = 0, savedRequests = [];
39
+ let sequence = 0, generation = 0, connected = false, busy = false, snapshot = null, catalog = 'agents', offset = 0, currentPage = null, addingTools = false;
40
+ const assetCache = new Map(), toolCache = new Map(), activity = [];
41
+ // Agent icons change in Command Center without any event reaching the panel. While the Agents list or an
42
+ // agent's details stay on screen, the shown icons are re-read once a minute (the shared transport keeps a
43
+ // directory read for 30 s, so this is a fresh read without invalidating other panels' caches) and only a
44
+ // changed icon is redrawn, in place. The timer ends with the screen and pauses after 240 re-checks.
45
+ const ICON_RECHECK_MS = 60000, ICON_RECHECK_LIMIT = 240, drawnIcons = new Map();
46
+ let iconWatch = null, iconChecks = 0;
47
+ function watchIcons(run, load) {
48
+ clearTimeout(iconWatch);
49
+ iconWatch = setTimeout(async () => {
50
+ if (!connected || run !== generation || iconChecks >= ICON_RECHECK_LIMIT) return;
51
+ iconChecks++;
52
+ try {
53
+ const current = await load();
54
+ if (run !== generation) return;
55
+ for (const { uid, icon: next } of current) {
56
+ if (JSON.stringify(next ?? null) === JSON.stringify(assetCache.get(uid) ?? null)) continue;
57
+ if (next) assetCache.set(uid, next); else assetCache.delete(uid);
58
+ const drawn = drawnIcons.get(uid) || new Set();
59
+ for (const entry of [...drawn]) { drawn.delete(entry); entry.el.replaceWith(icon(uid, entry.large)); }
60
+ }
61
+ } catch { /* A failed re-check keeps the drawn icons; the next one tries again. */ }
62
+ if (run === generation) watchIcons(run, load);
63
+ }, ICON_RECHECK_MS);
64
+ }
65
+ const environmentKey = state => state?.account?.checked ? 'mainsequence.environment.' + JSON.stringify([state.account.backend,state.account.username]) : null;
66
+ function rememberedEnvironment(state) {try {const key=environmentKey(state);return key ? localStorage.getItem(key) : null;} catch {return null;}}
67
+ function rememberEnvironment() {try {const key=environmentKey(snapshot),uid=$('environment').value;if(key && snapshot.environments.some(e=>e.uid===uid))localStorage.setItem(key,uid);} catch {}}
68
+ function accountIndicator(label,status='pending') {
69
+ const account=$('account');account.textContent=label;account.setAttribute('data-status',status);
70
+ const description=status==='connected'?'Connected':status==='error'?'Connection needs attention':'Connection not verified';
71
+ account.setAttribute('title',description);account.setAttribute('aria-label',description+': '+label);
72
+ }
23
73
  function rpc(method, params = {}) {
24
74
  const id = ++sequence;
25
- return new Promise((resolve, reject) => {
26
- const timeout = setTimeout(() => { pending.delete(id); reject(new Error('Codex did not respond. Refresh to try again.')); }, 125000);
27
- pending.set(id, { resolve, reject, timeout });
28
- parent.postMessage({ jsonrpc: '2.0', id, method, params }, '*');
29
- });
75
+ return new Promise((resolve, reject) => { const timeout = setTimeout(() => { pending.delete(id); reject(new Error('Codex did not respond. Refresh to try again.')); }, 125000); pending.set(id, { resolve, reject, timeout }); parent.postMessage({ jsonrpc: '2.0', id, method, params }, '*'); });
30
76
  }
31
77
  function styles(context = {}) {
32
78
  for (const [key, value] of Object.entries(context.styles?.variables || {})) if (/^--[a-zA-Z0-9_-]+$/.test(key) && typeof value === 'string') document.documentElement.style.setProperty(key, value);
33
79
  if (['light', 'dark'].includes(context.theme)) document.documentElement.style.colorScheme = context.theme;
34
80
  }
35
- function controls() {
36
- const ready = connected && snapshot?.contextId && $('environment').value;
37
- $('refresh').disabled = !connected || busy;
38
- $('environment').disabled = !snapshot?.contextId;
39
- $('search').disabled = !ready; $('search-button').disabled = !ready || busy;
40
- $('previous').disabled = busy || offset === 0;
41
- $('next').disabled = busy || !currentPage || currentPage.semantic || offset + currentPage.results.length >= currentPage.count || !currentPage.results.length;
42
- $('directory').setAttribute('aria-busy', String(busy));
43
- }
44
- function clearResults() { $('results').replaceChildren(); $('pager').hidden = true; currentPage = null; }
45
81
  function node(tag, value, cls) { const item = document.createElement(tag); if (value !== undefined) item.textContent = value; if (cls) item.className = cls; return item; }
46
- function failure(error) {
47
- $('notice').textContent = error.message;
48
- if (['CONTEXT_CHANGED', 'AUTH_REJECTED', 'NOT_AUTHENTICATED'].includes(error.code)) { snapshot = null; $('environment').value = ''; clearResults(); $('account').textContent = 'Session needs refreshing'; }
82
+ function append(parent,...children) {parent.append(...children.filter(Boolean));}
83
+ function group(cls, ...children) { const e = node('div', undefined, cls); e.append(...children.filter(Boolean)); return e; }
84
+ function button(label, fn, cls = 'plain') { const b = node('button', label, cls); b.onclick = async (...args) => { try { return await fn(...args); } catch(error) { failure(error); } }; return b; }
85
+ function card(title, ...children) { return group('card', node('h2', title), ...children); }
86
+ function caption(text) { return node('p', text, 'caption'); }
87
+ function empty(title, detail) { return group('empty', node('h2', title), caption(detail)); }
88
+ function facts(values) { const dl = node('dl', undefined, 'facts'); for (const [k, v] of values) if (v) dl.append(node('dt', k), node('dd', v)); return dl; }
89
+ function details(label, content) { const d = node('details'); d.append(node('summary', label), content); return d; }
90
+ function controls() { const ready = connected && snapshot?.contextId && $('environment').value; $('refresh').disabled = !connected || busy; $('environment').disabled = !snapshot?.contextId; $('search').disabled = !ready; $('search-button').disabled = !ready || busy; $('previous').disabled = busy || offset === 0; $('next').disabled = busy || !currentPage || currentPage.semantic || offset + currentPage.results.length >= currentPage.count || !currentPage.results.length; $('directory').setAttribute('aria-busy', String(busy)); for (const id of ['send-message', 'draft-message']) $(id).disabled = messageBusy || !recipient || !ready; $('message').disabled = messageBusy; $('response-kind').disabled = messageBusy || !recipient || recipientKinds.length < 2 || Boolean(recipient.taskRequestId); $('refresh-deliveries').disabled = !connected || !snapshot?.messagingAvailable; }
91
+ function clearResults() { clearTimeout(iconWatch); drawnIcons.clear(); $('code-filters').hidden=catalog!=='code';$('search-button').hidden=catalog==='code'; $('results').replaceChildren(); $('pager').hidden = true; currentPage = null; $('confirmation').replaceChildren(); }
92
+ function clearComposer() { ++preparationGeneration; recipient = null; recipientKinds=[]; draftRequest = null; $('response-kind-field').hidden=true; $('composer').hidden = true; $('message').value = ''; }
93
+ function errorText(error) { return [error.result && error.code ? error.code + ': ' + error.message : error.message, error.effects?.message, error.result ? JSON.stringify(error.result.structuredContent || error.result.content || error.result, null, 2) : ''].filter(Boolean).join('\n'); }
94
+ function failure(error) { $('notice').textContent = errorText(error); if (['CONTEXT_CHANGED', 'AUTH_REJECTED', 'NOT_AUTHENTICATED'].includes(error.code)) { snapshot = null; workspaceContext=null;operationCache=[];clearComposer(); ++deliveryGeneration; savedRequests = []; assetCache.clear(); $('deliveries').hidden = true; $('delivery-results').replaceChildren(); $('environment').value = ''; clearResults(); accountIndicator('Session needs refreshing','error'); } }
95
+ async function call(name, args = {}) { const output = await rpc('tools/call', { name, arguments: args }); const data = output.structuredContent || JSON.parse(output.content?.find(c => c.type === 'text')?.text || '{}'); if (output.isError || data.error) throw Object.assign(new Error(data.error?.message || 'MainSequence could not complete this action.'), { code: data.error?.code, result: data.result, effects: data.effects }); if (!BUILD || data.build?.panelId !== BUILD.panelId) throw Object.assign(new Error('The panel and server versions differ. Reopen MainSequence from its newest app card.'), { code: 'CONTEXT_CHANGED' }); return data; }
96
+ function selection() { return { contextId: snapshot.contextId, environmentUid: $('environment').value }; }
97
+ const environmentName = () => snapshot?.environments.find(e => e.uid === $('environment').value)?.name || 'this environment';
98
+ async function view(kind, args = {}, target = selection()) { return call('mainsequence_view', { ...target, view: kind, ...args }); }
99
+ function stateLabel(request) { if(request.task) return 'Task · '+(request.task.status || (request.task.terminal ? 'finished' : 'accepted')); return { pending: 'Waiting for Codex', preparing: 'Preparing agent', sending: 'Waiting for reply', delivered: 'Reply received', failed: 'Could not send', uncertain: 'Delivery unknown · resend blocked', cancelled: 'Cancelled', expired: 'Expired' }[request.state] || request.state; }
100
+ function latestAgent(uid, env) { return [...savedRequests].reverse().find(r => r.agentUid === uid && r.environmentUid === env); }
101
+ function defaults(agent) { return [agent.defaults?.provider, agent.defaults?.model, agent.defaults?.thinking].filter(Boolean).join(' · '); }
102
+ function icon(uid, large = false) { const e = drawIcon(uid, large); if (!drawnIcons.has(uid)) drawnIcons.set(uid, new Set()); drawnIcons.get(uid).add({ el: e, large }); return e; }
103
+ function drawIcon(uid, large = false) { const asset = assetCache.get(uid), reduced = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches; const source = asset && (reduced ? asset.stillSource : asset.source); if (source && /^data:image\/(png|webp|svg\+xml);base64,[A-Za-z0-9+/=]+$/.test(source)) { const e = node(asset.rendering === 'mask' ? 'span' : 'img', undefined, 'agent-icon' + (large ? ' large' : '') + (asset.rendering === 'mask' ? ' mask' : '')); if (asset.rendering === 'mask') e.style.setProperty('--agent-image', 'url("' + source + '")'); else { e.src = source; e.alt = ''; } e.style.imageRendering = asset.imageRendering; e.setAttribute('aria-hidden', 'true'); return e; } const e = node('span', '◇', 'agent-icon agent-placeholder' + (large ? ' large' : '')); e.setAttribute('aria-hidden', 'true'); return e; }
104
+ // A small Markdown subset; every content node is text. Remote HTML and links never execute.
105
+ function markdown(value) { const root = node('div', undefined, 'markdown'); let list; for (const line of String(value || '').split('\n')) { if (/^[-*] /.test(line)) { if (!list) { list = node('ul'); root.append(list); } list.append(node('li', line.slice(2))); } else { list = null; root.append(node(/^#{1,6} /.test(line) ? 'h3' : 'p', line.replace(/^#{1,6} /, ''))); } } return root; }
106
+ function responseKinds(kinds, selected) {
107
+ recipientKinds = ['message','task'].filter(kind=>kinds.includes(kind));
108
+ $('response-kind').replaceChildren(...recipientKinds.map(kind=>{const option=node('option',kind==='task'?'Task':'Message');option.value=kind;return option;}));
109
+ $('response-kind').value=selected; $('response-kind-field').hidden=recipientKinds.length===0;
49
110
  }
50
- async function call(name, args = {}) {
51
- const output = await rpc('tools/call', { name, arguments: args });
52
- const data = output.structuredContent || JSON.parse(output.content?.find(c => c.type === 'text')?.text || '{}');
53
- if (output.isError || data.error) throw Object.assign(new Error(data.error?.message || 'MainSequence could not complete this read.'), { code: data.error?.code });
54
- if (!BUILD || data.build?.panelId !== BUILD.panelId) throw Object.assign(new Error('The panel and server versions differ. Reopen MainSequence from its newest app card.'), { code: 'CONTEXT_CHANGED' });
55
- return data;
111
+ async function talk(agent, target) { if (messageBusy) return; const preserved = $('message').value;
112
+ const same=recipient?.agentUid===agent.uid && recipient?.environmentUid===target.environmentUid;
113
+ const kind=same && agent.responseKinds?.includes(recipient.responseKind) ? recipient.responseKind : agent.responseKinds?.includes('message') ? 'message' : 'task';
114
+ if(!same) draftRequest=null;
115
+ recipient = { ...target, agentUid: agent.uid, name: agent.name, responseKind:kind, ...((conversationKeys.get(agent.uid)||latestAgent(agent.uid,target.environmentUid)?.conversationKey) ? {conversationKey:conversationKeys.get(agent.uid)||latestAgent(agent.uid,target.environmentUid)?.conversationKey} : {}) };
116
+ responseKinds(agent.responseKinds || [],kind); $('recipient').textContent = agent.name; $('composer').hidden = false; $('message').value = preserved; controls(); $('message').focus();
117
+ const selected={...recipient}; delete selected.name;delete selected.responseKind;delete selected.taskRequestId;const run=++preparationGeneration;$('delivery-notice').textContent='Preparing '+agent.name+'…';
118
+ try{await call('mainsequence_agent_prepare',selected);if(run===preparationGeneration && recipient?.agentUid===agent.uid)$('delivery-notice').textContent='Agent ready · no message sent';}catch(error){if(run===preparationGeneration)$('delivery-notice').textContent=error.message;}
56
119
  }
120
+ async function continueTask(request) {
121
+ if(messageBusy)return;
122
+ if(!snapshot?.environments.some(env=>env.uid===request.environmentUid)){failure(new Error('This task’s environment is no longer available. Refresh your account.'));return;}
123
+ if($('environment').value!==request.environmentUid){$('environment').value=request.environmentUid;await $('environment').onchange();}
124
+ if(!snapshot?.contextId)return;
125
+ recipient={contextId:snapshot.contextId,environmentUid:request.environmentUid,agentUid:request.agentUid,name:request.agentName,responseKind:'task',taskRequestId:request.requestId,...(request.conversationKey?{conversationKey:request.conversationKey}:{})};
126
+ draftRequest=null;responseKinds(['task'],'task');$('recipient').textContent=request.agentName;$('composer').hidden=false;$('message').value='';controls();$('message').focus();
127
+ }
128
+ function talkButton(agent, target) { const b = button('Talk', () => talk(agent, target)); b.disabled = !snapshot?.messagingAvailable || !agent.responseKinds?.some(k=>['message','task'].includes(k)) || agent.runtimeState === 'not_deployed'; if (b.disabled) b.title = agent.responseKinds?.includes('message') ? 'Open from a Codex chat to talk.' : 'This agent does not support direct messages.'; return b; }
129
+ function agentRow(agent, target, run) { if(agent.icon)assetCache.set(agent.uid,agent.icon);else assetCache.delete(agent.uid); const latest = latestAgent(agent.uid, target.environmentUid); return group('item row', icon(agent.uid), group('grow', button(agent.name, () => agentDetail(agent, target, run), 'name'), caption(defaults(agent)), latest && caption(stateLabel(latest)), !agent.responseKinds?.includes('message') && caption('Task agent')), talkButton(agent, target)); }
57
130
  async function refresh() {
58
- const run = ++generation, previousEnvironment = $('environment').value;
59
- busy = true; snapshot = null; clearResults(); controls();
60
- $('account').textContent = 'Checking your MainSequence session…'; $('backend').textContent = ''; $('session').textContent = ''; $('account-issue').textContent = ''; $('notice').textContent = '';
131
+ clearComposer();assetCache.clear();++deliveryGeneration;$('delivery-results').replaceChildren();$('deliveries').hidden=true;
132
+ const run=++generation,previous=$('environment').value,previousKey=environmentKey(snapshot);
133
+ busy=true;snapshot=null;clearResults();controls();accountIndicator('Checking your session…');
134
+ $('account-issue').textContent='';$('notice').textContent='';
61
135
  try {
62
- const state = await call('mainsequence_open'); if (run !== generation) return;
63
- snapshot = state;
64
- const a = state.account;
65
- $('account').textContent = a.authenticated ? (a.username || 'Signed in') : 'Not signed in';
66
- $('backend').textContent = a.backend;
67
- const source = { machine: 'Shared machine session', environment: 'Environment session', host: 'Host session', none: 'No saved session' }[a.source] || 'Existing devtools session';
68
- $('session').textContent = source + (a.checked ? ' · Verified with MainSequence' : ' · Not verified with backend');
69
- $('account-issue').textContent = state.issue?.message || (!a.authenticated ? 'Sign in using the existing /ms login command, then refresh.' : '');
70
- const placeholder = node('option', 'Choose an environment'); placeholder.value = '';
71
- $('environment').replaceChildren(placeholder, ...state.environments.map(env => { const option = node('option', env.name + (env.isProduction ? ' · Production' : '')); option.value = env.uid; return option; }));
72
- if (state.environments.some(env => env.uid === previousEnvironment)) $('environment').value = previousEnvironment;
73
- else if (state.environments.length === 1) $('environment').value = state.environments[0].uid;
74
- else $('environment').value = '';
75
- $('notice').textContent = state.contextId ? (state.environments.length ? 'Choose an environment to browse.' : 'No environments are available to this account.') : 'Discovery is unavailable until the account is verified.';
76
- busy = false; controls();
77
- if (state.contextId && $('environment').value) await discover(0);
78
- } catch (error) { if (run === generation) { $('account').textContent = 'Account status unavailable'; failure(error); } }
79
- finally { if (run === generation) { busy = false; controls(); } }
136
+ const state=await call('mainsequence_open');if(run!==generation)return;snapshot=state;
137
+ accountIndicator(state.account.authenticated?(state.account.username||'Signed in'):'Not signed in',state.account.authenticated&&state.account.checked?'connected':state.issue?'error':'pending');
138
+ $('account-issue').textContent=state.issue?.message||(!state.account.authenticated?'Sign in with /ms login, then refresh.':'');
139
+ const placeholder=node('option',state.environments.length?'Choose environment ('+state.environments.length+')':state.issue?'Environments unavailable':'No environments available');placeholder.value='';
140
+ $('environment').replaceChildren(placeholder,...state.environments.map(env=>{const option=node('option',env.name);option.value=env.uid;return option;}));
141
+ const candidates=[previousKey===environmentKey(state)?previous:null,state.selectedEnvironmentUid,rememberedEnvironment(state),state.environments.length===1?state.environments[0].uid:null];
142
+ $('environment').value=candidates.find(uid=>state.environments.some(e=>e.uid===uid))||'';rememberEnvironment();
143
+ busy=false;controls();
144
+ // The account and environment must be usable even if receipt loading is slow.
145
+ if(state.messagingAvailable)void loadDeliveries();
146
+ await discover(0);
147
+ } catch(error) {if(run===generation){accountIndicator('Account status unavailable','error');failure(error);}}
148
+ finally {if(run===generation){busy=false;controls();}}
80
149
  }
81
- function selection() { return { contextId: snapshot.contextId, environmentUid: $('environment').value }; }
82
- function render(page, target, run) {
83
- clearResults(); currentPage = page;
84
- if (!page.results.length) {
85
- const empty = node('div', undefined, 'empty'); empty.append(node('h2', catalog === 'agents' ? 'No agents found' : 'No project MCPs found'), node('p', 'Try another search or environment.', 'muted')); $('results').append(empty);
86
- }
87
- for (const item of page.results) {
88
- const card = node('article', undefined, 'item'), heading = node('div', undefined, 'row');
89
- heading.append(node('h2', item.name || 'Unnamed agent'));
90
- if (item.runtimeState) heading.append(node('span', item.runtimeState, 'tag'));
91
- card.append(heading);
92
- if (catalog === 'agents') {
93
- if (item.description) card.append(node('p', item.description));
94
- const defaults = [item.defaults.provider, item.defaults.model, item.defaults.thinking].filter(Boolean).join(' · ');
95
- if (defaults) card.append(node('p', defaults, 'muted'));
96
- const detail = node('details'); detail.append(node('summary', 'Agent details'), node('p', 'ID: ' + item.uid, 'muted'), node('p', 'Response types: ' + (item.responseKinds.join(', ') || 'Not reported'), 'muted'));
97
- if (item.repositoryBranch) detail.append(node('p', 'Branch: ' + item.repositoryBranch, 'muted'));
98
- card.append(detail);
99
- } else {
100
- heading.append(node('span', item.saved ? 'Saved connection' : 'Not saved', 'tag'));
101
- if (item.repository && item.repository !== item.name) card.append(node('p', item.repository, 'muted'));
102
- card.append(node('p', 'Release: ' + item.uid, 'muted'));
103
- const button = node('button', 'Inspect tools'), output = node('div');
104
- button.disabled = !item.saved;
105
- if (!item.saved) output.append(node('p', 'Save a connection before inspecting its tools.', 'muted'));
106
- button.onclick = async () => {
107
- button.disabled = true; output.replaceChildren(node('p', 'Reading the current catalog…', 'muted'));
108
- try {
109
- const result = await call('mainsequence_tools', { ...target, releaseUid: item.uid }); if (run !== generation) return;
110
- output.replaceChildren();
111
- if (!result.tools.length) output.append(node('p', 'No tools are currently exposed.', 'muted'));
112
- for (const tool of result.tools) {
113
- const details = node('details'); details.append(node('summary', tool.name), node('p', tool.description));
114
- if (Object.keys(tool.annotations).length) details.append(node('small', Object.entries(tool.annotations).map(([key, value]) => key + ': ' + value).join(' · ')));
115
- details.append(node('pre', JSON.stringify(tool.inputSchema, null, 2))); output.append(details);
116
- }
117
- } catch (error) { if (run === generation) { output.replaceChildren(node('p', error.message, 'muted')); if (['CONTEXT_CHANGED', 'AUTH_REJECTED', 'NOT_AUTHENTICATED'].includes(error.code)) { failure(error); controls(); } } }
118
- finally { button.disabled = false; }
119
- };
120
- card.append(button, output);
121
- }
122
- $('results').append(card);
123
- }
124
- $('notice').textContent = catalog === 'agents' ? 'Browse agents before starting a conversation.' : 'Available project MCPs. Catalog inspection requires an existing saved connection; discovery does not connect or invoke tools.';
125
- $('pager').hidden = false;
126
- $('page').textContent = page.semantic ? `${page.results.length} ranked results` : `${page.results.length ? offset + 1 : 0}–${offset + page.results.length} of ${page.count}`;
150
+ function pageControls(page) { currentPage = page; $('pager').hidden = page.count <= 20; $('page').textContent = page.semantic ? `${page.results.length} ranked results` : `${page.results.length ? offset + 1 : 0}–${offset + page.results.length} of ${page.count}`; }
151
+ function back(label, fn) { return button('← ' + label, fn, 'plain muted'); }
152
+ async function detail(load, draw) { const run = ++generation; busy = true; clearResults(); $('list-toolbar').hidden = true; $('notice').textContent = 'Loading…'; controls(); try { const data = await load(); if (run !== generation) return; $('notice').textContent = ''; draw(data, run); } catch (error) { if (run === generation) { failure(error); append($('results'),back(catalog[0].toUpperCase() + catalog.slice(1), () => discover(offset))); } } finally { if (run === generation) { busy = false; controls(); } } }
153
+ async function agentDetail(agent, target, run) { if (run !== generation) return; return detail(() => view('agent', { agentUid: agent.uid }, target), ({ agent: full }, detailRun) => { if(full.icon)assetCache.set(full.uid,full.icon);else assetCache.delete(full.uid); iconChecks = 0; watchIcons(detailRun, async () => { const again = (await view('agent', { agentUid: full.uid }, target)).agent; return [{ uid: again.uid, icon: again.icon ?? null }]; }); const output = $('results'); append(output,back('Agents', () => discover(offset)), group('detail-heading row', icon(full.uid, true), group('grow', node('h2', full.name), caption(defaults(full))), talkButton(full, target)), facts([['Environment', environmentName()], ['Branch', full.repositoryBranch], ['Deployment', full.runtimeState === 'unknown' ? 'Status unknown' : full.runtimeState]]), markdown(full.description), button('New conversation…',()=>review('New conversation with '+full.name,'Start a fresh agent session. Existing replies and saved requests remain in this Codex chat.',generation,async()=>{conversationKeys.set(full.uid,crypto.randomUUID());draftRequest=null;talk(full,target);})), node('h3','Skills')); for (const skill of full.skills || []) { const section = group('detail-section', node('h3', skill.name), markdown(skill.description)); for (const example of skill.examples || []) { section.append(button(example, () => { if (detailRun !== generation) return; talk(full, target); $('message').value = example; $('message').focus(); }, 'example')); } append(output,section); } append(output,details('Technical details', facts([['Agent ID', full.uid], ['Response types', (full.responseKinds || []).join(', ')], ['Runtime release', full.runtimeReleaseUid]]))); }); }
154
+ function review(title, description, run, action) { const box = group('review', node('h3', title), node('p', description), group('row')); const controls = box.children[2], cancel = button('Cancel', () => box.replaceChildren()); const confirm = button('Confirm', async () => { if (run !== generation) return; confirm.disabled = true; cancel.disabled = true; try { await action(); if (run === generation) $('confirmation').replaceChildren(); } catch (error) { if (run === generation) { box.append(node('pre', errorText(error))); cancel.disabled = false; } } }, 'primary'); controls.append(confirm, cancel); $('confirmation').replaceChildren(box); }
155
+ async function connection(item, target, run, connect) { if (run !== generation) return; review((connect ? 'Connect ' : 'Disconnect ') + item.name, (connect ? 'Make this MCP available to this account in ' : 'Remove the saved connection from ') + environmentName() + '. This updates the shared devtools connection list.', run, async () => { const data = await call('mainsequence_connection', { ...target, releaseUid: item.uid, action: connect ? 'connect' : 'disconnect' }); activity.push({ title: (connect ? 'Connected ' : 'Disconnected ') + item.name, summary: data.summary }); await discover(offset); }); }
156
+ function toolRow(item, target, run) { return group('item row', group('grow', button(item.name, () => item.saved ? toolDetail(item, target, run) : connection(item, target, run, true), 'name'), caption(item.saved ? (toolCache.has(target.contextId+item.uid) ? toolCache.get(target.contextId+item.uid).tools.length+' tools' : 'Connected') : item.repository || 'Available MCP')), button(item.saved ? 'Disconnect' : 'Connect', () => connection(item, target, run, !item.saved))); }
157
+ async function toolDetail(item, target, run) { if (run !== generation) return; return detail(async()=>{try{const data=await call('mainsequence_tools',{...target,releaseUid:item.uid});toolCache.set(target.contextId+item.uid,data);return data;}catch(error){const cached=toolCache.get(target.contextId+item.uid);if(!cached)throw error;return {...cached,catalogError:error.message};}}, (data, detailRun) => { const output = $('results'); append(output,back('Tools', () => { addingTools = false; return discover(0); }), group('detail-heading row between', group('grow', node('h2', item.name), caption(`Connected · ${data.tools.length} tools`)), button('Reload tools', () => toolDetail(item, target, generation)), button('Disconnect', () => connection(item, target, generation, false)))); if(data.catalogError) append(output,caption('Showing the previous catalog. Reload failed: '+data.catalogError)); if (!data.tools.length) append(output,empty('No tools exposed', 'Reload after the MCP publishes its catalog.')); for (const tool of data.tools) { const row = group('item', node('h3', tool.name), markdown(tool.description)); const input = node('textarea'); input.setAttribute('aria-label', 'JSON arguments for ' + tool.name); input.value = '{}'; const result = node('div'); const advanced = details('Schema and execution', group('stack', node('pre', JSON.stringify(tool.inputSchema, null, 2)), caption('Arguments (JSON)'), input, button('Review call', () => { let args; try { args = JSON.parse(input.value); if (!args || typeof args !== 'object' || Array.isArray(args)) throw Error(); } catch { result.replaceChildren(caption('Enter a JSON object matching the schema.')); return; } review('Run ' + tool.name + '?', item.name + ' · ' + environmentName() + '\n' + JSON.stringify(args, null, 2), detailRun, async () => { const response = await call('mainsequence_call_tool', { ...target, releaseUid: item.uid, tool: tool.name, arguments: args }); activity.push({ title: tool.name, summary: 'Tool call completed' }); if (detailRun === generation) result.replaceChildren(node('pre', JSON.stringify(response.result, null, 2))); }); }), result)); row.append(advanced); append(output,row); } }); }
158
+ function renderHome() { const a = snapshot?.account; const output = $('results'); if (!a?.authenticated || !$('environment').value) append(output,card('Next steps', node('h3', a?.authenticated ? 'Choose an environment' : 'Sign in to MainSequence'), caption(a?.authenticated ? 'Use the environment selector above to browse your projects, agents and tools.' : 'Use /ms login in Codex to restore the shared session.'))); append(output,card('This session', facts([['Account', a?.username], ['Environment', environmentName()], ['Backend', a?.backend]]), caption('Your MainSequence agent replies are saved in this native Codex conversation.'), group('row wrap', button('Talk to an agent', () => tab('agents')), button('Connected tools', () => tab('tools'))))); if(snapshot?.contextId && $('environment').value) {const session=card('Local workspace',caption('Open a folder to inspect its branch, SDK and repository status.'),button('Open in Code',()=>tab('code')),button('All deployments',()=>deployments()));append(output,session);} const recent = savedRequests.slice(-3).reverse(); append(output,card('Recent', ...(recent.length ? recent.map(r => group('item', node('h3', r.agentName), caption(stateLabel(r)))) : [caption('No agent messages in this chat yet.')]), button('See all activity', () => tab('activity')))); }
159
+ function renderActivity() { const output = $('results'); append(output,group('row between', node('h2', 'Activity'), button('Refresh messages', async () => { await loadDeliveries(); if (catalog === 'activity') { $('results').replaceChildren(); renderActivity(); } }))); for (const request of [...savedRequests].reverse()) append(output,group('item', group('row between', node('h3', request.agentName), node('small', stateLabel(request))), caption(snapshot?.environments.find(e => e.uid === request.environmentUid)?.name || ''), request.receipt && node('p', request.receipt.message, 'receipt'), request.task && group('row wrap',button('Continue task',()=>continueTask(request)),button('Refresh task',async()=>{await call('mainsequence_agent_task',{action:'get',requestId:request.requestId});await loadDeliveries();discover();}),!request.task.terminal && button('Cancel task',()=>review('Cancel agent task','Request cancellation of this accepted task. Its saved request will not be sent again.',generation,async()=>{await call('mainsequence_agent_task',{action:'cancel',requestId:request.requestId});await loadDeliveries();discover();}))), request.reason && caption(request.reason), details('Message details', group('stack', node('p', request.message), caption('Request ' + request.requestId + ' · Dispatches ' + request.sendAttempts))), button(request.state === 'delivered' ? 'Show reply in Codex' : 'View recovery', () => { $('deliveries').open = true; $('deliveries').scrollIntoView?.({ block: 'nearest' }); }))); for(const op of [...operationCache].reverse()) append(output,operationRow(op)); for (const item of [...activity].reverse()) append(output,group('item', node('h3', item.title), caption(item.summary))); if (!savedRequests.length && !activity.length) append(output,empty('No activity yet', 'Agent messages and MCP connection changes appear here.')); }
160
+ async function discover(nextOffset = 0, fresh = false) { const run = ++generation; offset = nextOffset; clearResults(); busy = false; $('list-toolbar').hidden = !['agents', 'projects', 'code'].includes(catalog) && !(catalog === 'tools' && addingTools); $('notice').textContent = ''; controls(); if (catalog === 'home') { renderHome(); return; } if (catalog === 'activity') { try { await operations(); } catch(e) { failure(e); } renderActivity(); return; } if (catalog === 'settings' && !$('environment').value) { await renderSettings(null, run); return; } if (!snapshot?.contextId || !$('environment').value) { $('notice').textContent = snapshot?.contextId ? 'Choose an environment to continue.' : 'Refresh or sign in to continue.'; return; } const target = selection(); busy = true; controls(); $('notice').textContent = 'Loading…'; try { if (catalog === 'settings') await renderSettings(target, run); else if (catalog === 'code') { await renderCode(target,run); } else if (catalog === 'projects') { const page = await view('projects', { search: $('search').value, offset }, target); if (run !== generation) return; pageControls(page); if (!page.results.length) append($('results'),empty('No projects found', 'Try another search or environment.')); for (const project of page.results) append($('results'),group('item row', group('grow', button(project.name, () => projectDetail(project, target, run), 'name'), caption(project.description?.split('\n')[0])), caption((project.code_repository_branch_uids || []).length + ' branches'))); } else if (catalog === 'tools' && !addingTools) { const page = await view('connections', {}, target); if (run !== generation) return; append($('results'),group('row between', node('h2', 'Connected in ' + environmentName()), button('Add MCP', () => { addingTools = true; return discover(0); }))); if (!page.results.length) append($('results'),empty('No connected MCPs', 'Add an MCP to make its tools available in Codex.')); for (const item of page.results) append($('results'),toolRow(item, target, run)); } else { const page = await call('mainsequence_discover', { ...target, catalog, search: $('search').value, offset, ...(fresh ? {refresh:true} : {}) }); if (run !== generation) return; pageControls(page); if (catalog === 'tools') append($('results'),back('Tools', () => { addingTools = false; return discover(0); })); if (!page.results.length) append($('results'),empty(catalog === 'agents' ? 'No agents found' : 'No MCPs found', 'Try another search or environment.')); if(catalog==='agents') append($('results'),caption(page.semantic?page.results.length+' matching agents':page.count+' agents · showing '+(page.results.length?offset+1:0)+'–'+(offset+page.results.length))); const rows = group('stack'); rows.style.gap = '0'; append($('results'),rows); for (const item of page.results) rows.append(catalog === 'agents' ? agentRow(item, target, run) : toolRow(item, target, run)); if (catalog === 'agents') { iconChecks = 0; watchIcons(run, async () => (await call('mainsequence_discover', { ...target, catalog: 'agents', search: $('search').value, offset })).results.map(a => ({ uid: a.uid, icon: a.icon ?? null }))); } } if (run === generation) $('notice').textContent = ''; } catch (error) { if (run === generation) failure(error); } finally { if (run === generation) { busy = false; controls(); } } }
161
+ function tab(next, discoverView=true) { catalog = next; addingTools = false; $('search').value = ''; $('search').placeholder = 'Search ' + ({ code: 'repositories', tools: 'MCPs' }[next] || next); $('search').setAttribute('aria-label', $('search').placeholder); for (const key of sections) { $(key + '-tab').setAttribute('aria-selected', String(key === next)); $(key + '-tab').setAttribute('tabindex', ['activity','settings'].includes(key) || key === next ? '0' : '-1'); } $('directory').setAttribute('aria-labelledby', next + '-tab'); return discoverView ? discover(0) : undefined; }
162
+ $('refresh').onclick = () => snapshot?.contextId ? discover(offset, true) : refresh(); $('environment').onchange = () => { rememberEnvironment();clearComposer(); workspaceContext=null; assetCache.clear(); addingTools = false; $('search').value = ''; return discover(0); }; $('search-form').onsubmit = e => { e.preventDefault(); return discover(0); }; $('previous').onclick = () => discover(Math.max(0, offset - 20)); $('next').onclick = () => discover(offset + 20); $('switch-agent').onclick = () => tab('agents');
163
+ for (const key of sections) { $(key + '-tab').onclick = () => tab(key); $(key + '-tab').onkeydown = e => { if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) { e.preventDefault(); const next = e.key === 'Home' ? sections[0] : e.key === 'End' ? sections.at(-1) : sections[(sections.indexOf(key) + (e.key === 'ArrowRight' ? 1 : sections.length - 1)) % sections.length]; $(next + '-tab').focus(); void tab(next); } }; }
164
+ $('code-all').onclick=()=>{codeFilter='all';discover();};$('code-local').onclick=()=>{codeFilter='local';discover();};
165
+ let workspaceContext = null, workspaceContextKey = null, operationCache = [], codeFilter = 'all';
166
+ const conversationKeys = new Map();
167
+ function field(label, value = '', placeholder = '') { const input = node('input'); input.value = value; input.placeholder = placeholder; input.setAttribute('aria-label', label); const wrap = node('label', label, 'field'); wrap.append(input); return {input,element:wrap}; }
168
+ function selectField(label, options, value = '') { const input = node('select'); input.setAttribute('aria-label', label); for (const [v,l] of options) {const o=node('option',l);o.value=v;input.append(o);} input.value=value || options[0]?.[0] || ''; const element=node('label',label,'field'); element.append(input);return {input,element}; }
169
+ function resultBlock(result) { return group('stack',caption(result.summary),result.effects && caption(result.effects.message),result.data && details('Details',node('pre',JSON.stringify(result.data,null,2)))); }
170
+ async function readWorkspace(action,input={},target=selection()) { const {result}=await call('mainsequence_workspace',{...target,action,input}); if(result.status !== 'success') throw new Error(result.summary); return result.data; }
171
+ async function workspace() {const target=selection(),key=JSON.stringify(target);if(workspaceContext && workspaceContextKey===key)return workspaceContext;const data=await call('mainsequence_workspace',{...target,action:'local-context'});if(snapshot?.contextId===target.contextId && $('environment').value===target.environmentUid){workspaceContext=data;workspaceContextKey=key;}return data;}
172
+ async function operations() { if(!snapshot?.contextId || !$('environment').value) return; const target=selection(),response=await call('mainsequence_operations',{...target,action:'list'});if(snapshot?.contextId!==target.contextId || $('environment').value!==target.environmentUid)return;operationCache=response.operations; const count=operationCache.filter(op=>op.status==='running').length; $('activity-tab').textContent=count?count+' running':'Activity'; }
173
+ async function runOperation(action,input,output) { const target=selection(),run=generation; const {operation}=await call('mainsequence_action',{...target,action,input}); const id=operation.id; if(output) output.replaceChildren(operationRow(operation,target)); await operations();
174
+ const poll=async()=>{try{const data=await call('mainsequence_operations',{...target,action:'get',operationId:id});if(run===generation && output?.isConnected)output.replaceChildren(operationRow(data.operation,target));await operations();if(data.operation.status==='running'&&connected)setTimeout(poll,2000);else workspaceContext=null;}catch(error){if(run===generation)failure(error);}};setTimeout(poll,1000);return operation;
127
175
  }
128
- async function discover(nextOffset = 0) {
129
- const run = ++generation; offset = nextOffset; clearResults();
130
- if (!snapshot?.contextId || !$('environment').value) { busy = false; $('notice').textContent = snapshot?.contextId ? 'Choose an environment to browse.' : 'Refresh or sign in to browse agents and tools.'; controls(); return; }
131
- const target = selection(); busy = true; controls(); $('notice').textContent = catalog === 'agents' ? 'Loading agents…' : 'Discovering project MCPs…';
132
- try { const page = await call('mainsequence_discover', { ...target, catalog, search: $('search').value, offset }); if (run === generation) render(page, target, run); }
133
- catch (error) { if (run === generation) failure(error); }
134
- finally { if (run === generation) { busy = false; controls(); } }
176
+ function operationRow(op,target=selection()) {return group('item',group('row between',node('h3',op.action),caption(op.status)),caption(op.context),node('p',op.progress),op.result && resultBlock(op.result),op.result?.status==='success' && op.result.data?.root && button('Show workspace in Codex',()=>rpc('ui/message',{role:'user',content:[{type:'text',text:'Show the prepared MainSequence workspace at '+JSON.stringify(op.result.data.root)+' in Codex. Setup has completed; do not run it again. Read its .mainsequence-project.json if present and explain which repository paths this chat can access.'}],_meta:{'openai/message':{target:'active',send:true}}}).catch(failure)),op.status==='running' && button('Cancel operation',async()=>{await call('mainsequence_operations',{...target,action:'cancel',operationId:op.id});await operations();if(catalog==='activity')discover();}));}
177
+ function actionButton(label,action,input,output,options={}) {const b=button(label,()=>{const args=typeof input==='function'?input():input;review(label,options.description || 'Run '+label.toLowerCase()+' for '+(args.root || args.branch || args.projectUid || 'this selection')+'.',generation,()=>runOperation(action,args,output));},options.primary?'primary':'plain');if(options.reason){b.disabled=true;b.title=options.reason;}return b;}
178
+ function disabled(label,reason){const b=button(label,()=>{});b.disabled=true;b.title=reason;return group('stack',b,caption(reason));}
179
+ async function renderSettings(target,run) { const data=await call('mainsequence_settings',{action:'get'});if(run!==generation)return;const prefs=data.preferences,a=snapshot?.account||{},out=$('results');
180
+ const base=field('Code repositories folder',prefs.baseDir),backend=field('Backend URL',prefs.backend),center=field('Command Center URL (optional)',prefs.commandCenterUrl);const feedback=node('div'),scanfeedback=node('div');
181
+ const accountAction=async action=>{const data=await call('mainsequence_settings',{action,backend:backend.input.value});if(data.result?.status!=='success')throw new Error(data.result?.summary||'Account operation failed.');workspaceContext=null;await refresh();};
182
+ append(out,card('MainSequence account',node('p',a.authenticated?'Signed in as '+a.username:'Not signed in'),caption(a.checked?'Connection verified · shared machine session':'Connection needs checking'),group('row wrap',button('Check connection',refresh),!a.authenticated&&button('Sign in with browser',()=>accountAction('login').catch(failure),'primary')),a.authenticated&&details('Manage account',group('row wrap',button('Renew session',()=>accountAction('refresh-token').catch(failure)),button('Sign out',()=>review('Sign out of MainSequence','Remove this backend’s shared login. Other MainSequence clients use this same session.',generation,()=>accountAction('logout')))))),
183
+ card('Workspace',base.element,caption('Enter a folder path. Codex does not expose a native folder picker to plugin panels.'),backend.element,center.element,button('Save settings',async()=>{try{const saved=await call('mainsequence_settings',{action:'save',backend:backend.input.value,baseDir:base.input.value,commandCenterUrl:center.input.value});workspaceContext=null;feedback.replaceChildren(caption('Settings saved.'));if(saved.preferences.backend!==a.backend)await refresh();}catch(e){feedback.replaceChildren(caption(e.message));}},'primary'),feedback),
184
+ card('Advanced',details('Runtime & diagnostics',group('stack',facts([['Plugin',BUILD.version],['Devtools',snapshot?.build?.devtoolsVersion],['Node.js',data.runtime.nodeVersion],['Platform',data.runtime.platform],['Shared connection service',data.transport?.instanceId],['Connected chat processes',data.transport?.clients],['Upstream connections',data.transport?.connections]]),button('Run diagnostics',()=>discover()),details('Host capabilities',node('pre',JSON.stringify(data.runtime.capabilities,null,2))))),target&&details('Local discovery',group('stack',button('Scan local repositories',async()=>{workspaceContext=null;const data=await workspace();scanfeedback.replaceChildren(resultBlock(data.local));}),scanfeedback)))); }
185
+ async function renderCode(target,run) { const data=await view('repositories',{},target),local=await workspace();if(run!==generation)return;$('code-all').className=codeFilter==='all'?'selected':'plain';$('code-local').className=codeFilter==='local'?'selected':'plain';const output=$('results'),locals=local.local?.data?.items||[],query=$('search').value.toLowerCase();
186
+ append(output,
187
+ !query&&codeFilter==='all'&&local.pluginWorkspace&&group('item row',group('grow',node('h3',local.pluginWorkspace.split('/').at(-1)),caption(local.pluginWorkspace)),button('Open',()=>localRepository({root:local.pluginWorkspace}))),local.local?.status!=='success'&&caption(local.local?.summary||''));
188
+ const seen=new Set();for(const repo of data.results){const found=locals.find(x=>x.repositoryUid===repo.uid);if(found)seen.add(found.path);if(!repo.code_repository_name.toLowerCase().includes(query)||(codeFilter==='local'&&!found))continue;append(output,group('item row',group('grow',button(repo.code_repository_name,()=>found?localRepository({root:found.path,repo}):repositoryDetail(repo),'name'),caption([repo.code_repository_type==='vite_react'?'Vite / React':repo.primary_language,(repo.branches||[]).length+((repo.branches||[]).length===1?' branch':' branches'),found?'Local':''].filter(Boolean).join(' · '))),button(found?'Open':'Set up',()=>found?localRepository({root:found.path,repo}):repositoryDetail(repo),found?'plain':'primary')));}
189
+ for(const item of locals)if(!seen.has(item.path)&&item.name.toLowerCase().includes(query))append(output,group('item row',group('grow',node('h3',item.name),caption(item.path)),button('Open',()=>localRepository({root:item.path})))); }
190
+ async function repositoryDetail(repo) { return detail(workspace,ctx=>{const branch=selectField('Branch',(repo.branches||[]).map(b=>[b.repository_branch,b.repository_branch])),install=node('input');install.type='checkbox';install.setAttribute('aria-label','Install dependencies after setup');const output=node('div');const setup=button('Set up',()=>review('Set up '+repo.code_repository_name,branch.input.value+' → '+ctx.preferences.baseDir,generation,()=>runOperation('setup',{repositoryUid:repo.uid,branch:branch.input.value,baseDir:ctx.preferences.baseDir,install:install.checked},output)),'primary');setup.disabled=!repo.branches?.length;
191
+ append($('results'),back('Code',()=>discover()),node('h2',repo.code_repository_name),caption('Not on this computer'),card('Set up',branch.element,facts([['Destination',ctx.preferences.baseDir]]),group('row',install,caption('Install dependencies after setup')),setup,output));}); }
192
+ async function localRepository({root,repo}) {const ctx=await workspace();return detail(async()=>({}),()=>{const output=$('results'),folder=field('Folder',root),kind=selectField('Repository type',[['','Detect automatically'],['python','Python'],['vite_react','Vite / React']]),status=node('div'),progress=node('div'),input=()=>({root:folder.input.value,...(kind.input.value?{projectType:kind.input.value}:{})});
193
+ const inspect=async action=>{try{const data=await readWorkspace(action,input());status.replaceChildren(facts([['Branch',data.project?.branch],['Type',data.project?.projectType],['SDK',data.installed],['Latest',data.latest]]),details('Details',node('pre',JSON.stringify(data,null,2))));}catch(e){status.replaceChildren(caption(e.message));}};
194
+ const actions=(items)=>group('row wrap',...items.map(([label,action])=>actionButton(label,action,input,progress)));
195
+ const image=field('Container image','mainsequence-project:latest'),platform=field('Docker platform','linux/amd64'),containerResult=node('div');
196
+ async function preview(action){try{const args={root:folder.input.value,image:image.input.value,...(action==='docker-preview'?{platform:platform.input.value}:{})},data=await readWorkspace(action,args);containerResult.replaceChildren(node('pre',JSON.stringify(data,null,2)),actionButton(action==='docker-preview'?'Build image':'Save container configuration',action==='docker-preview'?'docker-build':'devcontainer-write',{...args,...(data.expectedDigest?{expectedDigest:data.expectedDigest}:{})},progress,{description:JSON.stringify(data,null,2)}));}catch(e){containerResult.replaceChildren(caption(e.message));}}
197
+ append(output,back('Code',()=>discover()),node('h2',root.split('/').at(-1)),caption(root),card('Status',folder.element,kind.element,group('row wrap',button('Check status',()=>inspect('status')),button('Check SDK',()=>inspect('sdk-status')),button('Detect repository',()=>inspect('resolve'))),status),card('Deployments',button('View deployments',()=>deployments(repo?.branches?.[0]?.uid))),card('Dependencies',actions([['Sync dependencies','sync'],['Install dependencies','install'],['Update SDK','update-sdk']]),button('Preview SDK update',()=>readWorkspace('update-sdk',{...input(),dryRun:true}).then(data=>status.replaceChildren(node('pre',JSON.stringify(data,null,2)))).catch(failure))),card('Instructions and skills',actions([['Update instructions','update-agents'],['Install skills','install-skills']]),details('More',group('stack',button('Preview skill installation',()=>readWorkspace('install-skills',{...input(),dryRun:true}).then(data=>status.replaceChildren(node('pre',JSON.stringify(data,null,2)))).catch(failure)),actions([['Sync skills','skills-sync'],['Remove old credentials from .env','env-cleanup']])))),card('More',actionButton('Open folder','open-folder',input,progress),details('Terminal',caption('Stock Codex does not expose a signed-terminal environment API to plugin panels. Repository actions run through devtools; use Codex’s Terminal control for an interactive terminal.')),details('Containers',group('stack',image.element,platform.element,group('row wrap',button('Review Docker build',()=>preview('docker-preview')),button('Review container configuration',()=>preview('devcontainer-preview'))),containerResult,caption('Opening a session inside a container requires a host container API; neither plugin exposes that in this host.'))),button('Delete local copy…',async()=>{try{const args={root:folder.input.value,baseDir:ctx.preferences.baseDir,organization:ctx.organization},data=await readWorkspace('delete-local-preview',args);review('Delete local copy permanently',JSON.stringify(data,null,2),generation,()=>runOperation('delete-local',{...args,confirmation:data.confirmation},progress));}catch(e){failure(e);}})),progress);}); }
198
+ async function projectDetail(project,target,run) {if(run!==generation)return;return detail(async()=>({...await view('project',{projectUid:project.uid},target),workspace:await workspace(),operations:await operations()}),data=>{const prepared=operationCache.find(op=>op.action==='organization-project-setup'&&op.context===project.uid&&op.status==='success')?.result?.data;const progress=node('div'),center=data.workspace.preferences.commandCenterUrl,link=(target,branchUid)=>runOperation('organization-project-link',{projectUid:project.uid,target,...(branchUid?{branchUid}:{}),commandCenterUrl:center},progress);const setup=actionButton(prepared?'Update local workspace':'Set up locally','organization-project-setup',{projectUid:project.uid,baseDir:data.workspace.preferences.baseDir},progress,{primary:true,description:'Prepare separate checkouts for '+data.branches.length+' assigned branches in '+data.workspace.preferences.baseDir+'. Existing changes are preserved.'});setup.disabled=!data.branches.length;
199
+ const centerButton=button('Open in Command Center',()=>link('project'));centerButton.disabled=!center;centerButton.title=center?'':'Set the Command Center URL in Settings.';
200
+ append($('results'),back('Projects',()=>discover()),node('h2',data.project.name),markdown(data.project.description),group('row wrap',setup,centerButton),prepared&&caption(prepared.root),prepared&&operationRow(operationCache.find(op=>op.result?.data===prepared),target),progress,node('h3','Repositories'),...data.branches.map(b=>group('item',button(b.code_repository_name,()=>link('repository',b.uid),'name'),group('row',button(b.repository_branch,()=>link('branch',b.uid)),caption(b.is_initialized?'Ready':'Initializing')))));}); }
201
+ async function deployments(branchUid,offset=0) {return detail(()=>readWorkspace('deployment-history',{limit:25,offset,...(branchUid?{codeRepositoryBranchUid:branchUid}:{})}),data=>{const out=$('results');append(out,back('Code',()=>tab('code')),group('row between',node('h2','Deployments'),button('Refresh',()=>deployments(branchUid,offset))),caption(environmentName()));for(const run of data.results)append(out,group('item row',group('grow',button(run.targetLabel||run.workflowPath||run.repositoryName||run.uid,()=>deploymentDetail(run,branchUid,offset),'name'),caption([run.branchName,run.commit,run.state,run.createdAt].filter(Boolean).join(' · '))),button('Details',()=>deploymentDetail(run,branchUid,offset))));if(!data.results.length)append(out,empty('No deployments','No deployment history in this environment.'));append(out,group('row between',offset>0&&button('Previous',()=>deployments(branchUid,Math.max(0,offset-25))),offset+data.results.length<data.count&&button('Next',()=>deployments(branchUid,offset+25))));}); }
202
+ async function deploymentDetail(run,branchUid,offset) {return detail(()=>readWorkspace('deployment-run',{runUid:run.uid,kind:run.kind}),(data,screen)=>{const out=$('results');append(out,back('Deployments',()=>deployments(branchUid,offset)));if(data.hidden){append(out,caption("You can't see this deployment."));return;}const r=data.run;append(out,group('row between',node('h2',r.targetLabel||r.workflowPath||'Deployment'),button('Refresh',()=>deploymentDetail(run,branchUid,offset))),facts([['State',r.state],['Branch',r.branchName],['Commit',r.commit],['Started',r.startedAt],['Finished',r.finishedAt],['Duration',r.durationMs==null?'':Math.round(r.durationMs/1000)+' s']]),r.error&&caption(r.error));for(const step of r.steps||[])append(out,group('item',node('h3',step.name||step.key),caption([step.state,step.reason,step.durationMs==null?'':Math.round(step.durationMs/1000)+' s'].filter(Boolean).join(' · ')),step.error&&caption(step.error),step.uid&&button('View stage logs',()=>deploymentLogs(run,step.uid)),step.deploymentRunUid&&button('Open deployment',()=>deploymentDetail({uid:step.deploymentRunUid,kind:'deployment_run'},branchUid,offset))));for(const owned of r.deploymentRuns||[])append(out,button(owned.targetLabel||owned.uid,()=>deploymentDetail({uid:owned.uid,kind:'deployment_run'},branchUid,offset)));if(r.workflowRun)append(out,button('Open workflow',()=>deploymentDetail(r.workflowRun,branchUid,offset)));if(r.kind==='deployment_run')append(out,button('View logs',()=>deploymentLogs(run)));if(['pending','queued','running'].includes(r.state))setTimeout(()=>{if(connected&&generation===screen)void deploymentDetail(run,branchUid,offset);},5000);}); }
203
+ async function deploymentLogs(run,stepUid,cursor) {return detail(()=>readWorkspace('deployment-logs',{runUid:run.uid,...(stepUid?{stepUid}:{}),...(cursor?{cursor}:{}),limit:200}),data=>{const out=$('results');append(out,back('Deployment',()=>deploymentDetail(run)),node('h2','Logs'));if(data.hidden){append(out,caption("You can't see this deployment."));return;}const search=field('Filter logs',''),log=node('pre');const draw=()=>log.textContent=data.entries.filter(e=>JSON.stringify(e).toLowerCase().includes(search.input.value.toLowerCase())).map(e=>[e.timestamp,e.level,e.text].filter(Boolean).join(' ')).join('\n')||'No matching log entries.';search.input.oninput=draw;draw();append(out,search.element,log,group('row wrap',button('Refresh',()=>deploymentLogs(run,stepUid,cursor)),data.nextCursor&&button('Next logs',()=>deploymentLogs(run,stepUid,data.nextCursor))),caption(data.complete?'Log stream complete':'More logs may arrive.'));}); }
204
+
205
+ function nativeRequestText(request) {
206
+ const action = request.state === 'delivered' ? 'Show the saved reply' : ['preparing', 'sending'].includes(request.state) ? 'Wait for the reply' : 'Deliver the saved message';
207
+ return action + ' for my MainSequence conversation with ' + JSON.stringify(request.agentName) + '.\nSaved request: ' + request.requestId + '.\nUse this saved request; do not create or resend a message. Keep the agent’s reply in this Codex chat.';
135
208
  }
136
- function tab(next) {
137
- catalog = next; $('search').value = ''; const label = next === 'agents' ? 'Search agents' : 'Search project MCPs';
138
- $('search').placeholder = label; $('search').setAttribute('aria-label', label);
139
- for (const key of ['agents', 'tools']) { $(key + '-tab').setAttribute('aria-selected', String(key === next)); $(key + '-tab').setAttribute('tabindex', key === next ? '0' : '-1'); }
140
- $('directory').setAttribute('aria-labelledby', next + '-tab'); return discover(0);
209
+
210
+ async function submitRequest(request, send) {
211
+ await call('mainsequence_submission', { requestId: request.requestId, outcome: 'attempted' });
212
+ try {
213
+ const output = await rpc('ui/message', { role: 'user', content: [{ type: 'text', text: nativeRequestText(request) }], _meta: { 'openai/message': { target: 'active', send } } });
214
+ if (output?.isError) throw new Error('Codex could not accept this message.');
215
+ await call('mainsequence_submission', { requestId: request.requestId, outcome: 'acknowledged' });
216
+ $('delivery-notice').textContent = send ? 'Queued in Codex. This is not yet a delivery receipt. Recover this saved request if interrupted.' : 'Drafted in Codex. Submit the native draft to deliver this saved request.';
217
+ } catch (error) {
218
+ await call('mainsequence_submission', { requestId: request.requestId, outcome: 'unknown' }).catch(() => { });
219
+ $('delivery-notice').textContent = 'Codex’s acknowledgement is unknown. Your request is saved. Use Messages and recovery; do not send it again.';
220
+ }
141
221
  }
142
- $('refresh').onclick = refresh; $('environment').onchange = () => { $('search').value = ''; return discover(0); };
143
- $('search-form').onsubmit = event => { event.preventDefault(); return discover(0); };
144
- $('previous').onclick = () => discover(Math.max(0, offset - 20)); $('next').onclick = () => discover(offset + 20);
145
- for (const key of ['agents', 'tools']) {
146
- $(key + '-tab').onclick = () => tab(key);
147
- $(key + '-tab').onkeydown = event => { if (['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) { event.preventDefault(); const next = event.key === 'Home' ? 'agents' : event.key === 'End' ? 'tools' : key === 'agents' ? 'tools' : 'agents'; $(next + '-tab').focus(); void tab(next); } };
222
+ async function sendMessage(send) {
223
+ if (messageBusy || !recipient || !$('message').value.trim()) return;
224
+ const selected = { ...recipient }, message = $('message').value, run = generation;
225
+ messageBusy = true; controls(); $('delivery-notice').textContent = 'Saving your message…';
226
+ if (!draftRequest || draftRequest.message !== message || draftRequest.agentUid !== selected.agentUid || draftRequest.environmentUid !== selected.environmentUid || draftRequest.responseKind !== selected.responseKind || draftRequest.taskRequestId !== selected.taskRequestId) draftRequest = { ...selected, message, requestId: crypto.randomUUID() };
227
+ const draft = { ...draftRequest }; delete draft.name;
228
+ try {
229
+ const { request } = await call('mainsequence_stage_message', draft);
230
+ if (run !== generation || recipient?.agentUid !== selected.agentUid) { $('delivery-notice').textContent = 'The selection changed. Your message is saved but was not submitted. Review it in Messages and recovery.'; return; }
231
+ draftRequest.requestId = request.requestId;
232
+ if (['failed', 'uncertain', 'expired', 'cancelled'].includes(request.state)) { $('delivery-notice').textContent = request.reason || request.state; return; }
233
+ await submitRequest(request, send);
234
+ } catch (error) { $('delivery-notice').textContent = error.message; }
235
+ finally { messageBusy = false; controls(); await loadDeliveries(); }
236
+ }
237
+ async function loadDeliveries() {
238
+ if (!snapshot?.messagingAvailable) return;
239
+ const run = ++deliveryGeneration;
240
+ try {
241
+ const { requests } = await call('mainsequence_agent_status', { action: 'list' });
242
+ if (run !== deliveryGeneration) return;
243
+ savedRequests = requests; $('deliveries').hidden = requests.length === 0; $('delivery-results').replaceChildren();
244
+ for (const request of [...requests].reverse()) {
245
+ const card = node('article', undefined, 'item');
246
+ const deliveryState = request.task ? stateLabel(request) : { pending: 'Waiting for Codex', preparing: 'Preparing agent', sending: 'Waiting for agent reply', delivered: 'Reply saved', failed: 'Could not send', uncertain: 'Delivery unknown — resend blocked', cancelled: 'Cancelled', expired: 'Expired' }[request.state] || request.state;
247
+ const environment = snapshot.environments.find(e => e.uid === request.environmentUid)?.name || request.environmentUid;
248
+ card.append(node('h3', request.agentName), node('p', [environment, deliveryState, request.createdAt ? new Date(request.createdAt).toLocaleString() : ''].filter(Boolean).join(' · '), 'caption'), details('Message', node('p', request.message)));
249
+ if (request.reason) card.append(node('p', request.reason, 'muted'));
250
+ if (request.receipt) card.append(node('p', request.agentName + ': ' + request.receipt.message, 'receipt'));
251
+ const deliveryDetails = node('details'); deliveryDetails.append(node('summary', 'Delivery details'), node('small', 'Request ' + request.requestId + ' · Agent dispatches: ' + request.sendAttempts)); card.append(deliveryDetails);
252
+ if (['pending', 'preparing', 'sending', 'delivered'].includes(request.state) || (request.state === 'failed' && request.sendAttempts === 0)) {
253
+ const recover = node('button', request.state === 'delivered' ? 'Show reply in Codex' : 'Recover in Codex');
254
+ recover.onclick = async () => {
255
+ if (messageBusy) return; messageBusy = true; recover.disabled = true; controls();
256
+ try { await submitRequest(request, true); } catch (error) { $('delivery-notice').textContent = error.message; }
257
+ finally { messageBusy = false; controls(); await loadDeliveries(); }
258
+ }; card.append(recover);
259
+ }
260
+ if (request.state === 'pending') {
261
+ const cancel = node('button', 'Cancel unsent message');
262
+ cancel.onclick = async () => { cancel.disabled = true; try { await call('mainsequence_agent_request', { action: 'cancel', requestId: request.requestId }); await loadDeliveries(); } catch (error) { $('delivery-notice').textContent = error.message; cancel.disabled = false; } }; card.append(cancel);
263
+ }
264
+ $('delivery-results').append(card);
265
+ }
266
+ if (!requests.length) $('delivery-results').append(node('p', 'No saved messages in this chat.', 'muted'));
267
+ } catch (error) { if (run === deliveryGeneration) $('delivery-notice').textContent = error.message; }
148
268
  }
149
- function teardown() { ++generation; connected = false; for (const p of pending.values()) { clearTimeout(p.timeout); p.reject(new Error('The panel closed.')); } pending.clear(); controls(); }
269
+ $('response-kind').onchange=()=>{if(!messageBusy && recipient && !recipient.taskRequestId && recipientKinds.includes($('response-kind').value)){recipient.responseKind=$('response-kind').value;draftRequest=null;}};
270
+ $('send-message').onclick = () => sendMessage(true); $('draft-message').onclick = () => sendMessage(false);
271
+ $('close-composer').onclick = () => { clearComposer(); controls(); };
272
+ $('refresh-deliveries').onclick = loadDeliveries;
273
+
274
+ function teardown() { ++deliveryGeneration; ++generation; connected = false; clearTimeout(iconWatch); for (const p of pending.values()) { clearTimeout(p.timeout); p.reject(new Error('The panel closed.')); } pending.clear(); controls(); }
150
275
  addEventListener('message', event => {
151
276
  if (event.source !== parent || event.data?.jsonrpc !== '2.0') return;
152
277
  const message = event.data, p = pending.get(message.id);