@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
@@ -12,3 +12,7 @@ export declare class SubagentAbortError extends AgentAbortError {
12
12
  }
13
13
  /** Shown when the model produced no user-visible content despite recovery attempts. */
14
14
  export declare const EMPTY_ASSISTANT_FALLBACK = "The model returned no user-visible response. Please retry, or switch models if this keeps happening.";
15
+ export declare function throwIfAborted(signal?: AbortSignal): void;
16
+ export declare function isAbortLikeError(error: unknown, signal?: AbortSignal): boolean;
17
+ export declare function isAbortError(error: unknown, signal?: AbortSignal): boolean;
18
+ export declare function summarizeInterruptError(error: unknown): string;
@@ -19,3 +19,33 @@ export class SubagentAbortError extends AgentAbortError {
19
19
  }
20
20
  /** Shown when the model produced no user-visible content despite recovery attempts. */
21
21
  export const EMPTY_ASSISTANT_FALLBACK = "The model returned no user-visible response. Please retry, or switch models if this keeps happening.";
22
+ export function throwIfAborted(signal) {
23
+ if (!signal?.aborted)
24
+ return;
25
+ const reason = signal.reason;
26
+ if (reason instanceof Error)
27
+ throw reason;
28
+ throw new AgentAbortError(typeof reason === "string" ? reason : undefined);
29
+ }
30
+ export function isAbortLikeError(error, signal) {
31
+ if (signal?.aborted)
32
+ return true;
33
+ if (error instanceof AgentAbortError)
34
+ return true;
35
+ if (error instanceof DOMException && error.name === "AbortError")
36
+ return true;
37
+ if (typeof error === "object" && error !== null && error.name === "AbortError")
38
+ return true;
39
+ return false;
40
+ }
41
+ export function isAbortError(error, signal) {
42
+ return isAbortLikeError(error, signal);
43
+ }
44
+ export function summarizeInterruptError(error) {
45
+ const message = error instanceof Error
46
+ ? error.message
47
+ : typeof error === "string"
48
+ ? error
49
+ : String(error);
50
+ return message.replace(/\s+/g, " ").trim().slice(0, 240) || "unknown error";
51
+ }
@@ -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", "todo_write"]),
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", "todo_write"]),
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", "todo_write"],
148
+ include: ["read", "glob", "grep", "edit", "write", "bash", "lsp"],
150
149
  exclude: [],
151
150
  },
152
151
  approval: "fail",
