squad-kit 0.9.1 → 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 +19 -0
- package/dist/cli.js +1359 -767
- 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,32 +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
|
-
const anthropicThinking = input5.provider === "anthropic" ? { anthropic: { thinking: { type: "enabled", budgetTokens: 1024 } } } : void 0;
|
|
4222
4834
|
let attempt = 0;
|
|
4223
4835
|
while (true) {
|
|
4224
4836
|
try {
|
|
4225
|
-
const
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
prompt: input5.userPrompt,
|
|
4837
|
+
const result = await input5.runtime.runScout({
|
|
4838
|
+
systemPrompt: input5.systemPrompt,
|
|
4839
|
+
userMessage: input5.userPrompt,
|
|
4229
4840
|
schema: ScoutOutputSchema,
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4841
|
+
bus: input5.bus,
|
|
4842
|
+
runId: input5.runId,
|
|
4843
|
+
abort: input5.abort,
|
|
4844
|
+
maxOutputTokens: maxTokens,
|
|
4845
|
+
providerSpecific: input5.providerSpecific
|
|
4233
4846
|
});
|
|
4234
|
-
|
|
4235
|
-
input5.budget.recordUsage(
|
|
4847
|
+
if (!result) return null;
|
|
4848
|
+
input5.budget.recordUsage(result.usage);
|
|
4236
4849
|
return {
|
|
4237
|
-
output:
|
|
4238
|
-
usage: { inputTokens:
|
|
4850
|
+
output: result.output,
|
|
4851
|
+
usage: { inputTokens: result.usage.inputTokens, outputTokens: result.usage.outputTokens }
|
|
4239
4852
|
};
|
|
4240
4853
|
} catch (e) {
|
|
4241
|
-
if (
|
|
4854
|
+
if (NoObjectGeneratedError2.isInstance(e)) {
|
|
4242
4855
|
return null;
|
|
4243
4856
|
}
|
|
4244
|
-
if (
|
|
4857
|
+
if (APICallError2.isInstance(e) && attempt === 0) {
|
|
4245
4858
|
const status = e.statusCode ?? 0;
|
|
4246
4859
|
if (status === 429) {
|
|
4247
4860
|
const ra = parseRetryAfterSec(e.responseHeaders ?? {});
|
|
@@ -4553,24 +5166,6 @@ function validatePlan(input5) {
|
|
|
4553
5166
|
}
|
|
4554
5167
|
|
|
4555
5168
|
// src/planner/loop.ts
|
|
4556
|
-
var MAX_RATE_LIMIT_RETRY_SEC = 90;
|
|
4557
|
-
var defaultSleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4558
|
-
function isAbortError(e) {
|
|
4559
|
-
return e instanceof Error && e.name === "AbortError";
|
|
4560
|
-
}
|
|
4561
|
-
async function sleepWithAbort(ms, sleepFn, abort) {
|
|
4562
|
-
if (!abort) {
|
|
4563
|
-
await sleepFn(ms);
|
|
4564
|
-
return;
|
|
4565
|
-
}
|
|
4566
|
-
if (abort.aborted) throw new DOMException("Aborted", "AbortError");
|
|
4567
|
-
await Promise.race([
|
|
4568
|
-
sleepFn(ms),
|
|
4569
|
-
new Promise((_, reject) => {
|
|
4570
|
-
abort.addEventListener("abort", () => reject(new DOMException("Aborted", "AbortError")), { once: true });
|
|
4571
|
-
})
|
|
4572
|
-
]);
|
|
4573
|
-
}
|
|
4574
5169
|
function buildPlannerRunStats(budget, turns, runStartedAt) {
|
|
4575
5170
|
const u = budget.snapshot().usage;
|
|
4576
5171
|
const cacheRead = u.cacheReadTokens ?? 0;
|
|
@@ -4726,450 +5321,81 @@ function scoutStageStats(usage, durationMs) {
|
|
|
4726
5321
|
inputTokens: usage.inputTokens,
|
|
4727
5322
|
outputTokens: usage.outputTokens,
|
|
4728
5323
|
cacheCreationTokens: 0,
|
|
4729
|
-
cacheReadTokens: 0,
|
|
4730
|
-
cacheHitRatio: 0,
|
|
4731
|
-
durationMs
|
|
4732
|
-
};
|
|
4733
|
-
}
|
|
4734
|
-
function extendAllSessionLimits(input5, maxIter, maxOut, baseMaxIter, baseMaxOut) {
|
|
4735
|
-
input5.budget.extendSession();
|
|
4736
|
-
maxIter.current += baseMaxIter;
|
|
4737
|
-
maxOut.current += baseMaxOut;
|
|
4738
|
-
}
|
|
4739
|
-
function recordUsageAndEmit(bus, input5, runId, turn, usage) {
|
|
4740
|
-
input5.budget.recordUsage(usage);
|
|
4741
|
-
input5.onUsage?.(usage);
|
|
4742
|
-
bus.emit({ kind: "usage", runId, turn, usage });
|
|
4743
|
-
const u = input5.budget.snapshot().usage;
|
|
4744
|
-
const cacheRead = u.cacheReadTokens ?? 0;
|
|
4745
|
-
const cacheCreate = u.cacheCreationTokens ?? 0;
|
|
4746
|
-
if (cacheRead > 0 || cacheCreate > 0) {
|
|
4747
|
-
const inTok = u.inputTokens;
|
|
4748
|
-
const totalInput = inTok + cacheRead;
|
|
4749
|
-
const cacheHitRatio = totalInput === 0 ? 0 : Math.round(cacheRead / totalInput * 100) / 100;
|
|
4750
|
-
bus.emit({
|
|
4751
|
-
kind: "cache_summary",
|
|
4752
|
-
runId,
|
|
4753
|
-
turn,
|
|
4754
|
-
cacheHitRatio,
|
|
4755
|
-
cacheReadTokens: cacheRead,
|
|
4756
|
-
cacheCreationTokens: cacheCreate
|
|
4757
|
-
});
|
|
4758
|
-
}
|
|
4759
|
-
}
|
|
4760
|
-
function cancelledOutput(input5, bus, runId, accumulatedText, budgetExhausted, iterations, runStartedAt) {
|
|
4761
|
-
bus.emit({ kind: "cancelled", runId });
|
|
4762
|
-
return {
|
|
4763
|
-
planText: accumulatedText,
|
|
4764
|
-
budgetExhausted,
|
|
4765
|
-
timedOut: false,
|
|
4766
|
-
finishedNormally: false,
|
|
4767
|
-
iterations,
|
|
4768
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt),
|
|
4769
|
-
userCancelled: true
|
|
4770
|
-
};
|
|
4771
|
-
}
|
|
4772
|
-
function asToolInput(args) {
|
|
4773
|
-
if (args && typeof args === "object" && !Array.isArray(args)) return args;
|
|
4774
|
-
if (typeof args === "string") {
|
|
4775
|
-
try {
|
|
4776
|
-
return JSON.parse(args);
|
|
4777
|
-
} catch {
|
|
4778
|
-
return {};
|
|
4779
|
-
}
|
|
4780
|
-
}
|
|
4781
|
-
return {};
|
|
4782
|
-
}
|
|
4783
|
-
function normalizeToolExecutionResult(result) {
|
|
4784
|
-
if (typeof result === "string") {
|
|
4785
|
-
try {
|
|
4786
|
-
const j = JSON.parse(result);
|
|
4787
|
-
if (j && typeof j === "object" && !Array.isArray(j) && "content" in j) {
|
|
4788
|
-
const o = j;
|
|
4789
|
-
return {
|
|
4790
|
-
content: typeof o.content === "string" ? o.content : JSON.stringify(o.content ?? ""),
|
|
4791
|
-
isError: o.isError === true
|
|
4792
|
-
};
|
|
4793
|
-
}
|
|
4794
|
-
} catch {
|
|
4795
|
-
}
|
|
4796
|
-
return { content: result, isError: false };
|
|
4797
|
-
}
|
|
4798
|
-
if (result && typeof result === "object" && !Array.isArray(result) && "content" in result) {
|
|
4799
|
-
const o = result;
|
|
4800
|
-
return {
|
|
4801
|
-
content: typeof o.content === "string" ? o.content : JSON.stringify(o.content ?? ""),
|
|
4802
|
-
isError: o.isError === true
|
|
4803
|
-
};
|
|
4804
|
-
}
|
|
4805
|
-
return { content: typeof result === "undefined" ? "" : JSON.stringify(result), isError: false };
|
|
4806
|
-
}
|
|
4807
|
-
function applyStepToTurns(turns, step2) {
|
|
4808
|
-
const callsById = /* @__PURE__ */ new Map();
|
|
4809
|
-
const toolCalls = step2.toolCalls.map((tc) => {
|
|
4810
|
-
callsById.set(tc.toolCallId, tc.toolName);
|
|
4811
|
-
return { id: tc.toolCallId, name: tc.toolName, input: asToolInput(tc.args) };
|
|
4812
|
-
});
|
|
4813
|
-
turns.push({
|
|
4814
|
-
role: "assistant",
|
|
4815
|
-
...step2.text ? { text: step2.text } : {},
|
|
4816
|
-
...toolCalls.length ? { toolCalls } : {}
|
|
4817
|
-
});
|
|
4818
|
-
if (step2.toolResults.length) {
|
|
4819
|
-
const toolResults = step2.toolResults.map((tr) => {
|
|
4820
|
-
const norm = normalizeToolExecutionResult(tr.result);
|
|
4821
|
-
return {
|
|
4822
|
-
toolCallId: tr.toolCallId,
|
|
4823
|
-
name: callsById.get(tr.toolCallId) ?? "read_file",
|
|
4824
|
-
content: norm.content,
|
|
4825
|
-
...norm.isError ? { isError: true } : {}
|
|
4826
|
-
};
|
|
4827
|
-
});
|
|
4828
|
-
turns.push({ role: "user", toolResults });
|
|
4829
|
-
}
|
|
4830
|
-
}
|
|
4831
|
-
function providerResponseFromApiError(provider, modelId, err2) {
|
|
4832
|
-
const body = err2.responseBody ?? "";
|
|
4833
|
-
const nf = detectModelNotFound(provider, modelId, err2.statusCode ?? 0, body);
|
|
4834
|
-
if (nf) {
|
|
4835
|
-
return {
|
|
4836
|
-
stopReason: "error",
|
|
4837
|
-
errorKind: "model_not_found",
|
|
4838
|
-
rawError: modelNotFoundMessage(nf)
|
|
4839
|
-
};
|
|
4840
|
-
}
|
|
4841
|
-
const rl = detectRateLimit(provider, err2.statusCode ?? 0, err2.responseHeaders ?? {}, body);
|
|
4842
|
-
if (rl) {
|
|
4843
|
-
return {
|
|
4844
|
-
stopReason: "error",
|
|
4845
|
-
errorKind: "rate_limit",
|
|
4846
|
-
retryAfterSec: rl.retryAfterSec,
|
|
4847
|
-
rawError: `${provider} ${err2.statusCode ?? "error"}: ${body.slice(0, 500)}`
|
|
4848
|
-
};
|
|
4849
|
-
}
|
|
4850
|
-
return {
|
|
4851
|
-
stopReason: "error",
|
|
4852
|
-
rawError: `${provider} ${err2.statusCode ?? "error"}: ${body.slice(0, 500)}`
|
|
5324
|
+
cacheReadTokens: 0,
|
|
5325
|
+
cacheHitRatio: 0,
|
|
5326
|
+
durationMs
|
|
4853
5327
|
};
|
|
4854
5328
|
}
|
|
4855
5329
|
async function runDraftStage(input5) {
|
|
4856
5330
|
const runStartedAt = Date.now();
|
|
4857
5331
|
const bus = input5.events ?? new PlannerEventBus();
|
|
4858
5332
|
const runId = input5.runId ?? newRunId();
|
|
4859
|
-
const
|
|
4860
|
-
const baseMaxIter = input5.maxIterations ?? DEFAULT_PLANNER_MAX_ITERATIONS;
|
|
4861
|
-
const maxIter = { current: baseMaxIter };
|
|
4862
|
-
const baseMaxOut = input5.maxOutputTokens ?? DEFAULT_PLANNER_MAX_OUTPUT_TOKENS;
|
|
4863
|
-
const maxOut = { current: baseMaxOut };
|
|
4864
|
-
const turns = [{ role: "user", text: input5.userPrompt }];
|
|
4865
|
-
if (input5.scoutedSection?.trim()) {
|
|
4866
|
-
turns.push({ role: "user", text: composeScoutedUserMessage({ scoutedSection: input5.scoutedSection }) });
|
|
4867
|
-
}
|
|
4868
|
-
let accumulatedText = "";
|
|
4869
|
-
let iterations = 0;
|
|
4870
|
-
let budgetExhausted = false;
|
|
4871
|
-
let finishedNormally = false;
|
|
4872
|
-
const decide = input5.decideOnLimit;
|
|
4873
|
-
const sleepFn = input5.sleep ?? defaultSleep;
|
|
5333
|
+
const iterProxy = { current: 0 };
|
|
4874
5334
|
const limitCtx = (kind) => ({
|
|
4875
5335
|
kind,
|
|
4876
5336
|
budgetSnapshot: input5.budget.snapshot(),
|
|
4877
|
-
iterations,
|
|
4878
|
-
maxIterations:
|
|
4879
|
-
maxOutputTokens:
|
|
5337
|
+
iterations: iterProxy.current,
|
|
5338
|
+
maxIterations: input5.maxIterations ?? DEFAULT_PLANNER_MAX_ITERATIONS,
|
|
5339
|
+
maxOutputTokens: input5.maxOutputTokens ?? DEFAULT_PLANNER_MAX_OUTPUT_TOKENS
|
|
4880
5340
|
});
|
|
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
|
-
|
|
4917
|
-
|
|
4918
|
-
return {
|
|
4919
|
-
planText: accumulatedText,
|
|
4920
|
-
budgetExhausted,
|
|
4921
|
-
timedOut: true,
|
|
4922
|
-
finishedNormally: false,
|
|
4923
|
-
iterations,
|
|
4924
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt),
|
|
4925
|
-
incompleteKind: "wall_clock",
|
|
4926
|
-
userCancelled: true
|
|
4927
|
-
};
|
|
4928
|
-
}
|
|
4929
|
-
extendAllSessionLimits(input5, maxIter, maxOut, baseMaxIter, baseMaxOut);
|
|
4930
|
-
iterations -= 1;
|
|
4931
|
-
continue;
|
|
4932
|
-
}
|
|
4933
|
-
return {
|
|
4934
|
-
planText: accumulatedText,
|
|
4935
|
-
budgetExhausted,
|
|
4936
|
-
timedOut: true,
|
|
4937
|
-
finishedNormally: false,
|
|
4938
|
-
iterations,
|
|
4939
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt)
|
|
4940
|
-
};
|
|
4941
|
-
}
|
|
4942
|
-
if (iterations >= 2) {
|
|
4943
|
-
bus.emit({ kind: "turn_started", runId, turn: iterations });
|
|
4944
|
-
}
|
|
4945
|
-
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,
|
|
4946
5378
|
root: input5.root,
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
const messages = turnsToCoreMessages(input5.systemPrompt, turns, {
|
|
4968
|
-
cacheEnabled,
|
|
4969
|
-
provider: input5.provider
|
|
4970
|
-
});
|
|
4971
|
-
bus.emit({ kind: "request_sent", runId, turn: iterations });
|
|
4972
|
-
let attempt = 0;
|
|
4973
|
-
let stepResult;
|
|
4974
|
-
let retrySkippedReason;
|
|
4975
|
-
let retriedRateLimit = false;
|
|
4976
|
-
const callStream = async () => {
|
|
4977
|
-
const { streamText } = await import("ai");
|
|
4978
|
-
const stream = streamText({
|
|
4979
|
-
model: input5.model,
|
|
4980
|
-
messages,
|
|
4981
|
-
tools,
|
|
4982
|
-
toolChoice: "auto",
|
|
4983
|
-
maxSteps: 1,
|
|
4984
|
-
maxRetries: 0,
|
|
4985
|
-
maxTokens: maxOut.current,
|
|
4986
|
-
abortSignal: input5.abort,
|
|
4987
|
-
// ai@4.x defaults `temperature: 0` (prepareCallSettings) and @ai-sdk/anthropic@1.x
|
|
4988
|
-
// always passes it to the wire unless extended thinking is on. Anthropic Opus 4.7+
|
|
4989
|
-
// rejects temperature entirely. Enabling thinking on Anthropic strips temperature
|
|
4990
|
-
// via the adapter and gives the planner reasoning budget for free.
|
|
4991
|
-
...input5.provider === "anthropic" && {
|
|
4992
|
-
providerOptions: {
|
|
4993
|
-
anthropic: { thinking: { type: "enabled", budgetTokens: 2048 } }
|
|
4994
|
-
}
|
|
4995
|
-
},
|
|
4996
|
-
onChunk: ({ chunk }) => {
|
|
4997
|
-
if (chunk.type === "text-delta") {
|
|
4998
|
-
accumulatedText += chunk.textDelta;
|
|
4999
|
-
input5.onAssistantText?.(chunk.textDelta);
|
|
5000
|
-
bus.emit({ kind: "assistant_text", runId, turn: iterations, delta: chunk.textDelta });
|
|
5001
|
-
}
|
|
5002
|
-
},
|
|
5003
|
-
onStepFinish: (step2) => {
|
|
5004
|
-
const u = usageFromLanguageModelStep(step2.usage, step2.providerMetadata);
|
|
5005
|
-
recordUsageAndEmit(bus, input5, runId, iterations, u);
|
|
5006
|
-
}
|
|
5007
|
-
});
|
|
5008
|
-
for await (const part of stream.fullStream) {
|
|
5009
|
-
const p = part;
|
|
5010
|
-
if (p.type === "error") {
|
|
5011
|
-
throw p.error;
|
|
5012
|
-
}
|
|
5013
|
-
}
|
|
5014
|
-
const steps = await stream.steps;
|
|
5015
|
-
const last = steps[steps.length - 1];
|
|
5016
|
-
if (!last) throw new Error("planner: empty model step");
|
|
5017
|
-
stepResult = {
|
|
5018
|
-
text: last.text,
|
|
5019
|
-
finishReason: last.finishReason,
|
|
5020
|
-
toolCalls: last.toolCalls.flatMap(
|
|
5021
|
-
(tc) => tc ? [
|
|
5022
|
-
{
|
|
5023
|
-
toolCallId: tc.toolCallId,
|
|
5024
|
-
toolName: tc.toolName,
|
|
5025
|
-
args: tc.args
|
|
5026
|
-
}
|
|
5027
|
-
] : []
|
|
5028
|
-
),
|
|
5029
|
-
toolResults: last.toolResults.flatMap(
|
|
5030
|
-
(tr) => tr ? [{ toolCallId: tr.toolCallId, result: tr.result }] : []
|
|
5031
|
-
),
|
|
5032
|
-
usage: last.usage,
|
|
5033
|
-
providerMetadata: last.providerMetadata
|
|
5034
|
-
};
|
|
5035
|
-
};
|
|
5036
|
-
try {
|
|
5037
|
-
while (true) {
|
|
5038
|
-
try {
|
|
5039
|
-
await callStream();
|
|
5040
|
-
break;
|
|
5041
|
-
} catch (e) {
|
|
5042
|
-
if (PlannerUserCancelledError.is(e)) {
|
|
5043
|
-
return {
|
|
5044
|
-
planText: e.planText,
|
|
5045
|
-
budgetExhausted: false,
|
|
5046
|
-
timedOut: false,
|
|
5047
|
-
finishedNormally: false,
|
|
5048
|
-
iterations,
|
|
5049
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt),
|
|
5050
|
-
incompleteKind: "budget_reads",
|
|
5051
|
-
userCancelled: true
|
|
5052
|
-
};
|
|
5053
|
-
}
|
|
5054
|
-
if (isAbortError(e)) {
|
|
5055
|
-
return cancelledOutput(input5, bus, runId, accumulatedText, budgetExhausted, iterations, runStartedAt);
|
|
5056
|
-
}
|
|
5057
|
-
if (APICallError2.isInstance(e)) {
|
|
5058
|
-
const synthetic = providerResponseFromApiError(input5.provider, input5.modelId, e);
|
|
5059
|
-
if (synthetic.errorKind === "rate_limit") {
|
|
5060
|
-
const asked = synthetic.retryAfterSec;
|
|
5061
|
-
const emitRateLimit = (phase, waitSec) => {
|
|
5062
|
-
bus.emit({
|
|
5063
|
-
kind: "rate_limit",
|
|
5064
|
-
runId,
|
|
5065
|
-
turn: iterations,
|
|
5066
|
-
retryAfterSec: synthetic.retryAfterSec,
|
|
5067
|
-
waitSec,
|
|
5068
|
-
capSec: MAX_RATE_LIMIT_RETRY_SEC,
|
|
5069
|
-
phase,
|
|
5070
|
-
provider: input5.provider,
|
|
5071
|
-
rawBody: (synthetic.rawError ?? "").slice(0, 200)
|
|
5072
|
-
});
|
|
5073
|
-
};
|
|
5074
|
-
if (asked !== void 0 && asked > MAX_RATE_LIMIT_RETRY_SEC) {
|
|
5075
|
-
emitRateLimit("aborted", asked);
|
|
5076
|
-
retrySkippedReason = "retry_after_too_long";
|
|
5077
|
-
throw composePlannerError(input5.provider, synthetic, false, retrySkippedReason);
|
|
5078
|
-
}
|
|
5079
|
-
if (attempt === 0) {
|
|
5080
|
-
const waitSec = Math.min(asked ?? 10, MAX_RATE_LIMIT_RETRY_SEC);
|
|
5081
|
-
emitRateLimit("retrying", waitSec);
|
|
5082
|
-
input5.onRateLimit?.(waitSec);
|
|
5083
|
-
await sleepWithAbort(waitSec * 1e3, sleepFn, input5.abort);
|
|
5084
|
-
attempt += 1;
|
|
5085
|
-
retriedRateLimit = true;
|
|
5086
|
-
continue;
|
|
5087
|
-
}
|
|
5088
|
-
throw composePlannerError(input5.provider, synthetic, true, retrySkippedReason);
|
|
5089
|
-
}
|
|
5090
|
-
throw composePlannerError(input5.provider, synthetic, retriedRateLimit, retrySkippedReason);
|
|
5091
|
-
}
|
|
5092
|
-
throw e;
|
|
5093
|
-
}
|
|
5094
|
-
}
|
|
5095
|
-
} catch (e) {
|
|
5096
|
-
if (isAbortError(e)) {
|
|
5097
|
-
return cancelledOutput(input5, bus, runId, accumulatedText, budgetExhausted, iterations, runStartedAt);
|
|
5098
|
-
}
|
|
5099
|
-
throw e;
|
|
5100
|
-
}
|
|
5101
|
-
const finishReason = stepResult.finishReason;
|
|
5102
|
-
const toolCallsLen = stepResult.toolCalls.length;
|
|
5103
|
-
if (finishReason === "length" && toolCallsLen === 0) {
|
|
5104
|
-
bus.emit({ kind: "turn_complete", runId, turn: iterations, stopReason: "max_tokens" });
|
|
5105
|
-
if (decide) {
|
|
5106
|
-
const d = await decide(limitCtx("max_output_tokens"));
|
|
5107
|
-
if (d === "cancel") {
|
|
5108
|
-
return {
|
|
5109
|
-
planText: accumulatedText,
|
|
5110
|
-
budgetExhausted,
|
|
5111
|
-
timedOut: false,
|
|
5112
|
-
finishedNormally: false,
|
|
5113
|
-
iterations,
|
|
5114
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt),
|
|
5115
|
-
incompleteKind: "max_output_tokens",
|
|
5116
|
-
userCancelled: true
|
|
5117
|
-
};
|
|
5118
|
-
}
|
|
5119
|
-
extendAllSessionLimits(input5, maxIter, maxOut, baseMaxIter, baseMaxOut);
|
|
5120
|
-
applyStepToTurns(turns, stepResult);
|
|
5121
|
-
turns.push({ role: "user", text: PLANNER_MARKDOWN_CONTINUATION_USER });
|
|
5122
|
-
iterations -= 1;
|
|
5123
|
-
continue;
|
|
5124
|
-
}
|
|
5125
|
-
return {
|
|
5126
|
-
planText: accumulatedText,
|
|
5127
|
-
budgetExhausted,
|
|
5128
|
-
timedOut: false,
|
|
5129
|
-
finishedNormally: false,
|
|
5130
|
-
iterations,
|
|
5131
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt),
|
|
5132
|
-
incompleteKind: "max_output_tokens"
|
|
5133
|
-
};
|
|
5134
|
-
}
|
|
5135
|
-
if (finishReason === "error" || finishReason === "content-filter") {
|
|
5136
|
-
throw new Error(
|
|
5137
|
-
stepResult.text || `planner: model reported ${finishReason}. Run \`squad doctor\` to diagnose, or retry \u2014 most 5xx errors are transient.`
|
|
5138
|
-
);
|
|
5139
|
-
}
|
|
5140
|
-
if ((finishReason === "stop" || finishReason === "unknown") && toolCallsLen === 0) {
|
|
5141
|
-
bus.emit({ kind: "turn_complete", runId, turn: iterations, stopReason: finishReason });
|
|
5142
|
-
finishedNormally = true;
|
|
5143
|
-
return {
|
|
5144
|
-
planText: accumulatedText,
|
|
5145
|
-
budgetExhausted,
|
|
5146
|
-
timedOut: false,
|
|
5147
|
-
finishedNormally,
|
|
5148
|
-
iterations,
|
|
5149
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt)
|
|
5150
|
-
};
|
|
5151
|
-
}
|
|
5152
|
-
if (toolCallsLen === 0) {
|
|
5153
|
-
bus.emit({ kind: "turn_complete", runId, turn: iterations, stopReason: finishReason });
|
|
5154
|
-
finishedNormally = true;
|
|
5155
|
-
return {
|
|
5156
|
-
planText: accumulatedText,
|
|
5157
|
-
budgetExhausted,
|
|
5158
|
-
timedOut: false,
|
|
5159
|
-
finishedNormally,
|
|
5160
|
-
iterations,
|
|
5161
|
-
stats: buildPlannerRunStats(input5.budget, iterations, runStartedAt)
|
|
5162
|
-
};
|
|
5163
|
-
}
|
|
5164
|
-
applyStepToTurns(turns, stepResult);
|
|
5165
|
-
if (budgetExhausted) {
|
|
5166
|
-
turns.push({
|
|
5167
|
-
role: "user",
|
|
5168
|
-
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."
|
|
5169
|
-
});
|
|
5170
|
-
}
|
|
5171
|
-
bus.emit({ kind: "turn_complete", runId, turn: iterations, stopReason: finishReason });
|
|
5172
|
-
}
|
|
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
|
+
};
|
|
5173
5399
|
}
|
|
5174
5400
|
async function runPlanner(input5) {
|
|
5175
5401
|
const bus = input5.events ?? new PlannerEventBus();
|
|
@@ -5180,7 +5406,8 @@ async function runPlanner(input5) {
|
|
|
5180
5406
|
runId,
|
|
5181
5407
|
provider: input5.provider,
|
|
5182
5408
|
model: input5.modelId,
|
|
5183
|
-
cacheEnabled
|
|
5409
|
+
cacheEnabled,
|
|
5410
|
+
plannerRuntime: input5.runtime.kind
|
|
5184
5411
|
});
|
|
5185
5412
|
const merged = { ...input5, events: bus, runId };
|
|
5186
5413
|
const systemPrompt = input5.systemPrompt;
|
|
@@ -5188,11 +5415,11 @@ async function runPlanner(input5) {
|
|
|
5188
5415
|
let scoutStats;
|
|
5189
5416
|
let scoutedSection;
|
|
5190
5417
|
const scoutOn = input5.stages?.scout?.enabled !== false;
|
|
5191
|
-
const
|
|
5418
|
+
const scoutRuntime = input5.stages?.scout?.runtime;
|
|
5192
5419
|
const maxScoutFiles = input5.stages?.scout?.maxFiles ?? 12;
|
|
5193
5420
|
const scoutMaxOut = input5.stages?.scout?.maxOutputTokens ?? 2048;
|
|
5194
5421
|
const scoutModelIdStr = input5.stages?.scout?.modelId ?? "";
|
|
5195
|
-
if (scoutOn &&
|
|
5422
|
+
if (scoutOn && scoutRuntime) {
|
|
5196
5423
|
if (!input5.scoutSystemPrompt?.trim()) {
|
|
5197
5424
|
throw new Error(
|
|
5198
5425
|
"planner: scout enabled but `scoutSystemPrompt` was not provided. Compose it via composeScoutSystemPrompt() or set stages.scout.enabled = false."
|
|
@@ -5202,13 +5429,15 @@ async function runPlanner(input5) {
|
|
|
5202
5429
|
bus.emit({ kind: "stage_started", runId, stage: "scout" });
|
|
5203
5430
|
try {
|
|
5204
5431
|
const scoutRes = await runScout({
|
|
5205
|
-
|
|
5206
|
-
provider: input5.provider,
|
|
5432
|
+
runtime: scoutRuntime,
|
|
5207
5433
|
systemPrompt: input5.scoutSystemPrompt,
|
|
5208
5434
|
userPrompt: input5.userPrompt,
|
|
5209
5435
|
budget: input5.budget,
|
|
5436
|
+
bus,
|
|
5437
|
+
runId,
|
|
5210
5438
|
abort: input5.abort,
|
|
5211
|
-
maxTokens: scoutMaxOut
|
|
5439
|
+
maxTokens: scoutMaxOut,
|
|
5440
|
+
providerSpecific: input5.anthropicProviderSpecific?.scout
|
|
5212
5441
|
});
|
|
5213
5442
|
const durationMs = Date.now() - t0;
|
|
5214
5443
|
if (scoutRes) {
|
|
@@ -5254,7 +5483,7 @@ async function runPlanner(input5) {
|
|
|
5254
5483
|
}
|
|
5255
5484
|
} catch (err2) {
|
|
5256
5485
|
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
5257
|
-
if (
|
|
5486
|
+
if (APICallError3.isInstance(err2)) {
|
|
5258
5487
|
const status = err2.statusCode ?? 0;
|
|
5259
5488
|
if (status === 401 || status === 403) {
|
|
5260
5489
|
throw err2;
|
|
@@ -5318,25 +5547,6 @@ async function runPlanner(input5) {
|
|
|
5318
5547
|
validation: validationOn ? { issues, durationMs: validationDurationMs } : { issues: [], durationMs: 0 }
|
|
5319
5548
|
};
|
|
5320
5549
|
}
|
|
5321
|
-
function composePlannerError(providerName, response, retriedRateLimit, retrySkippedReason) {
|
|
5322
|
-
if (response.errorKind === "rate_limit") {
|
|
5323
|
-
return new Error(
|
|
5324
|
-
rateLimitMessage({
|
|
5325
|
-
provider: providerName,
|
|
5326
|
-
retryAfterSec: response.retryAfterSec,
|
|
5327
|
-
rawBody: response.rawError ?? "",
|
|
5328
|
-
retryAlreadyAttempted: retriedRateLimit,
|
|
5329
|
-
retrySkippedReason,
|
|
5330
|
-
maxRetrySec: MAX_RATE_LIMIT_RETRY_SEC
|
|
5331
|
-
})
|
|
5332
|
-
);
|
|
5333
|
-
}
|
|
5334
|
-
if (response.errorKind === "model_not_found") {
|
|
5335
|
-
return new Error(response.rawError ?? "planner: model not found");
|
|
5336
|
-
}
|
|
5337
|
-
const base = response.rawError ?? "planner: provider error";
|
|
5338
|
-
return new Error(`${base} Run \`squad doctor\` to diagnose, or retry \u2014 most 5xx errors are transient.`);
|
|
5339
|
-
}
|
|
5340
5550
|
|
|
5341
5551
|
// src/planner/planner-limit-messages.ts
|
|
5342
5552
|
function printPlannerApiCostNotice() {
|
|
@@ -5410,19 +5620,260 @@ function printPlannerLimitNextSteps() {
|
|
|
5410
5620
|
blank();
|
|
5411
5621
|
}
|
|
5412
5622
|
|
|
5413
|
-
// src/planner/
|
|
5414
|
-
import {
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
function
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
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);
|
|
5425
5875
|
}
|
|
5876
|
+
return new VercelRuntime(input5.provider, input5.modelId, input5.apiKey);
|
|
5426
5877
|
}
|
|
5427
5878
|
|
|
5428
5879
|
// src/planner/budget.ts
|
|
@@ -5672,6 +6123,9 @@ function formatPlannerCacheLine(opts) {
|
|
|
5672
6123
|
if (!opts.cacheEnabled) {
|
|
5673
6124
|
return "cache disabled";
|
|
5674
6125
|
}
|
|
6126
|
+
if (opts.plannerRuntimeKind === "agent-sdk") {
|
|
6127
|
+
return "(agent-sdk: not exposed)";
|
|
6128
|
+
}
|
|
5675
6129
|
const { stats } = opts;
|
|
5676
6130
|
if (stats.cacheReadTokens > 0) {
|
|
5677
6131
|
const pct = Math.round(stats.cacheHitRatio * 100);
|
|
@@ -5859,12 +6313,33 @@ async function emitViaApi(story, paths, config2, opts) {
|
|
|
5859
6313
|
);
|
|
5860
6314
|
}
|
|
5861
6315
|
const modelId = modelFor(planner.provider, "plan", planner.modelOverride);
|
|
5862
|
-
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;
|
|
5863
6334
|
const budget = new Budget(planner.budget);
|
|
5864
6335
|
banner();
|
|
5865
6336
|
step(`planning ${story.feature} / ${story.id}`);
|
|
5866
6337
|
kv("provider", planner.provider);
|
|
5867
6338
|
kv("model", modelId);
|
|
6339
|
+
kv(
|
|
6340
|
+
"runtime",
|
|
6341
|
+
`${draftRuntime.kind} \xB7 ${draftRuntime.providerName}` + (planner.provider === "anthropic" ? ` (${anthropicRuntimeChoice})` : "")
|
|
6342
|
+
);
|
|
5868
6343
|
kv(
|
|
5869
6344
|
"budget",
|
|
5870
6345
|
`${planner.budget.maxFileReads} reads \xB7 ${(planner.budget.maxContextBytes / 1024).toFixed(0)} KB \xB7 ${planner.budget.maxDurationSeconds}s`
|
|
@@ -5892,7 +6367,7 @@ async function emitViaApi(story, paths, config2, opts) {
|
|
|
5892
6367
|
const strictValidation = !!opts.strictValidation || planner.validation?.strict === true;
|
|
5893
6368
|
const maxScoutFiles = opts.maxScoutFiles ?? planner.stages?.scout?.maxFiles ?? 12;
|
|
5894
6369
|
let scoutModelId = "";
|
|
5895
|
-
let
|
|
6370
|
+
let scoutRuntime;
|
|
5896
6371
|
let scoutSystemPrompt;
|
|
5897
6372
|
if (scoutEnabled) {
|
|
5898
6373
|
scoutModelId = modelFor(
|
|
@@ -5901,7 +6376,12 @@ async function emitViaApi(story, paths, config2, opts) {
|
|
|
5901
6376
|
planner.modelOverride,
|
|
5902
6377
|
opts.scoutModel ?? planner.stages?.scout?.modelOverride
|
|
5903
6378
|
);
|
|
5904
|
-
|
|
6379
|
+
scoutRuntime = resolveRuntime({
|
|
6380
|
+
provider: planner.provider,
|
|
6381
|
+
modelId: scoutModelId,
|
|
6382
|
+
apiKey,
|
|
6383
|
+
anthropicRuntime: planner.provider === "anthropic" ? anthropicRuntimeChoice : void 0
|
|
6384
|
+
});
|
|
5905
6385
|
scoutSystemPrompt = composeScoutSystemPrompt({
|
|
5906
6386
|
projectRoots: config2.project.projectRoots ?? ["."],
|
|
5907
6387
|
primaryLanguage: config2.project.primaryLanguage ?? "",
|
|
@@ -5990,9 +6470,10 @@ async function emitViaApi(story, paths, config2, opts) {
|
|
|
5990
6470
|
try {
|
|
5991
6471
|
result = await runPlanner({
|
|
5992
6472
|
root: paths.root,
|
|
5993
|
-
|
|
6473
|
+
runtime: draftRuntime,
|
|
5994
6474
|
provider: planner.provider,
|
|
5995
6475
|
modelId,
|
|
6476
|
+
anthropicProviderSpecific,
|
|
5996
6477
|
systemPrompt,
|
|
5997
6478
|
userPrompt,
|
|
5998
6479
|
budget,
|
|
@@ -6004,7 +6485,7 @@ async function emitViaApi(story, paths, config2, opts) {
|
|
|
6004
6485
|
stages: {
|
|
6005
6486
|
scout: {
|
|
6006
6487
|
enabled: scoutEnabled,
|
|
6007
|
-
|
|
6488
|
+
runtime: scoutRuntime,
|
|
6008
6489
|
modelId: scoutModelId,
|
|
6009
6490
|
maxFiles: maxScoutFiles,
|
|
6010
6491
|
maxOutputTokens: planner.stages?.scout?.maxOutputTokens ?? 2048
|
|
@@ -6083,7 +6564,9 @@ async function emitViaApi(story, paths, config2, opts) {
|
|
|
6083
6564
|
issuesCount: issueCount,
|
|
6084
6565
|
issuesByKind,
|
|
6085
6566
|
durationMs: result.validation?.durationMs
|
|
6086
|
-
}
|
|
6567
|
+
},
|
|
6568
|
+
plannerRuntime: { kind: draftRuntime.kind, provider: planner.provider },
|
|
6569
|
+
providerOptionsSnapshot: anthropicProviderSpecific ? { anthropic: anthropicProviderSpecific } : void 0
|
|
6087
6570
|
});
|
|
6088
6571
|
} catch {
|
|
6089
6572
|
}
|
|
@@ -6103,9 +6586,16 @@ async function emitViaApi(story, paths, config2, opts) {
|
|
|
6103
6586
|
{ key: "reads", value: `${snap.reads} files \xB7 ${(snap.bytes / 1024).toFixed(1)} KB` },
|
|
6104
6587
|
{
|
|
6105
6588
|
key: "tokens",
|
|
6106
|
-
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
|
+
})
|
|
6107
6598
|
},
|
|
6108
|
-
{ key: "cache", value: formatPlannerCacheLine({ cacheEnabled, stats: result.stats }) },
|
|
6109
6599
|
{
|
|
6110
6600
|
key: "max out",
|
|
6111
6601
|
value: `${planner.maxOutputTokens ?? DEFAULT_PLANNER_MAX_OUTPUT_TOKENS} tok/req`
|
|
@@ -6772,6 +7262,71 @@ async function checkPlannerCache(paths, ctx) {
|
|
|
6772
7262
|
detail: `caching active \u2014 last run ${hitPct}% hit (${formatTokenK(stats.cacheReadTokens)} read, ${lastRun.provider}/${lastRun.model})`
|
|
6773
7263
|
};
|
|
6774
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
|
+
}
|
|
6775
7330
|
async function checkTrackerConfig(_paths, ctx) {
|
|
6776
7331
|
if (!ctx.config) {
|
|
6777
7332
|
return { id: "tracker-config", name: "tracker configuration", status: "skip", detail: "config unavailable" };
|
|
@@ -6913,6 +7468,9 @@ async function runAllChecks(paths, ctx, fix) {
|
|
|
6913
7468
|
await add("planner model resolves at provider", () => checkPlannerModel(paths, ctx));
|
|
6914
7469
|
await add("planner tier vs. model", () => checkPlannerTierAwareness(paths, ctx));
|
|
6915
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));
|
|
6916
7474
|
await add("tracker configuration", () => checkTrackerConfig(paths, ctx));
|
|
6917
7475
|
await add("tracker credential resolves", () => checkTrackerCredential(paths, ctx));
|
|
6918
7476
|
await add("tracker connectivity", () => checkTrackerConnectivity(paths, ctx));
|
|
@@ -7367,7 +7925,7 @@ import { secureHeaders } from "hono/secure-headers";
|
|
|
7367
7925
|
|
|
7368
7926
|
// src/console/api/stories.ts
|
|
7369
7927
|
import fs37 from "fs";
|
|
7370
|
-
import { z as
|
|
7928
|
+
import { z as z6 } from "zod";
|
|
7371
7929
|
|
|
7372
7930
|
// src/utils/path-safety.ts
|
|
7373
7931
|
import path30 from "path";
|
|
@@ -7378,13 +7936,13 @@ function isInside(abs, parent) {
|
|
|
7378
7936
|
}
|
|
7379
7937
|
|
|
7380
7938
|
// src/console/api/stories.ts
|
|
7381
|
-
var CreateBody =
|
|
7382
|
-
feature:
|
|
7383
|
-
title:
|
|
7384
|
-
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()
|
|
7385
7943
|
});
|
|
7386
|
-
var PatchBody =
|
|
7387
|
-
intakeContent:
|
|
7944
|
+
var PatchBody = z6.object({
|
|
7945
|
+
intakeContent: z6.string().min(1).max(1e6)
|
|
7388
7946
|
});
|
|
7389
7947
|
function safeFeatureParam(feature) {
|
|
7390
7948
|
return feature.length > 0 && !feature.includes("/") && !feature.includes("..") && slugify(feature) === feature;
|
|
@@ -7476,12 +8034,12 @@ function mountStoriesApi(app, opts) {
|
|
|
7476
8034
|
// src/console/api/plans.ts
|
|
7477
8035
|
import fs38 from "fs";
|
|
7478
8036
|
import path31 from "path";
|
|
7479
|
-
import { z as
|
|
8037
|
+
import { z as z7 } from "zod";
|
|
7480
8038
|
import { diffLines } from "diff";
|
|
7481
|
-
var DiffQuery =
|
|
7482
|
-
feature:
|
|
7483
|
-
a:
|
|
7484
|
-
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)
|
|
7485
8043
|
});
|
|
7486
8044
|
function safeFeatureParam2(feature) {
|
|
7487
8045
|
return feature.length > 0 && !feature.includes("/") && !feature.includes("..") && slugify(feature) === feature;
|
|
@@ -7555,8 +8113,8 @@ function mountPlansApi(app, opts) {
|
|
|
7555
8113
|
}
|
|
7556
8114
|
|
|
7557
8115
|
// src/console/api/config.ts
|
|
7558
|
-
import { z as
|
|
7559
|
-
var ConfigBody =
|
|
8116
|
+
import { z as z8 } from "zod";
|
|
8117
|
+
var ConfigBody = z8.object({}).passthrough();
|
|
7560
8118
|
function mountConfigApi(app, opts) {
|
|
7561
8119
|
app.get("/api/config", (c) => {
|
|
7562
8120
|
const config2 = loadConfig(opts.paths.configFile);
|
|
@@ -7596,14 +8154,14 @@ function mountMetaApi(app, opts) {
|
|
|
7596
8154
|
|
|
7597
8155
|
// src/console/api/runs.ts
|
|
7598
8156
|
import { streamSSE } from "hono/streaming";
|
|
7599
|
-
import { z as
|
|
8157
|
+
import { z as z9 } from "zod";
|
|
7600
8158
|
import path32 from "path";
|
|
7601
8159
|
import fs39 from "fs";
|
|
7602
8160
|
var active = /* @__PURE__ */ new Map();
|
|
7603
8161
|
var EVENT_BUF_CAP = 512;
|
|
7604
|
-
var StartBody =
|
|
7605
|
-
feature:
|
|
7606
|
-
storyId:
|
|
8162
|
+
var StartBody = z9.object({
|
|
8163
|
+
feature: z9.string().min(1),
|
|
8164
|
+
storyId: z9.string().min(1)
|
|
7607
8165
|
});
|
|
7608
8166
|
function emptyStats(durationMs) {
|
|
7609
8167
|
return {
|
|
@@ -7660,7 +8218,17 @@ function mountRunsApi(app, opts) {
|
|
|
7660
8218
|
const cfgFresh = loadConfig(opts.paths.configFile);
|
|
7661
8219
|
const planner = cfgFresh.planner;
|
|
7662
8220
|
const modelId = modelFor(planner.provider, "plan", planner.modelOverride);
|
|
7663
|
-
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;
|
|
7664
8232
|
const budget = new Budget(planner.budget);
|
|
7665
8233
|
const cacheEnabled = planner.cache?.enabled ?? true;
|
|
7666
8234
|
const scoutEnabled = planner.stages?.scout?.enabled !== false;
|
|
@@ -7668,7 +8236,7 @@ function mountRunsApi(app, opts) {
|
|
|
7668
8236
|
const strictValidation = planner.validation?.strict === true;
|
|
7669
8237
|
const maxScoutFiles = planner.stages?.scout?.maxFiles ?? 12;
|
|
7670
8238
|
let scoutModelId = "";
|
|
7671
|
-
let
|
|
8239
|
+
let scoutRuntime;
|
|
7672
8240
|
let scoutSystemPrompt;
|
|
7673
8241
|
if (scoutEnabled) {
|
|
7674
8242
|
scoutModelId = modelFor(
|
|
@@ -7677,7 +8245,12 @@ function mountRunsApi(app, opts) {
|
|
|
7677
8245
|
planner.modelOverride,
|
|
7678
8246
|
planner.stages?.scout?.modelOverride
|
|
7679
8247
|
);
|
|
7680
|
-
|
|
8248
|
+
scoutRuntime = resolveRuntime({
|
|
8249
|
+
provider: planner.provider,
|
|
8250
|
+
modelId: scoutModelId,
|
|
8251
|
+
apiKey,
|
|
8252
|
+
anthropicRuntime: planner.provider === "anthropic" ? anthropicRuntimeChoice : void 0
|
|
8253
|
+
});
|
|
7681
8254
|
}
|
|
7682
8255
|
const repoMap = buildRepoMap(opts.paths.root, { format: "tree" });
|
|
7683
8256
|
if (scoutEnabled) {
|
|
@@ -7702,9 +8275,10 @@ function mountRunsApi(app, opts) {
|
|
|
7702
8275
|
try {
|
|
7703
8276
|
result = await runPlanner({
|
|
7704
8277
|
root: opts.paths.root,
|
|
7705
|
-
|
|
8278
|
+
runtime: draftRuntime,
|
|
7706
8279
|
provider: planner.provider,
|
|
7707
8280
|
modelId,
|
|
8281
|
+
anthropicProviderSpecific,
|
|
7708
8282
|
systemPrompt,
|
|
7709
8283
|
userPrompt,
|
|
7710
8284
|
budget,
|
|
@@ -7716,7 +8290,7 @@ function mountRunsApi(app, opts) {
|
|
|
7716
8290
|
stages: {
|
|
7717
8291
|
scout: {
|
|
7718
8292
|
enabled: scoutEnabled,
|
|
7719
|
-
|
|
8293
|
+
runtime: scoutRuntime,
|
|
7720
8294
|
modelId: scoutModelId,
|
|
7721
8295
|
maxFiles: maxScoutFiles,
|
|
7722
8296
|
maxOutputTokens: planner.stages?.scout?.maxOutputTokens ?? 2048
|
|
@@ -7806,7 +8380,9 @@ function mountRunsApi(app, opts) {
|
|
|
7806
8380
|
issuesCount: issueCount,
|
|
7807
8381
|
issuesByKind,
|
|
7808
8382
|
durationMs: result.validation?.durationMs
|
|
7809
|
-
}
|
|
8383
|
+
},
|
|
8384
|
+
plannerRuntime: { kind: draftRuntime.kind, provider: planner.provider },
|
|
8385
|
+
providerOptionsSnapshot: anthropicProviderSpecific ? { anthropic: anthropicProviderSpecific } : void 0
|
|
7810
8386
|
});
|
|
7811
8387
|
} catch {
|
|
7812
8388
|
}
|
|
@@ -7879,7 +8455,7 @@ function mountRunsApi(app, opts) {
|
|
|
7879
8455
|
}
|
|
7880
8456
|
|
|
7881
8457
|
// src/console/api/secrets.ts
|
|
7882
|
-
import { z as
|
|
8458
|
+
import { z as z10 } from "zod";
|
|
7883
8459
|
|
|
7884
8460
|
// src/core/mask-token.ts
|
|
7885
8461
|
function maskToken(t) {
|
|
@@ -7917,26 +8493,26 @@ function mountSecretsApi(app, opts) {
|
|
|
7917
8493
|
}
|
|
7918
8494
|
});
|
|
7919
8495
|
});
|
|
7920
|
-
const PutBody =
|
|
7921
|
-
planner:
|
|
7922
|
-
anthropic:
|
|
7923
|
-
openai:
|
|
7924
|
-
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()
|
|
7925
8501
|
}).optional(),
|
|
7926
|
-
tracker:
|
|
7927
|
-
jira:
|
|
7928
|
-
host:
|
|
7929
|
-
email:
|
|
7930
|
-
token:
|
|
8502
|
+
tracker: z10.object({
|
|
8503
|
+
jira: z10.object({
|
|
8504
|
+
host: z10.string().optional(),
|
|
8505
|
+
email: z10.string().optional(),
|
|
8506
|
+
token: z10.string().optional()
|
|
7931
8507
|
}).optional(),
|
|
7932
|
-
azure:
|
|
7933
|
-
organization:
|
|
7934
|
-
project:
|
|
7935
|
-
pat:
|
|
8508
|
+
azure: z10.object({
|
|
8509
|
+
organization: z10.string().optional(),
|
|
8510
|
+
project: z10.string().optional(),
|
|
8511
|
+
pat: z10.string().optional()
|
|
7936
8512
|
}).optional(),
|
|
7937
|
-
github:
|
|
7938
|
-
host:
|
|
7939
|
-
pat:
|
|
8513
|
+
github: z10.object({
|
|
8514
|
+
host: z10.string().optional(),
|
|
8515
|
+
pat: z10.string().optional()
|
|
7940
8516
|
}).optional()
|
|
7941
8517
|
}).optional()
|
|
7942
8518
|
});
|
|
@@ -7976,12 +8552,12 @@ function mountSecretsApi(app, opts) {
|
|
|
7976
8552
|
}
|
|
7977
8553
|
|
|
7978
8554
|
// src/console/api/tracker.ts
|
|
7979
|
-
import { z as
|
|
7980
|
-
var SearchQuery =
|
|
7981
|
-
var ImportBody =
|
|
7982
|
-
issueId:
|
|
7983
|
-
feature:
|
|
7984
|
-
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()
|
|
7985
8561
|
});
|
|
7986
8562
|
function mountTrackerApi(app, opts) {
|
|
7987
8563
|
app.get("/api/tracker/search", async (c) => {
|
|
@@ -8738,7 +9314,24 @@ program.command("new-plan [intake-path]").description("Generate a plan (via dire
|
|
|
8738
9314
|
"--max-scout-files <n>",
|
|
8739
9315
|
"Maximum files the scout can pre-select (default from config or 12)",
|
|
8740
9316
|
(v) => parseInt(v, 10)
|
|
8741
|
-
).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));
|
|
8742
9335
|
program.command("status").description("Show squad-kit workspace status").action(wrap(runStatus));
|
|
8743
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));
|
|
8744
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));
|
|
@@ -8804,4 +9397,3 @@ function wrapArgs(fn) {
|
|
|
8804
9397
|
}
|
|
8805
9398
|
};
|
|
8806
9399
|
}
|
|
8807
|
-
//# sourceMappingURL=cli.js.map
|