@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.
- package/dist/agent/abort-errors.d.ts +4 -0
- package/dist/agent/abort-errors.js +30 -0
- package/dist/agent/profiles.js +3 -4
- package/dist/agent/provider-fingerprint.d.ts +6 -0
- package/dist/agent/provider-fingerprint.js +89 -0
- package/dist/agent/resident-history.d.ts +7 -0
- package/dist/agent/resident-history.js +61 -0
- package/dist/agent/subtask-policy.js +4 -4
- package/dist/agent/transcript-helpers.d.ts +20 -0
- package/dist/agent/transcript-helpers.js +62 -0
- package/dist/agent/update-queue.d.ts +13 -0
- package/dist/agent/update-queue.js +69 -0
- package/dist/agent.d.ts +33 -26
- package/dist/agent.js +80 -334
- package/dist/debug-trace.js +0 -2
- package/dist/external-runtime/grok-runtime.js +1 -1
- package/dist/feishu/agent-host/run-driver.js +1 -8
- package/dist/feishu/card/run-state.js +0 -3
- package/dist/lsp/service.js +2 -2
- package/dist/main.js +0 -15
- package/dist/memory/phase1.js +0 -2
- package/dist/model-catalog.js +8 -6
- package/dist/permissions/settings.d.ts +0 -1
- package/dist/permissions/settings.js +0 -2
- package/dist/prompt/compose.js +0 -3
- package/dist/prompt/environment.js +0 -2
- package/dist/prompt/reminders.js +1 -1
- package/dist/provider-registry.js +13 -1
- package/dist/provider-transform.js +7 -8
- package/dist/sdk/index.d.ts +17 -2
- package/dist/sdk/index.js +37 -6
- package/dist/session-log.d.ts +2 -4
- package/dist/session-log.js +0 -23
- package/dist/session-types.d.ts +2 -6
- package/dist/session.d.ts +1 -3
- package/dist/session.js +0 -8
- package/dist/slash-commands/commands.js +13 -13
- package/dist/tools/child-tools.d.ts +1 -1
- package/dist/tools/child-tools.js +1 -1
- package/dist/tools/grep.js +1 -1
- package/dist/tools/index.d.ts +0 -3
- package/dist/tools/index.js +0 -3
- package/dist/tui-ink/app-helpers.d.ts +43 -0
- package/dist/tui-ink/app-helpers.js +150 -0
- package/dist/tui-ink/app.d.ts +4 -11
- package/dist/tui-ink/app.js +57 -657
- package/dist/tui-ink/command-palette.d.ts +32 -0
- package/dist/tui-ink/command-palette.js +158 -0
- package/dist/tui-ink/display-reconstruct.d.ts +3 -0
- package/dist/tui-ink/display-reconstruct.js +82 -0
- package/dist/tui-ink/message-list.js +0 -1
- package/dist/tui-ink/rewind-picker.d.ts +8 -0
- package/dist/tui-ink/rewind-picker.js +102 -0
- package/dist/tui-ink/session-picker.js +1 -1
- package/dist/tui-ink/stats-panel.d.ts +11 -0
- package/dist/tui-ink/stats-panel.js +65 -0
- package/dist/tui-ink/waiting-indicator.d.ts +12 -0
- package/dist/tui-ink/waiting-indicator.js +129 -0
- package/dist/types.d.ts +0 -9
- package/package.json +1 -1
- package/dist/tools/todo.d.ts +0 -12
- package/dist/tools/todo.js +0 -136
- package/dist/tui-ink/todos.d.ts +0 -7
- package/dist/tui-ink/todos.js +0 -46
|
@@ -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
|
+
}
|
package/dist/agent/profiles.js
CHANGED
|
@@ -12,7 +12,6 @@ const READONLY_PRESET = [
|
|
|
12
12
|
"web_fetch",
|
|
13
13
|
"memory",
|
|
14
14
|
"skill",
|
|
15
|
-
"todo_write",
|
|
16
15
|
];
|
|
17
16
|
const SUBAGENT_DENY_TOOLS = new Set([
|
|
18
17
|
"subagent",
|
|
@@ -124,12 +123,12 @@ export function builtinAgentProfiles() {
|
|
|
124
123
|
"You are an explorer subagent for codebase reconnaissance.",
|
|
125
124
|
"Answer the specific question by inspecting the repository directly. Prefer precise file paths and line-level evidence.",
|
|
126
125
|
"Keep the answer compact and avoid broad refactors or implementation plans unless asked.",
|
|
127
|
-
].join("\n"), ["read", "glob", "grep", "lsp", "memory", "skill"
|
|
126
|
+
].join("\n"), ["read", "glob", "grep", "lsp", "memory", "skill"]),
|
|
128
127
|
roleProfile("worker", "Implementation-planning worker subagent", [
|
|
129
128
|
"You are a worker subagent. In this Phase 1 runtime you are read-only, so you must not modify files.",
|
|
130
129
|
"Analyze the assigned implementation slice, identify exact files to change, and return a concrete patch plan or findings.",
|
|
131
130
|
"If write-capable worker mode is needed, say so explicitly.",
|
|
132
|
-
].join("\n"), ["read", "glob", "grep", "lsp", "memory", "skill"
|
|
131
|
+
].join("\n"), ["read", "glob", "grep", "lsp", "memory", "skill"]),
|
|
133
132
|
toProfile("search"),
|
|
134
133
|
toProfile("security_investigation"),
|
|
135
134
|
toProfile("evidence_correlation"),
|
|
@@ -146,7 +145,7 @@ export function builtinAgentProfiles() {
|
|
|
146
145
|
model: "inherit",
|
|
147
146
|
tools: {
|
|
148
147
|
preset: "explicit",
|
|
149
|
-
include: ["read", "glob", "grep", "edit", "write", "bash", "lsp"
|
|
148
|
+
include: ["read", "glob", "grep", "edit", "write", "bash", "lsp"],
|
|
150
149
|
exclude: [],
|
|
151
150
|
},
|
|
152
151
|
approval: "fail",
|
|
@@ -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"
|
|
4
|
+
allowedTools: ["read", "glob", "grep", "web_search", "web_fetch", "skill"],
|
|
5
5
|
reminder: [
|
|
6
6
|
"Subtask policy: search",
|
|
7
7
|
"- Focus on locating relevant files, symbols, and evidence quickly.",
|
|
@@ -13,7 +13,7 @@ const POLICY_MAP = {
|
|
|
13
13
|
},
|
|
14
14
|
security_investigation: {
|
|
15
15
|
type: "security_investigation",
|
|
16
|
-
allowedTools: ["read", "glob", "grep", "web_search", "web_fetch", "skill"
|
|
16
|
+
allowedTools: ["read", "glob", "grep", "web_search", "web_fetch", "skill"],
|
|
17
17
|
reminder: [
|
|
18
18
|
"Subtask policy: security_investigation",
|
|
19
19
|
"- Investigate only in read-only mode.",
|
|
@@ -25,7 +25,7 @@ const POLICY_MAP = {
|
|
|
25
25
|
},
|
|
26
26
|
evidence_correlation: {
|
|
27
27
|
type: "evidence_correlation",
|
|
28
|
-
allowedTools: ["read", "skill"
|
|
28
|
+
allowedTools: ["read", "skill"],
|
|
29
29
|
reminder: [
|
|
30
30
|
"Subtask policy: evidence_correlation",
|
|
31
31
|
"- Correlate evidence already discovered.",
|
|
@@ -37,7 +37,7 @@ const POLICY_MAP = {
|
|
|
37
37
|
},
|
|
38
38
|
general_readonly: {
|
|
39
39
|
type: "general_readonly",
|
|
40
|
-
allowedTools: ["read", "glob", "grep", "web_search", "web_fetch", "skill"
|
|
40
|
+
allowedTools: ["read", "glob", "grep", "web_search", "web_fetch", "skill"],
|
|
41
41
|
reminder: [
|
|
42
42
|
"Subtask policy: general_readonly",
|
|
43
43
|
"- Stay in read-only mode.",
|
|
@@ -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,
|
|
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 {
|
|
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
|
|
15
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
};
|