diff --git a/README.md b/README.md index e3ceb1b..e34c066 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,11 @@ with, so nothing else is downloaded or built. Then: 2. Start a new chat, or toggle MainSequence off and on in the desktop Plugins settings to reload an existing chat. -To update, refresh the marketplace and reinstall the entry, then start a new chat: +The panel header always shows the plugin's version and whether a newer one is published. +**Update to …** asks first, then runs Codex's own plugin commands; start a new chat to use +the new version. If those commands cannot run from the chat, the panel shows them to run +yourself. To update from a terminal, refresh the marketplace and reinstall the entry, then +start a new chat: ```sh codex plugin marketplace upgrade mainsequence diff --git a/docs/agents-tools-panel.md b/docs/agents-tools-panel.md index a8c4498..0e13b8a 100644 --- a/docs/agents-tools-panel.md +++ b/docs/agents-tools-panel.md @@ -129,6 +129,20 @@ dispatch are never retried; after a process dies during send, or a transport res ambiguous, the request is **uncertain** and resend is blocked. A new request must never be used to bypass an uncertain one. +Plugin version and update: the panel header always shows the running plugin version. When +the panel opens, the server compares it with the `mainsequence` catalog on this +repository's default branch and with the copy Codex has installed (`codex plugin list +--json`, run with the Codex binary that started the server; nothing else named `codex` is +run). One successful check serves the chat's panels for 30 minutes; **Check for updates** +reads again. The header then shows **Up to date**, **Update to** the published version, +or that a newer version is installed and needs a new chat. **Update to** asks for +confirmation, then runs `codex plugin marketplace upgrade mainsequence` and `codex plugin +add mainsequence@mainsequence`. The running chat keeps its version: the server captured its +files at start and keeps working after Codex removes the old version's folder. When Codex's +commands cannot run, the plugin came from another marketplace, or the update fails, the +panel shows the two commands to run in a terminal. The tool is app-only, so the model never +updates the plugin. + ## Boundaries | MCP tool | Public devtools operations | @@ -149,6 +163,7 @@ used to bypass an uncertain one. | `mainsequence_action` | Reviewed allowlisted devtools repository/project operations | | `mainsequence_operations` | Host-owned accepted-operation status and cancellation | | `mainsequence_settings` | Public shared preferences and existing shared-session login/renewal/sign-out | +| `mainsequence_plugin` | App-only plugin version check against the published catalog, and the confirmed update through Codex's plugin commands | The discovery tools are model- and app-accessible and return useful structured results without a UI. Their arguments are allowlisted; they do not accept arbitrary devtools diff --git a/scripts/check-panel-package.cjs b/scripts/check-panel-package.cjs index 2700bf8..0f5b267 100644 --- a/scripts/check-panel-package.cjs +++ b/scripts/check-panel-package.cjs @@ -13,7 +13,7 @@ module.exports = async function checkPanel(installed, env) { try { await rpc('initialize', { protocolVersion: '2025-11-25', clientInfo: { name: 'package-check', version: '1' }, capabilities: {} }); const { tools } = await rpc('tools/list'); - if (tools.length !== 16 || !tools.slice(0, 3).every(tool => tool.annotations.readOnlyHint) || tools[4].annotations.readOnlyHint) throw new Error('Expected discovery and durable agent delivery tools'); + if (tools.length !== 17 || !tools.slice(0, 3).every(tool => tool.annotations.readOnlyHint) || tools[4].annotations.readOnlyHint) throw new Error('Expected discovery and durable agent delivery tools'); const uri = tools.find(tool => tool.name === 'mainsequence_open')?._meta?.ui?.resourceUri; const { contents } = await rpc('resources/read', { uri }); if (!contents[0].text.includes('const BUILD =') || !contents[0].text.includes('"panelId":')) throw new Error('The panel must contain immutable build metadata'); diff --git a/scripts/verify-panel-host.mjs b/scripts/verify-panel-host.mjs index fecf05d..91f3668 100644 --- a/scripts/verify-panel-host.mjs +++ b/scripts/verify-panel-host.mjs @@ -29,7 +29,7 @@ try { } assert.ok(server, 'Codex discovers the packaged server'); const tools = Object.values(server.tools); - assert.deepEqual(tools.map(t => t.name).sort(), ['mainsequence_agent_request', 'mainsequence_agent_status', 'mainsequence_call_tool', 'mainsequence_connection', 'mainsequence_discover', 'mainsequence_open', 'mainsequence_stage_message', 'mainsequence_submission', 'mainsequence_tools', 'mainsequence_view','mainsequence_agent_task','mainsequence_workspace','mainsequence_action','mainsequence_operations','mainsequence_settings','mainsequence_agent_prepare'].sort()); + assert.deepEqual(tools.map(t => t.name).sort(), ['mainsequence_agent_request', 'mainsequence_agent_status', 'mainsequence_call_tool', 'mainsequence_connection', 'mainsequence_discover', 'mainsequence_open', 'mainsequence_stage_message', 'mainsequence_submission', 'mainsequence_tools', 'mainsequence_view','mainsequence_agent_task','mainsequence_workspace','mainsequence_action','mainsequence_operations','mainsequence_settings','mainsequence_agent_prepare','mainsequence_plugin'].sort()); assert.equal(tools.find(t => t.name === 'mainsequence_agent_request').annotations.readOnlyHint, false); assert.equal(tools.find(t => t.name === 'mainsequence_agent_status').annotations.readOnlyHint, true); const opener = tools.find(t => t.name === 'mainsequence_open'); diff --git a/src/hosts/panel/panel.html b/src/hosts/panel/panel.html index cdd8e99..b1969c1 100644 --- a/src/hosts/panel/panel.html +++ b/src/hosts/panel/panel.html @@ -6,12 +6,14 @@ :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} .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} @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}} +.brand{flex-wrap:wrap;row-gap:2px}.version{color:var(--muted);font-size:12px}.release{gap:6px;font-size:12px;white-space:normal}.release button{min-height:24px;padding:2px 8px;font-size:12px}.release .ok{color:var(--color-text-success,light-dark(#16803d,#40c977))}.release .warn{color:var(--color-text-warning,light-dark(#9a6700,#e3b341))}.plugin-help{margin:10px 0 0}