taskplane 0.24.16 → 0.24.18
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.
|
@@ -1017,6 +1017,7 @@ export function toOrchestratorConfig(config: TaskplaneConfig): import("./types.t
|
|
|
1017
1017
|
merge: {
|
|
1018
1018
|
model: o.merge.model,
|
|
1019
1019
|
tools: o.merge.tools,
|
|
1020
|
+
thinking: o.merge.thinking,
|
|
1020
1021
|
verify: [...o.merge.verify],
|
|
1021
1022
|
order: o.merge.order,
|
|
1022
1023
|
timeout_minutes: o.merge.timeoutMinutes ?? 90,
|
|
@@ -302,6 +302,8 @@ export interface MergeConfig {
|
|
|
302
302
|
model: string;
|
|
303
303
|
/** Merge-agent tool allowlist */
|
|
304
304
|
tools: string;
|
|
305
|
+
/** Merge-agent thinking mode (empty = inherit session thinking) */
|
|
306
|
+
thinking: string;
|
|
305
307
|
/** Verification commands run after merge operations */
|
|
306
308
|
verify: string[];
|
|
307
309
|
/** Lane merge ordering policy */
|
|
@@ -602,6 +604,7 @@ export const DEFAULT_ORCHESTRATOR_SECTION: OrchestratorSection = {
|
|
|
602
604
|
merge: {
|
|
603
605
|
model: "",
|
|
604
606
|
tools: "read,write,edit,bash,grep,find,ls",
|
|
607
|
+
thinking: "",
|
|
605
608
|
verify: [],
|
|
606
609
|
order: "fewest-files-first",
|
|
607
610
|
timeoutMinutes: 90,
|
|
@@ -127,8 +127,9 @@ export const SECTIONS: SectionDef[] = [
|
|
|
127
127
|
{
|
|
128
128
|
name: "Merge",
|
|
129
129
|
fields: [
|
|
130
|
-
{ configPath: "orchestrator.merge.model", label: "Merge Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "mergeModel", description: "Merge-agent model (
|
|
130
|
+
{ configPath: "orchestrator.merge.model", label: "Merge Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "mergeModel", description: "Merge-agent model (inherit = use session model)" },
|
|
131
131
|
{ configPath: "orchestrator.merge.tools", label: "Merge Tools", control: "input", layer: "L1", fieldType: "string", description: "Merge-agent tool allowlist" },
|
|
132
|
+
{ configPath: "orchestrator.merge.thinking", label: "Merge Thinking", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "mergeThinking", description: "Merge-agent thinking mode (empty = inherit session)" },
|
|
132
133
|
{ configPath: "orchestrator.merge.order", label: "Merge Order", control: "toggle", layer: "L1", fieldType: "enum", values: ["fewest-files-first", "sequential"], description: "Lane merge ordering policy" },
|
|
133
134
|
{ configPath: "orchestrator.merge.timeoutMinutes", label: "Merge Timeout (minutes)", control: "input", layer: "L1", fieldType: "number", description: "Max time for merge agent to complete. Increase for large batches (default: 10)" },
|
|
134
135
|
],
|
|
@@ -152,14 +153,14 @@ export const SECTIONS: SectionDef[] = [
|
|
|
152
153
|
{
|
|
153
154
|
name: "Supervisor",
|
|
154
155
|
fields: [
|
|
155
|
-
{ configPath: "orchestrator.supervisor.model", label: "Supervisor Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "supervisorModel", description: "Supervisor model (
|
|
156
|
+
{ configPath: "orchestrator.supervisor.model", label: "Supervisor Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "supervisorModel", description: "Supervisor model (inherit = use session model)" },
|
|
156
157
|
{ configPath: "orchestrator.supervisor.autonomy", label: "Autonomy Level", control: "toggle", layer: "L1", fieldType: "enum", values: ["interactive", "supervised", "autonomous"], description: "Recovery action confirmation behavior" },
|
|
157
158
|
],
|
|
158
159
|
},
|
|
159
160
|
{
|
|
160
161
|
name: "Worker",
|
|
161
162
|
fields: [
|
|
162
|
-
{ configPath: "taskRunner.worker.model", label: "Worker Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "workerModel", description: "Worker model (
|
|
163
|
+
{ configPath: "taskRunner.worker.model", label: "Worker Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "workerModel", description: "Worker model (inherit = use session model)" },
|
|
163
164
|
{ configPath: "taskRunner.worker.tools", label: "Worker Tools", control: "input", layer: "L1", fieldType: "string", description: "Worker tool allowlist" },
|
|
164
165
|
{ configPath: "taskRunner.worker.thinking", label: "Worker Thinking", control: "input", layer: "L1", fieldType: "string", description: "Worker thinking mode" },
|
|
165
166
|
{ configPath: "taskRunner.worker.spawnMode", label: "Spawn Mode", control: "toggle", layer: "L1", fieldType: "enum", values: ["subprocess"], optional: true, description: "How /task spawns workers and reviewers. Runtime V2 supports subprocess only." },
|
|
@@ -168,7 +169,7 @@ export const SECTIONS: SectionDef[] = [
|
|
|
168
169
|
{
|
|
169
170
|
name: "Reviewer",
|
|
170
171
|
fields: [
|
|
171
|
-
{ configPath: "taskRunner.reviewer.model", label: "Reviewer Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "reviewerModel", description: "Reviewer model (
|
|
172
|
+
{ configPath: "taskRunner.reviewer.model", label: "Reviewer Model", control: "input", layer: "L1+L2", fieldType: "string", prefsKey: "reviewerModel", description: "Reviewer model (inherit = use session model)" },
|
|
172
173
|
{ configPath: "taskRunner.reviewer.tools", label: "Reviewer Tools", control: "input", layer: "L1", fieldType: "string", description: "Reviewer tool allowlist" },
|
|
173
174
|
{ configPath: "taskRunner.reviewer.thinking", label: "Reviewer Thinking", control: "input", layer: "L1", fieldType: "string", description: "Reviewer thinking mode" },
|
|
174
175
|
],
|
|
@@ -929,6 +930,134 @@ function summarizeArray(arr: any[]): string {
|
|
|
929
930
|
* @param configRoot - Workspace/repo root (from execCtx.workspaceRoot)
|
|
930
931
|
* @param pointerConfigRoot - Optional pointer-resolved config root (workspace mode)
|
|
931
932
|
*/
|
|
933
|
+
// ── Model Picker (Sage-style provider → model selection) ────────────
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Interactive two-level model picker: provider first, then model within provider.
|
|
937
|
+
* Returns the selected model string (e.g., "anthropic/claude-sonnet-4-20250514")
|
|
938
|
+
* or "" for inherit, or undefined if cancelled.
|
|
939
|
+
*
|
|
940
|
+
* Adapted from Sage's pickModel implementation.
|
|
941
|
+
*/
|
|
942
|
+
async function pickModel(ctx: ExtensionContext, currentModel: string): Promise<string | undefined> {
|
|
943
|
+
const available = ctx.modelRegistry.getAvailable();
|
|
944
|
+
if (available.length === 0) {
|
|
945
|
+
ctx.ui.notify("No available models found in pi model registry", "warning");
|
|
946
|
+
// Fall back to manual input
|
|
947
|
+
const manual = await ctx.ui.input("Model (provider/model-id, or empty for inherit)", currentModel || "");
|
|
948
|
+
if (manual === null || manual === undefined) return undefined;
|
|
949
|
+
return manual;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
const currentLower = (currentModel || "").trim().toLowerCase();
|
|
953
|
+
const providers = [...new Set(available.map((m: any) => m.provider))].sort();
|
|
954
|
+
|
|
955
|
+
while (true) {
|
|
956
|
+
// Level 1: Provider selection (with "inherit" as first option)
|
|
957
|
+
const providerOptions: string[] = [
|
|
958
|
+
"inherit (use current session model)",
|
|
959
|
+
...providers.map((p: string) => {
|
|
960
|
+
const count = available.filter((m: any) => m.provider === p).length;
|
|
961
|
+
return `${p} (${count} models)`;
|
|
962
|
+
}),
|
|
963
|
+
];
|
|
964
|
+
|
|
965
|
+
const providerChoice = await selectScrollable(ctx, "Choose model provider", providerOptions);
|
|
966
|
+
if (!providerChoice) return undefined; // Cancelled
|
|
967
|
+
|
|
968
|
+
if (providerChoice.startsWith("inherit")) {
|
|
969
|
+
return ""; // Empty string = inherit
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Extract provider name (strip " (N models)" suffix)
|
|
973
|
+
const provider = providerChoice.replace(/\s*\(\d+ models?\)$/, "");
|
|
974
|
+
const providerModels = available
|
|
975
|
+
.filter((m: any) => m.provider === provider)
|
|
976
|
+
.sort((a: any, b: any) => {
|
|
977
|
+
// Current model first, then alphabetical
|
|
978
|
+
const aComposite = `${a.provider}/${a.id}`.toLowerCase();
|
|
979
|
+
const bComposite = `${b.provider}/${b.id}`.toLowerCase();
|
|
980
|
+
if (aComposite === currentLower) return -1;
|
|
981
|
+
if (bComposite === currentLower) return 1;
|
|
982
|
+
return a.id.localeCompare(b.id);
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
// Level 2: Model selection within provider
|
|
986
|
+
const modelOptionMap = new Map<string, string>();
|
|
987
|
+
const modelOptions = ["← Back to providers"];
|
|
988
|
+
|
|
989
|
+
for (const model of providerModels) {
|
|
990
|
+
const composite = `${model.provider}/${model.id}`;
|
|
991
|
+
const isCurrent = composite.toLowerCase() === currentLower;
|
|
992
|
+
const label = `${model.id}${isCurrent ? " ✓ current" : ""}`;
|
|
993
|
+
modelOptions.push(label);
|
|
994
|
+
modelOptionMap.set(label, composite);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
const modelChoice = await selectScrollable(ctx, `Choose model (${provider})`, modelOptions);
|
|
998
|
+
if (!modelChoice) continue; // Cancelled → back to providers
|
|
999
|
+
if (modelChoice === "← Back to providers") continue;
|
|
1000
|
+
|
|
1001
|
+
const resolved = modelOptionMap.get(modelChoice);
|
|
1002
|
+
if (resolved) return resolved;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Scrollable select list for model/provider picking.
|
|
1008
|
+
* Uses pi's TUI custom widget API.
|
|
1009
|
+
*/
|
|
1010
|
+
async function selectScrollable(
|
|
1011
|
+
ctx: ExtensionContext,
|
|
1012
|
+
title: string,
|
|
1013
|
+
options: string[],
|
|
1014
|
+
maxVisible = 12,
|
|
1015
|
+
): Promise<string | undefined> {
|
|
1016
|
+
if (options.length === 0) return undefined;
|
|
1017
|
+
|
|
1018
|
+
const items: SelectItem[] = options.map((option, index) => ({
|
|
1019
|
+
value: String(index),
|
|
1020
|
+
label: option,
|
|
1021
|
+
}));
|
|
1022
|
+
|
|
1023
|
+
const selectedValue = await ctx.ui.custom<string | undefined>((tui, theme, _keybindings, done) => {
|
|
1024
|
+
const container = new Container();
|
|
1025
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
1026
|
+
container.addChild(new Text(theme.fg("accent", title), 1, 0));
|
|
1027
|
+
container.addChild(new Text("", 0, 0));
|
|
1028
|
+
|
|
1029
|
+
const selectList = new SelectList(items, Math.max(3, Math.min(maxVisible, items.length)), {
|
|
1030
|
+
selectedPrefix: (text: string) => theme.fg("accent", text),
|
|
1031
|
+
selectedText: (text: string) => theme.fg("accent", text),
|
|
1032
|
+
description: (text: string) => theme.fg("muted", text),
|
|
1033
|
+
scrollInfo: (text: string) => theme.fg("dim", text),
|
|
1034
|
+
noMatch: (text: string) => theme.fg("warning", text),
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
selectList.onSelect = (item) => done(item.value);
|
|
1038
|
+
selectList.onCancel = () => done(undefined);
|
|
1039
|
+
container.addChild(selectList);
|
|
1040
|
+
|
|
1041
|
+
container.addChild(new Text("", 0, 0));
|
|
1042
|
+
container.addChild(new Text(theme.fg("dim", "↑↓ navigate • type to filter • enter select • esc back"), 1, 0));
|
|
1043
|
+
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
|
1044
|
+
|
|
1045
|
+
return {
|
|
1046
|
+
render: (width: number) => container.render(width),
|
|
1047
|
+
invalidate: () => container.invalidate(),
|
|
1048
|
+
handleInput: (data: string) => {
|
|
1049
|
+
selectList.handleInput(data);
|
|
1050
|
+
tui.requestRender();
|
|
1051
|
+
},
|
|
1052
|
+
};
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
if (selectedValue === undefined) return undefined;
|
|
1056
|
+
const selectedIndex = Number(selectedValue);
|
|
1057
|
+
if (!Number.isInteger(selectedIndex) || selectedIndex < 0 || selectedIndex >= options.length) return undefined;
|
|
1058
|
+
return options[selectedIndex];
|
|
1059
|
+
}
|
|
1060
|
+
|
|
932
1061
|
export async function openSettingsTui(
|
|
933
1062
|
ctx: ExtensionContext,
|
|
934
1063
|
configRoot: string,
|
|
@@ -1120,26 +1249,36 @@ async function showSectionSettingsLoop(
|
|
|
1120
1249
|
|
|
1121
1250
|
// Input fields: the submenu returned a sentinel — use ctx.ui.input() for actual editing
|
|
1122
1251
|
if (result.rawValue === "__EDIT_REQUESTED__" && field.control === "input") {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1252
|
+
// Model fields: use interactive provider → model picker instead of free-text
|
|
1253
|
+
if (field.configPath.endsWith(".model")) {
|
|
1254
|
+
const state = loadConfigState(configRoot, pointerConfigRoot);
|
|
1255
|
+
const currentDisplay = getFieldDisplayValue(field, state.mergedConfig, state.prefs);
|
|
1256
|
+
const currentClean = String(currentDisplay).replace(/\s+\((?:default|project|user)\)$/, "");
|
|
1257
|
+
const selected = await pickModel(ctx, currentClean === "(inherit)" ? "" : currentClean);
|
|
1258
|
+
if (selected === undefined) continue; // Cancelled
|
|
1259
|
+
result.rawValue = selected;
|
|
1260
|
+
} else {
|
|
1261
|
+
const state = loadConfigState(configRoot, pointerConfigRoot);
|
|
1262
|
+
const currentDisplay = getFieldDisplayValue(field, state.mergedConfig, state.prefs);
|
|
1263
|
+
const currentClean = String(currentDisplay).replace(/\s+\((?:default|project|user)\)$/, "");
|
|
1264
|
+
const placeholder = currentClean === "(not set)" || currentClean === "(inherit)" ? "" : currentClean;
|
|
1265
|
+
|
|
1266
|
+
const newValue = await ctx.ui.input(
|
|
1267
|
+
`${field.label}${field.description ? ` — ${field.description}` : ""}`,
|
|
1268
|
+
placeholder,
|
|
1269
|
+
);
|
|
1270
|
+
|
|
1271
|
+
if (newValue === null || newValue === undefined) continue; // Cancelled
|
|
1272
|
+
|
|
1273
|
+
// Validate
|
|
1274
|
+
const validation = validateFieldInput(field, newValue);
|
|
1275
|
+
if (!validation.valid) {
|
|
1276
|
+
ctx.ui.notify(`❌ Invalid value: ${validation.error}`, "error");
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1134
1279
|
|
|
1135
|
-
|
|
1136
|
-
const validation = validateFieldInput(field, newValue);
|
|
1137
|
-
if (!validation.valid) {
|
|
1138
|
-
ctx.ui.notify(`❌ Invalid value: ${validation.error}`, "error");
|
|
1139
|
-
continue;
|
|
1280
|
+
result.rawValue = newValue;
|
|
1140
1281
|
}
|
|
1141
|
-
|
|
1142
|
-
result.rawValue = newValue;
|
|
1143
1282
|
}
|
|
1144
1283
|
|
|
1145
1284
|
const typedValue = coerceValueForWrite(field, result.rawValue);
|
|
@@ -37,6 +37,8 @@ export interface OrchestratorConfig {
|
|
|
37
37
|
merge: {
|
|
38
38
|
model: string;
|
|
39
39
|
tools: string;
|
|
40
|
+
/** Merge-agent thinking mode (empty = inherit session thinking) */
|
|
41
|
+
thinking: string;
|
|
40
42
|
verify: string[];
|
|
41
43
|
order: "fewest-files-first" | "sequential";
|
|
42
44
|
/** Merge agent timeout in minutes. Default: 10. Increase for large batches. */
|
|
@@ -280,6 +282,7 @@ export const DEFAULT_ORCHESTRATOR_CONFIG: OrchestratorConfig = {
|
|
|
280
282
|
merge: {
|
|
281
283
|
model: "",
|
|
282
284
|
tools: "read,write,edit,bash,grep,find,ls",
|
|
285
|
+
thinking: "",
|
|
283
286
|
verify: [],
|
|
284
287
|
order: "fewest-files-first",
|
|
285
288
|
timeout_minutes: 90,
|