squad-kit 0.9.0 → 0.10.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/CHANGELOG.md +26 -0
- package/dist/cli.js +1361 -757
- package/package.json +5 -4
- package/dist/cli.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ var __export = (target, all) => {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
|
-
import { Command } from "commander";
|
|
9
|
+
import { Command, Option } from "commander";
|
|
10
10
|
|
|
11
11
|
// src/ui/banner.ts
|
|
12
12
|
import boxen from "boxen";
|
|
@@ -349,6 +349,20 @@ function mergePlanner(base, override) {
|
|
|
349
349
|
validation: {
|
|
350
350
|
enabled: override.validation?.enabled ?? base?.validation?.enabled ?? true,
|
|
351
351
|
strict: override.validation?.strict ?? base?.validation?.strict ?? false
|
|
352
|
+
},
|
|
353
|
+
runtime: {
|
|
354
|
+
anthropic: override.runtime?.anthropic ?? base?.runtime?.anthropic ?? "agent-sdk"
|
|
355
|
+
},
|
|
356
|
+
providerOptions: {
|
|
357
|
+
anthropic: {
|
|
358
|
+
thinking: override.providerOptions?.anthropic?.thinking ?? base?.providerOptions?.anthropic?.thinking ?? "adaptive",
|
|
359
|
+
effort: override.providerOptions?.anthropic?.effort ?? base?.providerOptions?.anthropic?.effort ?? "medium",
|
|
360
|
+
effortByPhase: {
|
|
361
|
+
scout: override.providerOptions?.anthropic?.effortByPhase?.scout ?? base?.providerOptions?.anthropic?.effortByPhase?.scout ?? "minimal",
|
|
362
|
+
draft: override.providerOptions?.anthropic?.effortByPhase?.draft ?? base?.providerOptions?.anthropic?.effortByPhase?.draft ?? "medium"
|
|
363
|
+
},
|
|
364
|
+
thinkingBudget: override.providerOptions?.anthropic?.thinkingBudget ?? base?.providerOptions?.anthropic?.thinkingBudget
|
|
365
|
+
}
|
|
352
366
|
}
|
|
353
367
|
};
|
|
354
368
|
if (merged.modelOverride) {
|
|
@@ -427,6 +441,12 @@ function parseConfig(raw, configFile) {
|
|
|
427
441
|
"planner.budget.maxDurationSeconds must be > 0. Run `squad config set planner` to fix planner.budget values."
|
|
428
442
|
);
|
|
429
443
|
}
|
|
444
|
+
const ar = merged.planner.runtime?.anthropic;
|
|
445
|
+
if (ar !== void 0 && ar !== "agent-sdk" && ar !== "vercel") {
|
|
446
|
+
throw new Error(
|
|
447
|
+
"planner.runtime.anthropic must be agent-sdk or vercel. Run `squad config set planner` to fix it."
|
|
448
|
+
);
|
|
449
|
+
}
|
|
430
450
|
}
|
|
431
451
|
return merged;
|
|
432
452
|
}
|
|
@@ -1025,6 +1045,14 @@ async function runConfigUnsetPlanner(opts = {}) {
|
|
|
1025
1045
|
info("Re-enable any time with `squad config set planner`.");
|
|
1026
1046
|
}
|
|
1027
1047
|
|
|
1048
|
+
// src/core/ci-env.ts
|
|
1049
|
+
function skipExternalProbesInAutomation() {
|
|
1050
|
+
const c = process.env.CI;
|
|
1051
|
+
if (c === "true" || c === "1") return true;
|
|
1052
|
+
if (process.env.GITHUB_ACTIONS) return true;
|
|
1053
|
+
return false;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1028
1056
|
// src/commands/config/set-planner.ts
|
|
1029
1057
|
function parseProvider(arg) {
|
|
1030
1058
|
if (!["anthropic", "openai", "google"].includes(arg)) {
|
|
@@ -1153,20 +1181,27 @@ async function runConfigSetPlanner(opts = {}) {
|
|
|
1153
1181
|
kv("model (execute)", execModel, 10);
|
|
1154
1182
|
kv("credential", `${cred.source} (${sourceText})`, 10);
|
|
1155
1183
|
}
|
|
1156
|
-
if (
|
|
1184
|
+
if (skipExternalProbesInAutomation() || !cred) {
|
|
1157
1185
|
printPlannerNextSteps(Boolean(cred));
|
|
1158
1186
|
return;
|
|
1159
1187
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1188
|
+
try {
|
|
1189
|
+
const listed = await fetchProviderModelIds(provider, cred.value);
|
|
1190
|
+
if (!listed.ok) {
|
|
1191
|
+
const st = listed.status;
|
|
1192
|
+
if (st === 401 || st === 403) {
|
|
1193
|
+
warning(`Could not list models (HTTP ${st}); check your key.`);
|
|
1194
|
+
} else {
|
|
1195
|
+
warning(`Model list probe failed (HTTP ${st}): ${listed.body.slice(0, 120)}`);
|
|
1196
|
+
}
|
|
1165
1197
|
} else {
|
|
1166
|
-
|
|
1198
|
+
info("Credential check: models API responded OK for this key.");
|
|
1167
1199
|
}
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1200
|
+
} catch (e) {
|
|
1201
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1202
|
+
warning(
|
|
1203
|
+
`Could not reach provider model list (${msg.slice(0, 160)}). Key was saved; run \`squad doctor\` to verify.`
|
|
1204
|
+
);
|
|
1170
1205
|
}
|
|
1171
1206
|
printPlannerNextSteps(true);
|
|
1172
1207
|
}
|
|
@@ -2010,7 +2045,7 @@ async function runConfigSetTracker(opts = {}) {
|
|
|
2010
2045
|
if (next.tracker.project) {
|
|
2011
2046
|
kv("project", next.tracker.project, 10);
|
|
2012
2047
|
}
|
|
2013
|
-
if (
|
|
2048
|
+
if (skipExternalProbesInAutomation()) {
|
|
2014
2049
|
printTrackerNextSteps(next.tracker.type);
|
|
2015
2050
|
return;
|
|
2016
2051
|
}
|
|
@@ -3296,17 +3331,32 @@ function noCandidateReason() {
|
|
|
3296
3331
|
// src/planner/loop.ts
|
|
3297
3332
|
import path19 from "path";
|
|
3298
3333
|
import fs27 from "fs";
|
|
3299
|
-
import { APICallError as
|
|
3334
|
+
import { APICallError as APICallError3 } from "ai";
|
|
3335
|
+
|
|
3336
|
+
// src/planner/tools/index.ts
|
|
3337
|
+
import { randomUUID } from "crypto";
|
|
3300
3338
|
|
|
3301
3339
|
// src/planner/tools/read-file.ts
|
|
3302
3340
|
import fs21 from "fs";
|
|
3303
3341
|
import path13 from "path";
|
|
3304
3342
|
import { tool } from "ai";
|
|
3305
3343
|
import { z } from "zod";
|
|
3306
|
-
var READ_FILE_TOOL_NAME = "read_file";
|
|
3307
3344
|
var MAX_BYTES_PER_FULL_READ = 32e3;
|
|
3308
3345
|
var MAX_LINES_PER_RANGED_READ = 400;
|
|
3309
3346
|
var MAX_BYTES_FOR_SYNC_LINE_SPLIT = 2e6;
|
|
3347
|
+
var rangedReadFileSchema = z.object({
|
|
3348
|
+
path: z.string().min(1).describe("Repo-relative POSIX path."),
|
|
3349
|
+
offset: z.number().int().min(1).optional().describe("1-indexed line number to start reading at (use with limit)."),
|
|
3350
|
+
limit: z.number().int().min(1).max(MAX_LINES_PER_RANGED_READ).optional().describe(`Line count from offset (max ${MAX_LINES_PER_RANGED_READ}).`)
|
|
3351
|
+
}).refine(
|
|
3352
|
+
(o) => o.offset === void 0 && o.limit === void 0 || o.offset !== void 0 && o.limit !== void 0,
|
|
3353
|
+
{ message: "Provide both offset and limit for a ranged read, or neither for a full read." }
|
|
3354
|
+
);
|
|
3355
|
+
var fullReadFileSchema = z.object({
|
|
3356
|
+
path: z.string().min(1).describe("Repo-relative POSIX path.")
|
|
3357
|
+
});
|
|
3358
|
+
var READ_FILE_TOOL_DESCRIPTION = "Read a UTF-8 text file from the project, returning its contents so you can plan against real code. Pass `offset` and `limit` together to read a line range (preferred when you know the region). Without them the whole file is read, capped at 32 KB. Paths must be relative to the project root. Binary files are refused. You have a bounded context budget; prefer small, targeted reads.";
|
|
3359
|
+
var READ_FILE_TOOL_DESCRIPTION_WHOLE_ONLY = "Read a UTF-8 text file from the project, returning its contents so you can plan against real code. Whole-file reads only; capped at 32 KB. Paths must be relative to the project root. Binary files are refused. You have a bounded context budget; prefer small, targeted reads.";
|
|
3310
3360
|
function readFileTool(root, budget, input5) {
|
|
3311
3361
|
const raw = typeof input5.path === "string" ? input5.path : "";
|
|
3312
3362
|
if (!raw.length) {
|
|
@@ -3392,66 +3442,6 @@ function readFileRanged(resolved, rawRel, budget, fileSize, offset, limit) {
|
|
|
3392
3442
|
budget.recordRead(bytes);
|
|
3393
3443
|
return { content: out, isError: false };
|
|
3394
3444
|
}
|
|
3395
|
-
function readFileToolFactory(root, budget, hooks, opts) {
|
|
3396
|
-
const ranged = opts?.ranged ?? true;
|
|
3397
|
-
const params = ranged ? z.object({
|
|
3398
|
-
path: z.string().min(1).describe("Repo-relative POSIX path."),
|
|
3399
|
-
offset: z.number().int().min(1).optional().describe("1-indexed line number to start reading at (use with limit)."),
|
|
3400
|
-
limit: z.number().int().min(1).max(MAX_LINES_PER_RANGED_READ).optional().describe(`Line count from offset (max ${MAX_LINES_PER_RANGED_READ}).`)
|
|
3401
|
-
}).refine(
|
|
3402
|
-
(o) => o.offset === void 0 && o.limit === void 0 || o.offset !== void 0 && o.limit !== void 0,
|
|
3403
|
-
{ message: "Provide both offset and limit for a ranged read, or neither for a full read." }
|
|
3404
|
-
) : z.object({
|
|
3405
|
-
path: z.string().min(1).describe("Repo-relative POSIX path.")
|
|
3406
|
-
});
|
|
3407
|
-
return tool({
|
|
3408
|
-
description: "Read a UTF-8 text file from the project, returning its contents so you can plan against real code. " + (ranged ? "Pass `offset` and `limit` together to read a line range (preferred when you know the region). Without them the whole file is read, capped at 32 KB. " : "Whole-file reads only; capped at 32 KB. ") + "Paths must be relative to the project root. Binary files are refused. You have a bounded context budget; prefer small, targeted reads.",
|
|
3409
|
-
parameters: params,
|
|
3410
|
-
execute: async (args, { toolCallId }) => {
|
|
3411
|
-
const tc = {
|
|
3412
|
-
id: toolCallId,
|
|
3413
|
-
name: READ_FILE_TOOL_NAME,
|
|
3414
|
-
input: { path: args.path, offset: args.offset, limit: args.limit }
|
|
3415
|
-
};
|
|
3416
|
-
const before = budget.snapshot().bytes;
|
|
3417
|
-
let result = readFileTool(root, budget, args);
|
|
3418
|
-
let after = budget.snapshot().bytes;
|
|
3419
|
-
let bytesLoaded = after - before;
|
|
3420
|
-
hooks.onToolCall?.(tc, bytesLoaded, after);
|
|
3421
|
-
hooks.bus.emit({
|
|
3422
|
-
kind: "tool_call",
|
|
3423
|
-
runId: hooks.runId,
|
|
3424
|
-
turn: hooks.turn,
|
|
3425
|
-
toolCall: tc,
|
|
3426
|
-
bytesLoaded,
|
|
3427
|
-
totalBytes: after
|
|
3428
|
-
});
|
|
3429
|
-
if (readBudgetishError(result)) {
|
|
3430
|
-
const decide = hooks.decideOnLimit;
|
|
3431
|
-
if (decide) {
|
|
3432
|
-
const d = await decide(hooks.getLimitCtx());
|
|
3433
|
-
if (d === "cancel") {
|
|
3434
|
-
throw new PlannerUserCancelledError(
|
|
3435
|
-
hooks.getAccumulatedText(),
|
|
3436
|
-
hooks.runId,
|
|
3437
|
-
hooks.turn,
|
|
3438
|
-
"budget_reads",
|
|
3439
|
-
tc,
|
|
3440
|
-
result
|
|
3441
|
-
);
|
|
3442
|
-
}
|
|
3443
|
-
hooks.extendSessionLimits();
|
|
3444
|
-
result = readFileTool(root, budget, args);
|
|
3445
|
-
after = budget.snapshot().bytes;
|
|
3446
|
-
bytesLoaded = after - before;
|
|
3447
|
-
} else {
|
|
3448
|
-
hooks.setBudgetExhausted(true);
|
|
3449
|
-
}
|
|
3450
|
-
}
|
|
3451
|
-
return { content: result.content, isError: result.isError };
|
|
3452
|
-
}
|
|
3453
|
-
});
|
|
3454
|
-
}
|
|
3455
3445
|
var PlannerUserCancelledError = class _PlannerUserCancelledError extends Error {
|
|
3456
3446
|
constructor(planText, runId, turn, incompleteKind, toolCall, pendingResult) {
|
|
3457
3447
|
super("PlannerUserCancelledError");
|
|
@@ -3630,38 +3620,13 @@ function walkFlat(root, dir, ig, out, max, includeHidden) {
|
|
|
3630
3620
|
var MAX_GREP_MATCHES = 200;
|
|
3631
3621
|
var MAX_GREP_FILE_SIZE_BYTES = 1e6;
|
|
3632
3622
|
var GREP_TOOL_NAME = "grep";
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
caseInsensitive: z2.boolean().optional().default(false)
|
|
3641
|
-
}),
|
|
3642
|
-
execute: async (args, { toolCallId }) => {
|
|
3643
|
-
const tc = {
|
|
3644
|
-
id: toolCallId,
|
|
3645
|
-
name: GREP_TOOL_NAME,
|
|
3646
|
-
input: { ...args }
|
|
3647
|
-
};
|
|
3648
|
-
const before = budget.snapshot().bytes;
|
|
3649
|
-
const out = runGrep(root, budget, args);
|
|
3650
|
-
const after = budget.snapshot().bytes;
|
|
3651
|
-
const bytesLoaded = after - before;
|
|
3652
|
-
hooks.onToolCall?.(tc, bytesLoaded, after);
|
|
3653
|
-
hooks.bus.emit({
|
|
3654
|
-
kind: "tool_call",
|
|
3655
|
-
runId: hooks.runId,
|
|
3656
|
-
turn: hooks.turn,
|
|
3657
|
-
toolCall: tc,
|
|
3658
|
-
bytesLoaded,
|
|
3659
|
-
totalBytes: after
|
|
3660
|
-
});
|
|
3661
|
-
return out;
|
|
3662
|
-
}
|
|
3663
|
-
});
|
|
3664
|
-
}
|
|
3623
|
+
var grepSchema = z2.object({
|
|
3624
|
+
pattern: z2.string().min(1).describe("Literal string or regex (default literal)."),
|
|
3625
|
+
regex: z2.boolean().optional().default(false),
|
|
3626
|
+
path: z2.string().optional().describe("Optional sub-path to scope the search to (relative)."),
|
|
3627
|
+
caseInsensitive: z2.boolean().optional().default(false)
|
|
3628
|
+
});
|
|
3629
|
+
var GREP_TOOL_DESCRIPTION = "Search the project for a literal string or regex pattern. Returns up to 200 matches across files, each shown as `path:line:match`. Use this BEFORE `read_file` to locate symbols or call sites; it is much cheaper than reading whole files.";
|
|
3665
3630
|
function runGrep(root, budget, args) {
|
|
3666
3631
|
const exec = budget.canExecuteTool();
|
|
3667
3632
|
if (!exec.ok) {
|
|
@@ -3791,34 +3756,10 @@ import { tool as tool3 } from "ai";
|
|
|
3791
3756
|
import { z as z3 } from "zod";
|
|
3792
3757
|
var MAX_ENTRIES_PER_LIST = 200;
|
|
3793
3758
|
var LIST_DIR_TOOL_NAME = "list_dir";
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
path: z3.string().describe('Repo-relative directory path. Use "." for the project root.')
|
|
3799
|
-
}),
|
|
3800
|
-
execute: async (args, { toolCallId }) => {
|
|
3801
|
-
const tc = {
|
|
3802
|
-
id: toolCallId,
|
|
3803
|
-
name: LIST_DIR_TOOL_NAME,
|
|
3804
|
-
input: { path: args.path }
|
|
3805
|
-
};
|
|
3806
|
-
const before = budget.snapshot().bytes;
|
|
3807
|
-
const out = runListDir(root, budget, args.path);
|
|
3808
|
-
const after = budget.snapshot().bytes;
|
|
3809
|
-
hooks.onToolCall?.(tc, after - before, after);
|
|
3810
|
-
hooks.bus.emit({
|
|
3811
|
-
kind: "tool_call",
|
|
3812
|
-
runId: hooks.runId,
|
|
3813
|
-
turn: hooks.turn,
|
|
3814
|
-
toolCall: tc,
|
|
3815
|
-
bytesLoaded: after - before,
|
|
3816
|
-
totalBytes: after
|
|
3817
|
-
});
|
|
3818
|
-
return out;
|
|
3819
|
-
}
|
|
3820
|
-
});
|
|
3821
|
-
}
|
|
3759
|
+
var listDirSchema = z3.object({
|
|
3760
|
+
path: z3.string().describe('Repo-relative directory path. Use "." for the project root.')
|
|
3761
|
+
});
|
|
3762
|
+
var LIST_DIR_TOOL_DESCRIPTION = "List the contents of a directory in the project (one level deep). Returns up to 200 entries with type (file/dir) and size. Cheap; use this to discover sibling files before reading them.";
|
|
3822
3763
|
function runListDir(root, budget, rawPath) {
|
|
3823
3764
|
const exec = budget.canExecuteTool();
|
|
3824
3765
|
if (!exec.ok) {
|
|
@@ -3895,26 +3836,132 @@ ${lines.join("\n")}` : "(empty directory)";
|
|
|
3895
3836
|
}
|
|
3896
3837
|
|
|
3897
3838
|
// src/planner/tools/index.ts
|
|
3898
|
-
|
|
3839
|
+
import { tool as tool4 } from "ai";
|
|
3840
|
+
function buildPlannerToolDefinitions(opts) {
|
|
3899
3841
|
const enabled = opts.enabled ?? { rangedRead: true, grep: true, listDir: true };
|
|
3900
|
-
const
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3842
|
+
const ranged = enabled.rangedRead ?? true;
|
|
3843
|
+
const defs = [];
|
|
3844
|
+
const readParams = ranged ? rangedReadFileSchema : fullReadFileSchema;
|
|
3845
|
+
const readDesc = ranged ? READ_FILE_TOOL_DESCRIPTION : READ_FILE_TOOL_DESCRIPTION_WHOLE_ONLY;
|
|
3846
|
+
defs.push({
|
|
3847
|
+
name: "read_file",
|
|
3848
|
+
description: readDesc,
|
|
3849
|
+
parameters: readParams,
|
|
3850
|
+
execute: async (args) => {
|
|
3851
|
+
const turn = opts.getTurn();
|
|
3852
|
+
const tc = {
|
|
3853
|
+
id: randomUUID(),
|
|
3854
|
+
name: "read_file",
|
|
3855
|
+
input: { path: args.path, offset: args.offset, limit: args.limit }
|
|
3856
|
+
};
|
|
3857
|
+
const before = opts.budget.snapshot().bytes;
|
|
3858
|
+
let result = readFileTool(opts.root, opts.budget, args);
|
|
3859
|
+
let after = opts.budget.snapshot().bytes;
|
|
3860
|
+
let bytesLoaded = after - before;
|
|
3861
|
+
opts.onToolCall?.(tc, bytesLoaded, after);
|
|
3862
|
+
opts.bus.emit({
|
|
3863
|
+
kind: "tool_call",
|
|
3864
|
+
runId: opts.runId,
|
|
3865
|
+
turn,
|
|
3866
|
+
toolCall: tc,
|
|
3867
|
+
bytesLoaded,
|
|
3868
|
+
totalBytes: after
|
|
3869
|
+
});
|
|
3870
|
+
if (readBudgetishError(result)) {
|
|
3871
|
+
const decide = opts.decideOnLimit;
|
|
3872
|
+
if (decide) {
|
|
3873
|
+
const d = await decide(opts.getLimitCtx());
|
|
3874
|
+
if (d === "cancel") {
|
|
3875
|
+
throw new PlannerUserCancelledError(opts.getAccumulatedText(), opts.runId, turn, "budget_reads", tc, result);
|
|
3876
|
+
}
|
|
3877
|
+
opts.extendSessionLimits();
|
|
3878
|
+
result = readFileTool(opts.root, opts.budget, args);
|
|
3879
|
+
after = opts.budget.snapshot().bytes;
|
|
3880
|
+
bytesLoaded = after - before;
|
|
3881
|
+
} else {
|
|
3882
|
+
opts.setBudgetExhausted(true);
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
return { content: result.content, isError: result.isError };
|
|
3886
|
+
}
|
|
3887
|
+
});
|
|
3888
|
+
if (enabled.grep) {
|
|
3889
|
+
defs.push({
|
|
3890
|
+
name: GREP_TOOL_NAME,
|
|
3891
|
+
description: GREP_TOOL_DESCRIPTION,
|
|
3892
|
+
parameters: grepSchema,
|
|
3893
|
+
execute: async (args) => {
|
|
3894
|
+
const turn = opts.getTurn();
|
|
3895
|
+
const tc = {
|
|
3896
|
+
id: randomUUID(),
|
|
3897
|
+
name: GREP_TOOL_NAME,
|
|
3898
|
+
input: { ...args }
|
|
3899
|
+
};
|
|
3900
|
+
const before = opts.budget.snapshot().bytes;
|
|
3901
|
+
const out = runGrep(opts.root, opts.budget, {
|
|
3902
|
+
pattern: args.pattern,
|
|
3903
|
+
regex: args.regex ?? false,
|
|
3904
|
+
path: args.path,
|
|
3905
|
+
caseInsensitive: args.caseInsensitive ?? false
|
|
3906
|
+
});
|
|
3907
|
+
const after = opts.budget.snapshot().bytes;
|
|
3908
|
+
const bytesLoaded = after - before;
|
|
3909
|
+
opts.onToolCall?.(tc, bytesLoaded, after);
|
|
3910
|
+
opts.bus.emit({
|
|
3911
|
+
kind: "tool_call",
|
|
3912
|
+
runId: opts.runId,
|
|
3913
|
+
turn,
|
|
3914
|
+
toolCall: tc,
|
|
3915
|
+
bytesLoaded,
|
|
3916
|
+
totalBytes: after
|
|
3917
|
+
});
|
|
3918
|
+
return { content: out.content, isError: out.isError };
|
|
3919
|
+
}
|
|
3920
|
+
});
|
|
3921
|
+
}
|
|
3922
|
+
if (enabled.listDir) {
|
|
3923
|
+
defs.push({
|
|
3924
|
+
name: LIST_DIR_TOOL_NAME,
|
|
3925
|
+
description: LIST_DIR_TOOL_DESCRIPTION,
|
|
3926
|
+
parameters: listDirSchema,
|
|
3927
|
+
execute: async (args) => {
|
|
3928
|
+
const turn = opts.getTurn();
|
|
3929
|
+
const tc = {
|
|
3930
|
+
id: randomUUID(),
|
|
3931
|
+
name: LIST_DIR_TOOL_NAME,
|
|
3932
|
+
input: { path: args.path }
|
|
3933
|
+
};
|
|
3934
|
+
const before = opts.budget.snapshot().bytes;
|
|
3935
|
+
const out = runListDir(opts.root, opts.budget, args.path);
|
|
3936
|
+
const after = opts.budget.snapshot().bytes;
|
|
3937
|
+
opts.onToolCall?.(tc, after - before, after);
|
|
3938
|
+
opts.bus.emit({
|
|
3939
|
+
kind: "tool_call",
|
|
3940
|
+
runId: opts.runId,
|
|
3941
|
+
turn,
|
|
3942
|
+
toolCall: tc,
|
|
3943
|
+
bytesLoaded: after - before,
|
|
3944
|
+
totalBytes: after
|
|
3945
|
+
});
|
|
3946
|
+
return { content: out.content, isError: out.isError };
|
|
3947
|
+
}
|
|
3948
|
+
});
|
|
3949
|
+
}
|
|
3950
|
+
return defs;
|
|
3951
|
+
}
|
|
3952
|
+
function vercelToolsFromDefinitions(defs) {
|
|
3953
|
+
const out = {};
|
|
3954
|
+
for (const def of defs) {
|
|
3955
|
+
out[def.name] = tool4({
|
|
3956
|
+
description: def.description,
|
|
3957
|
+
parameters: def.parameters,
|
|
3958
|
+
execute: async (args) => {
|
|
3959
|
+
const r = await def.execute(args);
|
|
3960
|
+
return typeof r === "string" ? r : r;
|
|
3961
|
+
}
|
|
3962
|
+
});
|
|
3963
|
+
}
|
|
3964
|
+
return out;
|
|
3918
3965
|
}
|
|
3919
3966
|
|
|
3920
3967
|
// src/planner/provider-errors.ts
|
|
@@ -4004,7 +4051,87 @@ function rateLimitMessage(err2) {
|
|
|
4004
4051
|
].join("\n");
|
|
4005
4052
|
}
|
|
4006
4053
|
|
|
4054
|
+
// src/core/runs.ts
|
|
4055
|
+
import fs25 from "fs/promises";
|
|
4056
|
+
import path17 from "path";
|
|
4057
|
+
import { randomBytes } from "crypto";
|
|
4058
|
+
var RING_SIZE = 20;
|
|
4059
|
+
function newRunId() {
|
|
4060
|
+
const ts = Date.now().toString(36).padStart(9, "0");
|
|
4061
|
+
const rnd = randomBytes(8).toString("hex");
|
|
4062
|
+
return `${ts}-${rnd}`;
|
|
4063
|
+
}
|
|
4064
|
+
async function appendRun(paths, rec) {
|
|
4065
|
+
const dir = path17.join(paths.squadDir, "runs");
|
|
4066
|
+
await fs25.mkdir(dir, { recursive: true });
|
|
4067
|
+
const file = path17.join(dir, `${rec.runId}.json`);
|
|
4068
|
+
await fs25.writeFile(file, JSON.stringify({ ...rec, version: 1 }, null, 2) + "\n", "utf8");
|
|
4069
|
+
await pruneOldRuns(dir);
|
|
4070
|
+
}
|
|
4071
|
+
async function pruneOldRuns(dir) {
|
|
4072
|
+
const entries = (await fs25.readdir(dir)).filter((n) => n.endsWith(".json"));
|
|
4073
|
+
if (entries.length <= RING_SIZE) return;
|
|
4074
|
+
entries.sort();
|
|
4075
|
+
const drop = entries.slice(0, entries.length - RING_SIZE);
|
|
4076
|
+
await Promise.all(drop.map((n) => fs25.rm(path17.join(dir, n), { force: true })));
|
|
4077
|
+
}
|
|
4078
|
+
async function listRuns(paths) {
|
|
4079
|
+
const dir = path17.join(paths.squadDir, "runs");
|
|
4080
|
+
let entries;
|
|
4081
|
+
try {
|
|
4082
|
+
entries = await fs25.readdir(dir);
|
|
4083
|
+
} catch {
|
|
4084
|
+
return [];
|
|
4085
|
+
}
|
|
4086
|
+
const files = entries.filter((n) => n.endsWith(".json")).sort().reverse();
|
|
4087
|
+
const out = [];
|
|
4088
|
+
for (const f of files) {
|
|
4089
|
+
try {
|
|
4090
|
+
const raw = await fs25.readFile(path17.join(dir, f), "utf8");
|
|
4091
|
+
const parsed = JSON.parse(raw);
|
|
4092
|
+
if (parsed.version === 1) out.push(parsed);
|
|
4093
|
+
} catch {
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
return out;
|
|
4097
|
+
}
|
|
4098
|
+
|
|
4099
|
+
// src/planner/session-limits.ts
|
|
4100
|
+
var DEFAULT_PLANNER_MAX_ITERATIONS = 40;
|
|
4101
|
+
var PLANNER_MARKDOWN_CONTINUATION_USER = "Your previous reply was cut off because it hit the per-response output token limit for this request. Continue the Markdown plan from the exact cut-off only: write the remainder, do not repeat headings or paragraphs already sent. When the plan is complete, use end_turn without unnecessary tool calls.";
|
|
4102
|
+
|
|
4103
|
+
// src/planner/runtimes/vercel-runtime.ts
|
|
4104
|
+
import { APICallError, generateObject, NoObjectGeneratedError } from "ai";
|
|
4105
|
+
|
|
4106
|
+
// src/planner/providers/index.ts
|
|
4107
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
4108
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
4109
|
+
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
4110
|
+
function resolveModel(provider, modelId, apiKey) {
|
|
4111
|
+
switch (provider) {
|
|
4112
|
+
case "anthropic":
|
|
4113
|
+
return { provider, model: createAnthropic({ apiKey })(modelId), modelId };
|
|
4114
|
+
case "openai":
|
|
4115
|
+
return { provider, model: createOpenAI({ apiKey })(modelId), modelId };
|
|
4116
|
+
case "google":
|
|
4117
|
+
return { provider, model: createGoogleGenerativeAI({ apiKey })(modelId), modelId };
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
|
|
4007
4121
|
// src/planner/usage-map.ts
|
|
4122
|
+
function usageFromAgentSdkResult(usage) {
|
|
4123
|
+
const inTok = usage?.input_tokens ?? 0;
|
|
4124
|
+
const outTok = usage?.output_tokens ?? 0;
|
|
4125
|
+
const out = {
|
|
4126
|
+
inputTokens: inTok ?? 0,
|
|
4127
|
+
outputTokens: outTok ?? 0
|
|
4128
|
+
};
|
|
4129
|
+
const cc = usage?.cache_creation_input_tokens;
|
|
4130
|
+
const cr = usage?.cache_read_input_tokens;
|
|
4131
|
+
if (cc != null && cc > 0) out.cacheCreationTokens = cc;
|
|
4132
|
+
if (cr != null && cr > 0) out.cacheReadTokens = cr;
|
|
4133
|
+
return out;
|
|
4134
|
+
}
|
|
4008
4135
|
function usageFromLanguageModelStep(usage, providerMetadata) {
|
|
4009
4136
|
const out = {
|
|
4010
4137
|
inputTokens: usage.promptTokens,
|
|
@@ -4135,74 +4262,558 @@ function turnsToCoreMessages(systemPrompt, turns, opts) {
|
|
|
4135
4262
|
return messages;
|
|
4136
4263
|
}
|
|
4137
4264
|
|
|
4138
|
-
// src/
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
var RING_SIZE = 20;
|
|
4143
|
-
function newRunId() {
|
|
4144
|
-
const ts = Date.now().toString(36).padStart(9, "0");
|
|
4145
|
-
const rnd = randomBytes(8).toString("hex");
|
|
4146
|
-
return `${ts}-${rnd}`;
|
|
4147
|
-
}
|
|
4148
|
-
async function appendRun(paths, rec) {
|
|
4149
|
-
const dir = path17.join(paths.squadDir, "runs");
|
|
4150
|
-
await fs25.mkdir(dir, { recursive: true });
|
|
4151
|
-
const file = path17.join(dir, `${rec.runId}.json`);
|
|
4152
|
-
await fs25.writeFile(file, JSON.stringify({ ...rec, version: 1 }, null, 2) + "\n", "utf8");
|
|
4153
|
-
await pruneOldRuns(dir);
|
|
4265
|
+
// src/planner/runtimes/vercel-runtime.ts
|
|
4266
|
+
var MAX_RATE_LIMIT_RETRY_SEC = 90;
|
|
4267
|
+
function isAbortError(e) {
|
|
4268
|
+
return e instanceof Error && e.name === "AbortError";
|
|
4154
4269
|
}
|
|
4155
|
-
async function
|
|
4156
|
-
|
|
4157
|
-
if (entries.length <= RING_SIZE) return;
|
|
4158
|
-
entries.sort();
|
|
4159
|
-
const drop = entries.slice(0, entries.length - RING_SIZE);
|
|
4160
|
-
await Promise.all(drop.map((n) => fs25.rm(path17.join(dir, n), { force: true })));
|
|
4270
|
+
async function defaultSleep(ms) {
|
|
4271
|
+
await new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4161
4272
|
}
|
|
4162
|
-
async function
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
entries = await fs25.readdir(dir);
|
|
4167
|
-
} catch {
|
|
4168
|
-
return [];
|
|
4273
|
+
async function sleepWithAbort(ms, sleepFn, abort) {
|
|
4274
|
+
if (!abort) {
|
|
4275
|
+
await sleepFn(ms);
|
|
4276
|
+
return;
|
|
4169
4277
|
}
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4278
|
+
if (abort.aborted) throw new DOMException("Aborted", "AbortError");
|
|
4279
|
+
await Promise.race([
|
|
4280
|
+
sleepFn(ms),
|
|
4281
|
+
new Promise((_, reject) => {
|
|
4282
|
+
abort.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true });
|
|
4283
|
+
})
|
|
4284
|
+
]);
|
|
4285
|
+
}
|
|
4286
|
+
function asToolInput(args) {
|
|
4287
|
+
if (args && typeof args === "object" && !Array.isArray(args)) return args;
|
|
4288
|
+
if (typeof args === "string") {
|
|
4173
4289
|
try {
|
|
4174
|
-
|
|
4175
|
-
const parsed = JSON.parse(raw);
|
|
4176
|
-
if (parsed.version === 1) out.push(parsed);
|
|
4290
|
+
return JSON.parse(args);
|
|
4177
4291
|
} catch {
|
|
4292
|
+
return {};
|
|
4178
4293
|
}
|
|
4179
4294
|
}
|
|
4180
|
-
return
|
|
4295
|
+
return {};
|
|
4181
4296
|
}
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4297
|
+
function normalizeToolExecutionResult(result) {
|
|
4298
|
+
if (typeof result === "string") {
|
|
4299
|
+
try {
|
|
4300
|
+
const j = JSON.parse(result);
|
|
4301
|
+
if (j && typeof j === "object" && !Array.isArray(j) && "content" in j) {
|
|
4302
|
+
const o = j;
|
|
4303
|
+
return {
|
|
4304
|
+
content: typeof o.content === "string" ? o.content : JSON.stringify(o.content ?? ""),
|
|
4305
|
+
isError: o.isError === true
|
|
4306
|
+
};
|
|
4307
|
+
}
|
|
4308
|
+
} catch {
|
|
4309
|
+
}
|
|
4310
|
+
return { content: result, isError: false };
|
|
4311
|
+
}
|
|
4312
|
+
if (result && typeof result === "object" && !Array.isArray(result) && "content" in result) {
|
|
4313
|
+
const o = result;
|
|
4314
|
+
return {
|
|
4315
|
+
content: typeof o.content === "string" ? o.content : JSON.stringify(o.content ?? ""),
|
|
4316
|
+
isError: o.isError === true
|
|
4317
|
+
};
|
|
4318
|
+
}
|
|
4319
|
+
return { content: typeof result === "undefined" ? "" : JSON.stringify(result), isError: false };
|
|
4320
|
+
}
|
|
4321
|
+
function applyStepToTurns(turns, step2) {
|
|
4322
|
+
const callsById = /* @__PURE__ */ new Map();
|
|
4323
|
+
const toolCalls = step2.toolCalls.map((tc) => {
|
|
4324
|
+
callsById.set(tc.toolCallId, tc.toolName);
|
|
4325
|
+
return { id: tc.toolCallId, name: tc.toolName, input: asToolInput(tc.args) };
|
|
4326
|
+
});
|
|
4327
|
+
turns.push({
|
|
4328
|
+
role: "assistant",
|
|
4329
|
+
...step2.text ? { text: step2.text } : {},
|
|
4330
|
+
...toolCalls.length ? { toolCalls } : {}
|
|
4331
|
+
});
|
|
4332
|
+
if (step2.toolResults.length) {
|
|
4333
|
+
const toolResults = step2.toolResults.map((tr) => {
|
|
4334
|
+
const norm = normalizeToolExecutionResult(tr.result);
|
|
4335
|
+
return {
|
|
4336
|
+
toolCallId: tr.toolCallId,
|
|
4337
|
+
name: callsById.get(tr.toolCallId) ?? "read_file",
|
|
4338
|
+
content: norm.content,
|
|
4339
|
+
...norm.isError ? { isError: true } : {}
|
|
4340
|
+
};
|
|
4341
|
+
});
|
|
4342
|
+
turns.push({ role: "user", toolResults });
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4345
|
+
function recordUsageAndEmit(bus, onUsage, budget, runId, turn, usage) {
|
|
4346
|
+
budget.recordUsage(usage);
|
|
4347
|
+
onUsage?.(usage);
|
|
4348
|
+
bus.emit({ kind: "usage", runId, turn, usage });
|
|
4349
|
+
const u = budget.snapshot().usage;
|
|
4350
|
+
const cacheRead = u.cacheReadTokens ?? 0;
|
|
4351
|
+
const cacheCreate = u.cacheCreationTokens ?? 0;
|
|
4352
|
+
if (cacheRead > 0 || cacheCreate > 0) {
|
|
4353
|
+
const inTok = u.inputTokens;
|
|
4354
|
+
const totalInput = inTok + cacheRead;
|
|
4355
|
+
const cacheHitRatio = totalInput === 0 ? 0 : Math.round(cacheRead / totalInput * 100) / 100;
|
|
4356
|
+
bus.emit({
|
|
4357
|
+
kind: "cache_summary",
|
|
4358
|
+
runId,
|
|
4359
|
+
turn,
|
|
4360
|
+
cacheHitRatio,
|
|
4361
|
+
cacheReadTokens: cacheRead,
|
|
4362
|
+
cacheCreationTokens: cacheCreate
|
|
4363
|
+
});
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4366
|
+
function providerResponseFromApiError(provider, modelId, err2) {
|
|
4367
|
+
const body = err2.responseBody ?? "";
|
|
4368
|
+
const nf = detectModelNotFound(provider, modelId, err2.statusCode ?? 0, body);
|
|
4369
|
+
if (nf) {
|
|
4370
|
+
return {
|
|
4371
|
+
stopReason: "error",
|
|
4372
|
+
errorKind: "model_not_found",
|
|
4373
|
+
rawError: modelNotFoundMessage(nf)
|
|
4374
|
+
};
|
|
4375
|
+
}
|
|
4376
|
+
const rl = detectRateLimit(provider, err2.statusCode ?? 0, err2.responseHeaders ?? {}, body);
|
|
4377
|
+
if (rl) {
|
|
4378
|
+
return {
|
|
4379
|
+
stopReason: "error",
|
|
4380
|
+
errorKind: "rate_limit",
|
|
4381
|
+
retryAfterSec: rl.retryAfterSec,
|
|
4382
|
+
rawError: `${provider} ${err2.statusCode ?? "error"}: ${body.slice(0, 500)}`
|
|
4383
|
+
};
|
|
4384
|
+
}
|
|
4385
|
+
return {
|
|
4386
|
+
stopReason: "error",
|
|
4387
|
+
rawError: `${provider} ${err2.statusCode ?? "error"}: ${body.slice(0, 500)}`
|
|
4388
|
+
};
|
|
4389
|
+
}
|
|
4390
|
+
function composePlannerError(providerName, response, retriedRateLimit, retrySkippedReason) {
|
|
4391
|
+
if (response.errorKind === "rate_limit") {
|
|
4392
|
+
return new Error(
|
|
4393
|
+
rateLimitMessage({
|
|
4394
|
+
provider: providerName,
|
|
4395
|
+
retryAfterSec: response.retryAfterSec,
|
|
4396
|
+
rawBody: response.rawError ?? "",
|
|
4397
|
+
retryAlreadyAttempted: retriedRateLimit,
|
|
4398
|
+
retrySkippedReason,
|
|
4399
|
+
maxRetrySec: MAX_RATE_LIMIT_RETRY_SEC
|
|
4400
|
+
})
|
|
4401
|
+
);
|
|
4402
|
+
}
|
|
4403
|
+
if (response.errorKind === "model_not_found") {
|
|
4404
|
+
return new Error(response.rawError ?? "planner: model not found");
|
|
4405
|
+
}
|
|
4406
|
+
const base = response.rawError ?? "planner: provider error";
|
|
4407
|
+
return new Error(`${base} Run \`squad doctor\` to diagnose, or retry \u2014 most 5xx errors are transient.`);
|
|
4408
|
+
}
|
|
4409
|
+
var VercelRuntime = class {
|
|
4410
|
+
kind = "vercel";
|
|
4411
|
+
providerName;
|
|
4412
|
+
modelId;
|
|
4413
|
+
languageModel;
|
|
4414
|
+
constructor(provider, modelId, apiKeyOrModel, injectModel) {
|
|
4415
|
+
this.providerName = provider;
|
|
4416
|
+
this.modelId = modelId;
|
|
4417
|
+
if (injectModel === true) {
|
|
4418
|
+
if (typeof apiKeyOrModel === "string") {
|
|
4419
|
+
throw new Error("VercelRuntime: injectModel mode expects a LanguageModelV1 as third argument");
|
|
4420
|
+
}
|
|
4421
|
+
this.languageModel = apiKeyOrModel;
|
|
4422
|
+
return;
|
|
4423
|
+
}
|
|
4424
|
+
if (typeof apiKeyOrModel !== "string") {
|
|
4425
|
+
throw new Error("VercelRuntime: apiKey must be a string unless injectModel is true");
|
|
4426
|
+
}
|
|
4427
|
+
this.languageModel = resolveModel(provider, modelId, apiKeyOrModel).model;
|
|
4428
|
+
}
|
|
4429
|
+
async runScout(input5) {
|
|
4430
|
+
let attempt = 0;
|
|
4431
|
+
while (true) {
|
|
4432
|
+
try {
|
|
4433
|
+
const { object, usage, providerMetadata } = await generateObject({
|
|
4434
|
+
model: this.languageModel,
|
|
4435
|
+
system: input5.systemPrompt,
|
|
4436
|
+
prompt: input5.userMessage,
|
|
4437
|
+
schema: input5.schema,
|
|
4438
|
+
maxTokens: input5.maxOutputTokens,
|
|
4439
|
+
abortSignal: input5.abort
|
|
4440
|
+
});
|
|
4441
|
+
const u = usageFromLanguageModelStep(usage, providerMetadata);
|
|
4442
|
+
return {
|
|
4443
|
+
output: object,
|
|
4444
|
+
usage: u
|
|
4445
|
+
};
|
|
4446
|
+
} catch (e) {
|
|
4447
|
+
if (NoObjectGeneratedError.isInstance(e)) {
|
|
4448
|
+
return null;
|
|
4449
|
+
}
|
|
4450
|
+
if (APICallError.isInstance(e) && attempt === 0) {
|
|
4451
|
+
const status = e.statusCode ?? 0;
|
|
4452
|
+
if (status === 429) {
|
|
4453
|
+
const ra = parseRetryAfterSec(e.responseHeaders ?? {});
|
|
4454
|
+
if (ra !== void 0 && ra <= 90) {
|
|
4455
|
+
await new Promise((r) => setTimeout(r, ra * 1e3));
|
|
4456
|
+
attempt += 1;
|
|
4457
|
+
continue;
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
throw e;
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4465
|
+
async runDraft(input5) {
|
|
4466
|
+
const vl = input5.vercelLoop;
|
|
4467
|
+
if (!vl) {
|
|
4468
|
+
throw new Error("planner: Vercel runtime requires vercelLoop context");
|
|
4469
|
+
}
|
|
4470
|
+
const bus = input5.bus;
|
|
4471
|
+
const runId = input5.runId;
|
|
4472
|
+
const sleepFn = vl.sleep ?? defaultSleep;
|
|
4473
|
+
const baseMaxIter = input5.maxSteps ?? DEFAULT_PLANNER_MAX_ITERATIONS;
|
|
4474
|
+
const maxIter = { current: baseMaxIter };
|
|
4475
|
+
const baseMaxOut = input5.maxOutputTokens ?? DEFAULT_PLANNER_MAX_OUTPUT_TOKENS;
|
|
4476
|
+
const maxOut = { current: baseMaxOut };
|
|
4477
|
+
const turns = [{ role: "user", text: vl.userPrompt }];
|
|
4478
|
+
if (vl.scoutedSection?.trim()) {
|
|
4479
|
+
turns.push({ role: "user", text: composeScoutedUserMessage({ scoutedSection: vl.scoutedSection }) });
|
|
4480
|
+
}
|
|
4481
|
+
let accumulatedText = "";
|
|
4482
|
+
let iterations = 0;
|
|
4483
|
+
let budgetExhausted = false;
|
|
4484
|
+
let finishedNormally = false;
|
|
4485
|
+
const decide = vl.decideOnLimit;
|
|
4486
|
+
const limitCtx = (kind) => ({
|
|
4487
|
+
kind,
|
|
4488
|
+
budgetSnapshot: input5.budget.snapshot(),
|
|
4489
|
+
iterations,
|
|
4490
|
+
maxIterations: maxIter.current,
|
|
4491
|
+
maxOutputTokens: maxOut.current
|
|
4492
|
+
});
|
|
4493
|
+
const extendAllSessionLimits = () => {
|
|
4494
|
+
input5.budget.extendSession();
|
|
4495
|
+
maxIter.current += baseMaxIter;
|
|
4496
|
+
maxOut.current += baseMaxOut;
|
|
4497
|
+
};
|
|
4498
|
+
for (; ; ) {
|
|
4499
|
+
if (input5.abort?.aborted) {
|
|
4500
|
+
bus.emit({ kind: "cancelled", runId });
|
|
4501
|
+
return {
|
|
4502
|
+
text: accumulatedText,
|
|
4503
|
+
finishedNormally: false,
|
|
4504
|
+
iterations,
|
|
4505
|
+
finalUsage: input5.budget.snapshot().usage,
|
|
4506
|
+
budgetExhausted,
|
|
4507
|
+
userCancelled: true
|
|
4508
|
+
};
|
|
4509
|
+
}
|
|
4510
|
+
if (iterations >= maxIter.current) {
|
|
4511
|
+
if (decide) {
|
|
4512
|
+
const d = await decide(limitCtx("max_iterations"));
|
|
4513
|
+
if (d === "cancel") {
|
|
4514
|
+
return {
|
|
4515
|
+
text: accumulatedText,
|
|
4516
|
+
finishedNormally: false,
|
|
4517
|
+
incompleteKind: "max_iterations",
|
|
4518
|
+
iterations,
|
|
4519
|
+
finalUsage: input5.budget.snapshot().usage,
|
|
4520
|
+
budgetExhausted,
|
|
4521
|
+
userCancelled: true
|
|
4522
|
+
};
|
|
4523
|
+
}
|
|
4524
|
+
extendAllSessionLimits();
|
|
4525
|
+
continue;
|
|
4526
|
+
}
|
|
4527
|
+
return {
|
|
4528
|
+
text: accumulatedText,
|
|
4529
|
+
finishedNormally: false,
|
|
4530
|
+
incompleteKind: "max_iterations",
|
|
4531
|
+
iterations,
|
|
4532
|
+
finalUsage: input5.budget.snapshot().usage,
|
|
4533
|
+
budgetExhausted
|
|
4534
|
+
};
|
|
4535
|
+
}
|
|
4536
|
+
iterations += 1;
|
|
4537
|
+
if (input5.budget.timedOut()) {
|
|
4538
|
+
if (decide) {
|
|
4539
|
+
const d = await decide(limitCtx("wall_clock"));
|
|
4540
|
+
if (d === "cancel") {
|
|
4541
|
+
return {
|
|
4542
|
+
text: accumulatedText,
|
|
4543
|
+
finishedNormally: false,
|
|
4544
|
+
incompleteKind: "wall_clock",
|
|
4545
|
+
iterations,
|
|
4546
|
+
finalUsage: input5.budget.snapshot().usage,
|
|
4547
|
+
budgetExhausted,
|
|
4548
|
+
timedOut: true,
|
|
4549
|
+
userCancelled: true
|
|
4550
|
+
};
|
|
4551
|
+
}
|
|
4552
|
+
extendAllSessionLimits();
|
|
4553
|
+
iterations -= 1;
|
|
4554
|
+
continue;
|
|
4555
|
+
}
|
|
4556
|
+
return {
|
|
4557
|
+
text: accumulatedText,
|
|
4558
|
+
finishedNormally: false,
|
|
4559
|
+
iterations,
|
|
4560
|
+
finalUsage: input5.budget.snapshot().usage,
|
|
4561
|
+
budgetExhausted,
|
|
4562
|
+
timedOut: true
|
|
4563
|
+
};
|
|
4564
|
+
}
|
|
4565
|
+
if (iterations >= 2) {
|
|
4566
|
+
bus.emit({ kind: "turn_started", runId, turn: iterations });
|
|
4567
|
+
}
|
|
4568
|
+
const toolDefs = buildPlannerToolDefinitions({
|
|
4569
|
+
root: vl.root,
|
|
4570
|
+
budget: input5.budget,
|
|
4571
|
+
enabled: vl.toolsEnabled,
|
|
4572
|
+
getTurn: () => iterations,
|
|
4573
|
+
runId,
|
|
4574
|
+
bus,
|
|
4575
|
+
onToolCall: input5.onToolCall,
|
|
4576
|
+
decideOnLimit: vl.decideOnLimit,
|
|
4577
|
+
getLimitCtx: () => limitCtx("file_or_context_reads"),
|
|
4578
|
+
extendSessionLimits: extendAllSessionLimits,
|
|
4579
|
+
getAccumulatedText: () => accumulatedText,
|
|
4580
|
+
setBudgetExhausted: (v) => {
|
|
4581
|
+
budgetExhausted = v;
|
|
4582
|
+
}
|
|
4583
|
+
});
|
|
4584
|
+
const tools = vercelToolsFromDefinitions(toolDefs);
|
|
4585
|
+
const messages = turnsToCoreMessages(input5.systemPrompt, turns, {
|
|
4586
|
+
cacheEnabled: vl.cacheEnabled,
|
|
4587
|
+
provider: vl.provider
|
|
4588
|
+
});
|
|
4589
|
+
bus.emit({ kind: "request_sent", runId, turn: iterations });
|
|
4590
|
+
let attempt = 0;
|
|
4591
|
+
let stepResult;
|
|
4592
|
+
let retrySkippedReason;
|
|
4593
|
+
let retriedRateLimit = false;
|
|
4594
|
+
const callStream = async () => {
|
|
4595
|
+
const { streamText } = await import("ai");
|
|
4596
|
+
const stream = streamText({
|
|
4597
|
+
model: this.languageModel,
|
|
4598
|
+
messages,
|
|
4599
|
+
tools,
|
|
4600
|
+
toolChoice: "auto",
|
|
4601
|
+
maxSteps: 1,
|
|
4602
|
+
maxRetries: 0,
|
|
4603
|
+
maxTokens: maxOut.current,
|
|
4604
|
+
abortSignal: input5.abort,
|
|
4605
|
+
onChunk: ({ chunk }) => {
|
|
4606
|
+
if (chunk.type === "text-delta") {
|
|
4607
|
+
accumulatedText += chunk.textDelta;
|
|
4608
|
+
input5.onAssistantText?.(chunk.textDelta);
|
|
4609
|
+
bus.emit({ kind: "assistant_text", runId, turn: iterations, delta: chunk.textDelta });
|
|
4610
|
+
}
|
|
4611
|
+
},
|
|
4612
|
+
onStepFinish: (step2) => {
|
|
4613
|
+
const u = usageFromLanguageModelStep(step2.usage, step2.providerMetadata);
|
|
4614
|
+
recordUsageAndEmit(bus, input5.onUsage, input5.budget, runId, iterations, u);
|
|
4615
|
+
}
|
|
4616
|
+
});
|
|
4617
|
+
for await (const part of stream.fullStream) {
|
|
4618
|
+
const p = part;
|
|
4619
|
+
if (p.type === "error") {
|
|
4620
|
+
throw p.error;
|
|
4621
|
+
}
|
|
4622
|
+
}
|
|
4623
|
+
const steps = await stream.steps;
|
|
4624
|
+
const last = steps[steps.length - 1];
|
|
4625
|
+
if (!last) throw new Error("planner: empty model step");
|
|
4626
|
+
stepResult = {
|
|
4627
|
+
text: last.text,
|
|
4628
|
+
finishReason: last.finishReason,
|
|
4629
|
+
toolCalls: last.toolCalls.flatMap(
|
|
4630
|
+
(tc) => tc ? [
|
|
4631
|
+
{
|
|
4632
|
+
toolCallId: tc.toolCallId,
|
|
4633
|
+
toolName: tc.toolName,
|
|
4634
|
+
args: tc.args
|
|
4635
|
+
}
|
|
4636
|
+
] : []
|
|
4637
|
+
),
|
|
4638
|
+
toolResults: last.toolResults.flatMap(
|
|
4639
|
+
(tr) => tr ? [{ toolCallId: tr.toolCallId, result: tr.result }] : []
|
|
4640
|
+
),
|
|
4641
|
+
usage: last.usage,
|
|
4642
|
+
providerMetadata: last.providerMetadata
|
|
4643
|
+
};
|
|
4644
|
+
};
|
|
4645
|
+
try {
|
|
4646
|
+
while (true) {
|
|
4647
|
+
try {
|
|
4648
|
+
await callStream();
|
|
4649
|
+
break;
|
|
4650
|
+
} catch (e) {
|
|
4651
|
+
if (PlannerUserCancelledError.is(e)) {
|
|
4652
|
+
return {
|
|
4653
|
+
text: e.planText,
|
|
4654
|
+
finishedNormally: false,
|
|
4655
|
+
iterations,
|
|
4656
|
+
finalUsage: input5.budget.snapshot().usage,
|
|
4657
|
+
budgetExhausted: false,
|
|
4658
|
+
userCancelled: true,
|
|
4659
|
+
incompleteKind: "budget_reads"
|
|
4660
|
+
};
|
|
4661
|
+
}
|
|
4662
|
+
if (isAbortError(e)) {
|
|
4663
|
+
bus.emit({ kind: "cancelled", runId });
|
|
4664
|
+
return {
|
|
4665
|
+
text: accumulatedText,
|
|
4666
|
+
finishedNormally: false,
|
|
4667
|
+
iterations,
|
|
4668
|
+
finalUsage: input5.budget.snapshot().usage,
|
|
4669
|
+
budgetExhausted,
|
|
4670
|
+
userCancelled: true
|
|
4671
|
+
};
|
|
4672
|
+
}
|
|
4673
|
+
if (APICallError.isInstance(e)) {
|
|
4674
|
+
const synthetic = providerResponseFromApiError(vl.provider, vl.modelId, e);
|
|
4675
|
+
if (synthetic.errorKind === "rate_limit") {
|
|
4676
|
+
const asked = synthetic.retryAfterSec;
|
|
4677
|
+
const emitRateLimit = (phase, waitSec) => {
|
|
4678
|
+
bus.emit({
|
|
4679
|
+
kind: "rate_limit",
|
|
4680
|
+
runId,
|
|
4681
|
+
turn: iterations,
|
|
4682
|
+
retryAfterSec: synthetic.retryAfterSec,
|
|
4683
|
+
waitSec,
|
|
4684
|
+
capSec: MAX_RATE_LIMIT_RETRY_SEC,
|
|
4685
|
+
phase,
|
|
4686
|
+
provider: vl.provider,
|
|
4687
|
+
rawBody: (synthetic.rawError ?? "").slice(0, 200)
|
|
4688
|
+
});
|
|
4689
|
+
};
|
|
4690
|
+
if (asked !== void 0 && asked > MAX_RATE_LIMIT_RETRY_SEC) {
|
|
4691
|
+
emitRateLimit("aborted", asked);
|
|
4692
|
+
retrySkippedReason = "retry_after_too_long";
|
|
4693
|
+
throw composePlannerError(vl.provider, synthetic, false, retrySkippedReason);
|
|
4694
|
+
}
|
|
4695
|
+
if (attempt === 0) {
|
|
4696
|
+
const waitSec = Math.min(asked ?? 10, MAX_RATE_LIMIT_RETRY_SEC);
|
|
4697
|
+
emitRateLimit("retrying", waitSec);
|
|
4698
|
+
vl.onRateLimit?.(waitSec);
|
|
4699
|
+
await sleepWithAbort(waitSec * 1e3, sleepFn, input5.abort);
|
|
4700
|
+
attempt += 1;
|
|
4701
|
+
retriedRateLimit = true;
|
|
4702
|
+
continue;
|
|
4703
|
+
}
|
|
4704
|
+
throw composePlannerError(vl.provider, synthetic, true, retrySkippedReason);
|
|
4705
|
+
}
|
|
4706
|
+
throw composePlannerError(vl.provider, synthetic, retriedRateLimit, retrySkippedReason);
|
|
4707
|
+
}
|
|
4708
|
+
throw e;
|
|
4709
|
+
}
|
|
4710
|
+
}
|
|
4711
|
+
} catch (e) {
|
|
4712
|
+
if (isAbortError(e)) {
|
|
4713
|
+
bus.emit({ kind: "cancelled", runId });
|
|
4714
|
+
return {
|
|
4715
|
+
text: accumulatedText,
|
|
4716
|
+
finishedNormally: false,
|
|
4717
|
+
iterations,
|
|
4718
|
+
finalUsage: input5.budget.snapshot().usage,
|
|
4719
|
+
budgetExhausted,
|
|
4720
|
+
userCancelled: true
|
|
4721
|
+
};
|
|
4722
|
+
}
|
|
4723
|
+
throw e;
|
|
4724
|
+
}
|
|
4725
|
+
const finishReason = stepResult.finishReason;
|
|
4726
|
+
const toolCallsLen = stepResult.toolCalls.length;
|
|
4727
|
+
if (finishReason === "length" && toolCallsLen === 0) {
|
|
4728
|
+
bus.emit({ kind: "turn_complete", runId, turn: iterations, stopReason: "max_tokens" });
|
|
4729
|
+
if (decide) {
|
|
4730
|
+
const d = await decide(limitCtx("max_output_tokens"));
|
|
4731
|
+
if (d === "cancel") {
|
|
4732
|
+
return {
|
|
4733
|
+
text: accumulatedText,
|
|
4734
|
+
finishedNormally: false,
|
|
4735
|
+
incompleteKind: "max_output_tokens",
|
|
4736
|
+
iterations,
|
|
4737
|
+
finalUsage: input5.budget.snapshot().usage,
|
|
4738
|
+
budgetExhausted,
|
|
4739
|
+
userCancelled: true
|
|
4740
|
+
};
|
|
4741
|
+
}
|
|
4742
|
+
extendAllSessionLimits();
|
|
4743
|
+
applyStepToTurns(turns, stepResult);
|
|
4744
|
+
turns.push({ role: "user", text: PLANNER_MARKDOWN_CONTINUATION_USER });
|
|
4745
|
+
iterations -= 1;
|
|
4746
|
+
continue;
|
|
4747
|
+
}
|
|
4748
|
+
return {
|
|
4749
|
+
text: accumulatedText,
|
|
4750
|
+
finishedNormally: false,
|
|
4751
|
+
incompleteKind: "max_output_tokens",
|
|
4752
|
+
iterations,
|
|
4753
|
+
finalUsage: input5.budget.snapshot().usage,
|
|
4754
|
+
budgetExhausted
|
|
4755
|
+
};
|
|
4756
|
+
}
|
|
4757
|
+
if (finishReason === "error" || finishReason === "content-filter") {
|
|
4758
|
+
throw new Error(
|
|
4759
|
+
stepResult.text || `planner: model reported ${finishReason}. Run \`squad doctor\` to diagnose, or retry \u2014 most 5xx errors are transient.`
|
|
4760
|
+
);
|
|
4761
|
+
}
|
|
4762
|
+
if ((finishReason === "stop" || finishReason === "unknown") && toolCallsLen === 0) {
|
|
4763
|
+
bus.emit({ kind: "turn_complete", runId, turn: iterations, stopReason: finishReason });
|
|
4764
|
+
finishedNormally = true;
|
|
4765
|
+
return {
|
|
4766
|
+
text: accumulatedText,
|
|
4767
|
+
finishedNormally,
|
|
4768
|
+
iterations,
|
|
4769
|
+
finalUsage: input5.budget.snapshot().usage,
|
|
4770
|
+
budgetExhausted
|
|
4771
|
+
};
|
|
4772
|
+
}
|
|
4773
|
+
if (toolCallsLen === 0) {
|
|
4774
|
+
bus.emit({ kind: "turn_complete", runId, turn: iterations, stopReason: finishReason });
|
|
4775
|
+
finishedNormally = true;
|
|
4776
|
+
return {
|
|
4777
|
+
text: accumulatedText,
|
|
4778
|
+
finishedNormally,
|
|
4779
|
+
iterations,
|
|
4780
|
+
finalUsage: input5.budget.snapshot().usage,
|
|
4781
|
+
budgetExhausted
|
|
4782
|
+
};
|
|
4783
|
+
}
|
|
4784
|
+
applyStepToTurns(turns, stepResult);
|
|
4785
|
+
if (budgetExhausted) {
|
|
4786
|
+
turns.push({
|
|
4787
|
+
role: "user",
|
|
4788
|
+
text: "Budget is exhausted. Finalise the plan with the information you already have. Do not call any more tools. Output the complete plan markdown now."
|
|
4789
|
+
});
|
|
4790
|
+
}
|
|
4791
|
+
bus.emit({ kind: "turn_complete", runId, turn: iterations, stopReason: finishReason });
|
|
4792
|
+
}
|
|
4793
|
+
}
|
|
4794
|
+
};
|
|
4795
|
+
|
|
4796
|
+
// src/planner/events.ts
|
|
4797
|
+
var PlannerEventBus = class {
|
|
4798
|
+
listeners = /* @__PURE__ */ new Set();
|
|
4799
|
+
emit(e) {
|
|
4800
|
+
for (const fn of this.listeners) {
|
|
4801
|
+
try {
|
|
4802
|
+
fn(e);
|
|
4803
|
+
} catch {
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
}
|
|
4807
|
+
subscribe(fn) {
|
|
4199
4808
|
this.listeners.add(fn);
|
|
4200
4809
|
return () => this.listeners.delete(fn);
|
|
4201
4810
|
}
|
|
4202
4811
|
};
|
|
4203
4812
|
|
|
4204
4813
|
// src/planner/stages/scout.ts
|
|
4205
|
-
import { APICallError
|
|
4814
|
+
import { APICallError as APICallError2, NoObjectGeneratedError as NoObjectGeneratedError2 } from "ai";
|
|
4815
|
+
|
|
4816
|
+
// src/planner/stages/scout-schema.ts
|
|
4206
4817
|
import { z as z4 } from "zod";
|
|
4207
4818
|
var ScoutOutputSchema = z4.object({
|
|
4208
4819
|
selectedFiles: z4.array(z4.string()).min(1).max(25),
|
|
@@ -4216,30 +4827,34 @@ var ScoutOutputSchema = z4.object({
|
|
|
4216
4827
|
})
|
|
4217
4828
|
).optional()
|
|
4218
4829
|
});
|
|
4830
|
+
|
|
4831
|
+
// src/planner/stages/scout.ts
|
|
4219
4832
|
async function runScout(input5) {
|
|
4220
4833
|
const maxTokens = input5.maxTokens ?? 2048;
|
|
4221
4834
|
let attempt = 0;
|
|
4222
4835
|
while (true) {
|
|
4223
4836
|
try {
|
|
4224
|
-
const
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
prompt: input5.userPrompt,
|
|
4837
|
+
const result = await input5.runtime.runScout({
|
|
4838
|
+
systemPrompt: input5.systemPrompt,
|
|
4839
|
+
userMessage: input5.userPrompt,
|
|
4228
4840
|
schema: ScoutOutputSchema,
|
|
4229
|
-
|
|
4230
|
-
|
|
4841
|
+
bus: input5.bus,
|
|
4842
|
+
runId: input5.runId,
|
|
4843
|
+
abort: input5.abort,
|
|
4844
|
+
maxOutputTokens: maxTokens,
|
|
4845
|
+
providerSpecific: input5.providerSpecific
|
|
4231
4846
|
});
|
|
4232
|
-
|
|
4233
|
-
input5.budget.recordUsage(
|
|
4847
|
+
if (!result) return null;
|
|
4848
|
+
input5.budget.recordUsage(result.usage);
|
|
4234
4849
|
return {
|
|
4235
|
-
output:
|
|
4236
|
-
usage: { inputTokens:
|
|
4850
|
+
output: result.output,
|
|
4851
|
+
usage: { inputTokens: result.usage.inputTokens, outputTokens: result.usage.outputTokens }
|
|
4237
4852
|
};
|
|
4238
4853
|
} catch (e) {
|
|
4239
|
-
if (
|
|
4854
|
+
if (NoObjectGeneratedError2.isInstance(e)) {
|
|
4240
4855
|
return null;
|
|
4241
4856
|
}
|
|
4242
|
-
if (
|
|
4857
|
+
if (APICallError2.isInstance(e) && attempt === 0) {
|
|
4243
4858
|
const status = e.statusCode ?? 0;
|
|
4244
4859
|
if (status === 429) {
|
|
4245
4860
|
const ra = parseRetryAfterSec(e.responseHeaders ?? {});
|
|
@@ -4551,24 +5166,6 @@ function validatePlan(input5) {
|
|
|
4551
5166
|
}
|
|
4552
5167
|
|
|
4553
5168
|
// src/planner/loop.ts
|
|
4554
|
-
var MAX_RATE_LIMIT_RETRY_SEC = 90;
|
|
4555
|
-
var defaultSleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4556
|
-
function isAbortError(e) {
|
|
4557
|
-
return e instanceof Error && e.name === "AbortError";
|
|
4558
|
-
}
|
|
4559
|
-
async function sleepWithAbort(ms, sleepFn, abort) {
|
|
4560
|
-
if (!abort) {
|
|
4561
|
-
await sleepFn(ms);
|
|
4562
|
-
return;
|
|
4563
|
-
}
|
|
4564
|
-
if (abort.aborted) throw new DOMException("Aborted", "AbortError");
|
|
4565
|
-
await Promise.race([
|
|
4566
|
-
sleepFn(ms),
|
|
4567
|
-
new Promise((_, reject) => {
|
|
4568
|
-
abort.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true });
|
|
4569
|
-
})
|
|
4570
|
-
]);
|
|
4571
|
-
}
|
|
4572
5169
|
function buildPlannerRunStats(budget, turns, runStartedAt) {
|
|
4573
5170
|
const u = budget.snapshot().usage;
|
|
4574
5171
|
const cacheRead = u.cacheReadTokens ?? 0;
|
|
@@ -4722,443 +5319,83 @@ function scoutStageStats(usage, durationMs) {
|
|
|
4722
5319
|
return {
|
|
4723
5320
|
turns: 0,
|
|
4724
5321
|
inputTokens: usage.inputTokens,
|
|
4725
|
-
outputTokens: usage.outputTokens,
|
|
4726
|
-
cacheCreationTokens: 0,
|
|
4727
|
-
cacheReadTokens: 0,
|
|
4728
|
-
cacheHitRatio: 0,
|
|
4729
|
-
durationMs
|
|
4730
|
-
};
|
|
4731
|
-
}
|
|
4732
|
-
function extendAllSessionLimits(input5, maxIter, maxOut, baseMaxIter, baseMaxOut) {
|
|
4733
|
-
input5.budget.extendSession();
|
|
4734
|
-
maxIter.current += baseMaxIter;
|
|
4735
|
-
maxOut.current += baseMaxOut;
|
|
4736
|
-
}
|
|
4737
|
-
function recordUsageAndEmit(bus, input5, runId, turn, usage) {
|
|
4738
|
-
input5.budget.recordUsage(usage);
|
|
4739
|
-
input5.onUsage?.(usage);
|
|
4740
|
-
bus.emit({ kind: "usage", runId, turn, usage });
|
|
4741
|
-
const u = input5.budget.snapshot().usage;
|
|
4742
|
-
const cacheRead = u.cacheReadTokens ?? 0;
|
|
4743
|
-
const cacheCreate = u.cacheCreationTokens ?? 0;
|
|
4744
|
-
if (cacheRead > 0 || cacheCreate > 0) {
|
|
4745
|
-
const inTok = u.inputTokens;
|
|
4746
|
-
const totalInput = inTok + cacheRead;
|
|
4747
|
-
const cacheHitRatio = totalInput === 0 ? 0 : Math.round(cacheRead / totalInput * 100) / 100;
|
|
4748
|
-
bus.emit({
|
|
4749
|
-
kind: "cache_summary",
|
|
4750
|
-
runId,
|
|
4751
|
-
turn,
|
|
4752
|
-
cacheHitRatio,
|
|
4753
|
-
cacheReadTokens: cacheRead,
|
|
4754
|
-
cacheCreationTokens: cacheCreate
|
|
4755
|
-
});
|
|
4756
|
-
}
|
|
4757
|
-
}
|
|
4758
|
-
function cancelledOutput(input5, bus, runId, accumulatedText, budgetExhausted, iterations, runStartedAt) {
|
|
4759
|
-
bus.emit({ kind: "cancelled", runId });
|
|
4760
|
-
return {
|
|
4761
|
-
planText: accumulatedText,
|
|
4762
|
-
budgetExhausted,
|
|
4763
|
-
timedOut: false,
|
|
4764
|
-
finishedNormally: false,
|
|
4765
|
-
iterations,
|
|
4766
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt),
|
|
4767
|
-
userCancelled: true
|
|
4768
|
-
};
|
|
4769
|
-
}
|
|
4770
|
-
function asToolInput(args) {
|
|
4771
|
-
if (args && typeof args === "object" && !Array.isArray(args)) return args;
|
|
4772
|
-
if (typeof args === "string") {
|
|
4773
|
-
try {
|
|
4774
|
-
return JSON.parse(args);
|
|
4775
|
-
} catch {
|
|
4776
|
-
return {};
|
|
4777
|
-
}
|
|
4778
|
-
}
|
|
4779
|
-
return {};
|
|
4780
|
-
}
|
|
4781
|
-
function normalizeToolExecutionResult(result) {
|
|
4782
|
-
if (typeof result === "string") {
|
|
4783
|
-
try {
|
|
4784
|
-
const j = JSON.parse(result);
|
|
4785
|
-
if (j && typeof j === "object" && !Array.isArray(j) && "content" in j) {
|
|
4786
|
-
const o = j;
|
|
4787
|
-
return {
|
|
4788
|
-
content: typeof o.content === "string" ? o.content : JSON.stringify(o.content ?? ""),
|
|
4789
|
-
isError: o.isError === true
|
|
4790
|
-
};
|
|
4791
|
-
}
|
|
4792
|
-
} catch {
|
|
4793
|
-
}
|
|
4794
|
-
return { content: result, isError: false };
|
|
4795
|
-
}
|
|
4796
|
-
if (result && typeof result === "object" && !Array.isArray(result) && "content" in result) {
|
|
4797
|
-
const o = result;
|
|
4798
|
-
return {
|
|
4799
|
-
content: typeof o.content === "string" ? o.content : JSON.stringify(o.content ?? ""),
|
|
4800
|
-
isError: o.isError === true
|
|
4801
|
-
};
|
|
4802
|
-
}
|
|
4803
|
-
return { content: typeof result === "undefined" ? "" : JSON.stringify(result), isError: false };
|
|
4804
|
-
}
|
|
4805
|
-
function applyStepToTurns(turns, step2) {
|
|
4806
|
-
const callsById = /* @__PURE__ */ new Map();
|
|
4807
|
-
const toolCalls = step2.toolCalls.map((tc) => {
|
|
4808
|
-
callsById.set(tc.toolCallId, tc.toolName);
|
|
4809
|
-
return { id: tc.toolCallId, name: tc.toolName, input: asToolInput(tc.args) };
|
|
4810
|
-
});
|
|
4811
|
-
turns.push({
|
|
4812
|
-
role: "assistant",
|
|
4813
|
-
...step2.text ? { text: step2.text } : {},
|
|
4814
|
-
...toolCalls.length ? { toolCalls } : {}
|
|
4815
|
-
});
|
|
4816
|
-
if (step2.toolResults.length) {
|
|
4817
|
-
const toolResults = step2.toolResults.map((tr) => {
|
|
4818
|
-
const norm = normalizeToolExecutionResult(tr.result);
|
|
4819
|
-
return {
|
|
4820
|
-
toolCallId: tr.toolCallId,
|
|
4821
|
-
name: callsById.get(tr.toolCallId) ?? "read_file",
|
|
4822
|
-
content: norm.content,
|
|
4823
|
-
...norm.isError ? { isError: true } : {}
|
|
4824
|
-
};
|
|
4825
|
-
});
|
|
4826
|
-
turns.push({ role: "user", toolResults });
|
|
4827
|
-
}
|
|
4828
|
-
}
|
|
4829
|
-
function providerResponseFromApiError(provider, modelId, err2) {
|
|
4830
|
-
const body = err2.responseBody ?? "";
|
|
4831
|
-
const nf = detectModelNotFound(provider, modelId, err2.statusCode ?? 0, body);
|
|
4832
|
-
if (nf) {
|
|
4833
|
-
return {
|
|
4834
|
-
stopReason: "error",
|
|
4835
|
-
errorKind: "model_not_found",
|
|
4836
|
-
rawError: modelNotFoundMessage(nf)
|
|
4837
|
-
};
|
|
4838
|
-
}
|
|
4839
|
-
const rl = detectRateLimit(provider, err2.statusCode ?? 0, err2.responseHeaders ?? {}, body);
|
|
4840
|
-
if (rl) {
|
|
4841
|
-
return {
|
|
4842
|
-
stopReason: "error",
|
|
4843
|
-
errorKind: "rate_limit",
|
|
4844
|
-
retryAfterSec: rl.retryAfterSec,
|
|
4845
|
-
rawError: `${provider} ${err2.statusCode ?? "error"}: ${body.slice(0, 500)}`
|
|
4846
|
-
};
|
|
4847
|
-
}
|
|
4848
|
-
return {
|
|
4849
|
-
stopReason: "error",
|
|
4850
|
-
rawError: `${provider} ${err2.statusCode ?? "error"}: ${body.slice(0, 500)}`
|
|
5322
|
+
outputTokens: usage.outputTokens,
|
|
5323
|
+
cacheCreationTokens: 0,
|
|
5324
|
+
cacheReadTokens: 0,
|
|
5325
|
+
cacheHitRatio: 0,
|
|
5326
|
+
durationMs
|
|
4851
5327
|
};
|
|
4852
5328
|
}
|
|
4853
5329
|
async function runDraftStage(input5) {
|
|
4854
5330
|
const runStartedAt = Date.now();
|
|
4855
5331
|
const bus = input5.events ?? new PlannerEventBus();
|
|
4856
5332
|
const runId = input5.runId ?? newRunId();
|
|
4857
|
-
const
|
|
4858
|
-
const baseMaxIter = input5.maxIterations ?? DEFAULT_PLANNER_MAX_ITERATIONS;
|
|
4859
|
-
const maxIter = { current: baseMaxIter };
|
|
4860
|
-
const baseMaxOut = input5.maxOutputTokens ?? DEFAULT_PLANNER_MAX_OUTPUT_TOKENS;
|
|
4861
|
-
const maxOut = { current: baseMaxOut };
|
|
4862
|
-
const turns = [{ role: "user", text: input5.userPrompt }];
|
|
4863
|
-
if (input5.scoutedSection?.trim()) {
|
|
4864
|
-
turns.push({ role: "user", text: composeScoutedUserMessage({ scoutedSection: input5.scoutedSection }) });
|
|
4865
|
-
}
|
|
4866
|
-
let accumulatedText = "";
|
|
4867
|
-
let iterations = 0;
|
|
4868
|
-
let budgetExhausted = false;
|
|
4869
|
-
let finishedNormally = false;
|
|
4870
|
-
const decide = input5.decideOnLimit;
|
|
4871
|
-
const sleepFn = input5.sleep ?? defaultSleep;
|
|
5333
|
+
const iterProxy = { current: 0 };
|
|
4872
5334
|
const limitCtx = (kind) => ({
|
|
4873
5335
|
kind,
|
|
4874
5336
|
budgetSnapshot: input5.budget.snapshot(),
|
|
4875
|
-
iterations,
|
|
4876
|
-
maxIterations:
|
|
4877
|
-
maxOutputTokens:
|
|
5337
|
+
iterations: iterProxy.current,
|
|
5338
|
+
maxIterations: input5.maxIterations ?? DEFAULT_PLANNER_MAX_ITERATIONS,
|
|
5339
|
+
maxOutputTokens: input5.maxOutputTokens ?? DEFAULT_PLANNER_MAX_OUTPUT_TOKENS
|
|
4878
5340
|
});
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
return {
|
|
4917
|
-
planText: accumulatedText,
|
|
4918
|
-
budgetExhausted,
|
|
4919
|
-
timedOut: true,
|
|
4920
|
-
finishedNormally: false,
|
|
4921
|
-
iterations,
|
|
4922
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt),
|
|
4923
|
-
incompleteKind: "wall_clock",
|
|
4924
|
-
userCancelled: true
|
|
4925
|
-
};
|
|
4926
|
-
}
|
|
4927
|
-
extendAllSessionLimits(input5, maxIter, maxOut, baseMaxIter, baseMaxOut);
|
|
4928
|
-
iterations -= 1;
|
|
4929
|
-
continue;
|
|
4930
|
-
}
|
|
4931
|
-
return {
|
|
4932
|
-
planText: accumulatedText,
|
|
4933
|
-
budgetExhausted,
|
|
4934
|
-
timedOut: true,
|
|
4935
|
-
finishedNormally: false,
|
|
4936
|
-
iterations,
|
|
4937
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt)
|
|
4938
|
-
};
|
|
4939
|
-
}
|
|
4940
|
-
if (iterations >= 2) {
|
|
4941
|
-
bus.emit({ kind: "turn_started", runId, turn: iterations });
|
|
4942
|
-
}
|
|
4943
|
-
const tools = buildPlannerTools({
|
|
5341
|
+
let budgetExhausted = false;
|
|
5342
|
+
const toolDefs = input5.runtime.kind === "agent-sdk" ? buildPlannerToolDefinitions({
|
|
5343
|
+
root: input5.root,
|
|
5344
|
+
budget: input5.budget,
|
|
5345
|
+
enabled: input5.toolsEnabled,
|
|
5346
|
+
getTurn: () => Math.max(1, iterProxy.current),
|
|
5347
|
+
runId,
|
|
5348
|
+
bus,
|
|
5349
|
+
onToolCall: input5.onToolCall,
|
|
5350
|
+
decideOnLimit: input5.decideOnLimit,
|
|
5351
|
+
getLimitCtx: () => limitCtx("file_or_context_reads"),
|
|
5352
|
+
extendSessionLimits: () => {
|
|
5353
|
+
input5.budget.extendSession();
|
|
5354
|
+
},
|
|
5355
|
+
getAccumulatedText: () => "",
|
|
5356
|
+
setBudgetExhausted: (v) => {
|
|
5357
|
+
budgetExhausted = v;
|
|
5358
|
+
}
|
|
5359
|
+
}) : [];
|
|
5360
|
+
const draft = await input5.runtime.runDraft({
|
|
5361
|
+
systemPrompt: input5.systemPrompt,
|
|
5362
|
+
userMessage: input5.userPrompt,
|
|
5363
|
+
tools: toolDefs,
|
|
5364
|
+
bus,
|
|
5365
|
+
runId,
|
|
5366
|
+
budget: input5.budget,
|
|
5367
|
+
abort: input5.abort,
|
|
5368
|
+
maxSteps: input5.maxIterations ?? DEFAULT_PLANNER_MAX_ITERATIONS,
|
|
5369
|
+
maxOutputTokens: input5.maxOutputTokens ?? DEFAULT_PLANNER_MAX_OUTPUT_TOKENS,
|
|
5370
|
+
providerSpecific: input5.anthropicProviderSpecific?.draft,
|
|
5371
|
+
onAssistantText: input5.onAssistantText,
|
|
5372
|
+
onToolCall: input5.onToolCall,
|
|
5373
|
+
onUsage: input5.onUsage,
|
|
5374
|
+
vercelLoop: input5.runtime instanceof VercelRuntime ? {
|
|
5375
|
+
model: input5.runtime.languageModel,
|
|
5376
|
+
provider: input5.provider,
|
|
5377
|
+
modelId: input5.modelId,
|
|
4944
5378
|
root: input5.root,
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
const messages = turnsToCoreMessages(input5.systemPrompt, turns, {
|
|
4966
|
-
cacheEnabled,
|
|
4967
|
-
provider: input5.provider
|
|
4968
|
-
});
|
|
4969
|
-
bus.emit({ kind: "request_sent", runId, turn: iterations });
|
|
4970
|
-
let attempt = 0;
|
|
4971
|
-
let stepResult;
|
|
4972
|
-
let retrySkippedReason;
|
|
4973
|
-
let retriedRateLimit = false;
|
|
4974
|
-
const callStream = async () => {
|
|
4975
|
-
const { streamText } = await import("ai");
|
|
4976
|
-
const stream = streamText({
|
|
4977
|
-
model: input5.model,
|
|
4978
|
-
messages,
|
|
4979
|
-
tools,
|
|
4980
|
-
toolChoice: "auto",
|
|
4981
|
-
maxSteps: 1,
|
|
4982
|
-
maxRetries: 0,
|
|
4983
|
-
maxTokens: maxOut.current,
|
|
4984
|
-
abortSignal: input5.abort,
|
|
4985
|
-
onChunk: ({ chunk }) => {
|
|
4986
|
-
if (chunk.type === "text-delta") {
|
|
4987
|
-
accumulatedText += chunk.textDelta;
|
|
4988
|
-
input5.onAssistantText?.(chunk.textDelta);
|
|
4989
|
-
bus.emit({ kind: "assistant_text", runId, turn: iterations, delta: chunk.textDelta });
|
|
4990
|
-
}
|
|
4991
|
-
},
|
|
4992
|
-
onStepFinish: (step2) => {
|
|
4993
|
-
const u = usageFromLanguageModelStep(step2.usage, step2.providerMetadata);
|
|
4994
|
-
recordUsageAndEmit(bus, input5, runId, iterations, u);
|
|
4995
|
-
}
|
|
4996
|
-
});
|
|
4997
|
-
for await (const part of stream.fullStream) {
|
|
4998
|
-
const p = part;
|
|
4999
|
-
if (p.type === "error") {
|
|
5000
|
-
throw p.error;
|
|
5001
|
-
}
|
|
5002
|
-
}
|
|
5003
|
-
const steps = await stream.steps;
|
|
5004
|
-
const last = steps[steps.length - 1];
|
|
5005
|
-
if (!last) throw new Error("planner: empty model step");
|
|
5006
|
-
stepResult = {
|
|
5007
|
-
text: last.text,
|
|
5008
|
-
finishReason: last.finishReason,
|
|
5009
|
-
toolCalls: last.toolCalls.flatMap(
|
|
5010
|
-
(tc) => tc ? [
|
|
5011
|
-
{
|
|
5012
|
-
toolCallId: tc.toolCallId,
|
|
5013
|
-
toolName: tc.toolName,
|
|
5014
|
-
args: tc.args
|
|
5015
|
-
}
|
|
5016
|
-
] : []
|
|
5017
|
-
),
|
|
5018
|
-
toolResults: last.toolResults.flatMap(
|
|
5019
|
-
(tr) => tr ? [{ toolCallId: tr.toolCallId, result: tr.result }] : []
|
|
5020
|
-
),
|
|
5021
|
-
usage: last.usage,
|
|
5022
|
-
providerMetadata: last.providerMetadata
|
|
5023
|
-
};
|
|
5024
|
-
};
|
|
5025
|
-
try {
|
|
5026
|
-
while (true) {
|
|
5027
|
-
try {
|
|
5028
|
-
await callStream();
|
|
5029
|
-
break;
|
|
5030
|
-
} catch (e) {
|
|
5031
|
-
if (PlannerUserCancelledError.is(e)) {
|
|
5032
|
-
return {
|
|
5033
|
-
planText: e.planText,
|
|
5034
|
-
budgetExhausted: false,
|
|
5035
|
-
timedOut: false,
|
|
5036
|
-
finishedNormally: false,
|
|
5037
|
-
iterations,
|
|
5038
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt),
|
|
5039
|
-
incompleteKind: "budget_reads",
|
|
5040
|
-
userCancelled: true
|
|
5041
|
-
};
|
|
5042
|
-
}
|
|
5043
|
-
if (isAbortError(e)) {
|
|
5044
|
-
return cancelledOutput(input5, bus, runId, accumulatedText, budgetExhausted, iterations, runStartedAt);
|
|
5045
|
-
}
|
|
5046
|
-
if (APICallError2.isInstance(e)) {
|
|
5047
|
-
const synthetic = providerResponseFromApiError(input5.provider, input5.modelId, e);
|
|
5048
|
-
if (synthetic.errorKind === "rate_limit") {
|
|
5049
|
-
const asked = synthetic.retryAfterSec;
|
|
5050
|
-
const emitRateLimit = (phase, waitSec) => {
|
|
5051
|
-
bus.emit({
|
|
5052
|
-
kind: "rate_limit",
|
|
5053
|
-
runId,
|
|
5054
|
-
turn: iterations,
|
|
5055
|
-
retryAfterSec: synthetic.retryAfterSec,
|
|
5056
|
-
waitSec,
|
|
5057
|
-
capSec: MAX_RATE_LIMIT_RETRY_SEC,
|
|
5058
|
-
phase,
|
|
5059
|
-
provider: input5.provider,
|
|
5060
|
-
rawBody: (synthetic.rawError ?? "").slice(0, 200)
|
|
5061
|
-
});
|
|
5062
|
-
};
|
|
5063
|
-
if (asked !== void 0 && asked > MAX_RATE_LIMIT_RETRY_SEC) {
|
|
5064
|
-
emitRateLimit("aborted", asked);
|
|
5065
|
-
retrySkippedReason = "retry_after_too_long";
|
|
5066
|
-
throw composePlannerError(input5.provider, synthetic, false, retrySkippedReason);
|
|
5067
|
-
}
|
|
5068
|
-
if (attempt === 0) {
|
|
5069
|
-
const waitSec = Math.min(asked ?? 10, MAX_RATE_LIMIT_RETRY_SEC);
|
|
5070
|
-
emitRateLimit("retrying", waitSec);
|
|
5071
|
-
input5.onRateLimit?.(waitSec);
|
|
5072
|
-
await sleepWithAbort(waitSec * 1e3, sleepFn, input5.abort);
|
|
5073
|
-
attempt += 1;
|
|
5074
|
-
retriedRateLimit = true;
|
|
5075
|
-
continue;
|
|
5076
|
-
}
|
|
5077
|
-
throw composePlannerError(input5.provider, synthetic, true, retrySkippedReason);
|
|
5078
|
-
}
|
|
5079
|
-
throw composePlannerError(input5.provider, synthetic, retriedRateLimit, retrySkippedReason);
|
|
5080
|
-
}
|
|
5081
|
-
throw e;
|
|
5082
|
-
}
|
|
5083
|
-
}
|
|
5084
|
-
} catch (e) {
|
|
5085
|
-
if (isAbortError(e)) {
|
|
5086
|
-
return cancelledOutput(input5, bus, runId, accumulatedText, budgetExhausted, iterations, runStartedAt);
|
|
5087
|
-
}
|
|
5088
|
-
throw e;
|
|
5089
|
-
}
|
|
5090
|
-
const finishReason = stepResult.finishReason;
|
|
5091
|
-
const toolCallsLen = stepResult.toolCalls.length;
|
|
5092
|
-
if (finishReason === "length" && toolCallsLen === 0) {
|
|
5093
|
-
bus.emit({ kind: "turn_complete", runId, turn: iterations, stopReason: "max_tokens" });
|
|
5094
|
-
if (decide) {
|
|
5095
|
-
const d = await decide(limitCtx("max_output_tokens"));
|
|
5096
|
-
if (d === "cancel") {
|
|
5097
|
-
return {
|
|
5098
|
-
planText: accumulatedText,
|
|
5099
|
-
budgetExhausted,
|
|
5100
|
-
timedOut: false,
|
|
5101
|
-
finishedNormally: false,
|
|
5102
|
-
iterations,
|
|
5103
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt),
|
|
5104
|
-
incompleteKind: "max_output_tokens",
|
|
5105
|
-
userCancelled: true
|
|
5106
|
-
};
|
|
5107
|
-
}
|
|
5108
|
-
extendAllSessionLimits(input5, maxIter, maxOut, baseMaxIter, baseMaxOut);
|
|
5109
|
-
applyStepToTurns(turns, stepResult);
|
|
5110
|
-
turns.push({ role: "user", text: PLANNER_MARKDOWN_CONTINUATION_USER });
|
|
5111
|
-
iterations -= 1;
|
|
5112
|
-
continue;
|
|
5113
|
-
}
|
|
5114
|
-
return {
|
|
5115
|
-
planText: accumulatedText,
|
|
5116
|
-
budgetExhausted,
|
|
5117
|
-
timedOut: false,
|
|
5118
|
-
finishedNormally: false,
|
|
5119
|
-
iterations,
|
|
5120
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt),
|
|
5121
|
-
incompleteKind: "max_output_tokens"
|
|
5122
|
-
};
|
|
5123
|
-
}
|
|
5124
|
-
if (finishReason === "error" || finishReason === "content-filter") {
|
|
5125
|
-
throw new Error(
|
|
5126
|
-
stepResult.text || `planner: model reported ${finishReason}. Run \`squad doctor\` to diagnose, or retry \u2014 most 5xx errors are transient.`
|
|
5127
|
-
);
|
|
5128
|
-
}
|
|
5129
|
-
if ((finishReason === "stop" || finishReason === "unknown") && toolCallsLen === 0) {
|
|
5130
|
-
bus.emit({ kind: "turn_complete", runId, turn: iterations, stopReason: finishReason });
|
|
5131
|
-
finishedNormally = true;
|
|
5132
|
-
return {
|
|
5133
|
-
planText: accumulatedText,
|
|
5134
|
-
budgetExhausted,
|
|
5135
|
-
timedOut: false,
|
|
5136
|
-
finishedNormally,
|
|
5137
|
-
iterations,
|
|
5138
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt)
|
|
5139
|
-
};
|
|
5140
|
-
}
|
|
5141
|
-
if (toolCallsLen === 0) {
|
|
5142
|
-
bus.emit({ kind: "turn_complete", runId, turn: iterations, stopReason: finishReason });
|
|
5143
|
-
finishedNormally = true;
|
|
5144
|
-
return {
|
|
5145
|
-
planText: accumulatedText,
|
|
5146
|
-
budgetExhausted,
|
|
5147
|
-
timedOut: false,
|
|
5148
|
-
finishedNormally,
|
|
5149
|
-
iterations,
|
|
5150
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt)
|
|
5151
|
-
};
|
|
5152
|
-
}
|
|
5153
|
-
applyStepToTurns(turns, stepResult);
|
|
5154
|
-
if (budgetExhausted) {
|
|
5155
|
-
turns.push({
|
|
5156
|
-
role: "user",
|
|
5157
|
-
text: "Budget is exhausted. Finalise the plan with the information you already have. Do not call any more tools. Output the complete plan markdown now."
|
|
5158
|
-
});
|
|
5159
|
-
}
|
|
5160
|
-
bus.emit({ kind: "turn_complete", runId, turn: iterations, stopReason: finishReason });
|
|
5161
|
-
}
|
|
5379
|
+
userPrompt: input5.userPrompt,
|
|
5380
|
+
scoutedSection: input5.scoutedSection,
|
|
5381
|
+
cacheEnabled: input5.cacheEnabled ?? true,
|
|
5382
|
+
toolsEnabled: input5.toolsEnabled,
|
|
5383
|
+
decideOnLimit: input5.decideOnLimit,
|
|
5384
|
+
sleep: input5.sleep,
|
|
5385
|
+
onRateLimit: input5.onRateLimit
|
|
5386
|
+
} : void 0
|
|
5387
|
+
});
|
|
5388
|
+
const incompleteKind = draft.incompleteKind;
|
|
5389
|
+
return {
|
|
5390
|
+
planText: draft.text,
|
|
5391
|
+
budgetExhausted: draft.budgetExhausted ?? budgetExhausted,
|
|
5392
|
+
timedOut: draft.timedOut ?? false,
|
|
5393
|
+
finishedNormally: draft.finishedNormally,
|
|
5394
|
+
iterations: draft.iterations,
|
|
5395
|
+
incompleteKind,
|
|
5396
|
+
stats: buildPlannerRunStats(input5.budget, draft.iterations, runStartedAt),
|
|
5397
|
+
userCancelled: draft.userCancelled
|
|
5398
|
+
};
|
|
5162
5399
|
}
|
|
5163
5400
|
async function runPlanner(input5) {
|
|
5164
5401
|
const bus = input5.events ?? new PlannerEventBus();
|
|
@@ -5169,7 +5406,8 @@ async function runPlanner(input5) {
|
|
|
5169
5406
|
runId,
|
|
5170
5407
|
provider: input5.provider,
|
|
5171
5408
|
model: input5.modelId,
|
|
5172
|
-
cacheEnabled
|
|
5409
|
+
cacheEnabled,
|
|
5410
|
+
plannerRuntime: input5.runtime.kind
|
|
5173
5411
|
});
|
|
5174
5412
|
const merged = { ...input5, events: bus, runId };
|
|
5175
5413
|
const systemPrompt = input5.systemPrompt;
|
|
@@ -5177,11 +5415,11 @@ async function runPlanner(input5) {
|
|
|
5177
5415
|
let scoutStats;
|
|
5178
5416
|
let scoutedSection;
|
|
5179
5417
|
const scoutOn = input5.stages?.scout?.enabled !== false;
|
|
5180
|
-
const
|
|
5418
|
+
const scoutRuntime = input5.stages?.scout?.runtime;
|
|
5181
5419
|
const maxScoutFiles = input5.stages?.scout?.maxFiles ?? 12;
|
|
5182
5420
|
const scoutMaxOut = input5.stages?.scout?.maxOutputTokens ?? 2048;
|
|
5183
5421
|
const scoutModelIdStr = input5.stages?.scout?.modelId ?? "";
|
|
5184
|
-
if (scoutOn &&
|
|
5422
|
+
if (scoutOn && scoutRuntime) {
|
|
5185
5423
|
if (!input5.scoutSystemPrompt?.trim()) {
|
|
5186
5424
|
throw new Error(
|
|
5187
5425
|
"planner: scout enabled but `scoutSystemPrompt` was not provided. Compose it via composeScoutSystemPrompt() or set stages.scout.enabled = false."
|
|
@@ -5191,12 +5429,15 @@ async function runPlanner(input5) {
|
|
|
5191
5429
|
bus.emit({ kind: "stage_started", runId, stage: "scout" });
|
|
5192
5430
|
try {
|
|
5193
5431
|
const scoutRes = await runScout({
|
|
5194
|
-
|
|
5432
|
+
runtime: scoutRuntime,
|
|
5195
5433
|
systemPrompt: input5.scoutSystemPrompt,
|
|
5196
5434
|
userPrompt: input5.userPrompt,
|
|
5197
5435
|
budget: input5.budget,
|
|
5436
|
+
bus,
|
|
5437
|
+
runId,
|
|
5198
5438
|
abort: input5.abort,
|
|
5199
|
-
maxTokens: scoutMaxOut
|
|
5439
|
+
maxTokens: scoutMaxOut,
|
|
5440
|
+
providerSpecific: input5.anthropicProviderSpecific?.scout
|
|
5200
5441
|
});
|
|
5201
5442
|
const durationMs = Date.now() - t0;
|
|
5202
5443
|
if (scoutRes) {
|
|
@@ -5242,7 +5483,7 @@ async function runPlanner(input5) {
|
|
|
5242
5483
|
}
|
|
5243
5484
|
} catch (err2) {
|
|
5244
5485
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
5245
|
-
if (
|
|
5486
|
+
if (APICallError3.isInstance(err2)) {
|
|
5246
5487
|
const status = err2.statusCode ?? 0;
|
|
5247
5488
|
if (status === 401 || status === 403) {
|
|
5248
5489
|
throw err2;
|
|
@@ -5306,25 +5547,6 @@ async function runPlanner(input5) {
|
|
|
5306
5547
|
validation: validationOn ? { issues, durationMs: validationDurationMs } : { issues: [], durationMs: 0 }
|
|
5307
5548
|
};
|
|
5308
5549
|
}
|
|
5309
|
-
function composePlannerError(providerName, response, retriedRateLimit, retrySkippedReason) {
|
|
5310
|
-
if (response.errorKind === "rate_limit") {
|
|
5311
|
-
return new Error(
|
|
5312
|
-
rateLimitMessage({
|
|
5313
|
-
provider: providerName,
|
|
5314
|
-
retryAfterSec: response.retryAfterSec,
|
|
5315
|
-
rawBody: response.rawError ?? "",
|
|
5316
|
-
retryAlreadyAttempted: retriedRateLimit,
|
|
5317
|
-
retrySkippedReason,
|
|
5318
|
-
maxRetrySec: MAX_RATE_LIMIT_RETRY_SEC
|
|
5319
|
-
})
|
|
5320
|
-
);
|
|
5321
|
-
}
|
|
5322
|
-
if (response.errorKind === "model_not_found") {
|
|
5323
|
-
return new Error(response.rawError ?? "planner: model not found");
|
|
5324
|
-
}
|
|
5325
|
-
const base = response.rawError ?? "planner: provider error";
|
|
5326
|
-
return new Error(`${base} Run \`squad doctor\` to diagnose, or retry \u2014 most 5xx errors are transient.`);
|
|
5327
|
-
}
|
|
5328
5550
|
|
|
5329
5551
|
// src/planner/planner-limit-messages.ts
|
|
5330
5552
|
function printPlannerApiCostNotice() {
|
|
@@ -5398,19 +5620,260 @@ function printPlannerLimitNextSteps() {
|
|
|
5398
5620
|
blank();
|
|
5399
5621
|
}
|
|
5400
5622
|
|
|
5401
|
-
// src/planner/
|
|
5402
|
-
import {
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
function
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5623
|
+
// src/planner/runtimes/agent-sdk-runtime.ts
|
|
5624
|
+
import { z as z5 } from "zod";
|
|
5625
|
+
|
|
5626
|
+
// src/planner/runtimes/anthropic-options.ts
|
|
5627
|
+
function extractAnthropicProviderSpecific(cfg, phase, cliOverrides) {
|
|
5628
|
+
const opts = cfg.providerOptions?.anthropic;
|
|
5629
|
+
const phaseEffort = opts?.effortByPhase?.[phase];
|
|
5630
|
+
const baseEffort = opts?.effort ?? "medium";
|
|
5631
|
+
const effort = cliOverrides?.effort ?? phaseEffort ?? baseEffort;
|
|
5632
|
+
return {
|
|
5633
|
+
thinking: cliOverrides?.thinking === "off" ? "off" : opts?.thinking ?? "adaptive",
|
|
5634
|
+
thinkingBudget: opts?.thinkingBudget,
|
|
5635
|
+
effort
|
|
5636
|
+
};
|
|
5637
|
+
}
|
|
5638
|
+
function sdkEffortFromPlanner(effort) {
|
|
5639
|
+
if (effort === "minimal") return "low";
|
|
5640
|
+
if (effort === "high") return "high";
|
|
5641
|
+
return "medium";
|
|
5642
|
+
}
|
|
5643
|
+
function thinkingConfigFromProviderSpecific(ps) {
|
|
5644
|
+
if (!ps || ps.thinking === "off") {
|
|
5645
|
+
return { type: "disabled" };
|
|
5646
|
+
}
|
|
5647
|
+
if (ps.thinking === "disabled") {
|
|
5648
|
+
return { type: "disabled" };
|
|
5649
|
+
}
|
|
5650
|
+
if (ps.thinking === "enabled") {
|
|
5651
|
+
return { type: "enabled", budgetTokens: ps.thinkingBudget ?? 2048 };
|
|
5652
|
+
}
|
|
5653
|
+
return { type: ps.thinking ?? "adaptive" };
|
|
5654
|
+
}
|
|
5655
|
+
|
|
5656
|
+
// src/planner/runtimes/agent-sdk-runtime.ts
|
|
5657
|
+
var BUILTIN_TOOL_DENY = [
|
|
5658
|
+
"Read",
|
|
5659
|
+
"Write",
|
|
5660
|
+
"Edit",
|
|
5661
|
+
"Bash",
|
|
5662
|
+
"Grep",
|
|
5663
|
+
"Glob",
|
|
5664
|
+
"NotebookEdit",
|
|
5665
|
+
"WebFetch",
|
|
5666
|
+
"WebSearch"
|
|
5667
|
+
];
|
|
5668
|
+
function streamEventTextDelta(ev) {
|
|
5669
|
+
if (!ev || typeof ev !== "object") return "";
|
|
5670
|
+
const o = ev;
|
|
5671
|
+
if (o.type === "content_block_delta") {
|
|
5672
|
+
const d = o.delta;
|
|
5673
|
+
if (d && d.type === "text_delta" && typeof d.text === "string") return d.text;
|
|
5674
|
+
}
|
|
5675
|
+
return "";
|
|
5676
|
+
}
|
|
5677
|
+
var AgentSdkRuntime = class {
|
|
5678
|
+
constructor(modelId, apiKey) {
|
|
5679
|
+
this.modelId = modelId;
|
|
5680
|
+
this.apiKey = apiKey;
|
|
5681
|
+
}
|
|
5682
|
+
modelId;
|
|
5683
|
+
apiKey;
|
|
5684
|
+
kind = "agent-sdk";
|
|
5685
|
+
providerName = "anthropic";
|
|
5686
|
+
async runDraft(input5) {
|
|
5687
|
+
const { query, createSdkMcpServer, tool: tool5 } = await import("@anthropic-ai/claude-agent-sdk");
|
|
5688
|
+
const sdkTools = input5.tools.map(
|
|
5689
|
+
(t) => tool5(t.name, t.description, this.zodToFieldsObject(t.parameters), async (args) => {
|
|
5690
|
+
const r = await t.execute(args);
|
|
5691
|
+
if (typeof r === "string") return { content: [{ type: "text", text: r }] };
|
|
5692
|
+
return { content: [{ type: "text", text: r.content }], isError: r.isError };
|
|
5693
|
+
})
|
|
5694
|
+
);
|
|
5695
|
+
const mcpServer = createSdkMcpServer({
|
|
5696
|
+
name: "squad-kit-planner",
|
|
5697
|
+
version: "1.0.0",
|
|
5698
|
+
tools: sdkTools
|
|
5699
|
+
});
|
|
5700
|
+
const ps = input5.providerSpecific;
|
|
5701
|
+
const thinking = thinkingConfigFromProviderSpecific(ps);
|
|
5702
|
+
const effort = ps?.effort !== void 0 ? sdkEffortFromPlanner(ps.effort) : sdkEffortFromPlanner("medium");
|
|
5703
|
+
const ac = new AbortController();
|
|
5704
|
+
const onAbort = () => ac.abort();
|
|
5705
|
+
if (input5.abort) {
|
|
5706
|
+
if (input5.abort.aborted) ac.abort();
|
|
5707
|
+
else input5.abort.addEventListener("abort", onAbort, { once: true });
|
|
5708
|
+
}
|
|
5709
|
+
const queryOptions = {
|
|
5710
|
+
model: this.modelId,
|
|
5711
|
+
tools: [],
|
|
5712
|
+
disallowedTools: [...BUILTIN_TOOL_DENY],
|
|
5713
|
+
mcpServers: { "squad-kit-planner": mcpServer },
|
|
5714
|
+
systemPrompt: input5.systemPrompt,
|
|
5715
|
+
maxTurns: input5.maxSteps,
|
|
5716
|
+
includePartialMessages: true,
|
|
5717
|
+
persistSession: false,
|
|
5718
|
+
settingSources: [],
|
|
5719
|
+
abortController: ac,
|
|
5720
|
+
env: { ...process.env, ANTHROPIC_API_KEY: this.apiKey },
|
|
5721
|
+
thinking,
|
|
5722
|
+
effort
|
|
5723
|
+
};
|
|
5724
|
+
let text = "";
|
|
5725
|
+
let assistantTurns = 0;
|
|
5726
|
+
let finalUsage = { inputTokens: 0, outputTokens: 0 };
|
|
5727
|
+
let finishedNormally = false;
|
|
5728
|
+
let incompleteKind;
|
|
5729
|
+
let userCancelled = false;
|
|
5730
|
+
try {
|
|
5731
|
+
const q = query({
|
|
5732
|
+
prompt: input5.userMessage,
|
|
5733
|
+
options: queryOptions
|
|
5734
|
+
});
|
|
5735
|
+
for await (const message of q) {
|
|
5736
|
+
const m = message;
|
|
5737
|
+
switch (m.type) {
|
|
5738
|
+
case "stream_event": {
|
|
5739
|
+
const delta = streamEventTextDelta(m.event);
|
|
5740
|
+
if (delta) {
|
|
5741
|
+
text += delta;
|
|
5742
|
+
input5.onAssistantText?.(delta);
|
|
5743
|
+
input5.bus.emit({ kind: "assistant_text", runId: input5.runId, turn: assistantTurns, delta });
|
|
5744
|
+
}
|
|
5745
|
+
break;
|
|
5746
|
+
}
|
|
5747
|
+
case "assistant": {
|
|
5748
|
+
assistantTurns += 1;
|
|
5749
|
+
input5.bus.emit({
|
|
5750
|
+
kind: "turn_complete",
|
|
5751
|
+
runId: input5.runId,
|
|
5752
|
+
turn: assistantTurns,
|
|
5753
|
+
stopReason: "tool_use_or_text"
|
|
5754
|
+
});
|
|
5755
|
+
break;
|
|
5756
|
+
}
|
|
5757
|
+
case "result": {
|
|
5758
|
+
if (typeof m.result === "string" && m.result && !text) text = m.result;
|
|
5759
|
+
finalUsage = usageFromAgentSdkResult(
|
|
5760
|
+
m.usage
|
|
5761
|
+
);
|
|
5762
|
+
const sub = m.subtype;
|
|
5763
|
+
if (sub === "success") {
|
|
5764
|
+
finishedNormally = true;
|
|
5765
|
+
} else if (sub === "error_max_turns") {
|
|
5766
|
+
incompleteKind = "max_iterations";
|
|
5767
|
+
finishedNormally = false;
|
|
5768
|
+
} else if (typeof sub === "string" && sub.startsWith("error")) {
|
|
5769
|
+
finishedNormally = false;
|
|
5770
|
+
}
|
|
5771
|
+
break;
|
|
5772
|
+
}
|
|
5773
|
+
default:
|
|
5774
|
+
break;
|
|
5775
|
+
}
|
|
5776
|
+
}
|
|
5777
|
+
} catch (e) {
|
|
5778
|
+
if (e instanceof Error && (e.name === "AbortError" || ac.signal.aborted)) {
|
|
5779
|
+
userCancelled = true;
|
|
5780
|
+
input5.bus.emit({ kind: "cancelled", runId: input5.runId });
|
|
5781
|
+
} else {
|
|
5782
|
+
throw e;
|
|
5783
|
+
}
|
|
5784
|
+
} finally {
|
|
5785
|
+
if (input5.abort) input5.abort.removeEventListener("abort", onAbort);
|
|
5786
|
+
}
|
|
5787
|
+
input5.budget.recordUsage(finalUsage);
|
|
5788
|
+
input5.onUsage?.(finalUsage);
|
|
5789
|
+
input5.bus.emit({ kind: "usage", runId: input5.runId, turn: assistantTurns, usage: finalUsage });
|
|
5790
|
+
return {
|
|
5791
|
+
text,
|
|
5792
|
+
finishedNormally: userCancelled ? false : finishedNormally,
|
|
5793
|
+
iterations: assistantTurns,
|
|
5794
|
+
incompleteKind,
|
|
5795
|
+
finalUsage,
|
|
5796
|
+
userCancelled: userCancelled || void 0
|
|
5797
|
+
};
|
|
5798
|
+
}
|
|
5799
|
+
async runScout(input5) {
|
|
5800
|
+
const { query, createSdkMcpServer, tool: tool5 } = await import("@anthropic-ai/claude-agent-sdk");
|
|
5801
|
+
let scoutOutput = null;
|
|
5802
|
+
const schemaFields = this.zodToFieldsObject(input5.schema);
|
|
5803
|
+
const respondTool = tool5(
|
|
5804
|
+
"respond_with_scout_result",
|
|
5805
|
+
"Respond with the scout selection by calling this tool exactly once. Do not call any other tool.",
|
|
5806
|
+
schemaFields,
|
|
5807
|
+
async (args) => {
|
|
5808
|
+
scoutOutput = args;
|
|
5809
|
+
return { content: [{ type: "text", text: "OK \u2014 scout result captured." }] };
|
|
5810
|
+
}
|
|
5811
|
+
);
|
|
5812
|
+
const mcpServer = createSdkMcpServer({
|
|
5813
|
+
name: "squad-kit-scout",
|
|
5814
|
+
version: "1.0.0",
|
|
5815
|
+
tools: [respondTool]
|
|
5816
|
+
});
|
|
5817
|
+
const ps = input5.providerSpecific;
|
|
5818
|
+
const thinking = thinkingConfigFromProviderSpecific(ps);
|
|
5819
|
+
const effort = ps?.effort !== void 0 ? sdkEffortFromPlanner(ps.effort) : sdkEffortFromPlanner("minimal");
|
|
5820
|
+
const queryOptions = {
|
|
5821
|
+
model: this.modelId,
|
|
5822
|
+
tools: [],
|
|
5823
|
+
disallowedTools: [...BUILTIN_TOOL_DENY],
|
|
5824
|
+
mcpServers: { "squad-kit-scout": mcpServer },
|
|
5825
|
+
systemPrompt: input5.systemPrompt + "\n\nIMPORTANT: respond by calling the `respond_with_scout_result` tool with the JSON object \u2014 do not write the JSON as text.",
|
|
5826
|
+
maxTurns: 2,
|
|
5827
|
+
persistSession: false,
|
|
5828
|
+
settingSources: [],
|
|
5829
|
+
env: { ...process.env, ANTHROPIC_API_KEY: this.apiKey },
|
|
5830
|
+
thinking,
|
|
5831
|
+
effort
|
|
5832
|
+
};
|
|
5833
|
+
let usage = { inputTokens: 0, outputTokens: 0 };
|
|
5834
|
+
const ac = new AbortController();
|
|
5835
|
+
const onAbort = () => ac.abort();
|
|
5836
|
+
if (input5.abort) {
|
|
5837
|
+
if (input5.abort.aborted) ac.abort();
|
|
5838
|
+
else input5.abort.addEventListener("abort", onAbort, { once: true });
|
|
5839
|
+
}
|
|
5840
|
+
try {
|
|
5841
|
+
const q = query({
|
|
5842
|
+
prompt: input5.userMessage,
|
|
5843
|
+
options: { ...queryOptions, abortController: ac }
|
|
5844
|
+
});
|
|
5845
|
+
for await (const message of q) {
|
|
5846
|
+
const m = message;
|
|
5847
|
+
if (m.type === "result") {
|
|
5848
|
+
usage = usageFromAgentSdkResult(
|
|
5849
|
+
m.usage
|
|
5850
|
+
);
|
|
5851
|
+
}
|
|
5852
|
+
}
|
|
5853
|
+
} finally {
|
|
5854
|
+
if (input5.abort) input5.abort.removeEventListener("abort", onAbort);
|
|
5855
|
+
}
|
|
5856
|
+
if (!scoutOutput) return null;
|
|
5857
|
+
const parsed = input5.schema.safeParse(scoutOutput);
|
|
5858
|
+
if (!parsed.success) return null;
|
|
5859
|
+
return { output: parsed.data, usage };
|
|
5860
|
+
}
|
|
5861
|
+
zodToFieldsObject(schema) {
|
|
5862
|
+
if (schema instanceof z5.ZodObject) {
|
|
5863
|
+
return schema.shape;
|
|
5864
|
+
}
|
|
5865
|
+
return { payload: schema };
|
|
5866
|
+
}
|
|
5867
|
+
};
|
|
5868
|
+
|
|
5869
|
+
// src/planner/runtimes/index.ts
|
|
5870
|
+
function resolveRuntime(input5) {
|
|
5871
|
+
if (input5.provider === "anthropic") {
|
|
5872
|
+
const choice = input5.anthropicRuntime ?? "agent-sdk";
|
|
5873
|
+
if (choice === "agent-sdk") return new AgentSdkRuntime(input5.modelId, input5.apiKey);
|
|
5874
|
+
return new VercelRuntime("anthropic", input5.modelId, input5.apiKey);
|
|
5413
5875
|
}
|
|
5876
|
+
return new VercelRuntime(input5.provider, input5.modelId, input5.apiKey);
|
|
5414
5877
|
}
|
|
5415
5878
|
|
|
5416
5879
|
// src/planner/budget.ts
|
|
@@ -5660,6 +6123,9 @@ function formatPlannerCacheLine(opts) {
|
|
|
5660
6123
|
if (!opts.cacheEnabled) {
|
|
5661
6124
|
return "cache disabled";
|
|
5662
6125
|
}
|
|
6126
|
+
if (opts.plannerRuntimeKind === "agent-sdk") {
|
|
6127
|
+
return "(agent-sdk: not exposed)";
|
|
6128
|
+
}
|
|
5663
6129
|
const { stats } = opts;
|
|
5664
6130
|
if (stats.cacheReadTokens > 0) {
|
|
5665
6131
|
const pct = Math.round(stats.cacheHitRatio * 100);
|
|
@@ -5847,12 +6313,33 @@ async function emitViaApi(story, paths, config2, opts) {
|
|
|
5847
6313
|
);
|
|
5848
6314
|
}
|
|
5849
6315
|
const modelId = modelFor(planner.provider, "plan", planner.modelOverride);
|
|
5850
|
-
const
|
|
6316
|
+
const anthropicRuntimeChoice = opts.anthropicRuntime ?? planner.runtime?.anthropic ?? "agent-sdk";
|
|
6317
|
+
const draftRuntime = resolveRuntime({
|
|
6318
|
+
provider: planner.provider,
|
|
6319
|
+
modelId,
|
|
6320
|
+
apiKey,
|
|
6321
|
+
anthropicRuntime: planner.provider === "anthropic" ? anthropicRuntimeChoice : void 0
|
|
6322
|
+
});
|
|
6323
|
+
const thinkingCli = opts.noThinking ? "off" : void 0;
|
|
6324
|
+
const anthropicProviderSpecific = planner.provider === "anthropic" ? {
|
|
6325
|
+
draft: extractAnthropicProviderSpecific(planner, "draft", {
|
|
6326
|
+
effort: opts.effort,
|
|
6327
|
+
thinking: thinkingCli
|
|
6328
|
+
}),
|
|
6329
|
+
scout: extractAnthropicProviderSpecific(planner, "scout", {
|
|
6330
|
+
effort: opts.scoutEffort,
|
|
6331
|
+
thinking: thinkingCli
|
|
6332
|
+
})
|
|
6333
|
+
} : void 0;
|
|
5851
6334
|
const budget = new Budget(planner.budget);
|
|
5852
6335
|
banner();
|
|
5853
6336
|
step(`planning ${story.feature} / ${story.id}`);
|
|
5854
6337
|
kv("provider", planner.provider);
|
|
5855
6338
|
kv("model", modelId);
|
|
6339
|
+
kv(
|
|
6340
|
+
"runtime",
|
|
6341
|
+
`${draftRuntime.kind} \xB7 ${draftRuntime.providerName}` + (planner.provider === "anthropic" ? ` (${anthropicRuntimeChoice})` : "")
|
|
6342
|
+
);
|
|
5856
6343
|
kv(
|
|
5857
6344
|
"budget",
|
|
5858
6345
|
`${planner.budget.maxFileReads} reads \xB7 ${(planner.budget.maxContextBytes / 1024).toFixed(0)} KB \xB7 ${planner.budget.maxDurationSeconds}s`
|
|
@@ -5880,7 +6367,7 @@ async function emitViaApi(story, paths, config2, opts) {
|
|
|
5880
6367
|
const strictValidation = !!opts.strictValidation || planner.validation?.strict === true;
|
|
5881
6368
|
const maxScoutFiles = opts.maxScoutFiles ?? planner.stages?.scout?.maxFiles ?? 12;
|
|
5882
6369
|
let scoutModelId = "";
|
|
5883
|
-
let
|
|
6370
|
+
let scoutRuntime;
|
|
5884
6371
|
let scoutSystemPrompt;
|
|
5885
6372
|
if (scoutEnabled) {
|
|
5886
6373
|
scoutModelId = modelFor(
|
|
@@ -5889,7 +6376,12 @@ async function emitViaApi(story, paths, config2, opts) {
|
|
|
5889
6376
|
planner.modelOverride,
|
|
5890
6377
|
opts.scoutModel ?? planner.stages?.scout?.modelOverride
|
|
5891
6378
|
);
|
|
5892
|
-
|
|
6379
|
+
scoutRuntime = resolveRuntime({
|
|
6380
|
+
provider: planner.provider,
|
|
6381
|
+
modelId: scoutModelId,
|
|
6382
|
+
apiKey,
|
|
6383
|
+
anthropicRuntime: planner.provider === "anthropic" ? anthropicRuntimeChoice : void 0
|
|
6384
|
+
});
|
|
5893
6385
|
scoutSystemPrompt = composeScoutSystemPrompt({
|
|
5894
6386
|
projectRoots: config2.project.projectRoots ?? ["."],
|
|
5895
6387
|
primaryLanguage: config2.project.primaryLanguage ?? "",
|
|
@@ -5978,9 +6470,10 @@ async function emitViaApi(story, paths, config2, opts) {
|
|
|
5978
6470
|
try {
|
|
5979
6471
|
result = await runPlanner({
|
|
5980
6472
|
root: paths.root,
|
|
5981
|
-
|
|
6473
|
+
runtime: draftRuntime,
|
|
5982
6474
|
provider: planner.provider,
|
|
5983
6475
|
modelId,
|
|
6476
|
+
anthropicProviderSpecific,
|
|
5984
6477
|
systemPrompt,
|
|
5985
6478
|
userPrompt,
|
|
5986
6479
|
budget,
|
|
@@ -5992,7 +6485,7 @@ async function emitViaApi(story, paths, config2, opts) {
|
|
|
5992
6485
|
stages: {
|
|
5993
6486
|
scout: {
|
|
5994
6487
|
enabled: scoutEnabled,
|
|
5995
|
-
|
|
6488
|
+
runtime: scoutRuntime,
|
|
5996
6489
|
modelId: scoutModelId,
|
|
5997
6490
|
maxFiles: maxScoutFiles,
|
|
5998
6491
|
maxOutputTokens: planner.stages?.scout?.maxOutputTokens ?? 2048
|
|
@@ -6071,7 +6564,9 @@ async function emitViaApi(story, paths, config2, opts) {
|
|
|
6071
6564
|
issuesCount: issueCount,
|
|
6072
6565
|
issuesByKind,
|
|
6073
6566
|
durationMs: result.validation?.durationMs
|
|
6074
|
-
}
|
|
6567
|
+
},
|
|
6568
|
+
plannerRuntime: { kind: draftRuntime.kind, provider: planner.provider },
|
|
6569
|
+
providerOptionsSnapshot: anthropicProviderSpecific ? { anthropic: anthropicProviderSpecific } : void 0
|
|
6075
6570
|
});
|
|
6076
6571
|
} catch {
|
|
6077
6572
|
}
|
|
@@ -6091,9 +6586,16 @@ async function emitViaApi(story, paths, config2, opts) {
|
|
|
6091
6586
|
{ key: "reads", value: `${snap.reads} files \xB7 ${(snap.bytes / 1024).toFixed(1)} KB` },
|
|
6092
6587
|
{
|
|
6093
6588
|
key: "tokens",
|
|
6094
|
-
value: `${snap.usage.inputTokens} in \xB7 ${snap.usage.outputTokens} out`
|
|
6589
|
+
value: `${snap.usage.inputTokens} in \xB7 ${snap.usage.outputTokens} out` + (draftRuntime.kind === "agent-sdk" ? " (agent-sdk: aggregate-only)" : "")
|
|
6590
|
+
},
|
|
6591
|
+
{
|
|
6592
|
+
key: "cache",
|
|
6593
|
+
value: formatPlannerCacheLine({
|
|
6594
|
+
cacheEnabled,
|
|
6595
|
+
stats: result.stats,
|
|
6596
|
+
plannerRuntimeKind: draftRuntime.kind
|
|
6597
|
+
})
|
|
6095
6598
|
},
|
|
6096
|
-
{ key: "cache", value: formatPlannerCacheLine({ cacheEnabled, stats: result.stats }) },
|
|
6097
6599
|
{
|
|
6098
6600
|
key: "max out",
|
|
6099
6601
|
value: `${planner.maxOutputTokens ?? DEFAULT_PLANNER_MAX_OUTPUT_TOKENS} tok/req`
|
|
@@ -6760,6 +7262,71 @@ async function checkPlannerCache(paths, ctx) {
|
|
|
6760
7262
|
detail: `caching active \u2014 last run ${hitPct}% hit (${formatTokenK(stats.cacheReadTokens)} read, ${lastRun.provider}/${lastRun.model})`
|
|
6761
7263
|
};
|
|
6762
7264
|
}
|
|
7265
|
+
async function checkPlannerRuntimeInfo(_paths, ctx) {
|
|
7266
|
+
if (!ctx.config?.planner?.enabled) {
|
|
7267
|
+
return {
|
|
7268
|
+
id: "planner-runtime-info",
|
|
7269
|
+
name: "planner runtime (resolved)",
|
|
7270
|
+
status: "skip",
|
|
7271
|
+
detail: "planner disabled"
|
|
7272
|
+
};
|
|
7273
|
+
}
|
|
7274
|
+
const p = ctx.config.planner;
|
|
7275
|
+
const anth = p.provider === "anthropic" ? p.runtime?.anthropic ?? "agent-sdk" : "vercel (openai/google use Vercel AI SDK)";
|
|
7276
|
+
return {
|
|
7277
|
+
id: "planner-runtime-info",
|
|
7278
|
+
name: "planner runtime (resolved)",
|
|
7279
|
+
status: "ok",
|
|
7280
|
+
detail: `${p.provider} \u2192 ${String(anth)}`
|
|
7281
|
+
};
|
|
7282
|
+
}
|
|
7283
|
+
function anthropicPlanModelLooksPost47(modelId) {
|
|
7284
|
+
return /opus[-_]4[-._]?7/i.test(modelId) || /claude-opus-4-7/i.test(modelId);
|
|
7285
|
+
}
|
|
7286
|
+
async function checkPlannerAnthropicRuntimeModelFit(_paths, ctx) {
|
|
7287
|
+
const id = "planner-anthropic-runtime-model-fit";
|
|
7288
|
+
const name = "Anthropic Opus 4.7+ vs Vercel runtime";
|
|
7289
|
+
if (!ctx.config?.planner?.enabled || ctx.config.planner.provider !== "anthropic") {
|
|
7290
|
+
return { id, name, status: "skip", detail: "not applicable" };
|
|
7291
|
+
}
|
|
7292
|
+
const p = ctx.config.planner;
|
|
7293
|
+
if ((p.runtime?.anthropic ?? "agent-sdk") !== "vercel") {
|
|
7294
|
+
return { id, name, status: "ok", detail: "using Agent SDK or default" };
|
|
7295
|
+
}
|
|
7296
|
+
const planModel = modelFor("anthropic", "plan", p.modelOverride);
|
|
7297
|
+
if (!anthropicPlanModelLooksPost47(planModel)) {
|
|
7298
|
+
return { id, name, status: "ok", detail: `${planModel} is fine on the Vercel runtime` };
|
|
7299
|
+
}
|
|
7300
|
+
return {
|
|
7301
|
+
id,
|
|
7302
|
+
name,
|
|
7303
|
+
status: "warn",
|
|
7304
|
+
detail: `Plan model ${planModel} needs the Agent SDK request shape; config uses Vercel runtime.`,
|
|
7305
|
+
fixHint: "Remove planner.runtime.anthropic: vercel (default is agent-sdk), or set planner.modelOverride.anthropic to a pre-4.7 id (e.g. claude-sonnet-4-5-20250929)."
|
|
7306
|
+
};
|
|
7307
|
+
}
|
|
7308
|
+
async function checkAgentSdkBinaryPresent(_paths, ctx) {
|
|
7309
|
+
const id = "agent-sdk-binary-present";
|
|
7310
|
+
const name = "Anthropic Agent SDK install";
|
|
7311
|
+
if (!ctx.config?.planner?.enabled || ctx.config.planner.provider !== "anthropic") {
|
|
7312
|
+
return { id, name, status: "skip", detail: "not applicable" };
|
|
7313
|
+
}
|
|
7314
|
+
if ((ctx.config.planner.runtime?.anthropic ?? "agent-sdk") === "vercel") {
|
|
7315
|
+
return { id, name, status: "skip", detail: "vercel runtime selected" };
|
|
7316
|
+
}
|
|
7317
|
+
try {
|
|
7318
|
+
await import("@anthropic-ai/claude-agent-sdk");
|
|
7319
|
+
return { id, name, status: "ok", detail: "package resolves" };
|
|
7320
|
+
} catch (e) {
|
|
7321
|
+
return {
|
|
7322
|
+
id,
|
|
7323
|
+
name,
|
|
7324
|
+
status: "warn",
|
|
7325
|
+
detail: e.message.slice(0, 160),
|
|
7326
|
+
fixHint: "Run pnpm install / npm install in the squad-kit package so the Agent SDK (and its platform binary) is present."
|
|
7327
|
+
};
|
|
7328
|
+
}
|
|
7329
|
+
}
|
|
6763
7330
|
async function checkTrackerConfig(_paths, ctx) {
|
|
6764
7331
|
if (!ctx.config) {
|
|
6765
7332
|
return { id: "tracker-config", name: "tracker configuration", status: "skip", detail: "config unavailable" };
|
|
@@ -6901,6 +7468,9 @@ async function runAllChecks(paths, ctx, fix) {
|
|
|
6901
7468
|
await add("planner model resolves at provider", () => checkPlannerModel(paths, ctx));
|
|
6902
7469
|
await add("planner tier vs. model", () => checkPlannerTierAwareness(paths, ctx));
|
|
6903
7470
|
await add("planner cache effectiveness", () => checkPlannerCache(paths, ctx));
|
|
7471
|
+
await add("planner runtime (resolved)", () => checkPlannerRuntimeInfo(paths, ctx));
|
|
7472
|
+
await add("Anthropic Opus 4.7+ vs Vercel runtime", () => checkPlannerAnthropicRuntimeModelFit(paths, ctx));
|
|
7473
|
+
await add("Anthropic Agent SDK install", () => checkAgentSdkBinaryPresent(paths, ctx));
|
|
6904
7474
|
await add("tracker configuration", () => checkTrackerConfig(paths, ctx));
|
|
6905
7475
|
await add("tracker credential resolves", () => checkTrackerCredential(paths, ctx));
|
|
6906
7476
|
await add("tracker connectivity", () => checkTrackerConnectivity(paths, ctx));
|
|
@@ -7355,7 +7925,7 @@ import { secureHeaders } from "hono/secure-headers";
|
|
|
7355
7925
|
|
|
7356
7926
|
// src/console/api/stories.ts
|
|
7357
7927
|
import fs37 from "fs";
|
|
7358
|
-
import { z as
|
|
7928
|
+
import { z as z6 } from "zod";
|
|
7359
7929
|
|
|
7360
7930
|
// src/utils/path-safety.ts
|
|
7361
7931
|
import path30 from "path";
|
|
@@ -7366,13 +7936,13 @@ function isInside(abs, parent) {
|
|
|
7366
7936
|
}
|
|
7367
7937
|
|
|
7368
7938
|
// src/console/api/stories.ts
|
|
7369
|
-
var CreateBody =
|
|
7370
|
-
feature:
|
|
7371
|
-
title:
|
|
7372
|
-
trackerId:
|
|
7939
|
+
var CreateBody = z6.object({
|
|
7940
|
+
feature: z6.string().min(1).max(80),
|
|
7941
|
+
title: z6.string().max(200).optional(),
|
|
7942
|
+
trackerId: z6.string().max(80).optional()
|
|
7373
7943
|
});
|
|
7374
|
-
var PatchBody =
|
|
7375
|
-
intakeContent:
|
|
7944
|
+
var PatchBody = z6.object({
|
|
7945
|
+
intakeContent: z6.string().min(1).max(1e6)
|
|
7376
7946
|
});
|
|
7377
7947
|
function safeFeatureParam(feature) {
|
|
7378
7948
|
return feature.length > 0 && !feature.includes("/") && !feature.includes("..") && slugify(feature) === feature;
|
|
@@ -7464,12 +8034,12 @@ function mountStoriesApi(app, opts) {
|
|
|
7464
8034
|
// src/console/api/plans.ts
|
|
7465
8035
|
import fs38 from "fs";
|
|
7466
8036
|
import path31 from "path";
|
|
7467
|
-
import { z as
|
|
8037
|
+
import { z as z7 } from "zod";
|
|
7468
8038
|
import { diffLines } from "diff";
|
|
7469
|
-
var DiffQuery =
|
|
7470
|
-
feature:
|
|
7471
|
-
a:
|
|
7472
|
-
b:
|
|
8039
|
+
var DiffQuery = z7.object({
|
|
8040
|
+
feature: z7.string().min(1).max(80),
|
|
8041
|
+
a: z7.string().min(1).max(200),
|
|
8042
|
+
b: z7.string().min(1).max(200)
|
|
7473
8043
|
});
|
|
7474
8044
|
function safeFeatureParam2(feature) {
|
|
7475
8045
|
return feature.length > 0 && !feature.includes("/") && !feature.includes("..") && slugify(feature) === feature;
|
|
@@ -7543,8 +8113,8 @@ function mountPlansApi(app, opts) {
|
|
|
7543
8113
|
}
|
|
7544
8114
|
|
|
7545
8115
|
// src/console/api/config.ts
|
|
7546
|
-
import { z as
|
|
7547
|
-
var ConfigBody =
|
|
8116
|
+
import { z as z8 } from "zod";
|
|
8117
|
+
var ConfigBody = z8.object({}).passthrough();
|
|
7548
8118
|
function mountConfigApi(app, opts) {
|
|
7549
8119
|
app.get("/api/config", (c) => {
|
|
7550
8120
|
const config2 = loadConfig(opts.paths.configFile);
|
|
@@ -7584,14 +8154,14 @@ function mountMetaApi(app, opts) {
|
|
|
7584
8154
|
|
|
7585
8155
|
// src/console/api/runs.ts
|
|
7586
8156
|
import { streamSSE } from "hono/streaming";
|
|
7587
|
-
import { z as
|
|
8157
|
+
import { z as z9 } from "zod";
|
|
7588
8158
|
import path32 from "path";
|
|
7589
8159
|
import fs39 from "fs";
|
|
7590
8160
|
var active = /* @__PURE__ */ new Map();
|
|
7591
8161
|
var EVENT_BUF_CAP = 512;
|
|
7592
|
-
var StartBody =
|
|
7593
|
-
feature:
|
|
7594
|
-
storyId:
|
|
8162
|
+
var StartBody = z9.object({
|
|
8163
|
+
feature: z9.string().min(1),
|
|
8164
|
+
storyId: z9.string().min(1)
|
|
7595
8165
|
});
|
|
7596
8166
|
function emptyStats(durationMs) {
|
|
7597
8167
|
return {
|
|
@@ -7648,7 +8218,17 @@ function mountRunsApi(app, opts) {
|
|
|
7648
8218
|
const cfgFresh = loadConfig(opts.paths.configFile);
|
|
7649
8219
|
const planner = cfgFresh.planner;
|
|
7650
8220
|
const modelId = modelFor(planner.provider, "plan", planner.modelOverride);
|
|
7651
|
-
const
|
|
8221
|
+
const anthropicRuntimeChoice = planner.runtime?.anthropic ?? "agent-sdk";
|
|
8222
|
+
const draftRuntime = resolveRuntime({
|
|
8223
|
+
provider: planner.provider,
|
|
8224
|
+
modelId,
|
|
8225
|
+
apiKey,
|
|
8226
|
+
anthropicRuntime: planner.provider === "anthropic" ? anthropicRuntimeChoice : void 0
|
|
8227
|
+
});
|
|
8228
|
+
const anthropicProviderSpecific = planner.provider === "anthropic" ? {
|
|
8229
|
+
draft: extractAnthropicProviderSpecific(planner, "draft"),
|
|
8230
|
+
scout: extractAnthropicProviderSpecific(planner, "scout")
|
|
8231
|
+
} : void 0;
|
|
7652
8232
|
const budget = new Budget(planner.budget);
|
|
7653
8233
|
const cacheEnabled = planner.cache?.enabled ?? true;
|
|
7654
8234
|
const scoutEnabled = planner.stages?.scout?.enabled !== false;
|
|
@@ -7656,7 +8236,7 @@ function mountRunsApi(app, opts) {
|
|
|
7656
8236
|
const strictValidation = planner.validation?.strict === true;
|
|
7657
8237
|
const maxScoutFiles = planner.stages?.scout?.maxFiles ?? 12;
|
|
7658
8238
|
let scoutModelId = "";
|
|
7659
|
-
let
|
|
8239
|
+
let scoutRuntime;
|
|
7660
8240
|
let scoutSystemPrompt;
|
|
7661
8241
|
if (scoutEnabled) {
|
|
7662
8242
|
scoutModelId = modelFor(
|
|
@@ -7665,7 +8245,12 @@ function mountRunsApi(app, opts) {
|
|
|
7665
8245
|
planner.modelOverride,
|
|
7666
8246
|
planner.stages?.scout?.modelOverride
|
|
7667
8247
|
);
|
|
7668
|
-
|
|
8248
|
+
scoutRuntime = resolveRuntime({
|
|
8249
|
+
provider: planner.provider,
|
|
8250
|
+
modelId: scoutModelId,
|
|
8251
|
+
apiKey,
|
|
8252
|
+
anthropicRuntime: planner.provider === "anthropic" ? anthropicRuntimeChoice : void 0
|
|
8253
|
+
});
|
|
7669
8254
|
}
|
|
7670
8255
|
const repoMap = buildRepoMap(opts.paths.root, { format: "tree" });
|
|
7671
8256
|
if (scoutEnabled) {
|
|
@@ -7690,9 +8275,10 @@ function mountRunsApi(app, opts) {
|
|
|
7690
8275
|
try {
|
|
7691
8276
|
result = await runPlanner({
|
|
7692
8277
|
root: opts.paths.root,
|
|
7693
|
-
|
|
8278
|
+
runtime: draftRuntime,
|
|
7694
8279
|
provider: planner.provider,
|
|
7695
8280
|
modelId,
|
|
8281
|
+
anthropicProviderSpecific,
|
|
7696
8282
|
systemPrompt,
|
|
7697
8283
|
userPrompt,
|
|
7698
8284
|
budget,
|
|
@@ -7704,7 +8290,7 @@ function mountRunsApi(app, opts) {
|
|
|
7704
8290
|
stages: {
|
|
7705
8291
|
scout: {
|
|
7706
8292
|
enabled: scoutEnabled,
|
|
7707
|
-
|
|
8293
|
+
runtime: scoutRuntime,
|
|
7708
8294
|
modelId: scoutModelId,
|
|
7709
8295
|
maxFiles: maxScoutFiles,
|
|
7710
8296
|
maxOutputTokens: planner.stages?.scout?.maxOutputTokens ?? 2048
|
|
@@ -7794,7 +8380,9 @@ function mountRunsApi(app, opts) {
|
|
|
7794
8380
|
issuesCount: issueCount,
|
|
7795
8381
|
issuesByKind,
|
|
7796
8382
|
durationMs: result.validation?.durationMs
|
|
7797
|
-
}
|
|
8383
|
+
},
|
|
8384
|
+
plannerRuntime: { kind: draftRuntime.kind, provider: planner.provider },
|
|
8385
|
+
providerOptionsSnapshot: anthropicProviderSpecific ? { anthropic: anthropicProviderSpecific } : void 0
|
|
7798
8386
|
});
|
|
7799
8387
|
} catch {
|
|
7800
8388
|
}
|
|
@@ -7867,7 +8455,7 @@ function mountRunsApi(app, opts) {
|
|
|
7867
8455
|
}
|
|
7868
8456
|
|
|
7869
8457
|
// src/console/api/secrets.ts
|
|
7870
|
-
import { z as
|
|
8458
|
+
import { z as z10 } from "zod";
|
|
7871
8459
|
|
|
7872
8460
|
// src/core/mask-token.ts
|
|
7873
8461
|
function maskToken(t) {
|
|
@@ -7905,26 +8493,26 @@ function mountSecretsApi(app, opts) {
|
|
|
7905
8493
|
}
|
|
7906
8494
|
});
|
|
7907
8495
|
});
|
|
7908
|
-
const PutBody =
|
|
7909
|
-
planner:
|
|
7910
|
-
anthropic:
|
|
7911
|
-
openai:
|
|
7912
|
-
google:
|
|
8496
|
+
const PutBody = z10.object({
|
|
8497
|
+
planner: z10.object({
|
|
8498
|
+
anthropic: z10.string().optional(),
|
|
8499
|
+
openai: z10.string().optional(),
|
|
8500
|
+
google: z10.string().optional()
|
|
7913
8501
|
}).optional(),
|
|
7914
|
-
tracker:
|
|
7915
|
-
jira:
|
|
7916
|
-
host:
|
|
7917
|
-
email:
|
|
7918
|
-
token:
|
|
8502
|
+
tracker: z10.object({
|
|
8503
|
+
jira: z10.object({
|
|
8504
|
+
host: z10.string().optional(),
|
|
8505
|
+
email: z10.string().optional(),
|
|
8506
|
+
token: z10.string().optional()
|
|
7919
8507
|
}).optional(),
|
|
7920
|
-
azure:
|
|
7921
|
-
organization:
|
|
7922
|
-
project:
|
|
7923
|
-
pat:
|
|
8508
|
+
azure: z10.object({
|
|
8509
|
+
organization: z10.string().optional(),
|
|
8510
|
+
project: z10.string().optional(),
|
|
8511
|
+
pat: z10.string().optional()
|
|
7924
8512
|
}).optional(),
|
|
7925
|
-
github:
|
|
7926
|
-
host:
|
|
7927
|
-
pat:
|
|
8513
|
+
github: z10.object({
|
|
8514
|
+
host: z10.string().optional(),
|
|
8515
|
+
pat: z10.string().optional()
|
|
7928
8516
|
}).optional()
|
|
7929
8517
|
}).optional()
|
|
7930
8518
|
});
|
|
@@ -7964,12 +8552,12 @@ function mountSecretsApi(app, opts) {
|
|
|
7964
8552
|
}
|
|
7965
8553
|
|
|
7966
8554
|
// src/console/api/tracker.ts
|
|
7967
|
-
import { z as
|
|
7968
|
-
var SearchQuery =
|
|
7969
|
-
var ImportBody =
|
|
7970
|
-
issueId:
|
|
7971
|
-
feature:
|
|
7972
|
-
withAttachments:
|
|
8555
|
+
import { z as z11 } from "zod";
|
|
8556
|
+
var SearchQuery = z11.object({ q: z11.string().max(500) });
|
|
8557
|
+
var ImportBody = z11.object({
|
|
8558
|
+
issueId: z11.string().min(1),
|
|
8559
|
+
feature: z11.string().min(1).max(80),
|
|
8560
|
+
withAttachments: z11.boolean().optional()
|
|
7973
8561
|
});
|
|
7974
8562
|
function mountTrackerApi(app, opts) {
|
|
7975
8563
|
app.get("/api/tracker/search", async (c) => {
|
|
@@ -8726,7 +9314,24 @@ program.command("new-plan [intake-path]").description("Generate a plan (via dire
|
|
|
8726
9314
|
"--max-scout-files <n>",
|
|
8727
9315
|
"Maximum files the scout can pre-select (default from config or 12)",
|
|
8728
9316
|
(v) => parseInt(v, 10)
|
|
8729
|
-
).option("--no-validation", "Disable post-write validation", false).option("--strict-validation", "Write partial plan when validation finds issues", false).
|
|
9317
|
+
).option("--no-validation", "Disable post-write validation", false).option("--strict-validation", "Write partial plan when validation finds issues", false).addOption(
|
|
9318
|
+
new Option("--anthropic-runtime <mode>", "Anthropic only: agent-sdk or legacy vercel AI SDK").choices([
|
|
9319
|
+
"agent-sdk",
|
|
9320
|
+
"vercel"
|
|
9321
|
+
])
|
|
9322
|
+
).addOption(
|
|
9323
|
+
new Option("--effort <name>", "Anthropic Agent SDK: draft-stage effort").choices([
|
|
9324
|
+
"minimal",
|
|
9325
|
+
"medium",
|
|
9326
|
+
"high"
|
|
9327
|
+
])
|
|
9328
|
+
).addOption(
|
|
9329
|
+
new Option("--scout-effort <name>", "Anthropic Agent SDK: scout-stage effort").choices([
|
|
9330
|
+
"minimal",
|
|
9331
|
+
"medium",
|
|
9332
|
+
"high"
|
|
9333
|
+
])
|
|
9334
|
+
).option("--no-thinking", "Anthropic Agent SDK: disable extended thinking for this run", false).action(wrapArgs(runNewPlan));
|
|
8730
9335
|
program.command("status").description("Show squad-kit workspace status").action(wrap(runStatus));
|
|
8731
9336
|
program.command("doctor").description("Run a health check on the local .squad/ workspace and its external integrations").option("--fix", "Apply non-destructive repairs (gitignore, permissions, missing dirs)", false).option("--json", "Emit results as JSON to stdout (for scripting)", false).action(wrap(runDoctor));
|
|
8732
9337
|
program.command("migrate").description("Apply one-shot structural migrations to bring .squad/ to the latest version").option("--dry-run", "Show what would change without touching the filesystem", false).option("-y, --yes", "Skip the confirmation prompt", false).action(wrap(runMigrate));
|
|
@@ -8792,4 +9397,3 @@ function wrapArgs(fn) {
|
|
|
8792
9397
|
}
|
|
8793
9398
|
};
|
|
8794
9399
|
}
|
|
8795
|
-
//# sourceMappingURL=cli.js.map
|