@ordewell/core 0.4.10 → 0.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ITerminalRunner-CbE6gKLI.d.ts → ITerminalRunner-BTaKZ659.d.mts} +2 -2
- package/dist/{ITerminalRunner-1NAv8wCC.d.mts → ITerminalRunner-DVwORcV9.d.ts} +2 -2
- package/dist/{ModeResolver-F5wnlSz1.d.mts → ModeResolver-CCKYqdZC.d.mts} +1 -1
- package/dist/{ModeResolver-Dx5gzNPC.d.ts → ModeResolver-CzZd-i6T.d.ts} +1 -1
- package/dist/{Task-1NwmUImI.d.mts → Task-cGrfYRXK.d.mts} +8 -1
- package/dist/{Task-1NwmUImI.d.ts → Task-cGrfYRXK.d.ts} +8 -1
- package/dist/{chunk-YI4ZDZEE.mjs → chunk-DNNZGH3I.mjs} +2 -2
- package/dist/chunk-HPNUFBIT.mjs +19 -0
- package/dist/chunk-HPNUFBIT.mjs.map +1 -0
- package/dist/{chunk-IO6PEEN6.mjs → chunk-N6HTBUKG.mjs} +11 -1
- package/dist/chunk-N6HTBUKG.mjs.map +1 -0
- package/dist/{chunk-TUGJXD4G.mjs → chunk-UEJ3SZQ7.mjs} +2 -2
- package/dist/index.d.mts +124 -73
- package/dist/index.d.ts +124 -73
- package/dist/index.js +611 -433
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +590 -442
- package/dist/index.mjs.map +1 -1
- package/dist/order-labels.d.mts +17 -0
- package/dist/order-labels.d.ts +17 -0
- package/dist/order-labels.js +44 -0
- package/dist/order-labels.js.map +1 -0
- package/dist/order-labels.mjs +9 -0
- package/dist/order-labels.mjs.map +1 -0
- package/dist/{parsing-BqbJySeo.d.mts → parsing-BT-dvFcu.d.mts} +2 -2
- package/dist/{parsing-X0Gvuz-q.d.ts → parsing-D87zKtTe.d.ts} +2 -2
- package/dist/parsing.d.mts +3 -3
- package/dist/parsing.d.ts +3 -3
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -2
- package/dist/{plan-utils-B5rVXNVI.d.mts → plan-utils-BHLbBym3.d.mts} +2 -2
- package/dist/{plan-utils-DtsfHnn_.d.ts → plan-utils-akoB6YCL.d.ts} +2 -2
- package/dist/plan-utils.d.mts +3 -3
- package/dist/plan-utils.d.ts +3 -3
- package/dist/plan-utils.mjs +3 -2
- package/dist/testing.d.mts +2 -2
- package/dist/testing.d.ts +2 -2
- package/package.json +12 -2
- package/skills/grilling/SKILL.md +31 -0
- package/skills/to-spec/SKILL.md +64 -0
- package/dist/chunk-IO6PEEN6.mjs.map +0 -1
- /package/dist/{chunk-YI4ZDZEE.mjs.map → chunk-DNNZGH3I.mjs.map} +0 -0
- /package/dist/{chunk-TUGJXD4G.mjs.map → chunk-UEJ3SZQ7.mjs.map} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
resolveTaskMode,
|
|
14
14
|
runnerModesFrom,
|
|
15
15
|
validatePlanModification
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-UEJ3SZQ7.mjs";
|
|
17
17
|
import {
|
|
18
18
|
UPDATABLE_FIELDS,
|
|
19
19
|
applyTaskOps,
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
summarizeToolCall,
|
|
32
32
|
textHasTaskOps,
|
|
33
33
|
validateTaskEdit
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-DNNZGH3I.mjs";
|
|
35
35
|
import {
|
|
36
36
|
PLAN_ENVELOPE_KEY,
|
|
37
37
|
PlanParseError,
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
extractObjectWithBalance,
|
|
47
47
|
extractObjectsWithKey,
|
|
48
48
|
flattenTasks,
|
|
49
|
+
flattenTasksWithParents,
|
|
49
50
|
migrateLegacyPlan,
|
|
50
51
|
migratePlanState,
|
|
51
52
|
migrateTask,
|
|
@@ -56,7 +57,11 @@ import {
|
|
|
56
57
|
updateTaskInPlan,
|
|
57
58
|
validateModifiedPlan,
|
|
58
59
|
warningsText
|
|
59
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-N6HTBUKG.mjs";
|
|
61
|
+
import {
|
|
62
|
+
resolveOrderLabel,
|
|
63
|
+
taskOrderLabel
|
|
64
|
+
} from "./chunk-HPNUFBIT.mjs";
|
|
60
65
|
|
|
61
66
|
// src/interfaces/IFileSystem.ts
|
|
62
67
|
var GREP_DEFAULT_HEAD_LIMIT = 100;
|
|
@@ -2575,7 +2580,7 @@ function redactSecrets(text) {
|
|
|
2575
2580
|
const { value, tail } = trimTrailingPunctuation(raw);
|
|
2576
2581
|
if (value.length < 8) return full;
|
|
2577
2582
|
if (looksLikeCode(value) || value.startsWith("$")) return full;
|
|
2578
|
-
if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && /\d/.test(value)) {
|
|
2583
|
+
if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && (/\d/.test(value) || value.length >= 12)) {
|
|
2579
2584
|
return `${head}${quote}${marker("credential", value.length)}${quote}${tail}`;
|
|
2580
2585
|
}
|
|
2581
2586
|
if (IDENTIFIER.test(value) || SLUG.test(value)) return full;
|
|
@@ -2614,33 +2619,33 @@ function grepOptions(args) {
|
|
|
2614
2619
|
};
|
|
2615
2620
|
}
|
|
2616
2621
|
var STOPPED_TOOL_RESULT = "Stopped by the user \u2014 this call was not executed, and no further tool calls will run this turn.";
|
|
2617
|
-
async function executeTool(name, args,
|
|
2618
|
-
const outcome = await runTool(name, args,
|
|
2622
|
+
async function executeTool(name, args, fs12, fetcher, signal) {
|
|
2623
|
+
const outcome = await runTool(name, args, fs12, fetcher, signal);
|
|
2619
2624
|
return { ...outcome, output: redactSecrets(outcome.output) };
|
|
2620
2625
|
}
|
|
2621
|
-
async function runTool(name, args,
|
|
2626
|
+
async function runTool(name, args, fs12, fetcher, signal) {
|
|
2622
2627
|
if (signal?.aborted) return { success: false, output: STOPPED_TOOL_RESULT, truncated: false };
|
|
2623
2628
|
switch (name) {
|
|
2624
2629
|
case "read_file": {
|
|
2625
2630
|
const offset = num(args.offset);
|
|
2626
2631
|
const limit = num(args.limit);
|
|
2627
2632
|
const maxBytes = num(args.maxBytes);
|
|
2628
|
-
return
|
|
2633
|
+
return fs12.readFile(String(args.path ?? ""), { offset, limit, maxBytes });
|
|
2629
2634
|
}
|
|
2630
2635
|
case "read_files": {
|
|
2631
2636
|
const paths = Array.isArray(args.paths) ? args.paths.map(String) : [];
|
|
2632
|
-
return
|
|
2637
|
+
return fs12.readFiles(paths);
|
|
2633
2638
|
}
|
|
2634
2639
|
case "glob":
|
|
2635
|
-
return
|
|
2640
|
+
return fs12.glob(String(args.pattern ?? ""), { path: str(args.path), headLimit: num(args.head_limit ?? args.headLimit) });
|
|
2636
2641
|
case "grep":
|
|
2637
|
-
return
|
|
2642
|
+
return fs12.grep(String(args.pattern ?? ""), grepOptions(args));
|
|
2638
2643
|
case "find_symbol":
|
|
2639
|
-
return
|
|
2644
|
+
return fs12.findSymbol(String(args.symbol ?? ""), { language: str(args.language), path: str(args.path) });
|
|
2640
2645
|
case "list_dir":
|
|
2641
|
-
return
|
|
2646
|
+
return fs12.listDir(String(args.path ?? ""), num(args.depth));
|
|
2642
2647
|
case "bash":
|
|
2643
|
-
return
|
|
2648
|
+
return fs12.bash(String(args.command ?? ""), signal);
|
|
2644
2649
|
case "fetch": {
|
|
2645
2650
|
if (!fetcher) return { success: false, output: "Web fetching is not available in this environment.", truncated: false };
|
|
2646
2651
|
const url = String(args.url ?? "");
|
|
@@ -3723,14 +3728,7 @@ import { v4 as uuidv4 } from "uuid";
|
|
|
3723
3728
|
|
|
3724
3729
|
// src/services/plannerModes.ts
|
|
3725
3730
|
var MODE_TOGGLES = [
|
|
3726
|
-
// Both of these interview the user, and a one-shot run has nobody to ask —
|
|
3727
|
-
// its prompt says so outright. Their absence there is a decision, not a gap.
|
|
3728
|
-
{ id: "grill-me", settingsKey: "grillMe", runtimeKey: "grillMeEnabled", scopes: ["chat"] },
|
|
3729
|
-
{ id: "prd", settingsKey: "prd", runtimeKey: "prdEnabled", scopes: ["chat"] },
|
|
3730
|
-
// Structural: append a final review task. Nothing about it needs a dialogue.
|
|
3731
|
-
{ id: "review", settingsKey: "review", runtimeKey: "reviewEnabled", scopes: ["chat", "one-shot"] },
|
|
3732
3731
|
{ id: "verify", settingsKey: "verification", runtimeKey: "verificationEnabled", scopes: ["chat", "one-shot"] },
|
|
3733
|
-
{ id: "research-subagents", settingsKey: "researchSubagents", runtimeKey: "researchSubagentsEnabled", scopes: ["chat", "one-shot"] },
|
|
3734
3732
|
{ id: "tdd", settingsKey: "tdd", runtimeKey: "tddEnabled", scopes: ["task"] }
|
|
3735
3733
|
];
|
|
3736
3734
|
function plannerRuntimeToggles(settings) {
|
|
@@ -3740,28 +3738,16 @@ function plannerRuntimeToggles(settings) {
|
|
|
3740
3738
|
}
|
|
3741
3739
|
var DEFAULT_PLANNER_MODES = {
|
|
3742
3740
|
autonomousDefault: true,
|
|
3743
|
-
|
|
3744
|
-
prd: false,
|
|
3745
|
-
review: false,
|
|
3746
|
-
verification: false,
|
|
3747
|
-
researchSubagents: false
|
|
3741
|
+
verification: false
|
|
3748
3742
|
};
|
|
3749
3743
|
function plannerModesFrom(settings, autonomousDefault) {
|
|
3750
3744
|
return {
|
|
3751
3745
|
autonomousDefault,
|
|
3752
|
-
|
|
3753
|
-
prd: settings.prdEnabled ?? false,
|
|
3754
|
-
review: settings.reviewEnabled ?? false,
|
|
3755
|
-
verification: settings.verificationEnabled ?? false,
|
|
3756
|
-
researchSubagents: settings.researchSubagentsEnabled ?? false
|
|
3746
|
+
verification: settings.verificationEnabled ?? false
|
|
3757
3747
|
};
|
|
3758
3748
|
}
|
|
3759
3749
|
var MODE_FIELD = {
|
|
3760
|
-
"grill-me": "grillMe",
|
|
3761
|
-
prd: "prd",
|
|
3762
|
-
review: "review",
|
|
3763
3750
|
verify: "verification",
|
|
3764
|
-
"research-subagents": "researchSubagents",
|
|
3765
3751
|
tdd: null
|
|
3766
3752
|
};
|
|
3767
3753
|
function modesFor(scope, modes) {
|
|
@@ -3945,7 +3931,7 @@ function renderTaskQueryAnswer(query, tasks, catalog) {
|
|
|
3945
3931
|
}
|
|
3946
3932
|
|
|
3947
3933
|
// src/services/PlanPrompts.ts
|
|
3948
|
-
function buildResearchToolsPrompt(
|
|
3934
|
+
function buildResearchToolsPrompt() {
|
|
3949
3935
|
const lines = [
|
|
3950
3936
|
"You have access to the following tools to explore the workspace:",
|
|
3951
3937
|
"",
|
|
@@ -3961,15 +3947,11 @@ function buildResearchToolsPrompt(subagentsEnabled = false) {
|
|
|
3961
3947
|
"- glob: for finding files by name/path pattern",
|
|
3962
3948
|
"- grep: for searching inside file contents",
|
|
3963
3949
|
"",
|
|
3964
|
-
"When multiple independent tool calls are needed, batch them in a single response to save round-trips. For example, if you need to read README.md and AGENTS.md, call read_files with both paths at once."
|
|
3950
|
+
"When multiple independent tool calls are needed, batch them in a single response to save round-trips. For example, if you need to read README.md and AGENTS.md, call read_files with both paths at once.",
|
|
3951
|
+
"",
|
|
3952
|
+
'spawn_research_agent(prompt: string) - Launch a stateless read-only research agent that explores the workspace with its own tools and returns a digest. Use it to delegate an open-ended exploration thread \u2014 a subsystem to map, or a "how does X work here" question \u2014 whenever one emerges during research. To explore several independent areas, launch the agents CONCURRENTLY: put multiple spawn_research_agent calls in one reply and they run in parallel.',
|
|
3953
|
+
"The agent sees nothing of this conversation, so its prompt must be self-contained: the area or question, the paths/symbols to start from, and exactly what the digest must report back. Do NOT use it on small or single-subsystem repos, or to read one known file \u2014 direct tools are faster there."
|
|
3965
3954
|
];
|
|
3966
|
-
if (subagentsEnabled) {
|
|
3967
|
-
lines.push(
|
|
3968
|
-
"",
|
|
3969
|
-
'spawn_research_agent(prompt: string) - Launch a stateless read-only research agent that explores the workspace with its own tools and returns a digest. Use it to delegate an open-ended exploration thread \u2014 a subsystem to map, or a "how does X work here" question \u2014 whenever one emerges during research. To explore several independent areas, launch the agents CONCURRENTLY: put multiple spawn_research_agent calls in one reply and they run in parallel.',
|
|
3970
|
-
"The agent sees nothing of this conversation, so its prompt must be self-contained: the area or question, the paths/symbols to start from, and exactly what the digest must report back. Do NOT use it on small or single-subsystem repos, or to read one known file \u2014 direct tools are faster there."
|
|
3971
|
-
);
|
|
3972
|
-
}
|
|
3973
3955
|
return lines.join("\n");
|
|
3974
3956
|
}
|
|
3975
3957
|
function buildSubagentSystemPrompt() {
|
|
@@ -4007,11 +3989,11 @@ function buildModeExamplesForRunners(runners) {
|
|
|
4007
3989
|
return `${r}: default|plan`;
|
|
4008
3990
|
}).join(", ");
|
|
4009
3991
|
}
|
|
4010
|
-
function verificationModeBlock(
|
|
3992
|
+
function verificationModeBlock() {
|
|
4011
3993
|
return [
|
|
4012
3994
|
"",
|
|
4013
3995
|
"VERIFICATION MODE:",
|
|
4014
|
-
|
|
3996
|
+
"Add a FINAL verification task to the end of the plan (highest order number).",
|
|
4015
3997
|
'This task closes the gap between "every task finished" and "the feature is correct": tasks executed in isolated sessions can each pass while the integrated feature is still short. Its outcome must come from commands and exit codes, never from judgement.',
|
|
4016
3998
|
"This task must:",
|
|
4017
3999
|
'- Have type "ai" and autonomy "AFK" \u2014 it needs no human input',
|
|
@@ -4047,35 +4029,14 @@ function researchPhaseBlock(harnessMode) {
|
|
|
4047
4029
|
"- Research and questions can interleave \u2014 you can explore a file, then ask a question grounded in it, in one response"
|
|
4048
4030
|
].join("\n");
|
|
4049
4031
|
}
|
|
4050
|
-
function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, runnerModes, autonomousDefault = true,
|
|
4032
|
+
function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, runnerModes, autonomousDefault = true, verificationEnabled = false, harnessMode = false) {
|
|
4051
4033
|
const modelsJson = modelsJsonFor(modelsByRunner, runners);
|
|
4052
4034
|
const modeGuide = runnerModes ? buildModeGuide(runnerModes, autonomousDefault) : buildModeGuideForRunners(runners, autonomousDefault);
|
|
4053
4035
|
const modeExamples = runnerModes ? Object.entries(runnerModes).map(([r, ms]) => {
|
|
4054
4036
|
const filtered = filteredBuildModes(ms, autonomousDefault);
|
|
4055
4037
|
return `${r}: ${filtered.map((m) => m.id).join("|")}${ms.some((m) => m.id === "plan") ? "|plan" : ""}`;
|
|
4056
4038
|
}).join(", ") : buildModeExamplesForRunners(runners);
|
|
4057
|
-
const
|
|
4058
|
-
"",
|
|
4059
|
-
"INTERVIEW MODE \u2014 GRILL-ME:",
|
|
4060
|
-
"Interview the user relentlessly about every aspect of their goal until you reach a shared understanding. Walk down each branch of the design tree, resolving dependencies between decisions one-by-one.",
|
|
4061
|
-
"Ask one question at a time, waiting for the user's answer before continuing. For each question, propose your recommended answer so they can confirm or correct it. Asking multiple questions at once is bewildering.",
|
|
4062
|
-
"If a fact can be found by exploring the codebase, look it up instead of asking. The decisions, though, are the user's \u2014 put each one to them and wait.",
|
|
4063
|
-
"Only propose an outline after the user has answered enough questions that you genuinely understand the goal. Then present the prose outline and wait for explicit confirmation before emitting the task plan JSON."
|
|
4064
|
-
].join("\n") : "";
|
|
4065
|
-
const prdBlock = prdEnabled ? [
|
|
4066
|
-
"",
|
|
4067
|
-
"PRD MODE: Before producing the outline, generate a PRD in two steps.",
|
|
4068
|
-
"Step 1 \u2014 Propose a short prose preview containing: the problem statement, the proposed approach, the testing seams, and the main risks. Prefer existing seams over new ones; propose new seams at the highest point possible. The fewer seams across the codebase, the better \u2014 the ideal number is one. Propose a feature-slug for saving the PRD (e.g. `my-feature`). Ask the user whether they agree.",
|
|
4069
|
-
"Step 2 \u2014 Only after the user explicitly agrees, write the full markdown PRD using the standard PRD format (problem, user stories, implementation decisions, testing seams, out of scope). The user may edit the proposed feature-slug before you write the PRD; use the agreed slug in the final output.",
|
|
4070
|
-
'PRD content rules: user stories are an extensive numbered list, each "As an <actor>, I want <feature>, so that <benefit>". Record implementation decisions as decisions (modules, interfaces, schema changes, API contracts) \u2014 do NOT include specific file paths or code snippets; they go stale fast. Exception: a snippet from a prototype that encodes a decision more precisely than prose (state machine, reducer, schema, type shape) may be inlined, trimmed to the decision-rich parts.',
|
|
4071
|
-
"When writing the full PRD, wrap it EXACTLY with these markers so the system can save it automatically:",
|
|
4072
|
-
'<!-- ORDEWELL_PRD_START slug="<feature-slug>" -->',
|
|
4073
|
-
"... full markdown PRD ...",
|
|
4074
|
-
"<!-- ORDEWELL_PRD_END -->",
|
|
4075
|
-
"Do NOT emit the full PRD before the user agrees to the preview. Do NOT emit task plan JSON before the PRD is agreed and the outline is confirmed."
|
|
4076
|
-
].join("\n") : "";
|
|
4077
|
-
const reviewBlock = reviewEnabled ? reviewModeBlock() : "";
|
|
4078
|
-
const verificationBlock = verificationEnabled ? verificationModeBlock(reviewEnabled) : "";
|
|
4039
|
+
const verificationBlock = verificationEnabled ? verificationModeBlock() : "";
|
|
4079
4040
|
return buildConversationBody(
|
|
4080
4041
|
goal,
|
|
4081
4042
|
context,
|
|
@@ -4084,34 +4045,10 @@ function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, r
|
|
|
4084
4045
|
modeGuide,
|
|
4085
4046
|
modeExamples,
|
|
4086
4047
|
harnessMode,
|
|
4087
|
-
grillMeBlock,
|
|
4088
|
-
prdBlock,
|
|
4089
|
-
reviewBlock,
|
|
4090
4048
|
verificationBlock
|
|
4091
4049
|
);
|
|
4092
4050
|
}
|
|
4093
|
-
function
|
|
4094
|
-
return [
|
|
4095
|
-
"",
|
|
4096
|
-
"REVIEW MODE:",
|
|
4097
|
-
"Add a FINAL review task to the end of the plan (highest order number).",
|
|
4098
|
-
"This task must:",
|
|
4099
|
-
'- Have type "ai" and use the STRONGEST available model',
|
|
4100
|
-
"- Have dependencies on ALL other AI/user tasks in the plan",
|
|
4101
|
-
"- Be assigned appropriately as HITL (human review) since the results need human sign-off",
|
|
4102
|
-
"- Its prompt should instruct the agent to:",
|
|
4103
|
-
" * Review every completed task's outputs and verify the original goal was met",
|
|
4104
|
-
" * Check that each task delivers what its description promised",
|
|
4105
|
-
" * Review the work on two axes, reported SEPARATELY so one cannot mask the other:",
|
|
4106
|
-
" - SPEC axis: if a PRD exists for this plan (`.scratch/<slug>/PRD.md`), verify against it: (a) requirements asked for but missing or partial; (b) behaviour delivered that the PRD never asked for (scope creep); (c) requirements that look implemented but wrongly. Quote the PRD line for each finding. With no PRD, review against the original goal.",
|
|
4107
|
-
" - STANDARDS axis: check the diff against the repo's documented coding standards (CONTRIBUTING.md, CLAUDE.md, etc.) plus this smell baseline: Mysterious Name (rename), Duplicated Code (extract shared shape), Feature Envy (move method to the data), Data Clumps (bundle into a type), Primitive Obsession (give the concept a type), Repeated Switches (polymorphism or shared map), Shotgun Surgery (gather into one module), Divergent Change (split by reason to change), Speculative Generality (delete it), Message Chains (hide the walk), Middle Man (call the target direct), Refused Bequest (composition over inheritance). Smell findings are always judgement calls, never hard failures; documented repo standards override the baseline; skip anything tooling already enforces.",
|
|
4108
|
-
" * Produce a structured review report listing what was checked and whether each check passed",
|
|
4109
|
-
" * Flag any gaps, bugs, or incomplete work",
|
|
4110
|
-
" * Output a final PASS/FAIL conclusion with evidence (standards-axis judgement calls alone must not fail the verification)",
|
|
4111
|
-
"- Use the same runner as the other tasks"
|
|
4112
|
-
].join("\n");
|
|
4113
|
-
}
|
|
4114
|
-
function buildConversationBody(goal, context, modelsJson, runners, modeGuide, modeExamples, harnessMode, grillMeBlock, prdBlock, reviewBlock, verificationBlock) {
|
|
4051
|
+
function buildConversationBody(goal, context, modelsJson, runners, modeGuide, modeExamples, harnessMode, verificationBlock) {
|
|
4115
4052
|
return [
|
|
4116
4053
|
"You are Ordewell's project planner. You explore the codebase, ask concise clarifying questions grounded in your findings, and produce structured task plans as JSON. Be direct: avoid unnecessary preamble, summaries, or explanations unless the user asks for detail.",
|
|
4117
4054
|
"",
|
|
@@ -4122,9 +4059,6 @@ function buildConversationBody(goal, context, modelsJson, runners, modeGuide, mo
|
|
|
4122
4059
|
'4. After the user confirms the outline, emit the final task plan as a JSON object with a "tasks" array.',
|
|
4123
4060
|
"",
|
|
4124
4061
|
researchPhaseBlock(harnessMode),
|
|
4125
|
-
grillMeBlock,
|
|
4126
|
-
prdBlock,
|
|
4127
|
-
reviewBlock,
|
|
4128
4062
|
verificationBlock,
|
|
4129
4063
|
"",
|
|
4130
4064
|
"OUTLINE PHASE:",
|
|
@@ -4311,7 +4245,6 @@ function getPlanTemplate() {
|
|
|
4311
4245
|
"",
|
|
4312
4246
|
"TASK MODE:",
|
|
4313
4247
|
"{{MODE_GUIDE}}",
|
|
4314
|
-
"{{REVIEW_BLOCK}}",
|
|
4315
4248
|
"{{VERIFICATION_BLOCK}}",
|
|
4316
4249
|
"",
|
|
4317
4250
|
"CONTEXT:",
|
|
@@ -4358,10 +4291,8 @@ function buildPlanPromptBase(userGoal, context, researchResults, modelsByRunner,
|
|
|
4358
4291
|
const filtered = filteredBuildModes(ms, autonomousDefault);
|
|
4359
4292
|
return `${r}: ${filtered.map((m) => m.id).join("|")}${ms.some((m) => m.id === "plan") ? "|plan" : ""}`;
|
|
4360
4293
|
}).join(", ") : buildModeExamplesForRunners(runners);
|
|
4361
|
-
return template.replace("{{RESEARCH_SECTION}}", researchReplacement).replace("{{CLARIFICATION_INSTRUCTIONS}}", ONE_SHOT_INSTRUCTIONS).replace("{{MODELS_JSON}}", modelsJson).replace("{{RUNNER_CHOICES}}", runners.join("|")).replace("{{RUNNER_INSTRUCTION}}", runnerInstruction(runners)).replace("{{MODE_GUIDE}}", modeGuide).replace("{{
|
|
4362
|
-
${
|
|
4363
|
-
` : "").replace("{{VERIFICATION_BLOCK}}", scoped.verification ? `
|
|
4364
|
-
${verificationModeBlock(scoped.review)}
|
|
4294
|
+
return template.replace("{{RESEARCH_SECTION}}", researchReplacement).replace("{{CLARIFICATION_INSTRUCTIONS}}", ONE_SHOT_INSTRUCTIONS).replace("{{MODELS_JSON}}", modelsJson).replace("{{RUNNER_CHOICES}}", runners.join("|")).replace("{{RUNNER_INSTRUCTION}}", runnerInstruction(runners)).replace("{{MODE_GUIDE}}", modeGuide).replace("{{VERIFICATION_BLOCK}}", scoped.verification ? `
|
|
4295
|
+
${verificationModeBlock()}
|
|
4365
4296
|
` : "").replace("{{MODE_EXAMPLES}}", modeExamples).replace("{{CONTEXT}}", context || "(no additional context)").replace("{{RESEARCH_RESULTS}}", researchResults ? `
|
|
4366
4297
|
|
|
4367
4298
|
RESEARCH FINDINGS:
|
|
@@ -4724,8 +4655,8 @@ var SUBAGENT_EXCLUDED = /* @__PURE__ */ new Set(["fetch", "web_search"]);
|
|
|
4724
4655
|
function subagentToolSpecs() {
|
|
4725
4656
|
return RESEARCH_TOOLS.filter((t) => !SUBAGENT_EXCLUDED.has(t.name));
|
|
4726
4657
|
}
|
|
4727
|
-
function withSpawn(
|
|
4728
|
-
return
|
|
4658
|
+
function withSpawn() {
|
|
4659
|
+
return [...RESEARCH_TOOLS, SPAWN_RESEARCH_AGENT_SPEC];
|
|
4729
4660
|
}
|
|
4730
4661
|
function projectOpenAi(specs) {
|
|
4731
4662
|
return specs.map((t) => ({
|
|
@@ -4746,8 +4677,8 @@ function projectOpenAi(specs) {
|
|
|
4746
4677
|
}
|
|
4747
4678
|
}));
|
|
4748
4679
|
}
|
|
4749
|
-
function toOpenAiTools(
|
|
4750
|
-
return projectOpenAi(withSpawn(
|
|
4680
|
+
function toOpenAiTools() {
|
|
4681
|
+
return projectOpenAi(withSpawn());
|
|
4751
4682
|
}
|
|
4752
4683
|
function toOpenAiSubagentTools() {
|
|
4753
4684
|
return projectOpenAi(subagentToolSpecs());
|
|
@@ -4758,8 +4689,8 @@ var SCHEMA_TYPE = {
|
|
|
4758
4689
|
array: SchemaType.ARRAY,
|
|
4759
4690
|
boolean: SchemaType.BOOLEAN
|
|
4760
4691
|
};
|
|
4761
|
-
function toGeminiToolDeclarations(
|
|
4762
|
-
return withSpawn(
|
|
4692
|
+
function toGeminiToolDeclarations() {
|
|
4693
|
+
return withSpawn().map((t) => {
|
|
4763
4694
|
const properties = {};
|
|
4764
4695
|
for (const [key, param] of Object.entries(t.properties)) {
|
|
4765
4696
|
properties[key] = {
|
|
@@ -4856,33 +4787,10 @@ function compactResearchResults(resultsText, maxChars = COMPACTION_LIMITS.resear
|
|
|
4856
4787
|
return (blocks[0].lines.length === 0 ? texts.slice(1) : texts).join("\n");
|
|
4857
4788
|
}
|
|
4858
4789
|
|
|
4859
|
-
// src/utils/prdStore.ts
|
|
4860
|
-
import * as fs from "fs";
|
|
4861
|
-
import * as path4 from "path";
|
|
4862
|
-
var PRD_BLOCK_RE = /<!--\s*ORDEWELL_PRD_START\s+slug="([^"]+)"\s*-->\s*([\s\S]*?)\s*<!--\s*ORDEWELL_PRD_END\s*-->/;
|
|
4863
|
-
function extractPrdBlock(text) {
|
|
4864
|
-
const match = PRD_BLOCK_RE.exec(text);
|
|
4865
|
-
if (!match) return null;
|
|
4866
|
-
const slug2 = sanitizeSlug(match[1]);
|
|
4867
|
-
const markdown = match[2].trim();
|
|
4868
|
-
if (!slug2 || !markdown) return null;
|
|
4869
|
-
return { slug: slug2, markdown };
|
|
4870
|
-
}
|
|
4871
|
-
function sanitizeSlug(raw) {
|
|
4872
|
-
return raw.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
4873
|
-
}
|
|
4874
|
-
function savePrdMarkdown(workspace, slug2, markdown) {
|
|
4875
|
-
const dir = path4.join(workspace, ".scratch", sanitizeSlug(slug2));
|
|
4876
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
4877
|
-
const file = path4.join(dir, "PRD.md");
|
|
4878
|
-
fs.writeFileSync(file, markdown.endsWith("\n") ? markdown : markdown + "\n", "utf8");
|
|
4879
|
-
return file;
|
|
4880
|
-
}
|
|
4881
|
-
|
|
4882
4790
|
// src/services/ContextCollector.ts
|
|
4883
4791
|
var ORDEWELL_CONTEXT_MAX = 8e3;
|
|
4884
|
-
async function collectResearchContext(
|
|
4885
|
-
const collector = new ContextCollector(
|
|
4792
|
+
async function collectResearchContext(fs12, runners) {
|
|
4793
|
+
const collector = new ContextCollector(fs12);
|
|
4886
4794
|
let contextStr = "";
|
|
4887
4795
|
let firstContext = null;
|
|
4888
4796
|
for (const r of runners) {
|
|
@@ -4909,8 +4817,8 @@ ${firstContext.dirStructure}
|
|
|
4909
4817
|
return contextStr;
|
|
4910
4818
|
}
|
|
4911
4819
|
var ContextCollector = class {
|
|
4912
|
-
constructor(
|
|
4913
|
-
this.fs =
|
|
4820
|
+
constructor(fs12, registry) {
|
|
4821
|
+
this.fs = fs12;
|
|
4914
4822
|
this.registry = registry;
|
|
4915
4823
|
}
|
|
4916
4824
|
fs;
|
|
@@ -4958,22 +4866,22 @@ var ALLOWED_TOOL_NAMES = new Set(subagentToolSpecs().map((t) => t.name));
|
|
|
4958
4866
|
function refused(output) {
|
|
4959
4867
|
return { success: false, output, truncated: false };
|
|
4960
4868
|
}
|
|
4961
|
-
function nonPromptingFs(
|
|
4962
|
-
const withinWorkspace = (p) => resolveWithin(
|
|
4869
|
+
function nonPromptingFs(fs12) {
|
|
4870
|
+
const withinWorkspace = (p) => resolveWithin(fs12.getWorkspaceRoot(), p ?? ".").inside;
|
|
4963
4871
|
const outsideRefusal = (p) => refused(
|
|
4964
4872
|
`Path "${p}" is outside the workspace. Research agents are confined to the workspace root \u2014 note the gap in your digest and let the planner request it directly.`
|
|
4965
4873
|
);
|
|
4966
4874
|
return {
|
|
4967
|
-
getWorkspaceRoot: () =>
|
|
4968
|
-
readFile: (p, opts) => withinWorkspace(p) ?
|
|
4875
|
+
getWorkspaceRoot: () => fs12.getWorkspaceRoot(),
|
|
4876
|
+
readFile: (p, opts) => withinWorkspace(p) ? fs12.readFile(p, opts) : Promise.resolve(outsideRefusal(p)),
|
|
4969
4877
|
readFiles: (paths) => {
|
|
4970
4878
|
const escaping = paths.find((p) => !withinWorkspace(p));
|
|
4971
|
-
return escaping ? Promise.resolve(outsideRefusal(escaping)) :
|
|
4879
|
+
return escaping ? Promise.resolve(outsideRefusal(escaping)) : fs12.readFiles(paths);
|
|
4972
4880
|
},
|
|
4973
|
-
glob: (pattern, opts) => withinWorkspace(opts?.path) ?
|
|
4974
|
-
grep: (pattern, opts) => withinWorkspace(opts?.path) ?
|
|
4975
|
-
findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ?
|
|
4976
|
-
listDir: (p, depth) => withinWorkspace(p) ?
|
|
4881
|
+
glob: (pattern, opts) => withinWorkspace(opts?.path) ? fs12.glob(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
4882
|
+
grep: (pattern, opts) => withinWorkspace(opts?.path) ? fs12.grep(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
4883
|
+
findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs12.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
4884
|
+
listDir: (p, depth) => withinWorkspace(p) ? fs12.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
|
|
4977
4885
|
bash: async (command, signal) => {
|
|
4978
4886
|
const dialect = resolveResearchShell().dialect;
|
|
4979
4887
|
const { tier, reason } = classifyCommand(command, { dialect });
|
|
@@ -4984,13 +4892,13 @@ function nonPromptingFs(fs10) {
|
|
|
4984
4892
|
}
|
|
4985
4893
|
const escaping = pathLikeArgs(command, { dialect }).find((p) => !withinWorkspace(p));
|
|
4986
4894
|
if (escaping) return outsideRefusal(escaping);
|
|
4987
|
-
return
|
|
4895
|
+
return fs12.bash(command, signal);
|
|
4988
4896
|
}
|
|
4989
4897
|
};
|
|
4990
4898
|
}
|
|
4991
4899
|
async function runLoop(prompt, deps) {
|
|
4992
4900
|
if (deps.signal?.aborted) return "[research agent aborted before starting]";
|
|
4993
|
-
const
|
|
4901
|
+
const fs12 = nonPromptingFs(deps.fs);
|
|
4994
4902
|
const chat = deps.createChat((delta) => deps.onProgress?.({ type: "thinking", text: delta }));
|
|
4995
4903
|
let turn = await chat.sendMessage(prompt, deps.signal);
|
|
4996
4904
|
for (let step = 0; turn.hasToolCalls && step < SUBAGENT_LIMITS.maxSteps; step++) {
|
|
@@ -5007,7 +4915,7 @@ ${turn.text}`;
|
|
|
5007
4915
|
if (tc.name === "read_file" && !("maxBytes" in args) && !("limit" in args)) args.limit = 2e3;
|
|
5008
4916
|
const toolArgs = JSON.stringify(tc.args);
|
|
5009
4917
|
deps.onProgress?.({ type: "tool_call", tool: tc.name, toolArgs, toolCallId: tc.id });
|
|
5010
|
-
const res = await executeTool(tc.name, args,
|
|
4918
|
+
const res = await executeTool(tc.name, args, fs12, void 0, deps.signal);
|
|
5011
4919
|
const output = res.output.length > SUBAGENT_LIMITS.toolOutputMaxChars ? res.output.slice(0, SUBAGENT_LIMITS.toolOutputMaxChars) + `
|
|
5012
4920
|
[... truncated to ${SUBAGENT_LIMITS.toolOutputMaxChars} chars, total ${res.output.length}]` : res.output;
|
|
5013
4921
|
const stepEntry = {
|
|
@@ -5065,8 +4973,6 @@ var BaseAiService = class _BaseAiService {
|
|
|
5065
4973
|
config;
|
|
5066
4974
|
conversation = null;
|
|
5067
4975
|
activeAbort = null;
|
|
5068
|
-
/** researchSubagents toggle (issue #34): set per operation from the live settings snapshot; off means bit-for-bit sequential behavior. */
|
|
5069
|
-
researchSubagentsEnabled = false;
|
|
5070
4976
|
hasActiveConversation() {
|
|
5071
4977
|
return this.conversation !== null;
|
|
5072
4978
|
}
|
|
@@ -5122,10 +5028,7 @@ var BaseAiService = class _BaseAiService {
|
|
|
5122
5028
|
* steering/failed result and the loop continues sequentially: a subagent can
|
|
5123
5029
|
* never fail a plan or a turn.
|
|
5124
5030
|
*/
|
|
5125
|
-
async executeSpawnAgent(args,
|
|
5126
|
-
if (!this.researchSubagentsEnabled) {
|
|
5127
|
-
return { success: false, output: "spawn_research_agent is not available in this session. Continue researching sequentially with your other tools (read_file, glob, grep, list_dir, bash).", truncated: false };
|
|
5128
|
-
}
|
|
5031
|
+
async executeSpawnAgent(args, fs12, onProgress, signal, subagentId) {
|
|
5129
5032
|
const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
|
|
5130
5033
|
if (!prompt) {
|
|
5131
5034
|
return { success: false, output: 'spawn_research_agent requires a non-empty "prompt" string: a self-contained task for the agent, including what its digest must report back.', truncated: false };
|
|
@@ -5136,20 +5039,20 @@ var BaseAiService = class _BaseAiService {
|
|
|
5136
5039
|
if (!chat) throw new Error("subagent chats are not available for this provider");
|
|
5137
5040
|
return chat;
|
|
5138
5041
|
},
|
|
5139
|
-
fs:
|
|
5042
|
+
fs: fs12,
|
|
5140
5043
|
signal,
|
|
5141
5044
|
onProgress: (progress) => onProgress({ ...progress, subagentId })
|
|
5142
5045
|
});
|
|
5143
5046
|
}
|
|
5144
5047
|
/** Collect project context for the planning phase. Shared with the harness backend. */
|
|
5145
|
-
static async collectResearchContext(
|
|
5146
|
-
return collectResearchContext(
|
|
5048
|
+
static async collectResearchContext(fs12, runners) {
|
|
5049
|
+
return collectResearchContext(fs12, runners);
|
|
5147
5050
|
}
|
|
5148
5051
|
/**
|
|
5149
5052
|
* Execute one round of tool calls and report each through onProgress.
|
|
5150
5053
|
* Returns the results to feed back plus the log entries produced.
|
|
5151
5054
|
*/
|
|
5152
|
-
async executeToolCalls(toolCalls,
|
|
5055
|
+
async executeToolCalls(toolCalls, fs12, onProgress, fetcher, stepIndex, thinking, signal) {
|
|
5153
5056
|
const toolResults = [];
|
|
5154
5057
|
const logEntries = [];
|
|
5155
5058
|
let resultsText = "";
|
|
@@ -5168,18 +5071,18 @@ var BaseAiService = class _BaseAiService {
|
|
|
5168
5071
|
const subagentId = uuidv4();
|
|
5169
5072
|
spawnSubagentIds.set(i, subagentId);
|
|
5170
5073
|
onProgress({ type: "tool_call", tool: toolCalls[i].name, toolArgs: JSON.stringify(toolCalls[i].args), subagentId, toolCallId: toolCalls[i].id });
|
|
5171
|
-
precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args },
|
|
5074
|
+
precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args }, fs12, onProgress, signal, subagentId));
|
|
5172
5075
|
}) : Promise.resolve(),
|
|
5173
5076
|
parallelIndexes.length > 1 ? mapWithConcurrency(parallelIndexes, TOOL_ROUND_CONCURRENCY, async (i) => {
|
|
5174
5077
|
onProgress({ type: "tool_call", tool: toolCalls[i].name, toolArgs: JSON.stringify(toolCalls[i].args), toolCallId: toolCalls[i].id });
|
|
5175
|
-
precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i],
|
|
5078
|
+
precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i], fs12, fetcher, signal));
|
|
5176
5079
|
}) : Promise.resolve()
|
|
5177
5080
|
]);
|
|
5178
5081
|
for (const [index, tc] of toolCalls.entries()) {
|
|
5179
5082
|
const toolArgs = normalizedArgs[index];
|
|
5180
5083
|
const preResult = precomputed.get(index);
|
|
5181
5084
|
if (!preResult) onProgress({ type: "tool_call", tool: tc.name, toolArgs: JSON.stringify(tc.args), toolCallId: tc.id });
|
|
5182
|
-
const toolResult = preResult ?? await executeTool(tc.name, toolArgs,
|
|
5085
|
+
const toolResult = preResult ?? await executeTool(tc.name, toolArgs, fs12, fetcher, signal);
|
|
5183
5086
|
const MAX_RESPONSE_CHARS = 5e4;
|
|
5184
5087
|
const truncatedResponse = toolResult.success && (toolResult.truncated || toolResult.output.length > MAX_RESPONSE_CHARS);
|
|
5185
5088
|
const llmOutput = toolResult.output.length > MAX_RESPONSE_CHARS ? toolResult.output.slice(0, MAX_RESPONSE_CHARS) + `
|
|
@@ -5311,7 +5214,6 @@ ${llmOutput}
|
|
|
5311
5214
|
researchLog
|
|
5312
5215
|
};
|
|
5313
5216
|
}
|
|
5314
|
-
if (extractPrdBlock(turn.text)) ctx.prdCaptured = true;
|
|
5315
5217
|
const reply = classifyPlannerReply(turn.text, {
|
|
5316
5218
|
runners: ctx.runners,
|
|
5317
5219
|
runnerModes: ctx.runnerModes,
|
|
@@ -5325,16 +5227,6 @@ ${llmOutput}
|
|
|
5325
5227
|
case "task_query":
|
|
5326
5228
|
return { kind: "task_query", query: reply.query, text: turn.text, researchLog };
|
|
5327
5229
|
case "plan":
|
|
5328
|
-
if (ctx.prdEnabled && !ctx.prdCaptured && !ctx.prdNudgeSent && !signal?.aborted) {
|
|
5329
|
-
ctx.prdNudgeSent = true;
|
|
5330
|
-
pending = [
|
|
5331
|
-
"PRD mode is enabled but no PRD has been produced yet, so the plan was NOT committed.",
|
|
5332
|
-
"In your next reply: first output the full markdown PRD wrapped EXACTLY in",
|
|
5333
|
-
'<!-- ORDEWELL_PRD_START slug="<feature-slug>" --> and <!-- ORDEWELL_PRD_END -->,',
|
|
5334
|
-
"then re-emit the exact same task plan JSON after the PRD block."
|
|
5335
|
-
].join(" ");
|
|
5336
|
-
continue;
|
|
5337
|
-
}
|
|
5338
5230
|
return { kind: "plan", tasks: reply.tasks, text: turn.text, researchLog };
|
|
5339
5231
|
// Botched attempts get a bounded corrective retry — otherwise the
|
|
5340
5232
|
// broken JSON would surface as a prose bubble and the edit or plan
|
|
@@ -5376,7 +5268,7 @@ ${llmOutput}
|
|
|
5376
5268
|
* parsed tasks if a plan was emitted mid-loop, or null if the loop
|
|
5377
5269
|
* exhausted without a valid plan.
|
|
5378
5270
|
*/
|
|
5379
|
-
async runResearchLoop(chat, firstMessage,
|
|
5271
|
+
async runResearchLoop(chat, firstMessage, fs12, onProgress, runners, initialLog, fetcher, userGoal, runnerModes, autonomousDefault, signal) {
|
|
5380
5272
|
const researchLog = initialLog ? [...initialLog] : [];
|
|
5381
5273
|
if (!initialLog && userGoal) {
|
|
5382
5274
|
researchLog.push({
|
|
@@ -5403,7 +5295,7 @@ ${llmOutput}
|
|
|
5403
5295
|
if (!turn.hasToolCalls) break;
|
|
5404
5296
|
const { toolResults, logEntries, resultsText } = await this.executeToolCalls(
|
|
5405
5297
|
turn.toolCalls,
|
|
5406
|
-
|
|
5298
|
+
fs12,
|
|
5407
5299
|
onProgress,
|
|
5408
5300
|
fetcher,
|
|
5409
5301
|
step,
|
|
@@ -5541,9 +5433,6 @@ var GeminiService = class extends BaseAiService {
|
|
|
5541
5433
|
req.runners,
|
|
5542
5434
|
req.runnerModes,
|
|
5543
5435
|
req.autonomousDefault ?? true,
|
|
5544
|
-
req.grillMeEnabled ?? false,
|
|
5545
|
-
req.prdEnabled ?? false,
|
|
5546
|
-
req.reviewEnabled ?? false,
|
|
5547
5436
|
req.verificationEnabled ?? false
|
|
5548
5437
|
);
|
|
5549
5438
|
const seededHistory = [
|
|
@@ -5582,8 +5471,7 @@ ${m.content}` });
|
|
|
5582
5471
|
runners: req.runners,
|
|
5583
5472
|
runnerModes: req.runnerModes,
|
|
5584
5473
|
autonomousDefault: req.autonomousDefault,
|
|
5585
|
-
fetcher: req.fetcher
|
|
5586
|
-
prdEnabled: req.prdEnabled ?? false
|
|
5474
|
+
fetcher: req.fetcher
|
|
5587
5475
|
};
|
|
5588
5476
|
this.conversation = { ctx, setProgress: (onProgress) => {
|
|
5589
5477
|
currentProgress = onProgress;
|
|
@@ -5641,11 +5529,11 @@ ${repairHint}` }] : parts;
|
|
|
5641
5529
|
return this.consumePlanStream(result, onToken);
|
|
5642
5530
|
}
|
|
5643
5531
|
// --- IAiService implementation ---
|
|
5644
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
5532
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
5645
5533
|
this.ensureInit();
|
|
5646
5534
|
const { autonomousDefault } = modes;
|
|
5647
5535
|
const genModel = this.getPlanningModel();
|
|
5648
|
-
const contextStr = await BaseAiService.collectResearchContext(
|
|
5536
|
+
const contextStr = await BaseAiService.collectResearchContext(fs12, runners);
|
|
5649
5537
|
const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
|
|
5650
5538
|
const firstMessage = `${buildResearchToolsPrompt()}
|
|
5651
5539
|
|
|
@@ -5656,17 +5544,17 @@ Explore the workspace to understand the codebase, then generate the plan.`;
|
|
|
5656
5544
|
generationConfig: { temperature: 0.3, topP: 0.95, maxOutputTokens: 16384 }
|
|
5657
5545
|
});
|
|
5658
5546
|
const researchChat = new GeminiResearchChat(chat);
|
|
5659
|
-
const result = await this.runResearchLoop(researchChat, firstMessage,
|
|
5547
|
+
const result = await this.runResearchLoop(researchChat, firstMessage, fs12, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
|
|
5660
5548
|
if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
|
|
5661
5549
|
const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
|
|
5662
5550
|
return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
|
|
5663
5551
|
}
|
|
5664
|
-
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken,
|
|
5552
|
+
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
|
|
5665
5553
|
this.ensureInit();
|
|
5666
5554
|
const { autonomousDefault } = modes;
|
|
5667
5555
|
let ctx = "";
|
|
5668
|
-
if (
|
|
5669
|
-
const collected = await new ContextCollector(
|
|
5556
|
+
if (fs12) {
|
|
5557
|
+
const collected = await new ContextCollector(fs12).collect(runners[0] ?? "claude-code");
|
|
5670
5558
|
if (collected.aiflowContext) ctx = `
|
|
5671
5559
|
<aiflow_context>
|
|
5672
5560
|
${collected.aiflowContext}
|
|
@@ -5682,12 +5570,12 @@ ${collected.aiflowContext}
|
|
|
5682
5570
|
autonomousDefault
|
|
5683
5571
|
);
|
|
5684
5572
|
}
|
|
5685
|
-
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress,
|
|
5573
|
+
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
|
|
5686
5574
|
this.ensureInit();
|
|
5687
5575
|
const { autonomousDefault } = modes;
|
|
5688
5576
|
let aiflowContext;
|
|
5689
|
-
if (
|
|
5690
|
-
const collected = await new ContextCollector(
|
|
5577
|
+
if (fs12) {
|
|
5578
|
+
const collected = await new ContextCollector(fs12).collect(existingPlan.runners[0] ?? "claude-code");
|
|
5691
5579
|
if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
|
|
5692
5580
|
}
|
|
5693
5581
|
const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
|
|
@@ -5870,7 +5758,6 @@ var OpenAiService = class extends BaseAiService {
|
|
|
5870
5758
|
async startConversation(req) {
|
|
5871
5759
|
this.ensureInit();
|
|
5872
5760
|
const client = this.getClient();
|
|
5873
|
-
this.researchSubagentsEnabled = req.researchSubagentsEnabled ?? false;
|
|
5874
5761
|
const contextStr = await BaseAiService.collectResearchContext(req.fs, req.runners);
|
|
5875
5762
|
const systemPrompt = buildConversationSystemPrompt(
|
|
5876
5763
|
req.goal,
|
|
@@ -5879,16 +5766,13 @@ var OpenAiService = class extends BaseAiService {
|
|
|
5879
5766
|
req.runners,
|
|
5880
5767
|
req.runnerModes,
|
|
5881
5768
|
req.autonomousDefault ?? true,
|
|
5882
|
-
req.grillMeEnabled ?? false,
|
|
5883
|
-
req.prdEnabled ?? false,
|
|
5884
|
-
req.reviewEnabled ?? false,
|
|
5885
5769
|
req.verificationEnabled ?? false
|
|
5886
5770
|
);
|
|
5887
5771
|
const userText = req.goal;
|
|
5888
5772
|
const messages = [
|
|
5889
5773
|
{ role: "system", content: `${systemPrompt}
|
|
5890
5774
|
|
|
5891
|
-
${buildResearchToolsPrompt(
|
|
5775
|
+
${buildResearchToolsPrompt()}` }
|
|
5892
5776
|
];
|
|
5893
5777
|
for (const m of req.priorHistory ?? []) {
|
|
5894
5778
|
messages.push({ role: m.role, content: m.content });
|
|
@@ -5898,7 +5782,7 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
|
|
|
5898
5782
|
messages,
|
|
5899
5783
|
client,
|
|
5900
5784
|
this.requireModel("orchestratorModel", this.config.orchestratorModel),
|
|
5901
|
-
toOpenAiTools(
|
|
5785
|
+
toOpenAiTools(),
|
|
5902
5786
|
(delta) => currentProgress({ type: "thinking", text: delta }),
|
|
5903
5787
|
(delta) => currentProgress({ type: "plan_token", planToken: delta })
|
|
5904
5788
|
);
|
|
@@ -5908,8 +5792,7 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
|
|
|
5908
5792
|
runners: req.runners,
|
|
5909
5793
|
runnerModes: req.runnerModes,
|
|
5910
5794
|
autonomousDefault: req.autonomousDefault,
|
|
5911
|
-
fetcher: req.fetcher
|
|
5912
|
-
prdEnabled: req.prdEnabled ?? false
|
|
5795
|
+
fetcher: req.fetcher
|
|
5913
5796
|
};
|
|
5914
5797
|
this.conversation = {
|
|
5915
5798
|
ctx,
|
|
@@ -5927,15 +5810,14 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
|
|
|
5927
5810
|
}
|
|
5928
5811
|
}
|
|
5929
5812
|
// --- IAiService implementation ---
|
|
5930
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
5813
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
5931
5814
|
this.ensureInit();
|
|
5932
5815
|
const client = this.getClient();
|
|
5933
|
-
const { autonomousDefault
|
|
5934
|
-
|
|
5935
|
-
const contextStr = await BaseAiService.collectResearchContext(fs10, runners);
|
|
5816
|
+
const { autonomousDefault } = modes;
|
|
5817
|
+
const contextStr = await BaseAiService.collectResearchContext(fs12, runners);
|
|
5936
5818
|
const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
|
|
5937
5819
|
const userText = `User goal: ${userDescription}`;
|
|
5938
|
-
const firstMessage = `${buildResearchToolsPrompt(
|
|
5820
|
+
const firstMessage = `${buildResearchToolsPrompt()}
|
|
5939
5821
|
|
|
5940
5822
|
Explore the workspace to understand the codebase, then generate the plan.
|
|
5941
5823
|
|
|
@@ -5947,20 +5829,20 @@ ${userText}`;
|
|
|
5947
5829
|
messages,
|
|
5948
5830
|
client,
|
|
5949
5831
|
this.requireModel("orchestratorModel", this.config.orchestratorModel),
|
|
5950
|
-
toOpenAiTools(
|
|
5832
|
+
toOpenAiTools(),
|
|
5951
5833
|
(delta) => onProgress({ type: "thinking", text: delta })
|
|
5952
5834
|
);
|
|
5953
|
-
const result = await this.runResearchLoop(researchChat, firstMessage,
|
|
5835
|
+
const result = await this.runResearchLoop(researchChat, firstMessage, fs12, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
|
|
5954
5836
|
if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
|
|
5955
5837
|
const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
|
|
5956
5838
|
return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
|
|
5957
5839
|
}
|
|
5958
|
-
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken,
|
|
5840
|
+
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
5959
5841
|
this.ensureInit();
|
|
5960
5842
|
const { autonomousDefault } = modes;
|
|
5961
5843
|
let ctx = "";
|
|
5962
|
-
if (
|
|
5963
|
-
const collected = await new ContextCollector(
|
|
5844
|
+
if (fs12) {
|
|
5845
|
+
const collected = await new ContextCollector(fs12).collect(runners[0] ?? "claude-code");
|
|
5964
5846
|
if (collected.aiflowContext) ctx = `
|
|
5965
5847
|
<aiflow_context>
|
|
5966
5848
|
${collected.aiflowContext}
|
|
@@ -5976,12 +5858,12 @@ ${collected.aiflowContext}
|
|
|
5976
5858
|
autonomousDefault
|
|
5977
5859
|
);
|
|
5978
5860
|
}
|
|
5979
|
-
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress,
|
|
5861
|
+
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
5980
5862
|
this.ensureInit();
|
|
5981
5863
|
const { autonomousDefault } = modes;
|
|
5982
5864
|
let aiflowContext;
|
|
5983
|
-
if (
|
|
5984
|
-
const collected = await new ContextCollector(
|
|
5865
|
+
if (fs12) {
|
|
5866
|
+
const collected = await new ContextCollector(fs12).collect(existingPlan.runners[0] ?? "claude-code");
|
|
5985
5867
|
if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
|
|
5986
5868
|
}
|
|
5987
5869
|
const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
|
|
@@ -6070,21 +5952,28 @@ function pickWindow(sorted, currentIdx, max) {
|
|
|
6070
5952
|
const window = sorted.slice(start, start + max);
|
|
6071
5953
|
return { window, omitted: sorted.length - window.length };
|
|
6072
5954
|
}
|
|
6073
|
-
function renderPlanMap(
|
|
6074
|
-
|
|
6075
|
-
|
|
5955
|
+
function renderPlanMap(planTasks, currentTaskId, opts) {
|
|
5956
|
+
const rows = flattenTasksWithParents(planTasks);
|
|
5957
|
+
if (rows.length < PLAN_MAP_MIN_TASKS) return "";
|
|
5958
|
+
const sorted = rows.map((r) => ({
|
|
5959
|
+
...r,
|
|
5960
|
+
label: taskOrderLabel(r.task, r.parent ?? void 0)
|
|
5961
|
+
})).sort((a, b) => {
|
|
5962
|
+
const parentDiff = (a.parent?.order ?? -1) - (b.parent?.order ?? -1);
|
|
5963
|
+
if (parentDiff !== 0) return parentDiff;
|
|
5964
|
+
return a.task.order - b.task.order;
|
|
5965
|
+
});
|
|
6076
5966
|
const max = opts?.maxEntries ?? DEFAULT_PLAN_MAP_MAX_ENTRIES;
|
|
6077
|
-
const currentIdx = sorted.findIndex((
|
|
5967
|
+
const currentIdx = sorted.findIndex((r) => r.task.id === currentTaskId);
|
|
6078
5968
|
const { window, omitted } = pickWindow(sorted, currentIdx, max);
|
|
6079
|
-
const currentTask = sorted.find((
|
|
6080
|
-
const
|
|
6081
|
-
|
|
6082
|
-
const
|
|
6083
|
-
const
|
|
6084
|
-
const order = String(t.order).padStart(widthOrder, " ");
|
|
5969
|
+
const currentTask = sorted.find((r) => r.task.id === currentTaskId)?.task;
|
|
5970
|
+
const lines = window.map((row) => {
|
|
5971
|
+
const isCurrent = row.task.id === currentTaskId;
|
|
5972
|
+
const tag = planMapStatus(row.task, isCurrent);
|
|
5973
|
+
const order = row.parent ? row.label : row.label.padStart(2, " ");
|
|
6085
5974
|
const arrow = isCurrent ? " \u2190 you are here" : "";
|
|
6086
|
-
const runner =
|
|
6087
|
-
return `${order}. [${tag.padEnd(7, " ")}] ${
|
|
5975
|
+
const runner = row.task.assignedRunner ? ` (${row.task.assignedRunner})` : "";
|
|
5976
|
+
return `${order}. [${tag.padEnd(7, " ")}] ${row.task.title}${runner}${arrow}`;
|
|
6088
5977
|
});
|
|
6089
5978
|
const footer = omitted > 0 ? `
|
|
6090
5979
|
(${omitted} task${omitted === 1 ? "" : "s"} omitted from this view)` : "";
|
|
@@ -6149,7 +6038,8 @@ function composeAugmentedPrompt(task, allTasks, opts) {
|
|
|
6149
6038
|
const map = renderPlanMap(allTasks, task.id, { maxEntries: opts?.planMapMaxEntries });
|
|
6150
6039
|
if (map) blocks.push(map);
|
|
6151
6040
|
}
|
|
6152
|
-
const
|
|
6041
|
+
const flat = flattenTasks(allTasks);
|
|
6042
|
+
const outputs = collectDirectDependencyOutputs(task, flat);
|
|
6153
6043
|
if (outputs.length > 0) blocks.push(renderPriorOutputs(outputs));
|
|
6154
6044
|
if (opts?.tddEnabled) {
|
|
6155
6045
|
blocks.push(renderTddInstruction());
|
|
@@ -6328,6 +6218,7 @@ function renderTerminalOutput(raw) {
|
|
|
6328
6218
|
return populated.map((r) => cells(r).join("")).join("\n");
|
|
6329
6219
|
}
|
|
6330
6220
|
var MARKER_SCAN_TAIL = 16384;
|
|
6221
|
+
var IDLE_TIMEOUT_MS = 6e4;
|
|
6331
6222
|
var VerdictEngine = class {
|
|
6332
6223
|
markerSeen = /* @__PURE__ */ new Set();
|
|
6333
6224
|
buffers = /* @__PURE__ */ new Map();
|
|
@@ -6335,6 +6226,7 @@ var VerdictEngine = class {
|
|
|
6335
6226
|
pausedSessions = /* @__PURE__ */ new Map();
|
|
6336
6227
|
listeners = [];
|
|
6337
6228
|
checkpointListeners = [];
|
|
6229
|
+
idleListeners = [];
|
|
6338
6230
|
/**
|
|
6339
6231
|
* Per-task generation counter. Incremented on every watch() and clear().
|
|
6340
6232
|
* Stale callbacks (from a prior session whose generation doesn't match
|
|
@@ -6342,12 +6234,47 @@ var VerdictEngine = class {
|
|
|
6342
6234
|
* wrong session.
|
|
6343
6235
|
*/
|
|
6344
6236
|
generations = /* @__PURE__ */ new Map();
|
|
6237
|
+
idleTimers = /* @__PURE__ */ new Map();
|
|
6238
|
+
idleSince = /* @__PURE__ */ new Map();
|
|
6345
6239
|
onVerdict(listener) {
|
|
6346
6240
|
this.listeners.push(listener);
|
|
6347
6241
|
}
|
|
6348
6242
|
onCheckpoint(listener) {
|
|
6349
6243
|
this.checkpointListeners.push(listener);
|
|
6350
6244
|
}
|
|
6245
|
+
onIdleChange(listener) {
|
|
6246
|
+
this.idleListeners.push(listener);
|
|
6247
|
+
}
|
|
6248
|
+
/** Advisory silence timestamp for a task, or null if it isn't idle. */
|
|
6249
|
+
getIdleSince(taskId) {
|
|
6250
|
+
return this.idleSince.get(taskId) ?? null;
|
|
6251
|
+
}
|
|
6252
|
+
/** Restart the silence timer on fresh output; broadcasts the null transition if it was idle. */
|
|
6253
|
+
touchIdle(taskId, gen) {
|
|
6254
|
+
const existing = this.idleTimers.get(taskId);
|
|
6255
|
+
if (existing) clearTimeout(existing);
|
|
6256
|
+
if (this.idleSince.get(taskId)) {
|
|
6257
|
+
this.idleSince.set(taskId, null);
|
|
6258
|
+
for (const l of this.idleListeners) l(taskId, null);
|
|
6259
|
+
}
|
|
6260
|
+
this.idleTimers.set(taskId, setTimeout(() => {
|
|
6261
|
+
if (this.generations.get(taskId) !== gen) return;
|
|
6262
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6263
|
+
this.idleSince.set(taskId, now);
|
|
6264
|
+
for (const l of this.idleListeners) l(taskId, now);
|
|
6265
|
+
}, IDLE_TIMEOUT_MS));
|
|
6266
|
+
}
|
|
6267
|
+
/** Tear down idle tracking for a task; broadcasts the null transition if it was idle. */
|
|
6268
|
+
clearIdle(taskId) {
|
|
6269
|
+
const timer = this.idleTimers.get(taskId);
|
|
6270
|
+
if (timer) clearTimeout(timer);
|
|
6271
|
+
this.idleTimers.delete(taskId);
|
|
6272
|
+
const wasIdle = this.idleSince.get(taskId);
|
|
6273
|
+
this.idleSince.delete(taskId);
|
|
6274
|
+
if (wasIdle) {
|
|
6275
|
+
for (const l of this.idleListeners) l(taskId, null);
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6351
6278
|
approveCheckpoint(taskId) {
|
|
6352
6279
|
const session = this.pausedSessions.get(taskId);
|
|
6353
6280
|
if (session) {
|
|
@@ -6378,6 +6305,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
6378
6305
|
this.checkpointCounts.set(task.id, 0);
|
|
6379
6306
|
session.onOutput((text) => {
|
|
6380
6307
|
if (this.generations.get(task.id) !== gen) return;
|
|
6308
|
+
this.touchIdle(task.id, gen);
|
|
6381
6309
|
if (this.markerSeen.has(task.id)) return;
|
|
6382
6310
|
const buf = (this.buffers.get(task.id) ?? "") + text;
|
|
6383
6311
|
this.buffers.set(task.id, buf);
|
|
@@ -6389,6 +6317,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
6389
6317
|
this.buffers.delete(task.id);
|
|
6390
6318
|
this.checkpointCounts.delete(task.id);
|
|
6391
6319
|
this.pausedSessions.delete(task.id);
|
|
6320
|
+
this.clearIdle(task.id);
|
|
6392
6321
|
this.generations.set(task.id, gen + 1);
|
|
6393
6322
|
const verdict = this.decide(task, 0);
|
|
6394
6323
|
for (const l of this.listeners) l(task.id, verdict, output);
|
|
@@ -6413,6 +6342,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
6413
6342
|
this.buffers.delete(task.id);
|
|
6414
6343
|
this.checkpointCounts.delete(task.id);
|
|
6415
6344
|
this.pausedSessions.delete(task.id);
|
|
6345
|
+
this.clearIdle(task.id);
|
|
6416
6346
|
const verdict = this.decide(task, exitCode);
|
|
6417
6347
|
for (const l of this.listeners) l(task.id, verdict, output);
|
|
6418
6348
|
});
|
|
@@ -6423,6 +6353,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
6423
6353
|
this.buffers.delete(task.id);
|
|
6424
6354
|
this.checkpointCounts.delete(task.id);
|
|
6425
6355
|
this.pausedSessions.delete(task.id);
|
|
6356
|
+
this.clearIdle(task.id);
|
|
6426
6357
|
this.generations.set(task.id, (this.generations.get(task.id) ?? 0) + 1);
|
|
6427
6358
|
return {
|
|
6428
6359
|
outcome: "pass",
|
|
@@ -6444,6 +6375,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
6444
6375
|
this.buffers.delete(task.id);
|
|
6445
6376
|
this.checkpointCounts.delete(task.id);
|
|
6446
6377
|
this.pausedSessions.delete(task.id);
|
|
6378
|
+
this.clearIdle(task.id);
|
|
6447
6379
|
this.generations.set(task.id, (this.generations.get(task.id) ?? 0) + 1);
|
|
6448
6380
|
}
|
|
6449
6381
|
/** Drop all tracking state (used on stop / loadPlan). */
|
|
@@ -6452,6 +6384,9 @@ ORDEWELL_REJECT: ${reason}
|
|
|
6452
6384
|
this.buffers.clear();
|
|
6453
6385
|
this.checkpointCounts.clear();
|
|
6454
6386
|
this.pausedSessions.clear();
|
|
6387
|
+
for (const timer of this.idleTimers.values()) clearTimeout(timer);
|
|
6388
|
+
this.idleTimers.clear();
|
|
6389
|
+
this.idleSince.clear();
|
|
6455
6390
|
this.generations.clear();
|
|
6456
6391
|
}
|
|
6457
6392
|
decide(task, exitCode) {
|
|
@@ -6857,6 +6792,7 @@ var TaskOrchestrator = class {
|
|
|
6857
6792
|
this.store.markAwaitingUser(taskId);
|
|
6858
6793
|
this.emit("onCheckpoint", { taskId, taskTitle: task.title, summary });
|
|
6859
6794
|
});
|
|
6795
|
+
this.verifier.onIdleChange(() => this.emit("onTaskChanged"));
|
|
6860
6796
|
}
|
|
6861
6797
|
config;
|
|
6862
6798
|
notifications;
|
|
@@ -6881,6 +6817,10 @@ var TaskOrchestrator = class {
|
|
|
6881
6817
|
workspaceRootFn = () => process.cwd();
|
|
6882
6818
|
observers = [];
|
|
6883
6819
|
tddEnabled = () => false;
|
|
6820
|
+
/** Advisory silence timestamp for a task's live runner, or null if not idle. */
|
|
6821
|
+
getIdleSince(taskId) {
|
|
6822
|
+
return this.verifier.getIdleSince(taskId);
|
|
6823
|
+
}
|
|
6884
6824
|
get storeInstance() {
|
|
6885
6825
|
return this.store;
|
|
6886
6826
|
}
|
|
@@ -7268,8 +7208,10 @@ ${output.slice(-3e3)}`);
|
|
|
7268
7208
|
if (ready.length === 0 && this.activeTaskSessions.size === 0) {
|
|
7269
7209
|
const remaining = this.store.allTasks.filter((t) => t.status !== "completed");
|
|
7270
7210
|
console.log(`[TaskOrchestrator] tick(): no work. remaining=${remaining.length}, completed=${this.store.isAllComplete()}`);
|
|
7271
|
-
|
|
7272
|
-
|
|
7211
|
+
const remainingById = new Map(remaining.map((t) => [t.id, t]));
|
|
7212
|
+
for (const { task, parent } of flattenTasksWithParents(this.store.planTasks)) {
|
|
7213
|
+
if (!remainingById.has(task.id)) continue;
|
|
7214
|
+
console.log(` - ${taskOrderLabel(task, parent ?? void 0)}. ${task.title} [${task.type}/${task.status}] prompt=${!!task.prompt}`);
|
|
7273
7215
|
}
|
|
7274
7216
|
if (this.store.isAllComplete()) {
|
|
7275
7217
|
this.running = false;
|
|
@@ -7296,7 +7238,7 @@ ${output.slice(-3e3)}`);
|
|
|
7296
7238
|
try {
|
|
7297
7239
|
const cwd = this.workspaceRootFn();
|
|
7298
7240
|
const runner = this.store.resolveTaskRunner(task);
|
|
7299
|
-
const finalPrompt = composeAugmentedPrompt(task, this.store.
|
|
7241
|
+
const finalPrompt = composeAugmentedPrompt(task, this.store.planTasks, {
|
|
7300
7242
|
planMapEnabled: this.config.planMapEnabled,
|
|
7301
7243
|
tddEnabled: this.tddEnabled()
|
|
7302
7244
|
});
|
|
@@ -7470,7 +7412,7 @@ import * as osMod from "os";
|
|
|
7470
7412
|
import { exec } from "child_process";
|
|
7471
7413
|
import { promisify } from "util";
|
|
7472
7414
|
import * as os2 from "os";
|
|
7473
|
-
import * as
|
|
7415
|
+
import * as path4 from "path";
|
|
7474
7416
|
var execAsync = promisify(exec);
|
|
7475
7417
|
var LOGIN_SHELL_TIMEOUT_MS = 5e3;
|
|
7476
7418
|
var PATH_MARKER = "__ORDEWELL_PATH__";
|
|
@@ -7481,40 +7423,40 @@ var defaultExec = async (command, options) => {
|
|
|
7481
7423
|
function posixBinDirs() {
|
|
7482
7424
|
const home = os2.homedir();
|
|
7483
7425
|
return [
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7426
|
+
path4.join(home, ".local", "bin"),
|
|
7427
|
+
path4.join(home, ".opencode", "bin"),
|
|
7428
|
+
path4.join(home, ".claude", "bin"),
|
|
7429
|
+
path4.join(home, ".bun", "bin"),
|
|
7430
|
+
path4.join(home, ".volta", "bin"),
|
|
7431
|
+
path4.join(home, ".npm-global", "bin"),
|
|
7432
|
+
path4.join(home, "bin"),
|
|
7491
7433
|
"/usr/local/bin",
|
|
7492
7434
|
"/opt/homebrew/bin"
|
|
7493
7435
|
];
|
|
7494
7436
|
}
|
|
7495
7437
|
function windowsBinDirs(env, home) {
|
|
7496
|
-
const
|
|
7497
|
-
const under = (base, ...parts) => base ? [
|
|
7438
|
+
const join19 = (...parts) => path4.win32.join(...parts);
|
|
7439
|
+
const under = (base, ...parts) => base ? [join19(base, ...parts)] : [];
|
|
7498
7440
|
const appData = env.APPDATA;
|
|
7499
7441
|
const localAppData = env.LOCALAPPDATA;
|
|
7500
7442
|
const programData = env.ProgramData;
|
|
7501
7443
|
return [
|
|
7502
7444
|
// Native per-user installers, PowerShell-driven and otherwise.
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7445
|
+
join19(home, ".local", "bin"),
|
|
7446
|
+
join19(home, ".opencode", "bin"),
|
|
7447
|
+
join19(home, ".claude", "bin"),
|
|
7448
|
+
join19(home, ".codex", "bin"),
|
|
7507
7449
|
...under(localAppData, "Programs", "claude"),
|
|
7508
7450
|
// Node package managers. npm's global prefix is `%APPDATA%\npm`; the
|
|
7509
7451
|
// literal spelling is kept as a fallback for a host that does not export
|
|
7510
7452
|
// APPDATA (a service account, a stripped CI container).
|
|
7511
7453
|
...under(appData, "npm"),
|
|
7512
|
-
|
|
7454
|
+
join19(home, "AppData", "Roaming", "npm"),
|
|
7513
7455
|
...under(localAppData, "pnpm"),
|
|
7514
7456
|
...under(localAppData, "Yarn", "bin"),
|
|
7515
|
-
|
|
7457
|
+
join19(home, ".bun", "bin"),
|
|
7516
7458
|
// Windows package managers.
|
|
7517
|
-
|
|
7459
|
+
join19(home, "scoop", "shims"),
|
|
7518
7460
|
...under(env.SCOOP, "shims"),
|
|
7519
7461
|
...under(env.SCOOP_GLOBAL ?? programData, "scoop", "shims"),
|
|
7520
7462
|
...under(programData, "chocolatey", "bin"),
|
|
@@ -7524,7 +7466,7 @@ function windowsBinDirs(env, home) {
|
|
|
7524
7466
|
...under(localAppData, "Microsoft", "WindowsApps"),
|
|
7525
7467
|
// Version managers. Volta's Windows home is not the POSIX `~/.volta`.
|
|
7526
7468
|
...under(localAppData, "Volta", "bin"),
|
|
7527
|
-
|
|
7469
|
+
join19(home, ".volta", "bin")
|
|
7528
7470
|
];
|
|
7529
7471
|
}
|
|
7530
7472
|
function wellKnownBinDirs(deps = {}) {
|
|
@@ -7553,14 +7495,14 @@ function augmentedPath(execImpl = defaultExec) {
|
|
|
7553
7495
|
const segments = [];
|
|
7554
7496
|
const push = (p) => {
|
|
7555
7497
|
if (!p) return;
|
|
7556
|
-
for (const seg of p.split(
|
|
7498
|
+
for (const seg of p.split(path4.delimiter)) {
|
|
7557
7499
|
if (seg && !segments.includes(seg)) segments.push(seg);
|
|
7558
7500
|
}
|
|
7559
7501
|
};
|
|
7560
7502
|
push(process.env.PATH);
|
|
7561
7503
|
push(await loginShellPath(execImpl));
|
|
7562
7504
|
for (const dir of wellKnownBinDirs()) push(dir);
|
|
7563
|
-
return segments.join(
|
|
7505
|
+
return segments.join(path4.delimiter);
|
|
7564
7506
|
})();
|
|
7565
7507
|
}
|
|
7566
7508
|
return cachedPath;
|
|
@@ -7580,6 +7522,50 @@ function withPath(base, resolvedPath, overrides = {}) {
|
|
|
7580
7522
|
return env;
|
|
7581
7523
|
}
|
|
7582
7524
|
|
|
7525
|
+
// src/utils/globalDataDir.ts
|
|
7526
|
+
import * as fs from "fs";
|
|
7527
|
+
import * as path5 from "path";
|
|
7528
|
+
import * as os3 from "os";
|
|
7529
|
+
function globalDataDir() {
|
|
7530
|
+
return path5.join(os3.homedir(), ".ordewell");
|
|
7531
|
+
}
|
|
7532
|
+
function copyDirSync(src, dest) {
|
|
7533
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
7534
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
7535
|
+
for (const entry of entries) {
|
|
7536
|
+
const srcPath = path5.join(src, entry.name);
|
|
7537
|
+
const destPath = path5.join(dest, entry.name);
|
|
7538
|
+
if (entry.isDirectory()) {
|
|
7539
|
+
copyDirSync(srcPath, destPath);
|
|
7540
|
+
} else {
|
|
7541
|
+
fs.copyFileSync(srcPath, destPath);
|
|
7542
|
+
}
|
|
7543
|
+
}
|
|
7544
|
+
}
|
|
7545
|
+
var migrated = false;
|
|
7546
|
+
function migrateOldConfigDir() {
|
|
7547
|
+
if (migrated) return;
|
|
7548
|
+
migrated = true;
|
|
7549
|
+
const oldDir = path5.join(os3.homedir(), ".config", "ordewell");
|
|
7550
|
+
if (!fs.existsSync(oldDir)) return;
|
|
7551
|
+
const newDir = globalDataDir();
|
|
7552
|
+
const liftFile = (name) => {
|
|
7553
|
+
const oldFile = path5.join(oldDir, name);
|
|
7554
|
+
const newFile = path5.join(newDir, name);
|
|
7555
|
+
if (fs.existsSync(oldFile) && !fs.existsSync(newFile)) {
|
|
7556
|
+
fs.mkdirSync(newDir, { recursive: true });
|
|
7557
|
+
fs.copyFileSync(oldFile, newFile);
|
|
7558
|
+
}
|
|
7559
|
+
};
|
|
7560
|
+
liftFile("settings.json");
|
|
7561
|
+
liftFile(".env");
|
|
7562
|
+
const oldPlugins = path5.join(oldDir, "plugins");
|
|
7563
|
+
const newPlugins = path5.join(newDir, "plugins");
|
|
7564
|
+
if (fs.existsSync(oldPlugins) && !fs.existsSync(newPlugins)) {
|
|
7565
|
+
copyDirSync(oldPlugins, newPlugins);
|
|
7566
|
+
}
|
|
7567
|
+
}
|
|
7568
|
+
|
|
7583
7569
|
// src/utils/launch.ts
|
|
7584
7570
|
import * as fsSync2 from "fs";
|
|
7585
7571
|
import * as path6 from "path";
|
|
@@ -7905,7 +7891,7 @@ function parseCodexModelsCache(content) {
|
|
|
7905
7891
|
}
|
|
7906
7892
|
var GEMINI_MODELS_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
7907
7893
|
function geminiDiscoveryCacheKey(apiKey) {
|
|
7908
|
-
const dir =
|
|
7894
|
+
const dir = globalDataDir();
|
|
7909
7895
|
const hash = apiKey.slice(-8);
|
|
7910
7896
|
return {
|
|
7911
7897
|
key: `gemini-models:${hash}`,
|
|
@@ -8435,12 +8421,14 @@ var ModelDiscovery = class {
|
|
|
8435
8421
|
// src/services/ModelCatalog.ts
|
|
8436
8422
|
import * as fs2 from "fs";
|
|
8437
8423
|
import * as path8 from "path";
|
|
8438
|
-
import * as os3 from "os";
|
|
8439
8424
|
var CACHE_KEY = "model-catalog";
|
|
8440
8425
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
8441
8426
|
function storageKey(baseUrl) {
|
|
8442
8427
|
return `${CACHE_KEY}:${baseUrl}`;
|
|
8443
8428
|
}
|
|
8429
|
+
function globalCacheDir() {
|
|
8430
|
+
return globalDataDir();
|
|
8431
|
+
}
|
|
8444
8432
|
var ModelCatalog = class {
|
|
8445
8433
|
static async fetchModels(apiKey, baseUrl, fetchImpl) {
|
|
8446
8434
|
const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
|
|
@@ -8483,7 +8471,7 @@ var ModelCatalog = class {
|
|
|
8483
8471
|
try {
|
|
8484
8472
|
const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
|
|
8485
8473
|
const key = storageKey(url);
|
|
8486
|
-
const cacheDir =
|
|
8474
|
+
const cacheDir = globalCacheDir();
|
|
8487
8475
|
const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
8488
8476
|
if (fs2.existsSync(cacheFile)) fs2.rmSync(cacheFile);
|
|
8489
8477
|
} catch {
|
|
@@ -8491,7 +8479,7 @@ var ModelCatalog = class {
|
|
|
8491
8479
|
}
|
|
8492
8480
|
static readCache(key) {
|
|
8493
8481
|
try {
|
|
8494
|
-
const cacheDir =
|
|
8482
|
+
const cacheDir = globalCacheDir();
|
|
8495
8483
|
const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
8496
8484
|
if (!fs2.existsSync(cacheFile)) return null;
|
|
8497
8485
|
const raw = fs2.readFileSync(cacheFile, "utf8");
|
|
@@ -8504,7 +8492,7 @@ var ModelCatalog = class {
|
|
|
8504
8492
|
}
|
|
8505
8493
|
static writeCache(key, models) {
|
|
8506
8494
|
try {
|
|
8507
|
-
const cacheDir =
|
|
8495
|
+
const cacheDir = globalCacheDir();
|
|
8508
8496
|
fs2.mkdirSync(cacheDir, { recursive: true });
|
|
8509
8497
|
const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
8510
8498
|
fs2.writeFileSync(
|
|
@@ -9946,14 +9934,14 @@ ${part.text}` : part.text });
|
|
|
9946
9934
|
}
|
|
9947
9935
|
}
|
|
9948
9936
|
}
|
|
9949
|
-
async json(method,
|
|
9950
|
-
const response = await this.deps.fetch(`${this.baseUrl}${
|
|
9937
|
+
async json(method, path20, body, signal) {
|
|
9938
|
+
const response = await this.deps.fetch(`${this.baseUrl}${path20}`, {
|
|
9951
9939
|
method,
|
|
9952
9940
|
headers: { "content-type": "application/json" },
|
|
9953
9941
|
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
9954
9942
|
...signal ? { signal } : {}
|
|
9955
9943
|
});
|
|
9956
|
-
if (!response.ok) throw new Error(`${method} ${
|
|
9944
|
+
if (!response.ok) throw new Error(`${method} ${path20} failed: ${response.status} ${response.statusText}`);
|
|
9957
9945
|
return await response.json().catch(() => null);
|
|
9958
9946
|
}
|
|
9959
9947
|
nativeSessionId() {
|
|
@@ -10108,9 +10096,6 @@ var CliAgentAiService = class {
|
|
|
10108
10096
|
req.runners,
|
|
10109
10097
|
req.runnerModes,
|
|
10110
10098
|
req.autonomousDefault ?? true,
|
|
10111
|
-
req.grillMeEnabled ?? false,
|
|
10112
|
-
req.prdEnabled ?? false,
|
|
10113
|
-
req.reviewEnabled ?? false,
|
|
10114
10099
|
req.verificationEnabled ?? false,
|
|
10115
10100
|
true
|
|
10116
10101
|
);
|
|
@@ -10125,10 +10110,7 @@ var CliAgentAiService = class {
|
|
|
10125
10110
|
startOptions,
|
|
10126
10111
|
runners: req.runners,
|
|
10127
10112
|
runnerModes: req.runnerModes,
|
|
10128
|
-
autonomousDefault: req.autonomousDefault
|
|
10129
|
-
prdEnabled: req.prdEnabled ?? false,
|
|
10130
|
-
prdCaptured: false,
|
|
10131
|
-
prdNudgeSent: false
|
|
10113
|
+
autonomousDefault: req.autonomousDefault
|
|
10132
10114
|
};
|
|
10133
10115
|
const opening = this.openingMessage(req);
|
|
10134
10116
|
return this.runConversation(opening, req.onProgress, req.signal);
|
|
@@ -10156,8 +10138,8 @@ var CliAgentAiService = class {
|
|
|
10156
10138
|
/**
|
|
10157
10139
|
* Drive one user message to a settled planner turn. Same shape as the API
|
|
10158
10140
|
* backend's conversation loop minus the tool rounds — those belong to the
|
|
10159
|
-
* agent now — and with the same
|
|
10160
|
-
*
|
|
10141
|
+
* agent now — and with the same two policies layered on top: the
|
|
10142
|
+
* empty-reply nudge, and a bounded corrective re-emit for botched JSON.
|
|
10161
10143
|
*/
|
|
10162
10144
|
async runConversation(message, onProgress, signal) {
|
|
10163
10145
|
const conversation = this.conversation;
|
|
@@ -10205,7 +10187,6 @@ var CliAgentAiService = class {
|
|
|
10205
10187
|
].join(" ");
|
|
10206
10188
|
continue;
|
|
10207
10189
|
}
|
|
10208
|
-
if (extractPrdBlock(turn.text)) conversation.prdCaptured = true;
|
|
10209
10190
|
const reply = classifyPlannerReply(turn.text, {
|
|
10210
10191
|
runners: conversation.runners,
|
|
10211
10192
|
runnerModes: conversation.runnerModes,
|
|
@@ -10219,16 +10200,6 @@ var CliAgentAiService = class {
|
|
|
10219
10200
|
case "task_query":
|
|
10220
10201
|
return { kind: "task_query", query: reply.query, text: replyText(turn.text), researchLog };
|
|
10221
10202
|
case "plan":
|
|
10222
|
-
if (conversation.prdEnabled && !conversation.prdCaptured && !conversation.prdNudgeSent && !combined?.aborted) {
|
|
10223
|
-
conversation.prdNudgeSent = true;
|
|
10224
|
-
pending = [
|
|
10225
|
-
"PRD mode is enabled but no PRD has been produced yet, so the plan was NOT committed.",
|
|
10226
|
-
"In your next reply: first output the full markdown PRD wrapped EXACTLY in",
|
|
10227
|
-
'<!-- ORDEWELL_PRD_START slug="<feature-slug>" --> and <!-- ORDEWELL_PRD_END -->,',
|
|
10228
|
-
"then re-emit the exact same task plan JSON after the PRD block."
|
|
10229
|
-
].join(" ");
|
|
10230
|
-
continue;
|
|
10231
|
-
}
|
|
10232
10203
|
this.conversation = null;
|
|
10233
10204
|
return { kind: "plan", tasks: reply.tasks, text: replyText(turn.text), researchLog };
|
|
10234
10205
|
case "broken_task_ops":
|
|
@@ -10407,10 +10378,7 @@ var CliAgentAiService = class {
|
|
|
10407
10378
|
oneShotAdapter = await this.startAdapter(startOptions);
|
|
10408
10379
|
this.conversation = {
|
|
10409
10380
|
startOptions,
|
|
10410
|
-
runners: []
|
|
10411
|
-
prdEnabled: false,
|
|
10412
|
-
prdCaptured: false,
|
|
10413
|
-
prdNudgeSent: false
|
|
10381
|
+
runners: []
|
|
10414
10382
|
};
|
|
10415
10383
|
const turn = await this.runTurn(
|
|
10416
10384
|
"Follow the instructions in your system prompt and produce the plan now.",
|
|
@@ -10427,8 +10395,8 @@ var CliAgentAiService = class {
|
|
|
10427
10395
|
this.lastNativeSessionId = previousSessionId;
|
|
10428
10396
|
}
|
|
10429
10397
|
}
|
|
10430
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
10431
|
-
const contextStr = await collectResearchContext(
|
|
10398
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
10399
|
+
const contextStr = await collectResearchContext(fs12, runners);
|
|
10432
10400
|
const researchLog = [
|
|
10433
10401
|
{ id: `up-${Date.now()}`, type: "user_prompt", content: userDescription, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
10434
10402
|
];
|
|
@@ -10751,6 +10719,180 @@ function retargetTaskRunner(task, runner, catalog) {
|
|
|
10751
10719
|
return { assignedRunner: runner, ...runnerAssignment(catalog, task) };
|
|
10752
10720
|
}
|
|
10753
10721
|
|
|
10722
|
+
// src/services/SkillsService.ts
|
|
10723
|
+
import * as fs4 from "fs";
|
|
10724
|
+
import * as path11 from "path";
|
|
10725
|
+
|
|
10726
|
+
// src/services/builtinSkills.ts
|
|
10727
|
+
import * as path10 from "path";
|
|
10728
|
+
import { fileURLToPath } from "url";
|
|
10729
|
+
function builtinSkillsDir() {
|
|
10730
|
+
const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path10.dirname(fileURLToPath(
|
|
10731
|
+
// @ts-expect-error - import.meta is only valid under an ESM module setting
|
|
10732
|
+
import.meta.url
|
|
10733
|
+
));
|
|
10734
|
+
return path10.join(dir, "..", "skills");
|
|
10735
|
+
}
|
|
10736
|
+
|
|
10737
|
+
// src/services/SkillsService.ts
|
|
10738
|
+
var BUILTIN_SKILL_NAMES = ["grilling", "to-spec"];
|
|
10739
|
+
var RETIRED_BUILTIN_SKILL_NAMES = ["grill-me"];
|
|
10740
|
+
function parseSkillFile(filePath, name, source) {
|
|
10741
|
+
const raw = fs4.readFileSync(filePath, "utf8");
|
|
10742
|
+
const frontmatter = {};
|
|
10743
|
+
let content = raw;
|
|
10744
|
+
if (raw.startsWith("---\n")) {
|
|
10745
|
+
const end = raw.indexOf("\n---", 4);
|
|
10746
|
+
if (end !== -1) {
|
|
10747
|
+
const fmText = raw.slice(4, end);
|
|
10748
|
+
for (const line of fmText.split("\n")) {
|
|
10749
|
+
const idx = line.indexOf(":");
|
|
10750
|
+
if (idx === -1) continue;
|
|
10751
|
+
const key = line.slice(0, idx).trim();
|
|
10752
|
+
const value = line.slice(idx + 1).trim();
|
|
10753
|
+
if (key === "disable-model-invocation") {
|
|
10754
|
+
frontmatter[key] = value === "true";
|
|
10755
|
+
} else if (key === "name" || key === "description") {
|
|
10756
|
+
frontmatter[key] = value.replace(/^"|"$/g, "");
|
|
10757
|
+
}
|
|
10758
|
+
}
|
|
10759
|
+
content = raw.slice(end + 4).replace(/^\n+/, "");
|
|
10760
|
+
}
|
|
10761
|
+
}
|
|
10762
|
+
const metadata = {
|
|
10763
|
+
name: frontmatter.name ?? name,
|
|
10764
|
+
description: frontmatter.description ?? "",
|
|
10765
|
+
...frontmatter["disable-model-invocation"] !== void 0 ? { disableModelInvocation: frontmatter["disable-model-invocation"] } : {}
|
|
10766
|
+
};
|
|
10767
|
+
return { name, description: metadata.description, metadata, content, path: filePath, source };
|
|
10768
|
+
}
|
|
10769
|
+
function copyDirSync2(src, dest) {
|
|
10770
|
+
fs4.mkdirSync(dest, { recursive: true });
|
|
10771
|
+
const entries = fs4.readdirSync(src, { withFileTypes: true });
|
|
10772
|
+
for (const entry of entries) {
|
|
10773
|
+
const srcPath = path11.join(src, entry.name);
|
|
10774
|
+
const destPath = path11.join(dest, entry.name);
|
|
10775
|
+
if (entry.isDirectory()) copyDirSync2(srcPath, destPath);
|
|
10776
|
+
else fs4.copyFileSync(srcPath, destPath);
|
|
10777
|
+
}
|
|
10778
|
+
}
|
|
10779
|
+
function readDir(dir, source) {
|
|
10780
|
+
if (!fs4.existsSync(dir)) return [];
|
|
10781
|
+
const entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
10782
|
+
const skills = [];
|
|
10783
|
+
for (const entry of entries) {
|
|
10784
|
+
if (!entry.isDirectory()) continue;
|
|
10785
|
+
const skillFile = path11.join(dir, entry.name, "SKILL.md");
|
|
10786
|
+
if (!fs4.existsSync(skillFile)) continue;
|
|
10787
|
+
const parsed = parseSkillFile(skillFile, entry.name, source);
|
|
10788
|
+
if (parsed) skills.push(parsed);
|
|
10789
|
+
}
|
|
10790
|
+
return skills;
|
|
10791
|
+
}
|
|
10792
|
+
var SkillsService = class {
|
|
10793
|
+
constructor(workspaceRoot) {
|
|
10794
|
+
this.workspaceRoot = workspaceRoot;
|
|
10795
|
+
}
|
|
10796
|
+
workspaceRoot;
|
|
10797
|
+
localDir() {
|
|
10798
|
+
return this.workspaceRoot ? path11.join(this.workspaceRoot, ".ordewell", "skills") : void 0;
|
|
10799
|
+
}
|
|
10800
|
+
globalDir() {
|
|
10801
|
+
return path11.join(globalDataDir(), "skills");
|
|
10802
|
+
}
|
|
10803
|
+
seed(name) {
|
|
10804
|
+
const dest = path11.join(this.globalDir(), name);
|
|
10805
|
+
if (fs4.existsSync(dest)) return false;
|
|
10806
|
+
let srcDir;
|
|
10807
|
+
try {
|
|
10808
|
+
srcDir = path11.join(builtinSkillsDir(), name);
|
|
10809
|
+
} catch (err) {
|
|
10810
|
+
console.warn(`Could not locate built-in skills dir: ${String(err)}`);
|
|
10811
|
+
return false;
|
|
10812
|
+
}
|
|
10813
|
+
if (!fs4.existsSync(srcDir)) {
|
|
10814
|
+
console.warn(`Built-in skill not found in package: ${name}`);
|
|
10815
|
+
return false;
|
|
10816
|
+
}
|
|
10817
|
+
copyDirSync2(srcDir, dest);
|
|
10818
|
+
return true;
|
|
10819
|
+
}
|
|
10820
|
+
seedBuiltinSkill(name) {
|
|
10821
|
+
return this.seed(name);
|
|
10822
|
+
}
|
|
10823
|
+
/** Removes a retired built-in's seed from the global dir, but only if it's untouched by the user. */
|
|
10824
|
+
isUnmodifiedRetiredSeed(dir, expectedName) {
|
|
10825
|
+
let entries;
|
|
10826
|
+
try {
|
|
10827
|
+
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
10828
|
+
} catch {
|
|
10829
|
+
return false;
|
|
10830
|
+
}
|
|
10831
|
+
if (entries.length !== 1 || entries[0].name !== "SKILL.md" || !entries[0].isFile()) return false;
|
|
10832
|
+
let raw;
|
|
10833
|
+
try {
|
|
10834
|
+
raw = fs4.readFileSync(path11.join(dir, "SKILL.md"), "utf8");
|
|
10835
|
+
} catch {
|
|
10836
|
+
return false;
|
|
10837
|
+
}
|
|
10838
|
+
if (!raw.startsWith("---\n")) return false;
|
|
10839
|
+
const end = raw.indexOf("\n---", 4);
|
|
10840
|
+
if (end === -1) return false;
|
|
10841
|
+
for (const line of raw.slice(4, end).split("\n")) {
|
|
10842
|
+
const idx = line.indexOf(":");
|
|
10843
|
+
if (idx === -1) continue;
|
|
10844
|
+
const key = line.slice(0, idx).trim();
|
|
10845
|
+
if (key === "name") return line.slice(idx + 1).trim().replace(/^"|"$/g, "") === expectedName;
|
|
10846
|
+
}
|
|
10847
|
+
return false;
|
|
10848
|
+
}
|
|
10849
|
+
pruneRetired() {
|
|
10850
|
+
for (const name of RETIRED_BUILTIN_SKILL_NAMES) {
|
|
10851
|
+
const dest = path11.join(this.globalDir(), name);
|
|
10852
|
+
if (!fs4.existsSync(dest)) continue;
|
|
10853
|
+
if (!this.isUnmodifiedRetiredSeed(dest, name)) continue;
|
|
10854
|
+
try {
|
|
10855
|
+
fs4.rmSync(dest, { recursive: true, force: true });
|
|
10856
|
+
} catch (err) {
|
|
10857
|
+
console.warn(`Could not prune retired skill ${name}: ${String(err)}`);
|
|
10858
|
+
}
|
|
10859
|
+
}
|
|
10860
|
+
}
|
|
10861
|
+
findSkill(name) {
|
|
10862
|
+
this.pruneRetired();
|
|
10863
|
+
if (BUILTIN_SKILL_NAMES.includes(name)) this.seed(name);
|
|
10864
|
+
const localDir = this.localDir();
|
|
10865
|
+
if (localDir) {
|
|
10866
|
+
const localFile = path11.join(localDir, name, "SKILL.md");
|
|
10867
|
+
if (fs4.existsSync(localFile)) return parseSkillFile(localFile, name, "local");
|
|
10868
|
+
}
|
|
10869
|
+
const globalFile = path11.join(this.globalDir(), name, "SKILL.md");
|
|
10870
|
+
if (fs4.existsSync(globalFile)) return parseSkillFile(globalFile, name, "global");
|
|
10871
|
+
return void 0;
|
|
10872
|
+
}
|
|
10873
|
+
listSkills() {
|
|
10874
|
+
this.pruneRetired();
|
|
10875
|
+
for (const name of BUILTIN_SKILL_NAMES) this.seed(name);
|
|
10876
|
+
const byName = /* @__PURE__ */ new Map();
|
|
10877
|
+
for (const skill of readDir(this.globalDir(), "global")) {
|
|
10878
|
+
byName.set(skill.name, skill);
|
|
10879
|
+
}
|
|
10880
|
+
const localDir = this.localDir();
|
|
10881
|
+
if (localDir) {
|
|
10882
|
+
for (const skill of readDir(localDir, "local")) {
|
|
10883
|
+
byName.set(skill.name, skill);
|
|
10884
|
+
}
|
|
10885
|
+
}
|
|
10886
|
+
return [...byName.values()];
|
|
10887
|
+
}
|
|
10888
|
+
getSkillContent(name) {
|
|
10889
|
+
return this.findSkill(name)?.content;
|
|
10890
|
+
}
|
|
10891
|
+
};
|
|
10892
|
+
function createSkillsService(workspaceRoot) {
|
|
10893
|
+
return new SkillsService(workspaceRoot);
|
|
10894
|
+
}
|
|
10895
|
+
|
|
10754
10896
|
// src/services/SessionMessage.ts
|
|
10755
10897
|
function serializeTask(t) {
|
|
10756
10898
|
return {
|
|
@@ -10772,11 +10914,12 @@ function serializeTask(t) {
|
|
|
10772
10914
|
userStoriesCovered: t.userStoriesCovered || void 0
|
|
10773
10915
|
};
|
|
10774
10916
|
}
|
|
10775
|
-
function serializeTaskStatus(t) {
|
|
10917
|
+
function serializeTaskStatus(t, idleSince = null) {
|
|
10776
10918
|
return {
|
|
10777
10919
|
id: t.id,
|
|
10778
10920
|
status: t.status,
|
|
10779
|
-
verdict: t.verdict ? { outcome: t.verdict.outcome, reason: t.verdict.reason, checks: t.verdict.checks || [] } : null
|
|
10921
|
+
verdict: t.verdict ? { outcome: t.verdict.outcome, reason: t.verdict.reason, checks: t.verdict.checks || [] } : null,
|
|
10922
|
+
idleSince
|
|
10780
10923
|
};
|
|
10781
10924
|
}
|
|
10782
10925
|
function serializePlan(plan) {
|
|
@@ -10798,27 +10941,27 @@ function executionSummary(tasks) {
|
|
|
10798
10941
|
}
|
|
10799
10942
|
|
|
10800
10943
|
// src/utils/sessionStore.ts
|
|
10801
|
-
import * as
|
|
10802
|
-
import * as
|
|
10944
|
+
import * as fs6 from "fs";
|
|
10945
|
+
import * as path13 from "path";
|
|
10803
10946
|
|
|
10804
10947
|
// src/utils/fsHelpers.ts
|
|
10805
|
-
import * as
|
|
10806
|
-
import * as
|
|
10948
|
+
import * as fs5 from "fs";
|
|
10949
|
+
import * as path12 from "path";
|
|
10807
10950
|
var STATE_DIR = ".ordewell";
|
|
10808
10951
|
function ensureDir(dir) {
|
|
10809
|
-
if (!
|
|
10810
|
-
|
|
10952
|
+
if (!fs5.existsSync(dir)) {
|
|
10953
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
10811
10954
|
}
|
|
10812
10955
|
}
|
|
10813
10956
|
function getStateDir(baseDir) {
|
|
10814
|
-
return
|
|
10957
|
+
return path12.join(baseDir ?? process.cwd(), STATE_DIR);
|
|
10815
10958
|
}
|
|
10816
10959
|
var IGNORE_FILE = ".gitignore";
|
|
10817
10960
|
function ensureStateDirIgnored(baseDir) {
|
|
10818
10961
|
const dir = getStateDir(baseDir);
|
|
10819
10962
|
ensureDir(dir);
|
|
10820
10963
|
try {
|
|
10821
|
-
|
|
10964
|
+
fs5.writeFileSync(path12.join(dir, IGNORE_FILE), "*\n", { flag: "wx" });
|
|
10822
10965
|
} catch (err) {
|
|
10823
10966
|
if (err.code !== "EEXIST") throw err;
|
|
10824
10967
|
}
|
|
@@ -10833,7 +10976,7 @@ function mintSessionId() {
|
|
|
10833
10976
|
// src/utils/sessionStore.ts
|
|
10834
10977
|
var SESSIONS_SUBDIR = "sessions";
|
|
10835
10978
|
function getSessionsDir(baseDir) {
|
|
10836
|
-
return
|
|
10979
|
+
return path13.join(getStateDir(baseDir), SESSIONS_SUBDIR);
|
|
10837
10980
|
}
|
|
10838
10981
|
var REJECTION_MESSAGE = "This session was created with an older version of Ordewell. Please re-plan.";
|
|
10839
10982
|
function hasScalarRunner(parsed) {
|
|
@@ -10868,17 +11011,17 @@ function saveSession(plan, goal, baseDir, id) {
|
|
|
10868
11011
|
plan: JSON.parse(JSON.stringify(plan))
|
|
10869
11012
|
};
|
|
10870
11013
|
const filename = sessionFilename(meta);
|
|
10871
|
-
const filePath =
|
|
10872
|
-
|
|
11014
|
+
const filePath = path13.join(sessionsDir, filename);
|
|
11015
|
+
fs6.writeFileSync(filePath, JSON.stringify(session, null, 2));
|
|
10873
11016
|
return meta;
|
|
10874
11017
|
}
|
|
10875
11018
|
function listSessions(baseDir, logger = defaultLogger) {
|
|
10876
11019
|
const sessionsDir = getSessionsDir(baseDir);
|
|
10877
|
-
if (!
|
|
10878
|
-
const files =
|
|
11020
|
+
if (!fs6.existsSync(sessionsDir)) return [];
|
|
11021
|
+
const files = fs6.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
|
|
10879
11022
|
return files.map((f) => {
|
|
10880
11023
|
try {
|
|
10881
|
-
const raw =
|
|
11024
|
+
const raw = fs6.readFileSync(path13.join(sessionsDir, f), "utf-8");
|
|
10882
11025
|
const parsed = JSON.parse(raw);
|
|
10883
11026
|
if (hasScalarRunner(parsed)) {
|
|
10884
11027
|
logger.warn("sessionStore", `session file ${f}: ${REJECTION_MESSAGE}`);
|
|
@@ -10893,10 +11036,10 @@ function listSessions(baseDir, logger = defaultLogger) {
|
|
|
10893
11036
|
}).filter((m) => m !== null).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
10894
11037
|
}
|
|
10895
11038
|
function findSessionFile(sessionsDir, sessionId, logger) {
|
|
10896
|
-
const files =
|
|
11039
|
+
const files = fs6.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
|
|
10897
11040
|
for (const f of files) {
|
|
10898
11041
|
try {
|
|
10899
|
-
const raw =
|
|
11042
|
+
const raw = fs6.readFileSync(path13.join(sessionsDir, f), "utf-8");
|
|
10900
11043
|
const session = JSON.parse(raw);
|
|
10901
11044
|
if (session.meta.id === sessionId) return f;
|
|
10902
11045
|
} catch (err) {
|
|
@@ -10907,11 +11050,11 @@ function findSessionFile(sessionsDir, sessionId, logger) {
|
|
|
10907
11050
|
}
|
|
10908
11051
|
function loadSession(sessionId, baseDir, logger = defaultLogger) {
|
|
10909
11052
|
const sessionsDir = getSessionsDir(baseDir);
|
|
10910
|
-
if (!
|
|
11053
|
+
if (!fs6.existsSync(sessionsDir)) return null;
|
|
10911
11054
|
try {
|
|
10912
11055
|
const file = findSessionFile(sessionsDir, sessionId, logger);
|
|
10913
11056
|
if (file) {
|
|
10914
|
-
const raw =
|
|
11057
|
+
const raw = fs6.readFileSync(path13.join(sessionsDir, file), "utf-8");
|
|
10915
11058
|
const parsed = JSON.parse(raw);
|
|
10916
11059
|
if (hasScalarRunner(parsed)) {
|
|
10917
11060
|
logger.warn("sessionStore", `session file ${file}: ${REJECTION_MESSAGE}`);
|
|
@@ -10941,11 +11084,11 @@ function getLatestSession(baseDir, logger = defaultLogger) {
|
|
|
10941
11084
|
}
|
|
10942
11085
|
function deleteSession(sessionId, baseDir, logger = defaultLogger) {
|
|
10943
11086
|
const sessionsDir = getSessionsDir(baseDir);
|
|
10944
|
-
if (!
|
|
11087
|
+
if (!fs6.existsSync(sessionsDir)) return false;
|
|
10945
11088
|
try {
|
|
10946
11089
|
const file = findSessionFile(sessionsDir, sessionId, logger);
|
|
10947
11090
|
if (file) {
|
|
10948
|
-
|
|
11091
|
+
fs6.unlinkSync(path13.join(sessionsDir, file));
|
|
10949
11092
|
return true;
|
|
10950
11093
|
}
|
|
10951
11094
|
} catch (err) {
|
|
@@ -10954,6 +11097,29 @@ function deleteSession(sessionId, baseDir, logger = defaultLogger) {
|
|
|
10954
11097
|
return false;
|
|
10955
11098
|
}
|
|
10956
11099
|
|
|
11100
|
+
// src/utils/prdStore.ts
|
|
11101
|
+
import * as fs7 from "fs";
|
|
11102
|
+
import * as path14 from "path";
|
|
11103
|
+
var PRD_BLOCK_RE = /<!--\s*ORDEWELL_PRD_START\s+slug="([^"]+)"\s*-->\s*([\s\S]*?)\s*<!--\s*ORDEWELL_PRD_END\s*-->/;
|
|
11104
|
+
function extractPrdBlock(text) {
|
|
11105
|
+
const match = PRD_BLOCK_RE.exec(text);
|
|
11106
|
+
if (!match) return null;
|
|
11107
|
+
const slug2 = sanitizeSlug(match[1]);
|
|
11108
|
+
const markdown = match[2].trim();
|
|
11109
|
+
if (!slug2 || !markdown) return null;
|
|
11110
|
+
return { slug: slug2, markdown };
|
|
11111
|
+
}
|
|
11112
|
+
function sanitizeSlug(raw) {
|
|
11113
|
+
return raw.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
11114
|
+
}
|
|
11115
|
+
function savePrdMarkdown(workspace, slug2, markdown) {
|
|
11116
|
+
const dir = path14.join(workspace, ".scratch", sanitizeSlug(slug2));
|
|
11117
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
11118
|
+
const file = path14.join(dir, "PRD.md");
|
|
11119
|
+
fs7.writeFileSync(file, markdown.endsWith("\n") ? markdown : markdown + "\n", "utf8");
|
|
11120
|
+
return file;
|
|
11121
|
+
}
|
|
11122
|
+
|
|
10957
11123
|
// src/services/createSession.ts
|
|
10958
11124
|
var CATALOG_MODEL_CAP = 100;
|
|
10959
11125
|
var MAX_TASK_QUERIES = 3;
|
|
@@ -10970,6 +11136,17 @@ var PlanEditError = class extends Error {
|
|
|
10970
11136
|
function sessionRuntimeSettings(settings) {
|
|
10971
11137
|
return { ...plannerRuntimeToggles(settings), modelAllowlist: settings.modelAllowlist };
|
|
10972
11138
|
}
|
|
11139
|
+
function resolveSkillInvocation(text, skillsService) {
|
|
11140
|
+
const match = text.trim().match(/^\/([a-z][a-z0-9_-]*)$/im);
|
|
11141
|
+
if (!match) return text;
|
|
11142
|
+
const skillName = match[1].toLowerCase();
|
|
11143
|
+
const skill = skillsService.findSkill(skillName);
|
|
11144
|
+
if (!skill) {
|
|
11145
|
+
const available = typeof skillsService.listSkills === "function" ? skillsService.listSkills().map((s) => s.name).join(", ") : "";
|
|
11146
|
+
return `Unknown skill: ${skillName}. Available skills: ${available}`;
|
|
11147
|
+
}
|
|
11148
|
+
return skill.content;
|
|
11149
|
+
}
|
|
10973
11150
|
var Session = class {
|
|
10974
11151
|
/** Injected by a test; when present it is the service, forever. */
|
|
10975
11152
|
pinnedAiService;
|
|
@@ -10997,6 +11174,7 @@ var Session = class {
|
|
|
10997
11174
|
unsubObserver = null;
|
|
10998
11175
|
hostSessionId;
|
|
10999
11176
|
currentSessionId;
|
|
11177
|
+
skillsService;
|
|
11000
11178
|
constructor(deps) {
|
|
11001
11179
|
this.config = deps.config;
|
|
11002
11180
|
this.registry = deps.registry;
|
|
@@ -11015,6 +11193,7 @@ var Session = class {
|
|
|
11015
11193
|
this.settingsFn = deps.settings;
|
|
11016
11194
|
this.hostSessionId = deps.sessionId;
|
|
11017
11195
|
this.currentSessionId = deps.sessionId ?? mintSessionId();
|
|
11196
|
+
this.skillsService = deps.skillsService ?? new SkillsService(this.workspaceRootFn());
|
|
11018
11197
|
this.approvals = new PendingApprovals({
|
|
11019
11198
|
onRequest: ({ id, request }) => this.broadcast({
|
|
11020
11199
|
type: "approval_request",
|
|
@@ -11109,7 +11288,7 @@ var Session = class {
|
|
|
11109
11288
|
onTaskChanged: () => {
|
|
11110
11289
|
if (!this.plan) return;
|
|
11111
11290
|
const tasks = this.store.allTasks;
|
|
11112
|
-
this.broadcast({ type: "status_update", tasks: tasks.map(serializeTaskStatus) });
|
|
11291
|
+
this.broadcast({ type: "status_update", tasks: tasks.map((t) => serializeTaskStatus(t, this.orchestrator.getIdleSince(t.id))) });
|
|
11113
11292
|
},
|
|
11114
11293
|
onQueueReady: () => {
|
|
11115
11294
|
this.broadcast({ type: "queue_ready" });
|
|
@@ -11127,7 +11306,7 @@ var Session = class {
|
|
|
11127
11306
|
onTick: () => {
|
|
11128
11307
|
if (!this.plan) return;
|
|
11129
11308
|
const tasks = this.store.allTasks;
|
|
11130
|
-
this.broadcast({ type: "status_update", tasks: tasks.map(serializeTaskStatus) });
|
|
11309
|
+
this.broadcast({ type: "status_update", tasks: tasks.map((t) => serializeTaskStatus(t, this.orchestrator.getIdleSince(t.id))) });
|
|
11131
11310
|
},
|
|
11132
11311
|
onExecutionComplete: () => {
|
|
11133
11312
|
if (!this.plan) return;
|
|
@@ -11339,7 +11518,7 @@ var Session = class {
|
|
|
11339
11518
|
*/
|
|
11340
11519
|
async startPlanning(goal, runners, options) {
|
|
11341
11520
|
this.plan = null;
|
|
11342
|
-
this.goal = goal;
|
|
11521
|
+
this.goal = this.resolveSkillInvocation(goal);
|
|
11343
11522
|
this.remintSessionId();
|
|
11344
11523
|
this.beginFreshPlan();
|
|
11345
11524
|
const enabled = this.config.enabledRunners;
|
|
@@ -11348,7 +11527,7 @@ var Session = class {
|
|
|
11348
11527
|
const modelsByRunner = await this.modelResolver.modelsForRunners(chosenRunners);
|
|
11349
11528
|
this.modelsCache = modelsByRunner;
|
|
11350
11529
|
const runnerModes = this.runnerModesFor(chosenRunners);
|
|
11351
|
-
const {
|
|
11530
|
+
const { verificationEnabled, modelAllowlist } = this.settingsFn();
|
|
11352
11531
|
this.currentAllowlist = modelAllowlist ?? {};
|
|
11353
11532
|
const filteredModels = filterModelsForPrompt(modelsByRunner, this.currentAllowlist);
|
|
11354
11533
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -11358,22 +11537,18 @@ var Session = class {
|
|
|
11358
11537
|
status: "draft",
|
|
11359
11538
|
runners: chosenRunners,
|
|
11360
11539
|
lastUpdated: now,
|
|
11361
|
-
researchLog: [{ id: `up-${Date.now()}`, type: "user_prompt", content: goal, timestamp: now }],
|
|
11362
|
-
conversationHistory: [{ role: "user", content: goal, timestamp: now }]
|
|
11540
|
+
researchLog: [{ id: `up-${Date.now()}`, type: "user_prompt", content: this.goal, timestamp: now }],
|
|
11541
|
+
conversationHistory: [{ role: "user", content: this.goal, timestamp: now }]
|
|
11363
11542
|
};
|
|
11364
11543
|
const releaseAbort = this.denyApprovalsOnAbort(options?.signal);
|
|
11365
11544
|
try {
|
|
11366
11545
|
const turn = await this.aiService.startConversation({
|
|
11367
|
-
goal,
|
|
11546
|
+
goal: this.goal,
|
|
11368
11547
|
runners: chosenRunners,
|
|
11369
11548
|
modelsByRunner: filteredModels,
|
|
11370
11549
|
runnerModes,
|
|
11371
11550
|
autonomousDefault: this.config.autonomousMode,
|
|
11372
|
-
grillMeEnabled: grillMeEnabled ?? false,
|
|
11373
|
-
prdEnabled: prdEnabled ?? false,
|
|
11374
|
-
reviewEnabled: reviewEnabled ?? false,
|
|
11375
11551
|
verificationEnabled: verificationEnabled ?? false,
|
|
11376
|
-
researchSubagentsEnabled: researchSubagentsEnabled ?? false,
|
|
11377
11552
|
fs: this.fsAdapter,
|
|
11378
11553
|
fetcher: this.fetcher,
|
|
11379
11554
|
onProgress: (p) => this.translateProgress(p),
|
|
@@ -11385,21 +11560,22 @@ var Session = class {
|
|
|
11385
11560
|
}
|
|
11386
11561
|
}
|
|
11387
11562
|
/**
|
|
11388
|
-
* Every subsequent user reply in the planning conversation —
|
|
11389
|
-
* answers,
|
|
11563
|
+
* Every subsequent user reply in the planning conversation — clarifying
|
|
11564
|
+
* answers, outline confirm. One branch, no phase ladder.
|
|
11390
11565
|
*/
|
|
11391
11566
|
async continueConversation(userMessage, options) {
|
|
11392
11567
|
if (!this.plan) throw new Error("No planning conversation to continue");
|
|
11393
11568
|
const priorHistory = this.plan.conversationHistory ?? [];
|
|
11394
11569
|
const priorResearchLog = this.plan.researchLog ?? [];
|
|
11395
11570
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11396
|
-
this.
|
|
11571
|
+
const resolved = this.resolveSkillInvocation(userMessage);
|
|
11572
|
+
this.appendTranscript("user", resolved, now);
|
|
11397
11573
|
const historyAfterOwnAppend = this.plan.conversationHistory;
|
|
11398
|
-
this.plan.researchLog = [...this.plan.researchLog ?? [], { id: `up-${Date.now()}`, type: "user_prompt", content:
|
|
11574
|
+
this.plan.researchLog = [...this.plan.researchLog ?? [], { id: `up-${Date.now()}`, type: "user_prompt", content: resolved, timestamp: now }];
|
|
11399
11575
|
const contextBlock = [this.catalogBlock(), this.planContextBlock()].filter(Boolean).join("\n\n");
|
|
11400
11576
|
const outgoing = contextBlock ? `${contextBlock}
|
|
11401
11577
|
|
|
11402
|
-
${
|
|
11578
|
+
${resolved}` : resolved;
|
|
11403
11579
|
const aiService = this.aiService;
|
|
11404
11580
|
const canContinueLive = aiService.hasActiveConversation() && (aiService.conversationMatchesConfig?.() ?? true);
|
|
11405
11581
|
const releaseAbort = this.denyApprovalsOnAbort(options?.signal);
|
|
@@ -11539,8 +11715,8 @@ The plan is unchanged. Rephrase the request, or adjust the tasks manually.`,
|
|
|
11539
11715
|
* The catalog the planner may actually draw from — model ids and task-mode
|
|
11540
11716
|
* ids, per selected runner — emitted on EVERY turn, before any plan exists
|
|
11541
11717
|
* or after. The system prompt shows this once at conversation start; a long
|
|
11542
|
-
*
|
|
11543
|
-
*
|
|
11718
|
+
* clarifying conversation outlives that single showing and the planner
|
|
11719
|
+
* starts misquoting it, so this re-states it per turn instead.
|
|
11544
11720
|
*
|
|
11545
11721
|
* Built from the allowlist-filtered view (`filterModelsForPrompt`), the same
|
|
11546
11722
|
* one the system prompt used — a restricted allowlist stays a hard bound on
|
|
@@ -11671,7 +11847,7 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
|
|
|
11671
11847
|
const modelsByRunner = await this.modelResolver.modelsForRunners(runners);
|
|
11672
11848
|
this.modelsCache = modelsByRunner;
|
|
11673
11849
|
const runnerModes = this.runnerModesFor(runners);
|
|
11674
|
-
const {
|
|
11850
|
+
const { verificationEnabled, modelAllowlist } = this.settingsFn();
|
|
11675
11851
|
this.currentAllowlist = modelAllowlist ?? {};
|
|
11676
11852
|
const filteredModels = filterModelsForPrompt(modelsByRunner, this.currentAllowlist);
|
|
11677
11853
|
const goal = this.goal || priorHistory.find((m) => m.role === "user")?.content || userMessage;
|
|
@@ -11681,11 +11857,7 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
|
|
|
11681
11857
|
modelsByRunner: filteredModels,
|
|
11682
11858
|
runnerModes,
|
|
11683
11859
|
autonomousDefault: this.config.autonomousMode,
|
|
11684
|
-
grillMeEnabled: grillMeEnabled ?? false,
|
|
11685
|
-
prdEnabled: prdEnabled ?? false,
|
|
11686
|
-
reviewEnabled: reviewEnabled ?? false,
|
|
11687
11860
|
verificationEnabled: verificationEnabled ?? false,
|
|
11688
|
-
researchSubagentsEnabled: researchSubagentsEnabled ?? false,
|
|
11689
11861
|
fs: this.fsAdapter,
|
|
11690
11862
|
fetcher: this.fetcher,
|
|
11691
11863
|
onProgress: (p) => this.translateProgress(p),
|
|
@@ -11698,6 +11870,17 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
|
|
|
11698
11870
|
get isConversationActive() {
|
|
11699
11871
|
return this.aiService.hasActiveConversation();
|
|
11700
11872
|
}
|
|
11873
|
+
/**
|
|
11874
|
+
* Intercept a skill invocation (/skill-name) and substitute the skill's
|
|
11875
|
+
* markdown content BEFORE the message reaches the planner. Prevents runners
|
|
11876
|
+
* (Claude Code, OpenCode) from trying to resolve the skill in their own
|
|
11877
|
+
* directory instead of .ordewell/skills/. An unknown skill is surfaced to
|
|
11878
|
+
* the planner as a notice naming what IS available, rather than passing a
|
|
11879
|
+
* bare /unknown through to be mis-resolved.
|
|
11880
|
+
*/
|
|
11881
|
+
resolveSkillInvocation(text) {
|
|
11882
|
+
return resolveSkillInvocation(text, this.skillsService);
|
|
11883
|
+
}
|
|
11701
11884
|
/** Append one dialogue entry to the persisted transcript. Callers persist via the mutatePlan ritual. */
|
|
11702
11885
|
appendTranscript(role, content, timestamp, kind) {
|
|
11703
11886
|
if (!this.plan) return;
|
|
@@ -12553,8 +12736,8 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
12553
12736
|
import { execFile as execFile2 } from "child_process";
|
|
12554
12737
|
import { promisify as promisify4 } from "util";
|
|
12555
12738
|
import { tmpdir } from "os";
|
|
12556
|
-
import { join as
|
|
12557
|
-
import { existsSync as
|
|
12739
|
+
import { join as join13 } from "path";
|
|
12740
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
12558
12741
|
|
|
12559
12742
|
// src/utils/tmux.ts
|
|
12560
12743
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -12659,7 +12842,7 @@ var TmuxSession = class extends AbstractTerminalSession {
|
|
|
12659
12842
|
if (this.exited) return;
|
|
12660
12843
|
let content;
|
|
12661
12844
|
try {
|
|
12662
|
-
content =
|
|
12845
|
+
content = existsSync8(this.logPath) ? readFileSync5(this.logPath, "utf8") : "";
|
|
12663
12846
|
} catch {
|
|
12664
12847
|
return;
|
|
12665
12848
|
}
|
|
@@ -12838,7 +13021,7 @@ var TmuxRunner = class extends AbstractRunner {
|
|
|
12838
13021
|
windowName,
|
|
12839
13022
|
this.execFileImpl,
|
|
12840
13023
|
this.pollIntervalMs,
|
|
12841
|
-
|
|
13024
|
+
join13(this.logDir, `${id}.log`),
|
|
12842
13025
|
this.socket
|
|
12843
13026
|
);
|
|
12844
13027
|
invocation.env = { ...invocation.env, PATH: await this.resolvePath() };
|
|
@@ -12850,22 +13033,21 @@ var TmuxRunner = class extends AbstractRunner {
|
|
|
12850
13033
|
|
|
12851
13034
|
// src/utils/daemonToken.ts
|
|
12852
13035
|
import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
12853
|
-
import { closeSync, mkdirSync as
|
|
12854
|
-
import {
|
|
12855
|
-
import { join as join11 } from "path";
|
|
13036
|
+
import { closeSync, mkdirSync as mkdirSync7, openSync, readFileSync as readFileSync6, unlinkSync as unlinkSync3, writeSync } from "fs";
|
|
13037
|
+
import { join as join14 } from "path";
|
|
12856
13038
|
var BEARER_PREFIX = "bearer ";
|
|
12857
13039
|
var DAEMON_TOKEN_SUBPROTOCOL_PREFIX = "ordewell.token.";
|
|
12858
13040
|
var DAEMON_SUBPROTOCOL = "ordewell.v1";
|
|
12859
13041
|
function configDir() {
|
|
12860
|
-
return
|
|
13042
|
+
return globalDataDir();
|
|
12861
13043
|
}
|
|
12862
13044
|
function daemonTokenPath(port) {
|
|
12863
|
-
return
|
|
13045
|
+
return join14(configDir(), `server-${port}.token`);
|
|
12864
13046
|
}
|
|
12865
13047
|
function mintDaemonToken(port) {
|
|
12866
13048
|
const token = randomBytes2(32).toString("base64url");
|
|
12867
13049
|
const file = daemonTokenPath(port);
|
|
12868
|
-
|
|
13050
|
+
mkdirSync7(configDir(), { recursive: true });
|
|
12869
13051
|
try {
|
|
12870
13052
|
unlinkSync3(file);
|
|
12871
13053
|
} catch {
|
|
@@ -12880,7 +13062,7 @@ function mintDaemonToken(port) {
|
|
|
12880
13062
|
}
|
|
12881
13063
|
function readDaemonToken(port) {
|
|
12882
13064
|
try {
|
|
12883
|
-
const token =
|
|
13065
|
+
const token = readFileSync6(daemonTokenPath(port), "utf8").trim();
|
|
12884
13066
|
return token === "" ? void 0 : token;
|
|
12885
13067
|
} catch {
|
|
12886
13068
|
return void 0;
|
|
@@ -12921,19 +13103,18 @@ function tokensMatch(presented, expected) {
|
|
|
12921
13103
|
}
|
|
12922
13104
|
|
|
12923
13105
|
// src/plugins/RunnerRegistry.ts
|
|
12924
|
-
import * as
|
|
12925
|
-
import * as
|
|
12926
|
-
import * as
|
|
13106
|
+
import * as fs9 from "fs";
|
|
13107
|
+
import * as path17 from "path";
|
|
13108
|
+
import * as os4 from "os";
|
|
12927
13109
|
import { execFileSync } from "child_process";
|
|
12928
13110
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
12929
13111
|
|
|
12930
13112
|
// src/plugins/FsPluginStore.ts
|
|
12931
|
-
import * as
|
|
12932
|
-
import * as
|
|
12933
|
-
import * as os4 from "os";
|
|
13113
|
+
import * as fs8 from "fs";
|
|
13114
|
+
import * as path16 from "path";
|
|
12934
13115
|
|
|
12935
13116
|
// src/plugins/pluginNames.ts
|
|
12936
|
-
import * as
|
|
13117
|
+
import * as path15 from "path";
|
|
12937
13118
|
var PLUGIN_NAME_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
12938
13119
|
var MAX_PLUGIN_NAME_LENGTH = 64;
|
|
12939
13120
|
function isPlainPluginName(name) {
|
|
@@ -12948,10 +13129,10 @@ function assertPlainPluginName(name) {
|
|
|
12948
13129
|
}
|
|
12949
13130
|
function resolvePluginInstallDir(pluginsDir, name) {
|
|
12950
13131
|
assertPlainPluginName(name);
|
|
12951
|
-
const root =
|
|
12952
|
-
const dest =
|
|
12953
|
-
const relative3 =
|
|
12954
|
-
if (relative3 !== name || relative3 === "" ||
|
|
13132
|
+
const root = path15.resolve(pluginsDir);
|
|
13133
|
+
const dest = path15.resolve(root, name);
|
|
13134
|
+
const relative3 = path15.relative(root, dest);
|
|
13135
|
+
if (relative3 !== name || relative3 === "" || path15.isAbsolute(relative3)) {
|
|
12955
13136
|
throw new Error(`Plugin install path escapes the plugins directory: ${JSON.stringify(name)}`);
|
|
12956
13137
|
}
|
|
12957
13138
|
return dest;
|
|
@@ -12966,13 +13147,13 @@ function isValidManifest(obj) {
|
|
|
12966
13147
|
|
|
12967
13148
|
// src/plugins/FsPluginStore.ts
|
|
12968
13149
|
function userPluginsDir() {
|
|
12969
|
-
return
|
|
13150
|
+
return path16.join(globalDataDir(), "plugins");
|
|
12970
13151
|
}
|
|
12971
13152
|
function loadManifestFromDir(dir) {
|
|
12972
|
-
const manifestPath =
|
|
12973
|
-
if (!
|
|
13153
|
+
const manifestPath = path16.join(dir, "manifest.json");
|
|
13154
|
+
if (!fs8.existsSync(manifestPath)) return null;
|
|
12974
13155
|
try {
|
|
12975
|
-
const raw =
|
|
13156
|
+
const raw = fs8.readFileSync(manifestPath, "utf8");
|
|
12976
13157
|
const parsed = JSON.parse(raw);
|
|
12977
13158
|
if (!isValidManifest(parsed)) return null;
|
|
12978
13159
|
return parsed;
|
|
@@ -12980,31 +13161,31 @@ function loadManifestFromDir(dir) {
|
|
|
12980
13161
|
return null;
|
|
12981
13162
|
}
|
|
12982
13163
|
}
|
|
12983
|
-
function
|
|
13164
|
+
function copyDirSync3(src, dest) {
|
|
12984
13165
|
ensureDir(dest);
|
|
12985
|
-
const entries =
|
|
13166
|
+
const entries = fs8.readdirSync(src, { withFileTypes: true });
|
|
12986
13167
|
for (const entry of entries) {
|
|
12987
|
-
const srcPath =
|
|
12988
|
-
const destPath =
|
|
13168
|
+
const srcPath = path16.join(src, entry.name);
|
|
13169
|
+
const destPath = path16.join(dest, entry.name);
|
|
12989
13170
|
if (entry.isDirectory()) {
|
|
12990
|
-
|
|
13171
|
+
copyDirSync3(srcPath, destPath);
|
|
12991
13172
|
} else {
|
|
12992
|
-
|
|
13173
|
+
fs8.copyFileSync(srcPath, destPath);
|
|
12993
13174
|
}
|
|
12994
13175
|
}
|
|
12995
13176
|
}
|
|
12996
13177
|
function rmDirSync(dir) {
|
|
12997
|
-
if (!
|
|
12998
|
-
const entries =
|
|
13178
|
+
if (!fs8.existsSync(dir)) return;
|
|
13179
|
+
const entries = fs8.readdirSync(dir, { withFileTypes: true });
|
|
12999
13180
|
for (const entry of entries) {
|
|
13000
|
-
const p =
|
|
13181
|
+
const p = path16.join(dir, entry.name);
|
|
13001
13182
|
if (entry.isDirectory()) {
|
|
13002
13183
|
rmDirSync(p);
|
|
13003
13184
|
} else {
|
|
13004
|
-
|
|
13185
|
+
fs8.unlinkSync(p);
|
|
13005
13186
|
}
|
|
13006
13187
|
}
|
|
13007
|
-
|
|
13188
|
+
fs8.rmdirSync(dir);
|
|
13008
13189
|
}
|
|
13009
13190
|
var FsPluginStore = class {
|
|
13010
13191
|
getUserPluginsDir() {
|
|
@@ -13012,10 +13193,10 @@ var FsPluginStore = class {
|
|
|
13012
13193
|
}
|
|
13013
13194
|
listUserPluginDirs() {
|
|
13014
13195
|
const dir = userPluginsDir();
|
|
13015
|
-
if (!
|
|
13196
|
+
if (!fs8.existsSync(dir)) return [];
|
|
13016
13197
|
let entries;
|
|
13017
13198
|
try {
|
|
13018
|
-
entries =
|
|
13199
|
+
entries = fs8.readdirSync(dir, { withFileTypes: true });
|
|
13019
13200
|
} catch {
|
|
13020
13201
|
return [];
|
|
13021
13202
|
}
|
|
@@ -13023,7 +13204,7 @@ var FsPluginStore = class {
|
|
|
13023
13204
|
}
|
|
13024
13205
|
listDir(dir) {
|
|
13025
13206
|
try {
|
|
13026
|
-
return
|
|
13207
|
+
return fs8.readdirSync(dir);
|
|
13027
13208
|
} catch {
|
|
13028
13209
|
return [];
|
|
13029
13210
|
}
|
|
@@ -13032,7 +13213,7 @@ var FsPluginStore = class {
|
|
|
13032
13213
|
return loadManifestFromDir(pluginDir);
|
|
13033
13214
|
}
|
|
13034
13215
|
copyDir(sourceDir, destDir) {
|
|
13035
|
-
|
|
13216
|
+
copyDirSync3(sourceDir, destDir);
|
|
13036
13217
|
}
|
|
13037
13218
|
removeDir(dir) {
|
|
13038
13219
|
rmDirSync(dir);
|
|
@@ -13041,20 +13222,20 @@ var FsPluginStore = class {
|
|
|
13041
13222
|
ensureDir(dir);
|
|
13042
13223
|
}
|
|
13043
13224
|
writeFile(filePath, content) {
|
|
13044
|
-
|
|
13225
|
+
fs8.writeFileSync(filePath, content);
|
|
13045
13226
|
}
|
|
13046
13227
|
readFile(filePath) {
|
|
13047
13228
|
try {
|
|
13048
|
-
return
|
|
13229
|
+
return fs8.readFileSync(filePath, "utf8");
|
|
13049
13230
|
} catch {
|
|
13050
13231
|
return null;
|
|
13051
13232
|
}
|
|
13052
13233
|
}
|
|
13053
|
-
dirExists(
|
|
13054
|
-
return
|
|
13234
|
+
dirExists(path20) {
|
|
13235
|
+
return fs8.existsSync(path20) && fs8.statSync(path20).isDirectory();
|
|
13055
13236
|
}
|
|
13056
|
-
exists(
|
|
13057
|
-
return
|
|
13237
|
+
exists(path20) {
|
|
13238
|
+
return fs8.existsSync(path20);
|
|
13058
13239
|
}
|
|
13059
13240
|
};
|
|
13060
13241
|
|
|
@@ -13481,7 +13662,7 @@ var RunnerRegistry = class {
|
|
|
13481
13662
|
return entry?.source === "builtin";
|
|
13482
13663
|
}
|
|
13483
13664
|
installFromPath(sourcePath) {
|
|
13484
|
-
const absPath =
|
|
13665
|
+
const absPath = path17.resolve(sourcePath);
|
|
13485
13666
|
if (!this.store.dirExists(absPath)) {
|
|
13486
13667
|
throw new Error(`Plugin source must be a directory: ${absPath}`);
|
|
13487
13668
|
}
|
|
@@ -13493,7 +13674,7 @@ var RunnerRegistry = class {
|
|
|
13493
13674
|
}
|
|
13494
13675
|
installFromGit(url) {
|
|
13495
13676
|
const validated = assertInstallablePluginUrl(url);
|
|
13496
|
-
const tmpDir =
|
|
13677
|
+
const tmpDir = path17.join(os4.tmpdir(), `ordewell-plugin-${randomBytes3(12).toString("hex")}`);
|
|
13497
13678
|
this.store.ensureDir(tmpDir);
|
|
13498
13679
|
try {
|
|
13499
13680
|
try {
|
|
@@ -13506,7 +13687,7 @@ var RunnerRegistry = class {
|
|
|
13506
13687
|
const hasManifest = contents.some((f) => f === "manifest.json");
|
|
13507
13688
|
let sourceDir = tmpDir;
|
|
13508
13689
|
if (!hasManifest) {
|
|
13509
|
-
const onlyEntry = contents.length === 1 ?
|
|
13690
|
+
const onlyEntry = contents.length === 1 ? path17.join(tmpDir, contents[0]) : void 0;
|
|
13510
13691
|
if (!onlyEntry || !this.store.dirExists(onlyEntry)) {
|
|
13511
13692
|
throw new Error(`No manifest.json found in repository: ${validated.toString()}`);
|
|
13512
13693
|
}
|
|
@@ -13554,7 +13735,7 @@ var RunnerRegistry = class {
|
|
|
13554
13735
|
}
|
|
13555
13736
|
createSkeleton(name, outputDir) {
|
|
13556
13737
|
assertPlainPluginName(name);
|
|
13557
|
-
const dir =
|
|
13738
|
+
const dir = path17.resolve(outputDir, name);
|
|
13558
13739
|
this.store.ensureDir(dir);
|
|
13559
13740
|
const manifest = {
|
|
13560
13741
|
name,
|
|
@@ -13586,8 +13767,8 @@ var RunnerRegistry = class {
|
|
|
13586
13767
|
]
|
|
13587
13768
|
}
|
|
13588
13769
|
};
|
|
13589
|
-
this.store.writeFile(
|
|
13590
|
-
const runSh =
|
|
13770
|
+
this.store.writeFile(path17.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
13771
|
+
const runSh = path17.join(dir, "run.sh");
|
|
13591
13772
|
const scriptContent = `#!/usr/bin/env bash
|
|
13592
13773
|
# Custom runner script for ${name}
|
|
13593
13774
|
# Receives:
|
|
@@ -13613,30 +13794,30 @@ echo "Running ${name} with prompt: $prompt"
|
|
|
13613
13794
|
};
|
|
13614
13795
|
function fsChmodSync(fpath, mode) {
|
|
13615
13796
|
try {
|
|
13616
|
-
|
|
13797
|
+
fs9.chmodSync(fpath, mode);
|
|
13617
13798
|
} catch {
|
|
13618
13799
|
}
|
|
13619
13800
|
}
|
|
13620
13801
|
|
|
13621
13802
|
// src/utils/stateStore.ts
|
|
13622
|
-
import * as
|
|
13623
|
-
import * as
|
|
13803
|
+
import * as fs10 from "fs";
|
|
13804
|
+
import * as path18 from "path";
|
|
13624
13805
|
var STATE_FILE = "state.json";
|
|
13625
13806
|
function getStatePath(baseDir) {
|
|
13626
|
-
return
|
|
13807
|
+
return path18.join(getStateDir(baseDir), STATE_FILE);
|
|
13627
13808
|
}
|
|
13628
13809
|
function saveState(plan, baseDir) {
|
|
13629
13810
|
const statePath = getStatePath(baseDir ?? process.cwd());
|
|
13630
|
-
ensureDir(
|
|
13811
|
+
ensureDir(path18.dirname(statePath));
|
|
13631
13812
|
plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
13632
|
-
|
|
13813
|
+
fs10.writeFileSync(statePath, JSON.stringify(plan, null, 2));
|
|
13633
13814
|
}
|
|
13634
13815
|
function loadState(baseDir, logger = defaultLogger) {
|
|
13635
13816
|
const dir = baseDir ?? process.cwd();
|
|
13636
13817
|
const statePath = getStatePath(dir);
|
|
13637
|
-
if (!
|
|
13818
|
+
if (!fs10.existsSync(statePath)) return null;
|
|
13638
13819
|
try {
|
|
13639
|
-
const raw =
|
|
13820
|
+
const raw = fs10.readFileSync(statePath, "utf-8");
|
|
13640
13821
|
const parsed = JSON.parse(raw);
|
|
13641
13822
|
if (!Array.isArray(parsed.runners)) {
|
|
13642
13823
|
logger.warn("stateStore", `rejecting state from ${statePath}: old format (scalar "runner" instead of "runners" array). Please re-plan.`);
|
|
@@ -13651,29 +13832,24 @@ function loadState(baseDir, logger = defaultLogger) {
|
|
|
13651
13832
|
function clearState(baseDir) {
|
|
13652
13833
|
const dir = baseDir ?? process.cwd();
|
|
13653
13834
|
const statePath = getStatePath(dir);
|
|
13654
|
-
if (
|
|
13835
|
+
if (fs10.existsSync(statePath)) fs10.unlinkSync(statePath);
|
|
13655
13836
|
}
|
|
13656
13837
|
function stateExists(baseDir) {
|
|
13657
13838
|
const dir = baseDir ?? process.cwd();
|
|
13658
|
-
return
|
|
13839
|
+
return fs10.existsSync(getStatePath(dir));
|
|
13659
13840
|
}
|
|
13660
13841
|
|
|
13661
13842
|
// src/services/SettingsService.ts
|
|
13662
|
-
import * as
|
|
13663
|
-
import * as
|
|
13664
|
-
import * as os6 from "os";
|
|
13843
|
+
import * as fs11 from "fs";
|
|
13844
|
+
import * as path19 from "path";
|
|
13665
13845
|
var DEFAULTS = {
|
|
13666
|
-
grillMe: { enabled: false },
|
|
13667
13846
|
tdd: { enabled: true },
|
|
13668
|
-
|
|
13669
|
-
review: { enabled: false },
|
|
13670
|
-
verification: { enabled: false },
|
|
13671
|
-
researchSubagents: { enabled: false }
|
|
13847
|
+
verification: { enabled: false }
|
|
13672
13848
|
};
|
|
13673
13849
|
function getSettingsPath() {
|
|
13674
13850
|
const override = process.env.ORDEWELL_SETTINGS_PATH;
|
|
13675
13851
|
if (override && override.trim()) return override.trim();
|
|
13676
|
-
return
|
|
13852
|
+
return path19.join(globalDataDir(), "settings.json");
|
|
13677
13853
|
}
|
|
13678
13854
|
function readPlannerModels(raw) {
|
|
13679
13855
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
@@ -13706,43 +13882,19 @@ var SettingsService = class {
|
|
|
13706
13882
|
}
|
|
13707
13883
|
fileMtimeMs() {
|
|
13708
13884
|
try {
|
|
13709
|
-
return
|
|
13885
|
+
return fs11.statSync(this.filePath).mtimeMs;
|
|
13710
13886
|
} catch {
|
|
13711
13887
|
return null;
|
|
13712
13888
|
}
|
|
13713
13889
|
}
|
|
13714
|
-
getGrillMe() {
|
|
13715
|
-
return this.getAll().grillMe.enabled;
|
|
13716
|
-
}
|
|
13717
13890
|
getTdd() {
|
|
13718
13891
|
return this.getAll().tdd.enabled;
|
|
13719
13892
|
}
|
|
13720
|
-
getPrd() {
|
|
13721
|
-
return this.getAll().prd.enabled;
|
|
13722
|
-
}
|
|
13723
|
-
setGrillMe(enabled) {
|
|
13724
|
-
this.getAll();
|
|
13725
|
-
this.cache.grillMe.enabled = enabled;
|
|
13726
|
-
this.persist();
|
|
13727
|
-
}
|
|
13728
13893
|
setTdd(enabled) {
|
|
13729
13894
|
this.getAll();
|
|
13730
13895
|
this.cache.tdd.enabled = enabled;
|
|
13731
13896
|
this.persist();
|
|
13732
13897
|
}
|
|
13733
|
-
setPrd(enabled) {
|
|
13734
|
-
this.getAll();
|
|
13735
|
-
this.cache.prd.enabled = enabled;
|
|
13736
|
-
this.persist();
|
|
13737
|
-
}
|
|
13738
|
-
getReview() {
|
|
13739
|
-
return this.getAll().review.enabled;
|
|
13740
|
-
}
|
|
13741
|
-
setReview(enabled) {
|
|
13742
|
-
this.getAll();
|
|
13743
|
-
this.cache.review.enabled = enabled;
|
|
13744
|
-
this.persist();
|
|
13745
|
-
}
|
|
13746
13898
|
getVerification() {
|
|
13747
13899
|
return this.getAll().verification.enabled;
|
|
13748
13900
|
}
|
|
@@ -13751,14 +13903,6 @@ var SettingsService = class {
|
|
|
13751
13903
|
this.cache.verification.enabled = enabled;
|
|
13752
13904
|
this.persist();
|
|
13753
13905
|
}
|
|
13754
|
-
getResearchSubagents() {
|
|
13755
|
-
return this.getAll().researchSubagents.enabled;
|
|
13756
|
-
}
|
|
13757
|
-
setResearchSubagents(enabled) {
|
|
13758
|
-
this.getAll();
|
|
13759
|
-
this.cache.researchSubagents.enabled = enabled;
|
|
13760
|
-
this.persist();
|
|
13761
|
-
}
|
|
13762
13906
|
getModelAllowlist(runner) {
|
|
13763
13907
|
return this.getAll().modelAllowlist?.[runner];
|
|
13764
13908
|
}
|
|
@@ -13808,18 +13952,13 @@ var SettingsService = class {
|
|
|
13808
13952
|
this.persist();
|
|
13809
13953
|
}
|
|
13810
13954
|
load() {
|
|
13955
|
+
migrateOldConfigDir();
|
|
13811
13956
|
try {
|
|
13812
|
-
if (
|
|
13813
|
-
const raw = JSON.parse(
|
|
13957
|
+
if (fs11.existsSync(this.filePath)) {
|
|
13958
|
+
const raw = JSON.parse(fs11.readFileSync(this.filePath, "utf-8"));
|
|
13814
13959
|
const settings = {
|
|
13815
|
-
grillMe: { enabled: raw.grillMe?.enabled ?? DEFAULTS.grillMe.enabled },
|
|
13816
13960
|
tdd: { enabled: raw.tdd?.enabled ?? DEFAULTS.tdd.enabled },
|
|
13817
|
-
|
|
13818
|
-
// `verify` is the legacy key for review mode; keep reading it so old settings files survive the rename.
|
|
13819
|
-
// The evidence-based verification mode uses the distinct `verification` key to avoid colliding with it.
|
|
13820
|
-
review: { enabled: raw.review?.enabled ?? raw.verify?.enabled ?? DEFAULTS.review.enabled },
|
|
13821
|
-
verification: { enabled: raw.verification?.enabled ?? DEFAULTS.verification.enabled },
|
|
13822
|
-
researchSubagents: { enabled: raw.researchSubagents?.enabled ?? DEFAULTS.researchSubagents.enabled }
|
|
13961
|
+
verification: { enabled: raw.verification?.enabled ?? DEFAULTS.verification.enabled }
|
|
13823
13962
|
};
|
|
13824
13963
|
if (raw.modelAllowlist !== void 0) {
|
|
13825
13964
|
settings.modelAllowlist = raw.modelAllowlist;
|
|
@@ -13835,14 +13974,14 @@ var SettingsService = class {
|
|
|
13835
13974
|
}
|
|
13836
13975
|
} catch {
|
|
13837
13976
|
}
|
|
13838
|
-
return { ...DEFAULTS,
|
|
13977
|
+
return { ...DEFAULTS, tdd: { ...DEFAULTS.tdd }, verification: { ...DEFAULTS.verification } };
|
|
13839
13978
|
}
|
|
13840
13979
|
persist() {
|
|
13841
|
-
const dir =
|
|
13842
|
-
if (!
|
|
13843
|
-
|
|
13980
|
+
const dir = path19.dirname(this.filePath);
|
|
13981
|
+
if (!fs11.existsSync(dir)) {
|
|
13982
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
13844
13983
|
}
|
|
13845
|
-
|
|
13984
|
+
fs11.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2));
|
|
13846
13985
|
this.cachedMtimeMs = this.fileMtimeMs();
|
|
13847
13986
|
}
|
|
13848
13987
|
};
|
|
@@ -13888,6 +14027,7 @@ export {
|
|
|
13888
14027
|
AbstractRunner,
|
|
13889
14028
|
AbstractTerminalSession,
|
|
13890
14029
|
ApprovalPolicy,
|
|
14030
|
+
BUILTIN_SKILL_NAMES,
|
|
13891
14031
|
BaseAiService,
|
|
13892
14032
|
BaseConfig,
|
|
13893
14033
|
BaseFileSystem,
|
|
@@ -13946,6 +14086,7 @@ export {
|
|
|
13946
14086
|
SYMBOL_LANGUAGES,
|
|
13947
14087
|
Session,
|
|
13948
14088
|
SettingsService,
|
|
14089
|
+
SkillsService,
|
|
13949
14090
|
StdioAgentAdapter,
|
|
13950
14091
|
TASK_OPS_ENVELOPE_KEY,
|
|
13951
14092
|
TASK_QUERY_ANSWER_OR_OPS,
|
|
@@ -14011,6 +14152,7 @@ export {
|
|
|
14011
14152
|
configuredProviders,
|
|
14012
14153
|
createAiService,
|
|
14013
14154
|
createEmptyPlan,
|
|
14155
|
+
createSkillsService,
|
|
14014
14156
|
createTask,
|
|
14015
14157
|
daemonTokenPath,
|
|
14016
14158
|
defaultLogger,
|
|
@@ -14037,12 +14179,14 @@ export {
|
|
|
14037
14179
|
filterModelsForPrompt,
|
|
14038
14180
|
filteredBuildModes,
|
|
14039
14181
|
flattenTasks,
|
|
14182
|
+
flattenTasksWithParents,
|
|
14040
14183
|
formatSearchOutput,
|
|
14041
14184
|
generatePlanWithRepair,
|
|
14042
14185
|
getLatestSession,
|
|
14043
14186
|
getProviderMeta,
|
|
14044
14187
|
getSettingsPath,
|
|
14045
14188
|
getStateDir,
|
|
14189
|
+
globalDataDir,
|
|
14046
14190
|
grantScopeFor,
|
|
14047
14191
|
hasTmux,
|
|
14048
14192
|
includeGlobFor,
|
|
@@ -14062,6 +14206,7 @@ export {
|
|
|
14062
14206
|
looksLikePlanAttempt,
|
|
14063
14207
|
mapAgentTool,
|
|
14064
14208
|
migrateLegacyPlan,
|
|
14209
|
+
migrateOldConfigDir,
|
|
14065
14210
|
migratePlanState,
|
|
14066
14211
|
migrateTask,
|
|
14067
14212
|
mintDaemonToken,
|
|
@@ -14096,10 +14241,12 @@ export {
|
|
|
14096
14241
|
resolveArgs,
|
|
14097
14242
|
resolveDefaultMode,
|
|
14098
14243
|
resolveModelShortcut,
|
|
14244
|
+
resolveOrderLabel,
|
|
14099
14245
|
resolvePluginInstallDir,
|
|
14100
14246
|
resolveProvider,
|
|
14101
14247
|
resolveProviderFromPrefix,
|
|
14102
14248
|
resolveResearchShell,
|
|
14249
|
+
resolveSkillInvocation,
|
|
14103
14250
|
resolveTaskMode,
|
|
14104
14251
|
resolveWithin,
|
|
14105
14252
|
retargetTaskRunner,
|
|
@@ -14122,6 +14269,7 @@ export {
|
|
|
14122
14269
|
summarizeOutput,
|
|
14123
14270
|
summarizeToolCall,
|
|
14124
14271
|
taskOpsRejectedPrompt,
|
|
14272
|
+
taskOrderLabel,
|
|
14125
14273
|
taskQuerySignature,
|
|
14126
14274
|
textHasTaskOps,
|
|
14127
14275
|
textHasTaskQuery,
|