@trim21/personal-pi-extensions 0.1.585 → 0.1.587
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/package.json +1 -1
- package/src/bwrap/core.ts +9 -0
- package/src/bwrap/runtime.ts +57 -19
- package/src/spawn-agent-agents.ts +20 -0
- package/src/spawn-agent.ts +60 -24
package/package.json
CHANGED
package/src/bwrap/core.ts
CHANGED
|
@@ -134,6 +134,15 @@ export function resolveBwrap(config: BwrapConfig): ResolvedBwrap {
|
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* 把配置文件形状(可缺省、容忍未知字段)补全成完整 BwrapConfig:合并默认值后走
|
|
139
|
+
* 同一套 schema 校验。与 loadBwrapConfig 的文件路径共用同一条补全语义,供
|
|
140
|
+
* frontmatter 等非文件来源的配置使用(配置的加载/构造与沙箱创建解耦)。
|
|
141
|
+
*/
|
|
142
|
+
export function completeBwrapConfig(file: BwrapConfigFile): BwrapConfig {
|
|
143
|
+
return Value.Parse(bwrapConfigSchema, deepMerge(DEFAULT_CONFIG, file));
|
|
144
|
+
}
|
|
145
|
+
|
|
137
146
|
function deepMerge(base: BwrapConfig, overrides: Partial<BwrapConfig>): BwrapConfig {
|
|
138
147
|
return {
|
|
139
148
|
mode: overrides.mode ?? base.mode,
|
package/src/bwrap/runtime.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { type SelectAction, selectMultiple, selectWithOptionalInput } from "../l
|
|
|
23
23
|
import { type ApprovalRule, evaluateBashApproval, matchRule } from "./approval-rules.js";
|
|
24
24
|
import { commandPatternsFor } from "./approval-suggest.js";
|
|
25
25
|
import {
|
|
26
|
+
type BwrapConfig,
|
|
26
27
|
type BwrapMode,
|
|
27
28
|
findBwrap,
|
|
28
29
|
findMihomo,
|
|
@@ -308,12 +309,17 @@ const SANDBOX_ESCAPE_HATCH =
|
|
|
308
309
|
* 命令没经沙箱(allow-all、审批通过的全权限、Windows)时返回 undefined:
|
|
309
310
|
* 没有沙箱就没什么可提示的。
|
|
310
311
|
*/
|
|
311
|
-
export function describeSandbox(
|
|
312
|
+
export function describeSandbox(
|
|
313
|
+
resolved: ResolvedBwrap,
|
|
314
|
+
unsandboxed: boolean,
|
|
315
|
+
fixed = false,
|
|
316
|
+
): string | undefined {
|
|
312
317
|
if (unsandboxed) return undefined;
|
|
313
318
|
return [
|
|
314
319
|
"<system-reminder>",
|
|
315
320
|
`This command ran in a sandbox: ${describeLimits(resolved)}.`,
|
|
316
|
-
|
|
321
|
+
// 固定沙箱的提权请求会被直接拒绝,再提示 escape hatch 只会诱导无效请求
|
|
322
|
+
...(fixed ? [] : [SANDBOX_ESCAPE_HATCH]),
|
|
317
323
|
"</system-reminder>",
|
|
318
324
|
].join("\n");
|
|
319
325
|
}
|
|
@@ -347,9 +353,16 @@ export class BwrapRuntime {
|
|
|
347
353
|
* 独立入口各持一份、经 pi.events 同步。
|
|
348
354
|
*/
|
|
349
355
|
private readonly policy: RequestPolicy;
|
|
356
|
+
/**
|
|
357
|
+
* 创建时传入的完整配置:加载配置与创建解耦——有它就不再读盘,且沙箱视为固定
|
|
358
|
+
* (不注册 /bwrap-* 命令、非沙盒请求一律拒绝)。undefined 表示从全局/项目
|
|
359
|
+
* bwrap.json 加载(主会话入口的现状行为)。
|
|
360
|
+
*/
|
|
361
|
+
private readonly config: BwrapConfig | undefined;
|
|
350
362
|
|
|
351
|
-
constructor(policy: RequestPolicy) {
|
|
363
|
+
constructor(policy: RequestPolicy, config?: BwrapConfig) {
|
|
352
364
|
this.policy = policy;
|
|
365
|
+
this.config = config;
|
|
353
366
|
}
|
|
354
367
|
|
|
355
368
|
setup(pi: ExtensionAPI): void {
|
|
@@ -394,11 +407,18 @@ export class BwrapRuntime {
|
|
|
394
407
|
pi.on("before_agent_start", (event, ctx) => {
|
|
395
408
|
const runtime = this.resolve(ctx);
|
|
396
409
|
const isWindows = process.platform === "win32";
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
410
|
+
let modeText: string;
|
|
411
|
+
if (ctx.hasUI) {
|
|
412
|
+
if (isWindows) {
|
|
413
|
+
modeText = "Every bash command requires user approval before it runs.";
|
|
414
|
+
} else if (this.config === undefined) {
|
|
415
|
+
modeText = `Current bwrap mode: **${runtime.mode}**. The bwrap runtime selects sandboxing and, when requested, user approval for unsandboxed execution.`;
|
|
416
|
+
} else {
|
|
417
|
+
modeText = `Current bwrap mode: **${runtime.mode}**, fixed by the agent's declared sandbox config. Unsandboxed execution requests are refused without approval.`;
|
|
418
|
+
}
|
|
419
|
+
} else {
|
|
420
|
+
modeText = `Current bwrap mode: **${runtime.mode}**. This headless session cannot ask for approval: commands that require user approval are denied.`;
|
|
421
|
+
}
|
|
402
422
|
const unavailableText =
|
|
403
423
|
!isWindows && this.bwrapUnavailable
|
|
404
424
|
? " bwrap is unavailable (binary not found): bash commands are refused unless the user explicitly approves unsandboxed execution."
|
|
@@ -414,11 +434,16 @@ export class BwrapRuntime {
|
|
|
414
434
|
};
|
|
415
435
|
});
|
|
416
436
|
|
|
417
|
-
|
|
437
|
+
// 固定沙箱(创建时显式传入配置)不注册 /bwrap-* 命令:切模式与恢复提权审批
|
|
438
|
+
// 都可能放宽声明的沙箱
|
|
439
|
+
if (this.config === undefined) this.registerCommands(pi);
|
|
418
440
|
}
|
|
419
441
|
|
|
420
442
|
setMode(cwd: string, mode: BwrapMode): ResolvedBwrap {
|
|
421
|
-
this.resolved =
|
|
443
|
+
this.resolved =
|
|
444
|
+
this.config === undefined
|
|
445
|
+
? loadSandboxConfig({ workspace: cwd, mode })
|
|
446
|
+
: resolveBwrap({ ...this.config, mode });
|
|
422
447
|
return this.resolved;
|
|
423
448
|
}
|
|
424
449
|
|
|
@@ -453,9 +478,13 @@ export class BwrapRuntime {
|
|
|
453
478
|
// 用户在审批框选「Run this in sandbox」拒绝提权:命令降级为沙盒内执行
|
|
454
479
|
let userChoseSandbox = false;
|
|
455
480
|
if (needsApproval && runtime.bwrapEnabled) {
|
|
456
|
-
// /bwrap-deny-request
|
|
457
|
-
//
|
|
458
|
-
|
|
481
|
+
// /bwrap-deny-request 与固定沙箱(创建时显式传入配置):非沙盒请求直接
|
|
482
|
+
// 拒绝——审批规则与审批框都不再参与,拒绝文案与用户点 Deny 相同
|
|
483
|
+
// (deny-request 可用 /bwrap-allow-request 恢复审批,固定沙箱不可恢复)。
|
|
484
|
+
if (
|
|
485
|
+
request.requestFullAccess === true &&
|
|
486
|
+
(this.config !== undefined || this.policy.deniesRequests())
|
|
487
|
+
) {
|
|
459
488
|
throw new Error(UNSANDBOXED_DENIED);
|
|
460
489
|
}
|
|
461
490
|
// 先按 approvalRules 自动判定:allow 直接放行,deny 直接拒绝,未命中才弹框
|
|
@@ -484,7 +513,9 @@ export class BwrapRuntime {
|
|
|
484
513
|
}
|
|
485
514
|
// 命令失败时附带的沙箱状态(写边界 + 网络层级),由上层拼进错误文本。
|
|
486
515
|
// 用户选「Run this in sandbox」时不附:沙箱作用域改由 sandboxReminder 说明
|
|
487
|
-
const sandboxHint = userChoseSandbox
|
|
516
|
+
const sandboxHint = userChoseSandbox
|
|
517
|
+
? undefined
|
|
518
|
+
: describeSandbox(runtime, local, this.config !== undefined);
|
|
488
519
|
// 提权被拒、命令在沙盒内执行的系统提醒:成功与失败都由上层附上
|
|
489
520
|
const sandboxReminder = userChoseSandbox ? describeSandboxChoice(runtime) : undefined;
|
|
490
521
|
// 计时起点放在审批之后:审批弹窗的等待时长属于用户 UI 操作,不是命令运行时间
|
|
@@ -592,8 +623,7 @@ export class BwrapRuntime {
|
|
|
592
623
|
}
|
|
593
624
|
|
|
594
625
|
private resolve(ctx: Pick<ExtensionContext, "cwd">): ResolvedBwrap {
|
|
595
|
-
|
|
596
|
-
if (!this.resolved) this.resolved = resolveBwrap(config);
|
|
626
|
+
if (!this.resolved) this.resolved = resolveBwrap(this.config ?? loadBwrapConfig(ctx.cwd));
|
|
597
627
|
return this.resolved;
|
|
598
628
|
}
|
|
599
629
|
|
|
@@ -960,7 +990,15 @@ export class BwrapRuntime {
|
|
|
960
990
|
}
|
|
961
991
|
}
|
|
962
992
|
|
|
963
|
-
/**
|
|
964
|
-
|
|
965
|
-
|
|
993
|
+
/**
|
|
994
|
+
* 缺省自建一份不跨入口同步的策略;入口通常显式传入 `createRequestPolicy(pi.events)`。
|
|
995
|
+
* config 为完整配置(completeBwrapConfig 的产物):加载配置与创建解耦,传入后不再
|
|
996
|
+
* 读盘,且沙箱固定(不注册 /bwrap-* 命令、非沙盒请求直接拒绝)。缺省从全局/项目
|
|
997
|
+
* bwrap.json 加载。
|
|
998
|
+
*/
|
|
999
|
+
export function createBwrapRuntime(
|
|
1000
|
+
policy: RequestPolicy = createRequestPolicy(),
|
|
1001
|
+
config?: BwrapConfig,
|
|
1002
|
+
): BwrapRuntime {
|
|
1003
|
+
return new BwrapRuntime(policy, config);
|
|
966
1004
|
}
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
* provider: openai # optional; overrides the global default
|
|
17
17
|
* model: claude-haiku-4-5 # optional; overrides the global default
|
|
18
18
|
* thinkingLevel: high # optional; overrides the global default
|
|
19
|
+
* sandbox: # optional; fixed bwrap sandbox for the bash tool
|
|
20
|
+
* mode: readonly
|
|
21
|
+
* extraWritablePaths:
|
|
22
|
+
* - /tmp
|
|
19
23
|
* ---
|
|
20
24
|
* System prompt for the agent goes here.
|
|
21
25
|
*
|
|
@@ -24,6 +28,16 @@
|
|
|
24
28
|
* omitted, the subagent runs with the read-only default toolset from the
|
|
25
29
|
* spawn-agent config (read/grep/find/ls) unless overridden there.
|
|
26
30
|
*
|
|
31
|
+
* `sandbox` is a complete bwrap config (same shape as bwrap.json) that becomes
|
|
32
|
+
* the bash tool's fixed sandbox: it is parsed here and passed to the subagent's
|
|
33
|
+
* bwrap runtime directly (config loading is decoupled from sandbox creation),
|
|
34
|
+
* so the user's bwrap.json is not consulted for that agent. Unsandboxed
|
|
35
|
+
* execution requests are refused and /bwrap-* mode commands are not registered.
|
|
36
|
+
* Fields are optional and fall back to the standard bwrap defaults (e.g.
|
|
37
|
+
* `sandbox: {mode: readonly}` alone is enough for a read-only shell);
|
|
38
|
+
* `extraWritablePaths` is the writable escape hatch under a read-only mode.
|
|
39
|
+
* Without `sandbox`, the subagent's bash follows the user bwrap config.
|
|
40
|
+
*
|
|
27
41
|
* Global defaults for provider/model/thinkingLevel come from
|
|
28
42
|
* `~/.pi/agent/spawn-agent.json` (see loadSpawnAgentConfig); frontmatter
|
|
29
43
|
* fields take precedence over them, which in turn take precedence over the
|
|
@@ -38,6 +52,8 @@ import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
|
38
52
|
import { type Static, Type } from "typebox";
|
|
39
53
|
import { Value } from "typebox/value";
|
|
40
54
|
|
|
55
|
+
import { type BwrapConfig, bwrapConfigFileSchema, completeBwrapConfig } from "./bwrap/core.js";
|
|
56
|
+
|
|
41
57
|
/**
|
|
42
58
|
* Valid thinking levels, mirroring pi's CLI --thinking validation
|
|
43
59
|
* (VALID_THINKING_LEVELS). "off" disables thinking; "max" is deliberately
|
|
@@ -52,6 +68,7 @@ const agentFrontmatterSchema = Type.Object({
|
|
|
52
68
|
provider: Type.Optional(Type.String()),
|
|
53
69
|
model: Type.Optional(Type.String()),
|
|
54
70
|
thinkingLevel: Type.Optional(Type.Union(THINKING_LEVELS.map((level) => Type.Literal(level)))),
|
|
71
|
+
sandbox: Type.Optional(bwrapConfigFileSchema),
|
|
55
72
|
});
|
|
56
73
|
|
|
57
74
|
function parseAgentFrontmatter(frontmatter: unknown) {
|
|
@@ -71,6 +88,8 @@ export interface AgentConfig {
|
|
|
71
88
|
model?: string;
|
|
72
89
|
/** Thinking level, applied via --thinking. */
|
|
73
90
|
thinkingLevel?: (typeof THINKING_LEVELS)[number];
|
|
91
|
+
/** bash 的完整沙箱配置(bwrap.json 同构);undefined 表示跟随用户 bwrap 配置。 */
|
|
92
|
+
sandbox?: BwrapConfig;
|
|
74
93
|
systemPrompt: string;
|
|
75
94
|
filePath: string;
|
|
76
95
|
}
|
|
@@ -118,6 +137,7 @@ export function discoverAgents(dir = join(getAgentDir(), "agents")): AgentConfig
|
|
|
118
137
|
provider: fm.provider,
|
|
119
138
|
model: fm.model,
|
|
120
139
|
thinkingLevel: fm.thinkingLevel,
|
|
140
|
+
sandbox: fm.sandbox ? completeBwrapConfig(fm.sandbox) : undefined,
|
|
121
141
|
systemPrompt: body,
|
|
122
142
|
filePath,
|
|
123
143
|
});
|
package/src/spawn-agent.ts
CHANGED
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
*
|
|
27
27
|
* Security default: without an explicit `tools:` in the frontmatter, the
|
|
28
28
|
* subagent only gets read-only tools (read/grep/find/ls) — no bash/write/edit.
|
|
29
|
+
* A frontmatter `sandbox:` gives the bash tool a fixed bwrap sandbox config
|
|
30
|
+
* (bwrap.json shape, e.g. mode readonly): it is passed to the subagent's bwrap
|
|
31
|
+
* runtime as the complete config, unsandboxed execution requests are refused,
|
|
32
|
+
* and /bwrap-* mode commands are not registered (see spawn-agent-agents.ts).
|
|
29
33
|
*/
|
|
30
34
|
|
|
31
35
|
import { existsSync } from "node:fs";
|
|
@@ -42,6 +46,7 @@ import {
|
|
|
42
46
|
type ExtensionAPI,
|
|
43
47
|
type ExtensionUIContext,
|
|
44
48
|
getAgentDir,
|
|
49
|
+
type InlineExtension,
|
|
45
50
|
ModelRuntime,
|
|
46
51
|
type PromptOptions,
|
|
47
52
|
SessionManager,
|
|
@@ -51,7 +56,11 @@ import {
|
|
|
51
56
|
} from "@earendil-works/pi-coding-agent";
|
|
52
57
|
import { Type } from "typebox";
|
|
53
58
|
|
|
59
|
+
import { type BwrapRuntime, createBwrapRuntime } from "./bwrap/runtime.js";
|
|
60
|
+
import { registerShellTools } from "./claude-code/shell.js";
|
|
54
61
|
import { type ToolPendant } from "./lib/pendant.js";
|
|
62
|
+
import { createRequestPolicy } from "./lib/request-policy.js";
|
|
63
|
+
import opencodeBash from "./opencode/bash.js";
|
|
55
64
|
import {
|
|
56
65
|
type AgentConfig,
|
|
57
66
|
applyAgentDefaults,
|
|
@@ -87,10 +96,11 @@ const SETTINGS_PATH = join(getAgentDir(), "settings.json");
|
|
|
87
96
|
* `additionalExtensionPaths` so the subagent uses the enhanced implementation
|
|
88
97
|
* instead of the built-in one.
|
|
89
98
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
99
|
+
* `bash` (opencode) and `Bash` (claude-code) are deliberately not in this map:
|
|
100
|
+
* they are injected via an inline extension factory (subagentShellExtension)
|
|
101
|
+
* instead, because their bwrap runtime carries the agent's declared sandbox
|
|
102
|
+
* config — a path-based override has no channel to pass per-agent config
|
|
103
|
+
* (see createSubagentSession).
|
|
94
104
|
* (Workspace write protection is embedded in the opencode write/edit tools.)
|
|
95
105
|
*
|
|
96
106
|
* Claude Code style tools (capitalized names) map to their claude-code
|
|
@@ -111,7 +121,6 @@ const TOOL_EXTENSION_OVERRIDES: Record<string, string> = {
|
|
|
111
121
|
read: "opencode/files.ts",
|
|
112
122
|
edit: "opencode/files.ts",
|
|
113
123
|
write: "opencode/files.ts",
|
|
114
|
-
bash: "opencode/bash.ts",
|
|
115
124
|
grep: "opencode/grep.ts",
|
|
116
125
|
glob: "opencode/glob.ts",
|
|
117
126
|
Grep: "claude-code/grep.ts",
|
|
@@ -139,7 +148,8 @@ interface UsageStats {
|
|
|
139
148
|
turns: number;
|
|
140
149
|
}
|
|
141
150
|
|
|
142
|
-
|
|
151
|
+
/** 子 agent 的一次运行结果:runAgent 的返回值,也是 details.result 的内容。 */
|
|
152
|
+
interface SubagentResult {
|
|
143
153
|
agent: string;
|
|
144
154
|
task: string;
|
|
145
155
|
exitCode: number;
|
|
@@ -149,6 +159,11 @@ interface SubagentDetails {
|
|
|
149
159
|
model?: string;
|
|
150
160
|
stopReason?: string;
|
|
151
161
|
errorMessage?: string;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
interface SubagentDetails {
|
|
165
|
+
/** 子 agent 运行结果(除 pendant 外的字段都收在这里)。 */
|
|
166
|
+
result: SubagentResult;
|
|
152
167
|
/** 折叠 markdown 面板:父 agent 的 prompt 与父 agent 看到的子 agent 结果。 */
|
|
153
168
|
pendant?: ToolPendant;
|
|
154
169
|
}
|
|
@@ -172,7 +187,7 @@ function getFinalOutput(messages: AgentMessage[]): string {
|
|
|
172
187
|
* stderr 截断到尾部(错误信息通常在最后)。全空时保底 "(no output)",
|
|
173
188
|
* 避免只回一个 exit code。
|
|
174
189
|
*/
|
|
175
|
-
export function formatSubagentError(result:
|
|
190
|
+
export function formatSubagentError(result: SubagentResult): { reason: string; message: string } {
|
|
176
191
|
const reason =
|
|
177
192
|
result.stopReason ?? (result.exitCode === 0 ? "failed" : `exit ${result.exitCode}`);
|
|
178
193
|
const parts: string[] = [];
|
|
@@ -265,6 +280,20 @@ export function overrideExtensionPaths(tools: string[]): string[] {
|
|
|
265
280
|
return paths;
|
|
266
281
|
}
|
|
267
282
|
|
|
283
|
+
/**
|
|
284
|
+
* bash 类工具(opencode `bash` / cc `Bash`)的内联扩展工厂:bwrap runtime 随闭包
|
|
285
|
+
* 携带该 agent 的完整沙箱配置(frontmatter `sandbox`,已在 discoverAgents 补全成
|
|
286
|
+
* BwrapConfig)。路径式 override(additionalExtensionPaths)没有 per-agent 配置
|
|
287
|
+
* 通道,extensionFactories 的工厂闭包是 SDK 提供的唯一注入点。agent.sandbox 为
|
|
288
|
+
* undefined 时与各注册函数的默认构造完全一致(跟随用户 bwrap 配置)。
|
|
289
|
+
*/
|
|
290
|
+
export function subagentShellExtension(
|
|
291
|
+
agent: AgentConfig,
|
|
292
|
+
register: (pi: ExtensionAPI, runtime: BwrapRuntime) => void,
|
|
293
|
+
): InlineExtension {
|
|
294
|
+
return (pi) => register(pi, createBwrapRuntime(createRequestPolicy(pi.events), agent.sandbox));
|
|
295
|
+
}
|
|
296
|
+
|
|
268
297
|
/**
|
|
269
298
|
* Resolve the frontmatter model to a runtime Model. A "provider/model" string
|
|
270
299
|
* carries its own provider; a bare model id uses the declared provider, then
|
|
@@ -322,13 +351,18 @@ export async function createSubagentSession(
|
|
|
322
351
|
cwd: string,
|
|
323
352
|
parentUI: ExtensionUIContext | undefined,
|
|
324
353
|
): Promise<AgentSession> {
|
|
354
|
+
const tools = agent.tools ?? DEFAULT_TOOLS;
|
|
325
355
|
const settingsManager = SettingsManager.create(cwd, getAgentDir());
|
|
326
356
|
const loader = new DefaultResourceLoader({
|
|
327
357
|
cwd,
|
|
328
358
|
agentDir: getAgentDir(),
|
|
329
359
|
settingsManager,
|
|
330
360
|
noExtensions: true,
|
|
331
|
-
additionalExtensionPaths: overrideExtensionPaths(
|
|
361
|
+
additionalExtensionPaths: overrideExtensionPaths(tools),
|
|
362
|
+
extensionFactories: [
|
|
363
|
+
...(tools.includes("bash") ? [subagentShellExtension(agent, opencodeBash)] : []),
|
|
364
|
+
...(tools.includes("Bash") ? [subagentShellExtension(agent, registerShellTools)] : []),
|
|
365
|
+
],
|
|
332
366
|
appendSystemPrompt: agent.systemPrompt ? [agent.systemPrompt] : undefined,
|
|
333
367
|
});
|
|
334
368
|
await loader.reload();
|
|
@@ -338,7 +372,7 @@ export async function createSubagentSession(
|
|
|
338
372
|
cwd,
|
|
339
373
|
model: resolveModel(modelRuntime, agent, settingsManager),
|
|
340
374
|
thinkingLevel: agent.thinkingLevel,
|
|
341
|
-
tools
|
|
375
|
+
tools,
|
|
342
376
|
sessionManager: SessionManager.inMemory(cwd),
|
|
343
377
|
settingsManager,
|
|
344
378
|
resourceLoader: loader,
|
|
@@ -356,8 +390,8 @@ export async function runAgent(
|
|
|
356
390
|
onUpdate: OnUpdateCallback | undefined,
|
|
357
391
|
parentUI?: ExtensionUIContext,
|
|
358
392
|
createSession: SessionFactory = createSubagentSession,
|
|
359
|
-
): Promise<
|
|
360
|
-
const result:
|
|
393
|
+
): Promise<SubagentResult> {
|
|
394
|
+
const result: SubagentResult = {
|
|
361
395
|
agent: agent.name,
|
|
362
396
|
task,
|
|
363
397
|
exitCode: 0,
|
|
@@ -431,7 +465,7 @@ export async function runAgent(
|
|
|
431
465
|
const footer = usageLine ? `\`${name}\` ${usageLine}` : `\`${name}\``;
|
|
432
466
|
onUpdate?.({
|
|
433
467
|
content: [{ type: "text", text: [...logLines, footer].join("\n") }],
|
|
434
|
-
details: { ...result },
|
|
468
|
+
details: { result: { ...result } },
|
|
435
469
|
});
|
|
436
470
|
};
|
|
437
471
|
|
|
@@ -558,15 +592,17 @@ export default function spawnAgent(pi: ExtensionAPI) {
|
|
|
558
592
|
},
|
|
559
593
|
],
|
|
560
594
|
details: {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
595
|
+
result: {
|
|
596
|
+
agent: params.agent,
|
|
597
|
+
task: params.task,
|
|
598
|
+
exitCode: 1,
|
|
599
|
+
messages: [],
|
|
600
|
+
stderr: "",
|
|
601
|
+
usage: {
|
|
602
|
+
cost: 0,
|
|
603
|
+
contextTokens: 0,
|
|
604
|
+
turns: 0,
|
|
605
|
+
},
|
|
570
606
|
},
|
|
571
607
|
},
|
|
572
608
|
isError: true,
|
|
@@ -591,7 +627,7 @@ export default function spawnAgent(pi: ExtensionAPI) {
|
|
|
591
627
|
{ type: "text", text: `Subagent "${result.agent}" failed (${reason}):\n${message}` },
|
|
592
628
|
],
|
|
593
629
|
details: {
|
|
594
|
-
|
|
630
|
+
result,
|
|
595
631
|
pendant: {
|
|
596
632
|
subtitle: result.agent,
|
|
597
633
|
markdown: formatPendantMarkdown(params.task, message),
|
|
@@ -609,7 +645,7 @@ export default function spawnAgent(pi: ExtensionAPI) {
|
|
|
609
645
|
return {
|
|
610
646
|
content: [{ type: "text", text }],
|
|
611
647
|
details: {
|
|
612
|
-
|
|
648
|
+
result,
|
|
613
649
|
pendant: {
|
|
614
650
|
subtitle: result.agent,
|
|
615
651
|
markdown: formatPendantMarkdown(params.task, text),
|
|
@@ -631,7 +667,7 @@ export default function spawnAgent(pi: ExtensionAPI) {
|
|
|
631
667
|
},
|
|
632
668
|
|
|
633
669
|
renderResult(result, { expanded }, theme) {
|
|
634
|
-
const details = result.details;
|
|
670
|
+
const details = result.details.result;
|
|
635
671
|
const isError =
|
|
636
672
|
details.exitCode !== 0 ||
|
|
637
673
|
details.stopReason === "error" ||
|