@oddessentials/agent-guild 1.21.2 → 1.22.1

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 (45) hide show
  1. package/bin/agent-guild.mjs +2 -0
  2. package/bin/native/win32-arm64/agent-guild-report.exe +0 -0
  3. package/bin/native/win32-x64/agent-guild-report.exe +0 -0
  4. package/bin/windows/arm64/agent-guild-start.exe +0 -0
  5. package/bin/windows/x64/agent-guild-start.exe +0 -0
  6. package/config/providers.default.json +4 -0
  7. package/docs/api.md +34 -3
  8. package/examples/terminal-ai-chat.json +27 -0
  9. package/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  10. package/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  11. package/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  12. package/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  13. package/node_modules/smol-toml/LICENSE +24 -0
  14. package/node_modules/smol-toml/README.md +605 -0
  15. package/node_modules/smol-toml/dist/date.d.ts +41 -0
  16. package/node_modules/smol-toml/dist/date.js +156 -0
  17. package/node_modules/smol-toml/dist/error.d.ts +37 -0
  18. package/node_modules/smol-toml/dist/error.js +67 -0
  19. package/node_modules/smol-toml/dist/extract.js +293 -0
  20. package/node_modules/smol-toml/dist/index.cjs +771 -0
  21. package/node_modules/smol-toml/dist/index.d.ts +44 -0
  22. package/node_modules/smol-toml/dist/index.js +34 -0
  23. package/node_modules/smol-toml/dist/parse.d.ts +73 -0
  24. package/node_modules/smol-toml/dist/parse.js +164 -0
  25. package/node_modules/smol-toml/dist/primitive.js +151 -0
  26. package/node_modules/smol-toml/dist/stringify.d.ts +32 -0
  27. package/node_modules/smol-toml/dist/stringify.js +219 -0
  28. package/node_modules/smol-toml/dist/struct.js +156 -0
  29. package/node_modules/smol-toml/dist/util.d.ts +43 -0
  30. package/node_modules/smol-toml/dist/util.js +57 -0
  31. package/node_modules/smol-toml/package.json +68 -0
  32. package/package.json +2 -1
  33. package/src/manager/config.mjs +10 -10
  34. package/src/manager/install-channels.mjs +3 -0
  35. package/src/manager/main.mjs +4 -0
  36. package/src/manager/mcp-connect.mjs +483 -0
  37. package/src/manager/providers.mjs +23 -4
  38. package/src/manager/server.mjs +16 -1
  39. package/src/manager/session-manager.mjs +2 -2
  40. package/web/app.js +92 -2
  41. package/web/index.html +11 -1
  42. package/web/styles.css +13 -2
  43. package/web/yard/art.json +1 -1
  44. package/web/yard/renderer.js +110 -36
  45. package/web/yard/view.js +13 -3
package/web/app.js CHANGED
@@ -1624,6 +1624,7 @@ function selectAccount(provider, id) {
1624
1624
  renderAccounts(card, provider);
1625
1625
  renderUsage(card, provider);
1626
1626
  renderReportingSetup(card, provider);
1627
+ renderMcpSetup(card, provider);
1627
1628
  }
1628
1629
  refreshPendingActions();
1629
1630
  notifyViews();
@@ -1966,6 +1967,7 @@ function renderAccounts(card, provider) {
1966
1967
  renderAccounts(card, provider);
1967
1968
  renderUsage(card, provider);
1968
1969
  renderReportingSetup(card, provider);
1970
+ renderMcpSetup(card, provider);
1969
1971
  });
1970
1972
  return chip;
1971
1973
  }));
@@ -2265,6 +2267,91 @@ function renderReportingSetup(card, provider) {
2265
2267
  };
2266
2268
  }
2267
2269
 
