topchester-ai 0.64.0 → 0.66.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-CnI4QA2l.mjs";
2
+ import { t as runTopchesterCli } from "./cli-BS67FTk8.mjs";
3
3
  //#region src/bin.ts
4
4
  await runTopchesterCli();
5
5
  //#endregion
@@ -21,6 +21,13 @@ import { diffWords } from "diff";
21
21
  import { highlight, supportsLanguage } from "cli-highlight";
22
22
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
23
23
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
24
+ //#region src/agent/benchmark-profile.ts
25
+ const BENCHMARK_PROFILES = ["terminal-bench"];
26
+ function parseBenchmarkProfile(value) {
27
+ if (BENCHMARK_PROFILES.includes(value)) return value;
28
+ throw new Error(`Unsupported benchmark profile '${value}'. Supported profiles: ${BENCHMARK_PROFILES.join(", ")}.`);
29
+ }
30
+ //#endregion
24
31
  //#region src/auth/codex.ts
25
32
  const CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
26
33
  const CODEX_ISSUER = "https://auth.openai.com";
@@ -638,9 +645,10 @@ async function validateBashPolicy(args, context) {
638
645
  if (!cwd.allowed) return reject(command, cwd.reason, false);
639
646
  const deniedRule = findMatchingBashRule(command, context.permissions?.deny ?? []);
640
647
  if (deniedRule) return reject(command, `bash policy rejected '${command}' because it matches deny rule '${deniedRule}'.`, false);
648
+ const shell = resolveBashShell(context.permissions?.shell);
649
+ if (context.benchmarkProfile === "terminal-bench") return allow(command, cwd.path, realWorkspaceRoot, shell, "benchmark_terminal", "terminal-bench");
641
650
  const destructive = getDestructiveReason(command);
642
651
  if (destructive) return reject(command, `bash policy rejected '${command}' because it looks destructive: ${destructive}.`, false);
643
- const shell = resolveBashShell(context.permissions?.shell);
644
652
  const allowedExactRule = findExactBashRule(command, context.permissions?.allowExact ?? []);
645
653
  if (allowedExactRule) return allow(command, cwd.path, realWorkspaceRoot, shell, "allow_exact", allowedExactRule);
646
654
  const approvedRule = findExactBashRule(command, context.approvedCommands ?? []);
@@ -668,7 +676,7 @@ function getBashApprovalCandidates(command) {
668
676
  };
669
677
  }
670
678
  function allow(command, cwd, workspaceRoot, shell, kind, matchedRule) {
671
- const reason = kind === "allow_exact" ? `bash exact command allowed by '${matchedRule}'` : kind === "allow_prefix" ? `bash command allowed by prefix '${matchedRule}'` : `bash exact command approved by '${matchedRule}'`;
679
+ const reason = kind === "allow_exact" ? `bash exact command allowed by '${matchedRule}'` : kind === "benchmark_terminal" ? `bash command allowed by benchmark profile '${matchedRule}'` : kind === "allow_prefix" ? `bash command allowed by prefix '${matchedRule}'` : `bash exact command approved by '${matchedRule}'`;
672
680
  return {
673
681
  allowed: true,
674
682
  reason,
@@ -758,14 +766,16 @@ const bashTool = defineTool({
758
766
  pathEnv: context.pathEnv,
759
767
  abortSignal: context.abortSignal,
760
768
  permissions: context.config?.tools?.bash,
761
- approvedCommands: context.bashApprovals?.allowExactCommands
769
+ approvedCommands: context.bashApprovals?.allowExactCommands,
770
+ benchmarkProfile: context.benchmarkProfile
762
771
  })
763
772
  });
