pi-subagents 0.35.0 → 0.36.0
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/CHANGELOG.md +59 -0
- package/README.md +132 -24
- package/agents/advisor.md +73 -0
- package/package.json +8 -12
- package/skills/pi-subagents/SKILL.md +22 -9
- package/src/agents/agents.ts +22 -5
- package/src/api/delegation.ts +125 -0
- package/src/extension/config.ts +7 -1
- package/src/extension/index.ts +50 -38
- package/src/extension/rpc.ts +27 -2
- package/src/extension/schemas.ts +22 -2
- package/src/extension/tool-description.ts +2 -2
- package/src/intercom/intercom-bridge.ts +1 -1
- package/src/intercom/native-supervisor-channel.ts +45 -6
- package/src/intercom/result-intercom.ts +7 -0
- package/src/runs/background/async-execution.ts +34 -4
- package/src/runs/background/async-job-tracker.ts +4 -0
- package/src/runs/background/async-resume.ts +27 -5
- package/src/runs/background/async-status.ts +76 -3
- package/src/runs/background/chain-append.ts +2 -0
- package/src/runs/background/completion-batcher.ts +6 -4
- package/src/runs/background/completion-dedupe.ts +2 -11
- package/src/runs/background/fleet-view.ts +9 -4
- package/src/runs/background/notify.ts +132 -120
- package/src/runs/background/result-watcher.ts +138 -78
- package/src/runs/background/run-status.ts +3 -1
- package/src/runs/background/subagent-runner.ts +225 -43
- package/src/runs/background/subagent-wait.ts +130 -4
- package/src/runs/background/wait-tool.ts +2 -2
- package/src/runs/foreground/chain-execution.ts +176 -111
- package/src/runs/foreground/execution.ts +90 -36
- package/src/runs/foreground/foreground-control.ts +90 -0
- package/src/runs/foreground/subagent-executor.ts +394 -163
- package/src/runs/shared/acceptance.ts +55 -13
- package/src/runs/shared/agent-contract.ts +38 -0
- package/src/runs/shared/child-protocol.ts +1 -1
- package/src/runs/shared/completion-guard.ts +36 -5
- package/src/runs/shared/context-mode.ts +44 -0
- package/src/runs/shared/dynamic-fanout.ts +4 -4
- package/src/runs/shared/long-running-guard.ts +4 -0
- package/src/runs/shared/nested-events.ts +27 -2
- package/src/runs/shared/parallel-handoff.ts +154 -0
- package/src/runs/shared/parallel-utils.ts +6 -0
- package/src/runs/shared/pi-args.ts +23 -14
- package/src/runs/shared/run-history.ts +90 -5
- package/src/runs/shared/structured-output.ts +112 -7
- package/src/runs/shared/subagent-control.ts +4 -0
- package/src/runs/shared/subagent-prompt-runtime.ts +17 -18
- package/src/runs/shared/task-intent.ts +10 -5
- package/src/runs/shared/tool-availability.ts +3 -1
- package/src/runs/shared/tool-budget.ts +11 -5
- package/src/runs/shared/turn-budget.ts +2 -1
- package/src/runs/shared/worktree.ts +63 -14
- package/src/shared/accessible-dir.ts +25 -0
- package/src/shared/artifacts.ts +37 -7
- package/src/shared/atomic-json.ts +14 -42
- package/src/shared/child-transcript.ts +52 -0
- package/src/shared/file-system-retry.ts +47 -0
- package/src/shared/settings.ts +9 -1
- package/src/shared/types.ts +211 -22
- package/src/slash/delegation-adapters.ts +152 -5
- package/src/slash/delegation-json.ts +108 -0
- package/src/slash/delegation-request.ts +182 -36
- package/src/slash/prompt-template-bridge.ts +222 -37
- package/src/slash/selector.ts +147 -0
- package/src/slash/slash-commands.ts +14 -5
- package/src/slash/slash-live-state.ts +2 -2
- package/src/slash/subagents-admin.ts +42 -42
- package/src/tui/fleet-status.ts +362 -0
- package/src/tui/fleet-transcript.ts +472 -0
- package/src/tui/fleet.ts +318 -59
- package/src/tui/render.ts +25 -15
- package/src/watchdog/change-signature.ts +105 -12
- package/src/watchdog/review.ts +7 -2
- package/src/watchdog/runtime.ts +5 -3
- package/src/slash/subagents-editor.ts +0 -86
package/src/agents/agents.ts
CHANGED
|
@@ -35,6 +35,7 @@ export interface AgentMemoryConfig {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export const BUILTIN_AGENT_NAMES = [
|
|
38
|
+
"advisor",
|
|
38
39
|
"context-builder",
|
|
39
40
|
"delegate",
|
|
40
41
|
"oracle",
|
|
@@ -476,7 +477,7 @@ function splitToolList(rawTools: string[] | undefined): { tools?: string[]; mcpD
|
|
|
476
477
|
}
|
|
477
478
|
}
|
|
478
479
|
return {
|
|
479
|
-
...(
|
|
480
|
+
...(rawTools !== undefined ? { tools } : {}),
|
|
480
481
|
...(mcpDirectTools.length > 0 ? { mcpDirectTools } : {}),
|
|
481
482
|
};
|
|
482
483
|
}
|
|
@@ -1159,7 +1160,19 @@ export function removeBuiltinAgentOverrideFields(
|
|
|
1159
1160
|
return { path: filePath, removed: true };
|
|
1160
1161
|
}
|
|
1161
1162
|
|
|
1162
|
-
|
|
1163
|
+
const DISCOVERY_PRUNED_DIR_NAMES = new Set([".git", "node_modules"]);
|
|
1164
|
+
|
|
1165
|
+
function isDiscoveryNestedProjectRoot(dir: string): boolean {
|
|
1166
|
+
return isDirectory(getProjectConfigDir(dir)) || isDirectory(path.join(dir, ".agents"));
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
function shouldPruneDiscoveryDir(rootDir: string, dir: string, dirName: string): boolean {
|
|
1170
|
+
if (DISCOVERY_PRUNED_DIR_NAMES.has(dirName)) return true;
|
|
1171
|
+
if (fs.existsSync(path.join(dir, ".git"))) return true;
|
|
1172
|
+
return path.resolve(dir) !== path.resolve(rootDir) && isDiscoveryNestedProjectRoot(dir);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
function listFilesRecursive(dir: string, predicate: (fileName: string) => boolean, rootDir = dir): string[] {
|
|
1163
1176
|
const files: string[] = [];
|
|
1164
1177
|
if (!fs.existsSync(dir)) return files;
|
|
1165
1178
|
|
|
@@ -1173,7 +1186,9 @@ function listFilesRecursive(dir: string, predicate: (fileName: string) => boolea
|
|
|
1173
1186
|
for (const entry of entries) {
|
|
1174
1187
|
const filePath = path.join(dir, entry.name);
|
|
1175
1188
|
if (entry.isDirectory()) {
|
|
1176
|
-
|
|
1189
|
+
if (!shouldPruneDiscoveryDir(rootDir, filePath, entry.name)) {
|
|
1190
|
+
files.push(...listFilesRecursive(filePath, predicate, rootDir));
|
|
1191
|
+
}
|
|
1177
1192
|
continue;
|
|
1178
1193
|
}
|
|
1179
1194
|
if (!entry.isFile() && !entry.isSymbolicLink()) continue;
|
|
@@ -1233,7 +1248,9 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
1233
1248
|
const runtimeName = buildRuntimeName(localName, packageName);
|
|
1234
1249
|
|
|
1235
1250
|
const rawTools = parseFrontmatterList(frontmatter.tools);
|
|
1236
|
-
const
|
|
1251
|
+
const parsedTools = splitToolList(rawTools);
|
|
1252
|
+
const tools = parsedTools.tools ?? [];
|
|
1253
|
+
const mcpDirectTools = parsedTools.mcpDirectTools ?? [];
|
|
1237
1254
|
const defaultReads = parseFrontmatterList(frontmatter.defaultReads);
|
|
1238
1255
|
const skillStr = frontmatter.skill || frontmatter.skills;
|
|
1239
1256
|
const skills = parseFrontmatterList(skillStr);
|
|
@@ -1315,7 +1332,7 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentConfig[] {
|
|
|
1315
1332
|
localName,
|
|
1316
1333
|
packageName,
|
|
1317
1334
|
description: frontmatter.description,
|
|
1318
|
-
tools:
|
|
1335
|
+
tools: rawTools !== undefined ? tools : undefined,
|
|
1319
1336
|
mcpDirectTools: mcpDirectTools.length > 0 ? mcpDirectTools : undefined,
|
|
1320
1337
|
model: frontmatter.model,
|
|
1321
1338
|
fallbackModels: fallbackModels && fallbackModels.length > 0 ? fallbackModels : undefined,
|
package/src/api/delegation.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const SUBAGENT_DELEGATION_PROTOCOL_VERSION = 1 as const;
|
|
2
|
+
export const SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION = 2 as const;
|
|
2
3
|
|
|
3
4
|
// This is the established extension-to-extension transport. The public API
|
|
4
5
|
// intentionally reuses it instead of adding a second event protocol.
|
|
@@ -19,6 +20,37 @@ export interface SubagentDelegationToolBudget {
|
|
|
19
20
|
block?: string[] | "*";
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
export interface SubagentDelegationAgentContract {
|
|
24
|
+
version: 1;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type SubagentDelegationJsonSchemaObject = Record<string, unknown>;
|
|
28
|
+
|
|
29
|
+
export interface SubagentDelegationExecutionResult {
|
|
30
|
+
status: "completed" | "failed" | "paused" | "stopped" | "detached";
|
|
31
|
+
success: boolean;
|
|
32
|
+
exitCode: number;
|
|
33
|
+
error?: string;
|
|
34
|
+
interrupted?: boolean;
|
|
35
|
+
timedOut?: boolean;
|
|
36
|
+
stopped?: boolean;
|
|
37
|
+
detached?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SubagentDelegationReviewResult {
|
|
41
|
+
status: "not-requested" | "no-blockers" | "blockers" | "needs-parent-decision";
|
|
42
|
+
findings?: Array<{ severity: "blocker" | "non-blocking"; file?: string; issue: string; rationale: string }>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface SubagentDelegationEffectsResult {
|
|
46
|
+
fileMutation?: {
|
|
47
|
+
status: "not-requested" | "not-applicable" | "observed" | "missing";
|
|
48
|
+
expected: boolean;
|
|
49
|
+
attempted: boolean;
|
|
50
|
+
message?: string;
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
22
54
|
export type SubagentDelegationAcceptanceEvidence =
|
|
23
55
|
| "changed-files"
|
|
24
56
|
| "tests-added"
|
|
@@ -87,6 +119,8 @@ export interface SubagentDelegationRequest {
|
|
|
87
119
|
skill?: string | string[] | boolean;
|
|
88
120
|
output?: string | boolean;
|
|
89
121
|
outputMode?: "inline" | "file-only";
|
|
122
|
+
outputSchema?: SubagentDelegationJsonSchemaObject;
|
|
123
|
+
agentContract?: SubagentDelegationAgentContract;
|
|
90
124
|
acceptance?: SubagentDelegationAcceptance;
|
|
91
125
|
artifacts?: boolean;
|
|
92
126
|
}
|
|
@@ -116,6 +150,7 @@ export type SubagentDelegationStatus =
|
|
|
116
150
|
| "interrupted"
|
|
117
151
|
| "turn_budget_exhausted"
|
|
118
152
|
| "tool_budget_exhausted"
|
|
153
|
+
| "structured_output_failed"
|
|
119
154
|
| "acceptance_failed"
|
|
120
155
|
| "invalid_request"
|
|
121
156
|
| "unavailable_context";
|
|
@@ -144,10 +179,13 @@ export interface SubagentDelegationResponse extends SubagentDelegationStarted {
|
|
|
144
179
|
agent?: string;
|
|
145
180
|
model?: string;
|
|
146
181
|
exitCode?: number;
|
|
182
|
+
execution?: SubagentDelegationExecutionResult;
|
|
147
183
|
output?: string;
|
|
148
184
|
outputPath?: string;
|
|
149
185
|
sessionFile?: string;
|
|
150
186
|
acceptance?: SubagentDelegationAcceptanceResult;
|
|
187
|
+
review?: SubagentDelegationReviewResult;
|
|
188
|
+
effects?: SubagentDelegationEffectsResult;
|
|
151
189
|
turns?: number;
|
|
152
190
|
toolCount?: number;
|
|
153
191
|
durationMs?: number;
|
|
@@ -156,3 +194,90 @@ export interface SubagentDelegationResponse extends SubagentDelegationStarted {
|
|
|
156
194
|
}
|
|
157
195
|
|
|
158
196
|
export interface SubagentDelegationCancel extends SubagentDelegationStarted {}
|
|
197
|
+
|
|
198
|
+
export type SubagentDelegationV2Thinking = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
199
|
+
|
|
200
|
+
export type SubagentDelegationV2ResultRequest =
|
|
201
|
+
| { kind: "text" }
|
|
202
|
+
| { kind: "structured"; schema: SubagentDelegationJsonSchemaObject };
|
|
203
|
+
|
|
204
|
+
export interface SubagentDelegationV2Request {
|
|
205
|
+
version: typeof SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION;
|
|
206
|
+
requestId: string;
|
|
207
|
+
ownerRunId: string;
|
|
208
|
+
nodeId: string;
|
|
209
|
+
agent: string;
|
|
210
|
+
task: string;
|
|
211
|
+
context: "fresh" | "fork";
|
|
212
|
+
cwd: string;
|
|
213
|
+
model?: string;
|
|
214
|
+
thinking?: SubagentDelegationV2Thinking;
|
|
215
|
+
timeoutMs?: number;
|
|
216
|
+
turnBudget?: SubagentDelegationTurnBudget;
|
|
217
|
+
toolBudget?: SubagentDelegationToolBudget;
|
|
218
|
+
skill?: string | string[] | boolean;
|
|
219
|
+
artifacts?: boolean;
|
|
220
|
+
result: SubagentDelegationV2ResultRequest;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface SubagentDelegationV2Started {
|
|
224
|
+
version: typeof SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION;
|
|
225
|
+
requestId: string;
|
|
226
|
+
ownerRunId: string;
|
|
227
|
+
nodeId: string;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export interface SubagentDelegationV2Update extends SubagentDelegationV2Started {
|
|
231
|
+
currentTool?: string;
|
|
232
|
+
currentToolArgs?: string;
|
|
233
|
+
recentOutput?: string;
|
|
234
|
+
recentOutputLines?: string[];
|
|
235
|
+
recentTools?: Array<{ tool: string; args: string }>;
|
|
236
|
+
model?: string;
|
|
237
|
+
toolCount?: number;
|
|
238
|
+
durationMs?: number;
|
|
239
|
+
tokens?: number;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export type SubagentDelegationV2Status = SubagentDelegationStatus | "duplicate_node";
|
|
243
|
+
|
|
244
|
+
export type SubagentDelegationV2Value =
|
|
245
|
+
| { kind: "text"; text: string }
|
|
246
|
+
| { kind: "structured"; value: unknown };
|
|
247
|
+
|
|
248
|
+
export interface SubagentDelegationV2Usage {
|
|
249
|
+
input: number;
|
|
250
|
+
output: number;
|
|
251
|
+
cacheRead: number;
|
|
252
|
+
cacheWrite: number;
|
|
253
|
+
cost: number;
|
|
254
|
+
turns: number;
|
|
255
|
+
toolCalls: number;
|
|
256
|
+
durationMs: number;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export interface SubagentDelegationV2TerminalResponse extends SubagentDelegationV2Started {
|
|
260
|
+
status: Exclude<SubagentDelegationV2Status, "invalid_request">;
|
|
261
|
+
error?: string;
|
|
262
|
+
runId?: string;
|
|
263
|
+
agent?: string;
|
|
264
|
+
model?: string;
|
|
265
|
+
thinking?: string;
|
|
266
|
+
exitCode?: number;
|
|
267
|
+
result?: SubagentDelegationV2Value;
|
|
268
|
+
usage?: SubagentDelegationV2Usage;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** A malformed V2 request can only be correlated by the valid identity fields it supplied. */
|
|
272
|
+
export interface SubagentDelegationV2InvalidResponse {
|
|
273
|
+
version: typeof SUBAGENT_DELEGATION_V2_PROTOCOL_VERSION;
|
|
274
|
+
requestId: string;
|
|
275
|
+
ownerRunId?: string;
|
|
276
|
+
nodeId?: string;
|
|
277
|
+
status: "invalid_request";
|
|
278
|
+
error?: string;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export type SubagentDelegationV2Response = SubagentDelegationV2TerminalResponse | SubagentDelegationV2InvalidResponse;
|
|
282
|
+
|
|
283
|
+
export interface SubagentDelegationV2Cancel extends SubagentDelegationV2Started {}
|
package/src/extension/config.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type { ExtensionConfig } from "../shared/types.ts";
|
|
3
|
+
import type { ArtifactDirPreference, ExtensionConfig } from "../shared/types.ts";
|
|
4
4
|
import { getAgentDir } from "../shared/utils.ts";
|
|
5
5
|
|
|
6
|
+
const ARTIFACT_DIR_PREFERENCES = new Set<ArtifactDirPreference>(["project", "session", "temp"]);
|
|
7
|
+
|
|
6
8
|
export function getConfigPath(): string {
|
|
7
9
|
return path.join(getAgentDir(), "extensions", "subagent", "config.json");
|
|
8
10
|
}
|
|
@@ -13,6 +15,10 @@ function readConfigForUpdate(configPath = getConfigPath()): ExtensionConfig {
|
|
|
13
15
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
14
16
|
throw new Error(`Subagent config at '${configPath}' must be a JSON object`);
|
|
15
17
|
}
|
|
18
|
+
const config = parsed as Record<string, unknown>;
|
|
19
|
+
if (config.artifactDir !== undefined && !ARTIFACT_DIR_PREFERENCES.has(config.artifactDir as ArtifactDirPreference)) {
|
|
20
|
+
throw new Error(`config.artifactDir must be "project", "session", or "temp"`);
|
|
21
|
+
}
|
|
16
22
|
return parsed as ExtensionConfig;
|
|
17
23
|
}
|
|
18
24
|
|
package/src/extension/index.ts
CHANGED
|
@@ -20,10 +20,13 @@ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
|
20
20
|
import { keyText, type ExtensionAPI, type ExtensionContext, type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
21
21
|
import { Box, Container, Spacer, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, type Component } from "@earendil-works/pi-tui";
|
|
22
22
|
import { discoverAgents } from "../agents/agents.ts";
|
|
23
|
+
import { ensureAccessibleDir } from "../shared/accessible-dir.ts";
|
|
23
24
|
import { cleanupAllArtifactDirs, cleanupOldArtifacts, getArtifactsDir } from "../shared/artifacts.ts";
|
|
24
25
|
import { resolveCurrentSessionId } from "../shared/session-identity.ts";
|
|
25
26
|
import { cleanupOldChainDirs } from "../shared/settings.ts";
|
|
26
27
|
import { clearLegacyResultAnimationTimer, renderSubagentResult } from "../tui/render.ts";
|
|
28
|
+
import { openSubagentFleet } from "../tui/fleet.ts";
|
|
29
|
+
import { SubagentFleetStatus } from "../tui/fleet-status.ts";
|
|
27
30
|
import { SubagentParams } from "./schemas.ts";
|
|
28
31
|
import { validateChainInput } from "./chain-validation.ts";
|
|
29
32
|
import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
@@ -92,28 +95,6 @@ function expandTilde(p: string): string {
|
|
|
92
95
|
return p.startsWith("~/") ? path.join(os.homedir(), p.slice(2)) : p;
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
/**
|
|
96
|
-
* Create a directory and verify it is actually accessible.
|
|
97
|
-
* On Windows with Azure AD/Entra ID, directories created shortly after
|
|
98
|
-
* wake-from-sleep can end up with broken NTFS ACLs (null DACL) when the
|
|
99
|
-
* cloud SID cannot be resolved without network connectivity. This leaves
|
|
100
|
-
* the directory completely inaccessible to the creating user.
|
|
101
|
-
*/
|
|
102
|
-
function ensureAccessibleDir(dirPath: string): void {
|
|
103
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
104
|
-
try {
|
|
105
|
-
fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
|
|
106
|
-
} catch {
|
|
107
|
-
try {
|
|
108
|
-
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
109
|
-
} catch {
|
|
110
|
-
// Best effort: retry mkdir/access even if cleanup fails.
|
|
111
|
-
}
|
|
112
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
113
|
-
fs.accessSync(dirPath, fs.constants.R_OK | fs.constants.W_OK);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
98
|
function isSlashResultRunning(result: { details?: Details }): boolean {
|
|
118
99
|
return result.details?.progress?.some((entry) => entry.status === "running")
|
|
119
100
|
|| result.details?.results.some((entry) => entry.progress?.status === "running")
|
|
@@ -210,12 +191,16 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
210
191
|
const config = loadConfig();
|
|
211
192
|
const waitToolConfig = resolveWaitToolConfig(config.waitTool);
|
|
212
193
|
const asyncByDefault = config.asyncByDefault === true;
|
|
194
|
+
const fleetViewEnabled = config.fleetView !== false;
|
|
195
|
+
const asyncWidgetEnabled = config.asyncWidget === true || (!fleetViewEnabled && config.asyncWidget !== false);
|
|
213
196
|
const tempArtifactsDir = getArtifactsDir(null);
|
|
214
197
|
cleanupAllArtifactDirs(DEFAULT_ARTIFACT_CONFIG.cleanupDays);
|
|
215
198
|
|
|
216
199
|
const state: SubagentState = {
|
|
217
200
|
baseCwd: "",
|
|
218
201
|
currentSessionId: null,
|
|
202
|
+
artifactDirPreference: config.artifactDir ?? DEFAULT_ARTIFACT_CONFIG.dir,
|
|
203
|
+
parentSessionFile: null,
|
|
219
204
|
subagentInProgress: false,
|
|
220
205
|
subagentSpawns: {
|
|
221
206
|
sessionId: null,
|
|
@@ -244,22 +229,30 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
244
229
|
|
|
245
230
|
const supervisorChannel = createNativeSupervisorChannel(pi, state);
|
|
246
231
|
const mainWatchdog = registerMainWatchdog(pi);
|
|
247
|
-
|
|
232
|
+
const completionNotifier = registerSubagentNotify(pi, state, { batchConfig: config.completionBatch });
|
|
233
|
+
const fleetStatus = fleetViewEnabled
|
|
234
|
+
? new SubagentFleetStatus(state, async (itemKey) => {
|
|
235
|
+
const ctx = state.lastUiContext;
|
|
236
|
+
if (!ctx?.hasUI) return;
|
|
237
|
+
await openSubagentFleet(ctx, state, { initialKey: itemKey });
|
|
238
|
+
})
|
|
239
|
+
: undefined;
|
|
248
240
|
const { startResultWatcher, primeExistingResults, stopResultWatcher } = createResultWatcher(
|
|
249
241
|
pi,
|
|
250
242
|
state,
|
|
251
243
|
RESULTS_DIR,
|
|
252
244
|
10 * 60 * 1000,
|
|
245
|
+
{ notifier: completionNotifier },
|
|
253
246
|
);
|
|
254
|
-
startResultWatcher();
|
|
255
|
-
primeExistingResults();
|
|
256
247
|
|
|
257
248
|
const runtimeCleanup = () => {
|
|
258
|
-
disposeSubagentNotify();
|
|
259
|
-
mainWatchdog.dispose();
|
|
260
249
|
stopResultWatcher();
|
|
250
|
+
state.currentSessionId = null;
|
|
251
|
+
completionNotifier.dispose();
|
|
252
|
+
mainWatchdog.dispose();
|
|
261
253
|
scheduledRunManager.stop();
|
|
262
254
|
supervisorChannel.dispose();
|
|
255
|
+
fleetStatus?.dispose();
|
|
263
256
|
clearPendingForegroundControlNotices(state);
|
|
264
257
|
if (state.poller) {
|
|
265
258
|
clearInterval(state.poller);
|
|
@@ -269,7 +262,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
269
262
|
globalStore[runtimeCleanupStoreKey] = runtimeCleanup;
|
|
270
263
|
|
|
271
264
|
const { ensurePoller, refreshWidget, handleStarted, handleComplete, resetJobs, restoreActiveJobs } = createAsyncJobTracker(pi, state, ASYNC_DIR, {
|
|
272
|
-
widgetEnabled:
|
|
265
|
+
widgetEnabled: asyncWidgetEnabled,
|
|
273
266
|
});
|
|
274
267
|
let executorExecute: ((id: string, params: SubagentParamsLike, signal: AbortSignal, onUpdate: ((r: AgentToolResult<Details>) => void) | undefined, ctx: ExtensionContext) => Promise<AgentToolResult<Details>>) | undefined;
|
|
275
268
|
const scheduledRunManager = createScheduledRunManager({
|
|
@@ -377,6 +370,10 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
377
370
|
getContext: () => state.lastUiContext,
|
|
378
371
|
execute: (requestId, params, signal, ctx, onUpdate) =>
|
|
379
372
|
executeSubagentCollapsed(requestId, params, signal, onUpdate, ctx),
|
|
373
|
+
executeVersioned: (requestId, params, signal, ctx, onUpdate) => {
|
|
374
|
+
if (ctx.hasUI) ctx.ui.setToolsExpanded(false);
|
|
375
|
+
return executor.executeDelegated(requestId, params, signal, onUpdate, ctx);
|
|
376
|
+
},
|
|
380
377
|
});
|
|
381
378
|
|
|
382
379
|
const rpcBridge = registerSubagentRpcBridge({
|
|
@@ -472,8 +469,6 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
472
469
|
}
|
|
473
470
|
}
|
|
474
471
|
}
|
|
475
|
-
disposeSubagentNotify = registerSubagentNotify(pi, state, { batchConfig: config.completionBatch });
|
|
476
|
-
|
|
477
472
|
const existingVisibleControlNotices = globalStore[controlNoticeSeenStoreKey];
|
|
478
473
|
const visibleControlNotices = existingVisibleControlNotices instanceof Set ? existingVisibleControlNotices as Set<string> : new Set<string>();
|
|
479
474
|
globalStore[controlNoticeSeenStoreKey] = visibleControlNotices;
|
|
@@ -488,9 +483,17 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
488
483
|
const steeringNoticeHandler = (payload: unknown) => {
|
|
489
484
|
handleSubagentSteeringNotice({ pi, state, details: payload as SubagentSteeringMessageDetails });
|
|
490
485
|
};
|
|
486
|
+
const asyncStartedHandler = (payload: unknown) => {
|
|
487
|
+
handleStarted(payload);
|
|
488
|
+
fleetStatus?.refresh();
|
|
489
|
+
};
|
|
490
|
+
const asyncCompleteHandler = (payload: unknown) => {
|
|
491
|
+
handleComplete(payload);
|
|
492
|
+
fleetStatus?.refresh();
|
|
493
|
+
};
|
|
491
494
|
const eventUnsubscribes = [
|
|
492
|
-
pi.events.on(SUBAGENT_ASYNC_STARTED_EVENT,
|
|
493
|
-
pi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
495
|
+
pi.events.on(SUBAGENT_ASYNC_STARTED_EVENT, asyncStartedHandler),
|
|
496
|
+
pi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, asyncCompleteHandler),
|
|
494
497
|
pi.events.on(SUBAGENT_CONTROL_EVENT, controlEventHandler),
|
|
495
498
|
pi.events.on(SUBAGENT_STEERING_NOTICE_EVENT, steeringNoticeHandler),
|
|
496
499
|
rpcBridge.dispose,
|
|
@@ -501,6 +504,8 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
501
504
|
if (event.toolName !== "subagent") return;
|
|
502
505
|
if (!ctx.hasUI) return;
|
|
503
506
|
state.lastUiContext = ctx;
|
|
507
|
+
fleetStatus?.setContext(ctx);
|
|
508
|
+
fleetStatus?.refresh();
|
|
504
509
|
if (state.asyncJobs.size > 0) {
|
|
505
510
|
refreshWidget(ctx);
|
|
506
511
|
ensurePoller();
|
|
@@ -518,9 +523,10 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
518
523
|
}
|
|
519
524
|
};
|
|
520
525
|
|
|
521
|
-
const resetSessionState = (ctx: ExtensionContext) => {
|
|
526
|
+
const resetSessionState = (ctx: ExtensionContext, recovering: boolean) => {
|
|
522
527
|
state.baseCwd = ctx.cwd;
|
|
523
528
|
state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
|
|
529
|
+
state.parentSessionFile = ctx.sessionManager.getSessionFile();
|
|
524
530
|
state.subagentSpawns = {
|
|
525
531
|
sessionId: state.currentSessionId,
|
|
526
532
|
count: 0,
|
|
@@ -546,17 +552,23 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
546
552
|
restoreActiveJobs(ctx);
|
|
547
553
|
scheduledRunManager.bindSession(ctx);
|
|
548
554
|
restoreSlashFinalSnapshots(ctx.sessionManager.getEntries());
|
|
549
|
-
|
|
555
|
+
startResultWatcher();
|
|
556
|
+
primeExistingResults({ triggerTurn: !recovering });
|
|
557
|
+
fleetStatus?.setContext(ctx);
|
|
550
558
|
};
|
|
551
559
|
|
|
552
|
-
pi.on("session_start", (
|
|
553
|
-
|
|
560
|
+
pi.on("session_start", (event, ctx) => {
|
|
561
|
+
const recovering = event.reason === "startup" || event.reason === "reload" || event.reason === "resume";
|
|
562
|
+
resetSessionState(ctx, recovering);
|
|
554
563
|
rpcBridge.emitReady(ctx);
|
|
555
564
|
supervisorChannel.start();
|
|
556
565
|
});
|
|
557
566
|
|
|
558
567
|
pi.on("session_shutdown", () => {
|
|
559
|
-
|
|
568
|
+
stopResultWatcher();
|
|
569
|
+
state.currentSessionId = null;
|
|
570
|
+
state.parentSessionFile = null;
|
|
571
|
+
completionNotifier.dispose();
|
|
560
572
|
delete process.env[SUBAGENT_PARENT_SESSION_ENV];
|
|
561
573
|
for (const unsubscribe of eventUnsubscribes) {
|
|
562
574
|
try {
|
|
@@ -568,7 +580,6 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
568
580
|
if (globalStore[eventUnsubscribeStoreKey] === eventUnsubscribes) {
|
|
569
581
|
delete globalStore[eventUnsubscribeStoreKey];
|
|
570
582
|
}
|
|
571
|
-
stopResultWatcher();
|
|
572
583
|
scheduledRunManager.stop();
|
|
573
584
|
if (state.poller) clearInterval(state.poller);
|
|
574
585
|
state.poller = null;
|
|
@@ -584,6 +595,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
|
|
|
584
595
|
promptTemplateBridge.cancelAll();
|
|
585
596
|
promptTemplateBridge.dispose();
|
|
586
597
|
supervisorChannel.dispose();
|
|
598
|
+
fleetStatus?.dispose();
|
|
587
599
|
if (globalStore[runtimeCleanupStoreKey] === runtimeCleanup) {
|
|
588
600
|
delete globalStore[runtimeCleanupStoreKey];
|
|
589
601
|
}
|
package/src/extension/rpc.ts
CHANGED
|
@@ -6,7 +6,12 @@ import { resolveAsyncRunLocation } from "../runs/background/async-resume.ts";
|
|
|
6
6
|
import { deliverStopRequest } from "../runs/background/control-channel.ts";
|
|
7
7
|
import { reconcileAsyncRun } from "../runs/background/stale-run-reconciler.ts";
|
|
8
8
|
import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
type Details,
|
|
11
|
+
ASYNC_DIR,
|
|
12
|
+
RESULTS_DIR,
|
|
13
|
+
SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
14
|
+
} from "../shared/types.ts";
|
|
10
15
|
import { readStatus } from "../shared/utils.ts";
|
|
11
16
|
import { SubagentParams } from "./schemas.ts";
|
|
12
17
|
import { validateChainInput } from "./chain-validation.ts";
|
|
@@ -16,7 +21,7 @@ export const SUBAGENT_RPC_REQUEST_EVENT = "subagents:rpc:v1:request";
|
|
|
16
21
|
export const SUBAGENT_RPC_READY_EVENT = "subagents:rpc:v1:ready";
|
|
17
22
|
export const SUBAGENT_RPC_REPLY_EVENT_PREFIX = "subagents:rpc:v1:reply:";
|
|
18
23
|
|
|
19
|
-
export const SUBAGENT_RPC_METHODS = ["ping", "status", "spawn", "interrupt", "stop"] as const;
|
|
24
|
+
export const SUBAGENT_RPC_METHODS = ["ping", "status", "spawn", "steer", "interrupt", "stop"] as const;
|
|
20
25
|
export type SubagentRpcMethod = typeof SUBAGENT_RPC_METHODS[number];
|
|
21
26
|
|
|
22
27
|
export interface SubagentRpcRequestEnvelope {
|
|
@@ -172,6 +177,8 @@ function pingData(ctx: ExtensionContext | null) {
|
|
|
172
177
|
capabilities: {
|
|
173
178
|
status: true,
|
|
174
179
|
asyncSpawn: true,
|
|
180
|
+
steer: true,
|
|
181
|
+
nonRecoveringSteer: true,
|
|
175
182
|
interrupt: true,
|
|
176
183
|
stop: true,
|
|
177
184
|
},
|
|
@@ -179,6 +186,7 @@ function pingData(ctx: ExtensionContext | null) {
|
|
|
179
186
|
ready: SUBAGENT_RPC_READY_EVENT,
|
|
180
187
|
request: SUBAGENT_RPC_REQUEST_EVENT,
|
|
181
188
|
replyPrefix: SUBAGENT_RPC_REPLY_EVENT_PREFIX,
|
|
189
|
+
asyncComplete: SUBAGENT_ASYNC_COMPLETE_EVENT,
|
|
182
190
|
},
|
|
183
191
|
session: sessionData(ctx),
|
|
184
192
|
};
|
|
@@ -212,6 +220,20 @@ function spawnParams(params: unknown): SubagentParamsLike {
|
|
|
212
220
|
return { ...(input as SubagentParamsLike), async: true, clarify: false };
|
|
213
221
|
}
|
|
214
222
|
|
|
223
|
+
function steerParams(params: unknown): SubagentParamsLike {
|
|
224
|
+
const input = assertRecordParams(params, "steer");
|
|
225
|
+
if (typeof input.message !== "string" || !input.message.trim())
|
|
226
|
+
throw new SubagentRpcError("invalid_params", "RPC steer requires a non-empty message.");
|
|
227
|
+
const target = normalizeTargetParams(input, "steer");
|
|
228
|
+
if (!target.id && !target.runId && !target.dir) throw new SubagentRpcError("invalid_params", "RPC steer requires id, runId, or dir.");
|
|
229
|
+
return {
|
|
230
|
+
action: "steer",
|
|
231
|
+
...target,
|
|
232
|
+
message: input.message.trim(),
|
|
233
|
+
steeringRecovery: false,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
215
237
|
function stopAsyncRun(
|
|
216
238
|
params: unknown,
|
|
217
239
|
options: RegisterSubagentRpcBridgeOptions,
|
|
@@ -289,6 +311,9 @@ async function handleRequest(
|
|
|
289
311
|
if (request.method === "status") {
|
|
290
312
|
return executeChecked(options, ctx, request.requestId, request.method, { action: "status", ...normalizeTargetParams(request.params, "status") });
|
|
291
313
|
}
|
|
314
|
+
if (request.method === "steer") {
|
|
315
|
+
return executeChecked(options, ctx, request.requestId, request.method, steerParams(request.params));
|
|
316
|
+
}
|
|
292
317
|
if (request.method === "interrupt") {
|
|
293
318
|
return executeChecked(options, ctx, request.requestId, request.method, { action: "interrupt", ...normalizeTargetParams(request.params, "interrupt") });
|
|
294
319
|
}
|
package/src/extension/schemas.ts
CHANGED
|
@@ -71,7 +71,16 @@ const AcceptanceOverride = Type.Unsafe({
|
|
|
71
71
|
{ type: "boolean", enum: [false] },
|
|
72
72
|
{ type: "object", additionalProperties: true },
|
|
73
73
|
],
|
|
74
|
-
description: "Optional acceptance policy.
|
|
74
|
+
description: "Optional acceptance policy. In the current/default contract, omitted means auto-inferred; verified requires configured runtime commands. With agentContract.version=1, omitted means not requested and acceptance failures are reported separately from execution.",
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const AgentContractOverride = Type.Object({
|
|
78
|
+
version: Type.Integer({ enum: [1], description: "Opt into generic agent contract v1 for this run/child." }),
|
|
79
|
+
}, { additionalProperties: false, description: "Opt-in compatibility contract. Omit to use current default behavior." });
|
|
80
|
+
|
|
81
|
+
const ChainGateOverride = Type.String({
|
|
82
|
+
enum: ["execution", "acceptance"],
|
|
83
|
+
description: "For agentContract.version=1 chain steps, choose whether the chain advances on execution success or acceptance success. Defaults to execution.",
|
|
75
84
|
});
|
|
76
85
|
|
|
77
86
|
const TurnBudgetOverride = Type.Object({
|
|
@@ -104,7 +113,9 @@ const TaskItem = Type.Object({
|
|
|
104
113
|
model: Type.Optional(Type.String({ description: "Override model for this task (e.g. 'google/gemini-3-pro')" })),
|
|
105
114
|
skill: Type.Optional(SkillOverride),
|
|
106
115
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
116
|
+
outputSchema: Type.Optional(JsonSchemaObject),
|
|
107
117
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
118
|
+
agentContract: Type.Optional(AgentContractOverride),
|
|
108
119
|
});
|
|
109
120
|
|
|
110
121
|
// Parallel task item (within a parallel step)
|
|
@@ -125,6 +136,8 @@ export const ParallelTaskSchema = Type.Object({
|
|
|
125
136
|
model: Type.Optional(Type.String({ description: "Override model for this task" })),
|
|
126
137
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
127
138
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
139
|
+
agentContract: Type.Optional(AgentContractOverride),
|
|
140
|
+
gateOn: Type.Optional(ChainGateOverride),
|
|
128
141
|
});
|
|
129
142
|
|
|
130
143
|
export const DynamicExpandSchema = Type.Object({
|
|
@@ -153,6 +166,8 @@ export const DynamicParallelTemplateSchema = Type.Object({
|
|
|
153
166
|
model: Type.Optional(Type.String({ description: "Override model for this task" })),
|
|
154
167
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
155
168
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
169
|
+
agentContract: Type.Optional(AgentContractOverride),
|
|
170
|
+
gateOn: Type.Optional(ChainGateOverride),
|
|
156
171
|
}, { additionalProperties: false });
|
|
157
172
|
|
|
158
173
|
export const DynamicCollectSchema = Type.Object({
|
|
@@ -179,6 +194,8 @@ export const ChainItem = Type.Object({
|
|
|
179
194
|
model: Type.Optional(Type.String({ description: "Override model for this step" })),
|
|
180
195
|
toolBudget: Type.Optional(ToolBudgetOverride),
|
|
181
196
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
197
|
+
agentContract: Type.Optional(AgentContractOverride),
|
|
198
|
+
gateOn: Type.Optional(ChainGateOverride),
|
|
182
199
|
parallel: Type.Optional(Type.Unsafe({
|
|
183
200
|
anyOf: [
|
|
184
201
|
Type.Array(ParallelTaskSchema, { minItems: 1, description: "Tasks to run in parallel" }),
|
|
@@ -218,7 +235,7 @@ const SubagentParamsSchema = Type.Object({
|
|
|
218
235
|
task: Type.Optional(Type.String({ description: "Task (SINGLE mode, optional for self-contained agents)" })),
|
|
219
236
|
// Management action (when present, tool operates in management mode)
|
|
220
237
|
action: Type.Optional(Type.String({
|
|
221
|
-
description: "
|
|
238
|
+
description: "Optional management/control action. Omit this field entirely for execution/delegation ({agent, task}, {tasks}, or {chain}); use it only for management/control actions."
|
|
222
239
|
})),
|
|
223
240
|
id: Type.Optional(Type.String({
|
|
224
241
|
description: "Run id or prefix for action='status', action='interrupt', action='stop', action='resume', action='steer', or action='append-step'."
|
|
@@ -236,6 +253,7 @@ const SubagentParamsSchema = Type.Object({
|
|
|
236
253
|
})),
|
|
237
254
|
lines: Type.Optional(Type.Integer({ minimum: 1, maximum: 500, description: "Maximum transcript lines for action='status', view='transcript'. Defaults to 80." })),
|
|
238
255
|
message: Type.Optional(Type.String({ description: "Follow-up message for action='resume' (revive paused, completed, or failed children, or reach a routed nested run) or live async guidance for action='steer'. Stopped runs are non-resumable. Use index to choose a child from multi-child runs." })),
|
|
256
|
+
steeringRecovery: Type.Optional(Type.Boolean({ description: "For action='steer', allow pause-and-revive recovery after a missed acknowledgment. Defaults true for direct tool calls; extension RPC steering forces false so callers retain exact child ownership." })),
|
|
239
257
|
additional: Type.Optional(Type.Integer({ minimum: 1, description: "Positive launches to add with action='grant-spawn-budget'. Root interactive parent with native user confirmation only; total grants cannot exceed the original configured cap." })),
|
|
240
258
|
scope: Type.Optional(Type.String({ enum: ["session", "user", "project"], description: "Scope for action='watchdog.configure'. Defaults to session to avoid persistent settings writes unless user/project is explicit." })),
|
|
241
259
|
target: Type.Optional(Type.String({ enum: ["main", "children", "child"], description: "Target for action='watchdog.configure'. Defaults to main. Use target='child' with agent for a per-agent child watchdog override." })),
|
|
@@ -292,6 +310,8 @@ const SubagentParamsSchema = Type.Object({
|
|
|
292
310
|
outputMode: Type.Optional(OutputModeOverride),
|
|
293
311
|
skill: Type.Optional(SkillOverride),
|
|
294
312
|
model: Type.Optional(Type.String({ description: "Override model for single agent (e.g. 'anthropic/claude-sonnet-4')" })),
|
|
313
|
+
outputSchema: Type.Optional(JsonSchemaObject),
|
|
314
|
+
agentContract: Type.Optional(AgentContractOverride),
|
|
295
315
|
acceptance: Type.Optional(AcceptanceOverride),
|
|
296
316
|
});
|
|
297
317
|
|
|
@@ -14,7 +14,7 @@ export const SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
|
|
|
14
14
|
• Writing/review safety: keep one writer for the same cwd/worktree. Use fresh-context read-only reviewers/validators for independent review, then have the parent synthesize and apply fixes as the sole writer unless an isolated worktree was intentionally requested.
|
|
15
15
|
• Artifacts/status essentials: chain outputs live under {chain_dir}; async runs expose asyncId/asyncDir with status.json, events.jsonl, output logs, and status via { action: "status", id }. Include output paths and residual risks when reporting results.`;
|
|
16
16
|
|
|
17
|
-
export const FULL_SUBAGENT_TOOL_DESCRIPTION = `
|
|
17
|
+
export const FULL_SUBAGENT_TOOL_DESCRIPTION = `To delegate work, call with { agent, task }, { tasks }, or { chain }; omit action. Use action only for management/control actions listed below.
|
|
18
18
|
|
|
19
19
|
EXECUTION (use exactly ONE mode):
|
|
20
20
|
• Before executing, use { action: "list" } to inspect configured agents/chains. Only execute agents listed as executable/non-disabled.
|
|
@@ -74,7 +74,7 @@ DIAGNOSTICS:
|
|
|
74
74
|
|
|
75
75
|
${SUBAGENT_SAFETY_GUIDANCE}`;
|
|
76
76
|
|
|
77
|
-
export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = `
|
|
77
|
+
export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = `To delegate work, call with { agent, task }, { tasks }, or { chain }; omit action. Use action only for management/control actions listed below. Use exactly one mode per call.
|
|
78
78
|
|
|
79
79
|
EXECUTE:
|
|
80
80
|
• Before execution, call { action: "list" }; run only executable/non-disabled configured agents/chains.
|
|
@@ -161,7 +161,7 @@ export function applyIntercomBridgeToAgent(agent: AgentConfig, bridge: IntercomB
|
|
|
161
161
|
if (!bridge.active || !bridge.orchestratorTarget) return agent;
|
|
162
162
|
|
|
163
163
|
const bridgeTools = ["intercom", "contact_supervisor"];
|
|
164
|
-
const tools = agent.tools
|
|
164
|
+
const tools = agent.tools && agent.tools.length > 0
|
|
165
165
|
? [...agent.tools, ...bridgeTools.filter((tool) => !agent.tools?.includes(tool))]
|
|
166
166
|
: agent.tools;
|
|
167
167
|
const instruction = bridge.instruction;
|