@bill10/agent-007 0.15.0 → 0.15.1000

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.15.0.0
1
+ 0.15.1.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bill10/agent-007",
3
- "version": "0.15.0",
3
+ "version": "0.15.1000",
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/server/billion.js CHANGED
@@ -134,12 +134,30 @@ export function suggestProjectsDir(repoPaths, { ignoreUnder = CONFIG_DIR } = {})
134
134
  }
135
135
 
136
136
 
137
+ // Claude Code keeps each deferred tool's definition from the moment a
138
+ // conversation first loads it (a deferred_tools_record in the transcript), and
139
+ // a resumed conversation goes on using that copy, as it does the MCP server's
140
+ // instructions. A fresh tools/list, notifications/tools/list_changed and
141
+ // loading the tool again with ToolSearch all leave it (Claude Code 2.1.283). So an
142
+ // upgrade that changes a board tool leaves a resumed Billion reading the old
143
+ // one. Its calls still reach this server, which takes the new fields, so
144
+ // Billion only needs telling. This saves the current definitions to file and
145
+ // returns the names that differ from the last saved copy: all of them when
146
+ // there is none, since the conversation may predate any of them.
147
+ export function changedBoardTools(file, tools) {
148
+ let saved = [];
149
+ try { saved = JSON.parse(readFileSync(file, 'utf8')); } catch {}
150
+ const before = new Map((Array.isArray(saved) ? saved : []).map(t => [t?.name, JSON.stringify(t)]));
151
+ writeFileSync(file, `${JSON.stringify(tools, null, 2)}\n`);
152
+ return tools.filter(t => before.get(t.name) !== JSON.stringify(t)).map(t => t.name);
153
+ }
154
+
137
155
  // Everything Billion must do lives in its charter; the prompt only says which
138
156
  // part applies. A fresh repo gets the introduction. Any later start says both,
139
157
  // because a restart can land mid-introduction: the charter tells it to finish
140
158
  // the introduction while STATE.md still says "not started". --continue only
141
159
  // when a conversation exists, so a lost transcript still starts cleanly.
142
- export function billionCommand({ created, hasConversation, dir, projectsHint }) {
160
+ export function billionCommand({ created, hasConversation, dir, projectsHint, changedTools = [], toolsFile }) {
143
161
  const where = `Your folder is ${dir}.`;
144
162
  const hint = projectsHint
145
163
  ? `Suggest ${projectsHint} as the projects folder: most of the owner's repos are there.`
@@ -147,7 +165,11 @@ export function billionCommand({ created, hasConversation, dir, projectsHint })
147
165
  const prompt = created
148
166
  ? `This is your first run. Introduce yourself as described in CHARTER.md under "First run". ${where} ${hint}`
149
167
  : `You were restarted. If STATE.md still says "Status: not started", do or finish your introduction (CHARTER.md, "First run"). ${hint} Otherwise start your operating loop (CHARTER.md, "Operating loop"). ${where}`;
150
- return `claude --dangerously-skip-permissions${!created && hasConversation ? ' --continue' : ''} ${quote(prompt)}`;
168
+ const resumed = !created && hasConversation;
169
+ const stale = resumed && changedTools.length && toolsFile
170
+ ? ` Agent 007 changed these board tools since you last started: ${changedTools.join(', ')}. This conversation keeps the definitions it first loaded, so yours are out of date, and loading them again does not help. Read the current ones in ${toolsFile} and call those tools by it: the board accepts the new fields even where your copy does not list them.`
171
+ : '';
172
+ return `claude --dangerously-skip-permissions${resumed ? ' --continue' : ''} ${quote(prompt + stale)}`;
151
173
  }
152
174
 
153
175
  // Billion is Claude Code. Without it, its tab runs this instead: one line
package/server.js CHANGED
@@ -30,10 +30,11 @@ import { createSessionFromConfig } from './server/pty.js';
30
30
  import { setupWebSocket, broadcast, sessionPayload, broadcastOrphansList, verifyClient, respawnAgent, respawnBoardWorkers } from './server/ws.js';
31
31
  import { setupRoutes } from './server/http.js';
32
32
  import { startDispatcher, stopDispatcher, boardSettings, releasePushedOrphans } from './server/jobs.js';
33
- import { orphans, config } from './server/state.js';
33
+ import { orphans, config, CONFIG_DIR } from './server/state.js';
34
+ import { toolsFor } from './server/mcp.js';
34
35
  import { sweepMcpConfigs } from './server/agent-mcp.js';
35
36
  import { withDefaultPermission, envPermissionMode, PERMISSION_MODES, ENV_PERMISSION_MODE, sessionAgentFromCommand } from './lib/jobs.js';
36
- import { BILLION_NAME, billionEnabled, billionRuns, billionDir, ensureBillionRepo, refreshCharter, suggestProjectsDir, billionCommand, noClaudeCommand } from './server/billion.js';
37
+ import { BILLION_NAME, billionEnabled, billionRuns, billionDir, ensureBillionRepo, refreshCharter, suggestProjectsDir, billionCommand, noClaudeCommand, changedBoardTools } from './server/billion.js';
37
38
  import { commandExists, missingCommandMessage } from './server/command-path.js';
38
39
  import { parseCommand } from './lib/helpers.js';
39
40
  import { hasClaudeTranscript } from './server/agent-transcripts.js';
@@ -202,11 +203,23 @@ function startBillion() {
202
203
  }
203
204
  }
204
205
  const hasClaude = commandExists('claude', process.env, process.platform, dir);
206
+ // Without the model lists toolsFor adds: those follow what is installed,
207
+ // not an upgrade. Only when claude starts, so no start without it uses up
208
+ // the notice. Best effort, like the charter.
209
+ const toolsFile = join(CONFIG_DIR, 'billion-tools.json');
210
+ let changedTools = [];
211
+ if (hasClaude) {
212
+ try { changedTools = changedBoardTools(toolsFile, toolsFor({ isBillion: true })); } catch (err) {
213
+ console.error(`Billion: could not save the board tool definitions to ${toolsFile}:`, err.message);
214
+ }
215
+ }
205
216
  const command = hasClaude ? billionCommand({
206
217
  created,
207
218
  hasConversation: !created && hasClaudeTranscript(dir),
208
219
  dir,
209
220
  projectsHint: suggestProjectsDir(config.repos.map(r => r.path)),
221
+ changedTools,
222
+ toolsFile,
210
223
  }) : noClaudeCommand();
211
224
  const result = createSessionFromConfig({
212
225
  sessionId: nextSessionId(), name: BILLION_NAME, color: colorCycler.next(), command,