@bubblebrain-ai/bubble 0.0.49 → 0.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/agent/abort-errors.d.ts +4 -0
  2. package/dist/agent/abort-errors.js +30 -0
  3. package/dist/agent/profiles.js +3 -4
  4. package/dist/agent/provider-fingerprint.d.ts +6 -0
  5. package/dist/agent/provider-fingerprint.js +89 -0
  6. package/dist/agent/resident-history.d.ts +7 -0
  7. package/dist/agent/resident-history.js +61 -0
  8. package/dist/agent/subtask-policy.js +4 -4
  9. package/dist/agent/transcript-helpers.d.ts +20 -0
  10. package/dist/agent/transcript-helpers.js +62 -0
  11. package/dist/agent/update-queue.d.ts +13 -0
  12. package/dist/agent/update-queue.js +69 -0
  13. package/dist/agent.d.ts +33 -26
  14. package/dist/agent.js +80 -334
  15. package/dist/debug-trace.js +0 -2
  16. package/dist/external-runtime/grok-runtime.js +1 -1
  17. package/dist/feishu/agent-host/run-driver.js +1 -8
  18. package/dist/feishu/card/run-state.js +0 -3
  19. package/dist/lsp/service.js +2 -2
  20. package/dist/main.js +0 -15
  21. package/dist/memory/phase1.js +0 -2
  22. package/dist/model-catalog.js +8 -6
  23. package/dist/permissions/settings.d.ts +0 -1
  24. package/dist/permissions/settings.js +0 -2
  25. package/dist/prompt/compose.js +0 -3
  26. package/dist/prompt/environment.js +0 -2
  27. package/dist/prompt/reminders.js +1 -1
  28. package/dist/provider-registry.js +13 -1
  29. package/dist/provider-transform.js +7 -8
  30. package/dist/sdk/index.d.ts +17 -2
  31. package/dist/sdk/index.js +37 -6
  32. package/dist/session-log.d.ts +2 -4
  33. package/dist/session-log.js +0 -23
  34. package/dist/session-types.d.ts +2 -6
  35. package/dist/session.d.ts +1 -3
  36. package/dist/session.js +0 -8
  37. package/dist/slash-commands/commands.js +13 -13
  38. package/dist/tools/child-tools.d.ts +1 -1
  39. package/dist/tools/child-tools.js +1 -1
  40. package/dist/tools/grep.js +1 -1
  41. package/dist/tools/index.d.ts +0 -3
  42. package/dist/tools/index.js +0 -3
  43. package/dist/tui-ink/app-helpers.d.ts +43 -0
  44. package/dist/tui-ink/app-helpers.js +150 -0
  45. package/dist/tui-ink/app.d.ts +4 -11
  46. package/dist/tui-ink/app.js +57 -657
  47. package/dist/tui-ink/command-palette.d.ts +32 -0
  48. package/dist/tui-ink/command-palette.js +158 -0
  49. package/dist/tui-ink/display-reconstruct.d.ts +3 -0
  50. package/dist/tui-ink/display-reconstruct.js +82 -0
  51. package/dist/tui-ink/message-list.js +0 -1
  52. package/dist/tui-ink/rewind-picker.d.ts +8 -0
  53. package/dist/tui-ink/rewind-picker.js +102 -0
  54. package/dist/tui-ink/session-picker.js +1 -1
  55. package/dist/tui-ink/stats-panel.d.ts +11 -0
  56. package/dist/tui-ink/stats-panel.js +65 -0
  57. package/dist/tui-ink/waiting-indicator.d.ts +12 -0
  58. package/dist/tui-ink/waiting-indicator.js +129 -0
  59. package/dist/types.d.ts +0 -9
  60. package/package.json +1 -1
  61. package/dist/tools/todo.d.ts +0 -12
  62. package/dist/tools/todo.js +0 -136
  63. package/dist/tui-ink/todos.d.ts +0 -7
  64. package/dist/tui-ink/todos.js +0 -46
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
  }
