@tritard/waterbrother 0.6.2 → 0.6.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tritard/waterbrother",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "Waterbrother: Grok-powered coding CLI with local tools, sessions, operator modes, and approval controls",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -5780,31 +5780,103 @@ async function promptLoop(agent, session, context) {
5780
5780
  continue;
5781
5781
  }
5782
5782
  if (naturalResult && typeof naturalResult === "object" && naturalResult.rewrittenLine) {
5783
- // Rewritten to a slash command — execute it directly as if typed
5784
- const rewritten = naturalResult.rewrittenLine;
5785
- if (rewritten === "/build" || rewritten.startsWith("/build ")) {
5786
- // Trigger the /build handler with the rewritten prompt
5787
- line = rewritten;
5788
- // Fall through — the /build handler above already ran or line is now /build
5789
- // We need to re-trigger, so just run build inline
5783
+ // Rewritten to a slash command — re-process through the command dispatch
5784
+ line = naturalResult.rewrittenLine;
5785
+ // Don't continue fall through so the rewritten line hits the slash handlers above
5786
+ // But we're past them, so we need to handle inline
5787
+ // Extract the actual /build handler's logic by calling runBuildWorkflow directly
5788
+ if (line === "/build" || line.startsWith("/build ")) {
5790
5789
  const task = context.runtime.activeTask;
5791
5790
  if (task) {
5792
- const buildArg = rewritten.replace("/build", "").trim();
5791
+ const buildArg = line.replace("/build", "").trim();
5793
5792
  const buildPrompt = buildArg || (task.chosenOption ? `Execute the chosen approach: ${task.chosenOption}` : `Build: ${task.name}`);
5793
+
5794
+ await maybeAutoCompactConversation({ agent, currentSession, context, pendingInput: buildPrompt });
5795
+
5796
+ const spinner = createProgressSpinner("building...");
5797
+ const railStates = new Set();
5798
+ let currentState = "planning";
5799
+
5800
+ function toolToPhaseRewritten(toolName) {
5801
+ if (toolName === "run_shell") return "running";
5802
+ if (toolName === "write_file" || toolName === "replace_in_file" || toolName === "apply_patch") return "editing";
5803
+ if (toolName === "read_file" || toolName === "search_text" || toolName === "list_files" || toolName === "glob_files" || toolName === "read_many_files") return "reading";
5804
+ if (toolName === "declare_contract") return "planning";
5805
+ return "reading";
5806
+ }
5807
+
5808
+ function printRailRewritten(phase) {
5809
+ if (railStates.has(phase)) return;
5810
+ railStates.add(phase);
5811
+ console.log(`${dim(" ▸")} ${phase}`);
5812
+ }
5813
+
5814
+ printRailRewritten("planning");
5815
+
5816
+ const abortController = typeof AbortController === "function" ? new AbortController() : null;
5817
+ let interrupted = false;
5818
+ const detachInterruptListener = createInterruptListener(() => {
5819
+ if (interrupted) return;
5820
+ interrupted = true;
5821
+ spinner.setLabel("interrupting...");
5822
+ if (abortController) abortController.abort();
5823
+ }, { enableEsc: process.stdin.isTTY, shouldIgnoreEsc: () => approvalPromptActive });
5824
+
5794
5825
  try {
5795
- await runTextTurnInteractive({
5796
- agent,
5797
- currentSession,
5798
- context,
5826
+ const buildResult = await runBuildWorkflow({
5827
+ agent, context, task,
5799
5828
  promptText: buildPrompt,
5800
- pendingInput: buildPrompt,
5801
- spinnerLabel: "building..."
5829
+ handlers: {
5830
+ signal: abortController?.signal,
5831
+ onStateChange(state) { currentState = state; },
5832
+ onAssistantDelta() {},
5833
+ onToolStart(toolCall) {
5834
+ const toolName = toolCall?.function?.name || "tool";
5835
+ printRailRewritten(toolToPhaseRewritten(toolName));
5836
+ spinner.setLabel(`${toolName}...`);
5837
+ },
5838
+ onToolEnd(toolCall, result) {
5839
+ const toolName = toolCall?.function?.name || "tool";
5840
+ const status = parseToolResultShape(result);
5841
+ if (status !== "ok") {
5842
+ console.log(`${dim(" ▸")} ${toolName}: ${status === "blocked" ? yellow("blocked") : red(status)}`);
5843
+ }
5844
+ spinner.setLabel("thinking...");
5845
+ },
5846
+ onAssistant() {}
5847
+ }
5802
5848
  });
5849
+
5850
+ detachInterruptListener();
5851
+ spinner.stop();
5852
+ printRailRewritten("done");
5853
+
5854
+ if (buildResult) {
5855
+ const { receipt, review, impactSummary } = buildResult;
5856
+ if (receipt) {
5857
+ task.latestReceiptId = receipt.id;
5858
+ task.lastVerdict = review?.verdict || null;
5859
+ task.state = "review-ready";
5860
+ await saveTask({ cwd: context.cwd, task });
5861
+ context.runtime.activeTask = task;
5862
+ context.runtime.lastReceipt = receipt;
5863
+ }
5864
+ console.log(`────────────────────────────────────────────────────────────────────────`);
5865
+ console.log(`task: ${task.name} → ${task.state}`);
5866
+ console.log(`────────────────────────────────────────────────────────────────────────`);
5867
+ if (review) {
5868
+ renderReviewCockpit(task, receipt);
5869
+ } else {
5870
+ updatePanel();
5871
+ }
5872
+ }
5803
5873
  } catch (error) {
5874
+ detachInterruptListener();
5875
+ spinner.stop();
5804
5876
  console.log(`build failed: ${error instanceof Error ? error.message : String(error)}`);
5805
5877
  }
5806
5878
  }
5807
- } else if (rewritten === "/challenge") {
5879
+ } else if (line === "/challenge") {
5808
5880
  const task = context.runtime.activeTask;
5809
5881
  const receipt = context.runtime.lastReceipt || (task && await agent.toolRuntime.readReceipt(task?.latestReceiptId || "last"));
5810
5882
  if (receipt) {
package/src/router.js CHANGED
@@ -14,9 +14,9 @@ function looksLikeWorkRequest(text) {
14
14
  if (/^(hi|hello|thanks|thank you|help|what can you do)\b/.test(l)) return false;
15
15
  if (/^(build it|go|check it|ship it|fix these|ignore|challenge harder|think deeper)\b/.test(l)) return false;
16
16
  if (/^[1-9]\d*$/.test(l)) return false;
17
- const verbStart = /^(add|build|implement|create|fix|refactor|remove|update|wire|support|migrate|improve|audit|review|debug|investigate|clean up|set up|setup|rewrite|port|optimize)\b/;
17
+ const verbStart = /^(add|build|implement|create|fix|refactor|remove|update|wire|support|migrate|improve|audit|review|debug|investigate|clean up|set up|setup|rewrite|port|optimize|write|make|deploy|install|configure|connect|test|run|generate|scaffold|move|rename|delete|extract|split|merge)\b/;
18
18
  const suffixHint = /( to the | for the | in the | across | using | with | without )/;
19
- return verbStart.test(l) || suffixHint.test(l) || (!l.endsWith("?") && raw.split(/\s+/).length >= 3);
19
+ return verbStart.test(l) || (suffixHint.test(l) && verbStart.test(l));
20
20
  }
21
21
 
22
22
  function inferPassFromText(text) {