764
773
  async function runBashCommand(workspaceRoot, args, options = {}) {
765
774
  const decision = await validateBashPolicy(args, {
766
775
  workspaceRoot,
767
776
  permissions: options.permissions,
768
- approvedCommands: options.approvedCommands
777
+ approvedCommands: options.approvedCommands,
778
+ benchmarkProfile: options.benchmarkProfile
769
779
  });
770
780
  if (!decision.allowed) throw new Error(decision.reason);
771
781
  const result = await runProcess({
@@ -1843,7 +1853,7 @@ async function deleteWorkspaceFile(workspaceRoot, path) {
1843
1853
  const finishTaskTool = defineTool({
1844
1854
  name: "finish_task",
1845
1855
  description: "Finish the current task only after the requested work has actually been completed with tools. Do not use this to claim edits, file reads, or validation that did not happen.",
1846
- prompt: "finish_task: complete the task with a brief final response only after tool results prove the work is done. For implementation tasks, do not call finish_task until source files were changed by edit_file, write_file, apply_patch, or another mutating tool, unless no code change is truly required. Example: {\"tool\":\"finish_task\",\"args\":{\"final_response\":\"Changed src/foo.ts and ran pnpm test foo.test.ts.\",\"files_changed\":[\"src/foo.ts\"],\"validation\":[\"pnpm test foo.test.ts\"],\"remaining_issues\":[]}}",
1856
+ prompt: "finish_task: complete the task with a brief final response only after tool results prove the work is done. In benchmark or require-finish mode, this is the only valid terminal action; normal assistant messages are progress notes and do not finish the task, and remaining_issues must be empty. For implementation tasks, do not call finish_task until source files were changed by edit_file, write_file, apply_patch, or another mutating tool, unless no code change is truly required. Example: {\"tool\":\"finish_task\",\"args\":{\"final_response\":\"Changed src/foo.ts and ran pnpm test foo.test.ts.\",\"files_changed\":[\"src/foo.ts\"],\"validation\":[\"pnpm test foo.test.ts\"],\"remaining_issues\":[]}}",
1847
1857
  argsSchema: z.object({
1848
1858
  final_response: z.string().describe("Brief final response for the user."),
1849
1859
  files_changed: z.array(z.string()).optional().describe("Workspace-relative files actually changed by successful edit tools."),
@@ -12054,6 +12064,7 @@ async function executeToolCall(workspaceRoot, call, options = {}) {
12054
12064
  subagents: options.subagents,
12055
12065
  projectInstructions: options.projectInstructions,
12056
12066
  currentUserMessage: options.currentUserMessage,
12067
+ benchmarkProfile: options.benchmarkProfile,
12057
12068
  eventSink: options.eventSink,
12058
12069
  abortSignal: options.abortSignal,
12059
12070
  toolCallId: options.toolCallId
@@ -12855,6 +12866,22 @@ function formatNoEditCompletionFailure(draftAnswer) {
12855
12866
  const trimmedDraft = draftAnswer.trim();
12856
12867
  return ["I could not complete the implementation because no successful source-file edit occurred in this turn.", trimmedDraft ? `The model tried to finish with this unsupported summary:\n${trimmedDraft}` : ""].filter(Boolean).join("\n");
12857
12868
  }
12869
+ function formatFinishTaskRequiredRepairInstruction(protocol, draftAnswer) {
12870
+ const toolInstruction = protocol === "text-xml" ? "Reply now with only one XML tool call for the next implementation or validation step, or call finish_task if the work is complete." : protocol === "text-json" ? "Reply now with only one tool JSON object for the next implementation or validation step, or call finish_task if the work is complete." : "Use the available tool calling path now for the next implementation or validation step, or call finish_task if the work is complete.";
12871
+ const trimmedDraft = draftAnswer.trim();
12872
+ return [
12873
+ "This run requires finish_task as the only terminal action.",
12874
+ "A normal assistant message is treated as a progress note, not completion.",
12875
+ "Do not summarize next steps in prose. Use tools to perform the next step.",
12876
+ "Call finish_task only when the requested work is complete and its files_changed, validation, and remaining_issues fields are accurate.",
12877
+ toolInstruction,
12878
+ trimmedDraft ? `Previous progress note that did not finish the task:\n${trimmedDraft}` : ""
12879
+ ].filter(Boolean).join("\n");
12880
+ }
12881
+ function formatFinishTaskRequiredFailure(draftAnswer) {
12882
+ const trimmedDraft = draftAnswer.trim();
12883
+ return ["I stopped because this run requires finish_task, but the model kept replying with normal assistant messages instead of using tools.", trimmedDraft ? `Last progress note:\n${trimmedDraft}` : ""].filter(Boolean).join("\n");
12884
+ }
12858
12885
  function getTextToolCallSources(protocol) {
12859
12886
  return protocol === "text-xml" ? ["text-xml"] : protocol === "text-json" ? ["text-json"] : ["text-json", "text-xml"];
12860
12887
  }
@@ -13153,7 +13180,9 @@ const DEFAULT_MAX_TOOL_CALLS_PER_TURN = 75;
13153
13180
  const MAX_TOOL_CALLS_PER_TURN_ENV = "TOPCHESTER_MAX_TOOL_CALLS_PER_TURN";
13154
13181
  const PLAN_TODO_MODE_ENV = "TOPCHESTER_PLAN_TODO_MODE";
13155
13182
  const MAX_PLAN_TODO_UPDATES_PER_TURN_ENV = "TOPCHESTER_MAX_PLAN_TODO_UPDATES_PER_TURN";
13183
+ const REQUIRE_FINISH_TASK_ENV = "TOPCHESTER_REQUIRE_FINISH_TASK";
13156
13184
  const DEFAULT_COMPACT_MAX_PLAN_TODO_UPDATES_PER_TURN = 3;
13185
+ const MAX_FINISH_TASK_REQUIRED_REPAIRS = 3;
13157
13186
  const DEFAULT_TASK_CONCURRENCY = 3;
13158
13187
  var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13159
13188
  context;
@@ -13280,12 +13309,14 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13280
13309
  let requestedPlanClosure = false;
13281
13310
  let invalidToolCallRepairs = 0;
13282
13311
  let noEditCompletionRepairs = 0;
13312
+ let finishTaskRequiredRepairs = 0;
13283
13313
  let hasSourceMutation = false;
13284
13314
  const maxToolCallsPerTurn = readMaxToolCallsPerTurn();
13285
13315
  const planTodoMode = readPlanTodoMode();
13286
13316
  const maxPlanTodoUpdatesPerTurn = readMaxPlanTodoUpdatesPerTurn(planTodoMode);
13287
13317
  let planTodoUpdates = 0;
13288
13318
  const implementationTask = isImplementationTaskRequest(message);
13319
+ const requireFinishTask = isFinishTaskRequiredByEnv() && isToolAllowed(permissions, "finish_task");
13289
13320
  const projectInstructionToolState = { shownSourceKeys: /* @__PURE__ */ new Set() };
13290
13321
  const persistedProjectInstructionKeys = /* @__PURE__ */ new Set();
13291
13322
  try {
@@ -13396,6 +13427,18 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13396
13427
  yield agentEvent.status("ready");
13397
13428
  return;
13398
13429
  }
13430
+ if (requireFinishTask) {
13431
+ if (finishTaskRequiredRepairs < MAX_FINISH_TASK_REQUIRED_REPAIRS) {
13432
+ finishTaskRequiredRepairs += 1;
13433
+ nextPrompt = `${nextPrompt}\n\n${formatFinishTaskRequiredRepairInstruction(result.toolProtocol, finalText)}`;
13434
+ continue;
13435
+ }
13436
+ const finalMessage = formatFinishTaskRequiredFailure(finalText);
13437
+ yield agentEvent.assistantMessage(finalMessage, formatAgentMessageMeta(result.modelId, totalDurationMs, tokenUsageTotals));
13438
+ for await (const event of this.streamStopHookEvents(session, finalMessage, "failed", abortSignal)) yield event;
13439
+ yield agentEvent.status("ready");
13440
+ return;
13441
+ }
13399
13442
  if (planTodoMode === "normal" && hasOpenTaskPlan(plan)) {
13400
13443
  if (!requestedPlanClosure) {
13401
13444
  requestedPlanClosure = true;
@@ -13460,6 +13503,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13460
13503
  subagents,
13461
13504
  projectInstructions: projectInstructionToolState,
13462
13505
  currentUserMessage: message,
13506
+ benchmarkProfile: options.benchmarkProfile,
13463
13507
  abortSignal,
13464
13508
  toolCallId: entry.toolCallId,
13465
13509
  toolCatalog,
@@ -13478,7 +13522,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13478
13522
  const call = taskCalls[index];
13479
13523
  const toolResult = taskResults[index];
13480
13524
  for (const event of createInstructionContextEventsFromToolResult(toolResult, persistedProjectInstructionKeys)) yield event;
13481
- if (isSourceMutationResult(toolResult)) hasSourceMutation = true;
13525
+ if (isSourceMutationResult(toolResult) || isBenchmarkMutationResult(toolResult, options.benchmarkProfile)) hasSourceMutation = true;
13482
13526
  yield agentEvent.toolCall(call, formatToolCallMessage(call, toolResult), getToolCallDisplayDiff(toolResult));
13483
13527
  const postHookRun = this.startPostToolUseHook(call, modelToolCalls[index]?.id, toolResult, session, abortSignal);
13484
13528
  for await (const event of postHookRun.statusEvents) yield event;
@@ -13530,6 +13574,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13530
13574
  subagents,
13531
13575
  projectInstructions: projectInstructionToolState,
13532
13576
  currentUserMessage: message,
13577
+ benchmarkProfile: options.benchmarkProfile,
13533
13578
  abortSignal,
13534
13579
  toolCallId: entry.toolCallId,
13535
13580
  toolCatalog
@@ -13542,7 +13587,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13542
13587
  const call = parallelCalls[index];
13543
13588
  const toolResult = parallelResults[index];
13544
13589
  for (const event of createInstructionContextEventsFromToolResult(toolResult, persistedProjectInstructionKeys)) yield event;
13545
- if (isSourceMutationResult(toolResult)) hasSourceMutation = true;
13590
+ if (isSourceMutationResult(toolResult) || isBenchmarkMutationResult(toolResult, options.benchmarkProfile)) hasSourceMutation = true;
13546
13591
  yield agentEvent.toolCall(call, formatToolCallMessage(call, toolResult), getToolCallDisplayDiff(toolResult));
13547
13592
  const postHookRun = this.startPostToolUseHook(call, modelToolCalls[index]?.id, toolResult, session, abortSignal);
13548
13593
  for await (const event of postHookRun.statusEvents) yield event;
@@ -13607,6 +13652,7 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13607
13652
  subagents,
13608
13653
  projectInstructions: projectInstructionToolState,
13609
13654
  currentUserMessage: message,
13655
+ benchmarkProfile: options.benchmarkProfile,
13610
13656
  abortSignal,
13611
13657
  toolCallId: toolCall.id,
13612
13658
  toolCatalog,
@@ -13625,11 +13671,12 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13625
13671
  implementationTask,
13626
13672
  hasSourceMutation,
13627
13673
  hasOpenPlan: planTodoMode === "normal" && hasOpenTaskPlan(this.taskPlan.get()),
13628
- canUseSourceEditTool: canStillUseSourceEditTool(permissions)
13674
+ canUseSourceEditTool: canStillUseSourceEditTool(permissions),
13675
+ requireFinishTask
13629
13676
  });
13630
13677
  if (finishError) toolResult = createToolErrorResult(executableToolCall.tool, finishError);
13631
13678
  }
13632
- if (isSourceMutationResult(toolResult)) hasSourceMutation = true;
13679
+ if (isSourceMutationResult(toolResult) || isBenchmarkMutationResult(toolResult, options.benchmarkProfile)) hasSourceMutation = true;
13633
13680
  yield agentEvent.toolCall(executableToolCall, formatToolCallMessage(executableToolCall, toolResult), getToolCallDisplayDiff(toolResult));
13634
13681
  if (!isToolErrorResult(toolResult) && toolResult.tool === "plan_todo") {
13635
13682
  planTodoUpdates += 1;
@@ -13838,7 +13885,8 @@ var TopchesterAgentRuntime = class TopchesterAgentRuntime {
13838
13885
  const decision = await validateBashPolicy(parsed.data, {
13839
13886
  workspaceRoot: this.context.workspaceRoot,
13840
13887
  permissions: this.context.config.tools?.bash,
13841
- approvedCommands
13888
+ approvedCommands,
13889
+ benchmarkProfile: options.benchmarkProfile
13842
13890
  });
13843
13891
  if (decision.allowed) return {
13844
13892
  cancelled: false,
@@ -14046,6 +14094,10 @@ function readMaxToolCallsPerTurn() {
14046
14094
  function readPlanTodoMode() {
14047
14095
  return process.env[PLAN_TODO_MODE_ENV]?.trim().toLowerCase() === "compact" ? "compact" : "normal";
14048
14096
  }
14097
+ function isFinishTaskRequiredByEnv() {
14098
+ const raw = process.env[REQUIRE_FINISH_TASK_ENV]?.trim().toLowerCase();
14099
+ return raw === "1" || raw === "true" || raw === "yes" || raw === "on";
14100
+ }
14049
14101
  function readMaxPlanTodoUpdatesPerTurn(mode) {
14050
14102
  const raw = process.env[MAX_PLAN_TODO_UPDATES_PER_TURN_ENV]?.trim();
14051
14103
  if (!raw) return mode === "compact" ? DEFAULT_COMPACT_MAX_PLAN_TODO_UPDATES_PER_TURN : void 0;
@@ -14089,6 +14141,10 @@ function isSourceMutationResult(result) {
14089
14141
  if (result.tool === "apply_patch") return result.changedFiles.some(isSourcePath);
14090
14142
  return false;
14091
14143
  }
14144
+ function isBenchmarkMutationResult(result, benchmarkProfile) {
14145
+ if (benchmarkProfile !== "terminal-bench" || isToolErrorResult(result)) return false;
14146
+ return result.tool === "bash" && "workspaceMayHaveChanged" in result && result.workspaceMayHaveChanged === true;
14147
+ }
14092
14148
  function isSourcePath(path) {
14093
14149
  if (!path) return false;
14094
14150
  return !path.startsWith(".agents/") && !path.startsWith(".git/") && !path.startsWith("topchester-kb/");
@@ -14097,6 +14153,7 @@ function validateFinishTaskResult(result, state) {
14097
14153
  if (isToolErrorResult(result) || result.tool !== "finish_task") return;
14098
14154
  if (state.hasOpenPlan) return "finish_task rejected because the visible plan is still open. Close or update plan_todo first.";
14099
14155
  if (state.implementationTask && state.canUseSourceEditTool && !state.hasSourceMutation) return "finish_task rejected because this appears to be an implementation task but no successful source-file edit has occurred. Use edit_file, write_file, or apply_patch first, unless you can prove no code change is required.";
14156
+ if (state.requireFinishTask && result.remainingIssues.length > 0) return "finish_task rejected because benchmark mode cannot finish with known remaining issues. Continue implementing or validating until remaining_issues is empty.";
14100
14157
  }
14101
14158
  function isL1ContextDisabledByEnv() {
14102
14159
  const value = process.env.TOPCHESTER_DISABLE_L1_CONTEXT?.trim().toLowerCase();
@@ -15674,12 +15731,14 @@ async function executeRunCommand(context, options) {
15674
15731
  json: Boolean(options.json),
15675
15732
  outputJson: options.outputJson,
15676
15733
  timeoutMs,
15677
- dangerouslyAutoApprove: Boolean(options.dangerouslyAutoApprove)
15734
+ dangerouslyAutoApprove: Boolean(options.dangerouslyAutoApprove),
15735
+ benchmarkProfile: options.benchmarkProfile
15678
15736
  }, "run started");
15679
15737
  pushJson(jsonEvents, runId, session.sessionId, "run.started", {
15680
15738
  workspaceRoot: runContext.workspaceRoot,
15681
15739
  timeoutMs,
15682
- dangerouslyAutoApprove: Boolean(options.dangerouslyAutoApprove)
15740
+ dangerouslyAutoApprove: Boolean(options.dangerouslyAutoApprove),
15741
+ benchmarkProfile: options.benchmarkProfile
15683
15742
  });
15684
15743
  try {
15685
15744
  if (!options.resume) await persistStartupMessages(session, runContext);
@@ -15736,7 +15795,8 @@ async function executeRunCommand(context, options) {
15736
15795
  });
15737
15796
  for await (const event of runtime.submitMessageStream(conversation, options.prompt, abortController.signal, {
15738
15797
  session,
15739
- userApprovalMode: options.dangerouslyAutoApprove ? "auto_allow" : "interactive"
15798
+ userApprovalMode: options.dangerouslyAutoApprove ? "auto_allow" : "interactive",
15799
+ benchmarkProfile: options.benchmarkProfile
15740
15800
  })) await applyRuntimeEvent({
15741
15801
  event,
15742
15802
  session,
@@ -16192,7 +16252,7 @@ function createTopchesterProgram() {
16192
16252
  process.exitCode = 1;
16193
16253
  }
16194
16254
  });
16195
- program.command("run").description("run one prompt or slash command without opening the TUI").argument("<prompt...>", "prompt text or slash command").option("--model <model>", "override the agent.primary model for this run").option("--timeout <ms>", "timeout for the run in milliseconds", parsePositiveInteger).option("--json", "write JSONL run events to stdout").option("--output-json <path>", "write JSONL run events to a file").option("--dangerously-auto-approve", "auto-approve prompt-gated tool calls for this non-interactive run").action(async (promptParts, options) => {
16255
+ program.command("run").description("run one prompt or slash command without opening the TUI").argument("<prompt...>", "prompt text or slash command").option("--model <model>", "override the agent.primary model for this run").option("--timeout <ms>", "timeout for the run in milliseconds", parsePositiveInteger).option("--json", "write JSONL run events to stdout").option("--output-json <path>", "write JSONL run events to a file").option("--dangerously-auto-approve", "auto-approve prompt-gated tool calls for this non-interactive run").option("--benchmark-profile <profile>", "enable an explicit benchmark runtime profile", parseBenchmarkProfile).action(async (promptParts, options) => {
16196
16256
  const context = createContextFromOptions(program);
16197
16257
  const globalOptions = program.opts();
16198
16258
  try {
@@ -16203,7 +16263,8 @@ function createTopchesterProgram() {
16203
16263
  json: options.json,
16204
16264
  outputJson: options.outputJson,
16205
16265
  resume: globalOptions.resume,
16206
- dangerouslyAutoApprove: options.dangerouslyAutoApprove
16266
+ dangerouslyAutoApprove: options.dangerouslyAutoApprove,
16267
+ benchmarkProfile: options.benchmarkProfile
16207
16268
  });
16208
16269
  } catch (error) {
16209
16270
  console.error(formatStartupError(error));
@@ -16493,4 +16554,4 @@ function formatDryRunSyncStatus(status) {
16493
16554
  //#endregion
16494
16555
  export { runTopchesterCli as t };
16495
16556
 
16496
- //# sourceMappingURL=cli-CnI4QA2l.mjs.map
16557
+ //# sourceMappingURL=cli-BS67FTk8.mjs.map