@yagni-app/code-staging 1.2.13-staging.2167.1 → 1.2.13-staging.2173.1
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/dist/extension/pipeline/planOutput.d.ts +15 -0
- package/dist/extension/pipeline/planOutput.js +25 -0
- package/dist/extension/pipeline/sessionLane.d.ts +6 -0
- package/dist/extension/pipeline/sessionLane.js +8 -0
- package/dist/extension/pipeline/stages.d.ts +8 -0
- package/dist/extension/pipeline/stages.js +21 -3
- package/dist/extension/subagents.d.ts +11 -2
- package/dist/extension/subagents.js +64 -36
- package/package.json +2 -2
|
@@ -46,6 +46,15 @@ export interface PlanFile {
|
|
|
46
46
|
repository: string;
|
|
47
47
|
what: string;
|
|
48
48
|
}>;
|
|
49
|
+
/**
|
|
50
|
+
* What the plan found that the ticket did not ask for (ENG-2966): kept off
|
|
51
|
+
* the plan, shown at its gate for a person to make a ticket of.
|
|
52
|
+
*/
|
|
53
|
+
followUps: Array<{
|
|
54
|
+
title: string;
|
|
55
|
+
why: string;
|
|
56
|
+
repository?: string;
|
|
57
|
+
}>;
|
|
49
58
|
}
|
|
50
59
|
/** The plan's `wrongRepository`, or null when it names no `owner/name`. Confidence is high only when said. */
|
|
51
60
|
export declare function readWrongRepository(value: unknown): PlanFile["wrongRepository"] | null;
|
|
@@ -59,6 +68,12 @@ export declare function repositoryName(value: unknown): string | null;
|
|
|
59
68
|
* for the same repository (case-insensitive) is dropped too.
|
|
60
69
|
*/
|
|
61
70
|
export declare function readRepositoriesNeeded(value: unknown): PlanFile["repositoriesNeeded"];
|
|
71
|
+
/**
|
|
72
|
+
* The plan's `followUps`, one per distinct title in the order written. An
|
|
73
|
+
* entry with no title is dropped; a repository that is not `owner/name` is
|
|
74
|
+
* left off its entry.
|
|
75
|
+
*/
|
|
76
|
+
export declare function readFollowUps(value: unknown): PlanFile["followUps"];
|
|
62
77
|
/** The plan's ```plan-json block as an object, or null when it has none that parses. */
|
|
63
78
|
export declare function extractPlanJson(text: string): Record<string, unknown> | null;
|
|
64
79
|
/** The plan for a person to read: the text without its machine-readable block. */
|
|
@@ -77,6 +77,30 @@ export function readRepositoriesNeeded(value) {
|
|
|
77
77
|
}
|
|
78
78
|
return out;
|
|
79
79
|
}
|
|
80
|
+
/** At most this many follow-ups; the host keeps the same number. */
|
|
81
|
+
const MAX_FOLLOW_UPS = 10;
|
|
82
|
+
/**
|
|
83
|
+
* The plan's `followUps`, one per distinct title in the order written. An
|
|
84
|
+
* entry with no title is dropped; a repository that is not `owner/name` is
|
|
85
|
+
* left off its entry.
|
|
86
|
+
*/
|
|
87
|
+
export function readFollowUps(value) {
|
|
88
|
+
const out = [];
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
for (const entry of list(value)) {
|
|
91
|
+
if (!isRecord(entry))
|
|
92
|
+
continue;
|
|
93
|
+
const title = text(entry.title, 200);
|
|
94
|
+
if (!title || seen.has(title.toLowerCase()))
|
|
95
|
+
continue;
|
|
96
|
+
seen.add(title.toLowerCase());
|
|
97
|
+
const repository = repositoryName(entry.repository);
|
|
98
|
+
out.push({ title, why: text(entry.why) ?? "", ...(repository ? { repository } : {}) });
|
|
99
|
+
if (out.length === MAX_FOLLOW_UPS)
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
80
104
|
/** The plan's ```plan-json block as an object, or null when it has none that parses. */
|
|
81
105
|
export function extractPlanJson(text) {
|
|
82
106
|
let found = null;
|
|
@@ -162,6 +186,7 @@ export function buildPlanFile(planText, fallbackPaths) {
|
|
|
162
186
|
repositoriesNeeded,
|
|
163
187
|
...(wrongRepository ? { wrongRepository } : {}),
|
|
164
188
|
dependsOn: readDependsOn(block?.dependsOn),
|
|
189
|
+
followUps: readFollowUps(block?.followUps),
|
|
165
190
|
},
|
|
166
191
|
found: block !== null,
|
|
167
192
|
};
|
|
@@ -48,6 +48,12 @@ import type { WorktreeExec } from "./worktree.js";
|
|
|
48
48
|
export declare const SESSION_TIER: ModelTier;
|
|
49
49
|
/** The caller the model proxy attributes the session's own turns to. */
|
|
50
50
|
export declare const SESSION_CALLER_LABEL = "go:session";
|
|
51
|
+
/**
|
|
52
|
+
* Whether this process is a session-lane turn: the pi child {@link runSessionTurn}
|
|
53
|
+
* starts, which the runner gives the session's caller label (`YAGNI_CALLER`).
|
|
54
|
+
* A subagent it spawns gets its own label, so only the turn itself answers yes.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isSessionLaneTurn(env?: NodeJS.ProcessEnv): boolean;
|
|
51
57
|
/** The model's own account of a turn that builds, beside `result.json` in the output folder. */
|
|
52
58
|
export declare const SESSION_REPORT_FILE_NAME = "session-report.json";
|
|
53
59
|
/** What a plan turn may use: everything that reads or runs, nothing that edits. */
|
|
@@ -56,6 +56,14 @@ import { snapshotWorkspace } from "./workspace.js";
|
|
|
56
56
|
export const SESSION_TIER = "peak";
|
|
57
57
|
/** The caller the model proxy attributes the session's own turns to. */
|
|
58
58
|
export const SESSION_CALLER_LABEL = "go:session";
|
|
59
|
+
/**
|
|
60
|
+
* Whether this process is a session-lane turn: the pi child {@link runSessionTurn}
|
|
61
|
+
* starts, which the runner gives the session's caller label (`YAGNI_CALLER`).
|
|
62
|
+
* A subagent it spawns gets its own label, so only the turn itself answers yes.
|
|
63
|
+
*/
|
|
64
|
+
export function isSessionLaneTurn(env = process.env) {
|
|
65
|
+
return env.YAGNI_CALLER === SESSION_CALLER_LABEL;
|
|
66
|
+
}
|
|
59
67
|
/** The model's own account of a turn that builds, beside `result.json` in the output folder. */
|
|
60
68
|
export const SESSION_REPORT_FILE_NAME = "session-report.json";
|
|
61
69
|
/** What a plan turn may use: everything that reads or runs, nothing that edits. */
|
|
@@ -74,6 +74,14 @@ export declare const DOCUMENTATION_REPOSITORY_RULE: string;
|
|
|
74
74
|
* without another repository is named, not left as a Risks bullet.
|
|
75
75
|
*/
|
|
76
76
|
export declare const RIGHT_REPOSITORY_RULE: string;
|
|
77
|
+
/**
|
|
78
|
+
* What the planner does with what it finds that the ticket did not ask for
|
|
79
|
+
* (ENG-2966): a nearby bug, a leak, a refactor is a follow-up a person can
|
|
80
|
+
* make a ticket of, never a question, a decision or a step of this plan. And
|
|
81
|
+
* nothing the ticket asked for is put off: work it needs in another
|
|
82
|
+
* repository goes in repositoriesNeeded, so the host offers the split.
|
|
83
|
+
*/
|
|
84
|
+
export declare const FOLLOW_UPS_RULE: string;
|
|
77
85
|
export declare const PLAN_JSON_CONTRACT: string;
|
|
78
86
|
/**
|
|
79
87
|
* The one-attempt lane's session (`yagni go --headless --cloud`): plan,
|
|
@@ -167,6 +167,20 @@ export const RIGHT_REPOSITORY_RULE = '- "wrongRepository" is for a ticket whose
|
|
|
167
167
|
'- "dependsOn" lists every other repository that needs a change before this one can work, each ' +
|
|
168
168
|
"with one line on what it needs there, whether or not the Other repositories section lists it. " +
|
|
169
169
|
"Such a dependency is never only a Risks bullet. Never list a repository the change only reads.";
|
|
170
|
+
/**
|
|
171
|
+
* What the planner does with what it finds that the ticket did not ask for
|
|
172
|
+
* (ENG-2966): a nearby bug, a leak, a refactor is a follow-up a person can
|
|
173
|
+
* make a ticket of, never a question, a decision or a step of this plan. And
|
|
174
|
+
* nothing the ticket asked for is put off: work it needs in another
|
|
175
|
+
* repository goes in repositoriesNeeded, so the host offers the split.
|
|
176
|
+
*/
|
|
177
|
+
export const FOLLOW_UPS_RULE = '- "followUps" lists what you found that the ticket did not ask for: a nearby bug, a security ' +
|
|
178
|
+
"leak, a refactor. Each is a title, one line on why it matters, and its repository when that is " +
|
|
179
|
+
"not this one. A follow-up is never a question, a decision or a step of this plan, however real " +
|
|
180
|
+
"it is: a person makes a ticket of it if they want one. Leave it out when you found nothing.\n" +
|
|
181
|
+
'- Never put off the ticket\'s own work ("for another session", "in a later ticket"). Plan all of ' +
|
|
182
|
+
"what it asked for. Work it needs in another repository goes in repositoriesNeeded, so the ticket " +
|
|
183
|
+
"is split, and is never left out of the plan.";
|
|
170
184
|
export const PLAN_JSON_CONTRACT = "End your answer with a fenced ```plan-json block and nothing after it:\n" +
|
|
171
185
|
"```plan-json\n" +
|
|
172
186
|
"{\n" +
|
|
@@ -181,12 +195,14 @@ export const PLAN_JSON_CONTRACT = "End your answer with a fenced ```plan-json bl
|
|
|
181
195
|
' "paths": ["apps/web"],\n' +
|
|
182
196
|
' "repositoriesNeeded": [{"repository": "owner/name", "summary": "one line on what changes there"}],\n' +
|
|
183
197
|
' "wrongRepository": {"repository": "owner/name", "why": "one line", "confidence": "high|low"},\n' +
|
|
184
|
-
' "dependsOn": [{"repository": "owner/name", "what": "one line on what it needs there"}]
|
|
198
|
+
' "dependsOn": [{"repository": "owner/name", "what": "one line on what it needs there"}],\n' +
|
|
199
|
+
' "followUps": [{"title": "...", "why": "one line", "repository": "owner/name"}]\n' +
|
|
185
200
|
"}\n" +
|
|
186
201
|
"```\n" +
|
|
187
202
|
"Rules the host enforces, so getting them wrong loses the question:\n" +
|
|
188
203
|
'- "kind" is "fact" when the answer could be looked up and "decision" when it is a person\'s call. ' +
|
|
189
|
-
'A decision you would make yourself carries your recommendation and is stated, not asked; mark "oneWay": true only when it cannot be undone
|
|
204
|
+
'A decision you would make yourself carries your recommendation and is stated, not asked; mark "oneWay": true only when it cannot be undone. ' +
|
|
205
|
+
"A recommendation whose planChange adds acceptance criteria widens the ticket, so a person is asked it.\n" +
|
|
190
206
|
'- On a fact, "checked" says in at most 4 short lines what you read or ran for it and what it showed, and what you could not read. ' +
|
|
191
207
|
"A fact whose every answer plans the same work (no option carries a planChange) is stated as your assumption, not asked.\n" +
|
|
192
208
|
"- At most two alternatives per question. A planChange only adds or rewords; it never removes.\n" +
|
|
@@ -200,7 +216,9 @@ export const PLAN_JSON_CONTRACT = "End your answer with a fenced ```plan-json bl
|
|
|
200
216
|
"never list a repository the change only reads. " +
|
|
201
217
|
DOCUMENTATION_REPOSITORY_RULE +
|
|
202
218
|
"\n" +
|
|
203
|
-
RIGHT_REPOSITORY_RULE
|
|
219
|
+
RIGHT_REPOSITORY_RULE +
|
|
220
|
+
"\n" +
|
|
221
|
+
FOLLOW_UPS_RULE;
|
|
204
222
|
/**
|
|
205
223
|
* The cloud plan stage's task: the input folder the host wrote, read first,
|
|
206
224
|
* then a plan grounded in the installed checkout, inside the stage's own
|
|
@@ -69,6 +69,15 @@ export interface SubagentDef {
|
|
|
69
69
|
* not a second build (on staging 2026-10-28, advanced-tier verifiers ran 34
|
|
70
70
|
* minutes after an 11-minute build). */
|
|
71
71
|
export declare const VERIFICATION_WALL_TIMEOUT_MS: number;
|
|
72
|
+
/**
|
|
73
|
+
* The tier the built-in verification agent asks for: `peak` in a session-lane
|
|
74
|
+
* turn (`pipeline/sessionLane.ts`), the one place the proxy serves a verifier
|
|
75
|
+
* above its advanced ceiling for subagents, and `advanced` everywhere else (the
|
|
76
|
+
* CLI, desktop, the attempt lane's stage graph), so a request never asks for a
|
|
77
|
+
* tier it would not be served. A repo's own `verification` agent keeps the tier
|
|
78
|
+
* it declares.
|
|
79
|
+
*/
|
|
80
|
+
export declare function verificationTier(env?: NodeJS.ProcessEnv): ModelTier;
|
|
72
81
|
/** Claude Code model names → YAGNI tiers; YAGNI tiers pass through. */
|
|
73
82
|
export declare function mapModelTier(model: string | undefined): ModelTier;
|
|
74
83
|
/**
|
|
@@ -80,7 +89,7 @@ export declare function parseAgentMarkdown(content: string, fallbackName: string
|
|
|
80
89
|
export interface DiscoverDeps {
|
|
81
90
|
cwd: string;
|
|
82
91
|
homeDir?: string;
|
|
83
|
-
/** Env seam for the plugin agent dirs (defaults to process.env). */
|
|
92
|
+
/** Env seam for the plugin agent dirs and the verification agent's tier (defaults to process.env). */
|
|
84
93
|
env?: NodeJS.ProcessEnv;
|
|
85
94
|
}
|
|
86
95
|
/**
|
|
@@ -112,7 +121,7 @@ export interface MakeSubagentToolDeps {
|
|
|
112
121
|
runStageImpl?: typeof runStage;
|
|
113
122
|
discover?: (deps: DiscoverDeps) => SubagentDef[];
|
|
114
123
|
homeDir?: string;
|
|
115
|
-
/** Env seam for the telemetry sessionId read (defaults to process.env). */
|
|
124
|
+
/** Env seam for discovery and the telemetry sessionId read (defaults to process.env). */
|
|
116
125
|
env?: NodeJS.ProcessEnv;
|
|
117
126
|
/** Live ultra-mode probe (/ultra): widens the per-call fan-out ceiling. */
|
|
118
127
|
isUltra?: () => boolean;
|
|
@@ -20,13 +20,14 @@
|
|
|
20
20
|
import * as fs from "node:fs";
|
|
21
21
|
import { homedir } from "node:os";
|
|
22
22
|
import { delimiter, join } from "node:path";
|
|
23
|
-
import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
23
|
+
import { parseFrontmatter, } from "@earendil-works/pi-coding-agent";
|
|
24
24
|
import { Type } from "typebox";
|
|
25
25
|
import { sanitizeCallerSegment } from "./config.js";
|
|
26
26
|
import { logEvent } from "./errorSink.js";
|
|
27
27
|
import { withResilience } from "./pipeline/resilience.js";
|
|
28
28
|
import { runStage } from "./pipeline/runner.js";
|
|
29
|
-
import {
|
|
29
|
+
import { isSessionLaneTurn } from "./pipeline/sessionLane.js";
|
|
30
|
+
import { DEFAULT_RESILIENCE_POLICY, } from "./pipeline/types.js";
|
|
30
31
|
import { applyChildEvent, finalizeTask, formatWorkingMessage, newTaskProgress, progressSummaryText, renderSubagentCall, renderSubagentResult, } from "./subagentRender.js";
|
|
31
32
|
/**
|
|
32
33
|
* pi's tool contract signals an error by THROWING, and a normally-returned
|
|
@@ -216,26 +217,38 @@ nothing, say exactly what you tried to break and how.`;
|
|
|
216
217
|
* not a second build (on staging 2026-10-28, advanced-tier verifiers ran 34
|
|
217
218
|
* minutes after an 11-minute build). */
|
|
218
219
|
export const VERIFICATION_WALL_TIMEOUT_MS = 15 * 60_000;
|
|
219
|
-
/**
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
220
|
+
/**
|
|
221
|
+
* The tier the built-in verification agent asks for: `peak` in a session-lane
|
|
222
|
+
* turn (`pipeline/sessionLane.ts`), the one place the proxy serves a verifier
|
|
223
|
+
* above its advanced ceiling for subagents, and `advanced` everywhere else (the
|
|
224
|
+
* CLI, desktop, the attempt lane's stage graph), so a request never asks for a
|
|
225
|
+
* tier it would not be served. A repo's own `verification` agent keeps the tier
|
|
226
|
+
* it declares.
|
|
227
|
+
*/
|
|
228
|
+
export function verificationTier(env = process.env) {
|
|
229
|
+
return isSessionLaneTurn(env) ? "peak" : "advanced";
|
|
230
|
+
}
|
|
231
|
+
/** The diamond's reduce layer: refute-first review of completed work, on
|
|
232
|
+
* {@link verificationTier} (judgment is the whole job, so the premium is worth
|
|
233
|
+
* paying where the lane serves it). One by default, on a fifteen-minute clock.
|
|
234
|
+
* It has no edit/write tools; bash is included for `git diff`-style inspection
|
|
235
|
+
* and is restricted to read-only USE by the persona, the same prompt-level
|
|
236
|
+
* stance as /go's reviewer, not a technical guarantee. */
|
|
237
|
+
function verificationAgent(env) {
|
|
238
|
+
return {
|
|
239
|
+
name: "verification",
|
|
240
|
+
description: "Adversarial verification of completed work: tries to refute a change, " +
|
|
241
|
+
"plan, or claim and reports concrete failure scenarios. Does not edit " +
|
|
242
|
+
"files. Send one; send two or three with different lenses (correctness, " +
|
|
243
|
+
"edge cases, codebase fit) only for a risky change such as a migration, " +
|
|
244
|
+
"auth or money, and treat agreement as confirmation.",
|
|
245
|
+
model: verificationTier(env),
|
|
246
|
+
tools: ["read", "grep", "find", "ls", "bash", "ask_yagni"],
|
|
247
|
+
body: VERIFICATION_BODY,
|
|
248
|
+
source: "builtin",
|
|
249
|
+
wallTimeoutMs: VERIFICATION_WALL_TIMEOUT_MS,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
239
252
|
// Concrete tiers a subagent can actually run on. `balanced` is deliberately NOT
|
|
240
253
|
// a member here even though it is a member of `ModelTier`: a subagent needs
|
|
241
254
|
// ONE model for its whole run, and balanced is a session-level routing policy,
|
|
@@ -331,9 +344,10 @@ function loadAgentsFromDir(dir, source) {
|
|
|
331
344
|
*/
|
|
332
345
|
export function discoverSubagents(deps) {
|
|
333
346
|
const home = deps.homeDir ?? homedir();
|
|
347
|
+
const env = deps.env ?? process.env;
|
|
334
348
|
const layers = [
|
|
335
|
-
[GENERAL_AGENT, SEARCHER_AGENT, IMPLEMENTER_AGENT,
|
|
336
|
-
...pluginAgentDirs(
|
|
349
|
+
[GENERAL_AGENT, SEARCHER_AGENT, IMPLEMENTER_AGENT, verificationAgent(env)],
|
|
350
|
+
...pluginAgentDirs(env).map((dir) => loadAgentsFromDir(dir, "plugin")),
|
|
337
351
|
loadAgentsFromDir(join(home, ".claude", "agents"), "user-claude"),
|
|
338
352
|
loadAgentsFromDir(join(deps.cwd, ".pi", "agents"), "project-pi"),
|
|
339
353
|
loadAgentsFromDir(join(deps.cwd, ".claude", "agents"), "project-claude"),
|
|
@@ -353,9 +367,7 @@ const SOURCE_LABEL = {
|
|
|
353
367
|
"project-claude": ".claude/agents",
|
|
354
368
|
};
|
|
355
369
|
export function formatAgentList(agents) {
|
|
356
|
-
return agents
|
|
357
|
-
.map((a) => `${a.name} (${SOURCE_LABEL[a.source]}): ${a.description}`)
|
|
358
|
-
.join("\n");
|
|
370
|
+
return agents.map((a) => `${a.name} (${SOURCE_LABEL[a.source]}): ${a.description}`).join("\n");
|
|
359
371
|
}
|
|
360
372
|
/**
|
|
361
373
|
* A subagent invocation is a synthetic pipeline stage: the task rides in as
|
|
@@ -450,7 +462,9 @@ export function buildSubagentStage(def, task, opts = {}) {
|
|
|
450
462
|
id: "implement",
|
|
451
463
|
agent: def.name,
|
|
452
464
|
model: def.model,
|
|
453
|
-
tools: grounded
|
|
465
|
+
tools: grounded
|
|
466
|
+
? (def.tools ?? DEFAULT_SUBAGENT_TOOLS)
|
|
467
|
+
: stripGroundingTool(def.tools ?? DEFAULT_SUBAGENT_TOOLS),
|
|
454
468
|
taskTemplate: "{ticket}",
|
|
455
469
|
},
|
|
456
470
|
ctx: { ticket: task, grounded },
|
|
@@ -463,7 +477,9 @@ const parameters = Type.Object({
|
|
|
463
477
|
agent: Type.Optional(Type.String({ description: "Agent name (see /agents). Defaults to general." })),
|
|
464
478
|
tasks: Type.Optional(Type.Array(Type.Object({
|
|
465
479
|
task: Type.String(),
|
|
466
|
-
agent: Type.Optional(Type.String({
|
|
480
|
+
agent: Type.Optional(Type.String({
|
|
481
|
+
description: "Agent name; defaults to the top-level `agent`, then general.",
|
|
482
|
+
})),
|
|
467
483
|
}), {
|
|
468
484
|
description: `Run several independent tasks in parallel (max ${MAX_PARALLEL_SUBAGENTS}; ${MAX_PARALLEL_SUBAGENTS_ULTRA} in ultra mode). Use INSTEAD of task. Entries without their own agent run as the top-level agent.`,
|
|
469
485
|
})),
|
|
@@ -504,7 +520,11 @@ export function subagentRunPolicy(wallTimeoutMs = SUBAGENT_WALL_TIMEOUT_MS) {
|
|
|
504
520
|
export function subagentTelemetry(env) {
|
|
505
521
|
return (rec) => logEvent({
|
|
506
522
|
source: "subagent",
|
|
507
|
-
level: rec.outcome === "ok"
|
|
523
|
+
level: rec.outcome === "ok"
|
|
524
|
+
? "info"
|
|
525
|
+
: rec.outcome === "timeout" || rec.outcome === "fatal"
|
|
526
|
+
? "error"
|
|
527
|
+
: "warn",
|
|
508
528
|
event: `attempt_${rec.outcome}`,
|
|
509
529
|
sessionId: env.YAGNI_SESSION_ID,
|
|
510
530
|
fields: {
|
|
@@ -563,7 +583,7 @@ export function makeSubagentTool(deps = {}) {
|
|
|
563
583
|
// Fails soft: discovery problems never block the tool.
|
|
564
584
|
let agentListBlock = "";
|
|
565
585
|
try {
|
|
566
|
-
const discovered = discover({ cwd: process.cwd(), homeDir: deps.homeDir });
|
|
586
|
+
const discovered = discover({ cwd: process.cwd(), homeDir: deps.homeDir, env: deps.env });
|
|
567
587
|
if (discovered.length > 0) {
|
|
568
588
|
agentListBlock =
|
|
569
589
|
" Available agents (first sentence only; /agents lists them in full):\n" +
|
|
@@ -589,7 +609,8 @@ export function makeSubagentTool(deps = {}) {
|
|
|
589
609
|
"spell out the task completely. Agents come from this repo's .claude/agents and .pi/agents " +
|
|
590
610
|
"(list them with /agents); omit `agent` for the general-purpose one. The list below was " +
|
|
591
611
|
"captured at session launch — if the session's cwd differs from the launch cwd it may be " +
|
|
592
|
-
"stale; an unknown name is a hard error, so trust /agents over this list when they differ." +
|
|
612
|
+
"stale; an unknown name is a hard error, so trust /agents over this list when they differ." +
|
|
613
|
+
agentListBlock,
|
|
593
614
|
promptSnippet: "subagent: delegate a self-contained task (or parallel tasks) to a fresh-context agent; returns its report.",
|
|
594
615
|
parameters,
|
|
595
616
|
// Self-framed: the condensed transcript look has no tinted tool boxes.
|
|
@@ -619,7 +640,7 @@ export function makeSubagentTool(deps = {}) {
|
|
|
619
640
|
return fail(`Error: at most ${maxParallel} parallel tasks per call.`);
|
|
620
641
|
}
|
|
621
642
|
const cwd = ctx?.cwd ?? process.cwd();
|
|
622
|
-
const agents = discover({ cwd, homeDir: deps.homeDir });
|
|
643
|
+
const agents = discover({ cwd, homeDir: deps.homeDir, env: deps.env });
|
|
623
644
|
const resolved = [];
|
|
624
645
|
for (const req of requested) {
|
|
625
646
|
// A tasks[] entry names its own agent first; absent that, it inherits
|
|
@@ -647,7 +668,11 @@ export function makeSubagentTool(deps = {}) {
|
|
|
647
668
|
onUpdate?.({
|
|
648
669
|
content: [{ type: "text", text: progressSummaryText(progresses, now) }],
|
|
649
670
|
details: {
|
|
650
|
-
tasks: progresses.map((p) => ({
|
|
671
|
+
tasks: progresses.map((p) => ({
|
|
672
|
+
...p,
|
|
673
|
+
actions: [...p.actions],
|
|
674
|
+
usage: { ...p.usage },
|
|
675
|
+
})),
|
|
651
676
|
},
|
|
652
677
|
});
|
|
653
678
|
const working = formatWorkingMessage(progresses, now);
|
|
@@ -678,7 +703,7 @@ export function makeSubagentTool(deps = {}) {
|
|
|
678
703
|
// Computed from the tools the runner will actually pass the
|
|
679
704
|
// child (`--tools`), so a custom definition cannot opt out by
|
|
680
705
|
// name.
|
|
681
|
-
stageWriteCapable:
|
|
706
|
+
stageWriteCapable: stage.tools.some((t) => t === "edit" || t === "write"),
|
|
682
707
|
// Content-free task discriminator: which entry of THIS call's
|
|
683
708
|
// task list the attempt belongs to (parallel same-agent
|
|
684
709
|
// failures stay attributable without leaking task text).
|
|
@@ -727,7 +752,10 @@ export function makeSubagentTool(deps = {}) {
|
|
|
727
752
|
// The verification agent's contract is a `## Verdict` header; an
|
|
728
753
|
// output without it is not a verdict, and the driver must not treat
|
|
729
754
|
// a truncated probe log as "HOLDS".
|
|
730
|
-
const missingVerdict = o.def.source === "builtin" &&
|
|
755
|
+
const missingVerdict = o.def.source === "builtin" &&
|
|
756
|
+
o.agent === "verification" &&
|
|
757
|
+
o.result.exitCode === 0 &&
|
|
758
|
+
!output.includes("## Verdict")
|
|
731
759
|
? "\n\n(no verdict — the verification run did not produce its verdict section)"
|
|
732
760
|
: "";
|
|
733
761
|
return outcomes.length === 1
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yagni-app/code-staging",
|
|
3
|
-
"version": "1.2.13-staging.
|
|
3
|
+
"version": "1.2.13-staging.2173.1",
|
|
4
4
|
"description": "YAGNI Code: a terminal coding agent that already knows your company. One YAGNI login routes the model and grounds the agent in your team's context.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"author": "YAGNI, Inc. <jack@yagni.app> (https://yagni.app)",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"turndown": "^7.2.4",
|
|
59
59
|
"typebox": "^1.3.15"
|
|
60
60
|
},
|
|
61
|
-
"yagniSourceSha": "
|
|
61
|
+
"yagniSourceSha": "d6206a240e12cff30f8e2e5e8975adfe67e52e83"
|
|
62
62
|
}
|