takomi 2.1.33 ā 2.1.34
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/extensions/takomi-runtime/command-text.ts +2 -4
- package/.pi/extensions/takomi-runtime/commands.ts +15 -21
- package/.pi/extensions/takomi-runtime/index.ts +127 -53
- package/.pi/extensions/takomi-runtime/ui.ts +18 -11
- package/.pi/extensions/takomi-subagents/index.ts +2 -0
- package/.pi/extensions/takomi-subagents/native-render.ts +27 -5
- package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +1 -1
- package/.pi/extensions/takomi-subagents/tool-runner.ts +1 -0
- package/package.json +1 -1
- package/src/cli.js +8 -4
- package/src/pi-harness.js +36 -14
|
@@ -35,9 +35,6 @@ const SUBCOMMAND_COMPLETIONS: Record<string, TakomiCompletion[]> = {
|
|
|
35
35
|
{ value: "status", label: "status", description: "Show active subagents" },
|
|
36
36
|
{ value: "on", label: "on", description: "Allow subagent delegation" },
|
|
37
37
|
{ value: "off", label: "off", description: "Disable subagent delegation" },
|
|
38
|
-
{ value: "expand", label: "expand", description: "Expand native tool results" },
|
|
39
|
-
{ value: "collapse", label: "collapse", description: "Collapse native tool results" },
|
|
40
|
-
{ value: "toggle", label: "toggle", description: "Toggle native tool result expansion" },
|
|
41
38
|
],
|
|
42
39
|
stats: [
|
|
43
40
|
{ value: "overview", label: "overview", description: "Show the full profile-card dashboard" },
|
|
@@ -98,7 +95,7 @@ export function commandHelp(): string {
|
|
|
98
95
|
"/takomi plan [title]",
|
|
99
96
|
"/takomi mode <direct|orchestrate|review>",
|
|
100
97
|
"/takomi gate <auto|review>",
|
|
101
|
-
"/takomi subagents <on|off|status
|
|
98
|
+
"/takomi subagents <on|off|status>",
|
|
102
99
|
"/takomi stats [overview|daily|models|projects|projects-full|sessions|sessions-full|tasks|tasks-full|tools|subagents|sources] [since 7d]",
|
|
103
100
|
"/takomi routing [show|where]",
|
|
104
101
|
"/takomi routing <policy text> # updates global policy",
|
|
@@ -134,6 +131,7 @@ export function statusText(state: TakomiRuntimeCommandState, subagentController:
|
|
|
134
131
|
return [
|
|
135
132
|
`Takomi: ${state.enabled ? "on" : "off"}`,
|
|
136
133
|
`Mode: ${state.role}`,
|
|
134
|
+
`Source: ${state.modeSource ?? "idle"}${state.modeReason ? ` (${state.modeReason})` : ""}`,
|
|
137
135
|
`Stage: ${state.stage ?? "-"}`,
|
|
138
136
|
`Workflow: ${state.workflow ?? "-"}`,
|
|
139
137
|
`Plan bias: ${state.planMode ? "on" : "off"}`,
|
|
@@ -20,6 +20,8 @@ export type TakomiRuntimeCommandState = {
|
|
|
20
20
|
workflow?: TakomiWorkflowId;
|
|
21
21
|
activeSessionId?: string;
|
|
22
22
|
subagentsEnabled: boolean;
|
|
23
|
+
modeSource?: "idle" | "manual" | "model" | "board";
|
|
24
|
+
modeReason?: string;
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
type RegisterTakomiCommandOptions = {
|
|
@@ -36,6 +38,8 @@ export function registerTakomiCommands(pi: ExtensionAPI, options: RegisterTakomi
|
|
|
36
38
|
async function handleStage(ctx: ExtensionCommandContext, stage: VibeLifecycleStage, prompt?: string): Promise<void> {
|
|
37
39
|
await options.updateState(ctx, () => {
|
|
38
40
|
options.getState().enabled = true;
|
|
41
|
+
options.getState().modeSource = "manual";
|
|
42
|
+
options.getState().modeReason = `/${stage}`;
|
|
39
43
|
options.setStageAndWorkflow(stage, { preserveRole: stage === "genesis" && options.getState().role === "orchestrator" });
|
|
40
44
|
options.getState().planMode = stage !== "build";
|
|
41
45
|
}, workflowPrompt(stage, prompt));
|
|
@@ -54,17 +58,27 @@ export function registerTakomiCommands(pi: ExtensionAPI, options: RegisterTakomi
|
|
|
54
58
|
state.autoOrch = false;
|
|
55
59
|
state.planMode = false;
|
|
56
60
|
state.role = "general";
|
|
61
|
+
state.stage = undefined;
|
|
62
|
+
state.workflow = undefined;
|
|
63
|
+
state.modeSource = "idle";
|
|
64
|
+
state.modeReason = undefined;
|
|
57
65
|
} else if (mode === "orchestrate") {
|
|
58
66
|
state.autoOrch = true;
|
|
59
67
|
state.planMode = true;
|
|
60
68
|
state.role = "orchestrator";
|
|
61
69
|
state.stage = hasGenesis ? "build" : "genesis";
|
|
62
70
|
state.workflow = hasGenesis ? "vibe-build" : "vibe-genesis";
|
|
71
|
+
state.modeSource = "manual";
|
|
72
|
+
state.modeReason = "/takomi mode orchestrate";
|
|
63
73
|
} else {
|
|
64
74
|
state.autoOrch = false;
|
|
65
75
|
state.planMode = true;
|
|
66
76
|
state.launchMode = "manual";
|
|
67
77
|
state.role = "review";
|
|
78
|
+
state.stage = undefined;
|
|
79
|
+
state.workflow = undefined;
|
|
80
|
+
state.modeSource = "manual";
|
|
81
|
+
state.modeReason = "/takomi mode review";
|
|
68
82
|
}
|
|
69
83
|
}, () => `Takomi mode set to ${mode}`);
|
|
70
84
|
}
|
|
@@ -198,27 +212,7 @@ export function registerTakomiCommands(pi: ExtensionAPI, options: RegisterTakomi
|
|
|
198
212
|
ctx.ui.notify(statusText(options.getState(), controller), controller.hasRuns() ? "info" : "warning");
|
|
199
213
|
return;
|
|
200
214
|
}
|
|
201
|
-
|
|
202
|
-
ctx.ui.setToolsExpanded(true);
|
|
203
|
-
ctx.ui.notify("Expanded native tool results for Takomi subagent output.", "info");
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
if (action === "collapse") {
|
|
207
|
-
ctx.ui.setToolsExpanded(false);
|
|
208
|
-
ctx.ui.notify("Collapsed native tool results.", "info");
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
if (action === "toggle") {
|
|
212
|
-
const expanded = !ctx.ui.getToolsExpanded();
|
|
213
|
-
ctx.ui.setToolsExpanded(expanded);
|
|
214
|
-
ctx.ui.notify(`${expanded ? "Expanded" : "Collapsed"} native tool results.`, "info");
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
if (action === "next" || action === "prev") {
|
|
218
|
-
ctx.ui.notify("Takomi now uses Pi's native inline result UI. Subagent navigation is handled by the transcript/tool results.", "info");
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
ctx.ui.notify("Usage: /takomi subagents <on|off|status|expand|collapse|toggle>", "warning");
|
|
215
|
+
ctx.ui.notify("Usage: /takomi subagents <on|off|status>", "warning");
|
|
222
216
|
}
|
|
223
217
|
|
|
224
218
|
pi.registerCommand("takomi", {
|
|
@@ -58,6 +58,8 @@ import {
|
|
|
58
58
|
} from "./profile";
|
|
59
59
|
import { installTakomiRoutingPolicy, previewTakomiRoutingPolicy, renderRoutingPolicyPreview, resolveTakomiRoutingPolicy } from "./routing-policy";
|
|
60
60
|
|
|
61
|
+
type TakomiModeSource = "idle" | "manual" | "model" | "board";
|
|
62
|
+
|
|
61
63
|
type TakomiState = {
|
|
62
64
|
enabled: boolean;
|
|
63
65
|
autoOrch: boolean;
|
|
@@ -69,15 +71,18 @@ type TakomiState = {
|
|
|
69
71
|
activeSessionId?: string;
|
|
70
72
|
subagentsEnabled: boolean;
|
|
71
73
|
lastFullPromptKey?: string;
|
|
74
|
+
modeSource?: TakomiModeSource;
|
|
75
|
+
modeReason?: string;
|
|
72
76
|
};
|
|
73
77
|
|
|
74
78
|
const DEFAULT_STATE: TakomiState = {
|
|
75
79
|
enabled: true,
|
|
76
|
-
autoOrch:
|
|
80
|
+
autoOrch: false,
|
|
77
81
|
launchMode: "auto",
|
|
78
82
|
planMode: false,
|
|
79
83
|
role: "general",
|
|
80
84
|
subagentsEnabled: true,
|
|
85
|
+
modeSource: "idle",
|
|
81
86
|
};
|
|
82
87
|
|
|
83
88
|
const STATE_ENTRY = "takomi-runtime-state";
|
|
@@ -97,6 +102,8 @@ const ThinkingSchema = Type.Union([
|
|
|
97
102
|
Type.Literal("xhigh"),
|
|
98
103
|
]);
|
|
99
104
|
|
|
105
|
+
const TakomiModeSchema = StringEnum(["idle", "code", "orchestrate", "review", "genesis", "design", "build"] as const);
|
|
106
|
+
|
|
100
107
|
function cloneState(state: TakomiState): TakomiState {
|
|
101
108
|
return { ...state };
|
|
102
109
|
}
|
|
@@ -111,6 +118,8 @@ function formatState(state: TakomiState): string {
|
|
|
111
118
|
`launch=${state.launchMode}`,
|
|
112
119
|
`plan=${state.planMode ? "on" : "off"}`,
|
|
113
120
|
`subagents=${state.subagentsEnabled ? "on" : "off"}`,
|
|
121
|
+
`source=${state.modeSource ?? "idle"}`,
|
|
122
|
+
state.modeReason ? `reason=${state.modeReason}` : "",
|
|
114
123
|
state.activeSessionId ? `session=${state.activeSessionId}` : "",
|
|
115
124
|
].filter(Boolean).join(" | ");
|
|
116
125
|
}
|
|
@@ -766,6 +775,8 @@ export default function takomiRuntime(pi: ExtensionAPI) {
|
|
|
766
775
|
state.stage = "genesis";
|
|
767
776
|
state.workflow = "vibe-genesis";
|
|
768
777
|
state.role = "orchestrator";
|
|
778
|
+
state.modeSource = "manual";
|
|
779
|
+
state.modeReason = "/takomi plan";
|
|
769
780
|
});
|
|
770
781
|
return `Takomi plan created session ${session.sessionId}\nMaster plan: ${paths.masterPlan}`;
|
|
771
782
|
},
|
|
@@ -783,34 +794,82 @@ export default function takomiRuntime(pi: ExtensionAPI) {
|
|
|
783
794
|
},
|
|
784
795
|
});
|
|
785
796
|
|
|
786
|
-
pi.registerShortcut("alt+t", {
|
|
787
|
-
description: "Toggle native tool result expansion",
|
|
788
|
-
handler: async (ctx) => {
|
|
789
|
-
const expanded = !ctx.ui.getToolsExpanded();
|
|
790
|
-
ctx.ui.setToolsExpanded(expanded);
|
|
791
|
-
ctx.ui.notify(`${expanded ? "Expanded" : "Collapsed"} native tool results.`, "info");
|
|
792
|
-
},
|
|
793
|
-
});
|
|
794
797
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
798
|
+
async function applyTakomiMode(ctx: ExtensionContext, mode: string, source: TakomiModeSource, reason?: string): Promise<string> {
|
|
799
|
+
const hasGenesis = await hasGenesisArtifacts(ctx.cwd);
|
|
800
|
+
state.enabled = true;
|
|
801
|
+
state.modeSource = source;
|
|
802
|
+
state.modeReason = reason?.trim() || undefined;
|
|
803
|
+
|
|
804
|
+
switch (mode) {
|
|
805
|
+
case "idle":
|
|
806
|
+
state.modeSource = "idle";
|
|
807
|
+
state.modeReason = undefined;
|
|
808
|
+
state.autoOrch = false;
|
|
809
|
+
state.planMode = false;
|
|
810
|
+
state.role = "general";
|
|
811
|
+
state.stage = undefined;
|
|
812
|
+
state.workflow = undefined;
|
|
813
|
+
break;
|
|
814
|
+
case "code":
|
|
815
|
+
state.autoOrch = false;
|
|
816
|
+
state.planMode = false;
|
|
817
|
+
state.role = "code";
|
|
818
|
+
state.stage = undefined;
|
|
819
|
+
state.workflow = undefined;
|
|
820
|
+
break;
|
|
821
|
+
case "review":
|
|
822
|
+
state.autoOrch = false;
|
|
823
|
+
state.planMode = true;
|
|
824
|
+
state.launchMode = "manual";
|
|
825
|
+
state.role = "review";
|
|
826
|
+
state.stage = undefined;
|
|
827
|
+
state.workflow = undefined;
|
|
828
|
+
break;
|
|
829
|
+
case "orchestrate":
|
|
830
|
+
state.autoOrch = true;
|
|
831
|
+
state.planMode = true;
|
|
832
|
+
state.role = "orchestrator";
|
|
833
|
+
state.stage = hasGenesis ? "build" : "genesis";
|
|
834
|
+
state.workflow = hasGenesis ? "vibe-build" : "vibe-genesis";
|
|
835
|
+
break;
|
|
836
|
+
case "genesis":
|
|
837
|
+
case "design":
|
|
838
|
+
case "build":
|
|
839
|
+
setStageAndWorkflow(state, mode);
|
|
840
|
+
state.autoOrch = mode === "build";
|
|
841
|
+
state.planMode = mode !== "build";
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
802
844
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
}
|
|
808
|
-
}
|
|
845
|
+
persistState();
|
|
846
|
+
syncContextPanelState();
|
|
847
|
+
await refreshUi(ctx, state);
|
|
848
|
+
const label = state.modeSource === "idle" ? "idle" : `${state.modeSource}:${state.stage ?? state.role}`;
|
|
849
|
+
return `Takomi mode set to ${label}${state.modeReason ? ` (${state.modeReason})` : ""}.`;
|
|
850
|
+
}
|
|
809
851
|
|
|
810
|
-
pi.
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
852
|
+
pi.registerTool({
|
|
853
|
+
name: "takomi_mode",
|
|
854
|
+
label: "Takomi Mode",
|
|
855
|
+
description: "Set or clear the visible Takomi runtime mode after the active model decides a task benefits from code, review, lifecycle, or orchestration handling.",
|
|
856
|
+
promptSnippet: "Optional: call takomi_mode when you decide the current request should visibly enter Takomi code/review/orchestration/lifecycle mode. Do not call it for normal creative/general conversation. Use mode=idle to clear Takomi mode.",
|
|
857
|
+
promptGuidelines: [
|
|
858
|
+
"Let the user's request drive the choice; do not switch modes just because a vague word like code/review/build appears alone.",
|
|
859
|
+
"Prefer mode=code for direct coding in the current chat, mode=orchestrate only for broad/multi-step durable work, and mode=review for critique/audit/QA.",
|
|
860
|
+
"Use mode=idle when the user asks normal non-coding/non-Takomi questions and Takomi should get out of the way.",
|
|
861
|
+
],
|
|
862
|
+
parameters: Type.Object({
|
|
863
|
+
mode: TakomiModeSchema,
|
|
864
|
+
reason: Type.Optional(Type.String({ description: "Short human-readable reason for the switch" })),
|
|
865
|
+
}),
|
|
866
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
867
|
+
const text = await applyTakomiMode(ctx, params.mode, "model", params.reason);
|
|
868
|
+
ctx.ui.notify(text, "info");
|
|
869
|
+
return {
|
|
870
|
+
content: [{ type: "text", text }],
|
|
871
|
+
details: { mode: params.mode, source: state.modeSource, reason: state.modeReason, role: state.role, stage: state.stage, workflow: state.workflow },
|
|
872
|
+
};
|
|
814
873
|
},
|
|
815
874
|
});
|
|
816
875
|
|
|
@@ -1007,6 +1066,12 @@ ${stateJson}`
|
|
|
1007
1066
|
}
|
|
1008
1067
|
}
|
|
1009
1068
|
sessionState.tasks[idx] = nextTask;
|
|
1069
|
+
state.activeSessionId = params.sessionId;
|
|
1070
|
+
state.modeSource = state.modeSource === "idle" ? "board" : state.modeSource;
|
|
1071
|
+
state.modeReason = state.modeReason ?? "board task update";
|
|
1072
|
+
persistState();
|
|
1073
|
+
syncContextPanelState();
|
|
1074
|
+
await refreshUi(ctx, state);
|
|
1010
1075
|
const nextState = buildSessionState(
|
|
1011
1076
|
sessionState.sessionId,
|
|
1012
1077
|
sessionState.title,
|
|
@@ -1062,8 +1127,11 @@ ${stateJson}`
|
|
|
1062
1127
|
}
|
|
1063
1128
|
}
|
|
1064
1129
|
state.activeSessionId = nextState.sessionId;
|
|
1130
|
+
state.modeSource = "board";
|
|
1131
|
+
state.modeReason = `expanded ${params.stage} stage`;
|
|
1065
1132
|
persistState();
|
|
1066
1133
|
syncContextPanelState();
|
|
1134
|
+
await refreshUi(ctx, state);
|
|
1067
1135
|
|
|
1068
1136
|
return {
|
|
1069
1137
|
content: [{ type: "text", text: `Expanded ${params.stage} stage in session ${nextState.sessionId}.\n\nDocs: ${paths.root}\nState: ${paths.stateFile}\n\n${buildTaskRows(nextState.tasks)}` }],
|
|
@@ -1104,8 +1172,11 @@ ${stateJson}`
|
|
|
1104
1172
|
state.role = "orchestrator";
|
|
1105
1173
|
state.stage = nextState.lifecycle.genesis.status === "completed" ? "build" : "genesis";
|
|
1106
1174
|
state.workflow = state.stage === "genesis" ? "vibe-genesis" : "vibe-build";
|
|
1175
|
+
state.modeSource = "board";
|
|
1176
|
+
state.modeReason = "orchestrator session";
|
|
1107
1177
|
persistState();
|
|
1108
1178
|
syncContextPanelState();
|
|
1179
|
+
await refreshUi(ctx, state);
|
|
1109
1180
|
|
|
1110
1181
|
return {
|
|
1111
1182
|
content: [{ type: "text", text: `Created Takomi orchestrator session ${nextState.sessionId} in hybrid mode\n\nDocs: ${paths.root}\nState: ${paths.stateFile}\n\n${buildTaskRows(nextState.tasks) || "No tasks provided."}` }],
|
|
@@ -1152,11 +1223,15 @@ ${stateJson}`
|
|
|
1152
1223
|
|
|
1153
1224
|
if (lowered === "use takomi") {
|
|
1154
1225
|
state.enabled = true;
|
|
1226
|
+
state.modeSource = "manual";
|
|
1227
|
+
state.modeReason = "explicit user request";
|
|
1155
1228
|
return { action: "transform", text: "Use the Takomi runtime, identify the correct lifecycle stage, and proceed accordingly." };
|
|
1156
1229
|
}
|
|
1157
1230
|
|
|
1158
1231
|
if (lowered.startsWith("use takomi ")) {
|
|
1159
1232
|
state.enabled = true;
|
|
1233
|
+
state.modeSource = "manual";
|
|
1234
|
+
state.modeReason = "explicit user request";
|
|
1160
1235
|
const route = decideRoute(text.slice("use takomi ".length));
|
|
1161
1236
|
if (route.stage) setStageAndWorkflow(state, route.stage, { preserveRole: state.role === "orchestrator" && route.stage === "genesis" });
|
|
1162
1237
|
else if (route.role !== "general") state.role = route.role;
|
|
@@ -1164,14 +1239,20 @@ ${stateJson}`
|
|
|
1164
1239
|
}
|
|
1165
1240
|
|
|
1166
1241
|
if (/\bvibe genesis\b/i.test(text)) {
|
|
1242
|
+
state.modeSource = "manual";
|
|
1243
|
+
state.modeReason = "vibe genesis";
|
|
1167
1244
|
setStageAndWorkflow(state, "genesis", { preserveRole: state.role === "orchestrator" });
|
|
1168
1245
|
return { action: "transform", text };
|
|
1169
1246
|
}
|
|
1170
1247
|
if (/\bvibe design\b/i.test(text)) {
|
|
1248
|
+
state.modeSource = "manual";
|
|
1249
|
+
state.modeReason = "vibe design";
|
|
1171
1250
|
setStageAndWorkflow(state, "design");
|
|
1172
1251
|
return { action: "transform", text };
|
|
1173
1252
|
}
|
|
1174
1253
|
if (/\bvibe build\b/i.test(text)) {
|
|
1254
|
+
state.modeSource = "manual";
|
|
1255
|
+
state.modeReason = "vibe build";
|
|
1175
1256
|
setStageAndWorkflow(state, "build");
|
|
1176
1257
|
return { action: "transform", text };
|
|
1177
1258
|
}
|
|
@@ -1180,43 +1261,36 @@ ${stateJson}`
|
|
|
1180
1261
|
});
|
|
1181
1262
|
|
|
1182
1263
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
1183
|
-
if (!state.enabled) return;
|
|
1264
|
+
if (!state.enabled || (state.modeSource ?? "idle") === "idle") return;
|
|
1184
1265
|
|
|
1185
1266
|
let effectiveState = cloneState(state);
|
|
1186
1267
|
const runtimeCwd = ctx.cwd;
|
|
1187
1268
|
const genesisExists = await hasGenesisArtifacts(runtimeCwd);
|
|
1188
1269
|
const route = decideRoute(event.prompt);
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
effectiveState.workflow = genesisExists ? "vibe-build" : "vibe-genesis";
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
const shouldHonorRoute = route.stage || route.role !== "general" || route.sessionRecommendation !== "none";
|
|
1196
|
-
if (shouldHonorRoute && route.stage) {
|
|
1197
|
-
effectiveState.stage = route.stage;
|
|
1198
|
-
effectiveState.workflow = route.workflow;
|
|
1199
|
-
effectiveState.role = effectiveState.role === "orchestrator" && route.stage === "genesis" ? "orchestrator" : route.role;
|
|
1200
|
-
} else if (shouldHonorRoute && route.role !== "general") {
|
|
1201
|
-
effectiveState.role = route.role;
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
let routingNote = route.reason;
|
|
1270
|
+
let routingNote = state.modeReason
|
|
1271
|
+
? `Takomi mode selected by ${state.modeSource}: ${state.modeReason}.`
|
|
1272
|
+
: `Takomi mode selected by ${state.modeSource ?? "runtime"}.`;
|
|
1205
1273
|
const explicitLifecycleWaiver = /skip genesis|waive genesis|genesis complete|already have (a )?(prd|requirements)|design complete|jump straight to build/i.test(event.prompt);
|
|
1206
|
-
const orchestrationActive = effectiveState.role === "orchestrator"
|
|
1274
|
+
const orchestrationActive = effectiveState.role === "orchestrator";
|
|
1207
1275
|
if (!genesisExists && orchestrationActive && !explicitLifecycleWaiver) {
|
|
1208
1276
|
effectiveState.stage = "genesis";
|
|
1209
1277
|
effectiveState.workflow = "vibe-genesis";
|
|
1210
1278
|
routingNote = "Blank project detected; orchestrator remains in control and must honor Genesis ā Design ā Build.";
|
|
1211
1279
|
}
|
|
1280
|
+
if (effectiveState.stage !== state.stage || effectiveState.workflow !== state.workflow || effectiveState.role !== state.role) {
|
|
1281
|
+
state.role = effectiveState.role;
|
|
1282
|
+
state.stage = effectiveState.stage;
|
|
1283
|
+
state.workflow = effectiveState.workflow;
|
|
1284
|
+
}
|
|
1212
1285
|
|
|
1213
1286
|
const promptKey = `${effectiveState.role}:${effectiveState.workflow ?? "none"}`;
|
|
1214
1287
|
const includeFullWorkflow = Boolean(effectiveState.workflow && effectiveState.lastFullPromptKey !== promptKey);
|
|
1215
1288
|
if (includeFullWorkflow) {
|
|
1216
1289
|
state.lastFullPromptKey = promptKey;
|
|
1217
|
-
persistState();
|
|
1218
|
-
syncContextPanelState();
|
|
1219
1290
|
}
|
|
1291
|
+
persistState();
|
|
1292
|
+
syncContextPanelState();
|
|
1293
|
+
await refreshUi(ctx, state);
|
|
1220
1294
|
|
|
1221
1295
|
const routingPolicy = await resolveTakomiRoutingPolicy(runtimeCwd);
|
|
1222
1296
|
const optionalFeatureContext = (() => {
|
|
@@ -1263,14 +1337,14 @@ ${stateJson}`
|
|
|
1263
1337
|
`Execution mode: ${route.executionMode}. Session recommendation: ${route.sessionRecommendation}.`,
|
|
1264
1338
|
`Takomi execution gate: ${effectiveState.launchMode === "manual" ? "review" : "auto"}. In review gate mode, show the delegation plan before launching and return to the user after each task with results, verification guidance, and the recommended next step.`,
|
|
1265
1339
|
!effectiveState.subagentsEnabled ? "Takomi subagents are disabled for this session. Do not call takomi_subagent or subagent until the user enables subagents." : "",
|
|
1266
|
-
!genesisExists ? "Project foundation is missing or incomplete. Do not skip Genesis unless the user explicitly waives it." : "",
|
|
1267
|
-
"
|
|
1268
|
-
"Task fan-out is flexible. Do not force exactly three tasks; decompose Genesis, Design, and Build work to fit the actual scope.",
|
|
1269
|
-
"A new orchestration session should usually begin with one Genesis foundation task that creates or updates the required markdown artifacts, then expand later stages only when the scope justifies it.",
|
|
1270
|
-
"If a follow-up request is small, one-shot it. If it is multi-part or large, create or expand an orchestration session instead of pretending it is a single task.",
|
|
1340
|
+
orchestrationActive && !genesisExists ? "Project foundation is missing or incomplete. Do not skip Genesis unless the user explicitly waives it." : "",
|
|
1341
|
+
"Do not escalate to orchestration or review just because this is coding-related; stay in the selected Takomi mode unless the user asks or a durable board/session is genuinely needed.",
|
|
1342
|
+
orchestrationActive ? "Task fan-out is flexible. Do not force exactly three tasks; decompose Genesis, Design, and Build work to fit the actual scope." : "",
|
|
1343
|
+
orchestrationActive ? "A new orchestration session should usually begin with one Genesis foundation task that creates or updates the required markdown artifacts, then expand later stages only when the scope justifies it." : "",
|
|
1344
|
+
orchestrationActive ? "If a follow-up request is small, one-shot it. If it is multi-part or large, create or expand an orchestration session instead of pretending it is a single task." : "",
|
|
1271
1345
|
"Before any Takomi subagent dispatch or model override, use the injected Pi model-registry context and project routing policy. Prefer provider-qualified model IDs. Do not run `pi --list-models` unless the registry context is missing or the user asks for a visible diagnostic.",
|
|
1272
|
-
"When useful, state the current Takomi stage and the recommended next
|
|
1273
|
-
effectiveState.stage === "build"
|
|
1346
|
+
"When useful, state the current Takomi mode/stage and the recommended next step.",
|
|
1347
|
+
orchestrationActive && effectiveState.stage === "build"
|
|
1274
1348
|
? "For build orchestration, use takomi_subagent to dispatch work to specialist subagents, then record the result on takomi_board; reuse the same conversation id when sending fixes back to the agent."
|
|
1275
1349
|
: "",
|
|
1276
1350
|
].filter(Boolean);
|
|
@@ -27,6 +27,8 @@ export type RuntimeHudState = {
|
|
|
27
27
|
activeSessionId?: string;
|
|
28
28
|
launchMode?: string;
|
|
29
29
|
subagentsEnabled?: boolean;
|
|
30
|
+
modeSource?: "idle" | "manual" | "model" | "board";
|
|
31
|
+
modeReason?: string;
|
|
30
32
|
};
|
|
31
33
|
|
|
32
34
|
type Tone = "accent" | "warning" | "success" | "error" | "muted" | "dim" | "thinkingMinimal";
|
|
@@ -70,24 +72,30 @@ function badge(theme: Theme, label: string, tone: Tone): string {
|
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
export function renderRuntimeStatus(theme: Theme, state: RuntimeHudState): string {
|
|
75
|
+
const source = state.modeSource ?? "idle";
|
|
76
|
+
if (!state.enabled) return [theme.fg("accent", "Takomi"), theme.fg("dim", "off")].join(" ");
|
|
77
|
+
if (source === "idle") return [theme.fg("accent", "Takomi"), theme.fg("dim", "idle")].join(" ");
|
|
78
|
+
|
|
73
79
|
const primary = state.stage ?? state.role;
|
|
74
80
|
const stageBadge = badge(theme, primary, stageTone(state.stage));
|
|
75
|
-
const
|
|
76
|
-
const
|
|
81
|
+
const sourceLabel = source === "manual" ? theme.fg("warning", "manual") : theme.fg("success", source);
|
|
82
|
+
const role = state.stage && state.role !== state.stage ? theme.fg("dim", state.role) : "";
|
|
77
83
|
const plan = state.planMode ? theme.fg("warning", "plan") : theme.fg("dim", "direct");
|
|
78
|
-
const
|
|
79
|
-
|
|
84
|
+
const gate = state.launchMode === "manual" ? theme.fg("warning", "review-gate") : "";
|
|
85
|
+
const subagents = state.subagentsEnabled === false ? theme.fg("error", "subagents:off") : "";
|
|
86
|
+
return [theme.fg("accent", "Takomi"), sourceLabel, stageBadge, role, gate || plan, subagents].filter(Boolean).join(" ");
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
export function renderRuntimeWidget(theme: Theme, state: RuntimeHudState): string[] {
|
|
83
|
-
if (!state.enabled) return [];
|
|
90
|
+
if (!state.enabled || (state.modeSource ?? "idle") === "idle") return [];
|
|
84
91
|
const primary = state.stage ?? state.role;
|
|
92
|
+
const sourceLabel = state.modeSource === "manual" ? theme.fg("warning", "manual") : theme.fg("success", state.modeSource ?? "model");
|
|
85
93
|
const parts = [
|
|
86
94
|
theme.fg("accent", "Takomi"),
|
|
95
|
+
sourceLabel,
|
|
87
96
|
badge(theme, primary, stageTone(state.stage)),
|
|
88
|
-
theme.fg("dim", `role:${state.role}`),
|
|
89
|
-
state.
|
|
90
|
-
state.launchMode === "manual" ? theme.fg("warning", "review-gate") : theme.fg("accent", "auto-gate"),
|
|
97
|
+
state.stage && state.role !== state.stage ? theme.fg("dim", `role:${state.role}`) : "",
|
|
98
|
+
state.launchMode === "manual" ? theme.fg("warning", "review-gate") : "",
|
|
91
99
|
state.planMode ? theme.fg("warning", "plan") : theme.fg("dim", "direct"),
|
|
92
100
|
state.subagentsEnabled === false ? theme.fg("error", "subagents:off") : theme.fg("dim", "subagents:on"),
|
|
93
101
|
state.workflow ? theme.fg("dim", `wf:${state.workflow}`) : "",
|
|
@@ -115,7 +123,7 @@ export class TakomiFooterComponent implements Component {
|
|
|
115
123
|
this.unsubscribeBranchChange();
|
|
116
124
|
}
|
|
117
125
|
|
|
118
|
-
invalidate(): void {}
|
|
126
|
+
invalidate(): void { }
|
|
119
127
|
|
|
120
128
|
render(width: number): string[] {
|
|
121
129
|
const state = this.getState();
|
|
@@ -141,8 +149,7 @@ export class TakomiFooterComponent implements Component {
|
|
|
141
149
|
.filter(([key]) => key !== "takomi-runtime")
|
|
142
150
|
.map(([, value]) => value)
|
|
143
151
|
.filter(Boolean);
|
|
144
|
-
const
|
|
145
|
-
const left = [runtimeStatus, ...extensionStatuses].join(this.theme.fg("dim", " | "));
|
|
152
|
+
const left = extensionStatuses.join(this.theme.fg("dim", " | "));
|
|
146
153
|
const branch = this.footerData.getGitBranch();
|
|
147
154
|
const rightText = [this.ctx.model?.id || "no-model", branch ? `git:${branch}` : ""].filter(Boolean).join(" | ");
|
|
148
155
|
const right = this.theme.fg("dim", rightText);
|
|
@@ -44,6 +44,7 @@ const SubagentParameters = Type.Object({
|
|
|
44
44
|
tasks: Type.Optional(Type.Array(TaskSchema, { description: "Parallel subagent tasks" })),
|
|
45
45
|
confirmLaunch: Type.Optional(Type.Boolean({ description: "Required to launch immediately in manual Takomi launch mode" })),
|
|
46
46
|
previewOnly: Type.Optional(Type.Boolean({ description: "Return the delegation plan without launching" })),
|
|
47
|
+
clarify: Type.Optional(Type.Boolean({ description: "Show the native pi-subagents TUI to preview/edit before execution. Especially useful for chains; requires an interactive Pi TUI." })),
|
|
47
48
|
chain: Type.Optional(Type.Array(TaskSchema, { description: "Sequential chain of subagent tasks" })),
|
|
48
49
|
agentScope: Type.Optional(Type.Union([Type.Literal("user"), Type.Literal("project"), Type.Literal("both")])),
|
|
49
50
|
confirmProjectAgents: Type.Optional(Type.Boolean({ description: "Prompt before running project-local agents. Default: true." })),
|
|
@@ -59,6 +60,7 @@ function registerSubagentTool(pi: ExtensionAPI): void {
|
|
|
59
60
|
promptGuidelines: [
|
|
60
61
|
"Use this tool during orchestration when a specialist should handle a task.",
|
|
61
62
|
"Use tasks for independent parallel work and chain for dependent handoffs with {previous}.",
|
|
63
|
+
"Set clarify=true when the user asks to preview/edit a subagent run in the native Pi TUI before launch.",
|
|
62
64
|
"Use model, fallbackModels, and thinking only when deliberate; otherwise let the agent/profile defaults apply.",
|
|
63
65
|
"If review sends work back to the same agent, reuse the same conversationId for continuity.",
|
|
64
66
|
"If a launch is blocked, cancelled, paused, or review-gated, do not retry automatically; wait for the user's next prompt. Use overrideUserBlock only after explicit user approval.",
|
|
@@ -30,15 +30,37 @@ export function renderTakomiSubagentCall(params: TakomiSubagentToolParams, theme
|
|
|
30
30
|
);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
function resultText(result: ToolResult): string {
|
|
34
|
+
return typeof (result as any)?.content === "string"
|
|
35
|
+
? (result as any).content
|
|
36
|
+
: Array.isArray((result as any)?.content)
|
|
37
|
+
? (result as any).content.map((part: any) => part?.text ?? "").filter(Boolean).join("\n")
|
|
38
|
+
: JSON.stringify((result as any)?.details ?? {}, null, 2);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function summarizeCollapsedResult(text: string, status: string, theme: Theme): string {
|
|
42
|
+
const policyMatch = text.match(/Required policies:\n((?:- .+\n?)+)/);
|
|
43
|
+
const policies = policyMatch?.[1]
|
|
44
|
+
?.split("\n")
|
|
45
|
+
.map((line) => line.replace(/^[-\s]+/, "").trim())
|
|
46
|
+
.filter(Boolean) ?? [];
|
|
47
|
+
const lineCount = text ? text.split(/\r?\n/).length : 0;
|
|
48
|
+
const label = policies.length > 0
|
|
49
|
+
? `policy context loaded: ${policies.join(", ")}`
|
|
50
|
+
: `${lineCount} result line${lineCount === 1 ? "" : "s"} hidden`;
|
|
51
|
+
const icon = status === "failed" ? "ā " : "ā";
|
|
52
|
+
const color = status === "failed" ? "warning" : "success";
|
|
53
|
+
return `${theme.fg(color, `${icon} takomi_subagent ${status}`)}${theme.fg("dim", ` Ā· ${label} (ctrl+o to expand)`)}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
33
56
|
export function renderTakomiSubagentResult(result: ToolResult, options: { expanded?: boolean; isPartial?: boolean }, theme: Theme, context: any): any {
|
|
34
57
|
const native = renderNativeSubagentResult(result, options, theme, context);
|
|
35
58
|
if (native) return native;
|
|
36
59
|
|
|
37
60
|
const status = (result as any)?.isError ? "failed" : "completed";
|
|
38
|
-
const text =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
: JSON.stringify((result as any)?.details ?? {}, null, 2);
|
|
61
|
+
const text = resultText(result);
|
|
62
|
+
if (!options.expanded && !options.isPartial) {
|
|
63
|
+
return new Text(summarizeCollapsedResult(text, status, theme), 0, 0);
|
|
64
|
+
}
|
|
43
65
|
return new Text(`${theme.fg("toolTitle", theme.bold(`takomi_subagent ${status}`))}\n${text || "No result content."}`, 0, 0);
|
|
44
66
|
}
|
|
@@ -186,7 +186,7 @@ function toSubagentParams(params: TakomiSubagentToolParams, rootCwd: string, dis
|
|
|
186
186
|
cwd: rootCwd,
|
|
187
187
|
context: "fresh" as const,
|
|
188
188
|
async: false,
|
|
189
|
-
clarify:
|
|
189
|
+
clarify: params.clarify === true,
|
|
190
190
|
includeProgress: true,
|
|
191
191
|
sessionDir: stableConversationSessionDir(rootCwd, tasks),
|
|
192
192
|
};
|
|
@@ -28,6 +28,7 @@ export type TakomiSubagentToolParams = Partial<TakomiSubagentToolTask> & {
|
|
|
28
28
|
chain?: TakomiSubagentToolTask[];
|
|
29
29
|
confirmLaunch?: boolean;
|
|
30
30
|
previewOnly?: boolean;
|
|
31
|
+
clarify?: boolean;
|
|
31
32
|
agentScope?: TakomiAgentScope;
|
|
32
33
|
confirmProjectAgents?: boolean;
|
|
33
34
|
overrideUserBlock?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "takomi",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.34",
|
|
4
4
|
"description": "šÆ Stop wrestling with AI. Start building with purpose. The artisan's toolkit for agent workflows, Codex skills, and original Takomi capabilities like 21st.dev integration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cli.js
CHANGED
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
isStoreInitialized,
|
|
44
44
|
} from './store.js';
|
|
45
45
|
import { runDoctor } from './doctor.js';
|
|
46
|
-
import { ensurePiInstalled, ensurePiSubagentsInstalled, launchTakomiHarness, printPiInstallResult, printPiSubagentsInstallResult, updatePiManagedPackages, printPiManagedPackageUpdateResult } from './pi-harness.js';
|
|
46
|
+
import { ensurePiInstalled, ensurePiSubagentsInstalled, launchTakomiHarness, printPiInstallResult, printPiSubagentsInstallResult, updatePiCliPackage, updatePiManagedPackages, printPiManagedPackageUpdateResult } from './pi-harness.js';
|
|
47
47
|
import { installPiHarnessAssets, printPiInstallSummary, syncPiHarnessAssets, validatePiHarnessInstall } from './pi-installer.js';
|
|
48
48
|
import { offerPiOptionalFeatures } from './pi-optional-features.js';
|
|
49
49
|
import {
|
|
@@ -459,7 +459,7 @@ async function installPiTarget(options = {}) {
|
|
|
459
459
|
console.log(pc.cyan('\nš§© Optional Takomi Pi feature packs...\n'));
|
|
460
460
|
await offerPiOptionalFeatures({ interactive: true });
|
|
461
461
|
}
|
|
462
|
-
console.log(pc.cyan('\nš¦ Checking Pi-managed package updates...\n'));
|
|
462
|
+
console.log(pc.cyan('\nš¦ Checking Pi-managed extension package updates...\n'));
|
|
463
463
|
printPiManagedPackageUpdateResult(await updatePiManagedPackages());
|
|
464
464
|
console.log(pc.dim('\nNext: cd <project> && takomi\n'));
|
|
465
465
|
} catch (error) {
|
|
@@ -474,7 +474,7 @@ async function installPiOptionalFeaturesTarget() {
|
|
|
474
474
|
printPiInstallResult(pi);
|
|
475
475
|
if (!pi.ok) return;
|
|
476
476
|
await offerPiOptionalFeatures({ interactive: true });
|
|
477
|
-
console.log(pc.cyan('\nš¦ Checking Pi-managed package updates...\n'));
|
|
477
|
+
console.log(pc.cyan('\nš¦ Checking Pi-managed extension package updates...\n'));
|
|
478
478
|
printPiManagedPackageUpdateResult(await updatePiManagedPackages());
|
|
479
479
|
}
|
|
480
480
|
|
|
@@ -484,7 +484,7 @@ async function syncPiTarget() {
|
|
|
484
484
|
const result = await syncPiHarnessAssets(program.version());
|
|
485
485
|
const validation = await validatePiHarnessInstall();
|
|
486
486
|
printPiInstallSummary(result, validation);
|
|
487
|
-
console.log(pc.cyan('\nš¦ Checking Pi-managed package updates...\n'));
|
|
487
|
+
console.log(pc.cyan('\nš¦ Checking Pi-managed extension package updates...\n'));
|
|
488
488
|
printPiManagedPackageUpdateResult(await updatePiManagedPackages());
|
|
489
489
|
} catch (error) {
|
|
490
490
|
console.log(pc.red('\nPi sync failed.'));
|
|
@@ -558,6 +558,8 @@ async function upgrade(target = 'all') {
|
|
|
558
558
|
}
|
|
559
559
|
|
|
560
560
|
if (normalizedTarget === 'pi') {
|
|
561
|
+
console.log(pc.cyan('\nš¦ Updating Pi CLI...\n'));
|
|
562
|
+
printPiInstallResult(await updatePiCliPackage());
|
|
561
563
|
await installPiTarget({ offerOptionalFeatures: false });
|
|
562
564
|
return;
|
|
563
565
|
}
|
|
@@ -567,6 +569,8 @@ async function upgrade(target = 'all') {
|
|
|
567
569
|
return;
|
|
568
570
|
}
|
|
569
571
|
|
|
572
|
+
console.log(pc.cyan('\nš¦ Updating Pi CLI...\n'));
|
|
573
|
+
printPiInstallResult(await updatePiCliPackage());
|
|
570
574
|
await installAllTargets({ offerOptionalFeatures: false });
|
|
571
575
|
console.log(pc.magenta('\n⨠Fully upgraded. Next: run `takomi` from your project.\n'));
|
|
572
576
|
}
|
package/src/pi-harness.js
CHANGED
|
@@ -275,9 +275,9 @@ function runCommandWithTimeout(command, args, timeoutMs) {
|
|
|
275
275
|
});
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
|
|
278
|
+
async function installOrUpdatePiCli({ force = false } = {}) {
|
|
279
279
|
const before = await detectPiCommand();
|
|
280
|
-
if (before.installed) {
|
|
280
|
+
if (before.installed && !force) {
|
|
281
281
|
return { ok: true, changed: false, report: 'Pi already available.' };
|
|
282
282
|
}
|
|
283
283
|
|
|
@@ -295,7 +295,9 @@ export async function ensurePiInstalled() {
|
|
|
295
295
|
return {
|
|
296
296
|
ok: true,
|
|
297
297
|
changed: true,
|
|
298
|
-
report: (result.stdout || result.stderr || 'Installed Pi.').trim(),
|
|
298
|
+
report: (result.stdout || result.stderr || (force ? 'Updated Pi.' : 'Installed Pi.')).trim(),
|
|
299
|
+
version: after.version,
|
|
300
|
+
action: force ? 'Updated' : 'Installed',
|
|
299
301
|
};
|
|
300
302
|
}
|
|
301
303
|
lastError = 'npm install reported success, but pi was still not detected.';
|
|
@@ -307,9 +309,17 @@ export async function ensurePiInstalled() {
|
|
|
307
309
|
return { ok: false, changed: false, report: lastError };
|
|
308
310
|
}
|
|
309
311
|
|
|
312
|
+
export async function ensurePiInstalled() {
|
|
313
|
+
return installOrUpdatePiCli({ force: false });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export async function updatePiCliPackage() {
|
|
317
|
+
return installOrUpdatePiCli({ force: true });
|
|
318
|
+
}
|
|
319
|
+
|
|
310
320
|
export function printPiInstallResult(result) {
|
|
311
321
|
if (result.ok) {
|
|
312
|
-
console.log(pc.green(`ā ${result.changed ? 'Installed' : 'Validated'} Pi`));
|
|
322
|
+
console.log(pc.green(`ā ${result.action || (result.changed ? 'Installed' : 'Validated')} Pi`));
|
|
313
323
|
if (result.report) console.log(pc.dim(result.report.split(/\r?\n/).slice(-4).join('\n')));
|
|
314
324
|
return;
|
|
315
325
|
}
|
|
@@ -359,42 +369,54 @@ export function printPiSubagentsInstallResult(result) {
|
|
|
359
369
|
if (result.report) console.log(pc.dim(result.report.split(/\r?\n/).slice(-8).join('\n')));
|
|
360
370
|
}
|
|
361
371
|
|
|
362
|
-
|
|
372
|
+
function getPiPackageUpdateTimeoutMs(timeoutMs) {
|
|
373
|
+
if (Number.isFinite(timeoutMs) && timeoutMs > 0) return timeoutMs;
|
|
374
|
+
const envValue = Number(process.env.TAKOMI_PI_PACKAGE_UPDATE_TIMEOUT_MS || '');
|
|
375
|
+
if (Number.isFinite(envValue) && envValue > 0) return envValue;
|
|
376
|
+
return 180_000;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
export async function updatePiManagedPackages({ timeoutMs } = {}) {
|
|
363
380
|
if (process.env.TAKOMI_SKIP_PI_PACKAGE_UPDATE === '1') {
|
|
364
|
-
return { ok: true, changed: false, report: 'Skipped Pi package update because TAKOMI_SKIP_PI_PACKAGE_UPDATE=1.' };
|
|
381
|
+
return { ok: true, changed: false, report: 'Skipped Pi extension package update because TAKOMI_SKIP_PI_PACKAGE_UPDATE=1.' };
|
|
365
382
|
}
|
|
366
383
|
|
|
367
384
|
const pi = await detectPiCommand();
|
|
368
385
|
if (!pi.installed) return { ok: false, changed: false, report: 'Pi is not installed.' };
|
|
369
386
|
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
387
|
+
// Only reconcile Pi-managed extension/package entries here. Plain `pi update`
|
|
388
|
+
// also self-updates the Pi CLI (`npm install -g @earendil-works/pi-coding-agent`).
|
|
389
|
+
// If Takomi times out and kills that process, npm can leave the global `pi`
|
|
390
|
+
// shim temporarily removed on Windows. `--extensions` avoids touching the Pi
|
|
391
|
+
// executable while still updating optional/user package packs.
|
|
392
|
+
const effectiveTimeoutMs = getPiPackageUpdateTimeoutMs(timeoutMs);
|
|
373
393
|
const command = pi.path || 'pi';
|
|
374
|
-
const
|
|
394
|
+
const updateArgs = ['update', '--extensions'];
|
|
395
|
+
const updateCommandText = `pi ${updateArgs.join(' ')}`;
|
|
396
|
+
const result = await runCommandWithTimeout(command, updateArgs, effectiveTimeoutMs);
|
|
375
397
|
const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
|
|
376
398
|
if (result.timedOut) {
|
|
377
399
|
return {
|
|
378
400
|
ok: false,
|
|
379
401
|
changed: false,
|
|
380
|
-
report: `Timed out after ${Math.round(
|
|
402
|
+
report: `Timed out after ${Math.round(effectiveTimeoutMs / 1000)}s while running \`${updateCommandText}\`. Takomi setup is complete; retry package updates later with \`${updateCommandText}\` or skip this step with TAKOMI_SKIP_PI_PACKAGE_UPDATE=1. Increase the timeout with TAKOMI_PI_PACKAGE_UPDATE_TIMEOUT_MS=<ms>. Last output:\n${output}`.trim(),
|
|
381
403
|
};
|
|
382
404
|
}
|
|
383
405
|
|
|
384
406
|
return {
|
|
385
407
|
ok: result.status === 0,
|
|
386
408
|
changed: result.status === 0,
|
|
387
|
-
report: output || (result.status === 0 ? 'All Pi-managed packages are up to date.' :
|
|
409
|
+
report: output || (result.status === 0 ? 'All Pi-managed extension packages are up to date.' : `${updateCommandText} failed.`),
|
|
388
410
|
};
|
|
389
411
|
}
|
|
390
412
|
|
|
391
413
|
export function printPiManagedPackageUpdateResult(result) {
|
|
392
414
|
if (result.ok) {
|
|
393
|
-
console.log(pc.green('ā Updated all Pi-managed packages'));
|
|
415
|
+
console.log(pc.green('ā Updated all Pi-managed extension packages'));
|
|
394
416
|
if (result.report) console.log(pc.dim(result.report.split(/\r?\n/).slice(-8).join('\n')));
|
|
395
417
|
return;
|
|
396
418
|
}
|
|
397
|
-
console.log(pc.yellow('ā Could not update Pi-managed packages'));
|
|
419
|
+
console.log(pc.yellow('ā Could not update Pi-managed extension packages'));
|
|
398
420
|
if (result.report) console.log(pc.dim(result.report.split(/\r?\n/).slice(-8).join('\n')));
|
|
399
421
|
}
|
|
400
422
|
|