@dev-mainsequence/codex-plugin 0.3.0 → 0.7.9
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/.codex-plugin/plugin.json +6 -5
- package/.mcp.json +17 -0
- package/README.md +49 -11
- package/assets/mainsequence-mark.svg +453 -0
- package/build-info.json +3 -3
- package/dist/hosts/codexPresentation.js +1 -0
- package/dist/hosts/discovery.js +252 -0
- package/dist/hosts/messaging.js +63 -0
- package/dist/hosts/panel/panel.html +265 -0
- package/dist/hosts/workspace.js +107 -0
- package/dist/panel-server.js +223 -0
- package/dist/vendor/devtools/index.js +45 -1
- package/dist/vendor/devtools/package.json +1 -1
- package/dist/vendor/devtools/shared/accountScope.js +19 -0
- package/dist/vendor/devtools/shared/actions.js +44 -7
- package/dist/vendor/devtools/shared/agentAssets.js +38 -0
- package/dist/vendor/devtools/shared/agentDelivery.js +477 -0
- package/dist/vendor/devtools/shared/agentIcon.js +43 -0
- package/dist/vendor/devtools/shared/agents.js +18 -7
- package/dist/vendor/devtools/shared/arguments.js +16 -9
- package/dist/vendor/devtools/shared/deployments.js +214 -0
- package/dist/vendor/devtools/shared/environments.js +38 -0
- package/dist/vendor/devtools/shared/mcp.js +237 -46
- package/dist/vendor/devtools/shared/mcpTransport.js +79 -23
- package/dist/vendor/devtools/shared/organizationProjectWorkspace.js +8 -3
- package/dist/vendor/devtools/shared/organizationProjects.js +2 -13
- package/dist/vendor/devtools/shared/projectTools.js +437 -0
- package/dist/vendor/devtools/shared/repositories.js +2 -2
- package/dist/vendor/devtools/shared/session.js +133 -14
- package/dist/vendor/devtools/shared/sharedTransport.js +506 -0
- package/dist/vendor/devtools/shared/stateDirectory.js +44 -0
- package/dist/vendor/devtools/shared/workingDirectory.js +13 -0
- package/dist/vendor/devtools/shared/workspaceSettings.js +96 -0
- package/docs/agents-tools-panel.md +522 -0
- package/docs/codex-verification.md +37 -0
- package/docs/conversational-mcp.md +16 -3
- package/package.json +9 -5
- package/skills/mainsequence-mcp/SKILL.md +19 -4
- package/skills/ms/assets/mainsequence-mark.svg +453 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>MainSequence</title>
|
|
3
|
+
<style>
|
|
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>
|
|
32
|
+
</main>
|
|
33
|
+
<script type="module">
|
|
34
|
+
const BUILD = /* MAINSEQUENCE_PANEL_BUILD */ null;
|
|
35
|
+
const $ = id => document.getElementById(id), pending = new Map();
|
|
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
|
+
const environmentKey = state => state?.account?.checked ? 'mainsequence.environment.' + JSON.stringify([state.account.backend,state.account.username]) : null;
|
|
42
|
+
function rememberedEnvironment(state) {try {const key=environmentKey(state);return key ? localStorage.getItem(key) : null;} catch {return null;}}
|
|
43
|
+
function rememberEnvironment() {try {const key=environmentKey(snapshot),uid=$('environment').value;if(key && snapshot.environments.some(e=>e.uid===uid))localStorage.setItem(key,uid);} catch {}}
|
|
44
|
+
function accountIndicator(label,status='pending') {
|
|
45
|
+
const account=$('account');account.textContent=label;account.setAttribute('data-status',status);
|
|
46
|
+
const description=status==='connected'?'Connected':status==='error'?'Connection needs attention':'Connection not verified';
|
|
47
|
+
account.setAttribute('title',description);account.setAttribute('aria-label',description+': '+label);
|
|
48
|
+
}
|
|
49
|
+
function rpc(method, params = {}) {
|
|
50
|
+
const id = ++sequence;
|
|
51
|
+
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 }, '*'); });
|
|
52
|
+
}
|
|
53
|
+
function styles(context = {}) {
|
|
54
|
+
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);
|
|
55
|
+
if (['light', 'dark'].includes(context.theme)) document.documentElement.style.colorScheme = context.theme;
|
|
56
|
+
}
|
|
57
|
+
function node(tag, value, cls) { const item = document.createElement(tag); if (value !== undefined) item.textContent = value; if (cls) item.className = cls; return item; }
|
|
58
|
+
function append(parent,...children) {parent.append(...children.filter(Boolean));}
|
|
59
|
+
function group(cls, ...children) { const e = node('div', undefined, cls); e.append(...children.filter(Boolean)); return e; }
|
|
60
|
+
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; }
|
|
61
|
+
function card(title, ...children) { return group('card', node('h2', title), ...children); }
|
|
62
|
+
function caption(text) { return node('p', text, 'caption'); }
|
|
63
|
+
function empty(title, detail) { return group('empty', node('h2', title), caption(detail)); }
|
|
64
|
+
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; }
|
|
65
|
+
function details(label, content) { const d = node('details'); d.append(node('summary', label), content); return d; }
|
|
66
|
+
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; }
|
|
67
|
+
function clearResults() { $('code-filters').hidden=catalog!=='code';$('search-button').hidden=catalog==='code'; $('results').replaceChildren(); $('pager').hidden = true; currentPage = null; $('confirmation').replaceChildren(); }
|
|
68
|
+
function clearComposer() { ++preparationGeneration; recipient = null; recipientKinds=[]; draftRequest = null; $('response-kind-field').hidden=true; $('composer').hidden = true; $('message').value = ''; }
|
|
69
|
+
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'); }
|
|
70
|
+
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'); } }
|
|
71
|
+
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; }
|
|
72
|
+
function selection() { return { contextId: snapshot.contextId, environmentUid: $('environment').value }; }
|
|
73
|
+
const environmentName = () => snapshot?.environments.find(e => e.uid === $('environment').value)?.name || 'this environment';
|
|
74
|
+
async function view(kind, args = {}, target = selection()) { return call('mainsequence_view', { ...target, view: kind, ...args }); }
|
|
75
|
+
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; }
|
|
76
|
+
function latestAgent(uid, env) { return [...savedRequests].reverse().find(r => r.agentUid === uid && r.environmentUid === env); }
|
|
77
|
+
function defaults(agent) { return [agent.defaults?.provider, agent.defaults?.model, agent.defaults?.thinking].filter(Boolean).join(' · '); }
|
|
78
|
+
function icon(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; }
|
|
79
|
+
// A small Markdown subset; every content node is text. Remote HTML and links never execute.
|
|
80
|
+
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; }
|
|
81
|
+
function responseKinds(kinds, selected) {
|
|
82
|
+
recipientKinds = ['message','task'].filter(kind=>kinds.includes(kind));
|
|
83
|
+
$('response-kind').replaceChildren(...recipientKinds.map(kind=>{const option=node('option',kind==='task'?'Task':'Message');option.value=kind;return option;}));
|
|
84
|
+
$('response-kind').value=selected; $('response-kind-field').hidden=recipientKinds.length===0;
|
|
85
|
+
}
|
|
86
|
+
async function talk(agent, target) { if (messageBusy) return; const preserved = $('message').value;
|
|
87
|
+
const same=recipient?.agentUid===agent.uid && recipient?.environmentUid===target.environmentUid;
|
|
88
|
+
const kind=same && agent.responseKinds?.includes(recipient.responseKind) ? recipient.responseKind : agent.responseKinds?.includes('message') ? 'message' : 'task';
|
|
89
|
+
if(!same) draftRequest=null;
|
|
90
|
+
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} : {}) };
|
|
91
|
+
responseKinds(agent.responseKinds || [],kind); $('recipient').textContent = agent.name; $('composer').hidden = false; $('message').value = preserved; controls(); $('message').focus();
|
|
92
|
+
const selected={...recipient}; delete selected.name;delete selected.responseKind;delete selected.taskRequestId;const run=++preparationGeneration;$('delivery-notice').textContent='Preparing '+agent.name+'…';
|
|
93
|
+
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;}
|
|
94
|
+
}
|
|
95
|
+
async function continueTask(request) {
|
|
96
|
+
if(messageBusy)return;
|
|
97
|
+
if(!snapshot?.environments.some(env=>env.uid===request.environmentUid)){failure(new Error('This task’s environment is no longer available. Refresh your account.'));return;}
|
|
98
|
+
if($('environment').value!==request.environmentUid){$('environment').value=request.environmentUid;await $('environment').onchange();}
|
|
99
|
+
if(!snapshot?.contextId)return;
|
|
100
|
+
recipient={contextId:snapshot.contextId,environmentUid:request.environmentUid,agentUid:request.agentUid,name:request.agentName,responseKind:'task',taskRequestId:request.requestId,...(request.conversationKey?{conversationKey:request.conversationKey}:{})};
|
|
101
|
+
draftRequest=null;responseKinds(['task'],'task');$('recipient').textContent=request.agentName;$('composer').hidden=false;$('message').value='';controls();$('message').focus();
|
|
102
|
+
}
|
|
103
|
+
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; }
|
|
104
|
+
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)); }
|
|
105
|
+
async function refresh() {
|
|
106
|
+
clearComposer();assetCache.clear();++deliveryGeneration;$('delivery-results').replaceChildren();$('deliveries').hidden=true;
|
|
107
|
+
const run=++generation,previous=$('environment').value,previousKey=environmentKey(snapshot);
|
|
108
|
+
busy=true;snapshot=null;clearResults();controls();accountIndicator('Checking your session…');
|
|
109
|
+
$('account-issue').textContent='';$('notice').textContent='';
|
|
110
|
+
try {
|
|
111
|
+
const state=await call('mainsequence_open');if(run!==generation)return;snapshot=state;
|
|
112
|
+
accountIndicator(state.account.authenticated?(state.account.username||'Signed in'):'Not signed in',state.account.authenticated&&state.account.checked?'connected':state.issue?'error':'pending');
|
|
113
|
+
$('account-issue').textContent=state.issue?.message||(!state.account.authenticated?'Sign in with /ms login, then refresh.':'');
|
|
114
|
+
const placeholder=node('option',state.environments.length?'Choose environment ('+state.environments.length+')':state.issue?'Environments unavailable':'No environments available');placeholder.value='';
|
|
115
|
+
$('environment').replaceChildren(placeholder,...state.environments.map(env=>{const option=node('option',env.name);option.value=env.uid;return option;}));
|
|
116
|
+
const candidates=[previousKey===environmentKey(state)?previous:null,state.selectedEnvironmentUid,rememberedEnvironment(state),state.environments.length===1?state.environments[0].uid:null];
|
|
117
|
+
$('environment').value=candidates.find(uid=>state.environments.some(e=>e.uid===uid))||'';rememberEnvironment();
|
|
118
|
+
busy=false;controls();
|
|
119
|
+
// The account and environment must be usable even if receipt loading is slow.
|
|
120
|
+
if(state.messagingAvailable)void loadDeliveries();
|
|
121
|
+
await discover(0);
|
|
122
|
+
} catch(error) {if(run===generation){accountIndicator('Account status unavailable','error');failure(error);}}
|
|
123
|
+
finally {if(run===generation){busy=false;controls();}}
|
|
124
|
+
}
|
|
125
|
+
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}`; }
|
|
126
|
+
function back(label, fn) { return button('← ' + label, fn, 'plain muted'); }
|
|
127
|
+
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(); } } }
|
|
128
|
+
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); 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]]))); }); }
|
|
129
|
+
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); }
|
|
130
|
+
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); }); }
|
|
131
|
+
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))); }
|
|
132
|
+
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); } }); }
|
|
133
|
+
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')))); }
|
|
134
|
+
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.')); }
|
|
135
|
+
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 (run === generation) $('notice').textContent = ''; } catch (error) { if (run === generation) failure(error); } finally { if (run === generation) { busy = false; controls(); } } }
|
|
136
|
+
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; }
|
|
137
|
+
$('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');
|
|
138
|
+
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); } }; }
|
|
139
|
+
$('code-all').onclick=()=>{codeFilter='all';discover();};$('code-local').onclick=()=>{codeFilter='local';discover();};
|
|
140
|
+
let workspaceContext = null, workspaceContextKey = null, operationCache = [], codeFilter = 'all';
|
|
141
|
+
const conversationKeys = new Map();
|
|
142
|
+
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}; }
|
|
143
|
+
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}; }
|
|
144
|
+
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)))); }
|
|
145
|
+
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; }
|
|
146
|
+
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;}
|
|
147
|
+
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'; }
|
|
148
|
+
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();
|
|
149
|
+
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;
|
|
150
|
+
}
|
|
151
|
+
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();}));}
|
|
152
|
+
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;}
|
|
153
|
+
function disabled(label,reason){const b=button(label,()=>{});b.disabled=true;b.title=reason;return group('stack',b,caption(reason));}
|
|
154
|
+
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');
|
|
155
|
+
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');
|
|
156
|
+
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();};
|
|
157
|
+
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')))))),
|
|
158
|
+
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),
|
|
159
|
+
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)))); }
|
|
160
|
+
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();
|
|
161
|
+
append(output,
|
|
162
|
+
!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||''));
|
|
163
|
+
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')));}
|
|
164
|
+
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})))); }
|
|
165
|
+
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;
|
|
166
|
+
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));}); }
|
|
167
|
+
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}:{})});
|
|
168
|
+
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));}};
|
|
169
|
+
const actions=(items)=>group('row wrap',...items.map(([label,action])=>actionButton(label,action,input,progress)));
|
|
170
|
+
const image=field('Container image','mainsequence-project:latest'),platform=field('Docker platform','linux/amd64'),containerResult=node('div');
|
|
171
|
+
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));}}
|
|
172
|
+
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);}); }
|
|
173
|
+
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;
|
|
174
|
+
const centerButton=button('Open in Command Center',()=>link('project'));centerButton.disabled=!center;centerButton.title=center?'':'Set the Command Center URL in Settings.';
|
|
175
|
+
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')))));}); }
|
|
176
|
+
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))));}); }
|
|
177
|
+
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);}); }
|
|
178
|
+
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.'));}); }
|
|
179
|
+
|
|
180
|
+
function nativeRequestText(request) {
|
|
181
|
+
const action = request.state === 'delivered' ? 'Show the saved reply' : ['preparing', 'sending'].includes(request.state) ? 'Wait for the reply' : 'Deliver the saved message';
|
|
182
|
+
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.';
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function submitRequest(request, send) {
|
|
186
|
+
await call('mainsequence_submission', { requestId: request.requestId, outcome: 'attempted' });
|
|
187
|
+
try {
|
|
188
|
+
const output = await rpc('ui/message', { role: 'user', content: [{ type: 'text', text: nativeRequestText(request) }], _meta: { 'openai/message': { target: 'active', send } } });
|
|
189
|
+
if (output?.isError) throw new Error('Codex could not accept this message.');
|
|
190
|
+
await call('mainsequence_submission', { requestId: request.requestId, outcome: 'acknowledged' });
|
|
191
|
+
$('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.';
|
|
192
|
+
} catch (error) {
|
|
193
|
+
await call('mainsequence_submission', { requestId: request.requestId, outcome: 'unknown' }).catch(() => { });
|
|
194
|
+
$('delivery-notice').textContent = 'Codex’s acknowledgement is unknown. Your request is saved. Use Messages and recovery; do not send it again.';
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async function sendMessage(send) {
|
|
198
|
+
if (messageBusy || !recipient || !$('message').value.trim()) return;
|
|
199
|
+
const selected = { ...recipient }, message = $('message').value, run = generation;
|
|
200
|
+
messageBusy = true; controls(); $('delivery-notice').textContent = 'Saving your message…';
|
|
201
|
+
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() };
|
|
202
|
+
const draft = { ...draftRequest }; delete draft.name;
|
|
203
|
+
try {
|
|
204
|
+
const { request } = await call('mainsequence_stage_message', draft);
|
|
205
|
+
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; }
|
|
206
|
+
draftRequest.requestId = request.requestId;
|
|
207
|
+
if (['failed', 'uncertain', 'expired', 'cancelled'].includes(request.state)) { $('delivery-notice').textContent = request.reason || request.state; return; }
|
|
208
|
+
await submitRequest(request, send);
|
|
209
|
+
} catch (error) { $('delivery-notice').textContent = error.message; }
|
|
210
|
+
finally { messageBusy = false; controls(); await loadDeliveries(); }
|
|
211
|
+
}
|
|
212
|
+
async function loadDeliveries() {
|
|
213
|
+
if (!snapshot?.messagingAvailable) return;
|
|
214
|
+
const run = ++deliveryGeneration;
|
|
215
|
+
try {
|
|
216
|
+
const { requests } = await call('mainsequence_agent_status', { action: 'list' });
|
|
217
|
+
if (run !== deliveryGeneration) return;
|
|
218
|
+
savedRequests = requests; $('deliveries').hidden = requests.length === 0; $('delivery-results').replaceChildren();
|
|
219
|
+
for (const request of [...requests].reverse()) {
|
|
220
|
+
const card = node('article', undefined, 'item');
|
|
221
|
+
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;
|
|
222
|
+
const environment = snapshot.environments.find(e => e.uid === request.environmentUid)?.name || request.environmentUid;
|
|
223
|
+
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)));
|
|
224
|
+
if (request.reason) card.append(node('p', request.reason, 'muted'));
|
|
225
|
+
if (request.receipt) card.append(node('p', request.agentName + ': ' + request.receipt.message, 'receipt'));
|
|
226
|
+
const deliveryDetails = node('details'); deliveryDetails.append(node('summary', 'Delivery details'), node('small', 'Request ' + request.requestId + ' · Agent dispatches: ' + request.sendAttempts)); card.append(deliveryDetails);
|
|
227
|
+
if (['pending', 'preparing', 'sending', 'delivered'].includes(request.state) || (request.state === 'failed' && request.sendAttempts === 0)) {
|
|
228
|
+
const recover = node('button', request.state === 'delivered' ? 'Show reply in Codex' : 'Recover in Codex');
|
|
229
|
+
recover.onclick = async () => {
|
|
230
|
+
if (messageBusy) return; messageBusy = true; recover.disabled = true; controls();
|
|
231
|
+
try { await submitRequest(request, true); } catch (error) { $('delivery-notice').textContent = error.message; }
|
|
232
|
+
finally { messageBusy = false; controls(); await loadDeliveries(); }
|
|
233
|
+
}; card.append(recover);
|
|
234
|
+
}
|
|
235
|
+
if (request.state === 'pending') {
|
|
236
|
+
const cancel = node('button', 'Cancel unsent message');
|
|
237
|
+
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);
|
|
238
|
+
}
|
|
239
|
+
$('delivery-results').append(card);
|
|
240
|
+
}
|
|
241
|
+
if (!requests.length) $('delivery-results').append(node('p', 'No saved messages in this chat.', 'muted'));
|
|
242
|
+
} catch (error) { if (run === deliveryGeneration) $('delivery-notice').textContent = error.message; }
|
|
243
|
+
}
|
|
244
|
+
$('response-kind').onchange=()=>{if(!messageBusy && recipient && !recipient.taskRequestId && recipientKinds.includes($('response-kind').value)){recipient.responseKind=$('response-kind').value;draftRequest=null;}};
|
|
245
|
+
$('send-message').onclick = () => sendMessage(true); $('draft-message').onclick = () => sendMessage(false);
|
|
246
|
+
$('close-composer').onclick = () => { clearComposer(); controls(); };
|
|
247
|
+
$('refresh-deliveries').onclick = loadDeliveries;
|
|
248
|
+
|
|
249
|
+
function teardown() { ++deliveryGeneration; ++generation; connected = false; for (const p of pending.values()) { clearTimeout(p.timeout); p.reject(new Error('The panel closed.')); } pending.clear(); controls(); }
|
|
250
|
+
addEventListener('message', event => {
|
|
251
|
+
if (event.source !== parent || event.data?.jsonrpc !== '2.0') return;
|
|
252
|
+
const message = event.data, p = pending.get(message.id);
|
|
253
|
+
if (p) { pending.delete(message.id); clearTimeout(p.timeout); message.error ? p.reject(new Error(message.error.message)) : p.resolve(message.result); return; }
|
|
254
|
+
if (message.method === 'ui/notifications/host-context-changed') styles(message.params);
|
|
255
|
+
if (message.method === 'ui/resource-teardown') { teardown(); parent.postMessage({ jsonrpc: '2.0', id: message.id, result: {} }, '*'); }
|
|
256
|
+
});
|
|
257
|
+
addEventListener('pagehide', teardown);
|
|
258
|
+
controls();
|
|
259
|
+
if (window.parent === window) { $('account').textContent = 'Open MainSequence in Codex'; $('notice').textContent = 'This panel uses the Codex MCP App connection to read your existing devtools session.'; }
|
|
260
|
+
else try {
|
|
261
|
+
const initialized = await rpc('ui/initialize', { appInfo: { name: 'mainsequence', version: BUILD?.version || 'preview' }, appCapabilities: {}, protocolVersion: '2026-01-26' });
|
|
262
|
+
parent.postMessage({ jsonrpc: '2.0', method: 'ui/notifications/initialized', params: {} }, '*');
|
|
263
|
+
styles(initialized.hostContext); connected = true; await refresh();
|
|
264
|
+
} catch (error) { failure(error); controls(); }
|
|
265
|
+
</script></body></html>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createWorkspace = createWorkspace;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const node_url_1 = require("node:url");
|
|
6
|
+
const devtools_1 = require("../vendor/devtools/index.js");
|
|
7
|
+
const discovery_1 = require("./discovery.js");
|
|
8
|
+
const READ = new Set(['status', 'resolve', 'sdk-status', 'local-repositories', 'delete-local-preview', 'docker-preview', 'devcontainer-preview', 'deployment-history', 'deployment-run', 'deployment-logs', 'update-sdk', 'install-skills']);
|
|
9
|
+
const WRITE = new Set(['setup', 'sync', 'update-sdk', 'update-agents', 'install-skills', 'skills-sync', 'install', 'env-cleanup', 'open-folder', 'docker-build', 'devcontainer-write', 'delete-local', 'organization-project-setup', 'organization-project-link']);
|
|
10
|
+
const ALLOWED = new Set(['root', 'projectType', 'repositoryUid', 'branch', 'baseDir', 'install', 'dryRun', 'organization', 'confirmation', 'image', 'platform', 'expectedDigest', 'projectUid', 'branchUid', 'target', 'commandCenterUrl', 'codeRepositoryBranchUid', 'limit', 'offset', 'runUid', 'kind', 'stepUid', 'cursor']);
|
|
11
|
+
function createWorkspace(discovery, dependencies = {}) {
|
|
12
|
+
const jobs = new Map();
|
|
13
|
+
const ownerKey = (context) => JSON.stringify([context.backend, context.account, context.conversationId]);
|
|
14
|
+
const publicJob = ({ owner: _, controller: __, ...job }) => job;
|
|
15
|
+
async function execute(action, args, scope, environmentUid, signal, progress) {
|
|
16
|
+
if (dependencies.execute)
|
|
17
|
+
return dependencies.execute(action, args, scope, environmentUid, signal, progress);
|
|
18
|
+
const host = (0, devtools_1.createNodeHost)(progress);
|
|
19
|
+
host.openFolder = async (root) => { if (!await host.openBrowser((0, node_url_1.pathToFileURL)(root).href))
|
|
20
|
+
throw new devtools_1.ToolingError('BROWSER_FAILED', 'The folder could not open.'); };
|
|
21
|
+
const session = await (0, devtools_1.bindSessionAccount)((0, devtools_1.createSessionService)(host, { signal, fetch: dependencies.fetch, sessionLock: dependencies.sessionLock }), scope.backend, scope.account);
|
|
22
|
+
const input = { ...args, action, backend: scope.backend, expectedAccount: scope.account, environmentUid };
|
|
23
|
+
let result;
|
|
24
|
+
if (action.startsWith('deployment-'))
|
|
25
|
+
result = await (0, devtools_1.createDeployments)({ host, signal, fetch: dependencies.fetch, sessionLock: dependencies.sessionLock }).dispatch(input);
|
|
26
|
+
else if (devtools_1.PROJECT_OPERATION_ACTIONS.has(action))
|
|
27
|
+
result = await (0, devtools_1.createProjectOperations)({ host, signal }).dispatch(input);
|
|
28
|
+
else
|
|
29
|
+
result = await (0, devtools_1.createDevTools)({ host, session, signal, fetch: dependencies.fetch, sessionLock: dependencies.sessionLock }).dispatch({ ...input, ...(action === 'organization-project-setup' ? { instructionsFile: 'AGENTS.md' } : {}) });
|
|
30
|
+
await session.status(scope.backend, true);
|
|
31
|
+
if (action === 'organization-project-setup' && result.data && typeof result.data === 'object') {
|
|
32
|
+
const { claudeUrl: _, ...workspace } = result.data;
|
|
33
|
+
result = { ...result, data: workspace };
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
async function call(name, args, dispatch, signal, conversationId) {
|
|
38
|
+
if (name === 'mainsequence_settings') {
|
|
39
|
+
if (args.action === 'get')
|
|
40
|
+
return { preferences: await (0, devtools_1.readDesktopPreferences)(), runtime: { pluginWorkspace: null, workingDirectory: process.cwd(), nodeVersion: process.version, platform: process.platform + ' ' + process.arch,
|
|
41
|
+
capabilities: { folderPicker: false, signedNativeTerminal: false, nativeWorkspaceAttachment: false, folderPath: true } } };
|
|
42
|
+
if (args.action === 'save')
|
|
43
|
+
return { preferences: await (0, devtools_1.writeWorkspacePreferences)({ backend: String(args.backend || ''), baseDir: String(args.baseDir || ''), commandCenterUrl: String(args.commandCenterUrl || '') }) };
|
|
44
|
+
if (!['login', 'logout', 'refresh-token'].includes(String(args.action)))
|
|
45
|
+
throw new discovery_1.DiscoveryError('INVALID_REQUEST', 'Choose a supported account action.');
|
|
46
|
+
// The normal shared login service owns the browser and private credential persistence.
|
|
47
|
+
return { result: await dispatch({ action: args.action, backend: typeof args.backend === 'string' ? args.backend : undefined }) };
|
|
48
|
+
}
|
|
49
|
+
return discovery.scoped(dispatch, args, async (context, environmentUid) => {
|
|
50
|
+
if (context.conversationId !== conversationId)
|
|
51
|
+
throw new discovery_1.DiscoveryError('CONTEXT_CHANGED', 'Open MainSequence from this chat.');
|
|
52
|
+
if (name === 'mainsequence_operations') {
|
|
53
|
+
const owned = [...jobs.values()].filter(job => job.owner === ownerKey(context));
|
|
54
|
+
if (args.action === 'list')
|
|
55
|
+
return { operations: owned.map(publicJob) };
|
|
56
|
+
const job = owned.find(job => job.id === args.operationId);
|
|
57
|
+
if (!job)
|
|
58
|
+
throw new discovery_1.DiscoveryError('INVALID_REQUEST', 'No operation belongs to this account and chat.');
|
|
59
|
+
if (args.action === 'cancel' && job.status === 'running') {
|
|
60
|
+
job.progress = 'Cancelling…';
|
|
61
|
+
job.controller.abort();
|
|
62
|
+
}
|
|
63
|
+
else if (args.action !== 'get' && args.action !== 'cancel')
|
|
64
|
+
throw new discovery_1.DiscoveryError('INVALID_REQUEST', 'Choose list, get or cancel.');
|
|
65
|
+
return { operation: publicJob(job) };
|
|
66
|
+
}
|
|
67
|
+
const action = String(args.action), input = (args.input || {});
|
|
68
|
+
if (!input || Array.isArray(input) || typeof input !== 'object' || Object.keys(input).some(k => !ALLOWED.has(k)))
|
|
69
|
+
throw new discovery_1.DiscoveryError('INVALID_REQUEST', 'Unsupported workspace arguments.');
|
|
70
|
+
if (name === 'mainsequence_workspace' && action === 'local-context') {
|
|
71
|
+
const prefs = await (0, devtools_1.readDesktopPreferences)();
|
|
72
|
+
const session = await (0, devtools_1.bindSessionAccount)((0, devtools_1.createSessionService)((0, devtools_1.createNodeHost)(), { signal, fetch: dependencies.fetch, sessionLock: dependencies.sessionLock }), context.backend, context.account);
|
|
73
|
+
const client = new devtools_1.RepositoryClient(session, context.backend), profile = await client.getCurrentUserProfile();
|
|
74
|
+
const repositories = (await client.getCodeRepositories()).map(repo => ({ uid: repo.uid, name: repo.code_repository_name }));
|
|
75
|
+
const organization = (0, devtools_1.safeSlug)(profile.organization || 'default');
|
|
76
|
+
const local = await execute('local-repositories', { baseDir: prefs.baseDir, organization, repositories }, context, environmentUid, signal);
|
|
77
|
+
return { preferences: prefs, organization, local, pluginWorkspace: null };
|
|
78
|
+
}
|
|
79
|
+
if (!(name === 'mainsequence_workspace' ? READ : WRITE).has(action))
|
|
80
|
+
throw new discovery_1.DiscoveryError('INVALID_REQUEST', 'Unsupported workspace action.');
|
|
81
|
+
if (action.startsWith('organization-project') && (!context.projects.get(environmentUid)?.has(String(input.projectUid))))
|
|
82
|
+
throw new discovery_1.DiscoveryError('INVALID_REQUEST', 'Choose a project from the current directory.');
|
|
83
|
+
if (name === 'mainsequence_workspace' && ['update-sdk', 'install-skills'].includes(action) && input.dryRun !== true)
|
|
84
|
+
throw new discovery_1.DiscoveryError('INVALID_REQUEST', 'Preview requires dryRun: true.');
|
|
85
|
+
if (name === 'mainsequence_workspace')
|
|
86
|
+
return { result: await execute(action, input, context, environmentUid, signal) };
|
|
87
|
+
// Accepted operations outlive panel close/native Stop. Cancellation is explicit; never replay a job.
|
|
88
|
+
if ([...jobs.values()].filter(j => j.status === 'running').length >= 8)
|
|
89
|
+
throw new discovery_1.DiscoveryError('PROJECT_BUSY', 'Wait for a running operation to finish.');
|
|
90
|
+
while (jobs.size >= 100) {
|
|
91
|
+
const old = [...jobs.values()].find(j => j.status !== 'running');
|
|
92
|
+
if (!old)
|
|
93
|
+
break;
|
|
94
|
+
jobs.delete(old.id);
|
|
95
|
+
}
|
|
96
|
+
const job = { id: (0, node_crypto_1.randomUUID)(), action, context: String(input.root || input.projectUid || input.repositoryUid || ''), status: 'running', progress: 'Starting…', startedAt: new Date().toISOString(), owner: ownerKey(context), controller: new AbortController() };
|
|
97
|
+
jobs.set(job.id, job);
|
|
98
|
+
void execute(action, input, context, environmentUid, job.controller.signal, message => { job.progress = message; }).then(result => { job.result = result; job.status = result.status; job.progress = result.summary; }, error => {
|
|
99
|
+
job.status = job.controller.signal.aborted ? 'cancelled' : 'failure';
|
|
100
|
+
job.progress = error instanceof devtools_1.ToolingError ? error.message : 'The operation stopped. Inspect partial changes before retrying.';
|
|
101
|
+
job.result = { schemaVersion: 1, action, status: job.status, summary: job.progress, effects: { state: 'partial-or-uncertain', message: 'Local or remote state may have changed. No automatic retry was attempted.' } };
|
|
102
|
+
});
|
|
103
|
+
return { operation: publicJob(job) };
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return { call };
|
|
107
|
+
}
|