takomi 2.1.44 → 2.5.1
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 +9 -8
- package/.pi/agents/architect.md +2 -2
- package/.pi/agents/designer.md +2 -2
- package/.pi/agents/worker.md +32 -0
- 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 +50 -117
- 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/command-text.ts +5 -4
- package/.pi/extensions/takomi-runtime/commands.ts +58 -25
- package/.pi/extensions/takomi-runtime/gate-provenance.ts +24 -0
- package/.pi/extensions/takomi-runtime/index.ts +385 -124
- package/.pi/extensions/takomi-runtime/model-routing-defaults.ts +262 -326
- package/.pi/extensions/takomi-runtime/profile.ts +9 -8
- package/.pi/extensions/takomi-runtime/routing-policy.ts +97 -66
- package/.pi/extensions/takomi-runtime/shared.ts +7 -30
- 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 +9 -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 +50 -1
- package/.pi/extensions/takomi-subagents/native-render.ts +250 -188
- package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +62 -47
- 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 +198 -29
- package/.pi/settings.json +39 -36
- package/.pi/takomi/model-routing.md +288 -3
- package/.pi/takomi-profile.json +54 -50
- package/assets/.agent/skills/shared-resend-portfolio/SKILL.md +124 -0
- package/package.json +4 -3
- package/src/pi-takomi-core/orchestration.ts +39 -21
- package/src/pi-takomi-core/routing.ts +8 -8
- package/src/pi-takomi-core/types.ts +35 -5
- package/src/pi-takomi-core/workflows.ts +86 -45
- package/src/skills-catalog.js +2 -202
- package/src/skills-installer.js +4 -1
|
@@ -1,103 +1,208 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
state.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { Container, Markdown, Text } from "@earendil-works/pi-tui";
|
|
5
|
+
import { getMarkdownTheme, keyHint } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { Type } from "typebox";
|
|
7
|
+
import type { ContextManagerState } from "./state";
|
|
8
|
+
import { discoverSkillsFromFilesystem, findSkill, mergeSkills, normalizeName, skillIndexRenderGroups, sortedSkills, type SkillIndexRenderGroup } from "./skill-registry";
|
|
9
|
+
import { persistReportSnapshot, restoreReportFromSession } from "./session-state";
|
|
10
|
+
import { renderCompactCard, renderExpandedMarkdown, renderToolCall, resultText, sanitizePresentation } from "./tool-renderers";
|
|
11
|
+
|
|
12
|
+
function renderSkillIndex(state: ContextManagerState): string {
|
|
13
|
+
const skills = sortedSkills(state.skills);
|
|
14
|
+
if (skills.length === 0) return "Available skills (names only): none discovered.";
|
|
15
|
+
return ["Available skills (names only):", ...skills.map((skill) => `- ${skill.name}`)].join("\n");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function renderManifest(state: ContextManagerState, names: string[]): string {
|
|
19
|
+
if (names.length === 0) return "No skills requested.";
|
|
20
|
+
return names.map((name) => {
|
|
21
|
+
const skill = findSkill(state.skills, name);
|
|
22
|
+
if (!skill) {
|
|
23
|
+
const close = sortedSkills(state.skills).filter((candidate) => normalizeName(candidate.name).includes(normalizeName(name).slice(0, 4))).slice(0, 5).map((candidate) => candidate.name);
|
|
24
|
+
return [`Skill not found: ${name}`, close.length ? `Known close matches: ${close.join(", ")}` : ""].filter(Boolean).join("\n");
|
|
25
|
+
}
|
|
26
|
+
return [`Skill: ${skill.name}`, `Description: ${skill.description ?? "(no description discovered)"}`, `Location: ${skill.location ?? "(no location discovered)"}`].join("\n");
|
|
27
|
+
}).join("\n\n");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const COMPACT_CATEGORY_LIMIT = 3;
|
|
31
|
+
|
|
32
|
+
function groupSummaryItems(groups: SkillIndexRenderGroup[], maximum = groups.length): string[] {
|
|
33
|
+
const visible = groups.slice(0, maximum).map((group) => `${group.category} ${group.skills.length}`);
|
|
34
|
+
const overflow = groups.length - visible.length;
|
|
35
|
+
return [...visible, ...(overflow > 0 ? [`+${overflow} more categories`] : [])];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function groupSummary(groups: SkillIndexRenderGroup[], maximum = groups.length): string {
|
|
39
|
+
return groupSummaryItems(groups, maximum).join(" · ");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function renderGroupedSkillIndex(groups: SkillIndexRenderGroup[]): string {
|
|
43
|
+
if (groups.length === 0) return "No skills discovered.";
|
|
44
|
+
return groups.map((group) => [
|
|
45
|
+
`## ${group.category}`,
|
|
46
|
+
"",
|
|
47
|
+
...group.skills.map((skill) => `- \`${skill.name}\`${skill.description ? ` — ${skill.description}` : ""}`),
|
|
48
|
+
].join("\n")).join("\n\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function skillMarkdown(text: string): string {
|
|
52
|
+
const separator = text.indexOf("\n\n");
|
|
53
|
+
return separator >= 0 ? text.slice(separator + 2) : text;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function loadSkillContent(location: string): Promise<string> {
|
|
57
|
+
const fileName = path.basename(location).toLowerCase();
|
|
58
|
+
if (fileName !== "skill.md" && !location.toLowerCase().endsWith(".md")) throw new Error(`Refusing to load non-markdown skill location: ${location}`);
|
|
59
|
+
return readFile(location, "utf8");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function ensureSkillsDiscovered(state: ContextManagerState, cwd: string): Promise<void> {
|
|
63
|
+
if (state.skills.size > 0) return;
|
|
64
|
+
state.skills = mergeSkills(await discoverSkillsFromFilesystem(cwd));
|
|
65
|
+
state.report.skillCount = state.skills.size;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function registerSkillTools(pi: ExtensionAPI, state: ContextManagerState): void {
|
|
69
|
+
pi.registerTool({
|
|
70
|
+
name: "skill_index",
|
|
71
|
+
label: "Skill Index",
|
|
72
|
+
description: "Return the available skill names only. Use this to inspect capability names without loading descriptions or full instructions.",
|
|
73
|
+
promptSnippet: "List available skill names only for progressive skill loading",
|
|
74
|
+
parameters: Type.Object({}),
|
|
75
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
76
|
+
restoreReportFromSession(state, ctx);
|
|
77
|
+
await ensureSkillsDiscovered(state, ctx.cwd);
|
|
78
|
+
state.report.timestamp = new Date().toISOString();
|
|
79
|
+
state.report.cwd = ctx.cwd;
|
|
80
|
+
state.report.toolCalls.skillIndex += 1;
|
|
81
|
+
persistReportSnapshot(pi, state, "skill_index");
|
|
82
|
+
const groups = skillIndexRenderGroups(state.skills.values());
|
|
83
|
+
return { content: [{ type: "text", text: renderSkillIndex(state) }], details: { skillCount: state.skills.size, groups } };
|
|
84
|
+
},
|
|
85
|
+
renderCall(_args, theme) {
|
|
86
|
+
return renderToolCall("skill_index", undefined, theme);
|
|
87
|
+
},
|
|
88
|
+
renderResult(result, { expanded }, theme) {
|
|
89
|
+
const details = result.details as { skillCount?: number; groups?: SkillIndexRenderGroup[] } | undefined;
|
|
90
|
+
const groups = details?.groups ?? [];
|
|
91
|
+
const count = details?.skillCount ?? groups.reduce((total, group) => total + group.skills.length, 0);
|
|
92
|
+
if (!expanded) {
|
|
93
|
+
return renderCompactCard({
|
|
94
|
+
status: count ? "success" : "pending",
|
|
95
|
+
title: "Skill index",
|
|
96
|
+
summary: count ? `${count} skills across ${groups.length} categories` : "no skills discovered",
|
|
97
|
+
responsiveMetadata: groups.length ? groupSummaryItems(groups, COMPACT_CATEGORY_LIMIT) : undefined,
|
|
98
|
+
}, theme);
|
|
99
|
+
}
|
|
100
|
+
return renderExpandedMarkdown({
|
|
101
|
+
status: count ? "success" : "pending",
|
|
102
|
+
title: "Skill index",
|
|
103
|
+
summary: count ? `${count} skills across ${groups.length} categories` : "no skills discovered",
|
|
104
|
+
metadata: groups.length ? [groupSummary(groups)] : undefined,
|
|
105
|
+
markdown: renderGroupedSkillIndex(groups),
|
|
106
|
+
}, theme);
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
pi.registerTool({
|
|
111
|
+
name: "skill_manifest",
|
|
112
|
+
label: "Skill Manifest",
|
|
113
|
+
description: "Return descriptions and locations for selected skills without loading full SKILL.md instructions.",
|
|
114
|
+
promptSnippet: "Show selected skill descriptions and locations without full instructions",
|
|
115
|
+
parameters: Type.Object({ skills: Type.Array(Type.String({ description: "Skill name to inspect" })) }),
|
|
116
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
117
|
+
restoreReportFromSession(state, ctx);
|
|
118
|
+
await ensureSkillsDiscovered(state, ctx.cwd);
|
|
119
|
+
state.report.timestamp = new Date().toISOString();
|
|
120
|
+
state.report.cwd = ctx.cwd;
|
|
121
|
+
state.report.toolCalls.skillManifest += 1;
|
|
122
|
+
persistReportSnapshot(pi, state, "skill_manifest");
|
|
123
|
+
const found = params.skills.filter((name) => Boolean(findSkill(state.skills, name)));
|
|
124
|
+
const missing = params.skills.filter((name) => !findSkill(state.skills, name));
|
|
125
|
+
return { content: [{ type: "text", text: renderManifest(state, params.skills) }], details: { requested: params.skills, found, missing } };
|
|
126
|
+
},
|
|
127
|
+
renderCall(args, theme) {
|
|
128
|
+
return renderToolCall("skill_manifest", `${args.skills.length} requested`, theme);
|
|
129
|
+
},
|
|
130
|
+
renderResult(result, { expanded }, theme) {
|
|
131
|
+
const details = result.details as { requested?: string[]; found?: string[]; missing?: string[] } | undefined;
|
|
132
|
+
const requested = details?.requested ?? [];
|
|
133
|
+
const found = details?.found ?? [];
|
|
134
|
+
const missing = details?.missing ?? [];
|
|
135
|
+
const status = missing.length ? "warning" : requested.length ? "success" : "pending";
|
|
136
|
+
const summary = missing.length ? `${found.length} found · ${missing.length} unavailable` : requested.length ? `${found.length} skills available` : "no skills requested";
|
|
137
|
+
const metadata = missing.length ? `Missing: ${missing.join(", ")}` : `${requested.length} requested`;
|
|
138
|
+
if (!expanded) return renderCompactCard({ status, title: "Skill manifest", summary, metadata }, theme);
|
|
139
|
+
return renderExpandedMarkdown({ status, title: "Skill manifest", summary, metadata: [metadata], markdown: resultText(result) }, theme);
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
pi.registerTool({
|
|
144
|
+
name: "skill_load",
|
|
145
|
+
label: "Skill Load",
|
|
146
|
+
description: "Load the full SKILL.md content for one selected skill that will actually be used.",
|
|
147
|
+
promptSnippet: "Load full SKILL.md instructions for one selected skill",
|
|
148
|
+
parameters: Type.Object({ skill: Type.String({ description: "Exact skill name to load" }) }),
|
|
149
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
150
|
+
restoreReportFromSession(state, ctx);
|
|
151
|
+
await ensureSkillsDiscovered(state, ctx.cwd);
|
|
152
|
+
state.report.timestamp = new Date().toISOString();
|
|
153
|
+
state.report.cwd = ctx.cwd;
|
|
154
|
+
state.report.toolCalls.skillLoad += 1;
|
|
155
|
+
const skill = findSkill(state.skills, params.skill);
|
|
156
|
+
if (!skill?.location) {
|
|
157
|
+
persistReportSnapshot(pi, state, "skill_load_not_found");
|
|
158
|
+
return { content: [{ type: "text", text: renderManifest(state, [params.skill]) }], details: { found: false, requested: params.skill }, isError: true };
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const content = await loadSkillContent(skill.location);
|
|
162
|
+
state.report.loadedByTool = [...new Set([...state.report.loadedByTool, skill.name])].sort();
|
|
163
|
+
persistReportSnapshot(pi, state, "skill_load");
|
|
164
|
+
return {
|
|
165
|
+
content: [{ type: "text", text: [`Skill: ${skill.name}`, `Location: ${skill.location}`, "", content].join("\n") }],
|
|
166
|
+
details: {
|
|
167
|
+
found: true,
|
|
168
|
+
skill: skill.name,
|
|
169
|
+
description: skill.description,
|
|
170
|
+
location: skill.location,
|
|
171
|
+
lineCount: content.split(/\r?\n/).length,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
} catch (error) {
|
|
175
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
176
|
+
persistReportSnapshot(pi, state, "skill_load_error");
|
|
177
|
+
return { content: [{ type: "text", text: message }], details: { found: true, skill: skill.name, error: message }, isError: true };
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
renderCall(args, theme) {
|
|
181
|
+
return renderToolCall("skill_load", args.skill, theme);
|
|
182
|
+
},
|
|
183
|
+
renderResult(result, { expanded }, theme) {
|
|
184
|
+
const details = result.details as { found?: boolean; skill?: string; description?: string; location?: string; lineCount?: number; error?: string } | undefined;
|
|
185
|
+
const text = resultText(result);
|
|
186
|
+
if (details?.error || details?.found === false) {
|
|
187
|
+
const summary = details?.error ?? "skill not found";
|
|
188
|
+
if (!expanded) return renderCompactCard({ status: "error", title: "Skill load", summary }, theme);
|
|
189
|
+
return renderExpandedMarkdown({ status: "error", title: "Skill load", summary, markdown: text }, theme);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const name = sanitizePresentation(details?.skill ?? "skill");
|
|
193
|
+
const description = details?.description ? sanitizePresentation(details.description) : undefined;
|
|
194
|
+
const summary = description ?? "skill instructions loaded";
|
|
195
|
+
const metadata = `${details?.lineCount ?? text.split(/\r?\n/).length} lines`;
|
|
196
|
+
if (!expanded) return renderCompactCard({ status: "success", title: name, summary, metadata }, theme);
|
|
197
|
+
|
|
198
|
+
const location = details?.location ? sanitizePresentation(details.location) : undefined;
|
|
199
|
+
const container = new Container();
|
|
200
|
+
container.addChild(new Text(`${theme.fg("success", "✓")} ${theme.fg("accent", theme.bold(name))} ${theme.fg("muted", "skill instructions")}`, 0, 0));
|
|
201
|
+
if (description) container.addChild(new Text(theme.fg("muted", description), 0, 0));
|
|
202
|
+
if (location) container.addChild(new Text(theme.fg("dim", location), 0, 0));
|
|
203
|
+
container.addChild(new Text(theme.fg("dim", keyHint("app.tools.expand", "collapse")), 0, 0));
|
|
204
|
+
container.addChild(new Markdown(sanitizePresentation(skillMarkdown(text)), 0, 1, getMarkdownTheme()));
|
|
205
|
+
return container;
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { getMarkdownTheme, keyHint } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Container, Markdown, Text, type Component } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
4
|
+
type Theme = {
|
|
5
|
+
fg(color: string, text: string): string;
|
|
6
|
+
bold(text: string): string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type ToolResult = {
|
|
10
|
+
content?: Array<{ type?: string; text?: string }>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const OSC_SEQUENCE = /\x1B\][\s\S]*?(?:\x07|\x1B\\|$)/g;
|
|
14
|
+
const STRING_TERMINATED_SEQUENCE = /\x1B[PX^_][\s\S]*?(?:\x1B\\|$)/g;
|
|
15
|
+
const CSI_SEQUENCE = /(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~]/g;
|
|
16
|
+
const ESC_SEQUENCE = /\x1B(?:[()][0-2A-Z0-9]|[=>]|[ -/]*[@-~]?)/g;
|
|
17
|
+
const UNSAFE_CONTROLS = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Remove terminal controls at the TUI boundary while retaining Markdown's
|
|
21
|
+
* printable syntax and line structure. Tool result content remains unchanged.
|
|
22
|
+
*/
|
|
23
|
+
export function sanitizePresentation(value: string): string {
|
|
24
|
+
return value
|
|
25
|
+
.replace(OSC_SEQUENCE, "")
|
|
26
|
+
.replace(STRING_TERMINATED_SEQUENCE, "")
|
|
27
|
+
.replace(CSI_SEQUENCE, "")
|
|
28
|
+
.replace(ESC_SEQUENCE, "")
|
|
29
|
+
.replace(UNSAFE_CONTROLS, "");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Read model-facing content without modifying it, then make a presentation-safe copy. */
|
|
33
|
+
export function resultText(result: ToolResult): string {
|
|
34
|
+
const text = result.content?.filter((part) => part.type === "text").map((part) => part.text ?? "").join("\n") ?? "";
|
|
35
|
+
return sanitizePresentation(text);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function renderToolCall(toolName: string, target: string | undefined, theme: Theme): Text {
|
|
39
|
+
const safeToolName = sanitizePresentation(toolName);
|
|
40
|
+
const safeTarget = target ? sanitizePresentation(target) : "";
|
|
41
|
+
return new Text(
|
|
42
|
+
`${theme.fg("toolTitle", theme.bold(`${safeToolName} `))}${safeTarget ? theme.fg("accent", safeTarget) : ""}`,
|
|
43
|
+
0,
|
|
44
|
+
0,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function renderCompactCard(options: {
|
|
49
|
+
status: "success" | "warning" | "error" | "pending";
|
|
50
|
+
title: string;
|
|
51
|
+
summary: string;
|
|
52
|
+
metadata?: string;
|
|
53
|
+
/** Items render inline on wide terminals and stack below 80 columns. */
|
|
54
|
+
responsiveMetadata?: string[];
|
|
55
|
+
}, theme: Theme): Component {
|
|
56
|
+
const status = {
|
|
57
|
+
success: ["✓", "success"],
|
|
58
|
+
warning: ["⚠", "warning"],
|
|
59
|
+
error: ["✗", "error"],
|
|
60
|
+
pending: ["…", "muted"],
|
|
61
|
+
} as const;
|
|
62
|
+
const [icon, color] = status[options.status];
|
|
63
|
+
const title = sanitizePresentation(options.title);
|
|
64
|
+
const summary = sanitizePresentation(options.summary);
|
|
65
|
+
const metadata = options.metadata ? sanitizePresentation(options.metadata) : undefined;
|
|
66
|
+
const responsiveMetadata = options.responsiveMetadata?.map(sanitizePresentation).filter(Boolean);
|
|
67
|
+
const header = `${theme.fg(color, icon)} ${theme.fg("accent", theme.bold(title))} ${theme.fg("muted", summary)}`;
|
|
68
|
+
const hint = keyHint("app.tools.expand", "view details");
|
|
69
|
+
|
|
70
|
+
if (!responsiveMetadata?.length) {
|
|
71
|
+
const detail = metadata ? `${metadata} · ${hint}` : hint;
|
|
72
|
+
return new Text([header, theme.fg("dim", detail)].join("\n"), 0, 0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
invalidate() {},
|
|
77
|
+
render(width: number): string[] {
|
|
78
|
+
const details = width < 80
|
|
79
|
+
? [...responsiveMetadata.map((item) => theme.fg("dim", item)), theme.fg("dim", hint)]
|
|
80
|
+
: [theme.fg("dim", `${responsiveMetadata.join(" · ")} · ${hint}`)];
|
|
81
|
+
return new Text([header, ...details].join("\n"), 0, 0).render(width);
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function renderExpandedMarkdown(options: {
|
|
87
|
+
status: "success" | "warning" | "error" | "pending";
|
|
88
|
+
title: string;
|
|
89
|
+
summary: string;
|
|
90
|
+
metadata?: string[];
|
|
91
|
+
markdown: string;
|
|
92
|
+
}, theme: Theme): Container {
|
|
93
|
+
const status = {
|
|
94
|
+
success: ["✓", "success"],
|
|
95
|
+
warning: ["⚠", "warning"],
|
|
96
|
+
error: ["✗", "error"],
|
|
97
|
+
pending: ["…", "muted"],
|
|
98
|
+
} as const;
|
|
99
|
+
const [icon, color] = status[options.status];
|
|
100
|
+
const title = sanitizePresentation(options.title);
|
|
101
|
+
const summary = sanitizePresentation(options.summary);
|
|
102
|
+
const container = new Container();
|
|
103
|
+
container.addChild(new Text(
|
|
104
|
+
`${theme.fg(color, icon)} ${theme.fg("accent", theme.bold(title))} ${theme.fg("muted", summary)}`,
|
|
105
|
+
0,
|
|
106
|
+
0,
|
|
107
|
+
));
|
|
108
|
+
for (const detail of options.metadata ?? []) container.addChild(new Text(theme.fg("dim", sanitizePresentation(detail)), 0, 0));
|
|
109
|
+
container.addChild(new Text(theme.fg("dim", keyHint("app.tools.expand", "collapse")), 0, 0));
|
|
110
|
+
container.addChild(new Markdown(sanitizePresentation(options.markdown), 0, 1, getMarkdownTheme()));
|
|
111
|
+
return container;
|
|
112
|
+
}
|
|
@@ -2,6 +2,12 @@ export type SkillRecord = {
|
|
|
2
2
|
name: string;
|
|
3
3
|
description?: string;
|
|
4
4
|
location?: string;
|
|
5
|
+
/** Explicit skill metadata, preferred over all other category sources. */
|
|
6
|
+
category?: string;
|
|
7
|
+
/** Category supplied by an installer/source registry for this exact skill path. */
|
|
8
|
+
sourceCategory?: string;
|
|
9
|
+
/** Package/source identifier used only when no explicit, registry, or path category exists. */
|
|
10
|
+
packageName?: string;
|
|
5
11
|
source: "systemPromptOptions" | "xml" | "filesystem" | "tool";
|
|
6
12
|
};
|
|
7
13
|
|
|
@@ -14,7 +14,7 @@ const ROOT_COMPLETIONS: TakomiCompletion[] = [
|
|
|
14
14
|
{ value: "design", label: "design", description: "Run UI/UX design from approved scope" },
|
|
15
15
|
{ value: "build", label: "build", description: "Implement against the agreed UI" },
|
|
16
16
|
{ value: "plan", label: "plan", description: "Create or update the orchestration plan" },
|
|
17
|
-
{ value: "mode", label: "mode", description: "Set
|
|
17
|
+
{ value: "mode", label: "mode", description: "Set idle, code, review, or orchestrate main-agent mode" },
|
|
18
18
|
{ value: "gate", label: "gate", description: "Set auto or review-gated execution" },
|
|
19
19
|
{ value: "subagents", label: "subagents", description: "Control subagent usage and view" },
|
|
20
20
|
{ value: "stats", label: "stats", description: "Show token, model, project, session, tool, and subagent usage stats" },
|
|
@@ -23,9 +23,10 @@ const ROOT_COMPLETIONS: TakomiCompletion[] = [
|
|
|
23
23
|
|
|
24
24
|
const SUBCOMMAND_COMPLETIONS: Record<string, TakomiCompletion[]> = {
|
|
25
25
|
mode: [
|
|
26
|
-
{ value: "
|
|
27
|
-
{ value: "
|
|
26
|
+
{ value: "idle", label: "idle", description: "Clear active Takomi main-agent behavior" },
|
|
27
|
+
{ value: "code", label: "code", description: "Implement directly in the main session" },
|
|
28
28
|
{ value: "review", label: "review", description: "Inspect outputs and route fixes" },
|
|
29
|
+
{ value: "orchestrate", label: "orchestrate", description: "Coordinate lifecycle work and specialist personas" },
|
|
29
30
|
],
|
|
30
31
|
gate: [
|
|
31
32
|
{ value: "review", label: "review", description: "Return to the user after each task" },
|
|
@@ -94,7 +95,7 @@ export function commandHelp(): string {
|
|
|
94
95
|
"/takomi design [prompt]",
|
|
95
96
|
"/takomi build [prompt]",
|
|
96
97
|
"/takomi plan [title]",
|
|
97
|
-
"/takomi mode <
|
|
98
|
+
"/takomi mode <idle|code|review|orchestrate>",
|
|
98
99
|
"/takomi gate <auto|review>",
|
|
99
100
|
"/takomi subagents <list|on|off|status>",
|
|
100
101
|
"/takomi stats [overview|daily|models|projects|projects-full|sessions|sessions-full|tasks|tasks-full|tools|subagents|sources] [since 7d]",
|