@bill10/agent-007 0.12.1000 → 0.12.2000

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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.12.1.0
1
+ 0.12.2.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bill10/agent-007",
3
- "version": "0.12.1000",
3
+ "version": "0.12.2000",
4
4
  "description": "From web terminals for your coding agents to a self-running agent company: Claude Code and Codex in parallel git worktrees, a job board they pick work from, and one agent that runs the board from a goal.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/public/index.html CHANGED
@@ -239,7 +239,7 @@
239
239
  <button data-view="files">Files</button>
240
240
  <button data-view="office" aria-current="true">Office</button>
241
241
  <button data-view="terminal">Terminal</button>
242
- <button data-view="waiting" class="mobile-nav-waiting" aria-label="Waiting on you"><svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round" aria-hidden="true"><path d="M3 8.5V5.5a3 3 0 0 1 6 0v3l1 1H2z"/><path d="M5 11h2"/></svg>Waiting<span class="board-tab-badge" id="mobile-nav-waiting-count" hidden></span></button>
242
+ <button data-view="waiting" class="mobile-nav-waiting empty" aria-label="Waiting on you" title="Waiting on you"><span class="bell"><svg width="14" height="14" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round" aria-hidden="true"><path d="M3 8.5V5.5a3 3 0 0 1 6 0v3l1 1H2z"/><path d="M5 11h2"/></svg><span class="board-tab-badge bell-badge" id="mobile-nav-waiting-count" hidden></span></span>Waiting</button>
243
243
  </nav>
244
244
 
245
245
  <!-- Reconnecting banner -->
@@ -368,14 +368,25 @@ export function removeSession(sessionId) {
368
368
  if (onSessionChanged) onSessionChanged();
369
369
  }
370
370
 
371
- export const BELL_ICON = '<svg class="board-tab-icon" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"><path d="M3 8.5V5.5a3 3 0 0 1 6 0v3l1 1H2z"/><path d="M5 11h2"/></svg>';
371
+ export const BELL_ICON = '<svg class="board-tab-icon" aria-hidden="true" width="14" height="14" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"><path d="M3 8.5V5.5a3 3 0 0 1 6 0v3l1 1H2z"/><path d="M5 11h2"/></svg>';
372
372
 
373
- // The phone's bottom bar carries the same count.
373
+ // The bell's notification badge: the open count, "9+" above 9, none at zero.
374
+ function bellBadge(badge, open) {
375
+ badge.textContent = open > 9 ? '9+' : open > 0 ? String(open) : '';
376
+ badge.hidden = open === 0;
377
+ }
378
+
379
+ const waitingLabel = (open) => open > 0 ? `Waiting on you, ${open} question${open === 1 ? '' : 's'}` : 'Waiting on you';
380
+
381
+ // The phone's bottom bar carries the same bell and badge.
374
382
  function updateNavBadge(open) {
375
383
  const badge = document.getElementById('mobile-nav-waiting-count');
376
384
  if (!badge) return;
377
- badge.textContent = open > 0 ? String(open) : '';
378
- badge.hidden = open === 0;
385
+ bellBadge(badge, open);
386
+ const btn = badge.closest('button');
387
+ btn.classList.toggle('empty', open === 0);
388
+ btn.setAttribute('aria-label', waitingLabel(open));
389
+ btn.title = waitingLabel(open);
379
390
  }
380
391
 