@@ -0,0 +1,6 @@
1
+ import type { Message, ProviderMessage, ProviderMetadataProvider, ProviderRawContentBlock, ToolDefinition } from "../types.js";
2
+ export declare function appendProviderContentBlock(message: Extract<Message, {
3
+ role: "assistant";
4
+ }>, provider: ProviderMetadataProvider, block: ProviderRawContentBlock): void;
5
+ export declare function buildProviderRequestFingerprint(messages: ProviderMessage[], tools: ToolDefinition[], providerId: string, toolChoice?: string): Record<string, unknown>;
6
+ export declare function cloneProviderRawContentBlock(block: ProviderRawContentBlock): ProviderRawContentBlock;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Provider-request fingerprints and raw content-block plumbing.
3
+ *
4
+ * The fingerprint is a structural digest of the projected request (roles,
5
+ * char volumes, JSON byte sizes) used to detect whether a retried request
6
+ * actually changed, and to trace what the model saw.
7
+ */
8
+ import { estimateContextTokens } from "../context/budget.js";
9
+ export function appendProviderContentBlock(message, provider, block) {
10
+ const current = message.providerMetadata?.[provider]?.contentBlocks ?? [];
11
+ message.providerMetadata = {
12
+ ...message.providerMetadata,
13
+ [provider]: {
14
+ ...message.providerMetadata?.[provider],
15
+ contentBlocks: [...current, cloneProviderRawContentBlock(block)],
16
+ },
17
+ };
18
+ }
19
+ export function buildProviderRequestFingerprint(messages, tools, providerId, toolChoice) {
20
+ const roleCounts = {};
21
+ let contentChars = 0;
22
+ let reasoningChars = 0;
23
+ let toolResultChars = 0;
24
+ let maxToolResultChars = 0;
25
+ let assistantToolCalls = 0;
26
+ let rawAnthropicBlocks = 0;
27
+ let rawAnthropicThinkingBlocks = 0;
28
+ let rawAnthropicSignatureChars = 0;
29
+ for (const message of messages) {
30
+ roleCounts[message.role] = (roleCounts[message.role] ?? 0) + 1;
31
+ if (message.role === "assistant") {
32
+ contentChars += message.content.length;
33
+ reasoningChars += message.reasoning?.length ?? 0;
34
+ assistantToolCalls += message.toolCalls?.length ?? 0;
35
+ const blocks = message.providerMetadata?.anthropic?.contentBlocks ?? [];
36
+ rawAnthropicBlocks += blocks.length;
37
+ for (const block of blocks) {
38
+ if (block.type === "thinking" || block.type === "redacted_thinking") {
39
+ rawAnthropicThinkingBlocks += 1;
40
+ }
41
+ if (typeof block.signature === "string") {
42
+ rawAnthropicSignatureChars += block.signature.length;
43
+ }
44
+ }
45
+ }
46
+ else if (message.role === "tool") {
47
+ toolResultChars += message.content.length;
48
+ maxToolResultChars = Math.max(maxToolResultChars, message.content.length);
49
+ }
50
+ else if (message.role === "user") {
51
+ contentChars += typeof message.content === "string"
52
+ ? message.content.length
53
+ : message.content.reduce((sum, part) => sum + (part.type === "text" ? part.text.length : part.image_url.url.length), 0);
54
+ }
55
+ else {
56
+ contentChars += message.content.length;
57
+ }
58
+ }
59
+ const systemMessages = messages.filter((message) => message.role === "system");
60
+ const bodyMessages = messages.filter((message) => message.role !== "system");
61
+ const systemJsonBytes = Buffer.byteLength(JSON.stringify(systemMessages), "utf8");
62
+ const bodyJsonBytes = Buffer.byteLength(JSON.stringify(bodyMessages), "utf8");
63
+ const toolSchemaJsonBytes = Buffer.byteLength(JSON.stringify(tools), "utf8");
64
+ return {
65
+ roleCounts,
66
+ estimatedTokens: estimateContextTokens(messages, providerId),
67
+ projectedJsonBytes: Buffer.byteLength(JSON.stringify(messages), "utf8"),
68
+ systemJsonBytes,
69
+ bodyJsonBytes,
70
+ toolSchemaJsonBytes,
71
+ staticPrefixJsonBytes: Buffer.byteLength(JSON.stringify({
72
+ system: systemMessages,
73
+ tools,
74
+ tool_choice: toolChoice,
75
+ }), "utf8"),
76
+ toolChoice,
77
+ contentChars,
78
+ reasoningChars,
79
+ toolResultChars,
80
+ maxToolResultChars,
81
+ assistantToolCalls,
82
+ rawAnthropicBlocks,
83
+ rawAnthropicThinkingBlocks,
84
+ rawAnthropicSignatureChars,
85
+ };
86
+ }
87
+ export function cloneProviderRawContentBlock(block) {
88
+ return JSON.parse(JSON.stringify(block));
89
+ }
@@ -0,0 +1,7 @@
1
+ import type { Message } from "../types.js";
2
+ export declare const RESIDENT_HISTORY_KEEP_RECENT_TURNS = 3;
3
+ export declare const RESIDENT_HISTORY_HEAP_HARD_LIMIT: number;
4
+ export declare function estimateResidentChars(messages: Message[]): number;
5
+ export declare function estimateToolPayloadChars(messages: Message[]): number;
6
+ export declare function countUserTurns(messages: Message[]): number;
7
+ export declare function getCurrentHeapUsed(): number;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Resident-history size estimation and the cliff-edge guards that decide when
3
+ * in-memory history is compacted (see docs/harness-thinning.md, wave 2).
4
+ */
5
+ import { isRealUserMessage } from "../context/compact.js";
6
+ export const RESIDENT_HISTORY_KEEP_RECENT_TURNS = 3;
7
+ export const RESIDENT_HISTORY_HEAP_HARD_LIMIT = 768 * 1024 * 1024;
8
+ export function estimateResidentChars(messages) {
9
+ let total = 0;
10
+ for (const message of messages) {
11
+ switch (message.role) {
12
+ case "system":
13
+ case "meta":
14
+ total += message.content.length;
15
+ break;
16
+ case "tool":
17
+ total += message.content.length + message.toolCallId.length;
18
+ break;
19
+ case "assistant":
20
+ total += message.content.length + (message.reasoning?.length ?? 0);
21
+ total += message.toolCalls?.reduce((sum, toolCall) => sum + toolCall.id.length + toolCall.name.length + toolCall.arguments.length, 0) ?? 0;
22
+ break;
23
+ case "user":
24
+ if (typeof message.content === "string") {
25
+ total += message.content.length;
26
+ }
27
+ else {
28
+ total += message.content.reduce((sum, part) => {
29
+ if (part.type === "text") {
30
+ return sum + part.text.length;
31
+ }
32
+ return sum + part.image_url.url.length;
33
+ }, 0);
34
+ }
35
+ break;
36
+ }
37
+ }
38
+ return total;
39
+ }
40
+ export function estimateToolPayloadChars(messages) {
41
+ return messages.reduce((sum, message) => {
42
+ if (message.role !== "tool") {
43
+ return sum;
44
+ }
45
+ return sum + message.content.length;
46
+ }, 0);
47
+ }
48
+ export function countUserTurns(messages) {
49
+ // Real user turns only: projected reminders and summary envelopes are
50
+ // user-ROLE but not user TURNS — counting them used to shrink the keep
51
+ // window and fire compaction almost every turn.
52
+ return messages.reduce((count, message) => count + (isRealUserMessage(message) ? 1 : 0), 0);
53
+ }
54
+ export function getCurrentHeapUsed() {
55
+ try {
56
+ return process.memoryUsage().heapUsed;
57
+ }
58
+ catch {
59
+ return 0;
60
+ }
61
+ }
@@ -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", "todo_write"],
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", "todo_write"],
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", "todo_write"],
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", "todo_write"],
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.",
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Transcript-level helpers: interrupted-run boundaries, the cancelled-tool
3
+ * sentinel, required-arg validation and the subagent-lifecycle tool-name
4
+ * predicate used when forking context for children.
5
+ */
6
+ import type { Message, ToolResult } from "../types.js";
7
+ export declare function findMissingRequiredArgs(schema: {
8
+ required?: string[];
9
+ } | undefined, args: Record<string, any> | undefined): string[];
10
+ export declare function shouldAppendModelInterruptedBoundary(messages: Message[]): boolean;
11
+ export declare function createModelInterruptedMessage(error: unknown, metadata: {
12
+ model: string;
13
+ providerId: string;
14
+ modelId: string;
15
+ }): Extract<Message, {
16
+ role: "assistant";
17
+ }>;
18
+ export declare function lastProviderMessage(messages: Message[]): Message | undefined;
19
+ export declare function cancelledToolResult(toolName: string): ToolResult;
20
+ export declare function isSubagentLifecycleTool(name: string): boolean;
@@ -0,0 +1,62 @@
1
+ import { summarizeInterruptError } from "./abort-errors.js";
2
+ export function findMissingRequiredArgs(schema, args) {
3
+ const required = schema?.required;
4
+ if (!required || required.length === 0)
5
+ return [];
6
+ const missing = [];
7
+ for (const name of required) {
8
+ const value = args ? args[name] : undefined;
9
+ // Empty strings/arrays are intentionally allowed — writing an empty file
10
+ // or passing an empty list can be legitimate. Only undefined/null counts
11
+ // as "missing", because the observed failure mode is `finalArgs: "{}"`
12
+ // where the field is entirely absent.
13
+ if (value === undefined || value === null) {
14
+ missing.push(name);
15
+ }
16
+ }
17
+ return missing;
18
+ }
19
+ export function shouldAppendModelInterruptedBoundary(messages) {
20
+ return messages.at(-1)?.role === "tool";
21
+ }
22
+ export function createModelInterruptedMessage(error, metadata) {
23
+ return {
24
+ role: "assistant",
25
+ content: `[model request interrupted before a final answer was produced: ${summarizeInterruptError(error)}]`,
26
+ model: metadata.model,
27
+ providerId: metadata.providerId,
28
+ modelId: metadata.modelId,
29
+ };
30
+ }
31
+ export function lastProviderMessage(messages) {
32
+ for (let i = messages.length - 1; i >= 0; i--) {
33
+ const message = messages[i];
34
+ if (message.role === "system" || message.role === "meta")
35
+ continue;
36
+ return message;
37
+ }
38
+ return undefined;
39
+ }
40
+ export function cancelledToolResult(toolName) {
41
+ return {
42
+ content: `Tool "${toolName}" was cancelled.`,
43
+ isError: true,
44
+ status: "cancelled",
45
+ metadata: { reason: "cancelled" },
46
+ };
47
+ }
48
+ export function isSubagentLifecycleTool(name) {
49
+ return name === "subagent"
50
+ || name === "spawn_agent"
51
+ || name === "wait_agent"
52
+ || name === "send_input"
53
+ || name === "close_agent"
54
+ || name === "list_agents"
55
+ || name === "run_workflow"
56
+ || name === "wait_workflow"
57
+ // Legacy names: still present in transcripts recorded before the tools
58
+ // were removed (2026-07-06); forked children must not inherit their
59
+ // dangling tool_calls either.
60
+ || name === "agent_team"
61
+ || name === "agent_batch";
62
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Wake-latched update queue shared by the agent run loop: tool updates,
3
+ * subagent deliveries and workflow completions all park here until the loop
4
+ * is ready to drain them.
5
+ */
6
+ /** Exported for tests: the wake latch below is easiest to pin directly. */
7
+ export declare function createUpdateQueue<T>(): {
8
+ push(item: T): void;
9
+ drain(): T[];
10
+ hasItems(): boolean;
11
+ wait(signal?: AbortSignal): Promise<"woken" | "aborted">;
12
+ wake(): void;
13
+ };
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Wake-latched update queue shared by the agent run loop: tool updates,
3
+ * subagent deliveries and workflow completions all park here until the loop
4
+ * is ready to drain them.
5
+ */
6
+ /** Exported for tests: the wake latch below is easiest to pin directly. */
7
+ export function createUpdateQueue() {
8
+ const items = [];
9
+ let waiter;
10
+ let abortCleanup;
11
+ // Latch for wakes that arrive with no parked waiter — a wake() fired while
12
+ // the consuming generator is suspended at a yield would otherwise be lost.
13
+ // Callers today each pair their wake with a synchronously-checked flag
14
+ // (items / settled / pendingSubagentUpdates), so no wake is currently
15
+ // dropped; the latch keeps that correctness inside the queue instead of
16
+ // depending on every future caller repeating the pairing.
17
+ let signaled = false;
18
+ return {
19
+ push(item) {
20
+ items.push(item);
21
+ this.wake();
22
+ },
23
+ drain() {
24
+ return items.splice(0, items.length);
25
+ },
26
+ hasItems() {
27
+ return items.length > 0;
28
+ },
29
+ wait(signal) {
30
+ if (items.length > 0)
31
+ return Promise.resolve("woken");
32
+ if (signaled) {
33
+ signaled = false;
34
+ return Promise.resolve("woken");
35
+ }
36
+ if (signal?.aborted)
37
+ return Promise.resolve("aborted");
38
+ return new Promise((resolve) => {
39
+ abortCleanup?.();
40
+ abortCleanup = undefined;
41
+ const finish = (status) => {
42
+ if (waiter !== resolve)
43
+ return;
44
+ waiter = undefined;
45
+ abortCleanup?.();
46
+ abortCleanup = undefined;
47
+ resolve(status);
48
+ };
49
+ if (signal) {
50
+ const onAbort = () => finish("aborted");
51
+ signal.addEventListener("abort", onAbort, { once: true });
52
+ abortCleanup = () => signal.removeEventListener("abort", onAbort);
53
+ }
54
+ waiter = resolve;
55
+ });
56
+ },
57
+ wake() {
58
+ const resolve = waiter;
59
+ if (!resolve) {
60
+ signaled = true;
61
+ return;
62
+ }
63
+ waiter = undefined;
64
+ abortCleanup?.();
65
+ abortCleanup = undefined;
66
+ resolve("woken");
67
+ },
68
+ };
69
+ }
package/dist/agent.d.ts CHANGED
@@ -3,16 +3,18 @@
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, Todo, ToolResult, ToolRegistryEntry, ToolUpdate } from "./types.js";
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
- import { type AgentCategoriesConfig, type ResolvedSubagentRoute } from "./agent/categories.js";
9
+ import type { AgentCategoriesConfig, ResolvedSubagentRoute } from "./agent/categories.js";
10
10
  import { type AgentRoutingConfig, type RoutableModelEntry, type RoutableModelIndex, type RoutingSnapshotAccessor } from "./agent/routing-catalog.js";
