flavor-code 1.2.14 → 1.2.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/README.zh-CN.md +8 -3
- package/dist/agent/planner.d.ts +2 -0
- package/dist/{app-UJAGMKD5.js → app-7ZCJ3UOI.js} +1 -1
- package/dist/auth/store.d.ts +7 -0
- package/dist/{chunk-4YXDGGIC.js → chunk-5SRARNAC.js} +640 -79
- package/dist/{chunk-KL6TR2KG.js → chunk-PGS3M32Y.js} +1 -1
- package/dist/cli.js +78 -16
- package/dist/desktop/main.js +965 -334
- package/dist/desktop/preload.cjs +2 -0
- package/dist/desktop-renderer/assets/index-BmXr_vX3.js +151 -0
- package/dist/desktop-renderer/assets/{index-CPxt3vFY.css → index-DEL8gXBO.css} +1 -1
- package/dist/desktop-renderer/index.html +2 -2
- package/dist/evolve/store.d.ts +26 -0
- package/dist/git/insights.d.ts +52 -0
- package/dist/git/service.d.ts +43 -0
- package/dist/git/tools.d.ts +9 -0
- package/dist/sdk/index.js +2 -2
- package/dist/session/store.d.ts +1 -0
- package/dist/tools/shell.d.ts +11 -0
- package/dist/ui/commands.d.ts +8 -2
- package/dist/ui/session.d.ts +3 -0
- package/package.json +1 -1
- package/dist/desktop-renderer/assets/index-CcD0D6x0.js +0 -151
|
@@ -2900,6 +2900,7 @@ var MVP_COMMANDS = [
|
|
|
2900
2900
|
"init",
|
|
2901
2901
|
"config",
|
|
2902
2902
|
"login",
|
|
2903
|
+
"logout",
|
|
2903
2904
|
"permissions",
|
|
2904
2905
|
"skills",
|
|
2905
2906
|
"plugins",
|
|
@@ -2916,6 +2917,8 @@ var MVP_COMMANDS = [
|
|
|
2916
2917
|
"goal",
|
|
2917
2918
|
"evolve",
|
|
2918
2919
|
"mcp",
|
|
2920
|
+
"commit",
|
|
2921
|
+
"review",
|
|
2919
2922
|
"ide",
|
|
2920
2923
|
"memory",
|
|
2921
2924
|
"remember",
|
|
@@ -2935,6 +2938,7 @@ var COMMAND_DESCRIPTIONS = {
|
|
|
2935
2938
|
init: "Initialize Flavor project files",
|
|
2936
2939
|
config: "Show the resolved configuration",
|
|
2937
2940
|
login: "Authenticate via OAuth PKCE",
|
|
2941
|
+
logout: "Clear stored OAuth credentials and restore configured models",
|
|
2938
2942
|
permissions: "Change the tool permission mode",
|
|
2939
2943
|
skills: "List discovered skills",
|
|
2940
2944
|
plugins: "List loaded plugins",
|
|
@@ -2950,6 +2954,8 @@ var COMMAND_DESCRIPTIONS = {
|
|
|
2950
2954
|
loop: "Run a verified autonomous loop toward a goal",
|
|
2951
2955
|
goal: "Run a goal pipeline with adversarial verification",
|
|
2952
2956
|
evolve: "Self-improvement loop: capture failures, suggest fixes, verify with tests",
|
|
2957
|
+
commit: "Generate a commit message for staged changes and commit after confirmation",
|
|
2958
|
+
review: "Review uncommitted changes for bugs and risks before committing",
|
|
2953
2959
|
mcp: "Manage MCP servers",
|
|
2954
2960
|
ide: "Show the connected IDE and editor context",
|
|
2955
2961
|
memory: "Show long-term project memory",
|
|
@@ -3001,6 +3007,14 @@ function parseSlashCommand(input, dynamicCommands = [], skillCommands = []) {
|
|
|
3001
3007
|
if (name === "evolve") {
|
|
3002
3008
|
return { name, args };
|
|
3003
3009
|
}
|
|
3010
|
+
if (name === "commit") {
|
|
3011
|
+
const hint = args.join(" ").trim();
|
|
3012
|
+
return hint.length === 0 ? { name } : { name, hint };
|
|
3013
|
+
}
|
|
3014
|
+
if (name === "review") {
|
|
3015
|
+
const focus = args.join(" ").trim();
|
|
3016
|
+
return focus.length === 0 ? { name } : { name, focus };
|
|
3017
|
+
}
|
|
3004
3018
|
if (name === "loop") {
|
|
3005
3019
|
const goal = args.join(" ").trim();
|
|
3006
3020
|
if (!goal) return { name: "invalid", command: name, message: "Use /loop <goal>." };
|
|
@@ -3202,12 +3216,15 @@ var HELP = [
|
|
|
3202
3216
|
"/model <main|subagent> <provider:model> switch any configured model",
|
|
3203
3217
|
"/permissions <default|acceptEdits|plan|bypassPermissions|auto|bubble>",
|
|
3204
3218
|
"/login authenticate via OAuth PKCE",
|
|
3219
|
+
"/logout clear stored OAuth credentials",
|
|
3205
3220
|
"/init /config /skills /plugins /hooks /tasks",
|
|
3206
3221
|
"/memory /remember [type] <text> /forget <text-or-id> /forget-cold /finish",
|
|
3207
3222
|
"/checkpoint [label] /tree /rewind <node> /unrevert /fork <node>",
|
|
3208
3223
|
"/compact /clear /help /exit",
|
|
3209
3224
|
"/loop <goal> run a verified autonomous loop",
|
|
3210
3225
|
"/goal <objective> run a goal pipeline with adversarial verification",
|
|
3226
|
+
"/commit [hint] draft a commit message for staged changes and commit",
|
|
3227
|
+
"/review [focus] review uncommitted changes before committing",
|
|
3211
3228
|
"/evolve <signals|suggest|improve <id>|verify <name>|reload <name>|test|revert <name>|done <id>> self-improvement loop",
|
|
3212
3229
|
"/mcp [status|tools|reconnect|enable|disable] manage MCP servers",
|
|
3213
3230
|
"/ide show VS Code connection and cursor/selection",
|
|
@@ -3548,6 +3565,10 @@ var FlavorSession = class {
|
|
|
3548
3565
|
for await (const event of this.#services.runLoop(command.goal, signal)) this.#services.output(event);
|
|
3549
3566
|
} else if (command.name === "goal") {
|
|
3550
3567
|
for await (const event of this.#services.runGoal(command.goal, signal)) this.#services.output(event);
|
|
3568
|
+
} else if (command.name === "commit") {
|
|
3569
|
+
this.#notice(await required(this.#services.gitCommit, "commit")(command.hint, signal));
|
|
3570
|
+
} else if (command.name === "review") {
|
|
3571
|
+
this.#notice(await required(this.#services.gitReview, "review")(command.focus, signal));
|
|
3551
3572
|
} else if (command.name === "mcp") {
|
|
3552
3573
|
this.#notice(await this.#services.mcp(command, signal));
|
|
3553
3574
|
} else if (command.name === "pals") {
|
|
@@ -3610,6 +3631,8 @@ var FlavorSession = class {
|
|
|
3610
3631
|
} else if (command.name === "login") {
|
|
3611
3632
|
this.#notice("Opening browser for authentication...");
|
|
3612
3633
|
this.#notice(await this.#services.login());
|
|
3634
|
+
} else if (command.name === "logout") {
|
|
3635
|
+
this.#notice(await this.#services.logout());
|
|
3613
3636
|
} else if (command.name === "help") this.#notice(HELP);
|
|
3614
3637
|
else if (command.name === "exit") this.#services.output({ type: "exit" });
|
|
3615
3638
|
}
|
|
@@ -4919,7 +4942,10 @@ var TaskNodeSchema = z4.object({
|
|
|
4919
4942
|
description: z4.string().trim().min(1),
|
|
4920
4943
|
dependencies: z4.array(z4.string().trim().min(1)),
|
|
4921
4944
|
expectedOutputs: z4.array(z4.string().trim().min(1)),
|
|
4922
|
-
verification: z4.array(z4.string().trim().min(1))
|
|
4945
|
+
verification: z4.array(z4.string().trim().min(1)),
|
|
4946
|
+
files: z4.array(z4.string().trim().min(1)).describe(
|
|
4947
|
+
"Workspace files this task owns for writing. Tasks whose files overlap are never run in parallel, so declare every file this task may create or modify."
|
|
4948
|
+
).optional()
|
|
4923
4949
|
}).strict();
|
|
4924
4950
|
var TaskGraphSchema = z4.object({
|
|
4925
4951
|
nodes: z4.array(TaskNodeSchema)
|
|
@@ -5106,6 +5132,7 @@ var SubagentScheduler = class {
|
|
|
5106
5132
|
const states = new Map(graph.nodes.map((task) => [task.id, "pending"]));
|
|
5107
5133
|
const results = /* @__PURE__ */ new Map();
|
|
5108
5134
|
const running = /* @__PURE__ */ new Map();
|
|
5135
|
+
const runningFiles = /* @__PURE__ */ new Map();
|
|
5109
5136
|
const started = /* @__PURE__ */ new Set();
|
|
5110
5137
|
try {
|
|
5111
5138
|
while (statesHaveWork(states)) {
|
|
@@ -5114,7 +5141,9 @@ var SubagentScheduler = class {
|
|
|
5114
5141
|
for (const task of graph.nodes) {
|
|
5115
5142
|
if (running.size >= this.#options.maxSubagents) break;
|
|
5116
5143
|
if (states.get(task.id) !== "pending" || !isReady(task, states)) continue;
|
|
5144
|
+
if (conflictsWithRunning(task, runningFiles)) continue;
|
|
5117
5145
|
states.set(task.id, "running");
|
|
5146
|
+
if (task.files !== void 0) runningFiles.set(task.id, task.files.map(normalizeOwnedFile));
|
|
5118
5147
|
const execution = this.#runTask(task, signal);
|
|
5119
5148
|
running.set(task.id, execution);
|
|
5120
5149
|
started.add(execution);
|
|
@@ -5127,6 +5156,7 @@ var SubagentScheduler = class {
|
|
|
5127
5156
|
const completion = await Promise.race(running.values());
|
|
5128
5157
|
signal.throwIfAborted();
|
|
5129
5158
|
running.delete(completion.id);
|
|
5159
|
+
runningFiles.delete(completion.id);
|
|
5130
5160
|
results.set(completion.id, completion.result);
|
|
5131
5161
|
states.set(completion.id, completion.result.status);
|
|
5132
5162
|
await this.#options.onResult?.(completion.result);
|
|
@@ -5204,6 +5234,26 @@ var SubagentScheduler = class {
|
|
|
5204
5234
|
function isReady(task, states) {
|
|
5205
5235
|
return task.dependencies.every((dependency) => states.get(dependency) === "completed");
|
|
5206
5236
|
}
|
|
5237
|
+
function normalizeOwnedFile(value) {
|
|
5238
|
+
return value.replace(/\\/g, "/");
|
|
5239
|
+
}
|
|
5240
|
+
function ownedFilesConflict(left, right) {
|
|
5241
|
+
for (const one of left) {
|
|
5242
|
+
for (const other of right) {
|
|
5243
|
+
if (one === other) return true;
|
|
5244
|
+
if (other.startsWith(`${one}/`) || one.startsWith(`${other}/`)) return true;
|
|
5245
|
+
}
|
|
5246
|
+
}
|
|
5247
|
+
return false;
|
|
5248
|
+
}
|
|
5249
|
+
function conflictsWithRunning(task, runningFiles) {
|
|
5250
|
+
if (task.files === void 0 || task.files.length === 0) return false;
|
|
5251
|
+
const owned = task.files.map(normalizeOwnedFile);
|
|
5252
|
+
for (const files of runningFiles.values()) {
|
|
5253
|
+
if (ownedFilesConflict(owned, files)) return true;
|
|
5254
|
+
}
|
|
5255
|
+
return false;
|
|
5256
|
+
}
|
|
5207
5257
|
function statesHaveWork(states) {
|
|
5208
5258
|
return [...states.values()].some((state) => state === "pending" || state === "running");
|
|
5209
5259
|
}
|
|
@@ -8618,23 +8668,45 @@ async function readJsonArray(file) {
|
|
|
8618
8668
|
return [];
|
|
8619
8669
|
}
|
|
8620
8670
|
}
|
|
8671
|
+
async function readRules(file) {
|
|
8672
|
+
try {
|
|
8673
|
+
const parsed = JSON.parse(await readFile7(file, "utf8"));
|
|
8674
|
+
if (!Array.isArray(parsed)) return [];
|
|
8675
|
+
return parsed.filter((item) => item !== null && typeof item === "object").filter((item) => typeof item.id === "string" && typeof item.text === "string").map((item) => ({
|
|
8676
|
+
id: item.id,
|
|
8677
|
+
text: item.text,
|
|
8678
|
+
addedAt: typeof item.addedAt === "string" ? item.addedAt : "",
|
|
8679
|
+
...typeof item.sourceId === "string" ? { sourceId: item.sourceId } : {}
|
|
8680
|
+
}));
|
|
8681
|
+
} catch {
|
|
8682
|
+
return [];
|
|
8683
|
+
}
|
|
8684
|
+
}
|
|
8685
|
+
async function writeRules(file, rules) {
|
|
8686
|
+
await mkdir9(dirname10(file), { recursive: true, mode: 448 });
|
|
8687
|
+
await writeFile7(file, JSON.stringify(rules, null, 2), { encoding: "utf8", mode: 384 });
|
|
8688
|
+
}
|
|
8621
8689
|
var EvolveStore = class {
|
|
8622
8690
|
dir;
|
|
8623
8691
|
signalsFile;
|
|
8624
8692
|
reflectionsFile;
|
|
8625
8693
|
doneFile;
|
|
8626
8694
|
verifiedFile;
|
|
8695
|
+
rulesFile;
|
|
8627
8696
|
maxSignals;
|
|
8697
|
+
maxRules;
|
|
8628
8698
|
// Serialize file mutations: tool hooks may fire in bursts.
|
|
8629
8699
|
#queue = Promise.resolve();
|
|
8630
8700
|
constructor(options) {
|
|
8631
|
-
const { workspace, maxSignals = 400 } = options;
|
|
8701
|
+
const { workspace, maxSignals = 400, maxRules = 20 } = options;
|
|
8632
8702
|
this.dir = join9(workspace, ".flavor", "evolve");
|
|
8633
8703
|
this.signalsFile = join9(this.dir, "signals.jsonl");
|
|
8634
8704
|
this.reflectionsFile = join9(this.dir, "reflections.jsonl");
|
|
8635
8705
|
this.doneFile = join9(this.dir, "done.json");
|
|
8636
8706
|
this.verifiedFile = join9(this.dir, "verified.json");
|
|
8707
|
+
this.rulesFile = join9(this.dir, "rules.json");
|
|
8637
8708
|
this.maxSignals = maxSignals;
|
|
8709
|
+
this.maxRules = maxRules;
|
|
8638
8710
|
}
|
|
8639
8711
|
#enqueue(operation) {
|
|
8640
8712
|
const next = this.#queue.then(operation, operation);
|
|
@@ -8780,6 +8852,43 @@ var EvolveStore = class {
|
|
|
8780
8852
|
}
|
|
8781
8853
|
});
|
|
8782
8854
|
}
|
|
8855
|
+
/** Learned guardrail rules, oldest first. */
|
|
8856
|
+
listRules() {
|
|
8857
|
+
return this.#enqueue(() => readRules(this.rulesFile));
|
|
8858
|
+
}
|
|
8859
|
+
/**
|
|
8860
|
+
* Add one guardrail rule. Dedupes by fingerprint of the normalized text and
|
|
8861
|
+
* keeps at most maxRules entries (oldest dropped first).
|
|
8862
|
+
*/
|
|
8863
|
+
addRule(input) {
|
|
8864
|
+
return this.#enqueue(async () => {
|
|
8865
|
+
const text = input.text.replace(/\s+/g, " ").trim().slice(0, 300);
|
|
8866
|
+
if (text === "") throw new Error("rule text must not be empty");
|
|
8867
|
+
const id = fingerprint("rule", void 0, text);
|
|
8868
|
+
const rules = await readRules(this.rulesFile);
|
|
8869
|
+
const existing = rules.find((rule2) => rule2.id === id);
|
|
8870
|
+
if (existing !== void 0) return { added: false, rule: existing };
|
|
8871
|
+
const rule = {
|
|
8872
|
+
id,
|
|
8873
|
+
text,
|
|
8874
|
+
addedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8875
|
+
...input.sourceId === void 0 ? {} : { sourceId: input.sourceId }
|
|
8876
|
+
};
|
|
8877
|
+
rules.push(rule);
|
|
8878
|
+
if (rules.length > this.maxRules) rules.splice(0, rules.length - this.maxRules);
|
|
8879
|
+
await writeRules(this.rulesFile, rules);
|
|
8880
|
+
return { added: true, rule };
|
|
8881
|
+
});
|
|
8882
|
+
}
|
|
8883
|
+
removeRule(id) {
|
|
8884
|
+
return this.#enqueue(async () => {
|
|
8885
|
+
const rules = await readRules(this.rulesFile);
|
|
8886
|
+
const next = rules.filter((rule) => rule.id !== id);
|
|
8887
|
+
if (next.length === rules.length) return { removed: false };
|
|
8888
|
+
await writeRules(this.rulesFile, next);
|
|
8889
|
+
return { removed: true };
|
|
8890
|
+
});
|
|
8891
|
+
}
|
|
8783
8892
|
};
|
|
8784
8893
|
|
|
8785
8894
|
// src/evolve/service.ts
|
|
@@ -8791,9 +8900,15 @@ a plugin, a memory rule, a tool wrapper, or a safer default), implement it in
|
|
|
8791
8900
|
this session when it is in scope; otherwise ignore them. Never act on them
|
|
8792
8901
|
without running the test suite afterwards.
|
|
8793
8902
|
|
|
8903
|
+
`;
|
|
8904
|
+
var GUARDRAILS_SECTION_HEADER = `# learned guardrails (evolve)
|
|
8905
|
+
|
|
8906
|
+
These rules were distilled from repeated tool failures in this workspace.
|
|
8907
|
+
Follow them unless the current task or the user explicitly contradicts one.
|
|
8908
|
+
|
|
8794
8909
|
`;
|
|
8795
8910
|
var USAGE = [
|
|
8796
|
-
"usage: /evolve <signals|suggest|improve <id>|verify <name>|reload <name>|test|revert <name>|done <id>|verified|clear>",
|
|
8911
|
+
"usage: /evolve <signals|suggest|improve <id>|verify <name>|reload <name>|test|revert <name>|done <id>|verified|trends [n]|rule <list|add|remove>|clear>",
|
|
8797
8912
|
" signals list recent failing tool results",
|
|
8798
8913
|
" suggest aggregate repeated failures into fix suggestions (trend-aware ordering)",
|
|
8799
8914
|
" improve scaffold a fix-<tool>/ plugin dir + PLAN.md for one suggestion",
|
|
@@ -8803,6 +8918,8 @@ var USAGE = [
|
|
|
8803
8918
|
" revert restore the last good snapshot of a plugin",
|
|
8804
8919
|
" done mark a suggestion as handled (no longer proposed)",
|
|
8805
8920
|
" verified list suggestions auto-verified by improving failure trends",
|
|
8921
|
+
" trends cross-run dashboard: tool calls, failures, signalDelta, per-tool movement",
|
|
8922
|
+
" rule manage learned guardrails injected into system prompts (list/add <text>/remove <id>)",
|
|
8806
8923
|
" clear reset signals, done markers, and verified markers"
|
|
8807
8924
|
].join("\n");
|
|
8808
8925
|
function runCommand(command, cwd, timeoutMs) {
|
|
@@ -8894,9 +9011,17 @@ function createEvolveService(options) {
|
|
|
8894
9011
|
}
|
|
8895
9012
|
async function refreshPromptCache() {
|
|
8896
9013
|
try {
|
|
8897
|
-
const suggestions = await openWithTrends(promptTop);
|
|
8898
|
-
|
|
8899
|
-
|
|
9014
|
+
const [suggestions, rules] = await Promise.all([openWithTrends(promptTop), store.listRules()]);
|
|
9015
|
+
const sections = [];
|
|
9016
|
+
if (rules.length > 0) {
|
|
9017
|
+
sections.push(`${GUARDRAILS_SECTION_HEADER}${rules.map((rule) => `- ${rule.text}`).join("\n")}
|
|
9018
|
+
`);
|
|
9019
|
+
}
|
|
9020
|
+
if (suggestions.length > 0) {
|
|
9021
|
+
sections.push(`${SUGGEST_SECTION_HEADER}${suggestions.map((suggestion) => `- [${suggestion.id}] ${suggestion.hint}`).join("\n")}
|
|
9022
|
+
`);
|
|
9023
|
+
}
|
|
9024
|
+
promptCache = sections.length === 0 ? void 0 : sections.join("\n");
|
|
8900
9025
|
} catch (error) {
|
|
8901
9026
|
logger.warn(`prompt section failed \u2014 ${error instanceof Error ? error.message : String(error)}`);
|
|
8902
9027
|
}
|
|
@@ -9076,6 +9201,41 @@ function createEvolveService(options) {
|
|
|
9076
9201
|
return signal === void 0 ? `[${id}] (signal no longer recorded)` : `[${id}] ${signal.tool} x${signal.count} \u2014 ${signal.error} (verified)`;
|
|
9077
9202
|
}).join("\n");
|
|
9078
9203
|
}
|
|
9204
|
+
if (arg === "trends" || arg.startsWith("trends ")) {
|
|
9205
|
+
const requested = Number.parseInt(arg.slice(6).trim(), 10);
|
|
9206
|
+
const count = Number.isNaN(requested) ? 5 : Math.min(Math.max(requested, 1), 50);
|
|
9207
|
+
const reflections = await store.reflections(count);
|
|
9208
|
+
if (reflections.length === 0) return "no reflections recorded yet \u2014 trends appear after runs end";
|
|
9209
|
+
const sign = (value) => value > 0 ? `+${value}` : String(value);
|
|
9210
|
+
const lines = [`evolve trends (last ${reflections.length} run(s), newest first)`];
|
|
9211
|
+
for (const reflection of reflections) {
|
|
9212
|
+
lines.push(`${reflection.at.replace("T", " ").slice(0, 19)} ${reflection.reason}: model calls ${reflection.iterations}, tool calls ${reflection.toolCalls} (${reflection.toolErrors} failed), failures ${reflection.totalFailures} (delta ${sign(reflection.signalDelta)})`);
|
|
9213
|
+
const moved = Object.entries(reflection.perTool ?? {}).filter(([, trend]) => trend.delta !== 0);
|
|
9214
|
+
for (const [tool, trend] of moved) {
|
|
9215
|
+
lines.push(` - ${tool}: ${trend.failures} failure(s) this run (${sign(trend.delta)} vs previous)`);
|
|
9216
|
+
}
|
|
9217
|
+
}
|
|
9218
|
+
return lines.join("\n");
|
|
9219
|
+
}
|
|
9220
|
+
if (arg === "rule" || arg === "rule list") {
|
|
9221
|
+
const rules = await store.listRules();
|
|
9222
|
+
if (rules.length === 0) return "no guardrail rules yet \u2014 add one with /evolve rule add <text>";
|
|
9223
|
+
return rules.map((rule) => `[${rule.id}] ${rule.text}`).join("\n");
|
|
9224
|
+
}
|
|
9225
|
+
if (arg.startsWith("rule add ")) {
|
|
9226
|
+
const text = arg.slice(9).trim();
|
|
9227
|
+
if (text === "") return "usage: /evolve rule add <text>";
|
|
9228
|
+
const { added, rule } = await store.addRule({ text });
|
|
9229
|
+
await refreshPromptCache();
|
|
9230
|
+
return added ? `added guardrail [${rule.id}]: ${rule.text}` : `guardrail already exists [${rule.id}]: ${rule.text}`;
|
|
9231
|
+
}
|
|
9232
|
+
if (arg.startsWith("rule remove ")) {
|
|
9233
|
+
const id = arg.slice(12).trim();
|
|
9234
|
+
if (id === "") return "usage: /evolve rule remove <id>";
|
|
9235
|
+
const { removed } = await store.removeRule(id);
|
|
9236
|
+
await refreshPromptCache();
|
|
9237
|
+
return removed ? `removed guardrail ${id}` : `no guardrail with id "${id}"`;
|
|
9238
|
+
}
|
|
9079
9239
|
if (arg.startsWith("improve ")) {
|
|
9080
9240
|
const suggestionId = arg.slice(8).trim();
|
|
9081
9241
|
const suggestions = await openWithTrends(100);
|
|
@@ -9147,18 +9307,31 @@ ${result.stderr.slice(-4e3)}` : ""}`;
|
|
|
9147
9307
|
toolDefinition() {
|
|
9148
9308
|
const inputSchema = z12.object({
|
|
9149
9309
|
suggestionId: z12.string().min(1).describe("Signal id from the evolve suggestions"),
|
|
9150
|
-
implementation: z12.string().min(1).describe("Concise description of the
|
|
9310
|
+
implementation: z12.string().min(1).describe("Concise description of the fix to implement (plugin plan, or the guardrail rule text when kind is prompt_rule)"),
|
|
9311
|
+
kind: z12.enum(["plugin", "prompt_rule"]).optional().describe(
|
|
9312
|
+
"plugin (default): scaffold a fix plugin; prompt_rule: store a guardrail rule that is injected into future system prompts"
|
|
9313
|
+
)
|
|
9151
9314
|
});
|
|
9152
9315
|
return {
|
|
9153
9316
|
name: "evolve_improve",
|
|
9154
|
-
description: "Implement a fix for one repeated tool failure
|
|
9317
|
+
description: "Implement a fix for one repeated tool failure. Default kind=plugin scaffolds the fix-<tool>/ plugin dir and writes PLAN.md with instructions for implementing, verifying, reloading, and testing it. kind=prompt_rule instead stores a concise guardrail rule (from `implementation`) that is injected into future system prompts and closes the suggestion. Use when the model proposes a concrete fix for a suggestion in the system prompt.",
|
|
9155
9318
|
inputSchema,
|
|
9156
9319
|
paths: () => [],
|
|
9157
9320
|
execute: async (input) => {
|
|
9158
|
-
const { suggestionId, implementation } = input;
|
|
9321
|
+
const { suggestionId, implementation, kind } = input;
|
|
9159
9322
|
const suggestions = await openWithTrends(100);
|
|
9160
9323
|
const suggestion = suggestions.find((item) => item.id === suggestionId);
|
|
9161
9324
|
if (suggestion === void 0) throw new Error(`No open suggestion with id "${suggestionId}".`);
|
|
9325
|
+
if (kind === "prompt_rule") {
|
|
9326
|
+
const { added, rule } = await store.addRule({ text: implementation, sourceId: suggestionId });
|
|
9327
|
+
await store.markSuggestionDone(suggestionId);
|
|
9328
|
+
await refreshPromptCache();
|
|
9329
|
+
return [
|
|
9330
|
+
added ? `Stored guardrail rule [${rule.id}]: ${rule.text}` : `Guardrail already exists [${rule.id}]: ${rule.text}`,
|
|
9331
|
+
`Suggestion [${suggestion.id}] marked done. The rule is injected into the system prompt of future runs.`,
|
|
9332
|
+
"Review rules with /evolve rule list; remove one with /evolve rule remove <id>."
|
|
9333
|
+
].join("\n");
|
|
9334
|
+
}
|
|
9162
9335
|
const name = sanitizePluginName(suggestion.tool);
|
|
9163
9336
|
const dir = await scaffoldFixPlugin(workspace, name);
|
|
9164
9337
|
await writeFile8(join10(dir, "PLAN.md"), buildPlan(suggestion, name, implementation), "utf8");
|
|
@@ -10478,7 +10651,7 @@ import { readFile as readFile11 } from "fs/promises";
|
|
|
10478
10651
|
import { join as join16 } from "path";
|
|
10479
10652
|
|
|
10480
10653
|
// src/tools/shell.ts
|
|
10481
|
-
import { spawn as spawn6 } from "child_process";
|
|
10654
|
+
import { spawn as spawn6, spawnSync } from "child_process";
|
|
10482
10655
|
import { isAbsolute as isAbsolute6, relative as relative11, resolve as resolve13, sep as sep10 } from "path";
|
|
10483
10656
|
import { z as z16 } from "zod";
|
|
10484
10657
|
|
|
@@ -10608,6 +10781,29 @@ function tokenizeCommandLine(command) {
|
|
|
10608
10781
|
function unquoteToken(token) {
|
|
10609
10782
|
return token.length >= 2 && token.startsWith('"') && token.endsWith('"') ? token.slice(1, -1) : token;
|
|
10610
10783
|
}
|
|
10784
|
+
var cachedWindowsShell;
|
|
10785
|
+
function hasExecutable(name) {
|
|
10786
|
+
try {
|
|
10787
|
+
return spawnSync("where", [name], { stdio: "ignore", windowsHide: true }).status === 0;
|
|
10788
|
+
} catch {
|
|
10789
|
+
return false;
|
|
10790
|
+
}
|
|
10791
|
+
}
|
|
10792
|
+
function detectWindowsShell() {
|
|
10793
|
+
if (cachedWindowsShell !== void 0) return cachedWindowsShell;
|
|
10794
|
+
cachedWindowsShell = hasExecutable("pwsh") ? "pwsh" : hasExecutable("powershell") ? "powershell" : "cmd";
|
|
10795
|
+
return cachedWindowsShell;
|
|
10796
|
+
}
|
|
10797
|
+
function psQuote(token) {
|
|
10798
|
+
return `'${token.replace(/'/g, "''")}'`;
|
|
10799
|
+
}
|
|
10800
|
+
function buildWindowsCommandLine(kind, commandLine, command, args) {
|
|
10801
|
+
if (kind === "cmd") return `chcp 65001>nul & ${commandLine}`;
|
|
10802
|
+
const executable = kind === "pwsh" ? "pwsh" : "powershell";
|
|
10803
|
+
const script = `& ${psQuote(command)}${args.length > 0 ? ` ${args.map(psQuote).join(" ")}` : ""}; exit $LASTEXITCODE`;
|
|
10804
|
+
const encoded = Buffer.from(script, "utf16le").toString("base64");
|
|
10805
|
+
return `chcp 65001>nul & ${executable} -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`;
|
|
10806
|
+
}
|
|
10611
10807
|
function createShellTool(workspace, options = {}) {
|
|
10612
10808
|
const root = resolve13(workspace);
|
|
10613
10809
|
const maxBytes = options.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES2;
|
|
@@ -10728,7 +10924,7 @@ async function executeShell(root, input, cancellation, maxBytes, onOutput) {
|
|
|
10728
10924
|
const quoteArg = (s) => s.includes(" ") ? `"${s.replace(/"/g, '""')}"` : s;
|
|
10729
10925
|
const quoted = input.args.map(quoteArg).join(" ");
|
|
10730
10926
|
const commandLine = quoted.length > 0 ? `${quoteArg(input.command)} ${quoted}` : quoteArg(input.command);
|
|
10731
|
-
const shellCommandLine = process.platform === "win32" ?
|
|
10927
|
+
const shellCommandLine = process.platform === "win32" ? buildWindowsCommandLine(detectWindowsShell(), commandLine, input.command, input.args) : commandLine;
|
|
10732
10928
|
const child = spawn6(shellCommandLine, {
|
|
10733
10929
|
cwd,
|
|
10734
10930
|
shell: true,
|
|
@@ -12267,6 +12463,11 @@ function escapeHtml(text) {
|
|
|
12267
12463
|
|
|
12268
12464
|
// src/auth/store.ts
|
|
12269
12465
|
import { dirname as dirname13 } from "path";
|
|
12466
|
+
async function retainOnlyCredentials(store, keepCredentialId) {
|
|
12467
|
+
const tokens = await store.load();
|
|
12468
|
+
const retained = Object.fromEntries(Object.entries(tokens).filter(([id]) => id === keepCredentialId));
|
|
12469
|
+
await store.save(retained);
|
|
12470
|
+
}
|
|
12270
12471
|
function createFileTokenStore(filePath) {
|
|
12271
12472
|
let baseline = {};
|
|
12272
12473
|
const store = {
|
|
@@ -12427,11 +12628,11 @@ Treat the assigned task as self-contained. The caller may not share its full con
|
|
|
12427
12628
|
Own the user's request end to end. Collaborate when a choice materially changes the result, but use sound judgment for ordinary in-scope implementation details. Keep the user informed at meaningful milestones, synthesize any child-agent results yourself, and do not stop while safe, relevant work remains. A forked child receives an explicit Subagent directive after the inherited conversation; in that case, follow that narrower directive for the child assignment.`;
|
|
12428
12629
|
}
|
|
12429
12630
|
function environmentSection(options) {
|
|
12430
|
-
const
|
|
12631
|
+
const git3 = options.environment.isGitRepository === "unknown" ? "unknown" : options.environment.isGitRepository ? "yes" : "no";
|
|
12431
12632
|
return `# Environment
|
|
12432
12633
|
|
|
12433
12634
|
- Working directory: ${data(options.workspace)}
|
|
12434
|
-
- Git repository: ${
|
|
12635
|
+
- Git repository: ${git3}
|
|
12435
12636
|
- Platform: ${data(options.environment.platform)}
|
|
12436
12637
|
- OS version: ${data(options.environment.osVersion)}
|
|
12437
12638
|
- Shell: ${data(options.environment.shell)}`;
|
|
@@ -15396,19 +15597,255 @@ function severityLabel(severity) {
|
|
|
15396
15597
|
}
|
|
15397
15598
|
}
|
|
15398
15599
|
|
|
15399
|
-
// src/
|
|
15600
|
+
// src/git/service.ts
|
|
15601
|
+
async function git2(workspace, args, timeoutMs = 3e4) {
|
|
15602
|
+
const result = await execFileNoThrow("git", ["-C", workspace, ...args], { timeout: timeoutMs, useCwd: false });
|
|
15603
|
+
return { ok: result.code === 0, stdout: result.stdout, stderr: result.stderr, code: result.code };
|
|
15604
|
+
}
|
|
15605
|
+
async function isGitRepository(workspace) {
|
|
15606
|
+
const result = await git2(workspace, ["rev-parse", "--is-inside-work-tree"], 2e3);
|
|
15607
|
+
return result.ok && result.stdout.trim() === "true";
|
|
15608
|
+
}
|
|
15609
|
+
function gitFailure(result, action) {
|
|
15610
|
+
const detail = result.stderr.trim() || result.stdout.trim() || `exit code ${result.code}`;
|
|
15611
|
+
return new Error(`git ${action} failed: ${detail.split("\n")[0]}`);
|
|
15612
|
+
}
|
|
15613
|
+
async function changeSummary(workspace) {
|
|
15614
|
+
const branch = await git2(workspace, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
15615
|
+
const head = await git2(workspace, ["rev-parse", "--short", "HEAD"]);
|
|
15616
|
+
const status = await git2(workspace, ["status", "--porcelain"]);
|
|
15617
|
+
if (!status.ok) throw gitFailure(status, "status");
|
|
15618
|
+
const statusLines = status.stdout.split("\n").map((line) => line.trimEnd()).filter((line) => line.length > 0);
|
|
15619
|
+
const stagedFiles = [];
|
|
15620
|
+
const unstagedFiles = [];
|
|
15621
|
+
const untrackedFiles = [];
|
|
15622
|
+
for (const line of statusLines) {
|
|
15623
|
+
const index = line[0] ?? " ";
|
|
15624
|
+
const worktree = line[1] ?? " ";
|
|
15625
|
+
const file = line.slice(3);
|
|
15626
|
+
if (index === "?" && worktree === "?") {
|
|
15627
|
+
untrackedFiles.push(file);
|
|
15628
|
+
continue;
|
|
15629
|
+
}
|
|
15630
|
+
if (index !== " " && index !== "?") stagedFiles.push(file);
|
|
15631
|
+
if (worktree !== " " && worktree !== "?") unstagedFiles.push(file);
|
|
15632
|
+
}
|
|
15633
|
+
return {
|
|
15634
|
+
branch: branch.ok ? branch.stdout.trim() : "HEAD",
|
|
15635
|
+
head: head.ok ? head.stdout.trim() : "unknown",
|
|
15636
|
+
statusLines,
|
|
15637
|
+
stagedFiles: [...new Set(stagedFiles)],
|
|
15638
|
+
unstagedFiles: [...new Set(unstagedFiles)],
|
|
15639
|
+
untrackedFiles
|
|
15640
|
+
};
|
|
15641
|
+
}
|
|
15642
|
+
async function stagedDiff(workspace, maxChars = 96e3) {
|
|
15643
|
+
const stat9 = await git2(workspace, ["diff", "--staged", "--stat"]);
|
|
15644
|
+
const diff = await git2(workspace, ["diff", "--staged", "--no-ext-diff", "--no-color"]);
|
|
15645
|
+
if (!diff.ok) throw gitFailure(diff, "diff --staged");
|
|
15646
|
+
const text = diff.stdout;
|
|
15647
|
+
return {
|
|
15648
|
+
diff: text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
15649
|
+
\u2026[diff truncated]` : text,
|
|
15650
|
+
stat: stat9.stdout.trim(),
|
|
15651
|
+
truncated: text.length > maxChars
|
|
15652
|
+
};
|
|
15653
|
+
}
|
|
15654
|
+
async function uncommittedDiff(workspace, maxChars = 96e3) {
|
|
15655
|
+
const stat9 = await git2(workspace, ["diff", "HEAD", "--stat"]);
|
|
15656
|
+
const diff = await git2(workspace, ["diff", "HEAD", "--no-ext-diff", "--no-color"]);
|
|
15657
|
+
if (!diff.ok && !diff.stderr.includes("unknown revision")) throw gitFailure(diff, "diff HEAD");
|
|
15658
|
+
const summary2 = await changeSummary(workspace);
|
|
15659
|
+
const text = diff.stdout;
|
|
15660
|
+
return {
|
|
15661
|
+
diff: text.length > maxChars ? `${text.slice(0, maxChars)}
|
|
15662
|
+
\u2026[diff truncated]` : text,
|
|
15663
|
+
stat: stat9.stdout.trim(),
|
|
15664
|
+
untracked: summary2.untrackedFiles
|
|
15665
|
+
};
|
|
15666
|
+
}
|
|
15667
|
+
async function stageAll(workspace) {
|
|
15668
|
+
const result = await git2(workspace, ["add", "-A"]);
|
|
15669
|
+
if (!result.ok) throw gitFailure(result, "add");
|
|
15670
|
+
}
|
|
15671
|
+
async function commit(workspace, commitMessage) {
|
|
15672
|
+
const result = await execFileNoThrow("git", ["-C", workspace, "commit", "-F", "-"], {
|
|
15673
|
+
timeout: 3e4,
|
|
15674
|
+
useCwd: false,
|
|
15675
|
+
input: commitMessage
|
|
15676
|
+
});
|
|
15677
|
+
if (result.code !== 0) {
|
|
15678
|
+
const detail = result.stderr.trim() || result.stdout.trim() || `exit code ${result.code}`;
|
|
15679
|
+
throw new Error(`git commit failed: ${detail.split("\n")[0]}`);
|
|
15680
|
+
}
|
|
15681
|
+
const head = await git2(workspace, ["log", "-1", "--pretty=%h %s"]);
|
|
15682
|
+
return head.stdout.trim() || "committed";
|
|
15683
|
+
}
|
|
15684
|
+
async function fileHistory(workspace, path, limit) {
|
|
15685
|
+
const args = [
|
|
15686
|
+
"log",
|
|
15687
|
+
`--max-count=${limit}`,
|
|
15688
|
+
"--date=short",
|
|
15689
|
+
"--pretty=format:%h%x1f%ad%x1f%an%x1f%s",
|
|
15690
|
+
...path === void 0 ? [] : ["--follow", "--", path]
|
|
15691
|
+
];
|
|
15692
|
+
const result = await git2(workspace, args, 15e3);
|
|
15693
|
+
if (!result.ok) throw gitFailure(result, "log");
|
|
15694
|
+
return result.stdout.split("\n").filter((line) => line.trim().length > 0).map((line) => {
|
|
15695
|
+
const [hash = "", date = "", author = "", ...subject] = line.split("");
|
|
15696
|
+
return { hash, date, author, subject: subject.join("") };
|
|
15697
|
+
});
|
|
15698
|
+
}
|
|
15699
|
+
async function gitMarker(workspace) {
|
|
15700
|
+
try {
|
|
15701
|
+
if (!await isGitRepository(workspace)) return void 0;
|
|
15702
|
+
const summary2 = await changeSummary(workspace);
|
|
15703
|
+
if (summary2.head === "unknown") return void 0;
|
|
15704
|
+
const dirty = summary2.statusLines.length > 0 ? "-dirty" : "";
|
|
15705
|
+
return `${summary2.branch}@${summary2.head}${dirty}`;
|
|
15706
|
+
} catch {
|
|
15707
|
+
return void 0;
|
|
15708
|
+
}
|
|
15709
|
+
}
|
|
15710
|
+
|
|
15711
|
+
// src/git/insights.ts
|
|
15400
15712
|
import { z as z24 } from "zod";
|
|
15401
|
-
|
|
15402
|
-
|
|
15403
|
-
|
|
15713
|
+
async function suggestCommitMessage(options, input, signal) {
|
|
15714
|
+
const prompt = [
|
|
15715
|
+
"Write a git commit message for the staged changes below.",
|
|
15716
|
+
"Rules: Conventional Commits style (feat/fix/refactor/docs/test/chore scope); a short imperative subject line <= 72 chars; an optional body only when the change needs explanation; no code fences; reply with the commit message only.",
|
|
15717
|
+
...input.hint === void 0 || input.hint.trim() === "" ? [] : [`The user added this context: ${input.hint.trim()}`],
|
|
15718
|
+
"",
|
|
15719
|
+
`Change summary:
|
|
15720
|
+
${input.stat}`,
|
|
15721
|
+
"",
|
|
15722
|
+
`Diff:
|
|
15723
|
+
${input.diff}`
|
|
15724
|
+
].join("\n");
|
|
15725
|
+
const { adapter, model } = options.registry.get(options.modelId());
|
|
15726
|
+
let text = "";
|
|
15727
|
+
for await (const event of adapter.stream({
|
|
15728
|
+
model,
|
|
15729
|
+
messages: [{ role: "user", content: prompt }],
|
|
15730
|
+
tools: [],
|
|
15731
|
+
signal
|
|
15732
|
+
})) {
|
|
15733
|
+
if (event.type === "text") text += event.text;
|
|
15734
|
+
else if (event.type === "error") throw new Error(`commit message generation failed: ${event.error.message}`);
|
|
15735
|
+
else if (event.type === "done") break;
|
|
15736
|
+
}
|
|
15737
|
+
const cleaned = cleanCommitMessage(text);
|
|
15738
|
+
if (cleaned === "") throw new Error("commit message generation returned no text");
|
|
15739
|
+
return cleaned;
|
|
15740
|
+
}
|
|
15741
|
+
function cleanCommitMessage(raw) {
|
|
15742
|
+
const trimmed = raw.trim().replace(/^```[a-z]*\s*/iu, "").replace(/```$/u, "").trim();
|
|
15743
|
+
const lines = trimmed.split("\n");
|
|
15744
|
+
const kept = [];
|
|
15745
|
+
for (const line of lines) {
|
|
15746
|
+
if (/^(here('|’)s|notes?:|explanation:)/iu.test(line.trim()) && kept.length > 0) break;
|
|
15747
|
+
kept.push(line);
|
|
15748
|
+
}
|
|
15749
|
+
return kept.join("\n").trim().slice(0, 2e3);
|
|
15750
|
+
}
|
|
15751
|
+
var ReviewFindingSchema = z24.object({
|
|
15752
|
+
severity: z24.enum(["critical", "warning", "nit"]),
|
|
15753
|
+
file: z24.string(),
|
|
15754
|
+
line: z24.string().describe('Line number or hunk header the finding refers to, e.g. "42" or "@@ -10,4 +10,6 @@"'),
|
|
15755
|
+
issue: z24.string(),
|
|
15756
|
+
suggestion: z24.string()
|
|
15757
|
+
}).strict();
|
|
15758
|
+
var ReviewReportSchema = z24.object({
|
|
15759
|
+
summary: z24.string().describe("One-paragraph overall assessment of the change"),
|
|
15760
|
+
verdict: z24.enum(["ship", "ship-with-fixes", "needs-work"]),
|
|
15761
|
+
findings: z24.array(ReviewFindingSchema).max(20)
|
|
15762
|
+
}).strict();
|
|
15763
|
+
async function reviewDiff(options, input, signal) {
|
|
15764
|
+
const prompt = [
|
|
15765
|
+
"You are reviewing uncommitted local changes before the user commits them.",
|
|
15766
|
+
"Focus on: correctness bugs, broken error handling, security issues (secrets, injection, path traversal), missing edge cases, and obvious test gaps.",
|
|
15767
|
+
"Do not comment on formatting or style unless it hides a bug. Report at most the 10 most important findings.",
|
|
15768
|
+
...input.focus === void 0 || input.focus.trim() === "" ? [] : [`The user specifically wants attention on: ${input.focus.trim()}`],
|
|
15769
|
+
...input.untracked.length === 0 ? [] : [`Untracked files not shown in the diff: ${input.untracked.join(", ")}`],
|
|
15770
|
+
"",
|
|
15771
|
+
`Change summary:
|
|
15772
|
+
${input.stat}`,
|
|
15773
|
+
"",
|
|
15774
|
+
`Diff:
|
|
15775
|
+
${input.diff}`
|
|
15776
|
+
].join("\n");
|
|
15777
|
+
const structured = withStructuredOutput({
|
|
15778
|
+
registry: options.registry,
|
|
15779
|
+
modelId: options.modelId(),
|
|
15780
|
+
name: "report_review",
|
|
15781
|
+
description: "Structured review report for uncommitted changes",
|
|
15782
|
+
schema: ReviewReportSchema
|
|
15783
|
+
});
|
|
15784
|
+
const { value } = await structured.invoke({
|
|
15785
|
+
messages: [{ role: "user", content: prompt }],
|
|
15786
|
+
signal
|
|
15787
|
+
});
|
|
15788
|
+
return value;
|
|
15789
|
+
}
|
|
15790
|
+
function formatReviewReport(report) {
|
|
15791
|
+
const lines = [`Review verdict: ${report.verdict}`, "", report.summary.trim()];
|
|
15792
|
+
if (report.findings.length > 0) {
|
|
15793
|
+
lines.push("", "Findings:");
|
|
15794
|
+
for (const finding of report.findings) {
|
|
15795
|
+
lines.push(`- [${finding.severity}] ${finding.file}${finding.line === "" ? "" : `:${finding.line}`} \u2014 ${finding.issue}`);
|
|
15796
|
+
if (finding.suggestion.trim() !== "") lines.push(` fix: ${finding.suggestion}`);
|
|
15797
|
+
}
|
|
15798
|
+
} else {
|
|
15799
|
+
lines.push("", "No issues found.");
|
|
15800
|
+
}
|
|
15801
|
+
return lines.join("\n");
|
|
15802
|
+
}
|
|
15803
|
+
|
|
15804
|
+
// src/git/tools.ts
|
|
15805
|
+
import { z as z25 } from "zod";
|
|
15806
|
+
var GitHistoryInput = z25.object({
|
|
15807
|
+
path: z25.string().describe("File to inspect; omit for the whole repository history").optional(),
|
|
15808
|
+
limit: z25.number().int().min(1).max(50).describe("Maximum number of commits to return (default 20)").optional()
|
|
15404
15809
|
});
|
|
15405
|
-
|
|
15406
|
-
|
|
15407
|
-
|
|
15408
|
-
|
|
15810
|
+
function createGitHistoryTool(workspace) {
|
|
15811
|
+
return {
|
|
15812
|
+
name: "GitHistory",
|
|
15813
|
+
description: "Show git commit history for the repository or for one file (follows renames). Use to understand when and why code changed.",
|
|
15814
|
+
inputSchema: GitHistoryInput,
|
|
15815
|
+
readOnly: true,
|
|
15816
|
+
paths: (input) => input.path === void 0 ? [] : [input.path],
|
|
15817
|
+
summarize: (input) => input.path ?? "repository",
|
|
15818
|
+
renderForModel: (output) => output,
|
|
15819
|
+
async execute(input, signal) {
|
|
15820
|
+
signal.throwIfAborted();
|
|
15821
|
+
if (!await isGitRepository(workspace)) throw new Error("Not a git repository");
|
|
15822
|
+
const limit = input.limit ?? 20;
|
|
15823
|
+
const entries = await fileHistory(workspace, input.path, limit);
|
|
15824
|
+
if (entries.length === 0) {
|
|
15825
|
+
return input.path === void 0 ? "No commits found." : `No commits found for ${input.path} (the file may be untracked).`;
|
|
15826
|
+
}
|
|
15827
|
+
const scope2 = input.path === void 0 ? "repository" : input.path;
|
|
15828
|
+
return [
|
|
15829
|
+
`git history for ${scope2} (${entries.length} commits):`,
|
|
15830
|
+
...entries.map((entry2) => `${entry2.hash} ${entry2.date} ${entry2.author}: ${entry2.subject}`)
|
|
15831
|
+
].join("\n");
|
|
15832
|
+
}
|
|
15833
|
+
};
|
|
15834
|
+
}
|
|
15835
|
+
|
|
15836
|
+
// src/tools/ask-user-question.ts
|
|
15837
|
+
import { z as z26 } from "zod";
|
|
15838
|
+
var QuestionOptionSchema = z26.object({
|
|
15839
|
+
label: z26.string().trim().min(1),
|
|
15840
|
+
description: z26.string().trim().min(1)
|
|
15841
|
+
});
|
|
15842
|
+
var QuestionSchema = z26.object({
|
|
15843
|
+
question: z26.string().trim().min(1),
|
|
15844
|
+
header: z26.string().trim().min(1),
|
|
15845
|
+
options: z26.array(QuestionOptionSchema).min(1).max(4)
|
|
15409
15846
|
});
|
|
15410
|
-
var AskUserQuestionInput =
|
|
15411
|
-
questions:
|
|
15847
|
+
var AskUserQuestionInput = z26.object({
|
|
15848
|
+
questions: z26.array(QuestionSchema).min(1).max(4)
|
|
15412
15849
|
});
|
|
15413
15850
|
function hookAnswersFromUpdatedInput(updatedInput, questions) {
|
|
15414
15851
|
if (typeof updatedInput !== "object" || updatedInput === null) return void 0;
|
|
@@ -15492,20 +15929,20 @@ function createAskUserQuestionTool(handler) {
|
|
|
15492
15929
|
}
|
|
15493
15930
|
|
|
15494
15931
|
// src/tools/todo-write.ts
|
|
15495
|
-
import { z as
|
|
15496
|
-
var TodoStatusSchema =
|
|
15932
|
+
import { z as z27 } from "zod";
|
|
15933
|
+
var TodoStatusSchema = z27.enum([
|
|
15497
15934
|
"pending",
|
|
15498
15935
|
"in_progress",
|
|
15499
15936
|
"completed",
|
|
15500
15937
|
"cancelled"
|
|
15501
15938
|
]);
|
|
15502
|
-
var TodoItemSchema =
|
|
15503
|
-
content:
|
|
15939
|
+
var TodoItemSchema = z27.object({
|
|
15940
|
+
content: z27.string().trim().min(1),
|
|
15504
15941
|
status: TodoStatusSchema,
|
|
15505
|
-
activeForm:
|
|
15942
|
+
activeForm: z27.string().trim().min(1)
|
|
15506
15943
|
});
|
|
15507
|
-
var TodoWriteInput =
|
|
15508
|
-
todos:
|
|
15944
|
+
var TodoWriteInput = z27.object({
|
|
15945
|
+
todos: z27.array(TodoItemSchema).min(1).max(50)
|
|
15509
15946
|
});
|
|
15510
15947
|
function createTodoWriteTool() {
|
|
15511
15948
|
return {
|
|
@@ -15542,36 +15979,36 @@ function createTodoWriteTool() {
|
|
|
15542
15979
|
import { randomUUID as randomUUID12 } from "crypto";
|
|
15543
15980
|
import { link as link2, lstat as lstat11, mkdir as mkdir17, readFile as readFile17, readdir as readdir9, unlink as unlink4, writeFile as writeFile10 } from "fs/promises";
|
|
15544
15981
|
import { dirname as dirname17, join as join19, resolve as resolve20 } from "path";
|
|
15545
|
-
import { z as
|
|
15982
|
+
import { z as z28 } from "zod";
|
|
15546
15983
|
var MANAGED_TOOL_VERSION = 1;
|
|
15547
15984
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
15548
15985
|
var TOOL_NAME = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
15549
|
-
var JsonSchemaObject =
|
|
15986
|
+
var JsonSchemaObject = z28.record(z28.string(), z28.unknown()).superRefine((schema, context) => {
|
|
15550
15987
|
if (schema.type !== "object") {
|
|
15551
15988
|
context.addIssue({ code: "custom", message: "Tool input JSON Schema must have type object" });
|
|
15552
15989
|
}
|
|
15553
15990
|
});
|
|
15554
|
-
var RegisterManagedToolInputSchema =
|
|
15555
|
-
name:
|
|
15556
|
-
description:
|
|
15991
|
+
var RegisterManagedToolInputSchema = z28.object({
|
|
15992
|
+
name: z28.string().regex(TOOL_NAME).describe("Unique callable tool name using letters, digits, and underscores"),
|
|
15993
|
+
description: z28.string().min(1).max(4096).describe("Purpose and appropriate use of the generated tool"),
|
|
15557
15994
|
inputSchema: JsonSchemaObject.describe("Provider-facing JSON Schema with an object at the root"),
|
|
15558
|
-
implementation:
|
|
15995
|
+
implementation: z28.string().min(1).max(64 * 1024).describe(
|
|
15559
15996
|
"Async JavaScript function body, or complete function/arrow expression, receiving input, signal, and context; return the tool result"
|
|
15560
15997
|
),
|
|
15561
|
-
scope:
|
|
15998
|
+
scope: z28.enum(["project", "global"]).default("project").describe(
|
|
15562
15999
|
"Project persists in the current workspace; global persists for every workspace"
|
|
15563
16000
|
),
|
|
15564
|
-
agents:
|
|
16001
|
+
agents: z28.array(z28.enum(["main", "subagent"])).min(1).max(2).optional().describe(
|
|
15565
16002
|
"Agent roles allowed to call the generated tool; omit to allow both"
|
|
15566
16003
|
)
|
|
15567
16004
|
}).strict();
|
|
15568
|
-
var RemoveManagedToolInputSchema =
|
|
15569
|
-
name:
|
|
15570
|
-
scope:
|
|
16005
|
+
var RemoveManagedToolInputSchema = z28.object({
|
|
16006
|
+
name: z28.string().regex(TOOL_NAME).describe("Managed tool name to remove"),
|
|
16007
|
+
scope: z28.enum(["project", "global"]).optional().describe("Required only when the same name exists in both scopes")
|
|
15571
16008
|
}).strict();
|
|
15572
16009
|
var ManagedToolRecordSchema = RegisterManagedToolInputSchema.extend({
|
|
15573
|
-
version:
|
|
15574
|
-
createdAt:
|
|
16010
|
+
version: z28.literal(MANAGED_TOOL_VERSION),
|
|
16011
|
+
createdAt: z28.string().datetime()
|
|
15575
16012
|
}).strict();
|
|
15576
16013
|
var AsyncFunction = Object.getPrototypeOf(async function() {
|
|
15577
16014
|
}).constructor;
|
|
@@ -15758,7 +16195,7 @@ function createManagedToolManagementTools(options) {
|
|
|
15758
16195
|
const listTools = {
|
|
15759
16196
|
name: "ListRegisteredTools",
|
|
15760
16197
|
description: "List durable tools created through RegisterTool, including scope, path, and active precedence",
|
|
15761
|
-
inputSchema:
|
|
16198
|
+
inputSchema: z28.object({}).strict(),
|
|
15762
16199
|
agents: mainOnly,
|
|
15763
16200
|
paths: () => [],
|
|
15764
16201
|
execute: async (_input, signal) => {
|
|
@@ -15771,7 +16208,7 @@ function createManagedToolManagementTools(options) {
|
|
|
15771
16208
|
function compileDefinition(record, scope2, workspace) {
|
|
15772
16209
|
let inputSchema;
|
|
15773
16210
|
try {
|
|
15774
|
-
inputSchema =
|
|
16211
|
+
inputSchema = z28.fromJSONSchema(record.inputSchema);
|
|
15775
16212
|
} catch (error) {
|
|
15776
16213
|
throw new Error(`Invalid input schema for managed tool "${record.name}": ${message(error)}`);
|
|
15777
16214
|
}
|
|
@@ -15871,7 +16308,7 @@ function isMissing4(error) {
|
|
|
15871
16308
|
}
|
|
15872
16309
|
|
|
15873
16310
|
// src/hallucination/confidence.ts
|
|
15874
|
-
import { z as
|
|
16311
|
+
import { z as z29 } from "zod";
|
|
15875
16312
|
|
|
15876
16313
|
// src/hallucination/types.ts
|
|
15877
16314
|
var DEFAULT_SLIDING_WINDOW_SIZE = 20;
|
|
@@ -15884,12 +16321,12 @@ var DEFAULT_EVALUATION_TIMEOUT_MS = 2e3;
|
|
|
15884
16321
|
var MAX_QUERY_CHARS = 5e3;
|
|
15885
16322
|
var MAX_OUTPUT_CHARS = 1e4;
|
|
15886
16323
|
var MAX_EVIDENCE_CHARS = 6e3;
|
|
15887
|
-
var ConfidenceSchema =
|
|
15888
|
-
taskAlignment:
|
|
15889
|
-
evidenceGrounding:
|
|
15890
|
-
processReliability:
|
|
15891
|
-
reason:
|
|
15892
|
-
unsupportedClaims:
|
|
16324
|
+
var ConfidenceSchema = z29.object({
|
|
16325
|
+
taskAlignment: z29.number(),
|
|
16326
|
+
evidenceGrounding: z29.number(),
|
|
16327
|
+
processReliability: z29.number(),
|
|
16328
|
+
reason: z29.string(),
|
|
16329
|
+
unsupportedClaims: z29.array(z29.string()).max(3)
|
|
15893
16330
|
});
|
|
15894
16331
|
var PROMPT = `You are a fast, evidence-aware task-completion verifier.
|
|
15895
16332
|
|
|
@@ -17888,15 +18325,15 @@ function workspacePath(root, input) {
|
|
|
17888
18325
|
}
|
|
17889
18326
|
|
|
17890
18327
|
// src/tools/terminal.ts
|
|
17891
|
-
import { z as
|
|
18328
|
+
import { z as z30 } from "zod";
|
|
17892
18329
|
import { resolve as resolve24 } from "path";
|
|
17893
|
-
var Id =
|
|
17894
|
-
var OpenInput =
|
|
17895
|
-
var OptionalBoolean =
|
|
17896
|
-
var WriteInput2 =
|
|
17897
|
-
var ReadInput3 =
|
|
17898
|
-
var ResizeInput =
|
|
17899
|
-
var CloseInput =
|
|
18330
|
+
var Id = z30.string().regex(/^term-[a-f0-9-]+$/);
|
|
18331
|
+
var OpenInput = z30.object({ cwd: z30.string().optional(), shell: z30.string().optional(), args: z30.array(z30.string()).optional(), columns: z30.coerce.number().int().min(10).max(500).optional(), rows: z30.coerce.number().int().min(2).max(300).optional() });
|
|
18332
|
+
var OptionalBoolean = z30.union([z30.boolean(), z30.string().refine((value) => value === "true" || value === "false")]);
|
|
18333
|
+
var WriteInput2 = z30.object({ id: Id, data: z30.string().max(1e5), enter: OptionalBoolean.optional() });
|
|
18334
|
+
var ReadInput3 = z30.object({ id: Id, cursor: z30.coerce.number().int().min(0).optional() });
|
|
18335
|
+
var ResizeInput = z30.object({ id: Id, columns: z30.coerce.number().int().min(10).max(500), rows: z30.coerce.number().int().min(2).max(300) });
|
|
18336
|
+
var CloseInput = z30.object({ id: Id });
|
|
17900
18337
|
function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
17901
18338
|
const tools = [
|
|
17902
18339
|
{
|
|
@@ -17972,7 +18409,7 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
|
17972
18409
|
{
|
|
17973
18410
|
name: "TerminalList",
|
|
17974
18411
|
description: "List persistent terminals owned by this agent",
|
|
17975
|
-
inputSchema:
|
|
18412
|
+
inputSchema: z30.object({}),
|
|
17976
18413
|
paths: () => [],
|
|
17977
18414
|
execute: async (_input, _signal, context) => terminals.list(owner(context))
|
|
17978
18415
|
}
|
|
@@ -18351,7 +18788,7 @@ var PalClient = class {
|
|
|
18351
18788
|
};
|
|
18352
18789
|
|
|
18353
18790
|
// src/pals/tools.ts
|
|
18354
|
-
import { z as
|
|
18791
|
+
import { z as z31 } from "zod";
|
|
18355
18792
|
var DEFAULT_SHARE_BUDGET_BYTES = 128 * 1024;
|
|
18356
18793
|
var CollaborationShareGuard = class {
|
|
18357
18794
|
redact;
|
|
@@ -18372,25 +18809,25 @@ var CollaborationShareGuard = class {
|
|
|
18372
18809
|
return redacted;
|
|
18373
18810
|
}
|
|
18374
18811
|
};
|
|
18375
|
-
var Uuid =
|
|
18376
|
-
var Hash =
|
|
18812
|
+
var Uuid = z31.uuid();
|
|
18813
|
+
var Hash = z31.string().regex(/^[a-f0-9]{64}$/);
|
|
18377
18814
|
var Target = PalTargetSchema;
|
|
18378
|
-
var BoundedText =
|
|
18815
|
+
var BoundedText = z31.string().trim().min(1).refine(
|
|
18379
18816
|
(value) => Buffer.byteLength(value, "utf8") <= MAX_MESSAGE_BYTES,
|
|
18380
18817
|
`Text must not exceed ${MAX_MESSAGE_BYTES} UTF-8 bytes`
|
|
18381
18818
|
);
|
|
18382
|
-
var CompletionEvidence =
|
|
18819
|
+
var CompletionEvidence = z31.string().trim().min(1).refine(
|
|
18383
18820
|
(value) => Buffer.byteLength(value, "utf8") <= MAX_COMPLETION_DETAIL_BYTES,
|
|
18384
18821
|
`Completion evidence must not exceed ${MAX_COMPLETION_DETAIL_BYTES} UTF-8 bytes`
|
|
18385
18822
|
);
|
|
18386
|
-
var EmptyInput =
|
|
18387
|
-
var StateInput =
|
|
18388
|
-
var TokenInput =
|
|
18389
|
-
var SendInput =
|
|
18390
|
-
var PlanInput =
|
|
18391
|
-
var ProgressInput =
|
|
18392
|
-
var CompleteInput = TokenInput.extend({ passed:
|
|
18393
|
-
var IntegrateInput = TokenInput.extend({ passed:
|
|
18823
|
+
var EmptyInput = z31.object({}).strict();
|
|
18824
|
+
var StateInput = z31.object({ coWorkId: Uuid }).strict();
|
|
18825
|
+
var TokenInput = z31.object({ coWorkId: Uuid, epoch: z31.number().int().positive(), planHash: Hash }).strict();
|
|
18826
|
+
var SendInput = z31.object({ target: Target, message: BoundedText, coWorkId: Uuid.optional() }).strict();
|
|
18827
|
+
var PlanInput = z31.object({ plan: CoWorkPlanSchema }).strict();
|
|
18828
|
+
var ProgressInput = z31.object({ coWorkId: Uuid, target: Target, detail: BoundedText }).strict();
|
|
18829
|
+
var CompleteInput = TokenInput.extend({ passed: z31.boolean(), detail: CompletionEvidence }).strict();
|
|
18830
|
+
var IntegrateInput = TokenInput.extend({ passed: z31.boolean(), evidence: CompletionEvidence }).strict();
|
|
18394
18831
|
var sharedBytes = (...values) => values.reduce((total, value) => total + Buffer.byteLength(value, "utf8"), 0);
|
|
18395
18832
|
function resolvePresence(presences, target) {
|
|
18396
18833
|
const normalized = normalizePalIdentity(target);
|
|
@@ -18743,6 +19180,7 @@ async function createProductionRuntime(options) {
|
|
|
18743
19180
|
createApplyPatchTool(workspace, fileMutationOptions),
|
|
18744
19181
|
createGlobTool(workspace),
|
|
18745
19182
|
createGrepTool(workspace),
|
|
19183
|
+
createGitHistoryTool(workspace),
|
|
18746
19184
|
createShellTool(workspace, {
|
|
18747
19185
|
jobs,
|
|
18748
19186
|
...executionEnvironment === void 0 ? {} : { executionEnvironment }
|
|
@@ -19059,7 +19497,10 @@ async function createProductionRuntime(options) {
|
|
|
19059
19497
|
states: { ...taskStates },
|
|
19060
19498
|
results: { ...taskResults }
|
|
19061
19499
|
},
|
|
19062
|
-
|
|
19500
|
+
// Persist the resolved decision, not the harness snapshot: after logout the
|
|
19501
|
+
// harness may still hold the old login model, and resuming from that stale
|
|
19502
|
+
// value is what made the welcome card show a previous service.
|
|
19503
|
+
models: { main: mainModel, subagent: childModel },
|
|
19063
19504
|
permissionMode: harness.permissionMode,
|
|
19064
19505
|
memory: memoryLifecycle,
|
|
19065
19506
|
timeline: { version: 1, state: timelineState }
|
|
@@ -19119,7 +19560,7 @@ async function createProductionRuntime(options) {
|
|
|
19119
19560
|
})) tools.push(tool);
|
|
19120
19561
|
const taskTool = {
|
|
19121
19562
|
name: "Task",
|
|
19122
|
-
description: "Validate a task graph and execute its nodes with isolated child agents",
|
|
19563
|
+
description: "Validate a task graph and execute its nodes with isolated child agents. Declare each node's `files` (the workspace files it may create or modify) whenever a task writes files; nodes with overlapping files are serialized automatically to prevent concurrent write conflicts.",
|
|
19123
19564
|
inputSchema: TaskGraphSchema,
|
|
19124
19565
|
paths: () => [],
|
|
19125
19566
|
execute: async (input, signal) => {
|
|
@@ -19700,8 +20141,8 @@ async function createProductionRuntime(options) {
|
|
|
19700
20141
|
const services = {
|
|
19701
20142
|
hooks,
|
|
19702
20143
|
workspace,
|
|
19703
|
-
mainModel: () =>
|
|
19704
|
-
subagentModel: () =>
|
|
20144
|
+
mainModel: () => mainModel,
|
|
20145
|
+
subagentModel: () => childModel,
|
|
19705
20146
|
llmServiceName: () => effectiveLlm?.serviceName,
|
|
19706
20147
|
permissionMode: () => harness.permissionMode,
|
|
19707
20148
|
...palServices === void 0 ? {} : { pals: palServices },
|
|
@@ -19775,6 +20216,8 @@ async function createProductionRuntime(options) {
|
|
|
19775
20216
|
}
|
|
19776
20217
|
}
|
|
19777
20218
|
harness.setModel(role, id);
|
|
20219
|
+
if (role === "main") mainModel = id;
|
|
20220
|
+
else childModel = id;
|
|
19778
20221
|
await persist();
|
|
19779
20222
|
},
|
|
19780
20223
|
setPermissionMode: async (mode) => {
|
|
@@ -19807,6 +20250,19 @@ async function createProductionRuntime(options) {
|
|
|
19807
20250
|
hooksStatus: () => HOOK_EVENT_NAMES.map((name) => ({ name, pluginHandlers: pluginHooks.filter((item) => item === name).length })),
|
|
19808
20251
|
tasks: () => ({ plan: taskPlan, graph: taskGraph, states: taskStates, results: taskResults }),
|
|
19809
20252
|
evolve: (args) => evolveService.handleCommand(args),
|
|
20253
|
+
gitCommit: (hint, signal) => runGitCommit({
|
|
20254
|
+
workspace,
|
|
20255
|
+
registry,
|
|
20256
|
+
questions,
|
|
20257
|
+
modelId: () => harness.subagentModelId,
|
|
20258
|
+
notify: (message2) => emitOutput({ type: "notice", message: message2 })
|
|
20259
|
+
}, hint, signal),
|
|
20260
|
+
gitReview: (focus, signal) => runGitReview({
|
|
20261
|
+
workspace,
|
|
20262
|
+
registry,
|
|
20263
|
+
modelId: () => harness.subagentModelId,
|
|
20264
|
+
notify: (message2) => emitOutput({ type: "notice", message: message2 })
|
|
20265
|
+
}, focus, signal),
|
|
19810
20266
|
audit: async (toolFilter) => {
|
|
19811
20267
|
try {
|
|
19812
20268
|
const raw = await readFile20(auditLogger.path, "utf8");
|
|
@@ -19894,7 +20350,11 @@ ${summary2}`;
|
|
|
19894
20350
|
},
|
|
19895
20351
|
ide: () => ide.status(),
|
|
19896
20352
|
ideContext: () => ide.editorContext(),
|
|
19897
|
-
checkpoint: (label) =>
|
|
20353
|
+
checkpoint: async (label) => {
|
|
20354
|
+
const marker = await gitMarker(workspace);
|
|
20355
|
+
const tagged = marker === void 0 ? label : label === void 0 ? `git: ${marker}` : `${label} (git: ${marker})`;
|
|
20356
|
+
return sessionHistory.checkpoint(tagged, harness.main.context.snapshot());
|
|
20357
|
+
},
|
|
19898
20358
|
tree: () => sessionHistory.tree(),
|
|
19899
20359
|
rewind: async (nodeId) => {
|
|
19900
20360
|
await sessionHistory.rewind(nodeId, harness.main.context.snapshot());
|
|
@@ -20004,12 +20464,48 @@ ${renderMemoryDocument(entries)}`;
|
|
|
20004
20464
|
goalOrchestrator.setModels(mainModel, mainModel);
|
|
20005
20465
|
delete selectedModels.mainError;
|
|
20006
20466
|
delete selectedModels.childError;
|
|
20467
|
+
await retainOnlyCredentials(tokenStore, credentialId);
|
|
20007
20468
|
if (!secrets.includes(next.accessToken)) secrets.push(next.accessToken);
|
|
20008
20469
|
await persist();
|
|
20009
20470
|
return `Authenticated to "${next.serviceName}". Main model ${mainModel}; subagent ${childModel}; gateway ${next.baseURL}. Configuration is active now.`;
|
|
20010
20471
|
} catch (error) {
|
|
20011
20472
|
return `Login failed: ${message(error)}`;
|
|
20012
20473
|
}
|
|
20474
|
+
},
|
|
20475
|
+
async logout() {
|
|
20476
|
+
const tokenStore = createFileTokenStore(join22(home, ".flavor-code", "auth.json"));
|
|
20477
|
+
const tokens = await tokenStore.load();
|
|
20478
|
+
const hadCredentials = Object.keys(tokens).length > 0 || effectiveLlm !== void 0;
|
|
20479
|
+
if (!hadCredentials) {
|
|
20480
|
+
return "Not authenticated \u2014 no stored OAuth credentials to clear.";
|
|
20481
|
+
}
|
|
20482
|
+
for (let i = registeredProviders.length - 1; i >= 0; i -= 1) {
|
|
20483
|
+
const provider = registeredProviders[i];
|
|
20484
|
+
if (provider !== void 0 && provider.pkceManaged === true) {
|
|
20485
|
+
registry.unregister(provider.name);
|
|
20486
|
+
registeredProviders.splice(i, 1);
|
|
20487
|
+
}
|
|
20488
|
+
}
|
|
20489
|
+
if (effectiveLlm !== void 0) {
|
|
20490
|
+
registry.unregister(effectiveLlm.providerId);
|
|
20491
|
+
effectiveLlm = void 0;
|
|
20492
|
+
}
|
|
20493
|
+
await tokenStore.save({});
|
|
20494
|
+
const fallback = selectModels(config, registeredProviders, diagnostics);
|
|
20495
|
+
mainModel = fallback.main;
|
|
20496
|
+
childModel = fallback.child;
|
|
20497
|
+
if (fallback.mainError === void 0) delete selectedModels.mainError;
|
|
20498
|
+
else selectedModels.mainError = fallback.mainError;
|
|
20499
|
+
if (fallback.childError === void 0) delete selectedModels.childError;
|
|
20500
|
+
else selectedModels.childError = fallback.childError;
|
|
20501
|
+
if (fallback.mainError === void 0 && registry.has(safeProvider(mainModel)) && registry.has(safeProvider(childModel))) {
|
|
20502
|
+
harness.setModel("main", mainModel);
|
|
20503
|
+
harness.setModel("subagent", childModel);
|
|
20504
|
+
hallucinationGuard.setModel(childModel);
|
|
20505
|
+
goalOrchestrator.setModels(mainModel, mainModel);
|
|
20506
|
+
}
|
|
20507
|
+
await persist();
|
|
20508
|
+
return fallback.mainError === void 0 ? `Logged out. Cleared OAuth credentials. Main model ${mainModel}; subagent ${childModel}.` : `Logged out. Cleared OAuth credentials. ${fallback.mainError}`;
|
|
20013
20509
|
}
|
|
20014
20510
|
};
|
|
20015
20511
|
if (config.sleep) {
|
|
@@ -20326,6 +20822,70 @@ async function* persistEach(source, persist) {
|
|
|
20326
20822
|
await persist();
|
|
20327
20823
|
}
|
|
20328
20824
|
}
|
|
20825
|
+
async function runGitCommit(deps, hint, signal) {
|
|
20826
|
+
if (!await isGitRepository(deps.workspace)) return "Not a git repository \u2014 /commit needs git.";
|
|
20827
|
+
let summary2 = await changeSummary(deps.workspace);
|
|
20828
|
+
if (summary2.statusLines.length === 0) return "Working tree clean \u2014 nothing to commit.";
|
|
20829
|
+
if (summary2.stagedFiles.length === 0) {
|
|
20830
|
+
if (deps.questions === void 0) throw new Error("Nothing is staged. Run `git add` on the files you want to commit first.");
|
|
20831
|
+
const stageAnswers = await deps.questions.ask([{
|
|
20832
|
+
header: "Staging",
|
|
20833
|
+
question: `Nothing is staged, but ${summary2.statusLines.length} change(s) exist. Stage everything before committing?`,
|
|
20834
|
+
options: [
|
|
20835
|
+
{ label: "Stage all", description: "Run git add -A, then generate the commit message." },
|
|
20836
|
+
{ label: "Cancel", description: "Abort /commit and leave the working tree as-is." }
|
|
20837
|
+
]
|
|
20838
|
+
}], signal);
|
|
20839
|
+
if (stageAnswers[0] !== "Stage all") return "/commit cancelled \u2014 nothing was staged.";
|
|
20840
|
+
await stageAll(deps.workspace);
|
|
20841
|
+
summary2 = await changeSummary(deps.workspace);
|
|
20842
|
+
if (summary2.stagedFiles.length === 0) return "Nothing ended up staged \u2014 /commit aborted.";
|
|
20843
|
+
}
|
|
20844
|
+
const staged = await stagedDiff(deps.workspace);
|
|
20845
|
+
signal.throwIfAborted();
|
|
20846
|
+
deps.notify?.("Generating commit message\u2026");
|
|
20847
|
+
let commitMessage;
|
|
20848
|
+
try {
|
|
20849
|
+
commitMessage = await suggestCommitMessage(
|
|
20850
|
+
{ registry: deps.registry, modelId: deps.modelId },
|
|
20851
|
+
{ stat: staged.stat, diff: staged.diff, ...hint === void 0 ? {} : { hint } },
|
|
20852
|
+
signal
|
|
20853
|
+
);
|
|
20854
|
+
} catch (error) {
|
|
20855
|
+
signal.throwIfAborted();
|
|
20856
|
+
const scope2 = summary2.stagedFiles.length === 1 ? summary2.stagedFiles[0] ?? "1 file" : `${summary2.stagedFiles.length} files`;
|
|
20857
|
+
commitMessage = `chore: update ${scope2}`;
|
|
20858
|
+
deps.notify?.(`Commit message generation failed (${message(error)}); using a fallback message.`);
|
|
20859
|
+
}
|
|
20860
|
+
deps.notify?.(`Proposed commit message:
|
|
20861
|
+
${commitMessage}`);
|
|
20862
|
+
if (deps.questions !== void 0) {
|
|
20863
|
+
const confirmAnswers = await deps.questions.ask([{
|
|
20864
|
+
header: "Commit",
|
|
20865
|
+
question: `Commit ${summary2.stagedFiles.length} staged file(s) on ${summary2.branch} with the message above?`,
|
|
20866
|
+
options: [
|
|
20867
|
+
{ label: "Commit", description: "Run git commit with the proposed message." },
|
|
20868
|
+
{ label: "Cancel", description: "Abort; the staged changes stay as-is." }
|
|
20869
|
+
]
|
|
20870
|
+
}], signal);
|
|
20871
|
+
if (confirmAnswers[0] !== "Commit") return "/commit cancelled \u2014 staged changes left untouched.";
|
|
20872
|
+
}
|
|
20873
|
+
const result = await commit(deps.workspace, commitMessage);
|
|
20874
|
+
return `Committed: ${result}`;
|
|
20875
|
+
}
|
|
20876
|
+
async function runGitReview(deps, focus, signal) {
|
|
20877
|
+
if (!await isGitRepository(deps.workspace)) return "Not a git repository \u2014 /review needs git.";
|
|
20878
|
+
const changes = await uncommittedDiff(deps.workspace);
|
|
20879
|
+
if (changes.diff.trim() === "" && changes.untracked.length === 0) return "No uncommitted changes to review.";
|
|
20880
|
+
signal.throwIfAborted();
|
|
20881
|
+
deps.notify?.("Reviewing uncommitted changes\u2026");
|
|
20882
|
+
const report = await reviewDiff(
|
|
20883
|
+
{ registry: deps.registry, modelId: deps.modelId },
|
|
20884
|
+
{ stat: changes.stat, diff: changes.diff, untracked: changes.untracked, ...focus === void 0 ? {} : { focus } },
|
|
20885
|
+
signal
|
|
20886
|
+
);
|
|
20887
|
+
return formatReviewReport(report);
|
|
20888
|
+
}
|
|
20329
20889
|
async function runChild(harness, skills, task, attempt, signal, parentContext) {
|
|
20330
20890
|
return harness.runSubagent(task, async (child, childSignal) => {
|
|
20331
20891
|
const skill = await skills.match(task.description);
|
|
@@ -20338,6 +20898,7 @@ ${await skills.loadBody(skill)}`;
|
|
|
20338
20898
|
`Complete task ${task.id}: ${task.description}`,
|
|
20339
20899
|
`Expected outputs: ${task.expectedOutputs.join("; ")}`,
|
|
20340
20900
|
`Verification: ${task.verification.join("; ")}`,
|
|
20901
|
+
...task.files === void 0 || task.files.length === 0 ? [] : [`Owned files: ${task.files.join(", ")}. Restrict your file writes to these paths; concurrent tasks own everything else.`],
|
|
20341
20902
|
`For completed work, finish by calling TaskOutput. Otherwise return only JSON matching these fields: ${Object.keys(SubagentResultSchema.shape).join(", ")}.${repair}`
|
|
20342
20903
|
].join("\n");
|
|
20343
20904
|
for await (const event of child.loop.run({ prompt, signal: childSignal })) {
|