topchester-ai 0.63.0 → 0.64.0

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/bin.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as runTopchesterCli } from "./cli-fySUzvp-.mjs";
2
+ import { t as runTopchesterCli } from "./cli-CnI4QA2l.mjs";
3
3
  //#region src/bin.ts
4
4
  await runTopchesterCli();
5
5
  //#endregion
@@ -4083,7 +4083,7 @@ function truncateText(text, width) {
4083
4083
  const planTodoTool = defineTool({
4084
4084
  name: "plan_todo",
4085
4085
  description: "Replace the visible session task plan for multi-step work.",
4086
- prompt: "plan_todo: replace the visible session task plan for non-trivial multi-step work; keep 2-6 short items, exactly one in_progress item while work remains, and use [] only to clear. Do not use plan_todo just to report completed work before a final answer. To use it, reply with only JSON: {\"tool\":\"plan_todo\",\"args\":{\"items\":[{\"text\":\"Inspect relevant files\",\"status\":\"in_progress\"},{\"text\":\"Implement focused change\",\"status\":\"pending\"}]}}",
4086
+ prompt: "plan_todo: replace the visible session task plan for genuinely multi-step work. Usually create it once after initial orientation, keep 2-6 short milestone items, exactly one in_progress item while work remains, and batch updates when milestones change. Do not call plan_todo twice in a row, after routine reads/searches, after failed edit attempts, for wording-only changes, or just to report completed work before a final answer. To use it, reply with only JSON: {\"tool\":\"plan_todo\",\"args\":{\"items\":[{\"text\":\"Inspect relevant files\",\"status\":\"in_progress\"},{\"text\":\"Implement focused change\",\"status\":\"pending\"}]}}",
4087
4087
  argsSchema: planTodoArgsSchema,
4088
4088
  async execute(context, args) {
4089
4089
  if (!context.taskPlan) throw new Error("plan_todo requires runtime task-plan state.");
@@ -12390,8 +12390,10 @@ function getChatSystemPrompt(options = {}) {
12390
12390
  "- Do not claim to have read, created, edited, staged, committed, or run anything unless a tool result in this turn confirms it.",
12391
12391
  "- If the task requires implementation, do not finish with a prose summary before a successful source-editing tool result. A summary of intended changes is not a substitute for editing files.",
12392
12392
  ...canUseTool("plan_todo") ? [
12393
- "- Use plan_todo for non-trivial multi-step work before the first substantive repository tool call.",
12394
- "- Keep plan_todo items short, user-safe, and usually 2 to 6 items. Maintain exactly one in_progress item while work remains, update it after major progress changes, and clear it only when abandoning the plan or when no visible plan is useful.",
12393
+ "- Use plan_todo for genuinely multi-step work when a visible checklist helps; for small or straightforward tasks, do the work directly.",
12394
+ "- If you use plan_todo, usually create it once after initial orientation. Keep items short, user-safe, and usually 2 to 6 items with exactly one in_progress item while work remains.",
12395
+ "- Batch plan_todo updates. Update only when a milestone completes, scope changes, or the next major phase starts; do not update after routine reads, searches, failed edit attempts, or wording-only changes.",
12396
+ "- Never call plan_todo twice in a row.",
12395
12397
  "- Do not use plan_todo for simple one-step answers, tiny reads, or trivial edits.",
12396
12398
  "- Do not call plan_todo only to summarize completed work before a final answer. If no visible plan is active and the work is done, answer directly."
12397
12399
  ] : [],
@@ -12797,17 +12799,18 @@ function formatToolResultForPrompt(result) {
12797
12799
  }
12798
12800
  /**
12799
12801
  * Builds the follow-up instruction appended after each tool result. It keeps
12800
- * the model on the active task, reminds it to maintain the visible plan, and
12801
- * restates the current tool-call protocol so the next model step remains
12802
- * parseable by the runtime.
12802
+ * the model on the active task, lightly references the visible plan when one
12803
+ * is active, and restates the current tool-call protocol so the next model
12804
+ * step remains parseable by the runtime.
12803
12805
  */
12804
- function formatContinuationInstruction(protocol, result, canUsePlanTodo = true) {
12806
+ function formatContinuationInstruction(protocol, result, canUsePlanTodo = true, planTodoMode = "normal") {
12805
12807
  const toolInstruction = protocol === "text-xml" ? "If another tool is needed, reply with only one XML tool call." : protocol === "text-json" ? "If another tool is needed, reply with only that tool JSON." : "If another tool is needed, use the available tool calling path.";
12806
12808
  return [
12807
12809
  "Continue the user's request using the tool result above and the visible plan when one is active.",
12808
12810
  result.tool === "find_file" ? "find_file results are paths only; if the user asked to read or answer from file contents, call read_file on the relevant path before answering. Do not ask the user to provide the read_file result or permission." : "",
12809
- canUsePlanTodo ? "Update plan_todo after major progress changes." : "",
12810
- canUsePlanTodo ? "Before a final answer, close the visible plan by calling plan_todo with all finished items marked completed, or with [] if abandoning the plan." : "",
12811
+ canUsePlanTodo && planTodoMode === "compact" ? "Compact plan mode is active: do not update plan_todo unless task scope materially changes; avoid final plan-closure calls." : "",
12812
+ canUsePlanTodo && planTodoMode === "normal" ? "Use plan_todo sparingly: batch updates only when a milestone completes, scope changes, or the next major phase starts." : "",
12813
+ canUsePlanTodo && planTodoMode === "normal" ? "Before a final answer, close the visible plan by calling plan_todo with all finished items marked completed, or with [] if abandoning the plan." : "",
12811
12814
  toolInstruction,
12812
12815
  "Otherwise answer the user. Do not guess."
12813
12816
  ].filter(Boolean).join(" ");
@@ -13146,7 +13149,11 @@ function isCompletedPlanTodoItem(item) {
13146
13149
  }
13147
13150
  //#endregion
13148
13151
  //#region src/agent/runtime/index.ts
13149
- const MAX_TOOL_CALLS_PER_TURN = 75;
13152
+ const DEFAULT_MAX_TOOL_CALLS_PER_TURN = 75;
13153
+ const MAX_TOOL_CALLS_PER_TURN_ENV = "TOPCHESTER_MAX_TOOL_CALLS_PER_TURN";
13154
+ const PLAN_TODO_MODE_ENV = "TOPCHESTER_PLAN_TODO_MODE";
13155
+ const MAX_PLAN_TODO_UPDATES_PER_TURN_ENV = "TOPCHESTER_MAX_PLAN_TODO_UPDATES_PER_TURN";
13156
+ const DEFAULT_COMPACT_MAX_PLAN_TODO_UPDATES_PER_TURN = 3;
13150
13157
  const DEFAULT_TASK_CONCURRENCY = 3;
13151
13158
  var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13152
13159
  context;
@@ -13274,11 +13281,15 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13274
13281
  let invalidToolCallRepairs = 0;
13275
13282
  let noEditCompletionRepairs = 0;
13276
13283
  let hasSourceMutation = false;
13284
+ const maxToolCallsPerTurn = readMaxToolCallsPerTurn();
13285
+ const planTodoMode = readPlanTodoMode();
13286
+ const maxPlanTodoUpdatesPerTurn = readMaxPlanTodoUpdatesPerTurn(planTodoMode);
13287
+ let planTodoUpdates = 0;
13277
13288
  const implementationTask = isImplementationTaskRequest(message);
13278
13289
  const projectInstructionToolState = { shownSourceKeys: /* @__PURE__ */ new Set() };
13279
13290
  const persistedProjectInstructionKeys = /* @__PURE__ */ new Set();
13280
13291
  try {
13281
- for (let toolCalls = 0; toolCalls <= MAX_TOOL_CALLS_PER_TURN; toolCalls += 1) {
13292
+ for (let toolCalls = 0; toolCalls <= maxToolCallsPerTurn; toolCalls += 1) {
13282
13293
  const startedAt = Date.now();
13283
13294
  const projectInstructions = await this.resolveBaseProjectInstructions();
13284
13295
  for (const event of createInstructionContextEventsFromProjectInstructions(projectInstructions, persistedProjectInstructionKeys)) yield event;
@@ -13385,7 +13396,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13385
13396
  yield agentEvent.status("ready");
13386
13397
  return;
13387
13398
  }
13388
- if (hasOpenTaskPlan(plan)) {
13399
+ if (planTodoMode === "normal" && hasOpenTaskPlan(plan)) {
13389
13400
  if (!requestedPlanClosure) {
13390
13401
  requestedPlanClosure = true;
13391
13402
  nextPrompt = `${nextPrompt}\n\n${formatOpenPlanClosureInstruction(finalText, result.toolProtocol)}`;
@@ -13399,11 +13410,11 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13399
13410
  yield agentEvent.status("ready");
13400
13411
  return;
13401
13412
  }
13402
- if (toolCalls === MAX_TOOL_CALLS_PER_TURN) {
13413
+ if (toolCalls === maxToolCallsPerTurn) {
13403
13414
  yield agentEvent.choice({
13404
13415
  tone: "warning",
13405
13416
  title: "Tool call limit reached",
13406
- body: `Stopped after ${MAX_TOOL_CALLS_PER_TURN} tool calls in one turn. Continue starts another turn; abort leaves the call stopped.`,
13417
+ body: `Stopped after ${maxToolCallsPerTurn} tool calls in one turn. Continue starts another turn; abort leaves the call stopped.`,
13407
13418
  actions: [choiceAction("Continue", "Continue the previous task from where you stopped."), choiceAction("Abort", ABORT_CHOICE_VALUE)]
13408
13419
  });
13409
13420
  yield agentEvent.status("ready");
@@ -13481,7 +13492,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13481
13492
  }
13482
13493
  }
13483
13494
  afterTool = "task";
13484
- nextPrompt = this.appendRuntimeSteeringToContinuationPrompt(this.appendHookContextsToPrompt(`${nextPrompt}\n\n${taskResults.map((toolResult) => formatToolResultForPrompt(toolResult)).join("\n\n")}\n\n${formatContinuationInstruction(result.toolProtocol, taskResults.at(-1), isToolAllowed(permissions, "plan_todo"))}`, "PostToolUse", postHookContexts), options.steering);
13495
+ nextPrompt = this.appendRuntimeSteeringToContinuationPrompt(this.appendHookContextsToPrompt(`${nextPrompt}\n\n${taskResults.map((toolResult) => formatToolResultForPrompt(toolResult)).join("\n\n")}\n\n${formatContinuationInstruction(result.toolProtocol, taskResults.at(-1), isToolAllowed(permissions, "plan_todo"), planTodoMode)}`, "PostToolUse", postHookContexts), options.steering);
13485
13496
  continue;
13486
13497
  }
13487
13498
  if (modelToolCalls.length > 1 && modelToolCalls.every((call) => toolCatalog.isParallelSafe(call.tool))) {
@@ -13545,7 +13556,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13545
13556
  }
13546
13557
  }
13547
13558
  afterTool = parallelCalls.at(-1)?.tool;
13548
- nextPrompt = this.appendRuntimeSteeringToContinuationPrompt(this.appendHookContextsToPrompt(`${nextPrompt}\n\n${parallelResults.map((toolResult) => formatToolResultForPrompt(toolResult)).join("\n\n")}\n\n${formatContinuationInstruction(result.toolProtocol, parallelResults.at(-1), isToolAllowed(permissions, "plan_todo"))}`, "PostToolUse", postHookContexts), options.steering);
13559
+ nextPrompt = this.appendRuntimeSteeringToContinuationPrompt(this.appendHookContextsToPrompt(`${nextPrompt}\n\n${parallelResults.map((toolResult) => formatToolResultForPrompt(toolResult)).join("\n\n")}\n\n${formatContinuationInstruction(result.toolProtocol, parallelResults.at(-1), isToolAllowed(permissions, "plan_todo"), planTodoMode)}`, "PostToolUse", postHookContexts), options.steering);
13549
13560
  continue;
13550
13561
  }
13551
13562
  const executableToolCall = toolCall;
@@ -13557,47 +13568,55 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13557
13568
  yield agentEvent.status("ready");
13558
13569
  return;
13559
13570
  }
13560
- const preHookRun = this.startPreToolUseHook(executableToolCall, toolCall.id, session, abortSignal);
13561
- for await (const event of preHookRun.statusEvents) yield event;
13562
- const preHook = await preHookRun.result;
13563
- for (const event of this.hookResultToEvents(preHook)) yield event;
13564
13571
  let toolResult;
13565
- if (preHook.stopped) {
13566
- if (preHook.messages.length === 0) yield agentEvent.systemMessage(preHook.stopped.message);
13567
- yield agentEvent.status("ready");
13568
- return;
13569
- }
13570
- if (preHook.blocked) toolResult = createToolErrorResult(executableToolCall.tool, preHook.blocked.message);
13572
+ const planTodoRejection = validatePlanTodoCall(executableToolCall, this.taskPlan.get(), {
13573
+ afterTool,
13574
+ planTodoUpdates,
13575
+ maxPlanTodoUpdatesPerTurn
13576
+ });
13577
+ if (planTodoRejection) toolResult = createToolErrorResult(executableToolCall.tool, planTodoRejection);
13571
13578
  else {
13572
- const approval = await this.resolveBashApproval(executableToolCall, toolCall.id, options, session, abortSignal);
13573
- for (const event of approval.events) yield event;
13574
- if (approval.cancelled && approval.stopped) {
13575
- if (!approval.events.some((event) => event.type === "message" && event.text === approval.reason)) yield agentEvent.systemMessage(approval.reason);
13579
+ const preHookRun = this.startPreToolUseHook(executableToolCall, toolCall.id, session, abortSignal);
13580
+ for await (const event of preHookRun.statusEvents) yield event;
13581
+ const preHook = await preHookRun.result;
13582
+ for (const event of this.hookResultToEvents(preHook)) yield event;
13583
+ if (preHook.stopped) {
13584
+ if (preHook.messages.length === 0) yield agentEvent.systemMessage(preHook.stopped.message);
13576
13585
  yield agentEvent.status("ready");
13577
13586
  return;
13578
13587
  }
13579
- if (approval.cancelled) toolResult = createToolErrorResult(executableToolCall.tool, approval.reason);
13588
+ if (preHook.blocked) toolResult = createToolErrorResult(executableToolCall.tool, preHook.blocked.message);
13580
13589
  else {
13581
- const toolEventQueue = createRuntimeEventQueue();
13582
- const toolResultPromise = executeToolCall(this.context.workspaceRoot, executableToolCall, {
13583
- logger: this.context.logger,
13584
- config: this.context.config,
13585
- bashApprovals: { allowExactCommands: approval.approvedCommands },
13586
- taskPlan: this.taskPlan,
13587
- profile,
13588
- permissions,
13589
- subagents,
13590
- projectInstructions: projectInstructionToolState,
13591
- currentUserMessage: message,
13592
- abortSignal,
13593
- toolCallId: toolCall.id,
13594
- toolCatalog,
13595
- eventSink: (event) => toolEventQueue.push(event)
13596
- }).finally(() => {
13597
- toolEventQueue.close();
13598
- });
13599
- for await (const event of toolEventQueue) yield event;
13600
- toolResult = await toolResultPromise;
13590
+ const approval = await this.resolveBashApproval(executableToolCall, toolCall.id, options, session, abortSignal);
13591
+ for (const event of approval.events) yield event;
13592
+ if (approval.cancelled && approval.stopped) {
13593
+ if (!approval.events.some((event) => event.type === "message" && event.text === approval.reason)) yield agentEvent.systemMessage(approval.reason);
13594
+ yield agentEvent.status("ready");
13595
+ return;
13596
+ }
13597
+ if (approval.cancelled) toolResult = createToolErrorResult(executableToolCall.tool, approval.reason);
13598
+ else {
13599
+ const toolEventQueue = createRuntimeEventQueue();
13600
+ const toolResultPromise = executeToolCall(this.context.workspaceRoot, executableToolCall, {
13601
+ logger: this.context.logger,
13602
+ config: this.context.config,
13603
+ bashApprovals: { allowExactCommands: approval.approvedCommands },
13604
+ taskPlan: this.taskPlan,
13605
+ profile,
13606
+ permissions,
13607
+ subagents,
13608
+ projectInstructions: projectInstructionToolState,
13609
+ currentUserMessage: message,
13610
+ abortSignal,
13611
+ toolCallId: toolCall.id,
13612
+ toolCatalog,
13613
+ eventSink: (event) => toolEventQueue.push(event)
13614
+ }).finally(() => {
13615
+ toolEventQueue.close();
13616
+ });
13617
+ for await (const event of toolEventQueue) yield event;
13618
+ toolResult = await toolResultPromise;
13619
+ }
13601
13620
  }
13602
13621
  }
13603
13622
  for (const event of createInstructionContextEventsFromToolResult(toolResult, persistedProjectInstructionKeys)) yield event;
@@ -13605,14 +13624,17 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13605
13624
  const finishError = validateFinishTaskResult(toolResult, {
13606
13625
  implementationTask,
13607
13626
  hasSourceMutation,
13608
- hasOpenPlan: hasOpenTaskPlan(this.taskPlan.get()),
13627
+ hasOpenPlan: planTodoMode === "normal" && hasOpenTaskPlan(this.taskPlan.get()),
13609
13628
  canUseSourceEditTool: canStillUseSourceEditTool(permissions)
13610
13629
  });
13611
13630
  if (finishError) toolResult = createToolErrorResult(executableToolCall.tool, finishError);
13612
13631
  }
13613
13632
  if (isSourceMutationResult(toolResult)) hasSourceMutation = true;
13614
13633
  yield agentEvent.toolCall(executableToolCall, formatToolCallMessage(executableToolCall, toolResult), getToolCallDisplayDiff(toolResult));
13615
- if (!isToolErrorResult(toolResult) && toolResult.tool === "plan_todo") yield agentEvent.taskPlan(toolResult.plan);
13634
+ if (!isToolErrorResult(toolResult) && toolResult.tool === "plan_todo") {
13635
+ planTodoUpdates += 1;
13636
+ yield agentEvent.taskPlan(toolResult.plan);
13637
+ }
13616
13638
  const postHookRun = this.startPostToolUseHook(executableToolCall, toolCall.id, toolResult, session, abortSignal);
13617
13639
  for await (const event of postHookRun.statusEvents) yield event;
13618
13640
  const postHook = await postHookRun.result;
@@ -13630,7 +13652,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13630
13652
  return;
13631
13653
  }
13632
13654
  afterTool = executableToolCall.tool;
13633
- nextPrompt = this.appendRuntimeSteeringToContinuationPrompt(this.appendHookContextsToPrompt(`${nextPrompt}\n\n${formatToolResultForPrompt(toolResult)}\n\n${formatContinuationInstruction(result.toolProtocol, toolResult, isToolAllowed(permissions, "plan_todo"))}`, "PostToolUse", postHook.contexts), options.steering);
13655
+ nextPrompt = this.appendRuntimeSteeringToContinuationPrompt(this.appendHookContextsToPrompt(`${nextPrompt}\n\n${formatToolResultForPrompt(toolResult)}\n\n${formatContinuationInstruction(result.toolProtocol, toolResult, isToolAllowed(permissions, "plan_todo"), planTodoMode)}`, "PostToolUse", postHook.contexts), options.steering);
13634
13656
  }
13635
13657
  const finalMessage = "I stopped because the tool loop ended unexpectedly.";
13636
13658
  yield agentEvent.assistantMessage(finalMessage, formatAgentMessageMeta(lastModelId, totalDurationMs, tokenUsageTotals));
@@ -14014,6 +14036,46 @@ function getToolCallDisplayDiff(result) {
14014
14036
  if (result.tool === "edit_file" && !isToolErrorResult(result) && "diff" in result) return result.diff;
14015
14037
  if (result.tool === "apply_patch" && !isToolErrorResult(result) && result.diffs.length > 0) return result.diffs.join("\n");
14016
14038
  }
14039
+ function readMaxToolCallsPerTurn() {
14040
+ const raw = process.env[MAX_TOOL_CALLS_PER_TURN_ENV]?.trim();
14041
+ if (!raw) return DEFAULT_MAX_TOOL_CALLS_PER_TURN;
14042
+ const parsed = Number(raw);
14043
+ if (!Number.isInteger(parsed) || parsed <= 0) return DEFAULT_MAX_TOOL_CALLS_PER_TURN;
14044
+ return parsed;
14045
+ }
14046
+ function readPlanTodoMode() {
14047
+ return process.env[PLAN_TODO_MODE_ENV]?.trim().toLowerCase() === "compact" ? "compact" : "normal";
14048
+ }
14049
+ function readMaxPlanTodoUpdatesPerTurn(mode) {
14050
+ const raw = process.env[MAX_PLAN_TODO_UPDATES_PER_TURN_ENV]?.trim();
14051
+ if (!raw) return mode === "compact" ? DEFAULT_COMPACT_MAX_PLAN_TODO_UPDATES_PER_TURN : void 0;
14052
+ const parsed = Number(raw);
14053
+ if (!Number.isInteger(parsed) || parsed < 0) return mode === "compact" ? DEFAULT_COMPACT_MAX_PLAN_TODO_UPDATES_PER_TURN : void 0;
14054
+ return parsed;
14055
+ }
14056
+ function validatePlanTodoCall(call, currentPlan, state) {
14057
+ if (call.tool !== "plan_todo") return;
14058
+ const allCompleted = isAllCompletedPlanTodoCall(call.args);
14059
+ if (state.afterTool === "plan_todo" && !allCompleted) return "plan_todo rejected because the previous tool call was also plan_todo. Batch checklist updates and proceed with repository work.";
14060
+ if (state.maxPlanTodoUpdatesPerTurn !== void 0 && state.planTodoUpdates >= state.maxPlanTodoUpdatesPerTurn) return `plan_todo rejected because this turn already used ${state.planTodoUpdates} plan update(s), which meets the configured limit of ${state.maxPlanTodoUpdatesPerTurn}. Continue with repository work and summarize progress in the final response.`;
14061
+ if (isSamePlanTodoItems(call.args, currentPlan) && !allCompleted) return "plan_todo rejected because it does not change the visible plan. Continue with the next substantive tool call or final response.";
14062
+ }
14063
+ function isAllCompletedPlanTodoCall(args) {
14064
+ const items = args.items;
14065
+ return Array.isArray(items) && items.length > 0 && items.every((item) => {
14066
+ return Boolean(item && typeof item === "object" && "status" in item && item.status === "completed");
14067
+ });
14068
+ }
14069
+ function isSamePlanTodoItems(args, currentPlan) {
14070
+ const items = args.items;
14071
+ if (!Array.isArray(items) || items.length !== currentPlan.items.length) return false;
14072
+ return items.every((item, index) => {
14073
+ const current = currentPlan.items[index];
14074
+ if (!current || typeof item !== "object" || item === null) return false;
14075
+ const candidate = item;
14076
+ return candidate.text === current.text && candidate.status === current.status;
14077
+ });
14078
+ }
14017
14079
  function isImplementationTaskRequest(message) {
14018
14080
  return /\b(implement|complete|fix|add|update|change|modify|refactor|write|create|wire|hook|support|benchmark|task end-to-end)\b/iu.test(message);
14019
14081
  }
@@ -16431,4 +16493,4 @@ function formatDryRunSyncStatus(status) {
16431
16493
  //#endregion
16432
16494
  export { runTopchesterCli as t };
16433
16495
 
16434
- //# sourceMappingURL=cli-fySUzvp-.mjs.map
16496
+ //# sourceMappingURL=cli-CnI4QA2l.mjs.map