@bermudi/pi-delegate 0.1.7 → 0.1.9
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 +14 -1
- package/agents.ts +54 -3
- package/config.ts +84 -9
- package/constants.ts +17 -0
- package/delegate.ts +12 -1
- package/dispatch.ts +4 -0
- package/extension.ts +11 -1
- package/host.ts +243 -78
- package/manual.ts +9 -6
- package/package.json +1 -1
- package/schema.ts +4 -4
- package/settings.ts +235 -13
- package/task-resolution.ts +109 -52
- package/types.ts +7 -1
package/settings.ts
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
5
|
+
import { VALID_THINKING } from "./constants.ts";
|
|
6
|
+
|
|
7
|
+
export interface AgentOverride {
|
|
8
|
+
model?: string;
|
|
9
|
+
thinking?: ThinkingLevel;
|
|
10
|
+
tools?: string[];
|
|
11
|
+
}
|
|
4
12
|
|
|
5
13
|
export interface DelegateSettings {
|
|
6
|
-
agentOverrides?: Record<
|
|
7
|
-
|
|
8
|
-
{ model?: string; thinking?: string; tools?: string[]; skills?: string[] }
|
|
9
|
-
>;
|
|
14
|
+
agentOverrides?: Record<string, AgentOverride>;
|
|
15
|
+
agentOverridesByParentModel?: Record<string, Record<string, AgentOverride>>;
|
|
10
16
|
}
|
|
11
17
|
|
|
12
18
|
/** Read and validate a JSON settings object, returning null on I/O or parse errors. */
|
|
@@ -16,29 +22,192 @@ export function readDelegateSettingsFile(
|
|
|
16
22
|
try {
|
|
17
23
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
18
24
|
const parsed = JSON.parse(raw);
|
|
19
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
25
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
26
|
+
console.warn(
|
|
27
|
+
`[delegate] ignoring malformed settings file ${filePath}: expected a JSON object.`,
|
|
28
|
+
);
|
|
20
29
|
return null;
|
|
30
|
+
}
|
|
21
31
|
return parsed as Record<string, unknown>;
|
|
22
|
-
} catch {
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (
|
|
34
|
+
error instanceof Error &&
|
|
35
|
+
"code" in error &&
|
|
36
|
+
(error as NodeJS.ErrnoException).code === "ENOENT"
|
|
37
|
+
) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
console.warn(
|
|
41
|
+
`[delegate] could not read settings file ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
42
|
+
);
|
|
23
43
|
return null;
|
|
24
44
|
}
|
|
25
45
|
}
|
|
26
46
|
|
|
47
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
48
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function normalizeOverride(
|
|
52
|
+
raw: unknown,
|
|
53
|
+
source: string,
|
|
54
|
+
agentName: string,
|
|
55
|
+
): AgentOverride | null {
|
|
56
|
+
if (!isRecord(raw)) {
|
|
57
|
+
console.warn(
|
|
58
|
+
`[delegate] ignoring malformed settings override for agent '${agentName}' in ${source}: expected an object.`,
|
|
59
|
+
);
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const result: AgentOverride = {};
|
|
64
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
65
|
+
if (key === "model") {
|
|
66
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
67
|
+
console.warn(
|
|
68
|
+
`[delegate] ignoring malformed settings override for agent '${agentName}' in ${source}: model must be a nonempty string.`,
|
|
69
|
+
);
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
result.model = value.trim();
|
|
73
|
+
} else if (key === "thinking") {
|
|
74
|
+
if (typeof value !== "string" || !VALID_THINKING.has(value)) {
|
|
75
|
+
console.warn(
|
|
76
|
+
`[delegate] ignoring malformed settings override for agent '${agentName}' in ${source}: thinking must be a supported level.`,
|
|
77
|
+
);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
result.thinking = value as ThinkingLevel;
|
|
81
|
+
} else if (key === "tools") {
|
|
82
|
+
if (
|
|
83
|
+
!Array.isArray(value) ||
|
|
84
|
+
value.some((tool) => typeof tool !== "string")
|
|
85
|
+
) {
|
|
86
|
+
console.warn(
|
|
87
|
+
`[delegate] ignoring malformed settings override for agent '${agentName}' in ${source}: tools must be a string array.`,
|
|
88
|
+
);
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
result.tools = [...value];
|
|
92
|
+
} else if (key === "skills") {
|
|
93
|
+
console.warn(
|
|
94
|
+
`[delegate] ignoring unsupported skills override for agent '${agentName}' in ${source}: per-agent skill filtering is not supported.`,
|
|
95
|
+
);
|
|
96
|
+
return null;
|
|
97
|
+
} else {
|
|
98
|
+
console.warn(
|
|
99
|
+
`[delegate] ignoring malformed settings override for agent '${agentName}' in ${source}: unknown field '${key}'.`,
|
|
100
|
+
);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function normalizeOverrides(
|
|
108
|
+
raw: unknown,
|
|
109
|
+
source: string,
|
|
110
|
+
): Record<string, AgentOverride> {
|
|
111
|
+
if (!isRecord(raw)) {
|
|
112
|
+
console.warn(
|
|
113
|
+
`[delegate] ignoring malformed agentOverrides in ${source}: expected an object.`,
|
|
114
|
+
);
|
|
115
|
+
return {};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const result: Record<string, AgentOverride> = {};
|
|
119
|
+
const seenNames = new Map<string, string>();
|
|
120
|
+
for (const [agentName, value] of Object.entries(raw)) {
|
|
121
|
+
const normalizedAgentName = agentName.trim();
|
|
122
|
+
if (normalizedAgentName.length === 0) {
|
|
123
|
+
console.warn(
|
|
124
|
+
`[delegate] ignoring malformed settings override in ${source}: agent name must be nonempty.`,
|
|
125
|
+
);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
const previousName = seenNames.get(normalizedAgentName);
|
|
129
|
+
if (previousName !== undefined) {
|
|
130
|
+
console.warn(
|
|
131
|
+
`[delegate] ignoring duplicate settings override in ${source}: agent keys '${previousName}' and '${agentName}' both normalize to '${normalizedAgentName}'.`,
|
|
132
|
+
);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
seenNames.set(normalizedAgentName, agentName);
|
|
136
|
+
const override = normalizeOverride(value, source, normalizedAgentName);
|
|
137
|
+
if (override) result[normalizedAgentName] = override;
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function normalizeOverridesByParentModel(
|
|
143
|
+
raw: unknown,
|
|
144
|
+
source: string,
|
|
145
|
+
): Record<string, Record<string, AgentOverride>> {
|
|
146
|
+
if (!isRecord(raw)) {
|
|
147
|
+
console.warn(
|
|
148
|
+
`[delegate] ignoring malformed agentOverridesByParentModel in ${source}: expected an object.`,
|
|
149
|
+
);
|
|
150
|
+
return {};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const result: Record<string, Record<string, AgentOverride>> = {};
|
|
154
|
+
const seenModels = new Map<string, string>();
|
|
155
|
+
for (const [parentModel, overrides] of Object.entries(raw)) {
|
|
156
|
+
const normalizedParentModel = parentModel.trim();
|
|
157
|
+
if (normalizedParentModel.length === 0) {
|
|
158
|
+
console.warn(
|
|
159
|
+
`[delegate] ignoring malformed parent-model override in ${source}: model key must be nonempty.`,
|
|
160
|
+
);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const previousModel = seenModels.get(normalizedParentModel);
|
|
164
|
+
if (previousModel !== undefined) {
|
|
165
|
+
console.warn(
|
|
166
|
+
`[delegate] ignoring duplicate parent-model override in ${source}: model keys '${previousModel}' and '${parentModel}' both normalize to '${normalizedParentModel}'.`,
|
|
167
|
+
);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
seenModels.set(normalizedParentModel, parentModel);
|
|
171
|
+
result[normalizedParentModel] = normalizeOverrides(
|
|
172
|
+
overrides,
|
|
173
|
+
`${source} (parent model '${normalizedParentModel}')`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
|
|
27
179
|
function getDelegateSettings(filePath: string): DelegateSettings | null {
|
|
28
180
|
const settings = readDelegateSettingsFile(filePath);
|
|
29
181
|
if (
|
|
30
182
|
!settings?.delegate ||
|
|
31
183
|
typeof settings.delegate !== "object" ||
|
|
32
184
|
Array.isArray(settings.delegate)
|
|
33
|
-
)
|
|
185
|
+
) {
|
|
186
|
+
if (settings?.delegate !== undefined) {
|
|
187
|
+
console.warn(
|
|
188
|
+
`[delegate] ignoring malformed delegate settings in ${filePath}: expected an object.`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
34
191
|
return null;
|
|
35
|
-
|
|
192
|
+
}
|
|
193
|
+
const raw = settings.delegate as Record<string, unknown>;
|
|
194
|
+
const result: DelegateSettings = {};
|
|
195
|
+
if (raw.agentOverrides !== undefined) {
|
|
196
|
+
result.agentOverrides = normalizeOverrides(raw.agentOverrides, filePath);
|
|
197
|
+
}
|
|
198
|
+
if (raw.agentOverridesByParentModel !== undefined) {
|
|
199
|
+
result.agentOverridesByParentModel = normalizeOverridesByParentModel(
|
|
200
|
+
raw.agentOverridesByParentModel,
|
|
201
|
+
filePath,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
return result;
|
|
36
205
|
}
|
|
37
206
|
|
|
38
207
|
const delegateSettingsCache = new Map<string, DelegateSettings | null>();
|
|
39
208
|
|
|
40
209
|
/** Load merged delegate settings: project overrides user.
|
|
41
|
-
* Result is cached per cwd
|
|
210
|
+
* Result is cached per cwd until the next delegate dispatch clears it. */
|
|
42
211
|
export function loadDelegateSettings(cwd: string): DelegateSettings | null {
|
|
43
212
|
const key = path.resolve(cwd);
|
|
44
213
|
const cached = delegateSettingsCache.get(key);
|
|
@@ -68,11 +237,64 @@ export function loadDelegateSettings(cwd: string): DelegateSettings | null {
|
|
|
68
237
|
return null;
|
|
69
238
|
}
|
|
70
239
|
const result: DelegateSettings = {
|
|
71
|
-
agentOverrides
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
240
|
+
...(user?.agentOverrides || project?.agentOverrides
|
|
241
|
+
? {
|
|
242
|
+
agentOverrides: mergeOverrides(
|
|
243
|
+
user?.agentOverrides,
|
|
244
|
+
project?.agentOverrides,
|
|
245
|
+
),
|
|
246
|
+
}
|
|
247
|
+
: {}),
|
|
248
|
+
...(user?.agentOverridesByParentModel ||
|
|
249
|
+
project?.agentOverridesByParentModel
|
|
250
|
+
? {
|
|
251
|
+
agentOverridesByParentModel: mergeParentModelOverrides(
|
|
252
|
+
user?.agentOverridesByParentModel,
|
|
253
|
+
project?.agentOverridesByParentModel,
|
|
254
|
+
),
|
|
255
|
+
}
|
|
256
|
+
: {}),
|
|
75
257
|
};
|
|
76
258
|
delegateSettingsCache.set(key, result);
|
|
77
259
|
return result;
|
|
78
260
|
}
|
|
261
|
+
|
|
262
|
+
function mergeOverride(
|
|
263
|
+
base: AgentOverride | undefined,
|
|
264
|
+
override: AgentOverride | undefined,
|
|
265
|
+
): AgentOverride {
|
|
266
|
+
return { ...(base ?? {}), ...(override ?? {}) };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function mergeOverrides(
|
|
270
|
+
user: Record<string, AgentOverride> | undefined,
|
|
271
|
+
project: Record<string, AgentOverride> | undefined,
|
|
272
|
+
): Record<string, AgentOverride> {
|
|
273
|
+
const result: Record<string, AgentOverride> = {};
|
|
274
|
+
for (const name of new Set([
|
|
275
|
+
...Object.keys(user ?? {}),
|
|
276
|
+
...Object.keys(project ?? {}),
|
|
277
|
+
])) {
|
|
278
|
+
result[name] = mergeOverride(user?.[name], project?.[name]);
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function mergeParentModelOverrides(
|
|
284
|
+
user: Record<string, Record<string, AgentOverride>> | undefined,
|
|
285
|
+
project: Record<string, Record<string, AgentOverride>> | undefined,
|
|
286
|
+
): Record<string, Record<string, AgentOverride>> {
|
|
287
|
+
const result: Record<string, Record<string, AgentOverride>> = {};
|
|
288
|
+
for (const model of new Set([
|
|
289
|
+
...Object.keys(user ?? {}),
|
|
290
|
+
...Object.keys(project ?? {}),
|
|
291
|
+
])) {
|
|
292
|
+
result[model] = mergeOverrides(user?.[model], project?.[model]);
|
|
293
|
+
}
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Clear settings read from earlier delegate calls so edits are visible. */
|
|
298
|
+
export function clearDelegateSettingsCache(): void {
|
|
299
|
+
delegateSettingsCache.clear();
|
|
300
|
+
}
|
package/task-resolution.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
3
3
|
import {
|
|
4
|
+
BUILTIN_AGENT_NAMES,
|
|
4
5
|
DEFAULT_AGENT_NAME,
|
|
5
6
|
DEFAULT_TOOLS,
|
|
6
7
|
VALID_THINKING,
|
|
@@ -8,7 +9,7 @@ import {
|
|
|
8
9
|
import { TOOL_FACTORIES, resolveToolGroups } from "./tools.ts";
|
|
9
10
|
import { configFor } from "./pool.ts";
|
|
10
11
|
import { isSessionBusy } from "./tickets.ts";
|
|
11
|
-
import { buildSubagentSystemPrompt } from "./agents.ts";
|
|
12
|
+
import { BUILTIN_AGENT_CONFIGS, buildSubagentSystemPrompt } from "./agents.ts";
|
|
12
13
|
import { buildParentTranscript } from "./parent-context.ts";
|
|
13
14
|
import { findAvailableAlternative, resolveModelRequest } from "./model.ts";
|
|
14
15
|
import { resolveModelSpec } from "./config.ts";
|
|
@@ -91,6 +92,52 @@ export function validateTasks(
|
|
|
91
92
|
agents: Map<string, AgentConfig>,
|
|
92
93
|
parentModelId: string | undefined,
|
|
93
94
|
): DelegateToolResult | null {
|
|
95
|
+
const unknown: string[] = [];
|
|
96
|
+
for (const task of tasks) {
|
|
97
|
+
if (
|
|
98
|
+
task.agent &&
|
|
99
|
+
!(BUILTIN_AGENT_NAMES as readonly string[]).includes(task.agent) &&
|
|
100
|
+
!agents.has(task.agent)
|
|
101
|
+
) {
|
|
102
|
+
unknown.push(task.agent);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (unknown.length) {
|
|
106
|
+
const names = [...new Set([...BUILTIN_AGENT_NAMES, ...agents.keys()])];
|
|
107
|
+
return noticeResult(
|
|
108
|
+
`Unknown agent(s): ${unknown.join(", ")}. Available: ${names.join(", ") || "(none)"}. Call delegate with an empty tasks array for help.`,
|
|
109
|
+
tasks,
|
|
110
|
+
parentModelId,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Scratch sessions are deliberately one-shot. This check uses the
|
|
115
|
+
// effective workspace, so reviewer gets the same protection even when the
|
|
116
|
+
// caller omits workspace. Explicit scratch is never silently promoted to
|
|
117
|
+
// shared.
|
|
118
|
+
for (const [index, task] of tasks.entries()) {
|
|
119
|
+
const agent = task.agent
|
|
120
|
+
? (agents.get(task.agent) ?? BUILTIN_AGENT_CONFIGS[task.agent])
|
|
121
|
+
: undefined;
|
|
122
|
+
const workspace = task.workspace ?? agent?.workspace ?? "shared";
|
|
123
|
+
const sessionAction = task.sessionAction ?? task.action;
|
|
124
|
+
if (
|
|
125
|
+
workspace === "scratch" &&
|
|
126
|
+
(task.sessionId || task.resumeFrom || sessionAction !== undefined)
|
|
127
|
+
) {
|
|
128
|
+
const defaultText =
|
|
129
|
+
task.workspace === undefined && agent?.workspace === "scratch"
|
|
130
|
+
? "defaults to workspace `scratch`"
|
|
131
|
+
: "uses workspace `scratch`";
|
|
132
|
+
const persistentAgent = task.agent ?? "agent";
|
|
133
|
+
return noticeResult(
|
|
134
|
+
`${formatTaskRef(index, task.id)}: Agent \`${persistentAgent}\` ${defaultText}, which is one-shot and cannot use \`sessionId\`, \`resumeFrom\`, or session actions. Set \`workspace: "shared"\` to use a persistent ${persistentAgent}.`,
|
|
135
|
+
tasks,
|
|
136
|
+
parentModelId,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
94
141
|
// Disallow same sessionId across multiple parallel tasks (one agent can't serve two prompts concurrently).
|
|
95
142
|
const sessionIds = tasks.map((t) => t.sessionId).filter(Boolean) as string[];
|
|
96
143
|
const duplicateSessions = sessionIds.filter(
|
|
@@ -136,21 +183,6 @@ export function validateTasks(
|
|
|
136
183
|
return noticeResult(duplicateIds.join(" "), tasks, parentModelId);
|
|
137
184
|
}
|
|
138
185
|
|
|
139
|
-
const unknown: string[] = [];
|
|
140
|
-
for (const t of tasks) {
|
|
141
|
-
if (t.agent && t.agent !== DEFAULT_AGENT_NAME && !agents.has(t.agent)) {
|
|
142
|
-
unknown.push(t.agent);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
if (unknown.length) {
|
|
146
|
-
const names = [DEFAULT_AGENT_NAME, ...agents.keys()];
|
|
147
|
-
return noticeResult(
|
|
148
|
-
`Unknown agent(s): ${unknown.join(", ")}. Available: ${names.join(", ") || "(none)"}. Call delegate with an empty tasks array for help.`,
|
|
149
|
-
tasks,
|
|
150
|
-
parentModelId,
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
186
|
return null;
|
|
155
187
|
}
|
|
156
188
|
|
|
@@ -187,11 +219,21 @@ export function resolveTasks(
|
|
|
187
219
|
|
|
188
220
|
return tasks.map((t, i) => {
|
|
189
221
|
const isDefaultAgent = t.agent === DEFAULT_AGENT_NAME;
|
|
190
|
-
const agent = t.agent
|
|
222
|
+
const agent = t.agent
|
|
223
|
+
? (agents.get(t.agent) ?? BUILTIN_AGENT_CONFIGS[t.agent])
|
|
224
|
+
: undefined;
|
|
225
|
+
const isBuiltinAgent = agent?.builtin === true;
|
|
191
226
|
const cwd = resolveCwd(t.cwd ?? ctx.cwd, ctx.cwd);
|
|
192
227
|
|
|
193
228
|
// Load settings-based overrides for this agent
|
|
194
229
|
const settings = loadDelegateSettings(cwd);
|
|
230
|
+
const parentModelKey = ctx.model
|
|
231
|
+
? `${ctx.model.provider}/${ctx.model.id}`
|
|
232
|
+
: undefined;
|
|
233
|
+
const parentModelOverride =
|
|
234
|
+
t.agent && !isDefaultAgent && parentModelKey
|
|
235
|
+
? settings?.agentOverridesByParentModel?.[parentModelKey]?.[t.agent]
|
|
236
|
+
: undefined;
|
|
195
237
|
const agentOverride =
|
|
196
238
|
t.agent && !isDefaultAgent && settings?.agentOverrides?.[t.agent]
|
|
197
239
|
? settings.agentOverrides[t.agent]
|
|
@@ -208,7 +250,8 @@ export function resolveTasks(
|
|
|
208
250
|
);
|
|
209
251
|
let tools: string[] = [];
|
|
210
252
|
const warnings: string[] = [];
|
|
211
|
-
|
|
253
|
+
const workspace = t.workspace ?? agent?.workspace ?? "shared";
|
|
254
|
+
if (workspace === "scratch") {
|
|
212
255
|
warnings.push(
|
|
213
256
|
"Scratch workspace: relative file changes run in a disposable CoW copy and are discarded.",
|
|
214
257
|
);
|
|
@@ -233,9 +276,11 @@ export function resolveTasks(
|
|
|
233
276
|
if (t.sessionAction !== "close" && t.sessionAction !== "list") {
|
|
234
277
|
tools = resolveToolGroups(
|
|
235
278
|
t.tools ??
|
|
279
|
+
parentModelOverride?.tools ??
|
|
236
280
|
agentOverride?.tools ??
|
|
237
|
-
agent?.tools ??
|
|
238
281
|
(isDefaultAgent ? parentNativeTools : undefined) ??
|
|
282
|
+
(isBuiltinAgent ? agent?.tools : undefined) ??
|
|
283
|
+
agent?.tools ??
|
|
239
284
|
(isPoolHit ? pooledConfig?.tools : undefined) ??
|
|
240
285
|
DEFAULT_TOOLS,
|
|
241
286
|
);
|
|
@@ -318,20 +363,32 @@ export function resolveTasks(
|
|
|
318
363
|
let thinking: ThinkingLevel = "off";
|
|
319
364
|
|
|
320
365
|
if (t.sessionAction !== "close" && t.sessionAction !== "list") {
|
|
366
|
+
const agentType = t.agent ?? "inline";
|
|
367
|
+
// The built-in `default` profile bypasses delegate/settings model
|
|
368
|
+
// overrides for backwards compatibility. The other built-ins accept
|
|
369
|
+
// task and settings.json model overrides, but deliberately ignore the
|
|
370
|
+
// legacy delegate.json agent model map so they inherit the parent unless
|
|
371
|
+
// an explicit modern override wins.
|
|
372
|
+
const modelSpec = isDefaultAgent
|
|
373
|
+
? t.model
|
|
374
|
+
: isBuiltinAgent
|
|
375
|
+
? (t.model ?? parentModelOverride?.model ?? agentOverride?.model)
|
|
376
|
+
: resolveModelSpec({
|
|
377
|
+
taskModel:
|
|
378
|
+
t.model ?? parentModelOverride?.model ?? agentOverride?.model,
|
|
379
|
+
agentType,
|
|
380
|
+
frontmatterModel: agent?.model,
|
|
381
|
+
});
|
|
382
|
+
|
|
321
383
|
// A pool hit always runs its frozen model, but an explicitly requested
|
|
322
384
|
// task/profile model still has to be resolved so checkout can reject a
|
|
323
385
|
// contradictory request rather than silently discarding it. Naming the
|
|
324
386
|
// built-in `default` profile is also explicit: it requests the live
|
|
325
387
|
// parent model, so reuse fails clearly if the pool was frozen differently.
|
|
326
388
|
if (pooledConfig) {
|
|
327
|
-
|
|
328
|
-
t.model ??
|
|
329
|
-
(t.agent && !isDefaultAgent
|
|
330
|
-
? (agentOverride?.model ?? agent?.model)
|
|
331
|
-
: undefined);
|
|
332
|
-
if (requestedModelSpec) {
|
|
389
|
+
if (modelSpec) {
|
|
333
390
|
const requested = resolveModelRequest(
|
|
334
|
-
|
|
391
|
+
modelSpec,
|
|
335
392
|
ctx.modelRegistry,
|
|
336
393
|
ctx.model,
|
|
337
394
|
);
|
|
@@ -339,7 +396,7 @@ export function resolveTasks(
|
|
|
339
396
|
modelSuffix = requested.strippedSuffix;
|
|
340
397
|
if (!requestedModel) {
|
|
341
398
|
throw new Error(
|
|
342
|
-
`${formatTaskRef(i, t.id)}: requested model '${
|
|
399
|
+
`${formatTaskRef(i, t.id)}: requested model '${modelSpec}' is not available. Check provider config or remove the model field to continue the pooled session.`,
|
|
343
400
|
);
|
|
344
401
|
}
|
|
345
402
|
} else if (isDefaultAgent) {
|
|
@@ -347,17 +404,6 @@ export function resolveTasks(
|
|
|
347
404
|
}
|
|
348
405
|
model = pooledConfig.model;
|
|
349
406
|
} else {
|
|
350
|
-
// The built-in `default` profile bypasses delegate.json and settings:
|
|
351
|
-
// absent a task override, it means this exact live parent Model object.
|
|
352
|
-
// Other tasks retain the normal task > config > frontmatter chain.
|
|
353
|
-
const agentType = t.agent ?? "inline";
|
|
354
|
-
const modelSpec = isDefaultAgent
|
|
355
|
-
? t.model
|
|
356
|
-
: resolveModelSpec({
|
|
357
|
-
taskModel: t.model ?? agentOverride?.model,
|
|
358
|
-
agentType,
|
|
359
|
-
frontmatterModel: agent?.model,
|
|
360
|
-
});
|
|
361
407
|
const resolvedRequest = modelSpec
|
|
362
408
|
? resolveModelRequest(modelSpec, ctx.modelRegistry, ctx.model)
|
|
363
409
|
: undefined;
|
|
@@ -376,7 +422,7 @@ export function resolveTasks(
|
|
|
376
422
|
);
|
|
377
423
|
}
|
|
378
424
|
|
|
379
|
-
model =
|
|
425
|
+
model = isBuiltinAgent
|
|
380
426
|
? (resolvedModel ?? ctx.model)
|
|
381
427
|
: (resolvedModel ??
|
|
382
428
|
findAvailableAlternative(ctx.model, ctx.modelRegistry) ??
|
|
@@ -398,15 +444,28 @@ export function resolveTasks(
|
|
|
398
444
|
// changed, rather than silently reusing a stale frozen value. The final
|
|
399
445
|
// pooled fallback is reachable only when parentDefaults.thinking is
|
|
400
446
|
// undefined (headless parent without a thinking level).
|
|
401
|
-
const thinkingRaw =
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
447
|
+
const thinkingRaw = isBuiltinAgent
|
|
448
|
+
? isDefaultAgent
|
|
449
|
+
? (t.thinking ??
|
|
450
|
+
parentModelOverride?.thinking ??
|
|
451
|
+
agentOverride?.thinking ??
|
|
452
|
+
modelSuffix ??
|
|
453
|
+
parentDefaults.thinking ??
|
|
454
|
+
(isPoolHit ? pooledConfig?.thinking : undefined) ??
|
|
455
|
+
"off")
|
|
456
|
+
: (t.thinking ??
|
|
457
|
+
parentModelOverride?.thinking ??
|
|
458
|
+
agentOverride?.thinking ??
|
|
459
|
+
(isPoolHit ? pooledConfig?.thinking : undefined) ??
|
|
460
|
+
modelSuffix ??
|
|
461
|
+
parentDefaults.thinking ??
|
|
462
|
+
"off")
|
|
463
|
+
: (t.thinking ??
|
|
464
|
+
agentOverride?.thinking ??
|
|
465
|
+
agent?.thinking ??
|
|
466
|
+
(isPoolHit ? pooledConfig?.thinking : undefined) ??
|
|
467
|
+
modelSuffix ??
|
|
468
|
+
"off");
|
|
410
469
|
thinking = VALID_THINKING.has(thinkingRaw)
|
|
411
470
|
? (thinkingRaw as ThinkingLevel)
|
|
412
471
|
: "off";
|
|
@@ -423,7 +482,7 @@ export function resolveTasks(
|
|
|
423
482
|
...t,
|
|
424
483
|
id: t.id,
|
|
425
484
|
cwd,
|
|
426
|
-
workspace
|
|
485
|
+
workspace,
|
|
427
486
|
systemPrompt,
|
|
428
487
|
model: model!,
|
|
429
488
|
tools,
|
|
@@ -433,9 +492,7 @@ export function resolveTasks(
|
|
|
433
492
|
prompt: prompt ?? "",
|
|
434
493
|
// Keep the built-in selector visible in progress/results. Omitted-agent
|
|
435
494
|
// inline tasks retain the established `ad-hoc` label and config namespace.
|
|
436
|
-
agentName:
|
|
437
|
-
? DEFAULT_AGENT_NAME
|
|
438
|
-
: (agent?.name ?? "ad-hoc"),
|
|
495
|
+
agentName: agent?.name ?? "ad-hoc",
|
|
439
496
|
warnings,
|
|
440
497
|
reuseIntent: {
|
|
441
498
|
model: requestedModel,
|
package/types.ts
CHANGED
|
@@ -18,9 +18,15 @@ export interface AgentConfig {
|
|
|
18
18
|
name: string;
|
|
19
19
|
description: string;
|
|
20
20
|
model?: string;
|
|
21
|
-
|
|
21
|
+
/** Markdown agents default invalid/omitted values to "off". Built-ins omit
|
|
22
|
+
* this field so they can inherit the parent's thinking level. */
|
|
23
|
+
thinking?: ThinkingLevel;
|
|
22
24
|
tools: string[];
|
|
23
25
|
systemPrompt: string;
|
|
26
|
+
/** Built-in profiles are immutable and cannot be shadowed by Markdown. */
|
|
27
|
+
builtin?: boolean;
|
|
28
|
+
/** Default workspace for a built-in profile. Custom agents use shared. */
|
|
29
|
+
workspace?: WorkspaceMode;
|
|
24
30
|
/** Origin of the profile. `claude` denotes imported .claude/agents files. */
|
|
25
31
|
scope?: "project" | "global" | "claude";
|
|
26
32
|
}
|