@ordewell/core 0.4.10 → 0.4.12
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/ApprovalPolicy-BVhGdECT.d.mts +79 -0
- package/dist/ApprovalPolicy-BVhGdECT.d.ts +79 -0
- package/dist/{ITerminalRunner-CbE6gKLI.d.ts → ITerminalRunner-D_RBDoHa.d.mts} +10 -81
- package/dist/{ITerminalRunner-1NAv8wCC.d.mts → ITerminalRunner-DqAs12sy.d.ts} +10 -81
- 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-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/{chunk-YI4ZDZEE.mjs → chunk-YTJ7EOV6.mjs} +62 -3
- package/dist/chunk-YTJ7EOV6.mjs.map +1 -0
- package/dist/index.d.mts +187 -235
- package/dist/index.d.ts +187 -235
- package/dist/index.js +643 -439
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +611 -486
- 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-DtsfHnn_.d.ts → plan-utils-BbC9KdK4.d.ts} +139 -3
- package/dist/{plan-utils-B5rVXNVI.d.mts → plan-utils-DHBIvkiT.d.mts} +139 -3
- package/dist/plan-utils.d.mts +4 -3
- package/dist/plan-utils.d.ts +4 -3
- package/dist/plan-utils.js +14 -2
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +9 -4
- package/dist/testing.d.mts +3 -2
- package/dist/testing.d.ts +3 -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 +0 -1
- /package/dist/{chunk-TUGJXD4G.mjs.map → chunk-UEJ3SZQ7.mjs.map} +0 -0
package/dist/index.mjs
CHANGED
|
@@ -13,8 +13,9 @@ import {
|
|
|
13
13
|
resolveTaskMode,
|
|
14
14
|
runnerModesFrom,
|
|
15
15
|
validatePlanModification
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-UEJ3SZQ7.mjs";
|
|
17
17
|
import {
|
|
18
|
+
CHECKPOINT_TRUNCATE_LENGTH,
|
|
18
19
|
UPDATABLE_FIELDS,
|
|
19
20
|
applyTaskOps,
|
|
20
21
|
canMergeTasks,
|
|
@@ -26,12 +27,17 @@ import {
|
|
|
26
27
|
dependencyCandidates,
|
|
27
28
|
dependentsOf,
|
|
28
29
|
effectiveAllowlist,
|
|
30
|
+
executionSummary,
|
|
29
31
|
filterModelsForPrompt,
|
|
30
32
|
parseTaskOpsJson,
|
|
33
|
+
serializePlan,
|
|
34
|
+
serializeTask,
|
|
35
|
+
serializeTaskStatus,
|
|
31
36
|
summarizeToolCall,
|
|
32
37
|
textHasTaskOps,
|
|
38
|
+
truncateCheckpointSummary,
|
|
33
39
|
validateTaskEdit
|
|
34
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-YTJ7EOV6.mjs";
|
|
35
41
|
import {
|
|
36
42
|
PLAN_ENVELOPE_KEY,
|
|
37
43
|
PlanParseError,
|
|
@@ -46,6 +52,7 @@ import {
|
|
|
46
52
|
extractObjectWithBalance,
|
|
47
53
|
extractObjectsWithKey,
|
|
48
54
|
flattenTasks,
|
|
55
|
+
flattenTasksWithParents,
|
|
49
56
|
migrateLegacyPlan,
|
|
50
57
|
migratePlanState,
|
|
51
58
|
migrateTask,
|
|
@@ -56,7 +63,11 @@ import {
|
|
|
56
63
|
updateTaskInPlan,
|
|
57
64
|
validateModifiedPlan,
|
|
58
65
|
warningsText
|
|
59
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-N6HTBUKG.mjs";
|
|
67
|
+
import {
|
|
68
|
+
resolveOrderLabel,
|
|
69
|
+
taskOrderLabel
|
|
70
|
+
} from "./chunk-HPNUFBIT.mjs";
|
|
60
71
|
|
|
61
72
|
// src/interfaces/IFileSystem.ts
|
|
62
73
|
var GREP_DEFAULT_HEAD_LIMIT = 100;
|
|
@@ -2575,7 +2586,7 @@ function redactSecrets(text) {
|
|
|
2575
2586
|
const { value, tail } = trimTrailingPunctuation(raw);
|
|
2576
2587
|
if (value.length < 8) return full;
|
|
2577
2588
|
if (looksLikeCode(value) || value.startsWith("$")) return full;
|
|
2578
|
-
if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && /\d/.test(value)) {
|
|
2589
|
+
if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && (/\d/.test(value) || value.length >= 12)) {
|
|
2579
2590
|
return `${head}${quote}${marker("credential", value.length)}${quote}${tail}`;
|
|
2580
2591
|
}
|
|
2581
2592
|
if (IDENTIFIER.test(value) || SLUG.test(value)) return full;
|
|
@@ -2614,33 +2625,33 @@ function grepOptions(args) {
|
|
|
2614
2625
|
};
|
|
2615
2626
|
}
|
|
2616
2627
|
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,
|
|
2628
|
+
async function executeTool(name, args, fs12, fetcher, signal) {
|
|
2629
|
+
const outcome = await runTool(name, args, fs12, fetcher, signal);
|
|
2619
2630
|
return { ...outcome, output: redactSecrets(outcome.output) };
|
|
2620
2631
|
}
|
|
2621
|
-
async function runTool(name, args,
|
|
2632
|
+
async function runTool(name, args, fs12, fetcher, signal) {
|
|
2622
2633
|
if (signal?.aborted) return { success: false, output: STOPPED_TOOL_RESULT, truncated: false };
|
|
2623
2634
|
switch (name) {
|
|
2624
2635
|
case "read_file": {
|
|
2625
2636
|
const offset = num(args.offset);
|
|
2626
2637
|
const limit = num(args.limit);
|
|
2627
2638
|
const maxBytes = num(args.maxBytes);
|
|
2628
|
-
return
|
|
2639
|
+
return fs12.readFile(String(args.path ?? ""), { offset, limit, maxBytes });
|
|
2629
2640
|
}
|
|
2630
2641
|
case "read_files": {
|
|
2631
2642
|
const paths = Array.isArray(args.paths) ? args.paths.map(String) : [];
|
|
2632
|
-
return
|
|
2643
|
+
return fs12.readFiles(paths);
|
|
2633
2644
|
}
|
|
2634
2645
|
case "glob":
|
|
2635
|
-
return
|
|
2646
|
+
return fs12.glob(String(args.pattern ?? ""), { path: str(args.path), headLimit: num(args.head_limit ?? args.headLimit) });
|
|
2636
2647
|
case "grep":
|
|
2637
|
-
return
|
|
2648
|
+
return fs12.grep(String(args.pattern ?? ""), grepOptions(args));
|
|
2638
2649
|
case "find_symbol":
|
|
2639
|
-
return
|
|
2650
|
+
return fs12.findSymbol(String(args.symbol ?? ""), { language: str(args.language), path: str(args.path) });
|
|
2640
2651
|
case "list_dir":
|
|
2641
|
-
return
|
|
2652
|
+
return fs12.listDir(String(args.path ?? ""), num(args.depth));
|
|
2642
2653
|
case "bash":
|
|
2643
|
-
return
|
|
2654
|
+
return fs12.bash(String(args.command ?? ""), signal);
|
|
2644
2655
|
case "fetch": {
|
|
2645
2656
|
if (!fetcher) return { success: false, output: "Web fetching is not available in this environment.", truncated: false };
|
|
2646
2657
|
const url = String(args.url ?? "");
|
|
@@ -3723,14 +3734,7 @@ import { v4 as uuidv4 } from "uuid";
|
|
|
3723
3734
|
|
|
3724
3735
|
// src/services/plannerModes.ts
|
|
3725
3736
|
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
3737
|
{ id: "verify", settingsKey: "verification", runtimeKey: "verificationEnabled", scopes: ["chat", "one-shot"] },
|
|
3733
|
-
{ id: "research-subagents", settingsKey: "researchSubagents", runtimeKey: "researchSubagentsEnabled", scopes: ["chat", "one-shot"] },
|
|
3734
3738
|
{ id: "tdd", settingsKey: "tdd", runtimeKey: "tddEnabled", scopes: ["task"] }
|
|
3735
3739
|
];
|
|
3736
3740
|
function plannerRuntimeToggles(settings) {
|
|
@@ -3740,28 +3744,16 @@ function plannerRuntimeToggles(settings) {
|
|
|
3740
3744
|
}
|
|
3741
3745
|
var DEFAULT_PLANNER_MODES = {
|
|
3742
3746
|
autonomousDefault: true,
|
|
3743
|
-
|
|
3744
|
-
prd: false,
|
|
3745
|
-
review: false,
|
|
3746
|
-
verification: false,
|
|
3747
|
-
researchSubagents: false
|
|
3747
|
+
verification: false
|
|
3748
3748
|
};
|
|
3749
3749
|
function plannerModesFrom(settings, autonomousDefault) {
|
|
3750
3750
|
return {
|
|
3751
3751
|
autonomousDefault,
|
|
3752
|
-
|
|
3753
|
-
prd: settings.prdEnabled ?? false,
|
|
3754
|
-
review: settings.reviewEnabled ?? false,
|
|
3755
|
-
verification: settings.verificationEnabled ?? false,
|
|
3756
|
-
researchSubagents: settings.researchSubagentsEnabled ?? false
|
|
3752
|
+
verification: settings.verificationEnabled ?? false
|
|
3757
3753
|
};
|
|
3758
3754
|
}
|
|
3759
3755
|
var MODE_FIELD = {
|
|
3760
|
-
"grill-me": "grillMe",
|
|
3761
|
-
prd: "prd",
|
|
3762
|
-
review: "review",
|
|
3763
3756
|
verify: "verification",
|
|
3764
|
-
"research-subagents": "researchSubagents",
|
|
3765
3757
|
tdd: null
|
|
3766
3758
|
};
|
|
3767
3759
|
function modesFor(scope, modes) {
|
|
@@ -3945,7 +3937,7 @@ function renderTaskQueryAnswer(query, tasks, catalog) {
|
|
|
3945
3937
|
}
|
|
3946
3938
|
|
|
3947
3939
|
// src/services/PlanPrompts.ts
|
|
3948
|
-
function buildResearchToolsPrompt(
|
|
3940
|
+
function buildResearchToolsPrompt() {
|
|
3949
3941
|
const lines = [
|
|
3950
3942
|
"You have access to the following tools to explore the workspace:",
|
|
3951
3943
|
"",
|
|
@@ -3961,15 +3953,11 @@ function buildResearchToolsPrompt(subagentsEnabled = false) {
|
|
|
3961
3953
|
"- glob: for finding files by name/path pattern",
|
|
3962
3954
|
"- grep: for searching inside file contents",
|
|
3963
3955
|
"",
|
|
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."
|
|
3956
|
+
"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.",
|
|
3957
|
+
"",
|
|
3958
|
+
'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.',
|
|
3959
|
+
"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
3960
|
];
|
|
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
3961
|
return lines.join("\n");
|
|
3974
3962
|
}
|
|
3975
3963
|
function buildSubagentSystemPrompt() {
|
|
@@ -4007,11 +3995,11 @@ function buildModeExamplesForRunners(runners) {
|
|
|
4007
3995
|
return `${r}: default|plan`;
|
|
4008
3996
|
}).join(", ");
|
|
4009
3997
|
}
|
|
4010
|
-
function verificationModeBlock(
|
|
3998
|
+
function verificationModeBlock() {
|
|
4011
3999
|
return [
|
|
4012
4000
|
"",
|
|
4013
4001
|
"VERIFICATION MODE:",
|
|
4014
|
-
|
|
4002
|
+
"Add a FINAL verification task to the end of the plan (highest order number).",
|
|
4015
4003
|
'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
4004
|
"This task must:",
|
|
4017
4005
|
'- Have type "ai" and autonomy "AFK" \u2014 it needs no human input',
|
|
@@ -4047,35 +4035,14 @@ function researchPhaseBlock(harnessMode) {
|
|
|
4047
4035
|
"- Research and questions can interleave \u2014 you can explore a file, then ask a question grounded in it, in one response"
|
|
4048
4036
|
].join("\n");
|
|
4049
4037
|
}
|
|
4050
|
-
function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, runnerModes, autonomousDefault = true,
|
|
4038
|
+
function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, runnerModes, autonomousDefault = true, verificationEnabled = false, harnessMode = false) {
|
|
4051
4039
|
const modelsJson = modelsJsonFor(modelsByRunner, runners);
|
|
4052
4040
|
const modeGuide = runnerModes ? buildModeGuide(runnerModes, autonomousDefault) : buildModeGuideForRunners(runners, autonomousDefault);
|
|
4053
4041
|
const modeExamples = runnerModes ? Object.entries(runnerModes).map(([r, ms]) => {
|
|
4054
4042
|
const filtered = filteredBuildModes(ms, autonomousDefault);
|
|
4055
4043
|
return `${r}: ${filtered.map((m) => m.id).join("|")}${ms.some((m) => m.id === "plan") ? "|plan" : ""}`;
|
|
4056
4044
|
}).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) : "";
|
|
4045
|
+
const verificationBlock = verificationEnabled ? verificationModeBlock() : "";
|
|
4079
4046
|
return buildConversationBody(
|
|
4080
4047
|
goal,
|
|
4081
4048
|
context,
|
|
@@ -4084,34 +4051,10 @@ function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, r
|
|
|
4084
4051
|
modeGuide,
|
|
4085
4052
|
modeExamples,
|
|
4086
4053
|
harnessMode,
|
|
4087
|
-
grillMeBlock,
|
|
4088
|
-
prdBlock,
|
|
4089
|
-
reviewBlock,
|
|
4090
4054
|
verificationBlock
|
|
4091
4055
|
);
|
|
4092
4056
|
}
|
|
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) {
|
|
4057
|
+
function buildConversationBody(goal, context, modelsJson, runners, modeGuide, modeExamples, harnessMode, verificationBlock) {
|
|
4115
4058
|
return [
|
|
4116
4059
|
"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
4060
|
"",
|
|
@@ -4122,9 +4065,6 @@ function buildConversationBody(goal, context, modelsJson, runners, modeGuide, mo
|
|
|
4122
4065
|
'4. After the user confirms the outline, emit the final task plan as a JSON object with a "tasks" array.',
|
|
4123
4066
|
"",
|
|
4124
4067
|
researchPhaseBlock(harnessMode),
|
|
4125
|
-
grillMeBlock,
|
|
4126
|
-
prdBlock,
|
|
4127
|
-
reviewBlock,
|
|
4128
4068
|
verificationBlock,
|
|
4129
4069
|
"",
|
|
4130
4070
|
"OUTLINE PHASE:",
|
|
@@ -4311,7 +4251,6 @@ function getPlanTemplate() {
|
|
|
4311
4251
|
"",
|
|
4312
4252
|
"TASK MODE:",
|
|
4313
4253
|
"{{MODE_GUIDE}}",
|
|
4314
|
-
"{{REVIEW_BLOCK}}",
|
|
4315
4254
|
"{{VERIFICATION_BLOCK}}",
|
|
4316
4255
|
"",
|
|
4317
4256
|
"CONTEXT:",
|
|
@@ -4358,10 +4297,8 @@ function buildPlanPromptBase(userGoal, context, researchResults, modelsByRunner,
|
|
|
4358
4297
|
const filtered = filteredBuildModes(ms, autonomousDefault);
|
|
4359
4298
|
return `${r}: ${filtered.map((m) => m.id).join("|")}${ms.some((m) => m.id === "plan") ? "|plan" : ""}`;
|
|
4360
4299
|
}).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)}
|
|
4300
|
+
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 ? `
|
|
4301
|
+
${verificationModeBlock()}
|
|
4365
4302
|
` : "").replace("{{MODE_EXAMPLES}}", modeExamples).replace("{{CONTEXT}}", context || "(no additional context)").replace("{{RESEARCH_RESULTS}}", researchResults ? `
|
|
4366
4303
|
|
|
4367
4304
|
RESEARCH FINDINGS:
|
|
@@ -4724,8 +4661,8 @@ var SUBAGENT_EXCLUDED = /* @__PURE__ */ new Set(["fetch", "web_search"]);
|
|
|
4724
4661
|
function subagentToolSpecs() {
|
|
4725
4662
|
return RESEARCH_TOOLS.filter((t) => !SUBAGENT_EXCLUDED.has(t.name));
|
|
4726
4663
|
}
|
|
4727
|
-
function withSpawn(
|
|
4728
|
-
return
|
|
4664
|
+
function withSpawn() {
|
|
4665
|
+
return [...RESEARCH_TOOLS, SPAWN_RESEARCH_AGENT_SPEC];
|
|
4729
4666
|
}
|
|
4730
4667
|
function projectOpenAi(specs) {
|
|
4731
4668
|
return specs.map((t) => ({
|
|
@@ -4746,8 +4683,8 @@ function projectOpenAi(specs) {
|
|
|
4746
4683
|
}
|
|
4747
4684
|
}));
|
|
4748
4685
|
}
|
|
4749
|
-
function toOpenAiTools(
|
|
4750
|
-
return projectOpenAi(withSpawn(
|
|
4686
|
+
function toOpenAiTools() {
|
|
4687
|
+
return projectOpenAi(withSpawn());
|
|
4751
4688
|
}
|
|
4752
4689
|
function toOpenAiSubagentTools() {
|
|
4753
4690
|
return projectOpenAi(subagentToolSpecs());
|
|
@@ -4758,8 +4695,8 @@ var SCHEMA_TYPE = {
|
|
|
4758
4695
|
array: SchemaType.ARRAY,
|
|
4759
4696
|
boolean: SchemaType.BOOLEAN
|
|
4760
4697
|
};
|
|
4761
|
-
function toGeminiToolDeclarations(
|
|
4762
|
-
return withSpawn(
|
|
4698
|
+
function toGeminiToolDeclarations() {
|
|
4699
|
+
return withSpawn().map((t) => {
|
|
4763
4700
|
const properties = {};
|
|
4764
4701
|
for (const [key, param] of Object.entries(t.properties)) {
|
|
4765
4702
|
properties[key] = {
|
|
@@ -4856,33 +4793,10 @@ function compactResearchResults(resultsText, maxChars = COMPACTION_LIMITS.resear
|
|
|
4856
4793
|
return (blocks[0].lines.length === 0 ? texts.slice(1) : texts).join("\n");
|
|
4857
4794
|
}
|
|
4858
4795
|
|
|
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
4796
|
// src/services/ContextCollector.ts
|
|
4883
4797
|
var ORDEWELL_CONTEXT_MAX = 8e3;
|
|
4884
|
-
async function collectResearchContext(
|
|
4885
|
-
const collector = new ContextCollector(
|
|
4798
|
+
async function collectResearchContext(fs12, runners) {
|
|
4799
|
+
const collector = new ContextCollector(fs12);
|
|
4886
4800
|
let contextStr = "";
|
|
4887
4801
|
let firstContext = null;
|
|
4888
4802
|
for (const r of runners) {
|
|
@@ -4909,8 +4823,8 @@ ${firstContext.dirStructure}
|
|
|
4909
4823
|
return contextStr;
|
|
4910
4824
|
}
|
|
4911
4825
|
var ContextCollector = class {
|
|
4912
|
-
constructor(
|
|
4913
|
-
this.fs =
|
|
4826
|
+
constructor(fs12, registry) {
|
|
4827
|
+
this.fs = fs12;
|
|
4914
4828
|
this.registry = registry;
|
|
4915
4829
|
}
|
|
4916
4830
|
fs;
|
|
@@ -4958,22 +4872,22 @@ var ALLOWED_TOOL_NAMES = new Set(subagentToolSpecs().map((t) => t.name));
|
|
|
4958
4872
|
function refused(output) {
|
|
4959
4873
|
return { success: false, output, truncated: false };
|
|
4960
4874
|
}
|
|
4961
|
-
function nonPromptingFs(
|
|
4962
|
-
const withinWorkspace = (p) => resolveWithin(
|
|
4875
|
+
function nonPromptingFs(fs12) {
|
|
4876
|
+
const withinWorkspace = (p) => resolveWithin(fs12.getWorkspaceRoot(), p ?? ".").inside;
|
|
4963
4877
|
const outsideRefusal = (p) => refused(
|
|
4964
4878
|
`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
4879
|
);
|
|
4966
4880
|
return {
|
|
4967
|
-
getWorkspaceRoot: () =>
|
|
4968
|
-
readFile: (p, opts) => withinWorkspace(p) ?
|
|
4881
|
+
getWorkspaceRoot: () => fs12.getWorkspaceRoot(),
|
|
4882
|
+
readFile: (p, opts) => withinWorkspace(p) ? fs12.readFile(p, opts) : Promise.resolve(outsideRefusal(p)),
|
|
4969
4883
|
readFiles: (paths) => {
|
|
4970
4884
|
const escaping = paths.find((p) => !withinWorkspace(p));
|
|
4971
|
-
return escaping ? Promise.resolve(outsideRefusal(escaping)) :
|
|
4885
|
+
return escaping ? Promise.resolve(outsideRefusal(escaping)) : fs12.readFiles(paths);
|
|
4972
4886
|
},
|
|
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) ?
|
|
4887
|
+
glob: (pattern, opts) => withinWorkspace(opts?.path) ? fs12.glob(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
4888
|
+
grep: (pattern, opts) => withinWorkspace(opts?.path) ? fs12.grep(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
4889
|
+
findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs12.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
4890
|
+
listDir: (p, depth) => withinWorkspace(p) ? fs12.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
|
|
4977
4891
|
bash: async (command, signal) => {
|
|
4978
4892
|
const dialect = resolveResearchShell().dialect;
|
|
4979
4893
|
const { tier, reason } = classifyCommand(command, { dialect });
|
|
@@ -4984,13 +4898,13 @@ function nonPromptingFs(fs10) {
|
|
|
4984
4898
|
}
|
|
4985
4899
|
const escaping = pathLikeArgs(command, { dialect }).find((p) => !withinWorkspace(p));
|
|
4986
4900
|
if (escaping) return outsideRefusal(escaping);
|
|
4987
|
-
return
|
|
4901
|
+
return fs12.bash(command, signal);
|
|
4988
4902
|
}
|
|
4989
4903
|
};
|
|
4990
4904
|
}
|
|
4991
4905
|
async function runLoop(prompt, deps) {
|
|
4992
4906
|
if (deps.signal?.aborted) return "[research agent aborted before starting]";
|
|
4993
|
-
const
|
|
4907
|
+
const fs12 = nonPromptingFs(deps.fs);
|
|
4994
4908
|
const chat = deps.createChat((delta) => deps.onProgress?.({ type: "thinking", text: delta }));
|
|
4995
4909
|
let turn = await chat.sendMessage(prompt, deps.signal);
|
|
4996
4910
|
for (let step = 0; turn.hasToolCalls && step < SUBAGENT_LIMITS.maxSteps; step++) {
|
|
@@ -5007,7 +4921,7 @@ ${turn.text}`;
|
|
|
5007
4921
|
if (tc.name === "read_file" && !("maxBytes" in args) && !("limit" in args)) args.limit = 2e3;
|
|
5008
4922
|
const toolArgs = JSON.stringify(tc.args);
|
|
5009
4923
|
deps.onProgress?.({ type: "tool_call", tool: tc.name, toolArgs, toolCallId: tc.id });
|
|
5010
|
-
const res = await executeTool(tc.name, args,
|
|
4924
|
+
const res = await executeTool(tc.name, args, fs12, void 0, deps.signal);
|
|
5011
4925
|
const output = res.output.length > SUBAGENT_LIMITS.toolOutputMaxChars ? res.output.slice(0, SUBAGENT_LIMITS.toolOutputMaxChars) + `
|
|
5012
4926
|
[... truncated to ${SUBAGENT_LIMITS.toolOutputMaxChars} chars, total ${res.output.length}]` : res.output;
|
|
5013
4927
|
const stepEntry = {
|
|
@@ -5065,8 +4979,6 @@ var BaseAiService = class _BaseAiService {
|
|
|
5065
4979
|
config;
|
|
5066
4980
|
conversation = null;
|
|
5067
4981
|
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
4982
|
hasActiveConversation() {
|
|
5071
4983
|
return this.conversation !== null;
|
|
5072
4984
|
}
|
|
@@ -5122,10 +5034,7 @@ var BaseAiService = class _BaseAiService {
|
|
|
5122
5034
|
* steering/failed result and the loop continues sequentially: a subagent can
|
|
5123
5035
|
* never fail a plan or a turn.
|
|
5124
5036
|
*/
|
|
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
|
-
}
|
|
5037
|
+
async executeSpawnAgent(args, fs12, onProgress, signal, subagentId) {
|
|
5129
5038
|
const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
|
|
5130
5039
|
if (!prompt) {
|
|
5131
5040
|
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 +5045,20 @@ var BaseAiService = class _BaseAiService {
|
|
|
5136
5045
|
if (!chat) throw new Error("subagent chats are not available for this provider");
|
|
5137
5046
|
return chat;
|
|
5138
5047
|
},
|
|
5139
|
-
fs:
|
|
5048
|
+
fs: fs12,
|
|
5140
5049
|
signal,
|
|
5141
5050
|
onProgress: (progress) => onProgress({ ...progress, subagentId })
|
|
5142
5051
|
});
|
|
5143
5052
|
}
|
|
5144
5053
|
/** Collect project context for the planning phase. Shared with the harness backend. */
|
|
5145
|
-
static async collectResearchContext(
|
|
5146
|
-
return collectResearchContext(
|
|
5054
|
+
static async collectResearchContext(fs12, runners) {
|
|
5055
|
+
return collectResearchContext(fs12, runners);
|
|
5147
5056
|
}
|
|
5148
5057
|
/**
|
|
5149
5058
|
* Execute one round of tool calls and report each through onProgress.
|
|
5150
5059
|
* Returns the results to feed back plus the log entries produced.
|
|
5151
5060
|
*/
|
|
5152
|
-
async executeToolCalls(toolCalls,
|
|
5061
|
+
async executeToolCalls(toolCalls, fs12, onProgress, fetcher, stepIndex, thinking, signal) {
|
|
5153
5062
|
const toolResults = [];
|
|
5154
5063
|
const logEntries = [];
|
|
5155
5064
|
let resultsText = "";
|
|
@@ -5168,18 +5077,18 @@ var BaseAiService = class _BaseAiService {
|
|
|
5168
5077
|
const subagentId = uuidv4();
|
|
5169
5078
|
spawnSubagentIds.set(i, subagentId);
|
|
5170
5079
|
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 },
|
|
5080
|
+
precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args }, fs12, onProgress, signal, subagentId));
|
|
5172
5081
|
}) : Promise.resolve(),
|
|
5173
5082
|
parallelIndexes.length > 1 ? mapWithConcurrency(parallelIndexes, TOOL_ROUND_CONCURRENCY, async (i) => {
|
|
5174
5083
|
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],
|
|
5084
|
+
precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i], fs12, fetcher, signal));
|
|
5176
5085
|
}) : Promise.resolve()
|
|
5177
5086
|
]);
|
|
5178
5087
|
for (const [index, tc] of toolCalls.entries()) {
|
|
5179
5088
|
const toolArgs = normalizedArgs[index];
|
|
5180
5089
|
const preResult = precomputed.get(index);
|
|
5181
5090
|
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,
|
|
5091
|
+
const toolResult = preResult ?? await executeTool(tc.name, toolArgs, fs12, fetcher, signal);
|
|
5183
5092
|
const MAX_RESPONSE_CHARS = 5e4;
|
|
5184
5093
|
const truncatedResponse = toolResult.success && (toolResult.truncated || toolResult.output.length > MAX_RESPONSE_CHARS);
|
|
5185
5094
|
const llmOutput = toolResult.output.length > MAX_RESPONSE_CHARS ? toolResult.output.slice(0, MAX_RESPONSE_CHARS) + `
|
|
@@ -5311,7 +5220,6 @@ ${llmOutput}
|
|
|
5311
5220
|
researchLog
|
|
5312
5221
|
};
|
|
5313
5222
|
}
|
|
5314
|
-
if (extractPrdBlock(turn.text)) ctx.prdCaptured = true;
|
|
5315
5223
|
const reply = classifyPlannerReply(turn.text, {
|
|
5316
5224
|
runners: ctx.runners,
|
|
5317
5225
|
runnerModes: ctx.runnerModes,
|
|
@@ -5325,16 +5233,6 @@ ${llmOutput}
|
|
|
5325
5233
|
case "task_query":
|
|
5326
5234
|
return { kind: "task_query", query: reply.query, text: turn.text, researchLog };
|
|
5327
5235
|
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
5236
|
return { kind: "plan", tasks: reply.tasks, text: turn.text, researchLog };
|
|
5339
5237
|
// Botched attempts get a bounded corrective retry — otherwise the
|
|
5340
5238
|
// broken JSON would surface as a prose bubble and the edit or plan
|
|
@@ -5376,7 +5274,7 @@ ${llmOutput}
|
|
|
5376
5274
|
* parsed tasks if a plan was emitted mid-loop, or null if the loop
|
|
5377
5275
|
* exhausted without a valid plan.
|
|
5378
5276
|
*/
|
|
5379
|
-
async runResearchLoop(chat, firstMessage,
|
|
5277
|
+
async runResearchLoop(chat, firstMessage, fs12, onProgress, runners, initialLog, fetcher, userGoal, runnerModes, autonomousDefault, signal) {
|
|
5380
5278
|
const researchLog = initialLog ? [...initialLog] : [];
|
|
5381
5279
|
if (!initialLog && userGoal) {
|
|
5382
5280
|
researchLog.push({
|
|
@@ -5403,7 +5301,7 @@ ${llmOutput}
|
|
|
5403
5301
|
if (!turn.hasToolCalls) break;
|
|
5404
5302
|
const { toolResults, logEntries, resultsText } = await this.executeToolCalls(
|
|
5405
5303
|
turn.toolCalls,
|
|
5406
|
-
|
|
5304
|
+
fs12,
|
|
5407
5305
|
onProgress,
|
|
5408
5306
|
fetcher,
|
|
5409
5307
|
step,
|
|
@@ -5541,9 +5439,6 @@ var GeminiService = class extends BaseAiService {
|
|
|
5541
5439
|
req.runners,
|
|
5542
5440
|
req.runnerModes,
|
|
5543
5441
|
req.autonomousDefault ?? true,
|
|
5544
|
-
req.grillMeEnabled ?? false,
|
|
5545
|
-
req.prdEnabled ?? false,
|
|
5546
|
-
req.reviewEnabled ?? false,
|
|
5547
5442
|
req.verificationEnabled ?? false
|
|
5548
5443
|
);
|
|
5549
5444
|
const seededHistory = [
|
|
@@ -5582,8 +5477,7 @@ ${m.content}` });
|
|
|
5582
5477
|
runners: req.runners,
|
|
5583
5478
|
runnerModes: req.runnerModes,
|
|
5584
5479
|
autonomousDefault: req.autonomousDefault,
|
|
5585
|
-
fetcher: req.fetcher
|
|
5586
|
-
prdEnabled: req.prdEnabled ?? false
|
|
5480
|
+
fetcher: req.fetcher
|
|
5587
5481
|
};
|
|
5588
5482
|
this.conversation = { ctx, setProgress: (onProgress) => {
|
|
5589
5483
|
currentProgress = onProgress;
|
|
@@ -5641,11 +5535,11 @@ ${repairHint}` }] : parts;
|
|
|
5641
5535
|
return this.consumePlanStream(result, onToken);
|
|
5642
5536
|
}
|
|
5643
5537
|
// --- IAiService implementation ---
|
|
5644
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
5538
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
5645
5539
|
this.ensureInit();
|
|
5646
5540
|
const { autonomousDefault } = modes;
|
|
5647
5541
|
const genModel = this.getPlanningModel();
|
|
5648
|
-
const contextStr = await BaseAiService.collectResearchContext(
|
|
5542
|
+
const contextStr = await BaseAiService.collectResearchContext(fs12, runners);
|
|
5649
5543
|
const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
|
|
5650
5544
|
const firstMessage = `${buildResearchToolsPrompt()}
|
|
5651
5545
|
|
|
@@ -5656,17 +5550,17 @@ Explore the workspace to understand the codebase, then generate the plan.`;
|
|
|
5656
5550
|
generationConfig: { temperature: 0.3, topP: 0.95, maxOutputTokens: 16384 }
|
|
5657
5551
|
});
|
|
5658
5552
|
const researchChat = new GeminiResearchChat(chat);
|
|
5659
|
-
const result = await this.runResearchLoop(researchChat, firstMessage,
|
|
5553
|
+
const result = await this.runResearchLoop(researchChat, firstMessage, fs12, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
|
|
5660
5554
|
if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
|
|
5661
5555
|
const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
|
|
5662
5556
|
return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
|
|
5663
5557
|
}
|
|
5664
|
-
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken,
|
|
5558
|
+
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
|
|
5665
5559
|
this.ensureInit();
|
|
5666
5560
|
const { autonomousDefault } = modes;
|
|
5667
5561
|
let ctx = "";
|
|
5668
|
-
if (
|
|
5669
|
-
const collected = await new ContextCollector(
|
|
5562
|
+
if (fs12) {
|
|
5563
|
+
const collected = await new ContextCollector(fs12).collect(runners[0] ?? "claude-code");
|
|
5670
5564
|
if (collected.aiflowContext) ctx = `
|
|
5671
5565
|
<aiflow_context>
|
|
5672
5566
|
${collected.aiflowContext}
|
|
@@ -5682,12 +5576,12 @@ ${collected.aiflowContext}
|
|
|
5682
5576
|
autonomousDefault
|
|
5683
5577
|
);
|
|
5684
5578
|
}
|
|
5685
|
-
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress,
|
|
5579
|
+
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
|
|
5686
5580
|
this.ensureInit();
|
|
5687
5581
|
const { autonomousDefault } = modes;
|
|
5688
5582
|
let aiflowContext;
|
|
5689
|
-
if (
|
|
5690
|
-
const collected = await new ContextCollector(
|
|
5583
|
+
if (fs12) {
|
|
5584
|
+
const collected = await new ContextCollector(fs12).collect(existingPlan.runners[0] ?? "claude-code");
|
|
5691
5585
|
if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
|
|
5692
5586
|
}
|
|
5693
5587
|
const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
|
|
@@ -5870,7 +5764,6 @@ var OpenAiService = class extends BaseAiService {
|
|
|
5870
5764
|
async startConversation(req) {
|
|
5871
5765
|
this.ensureInit();
|
|
5872
5766
|
const client = this.getClient();
|
|
5873
|
-
this.researchSubagentsEnabled = req.researchSubagentsEnabled ?? false;
|
|
5874
5767
|
const contextStr = await BaseAiService.collectResearchContext(req.fs, req.runners);
|
|
5875
5768
|
const systemPrompt = buildConversationSystemPrompt(
|
|
5876
5769
|
req.goal,
|
|
@@ -5879,16 +5772,13 @@ var OpenAiService = class extends BaseAiService {
|
|
|
5879
5772
|
req.runners,
|
|
5880
5773
|
req.runnerModes,
|
|
5881
5774
|
req.autonomousDefault ?? true,
|
|
5882
|
-
req.grillMeEnabled ?? false,
|
|
5883
|
-
req.prdEnabled ?? false,
|
|
5884
|
-
req.reviewEnabled ?? false,
|
|
5885
5775
|
req.verificationEnabled ?? false
|
|
5886
5776
|
);
|
|
5887
5777
|
const userText = req.goal;
|
|
5888
5778
|
const messages = [
|
|
5889
5779
|
{ role: "system", content: `${systemPrompt}
|
|
5890
5780
|
|
|
5891
|
-
${buildResearchToolsPrompt(
|
|
5781
|
+
${buildResearchToolsPrompt()}` }
|
|
5892
5782
|
];
|
|
5893
5783
|
for (const m of req.priorHistory ?? []) {
|
|
5894
5784
|
messages.push({ role: m.role, content: m.content });
|
|
@@ -5898,7 +5788,7 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
|
|
|
5898
5788
|
messages,
|
|
5899
5789
|
client,
|
|
5900
5790
|
this.requireModel("orchestratorModel", this.config.orchestratorModel),
|
|
5901
|
-
toOpenAiTools(
|
|
5791
|
+
toOpenAiTools(),
|
|
5902
5792
|
(delta) => currentProgress({ type: "thinking", text: delta }),
|
|
5903
5793
|
(delta) => currentProgress({ type: "plan_token", planToken: delta })
|
|
5904
5794
|
);
|
|
@@ -5908,8 +5798,7 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
|
|
|
5908
5798
|
runners: req.runners,
|
|
5909
5799
|
runnerModes: req.runnerModes,
|
|
5910
5800
|
autonomousDefault: req.autonomousDefault,
|
|
5911
|
-
fetcher: req.fetcher
|
|
5912
|
-
prdEnabled: req.prdEnabled ?? false
|
|
5801
|
+
fetcher: req.fetcher
|
|
5913
5802
|
};
|
|
5914
5803
|
this.conversation = {
|
|
5915
5804
|
ctx,
|
|
@@ -5927,15 +5816,14 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
|
|
|
5927
5816
|
}
|
|
5928
5817
|
}
|
|
5929
5818
|
// --- IAiService implementation ---
|
|
5930
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
5819
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
5931
5820
|
this.ensureInit();
|
|
5932
5821
|
const client = this.getClient();
|
|
5933
|
-
const { autonomousDefault
|
|
5934
|
-
|
|
5935
|
-
const contextStr = await BaseAiService.collectResearchContext(fs10, runners);
|
|
5822
|
+
const { autonomousDefault } = modes;
|
|
5823
|
+
const contextStr = await BaseAiService.collectResearchContext(fs12, runners);
|
|
5936
5824
|
const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
|
|
5937
5825
|
const userText = `User goal: ${userDescription}`;
|
|
5938
|
-
const firstMessage = `${buildResearchToolsPrompt(
|
|
5826
|
+
const firstMessage = `${buildResearchToolsPrompt()}
|
|
5939
5827
|
|
|
5940
5828
|
Explore the workspace to understand the codebase, then generate the plan.
|
|
5941
5829
|
|
|
@@ -5947,20 +5835,20 @@ ${userText}`;
|
|
|
5947
5835
|
messages,
|
|
5948
5836
|
client,
|
|
5949
5837
|
this.requireModel("orchestratorModel", this.config.orchestratorModel),
|
|
5950
|
-
toOpenAiTools(
|
|
5838
|
+
toOpenAiTools(),
|
|
5951
5839
|
(delta) => onProgress({ type: "thinking", text: delta })
|
|
5952
5840
|
);
|
|
5953
|
-
const result = await this.runResearchLoop(researchChat, firstMessage,
|
|
5841
|
+
const result = await this.runResearchLoop(researchChat, firstMessage, fs12, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
|
|
5954
5842
|
if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
|
|
5955
5843
|
const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
|
|
5956
5844
|
return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
|
|
5957
5845
|
}
|
|
5958
|
-
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken,
|
|
5846
|
+
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
5959
5847
|
this.ensureInit();
|
|
5960
5848
|
const { autonomousDefault } = modes;
|
|
5961
5849
|
let ctx = "";
|
|
5962
|
-
if (
|
|
5963
|
-
const collected = await new ContextCollector(
|
|
5850
|
+
if (fs12) {
|
|
5851
|
+
const collected = await new ContextCollector(fs12).collect(runners[0] ?? "claude-code");
|
|
5964
5852
|
if (collected.aiflowContext) ctx = `
|
|
5965
5853
|
<aiflow_context>
|
|
5966
5854
|
${collected.aiflowContext}
|
|
@@ -5976,12 +5864,12 @@ ${collected.aiflowContext}
|
|
|
5976
5864
|
autonomousDefault
|
|
5977
5865
|
);
|
|
5978
5866
|
}
|
|
5979
|
-
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress,
|
|
5867
|
+
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
5980
5868
|
this.ensureInit();
|
|
5981
5869
|
const { autonomousDefault } = modes;
|
|
5982
5870
|
let aiflowContext;
|
|
5983
|
-
if (
|
|
5984
|
-
const collected = await new ContextCollector(
|
|
5871
|
+
if (fs12) {
|
|
5872
|
+
const collected = await new ContextCollector(fs12).collect(existingPlan.runners[0] ?? "claude-code");
|
|
5985
5873
|
if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
|
|
5986
5874
|
}
|
|
5987
5875
|
const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
|
|
@@ -6070,21 +5958,28 @@ function pickWindow(sorted, currentIdx, max) {
|
|
|
6070
5958
|
const window = sorted.slice(start, start + max);
|
|
6071
5959
|
return { window, omitted: sorted.length - window.length };
|
|
6072
5960
|
}
|
|
6073
|
-
function renderPlanMap(
|
|
6074
|
-
|
|
6075
|
-
|
|
5961
|
+
function renderPlanMap(planTasks, currentTaskId, opts) {
|
|
5962
|
+
const rows = flattenTasksWithParents(planTasks);
|
|
5963
|
+
if (rows.length < PLAN_MAP_MIN_TASKS) return "";
|
|
5964
|
+
const sorted = rows.map((r) => ({
|
|
5965
|
+
...r,
|
|
5966
|
+
label: taskOrderLabel(r.task, r.parent ?? void 0)
|
|
5967
|
+
})).sort((a, b) => {
|
|
5968
|
+
const parentDiff = (a.parent?.order ?? -1) - (b.parent?.order ?? -1);
|
|
5969
|
+
if (parentDiff !== 0) return parentDiff;
|
|
5970
|
+
return a.task.order - b.task.order;
|
|
5971
|
+
});
|
|
6076
5972
|
const max = opts?.maxEntries ?? DEFAULT_PLAN_MAP_MAX_ENTRIES;
|
|
6077
|
-
const currentIdx = sorted.findIndex((
|
|
5973
|
+
const currentIdx = sorted.findIndex((r) => r.task.id === currentTaskId);
|
|
6078
5974
|
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, " ");
|
|
5975
|
+
const currentTask = sorted.find((r) => r.task.id === currentTaskId)?.task;
|
|
5976
|
+
const lines = window.map((row) => {
|
|
5977
|
+
const isCurrent = row.task.id === currentTaskId;
|
|
5978
|
+
const tag = planMapStatus(row.task, isCurrent);
|
|
5979
|
+
const order = row.parent ? row.label : row.label.padStart(2, " ");
|
|
6085
5980
|
const arrow = isCurrent ? " \u2190 you are here" : "";
|
|
6086
|
-
const runner =
|
|
6087
|
-
return `${order}. [${tag.padEnd(7, " ")}] ${
|
|
5981
|
+
const runner = row.task.assignedRunner ? ` (${row.task.assignedRunner})` : "";
|
|
5982
|
+
return `${order}. [${tag.padEnd(7, " ")}] ${row.task.title}${runner}${arrow}`;
|
|
6088
5983
|
});
|
|
6089
5984
|
const footer = omitted > 0 ? `
|
|
6090
5985
|
(${omitted} task${omitted === 1 ? "" : "s"} omitted from this view)` : "";
|
|
@@ -6149,7 +6044,8 @@ function composeAugmentedPrompt(task, allTasks, opts) {
|
|
|
6149
6044
|
const map = renderPlanMap(allTasks, task.id, { maxEntries: opts?.planMapMaxEntries });
|
|
6150
6045
|
if (map) blocks.push(map);
|
|
6151
6046
|
}
|
|
6152
|
-
const
|
|
6047
|
+
const flat = flattenTasks(allTasks);
|
|
6048
|
+
const outputs = collectDirectDependencyOutputs(task, flat);
|
|
6153
6049
|
if (outputs.length > 0) blocks.push(renderPriorOutputs(outputs));
|
|
6154
6050
|
if (opts?.tddEnabled) {
|
|
6155
6051
|
blocks.push(renderTddInstruction());
|
|
@@ -6328,6 +6224,7 @@ function renderTerminalOutput(raw) {
|
|
|
6328
6224
|
return populated.map((r) => cells(r).join("")).join("\n");
|
|
6329
6225
|
}
|
|
6330
6226
|
var MARKER_SCAN_TAIL = 16384;
|
|
6227
|
+
var IDLE_TIMEOUT_MS = 6e4;
|
|
6331
6228
|
var VerdictEngine = class {
|
|
6332
6229
|
markerSeen = /* @__PURE__ */ new Set();
|
|
6333
6230
|
buffers = /* @__PURE__ */ new Map();
|
|
@@ -6335,6 +6232,7 @@ var VerdictEngine = class {
|
|
|
6335
6232
|
pausedSessions = /* @__PURE__ */ new Map();
|
|
6336
6233
|
listeners = [];
|
|
6337
6234
|
checkpointListeners = [];
|
|
6235
|
+
idleListeners = [];
|
|
6338
6236
|
/**
|
|
6339
6237
|
* Per-task generation counter. Incremented on every watch() and clear().
|
|
6340
6238
|
* Stale callbacks (from a prior session whose generation doesn't match
|
|
@@ -6342,12 +6240,47 @@ var VerdictEngine = class {
|
|
|
6342
6240
|
* wrong session.
|
|
6343
6241
|
*/
|
|
6344
6242
|
generations = /* @__PURE__ */ new Map();
|
|
6243
|
+
idleTimers = /* @__PURE__ */ new Map();
|
|
6244
|
+
idleSince = /* @__PURE__ */ new Map();
|
|
6345
6245
|
onVerdict(listener) {
|
|
6346
6246
|
this.listeners.push(listener);
|
|
6347
6247
|
}
|
|
6348
6248
|
onCheckpoint(listener) {
|
|
6349
6249
|
this.checkpointListeners.push(listener);
|
|
6350
6250
|
}
|
|
6251
|
+
onIdleChange(listener) {
|
|
6252
|
+
this.idleListeners.push(listener);
|
|
6253
|
+
}
|
|
6254
|
+
/** Advisory silence timestamp for a task, or null if it isn't idle. */
|
|
6255
|
+
getIdleSince(taskId) {
|
|
6256
|
+
return this.idleSince.get(taskId) ?? null;
|
|
6257
|
+
}
|
|
6258
|
+
/** Restart the silence timer on fresh output; broadcasts the null transition if it was idle. */
|
|
6259
|
+
touchIdle(taskId, gen) {
|
|
6260
|
+
const existing = this.idleTimers.get(taskId);
|
|
6261
|
+
if (existing) clearTimeout(existing);
|
|
6262
|
+
if (this.idleSince.get(taskId)) {
|
|
6263
|
+
this.idleSince.set(taskId, null);
|
|
6264
|
+
for (const l of this.idleListeners) l(taskId, null);
|
|
6265
|
+
}
|
|
6266
|
+
this.idleTimers.set(taskId, setTimeout(() => {
|
|
6267
|
+
if (this.generations.get(taskId) !== gen) return;
|
|
6268
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6269
|
+
this.idleSince.set(taskId, now);
|
|
6270
|
+
for (const l of this.idleListeners) l(taskId, now);
|
|
6271
|
+
}, IDLE_TIMEOUT_MS));
|
|
6272
|
+
}
|
|
6273
|
+
/** Tear down idle tracking for a task; broadcasts the null transition if it was idle. */
|
|
6274
|
+
clearIdle(taskId) {
|
|
6275
|
+
const timer = this.idleTimers.get(taskId);
|
|
6276
|
+
if (timer) clearTimeout(timer);
|
|
6277
|
+
this.idleTimers.delete(taskId);
|
|
6278
|
+
const wasIdle = this.idleSince.get(taskId);
|
|
6279
|
+
this.idleSince.delete(taskId);
|
|
6280
|
+
if (wasIdle) {
|
|
6281
|
+
for (const l of this.idleListeners) l(taskId, null);
|
|
6282
|
+
}
|
|
6283
|
+
}
|
|
6351
6284
|
approveCheckpoint(taskId) {
|
|
6352
6285
|
const session = this.pausedSessions.get(taskId);
|
|
6353
6286
|
if (session) {
|
|
@@ -6378,6 +6311,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
6378
6311
|
this.checkpointCounts.set(task.id, 0);
|
|
6379
6312
|
session.onOutput((text) => {
|
|
6380
6313
|
if (this.generations.get(task.id) !== gen) return;
|
|
6314
|
+
this.touchIdle(task.id, gen);
|
|
6381
6315
|
if (this.markerSeen.has(task.id)) return;
|
|
6382
6316
|
const buf = (this.buffers.get(task.id) ?? "") + text;
|
|
6383
6317
|
this.buffers.set(task.id, buf);
|
|
@@ -6389,6 +6323,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
6389
6323
|
this.buffers.delete(task.id);
|
|
6390
6324
|
this.checkpointCounts.delete(task.id);
|
|
6391
6325
|
this.pausedSessions.delete(task.id);
|
|
6326
|
+
this.clearIdle(task.id);
|
|
6392
6327
|
this.generations.set(task.id, gen + 1);
|
|
6393
6328
|
const verdict = this.decide(task, 0);
|
|
6394
6329
|
for (const l of this.listeners) l(task.id, verdict, output);
|
|
@@ -6413,6 +6348,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
6413
6348
|
this.buffers.delete(task.id);
|
|
6414
6349
|
this.checkpointCounts.delete(task.id);
|
|
6415
6350
|
this.pausedSessions.delete(task.id);
|
|
6351
|
+
this.clearIdle(task.id);
|
|
6416
6352
|
const verdict = this.decide(task, exitCode);
|
|
6417
6353
|
for (const l of this.listeners) l(task.id, verdict, output);
|
|
6418
6354
|
});
|
|
@@ -6423,6 +6359,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
6423
6359
|
this.buffers.delete(task.id);
|
|
6424
6360
|
this.checkpointCounts.delete(task.id);
|
|
6425
6361
|
this.pausedSessions.delete(task.id);
|
|
6362
|
+
this.clearIdle(task.id);
|
|
6426
6363
|
this.generations.set(task.id, (this.generations.get(task.id) ?? 0) + 1);
|
|
6427
6364
|
return {
|
|
6428
6365
|
outcome: "pass",
|
|
@@ -6444,6 +6381,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
6444
6381
|
this.buffers.delete(task.id);
|
|
6445
6382
|
this.checkpointCounts.delete(task.id);
|
|
6446
6383
|
this.pausedSessions.delete(task.id);
|
|
6384
|
+
this.clearIdle(task.id);
|
|
6447
6385
|
this.generations.set(task.id, (this.generations.get(task.id) ?? 0) + 1);
|
|
6448
6386
|
}
|
|
6449
6387
|
/** Drop all tracking state (used on stop / loadPlan). */
|
|
@@ -6452,6 +6390,9 @@ ORDEWELL_REJECT: ${reason}
|
|
|
6452
6390
|
this.buffers.clear();
|
|
6453
6391
|
this.checkpointCounts.clear();
|
|
6454
6392
|
this.pausedSessions.clear();
|
|
6393
|
+
for (const timer of this.idleTimers.values()) clearTimeout(timer);
|
|
6394
|
+
this.idleTimers.clear();
|
|
6395
|
+
this.idleSince.clear();
|
|
6455
6396
|
this.generations.clear();
|
|
6456
6397
|
}
|
|
6457
6398
|
decide(task, exitCode) {
|
|
@@ -6857,6 +6798,7 @@ var TaskOrchestrator = class {
|
|
|
6857
6798
|
this.store.markAwaitingUser(taskId);
|
|
6858
6799
|
this.emit("onCheckpoint", { taskId, taskTitle: task.title, summary });
|
|
6859
6800
|
});
|
|
6801
|
+
this.verifier.onIdleChange(() => this.emit("onTaskChanged"));
|
|
6860
6802
|
}
|
|
6861
6803
|
config;
|
|
6862
6804
|
notifications;
|
|
@@ -6881,6 +6823,10 @@ var TaskOrchestrator = class {
|
|
|
6881
6823
|
workspaceRootFn = () => process.cwd();
|
|
6882
6824
|
observers = [];
|
|
6883
6825
|
tddEnabled = () => false;
|
|
6826
|
+
/** Advisory silence timestamp for a task's live runner, or null if not idle. */
|
|
6827
|
+
getIdleSince(taskId) {
|
|
6828
|
+
return this.verifier.getIdleSince(taskId);
|
|
6829
|
+
}
|
|
6884
6830
|
get storeInstance() {
|
|
6885
6831
|
return this.store;
|
|
6886
6832
|
}
|
|
@@ -7268,8 +7214,10 @@ ${output.slice(-3e3)}`);
|
|
|
7268
7214
|
if (ready.length === 0 && this.activeTaskSessions.size === 0) {
|
|
7269
7215
|
const remaining = this.store.allTasks.filter((t) => t.status !== "completed");
|
|
7270
7216
|
console.log(`[TaskOrchestrator] tick(): no work. remaining=${remaining.length}, completed=${this.store.isAllComplete()}`);
|
|
7271
|
-
|
|
7272
|
-
|
|
7217
|
+
const remainingById = new Map(remaining.map((t) => [t.id, t]));
|
|
7218
|
+
for (const { task, parent } of flattenTasksWithParents(this.store.planTasks)) {
|
|
7219
|
+
if (!remainingById.has(task.id)) continue;
|
|
7220
|
+
console.log(` - ${taskOrderLabel(task, parent ?? void 0)}. ${task.title} [${task.type}/${task.status}] prompt=${!!task.prompt}`);
|
|
7273
7221
|
}
|
|
7274
7222
|
if (this.store.isAllComplete()) {
|
|
7275
7223
|
this.running = false;
|
|
@@ -7296,7 +7244,7 @@ ${output.slice(-3e3)}`);
|
|
|
7296
7244
|
try {
|
|
7297
7245
|
const cwd = this.workspaceRootFn();
|
|
7298
7246
|
const runner = this.store.resolveTaskRunner(task);
|
|
7299
|
-
const finalPrompt = composeAugmentedPrompt(task, this.store.
|
|
7247
|
+
const finalPrompt = composeAugmentedPrompt(task, this.store.planTasks, {
|
|
7300
7248
|
planMapEnabled: this.config.planMapEnabled,
|
|
7301
7249
|
tddEnabled: this.tddEnabled()
|
|
7302
7250
|
});
|
|
@@ -7470,7 +7418,7 @@ import * as osMod from "os";
|
|
|
7470
7418
|
import { exec } from "child_process";
|
|
7471
7419
|
import { promisify } from "util";
|
|
7472
7420
|
import * as os2 from "os";
|
|
7473
|
-
import * as
|
|
7421
|
+
import * as path4 from "path";
|
|
7474
7422
|
var execAsync = promisify(exec);
|
|
7475
7423
|
var LOGIN_SHELL_TIMEOUT_MS = 5e3;
|
|
7476
7424
|
var PATH_MARKER = "__ORDEWELL_PATH__";
|
|
@@ -7481,40 +7429,40 @@ var defaultExec = async (command, options) => {
|
|
|
7481
7429
|
function posixBinDirs() {
|
|
7482
7430
|
const home = os2.homedir();
|
|
7483
7431
|
return [
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7432
|
+
path4.join(home, ".local", "bin"),
|
|
7433
|
+
path4.join(home, ".opencode", "bin"),
|
|
7434
|
+
path4.join(home, ".claude", "bin"),
|
|
7435
|
+
path4.join(home, ".bun", "bin"),
|
|
7436
|
+
path4.join(home, ".volta", "bin"),
|
|
7437
|
+
path4.join(home, ".npm-global", "bin"),
|
|
7438
|
+
path4.join(home, "bin"),
|
|
7491
7439
|
"/usr/local/bin",
|
|
7492
7440
|
"/opt/homebrew/bin"
|
|
7493
7441
|
];
|
|
7494
7442
|
}
|
|
7495
7443
|
function windowsBinDirs(env, home) {
|
|
7496
|
-
const
|
|
7497
|
-
const under = (base, ...parts) => base ? [
|
|
7444
|
+
const join19 = (...parts) => path4.win32.join(...parts);
|
|
7445
|
+
const under = (base, ...parts) => base ? [join19(base, ...parts)] : [];
|
|
7498
7446
|
const appData = env.APPDATA;
|
|
7499
7447
|
const localAppData = env.LOCALAPPDATA;
|
|
7500
7448
|
const programData = env.ProgramData;
|
|
7501
7449
|
return [
|
|
7502
7450
|
// Native per-user installers, PowerShell-driven and otherwise.
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7451
|
+
join19(home, ".local", "bin"),
|
|
7452
|
+
join19(home, ".opencode", "bin"),
|
|
7453
|
+
join19(home, ".claude", "bin"),
|
|
7454
|
+
join19(home, ".codex", "bin"),
|
|
7507
7455
|
...under(localAppData, "Programs", "claude"),
|
|
7508
7456
|
// Node package managers. npm's global prefix is `%APPDATA%\npm`; the
|
|
7509
7457
|
// literal spelling is kept as a fallback for a host that does not export
|
|
7510
7458
|
// APPDATA (a service account, a stripped CI container).
|
|
7511
7459
|
...under(appData, "npm"),
|
|
7512
|
-
|
|
7460
|
+
join19(home, "AppData", "Roaming", "npm"),
|
|
7513
7461
|
...under(localAppData, "pnpm"),
|
|
7514
7462
|
...under(localAppData, "Yarn", "bin"),
|
|
7515
|
-
|
|
7463
|
+
join19(home, ".bun", "bin"),
|
|
7516
7464
|
// Windows package managers.
|
|
7517
|
-
|
|
7465
|
+
join19(home, "scoop", "shims"),
|
|
7518
7466
|
...under(env.SCOOP, "shims"),
|
|
7519
7467
|
...under(env.SCOOP_GLOBAL ?? programData, "scoop", "shims"),
|
|
7520
7468
|
...under(programData, "chocolatey", "bin"),
|
|
@@ -7524,7 +7472,7 @@ function windowsBinDirs(env, home) {
|
|
|
7524
7472
|
...under(localAppData, "Microsoft", "WindowsApps"),
|
|
7525
7473
|
// Version managers. Volta's Windows home is not the POSIX `~/.volta`.
|
|
7526
7474
|
...under(localAppData, "Volta", "bin"),
|
|
7527
|
-
|
|
7475
|
+
join19(home, ".volta", "bin")
|
|
7528
7476
|
];
|
|
7529
7477
|
}
|
|
7530
7478
|
function wellKnownBinDirs(deps = {}) {
|
|
@@ -7553,14 +7501,14 @@ function augmentedPath(execImpl = defaultExec) {
|
|
|
7553
7501
|
const segments = [];
|
|
7554
7502
|
const push = (p) => {
|
|
7555
7503
|
if (!p) return;
|
|
7556
|
-
for (const seg of p.split(
|
|
7504
|
+
for (const seg of p.split(path4.delimiter)) {
|
|
7557
7505
|
if (seg && !segments.includes(seg)) segments.push(seg);
|
|
7558
7506
|
}
|
|
7559
7507
|
};
|
|
7560
7508
|
push(process.env.PATH);
|
|
7561
7509
|
push(await loginShellPath(execImpl));
|
|
7562
7510
|
for (const dir of wellKnownBinDirs()) push(dir);
|
|
7563
|
-
return segments.join(
|
|
7511
|
+
return segments.join(path4.delimiter);
|
|
7564
7512
|
})();
|
|
7565
7513
|
}
|
|
7566
7514
|
return cachedPath;
|
|
@@ -7580,6 +7528,50 @@ function withPath(base, resolvedPath, overrides = {}) {
|
|
|
7580
7528
|
return env;
|
|
7581
7529
|
}
|
|
7582
7530
|
|
|
7531
|
+
// src/utils/globalDataDir.ts
|
|
7532
|
+
import * as fs from "fs";
|
|
7533
|
+
import * as path5 from "path";
|
|
7534
|
+
import * as os3 from "os";
|
|
7535
|
+
function globalDataDir() {
|
|
7536
|
+
return path5.join(os3.homedir(), ".ordewell");
|
|
7537
|
+
}
|
|
7538
|
+
function copyDirSync(src, dest) {
|
|
7539
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
7540
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
7541
|
+
for (const entry of entries) {
|
|
7542
|
+
const srcPath = path5.join(src, entry.name);
|
|
7543
|
+
const destPath = path5.join(dest, entry.name);
|
|
7544
|
+
if (entry.isDirectory()) {
|
|
7545
|
+
copyDirSync(srcPath, destPath);
|
|
7546
|
+
} else {
|
|
7547
|
+
fs.copyFileSync(srcPath, destPath);
|
|
7548
|
+
}
|
|
7549
|
+
}
|
|
7550
|
+
}
|
|
7551
|
+
var migrated = false;
|
|
7552
|
+
function migrateOldConfigDir() {
|
|
7553
|
+
if (migrated) return;
|
|
7554
|
+
migrated = true;
|
|
7555
|
+
const oldDir = path5.join(os3.homedir(), ".config", "ordewell");
|
|
7556
|
+
if (!fs.existsSync(oldDir)) return;
|
|
7557
|
+
const newDir = globalDataDir();
|
|
7558
|
+
const liftFile = (name) => {
|
|
7559
|
+
const oldFile = path5.join(oldDir, name);
|
|
7560
|
+
const newFile = path5.join(newDir, name);
|
|
7561
|
+
if (fs.existsSync(oldFile) && !fs.existsSync(newFile)) {
|
|
7562
|
+
fs.mkdirSync(newDir, { recursive: true });
|
|
7563
|
+
fs.copyFileSync(oldFile, newFile);
|
|
7564
|
+
}
|
|
7565
|
+
};
|
|
7566
|
+
liftFile("settings.json");
|
|
7567
|
+
liftFile(".env");
|
|
7568
|
+
const oldPlugins = path5.join(oldDir, "plugins");
|
|
7569
|
+
const newPlugins = path5.join(newDir, "plugins");
|
|
7570
|
+
if (fs.existsSync(oldPlugins) && !fs.existsSync(newPlugins)) {
|
|
7571
|
+
copyDirSync(oldPlugins, newPlugins);
|
|
7572
|
+
}
|
|
7573
|
+
}
|
|
7574
|
+
|
|
7583
7575
|
// src/utils/launch.ts
|
|
7584
7576
|
import * as fsSync2 from "fs";
|
|
7585
7577
|
import * as path6 from "path";
|
|
@@ -7596,9 +7588,11 @@ function buildShellInvocation(command, args) {
|
|
|
7596
7588
|
const inner = [command, ...args].map(posixShellQuote).join(" ");
|
|
7597
7589
|
return { shellPath: "/bin/bash", shellArgs: ["-lc", inner] };
|
|
7598
7590
|
}
|
|
7599
|
-
function wrapWithPty(command, args) {
|
|
7591
|
+
function wrapWithPty(command, args, opts = {}) {
|
|
7600
7592
|
const inner = [command, ...args].map(posixShellQuote).join(" ");
|
|
7601
|
-
|
|
7593
|
+
const watcher = opts.controlChannel ? 'exec 4<&0; ( while read -r C R <&3; do stty cols "$C" rows "$R" <&4; done ) & ' : "";
|
|
7594
|
+
const setup = opts.size ? `stty cols ${opts.size.cols} rows ${opts.size.rows}; ` : "";
|
|
7595
|
+
return { command: "script", args: ["-q", "-e", "-f", "-c", `${watcher}${setup}${inner}`, "/dev/null"] };
|
|
7602
7596
|
}
|
|
7603
7597
|
|
|
7604
7598
|
// src/utils/launch.ts
|
|
@@ -7905,7 +7899,7 @@ function parseCodexModelsCache(content) {
|
|
|
7905
7899
|
}
|
|
7906
7900
|
var GEMINI_MODELS_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
7907
7901
|
function geminiDiscoveryCacheKey(apiKey) {
|
|
7908
|
-
const dir =
|
|
7902
|
+
const dir = globalDataDir();
|
|
7909
7903
|
const hash = apiKey.slice(-8);
|
|
7910
7904
|
return {
|
|
7911
7905
|
key: `gemini-models:${hash}`,
|
|
@@ -8435,12 +8429,14 @@ var ModelDiscovery = class {
|
|
|
8435
8429
|
// src/services/ModelCatalog.ts
|
|
8436
8430
|
import * as fs2 from "fs";
|
|
8437
8431
|
import * as path8 from "path";
|
|
8438
|
-
import * as os3 from "os";
|
|
8439
8432
|
var CACHE_KEY = "model-catalog";
|
|
8440
8433
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
8441
8434
|
function storageKey(baseUrl) {
|
|
8442
8435
|
return `${CACHE_KEY}:${baseUrl}`;
|
|
8443
8436
|
}
|
|
8437
|
+
function globalCacheDir() {
|
|
8438
|
+
return globalDataDir();
|
|
8439
|
+
}
|
|
8444
8440
|
var ModelCatalog = class {
|
|
8445
8441
|
static async fetchModels(apiKey, baseUrl, fetchImpl) {
|
|
8446
8442
|
const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
|
|
@@ -8483,7 +8479,7 @@ var ModelCatalog = class {
|
|
|
8483
8479
|
try {
|
|
8484
8480
|
const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
|
|
8485
8481
|
const key = storageKey(url);
|
|
8486
|
-
const cacheDir =
|
|
8482
|
+
const cacheDir = globalCacheDir();
|
|
8487
8483
|
const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
8488
8484
|
if (fs2.existsSync(cacheFile)) fs2.rmSync(cacheFile);
|
|
8489
8485
|
} catch {
|
|
@@ -8491,7 +8487,7 @@ var ModelCatalog = class {
|
|
|
8491
8487
|
}
|
|
8492
8488
|
static readCache(key) {
|
|
8493
8489
|
try {
|
|
8494
|
-
const cacheDir =
|
|
8490
|
+
const cacheDir = globalCacheDir();
|
|
8495
8491
|
const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
8496
8492
|
if (!fs2.existsSync(cacheFile)) return null;
|
|
8497
8493
|
const raw = fs2.readFileSync(cacheFile, "utf8");
|
|
@@ -8504,7 +8500,7 @@ var ModelCatalog = class {
|
|
|
8504
8500
|
}
|
|
8505
8501
|
static writeCache(key, models) {
|
|
8506
8502
|
try {
|
|
8507
|
-
const cacheDir =
|
|
8503
|
+
const cacheDir = globalCacheDir();
|
|
8508
8504
|
fs2.mkdirSync(cacheDir, { recursive: true });
|
|
8509
8505
|
const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
8510
8506
|
fs2.writeFileSync(
|
|
@@ -9946,14 +9942,14 @@ ${part.text}` : part.text });
|
|
|
9946
9942
|
}
|
|
9947
9943
|
}
|
|
9948
9944
|
}
|
|
9949
|
-
async json(method,
|
|
9950
|
-
const response = await this.deps.fetch(`${this.baseUrl}${
|
|
9945
|
+
async json(method, path20, body, signal) {
|
|
9946
|
+
const response = await this.deps.fetch(`${this.baseUrl}${path20}`, {
|
|
9951
9947
|
method,
|
|
9952
9948
|
headers: { "content-type": "application/json" },
|
|
9953
9949
|
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
9954
9950
|
...signal ? { signal } : {}
|
|
9955
9951
|
});
|
|
9956
|
-
if (!response.ok) throw new Error(`${method} ${
|
|
9952
|
+
if (!response.ok) throw new Error(`${method} ${path20} failed: ${response.status} ${response.statusText}`);
|
|
9957
9953
|
return await response.json().catch(() => null);
|
|
9958
9954
|
}
|
|
9959
9955
|
nativeSessionId() {
|
|
@@ -10108,9 +10104,6 @@ var CliAgentAiService = class {
|
|
|
10108
10104
|
req.runners,
|
|
10109
10105
|
req.runnerModes,
|
|
10110
10106
|
req.autonomousDefault ?? true,
|
|
10111
|
-
req.grillMeEnabled ?? false,
|
|
10112
|
-
req.prdEnabled ?? false,
|
|
10113
|
-
req.reviewEnabled ?? false,
|
|
10114
10107
|
req.verificationEnabled ?? false,
|
|
10115
10108
|
true
|
|
10116
10109
|
);
|
|
@@ -10125,10 +10118,7 @@ var CliAgentAiService = class {
|
|
|
10125
10118
|
startOptions,
|
|
10126
10119
|
runners: req.runners,
|
|
10127
10120
|
runnerModes: req.runnerModes,
|
|
10128
|
-
autonomousDefault: req.autonomousDefault
|
|
10129
|
-
prdEnabled: req.prdEnabled ?? false,
|
|
10130
|
-
prdCaptured: false,
|
|
10131
|
-
prdNudgeSent: false
|
|
10121
|
+
autonomousDefault: req.autonomousDefault
|
|
10132
10122
|
};
|
|
10133
10123
|
const opening = this.openingMessage(req);
|
|
10134
10124
|
return this.runConversation(opening, req.onProgress, req.signal);
|
|
@@ -10156,8 +10146,8 @@ var CliAgentAiService = class {
|
|
|
10156
10146
|
/**
|
|
10157
10147
|
* Drive one user message to a settled planner turn. Same shape as the API
|
|
10158
10148
|
* backend's conversation loop minus the tool rounds — those belong to the
|
|
10159
|
-
* agent now — and with the same
|
|
10160
|
-
*
|
|
10149
|
+
* agent now — and with the same two policies layered on top: the
|
|
10150
|
+
* empty-reply nudge, and a bounded corrective re-emit for botched JSON.
|
|
10161
10151
|
*/
|
|
10162
10152
|
async runConversation(message, onProgress, signal) {
|
|
10163
10153
|
const conversation = this.conversation;
|
|
@@ -10205,7 +10195,6 @@ var CliAgentAiService = class {
|
|
|
10205
10195
|
].join(" ");
|
|
10206
10196
|
continue;
|
|
10207
10197
|
}
|
|
10208
|
-
if (extractPrdBlock(turn.text)) conversation.prdCaptured = true;
|
|
10209
10198
|
const reply = classifyPlannerReply(turn.text, {
|
|
10210
10199
|
runners: conversation.runners,
|
|
10211
10200
|
runnerModes: conversation.runnerModes,
|
|
@@ -10219,16 +10208,6 @@ var CliAgentAiService = class {
|
|
|
10219
10208
|
case "task_query":
|
|
10220
10209
|
return { kind: "task_query", query: reply.query, text: replyText(turn.text), researchLog };
|
|
10221
10210
|
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
10211
|
this.conversation = null;
|
|
10233
10212
|
return { kind: "plan", tasks: reply.tasks, text: replyText(turn.text), researchLog };
|
|
10234
10213
|
case "broken_task_ops":
|
|
@@ -10407,10 +10386,7 @@ var CliAgentAiService = class {
|
|
|
10407
10386
|
oneShotAdapter = await this.startAdapter(startOptions);
|
|
10408
10387
|
this.conversation = {
|
|
10409
10388
|
startOptions,
|
|
10410
|
-
runners: []
|
|
10411
|
-
prdEnabled: false,
|
|
10412
|
-
prdCaptured: false,
|
|
10413
|
-
prdNudgeSent: false
|
|
10389
|
+
runners: []
|
|
10414
10390
|
};
|
|
10415
10391
|
const turn = await this.runTurn(
|
|
10416
10392
|
"Follow the instructions in your system prompt and produce the plan now.",
|
|
@@ -10427,8 +10403,8 @@ var CliAgentAiService = class {
|
|
|
10427
10403
|
this.lastNativeSessionId = previousSessionId;
|
|
10428
10404
|
}
|
|
10429
10405
|
}
|
|
10430
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
10431
|
-
const contextStr = await collectResearchContext(
|
|
10406
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
10407
|
+
const contextStr = await collectResearchContext(fs12, runners);
|
|
10432
10408
|
const researchLog = [
|
|
10433
10409
|
{ id: `up-${Date.now()}`, type: "user_prompt", content: userDescription, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
10434
10410
|
];
|
|
@@ -10751,74 +10727,202 @@ function retargetTaskRunner(task, runner, catalog) {
|
|
|
10751
10727
|
return { assignedRunner: runner, ...runnerAssignment(catalog, task) };
|
|
10752
10728
|
}
|
|
10753
10729
|
|
|
10754
|
-
// src/services/
|
|
10755
|
-
|
|
10756
|
-
|
|
10757
|
-
|
|
10758
|
-
|
|
10759
|
-
|
|
10760
|
-
|
|
10761
|
-
|
|
10762
|
-
|
|
10763
|
-
|
|
10764
|
-
|
|
10765
|
-
|
|
10766
|
-
|
|
10767
|
-
|
|
10768
|
-
|
|
10769
|
-
|
|
10770
|
-
|
|
10771
|
-
|
|
10772
|
-
|
|
10730
|
+
// src/services/SkillsService.ts
|
|
10731
|
+
import * as fs4 from "fs";
|
|
10732
|
+
import * as path11 from "path";
|
|
10733
|
+
|
|
10734
|
+
// src/services/builtinSkills.ts
|
|
10735
|
+
import * as path10 from "path";
|
|
10736
|
+
import { fileURLToPath } from "url";
|
|
10737
|
+
function builtinSkillsDir() {
|
|
10738
|
+
const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path10.dirname(fileURLToPath(
|
|
10739
|
+
// @ts-expect-error - import.meta is only valid under an ESM module setting
|
|
10740
|
+
import.meta.url
|
|
10741
|
+
));
|
|
10742
|
+
return path10.join(dir, "..", "skills");
|
|
10743
|
+
}
|
|
10744
|
+
|
|
10745
|
+
// src/services/SkillsService.ts
|
|
10746
|
+
var BUILTIN_SKILL_NAMES = ["grilling", "to-spec"];
|
|
10747
|
+
var RETIRED_BUILTIN_SKILL_NAMES = ["grill-me"];
|
|
10748
|
+
function parseSkillFile(filePath, name, source) {
|
|
10749
|
+
const raw = fs4.readFileSync(filePath, "utf8");
|
|
10750
|
+
const frontmatter = {};
|
|
10751
|
+
let content = raw;
|
|
10752
|
+
if (raw.startsWith("---\n")) {
|
|
10753
|
+
const end = raw.indexOf("\n---", 4);
|
|
10754
|
+
if (end !== -1) {
|
|
10755
|
+
const fmText = raw.slice(4, end);
|
|
10756
|
+
for (const line of fmText.split("\n")) {
|
|
10757
|
+
const idx = line.indexOf(":");
|
|
10758
|
+
if (idx === -1) continue;
|
|
10759
|
+
const key = line.slice(0, idx).trim();
|
|
10760
|
+
const value = line.slice(idx + 1).trim();
|
|
10761
|
+
if (key === "disable-model-invocation") {
|
|
10762
|
+
frontmatter[key] = value === "true";
|
|
10763
|
+
} else if (key === "name" || key === "description") {
|
|
10764
|
+
frontmatter[key] = value.replace(/^"|"$/g, "");
|
|
10765
|
+
}
|
|
10766
|
+
}
|
|
10767
|
+
content = raw.slice(end + 4).replace(/^\n+/, "");
|
|
10768
|
+
}
|
|
10769
|
+
}
|
|
10770
|
+
const metadata = {
|
|
10771
|
+
name: frontmatter.name ?? name,
|
|
10772
|
+
description: frontmatter.description ?? "",
|
|
10773
|
+
...frontmatter["disable-model-invocation"] !== void 0 ? { disableModelInvocation: frontmatter["disable-model-invocation"] } : {}
|
|
10773
10774
|
};
|
|
10775
|
+
return { name, description: metadata.description, metadata, content, path: filePath, source };
|
|
10774
10776
|
}
|
|
10775
|
-
function
|
|
10776
|
-
|
|
10777
|
-
|
|
10778
|
-
|
|
10779
|
-
|
|
10780
|
-
|
|
10777
|
+
function copyDirSync2(src, dest) {
|
|
10778
|
+
fs4.mkdirSync(dest, { recursive: true });
|
|
10779
|
+
const entries = fs4.readdirSync(src, { withFileTypes: true });
|
|
10780
|
+
for (const entry of entries) {
|
|
10781
|
+
const srcPath = path11.join(src, entry.name);
|
|
10782
|
+
const destPath = path11.join(dest, entry.name);
|
|
10783
|
+
if (entry.isDirectory()) copyDirSync2(srcPath, destPath);
|
|
10784
|
+
else fs4.copyFileSync(srcPath, destPath);
|
|
10785
|
+
}
|
|
10781
10786
|
}
|
|
10782
|
-
function
|
|
10783
|
-
return
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
|
|
10789
|
-
|
|
10790
|
-
|
|
10787
|
+
function readDir(dir, source) {
|
|
10788
|
+
if (!fs4.existsSync(dir)) return [];
|
|
10789
|
+
const entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
10790
|
+
const skills = [];
|
|
10791
|
+
for (const entry of entries) {
|
|
10792
|
+
if (!entry.isDirectory()) continue;
|
|
10793
|
+
const skillFile = path11.join(dir, entry.name, "SKILL.md");
|
|
10794
|
+
if (!fs4.existsSync(skillFile)) continue;
|
|
10795
|
+
const parsed = parseSkillFile(skillFile, entry.name, source);
|
|
10796
|
+
if (parsed) skills.push(parsed);
|
|
10797
|
+
}
|
|
10798
|
+
return skills;
|
|
10791
10799
|
}
|
|
10792
|
-
|
|
10793
|
-
|
|
10794
|
-
|
|
10795
|
-
|
|
10796
|
-
|
|
10797
|
-
|
|
10800
|
+
var SkillsService = class {
|
|
10801
|
+
constructor(workspaceRoot) {
|
|
10802
|
+
this.workspaceRoot = workspaceRoot;
|
|
10803
|
+
}
|
|
10804
|
+
workspaceRoot;
|
|
10805
|
+
localDir() {
|
|
10806
|
+
return this.workspaceRoot ? path11.join(this.workspaceRoot, ".ordewell", "skills") : void 0;
|
|
10807
|
+
}
|
|
10808
|
+
globalDir() {
|
|
10809
|
+
return path11.join(globalDataDir(), "skills");
|
|
10810
|
+
}
|
|
10811
|
+
seed(name) {
|
|
10812
|
+
const dest = path11.join(this.globalDir(), name);
|
|
10813
|
+
if (fs4.existsSync(dest)) return false;
|
|
10814
|
+
let srcDir;
|
|
10815
|
+
try {
|
|
10816
|
+
srcDir = path11.join(builtinSkillsDir(), name);
|
|
10817
|
+
} catch (err) {
|
|
10818
|
+
console.warn(`Could not locate built-in skills dir: ${String(err)}`);
|
|
10819
|
+
return false;
|
|
10820
|
+
}
|
|
10821
|
+
if (!fs4.existsSync(srcDir)) {
|
|
10822
|
+
console.warn(`Built-in skill not found in package: ${name}`);
|
|
10823
|
+
return false;
|
|
10824
|
+
}
|
|
10825
|
+
copyDirSync2(srcDir, dest);
|
|
10826
|
+
return true;
|
|
10827
|
+
}
|
|
10828
|
+
seedBuiltinSkill(name) {
|
|
10829
|
+
return this.seed(name);
|
|
10830
|
+
}
|
|
10831
|
+
/** Removes a retired built-in's seed from the global dir, but only if it's untouched by the user. */
|
|
10832
|
+
isUnmodifiedRetiredSeed(dir, expectedName) {
|
|
10833
|
+
let entries;
|
|
10834
|
+
try {
|
|
10835
|
+
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
10836
|
+
} catch {
|
|
10837
|
+
return false;
|
|
10838
|
+
}
|
|
10839
|
+
if (entries.length !== 1 || entries[0].name !== "SKILL.md" || !entries[0].isFile()) return false;
|
|
10840
|
+
let raw;
|
|
10841
|
+
try {
|
|
10842
|
+
raw = fs4.readFileSync(path11.join(dir, "SKILL.md"), "utf8");
|
|
10843
|
+
} catch {
|
|
10844
|
+
return false;
|
|
10845
|
+
}
|
|
10846
|
+
if (!raw.startsWith("---\n")) return false;
|
|
10847
|
+
const end = raw.indexOf("\n---", 4);
|
|
10848
|
+
if (end === -1) return false;
|
|
10849
|
+
for (const line of raw.slice(4, end).split("\n")) {
|
|
10850
|
+
const idx = line.indexOf(":");
|
|
10851
|
+
if (idx === -1) continue;
|
|
10852
|
+
const key = line.slice(0, idx).trim();
|
|
10853
|
+
if (key === "name") return line.slice(idx + 1).trim().replace(/^"|"$/g, "") === expectedName;
|
|
10854
|
+
}
|
|
10855
|
+
return false;
|
|
10856
|
+
}
|
|
10857
|
+
pruneRetired() {
|
|
10858
|
+
for (const name of RETIRED_BUILTIN_SKILL_NAMES) {
|
|
10859
|
+
const dest = path11.join(this.globalDir(), name);
|
|
10860
|
+
if (!fs4.existsSync(dest)) continue;
|
|
10861
|
+
if (!this.isUnmodifiedRetiredSeed(dest, name)) continue;
|
|
10862
|
+
try {
|
|
10863
|
+
fs4.rmSync(dest, { recursive: true, force: true });
|
|
10864
|
+
} catch (err) {
|
|
10865
|
+
console.warn(`Could not prune retired skill ${name}: ${String(err)}`);
|
|
10866
|
+
}
|
|
10867
|
+
}
|
|
10868
|
+
}
|
|
10869
|
+
findSkill(name) {
|
|
10870
|
+
this.pruneRetired();
|
|
10871
|
+
if (BUILTIN_SKILL_NAMES.includes(name)) this.seed(name);
|
|
10872
|
+
const localDir = this.localDir();
|
|
10873
|
+
if (localDir) {
|
|
10874
|
+
const localFile = path11.join(localDir, name, "SKILL.md");
|
|
10875
|
+
if (fs4.existsSync(localFile)) return parseSkillFile(localFile, name, "local");
|
|
10876
|
+
}
|
|
10877
|
+
const globalFile = path11.join(this.globalDir(), name, "SKILL.md");
|
|
10878
|
+
if (fs4.existsSync(globalFile)) return parseSkillFile(globalFile, name, "global");
|
|
10879
|
+
return void 0;
|
|
10880
|
+
}
|
|
10881
|
+
listSkills() {
|
|
10882
|
+
this.pruneRetired();
|
|
10883
|
+
for (const name of BUILTIN_SKILL_NAMES) this.seed(name);
|
|
10884
|
+
const byName = /* @__PURE__ */ new Map();
|
|
10885
|
+
for (const skill of readDir(this.globalDir(), "global")) {
|
|
10886
|
+
byName.set(skill.name, skill);
|
|
10887
|
+
}
|
|
10888
|
+
const localDir = this.localDir();
|
|
10889
|
+
if (localDir) {
|
|
10890
|
+
for (const skill of readDir(localDir, "local")) {
|
|
10891
|
+
byName.set(skill.name, skill);
|
|
10892
|
+
}
|
|
10893
|
+
}
|
|
10894
|
+
return [...byName.values()];
|
|
10895
|
+
}
|
|
10896
|
+
getSkillContent(name) {
|
|
10897
|
+
return this.findSkill(name)?.content;
|
|
10898
|
+
}
|
|
10899
|
+
};
|
|
10900
|
+
function createSkillsService(workspaceRoot) {
|
|
10901
|
+
return new SkillsService(workspaceRoot);
|
|
10798
10902
|
}
|
|
10799
10903
|
|
|
10800
10904
|
// src/utils/sessionStore.ts
|
|
10801
|
-
import * as
|
|
10802
|
-
import * as
|
|
10905
|
+
import * as fs6 from "fs";
|
|
10906
|
+
import * as path13 from "path";
|
|
10803
10907
|
|
|
10804
10908
|
// src/utils/fsHelpers.ts
|
|
10805
|
-
import * as
|
|
10806
|
-
import * as
|
|
10909
|
+
import * as fs5 from "fs";
|
|
10910
|
+
import * as path12 from "path";
|
|
10807
10911
|
var STATE_DIR = ".ordewell";
|
|
10808
10912
|
function ensureDir(dir) {
|
|
10809
|
-
if (!
|
|
10810
|
-
|
|
10913
|
+
if (!fs5.existsSync(dir)) {
|
|
10914
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
10811
10915
|
}
|
|
10812
10916
|
}
|
|
10813
10917
|
function getStateDir(baseDir) {
|
|
10814
|
-
return
|
|
10918
|
+
return path12.join(baseDir ?? process.cwd(), STATE_DIR);
|
|
10815
10919
|
}
|
|
10816
10920
|
var IGNORE_FILE = ".gitignore";
|
|
10817
10921
|
function ensureStateDirIgnored(baseDir) {
|
|
10818
10922
|
const dir = getStateDir(baseDir);
|
|
10819
10923
|
ensureDir(dir);
|
|
10820
10924
|
try {
|
|
10821
|
-
|
|
10925
|
+
fs5.writeFileSync(path12.join(dir, IGNORE_FILE), "*\n", { flag: "wx" });
|
|
10822
10926
|
} catch (err) {
|
|
10823
10927
|
if (err.code !== "EEXIST") throw err;
|
|
10824
10928
|
}
|
|
@@ -10833,7 +10937,7 @@ function mintSessionId() {
|
|
|
10833
10937
|
// src/utils/sessionStore.ts
|
|
10834
10938
|
var SESSIONS_SUBDIR = "sessions";
|
|
10835
10939
|
function getSessionsDir(baseDir) {
|
|
10836
|
-
return
|
|
10940
|
+
return path13.join(getStateDir(baseDir), SESSIONS_SUBDIR);
|
|
10837
10941
|
}
|
|
10838
10942
|
var REJECTION_MESSAGE = "This session was created with an older version of Ordewell. Please re-plan.";
|
|
10839
10943
|
function hasScalarRunner(parsed) {
|
|
@@ -10868,17 +10972,17 @@ function saveSession(plan, goal, baseDir, id) {
|
|
|
10868
10972
|
plan: JSON.parse(JSON.stringify(plan))
|
|
10869
10973
|
};
|
|
10870
10974
|
const filename = sessionFilename(meta);
|
|
10871
|
-
const filePath =
|
|
10872
|
-
|
|
10975
|
+
const filePath = path13.join(sessionsDir, filename);
|
|
10976
|
+
fs6.writeFileSync(filePath, JSON.stringify(session, null, 2));
|
|
10873
10977
|
return meta;
|
|
10874
10978
|
}
|
|
10875
10979
|
function listSessions(baseDir, logger = defaultLogger) {
|
|
10876
10980
|
const sessionsDir = getSessionsDir(baseDir);
|
|
10877
|
-
if (!
|
|
10878
|
-
const files =
|
|
10981
|
+
if (!fs6.existsSync(sessionsDir)) return [];
|
|
10982
|
+
const files = fs6.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
|
|
10879
10983
|
return files.map((f) => {
|
|
10880
10984
|
try {
|
|
10881
|
-
const raw =
|
|
10985
|
+
const raw = fs6.readFileSync(path13.join(sessionsDir, f), "utf-8");
|
|
10882
10986
|
const parsed = JSON.parse(raw);
|
|
10883
10987
|
if (hasScalarRunner(parsed)) {
|
|
10884
10988
|
logger.warn("sessionStore", `session file ${f}: ${REJECTION_MESSAGE}`);
|
|
@@ -10893,10 +10997,10 @@ function listSessions(baseDir, logger = defaultLogger) {
|
|
|
10893
10997
|
}).filter((m) => m !== null).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
10894
10998
|
}
|
|
10895
10999
|
function findSessionFile(sessionsDir, sessionId, logger) {
|
|
10896
|
-
const files =
|
|
11000
|
+
const files = fs6.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
|
|
10897
11001
|
for (const f of files) {
|
|
10898
11002
|
try {
|
|
10899
|
-
const raw =
|
|
11003
|
+
const raw = fs6.readFileSync(path13.join(sessionsDir, f), "utf-8");
|
|
10900
11004
|
const session = JSON.parse(raw);
|
|
10901
11005
|
if (session.meta.id === sessionId) return f;
|
|
10902
11006
|
} catch (err) {
|
|
@@ -10907,11 +11011,11 @@ function findSessionFile(sessionsDir, sessionId, logger) {
|
|
|
10907
11011
|
}
|
|
10908
11012
|
function loadSession(sessionId, baseDir, logger = defaultLogger) {
|
|
10909
11013
|
const sessionsDir = getSessionsDir(baseDir);
|
|
10910
|
-
if (!
|
|
11014
|
+
if (!fs6.existsSync(sessionsDir)) return null;
|
|
10911
11015
|
try {
|
|
10912
11016
|
const file = findSessionFile(sessionsDir, sessionId, logger);
|
|
10913
11017
|
if (file) {
|
|
10914
|
-
const raw =
|
|
11018
|
+
const raw = fs6.readFileSync(path13.join(sessionsDir, file), "utf-8");
|
|
10915
11019
|
const parsed = JSON.parse(raw);
|
|
10916
11020
|
if (hasScalarRunner(parsed)) {
|
|
10917
11021
|
logger.warn("sessionStore", `session file ${file}: ${REJECTION_MESSAGE}`);
|
|
@@ -10941,11 +11045,11 @@ function getLatestSession(baseDir, logger = defaultLogger) {
|
|
|
10941
11045
|
}
|
|
10942
11046
|
function deleteSession(sessionId, baseDir, logger = defaultLogger) {
|
|
10943
11047
|
const sessionsDir = getSessionsDir(baseDir);
|
|
10944
|
-
if (!
|
|
11048
|
+
if (!fs6.existsSync(sessionsDir)) return false;
|
|
10945
11049
|
try {
|
|
10946
11050
|
const file = findSessionFile(sessionsDir, sessionId, logger);
|
|
10947
11051
|
if (file) {
|
|
10948
|
-
|
|
11052
|
+
fs6.unlinkSync(path13.join(sessionsDir, file));
|
|
10949
11053
|
return true;
|
|
10950
11054
|
}
|
|
10951
11055
|
} catch (err) {
|
|
@@ -10954,6 +11058,29 @@ function deleteSession(sessionId, baseDir, logger = defaultLogger) {
|
|
|
10954
11058
|
return false;
|
|
10955
11059
|
}
|
|
10956
11060
|
|
|
11061
|
+
// src/utils/prdStore.ts
|
|
11062
|
+
import * as fs7 from "fs";
|
|
11063
|
+
import * as path14 from "path";
|
|
11064
|
+
var PRD_BLOCK_RE = /<!--\s*ORDEWELL_PRD_START\s+slug="([^"]+)"\s*-->\s*([\s\S]*?)\s*<!--\s*ORDEWELL_PRD_END\s*-->/;
|
|
11065
|
+
function extractPrdBlock(text) {
|
|
11066
|
+
const match = PRD_BLOCK_RE.exec(text);
|
|
11067
|
+
if (!match) return null;
|
|
11068
|
+
const slug2 = sanitizeSlug(match[1]);
|
|
11069
|
+
const markdown = match[2].trim();
|
|
11070
|
+
if (!slug2 || !markdown) return null;
|
|
11071
|
+
return { slug: slug2, markdown };
|
|
11072
|
+
}
|
|
11073
|
+
function sanitizeSlug(raw) {
|
|
11074
|
+
return raw.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
11075
|
+
}
|
|
11076
|
+
function savePrdMarkdown(workspace, slug2, markdown) {
|
|
11077
|
+
const dir = path14.join(workspace, ".scratch", sanitizeSlug(slug2));
|
|
11078
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
11079
|
+
const file = path14.join(dir, "PRD.md");
|
|
11080
|
+
fs7.writeFileSync(file, markdown.endsWith("\n") ? markdown : markdown + "\n", "utf8");
|
|
11081
|
+
return file;
|
|
11082
|
+
}
|
|
11083
|
+
|
|
10957
11084
|
// src/services/createSession.ts
|
|
10958
11085
|
var CATALOG_MODEL_CAP = 100;
|
|
10959
11086
|
var MAX_TASK_QUERIES = 3;
|
|
@@ -10970,6 +11097,17 @@ var PlanEditError = class extends Error {
|
|
|
10970
11097
|
function sessionRuntimeSettings(settings) {
|
|
10971
11098
|
return { ...plannerRuntimeToggles(settings), modelAllowlist: settings.modelAllowlist };
|
|
10972
11099
|
}
|
|
11100
|
+
function resolveSkillInvocation(text, skillsService) {
|
|
11101
|
+
const match = text.trim().match(/^\/([a-z][a-z0-9_-]*)$/im);
|
|
11102
|
+
if (!match) return text;
|
|
11103
|
+
const skillName = match[1].toLowerCase();
|
|
11104
|
+
const skill = skillsService.findSkill(skillName);
|
|
11105
|
+
if (!skill) {
|
|
11106
|
+
const available = typeof skillsService.listSkills === "function" ? skillsService.listSkills().map((s) => s.name).join(", ") : "";
|
|
11107
|
+
return `Unknown skill: ${skillName}. Available skills: ${available}`;
|
|
11108
|
+
}
|
|
11109
|
+
return skill.content;
|
|
11110
|
+
}
|
|
10973
11111
|
var Session = class {
|
|
10974
11112
|
/** Injected by a test; when present it is the service, forever. */
|
|
10975
11113
|
pinnedAiService;
|
|
@@ -10997,6 +11135,7 @@ var Session = class {
|
|
|
10997
11135
|
unsubObserver = null;
|
|
10998
11136
|
hostSessionId;
|
|
10999
11137
|
currentSessionId;
|
|
11138
|
+
skillsService;
|
|
11000
11139
|
constructor(deps) {
|
|
11001
11140
|
this.config = deps.config;
|
|
11002
11141
|
this.registry = deps.registry;
|
|
@@ -11015,6 +11154,7 @@ var Session = class {
|
|
|
11015
11154
|
this.settingsFn = deps.settings;
|
|
11016
11155
|
this.hostSessionId = deps.sessionId;
|
|
11017
11156
|
this.currentSessionId = deps.sessionId ?? mintSessionId();
|
|
11157
|
+
this.skillsService = deps.skillsService ?? new SkillsService(this.workspaceRootFn());
|
|
11018
11158
|
this.approvals = new PendingApprovals({
|
|
11019
11159
|
onRequest: ({ id, request }) => this.broadcast({
|
|
11020
11160
|
type: "approval_request",
|
|
@@ -11109,7 +11249,7 @@ var Session = class {
|
|
|
11109
11249
|
onTaskChanged: () => {
|
|
11110
11250
|
if (!this.plan) return;
|
|
11111
11251
|
const tasks = this.store.allTasks;
|
|
11112
|
-
this.broadcast({ type: "status_update", tasks: tasks.map(serializeTaskStatus) });
|
|
11252
|
+
this.broadcast({ type: "status_update", tasks: tasks.map((t) => serializeTaskStatus(t, this.orchestrator.getIdleSince(t.id))) });
|
|
11113
11253
|
},
|
|
11114
11254
|
onQueueReady: () => {
|
|
11115
11255
|
this.broadcast({ type: "queue_ready" });
|
|
@@ -11127,7 +11267,7 @@ var Session = class {
|
|
|
11127
11267
|
onTick: () => {
|
|
11128
11268
|
if (!this.plan) return;
|
|
11129
11269
|
const tasks = this.store.allTasks;
|
|
11130
|
-
this.broadcast({ type: "status_update", tasks: tasks.map(serializeTaskStatus) });
|
|
11270
|
+
this.broadcast({ type: "status_update", tasks: tasks.map((t) => serializeTaskStatus(t, this.orchestrator.getIdleSince(t.id))) });
|
|
11131
11271
|
},
|
|
11132
11272
|
onExecutionComplete: () => {
|
|
11133
11273
|
if (!this.plan) return;
|
|
@@ -11339,7 +11479,7 @@ var Session = class {
|
|
|
11339
11479
|
*/
|
|
11340
11480
|
async startPlanning(goal, runners, options) {
|
|
11341
11481
|
this.plan = null;
|
|
11342
|
-
this.goal = goal;
|
|
11482
|
+
this.goal = this.resolveSkillInvocation(goal);
|
|
11343
11483
|
this.remintSessionId();
|
|
11344
11484
|
this.beginFreshPlan();
|
|
11345
11485
|
const enabled = this.config.enabledRunners;
|
|
@@ -11348,7 +11488,7 @@ var Session = class {
|
|
|
11348
11488
|
const modelsByRunner = await this.modelResolver.modelsForRunners(chosenRunners);
|
|
11349
11489
|
this.modelsCache = modelsByRunner;
|
|
11350
11490
|
const runnerModes = this.runnerModesFor(chosenRunners);
|
|
11351
|
-
const {
|
|
11491
|
+
const { verificationEnabled, modelAllowlist } = this.settingsFn();
|
|
11352
11492
|
this.currentAllowlist = modelAllowlist ?? {};
|
|
11353
11493
|
const filteredModels = filterModelsForPrompt(modelsByRunner, this.currentAllowlist);
|
|
11354
11494
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -11358,22 +11498,18 @@ var Session = class {
|
|
|
11358
11498
|
status: "draft",
|
|
11359
11499
|
runners: chosenRunners,
|
|
11360
11500
|
lastUpdated: now,
|
|
11361
|
-
researchLog: [{ id: `up-${Date.now()}`, type: "user_prompt", content: goal, timestamp: now }],
|
|
11362
|
-
conversationHistory: [{ role: "user", content: goal, timestamp: now }]
|
|
11501
|
+
researchLog: [{ id: `up-${Date.now()}`, type: "user_prompt", content: this.goal, timestamp: now }],
|
|
11502
|
+
conversationHistory: [{ role: "user", content: this.goal, timestamp: now }]
|
|
11363
11503
|
};
|
|
11364
11504
|
const releaseAbort = this.denyApprovalsOnAbort(options?.signal);
|
|
11365
11505
|
try {
|
|
11366
11506
|
const turn = await this.aiService.startConversation({
|
|
11367
|
-
goal,
|
|
11507
|
+
goal: this.goal,
|
|
11368
11508
|
runners: chosenRunners,
|
|
11369
11509
|
modelsByRunner: filteredModels,
|
|
11370
11510
|
runnerModes,
|
|
11371
11511
|
autonomousDefault: this.config.autonomousMode,
|
|
11372
|
-
grillMeEnabled: grillMeEnabled ?? false,
|
|
11373
|
-
prdEnabled: prdEnabled ?? false,
|
|
11374
|
-
reviewEnabled: reviewEnabled ?? false,
|
|
11375
11512
|
verificationEnabled: verificationEnabled ?? false,
|
|
11376
|
-
researchSubagentsEnabled: researchSubagentsEnabled ?? false,
|
|
11377
11513
|
fs: this.fsAdapter,
|
|
11378
11514
|
fetcher: this.fetcher,
|
|
11379
11515
|
onProgress: (p) => this.translateProgress(p),
|
|
@@ -11385,21 +11521,22 @@ var Session = class {
|
|
|
11385
11521
|
}
|
|
11386
11522
|
}
|
|
11387
11523
|
/**
|
|
11388
|
-
* Every subsequent user reply in the planning conversation —
|
|
11389
|
-
* answers,
|
|
11524
|
+
* Every subsequent user reply in the planning conversation — clarifying
|
|
11525
|
+
* answers, outline confirm. One branch, no phase ladder.
|
|
11390
11526
|
*/
|
|
11391
11527
|
async continueConversation(userMessage, options) {
|
|
11392
11528
|
if (!this.plan) throw new Error("No planning conversation to continue");
|
|
11393
11529
|
const priorHistory = this.plan.conversationHistory ?? [];
|
|
11394
11530
|
const priorResearchLog = this.plan.researchLog ?? [];
|
|
11395
11531
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11396
|
-
this.
|
|
11532
|
+
const resolved = this.resolveSkillInvocation(userMessage);
|
|
11533
|
+
this.appendTranscript("user", resolved, now);
|
|
11397
11534
|
const historyAfterOwnAppend = this.plan.conversationHistory;
|
|
11398
|
-
this.plan.researchLog = [...this.plan.researchLog ?? [], { id: `up-${Date.now()}`, type: "user_prompt", content:
|
|
11535
|
+
this.plan.researchLog = [...this.plan.researchLog ?? [], { id: `up-${Date.now()}`, type: "user_prompt", content: resolved, timestamp: now }];
|
|
11399
11536
|
const contextBlock = [this.catalogBlock(), this.planContextBlock()].filter(Boolean).join("\n\n");
|
|
11400
11537
|
const outgoing = contextBlock ? `${contextBlock}
|
|
11401
11538
|
|
|
11402
|
-
${
|
|
11539
|
+
${resolved}` : resolved;
|
|
11403
11540
|
const aiService = this.aiService;
|
|
11404
11541
|
const canContinueLive = aiService.hasActiveConversation() && (aiService.conversationMatchesConfig?.() ?? true);
|
|
11405
11542
|
const releaseAbort = this.denyApprovalsOnAbort(options?.signal);
|
|
@@ -11539,8 +11676,8 @@ The plan is unchanged. Rephrase the request, or adjust the tasks manually.`,
|
|
|
11539
11676
|
* The catalog the planner may actually draw from — model ids and task-mode
|
|
11540
11677
|
* ids, per selected runner — emitted on EVERY turn, before any plan exists
|
|
11541
11678
|
* or after. The system prompt shows this once at conversation start; a long
|
|
11542
|
-
*
|
|
11543
|
-
*
|
|
11679
|
+
* clarifying conversation outlives that single showing and the planner
|
|
11680
|
+
* starts misquoting it, so this re-states it per turn instead.
|
|
11544
11681
|
*
|
|
11545
11682
|
* Built from the allowlist-filtered view (`filterModelsForPrompt`), the same
|
|
11546
11683
|
* one the system prompt used — a restricted allowlist stays a hard bound on
|
|
@@ -11671,7 +11808,7 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
|
|
|
11671
11808
|
const modelsByRunner = await this.modelResolver.modelsForRunners(runners);
|
|
11672
11809
|
this.modelsCache = modelsByRunner;
|
|
11673
11810
|
const runnerModes = this.runnerModesFor(runners);
|
|
11674
|
-
const {
|
|
11811
|
+
const { verificationEnabled, modelAllowlist } = this.settingsFn();
|
|
11675
11812
|
this.currentAllowlist = modelAllowlist ?? {};
|
|
11676
11813
|
const filteredModels = filterModelsForPrompt(modelsByRunner, this.currentAllowlist);
|
|
11677
11814
|
const goal = this.goal || priorHistory.find((m) => m.role === "user")?.content || userMessage;
|
|
@@ -11681,11 +11818,7 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
|
|
|
11681
11818
|
modelsByRunner: filteredModels,
|
|
11682
11819
|
runnerModes,
|
|
11683
11820
|
autonomousDefault: this.config.autonomousMode,
|
|
11684
|
-
grillMeEnabled: grillMeEnabled ?? false,
|
|
11685
|
-
prdEnabled: prdEnabled ?? false,
|
|
11686
|
-
reviewEnabled: reviewEnabled ?? false,
|
|
11687
11821
|
verificationEnabled: verificationEnabled ?? false,
|
|
11688
|
-
researchSubagentsEnabled: researchSubagentsEnabled ?? false,
|
|
11689
11822
|
fs: this.fsAdapter,
|
|
11690
11823
|
fetcher: this.fetcher,
|
|
11691
11824
|
onProgress: (p) => this.translateProgress(p),
|
|
@@ -11698,6 +11831,17 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
|
|
|
11698
11831
|
get isConversationActive() {
|
|
11699
11832
|
return this.aiService.hasActiveConversation();
|
|
11700
11833
|
}
|
|
11834
|
+
/**
|
|
11835
|
+
* Intercept a skill invocation (/skill-name) and substitute the skill's
|
|
11836
|
+
* markdown content BEFORE the message reaches the planner. Prevents runners
|
|
11837
|
+
* (Claude Code, OpenCode) from trying to resolve the skill in their own
|
|
11838
|
+
* directory instead of .ordewell/skills/. An unknown skill is surfaced to
|
|
11839
|
+
* the planner as a notice naming what IS available, rather than passing a
|
|
11840
|
+
* bare /unknown through to be mis-resolved.
|
|
11841
|
+
*/
|
|
11842
|
+
resolveSkillInvocation(text) {
|
|
11843
|
+
return resolveSkillInvocation(text, this.skillsService);
|
|
11844
|
+
}
|
|
11701
11845
|
/** Append one dialogue entry to the persisted transcript. Callers persist via the mutatePlan ritual. */
|
|
11702
11846
|
appendTranscript(role, content, timestamp, kind) {
|
|
11703
11847
|
if (!this.plan) return;
|
|
@@ -12444,17 +12588,25 @@ var HeadlessSession = class extends AbstractTerminalSession {
|
|
|
12444
12588
|
spawnImpl;
|
|
12445
12589
|
process = null;
|
|
12446
12590
|
outputBuffer = "";
|
|
12591
|
+
controlStream = null;
|
|
12447
12592
|
get isStarted() {
|
|
12448
12593
|
return this.process !== null;
|
|
12449
12594
|
}
|
|
12450
|
-
start(launch, cwd, resolvedPath, env) {
|
|
12595
|
+
start(launch, cwd, resolvedPath, env, options) {
|
|
12451
12596
|
if (this.exited || this.process) return;
|
|
12597
|
+
const stdio = ["pipe", "pipe", "pipe"];
|
|
12598
|
+
if (options?.controlChannel) stdio.push("pipe");
|
|
12452
12599
|
this.process = this.spawnImpl(launch.file, launch.args, {
|
|
12453
12600
|
env: withPath(process.env, resolvedPath, env),
|
|
12454
|
-
stdio
|
|
12601
|
+
stdio,
|
|
12455
12602
|
cwd,
|
|
12456
12603
|
windowsVerbatimArguments: launch.verbatim
|
|
12457
12604
|
});
|
|
12605
|
+
if (options?.controlChannel) {
|
|
12606
|
+
this.controlStream = this.process.stdio?.[3] ?? null;
|
|
12607
|
+
this.controlStream?.on("error", () => {
|
|
12608
|
+
});
|
|
12609
|
+
}
|
|
12458
12610
|
const onData = (data) => {
|
|
12459
12611
|
const raw = data.toString();
|
|
12460
12612
|
this.outputBuffer += stripAnsi(raw);
|
|
@@ -12487,6 +12639,12 @@ Process error: ${err.message}
|
|
|
12487
12639
|
this.process.stdin.write(text);
|
|
12488
12640
|
}
|
|
12489
12641
|
}
|
|
12642
|
+
/** PTY resize requests from a surface that owns the terminal rendering the wrapper. */
|
|
12643
|
+
writeControl(text) {
|
|
12644
|
+
if (this.controlStream && !this.controlStream.destroyed) {
|
|
12645
|
+
this.controlStream.write(text);
|
|
12646
|
+
}
|
|
12647
|
+
}
|
|
12490
12648
|
};
|
|
12491
12649
|
var HeadlessRunner = class extends AbstractRunner {
|
|
12492
12650
|
spawnImpl;
|
|
@@ -12512,7 +12670,7 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
12512
12670
|
return new HeadlessSession(id, taskId, this.spawnImpl);
|
|
12513
12671
|
}
|
|
12514
12672
|
/** Everything up to, but not including, spawning — so a surface that owns its own child reaches the same decisions. */
|
|
12515
|
-
async prepareLaunch(opts) {
|
|
12673
|
+
async prepareLaunch(opts, ptyOptions) {
|
|
12516
12674
|
const interactive = this.defaultInteractive;
|
|
12517
12675
|
const invocation = buildRunnerInvocation({
|
|
12518
12676
|
runner: opts.runner,
|
|
@@ -12529,7 +12687,7 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
12529
12687
|
const manifest = opts.registry?.get(opts.runner)?.manifest;
|
|
12530
12688
|
const pty = (manifest?.runner.requiresTty === true || interactive) && this.hasScriptCmd();
|
|
12531
12689
|
const resolvedPath = await this.resolvePath();
|
|
12532
|
-
const { command, args } = pty ? wrapWithPty(invocation.command, invocation.args) : { command: invocation.command, args: invocation.args };
|
|
12690
|
+
const { command, args } = pty ? wrapWithPty(invocation.command, invocation.args, ptyOptions) : { command: invocation.command, args: invocation.args };
|
|
12533
12691
|
return {
|
|
12534
12692
|
launch: await planDirectLaunch(command, args, this.launchDeps),
|
|
12535
12693
|
resolvedPath,
|
|
@@ -12553,8 +12711,8 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
12553
12711
|
import { execFile as execFile2 } from "child_process";
|
|
12554
12712
|
import { promisify as promisify4 } from "util";
|
|
12555
12713
|
import { tmpdir } from "os";
|
|
12556
|
-
import { join as
|
|
12557
|
-
import { existsSync as
|
|
12714
|
+
import { join as join13 } from "path";
|
|
12715
|
+
import { existsSync as existsSync8, readFileSync as readFileSync5, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
12558
12716
|
|
|
12559
12717
|
// src/utils/tmux.ts
|
|
12560
12718
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -12659,7 +12817,7 @@ var TmuxSession = class extends AbstractTerminalSession {
|
|
|
12659
12817
|
if (this.exited) return;
|
|
12660
12818
|
let content;
|
|
12661
12819
|
try {
|
|
12662
|
-
content =
|
|
12820
|
+
content = existsSync8(this.logPath) ? readFileSync5(this.logPath, "utf8") : "";
|
|
12663
12821
|
} catch {
|
|
12664
12822
|
return;
|
|
12665
12823
|
}
|
|
@@ -12838,7 +12996,7 @@ var TmuxRunner = class extends AbstractRunner {
|
|
|
12838
12996
|
windowName,
|
|
12839
12997
|
this.execFileImpl,
|
|
12840
12998
|
this.pollIntervalMs,
|
|
12841
|
-
|
|
12999
|
+
join13(this.logDir, `${id}.log`),
|
|
12842
13000
|
this.socket
|
|
12843
13001
|
);
|
|
12844
13002
|
invocation.env = { ...invocation.env, PATH: await this.resolvePath() };
|
|
@@ -12850,22 +13008,21 @@ var TmuxRunner = class extends AbstractRunner {
|
|
|
12850
13008
|
|
|
12851
13009
|
// src/utils/daemonToken.ts
|
|
12852
13010
|
import { randomBytes as randomBytes2, timingSafeEqual } from "crypto";
|
|
12853
|
-
import { closeSync, mkdirSync as
|
|
12854
|
-
import {
|
|
12855
|
-
import { join as join11 } from "path";
|
|
13011
|
+
import { closeSync, mkdirSync as mkdirSync7, openSync, readFileSync as readFileSync6, unlinkSync as unlinkSync3, writeSync } from "fs";
|
|
13012
|
+
import { join as join14 } from "path";
|
|
12856
13013
|
var BEARER_PREFIX = "bearer ";
|
|
12857
13014
|
var DAEMON_TOKEN_SUBPROTOCOL_PREFIX = "ordewell.token.";
|
|
12858
13015
|
var DAEMON_SUBPROTOCOL = "ordewell.v1";
|
|
12859
13016
|
function configDir() {
|
|
12860
|
-
return
|
|
13017
|
+
return globalDataDir();
|
|
12861
13018
|
}
|
|
12862
13019
|
function daemonTokenPath(port) {
|
|
12863
|
-
return
|
|
13020
|
+
return join14(configDir(), `server-${port}.token`);
|
|
12864
13021
|
}
|
|
12865
13022
|
function mintDaemonToken(port) {
|
|
12866
13023
|
const token = randomBytes2(32).toString("base64url");
|
|
12867
13024
|
const file = daemonTokenPath(port);
|
|
12868
|
-
|
|
13025
|
+
mkdirSync7(configDir(), { recursive: true });
|
|
12869
13026
|
try {
|
|
12870
13027
|
unlinkSync3(file);
|
|
12871
13028
|
} catch {
|
|
@@ -12880,7 +13037,7 @@ function mintDaemonToken(port) {
|
|
|
12880
13037
|
}
|
|
12881
13038
|
function readDaemonToken(port) {
|
|
12882
13039
|
try {
|
|
12883
|
-
const token =
|
|
13040
|
+
const token = readFileSync6(daemonTokenPath(port), "utf8").trim();
|
|
12884
13041
|
return token === "" ? void 0 : token;
|
|
12885
13042
|
} catch {
|
|
12886
13043
|
return void 0;
|
|
@@ -12921,19 +13078,18 @@ function tokensMatch(presented, expected) {
|
|
|
12921
13078
|
}
|
|
12922
13079
|
|
|
12923
13080
|
// src/plugins/RunnerRegistry.ts
|
|
12924
|
-
import * as
|
|
12925
|
-
import * as
|
|
12926
|
-
import * as
|
|
13081
|
+
import * as fs9 from "fs";
|
|
13082
|
+
import * as path17 from "path";
|
|
13083
|
+
import * as os4 from "os";
|
|
12927
13084
|
import { execFileSync } from "child_process";
|
|
12928
13085
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
12929
13086
|
|
|
12930
13087
|
// src/plugins/FsPluginStore.ts
|
|
12931
|
-
import * as
|
|
12932
|
-
import * as
|
|
12933
|
-
import * as os4 from "os";
|
|
13088
|
+
import * as fs8 from "fs";
|
|
13089
|
+
import * as path16 from "path";
|
|
12934
13090
|
|
|
12935
13091
|
// src/plugins/pluginNames.ts
|
|
12936
|
-
import * as
|
|
13092
|
+
import * as path15 from "path";
|
|
12937
13093
|
var PLUGIN_NAME_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
12938
13094
|
var MAX_PLUGIN_NAME_LENGTH = 64;
|
|
12939
13095
|
function isPlainPluginName(name) {
|
|
@@ -12948,10 +13104,10 @@ function assertPlainPluginName(name) {
|
|
|
12948
13104
|
}
|
|
12949
13105
|
function resolvePluginInstallDir(pluginsDir, name) {
|
|
12950
13106
|
assertPlainPluginName(name);
|
|
12951
|
-
const root =
|
|
12952
|
-
const dest =
|
|
12953
|
-
const relative3 =
|
|
12954
|
-
if (relative3 !== name || relative3 === "" ||
|
|
13107
|
+
const root = path15.resolve(pluginsDir);
|
|
13108
|
+
const dest = path15.resolve(root, name);
|
|
13109
|
+
const relative3 = path15.relative(root, dest);
|
|
13110
|
+
if (relative3 !== name || relative3 === "" || path15.isAbsolute(relative3)) {
|
|
12955
13111
|
throw new Error(`Plugin install path escapes the plugins directory: ${JSON.stringify(name)}`);
|
|
12956
13112
|
}
|
|
12957
13113
|
return dest;
|
|
@@ -12966,13 +13122,13 @@ function isValidManifest(obj) {
|
|
|
12966
13122
|
|
|
12967
13123
|
// src/plugins/FsPluginStore.ts
|
|
12968
13124
|
function userPluginsDir() {
|
|
12969
|
-
return
|
|
13125
|
+
return path16.join(globalDataDir(), "plugins");
|
|
12970
13126
|
}
|
|
12971
13127
|
function loadManifestFromDir(dir) {
|
|
12972
|
-
const manifestPath =
|
|
12973
|
-
if (!
|
|
13128
|
+
const manifestPath = path16.join(dir, "manifest.json");
|
|
13129
|
+
if (!fs8.existsSync(manifestPath)) return null;
|
|
12974
13130
|
try {
|
|
12975
|
-
const raw =
|
|
13131
|
+
const raw = fs8.readFileSync(manifestPath, "utf8");
|
|
12976
13132
|
const parsed = JSON.parse(raw);
|
|
12977
13133
|
if (!isValidManifest(parsed)) return null;
|
|
12978
13134
|
return parsed;
|
|
@@ -12980,31 +13136,31 @@ function loadManifestFromDir(dir) {
|
|
|
12980
13136
|
return null;
|
|
12981
13137
|
}
|
|
12982
13138
|
}
|
|
12983
|
-
function
|
|
13139
|
+
function copyDirSync3(src, dest) {
|
|
12984
13140
|
ensureDir(dest);
|
|
12985
|
-
const entries =
|
|
13141
|
+
const entries = fs8.readdirSync(src, { withFileTypes: true });
|
|
12986
13142
|
for (const entry of entries) {
|
|
12987
|
-
const srcPath =
|
|
12988
|
-
const destPath =
|
|
13143
|
+
const srcPath = path16.join(src, entry.name);
|
|
13144
|
+
const destPath = path16.join(dest, entry.name);
|
|
12989
13145
|
if (entry.isDirectory()) {
|
|
12990
|
-
|
|
13146
|
+
copyDirSync3(srcPath, destPath);
|
|
12991
13147
|
} else {
|
|
12992
|
-
|
|
13148
|
+
fs8.copyFileSync(srcPath, destPath);
|
|
12993
13149
|
}
|
|
12994
13150
|
}
|
|
12995
13151
|
}
|
|
12996
13152
|
function rmDirSync(dir) {
|
|
12997
|
-
if (!
|
|
12998
|
-
const entries =
|
|
13153
|
+
if (!fs8.existsSync(dir)) return;
|
|
13154
|
+
const entries = fs8.readdirSync(dir, { withFileTypes: true });
|
|
12999
13155
|
for (const entry of entries) {
|
|
13000
|
-
const p =
|
|
13156
|
+
const p = path16.join(dir, entry.name);
|
|
13001
13157
|
if (entry.isDirectory()) {
|
|
13002
13158
|
rmDirSync(p);
|
|
13003
13159
|
} else {
|
|
13004
|
-
|
|
13160
|
+
fs8.unlinkSync(p);
|
|
13005
13161
|
}
|
|
13006
13162
|
}
|
|
13007
|
-
|
|
13163
|
+
fs8.rmdirSync(dir);
|
|
13008
13164
|
}
|
|
13009
13165
|
var FsPluginStore = class {
|
|
13010
13166
|
getUserPluginsDir() {
|
|
@@ -13012,10 +13168,10 @@ var FsPluginStore = class {
|
|
|
13012
13168
|
}
|
|
13013
13169
|
listUserPluginDirs() {
|
|
13014
13170
|
const dir = userPluginsDir();
|
|
13015
|
-
if (!
|
|
13171
|
+
if (!fs8.existsSync(dir)) return [];
|
|
13016
13172
|
let entries;
|
|
13017
13173
|
try {
|
|
13018
|
-
entries =
|
|
13174
|
+
entries = fs8.readdirSync(dir, { withFileTypes: true });
|
|
13019
13175
|
} catch {
|
|
13020
13176
|
return [];
|
|
13021
13177
|
}
|
|
@@ -13023,7 +13179,7 @@ var FsPluginStore = class {
|
|
|
13023
13179
|
}
|
|
13024
13180
|
listDir(dir) {
|
|
13025
13181
|
try {
|
|
13026
|
-
return
|
|
13182
|
+
return fs8.readdirSync(dir);
|
|
13027
13183
|
} catch {
|
|
13028
13184
|
return [];
|
|
13029
13185
|
}
|
|
@@ -13032,7 +13188,7 @@ var FsPluginStore = class {
|
|
|
13032
13188
|
return loadManifestFromDir(pluginDir);
|
|
13033
13189
|
}
|
|
13034
13190
|
copyDir(sourceDir, destDir) {
|
|
13035
|
-
|
|
13191
|
+
copyDirSync3(sourceDir, destDir);
|
|
13036
13192
|
}
|
|
13037
13193
|
removeDir(dir) {
|
|
13038
13194
|
rmDirSync(dir);
|
|
@@ -13041,20 +13197,20 @@ var FsPluginStore = class {
|
|
|
13041
13197
|
ensureDir(dir);
|
|
13042
13198
|
}
|
|
13043
13199
|
writeFile(filePath, content) {
|
|
13044
|
-
|
|
13200
|
+
fs8.writeFileSync(filePath, content);
|
|
13045
13201
|
}
|
|
13046
13202
|
readFile(filePath) {
|
|
13047
13203
|
try {
|
|
13048
|
-
return
|
|
13204
|
+
return fs8.readFileSync(filePath, "utf8");
|
|
13049
13205
|
} catch {
|
|
13050
13206
|
return null;
|
|
13051
13207
|
}
|
|
13052
13208
|
}
|
|
13053
|
-
dirExists(
|
|
13054
|
-
return
|
|
13209
|
+
dirExists(path20) {
|
|
13210
|
+
return fs8.existsSync(path20) && fs8.statSync(path20).isDirectory();
|
|
13055
13211
|
}
|
|
13056
|
-
exists(
|
|
13057
|
-
return
|
|
13212
|
+
exists(path20) {
|
|
13213
|
+
return fs8.existsSync(path20);
|
|
13058
13214
|
}
|
|
13059
13215
|
};
|
|
13060
13216
|
|
|
@@ -13481,7 +13637,7 @@ var RunnerRegistry = class {
|
|
|
13481
13637
|
return entry?.source === "builtin";
|
|
13482
13638
|
}
|
|
13483
13639
|
installFromPath(sourcePath) {
|
|
13484
|
-
const absPath =
|
|
13640
|
+
const absPath = path17.resolve(sourcePath);
|
|
13485
13641
|
if (!this.store.dirExists(absPath)) {
|
|
13486
13642
|
throw new Error(`Plugin source must be a directory: ${absPath}`);
|
|
13487
13643
|
}
|
|
@@ -13493,7 +13649,7 @@ var RunnerRegistry = class {
|
|
|
13493
13649
|
}
|
|
13494
13650
|
installFromGit(url) {
|
|
13495
13651
|
const validated = assertInstallablePluginUrl(url);
|
|
13496
|
-
const tmpDir =
|
|
13652
|
+
const tmpDir = path17.join(os4.tmpdir(), `ordewell-plugin-${randomBytes3(12).toString("hex")}`);
|
|
13497
13653
|
this.store.ensureDir(tmpDir);
|
|
13498
13654
|
try {
|
|
13499
13655
|
try {
|
|
@@ -13506,7 +13662,7 @@ var RunnerRegistry = class {
|
|
|
13506
13662
|
const hasManifest = contents.some((f) => f === "manifest.json");
|
|
13507
13663
|
let sourceDir = tmpDir;
|
|
13508
13664
|
if (!hasManifest) {
|
|
13509
|
-
const onlyEntry = contents.length === 1 ?
|
|
13665
|
+
const onlyEntry = contents.length === 1 ? path17.join(tmpDir, contents[0]) : void 0;
|
|
13510
13666
|
if (!onlyEntry || !this.store.dirExists(onlyEntry)) {
|
|
13511
13667
|
throw new Error(`No manifest.json found in repository: ${validated.toString()}`);
|
|
13512
13668
|
}
|
|
@@ -13554,7 +13710,7 @@ var RunnerRegistry = class {
|
|
|
13554
13710
|
}
|
|
13555
13711
|
createSkeleton(name, outputDir) {
|
|
13556
13712
|
assertPlainPluginName(name);
|
|
13557
|
-
const dir =
|
|
13713
|
+
const dir = path17.resolve(outputDir, name);
|
|
13558
13714
|
this.store.ensureDir(dir);
|
|
13559
13715
|
const manifest = {
|
|
13560
13716
|
name,
|
|
@@ -13586,8 +13742,8 @@ var RunnerRegistry = class {
|
|
|
13586
13742
|
]
|
|
13587
13743
|
}
|
|
13588
13744
|
};
|
|
13589
|
-
this.store.writeFile(
|
|
13590
|
-
const runSh =
|
|
13745
|
+
this.store.writeFile(path17.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
13746
|
+
const runSh = path17.join(dir, "run.sh");
|
|
13591
13747
|
const scriptContent = `#!/usr/bin/env bash
|
|
13592
13748
|
# Custom runner script for ${name}
|
|
13593
13749
|
# Receives:
|
|
@@ -13613,30 +13769,30 @@ echo "Running ${name} with prompt: $prompt"
|
|
|
13613
13769
|
};
|
|
13614
13770
|
function fsChmodSync(fpath, mode) {
|
|
13615
13771
|
try {
|
|
13616
|
-
|
|
13772
|
+
fs9.chmodSync(fpath, mode);
|
|
13617
13773
|
} catch {
|
|
13618
13774
|
}
|
|
13619
13775
|
}
|
|
13620
13776
|
|
|
13621
13777
|
// src/utils/stateStore.ts
|
|
13622
|
-
import * as
|
|
13623
|
-
import * as
|
|
13778
|
+
import * as fs10 from "fs";
|
|
13779
|
+
import * as path18 from "path";
|
|
13624
13780
|
var STATE_FILE = "state.json";
|
|
13625
13781
|
function getStatePath(baseDir) {
|
|
13626
|
-
return
|
|
13782
|
+
return path18.join(getStateDir(baseDir), STATE_FILE);
|
|
13627
13783
|
}
|
|
13628
13784
|
function saveState(plan, baseDir) {
|
|
13629
13785
|
const statePath = getStatePath(baseDir ?? process.cwd());
|
|
13630
|
-
ensureDir(
|
|
13786
|
+
ensureDir(path18.dirname(statePath));
|
|
13631
13787
|
plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
13632
|
-
|
|
13788
|
+
fs10.writeFileSync(statePath, JSON.stringify(plan, null, 2));
|
|
13633
13789
|
}
|
|
13634
13790
|
function loadState(baseDir, logger = defaultLogger) {
|
|
13635
13791
|
const dir = baseDir ?? process.cwd();
|
|
13636
13792
|
const statePath = getStatePath(dir);
|
|
13637
|
-
if (!
|
|
13793
|
+
if (!fs10.existsSync(statePath)) return null;
|
|
13638
13794
|
try {
|
|
13639
|
-
const raw =
|
|
13795
|
+
const raw = fs10.readFileSync(statePath, "utf-8");
|
|
13640
13796
|
const parsed = JSON.parse(raw);
|
|
13641
13797
|
if (!Array.isArray(parsed.runners)) {
|
|
13642
13798
|
logger.warn("stateStore", `rejecting state from ${statePath}: old format (scalar "runner" instead of "runners" array). Please re-plan.`);
|
|
@@ -13651,29 +13807,24 @@ function loadState(baseDir, logger = defaultLogger) {
|
|
|
13651
13807
|
function clearState(baseDir) {
|
|
13652
13808
|
const dir = baseDir ?? process.cwd();
|
|
13653
13809
|
const statePath = getStatePath(dir);
|
|
13654
|
-
if (
|
|
13810
|
+
if (fs10.existsSync(statePath)) fs10.unlinkSync(statePath);
|
|
13655
13811
|
}
|
|
13656
13812
|
function stateExists(baseDir) {
|
|
13657
13813
|
const dir = baseDir ?? process.cwd();
|
|
13658
|
-
return
|
|
13814
|
+
return fs10.existsSync(getStatePath(dir));
|
|
13659
13815
|
}
|
|
13660
13816
|
|
|
13661
13817
|
// src/services/SettingsService.ts
|
|
13662
|
-
import * as
|
|
13663
|
-
import * as
|
|
13664
|
-
import * as os6 from "os";
|
|
13818
|
+
import * as fs11 from "fs";
|
|
13819
|
+
import * as path19 from "path";
|
|
13665
13820
|
var DEFAULTS = {
|
|
13666
|
-
grillMe: { enabled: false },
|
|
13667
13821
|
tdd: { enabled: true },
|
|
13668
|
-
|
|
13669
|
-
review: { enabled: false },
|
|
13670
|
-
verification: { enabled: false },
|
|
13671
|
-
researchSubagents: { enabled: false }
|
|
13822
|
+
verification: { enabled: false }
|
|
13672
13823
|
};
|
|
13673
13824
|
function getSettingsPath() {
|
|
13674
13825
|
const override = process.env.ORDEWELL_SETTINGS_PATH;
|
|
13675
13826
|
if (override && override.trim()) return override.trim();
|
|
13676
|
-
return
|
|
13827
|
+
return path19.join(globalDataDir(), "settings.json");
|
|
13677
13828
|
}
|
|
13678
13829
|
function readPlannerModels(raw) {
|
|
13679
13830
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
@@ -13706,43 +13857,19 @@ var SettingsService = class {
|
|
|
13706
13857
|
}
|
|
13707
13858
|
fileMtimeMs() {
|
|
13708
13859
|
try {
|
|
13709
|
-
return
|
|
13860
|
+
return fs11.statSync(this.filePath).mtimeMs;
|
|
13710
13861
|
} catch {
|
|
13711
13862
|
return null;
|
|
13712
13863
|
}
|
|
13713
13864
|
}
|
|
13714
|
-
getGrillMe() {
|
|
13715
|
-
return this.getAll().grillMe.enabled;
|
|
13716
|
-
}
|
|
13717
13865
|
getTdd() {
|
|
13718
13866
|
return this.getAll().tdd.enabled;
|
|
13719
13867
|
}
|
|
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
13868
|
setTdd(enabled) {
|
|
13729
13869
|
this.getAll();
|
|
13730
13870
|
this.cache.tdd.enabled = enabled;
|
|
13731
13871
|
this.persist();
|
|
13732
13872
|
}
|
|
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
13873
|
getVerification() {
|
|
13747
13874
|
return this.getAll().verification.enabled;
|
|
13748
13875
|
}
|
|
@@ -13751,14 +13878,6 @@ var SettingsService = class {
|
|
|
13751
13878
|
this.cache.verification.enabled = enabled;
|
|
13752
13879
|
this.persist();
|
|
13753
13880
|
}
|
|
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
13881
|
getModelAllowlist(runner) {
|
|
13763
13882
|
return this.getAll().modelAllowlist?.[runner];
|
|
13764
13883
|
}
|
|
@@ -13808,18 +13927,13 @@ var SettingsService = class {
|
|
|
13808
13927
|
this.persist();
|
|
13809
13928
|
}
|
|
13810
13929
|
load() {
|
|
13930
|
+
migrateOldConfigDir();
|
|
13811
13931
|
try {
|
|
13812
|
-
if (
|
|
13813
|
-
const raw = JSON.parse(
|
|
13932
|
+
if (fs11.existsSync(this.filePath)) {
|
|
13933
|
+
const raw = JSON.parse(fs11.readFileSync(this.filePath, "utf-8"));
|
|
13814
13934
|
const settings = {
|
|
13815
|
-
grillMe: { enabled: raw.grillMe?.enabled ?? DEFAULTS.grillMe.enabled },
|
|
13816
13935
|
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 }
|
|
13936
|
+
verification: { enabled: raw.verification?.enabled ?? DEFAULTS.verification.enabled }
|
|
13823
13937
|
};
|
|
13824
13938
|
if (raw.modelAllowlist !== void 0) {
|
|
13825
13939
|
settings.modelAllowlist = raw.modelAllowlist;
|
|
@@ -13835,14 +13949,14 @@ var SettingsService = class {
|
|
|
13835
13949
|
}
|
|
13836
13950
|
} catch {
|
|
13837
13951
|
}
|
|
13838
|
-
return { ...DEFAULTS,
|
|
13952
|
+
return { ...DEFAULTS, tdd: { ...DEFAULTS.tdd }, verification: { ...DEFAULTS.verification } };
|
|
13839
13953
|
}
|
|
13840
13954
|
persist() {
|
|
13841
|
-
const dir =
|
|
13842
|
-
if (!
|
|
13843
|
-
|
|
13955
|
+
const dir = path19.dirname(this.filePath);
|
|
13956
|
+
if (!fs11.existsSync(dir)) {
|
|
13957
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
13844
13958
|
}
|
|
13845
|
-
|
|
13959
|
+
fs11.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2));
|
|
13846
13960
|
this.cachedMtimeMs = this.fileMtimeMs();
|
|
13847
13961
|
}
|
|
13848
13962
|
};
|
|
@@ -13888,9 +14002,11 @@ export {
|
|
|
13888
14002
|
AbstractRunner,
|
|
13889
14003
|
AbstractTerminalSession,
|
|
13890
14004
|
ApprovalPolicy,
|
|
14005
|
+
BUILTIN_SKILL_NAMES,
|
|
13891
14006
|
BaseAiService,
|
|
13892
14007
|
BaseConfig,
|
|
13893
14008
|
BaseFileSystem,
|
|
14009
|
+
CHECKPOINT_TRUNCATE_LENGTH,
|
|
13894
14010
|
CLAUDE_CODE_MANIFEST,
|
|
13895
14011
|
CLI_PROVIDERS,
|
|
13896
14012
|
CMD_EXE_MAX_COMMAND_LINE,
|
|
@@ -13946,6 +14062,7 @@ export {
|
|
|
13946
14062
|
SYMBOL_LANGUAGES,
|
|
13947
14063
|
Session,
|
|
13948
14064
|
SettingsService,
|
|
14065
|
+
SkillsService,
|
|
13949
14066
|
StdioAgentAdapter,
|
|
13950
14067
|
TASK_OPS_ENVELOPE_KEY,
|
|
13951
14068
|
TASK_QUERY_ANSWER_OR_OPS,
|
|
@@ -14011,6 +14128,7 @@ export {
|
|
|
14011
14128
|
configuredProviders,
|
|
14012
14129
|
createAiService,
|
|
14013
14130
|
createEmptyPlan,
|
|
14131
|
+
createSkillsService,
|
|
14014
14132
|
createTask,
|
|
14015
14133
|
daemonTokenPath,
|
|
14016
14134
|
defaultLogger,
|
|
@@ -14037,12 +14155,14 @@ export {
|
|
|
14037
14155
|
filterModelsForPrompt,
|
|
14038
14156
|
filteredBuildModes,
|
|
14039
14157
|
flattenTasks,
|
|
14158
|
+
flattenTasksWithParents,
|
|
14040
14159
|
formatSearchOutput,
|
|
14041
14160
|
generatePlanWithRepair,
|
|
14042
14161
|
getLatestSession,
|
|
14043
14162
|
getProviderMeta,
|
|
14044
14163
|
getSettingsPath,
|
|
14045
14164
|
getStateDir,
|
|
14165
|
+
globalDataDir,
|
|
14046
14166
|
grantScopeFor,
|
|
14047
14167
|
hasTmux,
|
|
14048
14168
|
includeGlobFor,
|
|
@@ -14062,6 +14182,7 @@ export {
|
|
|
14062
14182
|
looksLikePlanAttempt,
|
|
14063
14183
|
mapAgentTool,
|
|
14064
14184
|
migrateLegacyPlan,
|
|
14185
|
+
migrateOldConfigDir,
|
|
14065
14186
|
migratePlanState,
|
|
14066
14187
|
migrateTask,
|
|
14067
14188
|
mintDaemonToken,
|
|
@@ -14096,10 +14217,12 @@ export {
|
|
|
14096
14217
|
resolveArgs,
|
|
14097
14218
|
resolveDefaultMode,
|
|
14098
14219
|
resolveModelShortcut,
|
|
14220
|
+
resolveOrderLabel,
|
|
14099
14221
|
resolvePluginInstallDir,
|
|
14100
14222
|
resolveProvider,
|
|
14101
14223
|
resolveProviderFromPrefix,
|
|
14102
14224
|
resolveResearchShell,
|
|
14225
|
+
resolveSkillInvocation,
|
|
14103
14226
|
resolveTaskMode,
|
|
14104
14227
|
resolveWithin,
|
|
14105
14228
|
retargetTaskRunner,
|
|
@@ -14122,6 +14245,7 @@ export {
|
|
|
14122
14245
|
summarizeOutput,
|
|
14123
14246
|
summarizeToolCall,
|
|
14124
14247
|
taskOpsRejectedPrompt,
|
|
14248
|
+
taskOrderLabel,
|
|
14125
14249
|
taskQuerySignature,
|
|
14126
14250
|
textHasTaskOps,
|
|
14127
14251
|
textHasTaskQuery,
|
|
@@ -14131,6 +14255,7 @@ export {
|
|
|
14131
14255
|
toOrchestratorOptions,
|
|
14132
14256
|
tokenSubprotocols,
|
|
14133
14257
|
tokensMatch,
|
|
14258
|
+
truncateCheckpointSummary,
|
|
14134
14259
|
truncatedPlanReEmitPrompt,
|
|
14135
14260
|
updateTaskInPlan,
|
|
14136
14261
|
validateModifiedPlan,
|