381
392
  export function updateTabs() {
@@ -407,18 +418,23 @@ export function updateTabs() {
407
418
  // Pinned next to it: Billion's questions to the owner, counted like Jobs.
408
419
  const waitingTab = document.createElement('div');
409
420
  waitingTab.className = `terminal-tab board-tab waiting-tab${waitingActive ? ' active' : ''}`;
410
- waitingTab.title = 'Waiting on you: Billion\'s questions';
411
- waitingTab.innerHTML = BELL_ICON;
412
- waitingTab.appendChild(document.createTextNode('Waiting'));
413
421
  const open = openCount();
414
- if (open > 0) {
415
- const badge = document.createElement('span');
416
- badge.className = 'board-tab-badge';
417
- badge.textContent = String(open);
418
- badge.title = `${open} question${open === 1 ? '' : 's'} waiting on you`;
419
- waitingTab.appendChild(badge);
420
- }
422
+ // Icon only: the label lives in aria-label and the tooltip.
423
+ waitingTab.classList.toggle('empty', open === 0);
424
+ waitingTab.setAttribute('role', 'button');
425
+ waitingTab.tabIndex = 0;
426
+ waitingTab.setAttribute('aria-label', waitingLabel(open));
427
+ waitingTab.title = waitingLabel(open);
428
+ const bell = document.createElement('span');
429
+ bell.className = 'bell';
430
+ bell.innerHTML = BELL_ICON;
431
+ const badge = document.createElement('span');
432
+ badge.className = 'board-tab-badge bell-badge';
433
+ bellBadge(badge, open);
434
+ bell.appendChild(badge);
435
+ waitingTab.appendChild(bell);
421
436
  waitingTab.onclick = () => { showWaiting(); updateTabs(); };
437
+ waitingTab.onkeydown = (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); waitingTab.onclick(); } };
422
438
  container.appendChild(waitingTab);
423
439
  updateNavBadge(open);
424
440
 
package/public/style.css CHANGED
@@ -1202,8 +1202,7 @@ body {
1202
1202
  cursor: pointer;
1203
1203
  }
1204
1204
  .mobile-nav button:hover, .mobile-nav button[aria-current="true"] { color: var(--accent); }
1205
- .mobile-nav-waiting { display: inline-flex; align-items: center; justify-content: center; gap: 5px; }
1206
- .mobile-nav-waiting .board-tab-badge { letter-spacing: 0; }
1205
+ .mobile-nav-waiting { display: inline-flex; align-items: center; justify-content: center; gap: 12px; }
1207
1206
  .waiting-choice, .waiting-send { min-height: 44px; }
1208
1207
  .waiting-reply input { min-height: 44px; font-size: 16px; }
1209
1208
  /* Overlays sized for desktop */
@@ -1301,6 +1300,25 @@ body {
1301
1300
  text-align: center;
1302
1301
  }
1303
1302
 
