@trydecember/cli 0.3.28 → 0.3.29

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.
Files changed (2) hide show
  1. package/dist/december.js +480 -143
  2. package/package.json +1 -1
package/dist/december.js CHANGED
@@ -65,7 +65,7 @@ var package_default;
65
65
  var init_package = __esm(() => {
66
66
  package_default = {
67
67
  name: "@trydecember/cli",
68
- version: "0.3.28",
68
+ version: "0.3.29",
69
69
  description: "a coding agent for terminal and cloud.",
70
70
  homepage: "https://trydecember.com",
71
71
  repository: {
@@ -107052,6 +107052,28 @@ async function exchangeGitHubTokenForCopilot(githubOAuthToken) {
107052
107052
  });
107053
107053
  if (!res.ok) {
107054
107054
  const text = await res.text().catch(() => "");
107055
+ let parsedJson = null;
107056
+ try {
107057
+ parsedJson = JSON.parse(text);
107058
+ } catch {}
107059
+ if (res.status === 403) {
107060
+ const errorDetails = parsedJson?.error_details;
107061
+ if (errorDetails?.notification_id === "no_copilot_access") {
107062
+ const detailMsg = errorDetails.message || "";
107063
+ const userMatch = detailMsg.match(/logged in as ([^.]+)/i);
107064
+ const userNotice = userMatch ? ` for account @${userMatch[1].trim()}` : "";
107065
+ throw new Error(`No active GitHub Copilot subscription found${userNotice}. ${detailMsg} Please sign up at https://github.com/github-copilot/signup or ensure your browser is logged into an authorized GitHub account.`);
107066
+ }
107067
+ if (errorDetails?.message) {
107068
+ throw new Error(`GitHub Copilot subscription verification failed (403): ${errorDetails.message}`);
107069
+ }
107070
+ if (parsedJson?.message) {
107071
+ throw new Error(`GitHub Copilot subscription verification failed (403): ${parsedJson.message}`);
107072
+ }
107073
+ }
107074
+ if (parsedJson?.message) {
107075
+ throw new Error(`Failed to exchange GitHub token for Copilot token (${res.status}): ${parsedJson.message}`);
107076
+ }
107055
107077
  throw new Error(`Failed to exchange GitHub token for Copilot token (${res.status}): ${text}`);
107056
107078
  }
107057
107079
  const data = await res.json();
@@ -107207,7 +107229,11 @@ var init_copilot2 = __esm(() => {
107207
107229
  try {
107208
107230
  exchangeResult = await exchangeGitHubTokenForCopilot(ghOAuthToken);
107209
107231
  } catch (err) {
107210
- throw new Error(`GitHub Copilot subscription verification failed. Please make sure your GitHub account has an active Copilot subscription and that external integrations are allowed by your organization. (${err?.message || err})`, { cause: err });
107232
+ const msg = err?.message || String(err);
107233
+ if (msg.includes("No active GitHub Copilot subscription") || msg.includes("GitHub Copilot subscription verification failed")) {
107234
+ throw err;
107235
+ }
107236
+ throw new Error(`GitHub Copilot subscription verification failed. Please make sure your GitHub account has an active Copilot subscription and that external integrations are allowed by your organization. (${msg})`, { cause: err });
107211
107237
  }
107212
107238
  const copilotToken = exchangeResult.token;
107213
107239
  const endpoint = exchangeResult.endpoint;
@@ -109462,6 +109488,9 @@ Initiating subscription login for ${providerName}...`);
109462
109488
  console.log(`
109463
109489
  Please open ${uri} in your browser and enter code: ${code}
109464
109490
  Waiting for authorization...`);
109491
+ if (targetProvider === "copilot" || targetProvider === "github") {
109492
+ console.log("*(Note: Ensure your browser is signed into the GitHub account that owns your active Copilot subscription)*");
109493
+ }
109465
109494
  });
109466
109495
  const configToSave = await loadConfig();
109467
109496
  configToSave.subscriptions = configToSave.subscriptions || {};
@@ -114460,10 +114489,11 @@ function formatError(e) {
114460
114489
  } catch {}
114461
114490
  return String(e) || "Unknown error";
114462
114491
  }
114463
- async function* runAgentLoop(agent, userInput) {
114492
+ async function* runAgentLoop(agent, userInput, options) {
114464
114493
  const eventQueue = new AsyncQueue2;
114465
114494
  const abortController = new AbortController;
114466
114495
  agent.activeAbortController = abortController;
114496
+ const isReadOnly = Boolean(options?.readOnly || typeof userInput === "object" && userInput !== null && userInput.readOnly);
114467
114497
  if (userInput) {
114468
114498
  if (typeof userInput === "string") {
114469
114499
  agent.addMessage({ role: "user", content: userInput });
@@ -114486,7 +114516,9 @@ async function* runAgentLoop(agent, userInput) {
114486
114516
  });
114487
114517
  } catch {}
114488
114518
  eventQueue.push({ type: "AgentStart" });
114489
- await runOuterLoop(agent, eventQueue, abortController.signal);
114519
+ await runOuterLoop(agent, eventQueue, abortController.signal, {
114520
+ readOnly: isReadOnly
114521
+ });
114490
114522
  try {
114491
114523
  if (abortController.signal.aborted) {
114492
114524
  agent.tracer?.endSession("ABORTED");
@@ -114525,9 +114557,9 @@ async function* runAgentLoop(agent, userInput) {
114525
114557
  })();
114526
114558
  yield* eventQueue;
114527
114559
  }
114528
- async function runOuterLoop(agent, eventQueue, signal) {
114560
+ async function runOuterLoop(agent, eventQueue, signal, options) {
114529
114561
  while (!signal.aborted) {
114530
- await runInnerLoop(agent, eventQueue, signal);
114562
+ await runInnerLoop(agent, eventQueue, signal, options);
114531
114563
  if (signal.aborted)
114532
114564
  break;
114533
114565
  if (agent.followUpQueue.length > 0) {
@@ -114540,7 +114572,7 @@ async function runOuterLoop(agent, eventQueue, signal) {
114540
114572
  break;
114541
114573
  }
114542
114574
  }
114543
- async function runInnerLoop(agent, eventQueue, signal) {
114575
+ async function runInnerLoop(agent, eventQueue, signal, options) {
114544
114576
  let isDone = false;
114545
114577
  let turnCount = 0;
114546
114578
  while (!isDone && turnCount < 100 && !signal.aborted) {
@@ -114561,7 +114593,7 @@ async function runInnerLoop(agent, eventQueue, signal) {
114561
114593
  }
114562
114594
  }
114563
114595
  eventQueue.push({ type: "TurnStart" });
114564
- const { assistantMessage, toolCalls, error } = await streamAssistantResponse(agent, eventQueue, signal, turnCount);
114596
+ const { assistantMessage, toolCalls, error } = await streamAssistantResponse(agent, eventQueue, signal, turnCount, options);
114565
114597
  if (error || signal.aborted) {
114566
114598
  break;
114567
114599
  }
@@ -114574,7 +114606,7 @@ async function runInnerLoop(agent, eventQueue, signal) {
114574
114606
  content: assistantMessage,
114575
114607
  toolCalls
114576
114608
  });
114577
- await executeToolCalls(agent, toolCalls, eventQueue, signal);
114609
+ await executeToolCalls(agent, toolCalls, eventQueue, signal, options);
114578
114610
  }
114579
114611
  await agent.saveContext();
114580
114612
  eventQueue.push({ type: "TurnEnd" });
@@ -114595,7 +114627,7 @@ async function runInnerLoop(agent, eventQueue, signal) {
114595
114627
  }
114596
114628
  }
114597
114629
  }
114598
- async function streamAssistantResponse(agent, eventQueue, signal, turnCount = 1) {
114630
+ async function streamAssistantResponse(agent, eventQueue, signal, turnCount = 1, options) {
114599
114631
  let assistantMessage = "";
114600
114632
  let toolCalls = [];
114601
114633
  let thinkingText = "";
@@ -114621,7 +114653,7 @@ async function streamAssistantResponse(agent, eventQueue, signal, turnCount = 1)
114621
114653
  });
114622
114654
  }
114623
114655
  const isConversational = isSimpleConversationalTurn(agent.messages);
114624
- const activeTools = isConversational ? Array.from(agent.tools.values()).filter((t) => ["read_file", "ls", "ask_question"].includes(t.name)) : Array.from(agent.tools.values());
114656
+ const activeTools = options?.readOnly ? Array.from(agent.tools.values()).filter((t) => READ_ONLY_TOOLS.has(t.name)) : isConversational ? Array.from(agent.tools.values()).filter((t) => ["read_file", "ls", "ask_question"].includes(t.name)) : Array.from(agent.tools.values());
114625
114657
  const toolsArray = activeTools.map((t, idx) => ({
114626
114658
  name: t.name,
114627
114659
  description: t.description ? t.description.replace(/\s+/g, " ").trim() : "",
@@ -114858,7 +114890,7 @@ ${errorMsg}`;
114858
114890
  return { assistantMessage, toolCalls, error: errorMsg };
114859
114891
  }
114860
114892
  }
114861
- async function executeToolCalls(agent, toolCalls, eventQueue, signal) {
114893
+ async function executeToolCalls(agent, toolCalls, eventQueue, signal, options) {
114862
114894
  const isSequentialTool = (tc) => {
114863
114895
  const tool = agent.tools.get(tc.name);
114864
114896
  return tool?.executionMode === "sequential" || ["bash", "write_file", "edit_file", "edit_diff"].includes(tc.name);
@@ -114873,18 +114905,34 @@ async function executeToolCalls(agent, toolCalls, eventQueue, signal) {
114873
114905
  }
114874
114906
  }
114875
114907
  if (parallelReadCalls.length > 0) {
114876
- await executeToolCallsParallel(agent, parallelReadCalls, eventQueue, signal);
114908
+ await executeToolCallsParallel(agent, parallelReadCalls, eventQueue, signal, options);
114877
114909
  }
114878
114910
  if (sequentialWriteCalls.length > 0 && !signal.aborted) {
114879
- await executeToolCallsSequential(agent, sequentialWriteCalls, eventQueue, signal);
114911
+ await executeToolCallsSequential(agent, sequentialWriteCalls, eventQueue, signal, options);
114880
114912
  }
114881
114913
  }
114882
- async function executeSingleTool(agent, toolCall, eventQueue, signal) {
114914
+ async function executeSingleTool(agent, toolCall, eventQueue, signal, options) {
114883
114915
  const toolStartTime = Date.now();
114884
114916
  eventQueue.push({ type: "ToolCallStart", toolCall });
114885
114917
  const tool = agent.tools.get(toolCall.name);
114886
114918
  let resultStr = "";
114887
114919
  let errorStr = undefined;
114920
+ if (options?.readOnly && !READ_ONLY_TOOLS.has(toolCall.name)) {
114921
+ errorStr = `Tool execution blocked: '${toolCall.name}' is not permitted in read-only / ask mode.`;
114922
+ const res = { toolCallId: toolCall.id, result: "", error: errorStr };
114923
+ eventQueue.push({ type: "ToolCallResult", result: res });
114924
+ try {
114925
+ agent.tracer?.recordToolExecution({
114926
+ toolCallId: toolCall.id,
114927
+ toolName: toolCall.name,
114928
+ input: toolCall.input,
114929
+ output: "",
114930
+ error: errorStr,
114931
+ durationMs: Date.now() - toolStartTime
114932
+ });
114933
+ } catch {}
114934
+ return { toolCall, toolResult: res, resultStr: "", errorStr };
114935
+ }
114888
114936
  if (agent.operations?.ui?.requestPermission) {
114889
114937
  const hookRes = await agent.operations.ui.requestPermission(toolCall);
114890
114938
  if (hookRes?.block) {
@@ -114949,11 +114997,11 @@ async function executeSingleTool(agent, toolCall, eventQueue, signal) {
114949
114997
  eventQueue.push({ type: "ToolCallResult", result: toolResult });
114950
114998
  return { toolCall, toolResult, resultStr: toolResult.result, errorStr: toolResult.error };
114951
114999
  }
114952
- async function executeToolCallsSequential(agent, toolCalls, eventQueue, signal) {
115000
+ async function executeToolCallsSequential(agent, toolCalls, eventQueue, signal, options) {
114953
115001
  for (const toolCall of toolCalls) {
114954
115002
  if (signal.aborted)
114955
115003
  break;
114956
- const r = await executeSingleTool(agent, toolCall, eventQueue, signal);
115004
+ const r = await executeSingleTool(agent, toolCall, eventQueue, signal, options);
114957
115005
  let finalContent = r.resultStr || "";
114958
115006
  if (r.errorStr) {
114959
115007
  finalContent = `Tool execution failed: ${r.errorStr}
@@ -114966,8 +115014,8 @@ Please adjust your arguments and try again.`;
114966
115014
  });
114967
115015
  }
114968
115016
  }
114969
- async function executeToolCallsParallel(agent, toolCalls, eventQueue, signal) {
114970
- const promises = toolCalls.map((tc) => executeSingleTool(agent, tc, eventQueue, signal));
115017
+ async function executeToolCallsParallel(agent, toolCalls, eventQueue, signal, options) {
115018
+ const promises = toolCalls.map((tc) => executeSingleTool(agent, tc, eventQueue, signal, options));
114971
115019
  const results = await Promise.all(promises);
114972
115020
  for (const r of results) {
114973
115021
  let finalContent = r.resultStr || "";
@@ -114982,7 +115030,7 @@ Please adjust your arguments and try again.`;
114982
115030
  });
114983
115031
  }
114984
115032
  }
114985
- var AsyncQueue2;
115033
+ var AsyncQueue2, READ_ONLY_TOOLS;
114986
115034
  var init_agent_loop = __esm(() => {
114987
115035
  init_src();
114988
115036
  init_p_retry();
@@ -115023,6 +115071,15 @@ var init_agent_loop = __esm(() => {
115023
115071
  }
115024
115072
  }
115025
115073
  };
115074
+ READ_ONLY_TOOLS = new Set([
115075
+ "read_file",
115076
+ "ls",
115077
+ "find_files",
115078
+ "grep_search",
115079
+ "web_search",
115080
+ "browser",
115081
+ "ask_question"
115082
+ ]);
115026
115083
  });
115027
115084
  // ../../packages/agent/src/harness/agent-harness.ts
115028
115085
  import fs22 from "node:fs";
@@ -115032,18 +115089,21 @@ function isReasoningModel(model, thinkingLevel) {
115032
115089
  return thinkingLevel !== undefined && thinkingLevel !== "off";
115033
115090
  }
115034
115091
  const m = model.toLowerCase();
115035
- if (m.includes("o1") || m.includes("o3") || m.includes("deepseek-reasoner") || m.includes("deepseek-r1")) {
115092
+ if (m.includes("o1") || m.includes("o3") || m.includes("deepseek-reasoner") || m.includes("deepseek-r1") || m.includes("thinking")) {
115036
115093
  return true;
115037
115094
  }
115038
115095
  if (m.includes("claude") && thinkingLevel && thinkingLevel !== "off") {
115039
115096
  return true;
115040
115097
  }
115098
+ if (m.includes("gemini") && (m.includes("gemini-2.5") || thinkingLevel !== undefined && thinkingLevel !== "off")) {
115099
+ return true;
115100
+ }
115041
115101
  return false;
115042
115102
  }
115043
115103
  function getModelFamilyPrompt(options) {
115044
115104
  const model = options.model?.toLowerCase() || "";
115045
115105
  const reasoning = isReasoningModel(options.model, options.thinkingLevel);
115046
- if (reasoning) {
115106
+ if (reasoning && !model.includes("gemini")) {
115047
115107
  return {
115048
115108
  roleGuidelines: `### Model Specialization (Autonomous Reasoning & Verification)
115049
115109
  - Persistent Problem Solving: Keep going until the user's task or bug is completely resolved and empirically verified before ending your turn.
@@ -115068,24 +115128,26 @@ function getModelFamilyPrompt(options) {
115068
115128
  };
115069
115129
  }
115070
115130
  if (model.includes("gemini")) {
115131
+ const isGeminiThinking = model.includes("thinking") || model.includes("gemini-2.5") || options.thinkingLevel !== undefined && options.thinkingLevel !== "off";
115071
115132
  return {
115072
115133
  roleGuidelines: `### Model Specialization (Gemini)
115073
115134
  - Structured Execution: Follow clear operational phases: 1) Explore and gather context, 2) Formulate targeted edits, 3) Verify thoroughly.
115074
115135
  - Absolute File Paths: STRICTLY specify absolute file paths when referencing, viewing, or editing files. NEVER output ambiguous relative paths.`,
115075
- reasoningProtocol: `### Reasoning & Communication Protocol
115136
+ reasoningProtocol: isGeminiThinking ? `### Communication Protocol
115137
+ - Conciseness & Chat Focus: Be direct and concise. In chat messages, provide ONLY high-level status updates, architectural decisions, and tool confirmations. Do not repeat file contents in chat.
115138
+ - Execution Summary: At the end of your work, provide a concise summary (max 4-5 lines, single cohesive paragraph) highlighting key actions, modified files, and test verification results.` : `### Reasoning & Communication Protocol
115076
115139
  - Thought Enclosure: Before calling any tool, you MUST enclose your step-by-step reasoning inside <thought>...</thought> tags. Write thoughts directly as natural, concise reasoning steps without outline scaffolding, markdown headers, or bullet lists (do NOT write labels like "* **Goal Understanding:**" or structured outlines).
115077
115140
  - Conciseness & Chat Focus: Be direct and concise. In chat messages, provide ONLY high-level status updates, architectural decisions, and tool confirmations. Do not repeat file contents in chat.
115078
115141
  - Execution Summary: At the end of your work, provide a concise summary (max 4-5 lines, single cohesive paragraph) highlighting key actions, modified files, and test verification results.`,
115079
- suppressThoughtTags: false
115142
+ suppressThoughtTags: isGeminiThinking
115080
115143
  };
115081
115144
  }
115082
115145
  return {
115083
115146
  roleGuidelines: "",
115084
- reasoningProtocol: `### Reasoning & Communication Protocol
115085
- - Thought Enclosure: Before calling any tool, you MUST enclose your step-by-step reasoning inside <thought>...</thought> tags. Write thoughts directly as natural, concise reasoning steps without outline scaffolding, markdown headers, or bullet lists (do NOT write labels like "* **Goal Understanding:**" or structured outlines).
115147
+ reasoningProtocol: `### Communication Protocol
115086
115148
  - Conciseness & Chat Focus: Be direct and concise. In chat messages, provide ONLY high-level status updates, architectural decisions, and tool confirmations. Do not repeat file contents in chat.
115087
115149
  - Execution Summary: At the end of your work, provide a concise summary (max 4-5 lines, single cohesive paragraph) highlighting key actions, modified files, and test verification results.`,
115088
- suppressThoughtTags: false
115150
+ suppressThoughtTags: true
115089
115151
  };
115090
115152
  }
115091
115153
  function buildDynamicToolGuidelines(activeTools) {
@@ -115503,6 +115565,20 @@ Select an option or type your answer: `).then((answer) => {
115503
115565
  });
115504
115566
  };
115505
115567
  agent.operations.ui.requestPermission = async (toolCall) => {
115568
+ if (options.readOnly && ![
115569
+ "read_file",
115570
+ "ls",
115571
+ "find_files",
115572
+ "grep_search",
115573
+ "web_search",
115574
+ "browser",
115575
+ "ask_question"
115576
+ ].includes(toolCall.name)) {
115577
+ return {
115578
+ block: true,
115579
+ reason: `Tool '${toolCall.name}' is not allowed in read-only / ask mode.`
115580
+ };
115581
+ }
115506
115582
  if (isNonInteractive) {
115507
115583
  return { block: false };
115508
115584
  }
@@ -115546,7 +115622,7 @@ Execute ${toolCall.name}? (y/n): `);
115546
115622
  effectivePrompt = resolved.expandedPrompt;
115547
115623
  } catch {}
115548
115624
  }
115549
- const stream = runAgentLoop(agent, effectivePrompt);
115625
+ const stream = runAgentLoop(agent, options.readOnly ? { content: effectivePrompt, readOnly: true } : effectivePrompt);
115550
115626
  for await (const event of stream) {
115551
115627
  switch (event.type) {
115552
115628
  case "StreamChunk":
@@ -168564,6 +168640,92 @@ var init_provider_factory = __esm(() => {
168564
168640
  init_subscription_manager();
168565
168641
  });
168566
168642
 
168643
+ // src/constants/prompts.ts
168644
+ var exports_prompts = {};
168645
+ __export(exports_prompts, {
168646
+ getAskPrompt: () => getAskPrompt,
168647
+ getGrillPrompt: () => getGrillPrompt,
168648
+ getPlanExecutionPrompt: () => getPlanExecutionPrompt,
168649
+ getPlanPrompt: () => getPlanPrompt,
168650
+ getPlanRefinePrompt: () => getPlanRefinePrompt
168651
+ });
168652
+ var getGrillPrompt = (userPrompt, projectContext) => `You are a principal software architect interviewing a developer to align on a technical specification before implementation.
168653
+
168654
+ The user wants to implement: "${userPrompt}"
168655
+
168656
+ ${projectContext ? `<project_context>
168657
+ ${projectContext}
168658
+ </project_context>
168659
+ ` : ""}
168660
+ Analyze the user's request and the project context. Generate between 1 and 4 targeted, high-impact multiple-choice questions to clarify critical requirements, architectural choices, tech stack decisions, and edge cases. If the task is simple and unambiguous, generate fewer questions (e.g. 1 or 2). Do not ask more than 4 questions.
168661
+
168662
+ Requirements for questions:
168663
+ 1. Focus on technical depth: probe architectural tradeoffs, API design, state management, edge cases, and error handling.
168664
+ 2. Each question MUST have 2 to 3 distinct, concrete options representing clear design choices.
168665
+ 3. Leverage the provided project context (if available) to tailor choices specifically to the existing codebase patterns.
168666
+
168667
+ Return the output strictly as a JSON array of objects with the following schema:
168668
+ [
168669
+ {
168670
+ "question": "Question text?",
168671
+ "options": ["Option 1 (Design choice A)", "Option 2 (Design choice B)", "Option 3 (Design choice C)"]
168672
+ }
168673
+ ]
168674
+
168675
+ Do not include any other text, markdown formatting, or code blocks. Return raw JSON only.`, getPlanPrompt = (originalPrompt, qaPairs) => `You are an autonomous software engineer.
168676
+ The user wants to implement: "${originalPrompt}"
168677
+ ${qaPairs && qaPairs.length > 0 ? `Here is the alignment interview results:
168678
+ ${qaPairs.map((qa) => `Q: ${qa.question}
168679
+ A: ${qa.answer}`).join(`
168680
+
168681
+ `)}
168682
+ ` : ""}
168683
+ Please create a detailed, step-by-step implementation plan based on these requirements.
168684
+ Do NOT execute any tools. Only describe the plan.
168685
+ Start your response with '### Implementation Plan' and list the concrete steps.
168686
+ Explain which files need to be created, modified, or deleted, and what the changes will be.`, getPlanExecutionPrompt = (originalPrompt, planText, qaPairs) => `You are an autonomous software engineer.
168687
+ The user has approved the implementation plan for: "${originalPrompt}".
168688
+
168689
+ ${qaPairs && qaPairs.length > 0 ? `Key alignment decisions from the interview:
168690
+ ${qaPairs.map((qa) => `Q: ${qa.question}
168691
+ A: ${qa.answer}`).join(`
168692
+
168693
+ `)}
168694
+
168695
+ ` : ""}Approved Implementation Plan:
168696
+ ${planText}
168697
+
168698
+ You are now in execution mode. Proceed with implementation immediately.
168699
+ Execute the plan step-by-step using your available tools (file editing, file creation, bash commands).
168700
+ Do not wait for further confirmation before starting tool execution.
168701
+ Report your progress as you complete each step.
168702
+ If a tool execution fails or encounters unexpected errors, do not repeat the failing action in a loop. Assess the failure, adjust your approach or remaining steps, and if critical information is missing, ask for clarification.`, getPlanRefinePrompt = (originalPrompt, previousPlan, feedback, qaPairs) => `You are an autonomous software engineer.
168703
+ The user wants to implement: "${originalPrompt}"
168704
+
168705
+ ${qaPairs && qaPairs.length > 0 ? `Previous alignment interview results:
168706
+ ${qaPairs.map((qa) => `Q: ${qa.question}
168707
+ A: ${qa.answer}`).join(`
168708
+
168709
+ `)}
168710
+
168711
+ ` : ""}Previous Implementation Plan:
168712
+ ${previousPlan}
168713
+
168714
+ The user provided the following feedback to refine the plan:
168715
+ "${feedback}"
168716
+
168717
+ Please update and refine the detailed, step-by-step implementation plan based on this feedback.
168718
+ Do NOT execute any tools. Only describe the updated plan.
168719
+ Start your response with '### Implementation Plan' and list the concrete updated steps.
168720
+ Explain which files need to be created, modified, or deleted, and what the changes will be.`, getAskPrompt = (question) => `You are in Ask mode (read-only Q&A).
168721
+ The user is asking: "${question}"
168722
+
168723
+ Guidelines:
168724
+ 1. Answer the user's question thoroughly, clearly, and accurately based on the codebase.
168725
+ 2. You can use your available tools (search, read files, list directories, search web) to inspect and verify code facts.
168726
+ 3. You CANNOT and MUST NOT modify any files, write new files, or run bash/terminal commands.
168727
+ 4. Provide comprehensive explanations, referencing relevant file paths, types, functions, and code snippets directly in your answer.`;
168728
+
168567
168729
  // ../../node_modules/.bun/react@19.2.7/node_modules/react/cjs/react.development.js
168568
168730
  var require_react_development = __commonJS(function(exports, module) {
168569
168731
  (function() {
@@ -207370,6 +207532,12 @@ var init_commands2 = __esm(() => {
207370
207532
  init_clipboard();
207371
207533
  init_handoff();
207372
207534
  COMMANDS = [
207535
+ {
207536
+ name: "ask",
207537
+ description: "Ask a question about the codebase (read-only Q&A mode)",
207538
+ value: "/ask",
207539
+ action: (ctx) => {}
207540
+ },
207373
207541
  {
207374
207542
  name: "clear",
207375
207543
  description: "Clear conversation history",
@@ -208573,6 +208741,7 @@ var init_input_bar = __esm(async () => {
208573
208741
  setValue("");
208574
208742
  handleContentChange("");
208575
208743
  const forwardCommands = [
208744
+ "/ask",
208576
208745
  "/grill",
208577
208746
  "/grill-me",
208578
208747
  "/login",
@@ -253981,19 +254150,114 @@ function formatThought(raw) {
253981
254150
  return cleanedLines.join(`
253982
254151
  `);
253983
254152
  }
253984
- function ThoughtView({ content }) {
254153
+ function CollapsibleThought({
254154
+ content,
254155
+ isStreaming,
254156
+ forceExpanded,
254157
+ durationMs
254158
+ }) {
254159
+ const [elapsedSeconds, setElapsedSeconds] = import_react47.useState(0);
254160
+ const startTimeRef = import_react47.useRef(null);
254161
+ const finalDurationRef = import_react47.useRef(null);
254162
+ import_react47.useEffect(() => {
254163
+ if (!isStreaming) {
254164
+ if (startTimeRef.current) {
254165
+ const totalElapsed = (Date.now() - startTimeRef.current) / 1000;
254166
+ const finalSec = Math.round(totalElapsed * 10) / 10;
254167
+ finalDurationRef.current = Math.max(0.1, finalSec);
254168
+ setElapsedSeconds(finalSec);
254169
+ startTimeRef.current = null;
254170
+ }
254171
+ return;
254172
+ }
254173
+ if (!startTimeRef.current) {
254174
+ startTimeRef.current = Date.now();
254175
+ }
254176
+ const timer = setInterval(() => {
254177
+ if (startTimeRef.current) {
254178
+ const diff = (Date.now() - startTimeRef.current) / 1000;
254179
+ setElapsedSeconds(Math.round(diff * 10) / 10);
254180
+ }
254181
+ }, 100);
254182
+ return () => clearInterval(timer);
254183
+ }, [isStreaming]);
253985
254184
  const formatted = formatThought(content);
253986
- if (!formatted)
254185
+ if (!formatted && !isStreaming)
253987
254186
  return null;
253988
- return /* @__PURE__ */ jsx_runtime14.jsx(Box_default, {
254187
+ const words = formatted.trim() ? formatted.trim().split(/\s+/).length : 0;
254188
+ const tokenCount = Math.max(1, Math.round(words * 1.33));
254189
+ const displayDuration = durationMs !== undefined ? (durationMs / 1000).toFixed(1) : finalDurationRef.current !== null && finalDurationRef.current > 0 ? finalDurationRef.current.toFixed(1) : elapsedSeconds > 0 ? elapsedSeconds.toFixed(1) : Math.max(0.5, Math.round(words / 25 * 10) / 10).toFixed(1);
254190
+ if (isStreaming) {
254191
+ return /* @__PURE__ */ jsx_runtime14.jsxs(Box_default, {
254192
+ flexDirection: "row",
254193
+ marginY: 0,
254194
+ gap: 1,
254195
+ alignItems: "center",
254196
+ children: [
254197
+ /* @__PURE__ */ jsx_runtime14.jsx(Text3, {
254198
+ color: THEME.colors.brand,
254199
+ bold: true,
254200
+ children: "✱"
254201
+ }),
254202
+ /* @__PURE__ */ jsx_runtime14.jsx(Text3, {
254203
+ color: THEME.colors.text,
254204
+ children: "Thinking"
254205
+ }),
254206
+ /* @__PURE__ */ jsx_runtime14.jsxs(Text3, {
254207
+ color: THEME.colors.muted,
254208
+ children: [
254209
+ "(",
254210
+ displayDuration,
254211
+ "s)..."
254212
+ ]
254213
+ })
254214
+ ]
254215
+ });
254216
+ }
254217
+ const isExpanded = forceExpanded ?? false;
254218
+ const formattedLines = formatted.split(/\r?\n/).filter((l) => l.trim() !== "");
254219
+ return /* @__PURE__ */ jsx_runtime14.jsxs(Box_default, {
253989
254220
  flexDirection: "column",
253990
- paddingLeft: 1,
253991
254221
  marginY: 0,
253992
- children: /* @__PURE__ */ jsx_runtime14.jsx(Text3, {
253993
- color: THEME.colors.muted,
253994
- italic: true,
253995
- children: formatted
253996
- })
254222
+ children: [
254223
+ /* @__PURE__ */ jsx_runtime14.jsxs(Box_default, {
254224
+ flexDirection: "row",
254225
+ gap: 1,
254226
+ alignItems: "center",
254227
+ children: [
254228
+ /* @__PURE__ */ jsx_runtime14.jsx(Text3, {
254229
+ color: THEME.colors.muted,
254230
+ children: "✱"
254231
+ }),
254232
+ /* @__PURE__ */ jsx_runtime14.jsxs(Text3, {
254233
+ color: THEME.colors.muted,
254234
+ children: [
254235
+ "Thought for ",
254236
+ displayDuration,
254237
+ "s (",
254238
+ tokenCount,
254239
+ " tokens)"
254240
+ ]
254241
+ }),
254242
+ /* @__PURE__ */ jsx_runtime14.jsxs(Text3, {
254243
+ color: THEME.colors.muted,
254244
+ children: [
254245
+ "· ctrl+o to ",
254246
+ isExpanded ? "collapse" : "view"
254247
+ ]
254248
+ })
254249
+ ]
254250
+ }),
254251
+ isExpanded && formattedLines.length > 0 && /* @__PURE__ */ jsx_runtime14.jsx(Box_default, {
254252
+ flexDirection: "column",
254253
+ paddingLeft: 2,
254254
+ marginTop: 0,
254255
+ children: formattedLines.map((line, lidx) => /* @__PURE__ */ jsx_runtime14.jsx(Text3, {
254256
+ color: THEME.colors.muted,
254257
+ children: line
254258
+ }, lidx))
254259
+ })
254260
+ ]
253997
254261
  });
253998
254262
  }
253999
254263
  function StyledCommand({ command, truncate = true }) {
@@ -254192,9 +254456,13 @@ var init_bot_message = __esm(async () => {
254192
254456
  }),
254193
254457
  parts.map((part, pidx) => {
254194
254458
  if (/^<(?:thought|think)(?:>| [^>]*>)/i.test(part)) {
254459
+ const isClosed = /<\/(?:thought|think)>$/i.test(part);
254460
+ const isStreaming = !isClosed && idx === blocks.length - 1 && pidx === parts.length - 1;
254195
254461
  const thoughtContent = part.replace(/^<(?:thought|think)(?:>| [^>]*>)/i, "").replace(/<\/(?:thought|think)>$/i, "").trim();
254196
- return /* @__PURE__ */ jsx_runtime14.jsx(ThoughtView, {
254197
- content: thoughtContent
254462
+ return /* @__PURE__ */ jsx_runtime14.jsx(CollapsibleThought, {
254463
+ content: thoughtContent,
254464
+ isStreaming,
254465
+ forceExpanded: expandCommands
254198
254466
  }, pidx);
254199
254467
  }
254200
254468
  if (part.trim() === "")
@@ -254248,8 +254516,11 @@ var init_bot_message = __esm(async () => {
254248
254516
  }, idx);
254249
254517
  }
254250
254518
  case "thinking": {
254251
- return /* @__PURE__ */ jsx_runtime14.jsx(ThoughtView, {
254252
- content: block.content
254519
+ return /* @__PURE__ */ jsx_runtime14.jsx(CollapsibleThought, {
254520
+ content: block.content,
254521
+ isStreaming: block.isStreaming,
254522
+ durationMs: block.durationMs,
254523
+ forceExpanded: expandCommands
254253
254524
  }, idx);
254254
254525
  }
254255
254526
  case "compaction": {
@@ -257402,6 +257673,17 @@ function SubscriptionSelectMenu(props) {
257402
257673
  /* @__PURE__ */ jsx_runtime32.jsx(Text3, {
257403
257674
  color: isSelected ? THEME.colors.brand : THEME.colors.text,
257404
257675
  children: item.label
257676
+ }),
257677
+ item.hint && /* @__PURE__ */ jsx_runtime32.jsx(Box_default, {
257678
+ marginLeft: 1,
257679
+ children: /* @__PURE__ */ jsx_runtime32.jsxs(Text3, {
257680
+ color: THEME.colors.muted,
257681
+ children: [
257682
+ "(",
257683
+ item.hint,
257684
+ ")"
257685
+ ]
257686
+ })
257405
257687
  })
257406
257688
  ]
257407
257689
  }, item.value);
@@ -257432,19 +257714,23 @@ var init_subscription_select_menu = __esm(async () => {
257432
257714
  SUBSCRIPTION_MENU_ITEMS = [
257433
257715
  {
257434
257716
  label: "Anthropic (Claude)",
257435
- value: "claude"
257717
+ value: "claude",
257718
+ hint: "Requires Claude Pro or Max"
257436
257719
  },
257437
257720
  {
257438
257721
  label: "GitHub (Copilot)",
257439
- value: "copilot"
257722
+ value: "copilot",
257723
+ hint: "Requires active Copilot plan"
257440
257724
  },
257441
257725
  {
257442
257726
  label: "Google (Gemini / Antigravity)",
257443
- value: "gemini"
257727
+ value: "gemini",
257728
+ hint: "Requires Gemini Advanced / Antigravity"
257444
257729
  },
257445
257730
  {
257446
257731
  label: "OpenAI (ChatGPT)",
257447
- value: "codex"
257732
+ value: "codex",
257733
+ hint: "Requires ChatGPT Plus / Pro"
257448
257734
  }
257449
257735
  ];
257450
257736
  });
@@ -259038,77 +259324,6 @@ var init_messages5 = __esm(() => {
259038
259324
  };
259039
259325
  });
259040
259326
 
259041
- // src/constants/prompts.ts
259042
- var getGrillPrompt = (userPrompt, projectContext) => `You are a principal software architect interviewing a developer to align on a technical specification before implementation.
259043
-
259044
- The user wants to implement: "${userPrompt}"
259045
-
259046
- ${projectContext ? `<project_context>
259047
- ${projectContext}
259048
- </project_context>
259049
- ` : ""}
259050
- Analyze the user's request and the project context. Generate between 1 and 4 targeted, high-impact multiple-choice questions to clarify critical requirements, architectural choices, tech stack decisions, and edge cases. If the task is simple and unambiguous, generate fewer questions (e.g. 1 or 2). Do not ask more than 4 questions.
259051
-
259052
- Requirements for questions:
259053
- 1. Focus on technical depth: probe architectural tradeoffs, API design, state management, edge cases, and error handling.
259054
- 2. Each question MUST have 2 to 3 distinct, concrete options representing clear design choices.
259055
- 3. Leverage the provided project context (if available) to tailor choices specifically to the existing codebase patterns.
259056
-
259057
- Return the output strictly as a JSON array of objects with the following schema:
259058
- [
259059
- {
259060
- "question": "Question text?",
259061
- "options": ["Option 1 (Design choice A)", "Option 2 (Design choice B)", "Option 3 (Design choice C)"]
259062
- }
259063
- ]
259064
-
259065
- Do not include any other text, markdown formatting, or code blocks. Return raw JSON only.`, getPlanPrompt = (originalPrompt, qaPairs) => `You are an autonomous software engineer.
259066
- The user wants to implement: "${originalPrompt}"
259067
- ${qaPairs && qaPairs.length > 0 ? `Here is the alignment interview results:
259068
- ${qaPairs.map((qa) => `Q: ${qa.question}
259069
- A: ${qa.answer}`).join(`
259070
-
259071
- `)}
259072
- ` : ""}
259073
- Please create a detailed, step-by-step implementation plan based on these requirements.
259074
- Do NOT execute any tools. Only describe the plan.
259075
- Start your response with '### Implementation Plan' and list the concrete steps.
259076
- Explain which files need to be created, modified, or deleted, and what the changes will be.`, getPlanExecutionPrompt = (originalPrompt, planText, qaPairs) => `You are an autonomous software engineer.
259077
- The user has approved the implementation plan for: "${originalPrompt}".
259078
-
259079
- ${qaPairs && qaPairs.length > 0 ? `Key alignment decisions from the interview:
259080
- ${qaPairs.map((qa) => `Q: ${qa.question}
259081
- A: ${qa.answer}`).join(`
259082
-
259083
- `)}
259084
-
259085
- ` : ""}Approved Implementation Plan:
259086
- ${planText}
259087
-
259088
- You are now in execution mode. Proceed with implementation immediately.
259089
- Execute the plan step-by-step using your available tools (file editing, file creation, bash commands).
259090
- Do not wait for further confirmation before starting tool execution.
259091
- Report your progress as you complete each step.
259092
- If a tool execution fails or encounters unexpected errors, do not repeat the failing action in a loop. Assess the failure, adjust your approach or remaining steps, and if critical information is missing, ask for clarification.`, getPlanRefinePrompt = (originalPrompt, previousPlan, feedback, qaPairs) => `You are an autonomous software engineer.
259093
- The user wants to implement: "${originalPrompt}"
259094
-
259095
- ${qaPairs && qaPairs.length > 0 ? `Previous alignment interview results:
259096
- ${qaPairs.map((qa) => `Q: ${qa.question}
259097
- A: ${qa.answer}`).join(`
259098
-
259099
- `)}
259100
-
259101
- ` : ""}Previous Implementation Plan:
259102
- ${previousPlan}
259103
-
259104
- The user provided the following feedback to refine the plan:
259105
- "${feedback}"
259106
-
259107
- Please update and refine the detailed, step-by-step implementation plan based on this feedback.
259108
- Do NOT execute any tools. Only describe the updated plan.
259109
- Start your response with '### Implementation Plan' and list the concrete updated steps.
259110
- Explain which files need to be created, modified, or deleted, and what the changes will be.`;
259111
-
259112
259327
  // ../../node_modules/.bun/zustand@5.0.14+93dbe784b8baa420/node_modules/zustand/esm/vanilla.mjs
259113
259328
  var createStoreImpl = (createState) => {
259114
259329
  let state;
@@ -260126,6 +260341,8 @@ function parseErrorMessage(err, context) {
260126
260341
  if (extracted)
260127
260342
  return extracted;
260128
260343
  }
260344
+ if (typeof parsed.error_details?.message === "string")
260345
+ return parsed.error_details.message;
260129
260346
  if (typeof parsed.error?.message === "string")
260130
260347
  return parsed.error.message;
260131
260348
  if (typeof parsed.message === "string")
@@ -260140,6 +260357,8 @@ function parseErrorMessage(err, context) {
260140
260357
  const jsonStr = str.slice(firstBrace, lastBrace + 1);
260141
260358
  try {
260142
260359
  const parsed = JSON.parse(jsonStr);
260360
+ if (parsed?.error_details?.message && typeof parsed.error_details.message === "string")
260361
+ return parsed.error_details.message;
260143
260362
  if (parsed?.error?.message && typeof parsed.error.message === "string")
260144
260363
  return parsed.error.message;
260145
260364
  if (parsed?.message && typeof parsed.message === "string")
@@ -260253,6 +260472,26 @@ function parseError(err, context) {
260253
260472
  hint: "Please switch models using `/model` (e.g. glm-5.2, gpt-5.5, claude-opus-4-6, glm-4-plus)."
260254
260473
  };
260255
260474
  }
260475
+ const isCopilotNoAccess = combined.includes("no_copilot_access") || combined.includes("no access to github copilot") || combined.includes("no active github copilot subscription");
260476
+ if (isCopilotNoAccess) {
260477
+ let cause = explicitCause;
260478
+ if (!cause) {
260479
+ const match = fullMessage.match(/No access to GitHub Copilot found[^\n]*/i);
260480
+ if (match) {
260481
+ cause = match[0].trim();
260482
+ } else {
260483
+ const parts = fullMessage.split(`
260484
+ `);
260485
+ cause = parts.length > 1 ? parts.slice(1).join(`
260486
+ `).trim() : fullMessage;
260487
+ }
260488
+ }
260489
+ return {
260490
+ message: "No active GitHub Copilot subscription found.",
260491
+ cause,
260492
+ hint: "Please subscribe at https://github.com/github-copilot/signup, ensure your browser is logged into the correct GitHub account, or use BYOK / Ollama instead."
260493
+ };
260494
+ }
260256
260495
  const isAuthError = combined.includes("401") || combined.includes("unauthorized") || combined.includes("invalid token") || combined.includes("access token expired") || combined.includes("session expired") || combined.includes("session not found");
260257
260496
  if (isAuthError) {
260258
260497
  const parts = fullMessage.split(`
@@ -260863,6 +261102,21 @@ async function processAgentStream({
260863
261102
  }) {
260864
261103
  let pendingEvents = [];
260865
261104
  let flushTimeout = null;
261105
+ let thinkingStartTime = null;
261106
+ const finalizeThinking = (bList) => {
261107
+ if (thinkingStartTime) {
261108
+ const elapsed = Date.now() - thinkingStartTime;
261109
+ for (let i = bList.length - 1;i >= 0; i--) {
261110
+ const b = bList[i];
261111
+ if (b && b.type === "thinking" && b.isStreaming) {
261112
+ b.isStreaming = false;
261113
+ b.durationMs = elapsed;
261114
+ break;
261115
+ }
261116
+ }
261117
+ thinkingStartTime = null;
261118
+ }
261119
+ };
260866
261120
  const flush = () => {
260867
261121
  if (pendingEvents.length === 0)
260868
261122
  return;
@@ -260879,6 +261133,7 @@ async function processAgentStream({
260879
261133
  blocks.push({ type: "text", content: "Thinking..." });
260880
261134
  break;
260881
261135
  case "AgentError": {
261136
+ finalizeThinking(blocks);
260882
261137
  const lastBlock = blocks[blocks.length - 1];
260883
261138
  if (lastBlock && (lastBlock.type === "thinking" || lastBlock.type === "text" && isStatusMessage(lastBlock.content))) {
260884
261139
  blocks.pop();
@@ -260893,6 +261148,7 @@ async function processAgentStream({
260893
261148
  break;
260894
261149
  }
260895
261150
  case "AgentInterrupt": {
261151
+ finalizeThinking(blocks);
260896
261152
  const lastBlock = blocks[blocks.length - 1];
260897
261153
  if (lastBlock && lastBlock.type === "text" && isStatusMessage(lastBlock.content)) {
260898
261154
  lastBlock.content = "";
@@ -260918,6 +261174,7 @@ async function processAgentStream({
260918
261174
  break;
260919
261175
  }
260920
261176
  case "StreamChunk": {
261177
+ finalizeThinking(blocks);
260921
261178
  if (!event.content)
260922
261179
  break;
260923
261180
  const lastBlock = blocks[blocks.length - 1];
@@ -260936,18 +261193,23 @@ async function processAgentStream({
260936
261193
  const chunk = event.content || "";
260937
261194
  if (!chunk)
260938
261195
  break;
261196
+ if (!thinkingStartTime) {
261197
+ thinkingStartTime = Date.now();
261198
+ }
260939
261199
  const lastBlock = blocks[blocks.length - 1];
260940
261200
  if (lastBlock && lastBlock.type === "thinking") {
260941
261201
  lastBlock.content += chunk;
261202
+ lastBlock.isStreaming = true;
260942
261203
  } else {
260943
261204
  if (lastBlock && lastBlock.type === "text" && isStatusMessage(lastBlock.content)) {
260944
261205
  blocks.pop();
260945
261206
  }
260946
- blocks.push({ type: "thinking", content: chunk });
261207
+ blocks.push({ type: "thinking", content: chunk, isStreaming: true });
260947
261208
  }
260948
261209
  break;
260949
261210
  }
260950
261211
  case "ToolCallStart": {
261212
+ finalizeThinking(blocks);
260951
261213
  const lastBlock = blocks[blocks.length - 1];
260952
261214
  if (lastBlock && lastBlock.type === "text" && isStatusMessage(lastBlock.content)) {
260953
261215
  blocks.pop();
@@ -261017,7 +261279,17 @@ async function processAgentStream({
261017
261279
  setActiveMessages((prev) => prev.map((msg) => {
261018
261280
  if (msg.id !== assistantMsgId)
261019
261281
  return msg;
261020
- const blocks = (msg.blocks || []).filter((b) => !(b.type === "text" && isStatusMessage(b.content)));
261282
+ const finalElapsed = thinkingStartTime ? Date.now() - thinkingStartTime : undefined;
261283
+ const blocks = (msg.blocks || []).map((b) => {
261284
+ if (b.type === "thinking" && b.isStreaming) {
261285
+ return {
261286
+ ...b,
261287
+ isStreaming: false,
261288
+ durationMs: b.durationMs ?? finalElapsed
261289
+ };
261290
+ }
261291
+ return b;
261292
+ }).filter((b) => !(b.type === "text" && isStatusMessage(b.content)));
261021
261293
  return { ...msg, blocks };
261022
261294
  }));
261023
261295
  }
@@ -261271,6 +261543,10 @@ Please open [${uri}](${uri}) in your browser to authorize.`;
261271
261543
  if (code && !code.endsWith("-AUTH")) {
261272
261544
  promptText = `
261273
261545
  Please open [${uri}](${uri}) on your device and enter code: \`${code}\``;
261546
+ }
261547
+ if (item.value === "copilot") {
261548
+ promptText += `
261549
+ *(Note: Ensure your browser is logged into the GitHub account that owns your active Copilot subscription)*`;
261274
261550
  }
261275
261551
  setActiveMessages([
261276
261552
  {
@@ -261332,7 +261608,7 @@ ${formatSubscriptionCard(bundle, targetModel)}`
261332
261608
  addToast(formatSubscriptionToast(bundle, targetModel), "success");
261333
261609
  } catch (err) {
261334
261610
  const parsed = parseError(err);
261335
- const errorText = `Subscription verification failed: ${parsed.message}`;
261611
+ const errorText = parsed.message.startsWith("Subscription verification failed") || parsed.message.startsWith("No active GitHub Copilot subscription") ? parsed.message : `Subscription verification failed: ${parsed.message}`;
261336
261612
  setAuthError(errorText);
261337
261613
  setStaticMessages((prev) => [...prev, ...useCliStore.getState().activeMessages]);
261338
261614
  setActiveMessages([
@@ -261661,6 +261937,7 @@ ${formatSubscriptionCard(bundle, targetModel)}`
261661
261937
  try {
261662
261938
  await agent.loadContext(item.value);
261663
261939
  const resumedMessages = convertAgentMessagesToTuiMessages(agent.messages);
261940
+ console.clear();
261664
261941
  setStaticMessages([{ id: "header", role: "header" }, ...resumedMessages]);
261665
261942
  setStaticKey((k) => k + 1);
261666
261943
  setActiveMessages([]);
@@ -262089,8 +262366,13 @@ function useAgentSession({
262089
262366
  } catch {}
262090
262367
  const nonSystem = (agent.messages || []).filter((m) => m.role !== "system");
262091
262368
  if (nonSystem.length > 0) {
262369
+ const currentStatic = useCliStore.getState().staticMessages;
262370
+ if (currentStatic.length > 1) {
262371
+ return;
262372
+ }
262092
262373
  const resumed = convertAgentMessagesToTuiMessages(agent.messages);
262093
262374
  if (resumed.length > 0) {
262375
+ console.clear();
262094
262376
  setStaticMessages([{ id: "header", role: "header" }, ...resumed]);
262095
262377
  setStaticKey((k) => k + 1);
262096
262378
  }
@@ -263290,6 +263572,10 @@ ${decStatus}
263290
263572
  }
263291
263573
  return;
263292
263574
  }
263575
+ if (text.trim() === "/ask") {
263576
+ addToast("Please enter your question after /ask (e.g. /ask how does auth work?)", "info");
263577
+ return;
263578
+ }
263293
263579
  if (text.trim() === "/plan" || text.trim().startsWith("/plan ")) {
263294
263580
  const goal = text.trim().slice("/plan".length).trim();
263295
263581
  if (!goal) {
@@ -263567,7 +263853,16 @@ If it doesn't open automatically, please click here:
263567
263853
  const promptToProcess = currentRawPrompt;
263568
263854
  let displayText = promptToProcess;
263569
263855
  let executionPrompt = promptToProcess;
263570
- if (promptToProcess.startsWith("/")) {
263856
+ let isAskPrompt = false;
263857
+ if (promptToProcess.trim() === "/ask" || promptToProcess.trim().startsWith("/ask ") || promptToProcess.trim().startsWith("/ask\t")) {
263858
+ const question = promptToProcess.trim().replace(/^\/ask\s*/, "").trim();
263859
+ if (question) {
263860
+ isAskPrompt = true;
263861
+ executionPrompt = getAskPrompt(question);
263862
+ displayText = `/ask ${question}`;
263863
+ }
263864
+ }
263865
+ if (!isAskPrompt && promptToProcess.startsWith("/")) {
263571
263866
  const [firstToken, ...restArgs] = promptToProcess.split(/\s+/);
263572
263867
  let potentialCmd = firstToken ? firstToken.slice(1).toLowerCase() : "";
263573
263868
  const isExplicitSkillPrefix = potentialCmd.startsWith("skill:");
@@ -263575,6 +263870,7 @@ If it doesn't open automatically, please click here:
263575
263870
  potentialCmd = potentialCmd.slice("skill:".length);
263576
263871
  }
263577
263872
  const coreBuiltins = new Set([
263873
+ "ask",
263578
263874
  "clear",
263579
263875
  "context",
263580
263876
  "copy",
@@ -263638,7 +263934,8 @@ If it doesn't open automatically, please click here:
263638
263934
  try {
263639
263935
  const stream = runAgentLoop(agent, {
263640
263936
  content: executionPrompt,
263641
- displayText
263937
+ displayText,
263938
+ readOnly: isAskPrompt
263642
263939
  });
263643
263940
  await processAgentStream({
263644
263941
  stream,
@@ -263706,6 +264003,7 @@ If it doesn't open automatically, please click here:
263706
264003
  setSessionsData,
263707
264004
  setSettingsFollowUpMode,
263708
264005
  setSettingsNonWorkspace,
264006
+ setSettingsPathGuard,
263709
264007
  setSettingsSteeringMode,
263710
264008
  setSettingsThinkingLevel,
263711
264009
  setSettingsToolPermission,
@@ -263713,7 +264011,6 @@ If it doesn't open automatically, please click here:
263713
264011
  setStaticMessages,
263714
264012
  setStaticKey,
263715
264013
  setOllamaModels,
263716
- setCurrentPlannedPrompt,
263717
264014
  authMethod,
263718
264015
  selectedProvider,
263719
264016
  setActiveModel,
@@ -264083,7 +264380,8 @@ function parseCliArgs(args) {
264083
264380
  "link",
264084
264381
  "key",
264085
264382
  "resume",
264086
- "docs"
264383
+ "docs",
264384
+ "ask"
264087
264385
  ];
264088
264386
  let command;
264089
264387
  let prompt;
@@ -264136,6 +264434,7 @@ a coding agent that lives in your terminal.
264136
264434
  Usage:
264137
264435
  december Launch interactive TUI session
264138
264436
  december "<prompt>" Execute headless agent task
264437
+ december ask "<question>" Ask a question about the codebase (read-only Q&A mode)
264139
264438
  december resume [session-id] Resume the most recent session or specified session ID
264140
264439
  december auth [status|import] Inspect active subscriptions and authentication status
264141
264440
  december link <provider> Link AI subscription (copilot, claude, chatgpt, gemini)
@@ -264273,6 +264572,17 @@ ${formattedStdin}`;
264273
264572
  await handleDocsCommand({ section });
264274
264573
  process.exit(0);
264275
264574
  }
264575
+ const isAskMode = parsedArgs.command === "ask";
264576
+ if (isAskMode) {
264577
+ const question = parsedArgs.positionals.slice(1).join(" ").trim() || parsedArgs.prompt;
264578
+ if (!question) {
264579
+ console.error("Error: Please provide a question to ask.");
264580
+ console.error(`Usage: december ask "<question>"
264581
+ `);
264582
+ process.exit(1);
264583
+ }
264584
+ parsedArgs.prompt = question;
264585
+ }
264276
264586
  if (parsedArgs.prompt) {
264277
264587
  const [
264278
264588
  { AgentHarness },
@@ -264283,7 +264593,8 @@ ${formattedStdin}`;
264283
264593
  { runHeadlessTask },
264284
264594
  { localOperations, setActiveScopeDir },
264285
264595
  { instantiateProvider },
264286
- { ensureValidModelForProvider }
264596
+ { ensureValidModelForProvider },
264597
+ { getAskPrompt }
264287
264598
  ] = await Promise.all([
264288
264599
  Promise.resolve().then(() => (init_src4(), exports_src3)),
264289
264600
  Promise.resolve().then(() => (init_src3(), exports_src2)),
@@ -264293,7 +264604,8 @@ ${formattedStdin}`;
264293
264604
  Promise.resolve().then(() => (init_headless_runner(), exports_headless_runner)),
264294
264605
  Promise.resolve().then(() => (init_local_operations(), exports_local_operations)),
264295
264606
  Promise.resolve().then(() => (init_provider_factory(), exports_provider_factory)),
264296
- Promise.resolve().then(() => (init_models5(), exports_models2))
264607
+ Promise.resolve().then(() => (init_models5(), exports_models2)),
264608
+ Promise.resolve().then(() => exports_prompts)
264297
264609
  ]);
264298
264610
  if (parsedArgs.scope) {
264299
264611
  setActiveScopeDir(parsedArgs.scope);
@@ -264323,22 +264635,31 @@ ${formattedStdin}`;
264323
264635
  const config = await loadConfig();
264324
264636
  const activeProvider = providerConfig?.provider || config.activeProvider || "gemini";
264325
264637
  const initialModel = providerConfig?.authMethod === "december" ? "december-auto" : parsedArgs.model || providerConfig?.model || ensureValidModelForProvider(activeProvider, config.activeModel);
264638
+ const effectiveTools = isAskMode ? [
264639
+ ReadFileTool,
264640
+ LsTool,
264641
+ FindFilesTool,
264642
+ GrepSearchTool,
264643
+ AskQuestionTool,
264644
+ BrowserTool,
264645
+ WebSearchTool
264646
+ ] : [
264647
+ BashTool,
264648
+ ReadFileTool,
264649
+ WriteFileTool,
264650
+ LsTool,
264651
+ EditFileTool,
264652
+ EditDiffTool,
264653
+ FindFilesTool,
264654
+ GrepSearchTool,
264655
+ AskQuestionTool,
264656
+ ManageTaskTool,
264657
+ BrowserTool,
264658
+ WebSearchTool
264659
+ ];
264326
264660
  const harness = new AgentHarness({
264327
264661
  llm,
264328
- tools: [
264329
- BashTool,
264330
- ReadFileTool,
264331
- WriteFileTool,
264332
- LsTool,
264333
- EditFileTool,
264334
- EditDiffTool,
264335
- FindFilesTool,
264336
- GrepSearchTool,
264337
- AskQuestionTool,
264338
- ManageTaskTool,
264339
- BrowserTool,
264340
- WebSearchTool
264341
- ],
264662
+ tools: effectiveTools,
264342
264663
  operations: localOperations,
264343
264664
  modelOptions: {
264344
264665
  model: initialModel,
@@ -264355,14 +264676,18 @@ ${formattedStdin}`;
264355
264676
  const agent = harness.getAgent();
264356
264677
  await agent.loadContext();
264357
264678
  if (!parsedArgs.json) {
264358
- console.log(`
264679
+ console.log(isAskMode ? `
264680
+ Executing Ask Q&A: "${parsedArgs.prompt}"
264681
+ ` : `
264359
264682
  Executing Headless Task: "${parsedArgs.prompt}"
264360
264683
  `);
264361
264684
  }
264362
- const result = await runHeadlessTask(parsedArgs.prompt, {
264685
+ const effectivePrompt = isAskMode ? getAskPrompt(parsedArgs.prompt) : parsedArgs.prompt;
264686
+ const result = await runHeadlessTask(effectivePrompt, {
264363
264687
  agent,
264364
264688
  nonInteractive: parsedArgs.yes,
264365
- json: parsedArgs.json
264689
+ json: parsedArgs.json,
264690
+ readOnly: isAskMode
264366
264691
  });
264367
264692
  process.exit(result.success ? 0 : 1);
264368
264693
  }
@@ -264465,7 +264790,19 @@ Executing Headless Task: "${parsedArgs.prompt}"
264465
264790
  followUpMode: config.followUpMode || "all"
264466
264791
  });
264467
264792
  const agent = harness.getAgent();
264468
- agent.loadContext().catch(() => {});
264793
+ if (targetSessionId) {
264794
+ await agent.loadContext().catch(() => {});
264795
+ const nonSystem = (agent.messages || []).filter((m) => m.role !== "system");
264796
+ if (nonSystem.length > 0) {
264797
+ await Promise.resolve().then(() => init_use_agent_runner());
264798
+ const resumed = convertAgentMessagesToTuiMessages(agent.messages);
264799
+ if (resumed.length > 0) {
264800
+ useCliStore.getState().setStaticMessages([{ id: "header", role: "header" }, ...resumed]);
264801
+ }
264802
+ }
264803
+ } else {
264804
+ agent.loadContext().catch(() => {});
264805
+ }
264469
264806
  function AppWrapper(props) {
264470
264807
  const [latestVersion, setLatestVersion] = React.useState(undefined);
264471
264808
  React.useEffect(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trydecember/cli",
3
- "version": "0.3.28",
3
+ "version": "0.3.29",
4
4
  "description": "a coding agent for terminal and cloud.",
5
5
  "homepage": "https://trydecember.com",
6
6
  "repository": {