@bubblebrain-ai/bubble 0.0.45 → 0.0.47

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 (93) hide show
  1. package/dist/agent/child-runner.js +11 -0
  2. package/dist/agent/internal-reminder-sanitizer.d.ts +7 -0
  3. package/dist/agent/internal-reminder-sanitizer.js +13 -0
  4. package/dist/agent/subagent/router.d.ts +65 -0
  5. package/dist/agent/subagent/router.js +198 -0
  6. package/dist/agent/subagent/runtime.d.ts +203 -0
  7. package/dist/agent/subagent/runtime.js +789 -0
  8. package/dist/agent/subagent-control.d.ts +5 -0
  9. package/dist/agent/subagent-store.d.ts +18 -2
  10. package/dist/agent/subagent-store.js +32 -1
  11. package/dist/agent/tool-intent.d.ts +10 -0
  12. package/dist/agent/tool-intent.js +18 -0
  13. package/dist/agent/tool-visibility.d.ts +10 -0
  14. package/dist/agent/tool-visibility.js +6 -0
  15. package/dist/agent/workflow/runs.d.ts +72 -0
  16. package/dist/agent/workflow/runs.js +157 -0
  17. package/dist/agent.d.ts +78 -96
  18. package/dist/agent.js +266 -1072
  19. package/dist/context/compact-llm.js +14 -5
  20. package/dist/context/compact.d.ts +9 -0
  21. package/dist/context/compact.js +36 -10
  22. package/dist/context/compaction-files.d.ts +44 -0
  23. package/dist/context/compaction-files.js +148 -0
  24. package/dist/context/llm-compactor.js +24 -5
  25. package/dist/feishu/agent-host/run-driver.js +3 -0
  26. package/dist/feishu/serve.js +13 -9
  27. package/dist/goal/tools.js +12 -3
  28. package/dist/main.js +24 -19
  29. package/dist/model-catalog.js +36 -2
  30. package/dist/model-config.d.ts +2 -0
  31. package/dist/model-data.generated.d.ts +16 -0
  32. package/dist/model-data.generated.js +275 -0
  33. package/dist/model-pricing.d.ts +11 -7
  34. package/dist/model-pricing.js +0 -0
  35. package/dist/oauth/grok.d.ts +1 -1
  36. package/dist/oauth/grok.js +58 -29
  37. package/dist/oauth/openai-codex.js +13 -0
  38. package/dist/oauth/storage.d.ts +9 -2
  39. package/dist/oauth/storage.js +16 -9
  40. package/dist/orchestrator/default-hooks.d.ts +20 -0
  41. package/dist/orchestrator/default-hooks.js +47 -295
  42. package/dist/orchestrator/hooks.d.ts +6 -39
  43. package/dist/permission/mode.js +1 -1
  44. package/dist/prompt/reminders.d.ts +9 -35
  45. package/dist/prompt/reminders.js +30 -136
  46. package/dist/prompt/task-reminders.d.ts +0 -31
  47. package/dist/prompt/task-reminders.js +0 -87
  48. package/dist/provider-ai-sdk.d.ts +2 -0
  49. package/dist/provider-ai-sdk.js +1 -0
  50. package/dist/provider-anthropic.d.ts +2 -0
  51. package/dist/provider-anthropic.js +3 -1
  52. package/dist/provider-ark-responses.d.ts +2 -0
  53. package/dist/provider-ark-responses.js +1 -0
  54. package/dist/provider-registry.d.ts +13 -0
  55. package/dist/provider-registry.js +83 -0
  56. package/dist/provider-transform.js +37 -1
  57. package/dist/provider.d.ts +7 -0
  58. package/dist/provider.js +13 -1
  59. package/dist/sdk/index.d.ts +152 -0
  60. package/dist/sdk/index.js +400 -0
  61. package/dist/session.js +9 -1
  62. package/dist/slash-commands/commands.js +33 -19
  63. package/dist/tools/exit-plan-mode.js +8 -0
  64. package/dist/tui/detect-theme.d.ts +1 -0
  65. package/dist/tui/detect-theme.js +11 -5
  66. package/dist/tui-ink/app.d.ts +2 -6
  67. package/dist/tui-ink/app.js +11 -18
  68. package/dist/tui-ink/display-history.d.ts +0 -2
  69. package/dist/tui-ink/display-history.js +0 -52
  70. package/dist/tui-ink/footer.js +2 -2
  71. package/dist/tui-ink/message-list.js +62 -23
  72. package/dist/tui-ink/run.js +10 -5
  73. package/dist/tui-ink/sync-output.d.ts +17 -0
  74. package/dist/tui-ink/sync-output.js +35 -0
  75. package/dist/tui-ink/theme.d.ts +0 -1
  76. package/dist/tui-ink/theme.js +0 -2
  77. package/dist/tui-ink/trace-groups.js +1 -1
  78. package/dist/types.d.ts +21 -1
  79. package/package.json +16 -2
  80. package/dist/agent/discovery-barrier.d.ts +0 -21
  81. package/dist/agent/discovery-barrier.js +0 -173
  82. package/dist/agent/evidence-tracker.d.ts +0 -15
  83. package/dist/agent/evidence-tracker.js +0 -93
  84. package/dist/agent/execution-governor.d.ts +0 -36
  85. package/dist/agent/execution-governor.js +0 -218
  86. package/dist/agent/task-classifier.d.ts +0 -3
  87. package/dist/agent/task-classifier.js +0 -114
  88. package/dist/agent/task-size.d.ts +0 -9
  89. package/dist/agent/task-size.js +0 -33
  90. package/dist/agent/tool-arbiter.d.ts +0 -7
  91. package/dist/agent/tool-arbiter.js +0 -33
  92. package/dist/orchestrator/workflow.d.ts +0 -10
  93. package/dist/orchestrator/workflow.js +0 -22
