@oddessentials/agent-guild 1.22.0 → 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 (36) hide show
  1. package/bin/native/win32-arm64/agent-guild-report.exe +0 -0
  2. package/bin/native/win32-x64/agent-guild-report.exe +0 -0
  3. package/bin/windows/arm64/agent-guild-start.exe +0 -0
  4. package/bin/windows/x64/agent-guild-start.exe +0 -0
  5. package/docs/api.md +23 -12
  6. package/node_modules/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
  7. package/node_modules/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
  8. package/node_modules/node-pty/prebuilds/win32-x64/conpty.node +0 -0
  9. package/node_modules/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
  10. package/node_modules/smol-toml/LICENSE +24 -0
  11. package/node_modules/smol-toml/README.md +605 -0
  12. package/node_modules/smol-toml/dist/date.d.ts +41 -0
  13. package/node_modules/smol-toml/dist/date.js +156 -0
  14. package/node_modules/smol-toml/dist/error.d.ts +37 -0
  15. package/node_modules/smol-toml/dist/error.js +67 -0
  16. package/node_modules/smol-toml/dist/extract.js +293 -0
  17. package/node_modules/smol-toml/dist/index.cjs +771 -0
  18. package/node_modules/smol-toml/dist/index.d.ts +44 -0
  19. package/node_modules/smol-toml/dist/index.js +34 -0
  20. package/node_modules/smol-toml/dist/parse.d.ts +73 -0
  21. package/node_modules/smol-toml/dist/parse.js +164 -0
  22. package/node_modules/smol-toml/dist/primitive.js +151 -0
  23. package/node_modules/smol-toml/dist/stringify.d.ts +32 -0
  24. package/node_modules/smol-toml/dist/stringify.js +219 -0
  25. package/node_modules/smol-toml/dist/struct.js +156 -0
  26. package/node_modules/smol-toml/dist/util.d.ts +43 -0
  27. package/node_modules/smol-toml/dist/util.js +57 -0
  28. package/node_modules/smol-toml/package.json +68 -0
  29. package/package.json +2 -1
  30. package/src/manager/config.mjs +10 -10
  31. package/src/manager/mcp-connect.mjs +332 -98
  32. package/src/manager/server.mjs +6 -3
  33. package/web/app.js +82 -41
  34. package/web/index.html +10 -6
  35. package/web/styles.css +13 -4
  36. package/web/yard/art.json +1 -1
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,51 +2267,89 @@ function renderReportingSetup(card, provider) {
2265
2267
  };
2266
2268
  }
2267
2269
 
2268
- const MCP_STATES = { on: 'on', off: 'off', disabled: 'turned off in the tool', other: 'another server is named agent-guild', unknown: 'settings could not be read' };
2270
+ const openMcp = new Set();
2271
+ const mcpBusy = new Set();
2269
2272
 
2270
- function mcpText(provider, mcp) {
2271
- if (mcp.state === 'checking') return 'Agent Guild MCP: checking';
2272
- if (mcp.state === 'on') return mcp.total > 1 ? `Agent Guild MCP on for all ${mcp.total} accounts` : 'Agent Guild MCP on';
2273
- if (mcp.state === 'partial') return `Agent Guild MCP on for ${mcp.on} of ${mcp.total} accounts`;
2274
- if (mcp.state === 'disabled') return `Agent Guild MCP turned off in ${provider.tool}`;
2275
- if (mcp.state === 'other') return 'Agent Guild MCP: another server is named agent-guild';
2276
- if (mcp.state === 'unknown') return 'Agent Guild MCP: settings could not be read';
2277
- return 'Agent Guild MCP off';
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}.` };
2278
2306
  }
2279
2307
 
2280
2308
  function renderMcpSetup(card, provider) {
2281
- const row = card.querySelector('.mcp-row');
2282
- const mcp = provider.mcp;
2283
- row.hidden = !provider.available || !mcp;
2284
- if (row.hidden) return;
2285
- const text = row.querySelector('.mcp-text');
2286
- text.textContent = mcpText(provider, mcp);
2287
- text.title = mcp.accounts.map((a) => `${a.label}: ${MCP_STATES[a.state] ?? a.state}${a.detail ? `. ${a.detail}` : ''}`).join('\n');
2288
- const states = new Set(mcp.accounts.map((a) => a.state));
2289
- const turnOn = row.querySelector('.mcp-on');
2290
- const turnOff = row.querySelector('.mcp-off');
2291
- turnOn.hidden = !states.has('off') && !states.has('disabled');
2292
- turnOff.hidden = !states.has('on') && !states.has('disabled');
2293
- turnOn.title = `Lets ${provider.tool} see your other Agent Guild sessions, read their answers and screens, and start or stop sessions when you approve. Typing into sessions stays off. Adds agent-guild to ${provider.tool}'s MCP servers for every account.`;
2294
- turnOff.title = `Removes Agent Guild's MCP server from ${provider.tool}'s settings. A server named agent-guild that runs something else is left as it is.`;
2295
- const change = async (enabled) => {
2296
- turnOn.disabled = turnOff.disabled = true;
2297
- try {
2298
- const { results } = await api('POST', `/providers/${provider.id}/mcp`, { enabled });
2299
- const problems = results.filter((r) => r.outcome === 'failed' || r.outcome === 'skipped');
2300
- const done = results.length - problems.length;
2301
- const lead = !problems.length ? `Agent Guild MCP is ${enabled ? `on for ${provider.tool}. New sessions can use it` : `off for ${provider.tool}`}.`
2302
- : `Agent Guild MCP is ${enabled ? 'on' : 'off'} for ${done} of ${results.length} ${provider.tool} accounts.`;
2303
- toast([lead, ...problems.map((r) => `${r.label}: ${r.detail ?? MCP_STATES[r.state] ?? r.state}.`)].join(' '), problems.length ? 12000 : undefined);
2304
- } catch (err) {
2305
- if (err instanceof AuthError) return showAuth(err.message);
2306
- toast(err.message, 10000);
2307
- } finally {
2308
- turnOn.disabled = turnOff.disabled = false;
2309
- }
2310
- };
2311
- turnOn.onclick = () => change(true);
2312
- turnOff.onclick = () => change(false);
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
+ }
2313
2353
  }
