gentle-pi 0.10.1 → 0.10.2
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/README.md +2 -2
- package/assets/orchestrator.md +9 -1
- package/extensions/gentle-ai.ts +83 -12
- package/package.json +1 -1
- package/tests/runtime-harness.mjs +156 -3
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ pi install npm:gentle-pi
|
|
|
68
68
|
Recommended companion packages:
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
|
-
pi install npm:pi-subagents
|
|
71
|
+
pi install npm:pi-subagents-j0k3r
|
|
72
72
|
pi install npm:pi-intercom
|
|
73
73
|
pi install npm:gentle-engram
|
|
74
74
|
pi install npm:pi-web-access
|
|
@@ -346,7 +346,7 @@ The modal discovers:
|
|
|
346
346
|
|
|
347
347
|
- project agents in `.pi/agents/` and `.agents/`;
|
|
348
348
|
- user agents in `~/.pi/agent/agents/` and `~/.agents/`;
|
|
349
|
-
- built-in agents from `pi-subagents
|
|
349
|
+
- built-in agents from `pi-subagents-j0k3r` when present.
|
|
350
350
|
|
|
351
351
|
Recommended model/effort shape:
|
|
352
352
|
|
package/assets/orchestrator.md
CHANGED
|
@@ -82,7 +82,15 @@ Examples:
|
|
|
82
82
|
- run tests/builds and summarize results;
|
|
83
83
|
- fresh-context review.
|
|
84
84
|
|
|
85
|
-
Use `pi-subagents` when available. Prefer background/async for long exploration, implementation, tests, or review when the parent has independent work.
|
|
85
|
+
Use `pi-subagents-j0k3r` when available. Prefer background/async for long exploration, implementation, tests, or review when the parent has independent work.
|
|
86
|
+
|
|
87
|
+
### Pi Subagent Model Routing
|
|
88
|
+
|
|
89
|
+
For generic Pi subagents (`delegate`, `worker`, `scout`, `reviewer`, `context-builder`, `oracle`, `planner`, `researcher`, or other non-SDD agents), do not pass the `model` parameter by default. Let `pi-subagents` resolve model and thinking from `.pi/settings.json`, `.pi/subagents.json`, global subagent config, and runtime defaults.
|
|
90
|
+
|
|
91
|
+
SDD model assignment tables apply only to SDD/Judgment-Day phase agents. They must not be used for generic Pi delegation.
|
|
92
|
+
|
|
93
|
+
Only pass `model` for generic subagents when the user explicitly requests a model override for that launch.
|
|
86
94
|
|
|
87
95
|
Default balanced pattern for bounded implementation:
|
|
88
96
|
|
package/extensions/gentle-ai.ts
CHANGED
|
@@ -734,7 +734,9 @@ function normalizeRoutingEntry(value: unknown): AgentRoutingEntry | undefined {
|
|
|
734
734
|
if (!isRecord(value)) return undefined;
|
|
735
735
|
const model = normalizeModelId(value.model);
|
|
736
736
|
const thinking = isThinkingLevel(value.thinking) ? value.thinking : undefined;
|
|
737
|
-
if (!model && !thinking)
|
|
737
|
+
if (!model && !thinking) {
|
|
738
|
+
return Object.keys(value).length === 0 ? {} : undefined;
|
|
739
|
+
}
|
|
738
740
|
return { model, thinking };
|
|
739
741
|
}
|
|
740
742
|
|
|
@@ -987,13 +989,38 @@ async function listAgentsFromDirAsync(
|
|
|
987
989
|
return entries;
|
|
988
990
|
}
|
|
989
991
|
|
|
990
|
-
function
|
|
991
|
-
|
|
992
|
-
|
|
992
|
+
function builtinAgentDirs(cwd: string): string[] {
|
|
993
|
+
return [
|
|
994
|
+
join(PACKAGE_ROOT, "..", "pi-subagents-j0k3r", "agents"),
|
|
995
|
+
join(cwd, ".pi", "npm", "node_modules", "pi-subagents-j0k3r", "agents"),
|
|
996
|
+
join(homedir(), ".local", "lib", "node_modules", "pi-subagents-j0k3r", "agents"),
|
|
993
997
|
join(PACKAGE_ROOT, "..", "pi-subagents", "agents"),
|
|
994
998
|
join(cwd, ".pi", "npm", "node_modules", "pi-subagents", "agents"),
|
|
995
999
|
join(homedir(), ".local", "lib", "node_modules", "pi-subagents", "agents"),
|
|
996
1000
|
];
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
function listBuiltinAgentNames(cwd: string): Set<string> {
|
|
1004
|
+
return new Set(
|
|
1005
|
+
builtinAgentDirs(cwd).flatMap((dir) =>
|
|
1006
|
+
listAgentsFromDir(dir, "builtin").map((agent) => agent.name),
|
|
1007
|
+
),
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
async function listBuiltinAgentNamesAsync(cwd: string): Promise<Set<string>> {
|
|
1012
|
+
const names = new Set<string>();
|
|
1013
|
+
for (const dir of builtinAgentDirs(cwd)) {
|
|
1014
|
+
for (const agent of await listAgentsFromDirAsync(dir, "builtin")) {
|
|
1015
|
+
names.add(agent.name);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return names;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function listDiscoverableAgents(cwd: string): AgentEntry[] {
|
|
1022
|
+
const globalAgentDir = join(gentlePiAgentHome(), "agents");
|
|
1023
|
+
const builtinDirs = builtinAgentDirs(cwd);
|
|
997
1024
|
const agents = [
|
|
998
1025
|
...builtinDirs.flatMap((dir) => listAgentsFromDir(dir, "builtin")),
|
|
999
1026
|
...listAgentsFromDir(globalAgentDir, "user"),
|
|
@@ -1008,11 +1035,7 @@ function listDiscoverableAgents(cwd: string): AgentEntry[] {
|
|
|
1008
1035
|
|
|
1009
1036
|
async function listDiscoverableAgentsAsync(cwd: string): Promise<AgentEntry[]> {
|
|
1010
1037
|
const globalAgentDir = join(gentlePiAgentHome(), "agents");
|
|
1011
|
-
const builtinDirs =
|
|
1012
|
-
join(PACKAGE_ROOT, "..", "pi-subagents", "agents"),
|
|
1013
|
-
join(cwd, ".pi", "npm", "node_modules", "pi-subagents", "agents"),
|
|
1014
|
-
join(homedir(), ".local", "lib", "node_modules", "pi-subagents", "agents"),
|
|
1015
|
-
];
|
|
1038
|
+
const builtinDirs = builtinAgentDirs(cwd);
|
|
1016
1039
|
const agents: AgentEntry[] = [];
|
|
1017
1040
|
for (const dir of builtinDirs) {
|
|
1018
1041
|
agents.push(...(await listAgentsFromDirAsync(dir, "builtin")));
|
|
@@ -1045,6 +1068,10 @@ function projectSettingsPath(cwd: string): string {
|
|
|
1045
1068
|
return join(cwd, ".pi", "settings.json");
|
|
1046
1069
|
}
|
|
1047
1070
|
|
|
1071
|
+
function isClearRoutingEntry(entry: AgentRoutingEntry): boolean {
|
|
1072
|
+
return entry.model === undefined && entry.thinking === undefined;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1048
1075
|
function updateBuiltinModelOverride(
|
|
1049
1076
|
cwd: string,
|
|
1050
1077
|
name: string,
|
|
@@ -1131,13 +1158,28 @@ export function applyModelConfig(
|
|
|
1131
1158
|
): { updated: number; skipped: number } {
|
|
1132
1159
|
let updated = 0;
|
|
1133
1160
|
let skipped = 0;
|
|
1161
|
+
const builtinNames = listBuiltinAgentNames(cwd);
|
|
1162
|
+
const seenAgents = new Set<string>();
|
|
1134
1163
|
for (const agent of listDiscoverableAgents(cwd)) {
|
|
1164
|
+
seenAgents.add(agent.name);
|
|
1135
1165
|
const entry = config[agent.name];
|
|
1136
1166
|
if (agent.source === "builtin") {
|
|
1167
|
+
if (entry === undefined) {
|
|
1168
|
+
skipped += 1;
|
|
1169
|
+
continue;
|
|
1170
|
+
}
|
|
1137
1171
|
if (updateBuiltinModelOverride(cwd, agent.name, entry)) updated += 1;
|
|
1138
1172
|
else skipped += 1;
|
|
1139
1173
|
continue;
|
|
1140
1174
|
}
|
|
1175
|
+
if (entry === undefined) {
|
|
1176
|
+
skipped += 1;
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
if (builtinNames.has(agent.name) || isClearRoutingEntry(entry)) {
|
|
1180
|
+
if (updateBuiltinModelOverride(cwd, agent.name, entry)) updated += 1;
|
|
1181
|
+
else skipped += 1;
|
|
1182
|
+
}
|
|
1141
1183
|
if (!agent.filePath || !existsSync(agent.filePath)) {
|
|
1142
1184
|
skipped += 1;
|
|
1143
1185
|
continue;
|
|
@@ -1151,6 +1193,12 @@ export function applyModelConfig(
|
|
|
1151
1193
|
writeFileSync(agent.filePath, next);
|
|
1152
1194
|
updated += 1;
|
|
1153
1195
|
}
|
|
1196
|
+
for (const [name, entry] of Object.entries(config)) {
|
|
1197
|
+
if (!seenAgents.has(name) && isClearRoutingEntry(entry)) {
|
|
1198
|
+
if (updateBuiltinModelOverride(cwd, name, entry)) updated += 1;
|
|
1199
|
+
else skipped += 1;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1154
1202
|
return { updated, skipped };
|
|
1155
1203
|
}
|
|
1156
1204
|
|
|
@@ -1160,14 +1208,30 @@ export async function applyModelConfigAsync(
|
|
|
1160
1208
|
): Promise<{ updated: number; skipped: number }> {
|
|
1161
1209
|
let updated = 0;
|
|
1162
1210
|
let skipped = 0;
|
|
1211
|
+
const builtinNames = await listBuiltinAgentNamesAsync(cwd);
|
|
1212
|
+
const seenAgents = new Set<string>();
|
|
1163
1213
|
for (const agent of await listDiscoverableAgentsAsync(cwd)) {
|
|
1214
|
+
seenAgents.add(agent.name);
|
|
1164
1215
|
const entry = config[agent.name];
|
|
1165
1216
|
if (agent.source === "builtin") {
|
|
1217
|
+
if (entry === undefined) {
|
|
1218
|
+
skipped += 1;
|
|
1219
|
+
continue;
|
|
1220
|
+
}
|
|
1166
1221
|
if (await updateBuiltinModelOverrideAsync(cwd, agent.name, entry))
|
|
1167
1222
|
updated += 1;
|
|
1168
1223
|
else skipped += 1;
|
|
1169
1224
|
continue;
|
|
1170
1225
|
}
|
|
1226
|
+
if (entry === undefined) {
|
|
1227
|
+
skipped += 1;
|
|
1228
|
+
continue;
|
|
1229
|
+
}
|
|
1230
|
+
if (builtinNames.has(agent.name) || isClearRoutingEntry(entry)) {
|
|
1231
|
+
if (await updateBuiltinModelOverrideAsync(cwd, agent.name, entry))
|
|
1232
|
+
updated += 1;
|
|
1233
|
+
else skipped += 1;
|
|
1234
|
+
}
|
|
1171
1235
|
if (!agent.filePath || !(await pathExists(agent.filePath))) {
|
|
1172
1236
|
skipped += 1;
|
|
1173
1237
|
continue;
|
|
@@ -1181,6 +1245,13 @@ export async function applyModelConfigAsync(
|
|
|
1181
1245
|
await writeFile(agent.filePath, next);
|
|
1182
1246
|
updated += 1;
|
|
1183
1247
|
}
|
|
1248
|
+
for (const [name, entry] of Object.entries(config)) {
|
|
1249
|
+
if (!seenAgents.has(name) && isClearRoutingEntry(entry)) {
|
|
1250
|
+
if (await updateBuiltinModelOverrideAsync(cwd, name, entry))
|
|
1251
|
+
updated += 1;
|
|
1252
|
+
else skipped += 1;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1184
1255
|
return { updated, skipped };
|
|
1185
1256
|
}
|
|
1186
1257
|
|
|
@@ -1500,7 +1571,7 @@ class SddModelPanel implements OverlayComponent {
|
|
|
1500
1571
|
const current = this.draft[name] ?? {};
|
|
1501
1572
|
if (model === undefined) delete current.model;
|
|
1502
1573
|
else current.model = model;
|
|
1503
|
-
if (!current.model && !current.thinking)
|
|
1574
|
+
if (!current.model && !current.thinking) this.draft[name] = {};
|
|
1504
1575
|
else this.draft[name] = current;
|
|
1505
1576
|
}
|
|
1506
1577
|
|
|
@@ -1508,12 +1579,12 @@ class SddModelPanel implements OverlayComponent {
|
|
|
1508
1579
|
const current = this.draft[name] ?? {};
|
|
1509
1580
|
if (thinking === undefined) delete current.thinking;
|
|
1510
1581
|
else current.thinking = thinking;
|
|
1511
|
-
if (!current.model && !current.thinking)
|
|
1582
|
+
if (!current.model && !current.thinking) this.draft[name] = {};
|
|
1512
1583
|
else this.draft[name] = current;
|
|
1513
1584
|
}
|
|
1514
1585
|
|
|
1515
1586
|
private clearEntry(name: string): void {
|
|
1516
|
-
|
|
1587
|
+
this.draft[name] = {};
|
|
1517
1588
|
}
|
|
1518
1589
|
|
|
1519
1590
|
private filteredModelOptions(): string[] {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gentle-pi",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Turn Pi into el Gentleman: a senior-architect development harness with SDD/OpenSpec, subagents, strict TDD evidence, review guardrails, and skill discovery.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -197,6 +197,10 @@ async function run() {
|
|
|
197
197
|
const promptResult = await promptHook({ systemPrompt: "base" }, createCtx(promptCwd));
|
|
198
198
|
assert.match(promptResult.systemPrompt, /base/);
|
|
199
199
|
assert.match(promptResult.systemPrompt, /el Gentleman/);
|
|
200
|
+
assert.match(promptResult.systemPrompt, /do not pass the `model` parameter by default/);
|
|
201
|
+
assert.match(promptResult.systemPrompt, /SDD model assignment tables apply only to SDD\/Judgment-Day phase agents/);
|
|
202
|
+
assert.doesNotMatch(promptResult.systemPrompt, /Every Agent tool call MUST include `model`/);
|
|
203
|
+
assert.doesNotMatch(promptResult.systemPrompt, /default\s*\|\s*sonnet\s*\|\s*Non-SDD general delegation/);
|
|
200
204
|
assert.match(promptResult.systemPrompt, /openspec\/config\.yaml.*not session preflight/s);
|
|
201
205
|
assert.match(promptResult.systemPrompt, /Do not mark SDD preflight complete/);
|
|
202
206
|
await writeFile(
|
|
@@ -765,21 +769,102 @@ async function run() {
|
|
|
765
769
|
assert.equal(await readFile(globalModelsPath, "utf8"), "{ invalid json");
|
|
766
770
|
assert.equal(legacyCtx.ui.notifications.at(-1).level, "warning");
|
|
767
771
|
assert.match(legacyCtx.ui.notifications.at(-1).message, /cannot open model config/);
|
|
772
|
+
await writeFile(
|
|
773
|
+
join(legacyModelsCwd, ".pi", "settings.json"),
|
|
774
|
+
JSON.stringify(
|
|
775
|
+
{
|
|
776
|
+
subagents: {
|
|
777
|
+
agentOverrides: {
|
|
778
|
+
"sdd-apply": { model: "settings/provider-model", thinking: "high" },
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
},
|
|
782
|
+
null,
|
|
783
|
+
2,
|
|
784
|
+
),
|
|
785
|
+
);
|
|
768
786
|
await writeFile(globalModelsPath, JSON.stringify({}, null, 2));
|
|
769
787
|
await hooks.get("session_start")[0]({ reason: "startup" }, legacyCtx);
|
|
770
|
-
const
|
|
788
|
+
const emptyGlobalPreservesAgent = await readFile(
|
|
789
|
+
join(legacyModelsCwd, ".pi", "agents", "sdd-apply.md"),
|
|
790
|
+
"utf8",
|
|
791
|
+
);
|
|
792
|
+
assert.match(emptyGlobalPreservesAgent, /model: global\/provider-model/);
|
|
793
|
+
const emptyGlobalPreservesSettings = JSON.parse(
|
|
794
|
+
await readFile(join(legacyModelsCwd, ".pi", "settings.json"), "utf8"),
|
|
795
|
+
);
|
|
796
|
+
assert.equal(
|
|
797
|
+
emptyGlobalPreservesSettings.subagents.agentOverrides["sdd-apply"].model,
|
|
798
|
+
"settings/provider-model",
|
|
799
|
+
);
|
|
800
|
+
await writeFile(
|
|
801
|
+
globalModelsPath,
|
|
802
|
+
JSON.stringify({ "sdd-apply": { model: "bad\nmodel: injected" } }, null, 2),
|
|
803
|
+
);
|
|
804
|
+
await hooks.get("session_start")[0]({ reason: "startup" }, legacyCtx);
|
|
805
|
+
const invalidEntryPreservesAgent = await readFile(
|
|
771
806
|
join(legacyModelsCwd, ".pi", "agents", "sdd-apply.md"),
|
|
772
807
|
"utf8",
|
|
773
808
|
);
|
|
774
|
-
assert.
|
|
809
|
+
assert.match(invalidEntryPreservesAgent, /model: global\/provider-model/);
|
|
810
|
+
const invalidEntryPreservesSettings = JSON.parse(
|
|
811
|
+
await readFile(join(legacyModelsCwd, ".pi", "settings.json"), "utf8"),
|
|
812
|
+
);
|
|
813
|
+
assert.equal(
|
|
814
|
+
invalidEntryPreservesSettings.subagents.agentOverrides["sdd-apply"].model,
|
|
815
|
+
"settings/provider-model",
|
|
816
|
+
);
|
|
817
|
+
await writeFile(globalModelsPath, JSON.stringify({ "sdd-apply": {} }, null, 2));
|
|
818
|
+
await hooks.get("session_start")[0]({ reason: "startup" }, legacyCtx);
|
|
819
|
+
const explicitInheritClearsAgent = await readFile(
|
|
820
|
+
join(legacyModelsCwd, ".pi", "agents", "sdd-apply.md"),
|
|
821
|
+
"utf8",
|
|
822
|
+
);
|
|
823
|
+
assert.doesNotMatch(explicitInheritClearsAgent, /model:/);
|
|
824
|
+
const explicitInheritClearsSettings = JSON.parse(
|
|
825
|
+
await readFile(join(legacyModelsCwd, ".pi", "settings.json"), "utf8"),
|
|
826
|
+
);
|
|
827
|
+
assert.equal(explicitInheritClearsSettings.subagents, undefined);
|
|
775
828
|
} finally {
|
|
776
829
|
await rm(legacyModelsCwd, { recursive: true, force: true });
|
|
777
830
|
await rm(globalModelsPath, { force: true });
|
|
778
831
|
}
|
|
779
832
|
|
|
833
|
+
const staleSettingsOnlyCwd = await tempWorkspace();
|
|
834
|
+
try {
|
|
835
|
+
await mkdir(join(staleSettingsOnlyCwd, ".pi"), { recursive: true });
|
|
836
|
+
await writeFile(
|
|
837
|
+
join(staleSettingsOnlyCwd, ".pi", "settings.json"),
|
|
838
|
+
JSON.stringify(
|
|
839
|
+
{
|
|
840
|
+
subagents: {
|
|
841
|
+
agentOverrides: {
|
|
842
|
+
worker: { model: "stale/model", thinking: "high" },
|
|
843
|
+
},
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
null,
|
|
847
|
+
2,
|
|
848
|
+
),
|
|
849
|
+
);
|
|
850
|
+
await writeFile(globalModelsPath, JSON.stringify({ worker: {} }, null, 2));
|
|
851
|
+
await hooks.get("session_start")[0]({ reason: "startup" }, createCtx(staleSettingsOnlyCwd, true));
|
|
852
|
+
const staleOnlyClearedSettings = JSON.parse(
|
|
853
|
+
await readFile(join(staleSettingsOnlyCwd, ".pi", "settings.json"), "utf8"),
|
|
854
|
+
);
|
|
855
|
+
assert.equal(staleOnlyClearedSettings.subagents, undefined);
|
|
856
|
+
} finally {
|
|
857
|
+
await rm(staleSettingsOnlyCwd, { recursive: true, force: true });
|
|
858
|
+
await rm(globalModelsPath, { force: true });
|
|
859
|
+
}
|
|
860
|
+
|
|
780
861
|
const modelsCwd = await tempWorkspace();
|
|
781
862
|
try {
|
|
782
863
|
await mkdir(join(modelsCwd, ".pi", "agents"), { recursive: true });
|
|
864
|
+
await mkdir(
|
|
865
|
+
join(modelsCwd, ".pi", "npm", "node_modules", "pi-subagents-j0k3r", "agents"),
|
|
866
|
+
{ recursive: true },
|
|
867
|
+
);
|
|
783
868
|
await mkdir(
|
|
784
869
|
join(modelsCwd, ".pi", "npm", "node_modules", "pi-subagents", "agents"),
|
|
785
870
|
{ recursive: true },
|
|
@@ -790,12 +875,28 @@ async function run() {
|
|
|
790
875
|
".pi",
|
|
791
876
|
"npm",
|
|
792
877
|
"node_modules",
|
|
793
|
-
"pi-subagents",
|
|
878
|
+
"pi-subagents-j0k3r",
|
|
794
879
|
"agents",
|
|
795
880
|
"worker.md",
|
|
796
881
|
),
|
|
797
882
|
`---\nname: worker\ndescription: Builtin worker\n---\n`,
|
|
798
883
|
);
|
|
884
|
+
await writeFile(
|
|
885
|
+
join(modelsCwd, ".pi", "agents", "worker.md"),
|
|
886
|
+
`---\nname: worker\ndescription: Project worker\nmodel: existing/project-worker\nthinking: high\n---\n`,
|
|
887
|
+
);
|
|
888
|
+
await writeFile(
|
|
889
|
+
join(
|
|
890
|
+
modelsCwd,
|
|
891
|
+
".pi",
|
|
892
|
+
"npm",
|
|
893
|
+
"node_modules",
|
|
894
|
+
"pi-subagents",
|
|
895
|
+
"agents",
|
|
896
|
+
"researcher.md",
|
|
897
|
+
),
|
|
898
|
+
`---\nname: researcher\ndescription: Legacy builtin researcher\n---\n`,
|
|
899
|
+
);
|
|
799
900
|
await writeFile(
|
|
800
901
|
join(modelsCwd, ".pi", "agents", "sdd-apply.md"),
|
|
801
902
|
`---\nname: sdd-apply\ndescription: Apply phase\n---\n\nbody\n`,
|
|
@@ -811,6 +912,52 @@ async function run() {
|
|
|
811
912
|
join(modelsCwd, ".pi", "agents", "escape-agent.md"),
|
|
812
913
|
`---\nname: evil\u001b]52;c;Zm9v\u0007-agent\ndescription: Escape fixture\n---\n`,
|
|
813
914
|
);
|
|
915
|
+
await writeFile(
|
|
916
|
+
join(modelsCwd, ".pi", "settings.json"),
|
|
917
|
+
JSON.stringify(
|
|
918
|
+
{
|
|
919
|
+
subagents: {
|
|
920
|
+
agentOverrides: {
|
|
921
|
+
worker: { model: "existing/model", thinking: "high" },
|
|
922
|
+
},
|
|
923
|
+
},
|
|
924
|
+
},
|
|
925
|
+
null,
|
|
926
|
+
2,
|
|
927
|
+
),
|
|
928
|
+
);
|
|
929
|
+
await writeFile(globalModelsPath, JSON.stringify({}, null, 2));
|
|
930
|
+
await hooks.get("session_start")[0]({ reason: "startup" }, createCtx(modelsCwd, true));
|
|
931
|
+
const preservedSettings = JSON.parse(
|
|
932
|
+
await readFile(join(modelsCwd, ".pi", "settings.json"), "utf8"),
|
|
933
|
+
);
|
|
934
|
+
assert.equal(
|
|
935
|
+
preservedSettings.subagents.agentOverrides.worker.model,
|
|
936
|
+
"existing/model",
|
|
937
|
+
);
|
|
938
|
+
assert.equal(
|
|
939
|
+
preservedSettings.subagents.agentOverrides.worker.thinking,
|
|
940
|
+
"high",
|
|
941
|
+
);
|
|
942
|
+
const preservedProjectWorker = await readFile(
|
|
943
|
+
join(modelsCwd, ".pi", "agents", "worker.md"),
|
|
944
|
+
"utf8",
|
|
945
|
+
);
|
|
946
|
+
assert.match(preservedProjectWorker, /model: existing\/project-worker/);
|
|
947
|
+
assert.match(preservedProjectWorker, /thinking: high/);
|
|
948
|
+
await writeFile(globalModelsPath, JSON.stringify({ worker: {} }, null, 2));
|
|
949
|
+
await hooks.get("session_start")[0]({ reason: "startup" }, createCtx(modelsCwd, true));
|
|
950
|
+
const clearedSettings = JSON.parse(
|
|
951
|
+
await readFile(join(modelsCwd, ".pi", "settings.json"), "utf8"),
|
|
952
|
+
);
|
|
953
|
+
assert.equal(clearedSettings.subagents, undefined);
|
|
954
|
+
const clearedProjectWorker = await readFile(
|
|
955
|
+
join(modelsCwd, ".pi", "agents", "worker.md"),
|
|
956
|
+
"utf8",
|
|
957
|
+
);
|
|
958
|
+
assert.doesNotMatch(clearedProjectWorker, /model:/);
|
|
959
|
+
assert.doesNotMatch(clearedProjectWorker, /thinking:/);
|
|
960
|
+
|
|
814
961
|
await writeFile(
|
|
815
962
|
globalModelsPath,
|
|
816
963
|
JSON.stringify({ "sdd-apply": "openai/gpt-5" }, null, 2),
|
|
@@ -891,6 +1038,7 @@ async function run() {
|
|
|
891
1038
|
config: {
|
|
892
1039
|
"sdd-apply": { model: "openai/gpt-5", thinking: "high" },
|
|
893
1040
|
worker: { model: "openai/gpt-5-mini", thinking: "low" },
|
|
1041
|
+
researcher: { model: "openai/gpt-5-mini", thinking: "low" },
|
|
894
1042
|
},
|
|
895
1043
|
});
|
|
896
1044
|
await commands.get("gentle:models").handler("", ctx);
|
|
@@ -928,6 +1076,11 @@ async function run() {
|
|
|
928
1076
|
"openai/gpt-5-mini",
|
|
929
1077
|
);
|
|
930
1078
|
assert.equal(settings.subagents.agentOverrides.worker.thinking, "low");
|
|
1079
|
+
assert.equal(
|
|
1080
|
+
settings.subagents.agentOverrides.researcher.model,
|
|
1081
|
+
"openai/gpt-5-mini",
|
|
1082
|
+
);
|
|
1083
|
+
assert.equal(settings.subagents.agentOverrides.researcher.thinking, "low");
|
|
931
1084
|
|
|
932
1085
|
const kittyE = "\x1b[101u";
|
|
933
1086
|
assert.notEqual(kittyE, "e");
|