@ordewell/core 0.4.10 → 0.4.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ApprovalPolicy-BVhGdECT.d.mts +79 -0
- package/dist/ApprovalPolicy-BVhGdECT.d.ts +79 -0
- package/dist/{ITerminalRunner-CbE6gKLI.d.ts → ITerminalRunner-D_RBDoHa.d.mts} +10 -81
- package/dist/{ITerminalRunner-1NAv8wCC.d.mts → ITerminalRunner-DqAs12sy.d.ts} +10 -81
- package/dist/{ModeResolver-F5wnlSz1.d.mts → ModeResolver-CCKYqdZC.d.mts} +1 -1
- package/dist/{ModeResolver-Dx5gzNPC.d.ts → ModeResolver-CzZd-i6T.d.ts} +1 -1
- package/dist/{Task-1NwmUImI.d.mts → Task-cGrfYRXK.d.mts} +8 -1
- package/dist/{Task-1NwmUImI.d.ts → Task-cGrfYRXK.d.ts} +8 -1
- package/dist/chunk-HPNUFBIT.mjs +19 -0
- package/dist/chunk-HPNUFBIT.mjs.map +1 -0
- package/dist/{chunk-IO6PEEN6.mjs → chunk-N6HTBUKG.mjs} +11 -1
- package/dist/chunk-N6HTBUKG.mjs.map +1 -0
- package/dist/{chunk-TUGJXD4G.mjs → chunk-UEJ3SZQ7.mjs} +2 -2
- package/dist/{chunk-YI4ZDZEE.mjs → chunk-YTJ7EOV6.mjs} +62 -3
- package/dist/chunk-YTJ7EOV6.mjs.map +1 -0
- package/dist/index.d.mts +187 -235
- package/dist/index.d.ts +187 -235
- package/dist/index.js +643 -439
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +611 -486
- package/dist/index.mjs.map +1 -1
- package/dist/order-labels.d.mts +17 -0
- package/dist/order-labels.d.ts +17 -0
- package/dist/order-labels.js +44 -0
- package/dist/order-labels.js.map +1 -0
- package/dist/order-labels.mjs +9 -0
- package/dist/order-labels.mjs.map +1 -0
- package/dist/{parsing-BqbJySeo.d.mts → parsing-BT-dvFcu.d.mts} +2 -2
- package/dist/{parsing-X0Gvuz-q.d.ts → parsing-D87zKtTe.d.ts} +2 -2
- package/dist/parsing.d.mts +3 -3
- package/dist/parsing.d.ts +3 -3
- package/dist/parsing.js.map +1 -1
- package/dist/parsing.mjs +3 -2
- package/dist/{plan-utils-DtsfHnn_.d.ts → plan-utils-BbC9KdK4.d.ts} +139 -3
- package/dist/{plan-utils-B5rVXNVI.d.mts → plan-utils-DHBIvkiT.d.mts} +139 -3
- package/dist/plan-utils.d.mts +4 -3
- package/dist/plan-utils.d.ts +4 -3
- package/dist/plan-utils.js +14 -2
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +9 -4
- package/dist/testing.d.mts +3 -2
- package/dist/testing.d.ts +3 -2
- package/package.json +12 -2
- package/skills/grilling/SKILL.md +31 -0
- package/skills/to-spec/SKILL.md +64 -0
- package/dist/chunk-IO6PEEN6.mjs.map +0 -1
- package/dist/chunk-YI4ZDZEE.mjs.map +0 -1
- /package/dist/{chunk-TUGJXD4G.mjs.map → chunk-UEJ3SZQ7.mjs.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -36,9 +36,11 @@ __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,
|
|
43
|
+
CHECKPOINT_TRUNCATE_LENGTH: () => CHECKPOINT_TRUNCATE_LENGTH,
|
|
42
44
|
CLAUDE_CODE_MANIFEST: () => CLAUDE_CODE_MANIFEST,
|
|
43
45
|
CLI_PROVIDERS: () => CLI_PROVIDERS,
|
|
44
46
|
CMD_EXE_MAX_COMMAND_LINE: () => CMD_EXE_MAX_COMMAND_LINE,
|
|
@@ -94,6 +96,7 @@ __export(index_exports, {
|
|
|
94
96
|
SYMBOL_LANGUAGES: () => SYMBOL_LANGUAGES,
|
|
95
97
|
Session: () => Session,
|
|
96
98
|
SettingsService: () => SettingsService,
|
|
99
|
+
SkillsService: () => SkillsService,
|
|
97
100
|
StdioAgentAdapter: () => StdioAgentAdapter,
|
|
98
101
|
TASK_OPS_ENVELOPE_KEY: () => TASK_OPS_ENVELOPE_KEY,
|
|
99
102
|
TASK_QUERY_ANSWER_OR_OPS: () => TASK_QUERY_ANSWER_OR_OPS,
|
|
@@ -159,6 +162,7 @@ __export(index_exports, {
|
|
|
159
162
|
configuredProviders: () => configuredProviders,
|
|
160
163
|
createAiService: () => createAiService,
|
|
161
164
|
createEmptyPlan: () => createEmptyPlan,
|
|
165
|
+
createSkillsService: () => createSkillsService,
|
|
162
166
|
createTask: () => createTask,
|
|
163
167
|
daemonTokenPath: () => daemonTokenPath,
|
|
164
168
|
defaultLogger: () => defaultLogger,
|
|
@@ -185,12 +189,14 @@ __export(index_exports, {
|
|
|
185
189
|
filterModelsForPrompt: () => filterModelsForPrompt,
|
|
186
190
|
filteredBuildModes: () => filteredBuildModes,
|
|
187
191
|
flattenTasks: () => flattenTasks,
|
|
192
|
+
flattenTasksWithParents: () => flattenTasksWithParents,
|
|
188
193
|
formatSearchOutput: () => formatSearchOutput,
|
|
189
194
|
generatePlanWithRepair: () => generatePlanWithRepair,
|
|
190
195
|
getLatestSession: () => getLatestSession,
|
|
191
196
|
getProviderMeta: () => getProviderMeta,
|
|
192
197
|
getSettingsPath: () => getSettingsPath,
|
|
193
198
|
getStateDir: () => getStateDir,
|
|
199
|
+
globalDataDir: () => globalDataDir,
|
|
194
200
|
grantScopeFor: () => grantScopeFor,
|
|
195
201
|
hasTmux: () => hasTmux,
|
|
196
202
|
includeGlobFor: () => includeGlobFor,
|
|
@@ -210,6 +216,7 @@ __export(index_exports, {
|
|
|
210
216
|
looksLikePlanAttempt: () => looksLikePlanAttempt,
|
|
211
217
|
mapAgentTool: () => mapAgentTool,
|
|
212
218
|
migrateLegacyPlan: () => migrateLegacyPlan,
|
|
219
|
+
migrateOldConfigDir: () => migrateOldConfigDir,
|
|
213
220
|
migratePlanState: () => migratePlanState,
|
|
214
221
|
migrateTask: () => migrateTask,
|
|
215
222
|
mintDaemonToken: () => mintDaemonToken,
|
|
@@ -244,10 +251,12 @@ __export(index_exports, {
|
|
|
244
251
|
resolveArgs: () => resolveArgs,
|
|
245
252
|
resolveDefaultMode: () => resolveDefaultMode,
|
|
246
253
|
resolveModelShortcut: () => resolveModelShortcut,
|
|
254
|
+
resolveOrderLabel: () => resolveOrderLabel,
|
|
247
255
|
resolvePluginInstallDir: () => resolvePluginInstallDir,
|
|
248
256
|
resolveProvider: () => resolveProvider,
|
|
249
257
|
resolveProviderFromPrefix: () => resolveProviderFromPrefix,
|
|
250
258
|
resolveResearchShell: () => resolveResearchShell,
|
|
259
|
+
resolveSkillInvocation: () => resolveSkillInvocation,
|
|
251
260
|
resolveTaskMode: () => resolveTaskMode,
|
|
252
261
|
resolveWithin: () => resolveWithin,
|
|
253
262
|
retargetTaskRunner: () => retargetTaskRunner,
|
|
@@ -270,6 +279,7 @@ __export(index_exports, {
|
|
|
270
279
|
summarizeOutput: () => summarizeOutput,
|
|
271
280
|
summarizeToolCall: () => summarizeToolCall,
|
|
272
281
|
taskOpsRejectedPrompt: () => taskOpsRejectedPrompt,
|
|
282
|
+
taskOrderLabel: () => taskOrderLabel,
|
|
273
283
|
taskQuerySignature: () => taskQuerySignature,
|
|
274
284
|
textHasTaskOps: () => textHasTaskOps,
|
|
275
285
|
textHasTaskQuery: () => textHasTaskQuery,
|
|
@@ -279,6 +289,7 @@ __export(index_exports, {
|
|
|
279
289
|
toOrchestratorOptions: () => toOrchestratorOptions,
|
|
280
290
|
tokenSubprotocols: () => tokenSubprotocols,
|
|
281
291
|
tokensMatch: () => tokensMatch,
|
|
292
|
+
truncateCheckpointSummary: () => truncateCheckpointSummary,
|
|
282
293
|
truncatedPlanReEmitPrompt: () => truncatedPlanReEmitPrompt,
|
|
283
294
|
updateTaskInPlan: () => updateTaskInPlan,
|
|
284
295
|
validateModifiedPlan: () => validateModifiedPlan,
|
|
@@ -293,6 +304,22 @@ module.exports = __toCommonJS(index_exports);
|
|
|
293
304
|
|
|
294
305
|
// src/models/Task.ts
|
|
295
306
|
var import_uuid = require("uuid");
|
|
307
|
+
|
|
308
|
+
// src/order-labels.ts
|
|
309
|
+
function taskOrderLabel(task, parent) {
|
|
310
|
+
return parent ? `${parent.order}.${task.order}` : String(task.order);
|
|
311
|
+
}
|
|
312
|
+
function resolveOrderLabel(tasks, label) {
|
|
313
|
+
const parts = label.split(".");
|
|
314
|
+
if (parts.length > 2 || parts.some((p) => !/^\d+$/.test(p))) return void 0;
|
|
315
|
+
const [topOrder, childOrder] = parts.map(Number);
|
|
316
|
+
const top = tasks.find((t) => t.order === topOrder);
|
|
317
|
+
if (!top) return void 0;
|
|
318
|
+
if (childOrder === void 0) return top;
|
|
319
|
+
return top.subtasks.find((s) => s.order === childOrder);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// src/models/Task.ts
|
|
296
323
|
function emptyWarnings() {
|
|
297
324
|
return { deletedCompleted: [], changedCompleted: [], deletedInProgress: [], modifiedInProgress: [], brokenDependencies: [] };
|
|
298
325
|
}
|
|
@@ -477,6 +504,15 @@ function createEmptyPlan() {
|
|
|
477
504
|
function flattenTasks(tasks) {
|
|
478
505
|
return tasks.flatMap((t) => [t, ...flattenTasks(t.subtasks ?? [])]);
|
|
479
506
|
}
|
|
507
|
+
function flattenTasksWithParents(tasks) {
|
|
508
|
+
const rows = [];
|
|
509
|
+
const walk = (t, parent) => {
|
|
510
|
+
rows.push({ task: t, parent });
|
|
511
|
+
for (const sub of t.subtasks ?? []) walk(sub, t);
|
|
512
|
+
};
|
|
513
|
+
for (const t of tasks) walk(t, null);
|
|
514
|
+
return rows;
|
|
515
|
+
}
|
|
480
516
|
function migrateTask(task) {
|
|
481
517
|
if (!task.assignedModel) {
|
|
482
518
|
task.assignedModel = void 0;
|
|
@@ -3103,7 +3139,7 @@ function redactSecrets(text) {
|
|
|
3103
3139
|
const { value, tail } = trimTrailingPunctuation(raw);
|
|
3104
3140
|
if (value.length < 8) return full;
|
|
3105
3141
|
if (looksLikeCode(value) || value.startsWith("$")) return full;
|
|
3106
|
-
if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && /\d/.test(value)) {
|
|
3142
|
+
if (UNAMBIGUOUS_CREDENTIAL_NAME.test(head) && (/\d/.test(value) || value.length >= 12)) {
|
|
3107
3143
|
return `${head}${quote}${marker("credential", value.length)}${quote}${tail}`;
|
|
3108
3144
|
}
|
|
3109
3145
|
if (IDENTIFIER.test(value) || SLUG.test(value)) return full;
|
|
@@ -3142,33 +3178,33 @@ function grepOptions(args) {
|
|
|
3142
3178
|
};
|
|
3143
3179
|
}
|
|
3144
3180
|
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,
|
|
3181
|
+
async function executeTool(name, args, fs12, fetcher, signal) {
|
|
3182
|
+
const outcome = await runTool(name, args, fs12, fetcher, signal);
|
|
3147
3183
|
return { ...outcome, output: redactSecrets(outcome.output) };
|
|
3148
3184
|
}
|
|
3149
|
-
async function runTool(name, args,
|
|
3185
|
+
async function runTool(name, args, fs12, fetcher, signal) {
|
|
3150
3186
|
if (signal?.aborted) return { success: false, output: STOPPED_TOOL_RESULT, truncated: false };
|
|
3151
3187
|
switch (name) {
|
|
3152
3188
|
case "read_file": {
|
|
3153
3189
|
const offset = num(args.offset);
|
|
3154
3190
|
const limit = num(args.limit);
|
|
3155
3191
|
const maxBytes = num(args.maxBytes);
|
|
3156
|
-
return
|
|
3192
|
+
return fs12.readFile(String(args.path ?? ""), { offset, limit, maxBytes });
|
|
3157
3193
|
}
|
|
3158
3194
|
case "read_files": {
|
|
3159
3195
|
const paths = Array.isArray(args.paths) ? args.paths.map(String) : [];
|
|
3160
|
-
return
|
|
3196
|
+
return fs12.readFiles(paths);
|
|
3161
3197
|
}
|
|
3162
3198
|
case "glob":
|
|
3163
|
-
return
|
|
3199
|
+
return fs12.glob(String(args.pattern ?? ""), { path: str(args.path), headLimit: num(args.head_limit ?? args.headLimit) });
|
|
3164
3200
|
case "grep":
|
|
3165
|
-
return
|
|
3201
|
+
return fs12.grep(String(args.pattern ?? ""), grepOptions(args));
|
|
3166
3202
|
case "find_symbol":
|
|
3167
|
-
return
|
|
3203
|
+
return fs12.findSymbol(String(args.symbol ?? ""), { language: str(args.language), path: str(args.path) });
|
|
3168
3204
|
case "list_dir":
|
|
3169
|
-
return
|
|
3205
|
+
return fs12.listDir(String(args.path ?? ""), num(args.depth));
|
|
3170
3206
|
case "bash":
|
|
3171
|
-
return
|
|
3207
|
+
return fs12.bash(String(args.command ?? ""), signal);
|
|
3172
3208
|
case "fetch": {
|
|
3173
3209
|
if (!fetcher) return { success: false, output: "Web fetching is not available in this environment.", truncated: false };
|
|
3174
3210
|
const url = String(args.url ?? "");
|
|
@@ -4320,14 +4356,7 @@ function resolveTaskMode(emittedMode, runnerId, runnerModes, autonomousDefault =
|
|
|
4320
4356
|
|
|
4321
4357
|
// src/services/plannerModes.ts
|
|
4322
4358
|
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
4359
|
{ id: "verify", settingsKey: "verification", runtimeKey: "verificationEnabled", scopes: ["chat", "one-shot"] },
|
|
4330
|
-
{ id: "research-subagents", settingsKey: "researchSubagents", runtimeKey: "researchSubagentsEnabled", scopes: ["chat", "one-shot"] },
|
|
4331
4360
|
{ id: "tdd", settingsKey: "tdd", runtimeKey: "tddEnabled", scopes: ["task"] }
|
|
4332
4361
|
];
|
|
4333
4362
|
function plannerRuntimeToggles(settings) {
|
|
@@ -4337,28 +4366,16 @@ function plannerRuntimeToggles(settings) {
|
|
|
4337
4366
|
}
|
|
4338
4367
|
var DEFAULT_PLANNER_MODES = {
|
|
4339
4368
|
autonomousDefault: true,
|
|
4340
|
-
|
|
4341
|
-
prd: false,
|
|
4342
|
-
review: false,
|
|
4343
|
-
verification: false,
|
|
4344
|
-
researchSubagents: false
|
|
4369
|
+
verification: false
|
|
4345
4370
|
};
|
|
4346
4371
|
function plannerModesFrom(settings, autonomousDefault) {
|
|
4347
4372
|
return {
|
|
4348
4373
|
autonomousDefault,
|
|
4349
|
-
|
|
4350
|
-
prd: settings.prdEnabled ?? false,
|
|
4351
|
-
review: settings.reviewEnabled ?? false,
|
|
4352
|
-
verification: settings.verificationEnabled ?? false,
|
|
4353
|
-
researchSubagents: settings.researchSubagentsEnabled ?? false
|
|
4374
|
+
verification: settings.verificationEnabled ?? false
|
|
4354
4375
|
};
|
|
4355
4376
|
}
|
|
4356
4377
|
var MODE_FIELD = {
|
|
4357
|
-
"grill-me": "grillMe",
|
|
4358
|
-
prd: "prd",
|
|
4359
|
-
review: "review",
|
|
4360
4378
|
verify: "verification",
|
|
4361
|
-
"research-subagents": "researchSubagents",
|
|
4362
4379
|
tdd: null
|
|
4363
4380
|
};
|
|
4364
4381
|
function modesFor(scope, modes) {
|
|
@@ -4686,7 +4703,7 @@ function renderTaskQueryAnswer(query, tasks, catalog) {
|
|
|
4686
4703
|
}
|
|
4687
4704
|
|
|
4688
4705
|
// src/services/PlanPrompts.ts
|
|
4689
|
-
function buildResearchToolsPrompt(
|
|
4706
|
+
function buildResearchToolsPrompt() {
|
|
4690
4707
|
const lines = [
|
|
4691
4708
|
"You have access to the following tools to explore the workspace:",
|
|
4692
4709
|
"",
|
|
@@ -4702,15 +4719,11 @@ function buildResearchToolsPrompt(subagentsEnabled = false) {
|
|
|
4702
4719
|
"- glob: for finding files by name/path pattern",
|
|
4703
4720
|
"- grep: for searching inside file contents",
|
|
4704
4721
|
"",
|
|
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."
|
|
4722
|
+
"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.",
|
|
4723
|
+
"",
|
|
4724
|
+
'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.',
|
|
4725
|
+
"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
4726
|
];
|
|
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
4727
|
return lines.join("\n");
|
|
4715
4728
|
}
|
|
4716
4729
|
function buildSubagentSystemPrompt() {
|
|
@@ -4748,11 +4761,11 @@ function buildModeExamplesForRunners(runners) {
|
|
|
4748
4761
|
return `${r}: default|plan`;
|
|
4749
4762
|
}).join(", ");
|
|
4750
4763
|
}
|
|
4751
|
-
function verificationModeBlock(
|
|
4764
|
+
function verificationModeBlock() {
|
|
4752
4765
|
return [
|
|
4753
4766
|
"",
|
|
4754
4767
|
"VERIFICATION MODE:",
|
|
4755
|
-
|
|
4768
|
+
"Add a FINAL verification task to the end of the plan (highest order number).",
|
|
4756
4769
|
'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
4770
|
"This task must:",
|
|
4758
4771
|
'- Have type "ai" and autonomy "AFK" \u2014 it needs no human input',
|
|
@@ -4788,35 +4801,14 @@ function researchPhaseBlock(harnessMode) {
|
|
|
4788
4801
|
"- Research and questions can interleave \u2014 you can explore a file, then ask a question grounded in it, in one response"
|
|
4789
4802
|
].join("\n");
|
|
4790
4803
|
}
|
|
4791
|
-
function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, runnerModes, autonomousDefault = true,
|
|
4804
|
+
function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, runnerModes, autonomousDefault = true, verificationEnabled = false, harnessMode = false) {
|
|
4792
4805
|
const modelsJson = modelsJsonFor(modelsByRunner, runners);
|
|
4793
4806
|
const modeGuide = runnerModes ? buildModeGuide(runnerModes, autonomousDefault) : buildModeGuideForRunners(runners, autonomousDefault);
|
|
4794
4807
|
const modeExamples = runnerModes ? Object.entries(runnerModes).map(([r, ms]) => {
|
|
4795
4808
|
const filtered = filteredBuildModes(ms, autonomousDefault);
|
|
4796
4809
|
return `${r}: ${filtered.map((m) => m.id).join("|")}${ms.some((m) => m.id === "plan") ? "|plan" : ""}`;
|
|
4797
4810
|
}).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) : "";
|
|
4811
|
+
const verificationBlock = verificationEnabled ? verificationModeBlock() : "";
|
|
4820
4812
|
return buildConversationBody(
|
|
4821
4813
|
goal,
|
|
4822
4814
|
context,
|
|
@@ -4825,34 +4817,10 @@ function buildConversationSystemPrompt(goal, context, modelsByRunner, runners, r
|
|
|
4825
4817
|
modeGuide,
|
|
4826
4818
|
modeExamples,
|
|
4827
4819
|
harnessMode,
|
|
4828
|
-
grillMeBlock,
|
|
4829
|
-
prdBlock,
|
|
4830
|
-
reviewBlock,
|
|
4831
4820
|
verificationBlock
|
|
4832
4821
|
);
|
|
4833
4822
|
}
|
|
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) {
|
|
4823
|
+
function buildConversationBody(goal, context, modelsJson, runners, modeGuide, modeExamples, harnessMode, verificationBlock) {
|
|
4856
4824
|
return [
|
|
4857
4825
|
"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
4826
|
"",
|
|
@@ -4863,9 +4831,6 @@ function buildConversationBody(goal, context, modelsJson, runners, modeGuide, mo
|
|
|
4863
4831
|
'4. After the user confirms the outline, emit the final task plan as a JSON object with a "tasks" array.',
|
|
4864
4832
|
"",
|
|
4865
4833
|
researchPhaseBlock(harnessMode),
|
|
4866
|
-
grillMeBlock,
|
|
4867
|
-
prdBlock,
|
|
4868
|
-
reviewBlock,
|
|
4869
4834
|
verificationBlock,
|
|
4870
4835
|
"",
|
|
4871
4836
|
"OUTLINE PHASE:",
|
|
@@ -5052,7 +5017,6 @@ function getPlanTemplate() {
|
|
|
5052
5017
|
"",
|
|
5053
5018
|
"TASK MODE:",
|
|
5054
5019
|
"{{MODE_GUIDE}}",
|
|
5055
|
-
"{{REVIEW_BLOCK}}",
|
|
5056
5020
|
"{{VERIFICATION_BLOCK}}",
|
|
5057
5021
|
"",
|
|
5058
5022
|
"CONTEXT:",
|
|
@@ -5099,10 +5063,8 @@ function buildPlanPromptBase(userGoal, context, researchResults, modelsByRunner,
|
|
|
5099
5063
|
const filtered = filteredBuildModes(ms, autonomousDefault);
|
|
5100
5064
|
return `${r}: ${filtered.map((m) => m.id).join("|")}${ms.some((m) => m.id === "plan") ? "|plan" : ""}`;
|
|
5101
5065
|
}).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)}
|
|
5066
|
+
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 ? `
|
|
5067
|
+
${verificationModeBlock()}
|
|
5106
5068
|
` : "").replace("{{MODE_EXAMPLES}}", modeExamples).replace("{{CONTEXT}}", context || "(no additional context)").replace("{{RESEARCH_RESULTS}}", researchResults ? `
|
|
5107
5069
|
|
|
5108
5070
|
RESEARCH FINDINGS:
|
|
@@ -6465,8 +6427,8 @@ var SUBAGENT_EXCLUDED = /* @__PURE__ */ new Set(["fetch", "web_search"]);
|
|
|
6465
6427
|
function subagentToolSpecs() {
|
|
6466
6428
|
return RESEARCH_TOOLS.filter((t) => !SUBAGENT_EXCLUDED.has(t.name));
|
|
6467
6429
|
}
|
|
6468
|
-
function withSpawn(
|
|
6469
|
-
return
|
|
6430
|
+
function withSpawn() {
|
|
6431
|
+
return [...RESEARCH_TOOLS, SPAWN_RESEARCH_AGENT_SPEC];
|
|
6470
6432
|
}
|
|
6471
6433
|
function projectOpenAi(specs) {
|
|
6472
6434
|
return specs.map((t) => ({
|
|
@@ -6487,8 +6449,8 @@ function projectOpenAi(specs) {
|
|
|
6487
6449
|
}
|
|
6488
6450
|
}));
|
|
6489
6451
|
}
|
|
6490
|
-
function toOpenAiTools(
|
|
6491
|
-
return projectOpenAi(withSpawn(
|
|
6452
|
+
function toOpenAiTools() {
|
|
6453
|
+
return projectOpenAi(withSpawn());
|
|
6492
6454
|
}
|
|
6493
6455
|
function toOpenAiSubagentTools() {
|
|
6494
6456
|
return projectOpenAi(subagentToolSpecs());
|
|
@@ -6499,8 +6461,8 @@ var SCHEMA_TYPE = {
|
|
|
6499
6461
|
array: import_generative_ai.SchemaType.ARRAY,
|
|
6500
6462
|
boolean: import_generative_ai.SchemaType.BOOLEAN
|
|
6501
6463
|
};
|
|
6502
|
-
function toGeminiToolDeclarations(
|
|
6503
|
-
return withSpawn(
|
|
6464
|
+
function toGeminiToolDeclarations() {
|
|
6465
|
+
return withSpawn().map((t) => {
|
|
6504
6466
|
const properties = {};
|
|
6505
6467
|
for (const [key, param] of Object.entries(t.properties)) {
|
|
6506
6468
|
properties[key] = {
|
|
@@ -6597,33 +6559,10 @@ function compactResearchResults(resultsText, maxChars = COMPACTION_LIMITS.resear
|
|
|
6597
6559
|
return (blocks[0].lines.length === 0 ? texts.slice(1) : texts).join("\n");
|
|
6598
6560
|
}
|
|
6599
6561
|
|
|
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
6562
|
// src/services/ContextCollector.ts
|
|
6624
6563
|
var ORDEWELL_CONTEXT_MAX = 8e3;
|
|
6625
|
-
async function collectResearchContext(
|
|
6626
|
-
const collector = new ContextCollector(
|
|
6564
|
+
async function collectResearchContext(fs12, runners) {
|
|
6565
|
+
const collector = new ContextCollector(fs12);
|
|
6627
6566
|
let contextStr = "";
|
|
6628
6567
|
let firstContext = null;
|
|
6629
6568
|
for (const r of runners) {
|
|
@@ -6650,8 +6589,8 @@ ${firstContext.dirStructure}
|
|
|
6650
6589
|
return contextStr;
|
|
6651
6590
|
}
|
|
6652
6591
|
var ContextCollector = class {
|
|
6653
|
-
constructor(
|
|
6654
|
-
this.fs =
|
|
6592
|
+
constructor(fs12, registry) {
|
|
6593
|
+
this.fs = fs12;
|
|
6655
6594
|
this.registry = registry;
|
|
6656
6595
|
}
|
|
6657
6596
|
fs;
|
|
@@ -6732,9 +6671,9 @@ function summarizeToolCall(tool, argsJson, toolLabel) {
|
|
|
6732
6671
|
const scope = args.include ? ` in ${args.include}` : "";
|
|
6733
6672
|
return `grep ${ellipsize(String(args.pattern), 40)}${scope}${mode}`;
|
|
6734
6673
|
}
|
|
6735
|
-
const
|
|
6736
|
-
if (
|
|
6737
|
-
const basename = String(
|
|
6674
|
+
const path20 = args.filePath || args.file_path || args.path;
|
|
6675
|
+
if (path20) {
|
|
6676
|
+
const basename = String(path20).split("/").pop() || String(path20);
|
|
6738
6677
|
return `${display} ${basename}`;
|
|
6739
6678
|
}
|
|
6740
6679
|
if (args.pattern) {
|
|
@@ -6768,22 +6707,22 @@ var ALLOWED_TOOL_NAMES = new Set(subagentToolSpecs().map((t) => t.name));
|
|
|
6768
6707
|
function refused(output) {
|
|
6769
6708
|
return { success: false, output, truncated: false };
|
|
6770
6709
|
}
|
|
6771
|
-
function nonPromptingFs(
|
|
6772
|
-
const withinWorkspace = (p) => resolveWithin(
|
|
6710
|
+
function nonPromptingFs(fs12) {
|
|
6711
|
+
const withinWorkspace = (p) => resolveWithin(fs12.getWorkspaceRoot(), p ?? ".").inside;
|
|
6773
6712
|
const outsideRefusal = (p) => refused(
|
|
6774
6713
|
`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
6714
|
);
|
|
6776
6715
|
return {
|
|
6777
|
-
getWorkspaceRoot: () =>
|
|
6778
|
-
readFile: (p, opts) => withinWorkspace(p) ?
|
|
6716
|
+
getWorkspaceRoot: () => fs12.getWorkspaceRoot(),
|
|
6717
|
+
readFile: (p, opts) => withinWorkspace(p) ? fs12.readFile(p, opts) : Promise.resolve(outsideRefusal(p)),
|
|
6779
6718
|
readFiles: (paths) => {
|
|
6780
6719
|
const escaping = paths.find((p) => !withinWorkspace(p));
|
|
6781
|
-
return escaping ? Promise.resolve(outsideRefusal(escaping)) :
|
|
6720
|
+
return escaping ? Promise.resolve(outsideRefusal(escaping)) : fs12.readFiles(paths);
|
|
6782
6721
|
},
|
|
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) ?
|
|
6722
|
+
glob: (pattern, opts) => withinWorkspace(opts?.path) ? fs12.glob(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
6723
|
+
grep: (pattern, opts) => withinWorkspace(opts?.path) ? fs12.grep(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
6724
|
+
findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs12.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
6725
|
+
listDir: (p, depth) => withinWorkspace(p) ? fs12.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
|
|
6787
6726
|
bash: async (command, signal) => {
|
|
6788
6727
|
const dialect = resolveResearchShell().dialect;
|
|
6789
6728
|
const { tier, reason } = classifyCommand(command, { dialect });
|
|
@@ -6794,13 +6733,13 @@ function nonPromptingFs(fs10) {
|
|
|
6794
6733
|
}
|
|
6795
6734
|
const escaping = pathLikeArgs(command, { dialect }).find((p) => !withinWorkspace(p));
|
|
6796
6735
|
if (escaping) return outsideRefusal(escaping);
|
|
6797
|
-
return
|
|
6736
|
+
return fs12.bash(command, signal);
|
|
6798
6737
|
}
|
|
6799
6738
|
};
|
|
6800
6739
|
}
|
|
6801
6740
|
async function runLoop(prompt, deps) {
|
|
6802
6741
|
if (deps.signal?.aborted) return "[research agent aborted before starting]";
|
|
6803
|
-
const
|
|
6742
|
+
const fs12 = nonPromptingFs(deps.fs);
|
|
6804
6743
|
const chat = deps.createChat((delta) => deps.onProgress?.({ type: "thinking", text: delta }));
|
|
6805
6744
|
let turn = await chat.sendMessage(prompt, deps.signal);
|
|
6806
6745
|
for (let step = 0; turn.hasToolCalls && step < SUBAGENT_LIMITS.maxSteps; step++) {
|
|
@@ -6817,7 +6756,7 @@ ${turn.text}`;
|
|
|
6817
6756
|
if (tc.name === "read_file" && !("maxBytes" in args) && !("limit" in args)) args.limit = 2e3;
|
|
6818
6757
|
const toolArgs = JSON.stringify(tc.args);
|
|
6819
6758
|
deps.onProgress?.({ type: "tool_call", tool: tc.name, toolArgs, toolCallId: tc.id });
|
|
6820
|
-
const res = await executeTool(tc.name, args,
|
|
6759
|
+
const res = await executeTool(tc.name, args, fs12, void 0, deps.signal);
|
|
6821
6760
|
const output = res.output.length > SUBAGENT_LIMITS.toolOutputMaxChars ? res.output.slice(0, SUBAGENT_LIMITS.toolOutputMaxChars) + `
|
|
6822
6761
|
[... truncated to ${SUBAGENT_LIMITS.toolOutputMaxChars} chars, total ${res.output.length}]` : res.output;
|
|
6823
6762
|
const stepEntry = {
|
|
@@ -6875,8 +6814,6 @@ var BaseAiService = class _BaseAiService {
|
|
|
6875
6814
|
config;
|
|
6876
6815
|
conversation = null;
|
|
6877
6816
|
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
6817
|
hasActiveConversation() {
|
|
6881
6818
|
return this.conversation !== null;
|
|
6882
6819
|
}
|
|
@@ -6932,10 +6869,7 @@ var BaseAiService = class _BaseAiService {
|
|
|
6932
6869
|
* steering/failed result and the loop continues sequentially: a subagent can
|
|
6933
6870
|
* never fail a plan or a turn.
|
|
6934
6871
|
*/
|
|
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
|
-
}
|
|
6872
|
+
async executeSpawnAgent(args, fs12, onProgress, signal, subagentId) {
|
|
6939
6873
|
const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
|
|
6940
6874
|
if (!prompt) {
|
|
6941
6875
|
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 +6880,20 @@ var BaseAiService = class _BaseAiService {
|
|
|
6946
6880
|
if (!chat) throw new Error("subagent chats are not available for this provider");
|
|
6947
6881
|
return chat;
|
|
6948
6882
|
},
|
|
6949
|
-
fs:
|
|
6883
|
+
fs: fs12,
|
|
6950
6884
|
signal,
|
|
6951
6885
|
onProgress: (progress) => onProgress({ ...progress, subagentId })
|
|
6952
6886
|
});
|
|
6953
6887
|
}
|
|
6954
6888
|
/** Collect project context for the planning phase. Shared with the harness backend. */
|
|
6955
|
-
static async collectResearchContext(
|
|
6956
|
-
return collectResearchContext(
|
|
6889
|
+
static async collectResearchContext(fs12, runners) {
|
|
6890
|
+
return collectResearchContext(fs12, runners);
|
|
6957
6891
|
}
|
|
6958
6892
|
/**
|
|
6959
6893
|
* Execute one round of tool calls and report each through onProgress.
|
|
6960
6894
|
* Returns the results to feed back plus the log entries produced.
|
|
6961
6895
|
*/
|
|
6962
|
-
async executeToolCalls(toolCalls,
|
|
6896
|
+
async executeToolCalls(toolCalls, fs12, onProgress, fetcher, stepIndex, thinking, signal) {
|
|
6963
6897
|
const toolResults = [];
|
|
6964
6898
|
const logEntries = [];
|
|
6965
6899
|
let resultsText = "";
|
|
@@ -6978,18 +6912,18 @@ var BaseAiService = class _BaseAiService {
|
|
|
6978
6912
|
const subagentId = (0, import_uuid3.v4)();
|
|
6979
6913
|
spawnSubagentIds.set(i, subagentId);
|
|
6980
6914
|
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 },
|
|
6915
|
+
precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args }, fs12, onProgress, signal, subagentId));
|
|
6982
6916
|
}) : Promise.resolve(),
|
|
6983
6917
|
parallelIndexes.length > 1 ? mapWithConcurrency(parallelIndexes, TOOL_ROUND_CONCURRENCY, async (i) => {
|
|
6984
6918
|
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],
|
|
6919
|
+
precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i], fs12, fetcher, signal));
|
|
6986
6920
|
}) : Promise.resolve()
|
|
6987
6921
|
]);
|
|
6988
6922
|
for (const [index, tc] of toolCalls.entries()) {
|
|
6989
6923
|
const toolArgs = normalizedArgs[index];
|
|
6990
6924
|
const preResult = precomputed.get(index);
|
|
6991
6925
|
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,
|
|
6926
|
+
const toolResult = preResult ?? await executeTool(tc.name, toolArgs, fs12, fetcher, signal);
|
|
6993
6927
|
const MAX_RESPONSE_CHARS = 5e4;
|
|
6994
6928
|
const truncatedResponse = toolResult.success && (toolResult.truncated || toolResult.output.length > MAX_RESPONSE_CHARS);
|
|
6995
6929
|
const llmOutput = toolResult.output.length > MAX_RESPONSE_CHARS ? toolResult.output.slice(0, MAX_RESPONSE_CHARS) + `
|
|
@@ -7121,7 +7055,6 @@ ${llmOutput}
|
|
|
7121
7055
|
researchLog
|
|
7122
7056
|
};
|
|
7123
7057
|
}
|
|
7124
|
-
if (extractPrdBlock(turn.text)) ctx.prdCaptured = true;
|
|
7125
7058
|
const reply = classifyPlannerReply(turn.text, {
|
|
7126
7059
|
runners: ctx.runners,
|
|
7127
7060
|
runnerModes: ctx.runnerModes,
|
|
@@ -7135,16 +7068,6 @@ ${llmOutput}
|
|
|
7135
7068
|
case "task_query":
|
|
7136
7069
|
return { kind: "task_query", query: reply.query, text: turn.text, researchLog };
|
|
7137
7070
|
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
7071
|
return { kind: "plan", tasks: reply.tasks, text: turn.text, researchLog };
|
|
7149
7072
|
// Botched attempts get a bounded corrective retry — otherwise the
|
|
7150
7073
|
// broken JSON would surface as a prose bubble and the edit or plan
|
|
@@ -7186,7 +7109,7 @@ ${llmOutput}
|
|
|
7186
7109
|
* parsed tasks if a plan was emitted mid-loop, or null if the loop
|
|
7187
7110
|
* exhausted without a valid plan.
|
|
7188
7111
|
*/
|
|
7189
|
-
async runResearchLoop(chat, firstMessage,
|
|
7112
|
+
async runResearchLoop(chat, firstMessage, fs12, onProgress, runners, initialLog, fetcher, userGoal, runnerModes, autonomousDefault, signal) {
|
|
7190
7113
|
const researchLog = initialLog ? [...initialLog] : [];
|
|
7191
7114
|
if (!initialLog && userGoal) {
|
|
7192
7115
|
researchLog.push({
|
|
@@ -7213,7 +7136,7 @@ ${llmOutput}
|
|
|
7213
7136
|
if (!turn.hasToolCalls) break;
|
|
7214
7137
|
const { toolResults, logEntries, resultsText } = await this.executeToolCalls(
|
|
7215
7138
|
turn.toolCalls,
|
|
7216
|
-
|
|
7139
|
+
fs12,
|
|
7217
7140
|
onProgress,
|
|
7218
7141
|
fetcher,
|
|
7219
7142
|
step,
|
|
@@ -7349,9 +7272,6 @@ var GeminiService = class extends BaseAiService {
|
|
|
7349
7272
|
req.runners,
|
|
7350
7273
|
req.runnerModes,
|
|
7351
7274
|
req.autonomousDefault ?? true,
|
|
7352
|
-
req.grillMeEnabled ?? false,
|
|
7353
|
-
req.prdEnabled ?? false,
|
|
7354
|
-
req.reviewEnabled ?? false,
|
|
7355
7275
|
req.verificationEnabled ?? false
|
|
7356
7276
|
);
|
|
7357
7277
|
const seededHistory = [
|
|
@@ -7390,8 +7310,7 @@ ${m.content}` });
|
|
|
7390
7310
|
runners: req.runners,
|
|
7391
7311
|
runnerModes: req.runnerModes,
|
|
7392
7312
|
autonomousDefault: req.autonomousDefault,
|
|
7393
|
-
fetcher: req.fetcher
|
|
7394
|
-
prdEnabled: req.prdEnabled ?? false
|
|
7313
|
+
fetcher: req.fetcher
|
|
7395
7314
|
};
|
|
7396
7315
|
this.conversation = { ctx, setProgress: (onProgress) => {
|
|
7397
7316
|
currentProgress = onProgress;
|
|
@@ -7449,11 +7368,11 @@ ${repairHint}` }] : parts;
|
|
|
7449
7368
|
return this.consumePlanStream(result, onToken);
|
|
7450
7369
|
}
|
|
7451
7370
|
// --- IAiService implementation ---
|
|
7452
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
7371
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
7453
7372
|
this.ensureInit();
|
|
7454
7373
|
const { autonomousDefault } = modes;
|
|
7455
7374
|
const genModel = this.getPlanningModel();
|
|
7456
|
-
const contextStr = await BaseAiService.collectResearchContext(
|
|
7375
|
+
const contextStr = await BaseAiService.collectResearchContext(fs12, runners);
|
|
7457
7376
|
const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
|
|
7458
7377
|
const firstMessage = `${buildResearchToolsPrompt()}
|
|
7459
7378
|
|
|
@@ -7464,17 +7383,17 @@ Explore the workspace to understand the codebase, then generate the plan.`;
|
|
|
7464
7383
|
generationConfig: { temperature: 0.3, topP: 0.95, maxOutputTokens: 16384 }
|
|
7465
7384
|
});
|
|
7466
7385
|
const researchChat = new GeminiResearchChat(chat);
|
|
7467
|
-
const result = await this.runResearchLoop(researchChat, firstMessage,
|
|
7386
|
+
const result = await this.runResearchLoop(researchChat, firstMessage, fs12, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
|
|
7468
7387
|
if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
|
|
7469
7388
|
const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
|
|
7470
7389
|
return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
|
|
7471
7390
|
}
|
|
7472
|
-
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken,
|
|
7391
|
+
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
|
|
7473
7392
|
this.ensureInit();
|
|
7474
7393
|
const { autonomousDefault } = modes;
|
|
7475
7394
|
let ctx = "";
|
|
7476
|
-
if (
|
|
7477
|
-
const collected = await new ContextCollector(
|
|
7395
|
+
if (fs12) {
|
|
7396
|
+
const collected = await new ContextCollector(fs12).collect(runners[0] ?? "claude-code");
|
|
7478
7397
|
if (collected.aiflowContext) ctx = `
|
|
7479
7398
|
<aiflow_context>
|
|
7480
7399
|
${collected.aiflowContext}
|
|
@@ -7490,12 +7409,12 @@ ${collected.aiflowContext}
|
|
|
7490
7409
|
autonomousDefault
|
|
7491
7410
|
);
|
|
7492
7411
|
}
|
|
7493
|
-
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress,
|
|
7412
|
+
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
|
|
7494
7413
|
this.ensureInit();
|
|
7495
7414
|
const { autonomousDefault } = modes;
|
|
7496
7415
|
let aiflowContext;
|
|
7497
|
-
if (
|
|
7498
|
-
const collected = await new ContextCollector(
|
|
7416
|
+
if (fs12) {
|
|
7417
|
+
const collected = await new ContextCollector(fs12).collect(existingPlan.runners[0] ?? "claude-code");
|
|
7499
7418
|
if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
|
|
7500
7419
|
}
|
|
7501
7420
|
const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
|
|
@@ -7678,7 +7597,6 @@ var OpenAiService = class extends BaseAiService {
|
|
|
7678
7597
|
async startConversation(req) {
|
|
7679
7598
|
this.ensureInit();
|
|
7680
7599
|
const client = this.getClient();
|
|
7681
|
-
this.researchSubagentsEnabled = req.researchSubagentsEnabled ?? false;
|
|
7682
7600
|
const contextStr = await BaseAiService.collectResearchContext(req.fs, req.runners);
|
|
7683
7601
|
const systemPrompt = buildConversationSystemPrompt(
|
|
7684
7602
|
req.goal,
|
|
@@ -7687,16 +7605,13 @@ var OpenAiService = class extends BaseAiService {
|
|
|
7687
7605
|
req.runners,
|
|
7688
7606
|
req.runnerModes,
|
|
7689
7607
|
req.autonomousDefault ?? true,
|
|
7690
|
-
req.grillMeEnabled ?? false,
|
|
7691
|
-
req.prdEnabled ?? false,
|
|
7692
|
-
req.reviewEnabled ?? false,
|
|
7693
7608
|
req.verificationEnabled ?? false
|
|
7694
7609
|
);
|
|
7695
7610
|
const userText = req.goal;
|
|
7696
7611
|
const messages = [
|
|
7697
7612
|
{ role: "system", content: `${systemPrompt}
|
|
7698
7613
|
|
|
7699
|
-
${buildResearchToolsPrompt(
|
|
7614
|
+
${buildResearchToolsPrompt()}` }
|
|
7700
7615
|
];
|
|
7701
7616
|
for (const m of req.priorHistory ?? []) {
|
|
7702
7617
|
messages.push({ role: m.role, content: m.content });
|
|
@@ -7706,7 +7621,7 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
|
|
|
7706
7621
|
messages,
|
|
7707
7622
|
client,
|
|
7708
7623
|
this.requireModel("orchestratorModel", this.config.orchestratorModel),
|
|
7709
|
-
toOpenAiTools(
|
|
7624
|
+
toOpenAiTools(),
|
|
7710
7625
|
(delta) => currentProgress({ type: "thinking", text: delta }),
|
|
7711
7626
|
(delta) => currentProgress({ type: "plan_token", planToken: delta })
|
|
7712
7627
|
);
|
|
@@ -7716,8 +7631,7 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
|
|
|
7716
7631
|
runners: req.runners,
|
|
7717
7632
|
runnerModes: req.runnerModes,
|
|
7718
7633
|
autonomousDefault: req.autonomousDefault,
|
|
7719
|
-
fetcher: req.fetcher
|
|
7720
|
-
prdEnabled: req.prdEnabled ?? false
|
|
7634
|
+
fetcher: req.fetcher
|
|
7721
7635
|
};
|
|
7722
7636
|
this.conversation = {
|
|
7723
7637
|
ctx,
|
|
@@ -7735,15 +7649,14 @@ ${buildResearchToolsPrompt(this.researchSubagentsEnabled)}` }
|
|
|
7735
7649
|
}
|
|
7736
7650
|
}
|
|
7737
7651
|
// --- IAiService implementation ---
|
|
7738
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
7652
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
7739
7653
|
this.ensureInit();
|
|
7740
7654
|
const client = this.getClient();
|
|
7741
|
-
const { autonomousDefault
|
|
7742
|
-
|
|
7743
|
-
const contextStr = await BaseAiService.collectResearchContext(fs10, runners);
|
|
7655
|
+
const { autonomousDefault } = modes;
|
|
7656
|
+
const contextStr = await BaseAiService.collectResearchContext(fs12, runners);
|
|
7744
7657
|
const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
|
|
7745
7658
|
const userText = `User goal: ${userDescription}`;
|
|
7746
|
-
const firstMessage = `${buildResearchToolsPrompt(
|
|
7659
|
+
const firstMessage = `${buildResearchToolsPrompt()}
|
|
7747
7660
|
|
|
7748
7661
|
Explore the workspace to understand the codebase, then generate the plan.
|
|
7749
7662
|
|
|
@@ -7755,20 +7668,20 @@ ${userText}`;
|
|
|
7755
7668
|
messages,
|
|
7756
7669
|
client,
|
|
7757
7670
|
this.requireModel("orchestratorModel", this.config.orchestratorModel),
|
|
7758
|
-
toOpenAiTools(
|
|
7671
|
+
toOpenAiTools(),
|
|
7759
7672
|
(delta) => onProgress({ type: "thinking", text: delta })
|
|
7760
7673
|
);
|
|
7761
|
-
const result = await this.runResearchLoop(researchChat, firstMessage,
|
|
7674
|
+
const result = await this.runResearchLoop(researchChat, firstMessage, fs12, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
|
|
7762
7675
|
if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
|
|
7763
7676
|
const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
|
|
7764
7677
|
return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
|
|
7765
7678
|
}
|
|
7766
|
-
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken,
|
|
7679
|
+
async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
7767
7680
|
this.ensureInit();
|
|
7768
7681
|
const { autonomousDefault } = modes;
|
|
7769
7682
|
let ctx = "";
|
|
7770
|
-
if (
|
|
7771
|
-
const collected = await new ContextCollector(
|
|
7683
|
+
if (fs12) {
|
|
7684
|
+
const collected = await new ContextCollector(fs12).collect(runners[0] ?? "claude-code");
|
|
7772
7685
|
if (collected.aiflowContext) ctx = `
|
|
7773
7686
|
<aiflow_context>
|
|
7774
7687
|
${collected.aiflowContext}
|
|
@@ -7784,12 +7697,12 @@ ${collected.aiflowContext}
|
|
|
7784
7697
|
autonomousDefault
|
|
7785
7698
|
);
|
|
7786
7699
|
}
|
|
7787
|
-
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress,
|
|
7700
|
+
async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs12, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
7788
7701
|
this.ensureInit();
|
|
7789
7702
|
const { autonomousDefault } = modes;
|
|
7790
7703
|
let aiflowContext;
|
|
7791
|
-
if (
|
|
7792
|
-
const collected = await new ContextCollector(
|
|
7704
|
+
if (fs12) {
|
|
7705
|
+
const collected = await new ContextCollector(fs12).collect(existingPlan.runners[0] ?? "claude-code");
|
|
7793
7706
|
if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
|
|
7794
7707
|
}
|
|
7795
7708
|
const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
|
|
@@ -7878,21 +7791,28 @@ function pickWindow(sorted, currentIdx, max) {
|
|
|
7878
7791
|
const window = sorted.slice(start, start + max);
|
|
7879
7792
|
return { window, omitted: sorted.length - window.length };
|
|
7880
7793
|
}
|
|
7881
|
-
function renderPlanMap(
|
|
7882
|
-
|
|
7883
|
-
|
|
7794
|
+
function renderPlanMap(planTasks, currentTaskId, opts) {
|
|
7795
|
+
const rows = flattenTasksWithParents(planTasks);
|
|
7796
|
+
if (rows.length < PLAN_MAP_MIN_TASKS) return "";
|
|
7797
|
+
const sorted = rows.map((r) => ({
|
|
7798
|
+
...r,
|
|
7799
|
+
label: taskOrderLabel(r.task, r.parent ?? void 0)
|
|
7800
|
+
})).sort((a, b) => {
|
|
7801
|
+
const parentDiff = (a.parent?.order ?? -1) - (b.parent?.order ?? -1);
|
|
7802
|
+
if (parentDiff !== 0) return parentDiff;
|
|
7803
|
+
return a.task.order - b.task.order;
|
|
7804
|
+
});
|
|
7884
7805
|
const max = opts?.maxEntries ?? DEFAULT_PLAN_MAP_MAX_ENTRIES;
|
|
7885
|
-
const currentIdx = sorted.findIndex((
|
|
7806
|
+
const currentIdx = sorted.findIndex((r) => r.task.id === currentTaskId);
|
|
7886
7807
|
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, " ");
|
|
7808
|
+
const currentTask = sorted.find((r) => r.task.id === currentTaskId)?.task;
|
|
7809
|
+
const lines = window.map((row) => {
|
|
7810
|
+
const isCurrent = row.task.id === currentTaskId;
|
|
7811
|
+
const tag = planMapStatus(row.task, isCurrent);
|
|
7812
|
+
const order = row.parent ? row.label : row.label.padStart(2, " ");
|
|
7893
7813
|
const arrow = isCurrent ? " \u2190 you are here" : "";
|
|
7894
|
-
const runner =
|
|
7895
|
-
return `${order}. [${tag.padEnd(7, " ")}] ${
|
|
7814
|
+
const runner = row.task.assignedRunner ? ` (${row.task.assignedRunner})` : "";
|
|
7815
|
+
return `${order}. [${tag.padEnd(7, " ")}] ${row.task.title}${runner}${arrow}`;
|
|
7896
7816
|
});
|
|
7897
7817
|
const footer = omitted > 0 ? `
|
|
7898
7818
|
(${omitted} task${omitted === 1 ? "" : "s"} omitted from this view)` : "";
|
|
@@ -7957,7 +7877,8 @@ function composeAugmentedPrompt(task, allTasks, opts) {
|
|
|
7957
7877
|
const map = renderPlanMap(allTasks, task.id, { maxEntries: opts?.planMapMaxEntries });
|
|
7958
7878
|
if (map) blocks.push(map);
|
|
7959
7879
|
}
|
|
7960
|
-
const
|
|
7880
|
+
const flat = flattenTasks(allTasks);
|
|
7881
|
+
const outputs = collectDirectDependencyOutputs(task, flat);
|
|
7961
7882
|
if (outputs.length > 0) blocks.push(renderPriorOutputs(outputs));
|
|
7962
7883
|
if (opts?.tddEnabled) {
|
|
7963
7884
|
blocks.push(renderTddInstruction());
|
|
@@ -8136,6 +8057,7 @@ function renderTerminalOutput(raw) {
|
|
|
8136
8057
|
return populated.map((r) => cells(r).join("")).join("\n");
|
|
8137
8058
|
}
|
|
8138
8059
|
var MARKER_SCAN_TAIL = 16384;
|
|
8060
|
+
var IDLE_TIMEOUT_MS = 6e4;
|
|
8139
8061
|
var VerdictEngine = class {
|
|
8140
8062
|
markerSeen = /* @__PURE__ */ new Set();
|
|
8141
8063
|
buffers = /* @__PURE__ */ new Map();
|
|
@@ -8143,6 +8065,7 @@ var VerdictEngine = class {
|
|
|
8143
8065
|
pausedSessions = /* @__PURE__ */ new Map();
|
|
8144
8066
|
listeners = [];
|
|
8145
8067
|
checkpointListeners = [];
|
|
8068
|
+
idleListeners = [];
|
|
8146
8069
|
/**
|
|
8147
8070
|
* Per-task generation counter. Incremented on every watch() and clear().
|
|
8148
8071
|
* Stale callbacks (from a prior session whose generation doesn't match
|
|
@@ -8150,12 +8073,47 @@ var VerdictEngine = class {
|
|
|
8150
8073
|
* wrong session.
|
|
8151
8074
|
*/
|
|
8152
8075
|
generations = /* @__PURE__ */ new Map();
|
|
8076
|
+
idleTimers = /* @__PURE__ */ new Map();
|
|
8077
|
+
idleSince = /* @__PURE__ */ new Map();
|
|
8153
8078
|
onVerdict(listener) {
|
|
8154
8079
|
this.listeners.push(listener);
|
|
8155
8080
|
}
|
|
8156
8081
|
onCheckpoint(listener) {
|
|
8157
8082
|
this.checkpointListeners.push(listener);
|
|
8158
8083
|
}
|
|
8084
|
+
onIdleChange(listener) {
|
|
8085
|
+
this.idleListeners.push(listener);
|
|
8086
|
+
}
|
|
8087
|
+
/** Advisory silence timestamp for a task, or null if it isn't idle. */
|
|
8088
|
+
getIdleSince(taskId) {
|
|
8089
|
+
return this.idleSince.get(taskId) ?? null;
|
|
8090
|
+
}
|
|
8091
|
+
/** Restart the silence timer on fresh output; broadcasts the null transition if it was idle. */
|
|
8092
|
+
touchIdle(taskId, gen) {
|
|
8093
|
+
const existing = this.idleTimers.get(taskId);
|
|
8094
|
+
if (existing) clearTimeout(existing);
|
|
8095
|
+
if (this.idleSince.get(taskId)) {
|
|
8096
|
+
this.idleSince.set(taskId, null);
|
|
8097
|
+
for (const l of this.idleListeners) l(taskId, null);
|
|
8098
|
+
}
|
|
8099
|
+
this.idleTimers.set(taskId, setTimeout(() => {
|
|
8100
|
+
if (this.generations.get(taskId) !== gen) return;
|
|
8101
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8102
|
+
this.idleSince.set(taskId, now);
|
|
8103
|
+
for (const l of this.idleListeners) l(taskId, now);
|
|
8104
|
+
}, IDLE_TIMEOUT_MS));
|
|
8105
|
+
}
|
|
8106
|
+
/** Tear down idle tracking for a task; broadcasts the null transition if it was idle. */
|
|
8107
|
+
clearIdle(taskId) {
|
|
8108
|
+
const timer = this.idleTimers.get(taskId);
|
|
8109
|
+
if (timer) clearTimeout(timer);
|
|
8110
|
+
this.idleTimers.delete(taskId);
|
|
8111
|
+
const wasIdle = this.idleSince.get(taskId);
|
|
8112
|
+
this.idleSince.delete(taskId);
|
|
8113
|
+
if (wasIdle) {
|
|
8114
|
+
for (const l of this.idleListeners) l(taskId, null);
|
|
8115
|
+
}
|
|
8116
|
+
}
|
|
8159
8117
|
approveCheckpoint(taskId) {
|
|
8160
8118
|
const session = this.pausedSessions.get(taskId);
|
|
8161
8119
|
if (session) {
|
|
@@ -8186,6 +8144,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
8186
8144
|
this.checkpointCounts.set(task.id, 0);
|
|
8187
8145
|
session.onOutput((text) => {
|
|
8188
8146
|
if (this.generations.get(task.id) !== gen) return;
|
|
8147
|
+
this.touchIdle(task.id, gen);
|
|
8189
8148
|
if (this.markerSeen.has(task.id)) return;
|
|
8190
8149
|
const buf = (this.buffers.get(task.id) ?? "") + text;
|
|
8191
8150
|
this.buffers.set(task.id, buf);
|
|
@@ -8197,6 +8156,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
8197
8156
|
this.buffers.delete(task.id);
|
|
8198
8157
|
this.checkpointCounts.delete(task.id);
|
|
8199
8158
|
this.pausedSessions.delete(task.id);
|
|
8159
|
+
this.clearIdle(task.id);
|
|
8200
8160
|
this.generations.set(task.id, gen + 1);
|
|
8201
8161
|
const verdict = this.decide(task, 0);
|
|
8202
8162
|
for (const l of this.listeners) l(task.id, verdict, output);
|
|
@@ -8221,6 +8181,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
8221
8181
|
this.buffers.delete(task.id);
|
|
8222
8182
|
this.checkpointCounts.delete(task.id);
|
|
8223
8183
|
this.pausedSessions.delete(task.id);
|
|
8184
|
+
this.clearIdle(task.id);
|
|
8224
8185
|
const verdict = this.decide(task, exitCode);
|
|
8225
8186
|
for (const l of this.listeners) l(task.id, verdict, output);
|
|
8226
8187
|
});
|
|
@@ -8231,6 +8192,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
8231
8192
|
this.buffers.delete(task.id);
|
|
8232
8193
|
this.checkpointCounts.delete(task.id);
|
|
8233
8194
|
this.pausedSessions.delete(task.id);
|
|
8195
|
+
this.clearIdle(task.id);
|
|
8234
8196
|
this.generations.set(task.id, (this.generations.get(task.id) ?? 0) + 1);
|
|
8235
8197
|
return {
|
|
8236
8198
|
outcome: "pass",
|
|
@@ -8252,6 +8214,7 @@ ORDEWELL_REJECT: ${reason}
|
|
|
8252
8214
|
this.buffers.delete(task.id);
|
|
8253
8215
|
this.checkpointCounts.delete(task.id);
|
|
8254
8216
|
this.pausedSessions.delete(task.id);
|
|
8217
|
+
this.clearIdle(task.id);
|
|
8255
8218
|
this.generations.set(task.id, (this.generations.get(task.id) ?? 0) + 1);
|
|
8256
8219
|
}
|
|
8257
8220
|
/** Drop all tracking state (used on stop / loadPlan). */
|
|
@@ -8260,6 +8223,9 @@ ORDEWELL_REJECT: ${reason}
|
|
|
8260
8223
|
this.buffers.clear();
|
|
8261
8224
|
this.checkpointCounts.clear();
|
|
8262
8225
|
this.pausedSessions.clear();
|
|
8226
|
+
for (const timer of this.idleTimers.values()) clearTimeout(timer);
|
|
8227
|
+
this.idleTimers.clear();
|
|
8228
|
+
this.idleSince.clear();
|
|
8263
8229
|
this.generations.clear();
|
|
8264
8230
|
}
|
|
8265
8231
|
decide(task, exitCode) {
|
|
@@ -8665,6 +8631,7 @@ var TaskOrchestrator = class {
|
|
|
8665
8631
|
this.store.markAwaitingUser(taskId);
|
|
8666
8632
|
this.emit("onCheckpoint", { taskId, taskTitle: task.title, summary });
|
|
8667
8633
|
});
|
|
8634
|
+
this.verifier.onIdleChange(() => this.emit("onTaskChanged"));
|
|
8668
8635
|
}
|
|
8669
8636
|
config;
|
|
8670
8637
|
notifications;
|
|
@@ -8689,6 +8656,10 @@ var TaskOrchestrator = class {
|
|
|
8689
8656
|
workspaceRootFn = () => process.cwd();
|
|
8690
8657
|
observers = [];
|
|
8691
8658
|
tddEnabled = () => false;
|
|
8659
|
+
/** Advisory silence timestamp for a task's live runner, or null if not idle. */
|
|
8660
|
+
getIdleSince(taskId) {
|
|
8661
|
+
return this.verifier.getIdleSince(taskId);
|
|
8662
|
+
}
|
|
8692
8663
|
get storeInstance() {
|
|
8693
8664
|
return this.store;
|
|
8694
8665
|
}
|
|
@@ -9076,8 +9047,10 @@ ${output.slice(-3e3)}`);
|
|
|
9076
9047
|
if (ready.length === 0 && this.activeTaskSessions.size === 0) {
|
|
9077
9048
|
const remaining = this.store.allTasks.filter((t) => t.status !== "completed");
|
|
9078
9049
|
console.log(`[TaskOrchestrator] tick(): no work. remaining=${remaining.length}, completed=${this.store.isAllComplete()}`);
|
|
9079
|
-
|
|
9080
|
-
|
|
9050
|
+
const remainingById = new Map(remaining.map((t) => [t.id, t]));
|
|
9051
|
+
for (const { task, parent } of flattenTasksWithParents(this.store.planTasks)) {
|
|
9052
|
+
if (!remainingById.has(task.id)) continue;
|
|
9053
|
+
console.log(` - ${taskOrderLabel(task, parent ?? void 0)}. ${task.title} [${task.type}/${task.status}] prompt=${!!task.prompt}`);
|
|
9081
9054
|
}
|
|
9082
9055
|
if (this.store.isAllComplete()) {
|
|
9083
9056
|
this.running = false;
|
|
@@ -9104,7 +9077,7 @@ ${output.slice(-3e3)}`);
|
|
|
9104
9077
|
try {
|
|
9105
9078
|
const cwd = this.workspaceRootFn();
|
|
9106
9079
|
const runner = this.store.resolveTaskRunner(task);
|
|
9107
|
-
const finalPrompt = composeAugmentedPrompt(task, this.store.
|
|
9080
|
+
const finalPrompt = composeAugmentedPrompt(task, this.store.planTasks, {
|
|
9108
9081
|
planMapEnabled: this.config.planMapEnabled,
|
|
9109
9082
|
tddEnabled: this.tddEnabled()
|
|
9110
9083
|
});
|
|
@@ -9278,7 +9251,7 @@ var osMod = __toESM(require("os"));
|
|
|
9278
9251
|
var import_child_process = require("child_process");
|
|
9279
9252
|
var import_util = require("util");
|
|
9280
9253
|
var os2 = __toESM(require("os"));
|
|
9281
|
-
var
|
|
9254
|
+
var path4 = __toESM(require("path"));
|
|
9282
9255
|
var execAsync = (0, import_util.promisify)(import_child_process.exec);
|
|
9283
9256
|
var LOGIN_SHELL_TIMEOUT_MS = 5e3;
|
|
9284
9257
|
var PATH_MARKER = "__ORDEWELL_PATH__";
|
|
@@ -9289,40 +9262,40 @@ var defaultExec = async (command, options) => {
|
|
|
9289
9262
|
function posixBinDirs() {
|
|
9290
9263
|
const home = os2.homedir();
|
|
9291
9264
|
return [
|
|
9292
|
-
|
|
9293
|
-
|
|
9294
|
-
|
|
9295
|
-
|
|
9296
|
-
|
|
9297
|
-
|
|
9298
|
-
|
|
9265
|
+
path4.join(home, ".local", "bin"),
|
|
9266
|
+
path4.join(home, ".opencode", "bin"),
|
|
9267
|
+
path4.join(home, ".claude", "bin"),
|
|
9268
|
+
path4.join(home, ".bun", "bin"),
|
|
9269
|
+
path4.join(home, ".volta", "bin"),
|
|
9270
|
+
path4.join(home, ".npm-global", "bin"),
|
|
9271
|
+
path4.join(home, "bin"),
|
|
9299
9272
|
"/usr/local/bin",
|
|
9300
9273
|
"/opt/homebrew/bin"
|
|
9301
9274
|
];
|
|
9302
9275
|
}
|
|
9303
9276
|
function windowsBinDirs(env, home) {
|
|
9304
|
-
const
|
|
9305
|
-
const under = (base, ...parts) => base ? [
|
|
9277
|
+
const join19 = (...parts) => path4.win32.join(...parts);
|
|
9278
|
+
const under = (base, ...parts) => base ? [join19(base, ...parts)] : [];
|
|
9306
9279
|
const appData = env.APPDATA;
|
|
9307
9280
|
const localAppData = env.LOCALAPPDATA;
|
|
9308
9281
|
const programData = env.ProgramData;
|
|
9309
9282
|
return [
|
|
9310
9283
|
// Native per-user installers, PowerShell-driven and otherwise.
|
|
9311
|
-
|
|
9312
|
-
|
|
9313
|
-
|
|
9314
|
-
|
|
9284
|
+
join19(home, ".local", "bin"),
|
|
9285
|
+
join19(home, ".opencode", "bin"),
|
|
9286
|
+
join19(home, ".claude", "bin"),
|
|
9287
|
+
join19(home, ".codex", "bin"),
|
|
9315
9288
|
...under(localAppData, "Programs", "claude"),
|
|
9316
9289
|
// Node package managers. npm's global prefix is `%APPDATA%\npm`; the
|
|
9317
9290
|
// literal spelling is kept as a fallback for a host that does not export
|
|
9318
9291
|
// APPDATA (a service account, a stripped CI container).
|
|
9319
9292
|
...under(appData, "npm"),
|
|
9320
|
-
|
|
9293
|
+
join19(home, "AppData", "Roaming", "npm"),
|
|
9321
9294
|
...under(localAppData, "pnpm"),
|
|
9322
9295
|
...under(localAppData, "Yarn", "bin"),
|
|
9323
|
-
|
|
9296
|
+
join19(home, ".bun", "bin"),
|
|
9324
9297
|
// Windows package managers.
|
|
9325
|
-
|
|
9298
|
+
join19(home, "scoop", "shims"),
|
|
9326
9299
|
...under(env.SCOOP, "shims"),
|
|
9327
9300
|
...under(env.SCOOP_GLOBAL ?? programData, "scoop", "shims"),
|
|
9328
9301
|
...under(programData, "chocolatey", "bin"),
|
|
@@ -9332,7 +9305,7 @@ function windowsBinDirs(env, home) {
|
|
|
9332
9305
|
...under(localAppData, "Microsoft", "WindowsApps"),
|
|
9333
9306
|
// Version managers. Volta's Windows home is not the POSIX `~/.volta`.
|
|
9334
9307
|
...under(localAppData, "Volta", "bin"),
|
|
9335
|
-
|
|
9308
|
+
join19(home, ".volta", "bin")
|
|
9336
9309
|
];
|
|
9337
9310
|
}
|
|
9338
9311
|
function wellKnownBinDirs(deps = {}) {
|
|
@@ -9361,14 +9334,14 @@ function augmentedPath(execImpl = defaultExec) {
|
|
|
9361
9334
|
const segments = [];
|
|
9362
9335
|
const push = (p) => {
|
|
9363
9336
|
if (!p) return;
|
|
9364
|
-
for (const seg of p.split(
|
|
9337
|
+
for (const seg of p.split(path4.delimiter)) {
|
|
9365
9338
|
if (seg && !segments.includes(seg)) segments.push(seg);
|
|
9366
9339
|
}
|
|
9367
9340
|
};
|
|
9368
9341
|
push(process.env.PATH);
|
|
9369
9342
|
push(await loginShellPath(execImpl));
|
|
9370
9343
|
for (const dir of wellKnownBinDirs()) push(dir);
|
|
9371
|
-
return segments.join(
|
|
9344
|
+
return segments.join(path4.delimiter);
|
|
9372
9345
|
})();
|
|
9373
9346
|
}
|
|
9374
9347
|
return cachedPath;
|
|
@@ -9388,6 +9361,50 @@ function withPath(base, resolvedPath, overrides = {}) {
|
|
|
9388
9361
|
return env;
|
|
9389
9362
|
}
|
|
9390
9363
|
|
|
9364
|
+
// src/utils/globalDataDir.ts
|
|
9365
|
+
var fs = __toESM(require("fs"));
|
|
9366
|
+
var path5 = __toESM(require("path"));
|
|
9367
|
+
var os3 = __toESM(require("os"));
|
|
9368
|
+
function globalDataDir() {
|
|
9369
|
+
return path5.join(os3.homedir(), ".ordewell");
|
|
9370
|
+
}
|
|
9371
|
+
function copyDirSync(src, dest) {
|
|
9372
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
9373
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
9374
|
+
for (const entry of entries) {
|
|
9375
|
+
const srcPath = path5.join(src, entry.name);
|
|
9376
|
+
const destPath = path5.join(dest, entry.name);
|
|
9377
|
+
if (entry.isDirectory()) {
|
|
9378
|
+
copyDirSync(srcPath, destPath);
|
|
9379
|
+
} else {
|
|
9380
|
+
fs.copyFileSync(srcPath, destPath);
|
|
9381
|
+
}
|
|
9382
|
+
}
|
|
9383
|
+
}
|
|
9384
|
+
var migrated = false;
|
|
9385
|
+
function migrateOldConfigDir() {
|
|
9386
|
+
if (migrated) return;
|
|
9387
|
+
migrated = true;
|
|
9388
|
+
const oldDir = path5.join(os3.homedir(), ".config", "ordewell");
|
|
9389
|
+
if (!fs.existsSync(oldDir)) return;
|
|
9390
|
+
const newDir = globalDataDir();
|
|
9391
|
+
const liftFile = (name) => {
|
|
9392
|
+
const oldFile = path5.join(oldDir, name);
|
|
9393
|
+
const newFile = path5.join(newDir, name);
|
|
9394
|
+
if (fs.existsSync(oldFile) && !fs.existsSync(newFile)) {
|
|
9395
|
+
fs.mkdirSync(newDir, { recursive: true });
|
|
9396
|
+
fs.copyFileSync(oldFile, newFile);
|
|
9397
|
+
}
|
|
9398
|
+
};
|
|
9399
|
+
liftFile("settings.json");
|
|
9400
|
+
liftFile(".env");
|
|
9401
|
+
const oldPlugins = path5.join(oldDir, "plugins");
|
|
9402
|
+
const newPlugins = path5.join(newDir, "plugins");
|
|
9403
|
+
if (fs.existsSync(oldPlugins) && !fs.existsSync(newPlugins)) {
|
|
9404
|
+
copyDirSync(oldPlugins, newPlugins);
|
|
9405
|
+
}
|
|
9406
|
+
}
|
|
9407
|
+
|
|
9391
9408
|
// src/utils/launch.ts
|
|
9392
9409
|
var fsSync2 = __toESM(require("fs"));
|
|
9393
9410
|
var path6 = __toESM(require("path"));
|
|
@@ -9404,9 +9421,11 @@ function buildShellInvocation(command, args) {
|
|
|
9404
9421
|
const inner = [command, ...args].map(posixShellQuote).join(" ");
|
|
9405
9422
|
return { shellPath: "/bin/bash", shellArgs: ["-lc", inner] };
|
|
9406
9423
|
}
|
|
9407
|
-
function wrapWithPty(command, args) {
|
|
9424
|
+
function wrapWithPty(command, args, opts = {}) {
|
|
9408
9425
|
const inner = [command, ...args].map(posixShellQuote).join(" ");
|
|
9409
|
-
|
|
9426
|
+
const watcher = opts.controlChannel ? 'exec 4<&0; ( while read -r C R <&3; do stty cols "$C" rows "$R" <&4; done ) & ' : "";
|
|
9427
|
+
const setup = opts.size ? `stty cols ${opts.size.cols} rows ${opts.size.rows}; ` : "";
|
|
9428
|
+
return { command: "script", args: ["-q", "-e", "-f", "-c", `${watcher}${setup}${inner}`, "/dev/null"] };
|
|
9410
9429
|
}
|
|
9411
9430
|
|
|
9412
9431
|
// src/utils/launch.ts
|
|
@@ -9713,7 +9732,7 @@ function parseCodexModelsCache(content) {
|
|
|
9713
9732
|
}
|
|
9714
9733
|
var GEMINI_MODELS_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
9715
9734
|
function geminiDiscoveryCacheKey(apiKey) {
|
|
9716
|
-
const dir =
|
|
9735
|
+
const dir = globalDataDir();
|
|
9717
9736
|
const hash = apiKey.slice(-8);
|
|
9718
9737
|
return {
|
|
9719
9738
|
key: `gemini-models:${hash}`,
|
|
@@ -10243,12 +10262,14 @@ var ModelDiscovery = class {
|
|
|
10243
10262
|
// src/services/ModelCatalog.ts
|
|
10244
10263
|
var fs2 = __toESM(require("fs"));
|
|
10245
10264
|
var path8 = __toESM(require("path"));
|
|
10246
|
-
var os3 = __toESM(require("os"));
|
|
10247
10265
|
var CACHE_KEY = "model-catalog";
|
|
10248
10266
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
10249
10267
|
function storageKey(baseUrl) {
|
|
10250
10268
|
return `${CACHE_KEY}:${baseUrl}`;
|
|
10251
10269
|
}
|
|
10270
|
+
function globalCacheDir() {
|
|
10271
|
+
return globalDataDir();
|
|
10272
|
+
}
|
|
10252
10273
|
var ModelCatalog = class {
|
|
10253
10274
|
static async fetchModels(apiKey, baseUrl, fetchImpl) {
|
|
10254
10275
|
const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
|
|
@@ -10291,7 +10312,7 @@ var ModelCatalog = class {
|
|
|
10291
10312
|
try {
|
|
10292
10313
|
const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
|
|
10293
10314
|
const key = storageKey(url);
|
|
10294
|
-
const cacheDir =
|
|
10315
|
+
const cacheDir = globalCacheDir();
|
|
10295
10316
|
const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
10296
10317
|
if (fs2.existsSync(cacheFile)) fs2.rmSync(cacheFile);
|
|
10297
10318
|
} catch {
|
|
@@ -10299,7 +10320,7 @@ var ModelCatalog = class {
|
|
|
10299
10320
|
}
|
|
10300
10321
|
static readCache(key) {
|
|
10301
10322
|
try {
|
|
10302
|
-
const cacheDir =
|
|
10323
|
+
const cacheDir = globalCacheDir();
|
|
10303
10324
|
const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
10304
10325
|
if (!fs2.existsSync(cacheFile)) return null;
|
|
10305
10326
|
const raw = fs2.readFileSync(cacheFile, "utf8");
|
|
@@ -10312,7 +10333,7 @@ var ModelCatalog = class {
|
|
|
10312
10333
|
}
|
|
10313
10334
|
static writeCache(key, models) {
|
|
10314
10335
|
try {
|
|
10315
|
-
const cacheDir =
|
|
10336
|
+
const cacheDir = globalCacheDir();
|
|
10316
10337
|
fs2.mkdirSync(cacheDir, { recursive: true });
|
|
10317
10338
|
const cacheFile = path8.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
10318
10339
|
fs2.writeFileSync(
|
|
@@ -11754,14 +11775,14 @@ ${part.text}` : part.text });
|
|
|
11754
11775
|
}
|
|
11755
11776
|
}
|
|
11756
11777
|
}
|
|
11757
|
-
async json(method,
|
|
11758
|
-
const response = await this.deps.fetch(`${this.baseUrl}${
|
|
11778
|
+
async json(method, path20, body, signal) {
|
|
11779
|
+
const response = await this.deps.fetch(`${this.baseUrl}${path20}`, {
|
|
11759
11780
|
method,
|
|
11760
11781
|
headers: { "content-type": "application/json" },
|
|
11761
11782
|
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
11762
11783
|
...signal ? { signal } : {}
|
|
11763
11784
|
});
|
|
11764
|
-
if (!response.ok) throw new Error(`${method} ${
|
|
11785
|
+
if (!response.ok) throw new Error(`${method} ${path20} failed: ${response.status} ${response.statusText}`);
|
|
11765
11786
|
return await response.json().catch(() => null);
|
|
11766
11787
|
}
|
|
11767
11788
|
nativeSessionId() {
|
|
@@ -11916,9 +11937,6 @@ var CliAgentAiService = class {
|
|
|
11916
11937
|
req.runners,
|
|
11917
11938
|
req.runnerModes,
|
|
11918
11939
|
req.autonomousDefault ?? true,
|
|
11919
|
-
req.grillMeEnabled ?? false,
|
|
11920
|
-
req.prdEnabled ?? false,
|
|
11921
|
-
req.reviewEnabled ?? false,
|
|
11922
11940
|
req.verificationEnabled ?? false,
|
|
11923
11941
|
true
|
|
11924
11942
|
);
|
|
@@ -11933,10 +11951,7 @@ var CliAgentAiService = class {
|
|
|
11933
11951
|
startOptions,
|
|
11934
11952
|
runners: req.runners,
|
|
11935
11953
|
runnerModes: req.runnerModes,
|
|
11936
|
-
autonomousDefault: req.autonomousDefault
|
|
11937
|
-
prdEnabled: req.prdEnabled ?? false,
|
|
11938
|
-
prdCaptured: false,
|
|
11939
|
-
prdNudgeSent: false
|
|
11954
|
+
autonomousDefault: req.autonomousDefault
|
|
11940
11955
|
};
|
|
11941
11956
|
const opening = this.openingMessage(req);
|
|
11942
11957
|
return this.runConversation(opening, req.onProgress, req.signal);
|
|
@@ -11964,8 +11979,8 @@ var CliAgentAiService = class {
|
|
|
11964
11979
|
/**
|
|
11965
11980
|
* Drive one user message to a settled planner turn. Same shape as the API
|
|
11966
11981
|
* backend's conversation loop minus the tool rounds — those belong to the
|
|
11967
|
-
* agent now — and with the same
|
|
11968
|
-
*
|
|
11982
|
+
* agent now — and with the same two policies layered on top: the
|
|
11983
|
+
* empty-reply nudge, and a bounded corrective re-emit for botched JSON.
|
|
11969
11984
|
*/
|
|
11970
11985
|
async runConversation(message, onProgress, signal) {
|
|
11971
11986
|
const conversation = this.conversation;
|
|
@@ -12013,7 +12028,6 @@ var CliAgentAiService = class {
|
|
|
12013
12028
|
].join(" ");
|
|
12014
12029
|
continue;
|
|
12015
12030
|
}
|
|
12016
|
-
if (extractPrdBlock(turn.text)) conversation.prdCaptured = true;
|
|
12017
12031
|
const reply = classifyPlannerReply(turn.text, {
|
|
12018
12032
|
runners: conversation.runners,
|
|
12019
12033
|
runnerModes: conversation.runnerModes,
|
|
@@ -12027,16 +12041,6 @@ var CliAgentAiService = class {
|
|
|
12027
12041
|
case "task_query":
|
|
12028
12042
|
return { kind: "task_query", query: reply.query, text: replyText(turn.text), researchLog };
|
|
12029
12043
|
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
12044
|
this.conversation = null;
|
|
12041
12045
|
return { kind: "plan", tasks: reply.tasks, text: replyText(turn.text), researchLog };
|
|
12042
12046
|
case "broken_task_ops":
|
|
@@ -12215,10 +12219,7 @@ var CliAgentAiService = class {
|
|
|
12215
12219
|
oneShotAdapter = await this.startAdapter(startOptions);
|
|
12216
12220
|
this.conversation = {
|
|
12217
12221
|
startOptions,
|
|
12218
|
-
runners: []
|
|
12219
|
-
prdEnabled: false,
|
|
12220
|
-
prdCaptured: false,
|
|
12221
|
-
prdNudgeSent: false
|
|
12222
|
+
runners: []
|
|
12222
12223
|
};
|
|
12223
12224
|
const turn = await this.runTurn(
|
|
12224
12225
|
"Follow the instructions in your system prompt and produce the plan now.",
|
|
@@ -12235,8 +12236,8 @@ var CliAgentAiService = class {
|
|
|
12235
12236
|
this.lastNativeSessionId = previousSessionId;
|
|
12236
12237
|
}
|
|
12237
12238
|
}
|
|
12238
|
-
async researchAndPlan(userDescription, runners, modelsByRunner,
|
|
12239
|
-
const contextStr = await collectResearchContext(
|
|
12239
|
+
async researchAndPlan(userDescription, runners, modelsByRunner, fs12, onProgress, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
|
|
12240
|
+
const contextStr = await collectResearchContext(fs12, runners);
|
|
12240
12241
|
const researchLog = [
|
|
12241
12242
|
{ id: `up-${Date.now()}`, type: "user_prompt", content: userDescription, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
12242
12243
|
];
|
|
@@ -12559,6 +12560,181 @@ function retargetTaskRunner(task, runner, catalog) {
|
|
|
12559
12560
|
return { assignedRunner: runner, ...runnerAssignment(catalog, task) };
|
|
12560
12561
|
}
|
|
12561
12562
|
|
|
12563
|
+
// src/services/SkillsService.ts
|
|
12564
|
+
var fs4 = __toESM(require("fs"));
|
|
12565
|
+
var path11 = __toESM(require("path"));
|
|
12566
|
+
|
|
12567
|
+
// src/services/builtinSkills.ts
|
|
12568
|
+
var path10 = __toESM(require("path"));
|
|
12569
|
+
var import_url = require("url");
|
|
12570
|
+
var import_meta = {};
|
|
12571
|
+
function builtinSkillsDir() {
|
|
12572
|
+
const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path10.dirname((0, import_url.fileURLToPath)(
|
|
12573
|
+
// @ts-expect-error - import.meta is only valid under an ESM module setting
|
|
12574
|
+
import_meta.url
|
|
12575
|
+
));
|
|
12576
|
+
return path10.join(dir, "..", "skills");
|
|
12577
|
+
}
|
|
12578
|
+
|
|
12579
|
+
// src/services/SkillsService.ts
|
|
12580
|
+
var BUILTIN_SKILL_NAMES = ["grilling", "to-spec"];
|
|
12581
|
+
var RETIRED_BUILTIN_SKILL_NAMES = ["grill-me"];
|
|
12582
|
+
function parseSkillFile(filePath, name, source) {
|
|
12583
|
+
const raw = fs4.readFileSync(filePath, "utf8");
|
|
12584
|
+
const frontmatter = {};
|
|
12585
|
+
let content = raw;
|
|
12586
|
+
if (raw.startsWith("---\n")) {
|
|
12587
|
+
const end = raw.indexOf("\n---", 4);
|
|
12588
|
+
if (end !== -1) {
|
|
12589
|
+
const fmText = raw.slice(4, end);
|
|
12590
|
+
for (const line of fmText.split("\n")) {
|
|
12591
|
+
const idx = line.indexOf(":");
|
|
12592
|
+
if (idx === -1) continue;
|
|
12593
|
+
const key = line.slice(0, idx).trim();
|
|
12594
|
+
const value = line.slice(idx + 1).trim();
|
|
12595
|
+
if (key === "disable-model-invocation") {
|
|
12596
|
+
frontmatter[key] = value === "true";
|
|
12597
|
+
} else if (key === "name" || key === "description") {
|
|
12598
|
+
frontmatter[key] = value.replace(/^"|"$/g, "");
|
|
12599
|
+
}
|
|
12600
|
+
}
|
|
12601
|
+
content = raw.slice(end + 4).replace(/^\n+/, "");
|
|
12602
|
+
}
|
|
12603
|
+
}
|
|
12604
|
+
const metadata = {
|
|
12605
|
+
name: frontmatter.name ?? name,
|
|
12606
|
+
description: frontmatter.description ?? "",
|
|
12607
|
+
...frontmatter["disable-model-invocation"] !== void 0 ? { disableModelInvocation: frontmatter["disable-model-invocation"] } : {}
|
|
12608
|
+
};
|
|
12609
|
+
return { name, description: metadata.description, metadata, content, path: filePath, source };
|
|
12610
|
+
}
|
|
12611
|
+
function copyDirSync2(src, dest) {
|
|
12612
|
+
fs4.mkdirSync(dest, { recursive: true });
|
|
12613
|
+
const entries = fs4.readdirSync(src, { withFileTypes: true });
|
|
12614
|
+
for (const entry of entries) {
|
|
12615
|
+
const srcPath = path11.join(src, entry.name);
|
|
12616
|
+
const destPath = path11.join(dest, entry.name);
|
|
12617
|
+
if (entry.isDirectory()) copyDirSync2(srcPath, destPath);
|
|
12618
|
+
else fs4.copyFileSync(srcPath, destPath);
|
|
12619
|
+
}
|
|
12620
|
+
}
|
|
12621
|
+
function readDir(dir, source) {
|
|
12622
|
+
if (!fs4.existsSync(dir)) return [];
|
|
12623
|
+
const entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
12624
|
+
const skills = [];
|
|
12625
|
+
for (const entry of entries) {
|
|
12626
|
+
if (!entry.isDirectory()) continue;
|
|
12627
|
+
const skillFile = path11.join(dir, entry.name, "SKILL.md");
|
|
12628
|
+
if (!fs4.existsSync(skillFile)) continue;
|
|
12629
|
+
const parsed = parseSkillFile(skillFile, entry.name, source);
|
|
12630
|
+
if (parsed) skills.push(parsed);
|
|
12631
|
+
}
|
|
12632
|
+
return skills;
|
|
12633
|
+
}
|
|
12634
|
+
var SkillsService = class {
|
|
12635
|
+
constructor(workspaceRoot) {
|
|
12636
|
+
this.workspaceRoot = workspaceRoot;
|
|
12637
|
+
}
|
|
12638
|
+
workspaceRoot;
|
|
12639
|
+
localDir() {
|
|
12640
|
+
return this.workspaceRoot ? path11.join(this.workspaceRoot, ".ordewell", "skills") : void 0;
|
|
12641
|
+
}
|
|
12642
|
+
globalDir() {
|
|
12643
|
+
return path11.join(globalDataDir(), "skills");
|
|
12644
|
+
}
|
|
12645
|
+
seed(name) {
|
|
12646
|
+
const dest = path11.join(this.globalDir(), name);
|
|
12647
|
+
if (fs4.existsSync(dest)) return false;
|
|
12648
|
+
let srcDir;
|
|
12649
|
+
try {
|
|
12650
|
+
srcDir = path11.join(builtinSkillsDir(), name);
|
|
12651
|
+
} catch (err) {
|
|
12652
|
+
console.warn(`Could not locate built-in skills dir: ${String(err)}`);
|
|
12653
|
+
return false;
|
|
12654
|
+
}
|
|
12655
|
+
if (!fs4.existsSync(srcDir)) {
|
|
12656
|
+
console.warn(`Built-in skill not found in package: ${name}`);
|
|
12657
|
+
return false;
|
|
12658
|
+
}
|
|
12659
|
+
copyDirSync2(srcDir, dest);
|
|
12660
|
+
return true;
|
|
12661
|
+
}
|
|
12662
|
+
seedBuiltinSkill(name) {
|
|
12663
|
+
return this.seed(name);
|
|
12664
|
+
}
|
|
12665
|
+
/** Removes a retired built-in's seed from the global dir, but only if it's untouched by the user. */
|
|
12666
|
+
isUnmodifiedRetiredSeed(dir, expectedName) {
|
|
12667
|
+
let entries;
|
|
12668
|
+
try {
|
|
12669
|
+
entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
12670
|
+
} catch {
|
|
12671
|
+
return false;
|
|
12672
|
+
}
|
|
12673
|
+
if (entries.length !== 1 || entries[0].name !== "SKILL.md" || !entries[0].isFile()) return false;
|
|
12674
|
+
let raw;
|
|
12675
|
+
try {
|
|
12676
|
+
raw = fs4.readFileSync(path11.join(dir, "SKILL.md"), "utf8");
|
|
12677
|
+
} catch {
|
|
12678
|
+
return false;
|
|
12679
|
+
}
|
|
12680
|
+
if (!raw.startsWith("---\n")) return false;
|
|
12681
|
+
const end = raw.indexOf("\n---", 4);
|
|
12682
|
+
if (end === -1) return false;
|
|
12683
|
+
for (const line of raw.slice(4, end).split("\n")) {
|
|
12684
|
+
const idx = line.indexOf(":");
|
|
12685
|
+
if (idx === -1) continue;
|
|
12686
|
+
const key = line.slice(0, idx).trim();
|
|
12687
|
+
if (key === "name") return line.slice(idx + 1).trim().replace(/^"|"$/g, "") === expectedName;
|
|
12688
|
+
}
|
|
12689
|
+
return false;
|
|
12690
|
+
}
|
|
12691
|
+
pruneRetired() {
|
|
12692
|
+
for (const name of RETIRED_BUILTIN_SKILL_NAMES) {
|
|
12693
|
+
const dest = path11.join(this.globalDir(), name);
|
|
12694
|
+
if (!fs4.existsSync(dest)) continue;
|
|
12695
|
+
if (!this.isUnmodifiedRetiredSeed(dest, name)) continue;
|
|
12696
|
+
try {
|
|
12697
|
+
fs4.rmSync(dest, { recursive: true, force: true });
|
|
12698
|
+
} catch (err) {
|
|
12699
|
+
console.warn(`Could not prune retired skill ${name}: ${String(err)}`);
|
|
12700
|
+
}
|
|
12701
|
+
}
|
|
12702
|
+
}
|
|
12703
|
+
findSkill(name) {
|
|
12704
|
+
this.pruneRetired();
|
|
12705
|
+
if (BUILTIN_SKILL_NAMES.includes(name)) this.seed(name);
|
|
12706
|
+
const localDir = this.localDir();
|
|
12707
|
+
if (localDir) {
|
|
12708
|
+
const localFile = path11.join(localDir, name, "SKILL.md");
|
|
12709
|
+
if (fs4.existsSync(localFile)) return parseSkillFile(localFile, name, "local");
|
|
12710
|
+
}
|
|
12711
|
+
const globalFile = path11.join(this.globalDir(), name, "SKILL.md");
|
|
12712
|
+
if (fs4.existsSync(globalFile)) return parseSkillFile(globalFile, name, "global");
|
|
12713
|
+
return void 0;
|
|
12714
|
+
}
|
|
12715
|
+
listSkills() {
|
|
12716
|
+
this.pruneRetired();
|
|
12717
|
+
for (const name of BUILTIN_SKILL_NAMES) this.seed(name);
|
|
12718
|
+
const byName = /* @__PURE__ */ new Map();
|
|
12719
|
+
for (const skill of readDir(this.globalDir(), "global")) {
|
|
12720
|
+
byName.set(skill.name, skill);
|
|
12721
|
+
}
|
|
12722
|
+
const localDir = this.localDir();
|
|
12723
|
+
if (localDir) {
|
|
12724
|
+
for (const skill of readDir(localDir, "local")) {
|
|
12725
|
+
byName.set(skill.name, skill);
|
|
12726
|
+
}
|
|
12727
|
+
}
|
|
12728
|
+
return [...byName.values()];
|
|
12729
|
+
}
|
|
12730
|
+
getSkillContent(name) {
|
|
12731
|
+
return this.findSkill(name)?.content;
|
|
12732
|
+
}
|
|
12733
|
+
};
|
|
12734
|
+
function createSkillsService(workspaceRoot) {
|
|
12735
|
+
return new SkillsService(workspaceRoot);
|
|
12736
|
+
}
|
|
12737
|
+
|
|
12562
12738
|
// src/services/SessionMessage.ts
|
|
12563
12739
|
function serializeTask(t) {
|
|
12564
12740
|
return {
|
|
@@ -12580,11 +12756,12 @@ function serializeTask(t) {
|
|
|
12580
12756
|
userStoriesCovered: t.userStoriesCovered || void 0
|
|
12581
12757
|
};
|
|
12582
12758
|
}
|
|
12583
|
-
function serializeTaskStatus(t) {
|
|
12759
|
+
function serializeTaskStatus(t, idleSince = null) {
|
|
12584
12760
|
return {
|
|
12585
12761
|
id: t.id,
|
|
12586
12762
|
status: t.status,
|
|
12587
|
-
verdict: t.verdict ? { outcome: t.verdict.outcome, reason: t.verdict.reason, checks: t.verdict.checks || [] } : null
|
|
12763
|
+
verdict: t.verdict ? { outcome: t.verdict.outcome, reason: t.verdict.reason, checks: t.verdict.checks || [] } : null,
|
|
12764
|
+
idleSince
|
|
12588
12765
|
};
|
|
12589
12766
|
}
|
|
12590
12767
|
function serializePlan(plan) {
|
|
@@ -12604,29 +12781,35 @@ function executionSummary(tasks) {
|
|
|
12604
12781
|
failed: tasks.filter((t) => t.status === "failed").length
|
|
12605
12782
|
};
|
|
12606
12783
|
}
|
|
12784
|
+
var CHECKPOINT_TRUNCATE_LENGTH = 120;
|
|
12785
|
+
function truncateCheckpointSummary(summary) {
|
|
12786
|
+
const firstLine = summary.split("\n")[0].trim();
|
|
12787
|
+
if (firstLine.length <= CHECKPOINT_TRUNCATE_LENGTH) return firstLine;
|
|
12788
|
+
return firstLine.slice(0, CHECKPOINT_TRUNCATE_LENGTH - 1) + "\u2026";
|
|
12789
|
+
}
|
|
12607
12790
|
|
|
12608
12791
|
// src/utils/sessionStore.ts
|
|
12609
|
-
var
|
|
12610
|
-
var
|
|
12792
|
+
var fs6 = __toESM(require("fs"));
|
|
12793
|
+
var path13 = __toESM(require("path"));
|
|
12611
12794
|
|
|
12612
12795
|
// src/utils/fsHelpers.ts
|
|
12613
|
-
var
|
|
12614
|
-
var
|
|
12796
|
+
var fs5 = __toESM(require("fs"));
|
|
12797
|
+
var path12 = __toESM(require("path"));
|
|
12615
12798
|
var STATE_DIR = ".ordewell";
|
|
12616
12799
|
function ensureDir(dir) {
|
|
12617
|
-
if (!
|
|
12618
|
-
|
|
12800
|
+
if (!fs5.existsSync(dir)) {
|
|
12801
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
12619
12802
|
}
|
|
12620
12803
|
}
|
|
12621
12804
|
function getStateDir(baseDir) {
|
|
12622
|
-
return
|
|
12805
|
+
return path12.join(baseDir ?? process.cwd(), STATE_DIR);
|
|
12623
12806
|
}
|
|
12624
12807
|
var IGNORE_FILE = ".gitignore";
|
|
12625
12808
|
function ensureStateDirIgnored(baseDir) {
|
|
12626
12809
|
const dir = getStateDir(baseDir);
|
|
12627
12810
|
ensureDir(dir);
|
|
12628
12811
|
try {
|
|
12629
|
-
|
|
12812
|
+
fs5.writeFileSync(path12.join(dir, IGNORE_FILE), "*\n", { flag: "wx" });
|
|
12630
12813
|
} catch (err) {
|
|
12631
12814
|
if (err.code !== "EEXIST") throw err;
|
|
12632
12815
|
}
|
|
@@ -12641,7 +12824,7 @@ function mintSessionId() {
|
|
|
12641
12824
|
// src/utils/sessionStore.ts
|
|
12642
12825
|
var SESSIONS_SUBDIR = "sessions";
|
|
12643
12826
|
function getSessionsDir(baseDir) {
|
|
12644
|
-
return
|
|
12827
|
+
return path13.join(getStateDir(baseDir), SESSIONS_SUBDIR);
|
|
12645
12828
|
}
|
|
12646
12829
|
var REJECTION_MESSAGE = "This session was created with an older version of Ordewell. Please re-plan.";
|
|
12647
12830
|
function hasScalarRunner(parsed) {
|
|
@@ -12676,17 +12859,17 @@ function saveSession(plan, goal, baseDir, id) {
|
|
|
12676
12859
|
plan: JSON.parse(JSON.stringify(plan))
|
|
12677
12860
|
};
|
|
12678
12861
|
const filename = sessionFilename(meta);
|
|
12679
|
-
const filePath =
|
|
12680
|
-
|
|
12862
|
+
const filePath = path13.join(sessionsDir, filename);
|
|
12863
|
+
fs6.writeFileSync(filePath, JSON.stringify(session, null, 2));
|
|
12681
12864
|
return meta;
|
|
12682
12865
|
}
|
|
12683
12866
|
function listSessions(baseDir, logger = defaultLogger) {
|
|
12684
12867
|
const sessionsDir = getSessionsDir(baseDir);
|
|
12685
|
-
if (!
|
|
12686
|
-
const files =
|
|
12868
|
+
if (!fs6.existsSync(sessionsDir)) return [];
|
|
12869
|
+
const files = fs6.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
|
|
12687
12870
|
return files.map((f) => {
|
|
12688
12871
|
try {
|
|
12689
|
-
const raw =
|
|
12872
|
+
const raw = fs6.readFileSync(path13.join(sessionsDir, f), "utf-8");
|
|
12690
12873
|
const parsed = JSON.parse(raw);
|
|
12691
12874
|
if (hasScalarRunner(parsed)) {
|
|
12692
12875
|
logger.warn("sessionStore", `session file ${f}: ${REJECTION_MESSAGE}`);
|
|
@@ -12701,10 +12884,10 @@ function listSessions(baseDir, logger = defaultLogger) {
|
|
|
12701
12884
|
}).filter((m) => m !== null).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
12702
12885
|
}
|
|
12703
12886
|
function findSessionFile(sessionsDir, sessionId, logger) {
|
|
12704
|
-
const files =
|
|
12887
|
+
const files = fs6.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
|
|
12705
12888
|
for (const f of files) {
|
|
12706
12889
|
try {
|
|
12707
|
-
const raw =
|
|
12890
|
+
const raw = fs6.readFileSync(path13.join(sessionsDir, f), "utf-8");
|
|
12708
12891
|
const session = JSON.parse(raw);
|
|
12709
12892
|
if (session.meta.id === sessionId) return f;
|
|
12710
12893
|
} catch (err) {
|
|
@@ -12715,11 +12898,11 @@ function findSessionFile(sessionsDir, sessionId, logger) {
|
|
|
12715
12898
|
}
|
|
12716
12899
|
function loadSession(sessionId, baseDir, logger = defaultLogger) {
|
|
12717
12900
|
const sessionsDir = getSessionsDir(baseDir);
|
|
12718
|
-
if (!
|
|
12901
|
+
if (!fs6.existsSync(sessionsDir)) return null;
|
|
12719
12902
|
try {
|
|
12720
12903
|
const file = findSessionFile(sessionsDir, sessionId, logger);
|
|
12721
12904
|
if (file) {
|
|
12722
|
-
const raw =
|
|
12905
|
+
const raw = fs6.readFileSync(path13.join(sessionsDir, file), "utf-8");
|
|
12723
12906
|
const parsed = JSON.parse(raw);
|
|
12724
12907
|
if (hasScalarRunner(parsed)) {
|
|
12725
12908
|
logger.warn("sessionStore", `session file ${file}: ${REJECTION_MESSAGE}`);
|
|
@@ -12749,11 +12932,11 @@ function getLatestSession(baseDir, logger = defaultLogger) {
|
|
|
12749
12932
|
}
|
|
12750
12933
|
function deleteSession(sessionId, baseDir, logger = defaultLogger) {
|
|
12751
12934
|
const sessionsDir = getSessionsDir(baseDir);
|
|
12752
|
-
if (!
|
|
12935
|
+
if (!fs6.existsSync(sessionsDir)) return false;
|
|
12753
12936
|
try {
|
|
12754
12937
|
const file = findSessionFile(sessionsDir, sessionId, logger);
|
|
12755
12938
|
if (file) {
|
|
12756
|
-
|
|
12939
|
+
fs6.unlinkSync(path13.join(sessionsDir, file));
|
|
12757
12940
|
return true;
|
|
12758
12941
|
}
|
|
12759
12942
|
} catch (err) {
|
|
@@ -12762,6 +12945,29 @@ function deleteSession(sessionId, baseDir, logger = defaultLogger) {
|
|
|
12762
12945
|
return false;
|
|
12763
12946
|
}
|
|
12764
12947
|
|
|
12948
|
+
// src/utils/prdStore.ts
|
|
12949
|
+
var fs7 = __toESM(require("fs"));
|
|
12950
|
+
var path14 = __toESM(require("path"));
|
|
12951
|
+
var PRD_BLOCK_RE = /<!--\s*ORDEWELL_PRD_START\s+slug="([^"]+)"\s*-->\s*([\s\S]*?)\s*<!--\s*ORDEWELL_PRD_END\s*-->/;
|
|
12952
|
+
function extractPrdBlock(text) {
|
|
12953
|
+
const match = PRD_BLOCK_RE.exec(text);
|
|
12954
|
+
if (!match) return null;
|
|
12955
|
+
const slug2 = sanitizeSlug(match[1]);
|
|
12956
|
+
const markdown = match[2].trim();
|
|
12957
|
+
if (!slug2 || !markdown) return null;
|
|
12958
|
+
return { slug: slug2, markdown };
|
|
12959
|
+
}
|
|
12960
|
+
function sanitizeSlug(raw) {
|
|
12961
|
+
return raw.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
12962
|
+
}
|
|
12963
|
+
function savePrdMarkdown(workspace, slug2, markdown) {
|
|
12964
|
+
const dir = path14.join(workspace, ".scratch", sanitizeSlug(slug2));
|
|
12965
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
12966
|
+
const file = path14.join(dir, "PRD.md");
|
|
12967
|
+
fs7.writeFileSync(file, markdown.endsWith("\n") ? markdown : markdown + "\n", "utf8");
|
|
12968
|
+
return file;
|
|
12969
|
+
}
|
|
12970
|
+
|
|
12765
12971
|
// src/services/createSession.ts
|
|
12766
12972
|
var CATALOG_MODEL_CAP = 100;
|
|
12767
12973
|
var MAX_TASK_QUERIES = 3;
|
|
@@ -12778,6 +12984,17 @@ var PlanEditError = class extends Error {
|
|
|
12778
12984
|
function sessionRuntimeSettings(settings) {
|
|
12779
12985
|
return { ...plannerRuntimeToggles(settings), modelAllowlist: settings.modelAllowlist };
|
|
12780
12986
|
}
|
|
12987
|
+
function resolveSkillInvocation(text, skillsService) {
|
|
12988
|
+
const match = text.trim().match(/^\/([a-z][a-z0-9_-]*)$/im);
|
|
12989
|
+
if (!match) return text;
|
|
12990
|
+
const skillName = match[1].toLowerCase();
|
|
12991
|
+
const skill = skillsService.findSkill(skillName);
|
|
12992
|
+
if (!skill) {
|
|
12993
|
+
const available = typeof skillsService.listSkills === "function" ? skillsService.listSkills().map((s) => s.name).join(", ") : "";
|
|
12994
|
+
return `Unknown skill: ${skillName}. Available skills: ${available}`;
|
|
12995
|
+
}
|
|
12996
|
+
return skill.content;
|
|
12997
|
+
}
|
|
12781
12998
|
var Session = class {
|
|
12782
12999
|
/** Injected by a test; when present it is the service, forever. */
|
|
12783
13000
|
pinnedAiService;
|
|
@@ -12805,6 +13022,7 @@ var Session = class {
|
|
|
12805
13022
|
unsubObserver = null;
|
|
12806
13023
|
hostSessionId;
|
|
12807
13024
|
currentSessionId;
|
|
13025
|
+
skillsService;
|
|
12808
13026
|
constructor(deps) {
|
|
12809
13027
|
this.config = deps.config;
|
|
12810
13028
|
this.registry = deps.registry;
|
|
@@ -12823,6 +13041,7 @@ var Session = class {
|
|
|
12823
13041
|
this.settingsFn = deps.settings;
|
|
12824
13042
|
this.hostSessionId = deps.sessionId;
|
|
12825
13043
|
this.currentSessionId = deps.sessionId ?? mintSessionId();
|
|
13044
|
+
this.skillsService = deps.skillsService ?? new SkillsService(this.workspaceRootFn());
|
|
12826
13045
|
this.approvals = new PendingApprovals({
|
|
12827
13046
|
onRequest: ({ id, request }) => this.broadcast({
|
|
12828
13047
|
type: "approval_request",
|
|
@@ -12917,7 +13136,7 @@ var Session = class {
|
|
|
12917
13136
|
onTaskChanged: () => {
|
|
12918
13137
|
if (!this.plan) return;
|
|
12919
13138
|
const tasks = this.store.allTasks;
|
|
12920
|
-
this.broadcast({ type: "status_update", tasks: tasks.map(serializeTaskStatus) });
|
|
13139
|
+
this.broadcast({ type: "status_update", tasks: tasks.map((t) => serializeTaskStatus(t, this.orchestrator.getIdleSince(t.id))) });
|
|
12921
13140
|
},
|
|
12922
13141
|
onQueueReady: () => {
|
|
12923
13142
|
this.broadcast({ type: "queue_ready" });
|
|
@@ -12935,7 +13154,7 @@ var Session = class {
|
|
|
12935
13154
|
onTick: () => {
|
|
12936
13155
|
if (!this.plan) return;
|
|
12937
13156
|
const tasks = this.store.allTasks;
|
|
12938
|
-
this.broadcast({ type: "status_update", tasks: tasks.map(serializeTaskStatus) });
|
|
13157
|
+
this.broadcast({ type: "status_update", tasks: tasks.map((t) => serializeTaskStatus(t, this.orchestrator.getIdleSince(t.id))) });
|
|
12939
13158
|
},
|
|
12940
13159
|
onExecutionComplete: () => {
|
|
12941
13160
|
if (!this.plan) return;
|
|
@@ -13147,7 +13366,7 @@ var Session = class {
|
|
|
13147
13366
|
*/
|
|
13148
13367
|
async startPlanning(goal, runners, options) {
|
|
13149
13368
|
this.plan = null;
|
|
13150
|
-
this.goal = goal;
|
|
13369
|
+
this.goal = this.resolveSkillInvocation(goal);
|
|
13151
13370
|
this.remintSessionId();
|
|
13152
13371
|
this.beginFreshPlan();
|
|
13153
13372
|
const enabled = this.config.enabledRunners;
|
|
@@ -13156,7 +13375,7 @@ var Session = class {
|
|
|
13156
13375
|
const modelsByRunner = await this.modelResolver.modelsForRunners(chosenRunners);
|
|
13157
13376
|
this.modelsCache = modelsByRunner;
|
|
13158
13377
|
const runnerModes = this.runnerModesFor(chosenRunners);
|
|
13159
|
-
const {
|
|
13378
|
+
const { verificationEnabled, modelAllowlist } = this.settingsFn();
|
|
13160
13379
|
this.currentAllowlist = modelAllowlist ?? {};
|
|
13161
13380
|
const filteredModels = filterModelsForPrompt(modelsByRunner, this.currentAllowlist);
|
|
13162
13381
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -13166,22 +13385,18 @@ var Session = class {
|
|
|
13166
13385
|
status: "draft",
|
|
13167
13386
|
runners: chosenRunners,
|
|
13168
13387
|
lastUpdated: now,
|
|
13169
|
-
researchLog: [{ id: `up-${Date.now()}`, type: "user_prompt", content: goal, timestamp: now }],
|
|
13170
|
-
conversationHistory: [{ role: "user", content: goal, timestamp: now }]
|
|
13388
|
+
researchLog: [{ id: `up-${Date.now()}`, type: "user_prompt", content: this.goal, timestamp: now }],
|
|
13389
|
+
conversationHistory: [{ role: "user", content: this.goal, timestamp: now }]
|
|
13171
13390
|
};
|
|
13172
13391
|
const releaseAbort = this.denyApprovalsOnAbort(options?.signal);
|
|
13173
13392
|
try {
|
|
13174
13393
|
const turn = await this.aiService.startConversation({
|
|
13175
|
-
goal,
|
|
13394
|
+
goal: this.goal,
|
|
13176
13395
|
runners: chosenRunners,
|
|
13177
13396
|
modelsByRunner: filteredModels,
|
|
13178
13397
|
runnerModes,
|
|
13179
13398
|
autonomousDefault: this.config.autonomousMode,
|
|
13180
|
-
grillMeEnabled: grillMeEnabled ?? false,
|
|
13181
|
-
prdEnabled: prdEnabled ?? false,
|
|
13182
|
-
reviewEnabled: reviewEnabled ?? false,
|
|
13183
13399
|
verificationEnabled: verificationEnabled ?? false,
|
|
13184
|
-
researchSubagentsEnabled: researchSubagentsEnabled ?? false,
|
|
13185
13400
|
fs: this.fsAdapter,
|
|
13186
13401
|
fetcher: this.fetcher,
|
|
13187
13402
|
onProgress: (p) => this.translateProgress(p),
|
|
@@ -13193,21 +13408,22 @@ var Session = class {
|
|
|
13193
13408
|
}
|
|
13194
13409
|
}
|
|
13195
13410
|
/**
|
|
13196
|
-
* Every subsequent user reply in the planning conversation —
|
|
13197
|
-
* answers,
|
|
13411
|
+
* Every subsequent user reply in the planning conversation — clarifying
|
|
13412
|
+
* answers, outline confirm. One branch, no phase ladder.
|
|
13198
13413
|
*/
|
|
13199
13414
|
async continueConversation(userMessage, options) {
|
|
13200
13415
|
if (!this.plan) throw new Error("No planning conversation to continue");
|
|
13201
13416
|
const priorHistory = this.plan.conversationHistory ?? [];
|
|
13202
13417
|
const priorResearchLog = this.plan.researchLog ?? [];
|
|
13203
13418
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13204
|
-
this.
|
|
13419
|
+
const resolved = this.resolveSkillInvocation(userMessage);
|
|
13420
|
+
this.appendTranscript("user", resolved, now);
|
|
13205
13421
|
const historyAfterOwnAppend = this.plan.conversationHistory;
|
|
13206
|
-
this.plan.researchLog = [...this.plan.researchLog ?? [], { id: `up-${Date.now()}`, type: "user_prompt", content:
|
|
13422
|
+
this.plan.researchLog = [...this.plan.researchLog ?? [], { id: `up-${Date.now()}`, type: "user_prompt", content: resolved, timestamp: now }];
|
|
13207
13423
|
const contextBlock = [this.catalogBlock(), this.planContextBlock()].filter(Boolean).join("\n\n");
|
|
13208
13424
|
const outgoing = contextBlock ? `${contextBlock}
|
|
13209
13425
|
|
|
13210
|
-
${
|
|
13426
|
+
${resolved}` : resolved;
|
|
13211
13427
|
const aiService = this.aiService;
|
|
13212
13428
|
const canContinueLive = aiService.hasActiveConversation() && (aiService.conversationMatchesConfig?.() ?? true);
|
|
13213
13429
|
const releaseAbort = this.denyApprovalsOnAbort(options?.signal);
|
|
@@ -13347,8 +13563,8 @@ The plan is unchanged. Rephrase the request, or adjust the tasks manually.`,
|
|
|
13347
13563
|
* The catalog the planner may actually draw from — model ids and task-mode
|
|
13348
13564
|
* ids, per selected runner — emitted on EVERY turn, before any plan exists
|
|
13349
13565
|
* or after. The system prompt shows this once at conversation start; a long
|
|
13350
|
-
*
|
|
13351
|
-
*
|
|
13566
|
+
* clarifying conversation outlives that single showing and the planner
|
|
13567
|
+
* starts misquoting it, so this re-states it per turn instead.
|
|
13352
13568
|
*
|
|
13353
13569
|
* Built from the allowlist-filtered view (`filterModelsForPrompt`), the same
|
|
13354
13570
|
* one the system prompt used — a restricted allowlist stays a hard bound on
|
|
@@ -13479,7 +13695,7 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
|
|
|
13479
13695
|
const modelsByRunner = await this.modelResolver.modelsForRunners(runners);
|
|
13480
13696
|
this.modelsCache = modelsByRunner;
|
|
13481
13697
|
const runnerModes = this.runnerModesFor(runners);
|
|
13482
|
-
const {
|
|
13698
|
+
const { verificationEnabled, modelAllowlist } = this.settingsFn();
|
|
13483
13699
|
this.currentAllowlist = modelAllowlist ?? {};
|
|
13484
13700
|
const filteredModels = filterModelsForPrompt(modelsByRunner, this.currentAllowlist);
|
|
13485
13701
|
const goal = this.goal || priorHistory.find((m) => m.role === "user")?.content || userMessage;
|
|
@@ -13489,11 +13705,7 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
|
|
|
13489
13705
|
modelsByRunner: filteredModels,
|
|
13490
13706
|
runnerModes,
|
|
13491
13707
|
autonomousDefault: this.config.autonomousMode,
|
|
13492
|
-
grillMeEnabled: grillMeEnabled ?? false,
|
|
13493
|
-
prdEnabled: prdEnabled ?? false,
|
|
13494
|
-
reviewEnabled: reviewEnabled ?? false,
|
|
13495
13708
|
verificationEnabled: verificationEnabled ?? false,
|
|
13496
|
-
researchSubagentsEnabled: researchSubagentsEnabled ?? false,
|
|
13497
13709
|
fs: this.fsAdapter,
|
|
13498
13710
|
fetcher: this.fetcher,
|
|
13499
13711
|
onProgress: (p) => this.translateProgress(p),
|
|
@@ -13506,6 +13718,17 @@ Execution is RUNNING${locked.length ? ` \u2014 these tasks are locked: ${locked.
|
|
|
13506
13718
|
get isConversationActive() {
|
|
13507
13719
|
return this.aiService.hasActiveConversation();
|
|
13508
13720
|
}
|
|
13721
|
+
/**
|
|
13722
|
+
* Intercept a skill invocation (/skill-name) and substitute the skill's
|
|
13723
|
+
* markdown content BEFORE the message reaches the planner. Prevents runners
|
|
13724
|
+
* (Claude Code, OpenCode) from trying to resolve the skill in their own
|
|
13725
|
+
* directory instead of .ordewell/skills/. An unknown skill is surfaced to
|
|
13726
|
+
* the planner as a notice naming what IS available, rather than passing a
|
|
13727
|
+
* bare /unknown through to be mis-resolved.
|
|
13728
|
+
*/
|
|
13729
|
+
resolveSkillInvocation(text) {
|
|
13730
|
+
return resolveSkillInvocation(text, this.skillsService);
|
|
13731
|
+
}
|
|
13509
13732
|
/** Append one dialogue entry to the persisted transcript. Callers persist via the mutatePlan ritual. */
|
|
13510
13733
|
appendTranscript(role, content, timestamp, kind) {
|
|
13511
13734
|
if (!this.plan) return;
|
|
@@ -14314,17 +14537,25 @@ var HeadlessSession = class extends AbstractTerminalSession {
|
|
|
14314
14537
|
spawnImpl;
|
|
14315
14538
|
process = null;
|
|
14316
14539
|
outputBuffer = "";
|
|
14540
|
+
controlStream = null;
|
|
14317
14541
|
get isStarted() {
|
|
14318
14542
|
return this.process !== null;
|
|
14319
14543
|
}
|
|
14320
|
-
start(launch, cwd, resolvedPath, env) {
|
|
14544
|
+
start(launch, cwd, resolvedPath, env, options) {
|
|
14321
14545
|
if (this.exited || this.process) return;
|
|
14546
|
+
const stdio = ["pipe", "pipe", "pipe"];
|
|
14547
|
+
if (options?.controlChannel) stdio.push("pipe");
|
|
14322
14548
|
this.process = this.spawnImpl(launch.file, launch.args, {
|
|
14323
14549
|
env: withPath(process.env, resolvedPath, env),
|
|
14324
|
-
stdio
|
|
14550
|
+
stdio,
|
|
14325
14551
|
cwd,
|
|
14326
14552
|
windowsVerbatimArguments: launch.verbatim
|
|
14327
14553
|
});
|
|
14554
|
+
if (options?.controlChannel) {
|
|
14555
|
+
this.controlStream = this.process.stdio?.[3] ?? null;
|
|
14556
|
+
this.controlStream?.on("error", () => {
|
|
14557
|
+
});
|
|
14558
|
+
}
|
|
14328
14559
|
const onData = (data) => {
|
|
14329
14560
|
const raw = data.toString();
|
|
14330
14561
|
this.outputBuffer += stripAnsi(raw);
|
|
@@ -14357,6 +14588,12 @@ Process error: ${err.message}
|
|
|
14357
14588
|
this.process.stdin.write(text);
|
|
14358
14589
|
}
|
|
14359
14590
|
}
|
|
14591
|
+
/** PTY resize requests from a surface that owns the terminal rendering the wrapper. */
|
|
14592
|
+
writeControl(text) {
|
|
14593
|
+
if (this.controlStream && !this.controlStream.destroyed) {
|
|
14594
|
+
this.controlStream.write(text);
|
|
14595
|
+
}
|
|
14596
|
+
}
|
|
14360
14597
|
};
|
|
14361
14598
|
var HeadlessRunner = class extends AbstractRunner {
|
|
14362
14599
|
spawnImpl;
|
|
@@ -14382,7 +14619,7 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
14382
14619
|
return new HeadlessSession(id, taskId, this.spawnImpl);
|
|
14383
14620
|
}
|
|
14384
14621
|
/** Everything up to, but not including, spawning — so a surface that owns its own child reaches the same decisions. */
|
|
14385
|
-
async prepareLaunch(opts) {
|
|
14622
|
+
async prepareLaunch(opts, ptyOptions) {
|
|
14386
14623
|
const interactive = this.defaultInteractive;
|
|
14387
14624
|
const invocation = buildRunnerInvocation({
|
|
14388
14625
|
runner: opts.runner,
|
|
@@ -14399,7 +14636,7 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
14399
14636
|
const manifest = opts.registry?.get(opts.runner)?.manifest;
|
|
14400
14637
|
const pty = (manifest?.runner.requiresTty === true || interactive) && this.hasScriptCmd();
|
|
14401
14638
|
const resolvedPath = await this.resolvePath();
|
|
14402
|
-
const { command, args } = pty ? wrapWithPty(invocation.command, invocation.args) : { command: invocation.command, args: invocation.args };
|
|
14639
|
+
const { command, args } = pty ? wrapWithPty(invocation.command, invocation.args, ptyOptions) : { command: invocation.command, args: invocation.args };
|
|
14403
14640
|
return {
|
|
14404
14641
|
launch: await planDirectLaunch(command, args, this.launchDeps),
|
|
14405
14642
|
resolvedPath,
|
|
@@ -14721,13 +14958,12 @@ var TmuxRunner = class extends AbstractRunner {
|
|
|
14721
14958
|
// src/utils/daemonToken.ts
|
|
14722
14959
|
var import_crypto2 = require("crypto");
|
|
14723
14960
|
var import_fs2 = require("fs");
|
|
14724
|
-
var import_os2 = require("os");
|
|
14725
14961
|
var import_path2 = require("path");
|
|
14726
14962
|
var BEARER_PREFIX = "bearer ";
|
|
14727
14963
|
var DAEMON_TOKEN_SUBPROTOCOL_PREFIX = "ordewell.token.";
|
|
14728
14964
|
var DAEMON_SUBPROTOCOL = "ordewell.v1";
|
|
14729
14965
|
function configDir() {
|
|
14730
|
-
return (
|
|
14966
|
+
return globalDataDir();
|
|
14731
14967
|
}
|
|
14732
14968
|
function daemonTokenPath(port) {
|
|
14733
14969
|
return (0, import_path2.join)(configDir(), `server-${port}.token`);
|
|
@@ -14791,19 +15027,18 @@ function tokensMatch(presented, expected) {
|
|
|
14791
15027
|
}
|
|
14792
15028
|
|
|
14793
15029
|
// src/plugins/RunnerRegistry.ts
|
|
14794
|
-
var
|
|
14795
|
-
var
|
|
14796
|
-
var
|
|
15030
|
+
var fs9 = __toESM(require("fs"));
|
|
15031
|
+
var path17 = __toESM(require("path"));
|
|
15032
|
+
var os4 = __toESM(require("os"));
|
|
14797
15033
|
var import_child_process9 = require("child_process");
|
|
14798
15034
|
var import_crypto3 = require("crypto");
|
|
14799
15035
|
|
|
14800
15036
|
// src/plugins/FsPluginStore.ts
|
|
14801
|
-
var
|
|
14802
|
-
var
|
|
14803
|
-
var os4 = __toESM(require("os"));
|
|
15037
|
+
var fs8 = __toESM(require("fs"));
|
|
15038
|
+
var path16 = __toESM(require("path"));
|
|
14804
15039
|
|
|
14805
15040
|
// src/plugins/pluginNames.ts
|
|
14806
|
-
var
|
|
15041
|
+
var path15 = __toESM(require("path"));
|
|
14807
15042
|
var PLUGIN_NAME_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
14808
15043
|
var MAX_PLUGIN_NAME_LENGTH = 64;
|
|
14809
15044
|
function isPlainPluginName(name) {
|
|
@@ -14818,10 +15053,10 @@ function assertPlainPluginName(name) {
|
|
|
14818
15053
|
}
|
|
14819
15054
|
function resolvePluginInstallDir(pluginsDir, name) {
|
|
14820
15055
|
assertPlainPluginName(name);
|
|
14821
|
-
const root =
|
|
14822
|
-
const dest =
|
|
14823
|
-
const relative3 =
|
|
14824
|
-
if (relative3 !== name || relative3 === "" ||
|
|
15056
|
+
const root = path15.resolve(pluginsDir);
|
|
15057
|
+
const dest = path15.resolve(root, name);
|
|
15058
|
+
const relative3 = path15.relative(root, dest);
|
|
15059
|
+
if (relative3 !== name || relative3 === "" || path15.isAbsolute(relative3)) {
|
|
14825
15060
|
throw new Error(`Plugin install path escapes the plugins directory: ${JSON.stringify(name)}`);
|
|
14826
15061
|
}
|
|
14827
15062
|
return dest;
|
|
@@ -14836,13 +15071,13 @@ function isValidManifest(obj) {
|
|
|
14836
15071
|
|
|
14837
15072
|
// src/plugins/FsPluginStore.ts
|
|
14838
15073
|
function userPluginsDir() {
|
|
14839
|
-
return
|
|
15074
|
+
return path16.join(globalDataDir(), "plugins");
|
|
14840
15075
|
}
|
|
14841
15076
|
function loadManifestFromDir(dir) {
|
|
14842
|
-
const manifestPath =
|
|
14843
|
-
if (!
|
|
15077
|
+
const manifestPath = path16.join(dir, "manifest.json");
|
|
15078
|
+
if (!fs8.existsSync(manifestPath)) return null;
|
|
14844
15079
|
try {
|
|
14845
|
-
const raw =
|
|
15080
|
+
const raw = fs8.readFileSync(manifestPath, "utf8");
|
|
14846
15081
|
const parsed = JSON.parse(raw);
|
|
14847
15082
|
if (!isValidManifest(parsed)) return null;
|
|
14848
15083
|
return parsed;
|
|
@@ -14850,31 +15085,31 @@ function loadManifestFromDir(dir) {
|
|
|
14850
15085
|
return null;
|
|
14851
15086
|
}
|
|
14852
15087
|
}
|
|
14853
|
-
function
|
|
15088
|
+
function copyDirSync3(src, dest) {
|
|
14854
15089
|
ensureDir(dest);
|
|
14855
|
-
const entries =
|
|
15090
|
+
const entries = fs8.readdirSync(src, { withFileTypes: true });
|
|
14856
15091
|
for (const entry of entries) {
|
|
14857
|
-
const srcPath =
|
|
14858
|
-
const destPath =
|
|
15092
|
+
const srcPath = path16.join(src, entry.name);
|
|
15093
|
+
const destPath = path16.join(dest, entry.name);
|
|
14859
15094
|
if (entry.isDirectory()) {
|
|
14860
|
-
|
|
15095
|
+
copyDirSync3(srcPath, destPath);
|
|
14861
15096
|
} else {
|
|
14862
|
-
|
|
15097
|
+
fs8.copyFileSync(srcPath, destPath);
|
|
14863
15098
|
}
|
|
14864
15099
|
}
|
|
14865
15100
|
}
|
|
14866
15101
|
function rmDirSync(dir) {
|
|
14867
|
-
if (!
|
|
14868
|
-
const entries =
|
|
15102
|
+
if (!fs8.existsSync(dir)) return;
|
|
15103
|
+
const entries = fs8.readdirSync(dir, { withFileTypes: true });
|
|
14869
15104
|
for (const entry of entries) {
|
|
14870
|
-
const p =
|
|
15105
|
+
const p = path16.join(dir, entry.name);
|
|
14871
15106
|
if (entry.isDirectory()) {
|
|
14872
15107
|
rmDirSync(p);
|
|
14873
15108
|
} else {
|
|
14874
|
-
|
|
15109
|
+
fs8.unlinkSync(p);
|
|
14875
15110
|
}
|
|
14876
15111
|
}
|
|
14877
|
-
|
|
15112
|
+
fs8.rmdirSync(dir);
|
|
14878
15113
|
}
|
|
14879
15114
|
var FsPluginStore = class {
|
|
14880
15115
|
getUserPluginsDir() {
|
|
@@ -14882,10 +15117,10 @@ var FsPluginStore = class {
|
|
|
14882
15117
|
}
|
|
14883
15118
|
listUserPluginDirs() {
|
|
14884
15119
|
const dir = userPluginsDir();
|
|
14885
|
-
if (!
|
|
15120
|
+
if (!fs8.existsSync(dir)) return [];
|
|
14886
15121
|
let entries;
|
|
14887
15122
|
try {
|
|
14888
|
-
entries =
|
|
15123
|
+
entries = fs8.readdirSync(dir, { withFileTypes: true });
|
|
14889
15124
|
} catch {
|
|
14890
15125
|
return [];
|
|
14891
15126
|
}
|
|
@@ -14893,7 +15128,7 @@ var FsPluginStore = class {
|
|
|
14893
15128
|
}
|
|
14894
15129
|
listDir(dir) {
|
|
14895
15130
|
try {
|
|
14896
|
-
return
|
|
15131
|
+
return fs8.readdirSync(dir);
|
|
14897
15132
|
} catch {
|
|
14898
15133
|
return [];
|
|
14899
15134
|
}
|
|
@@ -14902,7 +15137,7 @@ var FsPluginStore = class {
|
|
|
14902
15137
|
return loadManifestFromDir(pluginDir);
|
|
14903
15138
|
}
|
|
14904
15139
|
copyDir(sourceDir, destDir) {
|
|
14905
|
-
|
|
15140
|
+
copyDirSync3(sourceDir, destDir);
|
|
14906
15141
|
}
|
|
14907
15142
|
removeDir(dir) {
|
|
14908
15143
|
rmDirSync(dir);
|
|
@@ -14911,20 +15146,20 @@ var FsPluginStore = class {
|
|
|
14911
15146
|
ensureDir(dir);
|
|
14912
15147
|
}
|
|
14913
15148
|
writeFile(filePath, content) {
|
|
14914
|
-
|
|
15149
|
+
fs8.writeFileSync(filePath, content);
|
|
14915
15150
|
}
|
|
14916
15151
|
readFile(filePath) {
|
|
14917
15152
|
try {
|
|
14918
|
-
return
|
|
15153
|
+
return fs8.readFileSync(filePath, "utf8");
|
|
14919
15154
|
} catch {
|
|
14920
15155
|
return null;
|
|
14921
15156
|
}
|
|
14922
15157
|
}
|
|
14923
|
-
dirExists(
|
|
14924
|
-
return
|
|
15158
|
+
dirExists(path20) {
|
|
15159
|
+
return fs8.existsSync(path20) && fs8.statSync(path20).isDirectory();
|
|
14925
15160
|
}
|
|
14926
|
-
exists(
|
|
14927
|
-
return
|
|
15161
|
+
exists(path20) {
|
|
15162
|
+
return fs8.existsSync(path20);
|
|
14928
15163
|
}
|
|
14929
15164
|
};
|
|
14930
15165
|
|
|
@@ -15351,7 +15586,7 @@ var RunnerRegistry = class {
|
|
|
15351
15586
|
return entry?.source === "builtin";
|
|
15352
15587
|
}
|
|
15353
15588
|
installFromPath(sourcePath) {
|
|
15354
|
-
const absPath =
|
|
15589
|
+
const absPath = path17.resolve(sourcePath);
|
|
15355
15590
|
if (!this.store.dirExists(absPath)) {
|
|
15356
15591
|
throw new Error(`Plugin source must be a directory: ${absPath}`);
|
|
15357
15592
|
}
|
|
@@ -15363,7 +15598,7 @@ var RunnerRegistry = class {
|
|
|
15363
15598
|
}
|
|
15364
15599
|
installFromGit(url) {
|
|
15365
15600
|
const validated = assertInstallablePluginUrl(url);
|
|
15366
|
-
const tmpDir =
|
|
15601
|
+
const tmpDir = path17.join(os4.tmpdir(), `ordewell-plugin-${(0, import_crypto3.randomBytes)(12).toString("hex")}`);
|
|
15367
15602
|
this.store.ensureDir(tmpDir);
|
|
15368
15603
|
try {
|
|
15369
15604
|
try {
|
|
@@ -15376,7 +15611,7 @@ var RunnerRegistry = class {
|
|
|
15376
15611
|
const hasManifest = contents.some((f) => f === "manifest.json");
|
|
15377
15612
|
let sourceDir = tmpDir;
|
|
15378
15613
|
if (!hasManifest) {
|
|
15379
|
-
const onlyEntry = contents.length === 1 ?
|
|
15614
|
+
const onlyEntry = contents.length === 1 ? path17.join(tmpDir, contents[0]) : void 0;
|
|
15380
15615
|
if (!onlyEntry || !this.store.dirExists(onlyEntry)) {
|
|
15381
15616
|
throw new Error(`No manifest.json found in repository: ${validated.toString()}`);
|
|
15382
15617
|
}
|
|
@@ -15424,7 +15659,7 @@ var RunnerRegistry = class {
|
|
|
15424
15659
|
}
|
|
15425
15660
|
createSkeleton(name, outputDir) {
|
|
15426
15661
|
assertPlainPluginName(name);
|
|
15427
|
-
const dir =
|
|
15662
|
+
const dir = path17.resolve(outputDir, name);
|
|
15428
15663
|
this.store.ensureDir(dir);
|
|
15429
15664
|
const manifest = {
|
|
15430
15665
|
name,
|
|
@@ -15456,8 +15691,8 @@ var RunnerRegistry = class {
|
|
|
15456
15691
|
]
|
|
15457
15692
|
}
|
|
15458
15693
|
};
|
|
15459
|
-
this.store.writeFile(
|
|
15460
|
-
const runSh =
|
|
15694
|
+
this.store.writeFile(path17.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
15695
|
+
const runSh = path17.join(dir, "run.sh");
|
|
15461
15696
|
const scriptContent = `#!/usr/bin/env bash
|
|
15462
15697
|
# Custom runner script for ${name}
|
|
15463
15698
|
# Receives:
|
|
@@ -15483,30 +15718,30 @@ echo "Running ${name} with prompt: $prompt"
|
|
|
15483
15718
|
};
|
|
15484
15719
|
function fsChmodSync(fpath, mode) {
|
|
15485
15720
|
try {
|
|
15486
|
-
|
|
15721
|
+
fs9.chmodSync(fpath, mode);
|
|
15487
15722
|
} catch {
|
|
15488
15723
|
}
|
|
15489
15724
|
}
|
|
15490
15725
|
|
|
15491
15726
|
// src/utils/stateStore.ts
|
|
15492
|
-
var
|
|
15493
|
-
var
|
|
15727
|
+
var fs10 = __toESM(require("fs"));
|
|
15728
|
+
var path18 = __toESM(require("path"));
|
|
15494
15729
|
var STATE_FILE = "state.json";
|
|
15495
15730
|
function getStatePath(baseDir) {
|
|
15496
|
-
return
|
|
15731
|
+
return path18.join(getStateDir(baseDir), STATE_FILE);
|
|
15497
15732
|
}
|
|
15498
15733
|
function saveState(plan, baseDir) {
|
|
15499
15734
|
const statePath = getStatePath(baseDir ?? process.cwd());
|
|
15500
|
-
ensureDir(
|
|
15735
|
+
ensureDir(path18.dirname(statePath));
|
|
15501
15736
|
plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
15502
|
-
|
|
15737
|
+
fs10.writeFileSync(statePath, JSON.stringify(plan, null, 2));
|
|
15503
15738
|
}
|
|
15504
15739
|
function loadState(baseDir, logger = defaultLogger) {
|
|
15505
15740
|
const dir = baseDir ?? process.cwd();
|
|
15506
15741
|
const statePath = getStatePath(dir);
|
|
15507
|
-
if (!
|
|
15742
|
+
if (!fs10.existsSync(statePath)) return null;
|
|
15508
15743
|
try {
|
|
15509
|
-
const raw =
|
|
15744
|
+
const raw = fs10.readFileSync(statePath, "utf-8");
|
|
15510
15745
|
const parsed = JSON.parse(raw);
|
|
15511
15746
|
if (!Array.isArray(parsed.runners)) {
|
|
15512
15747
|
logger.warn("stateStore", `rejecting state from ${statePath}: old format (scalar "runner" instead of "runners" array). Please re-plan.`);
|
|
@@ -15521,29 +15756,24 @@ function loadState(baseDir, logger = defaultLogger) {
|
|
|
15521
15756
|
function clearState(baseDir) {
|
|
15522
15757
|
const dir = baseDir ?? process.cwd();
|
|
15523
15758
|
const statePath = getStatePath(dir);
|
|
15524
|
-
if (
|
|
15759
|
+
if (fs10.existsSync(statePath)) fs10.unlinkSync(statePath);
|
|
15525
15760
|
}
|
|
15526
15761
|
function stateExists(baseDir) {
|
|
15527
15762
|
const dir = baseDir ?? process.cwd();
|
|
15528
|
-
return
|
|
15763
|
+
return fs10.existsSync(getStatePath(dir));
|
|
15529
15764
|
}
|
|
15530
15765
|
|
|
15531
15766
|
// src/services/SettingsService.ts
|
|
15532
|
-
var
|
|
15533
|
-
var
|
|
15534
|
-
var os6 = __toESM(require("os"));
|
|
15767
|
+
var fs11 = __toESM(require("fs"));
|
|
15768
|
+
var path19 = __toESM(require("path"));
|
|
15535
15769
|
var DEFAULTS = {
|
|
15536
|
-
grillMe: { enabled: false },
|
|
15537
15770
|
tdd: { enabled: true },
|
|
15538
|
-
|
|
15539
|
-
review: { enabled: false },
|
|
15540
|
-
verification: { enabled: false },
|
|
15541
|
-
researchSubagents: { enabled: false }
|
|
15771
|
+
verification: { enabled: false }
|
|
15542
15772
|
};
|
|
15543
15773
|
function getSettingsPath() {
|
|
15544
15774
|
const override = process.env.ORDEWELL_SETTINGS_PATH;
|
|
15545
15775
|
if (override && override.trim()) return override.trim();
|
|
15546
|
-
return
|
|
15776
|
+
return path19.join(globalDataDir(), "settings.json");
|
|
15547
15777
|
}
|
|
15548
15778
|
function readPlannerModels(raw) {
|
|
15549
15779
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
@@ -15576,43 +15806,19 @@ var SettingsService = class {
|
|
|
15576
15806
|
}
|
|
15577
15807
|
fileMtimeMs() {
|
|
15578
15808
|
try {
|
|
15579
|
-
return
|
|
15809
|
+
return fs11.statSync(this.filePath).mtimeMs;
|
|
15580
15810
|
} catch {
|
|
15581
15811
|
return null;
|
|
15582
15812
|
}
|
|
15583
15813
|
}
|
|
15584
|
-
getGrillMe() {
|
|
15585
|
-
return this.getAll().grillMe.enabled;
|
|
15586
|
-
}
|
|
15587
15814
|
getTdd() {
|
|
15588
15815
|
return this.getAll().tdd.enabled;
|
|
15589
15816
|
}
|
|
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
15817
|
setTdd(enabled) {
|
|
15599
15818
|
this.getAll();
|
|
15600
15819
|
this.cache.tdd.enabled = enabled;
|
|
15601
15820
|
this.persist();
|
|
15602
15821
|
}
|
|
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
15822
|
getVerification() {
|
|
15617
15823
|
return this.getAll().verification.enabled;
|
|
15618
15824
|
}
|
|
@@ -15621,14 +15827,6 @@ var SettingsService = class {
|
|
|
15621
15827
|
this.cache.verification.enabled = enabled;
|
|
15622
15828
|
this.persist();
|
|
15623
15829
|
}
|
|
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
15830
|
getModelAllowlist(runner) {
|
|
15633
15831
|
return this.getAll().modelAllowlist?.[runner];
|
|
15634
15832
|
}
|
|
@@ -15678,18 +15876,13 @@ var SettingsService = class {
|
|
|
15678
15876
|
this.persist();
|
|
15679
15877
|
}
|
|
15680
15878
|
load() {
|
|
15879
|
+
migrateOldConfigDir();
|
|
15681
15880
|
try {
|
|
15682
|
-
if (
|
|
15683
|
-
const raw = JSON.parse(
|
|
15881
|
+
if (fs11.existsSync(this.filePath)) {
|
|
15882
|
+
const raw = JSON.parse(fs11.readFileSync(this.filePath, "utf-8"));
|
|
15684
15883
|
const settings = {
|
|
15685
|
-
grillMe: { enabled: raw.grillMe?.enabled ?? DEFAULTS.grillMe.enabled },
|
|
15686
15884
|
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 }
|
|
15885
|
+
verification: { enabled: raw.verification?.enabled ?? DEFAULTS.verification.enabled }
|
|
15693
15886
|
};
|
|
15694
15887
|
if (raw.modelAllowlist !== void 0) {
|
|
15695
15888
|
settings.modelAllowlist = raw.modelAllowlist;
|
|
@@ -15705,14 +15898,14 @@ var SettingsService = class {
|
|
|
15705
15898
|
}
|
|
15706
15899
|
} catch {
|
|
15707
15900
|
}
|
|
15708
|
-
return { ...DEFAULTS,
|
|
15901
|
+
return { ...DEFAULTS, tdd: { ...DEFAULTS.tdd }, verification: { ...DEFAULTS.verification } };
|
|
15709
15902
|
}
|
|
15710
15903
|
persist() {
|
|
15711
|
-
const dir =
|
|
15712
|
-
if (!
|
|
15713
|
-
|
|
15904
|
+
const dir = path19.dirname(this.filePath);
|
|
15905
|
+
if (!fs11.existsSync(dir)) {
|
|
15906
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
15714
15907
|
}
|
|
15715
|
-
|
|
15908
|
+
fs11.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2));
|
|
15716
15909
|
this.cachedMtimeMs = this.fileMtimeMs();
|
|
15717
15910
|
}
|
|
15718
15911
|
};
|
|
@@ -15759,9 +15952,11 @@ var PlannerModelMemory = class {
|
|
|
15759
15952
|
AbstractRunner,
|
|
15760
15953
|
AbstractTerminalSession,
|
|
15761
15954
|
ApprovalPolicy,
|
|
15955
|
+
BUILTIN_SKILL_NAMES,
|
|
15762
15956
|
BaseAiService,
|
|
15763
15957
|
BaseConfig,
|
|
15764
15958
|
BaseFileSystem,
|
|
15959
|
+
CHECKPOINT_TRUNCATE_LENGTH,
|
|
15765
15960
|
CLAUDE_CODE_MANIFEST,
|
|
15766
15961
|
CLI_PROVIDERS,
|
|
15767
15962
|
CMD_EXE_MAX_COMMAND_LINE,
|
|
@@ -15817,6 +16012,7 @@ var PlannerModelMemory = class {
|
|
|
15817
16012
|
SYMBOL_LANGUAGES,
|
|
15818
16013
|
Session,
|
|
15819
16014
|
SettingsService,
|
|
16015
|
+
SkillsService,
|
|
15820
16016
|
StdioAgentAdapter,
|
|
15821
16017
|
TASK_OPS_ENVELOPE_KEY,
|
|
15822
16018
|
TASK_QUERY_ANSWER_OR_OPS,
|
|
@@ -15882,6 +16078,7 @@ var PlannerModelMemory = class {
|
|
|
15882
16078
|
configuredProviders,
|
|
15883
16079
|
createAiService,
|
|
15884
16080
|
createEmptyPlan,
|
|
16081
|
+
createSkillsService,
|
|
15885
16082
|
createTask,
|
|
15886
16083
|
daemonTokenPath,
|
|
15887
16084
|
defaultLogger,
|
|
@@ -15908,12 +16105,14 @@ var PlannerModelMemory = class {
|
|
|
15908
16105
|
filterModelsForPrompt,
|
|
15909
16106
|
filteredBuildModes,
|
|
15910
16107
|
flattenTasks,
|
|
16108
|
+
flattenTasksWithParents,
|
|
15911
16109
|
formatSearchOutput,
|
|
15912
16110
|
generatePlanWithRepair,
|
|
15913
16111
|
getLatestSession,
|
|
15914
16112
|
getProviderMeta,
|
|
15915
16113
|
getSettingsPath,
|
|
15916
16114
|
getStateDir,
|
|
16115
|
+
globalDataDir,
|
|
15917
16116
|
grantScopeFor,
|
|
15918
16117
|
hasTmux,
|
|
15919
16118
|
includeGlobFor,
|
|
@@ -15933,6 +16132,7 @@ var PlannerModelMemory = class {
|
|
|
15933
16132
|
looksLikePlanAttempt,
|
|
15934
16133
|
mapAgentTool,
|
|
15935
16134
|
migrateLegacyPlan,
|
|
16135
|
+
migrateOldConfigDir,
|
|
15936
16136
|
migratePlanState,
|
|
15937
16137
|
migrateTask,
|
|
15938
16138
|
mintDaemonToken,
|
|
@@ -15967,10 +16167,12 @@ var PlannerModelMemory = class {
|
|
|
15967
16167
|
resolveArgs,
|
|
15968
16168
|
resolveDefaultMode,
|
|
15969
16169
|
resolveModelShortcut,
|
|
16170
|
+
resolveOrderLabel,
|
|
15970
16171
|
resolvePluginInstallDir,
|
|
15971
16172
|
resolveProvider,
|
|
15972
16173
|
resolveProviderFromPrefix,
|
|
15973
16174
|
resolveResearchShell,
|
|
16175
|
+
resolveSkillInvocation,
|
|
15974
16176
|
resolveTaskMode,
|
|
15975
16177
|
resolveWithin,
|
|
15976
16178
|
retargetTaskRunner,
|
|
@@ -15993,6 +16195,7 @@ var PlannerModelMemory = class {
|
|
|
15993
16195
|
summarizeOutput,
|
|
15994
16196
|
summarizeToolCall,
|
|
15995
16197
|
taskOpsRejectedPrompt,
|
|
16198
|
+
taskOrderLabel,
|
|
15996
16199
|
taskQuerySignature,
|
|
15997
16200
|
textHasTaskOps,
|
|
15998
16201
|
textHasTaskQuery,
|
|
@@ -16002,6 +16205,7 @@ var PlannerModelMemory = class {
|
|
|
16002
16205
|
toOrchestratorOptions,
|
|
16003
16206
|
tokenSubprotocols,
|
|
16004
16207
|
tokensMatch,
|
|
16208
|
+
truncateCheckpointSummary,
|
|
16005
16209
|
truncatedPlanReEmitPrompt,
|
|
16006
16210
|
updateTaskInPlan,
|
|
16007
16211
|
validateModifiedPlan,
|