@jwilger/pi-development-system 0.13.0 → 0.15.0
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/package.json
CHANGED
|
@@ -34,8 +34,11 @@ agent_spawn({path: "/impl-add-flag", type: "implementer", task: "...",
|
|
|
34
34
|
|
|
35
35
|
- Jev judges difficulty and risk; the project's `[routing]` table (slots, never
|
|
36
36
|
model ids) decides. Jev offline means `routine/low`.
|
|
37
|
-
- If the result says the slot is unresolvable,
|
|
38
|
-
|
|
37
|
+
- If the result says the slot is unresolvable, spawning without a pin fails the same
|
|
38
|
+
way (the agent's own `models:` list is the same candidate set). Run `/devsys-models`
|
|
39
|
+
to fix the matrix, or pin a model you know works.
|
|
40
|
+
- A pin is an explicit choice: it bypasses `/scoped-models` and the agent's `models:`
|
|
41
|
+
list, so only pin what the route (or you, with a stated reason) selected.
|
|
39
42
|
- You may override the route. Say why in your reply; overriding downwards for
|
|
40
43
|
risky work is a judgement call worth a line in the decision log.
|
|
41
44
|
|
package/src/core/routing.ts
CHANGED
|
@@ -55,7 +55,7 @@ export function recommendRoute(input: {
|
|
|
55
55
|
if (!resolved.ok) {
|
|
56
56
|
return {
|
|
57
57
|
...route,
|
|
58
|
-
note: `${prefix}No model with credentials resolves slot "${route.slot}" (unresolvable);
|
|
58
|
+
note: `${prefix}No model with credentials resolves slot "${route.slot}" (unresolvable); an agent spawned without a pin will fail the same way, so run /devsys-models to fix the matrix or pin a model you know works.`,
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
61
|
return {
|
|
@@ -16,7 +16,7 @@ export type Settings = {
|
|
|
16
16
|
readonly thinkingLevel: ThinkingLevel;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
const MODEL_REF = /^[^\s
|
|
19
|
+
const MODEL_REF = /^[^\s/*]+\/[^\s*]+$/;
|
|
20
20
|
|
|
21
21
|
const isLevel = (value: unknown): value is ThinkingLevel =>
|
|
22
22
|
THINKING_LEVELS.some((level) => level === value);
|
|
@@ -57,3 +57,21 @@ export function applySpawnOverrides(
|
|
|
57
57
|
...(overrides.thinkingLevel === undefined ? {} : { thinkingLevel: overrides.thinkingLevel }),
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
|
+
|
|
61
|
+
export type ModelSource = "current" | "preferences" | "restored" | "inherited";
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Where a subagent's starting model comes from. A per-spawn pin disqualifies the type's
|
|
65
|
+
* preference list (the pin itself is applied afterwards for fresh spawns), so a resumed pinned
|
|
66
|
+
* thread keeps the model it was already running instead of falling back to the preferences.
|
|
67
|
+
*/
|
|
68
|
+
export function modelSource(input: {
|
|
69
|
+
mode: "use-current" | "pick-first-scoped" | "pick-first-available";
|
|
70
|
+
pinned: boolean;
|
|
71
|
+
hasPreferences: boolean;
|
|
72
|
+
hasRestored: boolean;
|
|
73
|
+
}): ModelSource {
|
|
74
|
+
if (input.mode === "use-current") return "current";
|
|
75
|
+
if (!input.pinned && input.hasPreferences) return "preferences";
|
|
76
|
+
return input.hasRestored ? "restored" : "inherited";
|
|
77
|
+
}
|
|
@@ -56,7 +56,7 @@ export function medianLabel<T extends string>(
|
|
|
56
56
|
let cumulative = 0;
|
|
57
57
|
for (const level of levels) {
|
|
58
58
|
cumulative += (answer.probabilities[level] ?? 0) / total;
|
|
59
|
-
if (cumulative
|
|
59
|
+
if (cumulative > 0.5) return level;
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
return levels.find((level) => level === answer.choice);
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
1. Every `*.ts` file starts with `// @ts-nocheck`: upstream compiles under looser options than this repo's strict `tsconfig.json` (`exactOptionalPropertyTypes`, `erasableSyntaxOnly`). `biome.json` excludes `src/subagents`. Both are deliberate: the code is verbatim, and reformatting or retyping it would make upstream diffs unreadable.
|
|
13
13
|
2. Relative `.js` import specifiers were rewritten to `.ts` (upstream relies on a TypeScript-aware loader; this repo's tests run under Node's native type stripping).
|
|
14
14
|
3. `prefs/config.ts`: the bundled-agents directory is `../../../agents/` (was `../../agents/`) because the sources moved one level deeper.
|
|
15
|
-
4. Per-spawn pins (I5.2): `types.ts` `AgentType.spawnOverrides` and `ThreadService.spawn` args gain `model`/`thinkingLevel`; `orch/tools.ts` `agent_spawn` schema exposes both; `orch/manager.ts` `spawn` validates them with `src/core/spawn-overrides.ts` and stores them on the cloned type (so saved threads keep them); `orch/runtime.ts` `resolveInitialSettings` applies
|
|
15
|
+
4. Per-spawn pins (I5.2): `types.ts` `AgentType.spawnOverrides` and `ThreadService.spawn` args gain `model`/`thinkingLevel`; `orch/tools.ts` `agent_spawn` schema exposes both; `orch/manager.ts` `spawn` validates them with `src/core/spawn-overrides.ts` and stores them on the cloned type (so saved threads keep them); `orch/runtime.ts` `resolveInitialSettings` skips the type's preference list when a model is pinned (via `modelSource` in `src/core/spawn-overrides.ts`, so resumed threads keep their model), applies the pin last for fresh spawns, and `createDriver` drops model preferences/scope policy when a model is pinned; `ThreadView.pinned` (`types.ts`) is filled in `orch/manager.ts` `view()` so `agent_status` reports the pins. A pin therefore bypasses `/scoped-models`.
|
|
16
16
|
5. Family preferences (I5.3): `prefs/models.ts` `selectPreferredModel` resolves each `models:` entry with `resolveCandidate` from `src/core/models.ts`, so `provider/gpt-*-sol` picks the newest available match; exact ids behave as before.
|
|
17
17
|
|
|
18
18
|
## MIT notice
|
|
@@ -38,3 +38,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
38
38
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
39
39
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
40
40
|
SOFTWARE.
|
|
41
|
+
6. Empty scope (I5 review): `orch/runtime.ts` `availableScopedModels` treats an empty `/scoped-models` as no restriction (pi's meaning) instead of "matches nothing", because every devsys agent declares `models:` and would otherwise be unspawnable by default. Pinned models are also re-applied on resume (`resolveInitialSettings`), since a restored session may hold only inherited history.
|
|
@@ -26,7 +26,7 @@ import { selectTools } from "../prefs/config.ts";
|
|
|
26
26
|
import { TranscriptChannel } from "./transcript.ts";
|
|
27
27
|
import type { InheritedToolSource } from "./inherited-tools.ts";
|
|
28
28
|
import type { ModelSelectionMode, ToolFilteringMode } from "../prefs/settings.ts";
|
|
29
|
-
import { applySpawnOverrides } from "../../core/spawn-overrides.ts";
|
|
29
|
+
import { applySpawnOverrides, modelSource } from "../../core/spawn-overrides.ts";
|
|
30
30
|
import {
|
|
31
31
|
modelIdentity,
|
|
32
32
|
getModelPreferences,
|
|
@@ -165,6 +165,8 @@ export function createDriverFactory(
|
|
|
165
165
|
.map((model) => ({ model }));
|
|
166
166
|
const availableScopedModels = (scopedModels: readonly ScopedModel[]) => {
|
|
167
167
|
const available = new Set(availableModelCandidates().map(({ model }) => modelIdentity(model)));
|
|
168
|
+
// devsys: an empty scope means no /scoped-models restriction in pi, not "matches nothing".
|
|
169
|
+
if (scopedModels.length === 0) return [...availableModelCandidates()];
|
|
168
170
|
return scopedModels.filter(({ model }) => available.has(modelIdentity(model)));
|
|
169
171
|
};
|
|
170
172
|
const selectTypeModelPreference = (
|
|
@@ -202,13 +204,19 @@ export function createDriverFactory(
|
|
|
202
204
|
const mode = getModelSelection();
|
|
203
205
|
// devsys: an explicit per-spawn model pin bypasses preference/scope policy (also on resume).
|
|
204
206
|
const pinned = type.spawnOverrides?.model !== undefined;
|
|
205
|
-
const preferences =
|
|
207
|
+
const preferences = pinned ? undefined : getModelPreferences(type);
|
|
208
|
+
const source = modelSource({
|
|
209
|
+
mode,
|
|
210
|
+
pinned,
|
|
211
|
+
hasPreferences: preferences !== undefined,
|
|
212
|
+
hasRestored: restored?.model !== undefined,
|
|
213
|
+
});
|
|
206
214
|
let provider = parent?.provider ?? ctx.model?.provider;
|
|
207
215
|
let id = parent?.id ?? ctx.model?.id;
|
|
208
|
-
if (
|
|
216
|
+
if (source === "current") {
|
|
209
217
|
provider = ctx.model?.provider;
|
|
210
218
|
id = ctx.model?.id;
|
|
211
|
-
} else if (
|
|
219
|
+
} else if (source === "preferences") {
|
|
212
220
|
const filtering = mode === "pick-first-scoped";
|
|
213
221
|
const candidates = filtering
|
|
214
222
|
? availableScopedModels(normalizeScopedModels(ctx.scopedModels))
|
|
@@ -216,7 +224,7 @@ export function createDriverFactory(
|
|
|
216
224
|
({ provider, id } = parseModelIdentity(
|
|
217
225
|
selectTypeModelPreference(type, candidates, filtering)!,
|
|
218
226
|
));
|
|
219
|
-
} else if (restored?.model) {
|
|
227
|
+
} else if (source === "restored" && restored?.model) {
|
|
220
228
|
provider = restored.model.provider;
|
|
221
229
|
id = restored.model.modelId;
|
|
222
230
|
}
|
|
@@ -226,9 +234,12 @@ export function createDriverFactory(
|
|
|
226
234
|
: undefined;
|
|
227
235
|
const thinkingLevel =
|
|
228
236
|
savedThinking ?? type.thinkingLevel ?? parent?.thinking ?? ctx.thinkingLevel ?? "off";
|
|
229
|
-
// devsys: a per-spawn pin from agent_spawn wins
|
|
230
|
-
|
|
231
|
-
|
|
237
|
+
// devsys: a per-spawn pin from agent_spawn wins. On resume the thinking level is the saved one,
|
|
238
|
+
// but the pinned model still applies: the restored session may only hold inherited history.
|
|
239
|
+
const pins = type.spawnOverrides;
|
|
240
|
+
if (restored === undefined) return applySpawnOverrides({ provider, id, thinkingLevel }, pins);
|
|
241
|
+
if (pins?.model !== undefined)
|
|
242
|
+
return applySpawnOverrides({ provider, id, thinkingLevel }, { model: pins.model });
|
|
232
243
|
return { provider, id, thinkingLevel };
|
|
233
244
|
};
|
|
234
245
|
const resolveAgentSettings = (type: AgentType, parentPath: string): ResolvedAgentSettings => {
|