2270
+ const openMcp = new Set();
2271
+ const mcpBusy = new Set();
2272
+
2273
+ /** What the card's Agent Guild MCP section says about one account, and the change it offers. */
2274
+ function mcpView(provider, entry) {
2275
+ const tool = provider.tool;
2276
+ const from = entry.from ? `${tool} takes it from ${entry.from}'s settings. ` : '';
2277
+ const yours = !entry.managed && !entry.from ? 'You added it yourself. ' : '';
2278
+ const typing = entry.allowInput ? ' It can also type into sessions.' : '';
2279
+ if (entry.state === 'checking') return { label: 'Checking…', status: `Reading ${tool}'s MCP settings.` };
2280
+ if (entry.state === 'off') {
2281
+ return { label: 'Off', status: entry.detail ? `${entry.detail} Turn on adds Agent Guild's own server to ${tool}'s settings.` : `Not added to ${tool}.`, action: 'on', button: 'Turn on' };
2282
+ }
2283
+ if (entry.state === 'on') {
2284
+ return {
2285
+ label: 'On', status: `${from}${yours}New ${tool} sessions can use it.${typing}`, action: 'off', button: 'Turn off',
2286
+ ask: yours ? { message: `Remove the agent-guild server you added to ${tool}'s settings?\n\n${entry.command}`, ok: 'Remove' } : null,
2287
+ };
2288
+ }
2289
+ if (entry.state === 'disabled') {
2290
+ const replace = provider.mcp.mode === 'codex' && !entry.managed;
2291
+ return {
2292
+ label: 'Turned off', status: `${from}${yours}It is turned off in ${tool}.`, action: replace ? 'replace' : 'on', button: replace ? 'Replace…' : 'Turn on',
2293
+ ask: replace ? { message: `${tool} can turn this server on only by replacing it with Agent Guild's own. Replace it?\n\nNow: ${entry.command}`, ok: 'Replace' } : null,
2294
+ };
2295
+ }
2296
+ if (entry.state === 'conflict') {
2297
+ return {
2298
+ label: 'Conflict', warn: true, action: 'replace', button: 'Replace…',
2299
+ status: `${entry.from ? `${tool} takes a server named agent-guild from ${entry.from}'s settings that` : `${tool} already has a server named agent-guild that`} ${entry.detail}.`,
2300
+ ask: entry.from
2301
+ ? { message: `Add Agent Guild's own MCP server to ${tool}'s settings? ${tool} then uses it instead of the one in ${entry.from}'s settings, which stays as it is.`, ok: 'Add' }
2302
+ : { message: `Replace the agent-guild server in ${tool}'s settings with Agent Guild's own?${entry.command ? `\n\nNow: ${entry.command}` : ''}`, ok: 'Replace' },
2303
+ };
2304
+ }
2305
+ return { label: 'Unreadable', warn: true, status: `Agent Guild can't read ${tool}'s MCP settings: ${entry.detail}.` };
2306
+ }
2307
+
2308
+ function renderMcpSetup(card, provider) {
2309
+ const box = card.querySelector('.mcp');
2310
+ const account = selectedAccount(provider);
2311
+ const entry = provider.mcp?.accounts.find((a) => a.id === account.id);
2312
+ box.hidden = !provider.available || !entry;
2313
+ if (box.hidden) return;
2314
+ const view = mcpView(provider, entry);
2315
+ const key = `${provider.id}:${entry.id}`;
2316
+ box.open = openMcp.has(provider.id);
2317
+ box.ontoggle = () => (box.open ? openMcp.add(provider.id) : openMcp.delete(provider.id));
2318
+ box.classList.toggle('warned', Boolean(view.warn));
2319
+ box.querySelector('.mcp-state').textContent = view.label;
2320
+ box.querySelector('.mcp-about').textContent = `Lets ${provider.tool} see your Agent Guild sessions, read their replies and screens, and start or stop sessions when you approve.`;
2321
+ box.querySelector('.mcp-status').textContent = (provider.mcp.accounts.length > 1 ? `${entry.label} account: ` : '') + view.status;
2322
+ const command = box.querySelector('.mcp-command');
2323
+ command.hidden = !entry.command || entry.managed;
2324
+ command.textContent = entry.command ?? '';
2325
+ const button = box.querySelector('.mcp-action');
2326
+ button.hidden = !view.action;
2327
+ button.textContent = view.button ?? '';
2328
+ button.disabled = mcpBusy.has(key);
2329
+ button.onclick = () => changeMcp(provider, entry, view);
2330
+ }
2331
+
2332
+ function repaintMcp(id) {
2333
+ const provider = state.providers.find((p) => p.id === id);
2334
+ if (!provider) return;
2335
+ for (const card of document.querySelectorAll('.provider[data-id]')) if (card.dataset.id === id) renderMcpSetup(card, provider);
2336
+ }
2337
+
2338
+ async function changeMcp(provider, entry, view) {
2339
+ if (view.ask && !(await ask(view.ask.message, { ok: view.ask.ok }))) return;
2340
+ const key = `${provider.id}:${entry.id}`;
2341
+ mcpBusy.add(key);
2342
+ repaintMcp(provider.id);
2343
+ try {
2344
+ const { result } = await api('POST', `/providers/${provider.id}/mcp`, { account: entry.id, action: view.action, expect: entry.fingerprint });
2345
+ if (result.outcome === 'failed') toast(result.detail, 12000);
2346
+ } catch (err) {
2347
+ if (err instanceof AuthError) return showAuth(err.message);
2348
+ toast(err.message, 10000);
2349
+ } finally {
2350
+ mcpBusy.delete(key);
2351
+ repaintMcp(provider.id);
2352
+ }
2353
+ }
2354
+
2268
2355
  let dealt = false;
