takomi 2.1.44 → 2.1.45
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/.pi/README.md +4 -3
- package/.pi/extensions/oauth-router/commands.ts +66 -35
- package/.pi/extensions/oauth-router/index.ts +51 -7
- package/.pi/extensions/oauth-router/report-ui.ts +205 -0
- package/.pi/extensions/takomi-context-manager/diagnostics-tools.ts +41 -3
- package/.pi/extensions/takomi-context-manager/diagnostics.ts +26 -0
- package/.pi/extensions/takomi-context-manager/index.ts +3 -2
- package/.pi/extensions/takomi-context-manager/model-policy-gate.ts +36 -15
- package/.pi/extensions/takomi-context-manager/policy-tools.ts +93 -42
- package/.pi/extensions/takomi-context-manager/skill-categories.d.ts +11 -0
- package/.pi/extensions/takomi-context-manager/skill-categories.js +212 -0
- package/.pi/extensions/takomi-context-manager/skill-registry.ts +179 -8
- package/.pi/extensions/takomi-context-manager/skill-tools.ts +208 -103
- package/.pi/extensions/takomi-context-manager/tool-renderers.ts +112 -0
- package/.pi/extensions/takomi-context-manager/types.ts +6 -0
- package/.pi/extensions/takomi-runtime/commands.ts +45 -12
- package/.pi/extensions/takomi-runtime/gate-provenance.ts +24 -0
- package/.pi/extensions/takomi-runtime/index.ts +111 -42
- package/.pi/extensions/takomi-runtime/routing-policy.ts +14 -2
- package/.pi/extensions/takomi-runtime/takomi-stats.js +46 -26
- package/.pi/extensions/takomi-runtime/tool-renderers.ts +234 -0
- package/.pi/extensions/takomi-runtime/workflow-catalog.ts +54 -0
- package/.pi/extensions/takomi-subagents/agents.ts +4 -0
- package/.pi/extensions/takomi-subagents/async-lifecycle.ts +401 -0
- package/.pi/extensions/takomi-subagents/detached-results.ts +940 -0
- package/.pi/extensions/takomi-subagents/index.ts +46 -1
- package/.pi/extensions/takomi-subagents/native-render.ts +250 -188
- package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +55 -46
- package/.pi/extensions/takomi-subagents/pi-subagents-internal.ts +11 -5
- package/.pi/extensions/takomi-subagents/result-heartbeat.ts +55 -0
- package/.pi/extensions/takomi-subagents/subagent-ux.ts +162 -0
- package/.pi/extensions/takomi-subagents/tool-runner.ts +158 -28
- package/.pi/takomi/model-routing.md +282 -3
- package/assets/.agent/skills/shared-resend-portfolio/SKILL.md +124 -0
- package/package.json +4 -3
- package/src/pi-takomi-core/types.ts +10 -0
- package/src/pi-takomi-core/workflows.ts +86 -45
- package/src/skills-catalog.js +2 -202
- package/src/skills-installer.js +4 -1
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
+
import { initializeTakomiAsyncLifecycle, resetTakomiAsyncLifecycle } from "./async-lifecycle";
|
|
4
|
+
import {
|
|
5
|
+
clearDetachedResults,
|
|
6
|
+
initializeDetachedSession,
|
|
7
|
+
registerDetachedCompletionNotifications,
|
|
8
|
+
} from "./detached-results";
|
|
3
9
|
import { renderTakomiSubagentCall, renderTakomiSubagentResult } from "./native-render";
|
|
4
10
|
import { loadPiSubagentsInternals } from "./pi-subagents-internal";
|
|
5
|
-
import {
|
|
11
|
+
import { clearAllTakomiSubagentResultHeartbeats } from "./result-heartbeat";
|
|
12
|
+
import { executeTakomiSubagentTool, invalidateTakomiPiSubagentsEngine, type TakomiAcceptanceInput } from "./tool-runner";
|
|
6
13
|
|
|
7
14
|
const ChecklistItemSchema = Type.Object({
|
|
8
15
|
text: Type.String(),
|
|
9
16
|
done: Type.Optional(Type.Boolean()),
|
|
10
17
|
});
|
|
11
18
|
|
|
19
|
+
const AcceptanceSchema = Type.Unsafe<TakomiAcceptanceInput>({
|
|
20
|
+
anyOf: [
|
|
21
|
+
{ type: "string", enum: ["auto", "none", "attested", "checked", "verified", "reviewed"] },
|
|
22
|
+
{ type: "boolean", enum: [false] },
|
|
23
|
+
{ type: "object", additionalProperties: true },
|
|
24
|
+
],
|
|
25
|
+
description: "Optional explicit acceptance policy. Omitted Takomi tasks do not enforce acceptance; explicit contracts are forwarded to pi-subagents.",
|
|
26
|
+
});
|
|
27
|
+
|
|
12
28
|
const ThinkingSchema = Type.Union([
|
|
13
29
|
Type.Literal("off"),
|
|
14
30
|
Type.Literal("minimal"),
|
|
@@ -29,6 +45,7 @@ const TaskSchema = Type.Object({
|
|
|
29
45
|
conversationId: Type.Optional(Type.String()),
|
|
30
46
|
cwd: Type.Optional(Type.String()),
|
|
31
47
|
checklist: Type.Optional(Type.Array(Type.Union([Type.String(), ChecklistItemSchema]))),
|
|
48
|
+
acceptance: Type.Optional(AcceptanceSchema),
|
|
32
49
|
});
|
|
33
50
|
|
|
34
51
|
const ContextSchema = Type.Union([
|
|
@@ -58,6 +75,7 @@ const SubagentParameters = Type.Object({
|
|
|
58
75
|
conversationId: Type.Optional(Type.String({ description: "Persistent conversation id to resume the same subagent session" })),
|
|
59
76
|
cwd: Type.Optional(Type.String({ description: "Working directory override" })),
|
|
60
77
|
checklist: Type.Optional(Type.Array(Type.Union([Type.String(), ChecklistItemSchema]), { description: "Optional checklist for the subagent" })),
|
|
78
|
+
acceptance: Type.Optional(AcceptanceSchema),
|
|
61
79
|
tasks: Type.Optional(Type.Array(TaskSchema, { description: "Parallel subagent tasks" })),
|
|
62
80
|
confirmLaunch: Type.Optional(Type.Boolean({ description: "Required to launch immediately in manual Takomi launch mode" })),
|
|
63
81
|
previewOnly: Type.Optional(Type.Boolean({ description: "Return the delegation plan without launching" })),
|
|
@@ -108,5 +126,32 @@ export default async function takomiSubagents(pi: ExtensionAPI) {
|
|
|
108
126
|
// a completed takomi_subagent result can fall back to Takomi's plain text
|
|
109
127
|
// renderer and lose the native compact/expanded details shown by `subagent`.
|
|
110
128
|
await loadPiSubagentsInternals();
|
|
129
|
+
// A same-process Takomi reload reuses the ExtensionAPI identity, so explicitly
|
|
130
|
+
// discard the cached engine before replacing its lifecycle generation.
|
|
131
|
+
invalidateTakomiPiSubagentsEngine(pi);
|
|
132
|
+
const cleanupAsyncLifecycle = await initializeTakomiAsyncLifecycle(pi);
|
|
111
133
|
registerSubagentTool(pi);
|
|
134
|
+
|
|
135
|
+
// Replace native completion notification through pi-subagents' own reload-safe
|
|
136
|
+
// registration slot. The shared renderer and dedupe key remain authoritative;
|
|
137
|
+
// Takomi enriches the single native notice without adding polling or timers.
|
|
138
|
+
const unregisterCompletionNotifications = registerDetachedCompletionNotifications(pi);
|
|
139
|
+
|
|
140
|
+
// Tool rows normally settle and clear their own heartbeat. A turn can also end
|
|
141
|
+
// without Pi rendering a final result (for example, after an abnormal tool
|
|
142
|
+
// interruption), so clear abandoned rows and rehydrate only authenticated
|
|
143
|
+
// launch provenance from the replacement session's own custom entries.
|
|
144
|
+
pi.on("agent_end", () => clearAllTakomiSubagentResultHeartbeats());
|
|
145
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
146
|
+
clearAllTakomiSubagentResultHeartbeats();
|
|
147
|
+
await resetTakomiAsyncLifecycle(pi, ctx);
|
|
148
|
+
await initializeDetachedSession(pi, ctx);
|
|
149
|
+
});
|
|
150
|
+
pi.on("session_shutdown", () => {
|
|
151
|
+
clearAllTakomiSubagentResultHeartbeats();
|
|
152
|
+
clearDetachedResults(pi);
|
|
153
|
+
invalidateTakomiPiSubagentsEngine(pi);
|
|
154
|
+
cleanupAsyncLifecycle();
|
|
155
|
+
unregisterCompletionNotifications();
|
|
156
|
+
});
|
|
112
157
|
}
|
|
@@ -1,188 +1,250 @@
|
|
|
1
|
-
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { Text } from "@earendil-works/pi-tui";
|
|
4
|
-
import type { TakomiSubagentToolParams } from "./tool-runner";
|
|
5
|
-
import { renderNativeSubagentResult, type Details } from "./pi-subagents-internal";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (rows.length
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
1
|
+
import type { AgentToolResult } from "@earendil-works/pi-agent-core";
|
|
2
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { Container, Spacer, Text, type Component } from "@earendil-works/pi-tui";
|
|
4
|
+
import type { TakomiSubagentToolParams } from "./tool-runner";
|
|
5
|
+
import { renderNativeSubagentResult, type Details } from "./pi-subagents-internal";
|
|
6
|
+
import {
|
|
7
|
+
boundNarrative,
|
|
8
|
+
explicitAssistantTexts,
|
|
9
|
+
finalAnswer,
|
|
10
|
+
resolvedChecklist,
|
|
11
|
+
sanitizeUntrustedText,
|
|
12
|
+
sanitizeUntrustedValue,
|
|
13
|
+
type TakomiUxTask,
|
|
14
|
+
} from "./subagent-ux";
|
|
15
|
+
import {
|
|
16
|
+
clearTakomiSubagentResultHeartbeat,
|
|
17
|
+
ensureTakomiSubagentResultHeartbeat,
|
|
18
|
+
getTakomiSubagentHeartbeatFrame,
|
|
19
|
+
type TakomiSubagentRenderContext,
|
|
20
|
+
} from "./result-heartbeat";
|
|
21
|
+
|
|
22
|
+
type ToolResult = AgentToolResult<Details>;
|
|
23
|
+
const FALLBACK_RENDER_WIDTH = 80;
|
|
24
|
+
const COMPACT_CUSTOM_LINE_BUDGET = 3;
|
|
25
|
+
const EXPANDED_CUSTOM_LINE_BUDGET = 2;
|
|
26
|
+
const COMPACT_NARRATIVE_LINE_BUDGET = 2;
|
|
27
|
+
const NARRATIVE_PREFIX = " ↳ ";
|
|
28
|
+
|
|
29
|
+
class WidthAwareLines implements Component {
|
|
30
|
+
constructor(private readonly buildLines: (width: number) => string[]) {}
|
|
31
|
+
render(width: number): string[] {
|
|
32
|
+
const available = Number.isFinite(width) && width > 0 ? Math.floor(width) : FALLBACK_RENDER_WIDTH;
|
|
33
|
+
return this.buildLines(available);
|
|
34
|
+
}
|
|
35
|
+
invalidate(): void {}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function taskList(params: TakomiSubagentToolParams): Array<{ agent: string; task: string }> {
|
|
39
|
+
if (params.chain?.length) return params.chain;
|
|
40
|
+
if (params.tasks?.length) return params.tasks;
|
|
41
|
+
if (params.agent || params.task) return [{ agent: params.agent ?? "...", task: params.task ?? "..." }];
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function renderTakomiSubagentCall(params: TakomiSubagentToolParams, theme: Theme) {
|
|
46
|
+
const safeParams = sanitizeUntrustedValue(params);
|
|
47
|
+
const tasks = taskList(safeParams);
|
|
48
|
+
const mode = safeParams.chain?.length ? "chain" : safeParams.tasks?.length ? "parallel" : "single";
|
|
49
|
+
if (tasks.length === 1) {
|
|
50
|
+
return new Text(`${theme.fg("toolTitle", theme.bold("takomi_subagent "))}${theme.fg("accent", tasks[0]?.agent || "?")}`, 0, 0);
|
|
51
|
+
}
|
|
52
|
+
return new Text(`${theme.fg("toolTitle", theme.bold("takomi_subagent "))}${mode} (${tasks.length})`, 0, 0);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resultText(result: ToolResult): string {
|
|
56
|
+
const text = typeof (result as any)?.content === "string"
|
|
57
|
+
? (result as any).content
|
|
58
|
+
: Array.isArray((result as any)?.content)
|
|
59
|
+
? (result as any).content.map((part: any) => part?.text ?? "").filter(Boolean).join("\n")
|
|
60
|
+
: JSON.stringify((result as any)?.details ?? {}, null, 2);
|
|
61
|
+
return sanitizeUntrustedText(text);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function extractPolicyNames(text: string): string[] {
|
|
65
|
+
const policyMatch = text.match(/Required policies:\n((?:- .+\n?)+)/);
|
|
66
|
+
return policyMatch?.[1]?.split("\n").map((line) => line.replace(/^[-\s]+/, "").trim()).filter(Boolean) ?? [];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isPolicyGateBlock(text: string): boolean {
|
|
70
|
+
return /^Blocked\s+takomi_subagent:\s+required policy context had not been loaded yet\./m.test(text)
|
|
71
|
+
&& /\nRequired policies:\n/.test(text)
|
|
72
|
+
&& /\nLoaded policy context:\n/.test(text);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function renderPolicyGateBlock(text: string, expanded: boolean | undefined, theme: Theme): string {
|
|
76
|
+
const safeText = sanitizeUntrustedText(text);
|
|
77
|
+
const policies = extractPolicyNames(safeText);
|
|
78
|
+
const policyLabel = policies.length ? policies.join(", ") : "required policy";
|
|
79
|
+
if (!expanded) {
|
|
80
|
+
return [
|
|
81
|
+
theme.fg("warning", "⚠ takomi_subagent blocked"),
|
|
82
|
+
theme.fg("dim", `Required policy context loaded for this session: ${policyLabel}.`),
|
|
83
|
+
theme.fg("dim", "Retry the original tool call. Ctrl+O shows the complete policy detail."),
|
|
84
|
+
].join("\n");
|
|
85
|
+
}
|
|
86
|
+
return [
|
|
87
|
+
theme.fg("warning", "⚠ takomi_subagent blocked"),
|
|
88
|
+
theme.fg("dim", "Policy context was loaded and passed back to the model; retry the original call."),
|
|
89
|
+
"",
|
|
90
|
+
safeText,
|
|
91
|
+
].join("\n");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function uxTasks(details: any): TakomiUxTask[] {
|
|
95
|
+
return Array.isArray(details?.takomiUx?.tasks) ? details.takomiUx.tasks : [];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function taskFor(tasks: TakomiUxTask[], row: any, index: number): TakomiUxTask {
|
|
99
|
+
return tasks[index] ?? { agent: row?.agent ?? `task ${index + 1}`, task: row?.task ?? "", checklist: [] };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function checklistFor(task: TakomiUxTask, row: any): ReturnType<typeof resolvedChecklist> {
|
|
103
|
+
const texts = explicitAssistantTexts(row);
|
|
104
|
+
if (typeof row?.finalOutput === "string") texts.push(row.finalOutput);
|
|
105
|
+
return resolvedChecklist(task.checklist, texts);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function normalizedLine(value: string): string {
|
|
109
|
+
return sanitizeUntrustedText(value).replace(/\s+/g, " ").trim();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function nativePreview(row: any): string {
|
|
113
|
+
const output = typeof row?.truncation?.text === "string" && row.truncation.text.trim()
|
|
114
|
+
? row.truncation.text
|
|
115
|
+
: finalAnswer(row);
|
|
116
|
+
return output.split(/\r?\n/).find((line: string) => line.trim())?.trim() ?? "";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function compactNarratives(rows: any[], isPartial: boolean): string[] {
|
|
120
|
+
const narratives: string[] = [];
|
|
121
|
+
for (const row of rows) {
|
|
122
|
+
const source = isPartial ? explicitAssistantTexts(row).at(-1) ?? "" : finalAnswer(row);
|
|
123
|
+
const lines = source.split(/\r?\n/)
|
|
124
|
+
.filter((line) => !/^\s*[-*+]\s+\[[ xX]\]\s+/.test(line))
|
|
125
|
+
.map(normalizedLine)
|
|
126
|
+
.filter(Boolean);
|
|
127
|
+
// Native compact only previews successful single results. Acceptance rejection
|
|
128
|
+
// changes exitCode to non-zero, so keep the first final line in that case.
|
|
129
|
+
if (!isPartial && rows.length === 1 && row?.exitCode === 0 && lines.length && normalizedLine(nativePreview(row)) === lines[0]) lines.shift();
|
|
130
|
+
for (const line of lines) {
|
|
131
|
+
if (narratives.some((existing) => normalizedLine(existing) === line)) continue;
|
|
132
|
+
narratives.push(line);
|
|
133
|
+
if (narratives.length >= COMPACT_NARRATIVE_LINE_BUDGET) return narratives;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return narratives;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function checklistSummary(rows: any[], tasks: TakomiUxTask[]): { summary: string; expanded: string } | undefined {
|
|
140
|
+
const checklists = rows.map((row, index) => checklistFor(taskFor(tasks, row, index), row));
|
|
141
|
+
const items = checklists.flat();
|
|
142
|
+
if (!items.length) return undefined;
|
|
143
|
+
const done = items.filter((item) => item.done).length;
|
|
144
|
+
const reported = items.filter((item) => item.done && item.stateSource === "agent-reported").length;
|
|
145
|
+
const summary = `checklist ${done}/${items.length} complete${reported ? ` · ${reported} agent-reported` : ""}`;
|
|
146
|
+
if (rows.length !== 1) return { summary, expanded: `Checklist: ${done}/${items.length} complete${reported ? ` (${reported} agent-reported)` : ""}` };
|
|
147
|
+
const labels = items.map((item) => `[${item.done ? "x" : " "}] ${item.text}`);
|
|
148
|
+
return { summary, expanded: `Checklist: ${labels.join(" · ")}` };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function fallbackSummary(rows: any[]): string | undefined {
|
|
152
|
+
const states = rows.map((row) => row?.takomiDetachedOutput)
|
|
153
|
+
.filter((value) => value && value.fallbackState !== "not-needed");
|
|
154
|
+
if (!states.length) return undefined;
|
|
155
|
+
const first = states[0];
|
|
156
|
+
return `output ${first.source} fallback: ${first.fallbackState}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function compactAddition(details: any, isPartial: boolean, theme: Theme): Component | undefined {
|
|
160
|
+
const rows = Array.isArray(details?.results) ? details.results : [];
|
|
161
|
+
if (!rows.length) return undefined;
|
|
162
|
+
const tasks = uxTasks(details);
|
|
163
|
+
const narratives = compactNarratives(rows, isPartial);
|
|
164
|
+
const checklist = checklistSummary(rows, tasks);
|
|
165
|
+
const checklistProvenance = details?.takomiDetached?.checklistProvenance === "unavailable-after-restart"
|
|
166
|
+
? "checklist provenance unavailable after restart"
|
|
167
|
+
: checklist?.summary;
|
|
168
|
+
const provenance = [checklistProvenance, fallbackSummary(rows)].filter((value): value is string => Boolean(value));
|
|
169
|
+
if (!narratives.length && !provenance.length) return undefined;
|
|
170
|
+
|
|
171
|
+
return new WidthAwareLines((width) => {
|
|
172
|
+
const lines: string[] = [];
|
|
173
|
+
const narrativeWidth = Math.max(1, width - NARRATIVE_PREFIX.length);
|
|
174
|
+
for (const narrative of narratives) {
|
|
175
|
+
const bounded = boundNarrative(narrative, { maxLines: 1, maxColumns: narrativeWidth, from: "start" });
|
|
176
|
+
if (bounded.lines[0]) lines.push(theme.fg("dim", `${NARRATIVE_PREFIX}${bounded.lines[0]}`));
|
|
177
|
+
}
|
|
178
|
+
for (const item of provenance) {
|
|
179
|
+
if (lines.length >= COMPACT_CUSTOM_LINE_BUDGET) break;
|
|
180
|
+
const bounded = boundNarrative(item, { maxLines: 1, maxColumns: Math.max(1, width - 2), from: "start" });
|
|
181
|
+
if (bounded.lines[0]) lines.push(theme.fg("muted", ` ${bounded.lines[0]}`));
|
|
182
|
+
}
|
|
183
|
+
return lines.slice(0, COMPACT_CUSTOM_LINE_BUDGET);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function expandedAddition(details: any, theme: Theme): Component | undefined {
|
|
188
|
+
const rows = Array.isArray(details?.results) ? details.results : [];
|
|
189
|
+
if (!rows.length) return undefined;
|
|
190
|
+
const checklist = checklistSummary(rows, uxTasks(details));
|
|
191
|
+
const checklistProvenance = details?.takomiDetached?.checklistProvenance === "unavailable-after-restart"
|
|
192
|
+
? "Checklist provenance unavailable after restart"
|
|
193
|
+
: checklist ? checklist.summary.replace(/^checklist/, "Checklist provenance:") : undefined;
|
|
194
|
+
const provenance = [checklistProvenance, fallbackSummary(rows)]
|
|
195
|
+
.filter(Boolean)
|
|
196
|
+
.join("\n");
|
|
197
|
+
if (!provenance) return undefined;
|
|
198
|
+
|
|
199
|
+
return new WidthAwareLines((width) => boundNarrative(provenance, {
|
|
200
|
+
maxLines: EXPANDED_CUSTOM_LINE_BUDGET,
|
|
201
|
+
maxColumns: width,
|
|
202
|
+
from: "start",
|
|
203
|
+
}).lines.slice(0, EXPANDED_CUSTOM_LINE_BUDGET).map((line) => theme.fg("dim", line)));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function compose(
|
|
207
|
+
native: Component | undefined,
|
|
208
|
+
addition: Component | undefined,
|
|
209
|
+
fallback: Component,
|
|
210
|
+
errorNotice?: Component,
|
|
211
|
+
): Component {
|
|
212
|
+
if (!native && !addition && !errorNotice) return fallback;
|
|
213
|
+
const container = new Container();
|
|
214
|
+
if (errorNotice) container.addChild(errorNotice);
|
|
215
|
+
if (errorNotice && (native || addition)) container.addChild(new Spacer(1));
|
|
216
|
+
if (native) container.addChild(native);
|
|
217
|
+
if (native && addition) container.addChild(new Spacer(1));
|
|
218
|
+
if (addition) container.addChild(addition);
|
|
219
|
+
return container;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function renderTakomiSubagentResult(
|
|
223
|
+
result: ToolResult,
|
|
224
|
+
options: { expanded?: boolean; isPartial?: boolean },
|
|
225
|
+
theme: Theme,
|
|
226
|
+
context: TakomiSubagentRenderContext & { isError?: boolean },
|
|
227
|
+
): any {
|
|
228
|
+
if (options.isPartial) ensureTakomiSubagentResultHeartbeat(context);
|
|
229
|
+
else clearTakomiSubagentResultHeartbeat(context);
|
|
230
|
+
|
|
231
|
+
const safeResult = sanitizeUntrustedValue(result);
|
|
232
|
+
const text = resultText(safeResult);
|
|
233
|
+
if (isPolicyGateBlock(text)) return new Text(renderPolicyGateBlock(text, options.expanded, theme), 0, 0);
|
|
234
|
+
|
|
235
|
+
const frame = getTakomiSubagentHeartbeatFrame(context);
|
|
236
|
+
const native = renderNativeSubagentResult(
|
|
237
|
+
safeResult,
|
|
238
|
+
{ expanded: options.expanded === true, isPartial: options.isPartial === true },
|
|
239
|
+
theme,
|
|
240
|
+
frame,
|
|
241
|
+
) as Component | undefined;
|
|
242
|
+
const details: any = (safeResult as any)?.details ?? {};
|
|
243
|
+
const addition = options.expanded
|
|
244
|
+
? expandedAddition(details, theme)
|
|
245
|
+
: compactAddition(details, options.isPartial === true, theme);
|
|
246
|
+
const isError = Boolean((safeResult as any)?.isError || context?.isError);
|
|
247
|
+
const errorNotice = isError ? new Text(theme.fg("error", "takomi_subagent failed"), 0, 0) : undefined;
|
|
248
|
+
const fallback = new Text(`${isError ? `${theme.fg("error", "failed")}\n` : ""}${text || "No result content."}`, 0, 0);
|
|
249
|
+
return compose(native, addition, fallback, errorNotice);
|
|
250
|
+
}
|