@@ -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) {
@@ -41,13 +41,12 @@ const OPENAI_CHAT_LEVELS = ["off"];
41
41
  // Internal representation for APIs that expose thinking as enabled/disabled.
42
42
  // UI code must render supported toggle models as on/off, not as "medium" effort.
43
43
  const TOGGLE_THINKING_LEVELS = ["off", "medium"];
44
- // GLM-5.2 is the first GLM to accept OpenAI-style `reasoning_effort`. The API
45
- // enum is none/minimal/low/medium/high/xhigh/max; we expose high and max (the
46
- // two effort tiers worth offering a coding agent) plus "off", which disables
47
- // thinking outright via `thinking: {type: "disabled"}`. Order matters: "high"
48
- // is first so it is the default (getDefaultThinkingLevel falls back to levels[0]
49
- // when "medium" is absent), since GLM-5.2 is a thinking-on-by-default model.
44
+ // GLM-5.2 accepts OpenAI-style `reasoning_effort`. We expose high and max plus
45
+ // "off", which disables thinking outright via `thinking: {type: "disabled"}`.
50
46
  const GLM_5_2_LEVELS = ["high", "max", "off"];
47
+ // GLM-5.3 Coding Plan normalizes efforts into exactly three real tiers. Its
48
+ // default is max, and disabling thinking maps to low rather than turning it off.
49
+ const GLM_5_3_LEVELS = ["low", "high", "max"];
51
50
  // Kimi K2.7 Code variants only support thinking mode (disabling it errors), so
52
51
  // "off" is not offered — the model is always in its thinking variant.
53
52
  const KIMI_THINKING_ONLY_LEVELS = ["medium"];
@@ -124,6 +123,9 @@ export const BUILTIN_MODELS = [
124
123
  { id: "glm-5.1", name: "GLM-5.1", providerId: "zhipuai", tier: "balanced", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
125
124
  { id: "glm-4.7", name: "GLM-4.7", providerId: "zhipuai", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
126
125
  { id: "glm-4.6", name: "GLM-4.6", providerId: "zhipuai", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
126
+ // Coding Plan exposes GLM-5.3 before GET /models lists it (verified live
127
+ // 2026-08-14). The standard API is still marked "coming soon" in the docs.
128
+ { id: "glm-5.3", name: "GLM-5.3", providerId: "zhipuai-coding-plan", tier: "strong", reasoningLevels: GLM_5_3_LEVELS, defaultReasoningLevel: "max", contextWindow: 1000000 },
127
129
  { id: "glm-5.2", name: "GLM-5.2", providerId: "zhipuai-coding-plan", tier: "strong", reasoningLevels: GLM_5_2_LEVELS, contextWindow: 1000000 },
128
130
  { id: "glm-5.1", name: "GLM-5.1", providerId: "zhipuai-coding-plan", tier: "balanced", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 200000 },
129
131
  { id: "glm-4.7", name: "GLM-4.7", providerId: "zhipuai-coding-plan", reasoningLevels: TOGGLE_THINKING_LEVELS, contextWindow: 204800 },
@@ -43,7 +43,6 @@ export interface SettingsManagerOptions {
43
43
  bubbleHome?: string;
44
44
  }
45
45
  export declare class SettingsManager {
46
- private readonly cwd;
47
46
  private readonly paths;
48
47
  private raw;
49
48
  private fileDiagnostics;
@@ -25,7 +25,6 @@ const KNOWN_MODES = new Set([
25
25
  "bypassPermissions",
26
26
  ]);
27
27
  export class SettingsManager {
28
- cwd;
29
28
  paths;
30
29
  raw = {
31
30
  user: null,
@@ -34,7 +33,6 @@ export class SettingsManager {
34
33
  };
35
34
  fileDiagnostics = [];
36
35
  constructor(cwd, options = {}) {
37
- this.cwd = cwd;
38
36
  const bubbleHome = options.bubbleHome ?? getBubbleHome();
39
37
  this.paths = {
40
38
  user: join(bubbleHome, "settings.json"),
@@ -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";
@@ -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, todo_write, tool_search, question, and exit_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, 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.
@@ -458,7 +458,19 @@ export class ProviderRegistry {
458
458
  if (!options.forceRefresh) {
459
459
  const cached = this.modelDiscoveryCache.get(key);
460
460
  if (cached && cached.expiresAt > now) {
461
- const result = { ...cached.result, source: "cache" };
461
+ // A disk cache can outlive the application version that wrote it. Keep
462
+ // the current curated catalog authoritative over stale cached metadata,
463
+ // then retain cached remote-only ids for non-authoritative union results.
464
+ // This lets newly-added builtins surface immediately after an upgrade
465
+ // without waiting up to 24 hours or requiring a manual Ctrl+R refresh.
466
+ const local = this.localModelsForProvider(provider);
467
+ const localIds = new Set(local.map((model) => model.id));
468
+ const models = cached.result.source === "static"
469
+ ? local
470
+ : cached.result.authoritative
471
+ ? cached.result.models
472
+ : [...local, ...cached.result.models.filter((model) => !localIds.has(model.id))];
473
+ const result = { ...cached.result, models, source: "cache" };
462
474
  const current = this.getConfigured().find((item) => item.id === provider.id);
463
475
  if (!current || this.modelDiscoveryKey(current) === key) {
464
476
  this.applyDynamicDiscoveryMetadata(provider, result);
@@ -114,16 +114,15 @@ export function resolveProviderRequestConfig(providerId, modelId, requestedLevel
114
114
  // Zhipu/Z.AI OpenAI-compatible endpoints expose reasoning via a provider-specific
115
115
  // `thinking` block rather than OpenAI's `reasoning_effort` shape.
116
116
  if (["zhipuai", "zhipuai-coding-plan", "zai", "zai-coding-plan"].includes(providerId)) {
117
- // GLM-5.2 is the only GLM that also accepts `reasoning_effort` (we expose
118
- // high/max, which map 1:1 onto the API enum). "off" disables thinking via
119
- // `thinking: {type: "disabled"}` — otherwise the server default (thinking
120
- // on, effort max) would make "off" a no-op. The effort field rides inside
121
- // the body alongside `thinking`, so it goes in extraBody, not the
122
- // OpenRouter-style `reasoningEffort` config field.
123
- if (modelId === "glm-5.2") {
117
+ // GLM-5.2+ also accept `reasoning_effort`. GLM-5.2 can disable thinking;
118
+ // GLM-5.3 requires thinking and Coding Plan normalizes its effort into
119
+ // low/high/max. The catalog clamps 5.3 choices before this serialization
120
+ // boundary. The effort field rides inside the body alongside `thinking`,
121
+ // so it goes in extraBody, not the OpenRouter-style config field.
122
+ if (modelId === "glm-5.2" || modelId === "glm-5.3") {
124
123
  return {
125
124
  effectiveThinkingLevel,
126
- extraBody: effectiveThinkingLevel === "off"
125
+ extraBody: modelId === "glm-5.2" && effectiveThinkingLevel === "off"
127
126
  ? { thinking: { type: "disabled" } }
128
127
  : {
129
128
  thinking: { type: "enabled", clear_thinking: false },
@@ -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, Todo, TokenUsage, UsageCost, ToolRegistryEntry, ToolResult, ToolUpdate, } from "../types.js";
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)
@@ -1,5 +1,5 @@
1
- import type { Message, Todo } from "./types.js";
2
- import type { SessionLogEntry, SessionMarkerKind, SessionMetadata, SessionSummaryEntry, SessionTodosSnapshotEntry } from "./session-types.js";
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
  }
@@ -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) {
@@ -1,4 +1,4 @@
1
- import type { AssistantMessage, Message, ThinkingLevel, Todo, ToolCall, ToolMessage, UserMessage } from "./types.js";
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 interface SessionTodosSnapshotEntry extends BaseSessionLogEntry {
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, Todo } from "./types.js";
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) {
@@ -16,7 +16,7 @@ import { collectUsageStatsBundle, formatStatsText } from "../stats/usage.js";
16
16
  import { buildMemoryPrompt, getMemoryStatus, isMemoryDisabled, resetMemory, searchMemory, } from "../memory/index.js";
17
17
  import { feishuCommand } from "./feishu.js";
18
18
  import { GROK_LOCAL_COMMAND_HELP } from "../external-runtime/grok-input-policy.js";
19
- import { isGrokSubscriptionProviderId, } from "../external-runtime/grok-provider.js";
19
+ import { isGrokSubscriptionProviderId } from "../external-runtime/grok-provider.js";
20
20
  import { classifyExternalRuntimeBinding } from "../external-runtime/session-policy.js";
21
21
  const VALID_SCOPES = ["user", "project", "local"];
22
22
  const VALID_LISTS = ["allow", "deny"];
@@ -107,7 +107,7 @@ function persistSelectedModel(model, ctx) {
107
107
  }
108
108
  }
109
109
  function syncSystemPrompt(ctx, model) {
110
- const { providerId, modelId } = decodeModel(model);
110
+ const { providerId } = decodeModel(model);
111
111
  const toolPromptOptions = typeof ctx.agent.getSystemPromptToolOptions === "function"
112
112
  ? ctx.agent.getSystemPromptToolOptions()
113
113
  : {};
@@ -412,14 +412,14 @@ const builtinSlashCommandEntries = [
412
412
  {
413
413
  name: "skills",
414
414
  description: "Open the searchable skills picker",
415
- async handler(args, ctx) {
415
+ async handler(_args, ctx) {
416
416
  ctx.openPicker("skill");
417
417
  },
418
418
  },
419
419
  {
420
420
  name: "help",
421
421
  description: "Show available slash commands",
422
- async handler(args, ctx) {
422
+ async handler(_args, ctx) {
423
423
  if (hasExternalRuntimeSession(ctx)) {
424
424
  const grok = isGrokSessionActive(ctx);
425
425
  return [
@@ -448,14 +448,14 @@ const builtinSlashCommandEntries = [
448
448
  {
449
449
  name: "context",
450
450
  description: "Show current context window usage and breakdown",
451
- async handler(args, ctx) {
451
+ async handler(_args, ctx) {
452
452
  return `${formatContextUsage(ctx.agent.getContextUsageSnapshot())}\n\n${formatMcpContextStatus(ctx)}`;
453
453
  },
454
454
  },
455
455
  {
456
456
  name: "quit",
457
457
  description: "Exit the application",
458
- async handler(args, ctx) {
458
+ async handler(_args, ctx) {
459
459
  ctx.exit();
460
460
  },
461
461
  },
@@ -482,13 +482,14 @@ const builtinSlashCommandEntries = [
482
482
  {
483
483
  name: "clear",
484
484
  description: "Clear the current conversation history",
485
- async handler(args, ctx) {
485
+ async handler(_args, ctx) {
486
486
  ctx.agent.messages = ctx.agent.messages.filter((m) => m.role === "system" || m.role === "meta");
487
+ // The resident history just shrank: drop the incremental usage anchor
488
+ // too (same as /compact and /rewind), or budget accounting keeps
489
+ // measuring against the pre-clear message count.
490
+ ctx.agent.resetContextUsageAnchor();
487
491
  ctx.sessionManager?.appendMarker("conversation_clear", "");
488
492
  ctx.sessionManager?.clearTitleMetadata?.();
489
- if (ctx.agent.getTodos().length > 0) {
490
- ctx.agent.setTodos([]);
491
- }
492
493
  ctx.clearMessages();
493
494
  },
494
495
  },
@@ -559,7 +560,6 @@ const builtinSlashCommandEntries = [
559
560
  session.rewindToEntry(target.id);
560
561
  const head = ctx.agent.messages.filter((m) => m.role === "system" || m.role === "meta");
561
562
  ctx.agent.messages = [...head, ...session.getMessages()];
562
- ctx.agent.setTodos(session.getTodos());
563
563
  ctx.agent.resetContextUsageAnchor();
564
564
  if (ctx.fillComposer) {
565
565
  // Put the rewound message back into the input box for re-editing.
@@ -845,7 +845,7 @@ const builtinSlashCommandEntries = [
845
845
  {
846
846
  name: "plan",
847
847
  description: "Toggle plan mode on/off (Tab switches Build/Plan)",
848
- async handler(args, ctx) {
848
+ async handler(_args, ctx) {
849
849
  const next = ctx.agent.mode === "plan" ? "default" : "plan";
850
850
  ctx.agent.setMode(next);
851
851
  return next === "plan"
@@ -1069,7 +1069,7 @@ const builtinSlashCommandEntries = [
1069
1069
  {
1070
1070
  name: "compact",
1071
1071
  description: "Manually compact the current session context",
1072
- async handler(args, ctx) {
1072
+ async handler(_args, ctx) {
1073
1073
  if (!ctx.sessionManager) {
1074
1074
  return "Compaction requires session persistence. Start an interactive session first.";
1075
1075
  }
@@ -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/todo) and custom/mock tools without the hook are
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/todo) and custom/mock tools without the hook are
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
  */
@@ -54,7 +54,7 @@ export function createGrepTool(cwd) {
54
54
  }
55
55
  rgArgs.push(searchPath);
56
56
  return new Promise((resolve) => {
57
- execFile("rg", rgArgs, { cwd, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
57
+ execFile("rg", rgArgs, { cwd, maxBuffer: 10 * 1024 * 1024 }, (_error, stdout, _stderr) => {
58
58
  // rg returns exit code 1 when no matches found, which is not an error for us
59
59
  const lines = stdout.split("\n").filter((l) => l.trim() !== "");
60
60
  const matches = [];
@@ -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;
@@ -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
  ];