takomi 2.1.13 → 2.1.15
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/agents/architect.md +73 -73
- package/.pi/agents/coder.md +70 -70
- package/.pi/agents/designer.md +72 -72
- package/.pi/agents/orchestrator.md +122 -122
- package/.pi/agents/reviewer.md +71 -71
- package/.pi/extensions/oauth-router/provider.ts +3 -1
- package/.pi/extensions/takomi-context-manager/config.ts +48 -48
- package/.pi/extensions/takomi-context-manager/context-router.ts +57 -57
- package/.pi/extensions/takomi-context-manager/diagnostics-tools.ts +28 -28
- package/.pi/extensions/takomi-context-manager/diagnostics.ts +55 -55
- package/.pi/extensions/takomi-context-manager/extension-conflicts.ts +56 -56
- package/.pi/extensions/takomi-context-manager/index.ts +56 -56
- package/.pi/extensions/takomi-context-manager/model-policy-gate.ts +228 -206
- package/.pi/extensions/takomi-context-manager/policy-registry.ts +97 -97
- package/.pi/extensions/takomi-context-manager/policy-tools.ts +35 -35
- package/.pi/extensions/takomi-context-manager/prerequisite-gates.ts +39 -39
- package/.pi/extensions/takomi-context-manager/prompt-rewriter.ts +100 -100
- package/.pi/extensions/takomi-context-manager/skill-registry.ts +87 -87
- package/.pi/extensions/takomi-context-manager/skill-tools.ts +80 -80
- package/.pi/extensions/takomi-context-manager/state.ts +68 -68
- package/.pi/extensions/takomi-context-manager/types.ts +77 -77
- package/.pi/extensions/takomi-runtime/command-text.ts +10 -2
- package/.pi/extensions/takomi-runtime/commands.ts +78 -5
- package/.pi/extensions/takomi-runtime/routing-policy.ts +187 -145
- package/.pi/extensions/takomi-subagents/native-render.ts +41 -41
- package/.pi/extensions/takomi-subagents/pi-subagents-internal.ts +35 -32
- package/.pi/extensions/takomi-subagents/run-types.ts +25 -25
- package/.pi/prompts/build-prompt.md +259 -259
- package/.pi/prompts/design-prompt.md +95 -95
- package/.pi/prompts/genesis-prompt.md +140 -140
- package/.pi/prompts/prime-prompt.md +110 -110
- package/.pi/themes/takomi-aurora.json +88 -88
- package/README.md +2 -4
- package/assets/.agent/skills/21st-dev-components/SKILL.md +244 -244
- package/assets/.agent/skills/anti-gravity/SKILL.md +112 -0
- package/assets/.agent/skills/gemini/SKILL.md +14 -223
- package/assets/.agent/skills/git-commit-generation/SKILL.md +195 -0
- package/package.json +1 -1
- package/src/pi-takomi-core/validation.ts +135 -135
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
4
|
-
import { Type } from "typebox";
|
|
5
|
-
import type { ContextManagerState } from "./state";
|
|
6
|
-
import { findSkill, normalizeName, sortedSkills } from "./skill-registry";
|
|
7
|
-
|
|
8
|
-
function renderSkillIndex(state: ContextManagerState): string {
|
|
9
|
-
const skills = sortedSkills(state.skills);
|
|
10
|
-
if (skills.length === 0) return "Available skills (names only): none discovered.";
|
|
11
|
-
return ["Available skills (names only):", ...skills.map((skill) => `- ${skill.name}`)].join("\n");
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function renderManifest(state: ContextManagerState, names: string[]): string {
|
|
15
|
-
if (names.length === 0) return "No skills requested.";
|
|
16
|
-
return names.map((name) => {
|
|
17
|
-
const skill = findSkill(state.skills, name);
|
|
18
|
-
if (!skill) {
|
|
19
|
-
const close = sortedSkills(state.skills).filter((candidate) => normalizeName(candidate.name).includes(normalizeName(name).slice(0, 4))).slice(0, 5).map((candidate) => candidate.name);
|
|
20
|
-
return [`Skill not found: ${name}`, close.length ? `Known close matches: ${close.join(", ")}` : ""].filter(Boolean).join("\n");
|
|
21
|
-
}
|
|
22
|
-
return [`Skill: ${skill.name}`, `Description: ${skill.description ?? "(no description discovered)"}`, `Location: ${skill.location ?? "(no location discovered)"}`].join("\n");
|
|
23
|
-
}).join("\n\n");
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function loadSkillContent(location: string): Promise<string> {
|
|
27
|
-
const fileName = path.basename(location).toLowerCase();
|
|
28
|
-
if (fileName !== "skill.md" && !location.toLowerCase().endsWith(".md")) throw new Error(`Refusing to load non-markdown skill location: ${location}`);
|
|
29
|
-
return readFile(location, "utf8");
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function registerSkillTools(pi: ExtensionAPI, state: ContextManagerState): void {
|
|
33
|
-
pi.registerTool({
|
|
34
|
-
name: "skill_index",
|
|
35
|
-
label: "Skill Index",
|
|
36
|
-
description: "Return the available skill names only. Use this to inspect capability names without loading descriptions or full instructions.",
|
|
37
|
-
promptSnippet: "List available skill names only for progressive skill loading",
|
|
38
|
-
parameters: Type.Object({}),
|
|
39
|
-
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
40
|
-
state.report.cwd = ctx.cwd;
|
|
41
|
-
state.report.toolCalls.skillIndex += 1;
|
|
42
|
-
return { content: [{ type: "text", text: renderSkillIndex(state) }], details: { skillCount: state.skills.size } };
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
pi.registerTool({
|
|
47
|
-
name: "skill_manifest",
|
|
48
|
-
label: "Skill Manifest",
|
|
49
|
-
description: "Return descriptions and locations for selected skills without loading full SKILL.md instructions.",
|
|
50
|
-
promptSnippet: "Show selected skill descriptions and locations without full instructions",
|
|
51
|
-
parameters: Type.Object({ skills: Type.Array(Type.String({ description: "Skill name to inspect" })) }),
|
|
52
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
53
|
-
state.report.cwd = ctx.cwd;
|
|
54
|
-
state.report.toolCalls.skillManifest += 1;
|
|
55
|
-
return { content: [{ type: "text", text: renderManifest(state, params.skills) }], details: { requested: params.skills } };
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
pi.registerTool({
|
|
60
|
-
name: "skill_load",
|
|
61
|
-
label: "Skill Load",
|
|
62
|
-
description: "Load the full SKILL.md content for one selected skill that will actually be used.",
|
|
63
|
-
promptSnippet: "Load full SKILL.md instructions for one selected skill",
|
|
64
|
-
parameters: Type.Object({ skill: Type.String({ description: "Exact skill name to load" }) }),
|
|
65
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
66
|
-
state.report.cwd = ctx.cwd;
|
|
67
|
-
state.report.toolCalls.skillLoad += 1;
|
|
68
|
-
const skill = findSkill(state.skills, params.skill);
|
|
69
|
-
if (!skill?.location) return { content: [{ type: "text", text: renderManifest(state, [params.skill]) }], details: { found: false, requested: params.skill }, isError: true };
|
|
70
|
-
try {
|
|
71
|
-
const content = await loadSkillContent(skill.location);
|
|
72
|
-
state.report.loadedByTool.push(skill.name);
|
|
73
|
-
return { content: [{ type: "text", text: [`Skill: ${skill.name}`, `Location: ${skill.location}`, "", content].join("\n") }], details: { found: true, skill: skill.name, location: skill.location } };
|
|
74
|
-
} catch (error) {
|
|
75
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
76
|
-
return { content: [{ type: "text", text: message }], details: { found: true, skill: skill.name, error: message }, isError: true };
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
}
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
4
|
+
import { Type } from "typebox";
|
|
5
|
+
import type { ContextManagerState } from "./state";
|
|
6
|
+
import { findSkill, normalizeName, sortedSkills } from "./skill-registry";
|
|
7
|
+
|
|
8
|
+
function renderSkillIndex(state: ContextManagerState): string {
|
|
9
|
+
const skills = sortedSkills(state.skills);
|
|
10
|
+
if (skills.length === 0) return "Available skills (names only): none discovered.";
|
|
11
|
+
return ["Available skills (names only):", ...skills.map((skill) => `- ${skill.name}`)].join("\n");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function renderManifest(state: ContextManagerState, names: string[]): string {
|
|
15
|
+
if (names.length === 0) return "No skills requested.";
|
|
16
|
+
return names.map((name) => {
|
|
17
|
+
const skill = findSkill(state.skills, name);
|
|
18
|
+
if (!skill) {
|
|
19
|
+
const close = sortedSkills(state.skills).filter((candidate) => normalizeName(candidate.name).includes(normalizeName(name).slice(0, 4))).slice(0, 5).map((candidate) => candidate.name);
|
|
20
|
+
return [`Skill not found: ${name}`, close.length ? `Known close matches: ${close.join(", ")}` : ""].filter(Boolean).join("\n");
|
|
21
|
+
}
|
|
22
|
+
return [`Skill: ${skill.name}`, `Description: ${skill.description ?? "(no description discovered)"}`, `Location: ${skill.location ?? "(no location discovered)"}`].join("\n");
|
|
23
|
+
}).join("\n\n");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function loadSkillContent(location: string): Promise<string> {
|
|
27
|
+
const fileName = path.basename(location).toLowerCase();
|
|
28
|
+
if (fileName !== "skill.md" && !location.toLowerCase().endsWith(".md")) throw new Error(`Refusing to load non-markdown skill location: ${location}`);
|
|
29
|
+
return readFile(location, "utf8");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function registerSkillTools(pi: ExtensionAPI, state: ContextManagerState): void {
|
|
33
|
+
pi.registerTool({
|
|
34
|
+
name: "skill_index",
|
|
35
|
+
label: "Skill Index",
|
|
36
|
+
description: "Return the available skill names only. Use this to inspect capability names without loading descriptions or full instructions.",
|
|
37
|
+
promptSnippet: "List available skill names only for progressive skill loading",
|
|
38
|
+
parameters: Type.Object({}),
|
|
39
|
+
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
40
|
+
state.report.cwd = ctx.cwd;
|
|
41
|
+
state.report.toolCalls.skillIndex += 1;
|
|
42
|
+
return { content: [{ type: "text", text: renderSkillIndex(state) }], details: { skillCount: state.skills.size } };
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
pi.registerTool({
|
|
47
|
+
name: "skill_manifest",
|
|
48
|
+
label: "Skill Manifest",
|
|
49
|
+
description: "Return descriptions and locations for selected skills without loading full SKILL.md instructions.",
|
|
50
|
+
promptSnippet: "Show selected skill descriptions and locations without full instructions",
|
|
51
|
+
parameters: Type.Object({ skills: Type.Array(Type.String({ description: "Skill name to inspect" })) }),
|
|
52
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
53
|
+
state.report.cwd = ctx.cwd;
|
|
54
|
+
state.report.toolCalls.skillManifest += 1;
|
|
55
|
+
return { content: [{ type: "text", text: renderManifest(state, params.skills) }], details: { requested: params.skills } };
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
pi.registerTool({
|
|
60
|
+
name: "skill_load",
|
|
61
|
+
label: "Skill Load",
|
|
62
|
+
description: "Load the full SKILL.md content for one selected skill that will actually be used.",
|
|
63
|
+
promptSnippet: "Load full SKILL.md instructions for one selected skill",
|
|
64
|
+
parameters: Type.Object({ skill: Type.String({ description: "Exact skill name to load" }) }),
|
|
65
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
66
|
+
state.report.cwd = ctx.cwd;
|
|
67
|
+
state.report.toolCalls.skillLoad += 1;
|
|
68
|
+
const skill = findSkill(state.skills, params.skill);
|
|
69
|
+
if (!skill?.location) return { content: [{ type: "text", text: renderManifest(state, [params.skill]) }], details: { found: false, requested: params.skill }, isError: true };
|
|
70
|
+
try {
|
|
71
|
+
const content = await loadSkillContent(skill.location);
|
|
72
|
+
state.report.loadedByTool.push(skill.name);
|
|
73
|
+
return { content: [{ type: "text", text: [`Skill: ${skill.name}`, `Location: ${skill.location}`, "", content].join("\n") }], details: { found: true, skill: skill.name, location: skill.location } };
|
|
74
|
+
} catch (error) {
|
|
75
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
76
|
+
return { content: [{ type: "text", text: message }], details: { found: true, skill: skill.name, error: message }, isError: true };
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -1,68 +1,68 @@
|
|
|
1
|
-
import type { ContextReport, PolicyPack, SkillRecord } from "./types";
|
|
2
|
-
|
|
3
|
-
export type ContextManagerState = {
|
|
4
|
-
skills: Map<string, SkillRecord>;
|
|
5
|
-
policies: Map<string, PolicyPack>;
|
|
6
|
-
loadedPolicies: Set<string>;
|
|
7
|
-
readFiles: Set<string>;
|
|
8
|
-
editedFiles: Set<string>;
|
|
9
|
-
writtenFiles: Set<string>;
|
|
10
|
-
report: ContextReport;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export function createEmptyReport(): ContextReport {
|
|
14
|
-
return {
|
|
15
|
-
timestamp: new Date().toISOString(),
|
|
16
|
-
cwd: "",
|
|
17
|
-
userPrompt: "",
|
|
18
|
-
skillCount: 0,
|
|
19
|
-
candidates: [],
|
|
20
|
-
loadedByTool: [],
|
|
21
|
-
loadedPolicies: [],
|
|
22
|
-
readFiles: [],
|
|
23
|
-
editedFiles: [],
|
|
24
|
-
writtenFiles: [],
|
|
25
|
-
blockedActions: [],
|
|
26
|
-
duplicateExtensionWarnings: [],
|
|
27
|
-
promptRewrite: {
|
|
28
|
-
attempted: false,
|
|
29
|
-
changed: false,
|
|
30
|
-
originalLength: 0,
|
|
31
|
-
rewrittenLength: 0,
|
|
32
|
-
removedSections: [],
|
|
33
|
-
warnings: [],
|
|
34
|
-
},
|
|
35
|
-
toolCalls: {
|
|
36
|
-
skillIndex: 0,
|
|
37
|
-
skillManifest: 0,
|
|
38
|
-
skillLoad: 0,
|
|
39
|
-
policyManifest: 0,
|
|
40
|
-
policyLoad: 0,
|
|
41
|
-
contextReport: 0,
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function createState(): ContextManagerState {
|
|
47
|
-
return {
|
|
48
|
-
skills: new Map(),
|
|
49
|
-
policies: new Map(),
|
|
50
|
-
loadedPolicies: new Set(),
|
|
51
|
-
readFiles: new Set(),
|
|
52
|
-
editedFiles: new Set(),
|
|
53
|
-
writtenFiles: new Set(),
|
|
54
|
-
report: createEmptyReport(),
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function syncReportLedger(state: ContextManagerState): void {
|
|
59
|
-
state.report.loadedPolicies = [...state.loadedPolicies].sort();
|
|
60
|
-
state.report.readFiles = [...state.readFiles].sort();
|
|
61
|
-
state.report.editedFiles = [...state.editedFiles].sort();
|
|
62
|
-
state.report.writtenFiles = [...state.writtenFiles].sort();
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function recordBlocked(state: ContextManagerState, toolName: string, reason: string): void {
|
|
66
|
-
state.report.blockedActions.push({ toolName, reason, timestamp: new Date().toISOString() });
|
|
67
|
-
syncReportLedger(state);
|
|
68
|
-
}
|
|
1
|
+
import type { ContextReport, PolicyPack, SkillRecord } from "./types";
|
|
2
|
+
|
|
3
|
+
export type ContextManagerState = {
|
|
4
|
+
skills: Map<string, SkillRecord>;
|
|
5
|
+
policies: Map<string, PolicyPack>;
|
|
6
|
+
loadedPolicies: Set<string>;
|
|
7
|
+
readFiles: Set<string>;
|
|
8
|
+
editedFiles: Set<string>;
|
|
9
|
+
writtenFiles: Set<string>;
|
|
10
|
+
report: ContextReport;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export function createEmptyReport(): ContextReport {
|
|
14
|
+
return {
|
|
15
|
+
timestamp: new Date().toISOString(),
|
|
16
|
+
cwd: "",
|
|
17
|
+
userPrompt: "",
|
|
18
|
+
skillCount: 0,
|
|
19
|
+
candidates: [],
|
|
20
|
+
loadedByTool: [],
|
|
21
|
+
loadedPolicies: [],
|
|
22
|
+
readFiles: [],
|
|
23
|
+
editedFiles: [],
|
|
24
|
+
writtenFiles: [],
|
|
25
|
+
blockedActions: [],
|
|
26
|
+
duplicateExtensionWarnings: [],
|
|
27
|
+
promptRewrite: {
|
|
28
|
+
attempted: false,
|
|
29
|
+
changed: false,
|
|
30
|
+
originalLength: 0,
|
|
31
|
+
rewrittenLength: 0,
|
|
32
|
+
removedSections: [],
|
|
33
|
+
warnings: [],
|
|
34
|
+
},
|
|
35
|
+
toolCalls: {
|
|
36
|
+
skillIndex: 0,
|
|
37
|
+
skillManifest: 0,
|
|
38
|
+
skillLoad: 0,
|
|
39
|
+
policyManifest: 0,
|
|
40
|
+
policyLoad: 0,
|
|
41
|
+
contextReport: 0,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createState(): ContextManagerState {
|
|
47
|
+
return {
|
|
48
|
+
skills: new Map(),
|
|
49
|
+
policies: new Map(),
|
|
50
|
+
loadedPolicies: new Set(),
|
|
51
|
+
readFiles: new Set(),
|
|
52
|
+
editedFiles: new Set(),
|
|
53
|
+
writtenFiles: new Set(),
|
|
54
|
+
report: createEmptyReport(),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function syncReportLedger(state: ContextManagerState): void {
|
|
59
|
+
state.report.loadedPolicies = [...state.loadedPolicies].sort();
|
|
60
|
+
state.report.readFiles = [...state.readFiles].sort();
|
|
61
|
+
state.report.editedFiles = [...state.editedFiles].sort();
|
|
62
|
+
state.report.writtenFiles = [...state.writtenFiles].sort();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function recordBlocked(state: ContextManagerState, toolName: string, reason: string): void {
|
|
66
|
+
state.report.blockedActions.push({ toolName, reason, timestamp: new Date().toISOString() });
|
|
67
|
+
syncReportLedger(state);
|
|
68
|
+
}
|
|
@@ -1,77 +1,77 @@
|
|
|
1
|
-
export type SkillRecord = {
|
|
2
|
-
name: string;
|
|
3
|
-
description?: string;
|
|
4
|
-
location?: string;
|
|
5
|
-
source: "systemPromptOptions" | "xml" | "tool";
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
export type CandidateContext = {
|
|
9
|
-
name: string;
|
|
10
|
-
score: number;
|
|
11
|
-
confidence: "high" | "medium";
|
|
12
|
-
suggestedAction: "skill_load" | "skill_manifest";
|
|
13
|
-
reasons: string[];
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export type PolicyPack = {
|
|
17
|
-
name: string;
|
|
18
|
-
description: string;
|
|
19
|
-
content: string;
|
|
20
|
-
path?: string;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export type Prerequisite = { type: "policies"; policies: string[] };
|
|
24
|
-
|
|
25
|
-
export type SkillIndexDisplayMode = "hidden" | "candidates" | "all-names" | "auto";
|
|
26
|
-
|
|
27
|
-
export type ContextManagerConfig = {
|
|
28
|
-
skillDisplay: {
|
|
29
|
-
mode: SkillIndexDisplayMode;
|
|
30
|
-
maxVisibleSkillNames: number;
|
|
31
|
-
alwaysShowToolInstructions: boolean;
|
|
32
|
-
};
|
|
33
|
-
candidateRouter: {
|
|
34
|
-
maxCandidates: number;
|
|
35
|
-
highConfidence: number;
|
|
36
|
-
mediumConfidence: number;
|
|
37
|
-
};
|
|
38
|
-
policyPaths: string[];
|
|
39
|
-
policyFiles?: Record<string, string>;
|
|
40
|
-
toolPrerequisites: Record<string, Prerequisite[]>;
|
|
41
|
-
promptCompaction: {
|
|
42
|
-
compactModelRouting: boolean;
|
|
43
|
-
compactModelRegistry: boolean;
|
|
44
|
-
compactSkillDescriptions: boolean;
|
|
45
|
-
};
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
export type ContextReport = {
|
|
49
|
-
timestamp: string;
|
|
50
|
-
cwd: string;
|
|
51
|
-
userPrompt: string;
|
|
52
|
-
skillCount: number;
|
|
53
|
-
candidates: CandidateContext[];
|
|
54
|
-
loadedByTool: string[];
|
|
55
|
-
loadedPolicies: string[];
|
|
56
|
-
readFiles: string[];
|
|
57
|
-
editedFiles: string[];
|
|
58
|
-
writtenFiles: string[];
|
|
59
|
-
blockedActions: Array<{ toolName: string; reason: string; timestamp: string }>;
|
|
60
|
-
duplicateExtensionWarnings: Array<{ toolName: string; paths: string[] }>;
|
|
61
|
-
promptRewrite: {
|
|
62
|
-
attempted: boolean;
|
|
63
|
-
changed: boolean;
|
|
64
|
-
originalLength: number;
|
|
65
|
-
rewrittenLength: number;
|
|
66
|
-
removedSections: string[];
|
|
67
|
-
warnings: string[];
|
|
68
|
-
};
|
|
69
|
-
toolCalls: {
|
|
70
|
-
skillIndex: number;
|
|
71
|
-
skillManifest: number;
|
|
72
|
-
skillLoad: number;
|
|
73
|
-
policyManifest: number;
|
|
74
|
-
policyLoad: number;
|
|
75
|
-
contextReport: number;
|
|
76
|
-
};
|
|
77
|
-
};
|
|
1
|
+
export type SkillRecord = {
|
|
2
|
+
name: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
location?: string;
|
|
5
|
+
source: "systemPromptOptions" | "xml" | "tool";
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type CandidateContext = {
|
|
9
|
+
name: string;
|
|
10
|
+
score: number;
|
|
11
|
+
confidence: "high" | "medium";
|
|
12
|
+
suggestedAction: "skill_load" | "skill_manifest";
|
|
13
|
+
reasons: string[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type PolicyPack = {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
content: string;
|
|
20
|
+
path?: string;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type Prerequisite = { type: "policies"; policies: string[] };
|
|
24
|
+
|
|
25
|
+
export type SkillIndexDisplayMode = "hidden" | "candidates" | "all-names" | "auto";
|
|
26
|
+
|
|
27
|
+
export type ContextManagerConfig = {
|
|
28
|
+
skillDisplay: {
|
|
29
|
+
mode: SkillIndexDisplayMode;
|
|
30
|
+
maxVisibleSkillNames: number;
|
|
31
|
+
alwaysShowToolInstructions: boolean;
|
|
32
|
+
};
|
|
33
|
+
candidateRouter: {
|
|
34
|
+
maxCandidates: number;
|
|
35
|
+
highConfidence: number;
|
|
36
|
+
mediumConfidence: number;
|
|
37
|
+
};
|
|
38
|
+
policyPaths: string[];
|
|
39
|
+
policyFiles?: Record<string, string>;
|
|
40
|
+
toolPrerequisites: Record<string, Prerequisite[]>;
|
|
41
|
+
promptCompaction: {
|
|
42
|
+
compactModelRouting: boolean;
|
|
43
|
+
compactModelRegistry: boolean;
|
|
44
|
+
compactSkillDescriptions: boolean;
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type ContextReport = {
|
|
49
|
+
timestamp: string;
|
|
50
|
+
cwd: string;
|
|
51
|
+
userPrompt: string;
|
|
52
|
+
skillCount: number;
|
|
53
|
+
candidates: CandidateContext[];
|
|
54
|
+
loadedByTool: string[];
|
|
55
|
+
loadedPolicies: string[];
|
|
56
|
+
readFiles: string[];
|
|
57
|
+
editedFiles: string[];
|
|
58
|
+
writtenFiles: string[];
|
|
59
|
+
blockedActions: Array<{ toolName: string; reason: string; timestamp: string }>;
|
|
60
|
+
duplicateExtensionWarnings: Array<{ toolName: string; paths: string[] }>;
|
|
61
|
+
promptRewrite: {
|
|
62
|
+
attempted: boolean;
|
|
63
|
+
changed: boolean;
|
|
64
|
+
originalLength: number;
|
|
65
|
+
rewrittenLength: number;
|
|
66
|
+
removedSections: string[];
|
|
67
|
+
warnings: string[];
|
|
68
|
+
};
|
|
69
|
+
toolCalls: {
|
|
70
|
+
skillIndex: number;
|
|
71
|
+
skillManifest: number;
|
|
72
|
+
skillLoad: number;
|
|
73
|
+
policyManifest: number;
|
|
74
|
+
policyLoad: number;
|
|
75
|
+
contextReport: number;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
@@ -16,7 +16,7 @@ const ROOT_COMPLETIONS: TakomiCompletion[] = [
|
|
|
16
16
|
{ value: "mode", label: "mode", description: "Set direct, orchestrate, or review mode" },
|
|
17
17
|
{ value: "gate", label: "gate", description: "Set auto or review-gated execution" },
|
|
18
18
|
{ value: "subagents", label: "subagents", description: "Control subagent usage and view" },
|
|
19
|
-
{ value: "routing", label: "routing", description: "
|
|
19
|
+
{ value: "routing", label: "routing", description: "Show or update Takomi model routing policy" },
|
|
20
20
|
];
|
|
21
21
|
|
|
22
22
|
const SUBCOMMAND_COMPLETIONS: Record<string, TakomiCompletion[]> = {
|
|
@@ -37,6 +37,12 @@ const SUBCOMMAND_COMPLETIONS: Record<string, TakomiCompletion[]> = {
|
|
|
37
37
|
{ value: "collapse", label: "collapse", description: "Collapse native tool results" },
|
|
38
38
|
{ value: "toggle", label: "toggle", description: "Toggle native tool result expansion" },
|
|
39
39
|
],
|
|
40
|
+
routing: [
|
|
41
|
+
{ value: "show", label: "show", description: "Show active routing policy source, path, and contents" },
|
|
42
|
+
{ value: "global", label: "global", description: "Save following policy text globally" },
|
|
43
|
+
{ value: "local", label: "local", description: "Save following policy text as a project override" },
|
|
44
|
+
{ value: "where", label: "where", description: "Show where the active routing policy is loaded from" },
|
|
45
|
+
],
|
|
40
46
|
};
|
|
41
47
|
|
|
42
48
|
const SUBCOMMAND_ALIASES: Record<string, string> = {
|
|
@@ -71,7 +77,9 @@ export function commandHelp(): string {
|
|
|
71
77
|
"/takomi mode <direct|orchestrate|review>",
|
|
72
78
|
"/takomi gate <auto|review>",
|
|
73
79
|
"/takomi subagents <on|off|status|expand|collapse|toggle>",
|
|
74
|
-
"/takomi routing
|
|
80
|
+
"/takomi routing [show|where]",
|
|
81
|
+
"/takomi routing <policy text> # updates global policy",
|
|
82
|
+
"/takomi routing local <policy text> # project override",
|
|
75
83
|
"/takomi-status",
|
|
76
84
|
"/takomi-reset",
|
|
77
85
|
].join("\n");
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
} from "../../../src/pi-takomi-core";
|
|
8
8
|
import { commandHelp, completions, statusText, workflowPrompt } from "./command-text";
|
|
9
9
|
import type { TakomiSubagentController } from "./subagent-types";
|
|
10
|
-
import { installTakomiRoutingPolicy } from "./routing-policy";
|
|
10
|
+
import { installTakomiRoutingPolicy, resolveTakomiRoutingPolicy, type RoutingPolicyInstallScope } from "./routing-policy";
|
|
11
11
|
|
|
12
12
|
export type TakomiRuntimeCommandState = {
|
|
13
13
|
enabled: boolean;
|
|
@@ -82,14 +82,87 @@ export function registerTakomiCommands(pi: ExtensionAPI, options: RegisterTakomi
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
async function handleRouting(ctx: ExtensionCommandContext, body?: string): Promise<void> {
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
const trimmed = body?.trim() ?? "";
|
|
86
|
+
|
|
87
|
+
const usage = [
|
|
88
|
+
"Usage:",
|
|
89
|
+
"/takomi routing show Print the full active policy",
|
|
90
|
+
"/takomi routing where Show source/path only",
|
|
91
|
+
"/takomi routing <policy text> Update the global policy",
|
|
92
|
+
"/takomi routing local <policy text> Create/update this project's override",
|
|
93
|
+
].join("\n");
|
|
94
|
+
|
|
95
|
+
async function showRoutingHelp(): Promise<void> {
|
|
96
|
+
const resolved = await resolveTakomiRoutingPolicy(ctx.cwd);
|
|
97
|
+
ctx.ui.notify([
|
|
98
|
+
"Takomi routing options",
|
|
99
|
+
"",
|
|
100
|
+
`Active source: ${resolved.source}`,
|
|
101
|
+
`Active path: ${resolved.policyPath ?? "not found"}`,
|
|
102
|
+
"",
|
|
103
|
+
usage,
|
|
104
|
+
"",
|
|
105
|
+
"Resolution order: project .pi/takomi/model-routing.md → global ~/.pi/takomi/model-routing.md → bundled fallback.",
|
|
106
|
+
].join("\n"), "warning");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function showRoutingLocation(): Promise<void> {
|
|
110
|
+
const resolved = await resolveTakomiRoutingPolicy(ctx.cwd);
|
|
111
|
+
ctx.ui.notify([
|
|
112
|
+
"Active Takomi routing policy location",
|
|
113
|
+
"",
|
|
114
|
+
`Source: ${resolved.source}`,
|
|
115
|
+
`Path: ${resolved.policyPath ?? "not found"}`,
|
|
116
|
+
"",
|
|
117
|
+
usage,
|
|
118
|
+
].join("\n"), resolved.source === "missing" ? "warning" : "info");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function showActivePolicy(): Promise<void> {
|
|
122
|
+
const resolved = await resolveTakomiRoutingPolicy(ctx.cwd);
|
|
123
|
+
const text = resolved.text ?? "No Takomi routing policy found.";
|
|
124
|
+
const clipped = text.length > 6000 ? `${text.slice(0, 6000)}\n\n…truncated; open the file above for the full policy.` : text;
|
|
125
|
+
ctx.ui.notify([
|
|
126
|
+
"Active Takomi routing policy",
|
|
127
|
+
"",
|
|
128
|
+
`Source: ${resolved.source}`,
|
|
129
|
+
`Path: ${resolved.policyPath ?? "not found"}`,
|
|
130
|
+
"",
|
|
131
|
+
clipped,
|
|
132
|
+
"",
|
|
133
|
+
usage,
|
|
134
|
+
].join("\n"), resolved.source === "missing" ? "warning" : "info");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!trimmed) {
|
|
138
|
+
await showRoutingHelp();
|
|
87
139
|
return;
|
|
88
140
|
}
|
|
141
|
+
if (/^(where|path|status)$/i.test(trimmed)) {
|
|
142
|
+
await showRoutingLocation();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (/^(show|view)$/i.test(trimmed)) {
|
|
146
|
+
await showActivePolicy();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (/^(global|local|project|set)$/i.test(trimmed)) {
|
|
151
|
+
ctx.ui.notify(usage, "warning");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const scopeMatch = trimmed.match(/^(global|local|project)\s+([\s\S]+)$/i);
|
|
156
|
+
const scope: RoutingPolicyInstallScope = scopeMatch?.[1]?.toLowerCase() === "local" || scopeMatch?.[1]?.toLowerCase() === "project" ? "project" : "global";
|
|
157
|
+
const policyText = scopeMatch?.[2] ?? trimmed.replace(/^set\s+/i, "");
|
|
158
|
+
|
|
89
159
|
try {
|
|
90
|
-
const result = await installTakomiRoutingPolicy(ctx.cwd,
|
|
160
|
+
const result = await installTakomiRoutingPolicy(ctx.cwd, policyText, { scope });
|
|
91
161
|
const detected = result.detectedDefaults.length ? `\n\nDetected defaults:\n- ${result.detectedDefaults.join("\n- ")}` : "\n\nNo model names were auto-detected; saved policy only.";
|
|
92
|
-
|
|
162
|
+
const overrideNote = scope === "global"
|
|
163
|
+
? "\n\nThis will be used by every project unless that project has its own .pi/takomi/model-routing.md override."
|
|
164
|
+
: "\n\nThis project-local policy overrides the global policy for the current project.";
|
|
165
|
+
ctx.ui.notify(`Takomi routing policy updated (${scope}).\n\nPolicy: ${result.policyPath}\nSettings: ${result.settingsPath}${detected}${overrideNote}`, "info");
|
|
93
166
|
} catch (error) {
|
|
94
167
|
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
95
168
|
}
|