@yanzc/claude-pro 0.0.0-stage → 0.4.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.
- package/.env.example +41 -0
- package/LICENSE +21 -0
- package/README.md +314 -3
- package/bin/claude-pro.js +32 -0
- package/package.json +40 -6
- package/src/agent.js +752 -0
- package/src/agents.js +186 -0
- package/src/api.js +529 -0
- package/src/cache.js +150 -0
- package/src/checkpoints.js +184 -0
- package/src/claude-code.js +60 -0
- package/src/codex/client.js +307 -0
- package/src/codex/translate-request.js +239 -0
- package/src/codex/translate-stream.js +328 -0
- package/src/commands.js +839 -0
- package/src/config.js +453 -0
- package/src/context.js +109 -0
- package/src/filestate.js +57 -0
- package/src/gateway.js +143 -0
- package/src/hooks.js +81 -0
- package/src/main.js +193 -0
- package/src/mcp.js +290 -0
- package/src/modes.js +154 -0
- package/src/oauth.js +287 -0
- package/src/peers.js +157 -0
- package/src/print.js +106 -0
- package/src/prompts.js +179 -0
- package/src/ratelimit.js +244 -0
- package/src/restart.js +115 -0
- package/src/runtime.js +162 -0
- package/src/selfcheck.js +71 -0
- package/src/session.js +139 -0
- package/src/supervisor.js +110 -0
- package/src/tasks.js +76 -0
- package/src/tools/bash.js +227 -0
- package/src/tools/effort.js +41 -0
- package/src/tools/files.js +352 -0
- package/src/tools/index.js +52 -0
- package/src/tools/interaction.js +157 -0
- package/src/tools/notebook.js +91 -0
- package/src/tools/peers.js +45 -0
- package/src/tools/restart.js +50 -0
- package/src/tools/search.js +458 -0
- package/src/tools/skill.js +32 -0
- package/src/tools/task.js +139 -0
- package/src/tools/web.js +283 -0
- package/src/ui/ansi.js +228 -0
- package/src/ui/dialogs.js +122 -0
- package/src/ui/editor.js +389 -0
- package/src/ui/markdown.js +249 -0
- package/src/ui/render.js +155 -0
- package/src/ui/repl.js +1460 -0
- package/src/ui/screen.js +259 -0
- package/src/updater.js +93 -0
- package/src/utils/clipboard.js +197 -0
- package/src/utils/diff.js +95 -0
- package/src/utils/paths.js +37 -0
- package/src/utils/shell.js +251 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ReadTool, WriteTool, EditTool } from './files.js';
|
|
2
|
+
import { makeBashTool, BashOutputTool, KillShellTool } from './bash.js';
|
|
3
|
+
import { GlobTool, GrepTool } from './search.js';
|
|
4
|
+
import { makeWebFetchTool, WebSearchTool } from './web.js';
|
|
5
|
+
import { TodoWriteTool, ExitPlanModeTool, AskUserQuestionTool } from './interaction.js';
|
|
6
|
+
import { makeTaskTool, TaskOutputTool, TaskStopTool } from './task.js';
|
|
7
|
+
import { NotebookEditTool } from './notebook.js';
|
|
8
|
+
import { makeSkillTool } from './skill.js';
|
|
9
|
+
import { RestartTool } from './restart.js';
|
|
10
|
+
import { makeEffortTool } from './effort.js';
|
|
11
|
+
import { ListPeersTool, MessagePeerTool } from './peers.js';
|
|
12
|
+
|
|
13
|
+
// Build the full tool set for the main agent.
|
|
14
|
+
// `restart`: this session can restart itself (it runs under the supervisor), so it gets the Restart tool.
|
|
15
|
+
// `peers`: an interactive session, which can talk to the other sessions on this machine.
|
|
16
|
+
export function buildTools(config, { agents, skills, mcpTools = [], restart = false, peers = false }) {
|
|
17
|
+
return [
|
|
18
|
+
makeTaskTool(agents),
|
|
19
|
+
...(skills?.size ? [makeSkillTool(skills)] : []),
|
|
20
|
+
TaskOutputTool,
|
|
21
|
+
TaskStopTool,
|
|
22
|
+
makeBashTool(config),
|
|
23
|
+
GlobTool,
|
|
24
|
+
GrepTool,
|
|
25
|
+
ReadTool,
|
|
26
|
+
EditTool,
|
|
27
|
+
WriteTool,
|
|
28
|
+
NotebookEditTool,
|
|
29
|
+
makeWebFetchTool(config),
|
|
30
|
+
WebSearchTool,
|
|
31
|
+
TodoWriteTool,
|
|
32
|
+
BashOutputTool,
|
|
33
|
+
KillShellTool,
|
|
34
|
+
ExitPlanModeTool,
|
|
35
|
+
AskUserQuestionTool,
|
|
36
|
+
makeEffortTool(config),
|
|
37
|
+
...(restart ? [RestartTool] : []),
|
|
38
|
+
...(peers ? [ListPeersTool, MessagePeerTool] : []),
|
|
39
|
+
...mcpTools,
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Effort: a sub-agent shares the main agent's settings, so it must not change them.
|
|
44
|
+
const NEVER_FOR_SUBAGENTS = new Set(['TaskOutput', 'TaskStop', 'TodoWrite', 'ExitPlanMode', 'AskUserQuestion', 'Restart', 'Effort', 'ListPeers', 'MessagePeer']);
|
|
45
|
+
|
|
46
|
+
// Tools for a sub-agent. `allowTask`: it is below the nesting limit, so it may start sub-agents of its own.
|
|
47
|
+
export function toolsForSubagent(allTools, def, { allowTask = false } = {}) {
|
|
48
|
+
const base = allTools.filter((t) => !NEVER_FOR_SUBAGENTS.has(t.name) && (allowTask || t.name !== 'Task'));
|
|
49
|
+
if (def.tools === '*' || !def.tools) return base;
|
|
50
|
+
const wanted = new Set(def.tools);
|
|
51
|
+
return base.filter((t) => wanted.has(t.name) || [...wanted].some((w) => w.startsWith('mcp__') && t.name.startsWith(w)));
|
|
52
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Tools that interact with the session rather than the machine: TodoWrite,
|
|
2
|
+
// ExitPlanMode and AskUserQuestion.
|
|
3
|
+
|
|
4
|
+
export const TodoWriteTool = {
|
|
5
|
+
name: 'TodoWrite',
|
|
6
|
+
readOnly: true,
|
|
7
|
+
description: `Use this tool to create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
|
|
8
|
+
|
|
9
|
+
## When to Use This Tool
|
|
10
|
+
Use this tool proactively in these scenarios:
|
|
11
|
+
1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
|
|
12
|
+
2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
|
|
13
|
+
3. User explicitly requests todo list - When the user directly asks you to use the todo list
|
|
14
|
+
4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
|
|
15
|
+
5. After receiving new instructions - Immediately capture user requirements as todos
|
|
16
|
+
6. When you start working on a task - Mark it as in_progress BEFORE beginning work. Ideally you should only have one todo as in_progress at a time
|
|
17
|
+
7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation
|
|
18
|
+
|
|
19
|
+
## When NOT to Use This Tool
|
|
20
|
+
Skip using this tool when:
|
|
21
|
+
1. There is only a single, straightforward task
|
|
22
|
+
2. The task is trivial and tracking it provides no organizational benefit
|
|
23
|
+
3. The task can be completed in less than 3 trivial steps
|
|
24
|
+
4. The task is purely conversational or informational
|
|
25
|
+
|
|
26
|
+
## Task States and Management
|
|
27
|
+
1. **Task States**:
|
|
28
|
+
- pending: Task not yet started
|
|
29
|
+
- in_progress: Currently working on (limit to ONE task at a time)
|
|
30
|
+
- completed: Task finished successfully
|
|
31
|
+
**IMPORTANT**: Task descriptions must have two forms:
|
|
32
|
+
- content: The imperative form describing what needs to be done (e.g., "Run tests")
|
|
33
|
+
- activeForm: The present continuous form shown during execution (e.g., "Running tests")
|
|
34
|
+
2. **Task Management**:
|
|
35
|
+
- Update task status in real-time as you work
|
|
36
|
+
- Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
|
|
37
|
+
- Exactly ONE task must be in_progress at any time (not less, not more)
|
|
38
|
+
- Remove tasks that are no longer relevant from the list entirely
|
|
39
|
+
3. **Task Completion Requirements**:
|
|
40
|
+
- ONLY mark a task as completed when you have FULLY accomplished it
|
|
41
|
+
- If you encounter errors, blockers, or cannot finish, keep the task as in_progress
|
|
42
|
+
- Never mark a task as completed if tests are failing, the implementation is partial, or you hit unresolved errors
|
|
43
|
+
|
|
44
|
+
When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.`,
|
|
45
|
+
input_schema: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
todos: {
|
|
49
|
+
type: 'array',
|
|
50
|
+
description: 'The updated todo list',
|
|
51
|
+
items: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: {
|
|
54
|
+
content: { type: 'string', minLength: 1 },
|
|
55
|
+
status: { type: 'string', enum: ['pending', 'in_progress', 'completed'] },
|
|
56
|
+
activeForm: { type: 'string', minLength: 1 },
|
|
57
|
+
},
|
|
58
|
+
required: ['content', 'status', 'activeForm'],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
required: ['todos'],
|
|
63
|
+
},
|
|
64
|
+
summarize() { return ''; },
|
|
65
|
+
async call(input, ctx) {
|
|
66
|
+
const todos = Array.isArray(input.todos) ? input.todos.filter((t) => t && t.content) : [];
|
|
67
|
+
ctx.agent.todos = todos.every((t) => t.status === 'completed') ? [] : todos;
|
|
68
|
+
return {
|
|
69
|
+
content: 'Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable',
|
|
70
|
+
display: { todos },
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const ExitPlanModeTool = {
|
|
76
|
+
name: 'ExitPlanMode',
|
|
77
|
+
readOnly: true,
|
|
78
|
+
description: `Use this tool when you are in plan mode and have finished planning the implementation steps of a task that requires writing code, and are ready to code. This will prompt the user to approve your plan.
|
|
79
|
+
Only use this tool when the task requires planning the implementation steps of a task that requires writing code. For research tasks where you're gathering information, searching files, reading files or in general trying to understand the codebase - do NOT use this tool.`,
|
|
80
|
+
input_schema: {
|
|
81
|
+
type: 'object',
|
|
82
|
+
properties: {
|
|
83
|
+
plan: { type: 'string', description: 'The plan you came up with, that you want to run by the user for approval. Supports markdown. The plan should be pretty concise.' },
|
|
84
|
+
},
|
|
85
|
+
required: ['plan'],
|
|
86
|
+
},
|
|
87
|
+
summarize() { return ''; },
|
|
88
|
+
async call(input, ctx) {
|
|
89
|
+
if (ctx.modes.mode !== 'plan') return { isError: true, content: 'You are not in plan mode. Proceed with the task.' };
|
|
90
|
+
const choice = await ctx.io.approvePlan(input.plan);
|
|
91
|
+
if (choice === 'full') {
|
|
92
|
+
ctx.modes.setMode('full');
|
|
93
|
+
return { content: 'User has approved your plan. You can now start coding. Start with updating your todo list if applicable.', display: { plan: input.plan, approved: true } };
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
content: 'The user wants to keep planning. Do not start implementation. Wait for the user to explain what should change in the plan.',
|
|
97
|
+
display: { plan: input.plan, approved: false },
|
|
98
|
+
stopTurn: true,
|
|
99
|
+
};
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const AskUserQuestionTool = {
|
|
104
|
+
name: 'AskUserQuestion',
|
|
105
|
+
readOnly: true,
|
|
106
|
+
description: `Use this tool when you need to ask the user questions during execution. This allows you to:
|
|
107
|
+
1. Gather user preferences or requirements
|
|
108
|
+
2. Clarify ambiguous instructions
|
|
109
|
+
3. Get decisions on implementation choices as you work
|
|
110
|
+
4. Offer choices to the user about what direction to take.
|
|
111
|
+
|
|
112
|
+
Usage notes:
|
|
113
|
+
- Users will always be able to select "Other" to provide custom text input
|
|
114
|
+
- Use multiSelect: true to allow multiple answers to be selected for a question
|
|
115
|
+
- If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label`,
|
|
116
|
+
input_schema: {
|
|
117
|
+
type: 'object',
|
|
118
|
+
properties: {
|
|
119
|
+
questions: {
|
|
120
|
+
type: 'array',
|
|
121
|
+
minItems: 1,
|
|
122
|
+
maxItems: 4,
|
|
123
|
+
items: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
properties: {
|
|
126
|
+
question: { type: 'string', description: 'The complete question to ask the user' },
|
|
127
|
+
header: { type: 'string', description: 'Very short label (max 12 chars)' },
|
|
128
|
+
multiSelect: { type: 'boolean' },
|
|
129
|
+
options: {
|
|
130
|
+
type: 'array',
|
|
131
|
+
minItems: 2,
|
|
132
|
+
maxItems: 4,
|
|
133
|
+
items: {
|
|
134
|
+
type: 'object',
|
|
135
|
+
properties: { label: { type: 'string' }, description: { type: 'string' } },
|
|
136
|
+
required: ['label', 'description'],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
required: ['question', 'header', 'options', 'multiSelect'],
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
required: ['questions'],
|
|
145
|
+
},
|
|
146
|
+
summarize(input) { return (input.questions || []).map((q) => q.header).join(', '); },
|
|
147
|
+
async call(input, ctx) {
|
|
148
|
+
const answers = {};
|
|
149
|
+
for (const q of input.questions || []) {
|
|
150
|
+
const a = await ctx.io.askQuestion(q);
|
|
151
|
+
if (a === null) return { content: 'The user declined to answer the questions.', stopTurn: true, display: { summary: 'Declined' } };
|
|
152
|
+
answers[q.question] = a;
|
|
153
|
+
}
|
|
154
|
+
const text = Object.entries(answers).map(([q, a]) => `"${q}"="${a}"`).join(', ');
|
|
155
|
+
return { content: `User has answered your questions: ${text}. You can now continue with the user's answers in mind.`, display: { summary: Object.values(answers).join(' · ') } };
|
|
156
|
+
},
|
|
157
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// NotebookEdit: replace, insert or delete a cell of a Jupyter notebook. Like Edit, the
|
|
2
|
+
// notebook must have been read first and unchanged on disk since.
|
|
3
|
+
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import { resolvePath, displayPath } from '../utils/paths.js';
|
|
6
|
+
import { checkFreshness, mtime } from './files.js';
|
|
7
|
+
|
|
8
|
+
// Jupyter stores cell source as an array of lines, each keeping its trailing newline.
|
|
9
|
+
function toSource(text) {
|
|
10
|
+
const lines = text.split('\n');
|
|
11
|
+
return lines.map((l, i) => (i < lines.length - 1 ? l + '\n' : l)).filter((l, i, a) => !(i === a.length - 1 && l === ''));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function findCell(nb, id) {
|
|
15
|
+
const cells = nb.cells || [];
|
|
16
|
+
const byId = cells.findIndex((c) => c.id === id);
|
|
17
|
+
if (byId >= 0) return byId;
|
|
18
|
+
const m = /^cell-(\d+)$/.exec(String(id)) || /^(\d+)$/.exec(String(id));
|
|
19
|
+
if (m && +m[1] < cells.length) return +m[1];
|
|
20
|
+
return -1;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const NotebookEditTool = {
|
|
24
|
+
name: 'NotebookEdit',
|
|
25
|
+
editsFiles: true,
|
|
26
|
+
description: `Completely replaces the contents of a specific cell in a Jupyter notebook (.ipynb file) with new source. Jupyter notebooks are interactive documents that combine code, text, and visualizations, commonly used for data analysis and scientific computing.
|
|
27
|
+
- notebook_path must be an absolute path, and the notebook must have been read with Read first.
|
|
28
|
+
- cell_id is the id of the cell (or its 0-based index, as in "cell-3"). When inserting, the new cell goes after the cell with this id; omit it to insert at the beginning.
|
|
29
|
+
- edit_mode is "replace" (default), "insert" or "delete". cell_type ("code" or "markdown") is required for insert.`,
|
|
30
|
+
input_schema: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
notebook_path: { type: 'string', description: 'The absolute path to the Jupyter notebook file to edit' },
|
|
34
|
+
cell_id: { type: 'string', description: 'The ID (or "cell-N" index) of the cell to edit' },
|
|
35
|
+
new_source: { type: 'string', description: 'The new source for the cell' },
|
|
36
|
+
cell_type: { type: 'string', enum: ['code', 'markdown'], description: 'The type of the cell (required for insert)' },
|
|
37
|
+
edit_mode: { type: 'string', enum: ['replace', 'insert', 'delete'], description: 'The type of edit to make (default: replace)' },
|
|
38
|
+
},
|
|
39
|
+
required: ['notebook_path', 'new_source'],
|
|
40
|
+
},
|
|
41
|
+
summarize(input, ctx) { return displayPath(resolvePath(input.notebook_path, ctx.cwd), ctx.cwd); },
|
|
42
|
+
filePath(input, ctx) { return resolvePath(input.notebook_path, ctx.cwd); },
|
|
43
|
+
validate(input, ctx) {
|
|
44
|
+
const file = resolvePath(input.notebook_path, ctx.cwd);
|
|
45
|
+
if (!file.endsWith('.ipynb')) return 'File must be a Jupyter notebook (.ipynb file). For other file types, use the Edit tool.';
|
|
46
|
+
if (!fs.existsSync(file)) return `Notebook does not exist: ${file}`;
|
|
47
|
+
if ((input.edit_mode || 'replace') === 'insert' && !input.cell_type) return 'cell_type is required when edit_mode is insert.';
|
|
48
|
+
return checkFreshness(file, ctx);
|
|
49
|
+
},
|
|
50
|
+
async call(input, ctx) {
|
|
51
|
+
const err = this.validate(input, ctx);
|
|
52
|
+
if (err) return { isError: true, content: err };
|
|
53
|
+
const file = resolvePath(input.notebook_path, ctx.cwd);
|
|
54
|
+
let nb;
|
|
55
|
+
try { nb = JSON.parse(fs.readFileSync(file, 'utf8')); } catch (e) { return { isError: true, content: `Could not parse notebook: ${e.message}` }; }
|
|
56
|
+
nb.cells ||= [];
|
|
57
|
+
const mode = input.edit_mode || 'replace';
|
|
58
|
+
let summary;
|
|
59
|
+
if (mode === 'insert') {
|
|
60
|
+
let at = 0;
|
|
61
|
+
if (input.cell_id !== undefined && input.cell_id !== '') {
|
|
62
|
+
const i = findCell(nb, input.cell_id);
|
|
63
|
+
if (i < 0) return { isError: true, content: `Cell not found: ${input.cell_id}` };
|
|
64
|
+
at = i + 1;
|
|
65
|
+
}
|
|
66
|
+
const cell = { cell_type: input.cell_type, id: Math.random().toString(36).slice(2, 10), metadata: {}, source: toSource(input.new_source) };
|
|
67
|
+
if (input.cell_type === 'code') { cell.execution_count = null; cell.outputs = []; }
|
|
68
|
+
nb.cells.splice(at, 0, cell);
|
|
69
|
+
summary = `Inserted cell ${cell.id} at position ${at}`;
|
|
70
|
+
} else {
|
|
71
|
+
const i = findCell(nb, input.cell_id);
|
|
72
|
+
if (i < 0) return { isError: true, content: `Cell not found: ${input.cell_id ?? '(no cell_id given)'}` };
|
|
73
|
+
if (mode === 'delete') {
|
|
74
|
+
nb.cells.splice(i, 1);
|
|
75
|
+
summary = `Deleted cell ${input.cell_id}`;
|
|
76
|
+
} else {
|
|
77
|
+
const cell = nb.cells[i];
|
|
78
|
+
cell.source = toSource(input.new_source);
|
|
79
|
+
if (input.cell_type && input.cell_type !== cell.cell_type) {
|
|
80
|
+
cell.cell_type = input.cell_type;
|
|
81
|
+
if (input.cell_type === 'code') { cell.execution_count = null; cell.outputs = []; } else { delete cell.execution_count; delete cell.outputs; }
|
|
82
|
+
}
|
|
83
|
+
if (cell.cell_type === 'code') { cell.execution_count = null; cell.outputs = []; }
|
|
84
|
+
summary = `Updated cell ${input.cell_id}`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
fs.writeFileSync(file, JSON.stringify(nb, null, 1) + '\n', 'utf8');
|
|
88
|
+
ctx.fileState.set(file, { mtimeMs: mtime(file) });
|
|
89
|
+
return { content: summary, display: { summary } };
|
|
90
|
+
},
|
|
91
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// ListPeers and MessagePeer: talk to the other claude-pro sessions running on this machine (see peers.js).
|
|
2
|
+
|
|
3
|
+
const unavailable = { isError: true, content: 'Talking to other sessions is only available in an interactive session.' };
|
|
4
|
+
|
|
5
|
+
export const ListPeersTool = {
|
|
6
|
+
name: 'ListPeers',
|
|
7
|
+
description: 'List the other claude-pro sessions running on this machine (each is an agent with its own conversation, working in its own directory), with the name to send them a message with MessagePeer.',
|
|
8
|
+
input_schema: { type: 'object', properties: {} },
|
|
9
|
+
summarize() { return ''; },
|
|
10
|
+
async call(input, ctx) {
|
|
11
|
+
const node = ctx.io?.peers;
|
|
12
|
+
if (!node) return unavailable;
|
|
13
|
+
const peers = node.peers();
|
|
14
|
+
const me = `You are "${node.name}" (${node.cwd}).`;
|
|
15
|
+
if (!peers.length) return { content: `${me}\nNo other session is running.`, display: { summary: 'No other session' } };
|
|
16
|
+
return {
|
|
17
|
+
content: `${me}\nOther sessions:\n${peers.map((p) => `- ${p.name}: working in ${p.cwd}`).join('\n')}`,
|
|
18
|
+
display: { summary: `${peers.length} other session${peers.length === 1 ? '' : 's'}` },
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const MessagePeerTool = {
|
|
24
|
+
name: 'MessagePeer',
|
|
25
|
+
description: `Send a task or a question to another claude-pro session running on this machine (ListPeers lists them). The other session's agent works on it in its own conversation and directory, and the final text of its answer comes back to you later as a <peer-message kind="reply"> message: you do not wait for it here. Use it to delegate work that belongs to another session's project or context, or to ask it something only it knows. Write the message so that it stands on its own: the other agent has not seen your conversation.`,
|
|
26
|
+
input_schema: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
to: { type: 'string', description: 'The name of the session (as ListPeers shows it)' },
|
|
30
|
+
message: { type: 'string', description: 'The task or question, self-contained' },
|
|
31
|
+
},
|
|
32
|
+
required: ['to', 'message'],
|
|
33
|
+
},
|
|
34
|
+
summarize(input) { return `@${input.to}`; },
|
|
35
|
+
async call(input, ctx) {
|
|
36
|
+
const node = ctx.io?.peers;
|
|
37
|
+
if (!node) return unavailable;
|
|
38
|
+
try {
|
|
39
|
+
const { peer } = await ctx.io.sendToPeer(input.to, input.message, { byModel: true });
|
|
40
|
+
return { content: `Sent to ${peer.name}. Its answer will arrive later as a message; carry on meanwhile, or end your turn if you need the answer first.`, display: { summary: `Sent to ${peer.name}` } };
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return { isError: true, content: e.message };
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Restart: the model restarts this program after changing the program's own source code, so that the change
|
|
2
|
+
// takes effect and the conversation goes on in the new process.
|
|
3
|
+
|
|
4
|
+
import { APP_NAME, INSTALL_DIR } from '../config.js';
|
|
5
|
+
import { canRestart, runSelfCheck, MAX_RESTARTS } from '../restart.js';
|
|
6
|
+
|
|
7
|
+
export const RestartTool = {
|
|
8
|
+
name: 'Restart',
|
|
9
|
+
description: `Restart this program (${APP_NAME}) so that changes you made to its own source code take effect, and carry on in the new process. Its source is in ${INSTALL_DIR} (src/ and bin/). Editing those files does not change the process you are running in: it keeps the old code until it restarts.
|
|
10
|
+
|
|
11
|
+
Use it after you have changed the program's own code, to fix a bug you ran into or to add something you need. Do not use it for anything else.
|
|
12
|
+
|
|
13
|
+
How it works:
|
|
14
|
+
- First the new code is started in a separate throwaway process (every module loads, the interface comes up, the tools and the system prompt are well formed). If that fails, nothing is restarted and the error is returned to you: fix it and call Restart again.
|
|
15
|
+
- Then the restart happens as soon as your current turn ends, so finish the turn with a short message to the user. The conversation, your todo list, the model and effort settings, queued messages and whatever the user was typing carry over, and you get a message saying the new code is loaded.
|
|
16
|
+
- Use \`then\` to say what to do right after the restart (typically: check that the change works). Without it you simply carry on.
|
|
17
|
+
- Background agents and background shells are killed by a restart, so the tool refuses while any are running unless you pass force.
|
|
18
|
+
- Restarting more than ${MAX_RESTARTS} times within five minutes is refused: that is a loop. Stop and tell the user.`,
|
|
19
|
+
input_schema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
reason: { type: 'string', description: 'What changed, in a few words. Shown to the user.' },
|
|
23
|
+
then: { type: 'string', description: 'What to do right after the restart, for example how to check the change.' },
|
|
24
|
+
force: { type: 'boolean', description: 'Restart even though background agents or shells are running (they are killed).' },
|
|
25
|
+
},
|
|
26
|
+
required: ['reason'],
|
|
27
|
+
},
|
|
28
|
+
summarize(input) { return input.reason || ''; },
|
|
29
|
+
async call(input, ctx) {
|
|
30
|
+
const io = ctx.io;
|
|
31
|
+
if (!canRestart() || typeof io?.requestRestart !== 'function') {
|
|
32
|
+
return { isError: true, content: `Restart is not available here: it needs an interactive session started with \`${APP_NAME}\` (not -p, and not with CLAUDE_PRO_NO_SUPERVISOR set). Ask the user to restart the program themselves.` };
|
|
33
|
+
}
|
|
34
|
+
const blockers = io.restartBlockers?.() || [];
|
|
35
|
+
if (blockers.length && !input.force) {
|
|
36
|
+
return { isError: true, content: `A restart would kill what is still running: ${blockers.join('; ')}. Wait for it to finish (TaskOutput / BashOutput), stop it (TaskStop / KillShell), or call Restart again with force: true.` };
|
|
37
|
+
}
|
|
38
|
+
const check = await runSelfCheck({ signal: ctx.signal });
|
|
39
|
+
if (!check.ok) {
|
|
40
|
+
return { isError: true, content: `Not restarting: the new code does not start.\n\n${check.output}\n\nFix this and call Restart again.`, display: { summary: 'The new code does not start' } };
|
|
41
|
+
}
|
|
42
|
+
const res = io.requestRestart({ reason: input.reason, then: input.then, continueTurn: true, force: true });
|
|
43
|
+
if (!res.ok) return { isError: true, content: res.message };
|
|
44
|
+
const secs = Math.max(1, Math.round(check.ms / 1000));
|
|
45
|
+
return {
|
|
46
|
+
content: `The new code starts fine (checked in ${secs}s). ${APP_NAME} restarts as soon as this turn ends and the conversation continues in the new process${input.then ? ` with your note: "${input.then}"` : ''}. Finish this turn now with a short message to the user; do not change anything more and do not call Restart again.`,
|
|
47
|
+
display: { summary: `Restarting when this turn ends: ${input.reason}` },
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
};
|