@bermudi/pi-delegate 0.1.0 → 0.1.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 +55 -10
- package/agents.ts +176 -19
- package/concurrency.ts +70 -7
- package/constants.ts +3 -0
- package/delegate.ts +26 -1
- package/dispatch.ts +65 -9
- package/extension.ts +86 -6
- package/file-tracking.ts +27 -5
- package/format.ts +46 -7
- package/host-compat.ts +47 -12
- package/host.ts +93 -16
- package/leaf.ts +48 -0
- package/lifecycle.ts +292 -95
- package/manual.ts +45 -11
- package/model.ts +3 -4
- package/package.json +25 -20
- package/patches/@marcfargas%2Fpi-test-harness@0.6.1.patch +13 -0
- package/pool.ts +169 -51
- package/render-branches.ts +52 -16
- package/render-result.ts +12 -0
- package/runner.ts +255 -51
- package/schema.ts +252 -63
- package/status.ts +269 -0
- package/task-resolution.ts +196 -77
- package/tickets.ts +173 -62
- package/tools.ts +16 -15
- package/types.ts +62 -13
package/task-resolution.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_AGENT_NAME,
|
|
5
|
+
DEFAULT_TOOLS,
|
|
6
|
+
VALID_THINKING,
|
|
7
|
+
} from "./constants.ts";
|
|
4
8
|
import { TOOL_FACTORIES, resolveToolGroups } from "./tools.ts";
|
|
5
9
|
import { configFor } from "./pool.ts";
|
|
6
10
|
import { isSessionBusy } from "./tickets.ts";
|
|
@@ -14,10 +18,54 @@ import type {
|
|
|
14
18
|
AgentConfig,
|
|
15
19
|
DelegateToolCtx,
|
|
16
20
|
DelegateToolResult,
|
|
21
|
+
ParentAgentDefaults,
|
|
17
22
|
ResolvedTask,
|
|
18
23
|
TaskDef,
|
|
19
24
|
} from "./types.ts";
|
|
20
25
|
|
|
26
|
+
const PROJECT_CONTEXT_START =
|
|
27
|
+
"\n\n<project_context>\n\nProject-specific instructions and guidelines:\n\n";
|
|
28
|
+
const PROJECT_CONTEXT_END = "\n</project_context>\n";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parent `getSystemPrompt()` is the fully assembled prompt, including the
|
|
32
|
+
* parent's AGENTS.md files. A delegated session resolves resources for its own
|
|
33
|
+
* cwd, so carrying that section across would leak global instructions and
|
|
34
|
+
* duplicate project context. Preserve the parent's base prompt and everything
|
|
35
|
+
* outside Pi's structured context section; the child ResourceLoader appends its
|
|
36
|
+
* own (filtered) context afterward.
|
|
37
|
+
*
|
|
38
|
+
* The AGENTS.md/CLAUDE.md content is inserted verbatim inside
|
|
39
|
+
* `<project_instructions>...</project_instructions>` blocks. A file that
|
|
40
|
+
* itself contains `\n</project_context>\n` can therefore forge an early
|
|
41
|
+
* closing marker. It could also include a forged `</project_instructions>` to
|
|
42
|
+
* balance the open tag, making the fake `</project_context>` look like the
|
|
43
|
+
* section end. A content-controlled marker can only ever appear *inside* the
|
|
44
|
+
* generated `<project_context>` block, before the real `</project_context>`
|
|
45
|
+
* that Pi appends after the last file. Skills escape angle brackets, the
|
|
46
|
+
* `appendSystemPrompt` text is added before this section, and the trailing
|
|
47
|
+
* `Current working directory:` line contains no such marker, so the real
|
|
48
|
+
* closing marker is the final raw `\n</project_context>\n` in the prompt.
|
|
49
|
+
* We therefore match the Pi-generated opening marker to the final matching
|
|
50
|
+
* closing marker, deterministically stripping the whole generated section.
|
|
51
|
+
*/
|
|
52
|
+
export function stripInheritedProjectContext(
|
|
53
|
+
prompt: string | undefined,
|
|
54
|
+
): string | undefined {
|
|
55
|
+
if (!prompt) return prompt;
|
|
56
|
+
|
|
57
|
+
const start = prompt.indexOf(PROJECT_CONTEXT_START);
|
|
58
|
+
if (start < 0) return prompt;
|
|
59
|
+
|
|
60
|
+
// The real closing marker is the final occurrence: child content cannot
|
|
61
|
+
// place a `</project_context>` after the one Pi generates to close the
|
|
62
|
+
// section.
|
|
63
|
+
const end = prompt.lastIndexOf(PROJECT_CONTEXT_END);
|
|
64
|
+
if (end < start) return prompt;
|
|
65
|
+
|
|
66
|
+
return `${prompt.slice(0, start)}${prompt.slice(end + PROJECT_CONTEXT_END.length)}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
21
69
|
/** Build a tool result for an error/notice with no task progress. */
|
|
22
70
|
function noticeResult(
|
|
23
71
|
text: string,
|
|
@@ -30,6 +78,11 @@ function noticeResult(
|
|
|
30
78
|
};
|
|
31
79
|
}
|
|
32
80
|
|
|
81
|
+
/** Format a task reference for error messages: one-based index with an optional caller id. */
|
|
82
|
+
function formatTaskRef(index: number, id: string | undefined): string {
|
|
83
|
+
return `Task ${index + 1}${id ? `#${id}` : ""}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
33
86
|
/** Pre-dispatch validation: duplicate sessionIds, sessions busy with an async
|
|
34
87
|
* ticket, and unknown agent names. Returns an error result to short-circuit
|
|
35
88
|
* the call, or null when all checks pass. */
|
|
@@ -65,12 +118,32 @@ export function validateTasks(
|
|
|
65
118
|
);
|
|
66
119
|
}
|
|
67
120
|
|
|
121
|
+
// Disallow duplicate caller-provided task ids within one dispatch.
|
|
122
|
+
const seenIds = new Map<string, number>();
|
|
123
|
+
const duplicateIds: string[] = [];
|
|
124
|
+
for (const [index, task] of tasks.entries()) {
|
|
125
|
+
if (task.id) {
|
|
126
|
+
if (seenIds.has(task.id)) {
|
|
127
|
+
duplicateIds.push(
|
|
128
|
+
`task ${index + 1}: duplicate id '${task.id}' — ids must be unique within one dispatch.`,
|
|
129
|
+
);
|
|
130
|
+
} else {
|
|
131
|
+
seenIds.set(task.id, index);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (duplicateIds.length) {
|
|
136
|
+
return noticeResult(duplicateIds.join(" "), tasks, parentModelId);
|
|
137
|
+
}
|
|
138
|
+
|
|
68
139
|
const unknown: string[] = [];
|
|
69
140
|
for (const t of tasks) {
|
|
70
|
-
if (t.agent && !agents.has(t.agent))
|
|
141
|
+
if (t.agent && t.agent !== DEFAULT_AGENT_NAME && !agents.has(t.agent)) {
|
|
142
|
+
unknown.push(t.agent);
|
|
143
|
+
}
|
|
71
144
|
}
|
|
72
145
|
if (unknown.length) {
|
|
73
|
-
const names = [...agents.keys()];
|
|
146
|
+
const names = [DEFAULT_AGENT_NAME, ...agents.keys()];
|
|
74
147
|
return noticeResult(
|
|
75
148
|
`Unknown agent(s): ${unknown.join(", ")}. Available: ${names.join(", ") || "(none)"}. Call delegate with an empty tasks array for help.`,
|
|
76
149
|
tasks,
|
|
@@ -89,6 +162,7 @@ export function resolveTasks(
|
|
|
89
162
|
tasks: TaskDef[],
|
|
90
163
|
ctx: DelegateToolCtx,
|
|
91
164
|
agents: Map<string, AgentConfig>,
|
|
165
|
+
parentDefaults: ParentAgentDefaults,
|
|
92
166
|
): ResolvedTask[] {
|
|
93
167
|
// Build parent transcript lazily — only computed once if any task uses with-parent-transcript
|
|
94
168
|
let parentTranscript: string | null = null;
|
|
@@ -107,54 +181,104 @@ export function resolveTasks(
|
|
|
107
181
|
);
|
|
108
182
|
}
|
|
109
183
|
|
|
110
|
-
const parentSystemPrompt =
|
|
184
|
+
const parentSystemPrompt = stripInheritedProjectContext(
|
|
185
|
+
ctx.getSystemPrompt?.(),
|
|
186
|
+
);
|
|
111
187
|
|
|
112
188
|
return tasks.map((t, i) => {
|
|
113
|
-
const
|
|
189
|
+
const isDefaultAgent = t.agent === DEFAULT_AGENT_NAME;
|
|
190
|
+
const agent = t.agent && !isDefaultAgent ? agents.get(t.agent) : undefined;
|
|
114
191
|
const cwd = resolveCwd(t.cwd ?? ctx.cwd, ctx.cwd);
|
|
115
192
|
|
|
116
193
|
// Load settings-based overrides for this agent
|
|
117
194
|
const settings = loadDelegateSettings(cwd);
|
|
118
195
|
const agentOverride =
|
|
119
|
-
t.agent && settings?.agentOverrides?.[t.agent]
|
|
196
|
+
t.agent && !isDefaultAgent && settings?.agentOverrides?.[t.agent]
|
|
120
197
|
? settings.agentOverrides[t.agent]
|
|
121
198
|
: undefined;
|
|
122
199
|
|
|
123
200
|
// Build system prompt. Explicit task prompts and named agent prompts
|
|
124
|
-
// win; ad-hoc subagents inherit the parent prompt when Pi exposes
|
|
125
|
-
//
|
|
201
|
+
// win; ad-hoc subagents inherit the parent's base prompt when Pi exposes
|
|
202
|
+
// it. The assembled parent project-context section was stripped above;
|
|
203
|
+
// the child ResourceLoader supplies context for this task's cwd.
|
|
126
204
|
const pooledConfig = t.sessionId ? configFor(t.sessionId) : undefined;
|
|
205
|
+
const isPoolHit = pooledConfig !== undefined;
|
|
206
|
+
const parentNativeTools = parentDefaults.tools.filter((name) =>
|
|
207
|
+
Object.hasOwn(TOOL_FACTORIES, name),
|
|
208
|
+
);
|
|
209
|
+
let tools: string[] = [];
|
|
210
|
+
const warnings: string[] = [];
|
|
127
211
|
|
|
128
212
|
// Prompt is required for fresh tasks. ResumeFrom provides context already.
|
|
129
213
|
if (
|
|
130
|
-
t.
|
|
131
|
-
t.
|
|
214
|
+
t.sessionAction !== "close" &&
|
|
215
|
+
t.sessionAction !== "list" &&
|
|
132
216
|
!t.resumeFrom &&
|
|
133
217
|
!t.prompt?.trim()
|
|
134
218
|
) {
|
|
135
219
|
throw new Error(
|
|
136
|
-
|
|
220
|
+
`${formatTaskRef(i, t.id)}: prompt is required unless sessionAction is 'close'/'list' or resumeFrom is set.`,
|
|
137
221
|
);
|
|
138
222
|
}
|
|
139
223
|
|
|
224
|
+
// Resolve tools — warn about unknown tool names.
|
|
225
|
+
// For active pooled sessions, fall back to the frozen pooled config so
|
|
226
|
+
// "continue with only sessionId" works without re-supplying tools.
|
|
227
|
+
// Explicit overrides that don't match get rejected by acquireAgentSession.
|
|
228
|
+
if (t.sessionAction !== "close" && t.sessionAction !== "list") {
|
|
229
|
+
tools = resolveToolGroups(
|
|
230
|
+
t.tools ??
|
|
231
|
+
agentOverride?.tools ??
|
|
232
|
+
agent?.tools ??
|
|
233
|
+
(isDefaultAgent ? parentNativeTools : undefined) ??
|
|
234
|
+
(isPoolHit ? pooledConfig?.tools : undefined) ??
|
|
235
|
+
DEFAULT_TOOLS,
|
|
236
|
+
);
|
|
237
|
+
const unknownTools = tools.filter(
|
|
238
|
+
(name) => !Object.hasOwn(TOOL_FACTORIES, name),
|
|
239
|
+
);
|
|
240
|
+
if (unknownTools.length) {
|
|
241
|
+
warnings.push(
|
|
242
|
+
`Unknown tool(s) ignored: ${unknownTools.join(", ")}. Available: ${Object.keys(TOOL_FACTORIES).join(", ")}`,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
tools = tools.filter((name) => Object.hasOwn(TOOL_FACTORIES, name));
|
|
246
|
+
}
|
|
247
|
+
|
|
140
248
|
// System prompt resolution. AgentSession's resource loader owns
|
|
141
|
-
// skills + AGENTS.md discovery (it appends them via
|
|
142
|
-
// so we resolve only the *base* prompt here:
|
|
143
|
-
//
|
|
144
|
-
// passed as the loader's customPrompt (see
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
249
|
+
// skills + project AGENTS.md discovery (it appends them via
|
|
250
|
+
// _rebuildSystemPrompt), so we resolve only the *base* prompt here:
|
|
251
|
+
// explicit task prompt → named agent body → sanitized parent prompt →
|
|
252
|
+
// default. The resolved base is passed as the loader's customPrompt (see
|
|
253
|
+
// buildDelegateSession).
|
|
254
|
+
//
|
|
255
|
+
// The *requested* system prompt is what the reuse check compares against the
|
|
256
|
+
// frozen pooled value. A bare `{ prompt, sessionId }` omits it so the pool
|
|
257
|
+
// can keep using the frozen prompt even if the parent prompt changed. An
|
|
258
|
+
// explicit task/profile prompt is used as-is. The built-in `default` profile
|
|
259
|
+
// intentionally mirrors the live parent, so its requested prompt is the
|
|
260
|
+
// *sanitized* parent prompt — the same form that would be stored as the
|
|
261
|
+
// frozen base, avoiding a false mismatch when the inherited default prompt
|
|
262
|
+
// gets its stale tool inventory stripped.
|
|
263
|
+
const resolvedBasePrompt = buildSubagentSystemPrompt({
|
|
264
|
+
taskSystemPrompt: t.systemPrompt,
|
|
265
|
+
agentSystemPrompt: agent?.systemPrompt,
|
|
266
|
+
parentSystemPrompt,
|
|
267
|
+
tools,
|
|
268
|
+
});
|
|
148
269
|
const requestedSystemPrompt = t.systemPrompt?.trim()
|
|
149
270
|
? t.systemPrompt
|
|
150
271
|
: agent?.systemPrompt?.trim()
|
|
151
272
|
? agent.systemPrompt
|
|
152
|
-
:
|
|
273
|
+
: isDefaultAgent
|
|
274
|
+
? resolvedBasePrompt
|
|
275
|
+
: undefined;
|
|
153
276
|
const systemPrompt = buildSubagentSystemPrompt({
|
|
154
277
|
taskSystemPrompt: t.systemPrompt,
|
|
155
278
|
agentSystemPrompt: agent?.systemPrompt,
|
|
156
279
|
parentSystemPrompt,
|
|
157
280
|
pooledSystemPrompt: pooledConfig?.systemPrompt,
|
|
281
|
+
tools,
|
|
158
282
|
});
|
|
159
283
|
|
|
160
284
|
// Build prompt — wrap with parent context if using with-parent-transcript
|
|
@@ -186,46 +310,49 @@ export function resolveTasks(
|
|
|
186
310
|
let model: Model<Api> | undefined;
|
|
187
311
|
let requestedModel: Model<Api> | undefined;
|
|
188
312
|
let modelSuffix: ThinkingLevel | undefined;
|
|
189
|
-
let tools: string[] = [];
|
|
190
313
|
let thinking: ThinkingLevel = "off";
|
|
191
|
-
const warnings: string[] = [];
|
|
192
314
|
|
|
193
|
-
if (t.
|
|
315
|
+
if (t.sessionAction !== "close" && t.sessionAction !== "list") {
|
|
194
316
|
// A pool hit always runs its frozen model, but an explicitly requested
|
|
195
317
|
// task/profile model still has to be resolved so checkout can reject a
|
|
196
|
-
// contradictory request rather than silently discarding it.
|
|
318
|
+
// contradictory request rather than silently discarding it. Naming the
|
|
319
|
+
// built-in `default` profile is also explicit: it requests the live
|
|
320
|
+
// parent model, so reuse fails clearly if the pool was frozen differently.
|
|
197
321
|
if (pooledConfig) {
|
|
198
322
|
const requestedModelSpec =
|
|
199
323
|
t.model ??
|
|
200
|
-
(t.agent
|
|
324
|
+
(t.agent && !isDefaultAgent
|
|
325
|
+
? (agentOverride?.model ?? agent?.model)
|
|
326
|
+
: undefined);
|
|
201
327
|
if (requestedModelSpec) {
|
|
202
|
-
|
|
328
|
+
const requested = resolveModelRequest(
|
|
203
329
|
requestedModelSpec,
|
|
204
330
|
ctx.modelRegistry,
|
|
205
331
|
ctx.model,
|
|
206
|
-
)
|
|
332
|
+
);
|
|
333
|
+
requestedModel = requested.model;
|
|
334
|
+
modelSuffix = requested.strippedSuffix;
|
|
207
335
|
if (!requestedModel) {
|
|
208
336
|
throw new Error(
|
|
209
|
-
|
|
337
|
+
`${formatTaskRef(i, t.id)}: requested model '${requestedModelSpec}' is not available. Check provider config or remove the model field to continue the pooled session.`,
|
|
210
338
|
);
|
|
211
339
|
}
|
|
340
|
+
} else if (isDefaultAgent) {
|
|
341
|
+
requestedModel = ctx.model;
|
|
212
342
|
}
|
|
213
343
|
model = pooledConfig.model;
|
|
214
344
|
} else {
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
// (e.g. OpenRouter's "deepseek/deepseek-v4-flash") would split on "/"
|
|
219
|
-
// and misroute to the upstream provider. Leaving resolvedModel
|
|
220
|
-
// undefined also lets findAvailableAlternative run below: it returns
|
|
221
|
-
// ctx.model as-is when it has auth, or swaps to an authenticated
|
|
222
|
-
// same-id alternative when the parent's provider lost auth.
|
|
345
|
+
// The built-in `default` profile bypasses delegate.json and settings:
|
|
346
|
+
// absent a task override, it means this exact live parent Model object.
|
|
347
|
+
// Other tasks retain the normal task > config > frontmatter chain.
|
|
223
348
|
const agentType = t.agent ?? "inline";
|
|
224
|
-
const modelSpec =
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
349
|
+
const modelSpec = isDefaultAgent
|
|
350
|
+
? t.model
|
|
351
|
+
: resolveModelSpec({
|
|
352
|
+
taskModel: t.model ?? agentOverride?.model,
|
|
353
|
+
agentType,
|
|
354
|
+
frontmatterModel: agent?.model,
|
|
355
|
+
});
|
|
229
356
|
const resolvedRequest = modelSpec
|
|
230
357
|
? resolveModelRequest(modelSpec, ctx.modelRegistry, ctx.model)
|
|
231
358
|
: undefined;
|
|
@@ -234,56 +361,46 @@ export function resolveTasks(
|
|
|
234
361
|
// it is honored only as a last-resort thinking default (see below).
|
|
235
362
|
modelSuffix = resolvedRequest?.strippedSuffix;
|
|
236
363
|
|
|
237
|
-
//
|
|
238
|
-
|
|
364
|
+
// The selected model spec is explicit regardless of whether it came
|
|
365
|
+
// from the task, settings, or named-agent frontmatter. If it cannot
|
|
366
|
+
// resolve, fail loudly instead of silently falling back to the parent.
|
|
367
|
+
const explicitRequest = modelSpec;
|
|
239
368
|
if (explicitRequest && !resolvedModel) {
|
|
240
369
|
throw new Error(
|
|
241
|
-
|
|
370
|
+
`${formatTaskRef(i, t.id)}: requested model '${explicitRequest}' is not available. Check provider config or remove the model field to use the parent model.`,
|
|
242
371
|
);
|
|
243
372
|
}
|
|
244
373
|
|
|
245
|
-
model =
|
|
246
|
-
resolvedModel ??
|
|
247
|
-
|
|
248
|
-
|
|
374
|
+
model = isDefaultAgent
|
|
375
|
+
? (resolvedModel ?? ctx.model)
|
|
376
|
+
: (resolvedModel ??
|
|
377
|
+
findAvailableAlternative(ctx.model, ctx.modelRegistry) ??
|
|
378
|
+
ctx.model);
|
|
249
379
|
}
|
|
250
380
|
|
|
251
381
|
if (!model) {
|
|
252
382
|
throw new Error(
|
|
253
|
-
|
|
383
|
+
`${formatTaskRef(i, t.id)}: no model available — parent session has no model set.`,
|
|
254
384
|
);
|
|
255
385
|
}
|
|
256
386
|
|
|
257
|
-
// Resolve
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
//
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
(isPoolHit ? pooledConfig?.tools : undefined) ??
|
|
267
|
-
DEFAULT_TOOLS,
|
|
268
|
-
);
|
|
269
|
-
const unknownTools = tools.filter((name) => !(name in TOOL_FACTORIES));
|
|
270
|
-
if (unknownTools.length) {
|
|
271
|
-
warnings.push(
|
|
272
|
-
`Unknown tool(s) ignored: ${unknownTools.join(", ")}. Available: ${Object.keys(TOOL_FACTORIES).join(", ")}`,
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Resolve thinking. Precedence: task field → agent override → agent
|
|
277
|
-
// frontmatter → frozen pooled config → a Pi-style `:level` model suffix
|
|
278
|
-
// (honored only as a last-resort default, so a model-emitted `claude:max`
|
|
279
|
-
// runs at max when nothing else sets thinking). The suffix is lowest on
|
|
280
|
-
// purpose: an agent author's `thinking: low` must beat `model: x:max`.
|
|
387
|
+
// Resolve thinking. Precedence for most agents: explicit `thinking` >
|
|
388
|
+
// agent override > frontmatter > frozen pooled config > model `:level`
|
|
389
|
+
// suffix (last resort). The built-in `default` agent intentionally
|
|
390
|
+
// inverts the last two steps: model suffix beats the parent's live
|
|
391
|
+
// thinking, which beats the frozen pooled value. This surfaces a clear
|
|
392
|
+
// `config mismatch` error on reuse when the parent thinking level has
|
|
393
|
+
// changed, rather than silently reusing a stale frozen value. The final
|
|
394
|
+
// pooled fallback is reachable only when parentDefaults.thinking is
|
|
395
|
+
// undefined (headless parent without a thinking level).
|
|
281
396
|
const thinkingRaw =
|
|
282
397
|
t.thinking ??
|
|
283
398
|
agentOverride?.thinking ??
|
|
284
399
|
agent?.thinking ??
|
|
285
|
-
(isPoolHit ? pooledConfig?.thinking : undefined) ??
|
|
400
|
+
(isPoolHit && !isDefaultAgent ? pooledConfig?.thinking : undefined) ??
|
|
286
401
|
modelSuffix ??
|
|
402
|
+
(isDefaultAgent ? parentDefaults.thinking : undefined) ??
|
|
403
|
+
(isPoolHit ? pooledConfig?.thinking : undefined) ??
|
|
287
404
|
"off";
|
|
288
405
|
thinking = VALID_THINKING.has(thinkingRaw)
|
|
289
406
|
? (thinkingRaw as ThinkingLevel)
|
|
@@ -299,6 +416,7 @@ export function resolveTasks(
|
|
|
299
416
|
}
|
|
300
417
|
return {
|
|
301
418
|
...t,
|
|
419
|
+
id: t.id,
|
|
302
420
|
cwd,
|
|
303
421
|
systemPrompt,
|
|
304
422
|
model: model!,
|
|
@@ -307,10 +425,11 @@ export function resolveTasks(
|
|
|
307
425
|
// Empty only for close/list actions (validated above) — downstream
|
|
308
426
|
// display code treats "" and absent alike (`t.prompt || …`).
|
|
309
427
|
prompt: prompt ?? "",
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
428
|
+
// Keep the built-in selector visible in progress/results. Omitted-agent
|
|
429
|
+
// inline tasks retain the established `ad-hoc` label and config namespace.
|
|
430
|
+
agentName: isDefaultAgent
|
|
431
|
+
? DEFAULT_AGENT_NAME
|
|
432
|
+
: (agent?.name ?? "ad-hoc"),
|
|
314
433
|
warnings,
|
|
315
434
|
reuseIntent: {
|
|
316
435
|
model: requestedModel,
|