1303
+ /* The Waiting tab and phone button: a bell alone, with a phone-style count
1304
+ badge on its top-right corner; dimmed when nothing is open. */
1305
+ .waiting-tab { padding: 0 14px; }
1306
+ .bell { position: relative; display: inline-flex; flex-shrink: 0; }
1307
+ .bell-badge {
1308
+ position: absolute;
1309
+ top: -7px;
1310
+ left: 8px;
1311
+ min-width: 14px;
1312
+ padding: 0 3px;
1313
+ line-height: 14px;
1314
+ letter-spacing: 0;
1315
+ box-shadow: 0 0 0 2px var(--bg-tabs);
1316
+ }
1317
+ .terminal-tab.waiting-tab.active .bell-badge { box-shadow: 0 0 0 2px var(--bg-terminal); }
1318
+ .mobile-nav .bell-badge { box-shadow: 0 0 0 2px var(--bg-dark); }
1319
+ .terminal-tab.waiting-tab.empty:not(.active) .board-tab-icon,
1320
+ .mobile-nav-waiting.empty:not([aria-current="true"]) svg { opacity: 0.45; }
1321
+
1304
1322
  /* ============================================================
1305
1323
  Waiting on you
1306
1324
  Billion's questions to the owner. A pinned tab next to Jobs,
@@ -91,6 +91,10 @@ export function writeMcpConfig(sessionId, agentToken) {
91
91
  // server/permission-hook.js with this session's MCP config. Claude Code only;
92
92
  // Codex takes its hook differently and is not wired yet (docs/BILLION.md).
93
93
  const PERMISSION_HOOK = fileURLToPath(new URL('./permission-hook.js', import.meta.url));
94
+ // The two board tools the job prompt tells a worker on Billion's card to use:
95
+ // finishing its card, and asking Billion. Asking permission for those would
96
+ // only send Billion (or the owner) a request to approve its own instructions.
97
+ export const WORKER_BOARD_TOOLS = ['finish_job', 'send_message'];
94
98
  const shellQuote = (s) => `"${String(s).replace(/(["\\$`])/g, '\\$1')}"`;
95
99
  // Forward slashes on Windows, which node takes as well: a backslash means
96
100
  // something different to each shell a hook might run under (doubled by the
@@ -99,10 +103,7 @@ export const hookPath = (p, platform = process.platform) => (platform === 'win32
99
103
  export function withApprovalHook(file, args, configPath) {
100
104
  if (!configPath || agentName(file) !== 'claude' || args.includes('--settings')) return args;
101
105
  const settings = {
102
- // The two board tools the job prompt tells the worker to use: finishing
103
- // its card, and asking Billion. Asking permission for those would only
104
- // send Billion a request to approve its own instructions.
105
- permissions: { allow: ['finish_job', 'send_message'].map(tool => `mcp__${MCP_SERVER_NAME}__${tool}`) },
106
+ permissions: { allow: WORKER_BOARD_TOOLS.map(tool => `mcp__${MCP_SERVER_NAME}__${tool}`) },
106
107
  hooks: {
107
108
  PermissionRequest: [{
108
109
  matcher: '*',
@@ -113,6 +114,18 @@ export function withApprovalHook(file, args, configPath) {
113
114
  return ['--settings', JSON.stringify(settings), ...args];
114
115
  }
115
116
 
117
+ // The Codex side of the same pre-allow: per-run -c overrides, one separate argv
118
+ // element each, so ~/.codex/config.toml is never touched. Only these tools;
119
+ // everything else on the server keeps Codex's default (ask). Must come after
120
+ // withMcpConfig's server table on the command line, which replaces the table.
121
+ export function withCodexWorkerTools(file, args, configPath) {
122
+ if (!configPath || agentName(file) !== 'codex') return args;
123
+ return [
124
+ ...WORKER_BOARD_TOOLS.flatMap(tool => ['-c', `mcp_servers.${MCP_SERVER_NAME}.tools.${tool}.approval_mode="approve"`]),
125
+ ...args,
126
+ ];
127
+ }
128
+
116
129
  export function removeMcpConfig(sessionId) {
117
130
  try {
118
131
  rmSync(mcpConfigPath(sessionId), { force: true });
package/server/pty.js CHANGED
@@ -10,7 +10,7 @@ export { trackSyncFrames } from '../lib/helpers.js';
10
10
  import { resolveExecutable, isUsableCwd, commandExists, missingCommandMessage } from './command-path.js';
11
11
  import { RING_BUFFER_MAX } from './state.js';
12
12
  import { mintAgentToken, authEnabled } from './auth.js';
13
- import { writeMcpConfig, removeMcpConfig, withMcpConfig, takesMcpConfig, withApprovalHook } from './agent-mcp.js';
13
+ import { writeMcpConfig, removeMcpConfig, withMcpConfig, takesMcpConfig, withApprovalHook, withCodexWorkerTools } from './agent-mcp.js';
14
14
  import { broadcastJobs, requestDispatch } from './jobs.js';
15
15
  import { flushMessages, dropMessages } from './messages.js';
16
16
  import { sessionAgentFromCommand, permissionFlagsFromCommand } from '../lib/jobs.js';
@@ -244,7 +244,11 @@ export function createSessionFromConfig({ sessionId, name, color, command, repoP
244
244
  // use it — a plain `bash` tab does not need one.
245
245
  const mcpConfigPath = takesMcpConfig(file) ? writeMcpConfig(sessionId, agentToken) : null;
246
246
  const codexTrust = autoTrust && sessionAgentFromCommand(command) === 'codex';
247
- const mcpArgs = withMcpConfig(file, codexTrust ? [...codexTrustArgs(worktreePath), ...args] : args, mcpConfigPath);
247
+ const ownArgs = codexTrust ? [...codexTrustArgs(worktreePath), ...args] : args;
248
+ // Codex has no hook yet, but its worker on Billion's card still gets the
249
+ // same board tools pre-allowed. Inside withMcpConfig, whose server table
250
+ // override would otherwise replace them.
251
+ const mcpArgs = withMcpConfig(file, approvalsToBillion ? withCodexWorkerTools(file, ownArgs, mcpConfigPath) : ownArgs, mcpConfigPath);
248
252
  // A worker on one of Billion's cards asks Billion before it asks a person
249
253
  // (server/approvals.js) — where the CLI can be hooked, which today is
250
254
  // Claude Code only. Recorded as whether the hook actually went in.