2314
2354
 
2315
2355
  let dealt = false;
@@ -8595,6 +8635,7 @@ yardUi = initYard({
8595
8635
  renderLaunchFlags(node, provider);
8596
8636
  renderUsage(node, provider);
8597
8637
  renderReportingSetup(node, provider);
8638
+ renderMcpSetup(node, provider);
8598
8639
  renderModelStats(node, provider);
8599
8640
  node.dataset.contentSignature = contentSignature;
8600
8641
  }
package/web/index.html CHANGED
@@ -817,12 +817,6 @@
817
817
  <span class="reporting-text"></span>
818
818
  <button class="btn reporting-toggle" type="button"></button>
819
819
  </div>
820
- <div class="reporting-row mcp-row" hidden>
821
- <span class="mcp-text"></span>
822
- <a class="console-link mcp-docs" href="https://github.com/oddessentials/agent-guild/blob/main/docs/mcp.md" target="_blank" rel="noopener noreferrer" title="What Agent Guild's MCP server lets a coding agent do">About</a>
823
- <button class="btn mcp-on" type="button">Turn on</button>
824
- <button class="btn mcp-off" type="button">Turn off</button>
825
- </div>
826
820
  <div class="provider-links">
827
821
  <a class="console-link usage-link" target="_blank" rel="noopener noreferrer">Usage</a>
828
822
  <a class="console-link billing-link" target="_blank" rel="noopener noreferrer">Billing</a>
@@ -838,6 +832,16 @@
838
832
  <button class="btn multiplexer-refresh" type="button">Refresh</button>
839
833
  <div class="multiplexer-list"></div>
840
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>
841
845
  <p class="hint" hidden></p>
842
846
  <div class="provider-actions">
843
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
  }
@@ -308,8 +308,6 @@ main { max-width: calc(1200px + var(--dock-space, 0px)); margin: 0 auto; padding
308
308
  }
309
309
  .reporting-row { display: flex; align-items: center; gap: 10px; font-size: 0.78rem; color: var(--muted); }
310
310
  .reporting-row .btn { padding: 2px 10px; font-size: 0.75rem; white-space: nowrap; }
311
- .mcp-row { flex-wrap: wrap; row-gap: 4px; }
312
- .mcp-row .mcp-text { flex-basis: 100%; }
313
311
  .console-link { color: var(--accent); text-decoration: none; }
314
312
  .console-link:hover { text-decoration: underline; }
315
313
  .console-link:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 3px; }
@@ -338,6 +336,17 @@ main { max-width: calc(1200px + var(--dock-space, 0px)); margin: 0 auto; padding
338
336
  .provider .copies li span { display: block; }
339
337
  .provider .copies .copy-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; min-height: 24px; }
340
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; }
341
350
  .provider .copies .copy-path { font-family: var(--font-mono); font-size: 0.72rem; }
342
351
  .provider .copies .copy-uninstall { flex: none; padding: 2px 10px; font-size: 0.75rem; }
343
352
  .provider.busy { opacity: 0.6; pointer-events: none; }
@@ -1318,7 +1327,7 @@ body:has(#terminal-panel:not([hidden]) .terminal-controls:not([hidden])) .toast
1318
1327
 
1319
1328
  @media (prefers-reduced-motion: reduce) {
1320
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; }
1321
- .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; }
1322
1331
  .github-waiting::before, .github-running, body:has(#app[hidden]):has(#auth[hidden]):has(#stopped[hidden])::after { animation: none; }
1323
1332
  .btn:active:not(:disabled):not([aria-disabled="true"]) { transform: none; }
1324
1333
  .btn.voice[aria-pressed="true"]::before { animation: none; }