@bubblebrain-ai/bubble 0.0.35 → 0.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -7
- package/dist/agent/categories.d.ts +66 -2
- package/dist/agent/categories.js +120 -19
- package/dist/agent/discovery-barrier.js +1 -1
- package/dist/agent/profiles.d.ts +1 -0
- package/dist/agent/profiles.js +25 -11
- package/dist/agent/routing-catalog.d.ts +113 -0
- package/dist/agent/routing-catalog.js +240 -0
- package/dist/agent/workflow/control.d.ts +14 -0
- package/dist/agent/workflow/control.js +30 -9
- package/dist/agent.d.ts +75 -5
- package/dist/agent.js +313 -61
- package/dist/approval/controller.js +28 -0
- package/dist/approval/tool-helper.js +2 -0
- package/dist/approval/types.d.ts +21 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +2 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +4 -0
- package/dist/context/compact-llm.d.ts +2 -2
- package/dist/external-runtime/grok-acp.d.ts +56 -0
- package/dist/external-runtime/grok-acp.js +386 -0
- package/dist/external-runtime/grok-binary.d.ts +25 -0
- package/dist/external-runtime/grok-binary.js +108 -0
- package/dist/external-runtime/grok-errors.d.ts +7 -0
- package/dist/external-runtime/grok-errors.js +15 -0
- package/dist/external-runtime/grok-input-policy.d.ts +58 -0
- package/dist/external-runtime/grok-input-policy.js +52 -0
- package/dist/external-runtime/grok-ndjson.d.ts +14 -0
- package/dist/external-runtime/grok-ndjson.js +193 -0
- package/dist/external-runtime/grok-network.d.ts +15 -0
- package/dist/external-runtime/grok-network.js +81 -0
- package/dist/external-runtime/grok-preflight.d.ts +28 -0
- package/dist/external-runtime/grok-preflight.js +194 -0
- package/dist/external-runtime/grok-process.d.ts +26 -0
- package/dist/external-runtime/grok-process.js +184 -0
- package/dist/external-runtime/grok-profile.d.ts +32 -0
- package/dist/external-runtime/grok-profile.js +419 -0
- package/dist/external-runtime/grok-provider.d.ts +31 -0
- package/dist/external-runtime/grok-provider.js +41 -0
- package/dist/external-runtime/grok-runtime.d.ts +91 -0
- package/dist/external-runtime/grok-runtime.js +882 -0
- package/dist/external-runtime/index.d.ts +13 -0
- package/dist/external-runtime/index.js +13 -0
- package/dist/external-runtime/session-policy.d.ts +11 -0
- package/dist/external-runtime/session-policy.js +30 -0
- package/dist/external-runtime/types.d.ts +65 -0
- package/dist/external-runtime/types.js +1 -0
- package/dist/feishu/agent-host/approval-card.js +21 -0
- package/dist/feishu/agent-host/run-driver.js +19 -3
- package/dist/feishu/serve.js +1 -0
- package/dist/goal/tools.d.ts +6 -3
- package/dist/goal/tools.js +7 -19
- package/dist/main.js +105 -27
- package/dist/memory/db.d.ts +2 -0
- package/dist/memory/db.js +10 -0
- package/dist/memory/index.d.ts +1 -0
- package/dist/memory/index.js +1 -0
- package/dist/memory/phase1.js +14 -2
- package/dist/memory/phase2.js +21 -2
- package/dist/memory/prompts.js +1 -1
- package/dist/memory/session-policy.d.ts +25 -0
- package/dist/memory/session-policy.js +55 -0
- package/dist/memory/storage.d.ts +1 -0
- package/dist/memory/storage.js +13 -0
- package/dist/model-catalog.d.ts +18 -0
- package/dist/model-catalog.js +87 -41
- package/dist/model-config.d.ts +3 -1
- package/dist/model-config.js +11 -0
- package/dist/oauth/grok.d.ts +25 -0
- package/dist/oauth/grok.js +218 -0
- package/dist/oauth/index.d.ts +1 -0
- package/dist/oauth/index.js +1 -0
- package/dist/oauth/storage.d.ts +9 -0
- package/dist/oauth/storage.js +25 -0
- package/dist/orchestrator/default-hooks.js +17 -7
- package/dist/prompt/compose.d.ts +3 -0
- package/dist/prompt/compose.js +3 -0
- package/dist/prompt/delegation.js +22 -3
- package/dist/prompt/environment.js +0 -4
- package/dist/prompt/reminders.d.ts +0 -1
- package/dist/prompt/reminders.js +1 -11
- package/dist/prompt/routing.d.ts +14 -0
- package/dist/prompt/routing.js +71 -0
- package/dist/prompt/runtime.js +1 -1
- package/dist/prompt/task-reminders.d.ts +19 -0
- package/dist/prompt/task-reminders.js +49 -0
- package/dist/provider-ai-sdk.js +2 -2
- package/dist/provider-grok.d.ts +26 -0
- package/dist/provider-grok.js +59 -0
- package/dist/provider-openai-codex.d.ts +17 -1
- package/dist/provider-openai-codex.js +101 -29
- package/dist/provider-registry.d.ts +72 -0
- package/dist/provider-registry.js +336 -59
- package/dist/provider-transform.d.ts +1 -1
- package/dist/provider-transform.js +28 -5
- package/dist/provider.d.ts +3 -0
- package/dist/provider.js +14 -0
- package/dist/session-types.d.ts +19 -1
- package/dist/session.d.ts +1 -0
- package/dist/session.js +4 -0
- package/dist/slash-commands/commands.js +224 -14
- package/dist/slash-commands/types.d.ts +14 -1
- package/dist/system-prompt.d.ts +2 -0
- package/dist/tools/agent-lifecycle.js +9 -11
- package/dist/tools/bash.js +1 -1
- package/dist/tools/child-tools.js +2 -0
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +7 -5
- package/dist/tools/lsp.js +1 -0
- package/dist/tools/memory.d.ts +7 -2
- package/dist/tools/memory.js +28 -43
- package/dist/tools/server.js +4 -0
- package/dist/tools/tool-search.d.ts +1 -1
- package/dist/tools/tool-search.js +23 -3
- package/dist/tui/model-switch.d.ts +7 -0
- package/dist/tui/model-switch.js +15 -5
- package/dist/tui-ink/app.d.ts +3 -1
- package/dist/tui-ink/app.js +649 -233
- package/dist/tui-ink/approval/approval-dialog.js +36 -0
- package/dist/tui-ink/composer-buffer.d.ts +55 -0
- package/dist/tui-ink/composer-buffer.js +317 -0
- package/dist/tui-ink/footer.d.ts +1 -0
- package/dist/tui-ink/footer.js +7 -2
- package/dist/tui-ink/input-box.d.ts +35 -1
- package/dist/tui-ink/input-box.js +215 -122
- package/dist/tui-ink/message-list.js +5 -5
- package/dist/tui-ink/model-picker.d.ts +61 -4
- package/dist/tui-ink/model-picker.js +486 -133
- package/dist/tui-ink/paste-operation-tracker.d.ts +13 -0
- package/dist/tui-ink/paste-operation-tracker.js +25 -0
- package/dist/tui-ink/run.d.ts +3 -0
- package/dist/tui-ink/run.js +1 -1
- package/dist/tui-ink/subagent-view.d.ts +31 -0
- package/dist/tui-ink/subagent-view.js +82 -1
- package/dist/types.d.ts +5 -5
- package/dist/types.js +1 -1
- package/dist/variant/thinking-level.d.ts +2 -2
- package/dist/variant/thinking-level.js +2 -1
- package/dist/variant/variant-resolver.d.ts +9 -0
- package/dist/variant/variant-resolver.js +17 -0
- package/package.json +2 -1
- package/dist/tools/task.d.ts +0 -2
- package/dist/tools/task.js +0 -58
package/dist/agent.js
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { compactMessages } from "./context/compact.js";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { getBubbleHome } from "./bubble-home.js";
|
|
7
10
|
import { compactMessagesWithLLM } from "./context/compact-llm.js";
|
|
8
11
|
import { estimateContextTokens, getContextBudget } from "./context/budget.js";
|
|
9
12
|
import { buildContextUsageSnapshot } from "./context/usage.js";
|
|
@@ -17,12 +20,15 @@ import { HookBus } from "./orchestrator/hooks.js";
|
|
|
17
20
|
import { normalizeHookInput, truncateHookText, } from "./hooks/index.js";
|
|
18
21
|
import { createDefaultHooks } from "./orchestrator/default-hooks.js";
|
|
19
22
|
import { mergeAgentCategories, parseThinkingLevel, resolveModelRoute, resolveSubagentRoute } from "./agent/categories.js";
|
|
23
|
+
import { DEFAULT_AGENT_ROUTING, nearModelMatches, sanitizeAgentRouting, tierContextFromSnapshot } from "./agent/routing-catalog.js";
|
|
24
|
+
import { buildModelRoutingPrompt } from "./prompt/routing.js";
|
|
25
|
+
import { getBuiltinModel } from "./model-catalog.js";
|
|
26
|
+
import { getAvailableThinkingLevels, getDefaultThinkingLevel, normalizeInheritedThinkingLevel, normalizeThinkingLevel } from "./variant/variant-resolver.js";
|
|
20
27
|
import { appendOutputSchemaInstructions, buildSchemaCorrectionPrompt, validateStructuredSummary } from "./agent/structured-output.js";
|
|
21
28
|
import { runWorkflow, WorkflowConcurrencyGate } from "./agent/workflow/runtime.js";
|
|
22
|
-
import { buildWorkflowDeliveryNotice } from "./agent/workflow/control.js";
|
|
23
|
-
import { getSubtaskPolicy } from "./agent/subtask-policy.js";
|
|
29
|
+
import { buildWorkflowDeliveryNotice, renderWorkflowResultValue } from "./agent/workflow/control.js";
|
|
24
30
|
import { composeAbortSignals } from "./agent/budget-ledger.js";
|
|
25
|
-
import { assignAgentNickname,
|
|
31
|
+
import { assignAgentNickname, discoverAgentProfiles, findAgentProfile, validateAgentProfileTools } from "./agent/profiles.js";
|
|
26
32
|
import { snapshotSubagentThread, subagentResultFromThread } from "./agent/subagent-control.js";
|
|
27
33
|
import { SubagentStore } from "./agent/subagent-store.js";
|
|
28
34
|
import { SubagentScheduler } from "./agent/subagent-scheduler.js";
|
|
@@ -86,6 +92,10 @@ function agentEventFromHookProgress(event) {
|
|
|
86
92
|
error: event.error ?? "Hook failed.",
|
|
87
93
|
};
|
|
88
94
|
}
|
|
95
|
+
/** Detector threshold N (design §6): 1–2 strong-model children are often a
|
|
96
|
+
* deliberate deep side-investigation; at 3+ uniform defaults the odds that
|
|
97
|
+
* all need frontier capability drop sharply. Revisit against live traces. */
|
|
98
|
+
const ROUTING_REMINDER_THRESHOLD = 3;
|
|
89
99
|
export class Agent {
|
|
90
100
|
messages = [];
|
|
91
101
|
provider;
|
|
@@ -116,6 +126,13 @@ export class Agent {
|
|
|
116
126
|
memoryPrompt;
|
|
117
127
|
fileStateTracker;
|
|
118
128
|
agentCategories;
|
|
129
|
+
agentRouting;
|
|
130
|
+
routingSnapshotAccessor;
|
|
131
|
+
routableModelIndex;
|
|
132
|
+
/** Detector state (design §6): once per session, absolute defaulted count. */
|
|
133
|
+
routingReminderFired = false;
|
|
134
|
+
defaultedRoutingStreak = 0;
|
|
135
|
+
pendingRoutingReminder;
|
|
119
136
|
providerFactory;
|
|
120
137
|
subagentStore;
|
|
121
138
|
subagentScheduler;
|
|
@@ -128,6 +145,14 @@ export class Agent {
|
|
|
128
145
|
subagentsConfig;
|
|
129
146
|
rateLimitPolicy;
|
|
130
147
|
pendingSubagentUpdates = [];
|
|
148
|
+
/**
|
|
149
|
+
* Wakers for tool-execution update loops currently awaiting updates. A
|
|
150
|
+
* blocking lifecycle tool (wait_workflow / wait_agent) produces no updates of
|
|
151
|
+
* its own, so without this wake the background children's queued lifecycle
|
|
152
|
+
* updates sit undrained until the tool settles — the UI would only show
|
|
153
|
+
* subagent traces after the whole team finished.
|
|
154
|
+
*/
|
|
155
|
+
subagentUpdateWakers = new Set();
|
|
131
156
|
lastInputTokens = null;
|
|
132
157
|
lastAnchorMessageCount = null;
|
|
133
158
|
constructor(options) {
|
|
@@ -155,6 +180,11 @@ export class Agent {
|
|
|
155
180
|
this.memoryPrompt = options.memoryPrompt;
|
|
156
181
|
this.fileStateTracker = options.fileStateTracker;
|
|
157
182
|
this.agentCategories = options.agentCategories ?? {};
|
|
183
|
+
this.agentRouting = options.agentRouting
|
|
184
|
+
? sanitizeAgentRouting(options.agentRouting)
|
|
185
|
+
: { ...DEFAULT_AGENT_ROUTING };
|
|
186
|
+
this.routingSnapshotAccessor = options.routingSnapshot;
|
|
187
|
+
this.routableModelIndex = options.routableModels;
|
|
158
188
|
this.providerFactory = options.providerFactory;
|
|
159
189
|
this.subagentsConfig = options.subagents ?? {};
|
|
160
190
|
this.rateLimitPolicy = options.rateLimitPolicy;
|
|
@@ -214,6 +244,16 @@ export class Agent {
|
|
|
214
244
|
}
|
|
215
245
|
// Advertise any deferred tools so the model knows they exist and how to
|
|
216
246
|
// reach them. Keeps the per-turn tool list small; schemas load on demand.
|
|
247
|
+
this.injectDeferredToolsReminder();
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Re-inject the deferred-tools advertisement. Hosts that reassign
|
|
251
|
+
* agent.messages (session resume, Feishu conversation rebuild) drop the
|
|
252
|
+
* constructor-injected meta reminder — without re-injection the model has
|
|
253
|
+
* no way to discover deferred tools, since they are also filtered out of
|
|
254
|
+
* the prompt's tool list.
|
|
255
|
+
*/
|
|
256
|
+
injectDeferredToolsReminder() {
|
|
217
257
|
const deferredNames = [...this.tools.values()]
|
|
218
258
|
.filter((t) => t.deferred)
|
|
219
259
|
.map((t) => t.name);
|
|
@@ -268,7 +308,34 @@ export class Agent {
|
|
|
268
308
|
return [...this.tools.values()].filter((t) => t.deferred);
|
|
269
309
|
}
|
|
270
310
|
getSystemPromptToolOptions() {
|
|
271
|
-
return
|
|
311
|
+
return {
|
|
312
|
+
...buildToolPromptOptions(this.getActiveToolEntries()),
|
|
313
|
+
// Rendered through the live accessor (design §1.5), so every host-
|
|
314
|
+
// triggered prompt rebuild picks up the current catalog.
|
|
315
|
+
modelRoutingPrompt: this.buildModelRoutingPromptSection(),
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
/** Current routing menu (design §4); undefined when no accessor is wired. */
|
|
319
|
+
buildModelRoutingPromptSection() {
|
|
320
|
+
const snapshot = this.currentRoutingSnapshot();
|
|
321
|
+
if (!snapshot)
|
|
322
|
+
return undefined;
|
|
323
|
+
return buildModelRoutingPrompt(snapshot, this.agentRouting);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Routing menu rendered for a prospective parent route — used by model-
|
|
327
|
+
* switch transactions to build the NEXT prompt before mutating the agent
|
|
328
|
+
* (design §1.5).
|
|
329
|
+
*/
|
|
330
|
+
renderModelRoutingPromptFor(parent) {
|
|
331
|
+
if (!this.routingSnapshotAccessor)
|
|
332
|
+
return undefined;
|
|
333
|
+
try {
|
|
334
|
+
return buildModelRoutingPrompt(this.routingSnapshotAccessor(parent), this.agentRouting);
|
|
335
|
+
}
|
|
336
|
+
catch {
|
|
337
|
+
return undefined;
|
|
338
|
+
}
|
|
272
339
|
}
|
|
273
340
|
getContextUsageSnapshot() {
|
|
274
341
|
return buildContextUsageSnapshot({
|
|
@@ -698,6 +765,18 @@ export class Agent {
|
|
|
698
765
|
appendProviderContentBlock(assistantMsg, chunk.provider, chunk.block);
|
|
699
766
|
break;
|
|
700
767
|
case "tool_call":
|
|
768
|
+
// `toolChoice: "none"` is a governance boundary, not merely a
|
|
769
|
+
// provider hint. Ignore any provider-side violation so a forced
|
|
770
|
+
// text-only turn can never reach the tool execution path.
|
|
771
|
+
if (textOnly) {
|
|
772
|
+
traceEvent("text_only_tool_call_ignored", {
|
|
773
|
+
id: chunk.id,
|
|
774
|
+
name: chunk.name,
|
|
775
|
+
isStart: chunk.isStart,
|
|
776
|
+
isEnd: chunk.isEnd,
|
|
777
|
+
}, traceContext);
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
701
780
|
if (discoveryBarrier?.isEnabled()
|
|
702
781
|
&& (bufferedStreamingToolCallIds.has(chunk.id) || discoveryBarrier.shouldBufferStreamingToolCall(chunk.name))) {
|
|
703
782
|
bufferedStreamingToolCallIds.add(chunk.id);
|
|
@@ -1074,20 +1153,31 @@ export class Agent {
|
|
|
1074
1153
|
settled = true;
|
|
1075
1154
|
updateQueue.wake();
|
|
1076
1155
|
});
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1156
|
+
this.subagentUpdateWakers.add(updateQueue.wake);
|
|
1157
|
+
try {
|
|
1158
|
+
while (!settled || updateQueue.hasItems() || this.pendingSubagentUpdates.length > 0) {
|
|
1159
|
+
for (const update of updateQueue.drain()) {
|
|
1160
|
+
yield emit({ type: "tool_update", id: tc.id, name: tc.name, update });
|
|
1161
|
+
}
|
|
1162
|
+
for (const update of this.drainSubagentToolUpdates())
|
|
1163
|
+
yield emit(update);
|
|
1164
|
+
// A wake() that fires while this generator is suspended at a
|
|
1165
|
+
// yield above finds no parked waiter and is lost (the queue has
|
|
1166
|
+
// no wake latch), so re-check the subagent queue synchronously
|
|
1167
|
+
// before parking — otherwise an update pushed during the yield
|
|
1168
|
+
// stalls until the next unrelated wake or the tool settles.
|
|
1169
|
+
if (!settled && this.pendingSubagentUpdates.length === 0) {
|
|
1170
|
+
const waitStatus = await updateQueue.wait(abortSignal);
|
|
1171
|
+
if (waitStatus === "aborted" && !settled) {
|
|
1172
|
+
cancelledByAbort = true;
|
|
1173
|
+
break;
|
|
1174
|
+
}
|
|
1088
1175
|
}
|
|
1089
1176
|
}
|
|
1090
1177
|
}
|
|
1178
|
+
finally {
|
|
1179
|
+
this.subagentUpdateWakers.delete(updateQueue.wake);
|
|
1180
|
+
}
|
|
1091
1181
|
if (cancelledByAbort) {
|
|
1092
1182
|
result = cancelledToolResult(tc.name);
|
|
1093
1183
|
}
|
|
@@ -1397,42 +1487,6 @@ export class Agent {
|
|
|
1397
1487
|
// re-injected as a `Previous conversation summary` system message.
|
|
1398
1488
|
return sanitizeInternalReminderBlocks(full).trim();
|
|
1399
1489
|
}
|
|
1400
|
-
async runSubtask(input, cwd, options) {
|
|
1401
|
-
const subtaskType = options?.subtaskType;
|
|
1402
|
-
const profile = builtinAgentProfiles().find((item) => item.subtaskType === (subtaskType ?? "general_readonly"))
|
|
1403
|
-
?? builtinAgentProfiles().find((item) => item.subtaskType === "general_readonly");
|
|
1404
|
-
const run = await this.runSubAgent(input, cwd, {
|
|
1405
|
-
profile,
|
|
1406
|
-
runId: randomUUID(),
|
|
1407
|
-
subAgentId: randomUUID(),
|
|
1408
|
-
parentToolCallId: "task",
|
|
1409
|
-
route: this.resolveRouteForSubagent(profile, undefined),
|
|
1410
|
-
description: options?.description,
|
|
1411
|
-
});
|
|
1412
|
-
const lines = [
|
|
1413
|
-
"Note: task is deprecated. Use spawn_agent with a named profile instead.",
|
|
1414
|
-
`Subtask type: ${profile.subtaskType ?? "general_readonly"}`,
|
|
1415
|
-
];
|
|
1416
|
-
if (options?.description) {
|
|
1417
|
-
lines.push(`Subtask description: ${options.description}`);
|
|
1418
|
-
}
|
|
1419
|
-
if (run.summary) {
|
|
1420
|
-
lines.push("", "Subtask summary:", run.summary);
|
|
1421
|
-
}
|
|
1422
|
-
if (run.toolNotes.length > 0) {
|
|
1423
|
-
lines.push("", "Subtask tools:", ...run.toolNotes.slice(0, 8).map((note) => `- ${note}`));
|
|
1424
|
-
}
|
|
1425
|
-
return {
|
|
1426
|
-
content: lines.join("\n"),
|
|
1427
|
-
status: getSubtaskPolicy(subtaskType).resultStatus,
|
|
1428
|
-
isError: run.status !== "completed",
|
|
1429
|
-
metadata: {
|
|
1430
|
-
kind: "subagent",
|
|
1431
|
-
reason: `Subtask (${profile.subtaskType ?? "general_readonly"}) investigation completed.`,
|
|
1432
|
-
subagents: [run],
|
|
1433
|
-
},
|
|
1434
|
-
};
|
|
1435
|
-
}
|
|
1436
1490
|
async runSubAgent(input, cwd, options) {
|
|
1437
1491
|
const record = this.createSubagentThreadRecord({
|
|
1438
1492
|
profile: options.profile,
|
|
@@ -1461,13 +1515,21 @@ export class Agent {
|
|
|
1461
1515
|
return subagentResultFromThread(record);
|
|
1462
1516
|
}
|
|
1463
1517
|
async spawnSubAgent(input, cwd, options) {
|
|
1518
|
+
const route = options.route
|
|
1519
|
+
?? this.resolveRouteForSubagent(options.profile, options.category, { model: options.model, effort: options.effort });
|
|
1520
|
+
// Early validation (design §7): throws reach the model as a tool error it
|
|
1521
|
+
// can correct this turn, instead of a late provider-factory failure.
|
|
1522
|
+
const routeNote = this.validateRouteForDispatch(route);
|
|
1523
|
+
this.noteRoutingDispatch(route);
|
|
1464
1524
|
const record = this.createSubagentThreadRecord({
|
|
1465
1525
|
profile: options.profile,
|
|
1466
1526
|
task: typeof input === "string" ? input : "(multimodal task)",
|
|
1467
1527
|
parentToolCallId: options.parentToolCallId,
|
|
1468
1528
|
parentToolName: "spawn_agent",
|
|
1469
|
-
route
|
|
1529
|
+
route,
|
|
1470
1530
|
});
|
|
1531
|
+
if (routeNote)
|
|
1532
|
+
record.toolNotes.push(routeNote);
|
|
1471
1533
|
this.subagentStore.set(record);
|
|
1472
1534
|
const approval = options.approval ?? record.profile.approval;
|
|
1473
1535
|
// Admission validation runs before queueing (design §4.2): a request that
|
|
@@ -1638,6 +1700,8 @@ export class Agent {
|
|
|
1638
1700
|
record.logs = out.logs;
|
|
1639
1701
|
record.result = out.result;
|
|
1640
1702
|
record.status = out.result.ok ? "completed" : (abortController.signal.aborted ? "cancelled" : "failed");
|
|
1703
|
+
if (out.result.ok)
|
|
1704
|
+
record.resultPath = persistWorkflowResult(runId, out.result.value);
|
|
1641
1705
|
}, (error) => {
|
|
1642
1706
|
record.result = { ok: false, error: error?.message || String(error) };
|
|
1643
1707
|
record.status = "failed";
|
|
@@ -1687,6 +1751,7 @@ export class Agent {
|
|
|
1687
1751
|
status: record.status,
|
|
1688
1752
|
agentCount: record.agentCount,
|
|
1689
1753
|
result: record.result,
|
|
1754
|
+
resultPath: record.resultPath,
|
|
1690
1755
|
logs: record.logs,
|
|
1691
1756
|
snapshots: record.snapshots,
|
|
1692
1757
|
};
|
|
@@ -1732,11 +1797,16 @@ export class Agent {
|
|
|
1732
1797
|
tools: { ...baseProfile.tools, exclude: [...(baseProfile.tools.exclude ?? []), "web_fetch", "web_search"] },
|
|
1733
1798
|
};
|
|
1734
1799
|
let route;
|
|
1800
|
+
let routeNote;
|
|
1735
1801
|
try {
|
|
1736
1802
|
route = this.resolveRouteForSubagent(profile, spec.opts.category, {
|
|
1737
1803
|
model: spec.opts.model,
|
|
1738
1804
|
effort: parseThinkingLevel(spec.opts.effort),
|
|
1739
1805
|
});
|
|
1806
|
+
// Dispatch-time validation + defaulted-fan-out accounting (§6–7):
|
|
1807
|
+
// resolved routes, never script source text.
|
|
1808
|
+
routeNote = this.validateRouteForDispatch(route);
|
|
1809
|
+
this.noteRoutingDispatch(route);
|
|
1740
1810
|
}
|
|
1741
1811
|
catch (error) {
|
|
1742
1812
|
return { ok: false, error: error?.message || String(error) };
|
|
@@ -1753,6 +1823,8 @@ export class Agent {
|
|
|
1753
1823
|
workflowInternal: true,
|
|
1754
1824
|
});
|
|
1755
1825
|
record.expectsStructuredOutput = spec.opts.schema !== undefined;
|
|
1826
|
+
if (routeNote)
|
|
1827
|
+
record.toolNotes.push(routeNote);
|
|
1756
1828
|
const memberLabel = typeof spec.opts.label === "string" ? spec.opts.label.trim().slice(0, 40) : "";
|
|
1757
1829
|
if (memberLabel)
|
|
1758
1830
|
record.nickname = memberLabel;
|
|
@@ -1938,8 +2010,14 @@ export class Agent {
|
|
|
1938
2010
|
options.directEmit?.(update);
|
|
1939
2011
|
if (options.queueUpdates) {
|
|
1940
2012
|
this.pendingSubagentUpdates.push({ id: record.parentToolCallId, name: record.parentToolName, update });
|
|
2013
|
+
this.wakeSubagentUpdateWaiters();
|
|
1941
2014
|
}
|
|
1942
2015
|
}
|
|
2016
|
+
/** Lets a blocked tool-execution loop drain freshly queued subagent updates. */
|
|
2017
|
+
wakeSubagentUpdateWaiters() {
|
|
2018
|
+
for (const wake of this.subagentUpdateWakers)
|
|
2019
|
+
wake();
|
|
2020
|
+
}
|
|
1943
2021
|
async runSubagentLifecycleHookFor(record, cwd, eventName, status, error, abortSignal) {
|
|
1944
2022
|
try {
|
|
1945
2023
|
await this.runExternalHook({
|
|
@@ -1972,30 +2050,186 @@ export class Agent {
|
|
|
1972
2050
|
model: this.apiModel,
|
|
1973
2051
|
thinkingLevel: this.thinkingLevel,
|
|
1974
2052
|
};
|
|
1975
|
-
const
|
|
1976
|
-
|
|
1977
|
-
}, this.agentCategories);
|
|
2053
|
+
const snapshot = this.currentRoutingSnapshot();
|
|
2054
|
+
const resolved = resolveSubagentRoute(category ?? profile.category, { ...parentRoute }, this.agentCategories, snapshot ? tierContextFromSnapshot(snapshot, this.agentRouting) : undefined);
|
|
1978
2055
|
if ("error" in resolved) {
|
|
1979
2056
|
throw new Error(resolved.error);
|
|
1980
2057
|
}
|
|
1981
2058
|
let route = resolved.route;
|
|
2059
|
+
// modelSource is assigned while applying the chain (design §3.4): an
|
|
2060
|
+
// explicit layer naming the parent's own model is indistinguishable from
|
|
2061
|
+
// inherit by final-value comparison, yet carries different authority.
|
|
2062
|
+
let modelSource = route.categoryModelSource ?? "inherit";
|
|
1982
2063
|
if (profile.model && profile.model !== "inherit") {
|
|
1983
2064
|
const model = resolveModelRoute(profile.model, parentRoute.providerId);
|
|
1984
2065
|
if (model.model !== "inherit") {
|
|
1985
2066
|
route = { ...route, providerId: model.providerId, model: model.model, inherited: false };
|
|
2067
|
+
modelSource = "profile";
|
|
1986
2068
|
}
|
|
1987
2069
|
}
|
|
1988
|
-
// Call-site override beats profile and category.
|
|
2070
|
+
// Call-site override beats profile and category. Bare names resolve
|
|
2071
|
+
// against the PARENT provider per the tool contract (design §3.5) — not
|
|
2072
|
+
// against a provider that category/profile may already have switched.
|
|
1989
2073
|
if (override?.model) {
|
|
1990
|
-
const model = resolveModelRoute(override.model,
|
|
2074
|
+
const model = resolveModelRoute(override.model, parentRoute.providerId);
|
|
1991
2075
|
if (model.model !== "inherit") {
|
|
1992
2076
|
route = { ...route, providerId: model.providerId, model: model.model, inherited: false };
|
|
2077
|
+
modelSource = "callsite";
|
|
1993
2078
|
}
|
|
1994
2079
|
}
|
|
2080
|
+
const supportedLevels = getAvailableThinkingLevels(route.providerId, route.model);
|
|
2081
|
+
const modelMetadata = getBuiltinModel(route.providerId, route.model);
|
|
2082
|
+
const hasTrustedEffortMetadata = !!modelMetadata
|
|
2083
|
+
&& modelMetadata.reasoningLevels.some((level) => level !== "off");
|
|
1995
2084
|
if (override?.effort) {
|
|
1996
|
-
|
|
2085
|
+
// A call-site effort is explicit user/model intent: preserve the existing
|
|
2086
|
+
// value for legacy/unknown models, and downward-clamp only when the
|
|
2087
|
+
// catalog declares real effort capabilities (for example Luna ultra -> max).
|
|
2088
|
+
route = {
|
|
2089
|
+
...route,
|
|
2090
|
+
thinkingLevel: hasTrustedEffortMetadata
|
|
2091
|
+
? normalizeThinkingLevel(override.effort, supportedLevels)
|
|
2092
|
+
: override.effort,
|
|
2093
|
+
inherited: false,
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
else if (hasTrustedEffortMetadata) {
|
|
2097
|
+
const categoryThinkingLevel = route.category
|
|
2098
|
+
? mergeAgentCategories(this.agentCategories)[route.category]?.thinkingLevel
|
|
2099
|
+
: undefined;
|
|
2100
|
+
// A category's thinkingLevel is calibrated for the model the CATEGORY
|
|
2101
|
+
// resolved. When a later layer (profile/call-site) replaced the model,
|
|
2102
|
+
// that level is no longer explicit intent for the final model: keep it
|
|
2103
|
+
// only if supported, else use the final model's own default — never
|
|
2104
|
+
// downward-clamp a thinking-default model to "off" (v3.6; live case:
|
|
2105
|
+
// explore's "low" + call-site glm-5.2 [high/max/off] silently landed
|
|
2106
|
+
// on "off").
|
|
2107
|
+
const modelReplacedAfterCategory = modelSource === "profile" || modelSource === "callsite";
|
|
2108
|
+
route = {
|
|
2109
|
+
...route,
|
|
2110
|
+
thinkingLevel: categoryThinkingLevel
|
|
2111
|
+
? (modelReplacedAfterCategory && !supportedLevels.includes(route.thinkingLevel)
|
|
2112
|
+
? getDefaultThinkingLevel(route.providerId, route.model)
|
|
2113
|
+
: normalizeThinkingLevel(route.thinkingLevel, supportedLevels))
|
|
2114
|
+
: normalizeInheritedThinkingLevel(route.providerId, route.model, route.thinkingLevel),
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
return {
|
|
2118
|
+
...route,
|
|
2119
|
+
modelSource,
|
|
2120
|
+
modelInherited: route.providerId === parentRoute.providerId && route.model === parentRoute.model,
|
|
2121
|
+
};
|
|
2122
|
+
}
|
|
2123
|
+
/** Live snapshot for the CURRENT parent route; undefined when no accessor is wired. */
|
|
2124
|
+
currentRoutingSnapshot() {
|
|
2125
|
+
if (!this.routingSnapshotAccessor)
|
|
2126
|
+
return undefined;
|
|
2127
|
+
try {
|
|
2128
|
+
return this.routingSnapshotAccessor({ providerId: this.providerId, model: this.apiModel });
|
|
2129
|
+
}
|
|
2130
|
+
catch {
|
|
2131
|
+
// Catalog data is an enhancement, never a spawn blocker.
|
|
2132
|
+
return undefined;
|
|
1997
2133
|
}
|
|
1998
|
-
|
|
2134
|
+
}
|
|
2135
|
+
/**
|
|
2136
|
+
* Early route validation at dispatch time (design §7). Throws with an
|
|
2137
|
+
* actionable message so the model self-corrects in the same turn. The
|
|
2138
|
+
* cross-provider lock (§7.2.1) is snapshot-independent; catalog checks
|
|
2139
|
+
* degrade silently when no snapshot is available (§7.0).
|
|
2140
|
+
*/
|
|
2141
|
+
validateRouteForDispatch(route) {
|
|
2142
|
+
const snapshot = this.currentRoutingSnapshot();
|
|
2143
|
+
const crossProvider = !!route.providerId && route.providerId !== this.providerId;
|
|
2144
|
+
if (crossProvider) {
|
|
2145
|
+
// §7.2.1 — the lock. Profile/user-category routes are standing user
|
|
2146
|
+
// authorization and always pass; only call-site routes are lockable.
|
|
2147
|
+
if (!this.agentRouting.allowCrossProvider && route.modelSource === "callsite") {
|
|
2148
|
+
throw new Error("Cross-provider routing is disabled in this session's config (agentRouting.allowCrossProvider). "
|
|
2149
|
+
+ "Use a model from the parent provider, or ask the user to unlock cross-provider routing.");
|
|
2150
|
+
}
|
|
2151
|
+
// §7.2.2 — credentials, when a snapshot is available.
|
|
2152
|
+
if (snapshot && !snapshot.runnableProviderIds.includes(route.providerId)) {
|
|
2153
|
+
throw new Error(`Provider "${route.providerId}" is not configured with active credentials. `
|
|
2154
|
+
+ `Available: ${snapshot.runnableProviderIds.join(", ") || "(none)"}.`);
|
|
2155
|
+
}
|
|
2156
|
+
// §7.2.3 amended (v3.6): the provider stays the authority — no hard
|
|
2157
|
+
// catalog rejection — but a near-match against the target provider's
|
|
2158
|
+
// local catalog is positive evidence of a mistyped id (a Grok parent
|
|
2159
|
+
// invented "openai:gpt-5.6" for gpt-5.6-sol), so soft-reject with the
|
|
2160
|
+
// correction and let the model fix it this turn. Genuinely unknown ids
|
|
2161
|
+
// (no near candidates) still pass through with a note.
|
|
2162
|
+
const targetCatalog = this.routableModelIndex?.()
|
|
2163
|
+
.filter((entry) => entry.providerId === route.providerId) ?? [];
|
|
2164
|
+
if (targetCatalog.length > 0 && !targetCatalog.some((entry) => entry.id === route.model)) {
|
|
2165
|
+
const near = nearModelMatches(route.model, targetCatalog, { mode: "truncation" });
|
|
2166
|
+
if (near.length > 0) {
|
|
2167
|
+
throw new Error(`Unknown model "${route.model}" for provider "${route.providerId}". Did you mean: ${near.join(", ")}?`);
|
|
2168
|
+
}
|
|
2169
|
+
return `model ${route.providerId}:${route.model} is not in the local catalog; the provider validates it`;
|
|
2170
|
+
}
|
|
2171
|
+
return `model ${route.providerId}:${route.model} is not locally verifiable; the provider validates it`;
|
|
2172
|
+
}
|
|
2173
|
+
// §7.1 — same-provider unknown model, resolved-provider based (never
|
|
2174
|
+
// input-syntax based; qualified same-provider ids land here too).
|
|
2175
|
+
if (snapshot && route.modelSource === "callsite") {
|
|
2176
|
+
const known = snapshot.models.some((model) => model.id === route.model);
|
|
2177
|
+
if (!known) {
|
|
2178
|
+
if (snapshot.authoritative) {
|
|
2179
|
+
const available = snapshot.models
|
|
2180
|
+
.map((model) => (model.tier ? `${model.id} (${model.tier})` : model.id))
|
|
2181
|
+
.join(", ");
|
|
2182
|
+
throw new Error(`Unknown model "${route.model}" for provider "${this.providerId}". Available: ${available}.`);
|
|
2183
|
+
}
|
|
2184
|
+
const near = nearModelMatches(route.model, snapshot.models.map((model) => ({ providerId: this.providerId, id: model.id, name: model.name })));
|
|
2185
|
+
return near.length > 0
|
|
2186
|
+
? `model id "${route.model}" is unrecognized locally (did you mean: ${near.join(", ")}?); the provider validates it`
|
|
2187
|
+
: `model id "${route.model}" is unrecognized locally; the provider validates it`;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
return undefined;
|
|
2191
|
+
}
|
|
2192
|
+
/** Routable catalog across runnable providers (design v3.6); undefined when unwired. */
|
|
2193
|
+
listRoutableModels() {
|
|
2194
|
+
try {
|
|
2195
|
+
return this.routableModelIndex?.();
|
|
2196
|
+
}
|
|
2197
|
+
catch {
|
|
2198
|
+
return undefined;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
/**
|
|
2202
|
+
* Decision-point detector (design §6): counts dispatches whose model was
|
|
2203
|
+
* decided by NO routing layer (modelSource "inherit") under a strong-tier
|
|
2204
|
+
* parent. Fires once per session at the Nth qualifying dispatch; the
|
|
2205
|
+
* reminder rides the same channel as the lifecycle reminder.
|
|
2206
|
+
*/
|
|
2207
|
+
noteRoutingDispatch(route) {
|
|
2208
|
+
if (this.routingReminderFired)
|
|
2209
|
+
return;
|
|
2210
|
+
if (route.modelSource !== "inherit")
|
|
2211
|
+
return;
|
|
2212
|
+
const snapshot = this.currentRoutingSnapshot();
|
|
2213
|
+
if (snapshot?.parent.tier !== "strong")
|
|
2214
|
+
return;
|
|
2215
|
+
this.defaultedRoutingStreak++;
|
|
2216
|
+
if (this.defaultedRoutingStreak >= ROUTING_REMINDER_THRESHOLD) {
|
|
2217
|
+
this.routingReminderFired = true;
|
|
2218
|
+
this.pendingRoutingReminder = [
|
|
2219
|
+
`Routing note: ${this.defaultedRoutingStreak} children in this fan-out defaulted to the parent's`,
|
|
2220
|
+
"strong-tier model (no model/category given). If any of these tasks are mechanical",
|
|
2221
|
+
"(scan / summarize / search / extract), route them with category \"quick\"/\"explore\" or a",
|
|
2222
|
+
"fast-tier model next time. If they genuinely need this model, ignore this note.",
|
|
2223
|
+
].join(" ");
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
/** Consumed by the turn hooks; also closes the counting window (§6). */
|
|
2227
|
+
consumePendingRoutingReminder() {
|
|
2228
|
+
const reminder = this.pendingRoutingReminder;
|
|
2229
|
+
this.pendingRoutingReminder = undefined;
|
|
2230
|
+
if (!this.routingReminderFired)
|
|
2231
|
+
this.defaultedRoutingStreak = 0;
|
|
2232
|
+
return reminder;
|
|
1999
2233
|
}
|
|
2000
2234
|
createSubagentThreadRecord(options) {
|
|
2001
2235
|
const now = Date.now();
|
|
@@ -2060,7 +2294,7 @@ export class Agent {
|
|
|
2060
2294
|
mode: childMode,
|
|
2061
2295
|
workingDir: childCwd,
|
|
2062
2296
|
...buildToolPromptOptions(tools),
|
|
2063
|
-
memoryPrompt: childToolNames.some((name) => name === "
|
|
2297
|
+
memoryPrompt: childToolNames.some((name) => name === "memory")
|
|
2064
2298
|
? this.memoryPrompt
|
|
2065
2299
|
: undefined,
|
|
2066
2300
|
agentProfilePrompt: [
|
|
@@ -2179,6 +2413,7 @@ export class Agent {
|
|
|
2179
2413
|
name: record.parentToolName,
|
|
2180
2414
|
update: this.buildSubagentUpdate(record, status, event, message),
|
|
2181
2415
|
});
|
|
2416
|
+
this.wakeSubagentUpdateWaiters();
|
|
2182
2417
|
}
|
|
2183
2418
|
drainSubagentToolUpdates() {
|
|
2184
2419
|
return this.pendingSubagentUpdates.splice(0, this.pendingSubagentUpdates.length)
|
|
@@ -2311,7 +2546,7 @@ export class Agent {
|
|
|
2311
2546
|
if (this._mode === "plan" && !tool.readOnly) {
|
|
2312
2547
|
return {
|
|
2313
2548
|
content: `Error: Tool "${toolCall.name}" is not allowed in plan mode. ` +
|
|
2314
|
-
`In plan mode you may only use read-only tools (read, glob, grep,
|
|
2549
|
+
`In plan mode you may only use read-only tools (read, glob, grep, web_search, web_fetch, spawn_agent, wait_agent, send_input, skill_search, skill, todo_write, tool_search, question, exit_plan_mode). ` +
|
|
2315
2550
|
`To modify files or run commands, present your proposal and call exit_plan_mode so the user can review and approve it.`,
|
|
2316
2551
|
isError: true,
|
|
2317
2552
|
};
|
|
@@ -2638,6 +2873,23 @@ function isFinalSubagentStatus(status) {
|
|
|
2638
2873
|
|| status === "cancelled"
|
|
2639
2874
|
|| status === "closed";
|
|
2640
2875
|
}
|
|
2876
|
+
/**
|
|
2877
|
+
* Persists the full rendered workflow result so the inline preview can stay
|
|
2878
|
+
* bounded without losing data (the parent reads the file selectively when the
|
|
2879
|
+
* preview was cut). A write failure only costs the pointer, never the run.
|
|
2880
|
+
*/
|
|
2881
|
+
function persistWorkflowResult(runId, value) {
|
|
2882
|
+
try {
|
|
2883
|
+
const dir = join(getBubbleHome(), "workflows");
|
|
2884
|
+
mkdirSync(dir, { recursive: true });
|
|
2885
|
+
const path = join(dir, `${runId}.result.txt`);
|
|
2886
|
+
writeFileSync(path, renderWorkflowResultValue(value));
|
|
2887
|
+
return path;
|
|
2888
|
+
}
|
|
2889
|
+
catch {
|
|
2890
|
+
return undefined;
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2641
2893
|
function normalizeWaitTimeout(value) {
|
|
2642
2894
|
if (typeof value !== "number" || !Number.isFinite(value))
|
|
2643
2895
|
return 30_000;
|
|
@@ -87,6 +87,17 @@ export class PermissionAwareApprovalController {
|
|
|
87
87
|
return { tool: "Lsp", path: req.path, cwd: this.options.cwd };
|
|
88
88
|
case "agent_profile":
|
|
89
89
|
return { tool: "AgentProfile" };
|
|
90
|
+
case "external_tool": {
|
|
91
|
+
const command = externalCommand(req.rawInput);
|
|
92
|
+
if (req.kind === "execute" && command !== undefined) {
|
|
93
|
+
return { tool: "Bash", command };
|
|
94
|
+
}
|
|
95
|
+
const path = req.locations?.[0]?.path;
|
|
96
|
+
if ((req.kind === "edit" || req.kind === "delete" || req.kind === "move") && path) {
|
|
97
|
+
return { tool: "Edit", path, cwd: this.options.cwd };
|
|
98
|
+
}
|
|
99
|
+
return { tool: req.title.trim() || req.kind.trim() || "ExternalTool" };
|
|
100
|
+
}
|
|
90
101
|
}
|
|
91
102
|
}
|
|
92
103
|
checkRequestRules(req) {
|
|
@@ -176,6 +187,8 @@ function approvalTarget(req) {
|
|
|
176
187
|
return "Lsp";
|
|
177
188
|
case "agent_profile":
|
|
178
189
|
return "AgentProfile";
|
|
190
|
+
case "external_tool":
|
|
191
|
+
return req.title.trim() || req.kind.trim() || "ExternalTool";
|
|
179
192
|
}
|
|
180
193
|
}
|
|
181
194
|
function summarizeApprovalRequest(req) {
|
|
@@ -192,5 +205,20 @@ function summarizeApprovalRequest(req) {
|
|
|
192
205
|
return { type: req.type, path: req.path, operation: req.operation };
|
|
193
206
|
case "agent_profile":
|
|
194
207
|
return { type: req.type, name: req.name, path: req.path, contentHash: req.contentHash };
|
|
208
|
+
case "external_tool":
|
|
209
|
+
return {
|
|
210
|
+
type: req.type,
|
|
211
|
+
toolCallId: req.toolCallId,
|
|
212
|
+
title: req.title,
|
|
213
|
+
kind: req.kind,
|
|
214
|
+
locations: req.locations,
|
|
215
|
+
rawInput: req.rawInput,
|
|
216
|
+
};
|
|
195
217
|
}
|
|
196
218
|
}
|
|
219
|
+
function externalCommand(rawInput) {
|
|
220
|
+
if (typeof rawInput !== "object" || rawInput === null || Array.isArray(rawInput))
|
|
221
|
+
return undefined;
|
|
222
|
+
const command = rawInput.command;
|
|
223
|
+
return typeof command === "string" ? command : undefined;
|
|
224
|
+
}
|
package/dist/approval/types.d.ts
CHANGED
|
@@ -61,7 +61,27 @@ export interface AgentProfileApprovalRequest {
|
|
|
61
61
|
/** First lines of the profile prompt so the user can judge it. */
|
|
62
62
|
promptPreview: string;
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Permission gate for a tool exposed by an external ACP runtime.
|
|
66
|
+
*
|
|
67
|
+
* Keep this structural rather than importing ACP SDK types so the approval
|
|
68
|
+
* layer remains provider-agnostic. External runtimes are responsible for
|
|
69
|
+
* copying the relevant, user-visible fields from the protocol request.
|
|
70
|
+
*/
|
|
71
|
+
export interface ExternalToolApprovalRequest {
|
|
72
|
+
type: "external_tool";
|
|
73
|
+
toolCallId: string;
|
|
74
|
+
/** Human-readable tool name supplied by the external runtime. */
|
|
75
|
+
title: string;
|
|
76
|
+
/** ACP tool category (for example execute, edit, read, or other). */
|
|
77
|
+
kind: string;
|
|
78
|
+
rawInput?: unknown;
|
|
79
|
+
locations?: Array<{
|
|
80
|
+
path: string;
|
|
81
|
+
line?: number | null;
|
|
82
|
+
}>;
|
|
83
|
+
}
|
|
84
|
+
export type ApprovalRequest = EditApprovalRequest | WriteApprovalRequest | PatchApprovalRequest | BashApprovalRequest | LspApprovalRequest | AgentProfileApprovalRequest | ExternalToolApprovalRequest;
|
|
65
85
|
export type ApprovalDecision = {
|
|
66
86
|
action: "approve";
|
|
67
87
|
feedback?: string;
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI argument parsing.
|
|
3
3
|
*/
|
|
4
|
-
import type
|
|
4
|
+
import { type PermissionMode, type ThinkingLevel } from "./types.js";
|
|
5
5
|
export type CliCommand = "default" | "serve" | "update";
|
|
6
6
|
export interface CliArgs {
|
|
7
7
|
command: CliCommand;
|