@bubblebrain-ai/bubble 0.0.50 → 0.0.52
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/dist/agent/profiles.js +3 -4
- package/dist/agent/subtask-policy.js +4 -4
- package/dist/agent.d.ts +1 -11
- package/dist/agent.js +1 -37
- package/dist/debug-trace.js +0 -2
- package/dist/feishu/agent-host/run-driver.js +0 -7
- package/dist/feishu/card/run-state.js +0 -3
- package/dist/main.js +0 -15
- package/dist/memory/phase1.js +0 -2
- package/dist/prompt/compose.js +0 -3
- package/dist/prompt/environment.js +0 -2
- package/dist/prompt/reminders.js +1 -1
- package/dist/provider-registry.js +7 -1
- package/dist/provider.js +5 -5
- package/dist/sdk/index.d.ts +17 -2
- package/dist/sdk/index.js +37 -6
- package/dist/session-log.d.ts +2 -4
- package/dist/session-log.js +0 -23
- package/dist/session-types.d.ts +2 -6
- package/dist/session.d.ts +1 -3
- package/dist/session.js +0 -8
- package/dist/slash-commands/commands.js +0 -4
- package/dist/tools/child-tools.d.ts +1 -1
- package/dist/tools/child-tools.js +1 -1
- package/dist/tools/index.d.ts +0 -3
- package/dist/tools/index.js +0 -3
- package/dist/tui-ink/app.js +1 -12
- package/dist/tui-ink/message-list.js +0 -1
- package/dist/types.d.ts +0 -9
- package/package.json +1 -1
- package/dist/tools/todo.d.ts +0 -12
- package/dist/tools/todo.js +0 -136
- package/dist/tui-ink/todos.d.ts +0 -7
- package/dist/tui-ink/todos.js +0 -46
package/dist/agent/profiles.js
CHANGED
|
@@ -12,7 +12,6 @@ const READONLY_PRESET = [
|
|
|
12
12
|
"web_fetch",
|
|
13
13
|
"memory",
|
|
14
14
|
"skill",
|
|
15
|
-
"todo_write",
|
|
16
15
|
];
|
|
17
16
|
const SUBAGENT_DENY_TOOLS = new Set([
|
|
18
17
|
"subagent",
|
|
@@ -124,12 +123,12 @@ export function builtinAgentProfiles() {
|
|
|
124
123
|
"You are an explorer subagent for codebase reconnaissance.",
|
|
125
124
|
"Answer the specific question by inspecting the repository directly. Prefer precise file paths and line-level evidence.",
|
|
126
125
|
"Keep the answer compact and avoid broad refactors or implementation plans unless asked.",
|
|
127
|
-
].join("\n"), ["read", "glob", "grep", "lsp", "memory", "skill"
|
|
126
|
+
].join("\n"), ["read", "glob", "grep", "lsp", "memory", "skill"]),
|
|
128
127
|
roleProfile("worker", "Implementation-planning worker subagent", [
|
|
129
128
|
"You are a worker subagent. In this Phase 1 runtime you are read-only, so you must not modify files.",
|
|
130
129
|
"Analyze the assigned implementation slice, identify exact files to change, and return a concrete patch plan or findings.",
|
|
131
130
|
"If write-capable worker mode is needed, say so explicitly.",
|
|
132
|
-
].join("\n"), ["read", "glob", "grep", "lsp", "memory", "skill"
|
|
131
|
+
].join("\n"), ["read", "glob", "grep", "lsp", "memory", "skill"]),
|
|
133
132
|
toProfile("search"),
|
|
134
133
|
toProfile("security_investigation"),
|
|
135
134
|
toProfile("evidence_correlation"),
|
|
@@ -146,7 +145,7 @@ export function builtinAgentProfiles() {
|
|
|
146
145
|
model: "inherit",
|
|
147
146
|
tools: {
|
|
148
147
|
preset: "explicit",
|
|
149
|
-
include: ["read", "glob", "grep", "edit", "write", "bash", "lsp"
|
|
148
|
+
include: ["read", "glob", "grep", "edit", "write", "bash", "lsp"],
|
|
150
149
|
exclude: [],
|
|
151
150
|
},
|
|
152
151
|
approval: "fail",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const POLICY_MAP = {
|
|
2
2
|
search: {
|
|
3
3
|
type: "search",
|
|
4
|
-
allowedTools: ["read", "glob", "grep", "web_search", "web_fetch", "skill"
|
|
4
|
+
allowedTools: ["read", "glob", "grep", "web_search", "web_fetch", "skill"],
|
|
5
5
|
reminder: [
|
|
6
6
|
"Subtask policy: search",
|
|
7
7
|
"- Focus on locating relevant files, symbols, and evidence quickly.",
|
|
@@ -13,7 +13,7 @@ const POLICY_MAP = {
|
|
|
13
13
|
},
|
|
14
14
|
security_investigation: {
|
|
15
15
|
type: "security_investigation",
|
|
16
|
-
allowedTools: ["read", "glob", "grep", "web_search", "web_fetch", "skill"
|
|
16
|
+
allowedTools: ["read", "glob", "grep", "web_search", "web_fetch", "skill"],
|
|
17
17
|
reminder: [
|
|
18
18
|
"Subtask policy: security_investigation",
|
|
19
19
|
"- Investigate only in read-only mode.",
|
|
@@ -25,7 +25,7 @@ const POLICY_MAP = {
|
|
|
25
25
|
},
|
|
26
26
|
evidence_correlation: {
|
|
27
27
|
type: "evidence_correlation",
|
|
28
|
-
allowedTools: ["read", "skill"
|
|
28
|
+
allowedTools: ["read", "skill"],
|
|
29
29
|
reminder: [
|
|
30
30
|
"Subtask policy: evidence_correlation",
|
|
31
31
|
"- Correlate evidence already discovered.",
|
|
@@ -37,7 +37,7 @@ const POLICY_MAP = {
|
|
|
37
37
|
},
|
|
38
38
|
general_readonly: {
|
|
39
39
|
type: "general_readonly",
|
|
40
|
-
allowedTools: ["read", "glob", "grep", "web_search", "web_fetch", "skill"
|
|
40
|
+
allowedTools: ["read", "glob", "grep", "web_search", "web_fetch", "skill"],
|
|
41
41
|
reminder: [
|
|
42
42
|
"Subtask policy: general_readonly",
|
|
43
43
|
"- Stay in read-only mode.",
|
package/dist/agent.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* It maintains message state, calls the LLM, executes tools, and auto-continues.
|
|
4
4
|
*/
|
|
5
5
|
import { type ContextUsageSnapshot } from "./context/usage.js";
|
|
6
|
-
import type { AgentEvent, AgentInputController, ContentPart, PermissionMode, Message, Provider, ThinkingLevel,
|
|
6
|
+
import type { AgentEvent, AgentInputController, ContentPart, PermissionMode, Message, Provider, ThinkingLevel, ToolResult, ToolRegistryEntry, ToolUpdate } from "./types.js";
|
|
7
7
|
import { type TurnHooks } from "./orchestrator/hooks.js";
|
|
8
8
|
import type { ExternalHookController } from "./hooks/controller.js";
|
|
9
9
|
import type { AgentCategoriesConfig, ResolvedSubagentRoute } from "./agent/categories.js";
|
|
@@ -49,11 +49,9 @@ export interface AgentOptions {
|
|
|
49
49
|
taskBudget?: {
|
|
50
50
|
total: number;
|
|
51
51
|
};
|
|
52
|
-
todos?: Todo[];
|
|
53
52
|
systemPrompt?: string;
|
|
54
53
|
onMessageAppend?: (message: Message) => void;
|
|
55
54
|
onToolResult?: (toolName: string, result: ToolResult) => void;
|
|
56
|
-
onTodosUpdate?: (todos: Todo[]) => void;
|
|
57
55
|
onModeUpdate?: (mode: PermissionMode) => void;
|
|
58
56
|
/**
|
|
59
57
|
* Fired when MULTI-TURN compaction rewrites resident history, with the
|
|
@@ -152,9 +150,6 @@ export declare class Agent {
|
|
|
152
150
|
private _modeVersion;
|
|
153
151
|
private onModeUpdate?;
|
|
154
152
|
private onCompactionApplied?;
|
|
155
|
-
private _todos;
|
|
156
|
-
private _todosVersion;
|
|
157
|
-
private onTodosUpdate?;
|
|
158
153
|
private onMessageAppend?;
|
|
159
154
|
private onToolResult?;
|
|
160
155
|
private hookDefinitions;
|
|
@@ -291,10 +286,6 @@ export declare class Agent {
|
|
|
291
286
|
setMode(value: PermissionMode): void;
|
|
292
287
|
/** Internal: snapshot counter that bumps on every mode change. Used by run loop. */
|
|
293
288
|
get modeVersion(): number;
|
|
294
|
-
getTodos(): Todo[];
|
|
295
|
-
setTodos(next: Todo[]): void;
|
|
296
|
-
/** Internal: snapshot counter that bumps on every setTodos. Used by run loop to detect mutations. */
|
|
297
|
-
get todosVersion(): number;
|
|
298
289
|
setSystemPrompt(prompt: string): void;
|
|
299
290
|
run(userInput: string | ContentPart[], cwd: string, options?: AgentRunOptions): AsyncIterable<AgentEvent>;
|
|
300
291
|
private recoverFromOverflow;
|
|
@@ -440,6 +431,5 @@ export declare class Agent {
|
|
|
440
431
|
private persistCompactionSummary;
|
|
441
432
|
private appendMessage;
|
|
442
433
|
private appendInterruptedAssistantBoundary;
|
|
443
|
-
private clearTodosAfterInterruptedRun;
|
|
444
434
|
private executeTool;
|
|
445
435
|
}
|
package/dist/agent.js
CHANGED
|
@@ -120,9 +120,6 @@ export class Agent {
|
|
|
120
120
|
_modeVersion = 0;
|
|
121
121
|
onModeUpdate;
|
|
122
122
|
onCompactionApplied;
|
|
123
|
-
_todos;
|
|
124
|
-
_todosVersion = 0;
|
|
125
|
-
onTodosUpdate;
|
|
126
123
|
onMessageAppend;
|
|
127
124
|
onToolResult;
|
|
128
125
|
hookDefinitions;
|
|
@@ -170,10 +167,8 @@ export class Agent {
|
|
|
170
167
|
this.temperature = options.temperature ?? 0.2;
|
|
171
168
|
this.thinkingLevel = options.thinkingLevel ?? "off";
|
|
172
169
|
this._mode = options.mode ?? "default";
|
|
173
|
-
this._todos = options.todos ? [...options.todos] : [];
|
|
174
170
|
this.onMessageAppend = options.onMessageAppend;
|
|
175
171
|
this.onToolResult = options.onToolResult;
|
|
176
|
-
this.onTodosUpdate = options.onTodosUpdate;
|
|
177
172
|
this.onModeUpdate = options.onModeUpdate;
|
|
178
173
|
this.onCompactionApplied = options.onCompactionApplied;
|
|
179
174
|
this.hookDefinitions = options.hooks ?? [];
|
|
@@ -556,18 +551,6 @@ export class Agent {
|
|
|
556
551
|
get modeVersion() {
|
|
557
552
|
return this._modeVersion;
|
|
558
553
|
}
|
|
559
|
-
getTodos() {
|
|
560
|
-
return this._todos.map((todo) => ({ ...todo }));
|
|
561
|
-
}
|
|
562
|
-
setTodos(next) {
|
|
563
|
-
this._todos = next.map((todo) => ({ ...todo }));
|
|
564
|
-
this._todosVersion += 1;
|
|
565
|
-
this.onTodosUpdate?.(this.getTodos());
|
|
566
|
-
}
|
|
567
|
-
/** Internal: snapshot counter that bumps on every setTodos. Used by run loop to detect mutations. */
|
|
568
|
-
get todosVersion() {
|
|
569
|
-
return this._todosVersion;
|
|
570
|
-
}
|
|
571
554
|
setSystemPrompt(prompt) {
|
|
572
555
|
const systemMessage = { role: "system", content: prompt };
|
|
573
556
|
if (this.messages[0]?.role === "system") {
|
|
@@ -686,10 +669,6 @@ export class Agent {
|
|
|
686
669
|
this.injectSystemReminder(reminder);
|
|
687
670
|
}
|
|
688
671
|
};
|
|
689
|
-
if (this._todos.length > 0 && this._todos.every((t) => t.status === "completed")) {
|
|
690
|
-
this.setTodos([]);
|
|
691
|
-
yield emit({ type: "todos_updated", todos: [] });
|
|
692
|
-
}
|
|
693
672
|
if (!options.resumeWithoutInput) {
|
|
694
673
|
const promptHook = await this.runExternalHook({
|
|
695
674
|
eventName: "UserPromptSubmit",
|
|
@@ -1256,7 +1235,6 @@ export class Agent {
|
|
|
1256
1235
|
argsCorrupt: tc.argsCorrupt,
|
|
1257
1236
|
}, traceContext);
|
|
1258
1237
|
yield emit({ type: "tool_start", id: tc.id, name: tc.name, args: tc.parsedArgs });
|
|
1259
|
-
const todosVersionBefore = this._todosVersion;
|
|
1260
1238
|
const modeVersionBefore = this._modeVersion;
|
|
1261
1239
|
const updateQueue = createUpdateQueue();
|
|
1262
1240
|
let result;
|
|
@@ -1382,9 +1360,6 @@ export class Agent {
|
|
|
1382
1360
|
yield emit({ type: "tool_end", id: tc.id, name: tc.name, result });
|
|
1383
1361
|
for (const update of this.subagents.drainToolUpdates())
|
|
1384
1362
|
yield emit(update);
|
|
1385
|
-
if (this._todosVersion !== todosVersionBefore) {
|
|
1386
|
-
yield emit({ type: "todos_updated", todos: this.getTodos() });
|
|
1387
|
-
}
|
|
1388
1363
|
if (this._modeVersion !== modeVersionBefore) {
|
|
1389
1364
|
yield emit({ type: "mode_changed", mode: this._mode });
|
|
1390
1365
|
}
|
|
@@ -1456,15 +1431,10 @@ export class Agent {
|
|
|
1456
1431
|
catch (error) {
|
|
1457
1432
|
if (isAbortError(error, abortSignal)) {
|
|
1458
1433
|
const appendedBoundary = this.appendInterruptedAssistantBoundary(currentAssistantMsg, currentAssistantAppended);
|
|
1459
|
-
const clearedTodos = this.clearTodosAfterInterruptedRun();
|
|
1460
1434
|
traceEvent("agent_run_interrupted", {
|
|
1461
1435
|
appendedBoundary,
|
|
1462
|
-
clearedTodos,
|
|
1463
1436
|
messageCount: this.messages.length,
|
|
1464
1437
|
}, traceContext);
|
|
1465
|
-
if (clearedTodos) {
|
|
1466
|
-
yield emit({ type: "todos_updated", todos: this.getTodos() });
|
|
1467
|
-
}
|
|
1468
1438
|
}
|
|
1469
1439
|
else {
|
|
1470
1440
|
const stopFailureHook = await this.runExternalHook({
|
|
@@ -1930,12 +1900,6 @@ export class Agent {
|
|
|
1930
1900
|
});
|
|
1931
1901
|
return true;
|
|
1932
1902
|
}
|
|
1933
|
-
clearTodosAfterInterruptedRun() {
|
|
1934
|
-
if (this._todos.length === 0)
|
|
1935
|
-
return false;
|
|
1936
|
-
this.setTodos([]);
|
|
1937
|
-
return true;
|
|
1938
|
-
}
|
|
1939
1903
|
async executeTool(toolCall, cwd, abortSignal, emitUpdate) {
|
|
1940
1904
|
throwIfAborted(abortSignal);
|
|
1941
1905
|
if (toolCall.name === "exit_plan_mode" && this._mode !== "plan") {
|
|
@@ -1954,7 +1918,7 @@ export class Agent {
|
|
|
1954
1918
|
if (this._mode === "plan" && !tool.readOnly) {
|
|
1955
1919
|
return {
|
|
1956
1920
|
content: `Error: Tool "${toolCall.name}" is not allowed in plan mode. ` +
|
|
1957
|
-
`In plan mode you may only use read-only tools (read, glob, grep, web_search, web_fetch, spawn_agent, wait_agent, send_input, skill_search, skill,
|
|
1921
|
+
`In plan mode you may only use read-only tools (read, glob, grep, web_search, web_fetch, spawn_agent, wait_agent, send_input, skill_search, skill, tool_search, question, exit_plan_mode). ` +
|
|
1958
1922
|
`To modify files or run commands, present your proposal and call exit_plan_mode so the user can review and approve it.`,
|
|
1959
1923
|
isError: true,
|
|
1960
1924
|
};
|
package/dist/debug-trace.js
CHANGED
|
@@ -70,12 +70,7 @@ export class RunDriver {
|
|
|
70
70
|
({ action: "reject", reason: "Plan approval over Feishu not implemented; use /mode default." }),
|
|
71
71
|
setMode: (mode) => agentForPlan?.setMode(mode),
|
|
72
72
|
};
|
|
73
|
-
const todoStore = {
|
|
74
|
-
getTodos: () => agentRef?.getTodos() ?? [],
|
|
75
|
-
setTodos: (todos) => agentRef?.setTodos(todos),
|
|
76
|
-
};
|
|
77
73
|
const tools = createAllTools(session.cwd, this.opts.deps.skillRegistry, {
|
|
78
|
-
todoStore,
|
|
79
74
|
planController,
|
|
80
75
|
approvalController,
|
|
81
76
|
lspService,
|
|
@@ -125,7 +120,6 @@ export class RunDriver {
|
|
|
125
120
|
temperature: 0.2,
|
|
126
121
|
thinkingLevel,
|
|
127
122
|
mode: initialMode,
|
|
128
|
-
todos: session.manager.getTodos(),
|
|
129
123
|
onMessageAppend: (message) => {
|
|
130
124
|
if (message.role === "system" || message.role === "meta")
|
|
131
125
|
return;
|
|
@@ -145,7 +139,6 @@ export class RunDriver {
|
|
|
145
139
|
if (match?.[1])
|
|
146
140
|
session.manager.appendMarker("skill_activated", match[1].trim());
|
|
147
141
|
},
|
|
148
|
-
onTodosUpdate: (todos) => session.manager.appendTodosSnapshot(todos),
|
|
149
142
|
onModeUpdate: (mode) => {
|
|
150
143
|
session.manager.appendMarker("mode_switch", mode);
|
|
151
144
|
this.opts.binder.setMode(req.scopeKey, mode);
|
|
@@ -97,9 +97,6 @@ export function reduceRunState(state, event) {
|
|
|
97
97
|
case "mode_changed":
|
|
98
98
|
state.mode = event.mode;
|
|
99
99
|
return state;
|
|
100
|
-
case "todos_updated":
|
|
101
|
-
// Todos render is deferred (would need a todos block kind). v1 ignores.
|
|
102
|
-
return state;
|
|
103
100
|
case "context_recovered":
|
|
104
101
|
// Internal recovery — silent.
|
|
105
102
|
return state;
|
package/dist/main.js
CHANGED
|
@@ -128,10 +128,6 @@ async function main() {
|
|
|
128
128
|
return createProvider(providerId, target.apiKey, target.baseURL);
|
|
129
129
|
};
|
|
130
130
|
let agentRef;
|
|
131
|
-
const todoStore = {
|
|
132
|
-
getTodos: () => agentRef?.getTodos() ?? [],
|
|
133
|
-
setTodos: (todos) => agentRef?.setTodos(todos),
|
|
134
|
-
};
|
|
135
131
|
const planHandlerRef = {};
|
|
136
132
|
const planController = {
|
|
137
133
|
getMode: () => agentRef?.mode ?? "default",
|
|
@@ -181,7 +177,6 @@ async function main() {
|
|
|
181
177
|
const allowBackgroundTasks = !printMode;
|
|
182
178
|
const promotionChannel = new PromotionChannel();
|
|
183
179
|
const tools = createAllTools(args.cwd, skillRegistry, {
|
|
184
|
-
todoStore,
|
|
185
180
|
planController,
|
|
186
181
|
approvalController,
|
|
187
182
|
questionController: printMode ? undefined : questionController,
|
|
@@ -338,7 +333,6 @@ async function main() {
|
|
|
338
333
|
? normalizeThinkingLevel(configuredThinkingLevel, getAvailableThinkingLevels(activeProviderId, effectiveModelId))
|
|
339
334
|
: getDefaultThinkingLevel(activeProviderId, effectiveModelId)
|
|
340
335
|
: (args.thinkingLevel ?? sessionThinkingLevel ?? configuredThinkingLevel ?? "off");
|
|
341
|
-
const restoredTodos = sessionManager?.getTodos() ?? [];
|
|
342
336
|
const initialMode = args.mode ?? "default";
|
|
343
337
|
const skillSummaries = skillRegistry.summaries();
|
|
344
338
|
// This synchronous provenance gate must run before buildMemoryPrompt: the
|
|
@@ -401,7 +395,6 @@ async function main() {
|
|
|
401
395
|
temperature: 0.2,
|
|
402
396
|
thinkingLevel: initialThinkingLevel,
|
|
403
397
|
mode: initialMode,
|
|
404
|
-
todos: restoredTodos,
|
|
405
398
|
onMessageAppend: (message) => {
|
|
406
399
|
if (!sessionManager)
|
|
407
400
|
return;
|
|
@@ -435,9 +428,6 @@ async function main() {
|
|
|
435
428
|
sessionManager.appendMarker("skill_activated", match[1].trim());
|
|
436
429
|
}
|
|
437
430
|
},
|
|
438
|
-
onTodosUpdate: (todos) => {
|
|
439
|
-
sessionManager?.appendTodosSnapshot(todos);
|
|
440
|
-
},
|
|
441
431
|
onModeUpdate: (mode) => {
|
|
442
432
|
sessionManager?.appendMarker("mode_switch", mode);
|
|
443
433
|
},
|
|
@@ -703,7 +693,6 @@ async function main() {
|
|
|
703
693
|
// mirroring the /rewind history-replacement pattern.
|
|
704
694
|
const head = agent.messages.filter((m) => m.role === "system" || m.role === "meta");
|
|
705
695
|
const nextMessages = [...head, ...history];
|
|
706
|
-
const nextTodos = next.getTodos();
|
|
707
696
|
// Commit only after every file read/write and reconstruction step has
|
|
708
697
|
// succeeded. Callers can safely prepare candidate sessions without a
|
|
709
698
|
// failed switch rebinding persistence or replacing the live history.
|
|
@@ -714,10 +703,6 @@ async function main() {
|
|
|
714
703
|
// These update only live in-memory/UI mirrors after the persistence
|
|
715
704
|
// commit. A best-effort callback failure must not report the switch as
|
|
716
705
|
// rolled back after the outer session binding has already changed.
|
|
717
|
-
try {
|
|
718
|
-
agent.setTodos(nextTodos);
|
|
719
|
-
}
|
|
720
|
-
catch { /* candidate already committed */ }
|
|
721
706
|
try {
|
|
722
707
|
agent.resetContextUsageAnchor();
|
|
723
708
|
}
|
package/dist/memory/phase1.js
CHANGED
|
@@ -160,8 +160,6 @@ function serializeSessionEntry(entry) {
|
|
|
160
160
|
return `[tool_call:${entry.toolCall.name}] ${truncate(entry.toolCall.arguments, 1_500)}`;
|
|
161
161
|
case "tool_result":
|
|
162
162
|
return `[tool_result${entry.message.isError ? " error=true" : ""}] ${truncate(entry.message.content, 2_000)}`;
|
|
163
|
-
case "todos_snapshot":
|
|
164
|
-
return `[todos] ${truncate(JSON.stringify(entry.todos), 2_000)}`;
|
|
165
163
|
}
|
|
166
164
|
}
|
|
167
165
|
function countMeaningfulEntries(entries) {
|
package/dist/prompt/compose.js
CHANGED
|
@@ -86,9 +86,6 @@ function buildGuidelines(tools, extraGuidelines) {
|
|
|
86
86
|
if (tools.includes("skill_search") && tools.includes("skill")) {
|
|
87
87
|
add("Skills may provide specialized workflows. When a task appears to match a specialized workflow, call skill_search to find relevant skills, then call skill with the exact name to load the selected skill before applying it");
|
|
88
88
|
}
|
|
89
|
-
if (tools.includes("todo_write")) {
|
|
90
|
-
add("Default to just doing the work. Reach for todo_write only when tracking progress would genuinely help — multi-phase work with real dependencies, a long task spanning several areas, an explicit user request, or steps you discover mid-task. Don't make a list to pad a task you could just do; when in doubt, skip it. Mark each item completed as soon as it is done; do not batch updates");
|
|
91
|
-
}
|
|
92
89
|
for (const item of extraGuidelines) {
|
|
93
90
|
add(item);
|
|
94
91
|
}
|
|
@@ -14,7 +14,6 @@ export const defaultToolSnippets = {
|
|
|
14
14
|
question: "Ask the user structured questions when clarification or preference choices would materially improve the work",
|
|
15
15
|
skill_search: "Search available skills by name, description, tags, and source",
|
|
16
16
|
skill: "Load a named skill with specialized instructions and bundled resources",
|
|
17
|
-
todo_write: "Plan and track multi-step work. Mark each task completed as soon as it is done — do not batch.",
|
|
18
17
|
};
|
|
19
18
|
export const defaultToolNames = [
|
|
20
19
|
"read",
|
|
@@ -31,7 +30,6 @@ export const defaultToolNames = [
|
|
|
31
30
|
"question",
|
|
32
31
|
"skill_search",
|
|
33
32
|
"skill",
|
|
34
|
-
"todo_write",
|
|
35
33
|
];
|
|
36
34
|
export function buildEnvironmentPrompt(options = {}) {
|
|
37
35
|
const configuredProvider = options.configuredProvider ?? "unknown";
|
package/dist/prompt/reminders.js
CHANGED
|
@@ -29,7 +29,7 @@ const PLAN_MODE_ENTER = `
|
|
|
29
29
|
Plan mode is now ACTIVE.
|
|
30
30
|
|
|
31
31
|
Rules while in plan mode:
|
|
32
|
-
- Only read-only tools are allowed, including read, glob, grep, web_search, web_fetch, spawn_agent, wait_agent, send_input, skill_search, skill,
|
|
32
|
+
- Only read-only tools are allowed, including read, glob, grep, web_search, web_fetch, spawn_agent, wait_agent, send_input, skill_search, skill, tool_search, question, and exit_plan_mode.
|
|
33
33
|
- Writes, edits, and shell commands WILL be rejected by the harness; do not try them.
|
|
34
34
|
- Do not edit files or claim implementation is complete while plan mode is active.
|
|
35
35
|
- Investigate the codebase, then use the question tool to clarify important ambiguities, tradeoffs, requirements, or preference choices that would materially change the plan.
|
|
@@ -357,7 +357,13 @@ export class ProviderRegistry {
|
|
|
357
357
|
else {
|
|
358
358
|
// 2. Fall back to config.json providers (interactive TUI style)
|
|
359
359
|
providers = this.config.getProviders().map((provider) => {
|
|
360
|
-
|
|
360
|
+
// config.json entries may omit baseURL (a bare {id, apiKey} written
|
|
361
|
+
// by minimal setups / benchmark harnesses); merge the builtin default
|
|
362
|
+
// the same way the models.json path above does, instead of propagating
|
|
363
|
+
// undefined into provider construction.
|
|
364
|
+
const builtinBase = getBuiltinProvider(provider.id)?.baseURL ?? "";
|
|
365
|
+
const rawBaseURL = provider.baseURL || builtinBase;
|
|
366
|
+
const baseURL = upgradeLegacyBaseURL(provider.id, rawBaseURL, provider.protocol);
|
|
361
367
|
return {
|
|
362
368
|
...provider,
|
|
363
369
|
baseURL,
|
package/dist/provider.js
CHANGED
|
@@ -201,9 +201,9 @@ export function createProviderInstance(options) {
|
|
|
201
201
|
// paths already wrap — this generic chat-completions path did not).
|
|
202
202
|
try {
|
|
203
203
|
yield* translateOpenAIStream(stream, {
|
|
204
|
-
toolArgsMergeMode: resolveToolArgsMergeMode(options.providerId || "", options.baseURL),
|
|
205
|
-
reasoningMergeMode: resolveReasoningMergeMode(options.providerId || "", options.baseURL),
|
|
206
|
-
textMergeMode: resolveTextMergeMode(options.providerId || "", options.baseURL),
|
|
204
|
+
toolArgsMergeMode: resolveToolArgsMergeMode(options.providerId || "", options.baseURL || ""),
|
|
205
|
+
reasoningMergeMode: resolveReasoningMergeMode(options.providerId || "", options.baseURL || ""),
|
|
206
|
+
textMergeMode: resolveTextMergeMode(options.providerId || "", options.baseURL || ""),
|
|
207
207
|
debugProviderId: options.providerId || "",
|
|
208
208
|
debugModelId: chatOptions.model,
|
|
209
209
|
});
|
|
@@ -246,7 +246,7 @@ function resolveProviderProtocol(options) {
|
|
|
246
246
|
if (options.protocol)
|
|
247
247
|
return options.protocol;
|
|
248
248
|
const providerId = (options.providerId || "").toLowerCase();
|
|
249
|
-
const baseURL = options.baseURL.toLowerCase();
|
|
249
|
+
const baseURL = (options.baseURL || "").toLowerCase();
|
|
250
250
|
if (providerId === "doubao"
|
|
251
251
|
&& baseURL.replace(/\/+$/, "") === "https://ark.cn-beijing.volces.com/api/v3") {
|
|
252
252
|
return "ark-responses";
|
|
@@ -260,7 +260,7 @@ function resolveProviderProtocol(options) {
|
|
|
260
260
|
}
|
|
261
261
|
function isMiniMaxOpenAICompatible(options) {
|
|
262
262
|
const providerId = (options.providerId || "").toLowerCase();
|
|
263
|
-
const baseURL = options.baseURL.toLowerCase();
|
|
263
|
+
const baseURL = (options.baseURL || "").toLowerCase();
|
|
264
264
|
return providerId === "minimax-openai"
|
|
265
265
|
|| (providerId === "minimax" && !baseURL.includes("/anthropic"))
|
|
266
266
|
|| baseURL.includes("api.minimaxi.com/v1")
|
package/dist/sdk/index.d.ts
CHANGED
|
@@ -17,10 +17,11 @@ import { type SessionSummary } from "../session.js";
|
|
|
17
17
|
import type { ApprovalDecision, ApprovalRequest } from "../approval/types.js";
|
|
18
18
|
import { UserConfig } from "../config.js";
|
|
19
19
|
import { ProviderRegistry } from "../provider-registry.js";
|
|
20
|
+
import type { ResolvedSubagentRoute } from "../agent/categories.js";
|
|
20
21
|
import { type QuestionAnswer, type QuestionRequest } from "../question/controller.js";
|
|
21
22
|
import { SkillRegistry } from "../skills/registry.js";
|
|
22
23
|
import type { SkillSummary } from "../skills/types.js";
|
|
23
|
-
import type { AgentEvent, ContentPart, Message, PermissionMode, ThinkingLevel } from "../types.js";
|
|
24
|
+
import type { AgentEvent, ContentPart, Message, PermissionMode, Provider, ThinkingLevel } from "../types.js";
|
|
24
25
|
export interface BubbleSdkOptions {
|
|
25
26
|
/** Fallback working directory for sessions created without an explicit cwd. */
|
|
26
27
|
defaultCwd?: string;
|
|
@@ -66,6 +67,14 @@ export interface RunTurnOptions extends TurnHandlers {
|
|
|
66
67
|
* harness) A/B prompt variants without forking the prompt builder.
|
|
67
68
|
*/
|
|
68
69
|
appendSystemPrompt?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Creates the provider instance for a subagent route whose provider differs
|
|
72
|
+
* from the turn's provider (e.g. spawn_agent with `model: "provider:model"`).
|
|
73
|
+
* When omitted the SDK builds a default factory over its own registry —
|
|
74
|
+
* same semantics as the TUI's createProviderForRoute (OAuth prepare, then
|
|
75
|
+
* configured+enabled+keyed profile lookup). Pass your own to override.
|
|
76
|
+
*/
|
|
77
|
+
providerFactory?: (route: ResolvedSubagentRoute) => Provider | Promise<Provider>;
|
|
69
78
|
signal?: AbortSignal;
|
|
70
79
|
}
|
|
71
80
|
export interface SdkSessionRef {
|
|
@@ -106,6 +115,12 @@ export declare class BubbleSdk {
|
|
|
106
115
|
};
|
|
107
116
|
runTurn(sessionId: string, options: RunTurnOptions): AsyncGenerator<AgentEvent>;
|
|
108
117
|
private bashAllowlistFor;
|
|
118
|
+
/**
|
|
119
|
+
* Default cross-provider subagent factory over this SDK instance's own
|
|
120
|
+
* registry — the same semantics as the TUI's createProviderForRoute. The
|
|
121
|
+
* registry is reloaded first so a key added after construction works.
|
|
122
|
+
*/
|
|
123
|
+
private defaultProviderFactory;
|
|
109
124
|
/** MCP servers are started lazily, once per cwd (McpManager has no stop). */
|
|
110
125
|
private mcpToolsFor;
|
|
111
126
|
private resolveSession;
|
|
@@ -149,4 +164,4 @@ export { ExternalHookController } from "../hooks/controller.js";
|
|
|
149
164
|
export { calculateUsageCost, getModelPricing, type ModelPricing, type PricingCurrency, } from "../model-pricing.js";
|
|
150
165
|
export { registry as slashCommandRegistry } from "../slash-commands/index.js";
|
|
151
166
|
export type { SlashCommandContext } from "../slash-commands/types.js";
|
|
152
|
-
export type { AgentEvent, ContentPart, Message, PermissionMode, Provider, ThinkingLevel,
|
|
167
|
+
export type { AgentEvent, ContentPart, Message, PermissionMode, Provider, ThinkingLevel, TokenUsage, UsageCost, ToolRegistryEntry, ToolResult, ToolUpdate, } from "../types.js";
|
package/dist/sdk/index.js
CHANGED
|
@@ -171,10 +171,6 @@ export class BubbleSdk {
|
|
|
171
171
|
approvalController,
|
|
172
172
|
fileStateTracker,
|
|
173
173
|
planController,
|
|
174
|
-
todoStore: {
|
|
175
|
-
getTodos: () => agentRef?.getTodos() ?? [],
|
|
176
|
-
setTodos: (todos) => agentRef?.setTodos(todos),
|
|
177
|
-
},
|
|
178
174
|
questionController,
|
|
179
175
|
goalStore: new GoalStore(),
|
|
180
176
|
checkpoints: () => session.getCheckpoints(),
|
|
@@ -214,12 +210,17 @@ export class BubbleSdk {
|
|
|
214
210
|
temperature: 0.2,
|
|
215
211
|
thinkingLevel,
|
|
216
212
|
mode,
|
|
217
|
-
todos: session.getTodos(),
|
|
218
213
|
budgetLedger: new BudgetLedger(),
|
|
219
214
|
fileStateTracker,
|
|
220
215
|
skills: skillRegistry.summaries(),
|
|
221
216
|
memoryPrompt,
|
|
222
217
|
externalHooks: hookController,
|
|
218
|
+
// Cross-provider subagent routes (spawn_agent with "provider:model")
|
|
219
|
+
// need a factory; without one the child blocks with
|
|
220
|
+
// "no provider factory is configured". Default mirrors the TUI's
|
|
221
|
+
// createProviderForRoute (OAuth prepare + configured+enabled+keyed
|
|
222
|
+
// profile lookup); hosts may override per turn.
|
|
223
|
+
providerFactory: options.providerFactory ?? this.defaultProviderFactory(),
|
|
223
224
|
onMessageAppend: (message) => {
|
|
224
225
|
if (message.role === "system" || message.role === "meta")
|
|
225
226
|
return;
|
|
@@ -230,7 +231,6 @@ export class BubbleSdk {
|
|
|
230
231
|
onCompactionApplied: (summary) => {
|
|
231
232
|
session.applyLLMCompaction(summary);
|
|
232
233
|
},
|
|
233
|
-
onTodosUpdate: (todos) => session.appendTodosSnapshot(todos),
|
|
234
234
|
onModeUpdate: (m) => session.appendMarker("mode_switch", m),
|
|
235
235
|
});
|
|
236
236
|
agentRef = agent;
|
|
@@ -276,6 +276,37 @@ export class BubbleSdk {
|
|
|
276
276
|
}
|
|
277
277
|
return allowlist;
|
|
278
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* Default cross-provider subagent factory over this SDK instance's own
|
|
281
|
+
* registry — the same semantics as the TUI's createProviderForRoute. The
|
|
282
|
+
* registry is reloaded first so a key added after construction works.
|
|
283
|
+
*/
|
|
284
|
+
defaultProviderFactory() {
|
|
285
|
+
return async (route) => {
|
|
286
|
+
const providerId = route.providerId;
|
|
287
|
+
if (!providerId) {
|
|
288
|
+
throw new Error(`Subagent route for model "${route.model}" did not include a provider.`);
|
|
289
|
+
}
|
|
290
|
+
// getConfigured() reads config.json from disk every call, so keys
|
|
291
|
+
// added after SDK construction are picked up without a reload call.
|
|
292
|
+
if (this.registry.supportsOAuth(providerId) && this.registry.getAuthStorage().has(providerId)) {
|
|
293
|
+
await this.registry.prepareProvider(providerId);
|
|
294
|
+
}
|
|
295
|
+
const target = this.registry.getConfigured().find((item) => item.id === providerId);
|
|
296
|
+
if (!target?.enabled || !target.apiKey) {
|
|
297
|
+
throw new Error(`Subagent route requires provider "${providerId}", but it is not configured or has no active credentials.`);
|
|
298
|
+
}
|
|
299
|
+
return createProviderInstance({
|
|
300
|
+
providerId,
|
|
301
|
+
apiKey: target.apiKey,
|
|
302
|
+
baseURL: target.baseURL,
|
|
303
|
+
protocol: target.protocol,
|
|
304
|
+
headers: target.headers,
|
|
305
|
+
openAICodexAuth: this.registry.createOpenAICodexAuthAdapter(providerId),
|
|
306
|
+
grokAuth: this.registry.createGrokAuthAdapter(providerId),
|
|
307
|
+
});
|
|
308
|
+
};
|
|
309
|
+
}
|
|
279
310
|
/** MCP servers are started lazily, once per cwd (McpManager has no stop). */
|
|
280
311
|
mcpToolsFor(cwd) {
|
|
281
312
|
if (!this.mcpEnabled)
|
package/dist/session-log.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Message
|
|
2
|
-
import type { SessionLogEntry, SessionMarkerKind, SessionMetadata, SessionSummaryEntry
|
|
1
|
+
import type { Message } from "./types.js";
|
|
2
|
+
import type { SessionLogEntry, SessionMarkerKind, SessionMetadata, SessionSummaryEntry } from "./session-types.js";
|
|
3
3
|
export declare class SessionLog {
|
|
4
4
|
private entries;
|
|
5
5
|
load(lines: string[]): void;
|
|
@@ -9,8 +9,6 @@ export declare class SessionLog {
|
|
|
9
9
|
setMetadata(metadata: SessionMetadata): SessionLogEntry[];
|
|
10
10
|
appendMessage(message: Message): SessionLogEntry[];
|
|
11
11
|
appendSummary(summary: string): SessionSummaryEntry;
|
|
12
|
-
appendTodosSnapshot(todos: Todo[]): SessionTodosSnapshotEntry;
|
|
13
|
-
getTodos(): Todo[];
|
|
14
12
|
appendMarker(kind: SessionMarkerKind, value: string): SessionLogEntry;
|
|
15
13
|
toMessages(): Message[];
|
|
16
14
|
}
|
package/dist/session-log.js
CHANGED
|
@@ -60,28 +60,6 @@ export class SessionLog {
|
|
|
60
60
|
this.entries.push(entry);
|
|
61
61
|
return entry;
|
|
62
62
|
}
|
|
63
|
-
appendTodosSnapshot(todos) {
|
|
64
|
-
const entry = {
|
|
65
|
-
id: nextEntryId(this.entries),
|
|
66
|
-
type: "todos_snapshot",
|
|
67
|
-
todos: todos.map((todo) => ({ ...todo })),
|
|
68
|
-
timestamp: Date.now(),
|
|
69
|
-
};
|
|
70
|
-
this.entries.push(entry);
|
|
71
|
-
return entry;
|
|
72
|
-
}
|
|
73
|
-
getTodos() {
|
|
74
|
-
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
75
|
-
const entry = this.entries[i];
|
|
76
|
-
if (entry.type === "marker" && entry.kind === "conversation_clear") {
|
|
77
|
-
return [];
|
|
78
|
-
}
|
|
79
|
-
if (entry.type === "todos_snapshot") {
|
|
80
|
-
return entry.todos.map((todo) => ({ ...todo }));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return [];
|
|
84
|
-
}
|
|
85
63
|
appendMarker(kind, value) {
|
|
86
64
|
const entry = {
|
|
87
65
|
id: nextEntryId(this.entries),
|
|
@@ -239,7 +217,6 @@ function isSessionLogEntry(entry) {
|
|
|
239
217
|
"assistant_message",
|
|
240
218
|
"tool_call",
|
|
241
219
|
"tool_result",
|
|
242
|
-
"todos_snapshot",
|
|
243
220
|
].includes(entry.type);
|
|
244
221
|
}
|
|
245
222
|
function nextEntryId(entries) {
|
package/dist/session-types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AssistantMessage, Message, ThinkingLevel,
|
|
1
|
+
import type { AssistantMessage, Message, ThinkingLevel, ToolCall, ToolMessage, UserMessage } from "./types.js";
|
|
2
2
|
import type { GoalState } from "./goal/store.js";
|
|
3
3
|
export interface ExternalRuntimeSessionMetadata {
|
|
4
4
|
/** External agent runtime that owns the conversational state. */
|
|
@@ -65,11 +65,7 @@ export interface SessionToolResultEntry extends BaseSessionLogEntry {
|
|
|
65
65
|
type: "tool_result";
|
|
66
66
|
message: ToolMessage;
|
|
67
67
|
}
|
|
68
|
-
export
|
|
69
|
-
type: "todos_snapshot";
|
|
70
|
-
todos: Todo[];
|
|
71
|
-
}
|
|
72
|
-
export type SessionLogEntry = SessionMetadataEntry | SessionSummaryEntry | SessionMarkerEntry | SessionUserMessageEntry | SessionAssistantMessageEntry | SessionToolCallEntry | SessionToolResultEntry | SessionTodosSnapshotEntry;
|
|
68
|
+
export type SessionLogEntry = SessionMetadataEntry | SessionSummaryEntry | SessionMarkerEntry | SessionUserMessageEntry | SessionAssistantMessageEntry | SessionToolCallEntry | SessionToolResultEntry;
|
|
73
69
|
export interface LegacySessionEntry {
|
|
74
70
|
id: string;
|
|
75
71
|
type: "metadata" | "message" | "compaction";
|
package/dist/session.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { CheckpointStore } from "./checkpoints.js";
|
|
5
5
|
import { type CompactOptions, type CompactResult } from "./context/compact.js";
|
|
6
|
-
import type { Message
|
|
6
|
+
import type { Message } from "./types.js";
|
|
7
7
|
import type { SessionLogEntry, SessionMarkerKind, SessionMetadata } from "./session-types.js";
|
|
8
8
|
export interface SessionSummary {
|
|
9
9
|
file: string;
|
|
@@ -55,8 +55,6 @@ export declare class SessionManager {
|
|
|
55
55
|
appendMessage(message: Message): void;
|
|
56
56
|
appendCompaction(summary: string): void;
|
|
57
57
|
appendMarker(kind: SessionMarkerKind, value: string): void;
|
|
58
|
-
appendTodosSnapshot(todos: Todo[]): void;
|
|
59
|
-
getTodos(): Todo[];
|
|
60
58
|
compact(options?: CompactOptions): CompactResult;
|
|
61
59
|
/**
|
|
62
60
|
* Inspect whether the session is large enough to compact and, if so, return
|
package/dist/session.js
CHANGED
|
@@ -152,14 +152,6 @@ export class SessionManager {
|
|
|
152
152
|
const entry = this.log.appendMarker(kind, value);
|
|
153
153
|
this.persist(entry);
|
|
154
154
|
}
|
|
155
|
-
appendTodosSnapshot(todos) {
|
|
156
|
-
const entry = this.log.appendTodosSnapshot(todos);
|
|
157
|
-
this.persist(entry);
|
|
158
|
-
this.maybeAutoCompact();
|
|
159
|
-
}
|
|
160
|
-
getTodos() {
|
|
161
|
-
return this.log.getTodos();
|
|
162
|
-
}
|
|
163
155
|
compact(options) {
|
|
164
156
|
const result = compactSessionEntries(this.log.list(), options);
|
|
165
157
|
if (result.compacted && result.entries) {
|
|
@@ -490,9 +490,6 @@ const builtinSlashCommandEntries = [
|
|
|
490
490
|
ctx.agent.resetContextUsageAnchor();
|
|
491
491
|
ctx.sessionManager?.appendMarker("conversation_clear", "");
|
|
492
492
|
ctx.sessionManager?.clearTitleMetadata?.();
|
|
493
|
-
if (ctx.agent.getTodos().length > 0) {
|
|
494
|
-
ctx.agent.setTodos([]);
|
|
495
|
-
}
|
|
496
493
|
ctx.clearMessages();
|
|
497
494
|
},
|
|
498
495
|
},
|
|
@@ -563,7 +560,6 @@ const builtinSlashCommandEntries = [
|
|
|
563
560
|
session.rewindToEntry(target.id);
|
|
564
561
|
const head = ctx.agent.messages.filter((m) => m.role === "system" || m.role === "meta");
|
|
565
562
|
ctx.agent.messages = [...head, ...session.getMessages()];
|
|
566
|
-
ctx.agent.setTodos(session.getTodos());
|
|
567
563
|
ctx.agent.resetContextUsageAnchor();
|
|
568
564
|
if (ctx.fillComposer) {
|
|
569
565
|
// Put the rewound message back into the input box for re-editing.
|
|
@@ -33,7 +33,7 @@ export declare class WorktreeApprovalController implements ApprovalController {
|
|
|
33
33
|
* exposes a cloneForChild hook (the standard `read`, which carries a
|
|
34
34
|
* FileStateTracker) is rebuilt as a fresh per-child instance, so concurrent
|
|
35
35
|
* members of a fan-out never share mutable tool state. Stateless tools
|
|
36
|
-
* (glob/grep, web/memory/skill
|
|
36
|
+
* (glob/grep, web/memory/skill) and custom/mock tools without the hook are
|
|
37
37
|
* passed through unchanged. Write children get full isolation via
|
|
38
38
|
* createWorktreeChildTools instead.
|
|
39
39
|
*/
|
|
@@ -97,7 +97,7 @@ const WORKTREE_TOOL_NAMES = new Set(["read", "glob", "grep", "edit", "write", "b
|
|
|
97
97
|
* exposes a cloneForChild hook (the standard `read`, which carries a
|
|
98
98
|
* FileStateTracker) is rebuilt as a fresh per-child instance, so concurrent
|
|
99
99
|
* members of a fan-out never share mutable tool state. Stateless tools
|
|
100
|
-
* (glob/grep, web/memory/skill
|
|
100
|
+
* (glob/grep, web/memory/skill) and custom/mock tools without the hook are
|
|
101
101
|
* passed through unchanged. Write children get full isolation via
|
|
102
102
|
* createWorktreeChildTools instead.
|
|
103
103
|
*/
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -15,7 +15,6 @@ export { createWebSearchTool } from "./web-search.js";
|
|
|
15
15
|
export { createSkillTool } from "./skill.js";
|
|
16
16
|
export { createSkillSearchTool } from "./skill-search.js";
|
|
17
17
|
export { createAgentLifecycleTools, createCloseAgentTool, createSendInputTool, createSpawnAgentTool, createWaitAgentTool } from "./agent-lifecycle.js";
|
|
18
|
-
export { createTodoTool, type TodoStore } from "./todo.js";
|
|
19
18
|
export { createExitPlanModeTool, type PlanController } from "./exit-plan-mode.js";
|
|
20
19
|
export { createToolSearchTool, type ToolSearchController } from "./tool-search.js";
|
|
21
20
|
export { createQuestionTool } from "./question.js";
|
|
@@ -25,7 +24,6 @@ import type { ApprovalController } from "../approval/types.js";
|
|
|
25
24
|
import type { SkillRegistry } from "../skills/registry.js";
|
|
26
25
|
import { type PlanController } from "./exit-plan-mode.js";
|
|
27
26
|
import { type LspService } from "../lsp/index.js";
|
|
28
|
-
import { type TodoStore } from "./todo.js";
|
|
29
27
|
import { type ToolSearchController } from "./tool-search.js";
|
|
30
28
|
import type { QuestionController } from "../question/index.js";
|
|
31
29
|
import type { CheckpointStore } from "../checkpoints.js";
|
|
@@ -34,7 +32,6 @@ import type { GoalStore } from "../goal/store.js";
|
|
|
34
32
|
import type { ProcessManager } from "../tasks/manager.js";
|
|
35
33
|
import type { PromotionChannel } from "../tasks/promotion.js";
|
|
36
34
|
export interface CreateAllToolsOptions {
|
|
37
|
-
todoStore?: TodoStore;
|
|
38
35
|
planController?: PlanController;
|
|
39
36
|
approvalController?: ApprovalController;
|
|
40
37
|
questionController?: QuestionController;
|
package/dist/tools/index.js
CHANGED
|
@@ -15,7 +15,6 @@ export { createWebSearchTool } from "./web-search.js";
|
|
|
15
15
|
export { createSkillTool } from "./skill.js";
|
|
16
16
|
export { createSkillSearchTool } from "./skill-search.js";
|
|
17
17
|
export { createAgentLifecycleTools, createCloseAgentTool, createSendInputTool, createSpawnAgentTool, createWaitAgentTool } from "./agent-lifecycle.js";
|
|
18
|
-
export { createTodoTool } from "./todo.js";
|
|
19
18
|
export { createExitPlanModeTool } from "./exit-plan-mode.js";
|
|
20
19
|
export { createToolSearchTool } from "./tool-search.js";
|
|
21
20
|
export { createQuestionTool } from "./question.js";
|
|
@@ -32,7 +31,6 @@ import { createReadTool } from "./read.js";
|
|
|
32
31
|
import { createSkillTool } from "./skill.js";
|
|
33
32
|
import { createSkillSearchTool } from "./skill-search.js";
|
|
34
33
|
import { createAgentLifecycleTools } from "./agent-lifecycle.js";
|
|
35
|
-
import { createTodoTool } from "./todo.js";
|
|
36
34
|
import { createToolSearchTool } from "./tool-search.js";
|
|
37
35
|
import { createWebFetchTool } from "./web-fetch.js";
|
|
38
36
|
import { createWebSearchTool } from "./web-search.js";
|
|
@@ -71,7 +69,6 @@ export function createAllTools(cwd, skillRegistry, options = {}) {
|
|
|
71
69
|
createToolSearchTool(options.toolSearchController),
|
|
72
70
|
...(options.questionController ? [createQuestionTool(options.questionController)] : []),
|
|
73
71
|
...(skillRegistry ? [createSkillSearchTool(skillRegistry), createSkillTool(skillRegistry)] : []),
|
|
74
|
-
...(options.todoStore ? [createTodoTool(options.todoStore)] : []),
|
|
75
72
|
...(options.planController ? [createExitPlanModeTool(options.planController)] : []),
|
|
76
73
|
...(options.goalStore ? createGoalTools(options.goalStore) : []),
|
|
77
74
|
];
|
package/dist/tui-ink/app.js
CHANGED
|
@@ -23,7 +23,6 @@ import { useTerminalSize } from "./use-terminal-size.js";
|
|
|
23
23
|
import { WelcomeBanner, shouldShowWelcomeBanner } from "./welcome.js";
|
|
24
24
|
import { BubbleCodeWordmark } from "./wordmark.js";
|
|
25
25
|
import { expandAtMentions } from "./file-mentions.js";
|
|
26
|
-
import { TodosPanel } from "./todos.js";
|
|
27
26
|
import { CompactionProgressCard } from "./compaction-progress.js";
|
|
28
27
|
import { PlanConfirm } from "./plan-confirm.js";
|
|
29
28
|
import { ApprovalDialog } from "./approval/approval-dialog.js";
|
|
@@ -146,7 +145,6 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
146
145
|
// model ladder.
|
|
147
146
|
const [thinkingLevel, setThinkingLevel] = useState(agent.thinking);
|
|
148
147
|
const [permissionMode, setPermissionMode] = useState(agent.mode);
|
|
149
|
-
const [todos, setTodos] = useState(() => agent.getTodos());
|
|
150
148
|
const [goalLine, setGoalLine] = useState("");
|
|
151
149
|
const [branch, setBranch] = useState(undefined);
|
|
152
150
|
const [contextUsage, setContextUsage] = useState("");
|
|
@@ -644,10 +642,6 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
644
642
|
// <Static>), so clearing React state is not enough — resetTranscript wipes
|
|
645
643
|
// the screen + scrollback and re-prints the (now empty) transcript.
|
|
646
644
|
resetTranscript(() => []);
|
|
647
|
-
// The todos panel renders off React state, not the transcript, so wiping
|
|
648
|
-
// messages alone leaves a stale To-Do list on screen. /clear already reset
|
|
649
|
-
// the agent's todos; mirror that into the UI (same as session switch).
|
|
650
|
-
setTodos(agent.getTodos());
|
|
651
645
|
clearLiveSubagentTools();
|
|
652
646
|
}, [resetTranscript, agent, clearLiveSubagentTools]);
|
|
653
647
|
// Render a placeholder user row for input waiting to enter the run.
|
|
@@ -805,7 +799,6 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
805
799
|
const nextExternalBinding = result.manager.getMetadata().externalRuntime;
|
|
806
800
|
externalRuntimeBindingRef.current = nextExternalBinding;
|
|
807
801
|
setExternalRuntimeBinding(nextExternalBinding);
|
|
808
|
-
setTodos(agent.getTodos());
|
|
809
802
|
clearLiveSubagentTools();
|
|
810
803
|
resetTranscript(() => [
|
|
811
804
|
...reconstructDisplayMessages(agent.messages).filter((message) => !queuedDisplayKeys.has(message.key ?? "")),
|
|
@@ -1513,10 +1506,6 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
1513
1506
|
}
|
|
1514
1507
|
break;
|
|
1515
1508
|
}
|
|
1516
|
-
case "todos_updated": {
|
|
1517
|
-
setTodos(event.todos);
|
|
1518
|
-
break;
|
|
1519
|
-
}
|
|
1520
1509
|
case "mode_changed": {
|
|
1521
1510
|
// Marker already persisted by onModeUpdate at setMode time.
|
|
1522
1511
|
setPermissionMode(event.mode);
|
|
@@ -2318,7 +2307,7 @@ export function App({ agent, args, sessionManager: initialSessionManager, switch
|
|
|
2318
2307
|
}, onCancel: () => {
|
|
2319
2308
|
closePicker();
|
|
2320
2309
|
addMessage("assistant", "已取消 Feishu setup。");
|
|
2321
|
-
} }) })), statsPanel && !pickerMode && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(StatsPanel, { panel: statsPanel, terminalColumns: mainWidth, terminalRows: terminalRows, onRangeChange: (range) => setStatsPanel((current) => current ? { ...current, range } : current), onCancel: closeStatsPanel }) })),
|
|
2310
|
+
} }) })), statsPanel && !pickerMode && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(StatsPanel, { panel: statsPanel, terminalColumns: mainWidth, terminalRows: terminalRows, onRangeChange: (range) => setStatsPanel((current) => current ? { ...current, range } : current), onCancel: closeStatsPanel }) })), pendingPlan && !pickerMode && !statsPanel && !pendingQuestion && (_jsx(Box, { paddingX: 1, flexShrink: 0, children: _jsx(PlanConfirm, { initialPlan: pendingPlan.plan, onApprove: (finalPlan) => {
|
|
2322
2311
|
const resolve = pendingPlan.resolve;
|
|
2323
2312
|
setPendingPlan(null);
|
|
2324
2313
|
resolve({ action: "approve", plan: finalPlan });
|
package/dist/types.d.ts
CHANGED
|
@@ -284,12 +284,6 @@ export type PlanDecision = {
|
|
|
284
284
|
action: "reject";
|
|
285
285
|
reason?: string;
|
|
286
286
|
};
|
|
287
|
-
export type TodoStatus = "pending" | "in_progress" | "completed";
|
|
288
|
-
export interface Todo {
|
|
289
|
-
content: string;
|
|
290
|
-
status: TodoStatus;
|
|
291
|
-
activeForm: string;
|
|
292
|
-
}
|
|
293
287
|
export type StreamChunk = {
|
|
294
288
|
type: "text";
|
|
295
289
|
content: string;
|
|
@@ -463,9 +457,6 @@ export type AgentEvent = {
|
|
|
463
457
|
} | {
|
|
464
458
|
type: "mode_changed";
|
|
465
459
|
mode: PermissionMode;
|
|
466
|
-
} | {
|
|
467
|
-
type: "todos_updated";
|
|
468
|
-
todos: Todo[];
|
|
469
460
|
} | {
|
|
470
461
|
type: "agent_end";
|
|
471
462
|
};
|
package/package.json
CHANGED
package/dist/tools/todo.d.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Todo tool - manage an agent-visible task list for multi-step work.
|
|
3
|
-
*
|
|
4
|
-
* The tool overwrites the entire list on each call. The model should re-send the full
|
|
5
|
-
* array with updated statuses rather than trying to patch individual items.
|
|
6
|
-
*/
|
|
7
|
-
import type { Todo, ToolRegistryEntry } from "../types.js";
|
|
8
|
-
export interface TodoStore {
|
|
9
|
-
getTodos: () => Todo[];
|
|
10
|
-
setTodos: (todos: Todo[]) => void;
|
|
11
|
-
}
|
|
12
|
-
export declare function createTodoTool(store: TodoStore): ToolRegistryEntry;
|
package/dist/tools/todo.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Todo tool - manage an agent-visible task list for multi-step work.
|
|
3
|
-
*
|
|
4
|
-
* The tool overwrites the entire list on each call. The model should re-send the full
|
|
5
|
-
* array with updated statuses rather than trying to patch individual items.
|
|
6
|
-
*/
|
|
7
|
-
export function createTodoTool(store) {
|
|
8
|
-
return {
|
|
9
|
-
name: "todo_write",
|
|
10
|
-
readOnly: true,
|
|
11
|
-
effect: "read",
|
|
12
|
-
description: `Create or update the task list for the current work. Send the COMPLETE list each call; this overwrites the prior list entirely.
|
|
13
|
-
|
|
14
|
-
## When to use
|
|
15
|
-
|
|
16
|
-
Default to just doing the work. Reach for a list only when actively tracking progress would genuinely help you or the user follow it — never to pad simple work with filler steps or to state the obvious. When in doubt, skip the list and do the task; a list you never meaningfully update is just noise.
|
|
17
|
-
|
|
18
|
-
A list earns its place when:
|
|
19
|
-
- The task is non-trivial and spans many actions across several areas of the codebase
|
|
20
|
-
- There are non-obvious phases or dependencies you must hold in mind to avoid losing track (a plain read → edit → test sequence does not count)
|
|
21
|
-
- The work is ambiguous and benefits from outlining the goals up front
|
|
22
|
-
- The user asked for several distinct things in one prompt, or gave a numbered/bulleted list
|
|
23
|
-
- The user explicitly asked for a todo list (aka TODOs)
|
|
24
|
-
- You discover extra steps mid-task and intend to finish them before yielding
|
|
25
|
-
|
|
26
|
-
## Quality bar
|
|
27
|
-
|
|
28
|
-
If you do make a list, make a good one: meaningful, logically ordered steps that are easy to verify as you go.
|
|
29
|
-
|
|
30
|
-
Good — distinct, verifiable steps for genuinely multi-part work:
|
|
31
|
-
1. Add CSS variables for the color palette
|
|
32
|
-
2. Add the toggle with localStorage state
|
|
33
|
-
3. Refactor components to use the variables
|
|
34
|
-
4. Verify every view for readability
|
|
35
|
-
|
|
36
|
-
Good — scope a search uncovers makes the list worth it:
|
|
37
|
-
"Rename getCwd across the project" → grep finds 15 call sites in 8 files → one item per file so none are missed.
|
|
38
|
-
|
|
39
|
-
Bad — padding a task you could just do; do NOT create a list for this:
|
|
40
|
-
"Fix the typo in the README title" → 1. Find typo 2. Open file 3. Fix it 4. Save. That is one edit — just make it.
|
|
41
|
-
|
|
42
|
-
Bad — vague, unverifiable filler: "Make it work", "Improve the styling", "Clean things up".
|
|
43
|
-
|
|
44
|
-
## Task states
|
|
45
|
-
|
|
46
|
-
- pending: not yet started
|
|
47
|
-
- in_progress: currently working on — exactly ONE at a time
|
|
48
|
-
- completed: finished successfully
|
|
49
|
-
|
|
50
|
-
Each item needs:
|
|
51
|
-
- content: imperative form (e.g. "Run tests")
|
|
52
|
-
- activeForm: present continuous, shown while in progress (e.g. "Running tests")
|
|
53
|
-
|
|
54
|
-
## Rules
|
|
55
|
-
|
|
56
|
-
- Update status in real time; mark completed IMMEDIATELY on finishing.
|
|
57
|
-
- Never mark completed if tests are failing, implementation is partial, errors are unresolved, or needed files are missing — keep as in_progress.
|
|
58
|
-
- When blocked, add a new task describing what must be resolved.
|
|
59
|
-
- Remove items that are no longer relevant; don't leave stale entries.
|
|
60
|
-
- Do not re-send the list when nothing meaningful has changed since the last call; update only after real progress.`,
|
|
61
|
-
parameters: {
|
|
62
|
-
type: "object",
|
|
63
|
-
properties: {
|
|
64
|
-
todos: {
|
|
65
|
-
type: "array",
|
|
66
|
-
description: "The complete list of todos. Replaces any existing list.",
|
|
67
|
-
items: {
|
|
68
|
-
type: "object",
|
|
69
|
-
properties: {
|
|
70
|
-
content: {
|
|
71
|
-
type: "string",
|
|
72
|
-
description: "Imperative form describing the task (e.g., 'Add unit tests for foo')",
|
|
73
|
-
},
|
|
74
|
-
activeForm: {
|
|
75
|
-
type: "string",
|
|
76
|
-
description: "Present continuous form shown while in progress (e.g., 'Adding unit tests for foo')",
|
|
77
|
-
},
|
|
78
|
-
status: {
|
|
79
|
-
type: "string",
|
|
80
|
-
enum: ["pending", "in_progress", "completed"],
|
|
81
|
-
description: "Current status of the task",
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
required: ["content", "activeForm", "status"],
|
|
85
|
-
},
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
required: ["todos"],
|
|
89
|
-
},
|
|
90
|
-
async execute(args) {
|
|
91
|
-
if (!Array.isArray(args.todos)) {
|
|
92
|
-
return { content: "Error: 'todos' must be an array", isError: true };
|
|
93
|
-
}
|
|
94
|
-
const normalized = [];
|
|
95
|
-
for (let i = 0; i < args.todos.length; i++) {
|
|
96
|
-
const raw = args.todos[i];
|
|
97
|
-
if (!raw || typeof raw !== "object") {
|
|
98
|
-
return { content: `Error: todo at index ${i} is not an object`, isError: true };
|
|
99
|
-
}
|
|
100
|
-
const content = typeof raw.content === "string" ? raw.content.trim() : "";
|
|
101
|
-
const activeForm = typeof raw.activeForm === "string" ? raw.activeForm.trim() : "";
|
|
102
|
-
const status = raw.status;
|
|
103
|
-
if (!content) {
|
|
104
|
-
return { content: `Error: todo at index ${i} has empty content`, isError: true };
|
|
105
|
-
}
|
|
106
|
-
if (!activeForm) {
|
|
107
|
-
return { content: `Error: todo at index ${i} has empty activeForm`, isError: true };
|
|
108
|
-
}
|
|
109
|
-
if (status !== "pending" && status !== "in_progress" && status !== "completed") {
|
|
110
|
-
return {
|
|
111
|
-
content: `Error: todo at index ${i} has invalid status "${status}". Must be pending|in_progress|completed`,
|
|
112
|
-
isError: true,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
normalized.push({ content, activeForm, status });
|
|
116
|
-
}
|
|
117
|
-
const inProgressCount = normalized.filter((t) => t.status === "in_progress").length;
|
|
118
|
-
if (inProgressCount > 1) {
|
|
119
|
-
return {
|
|
120
|
-
content: `Error: at most one todo may be in_progress at a time, found ${inProgressCount}`,
|
|
121
|
-
isError: true,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
store.setTodos(normalized);
|
|
125
|
-
const counts = {
|
|
126
|
-
pending: normalized.filter((t) => t.status === "pending").length,
|
|
127
|
-
in_progress: inProgressCount,
|
|
128
|
-
completed: normalized.filter((t) => t.status === "completed").length,
|
|
129
|
-
};
|
|
130
|
-
return {
|
|
131
|
-
content: `Todo list updated: ${normalized.length} item${normalized.length === 1 ? "" : "s"} ` +
|
|
132
|
-
`(${counts.completed} completed, ${counts.in_progress} in progress, ${counts.pending} pending).`,
|
|
133
|
-
};
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
}
|
package/dist/tui-ink/todos.d.ts
DELETED
package/dist/tui-ink/todos.js
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Box, Text } from "ink";
|
|
3
|
-
import { useTheme } from "./theme.js";
|
|
4
|
-
const MAX_ROWS = 8;
|
|
5
|
-
export function TodosPanel({ todos, terminalColumns }) {
|
|
6
|
-
const theme = useTheme();
|
|
7
|
-
if (todos.length === 0) {
|
|
8
|
-
return null;
|
|
9
|
-
}
|
|
10
|
-
const rows = selectVisibleRows(todos);
|
|
11
|
-
const hiddenCount = todos.length - rows.length;
|
|
12
|
-
const contentWidth = Math.max(20, terminalColumns - 4);
|
|
13
|
-
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: theme.accent, bold: true, children: "\u25CF Todos" }), rows.map((todo, index) => (_jsx(TodoRow, { todo: todo, maxWidth: contentWidth }, index))), hiddenCount > 0 && (_jsxs(Text, { color: theme.muted, children: ["... ", hiddenCount, " more item", hiddenCount === 1 ? "" : "s", " hidden"] }))] }));
|
|
14
|
-
}
|
|
15
|
-
function selectVisibleRows(todos) {
|
|
16
|
-
if (todos.length <= MAX_ROWS) {
|
|
17
|
-
return todos;
|
|
18
|
-
}
|
|
19
|
-
// Prefer to show: all in_progress, the last N completed just before current, and upcoming pending.
|
|
20
|
-
const inProgressIdx = todos.findIndex((t) => t.status === "in_progress");
|
|
21
|
-
const anchor = inProgressIdx >= 0 ? inProgressIdx : todos.findIndex((t) => t.status === "pending");
|
|
22
|
-
const pivot = anchor >= 0 ? anchor : 0;
|
|
23
|
-
const half = Math.floor(MAX_ROWS / 2);
|
|
24
|
-
let start = Math.max(0, pivot - half);
|
|
25
|
-
let end = Math.min(todos.length, start + MAX_ROWS);
|
|
26
|
-
if (end - start < MAX_ROWS) {
|
|
27
|
-
start = Math.max(0, end - MAX_ROWS);
|
|
28
|
-
}
|
|
29
|
-
return todos.slice(start, end);
|
|
30
|
-
}
|
|
31
|
-
function TodoRow({ todo, maxWidth }) {
|
|
32
|
-
const theme = useTheme();
|
|
33
|
-
const { glyph, color, dim, label } = statusStyle(todo, theme);
|
|
34
|
-
const text = label || todo.content;
|
|
35
|
-
const trimmed = text.length > maxWidth - 4 ? text.slice(0, maxWidth - 5) + "…" : text;
|
|
36
|
-
return (_jsx(Box, { height: 1, children: _jsxs(Text, { color: color, dimColor: dim, children: [glyph, " ", trimmed] }) }));
|
|
37
|
-
}
|
|
38
|
-
function statusStyle(todo, theme) {
|
|
39
|
-
if (todo.status === "completed") {
|
|
40
|
-
return { glyph: "✔", color: theme.muted, dim: true, label: todo.content };
|
|
41
|
-
}
|
|
42
|
-
if (todo.status === "in_progress") {
|
|
43
|
-
return { glyph: "▶", color: theme.accent, dim: false, label: todo.activeForm || todo.content };
|
|
44
|
-
}
|
|
45
|
-
return { glyph: "○", color: theme.muted, dim: false, label: todo.content };
|
|
46
|
-
}
|