2269
2356
 
2270
2357
  // Both layouts mount these same controls with these same command handlers.
@@ -2311,7 +2398,8 @@ function buildProvider(provider) {
2311
2398
  if (provider.available && provider.instructionsSource && !instructionCounts.has(instructionKey(provider))) refreshInstructionCounts();
2312
2399
  const install = node.querySelector('.install');
2313
2400
  install.hidden = provider.available || !provider.installable;
2314
- install.title = provider.installCommand ? `Downloads the latest ${provider.tool} release into Docker's plugin folder, in a session.` : `Install ${provider.tool} using npm.${provider.npmNote ? ` ${provider.npmNote}` : ''}`;
2401
+ install.title = provider.plugin ? `Downloads the latest ${provider.tool} release into Docker's plugin folder, in a session.`
2402
+ : provider.installCommand ? `Run "${provider.installCommand}" in a session` : `Install ${provider.tool} using npm.${provider.npmNote ? ` ${provider.npmNote}` : ''}`;
2315
2403
  install.addEventListener('click', () => installProvider(provider, node));
2316
2404
  const update = node.querySelector('.update');
2317
2405
  update.hidden = !(provider.available && provider.updateCommand && (provider.updateAvailable || checkFailed));
@@ -2336,6 +2424,7 @@ function buildProvider(provider) {
2336
2424
  renderMultiplexers(node, provider);
2337
2425
  renderUsage(node, provider);
2338
2426
  renderReportingSetup(node, provider);
2427
+ renderMcpSetup(node, provider);
2339
2428
  renderModelStats(node, provider);
2340
2429
  renderProviderPending(node, provider);
2341
2430
  paintManagerActions(node);
@@ -3830,7 +3919,7 @@ function installNote(provider) {
3830
3919
  }
3831
3920
  if (last.kind === 'uninstall') return last.outcome === 'remaining' ? 'Uninstalled copy is still present' : '';
3832
3921
  if (last.verification === 'failed') return `Installation completed, but ${provider.tool} verification failed`;
3833
- if (last.outcome === 'missing') return provider.installCommand ? 'Installed, but docker does not list it' : 'Installed, but not found on PATH';
3922
+ if (last.outcome === 'missing') return provider.plugin ? 'Installed, but docker does not list it' : 'Installed, but not found on PATH';
3834
3923
  if (last.outcome === 'unchanged') return 'No version change after update';
3835
3924
  return '';
3836
3925
  }
@@ -8546,6 +8635,7 @@ yardUi = initYard({
8546
8635
  renderLaunchFlags(node, provider);
8547
8636
  renderUsage(node, provider);
8548
8637
  renderReportingSetup(node, provider);
8638
+ renderMcpSetup(node, provider);
8549
8639
  renderModelStats(node, provider);
8550
8640
  node.dataset.contentSignature = contentSignature;
8551
8641
  }
package/web/index.html CHANGED
@@ -137,7 +137,7 @@
137
137
  <div class="yard-overlay">
138
138
  <div id="yard-loading" class="yard-loading" role="status" data-mode="scene"><span id="yard-loading-text">Preparing your yard…</span><span id="yard-loading-size" class="yard-loading-size" aria-hidden="true"></span><span class="yard-loading-bar" aria-hidden="true"></span></div>
139
139
  <div id="yard-swap-failure" class="yard-swap-failure" hidden role="status"><span id="yard-swap-failure-text"></span><button id="yard-swap-retry" class="btn" type="button">Retry</button></div>
140
- <div class="yard-camera" role="group" aria-label="Camera"><button id="yard-zoom-in" type="button" class="btn" aria-label="Zoom in">+</button><button id="yard-zoom-out" type="button" class="btn" aria-label="Zoom out">−</button><button id="yard-fit" type="button" class="btn">Overview</button><button id="yard-focus" type="button" class="btn" disabled>Focus selected</button><button id="yard-photo" type="button" class="btn" aria-haspopup="dialog" aria-label="Save a picture of the Yard" title="Save a picture of the Yard" disabled hidden><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M4 8.5A1.5 1.5 0 0 1 5.5 7h2.3l1.4-2h5.6l1.4 2h2.3A1.5 1.5 0 0 1 20 8.5v9a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 4 17.5z"/><circle cx="12" cy="12.75" r="3.25"/></svg></button></div>
140
+ <div class="yard-camera" role="group" aria-label="Yard controls"><button id="yard-zoom-in" type="button" class="btn" aria-label="Zoom in">+</button><button id="yard-zoom-out" type="button" class="btn" aria-label="Zoom out">−</button><button id="yard-fit" type="button" class="btn">Overview</button><button id="yard-focus" type="button" class="btn" disabled>Focus selected</button><button id="yard-photo" type="button" class="btn" aria-haspopup="dialog" aria-label="Save a picture of the Yard" title="Save a picture of the Yard" disabled hidden><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path d="M4 8.5A1.5 1.5 0 0 1 5.5 7h2.3l1.4-2h5.6l1.4 2h2.3A1.5 1.5 0 0 1 20 8.5v9a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 4 17.5z"/><circle cx="12" cy="12.75" r="3.25"/></svg></button><button id="yard-fireworks" type="button" class="btn" aria-label="Fireworks in this Yard only" title="Fireworks in this Yard only" disabled><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M12 3v3m0 9v6M3 10h3m12 0h3M5.6 3.6l2.2 2.2m8.4 8.4 2.2 2.2M18.4 3.6l-2.2 2.2m-8.4 8.4-2.2 2.2"/><circle cx="12" cy="10" r="1"/></svg></button></div>
141
141
  <p id="yard-help"><span class="yard-help-pointer">Drag to explore · Scroll to zoom · Select to inspect · Double-click a session to open</span><span class="yard-help-touch">Drag to explore · Pinch to zoom · Tap to inspect · Open from the Inspector</span></p>
142
142
  </div>
143
143
  </div>
@@ -832,6 +832,16 @@
832
832
  <button class="btn multiplexer-refresh" type="button">Refresh</button>
833
833
  <div class="multiplexer-list"></div>
834
834
  </details>
835
+ <details class="mcp" hidden>
836
+ <summary><span class="mcp-title">Agent Guild MCP</span><span class="mcp-state"></span></summary>
837
+ <p class="mcp-about"></p>
838
+ <p class="mcp-status"></p>
839
+ <code class="mcp-command" hidden></code>
840
+ <div class="mcp-actions">
841
+ <a class="console-link" href="https://github.com/oddessentials/agent-guild/blob/main/docs/mcp.md" target="_blank" rel="noopener noreferrer">How it works</a>
842
+ <button class="btn mcp-action" type="button" hidden></button>
843
+ </div>
844
+ </details>
835
845
  <p class="hint" hidden></p>
836
846
  <div class="provider-actions">
837
847
  <button class="btn primary new" type="button">New</button>
package/web/styles.css CHANGED
@@ -243,7 +243,7 @@ main { max-width: calc(1200px + var(--dock-space, 0px)); margin: 0 auto; padding
243
243
  font: inherit; font-size: 0.78rem; text-align: left; cursor: pointer;
244
244
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
245
245
  }
246
- .model-stats-head::after, .console-link::after, .menu-button::after, .model-name::before, .provider .copies summary::before {
246
+ .model-stats-head::after, .console-link::after, .menu-button::after, .model-name::before, .provider .copies summary::before, .provider .mcp summary::before {
247
247
  content: ""; display: inline-block; flex: none; width: 0.75em; height: 0.75em; margin-left: 0.3em; vertical-align: -0.05em;
248
248
  background: currentColor; mask: var(--icon-chevron) center / contain no-repeat;
249
249
  }
@@ -336,6 +336,17 @@ main { max-width: calc(1200px + var(--dock-space, 0px)); margin: 0 auto; padding
336
336
  .provider .copies li span { display: block; }
337
337
  .provider .copies .copy-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 24px; }
338
338
  .provider .copies .copy-name { color: var(--text); min-width: 0; }
339
+ .provider .mcp { font-size: 0.78rem; color: var(--muted); overflow-wrap: anywhere; }
340
+ .provider .mcp summary { display: flex; align-items: baseline; cursor: pointer; list-style: none; white-space: nowrap; }
341
+ .provider .mcp summary::-webkit-details-marker { display: none; }
342
+ .provider .mcp summary::before { margin: 0 0.3em 0 0; align-self: center; transition: transform 0.15s var(--ease); }
343
+ .provider .mcp[open] summary::before { transform: rotate(90deg); }
344
+ .provider .mcp .mcp-state { margin-left: auto; padding-left: 8px; font-weight: 600; white-space: nowrap; }
345
+ .provider .mcp.warned .mcp-state { color: var(--warn); }
346
+ .provider .mcp p { margin: 6px 0 0; line-height: 1.45; }
347
+ .provider .mcp .mcp-command { display: block; margin-top: 4px; font-size: 0.72rem; color: var(--text); }
348
+ .provider .mcp .mcp-actions { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 8px; }
349
+ .provider .mcp .mcp-action { padding: 2px 10px; font-size: 0.75rem; white-space: nowrap; }
339
350
  .provider .copies .copy-path { font-family: var(--font-mono); font-size: 0.72rem; }
340
351
  .provider .copies .copy-uninstall { flex: none; padding: 2px 10px; font-size: 0.75rem; }
341
352
  .provider.busy { opacity: 0.6; pointer-events: none; }
@@ -1316,7 +1327,7 @@ body:has(#terminal-panel:not([hidden]) .terminal-controls:not([hidden])) .toast
1316
1327
 
1317
1328
  @media (prefers-reduced-motion: reduce) {
1318
1329
  .status-pill.active::before, .agent.working::after, .model-detail .stat-fill, .toast:not([hidden]), .tip:not([hidden]), .run-failed:not([hidden]), .menu:popover-open { animation: none; }
1319
- .model-name::before, .provider .copies summary::before, .menu-button::after, .btn, .videos-play img, .videos-play-icon, .models, .news-panel, .models::backdrop, .news-panel::backdrop { transition: none; }
1330
+ .model-name::before, .provider .copies summary::before, .provider .mcp summary::before, .menu-button::after, .btn, .videos-play img, .videos-play-icon, .models, .news-panel, .models::backdrop, .news-panel::backdrop { transition: none; }
1320
1331
  .github-waiting::before, .github-running, body:has(#app[hidden]):has(#auth[hidden]):has(#stopped[hidden])::after { animation: none; }
1321
1332
  .btn:active:not(:disabled):not([aria-disabled="true"]) { transform: none; }
1322
1333
  .btn.voice[aria-pressed="true"]::before { animation: none; }