11
11
  import { SubagentRuntime } from "./agent/subagent/runtime.js";
12
- import { type WorkflowRunSnapshot } from "./agent/workflow/control.js";
13
12
  import { BudgetLedger } from "./agent/budget-ledger.js";
14
- import { type AgentProfile, type SubagentRunResult } from "./agent/profiles.js";
15
- import { type SubagentThreadSnapshot } from "./agent/subagent-control.js";
13
+ import { type WorkflowRunSnapshot } from "./agent/workflow/control.js";
14
+ import type { AgentProfile, SubagentRunResult } from "./agent/profiles.js";
15
+ import type { SubagentThreadSnapshot } from "./agent/subagent-control.js";
16
+ import { SubagentStore } from "./agent/subagent-store.js";
17
+ export { createUpdateQueue } from "./agent/update-queue.js";
16
18
  import { type RateLimitPolicy } from "./network/errors.js";
17
19
  import type { SkillSummary } from "./skills/types.js";
18
20
  import type { FileStateTracker } from "./tools/file-state.js";
@@ -47,11 +49,9 @@ export interface AgentOptions {
47
49
  taskBudget?: {
48
50
  total: number;
49
51
  };
50
- todos?: Todo[];
51
52
  systemPrompt?: string;
52
53
  onMessageAppend?: (message: Message) => void;
53
54
  onToolResult?: (toolName: string, result: ToolResult) => void;
54
- onTodosUpdate?: (todos: Todo[]) => void;
55
55
  onModeUpdate?: (mode: PermissionMode) => void;
56
56
  /**
57
57
  * Fired when MULTI-TURN compaction rewrites resident history, with the
@@ -108,7 +108,18 @@ export interface AgentRunOptions {
108
108
  resumeWithoutInput?: boolean;
109
109
  }
110
110
  export declare class Agent {
111
- messages: Message[];
111
+ /**
112
+ * Backing store for the public `messages` accessor. All whole-array
113
+ * rewrites (clear/compact/rewind/session switch/subagent fork) go through
114
+ * the setter, which notifies `onContextChanged` subscribers — that push
115
+ * channel is what keeps derived UI (e.g. the footer usage readout) in sync
116
+ * without per-path refresh calls that are easy to forget.
117
+ */
118
+ private _messages;
119
+ private contextChangedListeners;
120
+ private notifyingContextChange;
121
+ get messages(): Message[];
122
+ set messages(value: Message[]);
112
123
  private provider;
113
124
  private sessionID?;
114
125
  /**
@@ -139,9 +150,6 @@ export declare class Agent {
139
150
  private _modeVersion;
140
151
  private onModeUpdate?;
141
152
  private onCompactionApplied?;
142
- private _todos;
143
- private _todosVersion;
144
- private onTodosUpdate?;
145
153
  private onMessageAppend?;
146
154
  private onToolResult?;
147
155
  private hookDefinitions;
@@ -226,6 +234,15 @@ export declare class Agent {
226
234
  providerId: string;
227
235
  model: string;
228
236
  }): string | undefined;
237
+ /**
238
+ * Push subscription for derived-context consumers (TUI footer, hosts).
239
+ * Fires whenever resident context mutates in a way that changes a
240
+ * `getContextUsageSnapshot()` reading: whole-array message rewrites,
241
+ * appended messages, system-prompt replacement, or model/provider switches
242
+ * (window size / estimation basis). Returns an unsubscribe function.
243
+ */
244
+ onContextChanged(listener: () => void): () => void;
245
+ private notifyContextChanged;
229
246
  getContextUsageSnapshot(): ContextUsageSnapshot;
230
247
  resetContextUsageAnchor(): void;
231
248
  /** Whether a given tool is deferred and not yet unlocked. */
@@ -269,10 +286,6 @@ export declare class Agent {
269
286
  setMode(value: PermissionMode): void;
270
287
  /** Internal: snapshot counter that bumps on every mode change. Used by run loop. */
271
288
  get modeVersion(): number;
272
- getTodos(): Todo[];
273
- setTodos(next: Todo[]): void;
274
- /** Internal: snapshot counter that bumps on every setTodos. Used by run loop to detect mutations. */
275
- get todosVersion(): number;
276
289
  setSystemPrompt(prompt: string): void;
277
290
  run(userInput: string | ContentPart[], cwd: string, options?: AgentRunOptions): AsyncIterable<AgentEvent>;
278
291
  private recoverFromOverflow;
@@ -326,7 +339,7 @@ export declare class Agent {
326
339
  * `(agent as any).subagentStore`; keeping this forward means the extraction
327
340
  * changed no caller, test or otherwise.
328
341
  */
329
- private get subagentStore();
342
+ get subagentStore(): SubagentStore;
330
343
  /** run_workflow children are workflowInternal and invisible to listSubAgents. */
331
344
  hasRunningWorkflow(): boolean;
332
345
  /**
@@ -393,7 +406,10 @@ export declare class Agent {
393
406
  * haiku for these twenty scouts" per spawn/batch member at request time.
394
407
  */
395
408
  /** Kept as a thin delegator: routing decisions now live in SubagentRouter. */
396
- private resolveRouteForSubagent;
409
+ resolveRouteForSubagent(profile: AgentProfile, category: string | undefined, override?: {
410
+ model?: string;
411
+ effort?: ThinkingLevel;
412
+ }): ResolvedSubagentRoute;
397
413
  /** Routable catalog across runnable providers (design v3.6); undefined when unwired. */
398
414
  listRoutableModels(): RoutableModelEntry[] | undefined;
399
415
  /**
@@ -415,14 +431,5 @@ export declare class Agent {
415
431
  private persistCompactionSummary;
416
432
  private appendMessage;
417
433
  private appendInterruptedAssistantBoundary;
418
- private clearTodosAfterInterruptedRun;
419
434
  private executeTool;
420
435
  }
421
- /** Exported for tests: the wake latch below is easiest to pin directly. */
422
- export declare function createUpdateQueue<T>(): {
423
- push(item: T): void;
424
- drain(): T[];
425
- hasItems(): boolean;
426
- wait(signal?: AbortSignal): Promise<"woken" | "aborted">;
427
- wake(): void;
428
- };