@ordewell/core 0.4.10 → 0.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{ITerminalRunner-CbE6gKLI.d.ts → ITerminalRunner-BTaKZ659.d.mts} +2 -2
- package/dist/{ITerminalRunner-1NAv8wCC.d.mts → ITerminalRunner-DVwORcV9.d.ts} +2 -2
- package/dist/{ModeResolver-F5wnlSz1.d.mts → ModeResolver-CCKYqdZC.d.mts} +1 -1
- package/dist/{ModeResolver-Dx5gzNPC.d.ts → ModeResolver-CzZd-i6T.d.ts} +1 -1
- package/dist/{Task-1NwmUImI.d.mts → Task-cGrfYRXK.d.mts} +8 -1
- package/dist/{Task-1NwmUImI.d.ts → Task-cGrfYRXK.d.ts} +8 -1
- package/dist/{chunk-YI4ZDZEE.mjs → chunk-DNNZGH3I.mjs} +2 -2
- package/dist/chunk-HPNUFBIT.mjs +19 -0
- package/dist/chunk-HPNUFBIT.mjs.map +1 -0
- package/dist/{chunk-IO6PEEN6.mjs → chunk-N6HTBUKG.mjs} +11 -1
- package/dist/chunk-N6HTBUKG.mjs.map +1 -0
- package/dist/{chunk-TUGJXD4G.mjs → chunk-UEJ3SZQ7.mjs} +2 -2
- package/dist/index.d.mts +124 -73
- package/dist/index.d.ts +124 -73
- package/dist/index.js +611 -433
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +590 -442
- package/dist/index.mjs.map +1 -1
- package/dist/order-labels.d.mts +17 -0
- package/dist/order-labels.d.ts +17 -0
- package/dist/order-labels.js +44 -0
- package/dist/order-labels.js.map +1 -0
- package/dist/order-labels.mjs +9 -0
- package/dist/order-labels.mjs.map +1 -0
- package/dist/{parsing-BqbJySeo.d.mts → parsing-BT-dvFcu.d.mts} +2 -2
- package/dist/{parsing-X0Gvuz-q.d.ts → parsing-D87zKtTe.d.ts} +2 -2
- package/dist/parsing.d.mts +3 -3
- package/dist/parsing.d.ts +3 -3
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -2
- package/dist/{plan-utils-B5rVXNVI.d.mts → plan-utils-BHLbBym3.d.mts} +2 -2
- package/dist/{plan-utils-DtsfHnn_.d.ts → plan-utils-akoB6YCL.d.ts} +2 -2
- package/dist/plan-utils.d.mts +3 -3
- package/dist/plan-utils.d.ts +3 -3
- package/dist/plan-utils.mjs +3 -2
- package/dist/testing.d.mts +2 -2
- package/dist/testing.d.ts +2 -2
- package/package.json +12 -2
- package/skills/grilling/SKILL.md +31 -0
- package/skills/to-spec/SKILL.md +64 -0
- package/dist/chunk-IO6PEEN6.mjs.map +0 -1
- /package/dist/{chunk-YI4ZDZEE.mjs.map → chunk-DNNZGH3I.mjs.map} +0 -0
- /package/dist/{chunk-TUGJXD4G.mjs.map → chunk-UEJ3SZQ7.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -36,6 +36,7 @@ __export(index_exports, {
|
|
|
36
36
|
AbstractRunner: () => AbstractRunner,
|
|
37
37
|
AbstractTerminalSession: () => AbstractTerminalSession,
|
|
38
38
|
ApprovalPolicy: () => ApprovalPolicy,
|
|
39
|
+
BUILTIN_SKILL_NAMES: () => BUILTIN_SKILL_NAMES,
|
|
39
40
|
BaseAiService: () => BaseAiService,
|
|
40
41
|
BaseConfig: () => BaseConfig,
|
|
41
42
|
BaseFileSystem: () => BaseFileSystem,
|
|
@@ -94,6 +95,7 @@ __export(index_exports, {
|
|
|
94
95
|
SYMBOL_LANGUAGES: () => SYMBOL_LANGUAGES,
|
|
95
96
|
Session: () => Session,
|
|
96
97
|
SettingsService: () => SettingsService,
|
|
98
|
+
SkillsService: () => SkillsService,
|
|
97
99
|
StdioAgentAdapter: () => StdioAgentAdapter,
|
|
98
100
|
TASK_OPS_ENVELOPE_KEY: () => TASK_OPS_ENVELOPE_KEY,
|
|
99
101
|
TASK_QUERY_ANSWER_OR_OPS: () => TASK_QUERY_ANSWER_OR_OPS,
|
|
@@ -159,6 +161,7 @@ __export(index_exports, {
|
|
|
159
161
|
configuredProviders: () => configuredProviders,
|
|
160
162
|
createAiService: () => createAiService,
|
|
161
163
|
createEmptyPlan: () => createEmptyPlan,
|
|
164
|
+
createSkillsService: () => createSkillsService,
|
|
162
165
|
createTask: () => createTask,
|
|
163
166
|
daemonTokenPath: () => daemonTokenPath,
|
|
164
167
|
defaultLogger: () => defaultLogger,
|
|
@@ -185,12 +188,14 @@ __export(index_exports, {
|
|
|
185
188
|
filterModelsForPrompt: () => filterModelsForPrompt,
|
|
186
189
|
filteredBuildModes: () => filteredBuildModes,
|
|
187
190
|
flattenTasks: () => flattenTasks,
|
|
191
|
+
flattenTasksWithParents: () => flattenTasksWithParents,
|
|
188
192
|
formatSearchOutput: () => formatSearchOutput,
|
|
189
193
|
generatePlanWithRepair: () => generatePlanWithRepair,
|
|
190
194
|
getLatestSession: () => getLatestSession,
|
|
191
195
|
getProviderMeta: () => getProviderMeta,
|
|
192
196
|
getSettingsPath: () => getSettingsPath,
|
|
193
197
|
getStateDir: () => getStateDir,
|
|
198
|
+
globalDataDir: () => globalDataDir,
|
|
194
199
|
grantScopeFor: () => grantScopeFor,
|
|
195
200
|
hasTmux: () => hasTmux,
|
|
196
201
|
includeGlobFor: () => includeGlobFor,
|
|
@@ -210,6 +215,7 @@ __export(index_exports, {
|
|
|
210
215
|
looksLikePlanAttempt: () => looksLikePlanAttempt,
|
|
211
216
|
mapAgentTool: () => mapAgentTool,
|
|
212
217
|
migrateLegacyPlan: () => migrateLegacyPlan,
|
|
218
|
+
migrateOldConfigDir: () => migrateOldConfigDir,
|
|
213
219
|
migratePlanState: () => migratePlanState,
|
|
214
220
|
migrateTask: () => migrateTask,
|
|
215
221
|
mintDaemonToken: () => mintDaemonToken,
|
|
@@ -244,10 +250,12 @@ __export(index_exports, {
|
|
|
244
250
|
resolveArgs: () => resolveArgs,
|
|
245
251
|
resolveDefaultMode: () => resolveDefaultMode,
|
|
246
252
|
resolveModelShortcut: () => resolveModelShortcut,
|
|
253
|
+
resolveOrderLabel: () => resolveOrderLabel,
|
|
247
254
|
resolvePluginInstallDir: () => resolvePluginInstallDir,
|
|
248
255
|
resolveProvider: () => resolveProvider,
|
|
249
256
|
resolveProviderFromPrefix: () => resolveProviderFromPrefix,
|
|
250
257
|
resolveResearchShell: () => resolveResearchShell,
|
|
258
|
+
resolveSkillInvocation: () => resolveSkillInvocation,
|
|
251
259
|
resolveTaskMode: () => resolveTaskMode,
|
|
252
260
|
resolveWithin: () => resolveWithin,
|
|
253
261
|
retargetTaskRunner: () => retargetTaskRunner,
|
|
@@ -270,6 +278,7 @@ __export(index_exports, {
|
|
|
270
278
|
summarizeOutput: () => summarizeOutput,
|
|
271
279
|
summarizeToolCall: () => summarizeToolCall,
|
|
272
280
|
taskOpsRejectedPrompt: () => taskOpsRejectedPrompt,
|
|
281
|
+
taskOrderLabel: () => taskOrderLabel,
|
|
273
282
|
taskQuerySignature: () => taskQuerySignature,
|
|
274
283
|
textHasTaskOps: () => textHasTaskOps,
|
|
275
284
|
textHasTaskQuery: () => textHasTaskQuery,
|
|
@@ -293,6 +302,22 @@ module.exports = __toCommonJS(index_exports);
|
|
|
293
302
|
|
|
294
303
|
// src/models/Task.ts
|
|
295
304
|
var import_uuid = require("uuid");
|
|
305
|
+
|
|
306
|
+
// src/order-labels.ts
|
|
307
|
+
function taskOrderLabel(task, parent) {
|
|
308
|
+
return parent ? `${parent.order}.${task.order}` : String(task.order);
|
|
309
|
+
}
|
|
310
|
+
function resolveOrderLabel(tasks, label) {
|
|
311
|
+
const parts = label.split(".");
|
|
312
|
+
if (parts.length > 2 || parts.some((p) => !/^\d+$/.test(p))) return void 0;
|
|
313
|
+
const [topOrder, childOrder] = parts.map(Number);
|
|
314
|
+
const top = tasks.find((t) => t.order === topOrder);
|
|
315
|
+
if (!top) return void 0;
|
|
316
|
+
if (childOrder === void 0) return top;
|
|
317
|
+
return top.subtasks.find((s) => s.order === childOrder);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// src/models/Task.ts
|
|
296
321
|
function emptyWarnings() {
|
|
297
322
|
return { deletedCompleted: [], changedCompleted: [], deletedInProgress: [], modifiedInProgress: [], brokenDependencies: [] };
|
|
298
323
|
}
|
|
@@ -477,6 +502,15 @@ function createEmptyPlan() {
|
|
|
477
502
|
function flattenTasks(tasks) {
|
|
478
503
|
return tasks.flatMap((t) => [t, ...flattenTasks(t.subtasks ?? [])]);
|
|
479
504
|
}
|
|
505
|
+
function flattenTasksWithParents(tasks) {
|
|
506
|
+
const rows = [];
|
|
507
|
+
const walk = (t, parent) => {
|
|
508
|
+
rows.push({ task: t, parent });
|
|
509
|
+
for (const sub of t.subtasks ?? []) walk(sub, t);
|
|
510
|
+
};
|
|
511
|
+
for (const t of tasks) walk(t, null);
|
|
512
|
+
return rows;
|
|
513
|
+
}
|
|
480
514
|
function migrateTask(task) {
|
|
481
515
|
if (!task.assignedModel) {
|
|
482
516
|
task.assignedModel = void 0;
|
|
@@ -3103,7 +3137,7 @@ function redactSecrets(text) {
|
|
|
3103
3137
|
const { value, tail } = trimTrailingPunctuation(raw);
|
|
3104
3138
|
if (value.length < 8) return full;
|
|
3105
3139
|
if (looksLikeCode(value) || value.startsWith("$")) return full;
|
|
3106
|
-
if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && /\d/.test(value)) {
|
|
3140
|
+
if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && (/\d/.test(value) || value.length >= 12)) {
|
|
3107
3141
|
return `${head}${quote}${marker("credential", value.length)}${quote}${tail}`;
|
|
3108
3142
|
}
|
|
3109
3143
|
if (IDENTIFIER.test(value) || SLUG.test(value)) return full;
|
|
@@ -3142,33 +3176,33 @@ function grepOptions(args) {
|
|
|
3142
3176
|
};
|
|
3143
3177
|
}
|
|
3144
3178
|
var STOPPED_TOOL_RESULT = "Stopped by the user \u2014 this call was not executed, and no further tool calls will run this turn.";
|
|
3145
|
-
async function executeTool(name, args,
|
|
3146
|
-
const outcome = await runTool(name, args,
|
|
3179
|
+
async function executeTool(name, args, fs12, fetcher, signal) {
|
|
3180
|
+
const outcome = await runTool(name, args, fs12, fetcher, signal);
|
|
3147
3181
|
return { ...outcome, output: redactSecrets(outcome.output) };
|
|
3148
3182
|
}
|
|
3149
|
-
async function runTool(name, args,
|
|
3183
|
+
async function runTool(name, args, fs12, fetcher, signal) {
|
|
3150
3184
|
if (signal?.aborted) return { success: false, output: STOPPED_TOOL_RESULT, truncated: false };
|
|
3151
3185
|
switch (name) {
|
|
3152
3186
|
case "read_file": {
|
|
3153
3187
|
const offset = num(args.offset);
|
|
3154
3188
|
const limit = num(args.limit);
|
|
3155
3189
|
const maxBytes = num(args.maxBytes);
|
|
3156
|
-
return
|
|
3190
|
+
return fs12.readFile(String(args.path ?? ""), { offset, limit, maxBytes });
|
|
3157
3191
|
}
|
|
3158
3192
|
case "read_files": {
|
|
3159
3193
|
const paths = Array.isArray(args.paths) ? args.paths.map(String) : [];
|
|
3160
|
-
return
|
|
3194
|
+
return fs12.readFiles(paths);
|
|
3161
3195
|
}
|
|
3162
3196
|
case "glob":
|
|
3163
|
-
return
|
|
3197
|
+
return fs12.glob(String(args.pattern ?? ""), { path: str(args.path), headLimit: num(args.head_limit ?? args.headLimit) });
|
|
3164
3198
|
case "grep":
|
|
3165
|
-
return
|
|
3199
|
+
return fs12.grep(String(args.pattern ?? ""), grepOptions(args));
|
|
3166
3200
|
case "find_symbol":
|
|
3167
|
-
return
|
|
3201
|
+
return fs12.findSymbol(String(args.symbol ?? ""), { language: str(args.language), path: str(args.path) });
|
|
3168
3202
|
case "list_dir":
|
|
3169
|
-
return
|
|
3203
|
+
return fs12.listDir(String(args.path ?? ""), num(args.depth));
|
|
3170
3204
|
case "bash":
|
|
3171
|
-
return
|
|
3205
|
+
return fs12.bash(String(args.command ?? ""), signal);
|
|
3172
3206
|
case "fetch": {
|
|
3173
3207
|
if (!fetcher) return { success: false, output: "Web fetching is not available in this environment.", truncated: false };
|
|
3174
3208
|
const url = String(args.url ?? "");
|
|
@@ -4320,14 +4354,7 @@ function resolveTaskMode(emittedMode, runnerId, runnerModes, autonomousDefault =
|
|
|
4320
4354
|
|
|
4321
4355
|
// src/services/plannerModes.ts
|
|
4322
4356
|
var MODE_TOGGLES = [
|
|
4323
|
-
// Both of these interview the user, and a one-shot run has nobody to ask —
|
|
4324
|
-
// its prompt says so outright. Their absence there is a decision, not a gap.
|
|
4325
|
-
{ id: "grill-me", settingsKey: "grillMe", runtimeKey: "grillMeEnabled", scopes: ["chat"] },
|
|
4326
|
-
{ id: "prd", settingsKey: "prd", runtimeKey: "prdEnabled", scopes: ["chat"] },
|
|
4327
|
-
// Structural: append a final review task. Nothing about it needs a dialogue.
|
|
4328
|
-
{ id: "review", settingsKey: "review", runtimeKey: "reviewEnabled", scopes: ["chat", "one-shot"] },
|
|
4329
4357
|
{ id: "verify", settingsKey: "verification", runtimeKey: "verificationEnabled", scopes: ["chat", "one-shot"] },
|
|
4330
|
-
{ id: "research-subagents", settingsKey: "researchSubagents", runtimeKey: "researchSubagentsEnabled", scopes: ["chat", "one-shot"] },
|
|
4331
4358
|
{ id: "tdd", settingsKey: "tdd", runtimeKey: "tddEnabled", scopes: ["task"] }
|
|
4332
4359
|
];
|
|
4333
4360
|
function plannerRuntimeToggles(settings) {
|
|
@@ -4337,28 +4364,16 @@ function plannerRuntimeToggles(settings) {
|
|
|
4337
4364
|
}
|
|
4338
4365
|
var DEFAULT_PLANNER_MODES = {
|
|
4339
4366
|
autonomousDefault: true,
|
|
4340
|
-
|
|
4341
|
-
prd: false,
|
|
4342
|
-
review: false,
|
|
4343
|
-
verification: false,
|
|
4344
|
-
researchSubagents: false
|
|
4367
|
+
verification: false
|
|
4345
4368
|
};
|
|
4346
4369
|
function plannerModesFrom(settings, autonomousDefault) {
|
|
4347
4370
|
return {
|
|
4348
4371
|
autonomousDefault,
|
|
4349
|
-
|
|
4350
|
-
prd: settings.prdEnabled ?? false,
|
|
4351
|
-
review: settings.reviewEnabled ?? false,
|
|
4352
|
-
verification: settings.verificationEnabled ?? false,
|
|
4353
|
-
researchSubagents: settings.researchSubagentsEnabled ?? false
|
|
4372
|
+
verification: settings.verificationEnabled ?? false
|
|
4354
4373
|
};
|
|
4355
4374
|
}
|
|
4356
4375
|
var MODE_FIELD = {
|
|
4357
|
-
"grill-me": "grillMe",
|
|
4358
|
-
prd: "prd",
|
|
4359
|
-
review: "review",
|
|
4360
4376
|
verify: "verification",
|
|
4361
|
-
"research-subagents": "researchSubagents",
|
|
4362
4377
|
tdd: null
|
|
4363
4378
|
};
|
|
4364
4379
|
function modesFor(scope, modes) {
|
|
@@ -4686,7 +4701,7 @@ function renderTaskQueryAnswer(query, tasks, catalog) {
|
|
|
4686
4701
|
}
|
|
4687
4702
|
|
|
4688
4703
|
// src/services/PlanPrompts.ts
|
|
4689
|
-
function buildResearchToolsPrompt(
|
|
4704
|
+
function buildResearchToolsPrompt() {
|
|
4690
4705
|
const lines = [
|
|
4691
4706
|
"You have access to the following tools to explore the workspace:",
|
|
4692
4707
|
"",
|
|
@@ -4702,15 +4717,11 @@ function buildResearchToolsPrompt(subagentsEnabled = false) {
|
|
|
4702
4717
|
"- glob: for finding files by name/path pattern",
|
|
4703
4718
|
"- grep: for searching inside file contents",
|
|
4704
4719
|
"",
|
|
4705
|
-
"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."
|
|
4720
|
+
"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.",
|
|
4721
|
+
"",
|
|
4722
|
+
'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.',
|
|
4723
|
+
"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."
|
|
4706
4724
|
];
|
|
4707
|
-
if (subagentsEnabled) {
|
|
4708
|
-
lines.push(
|
|
4709
|
-
"",
|
|
4710
|
-
'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.',
|
|
4711
|
-
"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."
|
|
4712
|
-
);
|
|
4713
|
-
}
|
|
4714
4725
|
return lines.join("\n");
|
|
4715
4726
|
}
|
|
4716
4727
|
function buildSubagentSystemPrompt() {
|
|
@@ -4748,11 +4759,11 @@ function buildModeExamplesForRunners(runners) {
|
|
|
4748
4759
|
return `${r}: default|plan`;
|
|
4749
4760
|
}).join(", ");
|
|
4750
4761
|
}
|
|
4751
|
-
function verificationModeBlock(
|
|
4762
|
+
function verificationModeBlock() {
|
|
4752
4763
|
return [
|
|
4753
4764
|
"",
|
|
4754
4765
|
"VERIFICATION MODE:",
|
|
4755
|
-
|
|
4766
|
+
"Add a FINAL verification task to the end of the plan (highest order number).",
|
|
4756
4767
|
'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.',
|
|
4757
4768
|
"This task must:",
|
|
4758
4769
|
'- Have type "ai" and autonomy "AFK" \u2014 it needs no human input',
|
|
@@ -4788,35 +4799,14 @@ function researchPhaseBlock(harnessMode) {
|
|
|
4788
4799
|
"- Research and questions can interleave \u2014 you can explore a file, then ask a question grounded in it, in one response"
|
|
4789
4800
|
].join("\n");
|
|
4790
4801
|
}
|
|
4791
|
-
function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, runnerModes, autonomousDefault = true,
|
|
4802
|
+
function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, runnerModes, autonomousDefault = true, verificationEnabled = false, harnessMode = false) {
|
|
4792
4803
|
const modelsJson = modelsJsonFor(modelsByRunner, runners);
|
|
4793
4804
|
const modeGuide = runnerModes ? buildModeGuide(runnerModes, autonomousDefault) : buildModeGuideForRunners(runners, autonomousDefault);
|
|
4794
4805
|
const modeExamples = runnerModes ? Object.entries(runnerModes).map(([r, ms]) => {
|
|
4795
4806
|
const filtered = filteredBuildModes(ms, autonomousDefault);
|
|
4796
4807
|
return `${r}: ${filtered.map((m) => m.id).join("|")}${ms.some((m) => m.id === "plan") ? "|plan" : ""}`;
|
|
4797
4808
|
}).join(", ") : buildModeExamplesForRunners(runners);
|
|
4798
|
-
const
|
|
4799
|
-
"",
|
|
4800
|
-
"INTERVIEW MODE \u2014 GRILL-ME:",
|
|
4801
|
-
"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.",
|
|
4802
|
-
"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.",
|
|
4803
|
-
"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.",
|
|
4804
|
-
"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."
|
|
4805
|
-
].join("\n") : "";
|
|
4806
|
-
const prdBlock = prdEnabled ? [
|
|
4807
|
-
"",
|
|
4808
|
-
"PRD MODE: Before producing the outline, generate a PRD in two steps.",
|
|
4809
|
-
"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.",
|
|
4810
|
-
"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.",
|
|
4811
|
-
'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.',
|
|
4812
|
-
"When writing the full PRD, wrap it EXACTLY with these markers so the system can save it automatically:",
|
|
4813
|
-
'<!-- ORDEWELL_PRD_START slug="<feature-slug>" -->',
|
|
4814
|
-
"... full markdown PRD ...",
|
|
4815
|
-
"<!-- ORDEWELL_PRD_END -->",
|
|
4816
|
-
"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."
|
|
4817
|
-
].join("\n") : "";
|
|
4818
|
-
const reviewBlock = reviewEnabled ? reviewModeBlock() : "";
|
|
4819
|
-
const verificationBlock = verificationEnabled ? verificationModeBlock(reviewEnabled) : "";
|
|
4809
|
+
const verificationBlock = verificationEnabled ? verificationModeBlock() : "";
|
|
4820
4810
|
return buildConversationBody(
|
|
4821
4811
|
goal,
|
|
4822
4812
|
context,
|
|
@@ -4825,34 +4815,10 @@ function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, r
|
|
|
4825
4815
|
modeGuide,
|
|
4826
4816
|
modeExamples,
|
|
4827
4817
|
harnessMode,
|
|
4828
|
-
grillMeBlock,
|
|
4829
|
-
prdBlock,
|
|
4830
|
-
reviewBlock,
|
|
4831
4818
|
verificationBlock
|
|
4832
4819
|
);
|
|
4833
4820
|
}
|
|
4834
|
-
function
|
|
4835
|
-
return [
|
|
4836
|
-
"",
|
|
4837
|
-
"REVIEW MODE:",
|
|
4838
|
-
"Add a FINAL review task to the end of the plan (highest order number).",
|
|
4839
|
-
"This task must:",
|
|
4840
|
-
'- Have type "ai" and use the STRONGEST available model',
|
|
4841
|
-
"- Have dependencies on ALL other AI/user tasks in the plan",
|
|
4842
|
-
"- Be assigned appropriately as HITL (human review) since the results need human sign-off",
|
|
4843
|
-
"- Its prompt should instruct the agent to:",
|
|
4844
|
-
" * Review every completed task's outputs and verify the original goal was met",
|
|
4845
|
-
" * Check that each task delivers what its description promised",
|
|
4846
|
-
" * Review the work on two axes, reported SEPARATELY so one cannot mask the other:",
|
|
4847
|
-
" - 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.",
|
|
4848
|
-
" - 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.",
|
|
4849
|
-
" * Produce a structured review report listing what was checked and whether each check passed",
|
|
4850
|
-
" * Flag any gaps, bugs, or incomplete work",
|
|
4851
|
-
" * Output a final PASS/FAIL conclusion with evidence (standards-axis judgement calls alone must not fail the verification)",
|
|
4852
|
-
"- Use the same runner as the other tasks"
|
|
4853
|
-
].join("\n");
|
|
4854
|
-
}
|
|
4855
|
-
function buildConversationBody(goal, context, modelsJson, runners, modeGuide, modeExamples, harnessMode, grillMeBlock, prdBlock, reviewBlock, verificationBlock) {
|
|
4821
|
+
function buildConversationBody(goal, context, modelsJson, runners, modeGuide, modeExamples, harnessMode, verificationBlock) {
|
|
4856
4822
|
return [
|
|
4857
4823
|
"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.",
|
|
4858
4824
|
"",
|
|
@@ -4863,9 +4829,6 @@ function buildConversationBody(goal, context, modelsJson, runners, modeGuide, mo
|
|
|
4863
4829
|
'4. After the user confirms the outline, emit the final task plan as a JSON object with a "tasks" array.',
|
|
4864
4830
|
"",
|
|
4865
4831
|
researchPhaseBlock(harnessMode),
|
|
4866
|
-
grillMeBlock,
|
|
4867
|
-
prdBlock,
|
|
4868
|
-
reviewBlock,
|
|
4869
4832
|
verificationBlock,
|
|
4870
4833
|
"",
|
|
4871
4834
|
"OUTLINE PHASE:",
|
|
@@ -5052,7 +5015,6 @@ function getPlanTemplate() {
|
|
|
5052
5015
|
"",
|
|
5053
5016
|
"TASK MODE:",
|
|
5054
5017
|
"{{MODE_GUIDE}}",
|
|
5055
|
-
"{{REVIEW_BLOCK}}",
|
|
5056
5018
|
"{{VERIFICATION_BLOCK}}",
|
|
5057
5019
|
"",
|
|
5058
5020
|
"CONTEXT:",
|
|
@@ -5099,10 +5061,8 @@ function buildPlanPromptBase(userGoal, context, researchResults, modelsByRunner,
|
|
|
5099
5061
|
const filtered = filteredBuildModes(ms, autonomousDefault);
|
|
5100
5062
|
return `${r}: ${filtered.map((m) => m.id).join("|")}${ms.some((m) => m.id === "plan") ? "|plan" : ""}`;
|
|
5101
5063
|
}).join(", ") : buildModeExamplesForRunners(runners);
|
|
5102
|
-
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("{{
|
|
5103
|
-
${
|
|
5104
|
-
` : "").replace("{{VERIFICATION_BLOCK}}", scoped.verification ? `
|
|
5105
|
-
${verificationModeBlock(scoped.review)}
|
|
5064
|
+
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 ? `
|
|
5065
|
+
${verificationModeBlock()}
|
|
5106
5066
|
` : "").replace("{{MODE_EXAMPLES}}", modeExamples).replace("{{CONTEXT}}", context || "(no additional context)").replace("{{RESEARCH_RESULTS}}", researchResults ? `
|
|
5107
5067
|
|
|
5108
5068
|
RESEARCH FINDINGS:
|
|
@@ -6465,8 +6425,8 @@ var SUBAGENT_EXCLUDED = /* @__PURE__ */ new Set(["fetch", "web_search"]);
|
|
|
6465
6425
|
function subagentToolSpecs() {
|
|
6466
6426
|
return RESEARCH_TOOLS.filter((t) => !SUBAGENT_EXCLUDED.has(t.name));
|
|
6467
6427
|
}
|
|
6468
|
-
function withSpawn(
|
|
6469
|
-
return
|
|
6428
|
+
function withSpawn() {
|
|
6429
|
+
return [...RESEARCH_TOOLS, SPAWN_RESEARCH_AGENT_SPEC];
|
|
6470
6430
|
}
|
|
6471
6431
|
function projectOpenAi(specs) {
|
|
6472
6432
|
return specs.map((t) => ({
|
|
@@ -6487,8 +6447,8 @@ function projectOpenAi(specs) {
|
|
|
6487
6447
|
}
|
|
6488
6448
|
}));
|
|
6489
6449
|
}
|
|
6490
|
-
function toOpenAiTools(
|
|
6491
|
-
return projectOpenAi(withSpawn(
|
|
6450
|
+
function toOpenAiTools() {
|
|
6451
|
+
return projectOpenAi(withSpawn());
|
|
6492
6452
|
}
|
|
6493
6453
|
function toOpenAiSubagentTools() {
|
|
6494
6454
|
return projectOpenAi(subagentToolSpecs());
|
|
@@ -6499,8 +6459,8 @@ var SCHEMA_TYPE = {
|
|
|
6499
6459
|
array: import_generative_ai.SchemaType.ARRAY,
|
|
6500
6460
|
boolean: import_generative_ai.SchemaType.BOOLEAN
|
|
6501
6461
|
};
|
|
6502
|
-
function toGeminiToolDeclarations(
|
|
6503
|
-
return withSpawn(
|
|
6462
|
+
function toGeminiToolDeclarations() {
|
|
6463
|
+
return withSpawn().map((t) => {
|
|
6504
6464
|
const properties = {};
|
|
6505
6465
|
for (const [key, param] of Object.entries(t.properties)) {
|
|
6506
6466
|
properties[key] = {
|
|
@@ -6597,33 +6557,10 @@ function compactResearchResults(resultsText, maxChars = COMPACTION_LIMITS.resear
|
|
|
6597
6557
|
return (blocks[0].lines.length === 0 ? texts.slice(1) : texts).join("\n");
|
|
6598
6558
|
}
|
|
6599
6559
|
|
|
6600
|
-
// src/utils/prdStore.ts
|
|
6601
|
-
var fs = __toESM(require("fs"));
|
|
6602
|
-
var path4 = __toESM(require("path"));
|
|
6603
|
-
var PRD_BLOCK_RE = /<!--\s*ORDEWELL_PRD_START\s+slug="([^"]+)"\s*-->\s*([\s\S]*?)\s*<!--\s*ORDEWELL_PRD_END\s*-->/;
|
|
6604
|
-
function extractPrdBlock(text) {
|
|
6605
|
-
const match = PRD_BLOCK_RE.exec(text);
|
|
6606
|
-
if (!match) return null;
|
|
6607
|
-
const slug2 = sanitizeSlug(match[1]);
|
|
6608
|
-
const markdown = match[2].trim();
|
|
6609
|
-
if (!slug2 || !markdown) return null;
|
|
6610
|
-
return { slug: slug2, markdown };
|
|
6611
|
-
}
|
|
6612
|
-
function sanitizeSlug(raw) {
|
|
6613
|
-
return raw.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
6614
|
-
}
|
|
6615
|
-
function savePrdMarkdown(workspace, slug2, markdown) {
|
|
6616
|
-
const dir = path4.join(workspace, ".scratch", sanitizeSlug(slug2));
|
|
6617
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
6618
|
-
const file = path4.join(dir, "PRD.md");
|
|
6619
|
-
fs.writeFileSync(file, markdown.endsWith("\n") ? markdown : markdown + "\n", "utf8");
|
|
6620
|
-
return file;
|
|
6621
|
-
}
|
|
6622
|
-
|
|
6623
6560
|
// src/services/ContextCollector.ts
|
|
6624
6561
|
var ORDEWELL_CONTEXT_MAX = 8e3;
|
|
6625
|
-
async function collectResearchContext(
|
|
6626
|
-
const collector = new ContextCollector(
|
|
6562
|
+
async function collectResearchContext(fs12, runners) {
|
|
6563
|
+
const collector = new ContextCollector(fs12);
|
|
6627
6564
|
let contextStr = "";
|
|
6628
6565
|
let firstContext = null;
|
|
6629
6566
|
for (const r of runners) {
|
|
@@ -6650,8 +6587,8 @@ ${firstContext.dirStructure}
|
|
|
6650
6587
|
return contextStr;
|
|
6651
6588
|
}
|
|
6652
6589
|
var ContextCollector = class {
|
|
6653
|
-
constructor(
|
|
6654
|
-
this.fs =
|
|
6590
|
+
constructor(fs12, registry) {
|
|
6591
|
+
this.fs = fs12;
|
|
6655
6592
|
this.registry = registry;
|
|
6656
6593
|
}
|
|
6657
6594
|
fs;
|
|
@@ -6732,9 +6669,9 @@ function summarizeToolCall(tool, argsJson, toolLabel) {
|
|
|
6732
6669
|
const scope = args.include ? ` in ${args.include}` : "";
|
|
6733
6670
|
return `grep ${ellipsize(String(args.pattern), 40)}${scope}${mode}`;
|
|
6734
6671
|
}
|
|
6735
|
-
const
|
|
6736
|
-
if (
|
|
6737
|
-
const basename = String(
|
|
6672
|
+
const path20 = args.filePath || args.file_path || args.path;
|
|
6673
|
+
if (path20) {
|
|
6674
|
+
const basename = String(path20).split("/").pop() || String(path20);
|
|
6738
6675
|
return `${display} ${basename}`;
|
|
6739
6676
|
}
|
|
6740
6677
|
if (args.pattern) {
|
|
@@ -6768,22 +6705,22 @@ var ALLOWED_TOOL_NAMES = new Set(subagentToolSpecs().map((t) => t.name));
|
|
|
6768
6705
|
function refused(output) {
|
|
6769
6706
|
return { success: false, output, truncated: false };
|
|
6770
6707
|
}
|
|
6771
|
-
function nonPromptingFs(
|
|
6772
|
-
const withinWorkspace = (p) => resolveWithin(
|
|
6708
|
+
function nonPromptingFs(fs12) {
|
|
6709
|
+
const withinWorkspace = (p) => resolveWithin(fs12.getWorkspaceRoot(), p ?? ".").inside;
|
|
6773
6710
|
const outsideRefusal = (p) => refused(
|
|
6774
6711
|
`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.`
|
|
6775
6712
|
);
|
|
6776
6713
|
return {
|
|
6777
|
-
getWorkspaceRoot: () =>
|
|
6778
|
-
readFile: (p, opts) => withinWorkspace(p) ?
|
|
6714
|
+
getWorkspaceRoot: () => fs12.getWorkspaceRoot(),
|
|
6715
|
+
readFile: (p, opts) => withinWorkspace(p) ? fs12.readFile(p, opts) : Promise.resolve(outsideRefusal(p)),
|
|
6779
6716
|
readFiles: (paths) => {
|
|
6780
6717
|
const escaping = paths.find((p) => !withinWorkspace(p));
|
|
6781
|
-
return escaping ? Promise.resolve(outsideRefusal(escaping)) :
|
|
6718
|
+
return escaping ? Promise.resolve(outsideRefusal(escaping)) : fs12.readFiles(paths);
|
|
6782
6719
|
},
|
|
6783
|
-
glob: (pattern, opts) => withinWorkspace(opts?.path) ?
|
|
6784
|
-
grep: (pattern, opts) => withinWorkspace(opts?.path) ?
|
|
6785
|
-
findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ?
|
|
6786
|
-
listDir: (p, depth) => withinWorkspace(p) ?
|
|
6720
|
+
glob: (pattern, opts) => withinWorkspace(opts?.path) ? fs12.glob(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
6721
|
+
grep: (pattern, opts) => withinWorkspace(opts?.path) ? fs12.grep(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
6722
|
+
findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs12.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
6723
|
+
listDir: (p, depth) => withinWorkspace(p) ? fs12.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
|
|
6787
6724
|
bash: async (command, signal) => {
|
|
6788
6725
|
const dialect = resolveResearchShell().dialect;
|
|
6789
6726
|
const { tier, reason } = classifyCommand(command, { dialect });
|
|
@@ -6794,13 +6731,13 @@ function nonPromptingFs(fs10) {
|
|
|
6794
6731
|
}
|
|
6795
6732
|
const escaping = pathLikeArgs(command, { dialect }).find((p) => !withinWorkspace(p));
|
|
6796
6733
|
if (escaping) return outsideRefusal(escaping);
|
|
6797
|
-
return
|
|
6734
|
+
return fs12.bash(command, signal);
|
|
6798
6735
|
}
|
|
6799
6736
|
};
|
|
6800
6737
|
}
|
|
6801
6738
|
async function runLoop(prompt, deps) {
|
|
6802
6739
|
if (deps.signal?.aborted) return "[research agent aborted before starting]";
|
|
6803
|
-
const
|
|
6740
|
+
const fs12 = nonPromptingFs(deps.fs);
|
|
6804
6741
|
const chat = deps.createChat((delta) => deps.onProgress?.({ type: "thinking", text: delta }));
|
|
6805
6742
|
let turn = await chat.sendMessage(prompt, deps.signal);
|
|
6806
6743
|
for (let step = 0; turn.hasToolCalls && step < SUBAGENT_LIMITS.maxSteps; step++) {
|
|
@@ -6817,7 +6754,7 @@ ${turn.text}`;
|
|
|
6817
6754
|
if (tc.name === "read_file" && !("maxBytes" in args) && !("limit" in args)) args.limit = 2e3;
|
|
6818
6755
|
const toolArgs = JSON.stringify(tc.args);
|
|
6819
6756
|
deps.onProgress?.({ type: "tool_call", tool: tc.name, toolArgs, toolCallId: tc.id });
|
|
6820
|
-
const res = await executeTool(tc.name, args,
|
|
6757
|
+
const res = await executeTool(tc.name, args, fs12, void 0, deps.signal);
|
|
6821
6758
|
const output = res.output.length > SUBAGENT_LIMITS.toolOutputMaxChars ? res.output.slice(0, SUBAGENT_LIMITS.toolOutputMaxChars) + `
|
|
6822
6759
|
[... truncated to ${SUBAGENT_LIMITS.toolOutputMaxChars} chars, total ${res.output.length}]` : res.output;
|
|
6823
6760
|
const stepEntry = {
|
|
@@ -6875,8 +6812,6 @@ var BaseAiService = class _BaseAiService {
|
|
|
6875
6812
|
config;
|
|
6876
6813
|
conversation = null;
|
|
6877
6814
|
activeAbort = null;
|
|
6878
|
-
/** researchSubagents toggle (issue #34): set per operation from the live settings snapshot; off means bit-for-bit sequential behavior. */
|
|
6879
|
-
researchSubagentsEnabled = false;
|
|
6880
6815
|
hasActiveConversation() {
|
|
6881
6816
|
return this.conversation !== null;
|
|
6882
6817
|
}
|
|
@@ -6932,10 +6867,7 @@ var BaseAiService = class _BaseAiService {
|
|
|
6932
6867
|
* steering/failed result and the loop continues sequentially: a subagent can
|
|
6933
6868
|
* never fail a plan or a turn.
|
|
6934
6869
|
*/
|
|
6935
|
-
async executeSpawnAgent(args,
|
|
6936
|
-
if (!this.researchSubagentsEnabled) {
|
|
6937
|
-
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 };
|
|
6938
|
-
}
|
|
6870
|
+
async executeSpawnAgent(args, fs12, onProgress, signal, subagentId) {
|
|
6939
6871
|
const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
|
|
6940
6872
|
if (!prompt) {
|
|
6941
6873
|
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 };
|
|
@@ -6946,20 +6878,20 @@ var BaseAiService = class _BaseAiService {
|
|
|
6946
6878
|
if (!chat) throw new Error("subagent chats are not available for this provider");
|
|
6947
6879
|
return chat;
|
|
6948
6880
|
},
|
|
6949
|
-
fs:
|
|
6881
|
+
fs: fs12,
|
|
6950
6882
|
signal,
|
|
6951
6883
|
onProgress: (progress) => onProgress({ ...progress, subagentId })
|
|
6952
6884
|
});
|
|
6953
6885
|
}
|
|
6954
6886
|
/** Collect project context for the planning phase. Shared with the harness backend. */
|
|
6955
|
-
static async collectResearchContext(
|
|
6956
|
-
return collectResearchContext(
|
|
6887
|
+
static async collectResearchContext(fs12, runners) {
|
|
6888
|
+
return collectResearchContext(fs12, runners);
|
|
6957
6889
|
}
|
|
6958
6890
|
/**
|
|
6959
6891
|
* Execute one round of tool calls and report each through onProgress.
|
|
6960
6892
|
* Returns the results to feed back plus the log entries produced.
|
|
6961
6893
|
*/
|
|
6962
|
-
async executeToolCalls(toolCalls,
|
|
6894
|
+
async executeToolCalls(toolCalls, fs12, onProgress, fetcher, stepIndex, thinking, signal) {
|
|
6963
6895
|
const toolResults = [];
|
|
6964
6896
|
const logEntries = [];
|
|
6965
6897
|
let resultsText = "";
|
|
@@ -6978,18 +6910,18 @@ var BaseAiService = class _BaseAiService {
|
|
|
6978
6910
|
const subagentId = (0, import_uuid3.v4)();
|
|
6979
6911
|
spawnSubagentIds.set(i, subagentId);
|
|
6980
6912
|
onProgress({ type: "tool_call", tool: toolCalls[i].name, toolArgs: JSON.stringify(toolCalls[i].args), subagentId, toolCallId: toolCalls[i].id });
|
|
6981
|
-
precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args },
|
|
6913
|
+
precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args }, fs12, onProgress, signal, subagentId));
|
|
6982
6914
|
}) : Promise.resolve(),
|
|
6983
6915
|
parallelIndexes.length > 1 ? mapWithConcurrency(parallelIndexes, TOOL_ROUND_CONCURRENCY, async (i) => {
|
|
6984
6916
|
onProgress({ type: "tool_call", tool: toolCalls[i].name, toolArgs: JSON.stringify(toolCalls[i].args), toolCallId: toolCalls[i].id });
|
|
6985
|
-
precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i],
|
|
6917
|
+
precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i], fs12, fetcher, signal));
|
|
6986
6918
|
}) : Promise.resolve()
|
|
6987
6919
|
]);
|
|
6988
6920
|
for (const [index, tc] of toolCalls.entries()) {
|
|
6989
6921
|
const toolArgs = normalizedArgs[index];
|
|
6990
6922
|
const preResult = precomputed.get(index);
|
|
6991
6923
|
if (!preResult) onProgress({ type: "tool_call", tool: tc.name, toolArgs: JSON.stringify(tc.args), toolCallId: tc.id });
|
|
6992
|
-
const toolResult = preResult ?? await executeTool(tc.name, toolArgs,
|
|
6924
|
+
const toolResult = preResult ?? await executeTool(tc.name, toolArgs, fs12, fetcher, signal);
|
|
6993
6925
|
const MAX_RESPONSE_CHARS = 5e4;
|
|
6994
6926
|
const truncatedResponse = toolResult.success && (toolResult.truncated || toolResult.output.length > MAX_RESPONSE_CHARS);
|
|
6995
6927
|
const llmOutput = toolResult.output.length > MAX_RESPONSE_CHARS ? toolResult.output.slice(0, MAX_RESPONSE_CHARS) + `
|
|
@@ -7121,7 +7053,6 @@ ${llmOutput}
|
|
|
7121
7053
|
researchLog
|
|
7122
7054
|
};
|
|
7123
7055
|
}
|
|
7124
|
-
if (extractPrdBlock(turn.text)) ctx.prdCaptured = true;
|
|
7125
7056
|
const reply = classifyPlannerReply(turn.text, {
|
|
7126
7057
|
runners: ctx.runners,
|
|
7127
7058
|
runnerModes: ctx.runnerModes,
|
|
@@ -7135,16 +7066,6 @@ ${llmOutput}
|
|
|
7135
7066
|
case "task_query":
|
|
7136
7067
|
return { kind: "task_query", query: reply.query, text: turn.text, researchLog };
|
|
7137
7068
|
case "plan":
|
|
7138
|
-
if (ctx.prdEnabled && !ctx.prdCaptured && !ctx.prdNudgeSent && !signal?.aborted) {
|
|
7139
|
-
ctx.prdNudgeSent = true;
|
|
7140
|
-
pending = [
|
|
7141
|
-
"PRD mode is enabled but no PRD has been produced yet, so the plan was NOT committed.",
|
|
7142
|
-
"In your next reply: first output the full markdown PRD wrapped EXACTLY in",
|
|
7143
|
-
'<!-- ORDEWELL_PRD_START slug="<feature-slug>" --> and <!-- ORDEWELL_PRD_END -->,',
|
|
7144
|
-
"then re-emit the exact same task plan JSON after the PRD block."
|
|
7145
|
-
].join(" ");
|
|
7146
|
-
continue;
|
|
7147
|
-
}
|
|
7148
7069
|
return { kind: "plan", tasks: reply.tasks, text: turn.text, researchLog };
|
|
7149
7070
|
// Botched attempts get a bounded corrective retry — otherwise the
|
|
7150
7071
|
// broken JSON would surface as a prose bubble and the edit or plan
|
|
@@ -7186,7 +7107,7 @@ ${llmOutput}
|
|
|
7186
7107
|
* parsed tasks if a plan was emitted mid-loop, or null if the loop
|
|
7187
7108
|
* exhausted without a valid plan.
|
|
7188
7109
|
*/
|
|
7189
|
-
async runResearchLoop(chat, firstMessage,
|
|
7110
|
+
async runResearchLoop(chat, firstMessage, fs12, onProgress, runners, initialLog, fetcher, userGoal, runnerModes, autonomousDefault, signal) {
|
|
7190
7111
|
const researchLog = initialLog ? [...initialLog] : [];
|
|
7191
7112
|
if (!initialLog && userGoal) {
|
|
7192
7113
|
researchLog.push({
|
|
@@ -7213,7 +7134,7 @@ ${llmOutput}
|
|
|
7213
7134
|
if (!turn.hasToolCalls) break;
|
|
7214
7135
|
const { toolResults, logEntries, resultsText } = await this.executeToolCalls(
|
|
7215
7136
|
turn.toolCalls,
|
|
7216
|
-
|
|
7137
|
+
fs12,
|
|
7217
7138
|
onProgress,
|
|
7218
7139
|
fetcher,
|
|
7219
7140
|
step,
|
|
@@ -7349,9 +7270,6 @@ var GeminiService = class extends BaseAiService {
|
|
|
7349
7270
|
req.runners,
|
|
7350
7271
|
req.runnerModes,
|
|
7351
7272
|
req.autonomousDefault ?? true,
|
|
7352
|
-
req.grillMeEnabled ?? false,
|
|
7353
|
-
req.prdEnabled ?? false,
|
|
7354
|
-
req.reviewEnabled ?? false,
|
|
7355
7273
|
req.verificationEnabled ?? false
|
|
7356
7274
|
);
|
|
7357
7275
|
const seededHistory = [
|
|
@@ -7390,8 +7308,7 @@ ${m.content}` });
|
|
|
7390
7308
|
runners: req.runners,
|
|
7391
7309
|
runnerModes: req.runnerModes,
|
|
7392
7310
|
autonomousDefault: req.autonomousDefault,
|
|
7393
|
-
fetcher: req.fetcher
|
|
7394
|
-
prdEnabled: req.prdEnabled ?? false
|
|
7311
|
+
fetcher: req.fetcher
|
|
7395
7312
|
};
|
|
7396
7313
|
this.conversation = { ctx, setProgress: (onProgress) => {
|
|
7397
7314
|
currentProgress = onProgress;
|
|
@@ -7449,11 +7366,11 @@ ${repairHint}` }] : parts;
|
|
|
7449
7366
|
return this.consumePlanStream(result, onToken);
|
|
7450
7367
|
}
|
|
7451
7368
|
// --- IAiService implementation ---
|
|
7452
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
7369
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
7453
7370
|
this.ensureInit();
|
|
7454
7371
|
const { autonomousDefault } = modes;
|
|
7455
7372
|
const genModel = this.getPlanningModel();
|
|
7456
|
-
const contextStr = await BaseAiService.collectResearchContext(
|
|
7373
|
+
const contextStr = await BaseAiService.collectResearchContext(fs12, runners);
|
|
7457
7374
|
const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
|
|
7458
7375
|
const firstMessage = `${buildResearchToolsPrompt()}
|
|
7459
7376
|
|
|
@@ -7464,17 +7381,17 @@ Explore the workspace to understand the codebase, then generate the plan.`;
|
|
|
7464
7381
|
generationConfig: { temperature: 0.3, topP: 0.95, maxOutputTokens: 16384 }
|
|
7465
7382
|
});
|
|
7466
7383
|
const researchChat = new GeminiResearchChat(chat);
|
|
7467
|
-
const result = await this.runResearchLoop(researchChat, firstMessage,
|
|
7384
|
+
const result = await this.runResearchLoop(researchChat, firstMessage, fs12, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
|
|
7468
7385
|
if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
|
|
7469
7386
|
const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
|
|
7470
7387
|
return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
|
|
7471
7388
|
}
|
|
7472
|
-
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken,
|
|
7389
|
+
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
|
|
7473
7390
|
this.ensureInit();
|
|
7474
7391
|
const { autonomousDefault } = modes;
|
|
7475
7392
|
let ctx = "";
|
|
7476
|
-
if (
|
|
7477
|
-
const collected = await new ContextCollector(
|
|
7393
|
+
if (fs12) {
|
|
7394
|
+
const collected = await new ContextCollector(fs12).collect(runners[0] ?? "claude-code");
|
|
7478
7395
|
if (collected.aiflowContext) ctx = `
|
|
7479
7396
|
<aiflow_context>
|
|
7480
7397
|
${collected.aiflowContext}
|
|
@@ -7490,12 +7407,12 @@ ${collected.aiflowContext}
|
|
|
7490
7407
|
autonomousDefault
|
|
7491
7408
|
);
|
|
7492
7409
|
}
|
|
7493
|
-
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress,
|
|
7410
|
+
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
|
|
7494
7411
|
this.ensureInit();
|
|
7495
7412
|
const { autonomousDefault } = modes;
|
|
7496
7413
|
let aiflowContext;
|
|
7497
|
-
if (
|
|
7498
|
-
const collected = await new ContextCollector(
|
|
7414
|
+
if (fs12) {
|
|
7415
|
+
const collected = await new ContextCollector(fs12).collect(existingPlan.runners[0] ?? "claude-code");
|
|
7499
7416
|
if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
|
|
7500
7417
|
}
|
|
7501
7418
|
const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
|
|
@@ -7678,7 +7595,6 @@ var OpenAiService = class extends BaseAiService {
|
|
|
7678
7595
|
async startConversation(req) {
|
|
7679
7596
|
this.ensureInit();
|
|
7680
7597
|
const client = this.getClient();
|
|
7681
|
-
this.researchSubagentsEnabled = req.researchSubagentsEnabled ?? false;
|
|
7682
7598
|
const contextStr = await BaseAiService.collectResearchContext(req.fs, req.runners);
|
|
7683
7599
|
const systemPrompt = buildConversationSystemPrompt(
|
|
7684
7600
|
req.goal,
|
|
@@ -7687,16 +7603,13 @@ var OpenAiService = class extends BaseAiService {
|
|
|
7687
7603
|
req.runners,
|
|
7688
7604
|
req.runnerModes,
|
|
7689
7605
|
req.autonomousDefault ?? true,
|
|
7690
|
-
req.grillMeEnabled ?? false,
|
|
7691
|
-
req.prdEnabled ?? false,
|
|
7692
|
-
req.reviewEnabled ?? false,
|
|
7693
7606
|
req.verificationEnabled ?? false
|
|
7694
7607
|
);
|
|
7695
7608
|
const userText = req.goal;
|
|
7696
7609
|
const messages = [
|
|
7697
7610
|
{ role: "system", content: `${systemPrompt}
|
|
7698
7611
|
|
|
7699
|
-
${buildResearchToolsPrompt(
|
|
7612
|
+
${buildResearchToolsPrompt()}` }
|
|
7700
7613
|
];
|
|
7701
7614
|
for (const m of req.priorHistory ?? []) {
|
|
7702
7615
|
messages.push({ role: m.role, content: m.content });
|
|
@@ -7706,7 +7619,7 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
|
|
|
7706
7619
|
messages,
|
|
7707
7620
|
client,
|
|
7708
7621
|
this.requireModel("orchestratorModel", this.config.orchestratorModel),
|
|
7709
|
-
toOpenAiTools(
|
|
7622
|
+
toOpenAiTools(),
|
|
7710
7623
|
(delta) => currentProgress({ type: "thinking", text: delta }),
|
|
7711
7624
|
(delta) => currentProgress({ type: "plan_token", planToken: delta })
|
|
7712
7625
|
);
|
|
@@ -7716,8 +7629,7 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
|
|
|
7716
7629
|
runners: req.runners,
|
|
7717
7630
|
runnerModes: req.runnerModes,
|
|
7718
7631
|
autonomousDefault: req.autonomousDefault,
|
|
7719
|
-
fetcher: req.fetcher
|
|
7720
|
-
prdEnabled: req.prdEnabled ?? false
|
|
7632
|
+
fetcher: req.fetcher
|
|
7721
7633
|
};
|
|
7722
7634
|
this.conversation = {
|
|
7723
7635
|
ctx,
|
|
@@ -7735,15 +7647,14 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
|
|
|
7735
7647
|
}
|
|
7736
7648
|
}
|
|
7737
7649
|
// --- IAiService implementation ---
|
|
7738
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
7650
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
7739
7651
|
this.ensureInit();
|
|
7740
7652
|
const client = this.getClient();
|
|
7741
|
-
const { autonomousDefault
|
|
7742
|
-
|
|
7743
|
-
const contextStr = await BaseAiService.collectResearchContext(fs10, runners);
|
|
7653
|
+
const { autonomousDefault } = modes;
|
|
7654
|
+
const contextStr = await BaseAiService.collectResearchContext(fs12, runners);
|
|
7744
7655
|
const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
|
|
7745
7656
|
const userText = `User goal: ${userDescription}`;
|
|
7746
|
-
const firstMessage = `${buildResearchToolsPrompt(
|
|
7657
|
+
const firstMessage = `${buildResearchToolsPrompt()}
|
|
7747
7658
|
|
|
7748
7659
|
Explore the workspace to understand the codebase, then generate the plan.
|
|
7749
7660
|
|
|
@@ -7755,20 +7666,20 @@ ${userText}`;
|
|
|
7755
7666
|
messages,
|
|
7756
7667
|
client,
|
|
7757
7668
|
this.requireModel("orchestratorModel", this.config.orchestratorModel),
|
|
7758
|
-
toOpenAiTools(
|
|
7669
|
+
toOpenAiTools(),
|
|
7759
7670
|
(delta) => onProgress({ type: "thinking", text: delta })
|
|
7760
7671
|
);
|
|
7761
|
-
const result = await this.runResearchLoop(researchChat, firstMessage,
|
|
7672
|
+
const result = await this.runResearchLoop(researchChat, firstMessage, fs12, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
|
|
7762
7673
|
if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
|
|
7763
7674
|
const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
|
|
7764
7675
|
return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
|
|
7765
7676
|
}
|
|
7766
|
-
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken,
|
|
7677
|
+
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
7767
7678
|
this.ensureInit();
|
|
7768
7679
|
const { autonomousDefault } = modes;
|
|
7769
7680
|
let ctx = "";
|
|
7770
|
-
if (
|
|
7771
|
-
const collected = await new ContextCollector(
|
|
7681
|
+
if (fs12) {
|
|
7682
|
+
const collected = await new ContextCollector(fs12).collect(runners[0] ?? "claude-code");
|
|
7772
7683
|
if (collected.aiflowContext) ctx = `
|
|
7773
7684
|
<aiflow_context>
|
|
7774
7685
|
${collected.aiflowContext}
|
|
@@ -7784,12 +7695,12 @@ ${collected.aiflowContext}
|
|
|
7784
7695
|
autonomousDefault
|
|
7785
7696
|
);
|
|
7786
7697
|
}
|
|
7787
|
-
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress,
|
|
7698
|
+
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
7788
7699
|
this.ensureInit();
|
|
7789
7700
|
const { autonomousDefault } = modes;
|
|
7790
7701
|
let aiflowContext;
|
|
7791
|
-
if (
|
|
7792
|
-
const collected = await new ContextCollector(
|
|
7702
|
+
if (fs12) {
|
|
7703
|
+
const collected = await new ContextCollector(fs12).collect(existingPlan.runners[0] ?? "claude-code");
|
|
7793
7704
|
if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
|
|
7794
7705
|
}
|
|
7795
7706
|
const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
|
|
@@ -7878,21 +7789,28 @@ function pickWindow(sorted, currentIdx, max) {
|
|
|
7878
7789
|
const window = sorted.slice(start, start + max);
|
|
7879
7790
|
return { window, omitted: sorted.length - window.length };
|
|
7880
7791
|
}
|
|
7881
|
-
function renderPlanMap(
|
|
7882
|
-
|
|
7883
|
-
|
|
7792
|
+
function renderPlanMap(planTasks, currentTaskId, opts) {
|
|
7793
|
+
const rows = flattenTasksWithParents(planTasks);
|
|
7794
|
+
if (rows.length < PLAN_MAP_MIN_TASKS) return "";
|
|
7795
|
+
const sorted = rows.map((r) => ({
|
|
7796
|
+
...r,
|
|
7797
|
+
label: taskOrderLabel(r.task, r.parent ?? void 0)
|
|
7798
|
+
})).sort((a, b) => {
|
|
7799
|
+
const parentDiff = (a.parent?.order ?? -1) - (b.parent?.order ?? -1);
|
|
7800
|
+
if (parentDiff !== 0) return parentDiff;
|
|
7801
|
+
return a.task.order - b.task.order;
|
|
7802
|
+
});
|
|
7884
7803
|
const max = opts?.maxEntries ?? DEFAULT_PLAN_MAP_MAX_ENTRIES;
|
|
7885
|
-
const currentIdx = sorted.findIndex((
|
|
7804
|
+
const currentIdx = sorted.findIndex((r) => r.task.id === currentTaskId);
|
|
7886
7805
|
const { window, omitted } = pickWindow(sorted, currentIdx, max);
|
|
7887
|
-
const currentTask = sorted.find((
|
|
7888
|
-
const
|
|
7889
|
-
|
|
7890
|
-
const
|
|
7891
|
-
const
|
|
7892
|
-
const order = String(t.order).padStart(widthOrder, " ");
|
|
7806
|
+
const currentTask = sorted.find((r) => r.task.id === currentTaskId)?.task;
|
|
7807
|
+
const lines = window.map((row) => {
|
|
7808
|
+
const isCurrent = row.task.id === currentTaskId;
|
|
7809
|
+
const tag = planMapStatus(row.task, isCurrent);
|
|
7810
|
+
const order = row.parent ? row.label : row.label.padStart(2, " ");
|
|
7893
7811
|
const arrow = isCurrent ? " \u2190 you are here" : "";
|
|
7894
|
-
const runner =
|
|
7895
|
-
return `${order}. [${tag.padEnd(7, " ")}] ${
|
|
7812
|
+
const runner = row.task.assignedRunner ? ` (${row.task.assignedRunner})` : "";
|
|
7813
|
+
return `${order}. [${tag.padEnd(7, " ")}] ${row.task.title}${runner}${arrow}`;
|
|
7896
7814
|
});
|
|
7897
7815
|
const footer = omitted > 0 ? `
|
|
7898
7816
|
(${omitted} task${omitted === 1 ? "" : "s"} omitted from this view)` : "";
|
|
@@ -7957,7 +7875,8 @@ function composeAugmentedPrompt(task, allTasks, opts) {
|
|
|
7957
7875
|
const map = renderPlanMap(allTasks, task.id, { maxEntries: opts?.planMapMaxEntries });
|
|
7958
7876
|
if (map) blocks.push(map);
|
|
7959
7877
|
}
|
|
7960
|
-
const
|
|
7878
|
+
const flat = flattenTasks(allTasks);
|
|
7879
|
+
const outputs = collectDirectDependencyOutputs(task, flat);
|
|
7961
7880
|
if (outputs.length > 0) blocks.push(renderPriorOutputs(outputs));
|
|
7962
7881
|
if (opts?.tddEnabled) {
|
|
7963
7882
|
blocks.push(renderTddInstruction());
|
|
@@ -8136,6 +8055,7 @@ function renderTerminalOutput(raw) {
|
|
|
8136
8055
|
return populated.map((r) => cells(r).join("")).join("\n");
|
|
8137
8056
|
}
|
|
8138
8057
|
var MARKER_SCAN_TAIL = 16384;
|
|
8058
|
+
var IDLE_TIMEOUT_MS = 6e4;
|
|
8139
8059
|
var VerdictEngine = class {
|
|
8140
8060
|
markerSeen = /* @__PURE__ */ new Set();
|
|
8141
8061
|
buffers = /* @__PURE__ */ new Map();
|
|
@@ -8143,6 +8063,7 @@ var VerdictEngine = class {
|
|
|
8143
8063
|
pausedSessions = /* @__PURE__ */ new Map();
|
|
8144
8064
|
listeners = [];
|
|
8145
8065
|
checkpointListeners = [];
|
|
8066
|
+
idleListeners = [];
|
|
8146
8067
|
/**
|
|
8147
8068
|
* Per-task generation counter. Incremented on every watch() and clear().
|
|
8148
8069
|
* Stale callbacks (from a prior session whose generation doesn't match
|
|
@@ -8150,12 +8071,47 @@ var VerdictEngine = class {
|
|
|
8150
8071
|
* wrong session.
|
|
8151
8072
|
*/
|
|
8152
8073
|
generations = /* @__PURE__ */ new Map();
|
|
8074
|
+
idleTimers = /* @__PURE__ */ new Map();
|
|
8075
|
+
idleSince = /* @__PURE__ */ new Map();
|
|
8153
8076
|
onVerdict(listener) {
|
|
8154
8077
|
this.listeners.push(listener);
|
|
8155
8078
|
}
|
|
8156
8079
|
onCheckpoint(listener) {
|
|
8157
8080
|
this.checkpointListeners.push(listener);
|
|
8158
8081
|
}
|
|
8082
|
+
onIdleChange(listener) {
|
|
8083
|
+
this.idleListeners.push(listener);
|
|
8084
|
+
}
|
|
8085
|
+
/** Advisory silence timestamp for a task, or null if it isn't idle. */
|
|
8086
|
+
getIdleSince(taskId) {
|
|
8087
|
+
return this.idleSince.get(taskId) ?? null;
|
|
8088
|
+
}
|
|
8089
|
+
/** Restart the silence timer on fresh output; broadcasts the null transition if it was idle. */
|
|
8090
|
+
touchIdle(taskId, gen) {
|
|
8091
|
+
const existing = this.idleTimers.get(taskId);
|
|
8092
|
+
if (existing) clearTimeout(existing);
|
|
8093
|
+
if (this.idleSince.get(taskId)) {
|
|
8094
|
+
this.idleSince.set(taskId, null);
|
|
8095
|
+
for (const l of this.idleListeners) l(taskId, null);
|
|
8096
|
+
}
|
|
8097
|
+
this.idleTimers.set(taskId, setTimeout(() => {
|
|
8098
|
+
if (this.generations.get(taskId) !== gen) return;
|
|
8099
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8100
|
+
this.idleSince.set(taskId, now);
|
|
8101
|
+
for (const l of this.idleListeners) l(taskId, now);
|
|
8102
|
+
}, IDLE_TIMEOUT_MS));
|
|
8103
|
+
}
|
|
8104
|
+
/** Tear down idle tracking for a task; broadcasts the null transition if it was idle. */
|
|
8105
|
+
clearIdle(taskId) {
|
|
8106
|
+
const timer = this.idleTimers.get(taskId);
|
|
8107
|
+
if (timer) clearTimeout(timer);
|
|
8108
|
+
this.idleTimers.delete(taskId);
|
|
8109
|
+
const wasIdle = this.idleSince.get(taskId);
|
|
8110
|
+
this.idleSince.delete(taskId);
|
|
8111
|
+
if (wasIdle) {
|
|
8112
|
+
for (const l of this.idleListeners) l(taskId, null);
|
|
8113
|
+
}
|
|
8114
|
+
}
|
|
8159
8115
|
approveCheckpoint(taskId) {
|
|
8160
8116
|
const session = this.pausedSessions.get(taskId);
|
|
8161
8117
|
if (session) {
|
|
@@ -8186,6 +8142,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
8186
8142
|
this.checkpointCounts.set(task.id, 0);
|
|
8187
8143
|
session.onOutput((text) => {
|
|
8188
8144
|
if (this.generations.get(task.id) !== gen) return;
|
|
8145
|
+
this.touchIdle(task.id, gen);
|
|
8189
8146
|
if (this.markerSeen.has(task.id)) return;
|
|
8190
8147
|
const buf = (this.buffers.get(task.id) ?? "") + text;
|
|
8191
8148
|
this.buffers.set(task.id, buf);
|
|
@@ -8197,6 +8154,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
8197
8154
|
this.buffers.delete(task.id);
|
|
8198
8155
|
this.checkpointCounts.delete(task.id);
|
|
8199
8156
|
this.pausedSessions.delete(task.id);
|
|
8157
|
+
this.clearIdle(task.id);
|
|
8200
8158
|
this.generations.set(task.id, gen + 1);
|
|
8201
8159
|
const verdict = this.decide(task, 0);
|
|
8202
8160
|
for (const l of this.listeners) l(task.id, verdict, output);
|
|
@@ -8221,6 +8179,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
8221
8179
|
this.buffers.delete(task.id);
|
|
8222
8180
|
this.checkpointCounts.delete(task.id);
|
|
8223
8181
|
this.pausedSessions.delete(task.id);
|
|
8182
|
+
this.clearIdle(task.id);
|
|
8224
8183
|
const verdict = this.decide(task, exitCode);
|
|
8225
8184
|
for (const l of this.listeners) l(task.id, verdict, output);
|
|
8226
8185
|
});
|
|
@@ -8231,6 +8190,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
8231
8190
|
this.buffers.delete(task.id);
|
|
8232
8191
|
this.checkpointCounts.delete(task.id);
|
|
8233
8192
|
this.pausedSessions.delete(task.id);
|
|
8193
|
+
this.clearIdle(task.id);
|
|
8234
8194
|
this.generations.set(task.id, (this.generations.get(task.id) ?? 0) + 1);
|
|
8235
8195
|
return {
|
|
8236
8196
|
outcome: "pass",
|
|
@@ -8252,6 +8212,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
8252
8212
|
this.buffers.delete(task.id);
|
|
8253
8213
|
this.checkpointCounts.delete(task.id);
|
|
8254
8214
|
this.pausedSessions.delete(task.id);
|
|
8215
|
+
this.clearIdle(task.id);
|
|
8255
8216
|
this.generations.set(task.id, (this.generations.get(task.id) ?? 0) + 1);
|
|
8256
8217
|
}
|
|
8257
8218
|
/** Drop all tracking state (used on stop / loadPlan). */
|
|
@@ -8260,6 +8221,9 @@ ORDEWELL_REJECT: ${reason}
|
|
|
8260
8221
|
this.buffers.clear();
|
|
8261
8222
|
this.checkpointCounts.clear();
|
|
8262
8223
|
this.pausedSessions.clear();
|
|
8224
|
+
for (const timer of this.idleTimers.values()) clearTimeout(timer);
|
|
8225
|
+
this.idleTimers.clear();
|
|
8226
|
+
this.idleSince.clear();
|
|
8263
8227
|
this.generations.clear();
|
|
8264
8228
|
}
|
|
8265
8229
|
decide(task, exitCode) {
|
|
@@ -8665,6 +8629,7 @@ var TaskOrchestrator = class {
|
|
|
8665
8629
|
this.store.markAwaitingUser(taskId);
|
|
8666
8630
|
this.emit("onCheckpoint", { taskId, taskTitle: task.title, summary });
|
|
8667
8631
|
});
|
|
8632
|
+
this.verifier.onIdleChange(() => this.emit("onTaskChanged"));
|
|
8668
8633
|
}
|
|
8669
8634
|
config;
|
|
8670
8635
|
notifications;
|
|
@@ -8689,6 +8654,10 @@ var TaskOrchestrator = class {
|
|
|
8689
8654
|
workspaceRootFn = () => process.cwd();
|
|
8690
8655
|
observers = [];
|
|
8691
8656
|
tddEnabled = () => false;
|
|
8657
|
+
/** Advisory silence timestamp for a task's live runner, or null if not idle. */
|
|
8658
|
+
getIdleSince(taskId) {
|
|
8659
|
+
return this.verifier.getIdleSince(taskId);
|
|
8660
|
+
}
|
|
8692
8661
|
get storeInstance() {
|
|
8693
8662
|
return this.store;
|
|
8694
8663
|
}
|
|
@@ -9076,8 +9045,10 @@ ${output.slice(-3e3)}`);
|
|
|
9076
9045
|
if (ready.length === 0 && this.activeTaskSessions.size === 0) {
|
|
9077
9046
|
const remaining = this.store.allTasks.filter((t) => t.status !== "completed");
|
|
9078
9047
|
console.log(`[TaskOrchestrator] tick(): no work. remaining=${remaining.length}, completed=${this.store.isAllComplete()}`);
|
|
9079
|
-
|
|
9080
|
-
|
|
9048
|
+
const remainingById = new Map(remaining.map((t) => [t.id, t]));
|
|
9049
|
+
for (const { task, parent } of flattenTasksWithParents(this.store.planTasks)) {
|
|
9050
|
+
if (!remainingById.has(task.id)) continue;
|
|
9051
|
+
console.log(` - ${taskOrderLabel(task, parent ?? void 0)}. ${task.title} [${task.type}/${task.status}] prompt=${!!task.prompt}`);
|
|
9081
9052
|
}
|
|
9082
9053
|
if (this.store.isAllComplete()) {
|
|
9083
9054
|
this.running = false;
|
|
@@ -9104,7 +9075,7 @@ ${output.slice(-3e3)}`);
|
|
|
9104
9075
|
try {
|
|
9105
9076
|
const cwd = this.workspaceRootFn();
|
|
9106
9077
|
const runner = this.store.resolveTaskRunner(task);
|
|
9107
|
-
const finalPrompt = composeAugmentedPrompt(task, this.store.
|
|
9078
|
+
const finalPrompt = composeAugmentedPrompt(task, this.store.planTasks, {
|
|
9108
9079
|
planMapEnabled: this.config.planMapEnabled,
|
|
9109
9080
|
tddEnabled: this.tddEnabled()
|
|
9110
9081
|
});
|
|
@@ -9278,7 +9249,7 @@ var osMod = __toESM(require("os"));
|
|
|
9278
9249
|
var import_child_process = require("child_process");
|
|
9279
9250
|
var import_util = require("util");
|
|
9280
9251
|
var os2 = __toESM(require("os"));
|
|
9281
|
-
var
|
|
9252
|
+
var path4 = __toESM(require("path"));
|
|
9282
9253
|
var execAsync = (0, import_util.promisify)(import_child_process.exec);
|
|
9283
9254
|
var LOGIN_SHELL_TIMEOUT_MS = 5e3;
|
|
9284
9255
|
var PATH_MARKER = "__ORDEWELL_PATH__";
|
|
@@ -9289,40 +9260,40 @@ var defaultExec = async (command, options) => {
|
|
|
9289
9260
|
function posixBinDirs() {
|
|
9290
9261
|
const home = os2.homedir();
|
|
9291
9262
|
return [
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9263
|
+
path4.join(home, ".local", "bin"),
|
|
9264
|
+
path4.join(home, ".opencode", "bin"),
|
|
9265
|
+
path4.join(home, ".claude", "bin"),
|
|
9266
|
+
path4.join(home, ".bun", "bin"),
|
|
9267
|
+
path4.join(home, ".volta", "bin"),
|
|
9268
|
+
path4.join(home, ".npm-global", "bin"),
|
|
9269
|
+
path4.join(home, "bin"),
|
|
9299
9270
|
"/usr/local/bin",
|
|
9300
9271
|
"/opt/homebrew/bin"
|
|
9301
9272
|
];
|
|
9302
9273
|
}
|
|
9303
9274
|
function windowsBinDirs(env, home) {
|
|
9304
|
-
const
|
|
9305
|
-
const under = (base, ...parts) => base ? [
|
|
9275
|
+
const join19 = (...parts) => path4.win32.join(...parts);
|
|
9276
|
+
const under = (base, ...parts) => base ? [join19(base, ...parts)] : [];
|
|
9306
9277
|
const appData = env.APPDATA;
|
|
9307
9278
|
const localAppData = env.LOCALAPPDATA;
|
|
9308
9279
|
const programData = env.ProgramData;
|
|
9309
9280
|
return [
|
|
9310
9281
|
// Native per-user installers, PowerShell-driven and otherwise.
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9282
|
+
join19(home, ".local", "bin"),
|
|
9283
|
+
join19(home, ".opencode", "bin"),
|
|
9284
|
+
join19(home, ".claude", "bin"),
|
|
9285
|
+
join19(home, ".codex", "bin"),
|
|
9315
9286
|
...under(localAppData, "Programs", "claude"),
|
|
9316
9287
|
// Node package managers. npm's global prefix is `%APPDATA%\npm`; the
|
|
9317
9288
|
// literal spelling is kept as a fallback for a host that does not export
|
|
9318
9289
|
// APPDATA (a service account, a stripped CI container).
|
|
9319
9290
|
...under(appData, "npm"),
|
|
9320
|
-
|
|
9291
|
+
join19(home, "AppData", "Roaming", "npm"),
|
|
9321
9292
|
...under(localAppData, "pnpm"),
|
|
9322
9293
|
...under(localAppData, "Yarn", "bin"),
|
|
9323
|
-
|
|
9294
|
+
join19(home, ".bun", "bin"),
|
|
9324
9295
|
// Windows package managers.
|
|
9325
|
-
|
|
9296
|
+
join19(home, "scoop", "shims"),
|
|
9326
9297
|
...under(env.SCOOP, "shims"),
|
|
9327
9298
|
...under(env.SCOOP_GLOBAL ?? programData, "scoop", "shims"),
|
|
9328
9299
|
...under(programData, "chocolatey", "bin"),
|
|
@@ -9332,7 +9303,7 @@ function windowsBinDirs(env, home) {
|
|
|
9332
9303
|
...under(localAppData, "Microsoft", "WindowsApps"),
|
|
9333
9304
|
// Version managers. Volta's Windows home is not the POSIX `~/.volta`.
|
|
9334
9305
|
...under(localAppData, "Volta", "bin"),
|
|
9335
|
-
|
|
9306
|
+
join19(home, ".volta", "bin")
|
|
9336
9307
|
];
|
|
9337
9308
|
}
|
|
9338
9309
|
function wellKnownBinDirs(deps = {}) {
|
|
@@ -9361,14 +9332,14 @@ function augmentedPath(execImpl = defaultExec) {
|
|
|
9361
9332
|
const segments = [];
|
|
9362
9333
|
const push = (p) => {
|
|
9363
9334
|
if (!p) return;
|
|
9364
|
-
for (const seg of p.split(
|
|
9335
|
+
for (const seg of p.split(path4.delimiter)) {
|
|
9365
9336
|
if (seg && !segments.includes(seg)) segments.push(seg);
|
|
9366
9337
|
}
|
|
9367
9338
|
};
|
|
9368
9339
|
push(process.env.PATH);
|
|
9369
9340
|
push(await loginShellPath(execImpl));
|
|
9370
9341
|
for (const dir of wellKnownBinDirs()) push(dir);
|
|
9371
|
-
return segments.join(
|
|
9342
|
+
return segments.join(path4.delimiter);
|
|
9372
9343
|
})();
|
|
9373
9344
|
}
|
|
9374
9345
|
return cachedPath;
|
|
@@ -9388,6 +9359,50 @@ function withPath(base, resolvedPath, overrides = {}) {
|
|
|
9388
9359
|
return env;
|
|
9389
9360
|
}
|
|
9390
9361
|
|
|
9362
|
+
// src/utils/globalDataDir.ts
|
|
9363
|
+
var fs = __toESM(require("fs"));
|
|
9364
|
+
var path5 = __toESM(require("path"));
|
|
9365
|
+
var os3 = __toESM(require("os"));
|
|
9366
|
+
function globalDataDir() {
|
|
9367
|
+
return path5.join(os3.homedir(), ".ordewell");
|
|
9368
|
+
}
|
|
9369
|
+
function copyDirSync(src, dest) {
|
|
9370
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
9371
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
9372
|
+
for (const entry of entries) {
|
|
9373
|
+
const srcPath = path5.join(src, entry.name);
|
|
9374
|
+
const destPath = path5.join(dest, entry.name);
|
|
9375
|
+
if (entry.isDirectory()) {
|
|
9376
|
+
copyDirSync(srcPath, destPath);
|
|
9377
|
+
} else {
|
|
9378
|
+
fs.copyFileSync(srcPath, destPath);
|
|
9379
|
+
}
|
|
9380
|
+
}
|
|
9381
|
+
}
|
|
9382
|
+
var migrated = false;
|
|
9383
|
+
function migrateOldConfigDir() {
|
|
9384
|
+
if (migrated) return;
|
|
9385
|
+
migrated = true;
|
|
9386
|
+
const oldDir = path5.join(os3.homedir(), ".config", "ordewell");
|
|
9387
|
+
if (!fs.existsSync(oldDir)) return;
|
|
9388
|
+
const newDir = globalDataDir();
|
|
9389
|
+
const liftFile = (name) => {
|
|
9390
|
+
const oldFile = path5.join(oldDir, name);
|
|
9391
|
+
const newFile = path5.join(newDir, name);
|
|
9392
|
+
if (fs.existsSync(oldFile) && !fs.existsSync(newFile)) {
|
|
9393
|
+
fs.mkdirSync(newDir, { recursive: true });
|
|
9394
|
+
fs.copyFileSync(oldFile, newFile);
|
|
9395
|
+
}
|
|
9396
|
+
};
|
|
9397
|
+
liftFile("settings.json");
|
|
9398
|
+
liftFile(".env");
|
|
9399
|
+
const oldPlugins = path5.join(oldDir, "plugins");
|
|
9400
|
+
const newPlugins = path5.join(newDir, "plugins");
|
|
9401
|
+
if (fs.existsSync(oldPlugins) && !fs.existsSync(newPlugins)) {
|
|
9402
|
+
copyDirSync(oldPlugins, newPlugins);
|
|
9403
|
+
}
|
|
9404
|
+
}
|
|
9405
|
+
|
|
9391
9406
|
// src/utils/launch.ts
|
|
9392
9407
|
var fsSync2 = __toESM(require("fs"));
|
|
9393
9408
|
var path6 = __toESM(require("path"));
|
|
@@ -9713,7 +9728,7 @@ function parseCodexModelsCache(content) {
|
|
|
9713
9728
|
}
|
|
9714
9729
|
var GEMINI_MODELS_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
9715
9730
|
function geminiDiscoveryCacheKey(apiKey) {
|
|
9716
|
-
const dir =
|
|
9731
|
+
const dir = globalDataDir();
|
|
9717
9732
|
const hash = apiKey.slice(-8);
|
|
9718
9733
|
return {
|
|
9719
9734
|
key: `gemini-models:${hash}`,
|
|
@@ -10243,12 +10258,14 @@ var ModelDiscovery = class {
|
|
|
10243
10258
|
// src/services/ModelCatalog.ts
|
|
10244
10259
|
var fs2 = __toESM(require("fs"));
|
|
10245
10260
|
var path8 = __toESM(require("path"));
|
|
10246
|
-
var os3 = __toESM(require("os"));
|
|
10247
10261
|
var CACHE_KEY = "model-catalog";
|
|
10248
10262
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
10249
10263
|
function storageKey(baseUrl) {
|
|
10250
10264
|
return `${CACHE_KEY}:${baseUrl}`;
|
|
10251
10265
|
}
|
|
10266
|
+
function globalCacheDir() {
|
|
10267
|
+
return globalDataDir();
|
|
10268
|
+
}
|
|
10252
10269
|
var ModelCatalog = class {
|
|
10253
10270
|
static async fetchModels(apiKey, baseUrl, fetchImpl) {
|
|
10254
10271
|
const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
|
|
@@ -10291,7 +10308,7 @@ var ModelCatalog = class {
|
|
|
10291
10308
|
try {
|
|
10292
10309
|
const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
|
|
10293
10310
|
const key = storageKey(url);
|
|
10294
|
-
const cacheDir =
|
|
10311
|
+
const cacheDir = globalCacheDir();
|
|
10295
10312
|
const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
10296
10313
|
if (fs2.existsSync(cacheFile)) fs2.rmSync(cacheFile);
|
|
10297
10314
|
} catch {
|
|
@@ -10299,7 +10316,7 @@ var ModelCatalog = class {
|
|
|
10299
10316
|
}
|
|
10300
10317
|
static readCache(key) {
|
|
10301
10318
|
try {
|
|
10302
|
-
const cacheDir =
|
|
10319
|
+
const cacheDir = globalCacheDir();
|
|
10303
10320
|
const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
10304
10321
|
if (!fs2.existsSync(cacheFile)) return null;
|
|
10305
10322
|
const raw = fs2.readFileSync(cacheFile, "utf8");
|
|
@@ -10312,7 +10329,7 @@ var ModelCatalog = class {
|
|
|
10312
10329
|
}
|
|
10313
10330
|
static writeCache(key, models) {
|
|
10314
10331
|
try {
|
|
10315
|
-
const cacheDir =
|
|
10332
|
+
const cacheDir = globalCacheDir();
|
|
10316
10333
|
fs2.mkdirSync(cacheDir, { recursive: true });
|
|
10317
10334
|
const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
10318
10335
|
fs2.writeFileSync(
|
|
@@ -11754,14 +11771,14 @@ ${part.text}` : part.text });
|
|
|
11754
11771
|
}
|
|
11755
11772
|
}
|
|
11756
11773
|
}
|
|
11757
|
-
async json(method,
|
|
11758
|
-
const response = await this.deps.fetch(`${this.baseUrl}${
|
|
11774
|
+
async json(method, path20, body, signal) {
|
|
11775
|
+
const response = await this.deps.fetch(`${this.baseUrl}${path20}`, {
|
|
11759
11776
|
method,
|
|
11760
11777
|
headers: { "content-type": "application/json" },
|
|
11761
11778
|
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
11762
11779
|
...signal ? { signal } : {}
|
|
11763
11780
|
});
|
|
11764
|
-
if (!response.ok) throw new Error(`${method} ${
|
|
11781
|
+
if (!response.ok) throw new Error(`${method} ${path20} failed: ${response.status} ${response.statusText}`);
|
|
11765
11782
|
return await response.json().catch(() => null);
|
|
11766
11783
|
}
|
|
11767
11784
|
nativeSessionId() {
|
|
@@ -11916,9 +11933,6 @@ var CliAgentAiService = class {
|
|
|
11916
11933
|
req.runners,
|
|
11917
11934
|
req.runnerModes,
|
|
11918
11935
|
req.autonomousDefault ?? true,
|
|
11919
|
-
req.grillMeEnabled ?? false,
|
|
11920
|
-
req.prdEnabled ?? false,
|
|
11921
|
-
req.reviewEnabled ?? false,
|
|
11922
11936
|
req.verificationEnabled ?? false,
|
|
11923
11937
|
true
|
|
11924
11938
|
);
|
|
@@ -11933,10 +11947,7 @@ var CliAgentAiService = class {
|
|
|
11933
11947
|
startOptions,
|
|
11934
11948
|
runners: req.runners,
|
|
11935
11949
|
runnerModes: req.runnerModes,
|
|
11936
|
-
autonomousDefault: req.autonomousDefault
|
|
11937
|
-
prdEnabled: req.prdEnabled ?? false,
|
|
11938
|
-
prdCaptured: false,
|
|
11939
|
-
prdNudgeSent: false
|
|
11950
|
+
autonomousDefault: req.autonomousDefault
|
|
11940
11951
|
};
|
|
11941
11952
|
const opening = this.openingMessage(req);
|
|
11942
11953
|
return this.runConversation(opening, req.onProgress, req.signal);
|
|
@@ -11964,8 +11975,8 @@ var CliAgentAiService = class {
|
|
|
11964
11975
|
/**
|
|
11965
11976
|
* Drive one user message to a settled planner turn. Same shape as the API
|
|
11966
11977
|
* backend's conversation loop minus the tool rounds — those belong to the
|
|
11967
|
-
* agent now — and with the same
|
|
11968
|
-
*
|
|
11978
|
+
* agent now — and with the same two policies layered on top: the
|
|
11979
|
+
* empty-reply nudge, and a bounded corrective re-emit for botched JSON.
|
|
11969
11980
|
*/
|
|
11970
11981
|
async runConversation(message, onProgress, signal) {
|
|
11971
11982
|
const conversation = this.conversation;
|
|
@@ -12013,7 +12024,6 @@ var CliAgentAiService = class {
|
|
|
12013
12024
|
].join(" ");
|
|
12014
12025
|
continue;
|
|
12015
12026
|
}
|
|
12016
|
-
if (extractPrdBlock(turn.text)) conversation.prdCaptured = true;
|
|
12017
12027
|
const reply = classifyPlannerReply(turn.text, {
|
|
12018
12028
|
runners: conversation.runners,
|
|
12019
12029
|
runnerModes: conversation.runnerModes,
|
|
@@ -12027,16 +12037,6 @@ var CliAgentAiService = class {
|
|
|
12027
12037
|
case "task_query":
|
|
12028
12038
|
return { kind: "task_query", query: reply.query, text: replyText(turn.text), researchLog };
|
|
12029
12039
|
case "plan":
|
|
12030
|
-
if (conversation.prdEnabled && !conversation.prdCaptured && !conversation.prdNudgeSent && !combined?.aborted) {
|
|
12031
|
-
conversation.prdNudgeSent = true;
|
|
12032
|
-
pending = [
|
|
12033
|
-
"PRD mode is enabled but no PRD has been produced yet, so the plan was NOT committed.",
|
|
12034
|
-
"In your next reply: first output the full markdown PRD wrapped EXACTLY in",
|
|
12035
|
-
'<!-- ORDEWELL_PRD_START slug="<feature-slug>" --> and <!-- ORDEWELL_PRD_END -->,',
|
|
12036
|
-
"then re-emit the exact same task plan JSON after the PRD block."
|
|
12037
|
-
].join(" ");
|
|
12038
|
-
continue;
|
|
12039
|
-
}
|
|
12040
12040
|
this.conversation = null;
|
|
12041
12041
|
return { kind: "plan", tasks: reply.tasks, text: replyText(turn.text), researchLog };
|
|
12042
12042
|
case "broken_task_ops":
|
|
@@ -12215,10 +12215,7 @@ var CliAgentAiService = class {
|
|
|
12215
12215
|
oneShotAdapter = await this.startAdapter(startOptions);
|
|
12216
12216
|
this.conversation = {
|
|
12217
12217
|
startOptions,
|
|
12218
|
-
runners: []
|
|
12219
|
-
prdEnabled: false,
|
|
12220
|
-
prdCaptured: false,
|
|
12221
|
-
prdNudgeSent: false
|
|
12218
|
+
runners: []
|
|
12222
12219
|
};
|
|
12223
12220
|
const turn = await this.runTurn(
|
|
12224
12221
|
"Follow the instructions in your system prompt and produce the plan now.",
|
|
@@ -12235,8 +12232,8 @@ var CliAgentAiService = class {
|
|
|
12235
12232
|
this.lastNativeSessionId = previousSessionId;
|
|
12236
12233
|
}
|
|
12237
12234
|
}
|
|
12238
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
12239
|
-
const contextStr = await collectResearchContext(
|
|
12235
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
12236
|
+
const contextStr = await collectResearchContext(fs12, runners);
|
|
12240
12237
|
const researchLog = [
|
|
12241
12238
|
{ id: `up-${Date.now()}`, type: "user_prompt", content: userDescription, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
12242
12239
|
];
|
|
@@ -12559,6 +12556,181 @@ function retargetTaskRunner(task, runner, catalog) {
|
|
|
12559
12556
|
return { assignedRunner: runner, ...runnerAssignment(catalog, task) };
|
|
12560
12557
|
}
|
|
12561
12558
|
|
|
12559
|
+
// src/services/SkillsService.ts
|
|
12560
|
+
var fs4 = __toESM(require("fs"));
|
|
12561
|
+
var path11 = __toESM(require("path"));
|
|
12562
|
+
|
|
12563
|
+
// src/services/builtinSkills.ts
|
|
12564
|
+
var path10 = __toESM(require("path"));
|
|
12565
|
+
var import_url = require("url");
|
|
12566
|
+
var import_meta = {};
|
|
12567
|
+
function builtinSkillsDir() {
|
|
12568
|
+
const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path10.dirname((0, import_url.fileURLToPath)(
|
|
12569
|
+
// @ts-expect-error - import.meta is only valid under an ESM module setting
|
|
12570
|
+
import_meta.url
|
|
12571
|
+
));
|
|
12572
|
+
return path10.join(dir, "..", "skills");
|
|
12573
|
+
}
|
|
12574
|
+
|
|
12575
|
+
// src/services/SkillsService.ts
|
|
12576
|
+
var BUILTIN_SKILL_NAMES = ["grilling", "to-spec"];
|
|
12577
|
+
var RETIRED_BUILTIN_SKILL_NAMES = ["grill-me"];
|
|
12578
|
+
function parseSkillFile(filePath, name, source) {
|
|
12579
|
+
const raw = fs4.readFileSync(filePath, "utf8");
|
|
12580
|
+
const frontmatter = {};
|
|
12581
|
+
let content = raw;
|
|
12582
|
+
if (raw.startsWith("---\n")) {
|
|
12583
|
+
const end = raw.indexOf("\n---", 4);
|
|
12584
|
+
if (end !== -1) {
|
|
12585
|
+
const fmText = raw.slice(4, end);
|
|
12586
|
+
for (const line of fmText.split("\n")) {
|
|
12587
|
+
const idx = line.indexOf(":");
|
|
12588
|
+
if (idx === -1) continue;
|
|
12589
|
+
const key = line.slice(0, idx).trim();
|
|
12590
|
+
const value = line.slice(idx + 1).trim();
|
|
12591
|
+
if (key === "disable-model-invocation") {
|
|
12592
|
+
frontmatter[key] = value === "true";
|
|
12593
|
+
} else if (key === "name" || key === "description") {
|
|
12594
|
+
frontmatter[key] = value.replace(/^"|"$/g, "");
|
|
12595
|
+
}
|
|
12596
|
+
}
|
|
12597
|
+
content = raw.slice(end + 4).replace(/^\n+/, "");
|
|
12598
|
+
}
|
|
12599
|
+
}
|
|
12600
|
+
const metadata = {
|
|
12601
|
+
name: frontmatter.name ?? name,
|
|
12602
|
+
description: frontmatter.description ?? "",
|
|
12603
|
+
...frontmatter["disable-model-invocation"] !== void 0 ? { disableModelInvocation: frontmatter["disable-model-invocation"] } : {}
|
|
12604
|
+
};
|
|
12605
|
+
return { name, description: metadata.description, metadata, content, path: filePath, source };
|
|
12606
|
+
}
|
|
12607
|
+
function copyDirSync2(src, dest) {
|
|
12608
|
+
fs4.mkdirSync(dest, { recursive: true });
|
|
12609
|
+
const entries = fs4.readdirSync(src, { withFileTypes: true });
|
|
12610
|
+
for (const entry of entries) {
|
|
12611
|
+
const srcPath = path11.join(src, entry.name);
|
|
12612
|
+
const destPath = path11.join(dest, entry.name);
|
|
12613
|
+
if (entry.isDirectory()) copyDirSync2(srcPath, destPath);
|
|
12614
|
+
else fs4.copyFileSync(srcPath, destPath);
|
|
12615
|
+
}
|
|
12616
|
+
}
|
|
12617
|
+
function readDir(dir, source) {
|
|
12618
|
+
if (!fs4.existsSync(dir)) return [];
|
|
12619
|
+
const entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
12620
|
+
const skills = [];
|
|
12621
|
+
for (const entry of entries) {
|
|
12622
|
+
if (!entry.isDirectory()) continue;
|
|
12623
|
+
const skillFile = path11.join(dir, entry.name, "SKILL.md");
|
|
12624
|
+
if (!fs4.existsSync(skillFile)) continue;
|
|
12625
|
+
const parsed = parseSkillFile(skillFile, entry.name, source);
|
|
12626
|
+
if (parsed) skills.push(parsed);
|
|
12627
|
+
}
|
|
12628
|
+
return skills;
|
|
12629
|
+
}
|
|
12630
|
+
var SkillsService = class {
|
|
12631
|
+
constructor(workspaceRoot) {
|
|
12632
|
+
this.workspaceRoot = workspaceRoot;
|
|
12633
|
+
}
|
|
12634
|
+
workspaceRoot;
|
|
12635
|
+
localDir() {
|
|
12636
|
+
return this.workspaceRoot ? path11.join(this.workspaceRoot, ".ordewell", "skills") : void 0;
|
|
12637
|
+
}
|
|
12638
|
+
globalDir() {
|
|
12639
|
+
return path11.join(globalDataDir(), "skills");
|
|
12640
|
+
}
|
|
12641
|
+
seed(name) {
|
|
12642
|
+
const dest = path11.join(this.globalDir(), name);
|
|
12643
|
+
if (fs4.existsSync(dest)) return false;
|
|
12644
|
+
let srcDir;
|
|
12645
|
+
try {
|
|
12646
|
+
srcDir = path11.join(builtinSkillsDir(), name);
|
|
12647
|
+
} catch (err) {
|
|
12648
|
+
console.warn(`Could not locate built-in skills dir: ${String(err)}`);
|
|
12649
|
+
return false;
|
|
12650
|
+
}
|
|
12651
|
+
if (!fs4.existsSync(srcDir)) {
|
|
12652
|
+
console.warn(`Built-in skill not found in package: ${name}`);
|
|
12653
|
+
return false;
|
|
12654
|
+
}
|
|
12655
|
+
copyDirSync2(srcDir, dest);
|
|
12656
|
+
return true;
|
|
12657
|
+
}
|
|
12658
|
+
seedBuiltinSkill(name) {
|
|
12659
|
+
return this.seed(name);
|
|
12660
|
+
}
|
|
12661
|
+
/** Removes a retired built-in's seed from the global dir, but only if it's untouched by the user. */
|
|
12662
|
+
isUnmodifiedRetiredSeed(dir, expectedName) {
|
|
12663
|
+
let entries;
|
|
12664
|
+
try {
|
|
12665
|
+
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
12666
|
+
} catch {
|
|
12667
|
+
return false;
|
|
12668
|
+
}
|
|
12669
|
+
if (entries.length !== 1 || entries[0].name !== "SKILL.md" || !entries[0].isFile()) return false;
|
|
12670
|
+
let raw;
|
|
12671
|
+
try {
|
|
12672
|
+
raw = fs4.readFileSync(path11.join(dir, "SKILL.md"), "utf8");
|
|
12673
|
+
} catch {
|
|
12674
|
+
return false;
|
|
12675
|
+
}
|
|
12676
|
+
if (!raw.startsWith("---\n")) return false;
|
|
12677
|
+
const end = raw.indexOf("\n---", 4);
|
|
12678
|
+
if (end === -1) return false;
|
|
12679
|
+
for (const line of raw.slice(4, end).split("\n")) {
|
|
12680
|
+
const idx = line.indexOf(":");
|
|
12681
|
+
if (idx === -1) continue;
|
|
12682
|
+
const key = line.slice(0, idx).trim();
|
|
12683
|
+
if (key === "name") return line.slice(idx + 1).trim().replace(/^"|"$/g, "") === expectedName;
|
|
12684
|
+
}
|
|
12685
|
+
return false;
|
|
12686
|
+
}
|
|
12687
|
+
pruneRetired() {
|
|
12688
|
+
for (const name of RETIRED_BUILTIN_SKILL_NAMES) {
|
|
12689
|
+
const dest = path11.join(this.globalDir(), name);
|
|
12690
|
+
if (!fs4.existsSync(dest)) continue;
|
|
12691
|
+
if (!this.isUnmodifiedRetiredSeed(dest, name)) continue;
|
|
12692
|
+
try {
|
|
12693
|
+
fs4.rmSync(dest, { recursive: true, force: true });
|
|
12694
|
+
} catch (err) {
|
|
12695
|
+
console.warn(`Could not prune retired skill ${name}: ${String(err)}`);
|
|
12696
|
+
}
|
|
12697
|
+
}
|
|
12698
|
+
}
|
|
12699
|
+
findSkill(name) {
|
|
12700
|
+
this.pruneRetired();
|
|
12701
|
+
if (BUILTIN_SKILL_NAMES.includes(name)) this.seed(name);
|
|
12702
|
+
const localDir = this.localDir();
|
|
12703
|
+
if (localDir) {
|
|
12704
|
+
const localFile = path11.join(localDir, name, "SKILL.md");
|
|
12705
|
+
if (fs4.existsSync(localFile)) return parseSkillFile(localFile, name, "local");
|
|
12706
|
+
}
|
|
12707
|
+
const globalFile = path11.join(this.globalDir(), name, "SKILL.md");
|
|
12708
|
+
if (fs4.existsSync(globalFile)) return parseSkillFile(globalFile, name, "global");
|
|
12709
|
+
return void 0;
|
|
12710
|
+
}
|
|
12711
|
+
listSkills() {
|
|
12712
|
+
this.pruneRetired();
|
|
12713
|
+
for (const name of BUILTIN_SKILL_NAMES) this.seed(name);
|
|
12714
|
+
const byName = /* @__PURE__ */ new Map();
|
|
12715
|
+
for (const skill of readDir(this.globalDir(), "global")) {
|
|
12716
|
+
byName.set(skill.name, skill);
|
|
12717
|
+
}
|
|
12718
|
+
const localDir = this.localDir();
|
|
12719
|
+
if (localDir) {
|
|
12720
|
+
for (const skill of readDir(localDir, "local")) {
|
|
12721
|
+
byName.set(skill.name, skill);
|
|
12722
|
+
}
|
|
12723
|
+
}
|
|
12724
|
+
return [...byName.values()];
|
|
12725
|
+
}
|
|
12726
|
+
getSkillContent(name) {
|
|
12727
|
+
return this.findSkill(name)?.content;
|
|
12728
|
+
}
|
|
12729
|
+
};
|
|
12730
|
+
function createSkillsService(workspaceRoot) {
|
|
12731
|
+
return new SkillsService(workspaceRoot);
|
|
12732
|
+
}
|
|
12733
|
+
|
|
12562
12734
|
// src/services/SessionMessage.ts
|
|
12563
12735
|
function serializeTask(t) {
|
|
12564
12736
|
return {
|
|
@@ -12580,11 +12752,12 @@ function serializeTask(t) {
|
|
|
12580
12752
|
userStoriesCovered: t.userStoriesCovered || void 0
|
|
12581
12753
|
};
|
|
12582
12754
|
}
|
|
12583
|
-
function serializeTaskStatus(t) {
|
|
12755
|
+
function serializeTaskStatus(t, idleSince = null) {
|
|
12584
12756
|
return {
|
|
12585
12757
|
id: t.id,
|
|
12586
12758
|
status: t.status,
|
|
12587
|
-
verdict: t.verdict ? { outcome: t.verdict.outcome, reason: t.verdict.reason, checks: t.verdict.checks || [] } : null
|
|
12759
|
+
verdict: t.verdict ? { outcome: t.verdict.outcome, reason: t.verdict.reason, checks: t.verdict.checks || [] } : null,
|
|
12760
|
+
idleSince
|
|
12588
12761
|
};
|
|
12589
12762
|
}
|
|
12590
12763
|
function serializePlan(plan) {
|
|
@@ -12606,27 +12779,27 @@ function executionSummary(tasks) {
|
|
|
12606
12779
|
}
|
|
12607
12780
|
|
|
12608
12781
|
// src/utils/sessionStore.ts
|
|
12609
|
-
var
|
|
12610
|
-
var
|
|
12782
|
+
var fs6 = __toESM(require("fs"));
|
|
12783
|
+
var path13 = __toESM(require("path"));
|
|
12611
12784
|
|
|
12612
12785
|
// src/utils/fsHelpers.ts
|
|
12613
|
-
var
|
|
12614
|
-
var
|
|
12786
|
+
var fs5 = __toESM(require("fs"));
|
|
12787
|
+
var path12 = __toESM(require("path"));
|
|
12615
12788
|
var STATE_DIR = ".ordewell";
|
|
12616
12789
|
function ensureDir(dir) {
|
|
12617
|
-
if (!
|
|
12618
|
-
|
|
12790
|
+
if (!fs5.existsSync(dir)) {
|
|
12791
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
12619
12792
|
}
|
|
12620
12793
|
}
|
|
12621
12794
|
function getStateDir(baseDir) {
|
|
12622
|
-
return
|
|
12795
|
+
return path12.join(baseDir ?? process.cwd(), STATE_DIR);
|
|
12623
12796
|
}
|
|
12624
12797
|
var IGNORE_FILE = ".gitignore";
|
|
12625
12798
|
function ensureStateDirIgnored(baseDir) {
|
|
12626
12799
|
const dir = getStateDir(baseDir);
|
|
12627
12800
|
ensureDir(dir);
|
|
12628
12801
|
try {
|
|
12629
|
-
|
|
12802
|
+
fs5.writeFileSync(path12.join(dir, IGNORE_FILE), "*\n", { flag: "wx" });
|
|
12630
12803
|
} catch (err) {
|
|
12631
12804
|
if (err.code !== "EEXIST") throw err;
|
|
12632
12805
|
}
|
|
@@ -12641,7 +12814,7 @@ function mintSessionId() {
|
|
|
12641
12814
|
// src/utils/sessionStore.ts
|
|
12642
12815
|
var SESSIONS_SUBDIR = "sessions";
|
|
12643
12816
|
function getSessionsDir(baseDir) {
|
|
12644
|
-
return
|
|
12817
|
+
return path13.join(getStateDir(baseDir), SESSIONS_SUBDIR);
|
|
12645
12818
|
}
|
|
12646
12819
|
var REJECTION_MESSAGE = "This session was created with an older version of Ordewell. Please re-plan.";
|
|
12647
12820
|
function hasScalarRunner(parsed) {
|
|
@@ -12676,17 +12849,17 @@ function saveSession(plan, goal, baseDir, id) {
|
|
|
12676
12849
|
plan: JSON.parse(JSON.stringify(plan))
|
|
12677
12850
|
};
|
|
12678
12851
|
const filename = sessionFilename(meta);
|
|
12679
|
-
const filePath =
|
|
12680
|
-
|
|
12852
|
+
const filePath = path13.join(sessionsDir, filename);
|
|
12853
|
+
fs6.writeFileSync(filePath, JSON.stringify(session, null, 2));
|
|
12681
12854
|
return meta;
|
|
12682
12855
|
}
|
|
12683
12856
|
function listSessions(baseDir, logger = defaultLogger) {
|
|
12684
12857
|
const sessionsDir = getSessionsDir(baseDir);
|
|
12685
|
-
if (!
|
|
12686
|
-
const files =
|
|
12858
|
+
if (!fs6.existsSync(sessionsDir)) return [];
|
|
12859
|
+
const files = fs6.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
|
|
12687
12860
|
return files.map((f) => {
|
|
12688
12861
|
try {
|
|
12689
|
-
const raw =
|
|
12862
|
+
const raw = fs6.readFileSync(path13.join(sessionsDir, f), "utf-8");
|
|
12690
12863
|
const parsed = JSON.parse(raw);
|
|
12691
12864
|
if (hasScalarRunner(parsed)) {
|
|
12692
12865
|
logger.warn("sessionStore", `session file ${f}: ${REJECTION_MESSAGE}`);
|
|
@@ -12701,10 +12874,10 @@ function listSessions(baseDir, logger = defaultLogger) {
|
|
|
12701
12874
|
}).filter((m) => m !== null).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
12702
12875
|
}
|
|
12703
12876
|
function findSessionFile(sessionsDir, sessionId, logger) {
|
|
12704
|
-
const files =
|
|
12877
|
+
const files = fs6.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
|
|
12705
12878
|
for (const f of files) {
|
|
12706
12879
|
try {
|
|
12707
|
-
const raw =
|
|
12880
|
+
const raw = fs6.readFileSync(path13.join(sessionsDir, f), "utf-8");
|
|
12708
12881
|
const session = JSON.parse(raw);
|
|
12709
12882
|
if (session.meta.id === sessionId) return f;
|
|
12710
12883
|
} catch (err) {
|
|
@@ -12715,11 +12888,11 @@ function findSessionFile(sessionsDir, sessionId, logger) {
|
|
|
12715
12888
|
}
|
|
12716
12889
|
function loadSession(sessionId, baseDir, logger = defaultLogger) {
|
|
12717
12890
|
const sessionsDir = getSessionsDir(baseDir);
|
|
12718
|
-
if (!
|
|
12891
|
+
if (!fs6.existsSync(sessionsDir)) return null;
|
|
12719
12892
|
try {
|
|
12720
12893
|
const file = findSessionFile(sessionsDir, sessionId, logger);
|
|
12721
12894
|
if (file) {
|
|
12722
|
-
const raw =
|
|
12895
|
+
const raw = fs6.readFileSync(path13.join(sessionsDir, file), "utf-8");
|
|
12723
12896
|
const parsed = JSON.parse(raw);
|
|
12724
12897
|
if (hasScalarRunner(parsed)) {
|
|
12725
12898
|
logger.warn("sessionStore", `session file ${file}: ${REJECTION_MESSAGE}`);
|
|
@@ -12749,11 +12922,11 @@ function getLatestSession(baseDir, logger = defaultLogger) {
|
|
|
12749
12922
|
}
|
|
12750
12923
|
function deleteSession(sessionId, baseDir, logger = defaultLogger) {
|
|
12751
12924
|
const sessionsDir = getSessionsDir(baseDir);
|
|
12752
|
-
if (!
|
|
12925
|
+
if (!fs6.existsSync(sessionsDir)) return false;
|
|
12753
12926
|
try {
|
|
12754
12927
|
const file = findSessionFile(sessionsDir, sessionId, logger);
|
|
12755
12928
|
if (file) {
|
|
12756
|
-
|
|
12929
|
+
fs6.unlinkSync(path13.join(sessionsDir, file));
|
|
12757
12930
|
return true;
|
|
12758
12931
|
}
|
|
12759
12932
|
} catch (err) {
|
|
@@ -12762,6 +12935,29 @@ function deleteSession(sessionId, baseDir, logger = defaultLogger) {
|
|
|
12762
12935
|
return false;
|
|
12763
12936
|
}
|
|
12764
12937
|
|
|
12938
|
+
// src/utils/prdStore.ts
|
|
12939
|
+
var fs7 = __toESM(require("fs"));
|
|
12940
|
+
var path14 = __toESM(require("path"));
|
|
12941
|
+
var PRD_BLOCK_RE = /<!--\s*ORDEWELL_PRD_START\s+slug="([^"]+)"\s*-->\s*([\s\S]*?)\s*<!--\s*ORDEWELL_PRD_END\s*-->/;
|
|
12942
|
+
function extractPrdBlock(text) {
|
|
12943
|
+
const match = PRD_BLOCK_RE.exec(text);
|
|
12944
|
+
if (!match) return null;
|
|
12945
|
+
const slug2 = sanitizeSlug(match[1]);
|
|
12946
|
+
const markdown = match[2].trim();
|
|
12947
|
+
if (!slug2 || !markdown) return null;
|
|
12948
|
+
return { slug: slug2, markdown };
|
|
12949
|
+
}
|
|
12950
|
+
function sanitizeSlug(raw) {
|
|
12951
|
+
return raw.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
12952
|
+
}
|
|
12953
|
+
function savePrdMarkdown(workspace, slug2, markdown) {
|
|
12954
|
+
const dir = path14.join(workspace, ".scratch", sanitizeSlug(slug2));
|
|
12955
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
12956
|
+
const file = path14.join(dir, "PRD.md");
|
|
12957
|
+
fs7.writeFileSync(file, markdown.endsWith("\n") ? markdown : markdown + "\n", "utf8");
|
|
12958
|
+
return file;
|
|
12959
|
+
}
|
|
12960
|
+
|
|
12765
12961
|
// src/services/createSession.ts
|
|
12766
12962
|
var CATALOG_MODEL_CAP = 100;
|
|
12767
12963
|
var MAX_TASK_QUERIES = 3;
|
|
@@ -12778,6 +12974,17 @@ var PlanEditError = class extends Error {
|
|
|
12778
12974
|
function sessionRuntimeSettings(settings) {
|
|
12779
12975
|
return { ...plannerRuntimeToggles(settings), modelAllowlist: settings.modelAllowlist };
|
|
12780
12976
|
}
|
|
12977
|
+
function resolveSkillInvocation(text, skillsService) {
|
|
12978
|
+
const match = text.trim().match(/^\/([a-z][a-z0-9_-]*)$/im);
|
|
12979
|
+
if (!match) return text;
|
|
12980
|
+
const skillName = match[1].toLowerCase();
|
|
12981
|
+
const skill = skillsService.findSkill(skillName);
|
|
12982
|
+
if (!skill) {
|
|
12983
|
+
const available = typeof skillsService.listSkills === "function" ? skillsService.listSkills().map((s) => s.name).join(", ") : "";
|
|
12984
|
+
return `Unknown skill: ${skillName}. Available skills: ${available}`;
|
|
12985
|
+
}
|
|
12986
|
+
return skill.content;
|
|
12987
|
+
}
|
|
12781
12988
|
var Session = class {
|
|
12782
12989
|
/** Injected by a test; when present it is the service, forever. */
|
|
12783
12990
|
pinnedAiService;
|
|
@@ -12805,6 +13012,7 @@ var Session = class {
|
|
|
12805
13012
|
unsubObserver = null;
|
|
12806
13013
|
hostSessionId;
|
|
12807
13014
|
currentSessionId;
|
|
13015
|
+
skillsService;
|
|
12808
13016
|
constructor(deps) {
|
|
12809
13017
|
this.config = deps.config;
|
|
12810
13018
|
this.registry = deps.registry;
|
|
@@ -12823,6 +13031,7 @@ var Session = class {
|
|
|
12823
13031
|
this.settingsFn = deps.settings;
|
|
12824
13032
|
this.hostSessionId = deps.sessionId;
|
|
12825
13033
|
this.currentSessionId = deps.sessionId ?? mintSessionId();
|
|
13034
|
+
this.skillsService = deps.skillsService ?? new SkillsService(this.workspaceRootFn());
|
|
12826
13035
|
this.approvals = new PendingApprovals({
|
|
12827
13036
|
onRequest: ({ id, request }) => this.broadcast({
|
|
12828
13037
|
type: "approval_request",
|
|
@@ -12917,7 +13126,7 @@ var Session = class {
|
|
|
12917
13126
|
onTaskChanged: () => {
|
|
12918
13127
|
if (!this.plan) return;
|
|
12919
13128
|
const tasks = this.store.allTasks;
|
|
12920
|
-
this.broadcast({ type: "status_update", tasks: tasks.map(serializeTaskStatus) });
|
|
13129
|
+
this.broadcast({ type: "status_update", tasks: tasks.map((t) => serializeTaskStatus(t, this.orchestrator.getIdleSince(t.id))) });
|
|
12921
13130
|
},
|
|
12922
13131
|
onQueueReady: () => {
|
|
12923
13132
|
this.broadcast({ type: "queue_ready" });
|
|
@@ -12935,7 +13144,7 @@ var Session = class {
|
|
|
12935
13144
|
onTick: () => {
|
|
12936
13145
|
if (!this.plan) return;
|
|
12937
13146
|
const tasks = this.store.allTasks;
|
|
12938
|
-
this.broadcast({ type: "status_update", tasks: tasks.map(serializeTaskStatus) });
|
|
13147
|
+
this.broadcast({ type: "status_update", tasks: tasks.map((t) => serializeTaskStatus(t, this.orchestrator.getIdleSince(t.id))) });
|
|
12939
13148
|
},
|
|
12940
13149
|
onExecutionComplete: () => {
|
|
12941
13150
|
if (!this.plan) return;
|
|
@@ -13147,7 +13356,7 @@ var Session = class {
|
|
|
13147
13356
|
*/
|
|
13148
13357
|
async startPlanning(goal, runners, options) {
|
|
13149
13358
|
this.plan = null;
|
|
13150
|
-
this.goal = goal;
|
|
13359
|
+
this.goal = this.resolveSkillInvocation(goal);
|
|
13151
13360
|
this.remintSessionId();
|
|
13152
13361
|
this.beginFreshPlan();
|
|
13153
13362
|
const enabled = this.config.enabledRunners;
|
|
@@ -13156,7 +13365,7 @@ var Session = class {
|
|
|
13156
13365
|
const modelsByRunner = await this.modelResolver.modelsForRunners(chosenRunners);
|
|
13157
13366
|
this.modelsCache = modelsByRunner;
|
|
13158
13367
|
const runnerModes = this.runnerModesFor(chosenRunners);
|
|
13159
|
-
const {
|
|
13368
|
+
const { verificationEnabled, modelAllowlist } = this.settingsFn();
|
|
13160
13369
|
this.currentAllowlist = modelAllowlist ?? {};
|
|
13161
13370
|
const filteredModels = filterModelsForPrompt(modelsByRunner, this.currentAllowlist);
|
|
13162
13371
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -13166,22 +13375,18 @@ var Session = class {
|
|
|
13166
13375
|
status: "draft",
|
|
13167
13376
|
runners: chosenRunners,
|
|
13168
13377
|
lastUpdated: now,
|
|
13169
|
-
researchLog: [{ id: `up-${Date.now()}`, type: "user_prompt", content: goal, timestamp: now }],
|
|
13170
|
-
conversationHistory: [{ role: "user", content: goal, timestamp: now }]
|
|
13378
|
+
researchLog: [{ id: `up-${Date.now()}`, type: "user_prompt", content: this.goal, timestamp: now }],
|
|
13379
|
+
conversationHistory: [{ role: "user", content: this.goal, timestamp: now }]
|
|
13171
13380
|
};
|
|
13172
13381
|
const releaseAbort = this.denyApprovalsOnAbort(options?.signal);
|
|
13173
13382
|
try {
|
|
13174
13383
|
const turn = await this.aiService.startConversation({
|
|
13175
|
-
goal,
|
|
13384
|
+
goal: this.goal,
|
|
13176
13385
|
runners: chosenRunners,
|
|
13177
13386
|
modelsByRunner: filteredModels,
|
|
13178
13387
|
runnerModes,
|
|
13179
13388
|
autonomousDefault: this.config.autonomousMode,
|
|
13180
|
-
grillMeEnabled: grillMeEnabled ?? false,
|
|
13181
|
-
prdEnabled: prdEnabled ?? false,
|
|
13182
|
-
reviewEnabled: reviewEnabled ?? false,
|
|
13183
13389
|
verificationEnabled: verificationEnabled ?? false,
|
|
13184
|
-
researchSubagentsEnabled: researchSubagentsEnabled ?? false,
|
|
13185
13390
|
fs: this.fsAdapter,
|
|
13186
13391
|
fetcher: this.fetcher,
|
|
13187
13392
|
onProgress: (p) => this.translateProgress(p),
|
|
@@ -13193,21 +13398,22 @@ var Session = class {
|
|
|
13193
13398
|
}
|
|
13194
13399
|
}
|
|
13195
13400
|
/**
|
|
13196
|
-
* Every subsequent user reply in the planning conversation —
|
|
13197
|
-
* answers,
|
|
13401
|
+
* Every subsequent user reply in the planning conversation — clarifying
|
|
13402
|
+
* answers, outline confirm. One branch, no phase ladder.
|
|
13198
13403
|
*/
|
|
13199
13404
|
async continueConversation(userMessage, options) {
|
|
13200
13405
|
if (!this.plan) throw new Error("No planning conversation to continue");
|
|
13201
13406
|
const priorHistory = this.plan.conversationHistory ?? [];
|
|
13202
13407
|
const priorResearchLog = this.plan.researchLog ?? [];
|
|
13203
13408
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13204
|
-
this.
|
|
13409
|
+
const resolved = this.resolveSkillInvocation(userMessage);
|
|
13410
|
+
this.appendTranscript("user", resolved, now);
|
|
13205
13411
|
const historyAfterOwnAppend = this.plan.conversationHistory;
|
|
13206
|
-
this.plan.researchLog = [...this.plan.researchLog ?? [], { id: `up-${Date.now()}`, type: "user_prompt", content:
|
|
13412
|
+
this.plan.researchLog = [...this.plan.researchLog ?? [], { id: `up-${Date.now()}`, type: "user_prompt", content: resolved, timestamp: now }];
|
|
13207
13413
|
const contextBlock = [this.catalogBlock(), this.planContextBlock()].filter(Boolean).join("\n\n");
|
|
13208
13414
|
const outgoing = contextBlock ? `${contextBlock}
|
|
13209
13415
|
|
|
13210
|
-
${
|
|
13416
|
+
${resolved}` : resolved;
|
|
13211
13417
|
const aiService = this.aiService;
|
|
13212
13418
|
const canContinueLive = aiService.hasActiveConversation() && (aiService.conversationMatchesConfig?.() ?? true);
|
|
13213
13419
|
const releaseAbort = this.denyApprovalsOnAbort(options?.signal);
|
|
@@ -13347,8 +13553,8 @@ The plan is unchanged. Rephrase the request, or adjust the tasks manually.`,
|
|
|
13347
13553
|
* The catalog the planner may actually draw from — model ids and task-mode
|
|
13348
13554
|
* ids, per selected runner — emitted on EVERY turn, before any plan exists
|
|
13349
13555
|
* or after. The system prompt shows this once at conversation start; a long
|
|
13350
|
-
*
|
|
13351
|
-
*
|
|
13556
|
+
* clarifying conversation outlives that single showing and the planner
|
|
13557
|
+
* starts misquoting it, so this re-states it per turn instead.
|
|
13352
13558
|
*
|
|
13353
13559
|
* Built from the allowlist-filtered view (`filterModelsForPrompt`), the same
|
|
13354
13560
|
* one the system prompt used — a restricted allowlist stays a hard bound on
|
|
@@ -13479,7 +13685,7 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
|
|
|
13479
13685
|
const modelsByRunner = await this.modelResolver.modelsForRunners(runners);
|
|
13480
13686
|
this.modelsCache = modelsByRunner;
|
|
13481
13687
|
const runnerModes = this.runnerModesFor(runners);
|
|
13482
|
-
const {
|
|
13688
|
+
const { verificationEnabled, modelAllowlist } = this.settingsFn();
|
|
13483
13689
|
this.currentAllowlist = modelAllowlist ?? {};
|
|
13484
13690
|
const filteredModels = filterModelsForPrompt(modelsByRunner, this.currentAllowlist);
|
|
13485
13691
|
const goal = this.goal || priorHistory.find((m) => m.role === "user")?.content || userMessage;
|
|
@@ -13489,11 +13695,7 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
|
|
|
13489
13695
|
modelsByRunner: filteredModels,
|
|
13490
13696
|
runnerModes,
|
|
13491
13697
|
autonomousDefault: this.config.autonomousMode,
|
|
13492
|
-
grillMeEnabled: grillMeEnabled ?? false,
|
|
13493
|
-
prdEnabled: prdEnabled ?? false,
|
|
13494
|
-
reviewEnabled: reviewEnabled ?? false,
|
|
13495
13698
|
verificationEnabled: verificationEnabled ?? false,
|
|
13496
|
-
researchSubagentsEnabled: researchSubagentsEnabled ?? false,
|
|
13497
13699
|
fs: this.fsAdapter,
|
|
13498
13700
|
fetcher: this.fetcher,
|
|
13499
13701
|
onProgress: (p) => this.translateProgress(p),
|
|
@@ -13506,6 +13708,17 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
|
|
|
13506
13708
|
get isConversationActive() {
|
|
13507
13709
|
return this.aiService.hasActiveConversation();
|
|
13508
13710
|
}
|
|
13711
|
+
/**
|
|
13712
|
+
* Intercept a skill invocation (/skill-name) and substitute the skill's
|
|
13713
|
+
* markdown content BEFORE the message reaches the planner. Prevents runners
|
|
13714
|
+
* (Claude Code, OpenCode) from trying to resolve the skill in their own
|
|
13715
|
+
* directory instead of .ordewell/skills/. An unknown skill is surfaced to
|
|
13716
|
+
* the planner as a notice naming what IS available, rather than passing a
|
|
13717
|
+
* bare /unknown through to be mis-resolved.
|
|
13718
|
+
*/
|
|
13719
|
+
resolveSkillInvocation(text) {
|
|
13720
|
+
return resolveSkillInvocation(text, this.skillsService);
|
|
13721
|
+
}
|
|
13509
13722
|
/** Append one dialogue entry to the persisted transcript. Callers persist via the mutatePlan ritual. */
|
|
13510
13723
|
appendTranscript(role, content, timestamp, kind) {
|
|
13511
13724
|
if (!this.plan) return;
|
|
@@ -14721,13 +14934,12 @@ var TmuxRunner = class extends AbstractRunner {
|
|
|
14721
14934
|
// src/utils/daemonToken.ts
|
|
14722
14935
|
var import_crypto2 = require("crypto");
|
|
14723
14936
|
var import_fs2 = require("fs");
|
|
14724
|
-
var import_os2 = require("os");
|
|
14725
14937
|
var import_path2 = require("path");
|
|
14726
14938
|
var BEARER_PREFIX = "bearer ";
|
|
14727
14939
|
var DAEMON_TOKEN_SUBPROTOCOL_PREFIX = "ordewell.token.";
|
|
14728
14940
|
var DAEMON_SUBPROTOCOL = "ordewell.v1";
|
|
14729
14941
|
function configDir() {
|
|
14730
|
-
return (
|
|
14942
|
+
return globalDataDir();
|
|
14731
14943
|
}
|
|
14732
14944
|
function daemonTokenPath(port) {
|
|
14733
14945
|
return (0, import_path2.join)(configDir(), `server-${port}.token`);
|
|
@@ -14791,19 +15003,18 @@ function tokensMatch(presented, expected) {
|
|
|
14791
15003
|
}
|
|
14792
15004
|
|
|
14793
15005
|
// src/plugins/RunnerRegistry.ts
|
|
14794
|
-
var
|
|
14795
|
-
var
|
|
14796
|
-
var
|
|
15006
|
+
var fs9 = __toESM(require("fs"));
|
|
15007
|
+
var path17 = __toESM(require("path"));
|
|
15008
|
+
var os4 = __toESM(require("os"));
|
|
14797
15009
|
var import_child_process9 = require("child_process");
|
|
14798
15010
|
var import_crypto3 = require("crypto");
|
|
14799
15011
|
|
|
14800
15012
|
// src/plugins/FsPluginStore.ts
|
|
14801
|
-
var
|
|
14802
|
-
var
|
|
14803
|
-
var os4 = __toESM(require("os"));
|
|
15013
|
+
var fs8 = __toESM(require("fs"));
|
|
15014
|
+
var path16 = __toESM(require("path"));
|
|
14804
15015
|
|
|
14805
15016
|
// src/plugins/pluginNames.ts
|
|
14806
|
-
var
|
|
15017
|
+
var path15 = __toESM(require("path"));
|
|
14807
15018
|
var PLUGIN_NAME_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
14808
15019
|
var MAX_PLUGIN_NAME_LENGTH = 64;
|
|
14809
15020
|
function isPlainPluginName(name) {
|
|
@@ -14818,10 +15029,10 @@ function assertPlainPluginName(name) {
|
|
|
14818
15029
|
}
|
|
14819
15030
|
function resolvePluginInstallDir(pluginsDir, name) {
|
|
14820
15031
|
assertPlainPluginName(name);
|
|
14821
|
-
const root =
|
|
14822
|
-
const dest =
|
|
14823
|
-
const relative3 =
|
|
14824
|
-
if (relative3 !== name || relative3 === "" ||
|
|
15032
|
+
const root = path15.resolve(pluginsDir);
|
|
15033
|
+
const dest = path15.resolve(root, name);
|
|
15034
|
+
const relative3 = path15.relative(root, dest);
|
|
15035
|
+
if (relative3 !== name || relative3 === "" || path15.isAbsolute(relative3)) {
|
|
14825
15036
|
throw new Error(`Plugin install path escapes the plugins directory: ${JSON.stringify(name)}`);
|
|
14826
15037
|
}
|
|
14827
15038
|
return dest;
|
|
@@ -14836,13 +15047,13 @@ function isValidManifest(obj) {
|
|
|
14836
15047
|
|
|
14837
15048
|
// src/plugins/FsPluginStore.ts
|
|
14838
15049
|
function userPluginsDir() {
|
|
14839
|
-
return
|
|
15050
|
+
return path16.join(globalDataDir(), "plugins");
|
|
14840
15051
|
}
|
|
14841
15052
|
function loadManifestFromDir(dir) {
|
|
14842
|
-
const manifestPath =
|
|
14843
|
-
if (!
|
|
15053
|
+
const manifestPath = path16.join(dir, "manifest.json");
|
|
15054
|
+
if (!fs8.existsSync(manifestPath)) return null;
|
|
14844
15055
|
try {
|
|
14845
|
-
const raw =
|
|
15056
|
+
const raw = fs8.readFileSync(manifestPath, "utf8");
|
|
14846
15057
|
const parsed = JSON.parse(raw);
|
|
14847
15058
|
if (!isValidManifest(parsed)) return null;
|
|
14848
15059
|
return parsed;
|
|
@@ -14850,31 +15061,31 @@ function loadManifestFromDir(dir) {
|
|
|
14850
15061
|
return null;
|
|
14851
15062
|
}
|
|
14852
15063
|
}
|
|
14853
|
-
function
|
|
15064
|
+
function copyDirSync3(src, dest) {
|
|
14854
15065
|
ensureDir(dest);
|
|
14855
|
-
const entries =
|
|
15066
|
+
const entries = fs8.readdirSync(src, { withFileTypes: true });
|
|
14856
15067
|
for (const entry of entries) {
|
|
14857
|
-
const srcPath =
|
|
14858
|
-
const destPath =
|
|
15068
|
+
const srcPath = path16.join(src, entry.name);
|
|
15069
|
+
const destPath = path16.join(dest, entry.name);
|
|
14859
15070
|
if (entry.isDirectory()) {
|
|
14860
|
-
|
|
15071
|
+
copyDirSync3(srcPath, destPath);
|
|
14861
15072
|
} else {
|
|
14862
|
-
|
|
15073
|
+
fs8.copyFileSync(srcPath, destPath);
|
|
14863
15074
|
}
|
|
14864
15075
|
}
|
|
14865
15076
|
}
|
|
14866
15077
|
function rmDirSync(dir) {
|
|
14867
|
-
if (!
|
|
14868
|
-
const entries =
|
|
15078
|
+
if (!fs8.existsSync(dir)) return;
|
|
15079
|
+
const entries = fs8.readdirSync(dir, { withFileTypes: true });
|
|
14869
15080
|
for (const entry of entries) {
|
|
14870
|
-
const p =
|
|
15081
|
+
const p = path16.join(dir, entry.name);
|
|
14871
15082
|
if (entry.isDirectory()) {
|
|
14872
15083
|
rmDirSync(p);
|
|
14873
15084
|
} else {
|
|
14874
|
-
|
|
15085
|
+
fs8.unlinkSync(p);
|
|
14875
15086
|
}
|
|
14876
15087
|
}
|
|
14877
|
-
|
|
15088
|
+
fs8.rmdirSync(dir);
|
|
14878
15089
|
}
|
|
14879
15090
|
var FsPluginStore = class {
|
|
14880
15091
|
getUserPluginsDir() {
|
|
@@ -14882,10 +15093,10 @@ var FsPluginStore = class {
|
|
|
14882
15093
|
}
|
|
14883
15094
|
listUserPluginDirs() {
|
|
14884
15095
|
const dir = userPluginsDir();
|
|
14885
|
-
if (!
|
|
15096
|
+
if (!fs8.existsSync(dir)) return [];
|
|
14886
15097
|
let entries;
|
|
14887
15098
|
try {
|
|
14888
|
-
entries =
|
|
15099
|
+
entries = fs8.readdirSync(dir, { withFileTypes: true });
|
|
14889
15100
|
} catch {
|
|
14890
15101
|
return [];
|
|
14891
15102
|
}
|
|
@@ -14893,7 +15104,7 @@ var FsPluginStore = class {
|
|
|
14893
15104
|
}
|
|
14894
15105
|
listDir(dir) {
|
|
14895
15106
|
try {
|
|
14896
|
-
return
|
|
15107
|
+
return fs8.readdirSync(dir);
|
|
14897
15108
|
} catch {
|
|
14898
15109
|
return [];
|
|
14899
15110
|
}
|
|
@@ -14902,7 +15113,7 @@ var FsPluginStore = class {
|
|
|
14902
15113
|
return loadManifestFromDir(pluginDir);
|
|
14903
15114
|
}
|
|
14904
15115
|
copyDir(sourceDir, destDir) {
|
|
14905
|
-
|
|
15116
|
+
copyDirSync3(sourceDir, destDir);
|
|
14906
15117
|
}
|
|
14907
15118
|
removeDir(dir) {
|
|
14908
15119
|
rmDirSync(dir);
|
|
@@ -14911,20 +15122,20 @@ var FsPluginStore = class {
|
|
|
14911
15122
|
ensureDir(dir);
|
|
14912
15123
|
}
|
|
14913
15124
|
writeFile(filePath, content) {
|
|
14914
|
-
|
|
15125
|
+
fs8.writeFileSync(filePath, content);
|
|
14915
15126
|
}
|
|
14916
15127
|
readFile(filePath) {
|
|
14917
15128
|
try {
|
|
14918
|
-
return
|
|
15129
|
+
return fs8.readFileSync(filePath, "utf8");
|
|
14919
15130
|
} catch {
|
|
14920
15131
|
return null;
|
|
14921
15132
|
}
|
|
14922
15133
|
}
|
|
14923
|
-
dirExists(
|
|
14924
|
-
return
|
|
15134
|
+
dirExists(path20) {
|
|
15135
|
+
return fs8.existsSync(path20) && fs8.statSync(path20).isDirectory();
|
|
14925
15136
|
}
|
|
14926
|
-
exists(
|
|
14927
|
-
return
|
|
15137
|
+
exists(path20) {
|
|
15138
|
+
return fs8.existsSync(path20);
|
|
14928
15139
|
}
|
|
14929
15140
|
};
|
|
14930
15141
|
|
|
@@ -15351,7 +15562,7 @@ var RunnerRegistry = class {
|
|
|
15351
15562
|
return entry?.source === "builtin";
|
|
15352
15563
|
}
|
|
15353
15564
|
installFromPath(sourcePath) {
|
|
15354
|
-
const absPath =
|
|
15565
|
+
const absPath = path17.resolve(sourcePath);
|
|
15355
15566
|
if (!this.store.dirExists(absPath)) {
|
|
15356
15567
|
throw new Error(`Plugin source must be a directory: ${absPath}`);
|
|
15357
15568
|
}
|
|
@@ -15363,7 +15574,7 @@ var RunnerRegistry = class {
|
|
|
15363
15574
|
}
|
|
15364
15575
|
installFromGit(url) {
|
|
15365
15576
|
const validated = assertInstallablePluginUrl(url);
|
|
15366
|
-
const tmpDir =
|
|
15577
|
+
const tmpDir = path17.join(os4.tmpdir(), `ordewell-plugin-${(0, import_crypto3.randomBytes)(12).toString("hex")}`);
|
|
15367
15578
|
this.store.ensureDir(tmpDir);
|
|
15368
15579
|
try {
|
|
15369
15580
|
try {
|
|
@@ -15376,7 +15587,7 @@ var RunnerRegistry = class {
|
|
|
15376
15587
|
const hasManifest = contents.some((f) => f === "manifest.json");
|
|
15377
15588
|
let sourceDir = tmpDir;
|
|
15378
15589
|
if (!hasManifest) {
|
|
15379
|
-
const onlyEntry = contents.length === 1 ?
|
|
15590
|
+
const onlyEntry = contents.length === 1 ? path17.join(tmpDir, contents[0]) : void 0;
|
|
15380
15591
|
if (!onlyEntry || !this.store.dirExists(onlyEntry)) {
|
|
15381
15592
|
throw new Error(`No manifest.json found in repository: ${validated.toString()}`);
|
|
15382
15593
|
}
|
|
@@ -15424,7 +15635,7 @@ var RunnerRegistry = class {
|
|
|
15424
15635
|
}
|
|
15425
15636
|
createSkeleton(name, outputDir) {
|
|
15426
15637
|
assertPlainPluginName(name);
|
|
15427
|
-
const dir =
|
|
15638
|
+
const dir = path17.resolve(outputDir, name);
|
|
15428
15639
|
this.store.ensureDir(dir);
|
|
15429
15640
|
const manifest = {
|
|
15430
15641
|
name,
|
|
@@ -15456,8 +15667,8 @@ var RunnerRegistry = class {
|
|
|
15456
15667
|
]
|
|
15457
15668
|
}
|
|
15458
15669
|
};
|
|
15459
|
-
this.store.writeFile(
|
|
15460
|
-
const runSh =
|
|
15670
|
+
this.store.writeFile(path17.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
15671
|
+
const runSh = path17.join(dir, "run.sh");
|
|
15461
15672
|
const scriptContent = `#!/usr/bin/env bash
|
|
15462
15673
|
# Custom runner script for ${name}
|
|
15463
15674
|
# Receives:
|
|
@@ -15483,30 +15694,30 @@ echo "Running ${name} with prompt: $prompt"
|
|
|
15483
15694
|
};
|
|
15484
15695
|
function fsChmodSync(fpath, mode) {
|
|
15485
15696
|
try {
|
|
15486
|
-
|
|
15697
|
+
fs9.chmodSync(fpath, mode);
|
|
15487
15698
|
} catch {
|
|
15488
15699
|
}
|
|
15489
15700
|
}
|
|
15490
15701
|
|
|
15491
15702
|
// src/utils/stateStore.ts
|
|
15492
|
-
var
|
|
15493
|
-
var
|
|
15703
|
+
var fs10 = __toESM(require("fs"));
|
|
15704
|
+
var path18 = __toESM(require("path"));
|
|
15494
15705
|
var STATE_FILE = "state.json";
|
|
15495
15706
|
function getStatePath(baseDir) {
|
|
15496
|
-
return
|
|
15707
|
+
return path18.join(getStateDir(baseDir), STATE_FILE);
|
|
15497
15708
|
}
|
|
15498
15709
|
function saveState(plan, baseDir) {
|
|
15499
15710
|
const statePath = getStatePath(baseDir ?? process.cwd());
|
|
15500
|
-
ensureDir(
|
|
15711
|
+
ensureDir(path18.dirname(statePath));
|
|
15501
15712
|
plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
15502
|
-
|
|
15713
|
+
fs10.writeFileSync(statePath, JSON.stringify(plan, null, 2));
|
|
15503
15714
|
}
|
|
15504
15715
|
function loadState(baseDir, logger = defaultLogger) {
|
|
15505
15716
|
const dir = baseDir ?? process.cwd();
|
|
15506
15717
|
const statePath = getStatePath(dir);
|
|
15507
|
-
if (!
|
|
15718
|
+
if (!fs10.existsSync(statePath)) return null;
|
|
15508
15719
|
try {
|
|
15509
|
-
const raw =
|
|
15720
|
+
const raw = fs10.readFileSync(statePath, "utf-8");
|
|
15510
15721
|
const parsed = JSON.parse(raw);
|
|
15511
15722
|
if (!Array.isArray(parsed.runners)) {
|
|
15512
15723
|
logger.warn("stateStore", `rejecting state from ${statePath}: old format (scalar "runner" instead of "runners" array). Please re-plan.`);
|
|
@@ -15521,29 +15732,24 @@ function loadState(baseDir, logger = defaultLogger) {
|
|
|
15521
15732
|
function clearState(baseDir) {
|
|
15522
15733
|
const dir = baseDir ?? process.cwd();
|
|
15523
15734
|
const statePath = getStatePath(dir);
|
|
15524
|
-
if (
|
|
15735
|
+
if (fs10.existsSync(statePath)) fs10.unlinkSync(statePath);
|
|
15525
15736
|
}
|
|
15526
15737
|
function stateExists(baseDir) {
|
|
15527
15738
|
const dir = baseDir ?? process.cwd();
|
|
15528
|
-
return
|
|
15739
|
+
return fs10.existsSync(getStatePath(dir));
|
|
15529
15740
|
}
|
|
15530
15741
|
|
|
15531
15742
|
// src/services/SettingsService.ts
|
|
15532
|
-
var
|
|
15533
|
-
var
|
|
15534
|
-
var os6 = __toESM(require("os"));
|
|
15743
|
+
var fs11 = __toESM(require("fs"));
|
|
15744
|
+
var path19 = __toESM(require("path"));
|
|
15535
15745
|
var DEFAULTS = {
|
|
15536
|
-
grillMe: { enabled: false },
|
|
15537
15746
|
tdd: { enabled: true },
|
|
15538
|
-
|
|
15539
|
-
review: { enabled: false },
|
|
15540
|
-
verification: { enabled: false },
|
|
15541
|
-
researchSubagents: { enabled: false }
|
|
15747
|
+
verification: { enabled: false }
|
|
15542
15748
|
};
|
|
15543
15749
|
function getSettingsPath() {
|
|
15544
15750
|
const override = process.env.ORDEWELL_SETTINGS_PATH;
|
|
15545
15751
|
if (override && override.trim()) return override.trim();
|
|
15546
|
-
return
|
|
15752
|
+
return path19.join(globalDataDir(), "settings.json");
|
|
15547
15753
|
}
|
|
15548
15754
|
function readPlannerModels(raw) {
|
|
15549
15755
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
@@ -15576,43 +15782,19 @@ var SettingsService = class {
|
|
|
15576
15782
|
}
|
|
15577
15783
|
fileMtimeMs() {
|
|
15578
15784
|
try {
|
|
15579
|
-
return
|
|
15785
|
+
return fs11.statSync(this.filePath).mtimeMs;
|
|
15580
15786
|
} catch {
|
|
15581
15787
|
return null;
|
|
15582
15788
|
}
|
|
15583
15789
|
}
|
|
15584
|
-
getGrillMe() {
|
|
15585
|
-
return this.getAll().grillMe.enabled;
|
|
15586
|
-
}
|
|
15587
15790
|
getTdd() {
|
|
15588
15791
|
return this.getAll().tdd.enabled;
|
|
15589
15792
|
}
|
|
15590
|
-
getPrd() {
|
|
15591
|
-
return this.getAll().prd.enabled;
|
|
15592
|
-
}
|
|
15593
|
-
setGrillMe(enabled) {
|
|
15594
|
-
this.getAll();
|
|
15595
|
-
this.cache.grillMe.enabled = enabled;
|
|
15596
|
-
this.persist();
|
|
15597
|
-
}
|
|
15598
15793
|
setTdd(enabled) {
|
|
15599
15794
|
this.getAll();
|
|
15600
15795
|
this.cache.tdd.enabled = enabled;
|
|
15601
15796
|
this.persist();
|
|
15602
15797
|
}
|
|
15603
|
-
setPrd(enabled) {
|
|
15604
|
-
this.getAll();
|
|
15605
|
-
this.cache.prd.enabled = enabled;
|
|
15606
|
-
this.persist();
|
|
15607
|
-
}
|
|
15608
|
-
getReview() {
|
|
15609
|
-
return this.getAll().review.enabled;
|
|
15610
|
-
}
|
|
15611
|
-
setReview(enabled) {
|
|
15612
|
-
this.getAll();
|
|
15613
|
-
this.cache.review.enabled = enabled;
|
|
15614
|
-
this.persist();
|
|
15615
|
-
}
|
|
15616
15798
|
getVerification() {
|
|
15617
15799
|
return this.getAll().verification.enabled;
|
|
15618
15800
|
}
|
|
@@ -15621,14 +15803,6 @@ var SettingsService = class {
|
|
|
15621
15803
|
this.cache.verification.enabled = enabled;
|
|
15622
15804
|
this.persist();
|
|
15623
15805
|
}
|
|
15624
|
-
getResearchSubagents() {
|
|
15625
|
-
return this.getAll().researchSubagents.enabled;
|
|
15626
|
-
}
|
|
15627
|
-
setResearchSubagents(enabled) {
|
|
15628
|
-
this.getAll();
|
|
15629
|
-
this.cache.researchSubagents.enabled = enabled;
|
|
15630
|
-
this.persist();
|
|
15631
|
-
}
|
|
15632
15806
|
getModelAllowlist(runner) {
|
|
15633
15807
|
return this.getAll().modelAllowlist?.[runner];
|
|
15634
15808
|
}
|
|
@@ -15678,18 +15852,13 @@ var SettingsService = class {
|
|
|
15678
15852
|
this.persist();
|
|
15679
15853
|
}
|
|
15680
15854
|
load() {
|
|
15855
|
+
migrateOldConfigDir();
|
|
15681
15856
|
try {
|
|
15682
|
-
if (
|
|
15683
|
-
const raw = JSON.parse(
|
|
15857
|
+
if (fs11.existsSync(this.filePath)) {
|
|
15858
|
+
const raw = JSON.parse(fs11.readFileSync(this.filePath, "utf-8"));
|
|
15684
15859
|
const settings = {
|
|
15685
|
-
grillMe: { enabled: raw.grillMe?.enabled ?? DEFAULTS.grillMe.enabled },
|
|
15686
15860
|
tdd: { enabled: raw.tdd?.enabled ?? DEFAULTS.tdd.enabled },
|
|
15687
|
-
|
|
15688
|
-
// `verify` is the legacy key for review mode; keep reading it so old settings files survive the rename.
|
|
15689
|
-
// The evidence-based verification mode uses the distinct `verification` key to avoid colliding with it.
|
|
15690
|
-
review: { enabled: raw.review?.enabled ?? raw.verify?.enabled ?? DEFAULTS.review.enabled },
|
|
15691
|
-
verification: { enabled: raw.verification?.enabled ?? DEFAULTS.verification.enabled },
|
|
15692
|
-
researchSubagents: { enabled: raw.researchSubagents?.enabled ?? DEFAULTS.researchSubagents.enabled }
|
|
15861
|
+
verification: { enabled: raw.verification?.enabled ?? DEFAULTS.verification.enabled }
|
|
15693
15862
|
};
|
|
15694
15863
|
if (raw.modelAllowlist !== void 0) {
|
|
15695
15864
|
settings.modelAllowlist = raw.modelAllowlist;
|
|
@@ -15705,14 +15874,14 @@ var SettingsService = class {
|
|
|
15705
15874
|
}
|
|
15706
15875
|
} catch {
|
|
15707
15876
|
}
|
|
15708
|
-
return { ...DEFAULTS,
|
|
15877
|
+
return { ...DEFAULTS, tdd: { ...DEFAULTS.tdd }, verification: { ...DEFAULTS.verification } };
|
|
15709
15878
|
}
|
|
15710
15879
|
persist() {
|
|
15711
|
-
const dir =
|
|
15712
|
-
if (!
|
|
15713
|
-
|
|
15880
|
+
const dir = path19.dirname(this.filePath);
|
|
15881
|
+
if (!fs11.existsSync(dir)) {
|
|
15882
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
15714
15883
|
}
|
|
15715
|
-
|
|
15884
|
+
fs11.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2));
|
|
15716
15885
|
this.cachedMtimeMs = this.fileMtimeMs();
|
|
15717
15886
|
}
|
|
15718
15887
|
};
|
|
@@ -15759,6 +15928,7 @@ var PlannerModelMemory = class {
|
|
|
15759
15928
|
AbstractRunner,
|
|
15760
15929
|
AbstractTerminalSession,
|
|
15761
15930
|
ApprovalPolicy,
|
|
15931
|
+
BUILTIN_SKILL_NAMES,
|
|
15762
15932
|
BaseAiService,
|
|
15763
15933
|
BaseConfig,
|
|
15764
15934
|
BaseFileSystem,
|
|
@@ -15817,6 +15987,7 @@ var PlannerModelMemory = class {
|
|
|
15817
15987
|
SYMBOL_LANGUAGES,
|
|
15818
15988
|
Session,
|
|
15819
15989
|
SettingsService,
|
|
15990
|
+
SkillsService,
|
|
15820
15991
|
StdioAgentAdapter,
|
|
15821
15992
|
TASK_OPS_ENVELOPE_KEY,
|
|
15822
15993
|
TASK_QUERY_ANSWER_OR_OPS,
|
|
@@ -15882,6 +16053,7 @@ var PlannerModelMemory = class {
|
|
|
15882
16053
|
configuredProviders,
|
|
15883
16054
|
createAiService,
|
|
15884
16055
|
createEmptyPlan,
|
|
16056
|
+
createSkillsService,
|
|
15885
16057
|
createTask,
|
|
15886
16058
|
daemonTokenPath,
|
|
15887
16059
|
defaultLogger,
|
|
@@ -15908,12 +16080,14 @@ var PlannerModelMemory = class {
|
|
|
15908
16080
|
filterModelsForPrompt,
|
|
15909
16081
|
filteredBuildModes,
|
|
15910
16082
|
flattenTasks,
|
|
16083
|
+
flattenTasksWithParents,
|
|
15911
16084
|
formatSearchOutput,
|
|
15912
16085
|
generatePlanWithRepair,
|
|
15913
16086
|
getLatestSession,
|
|
15914
16087
|
getProviderMeta,
|
|
15915
16088
|
getSettingsPath,
|
|
15916
16089
|
getStateDir,
|
|
16090
|
+
globalDataDir,
|
|
15917
16091
|
grantScopeFor,
|
|
15918
16092
|
hasTmux,
|
|
15919
16093
|
includeGlobFor,
|
|
@@ -15933,6 +16107,7 @@ var PlannerModelMemory = class {
|
|
|
15933
16107
|
looksLikePlanAttempt,
|
|
15934
16108
|
mapAgentTool,
|
|
15935
16109
|
migrateLegacyPlan,
|
|
16110
|
+
migrateOldConfigDir,
|
|
15936
16111
|
migratePlanState,
|
|
15937
16112
|
migrateTask,
|
|
15938
16113
|
mintDaemonToken,
|
|
@@ -15967,10 +16142,12 @@ var PlannerModelMemory = class {
|
|
|
15967
16142
|
resolveArgs,
|
|
15968
16143
|
resolveDefaultMode,
|
|
15969
16144
|
resolveModelShortcut,
|
|
16145
|
+
resolveOrderLabel,
|
|
15970
16146
|
resolvePluginInstallDir,
|
|
15971
16147
|
resolveProvider,
|
|
15972
16148
|
resolveProviderFromPrefix,
|
|
15973
16149
|
resolveResearchShell,
|
|
16150
|
+
resolveSkillInvocation,
|
|
15974
16151
|
resolveTaskMode,
|
|
15975
16152
|
resolveWithin,
|
|
15976
16153
|
retargetTaskRunner,
|
|
@@ -15993,6 +16170,7 @@ var PlannerModelMemory = class {
|
|
|
15993
16170
|
summarizeOutput,
|
|
15994
16171
|
summarizeToolCall,
|
|
15995
16172
|
taskOpsRejectedPrompt,
|
|
16173
|
+
taskOrderLabel,
|
|
15996
16174
|
taskQuerySignature,
|
|
15997
16175
|
textHasTaskOps,
|
|
15998
16176
|
textHasTaskQuery,
|