@@ -37,6 +37,17 @@ export class ChildRunner {
37
37
  return { kind: "final" };
38
38
  }
39
39
  const tools = selectToolsForAgentProfile(allTools, record.profile, options.approval);
40
+ // A write child whose worktree was reclaimed (unchanged → removed at a
41
+ // terminal outcome) must NOT reuse its live instance: the instance's
42
+ // tools are fenced to the deleted directory, so every write would be
43
+ // rejected while the run itself falls back to the parent cwd. Carry the
44
+ // conversation over and rebuild through createInstance, which allocates
45
+ // a fresh worktree. Mid-run retries (attempt > 1) never hit this — the
46
+ // worktree is only reclaimed at terminal outcomes.
47
+ if (record.profile.mode === "write_worktree" && !record.worktree && record.agent) {
48
+ record.messages = record.agent.messages.map((message) => ({ ...message }));
49
+ record.agent = undefined;
50
+ }
40
51
  const reuseExistingAgent = (options.reuseAgent || attempt > 1) && !!record.agent;
41
52
  let subAgent;
42
53
  try {
@@ -7,6 +7,13 @@ export declare const INTERNAL_BLOCK_PREFIX = "<bubble_internal_";
7
7
  * the user actually typed.
8
8
  */
9
9
  export declare function isInternalBlockContent(text: string): boolean;
10
+ /**
11
+ * True when the ENTIRE content is one internal block (start- and end-anchored)
12
+ * — the shape harness-injected user-role messages (goal kicks, task wakes)
13
+ * have. Display surfaces drop these rows outright; anything looser (a block
14
+ * plus trailing text) must instead be sanitized, never dropped.
15
+ */
16
+ export declare function isInternalBlockOnlyContent(content: unknown): boolean;
10
17
  export declare function formatInternalReminderBlock(kind: string, content: string): string;
11
18
  export declare function formatInternalContextBlock(kind: string, content: string): string;
12
19
  export declare function sanitizeInternalReminderBlocks(text: string): string;
@@ -37,6 +37,19 @@ export const INTERNAL_BLOCK_PREFIX = "<bubble_internal_";
37
37
  export function isInternalBlockContent(text) {
38
38
  return text.trimStart().startsWith(INTERNAL_BLOCK_PREFIX);
39
39
  }
40
+ /**
41
+ * True when the ENTIRE content is one internal block (start- and end-anchored)
42
+ * — the shape harness-injected user-role messages (goal kicks, task wakes)
43
+ * have. Display surfaces drop these rows outright; anything looser (a block
44
+ * plus trailing text) must instead be sanitized, never dropped.
45
+ */
46
+ export function isInternalBlockOnlyContent(content) {
47
+ if (typeof content !== "string")
48
+ return false;
49
+ const trimmed = content.trim();
50
+ return /^<bubble_internal_(?:context|reminder)\b/.test(trimmed)
51
+ && /<\/bubble_internal_(?:context|reminder)>$/.test(trimmed);
52
+ }
40
53
  export function formatInternalReminderBlock(kind, content) {
41
54
  return formatInternalBlock("reminder", kind, content);
42
55
  }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Subagent model routing (design §3-§7).
3
+ *
4
+ * Extracted from Agent so the routing decision chain and the routing prompt
5
+ * rendering live in one place: before this, the decision side sat next to the
6
+ * subagent dispatch code while `buildModelRoutingPromptSection` /
7
+ * `renderModelRoutingPromptFor` sat 2,000 lines away at the top of the class,
8
+ * with both halves reading the same `agentRouting` / `routingSnapshot` fields.
9
+ *
10
+ * The parent route is supplied as a thunk, never a snapshot: `/model` mutates
11
+ * the parent's provider, model and thinking level mid-session, and children
12
+ * spawned afterwards must inherit the CURRENT values.
13
+ */
14
+ import { type AgentCategoriesConfig, type ResolvedSubagentRoute } from "../categories.js";
15
+ import { type AgentRoutingConfig, type RoutableModelEntry, type RoutableModelIndex, type RoutingSnapshot, type RoutingSnapshotAccessor } from "../routing-catalog.js";
16
+ import type { AgentProfile } from "../profiles.js";
17
+ import type { ThinkingLevel } from "../../types.js";
18
+ export interface ParentRoute {
19
+ providerId: string;
20
+ /** The API-facing model id — already stripped of any "provider:" prefix. */
21
+ model: string;
22
+ thinkingLevel: ThinkingLevel;
23
+ }
24
+ export interface SubagentRouterDeps {
25
+ /**
26
+ * Live parent route. MUST read through to the agent on every call — `/model`
27
+ * reassigns provider/model/thinking mid-session (see slash-commands), and a
28
+ * captured snapshot would route children to the pre-switch model.
29
+ */
30
+ parentRoute(): ParentRoute;
31
+ categories: AgentCategoriesConfig;
32
+ routing: AgentRoutingConfig;
33
+ snapshot?: RoutingSnapshotAccessor;
34
+ routableModels?: RoutableModelIndex;
35
+ }
36
+ export declare class SubagentRouter {
37
+ private readonly deps;
38
+ constructor(deps: SubagentRouterDeps);
39
+ /** Live snapshot for the CURRENT parent route; undefined when no accessor is wired. */
40
+ currentSnapshot(): RoutingSnapshot | undefined;
41
+ resolve(profile: AgentProfile, category: string | undefined, override?: {
42
+ model?: string;
43
+ effort?: ThinkingLevel;
44
+ }): ResolvedSubagentRoute;
45
+ /**
46
+ * Early route validation at dispatch time (design §7). Throws with an
47
+ * actionable message so the model self-corrects in the same turn. The
48
+ * cross-provider lock (§7.2.1) is snapshot-independent; catalog checks
49
+ * degrade silently when no snapshot is available (§7.0).
50
+ */
51
+ validateForDispatch(route: ResolvedSubagentRoute): string | undefined;
52
+ /** Routable catalog across runnable providers (design v3.6); undefined when unwired. */
53
+ listRoutableModels(): RoutableModelEntry[] | undefined;
54
+ /** Current routing menu (design §4); undefined when no accessor is wired. */
55
+ promptSection(): string | undefined;
56
+ /**
57
+ * Routing menu rendered for a prospective parent route — used by model-
58
+ * switch transactions to build the NEXT prompt before mutating the agent
59
+ * (design §1.5).
60
+ */
61
+ promptSectionFor(parent: {
62
+ providerId: string;
63
+ model: string;
64
+ }): string | undefined;
65
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Subagent model routing (design §3-§7).
3
+ *
4
+ * Extracted from Agent so the routing decision chain and the routing prompt
5
+ * rendering live in one place: before this, the decision side sat next to the
6
+ * subagent dispatch code while `buildModelRoutingPromptSection` /
7
+ * `renderModelRoutingPromptFor` sat 2,000 lines away at the top of the class,
8
+ * with both halves reading the same `agentRouting` / `routingSnapshot` fields.
9
+ *
10
+ * The parent route is supplied as a thunk, never a snapshot: `/model` mutates
11
+ * the parent's provider, model and thinking level mid-session, and children
12
+ * spawned afterwards must inherit the CURRENT values.
13
+ */
14
+ import { mergeAgentCategories, resolveModelRoute, resolveSubagentRoute } from "../categories.js";
15
+ import { nearModelMatches, tierContextFromSnapshot } from "../routing-catalog.js";
16
+ import { buildModelRoutingPrompt } from "../../prompt/routing.js";
17
+ import { getBuiltinModel } from "../../model-catalog.js";
18
+ import { getAvailableThinkingLevels, getDefaultThinkingLevel, normalizeInheritedThinkingLevel, normalizeThinkingLevel } from "../../variant/variant-resolver.js";
19
+ export class SubagentRouter {
20
+ deps;
21
+ constructor(deps) {
22
+ this.deps = deps;
23
+ }
24
+ /** Live snapshot for the CURRENT parent route; undefined when no accessor is wired. */
25
+ currentSnapshot() {
26
+ if (!this.deps.snapshot)
27
+ return undefined;
28
+ const parent = this.deps.parentRoute();
29
+ try {
30
+ return this.deps.snapshot({ providerId: parent.providerId, model: parent.model });
31
+ }
32
+ catch {
33
+ // Catalog data is an enhancement, never a spawn blocker.
34
+ return undefined;
35
+ }
36
+ }
37
+ resolve(profile, category, override) {
38
+ const parentRoute = this.deps.parentRoute();
39
+ const snapshot = this.currentSnapshot();
40
+ const resolved = resolveSubagentRoute(category ?? profile.category, { ...parentRoute }, this.deps.categories, snapshot ? tierContextFromSnapshot(snapshot, this.deps.routing) : undefined);
41
+ if ("error" in resolved) {
42
+ throw new Error(resolved.error);
43
+ }
44
+ let route = resolved.route;
45
+ // modelSource is assigned while applying the chain (design §3.4): an
46
+ // explicit layer naming the parent's own model is indistinguishable from
47
+ // inherit by final-value comparison, yet carries different authority.
48
+ let modelSource = route.categoryModelSource ?? "inherit";
49
+ if (profile.model && profile.model !== "inherit") {
50
+ const model = resolveModelRoute(profile.model, parentRoute.providerId);
51
+ if (model.model !== "inherit") {
52
+ route = { ...route, providerId: model.providerId, model: model.model, inherited: false };
53
+ modelSource = "profile";
54
+ }
55
+ }
56
+ // Call-site override beats profile and category. Bare names resolve
57
+ // against the PARENT provider per the tool contract (design §3.5) — not
58
+ // against a provider that category/profile may already have switched.
59
+ if (override?.model) {
60
+ const model = resolveModelRoute(override.model, parentRoute.providerId);
61
+ if (model.model !== "inherit") {
62
+ route = { ...route, providerId: model.providerId, model: model.model, inherited: false };
63
+ modelSource = "callsite";
64
+ }
65
+ }
66
+ const supportedLevels = getAvailableThinkingLevels(route.providerId, route.model);
67
+ const modelMetadata = getBuiltinModel(route.providerId, route.model);
68
+ const hasTrustedEffortMetadata = !!modelMetadata
69
+ && modelMetadata.reasoningLevels.some((level) => level !== "off");
70
+ if (override?.effort) {
71
+ // A call-site effort is explicit user/model intent: preserve the existing
72
+ // value for legacy/unknown models, and downward-clamp only when the
73
+ // catalog declares real effort capabilities (for example Luna ultra -> max).
74
+ route = {
75
+ ...route,
76
+ thinkingLevel: hasTrustedEffortMetadata
77
+ ? normalizeThinkingLevel(override.effort, supportedLevels)
78
+ : override.effort,
79
+ inherited: false,
80
+ };
81
+ }
82
+ else if (hasTrustedEffortMetadata) {
83
+ const categoryThinkingLevel = route.category
84
+ ? mergeAgentCategories(this.deps.categories)[route.category]?.thinkingLevel
85
+ : undefined;
86
+ // A category's thinkingLevel is calibrated for the model the CATEGORY
87
+ // resolved. When a later layer (profile/call-site) replaced the model,
88
+ // that level is no longer explicit intent for the final model: keep it
89
+ // only if supported, else use the final model's own default — never
90
+ // downward-clamp a thinking-default model to "off" (v3.6; live case:
91
+ // explore's "low" + call-site glm-5.2 [high/max/off] silently landed
92
+ // on "off").
93
+ const modelReplacedAfterCategory = modelSource === "profile" || modelSource === "callsite";
94
+ route = {
95
+ ...route,
96
+ thinkingLevel: categoryThinkingLevel
97
+ ? (modelReplacedAfterCategory && !supportedLevels.includes(route.thinkingLevel)
98
+ ? getDefaultThinkingLevel(route.providerId, route.model)
99
+ : normalizeThinkingLevel(route.thinkingLevel, supportedLevels))
100
+ : normalizeInheritedThinkingLevel(route.providerId, route.model, route.thinkingLevel),
101
+ };
102
+ }
103
+ return {
104
+ ...route,
105
+ modelSource,
106
+ modelInherited: route.providerId === parentRoute.providerId && route.model === parentRoute.model,
107
+ };
108
+ }
109
+ /**
110
+ * Early route validation at dispatch time (design §7). Throws with an
111
+ * actionable message so the model self-corrects in the same turn. The
112
+ * cross-provider lock (§7.2.1) is snapshot-independent; catalog checks
113
+ * degrade silently when no snapshot is available (§7.0).
114
+ */
115
+ validateForDispatch(route) {
116
+ const parentProviderId = this.deps.parentRoute().providerId;
117
+ const snapshot = this.currentSnapshot();
118
+ const crossProvider = !!route.providerId && route.providerId !== parentProviderId;
119
+ if (crossProvider) {
120
+ // §7.2.1 — the lock. Profile/user-category routes are standing user
121
+ // authorization and always pass; only call-site routes are lockable.
122
+ if (!this.deps.routing.allowCrossProvider && route.modelSource === "callsite") {
123
+ throw new Error("Cross-provider routing is disabled in this session's config (agentRouting.allowCrossProvider). "
124
+ + "Use a model from the parent provider, or ask the user to unlock cross-provider routing.");
125
+ }
126
+ // §7.2.2 — credentials, when a snapshot is available.
127
+ if (snapshot && !snapshot.runnableProviderIds.includes(route.providerId)) {
128
+ throw new Error(`Provider "${route.providerId}" is not configured with active credentials. `
129
+ + `Available: ${snapshot.runnableProviderIds.join(", ") || "(none)"}.`);
130
+ }
131
+ // §7.2.3 amended (v3.6): the provider stays the authority — no hard
132
+ // catalog rejection — but a near-match against the target provider's
133
+ // local catalog is positive evidence of a mistyped id (a Grok parent
134
+ // invented "openai:gpt-5.6" for gpt-5.6-sol), so soft-reject with the
135
+ // correction and let the model fix it this turn. Genuinely unknown ids
136
+ // (no near candidates) still pass through with a note.
137
+ const targetCatalog = this.deps.routableModels?.()
138
+ .filter((entry) => entry.providerId === route.providerId) ?? [];
139
+ if (targetCatalog.length > 0 && !targetCatalog.some((entry) => entry.id === route.model)) {
140
+ const near = nearModelMatches(route.model, targetCatalog, { mode: "truncation" });
141
+ if (near.length > 0) {
142
+ throw new Error(`Unknown model "${route.model}" for provider "${route.providerId}". Did you mean: ${near.join(", ")}?`);
143
+ }
144
+ return `model ${route.providerId}:${route.model} is not in the local catalog; the provider validates it`;
145
+ }
146
+ return `model ${route.providerId}:${route.model} is not locally verifiable; the provider validates it`;
147
+ }
148
+ // §7.1 — same-provider unknown model, resolved-provider based (never
149
+ // input-syntax based; qualified same-provider ids land here too).
150
+ if (snapshot && route.modelSource === "callsite") {
151
+ const known = snapshot.models.some((model) => model.id === route.model);
152
+ if (!known) {
153
+ if (snapshot.authoritative) {
154
+ const available = snapshot.models
155
+ .map((model) => (model.tier ? `${model.id} (${model.tier})` : model.id))
156
+ .join(", ");
157
+ throw new Error(`Unknown model "${route.model}" for provider "${parentProviderId}". Available: ${available}.`);
158
+ }
159
+ const near = nearModelMatches(route.model, snapshot.models.map((model) => ({ providerId: parentProviderId, id: model.id, name: model.name })));
160
+ return near.length > 0
161
+ ? `model id "${route.model}" is unrecognized locally (did you mean: ${near.join(", ")}?); the provider validates it`
162
+ : `model id "${route.model}" is unrecognized locally; the provider validates it`;
163
+ }
164
+ }
165
+ return undefined;
166
+ }
167
+ /** Routable catalog across runnable providers (design v3.6); undefined when unwired. */
168
+ listRoutableModels() {
169
+ try {
170
+ return this.deps.routableModels?.();
171
+ }
172
+ catch {
173
+ return undefined;
174
+ }
175
+ }
176
+ /** Current routing menu (design §4); undefined when no accessor is wired. */
177
+ promptSection() {
178
+ const snapshot = this.currentSnapshot();
179
+ if (!snapshot)
180
+ return undefined;
181
+ return buildModelRoutingPrompt(snapshot, this.deps.routing);
182
+ }
183
+ /**
184
+ * Routing menu rendered for a prospective parent route — used by model-
185
+ * switch transactions to build the NEXT prompt before mutating the agent
186
+ * (design §1.5).
187
+ */
188
+ promptSectionFor(parent) {
189
+ if (!this.deps.snapshot)
190
+ return undefined;
191
+ try {
192
+ return buildModelRoutingPrompt(this.deps.snapshot(parent), this.deps.routing);
193
+ }
194
+ catch {
195
+ return undefined;
196
+ }
197
+ }
198
+ }
@@ -0,0 +1,203 @@
1
+ import { type AgentProfile, type SubagentRunResult } from "../profiles.js";
2
+ import { type SubagentThreadRecord, type SubagentThreadSnapshot } from "../subagent-control.js";
3
+ import { SubagentStore } from "../subagent-store.js";
4
+ import { type AgentCategoriesConfig, type ResolvedSubagentRoute } from "../categories.js";
5
+ import type { SubagentRouter } from "./router.js";
6
+ import type { AgentEvent, ContentPart, Message, PermissionMode, Provider, ThinkingLevel, ToolRegistryEntry, ToolUpdate } from "../../types.js";
7
+ import type { AgentSubagentRuntimeConfig } from "../../agent.js";
8
+ import type { HookCombinedResult } from "../../hooks/index.js";
9
+ /** The child agent surface the runtime drives; structurally satisfied by Agent. */
10
+ export type ChildAgentLike = NonNullable<SubagentThreadRecord["agent"]>;
11
+ export interface ChildAgentSpec {
12
+ provider: Provider;
13
+ providerId: string;
14
+ model: string;
15
+ tools: ToolRegistryEntry[];
16
+ thinkingLevel: ThinkingLevel;
17
+ mode: PermissionMode;
18
+ maxTurns?: number;
19
+ budgetSource: {
20
+ runId: string;
21
+ subAgentId?: string;
22
+ };
23
+ systemPrompt: string;
24
+ subAgentId: string;
25
+ /** Cross-restart resume history (design §7); already detached from the record. */
26
+ resumeMessages?: Message[];
27
+ /** Seed the child with the parent's recent context under its own system prompt. */
28
+ forkContext?: boolean;
29
+ }
30
+ export interface SubagentRuntimeParent {
31
+ /** LIVE — /model reassigns these mid-session. */
32
+ readonly provider: Provider;
33
+ readonly providerId: string;
34
+ /** API-facing model id, already stripped of any "provider:" prefix. */
35
+ readonly apiModel: string;
36
+ readonly thinkingLevel: ThinkingLevel;
37
+ readonly memoryPrompt: string | undefined;
38
+ readonly providerFactory: ((route: ResolvedSubagentRoute) => Provider | Promise<Provider>) | undefined;
39
+ /**
40
+ * The UNFILTERED tool map — deferred-but-unlocked entries included. Profile
41
+ * admission and child tool selection both depend on seeing every registered
42
+ * tool; handing them the active (deferred-filtered) list would block
43
+ * profiles that explicitly include an MCP or otherwise deferred tool.
44
+ */
45
+ allTools(): ToolRegistryEntry[];
46
+ createChild(spec: ChildAgentSpec): ChildAgentLike;
47
+ runExternalHook(input: {
48
+ eventName: string;
49
+ cwd: string;
50
+ runId?: string;
51
+ target?: string;
52
+ payload?: Record<string, unknown>;
53
+ }, abortSignal?: AbortSignal): Promise<{
54
+ result: HookCombinedResult;
55
+ events: AgentEvent[];
56
+ }>;
57
+ }
58
+ export interface SubagentRuntimeDeps {
59
+ parent: SubagentRuntimeParent;
60
+ router: SubagentRouter;
61
+ categories: AgentCategoriesConfig;
62
+ config: AgentSubagentRuntimeConfig;
63
+ /**
64
+ * Initial persist directory. Session switches repoint it through
65
+ * `repointPersistDir` (the TUI reuses one Agent across switches).
66
+ */
67
+ persistDir?: string;
68
+ }
69
+ export declare class SubagentRuntime {
70
+ private readonly parent;
71
+ private readonly router;
72
+ private readonly categories;
73
+ private readonly subagentsConfig;
74
+ /** Public so Agent can expose the same private-field access tests rely on. */
75
+ readonly store: SubagentStore;
76
+ private readonly scheduler;
77
+ private readonly childRunner;
78
+ private readonly resultIntegrator;
79
+ private pendingUpdates;
80
+ private readonly updateWakers;
81
+ constructor(deps: SubagentRuntimeDeps);
82
+ /** True when a background child has queued an update the loop should drain. */
83
+ hasPendingUpdates(): boolean;
84
+ /** Follows a session switch: evicts the old session's final children and loads the new one's. */
85
+ repointPersistDir(persistDir: string | undefined): void;
86
+ /**
87
+ * Inspects and cleans up a child's worktree at ANY terminal outcome:
88
+ * unchanged → removed; changed → kept for the parent to review, with a
89
+ * diff stat in the handoff (§8). Called from ChildRunner's onFinal and
90
+ * from all three scheduler-terminal callbacks — the scheduler paths used
91
+ * to skip reclamation entirely, leaking the directory and its git
92
+ * registration whenever a write child exhausted its retries
93
+ * (known-defects #1).
94
+ *
95
+ * When the directory was removed (= the child left no changes, so there
96
+ * is no file state to lose), the record's worktree reference is cleared:
97
+ * a kept reference would send a later resume into a deleted directory —
98
+ * clearing it makes the resume path rebuild a fresh worktree instead
99
+ * (see ChildRunner's reuse guard).
100
+ */
101
+ private reclaimWorktree;
102
+ /** Registers a waker so a blocked tool-execution loop learns about updates. */
103
+ subscribe(wake: () => void): () => void;
104
+ /**
105
+ * Completed background children whose summaries should reach parent context
106
+ * before the next inference turn (design §5). Pulled, not injected.
107
+ */
108
+ drainIngestionNotices(): string[];
109
+ runSubAgent(input: string | ContentPart[], cwd: string, options: {
110
+ profile: AgentProfile;
111
+ runId: string;
112
+ subAgentId: string;
113
+ parentToolCallId: string;
114
+ category?: string;
115
+ route?: ResolvedSubagentRoute;
116
+ approval?: "fail" | "disabled";
117
+ emitUpdate?: (update: ToolUpdate) => void;
118
+ description?: string;
119
+ abortSignal?: AbortSignal;
120
+ nickname?: string;
121
+ forkContext?: boolean;
122
+ }): Promise<SubagentRunResult>;
123
+ spawnSubAgent(input: string | ContentPart[], cwd: string, options: {
124
+ profile: AgentProfile;
125
+ parentToolCallId: string;
126
+ category?: string;
127
+ model?: string;
128
+ effort?: ThinkingLevel;
129
+ route?: ResolvedSubagentRoute;
130
+ approval?: "fail" | "disabled";
131
+ description?: string;
132
+ abortSignal?: AbortSignal;
133
+ forkContext?: boolean;
134
+ }): Promise<SubagentThreadSnapshot>;
135
+ waitSubAgents(options?: {
136
+ agentIds?: string[];
137
+ timeoutMs?: number;
138
+ }): Promise<SubagentThreadSnapshot[]>;
139
+ sendSubAgentInput(agentId: string, input: string | ContentPart[], cwd: string, options?: {
140
+ interrupt?: boolean;
141
+ parentToolCallId?: string;
142
+ abortSignal?: AbortSignal;
143
+ }): Promise<SubagentThreadSnapshot>;
144
+ closeSubAgent(agentId: string): Promise<SubagentThreadSnapshot>;
145
+ /**
146
+ * Live (non-final) children only — the delegation-nudge gate
147
+ * (large-task-delegation design §2). listSubAgents() is a grows-only
148
+ * session history (finished + resumed children stay forever), so it must
149
+ * never be used to answer "am I currently delegating?".
150
+ */
151
+ activeSubAgentCount(): number;
152
+ listSubAgents(): SubagentThreadSnapshot[];
153
+ executeWorkflow(cwd: string, options: {
154
+ script: string;
155
+ args?: unknown;
156
+ parentToolCallId: string;
157
+ abortSignal?: AbortSignal;
158
+ directEmit?: (update: ToolUpdate) => void;
159
+ queueUpdates?: boolean;
160
+ ensureProfileTrusted?: (profile: AgentProfile) => Promise<{
161
+ content: string | unknown;
162
+ } | undefined>;
163
+ }): Promise<{
164
+ result: {
165
+ ok: true;
166
+ value: unknown;
167
+ } | {
168
+ ok: false;
169
+ error: string;
170
+ };
171
+ agentCount: number;
172
+ logs: string[];
173
+ snapshots: SubagentThreadSnapshot[];
174
+ }>;
175
+ markSubagentDelivered(agentId: string): void;
176
+ private snapshotSubagent;
177
+ /** Returns the blocking diagnostic message when the profile cannot run, else undefined. */
178
+ private admitSubagentProfile;
179
+ /**
180
+ * Background children (queueUpdates) get their results ingested before the
181
+ * parent's next inference turn (design §5); foreground children (team,
182
+ * legacy task) deliver through their tool result instead.
183
+ */
184
+ private maybeEnqueueIngestion;
185
+ private finalizeSubagentBlocked;
186
+ private dispatchSubagentRun;
187
+ private emitSubagentLifecycle;
188
+ /** Lets a blocked tool-execution loop drain freshly queued subagent updates. */
189
+ private wakeSubagentUpdateWaiters;
190
+ private runSubagentLifecycleHookFor;
191
+ private createSubagentThreadRecord;
192
+ private runSubagentThread;
193
+ private createSubAgentInstance;
194
+ private resolveProviderForRoute;
195
+ private buildSubagentUpdate;
196
+ private queueSubagentUpdate;
197
+ /** Queued background-child updates for Agent.run() to emit. */
198
+ drainToolUpdates(): AgentEvent[];
199
+ private activeSubagentNicknames;
200
+ private resolveSubagentTargets;
201
+ }
202
+ export declare function isFinalSubagentStatus(status: SubagentThreadRecord["status"]): boolean;
203
+ export declare function normalizeWaitTimeout(value: number | undefined): number;