@trim21/personal-pi-extensions 0.1.584 → 0.1.586
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/runtime.ts +76 -17
- package/src/claude-code/shell.ts +11 -3
- package/src/opencode/bash.ts +2 -0
- package/src/spawn-agent.ts +25 -17
package/package.json
CHANGED
package/src/bwrap/runtime.ts
CHANGED
|
@@ -37,6 +37,8 @@ import { loadSandboxConfig, runInSandbox } from "./sandbox.js";
|
|
|
37
37
|
|
|
38
38
|
/** 全权限审批对话框的选项 label(也作为 switch 匹配键与测试引用)。 */
|
|
39
39
|
export const ALLOW_ONCE = "Allow once";
|
|
40
|
+
/** 拒绝提权、命令降级为在沙盒内执行。 */
|
|
41
|
+
export const RUN_IN_SANDBOX = "Run this in sandbox";
|
|
40
42
|
export const DENY = "Deny";
|
|
41
43
|
export const DENY_WITH_REASON = "Deny with reason";
|
|
42
44
|
/** 第一层的折叠入口:进入按 pattern 勾选持久化规则的子菜单。 */
|
|
@@ -49,7 +51,7 @@ export const BACK = "Back";
|
|
|
49
51
|
* 决定放行、拒绝并持久化勾选的规则,UI 层不直接产生副作用。
|
|
50
52
|
*/
|
|
51
53
|
export interface FullAccessUIDecision {
|
|
52
|
-
/** 用户选择的动作 label(ALLOW_ONCE / DENY / DENY_WITH_REASON)。 */
|
|
54
|
+
/** 用户选择的动作 label(ALLOW_ONCE / RUN_IN_SANDBOX / DENY / DENY_WITH_REASON)。 */
|
|
53
55
|
result: string;
|
|
54
56
|
/** 用户勾选、需持久化为 allow 规则的 pattern;未勾选时为空数组。 */
|
|
55
57
|
foreverApprovedPattern: string[];
|
|
@@ -57,6 +59,12 @@ export interface FullAccessUIDecision {
|
|
|
57
59
|
reason?: string;
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
/**
|
|
63
|
+
* 全权限审批的放行结果:允许一次性全权限执行,或用户拒绝提权、
|
|
64
|
+
* 命令降级为在沙盒内执行(输出后附系统提醒)。
|
|
65
|
+
*/
|
|
66
|
+
type FullAccessGrant = "full-access" | "sandbox";
|
|
67
|
+
|
|
60
68
|
export interface BwrapExecutionRequest {
|
|
61
69
|
toolCallId: string;
|
|
62
70
|
command: string;
|
|
@@ -78,6 +86,8 @@ export interface BwrapExecutionRequest {
|
|
|
78
86
|
*/
|
|
79
87
|
export interface BwrapExecutionResult {
|
|
80
88
|
exitCode: number | null;
|
|
89
|
+
/** 用户选「Run this in sandbox」拒绝提权时的系统提醒,成功与失败都由上层附上;其余情况缺省。 */
|
|
90
|
+
sandboxReminder?: string;
|
|
81
91
|
/** 沙箱状态说明(写边界 + 网络层级),失败时由上层工具作为独立信息块附上;未沙箱执行时为 undefined。 */
|
|
82
92
|
sandboxHint: string | undefined;
|
|
83
93
|
/** 截断后的输出(尾部),未截断时为完整输出;空输出为空字符串。 */
|
|
@@ -102,6 +112,8 @@ export class BashInterruptedError extends Error {
|
|
|
102
112
|
readonly kind: "timeout" | "aborted";
|
|
103
113
|
readonly partial: BashExecutionPartial;
|
|
104
114
|
readonly sandboxHint: string | undefined;
|
|
115
|
+
/** 用户选「Run this in sandbox」拒绝提权时的系统提醒;其余情况为 undefined。 */
|
|
116
|
+
readonly sandboxReminder: string | undefined;
|
|
105
117
|
/**
|
|
106
118
|
* 命令实际运行时长(毫秒):从审批结束、命令真正开始执行算到终止,
|
|
107
119
|
* 不含审批弹窗等用户 UI 交互耗时。
|
|
@@ -115,11 +127,13 @@ export class BashInterruptedError extends Error {
|
|
|
115
127
|
sandboxHint: string | undefined,
|
|
116
128
|
elapsedMs: number,
|
|
117
129
|
cause: unknown,
|
|
130
|
+
sandboxReminder?: string,
|
|
118
131
|
) {
|
|
119
132
|
super(message, { cause });
|
|
120
133
|
this.kind = kind;
|
|
121
134
|
this.partial = partial;
|
|
122
135
|
this.sandboxHint = sandboxHint;
|
|
136
|
+
this.sandboxReminder = sandboxReminder;
|
|
123
137
|
this.elapsedMs = elapsedMs;
|
|
124
138
|
// 对齐标准错误分类:中断=AbortError(用户取消),超时=TimeoutError
|
|
125
139
|
this.name = kind === "aborted" ? "AbortError" : "TimeoutError";
|
|
@@ -270,6 +284,12 @@ function describeNetwork(resolved: ResolvedBwrap): string {
|
|
|
270
284
|
return "network access is unrestricted";
|
|
271
285
|
}
|
|
272
286
|
|
|
287
|
+
/** 沙箱作用域一句话:写边界 + 网络层级(沙箱状态块与沙盒内执行提醒共用)。 */
|
|
288
|
+
function describeLimits(resolved: ResolvedBwrap): string {
|
|
289
|
+
const writes = resolved.mode === "readonly" ? "the filesystem is read-only" : SANDBOX_WRITE_RULES;
|
|
290
|
+
return `${writes}; ${describeNetwork(resolved)}`;
|
|
291
|
+
}
|
|
292
|
+
|
|
273
293
|
/**
|
|
274
294
|
* 用户无理由拒绝非沙盒请求的文案。`/bwrap-deny-request` 生效时与无 UI 会话里
|
|
275
295
|
* 需要审批的请求,都必须与用户在审批框点 Deny 完全一致——模型看到的是一次
|
|
@@ -290,15 +310,28 @@ const SANDBOX_ESCAPE_HATCH =
|
|
|
290
310
|
*/
|
|
291
311
|
export function describeSandbox(resolved: ResolvedBwrap, unsandboxed: boolean): string | undefined {
|
|
292
312
|
if (unsandboxed) return undefined;
|
|
293
|
-
const writes = resolved.mode === "readonly" ? "the filesystem is read-only" : SANDBOX_WRITE_RULES;
|
|
294
313
|
return [
|
|
295
314
|
"<system-reminder>",
|
|
296
|
-
`This command ran in a sandbox: ${
|
|
315
|
+
`This command ran in a sandbox: ${describeLimits(resolved)}.`,
|
|
297
316
|
SANDBOX_ESCAPE_HATCH,
|
|
298
317
|
"</system-reminder>",
|
|
299
318
|
].join("\n");
|
|
300
319
|
}
|
|
301
320
|
|
|
321
|
+
/**
|
|
322
|
+
* 用户在审批框选「Run this in sandbox」拒绝提权后,命令输出后附带的系统提醒:
|
|
323
|
+
* 说明命令在沙盒内执行及其作用域,成功与失败都附上。此时 describeSandbox 的
|
|
324
|
+
* 状态块不再附——提权请求刚被用户拒绝,再提示 dangerouslyDisableSandbox 只会
|
|
325
|
+
* 诱导重复请求,作用域描述由本提醒承担。
|
|
326
|
+
*/
|
|
327
|
+
export function describeSandboxChoice(resolved: ResolvedBwrap): string {
|
|
328
|
+
return [
|
|
329
|
+
"<system-reminder>",
|
|
330
|
+
`The user ran this command in the sandbox instead of approving unsandboxed execution: ${describeLimits(resolved)}.`,
|
|
331
|
+
"</system-reminder>",
|
|
332
|
+
].join("\n");
|
|
333
|
+
}
|
|
334
|
+
|
|
302
335
|
/** 失败结果里附加的沙箱状态块;未沙箱执行(hint 为 undefined)时没有这一块。 */
|
|
303
336
|
export function sandboxHintBlock(hint: string | undefined): { type: "text"; text: string }[] {
|
|
304
337
|
return hint === undefined ? [] : [{ type: "text", text: hint }];
|
|
@@ -417,6 +450,8 @@ export class BwrapRuntime {
|
|
|
417
450
|
// 需要人工审批:非 Windows 仅 requestFullAccess;Windows 上默认所有命令
|
|
418
451
|
// (allow-all 模式是显式 opt-out,仍直接执行)。
|
|
419
452
|
const needsApproval = request.requestFullAccess === true || (isWindows && runtime.bwrapEnabled);
|
|
453
|
+
// 用户在审批框选「Run this in sandbox」拒绝提权:命令降级为沙盒内执行
|
|
454
|
+
let userChoseSandbox = false;
|
|
420
455
|
if (needsApproval && runtime.bwrapEnabled) {
|
|
421
456
|
// /bwrap-deny-request:非沙盒请求直接拒绝——审批规则与审批框都不再参与,
|
|
422
457
|
// 拒绝文案与用户点 Deny 相同,直到用户用 /bwrap-allow-request 恢复审批。
|
|
@@ -429,19 +464,29 @@ export class BwrapRuntime {
|
|
|
429
464
|
throw new Error(`Command denied by bwrap approval rule: ${request.command}`);
|
|
430
465
|
}
|
|
431
466
|
if (decision === undefined) {
|
|
432
|
-
|
|
467
|
+
userChoseSandbox =
|
|
468
|
+
(await this.approveFullAccess(
|
|
469
|
+
request.ctx,
|
|
470
|
+
request.command,
|
|
471
|
+
request.description,
|
|
472
|
+
execCwd,
|
|
473
|
+
)) === "sandbox";
|
|
433
474
|
}
|
|
434
475
|
}
|
|
435
|
-
// 不经沙箱的三种情形:Windows(无 bubblewrap)、审批通过的全权限、allow-all
|
|
436
|
-
|
|
476
|
+
// 不经沙箱的三种情形:Windows(无 bubblewrap)、审批通过的全权限、allow-all 模式;
|
|
477
|
+
// 例外是用户选「Run this in sandbox」:提权被拒,命令照常进沙箱
|
|
478
|
+
const local = !userChoseSandbox && (isWindows || needsApproval || !runtime.bwrapEnabled);
|
|
437
479
|
// mihomoPath 是 ResolvedBwrap 的 override 语义:首次 net-allowlist 执行时解析
|
|
438
480
|
// 一次存入私有字段,之后写回 resolved 直达 createNetworkStack,不逐命令扫描 PATH
|
|
439
481
|
if (runtime.network && runtime.networkAllowlist.length > 0) {
|
|
440
482
|
runtime.mihomoPath ??= this.mihomoPath ?? findMihomo();
|
|
441
483
|
this.mihomoPath = runtime.mihomoPath;
|
|
442
484
|
}
|
|
443
|
-
// 命令失败时附带的沙箱状态(写边界 +
|
|
444
|
-
|
|
485
|
+
// 命令失败时附带的沙箱状态(写边界 + 网络层级),由上层拼进错误文本。
|
|
486
|
+
// 用户选「Run this in sandbox」时不附:沙箱作用域改由 sandboxReminder 说明
|
|
487
|
+
const sandboxHint = userChoseSandbox ? undefined : describeSandbox(runtime, local);
|
|
488
|
+
// 提权被拒、命令在沙盒内执行的系统提醒:成功与失败都由上层附上
|
|
489
|
+
const sandboxReminder = userChoseSandbox ? describeSandboxChoice(runtime) : undefined;
|
|
445
490
|
// 计时起点放在审批之后:审批弹窗的等待时长属于用户 UI 操作,不是命令运行时间
|
|
446
491
|
const startedAt = Date.now();
|
|
447
492
|
await using output = new BashOutput(request.ctx.sessionManager.getSessionId());
|
|
@@ -477,6 +522,7 @@ export class BwrapRuntime {
|
|
|
477
522
|
return {
|
|
478
523
|
exitCode,
|
|
479
524
|
sandboxHint,
|
|
525
|
+
...(sandboxReminder && { sandboxReminder }),
|
|
480
526
|
output: partial.output,
|
|
481
527
|
...(partial.fullOutputPath && { fullOutputPath: partial.fullOutputPath }),
|
|
482
528
|
truncation: partial.truncation,
|
|
@@ -499,6 +545,7 @@ export class BwrapRuntime {
|
|
|
499
545
|
sandboxHint,
|
|
500
546
|
elapsedMs,
|
|
501
547
|
error,
|
|
548
|
+
sandboxReminder,
|
|
502
549
|
);
|
|
503
550
|
}
|
|
504
551
|
// 中断识别:优先 name=AbortError(throwIfAborted/signal.reason),
|
|
@@ -512,6 +559,7 @@ export class BwrapRuntime {
|
|
|
512
559
|
sandboxHint,
|
|
513
560
|
elapsedMs,
|
|
514
561
|
error,
|
|
562
|
+
sandboxReminder,
|
|
515
563
|
);
|
|
516
564
|
}
|
|
517
565
|
throw error;
|
|
@@ -554,7 +602,7 @@ export class BwrapRuntime {
|
|
|
554
602
|
command: string,
|
|
555
603
|
reason: string | undefined,
|
|
556
604
|
execCwd: string,
|
|
557
|
-
): Promise<
|
|
605
|
+
): Promise<FullAccessGrant> {
|
|
558
606
|
// hasUI 判定推迟到审批时刻:无 UI 会话弹不了审批框,按用户点 Deny 的标准文案拒绝
|
|
559
607
|
if (!ctx.hasUI) throw new Error(UNSANDBOXED_DENIED);
|
|
560
608
|
const decision = await this.approveFullAccessUI(ctx, command, reason, execCwd);
|
|
@@ -565,11 +613,10 @@ export class BwrapRuntime {
|
|
|
565
613
|
}
|
|
566
614
|
const { result, foreverApprovedPattern } = decision;
|
|
567
615
|
switch (result) {
|
|
568
|
-
case
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
return;
|
|
616
|
+
case RUN_IN_SANDBOX: {
|
|
617
|
+
// 用户拒绝提权、改为沙盒内执行:allow 规则意味着自动放行非沙盒执行,
|
|
618
|
+
// 与该选择矛盾,即使在子菜单勾选了 pattern 也不持久化
|
|
619
|
+
return "sandbox";
|
|
573
620
|
}
|
|
574
621
|
case DENY: {
|
|
575
622
|
if (foreverApprovedPattern.length > 0) {
|
|
@@ -588,6 +635,16 @@ export class BwrapRuntime {
|
|
|
588
635
|
: "User denied command execution.",
|
|
589
636
|
);
|
|
590
637
|
}
|
|
638
|
+
case ALLOW_ONCE: {
|
|
639
|
+
if (foreverApprovedPattern.length > 0) {
|
|
640
|
+
await this.persistAllowRule(ctx, command, foreverApprovedPattern);
|
|
641
|
+
}
|
|
642
|
+
return "full-access";
|
|
643
|
+
}
|
|
644
|
+
default: {
|
|
645
|
+
// UI 层只会产出上面几种 label;未识别的按历史行为放行,不持久化规则
|
|
646
|
+
return "full-access";
|
|
647
|
+
}
|
|
591
648
|
}
|
|
592
649
|
}
|
|
593
650
|
|
|
@@ -647,6 +704,7 @@ export class BwrapRuntime {
|
|
|
647
704
|
// 追加折叠入口,进入子菜单逐项勾选。
|
|
648
705
|
const actions: SelectAction[] = [
|
|
649
706
|
{ label: ALLOW_ONCE },
|
|
707
|
+
{ label: RUN_IN_SANDBOX },
|
|
650
708
|
{ label: DENY },
|
|
651
709
|
{ label: DENY_WITH_REASON, inputPrompt: "Why was this denied?" },
|
|
652
710
|
];
|
|
@@ -661,9 +719,10 @@ export class BwrapRuntime {
|
|
|
661
719
|
description,
|
|
662
720
|
].join("\n");
|
|
663
721
|
|
|
664
|
-
// 两层循环:子菜单勾选后回到主决策,直到用户在 Allow once /
|
|
665
|
-
//
|
|
666
|
-
//
|
|
722
|
+
// 两层循环:子菜单勾选后回到主决策,直到用户在 Allow once / Run this in sandbox /
|
|
723
|
+
// Deny 系列中做出选择。勾选的规则在放行时持久化;Deny 系列同样持久化(用户确认
|
|
724
|
+
// 该模式可信,只是本次命令不执行);Run this in sandbox 不持久化。
|
|
725
|
+
// 子菜单关闭 = 返回主决策;主决策关闭 = 取消(上层按拒绝处理)。
|
|
667
726
|
let selected: string[] = [];
|
|
668
727
|
for (;;) {
|
|
669
728
|
const pending =
|
package/src/claude-code/shell.ts
CHANGED
|
@@ -75,7 +75,7 @@ export function formatBashSuccess(result: Awaited<ReturnType<BwrapRuntime["execu
|
|
|
75
75
|
const { output, truncation, fullOutputPath } = result;
|
|
76
76
|
const text = appendTruncationNotice(output || "(no output)", truncation, fullOutputPath);
|
|
77
77
|
return {
|
|
78
|
-
content: [{ type: "text", text }],
|
|
78
|
+
content: [{ type: "text", text }, ...sandboxHintBlock(result.sandboxReminder)],
|
|
79
79
|
details: fullOutputPath && truncation.truncated ? { truncation, fullOutputPath } : undefined,
|
|
80
80
|
};
|
|
81
81
|
}
|
|
@@ -158,7 +158,10 @@ export function registerShellTools(
|
|
|
158
158
|
// 不含审批弹窗等 UI 交互
|
|
159
159
|
const status = `Command aborted by user after ${formatElapsedSeconds(error.elapsedMs)}`;
|
|
160
160
|
return {
|
|
161
|
-
content: [
|
|
161
|
+
content: [
|
|
162
|
+
{ type: "text", text: text ? `${text}\n\n${status}` : status },
|
|
163
|
+
...sandboxHintBlock(error.sandboxReminder),
|
|
164
|
+
],
|
|
162
165
|
details: undefined,
|
|
163
166
|
};
|
|
164
167
|
}
|
|
@@ -166,7 +169,11 @@ export function registerShellTools(
|
|
|
166
169
|
? `${text}\n\nCommand timed out after ${timeout} milliseconds`
|
|
167
170
|
: `Command timed out after ${timeout} milliseconds`;
|
|
168
171
|
return {
|
|
169
|
-
content: [
|
|
172
|
+
content: [
|
|
173
|
+
{ type: "text", text: full },
|
|
174
|
+
...sandboxHintBlock(error.sandboxHint),
|
|
175
|
+
...sandboxHintBlock(error.sandboxReminder),
|
|
176
|
+
],
|
|
170
177
|
details: undefined,
|
|
171
178
|
};
|
|
172
179
|
}
|
|
@@ -184,6 +191,7 @@ export function registerShellTools(
|
|
|
184
191
|
content: [
|
|
185
192
|
{ type: "text", text: formatBashError(result.exitCode, full) },
|
|
186
193
|
...sandboxHintBlock(result.sandboxHint),
|
|
194
|
+
...sandboxHintBlock(result.sandboxReminder),
|
|
187
195
|
],
|
|
188
196
|
details: undefined,
|
|
189
197
|
};
|
package/src/opencode/bash.ts
CHANGED
|
@@ -124,6 +124,7 @@ export default function opencodeBash(
|
|
|
124
124
|
content: [
|
|
125
125
|
{ type: "text" as const, text: full },
|
|
126
126
|
...sandboxHintBlock(error.kind === "timeout" ? error.sandboxHint : undefined),
|
|
127
|
+
...sandboxHintBlock(error.sandboxReminder),
|
|
127
128
|
],
|
|
128
129
|
details: error.kind === "timeout" ? { timeout: true } : {},
|
|
129
130
|
};
|
|
@@ -141,6 +142,7 @@ export default function opencodeBash(
|
|
|
141
142
|
{ type: "text" as const, text: `Command exited with code ${result.exitCode}.` },
|
|
142
143
|
// 失败可能是被沙箱的写边界或网络限制挡住的,附一块沙箱状态
|
|
143
144
|
...sandboxHintBlock(failed ? result.sandboxHint : undefined),
|
|
145
|
+
...sandboxHintBlock(result.sandboxReminder),
|
|
144
146
|
],
|
|
145
147
|
details: {
|
|
146
148
|
exitCode: result.exitCode,
|
package/src/spawn-agent.ts
CHANGED
|
@@ -139,7 +139,8 @@ interface UsageStats {
|
|
|
139
139
|
turns: number;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
/** 子 agent 的一次运行结果:runAgent 的返回值,也是 details.result 的内容。 */
|
|
143
|
+
interface SubagentResult {
|
|
143
144
|
agent: string;
|
|
144
145
|
task: string;
|
|
145
146
|
exitCode: number;
|
|
@@ -149,6 +150,11 @@ interface SubagentDetails {
|
|
|
149
150
|
model?: string;
|
|
150
151
|
stopReason?: string;
|
|
151
152
|
errorMessage?: string;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
interface SubagentDetails {
|
|
156
|
+
/** 子 agent 运行结果(除 pendant 外的字段都收在这里)。 */
|
|
157
|
+
result: SubagentResult;
|
|
152
158
|
/** 折叠 markdown 面板:父 agent 的 prompt 与父 agent 看到的子 agent 结果。 */
|
|
153
159
|
pendant?: ToolPendant;
|
|
154
160
|
}
|
|
@@ -172,7 +178,7 @@ function getFinalOutput(messages: AgentMessage[]): string {
|
|
|
172
178
|
* stderr 截断到尾部(错误信息通常在最后)。全空时保底 "(no output)",
|
|
173
179
|
* 避免只回一个 exit code。
|
|
174
180
|
*/
|
|
175
|
-
export function formatSubagentError(result:
|
|
181
|
+
export function formatSubagentError(result: SubagentResult): { reason: string; message: string } {
|
|
176
182
|
const reason =
|
|
177
183
|
result.stopReason ?? (result.exitCode === 0 ? "failed" : `exit ${result.exitCode}`);
|
|
178
184
|
const parts: string[] = [];
|
|
@@ -356,8 +362,8 @@ export async function runAgent(
|
|
|
356
362
|
onUpdate: OnUpdateCallback | undefined,
|
|
357
363
|
parentUI?: ExtensionUIContext,
|
|
358
364
|
createSession: SessionFactory = createSubagentSession,
|
|
359
|
-
): Promise<
|
|
360
|
-
const result:
|
|
365
|
+
): Promise<SubagentResult> {
|
|
366
|
+
const result: SubagentResult = {
|
|
361
367
|
agent: agent.name,
|
|
362
368
|
task,
|
|
363
369
|
exitCode: 0,
|
|
@@ -431,7 +437,7 @@ export async function runAgent(
|
|
|
431
437
|
const footer = usageLine ? `\`${name}\` ${usageLine}` : `\`${name}\``;
|
|
432
438
|
onUpdate?.({
|
|
433
439
|
content: [{ type: "text", text: [...logLines, footer].join("\n") }],
|
|
434
|
-
details: { ...result },
|
|
440
|
+
details: { result: { ...result } },
|
|
435
441
|
});
|
|
436
442
|
};
|
|
437
443
|
|
|
@@ -558,15 +564,17 @@ export default function spawnAgent(pi: ExtensionAPI) {
|
|
|
558
564
|
},
|
|
559
565
|
],
|
|
560
566
|
details: {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
567
|
+
result: {
|
|
568
|
+
agent: params.agent,
|
|
569
|
+
task: params.task,
|
|
570
|
+
exitCode: 1,
|
|
571
|
+
messages: [],
|
|
572
|
+
stderr: "",
|
|
573
|
+
usage: {
|
|
574
|
+
cost: 0,
|
|
575
|
+
contextTokens: 0,
|
|
576
|
+
turns: 0,
|
|
577
|
+
},
|
|
570
578
|
},
|
|
571
579
|
},
|
|
572
580
|
isError: true,
|
|
@@ -591,7 +599,7 @@ export default function spawnAgent(pi: ExtensionAPI) {
|
|
|
591
599
|
{ type: "text", text: `Subagent "${result.agent}" failed (${reason}):\n${message}` },
|
|
592
600
|
],
|
|
593
601
|
details: {
|
|
594
|
-
|
|
602
|
+
result,
|
|
595
603
|
pendant: {
|
|
596
604
|
subtitle: result.agent,
|
|
597
605
|
markdown: formatPendantMarkdown(params.task, message),
|
|
@@ -609,7 +617,7 @@ export default function spawnAgent(pi: ExtensionAPI) {
|
|
|
609
617
|
return {
|
|
610
618
|
content: [{ type: "text", text }],
|
|
611
619
|
details: {
|
|
612
|
-
|
|
620
|
+
result,
|
|
613
621
|
pendant: {
|
|
614
622
|
subtitle: result.agent,
|
|
615
623
|
markdown: formatPendantMarkdown(params.task, text),
|
|
@@ -631,7 +639,7 @@ export default function spawnAgent(pi: ExtensionAPI) {
|
|
|
631
639
|
},
|
|
632
640
|
|
|
633
641
|
renderResult(result, { expanded }, theme) {
|
|
634
|
-
const details = result.details;
|
|
642
|
+
const details = result.details.result;
|
|
635
643
|
const isError =
|
|
636
644
|
details.exitCode !== 0 ||
|
|
637
645
|
details.stopReason === "error" ||
|