@pikaa-ai/pikaa 0.2.4 → 0.2.5

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/cli.js CHANGED
@@ -475,6 +475,7 @@ function buildSystemPrompt(params) {
475
475
  "- NEVER create temporary scripts, scratch files, or chunk files (e.g. `_tmp_*.ps1`, `_tmp_*.txt`, `split_*.py`) in the workspace to manipulate, split, or read files.",
476
476
  "- NEVER execute shell or PowerShell scripts as a workaround for reading, writing, or editing text files.",
477
477
  "- Use the `shell` tool ONLY for running tests, build targets, package installations, or checking environment/git status.",
478
+ "- When user requirements are ambiguous or require architectural decisions, use `request_user_input` or `ask_question` to present 2-4 clear options. Prefix your recommended choice with `(Recommended)` (e.g. `['(Recommended) Option A', 'Option B']`).",
478
479
  "- You may be in a dirty git worktree. NEVER revert existing changes made by the user.",
479
480
  "- NEVER use destructive commands like `git reset --hard` or `git checkout --`.",
480
481
  "- Be concise, direct, and act surgically. Write clean, correct code with minimal necessary modifications."
@@ -644,6 +645,15 @@ async function runTurn(session, turnContext, input) {
644
645
  description,
645
646
  command
646
647
  });
648
+ },
649
+ requestInput: async (question, options) => {
650
+ const questionId = `quest_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
651
+ return session.requestUserQuestion({
652
+ questionId,
653
+ turnId,
654
+ question,
655
+ options
656
+ });
647
657
  }
648
658
  });
649
659
  const functionOutputItem = {
@@ -815,6 +825,7 @@ class Session {
815
825
  status = "idle";
816
826
  eventListeners = [];
817
827
  pendingApprovals = new Map;
828
+ pendingUserQuestions = new Map;
818
829
  submissionResolvers = [];
819
830
  submissionQueue = [];
820
831
  isTerminated = false;
@@ -930,6 +941,35 @@ class Session {
930
941
  resolver(approved);
931
942
  }
932
943
  }
944
+ requestUserQuestion(params) {
945
+ this.emitEvent({
946
+ type: "UserQuestionRequired",
947
+ questionId: params.questionId,
948
+ turnId: params.turnId,
949
+ question: params.question,
950
+ options: params.options
951
+ });
952
+ this.emitEvent({
953
+ type: "StatusChanged",
954
+ status: "waiting_user_input"
955
+ });
956
+ return new Promise((resolve2) => {
957
+ this.pendingUserQuestions.set(params.questionId, (answer) => {
958
+ this.emitEvent({
959
+ type: "StatusChanged",
960
+ status: "running"
961
+ });
962
+ resolve2(answer);
963
+ });
964
+ });
965
+ }
966
+ resolveUserQuestion(questionId, answer) {
967
+ const resolver = this.pendingUserQuestions.get(questionId);
968
+ if (resolver) {
969
+ this.pendingUserQuestions.delete(questionId);
970
+ resolver(answer);
971
+ }
972
+ }
933
973
  async submit(op) {
934
974
  const subId = `sub_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
935
975
  const submission = {
@@ -1256,14 +1296,14 @@ var writeFileTool = {
1256
1296
  // src/tools/handlers/request-user-input.ts
1257
1297
  var requestUserInputTool = {
1258
1298
  name: "request_user_input",
1259
- description: "Ask the user a clarifying question or present options when instructions are ambiguous.",
1299
+ description: "Ask the user a clarifying question or present multiple-choice options when instructions are ambiguous. You can include recommendations prefixing choices with '(Recommended)'.",
1260
1300
  parameters: {
1261
1301
  type: "object",
1262
1302
  properties: {
1263
1303
  question: { type: "string", description: "The specific question to ask the user." },
1264
1304
  options: {
1265
1305
  type: "array",
1266
- description: "Optional list of choices for the user to select from.",
1306
+ description: "Optional list of choices for the user to select from (e.g. ['(Recommended) Option A', 'Option B']).",
1267
1307
  items: { type: "string", description: "Choice text" }
1268
1308
  }
1269
1309
  },
@@ -1272,19 +1312,31 @@ var requestUserInputTool = {
1272
1312
  async execute(args, ctx) {
1273
1313
  const question = String(args.question || "");
1274
1314
  const options = Array.isArray(args.options) ? args.options : [];
1315
+ if (ctx.requestInput) {
1316
+ const answer = await ctx.requestInput(question, options);
1317
+ return {
1318
+ output: `User responded: "${answer}"`
1319
+ };
1320
+ }
1275
1321
  if (ctx.requestApproval) {
1276
1322
  const description = options.length > 0 ? `${question}
1277
- Options: ${options.map((o, idx) => `${idx + 1}. ${o}`).join(", ")}` : question;
1323
+ Options:
1324
+ ${options.map((o, idx) => ` [${idx + 1}] ${o}`).join(`
1325
+ `)}` : question;
1278
1326
  const answered = await ctx.requestApproval(description);
1279
1327
  return {
1280
1328
  output: answered ? "User confirmed to proceed." : "User declined or requested alternative."
1281
1329
  };
1282
1330
  }
1283
1331
  return {
1284
- output: `Question sent to user: '${question}'. Waiting for input.`
1332
+ output: `Question presented to user: '${question}' with options [${options.join(", ")}].`
1285
1333
  };
1286
1334
  }
1287
1335
  };
1336
+ var askQuestionTool = {
1337
+ ...requestUserInputTool,
1338
+ name: "ask_question"
1339
+ };
1288
1340
  // src/search/engine.ts
1289
1341
  import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2, existsSync as existsSync4 } from "fs";
1290
1342
  import { resolve as resolve4, relative, join, extname } from "path";
@@ -1943,6 +1995,7 @@ function createDefaultTools(options = {}) {
1943
1995
  router2.register(writeFileTool);
1944
1996
  router2.register(listDirTool);
1945
1997
  router2.register(requestUserInputTool);
1998
+ router2.register(askQuestionTool);
1946
1999
  for (const tool of createFileSearchTools()) {
1947
2000
  router2.register(tool);
1948
2001
  }
@@ -5399,6 +5452,72 @@ async function promptToolApproval(params) {
5399
5452
  });
5400
5453
  return decision;
5401
5454
  }
5455
+ async function promptUserQuestion(params) {
5456
+ const { question, options = [] } = params;
5457
+ const boxWidth = Math.min(process.stdout.columns ?? 80, 75);
5458
+ const border = "\u2500".repeat(Math.max(10, boxWidth - 20));
5459
+ console.log(`
5460
+ ${style.cyan("\u250C\u2500\u2500")} ${style.bold("AI Question")} ${style.cyan(border)}`);
5461
+ const qLines = question.split(`
5462
+ `);
5463
+ for (const line of qLines) {
5464
+ console.log(` ${style.cyan("\u2502")} ${style.bold(line)}`);
5465
+ }
5466
+ if (options.length > 0) {
5467
+ console.log(` ${style.cyan("\u2502")}`);
5468
+ options.forEach((opt, idx) => {
5469
+ const numTag = style.cyan(`[${idx + 1}]`);
5470
+ const isRec = opt.includes("(Recommended)") || opt.includes("(recommended)");
5471
+ const optText = isRec ? opt.replace(/\(Recommended\)/i, style.green("(Recommended)")) : opt;
5472
+ console.log(` ${style.cyan("\u2502")} ${numTag} ${optText}`);
5473
+ });
5474
+ }
5475
+ console.log(` ${style.cyan("\u2514" + "\u2500".repeat(Math.max(10, boxWidth - 4)))}
5476
+ `);
5477
+ if (!process.stdin.isTTY || false || !process.stdin.readable) {
5478
+ return options[0] || "yes";
5479
+ }
5480
+ return new Promise((resolve13) => {
5481
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
5482
+ const promptLabel = options.length > 0 ? `Select [1-${options.length}] or type custom response: ` : `Your response: `;
5483
+ rl.question(` ${style.bold(promptLabel)}`, (answer) => {
5484
+ rl.close();
5485
+ const trimmed = answer.trim();
5486
+ if (!trimmed) {
5487
+ const fallback = options[0] || "";
5488
+ console.log(style.dim(` \u21B3 Default: ${fallback || "(empty)"}
5489
+ `));
5490
+ resolve13(fallback);
5491
+ return;
5492
+ }
5493
+ const num = parseInt(trimmed, 10);
5494
+ if (!isNaN(num) && num >= 1 && num <= options.length) {
5495
+ const picked = options[num - 1];
5496
+ console.log(style.green(` \u2714 Selected: ${picked}
5497
+ `));
5498
+ resolve13(picked);
5499
+ return;
5500
+ }
5501
+ if (options.length === 2) {
5502
+ if (/^(y|yes)$/i.test(trimmed)) {
5503
+ console.log(style.green(` \u2714 Selected: ${options[0]}
5504
+ `));
5505
+ resolve13(options[0]);
5506
+ return;
5507
+ }
5508
+ if (/^(n|no)$/i.test(trimmed)) {
5509
+ console.log(style.green(` \u2714 Selected: ${options[1]}
5510
+ `));
5511
+ resolve13(options[1]);
5512
+ return;
5513
+ }
5514
+ }
5515
+ console.log(style.green(` \u2714 Answer: ${trimmed}
5516
+ `));
5517
+ resolve13(trimmed);
5518
+ });
5519
+ });
5520
+ }
5402
5521
 
5403
5522
  // src/cli/ui/markdown.ts
5404
5523
  class MarkdownHighlighter {
@@ -5750,6 +5869,10 @@ class CliRepl {
5750
5869
  this.spinner.stop();
5751
5870
  await this.handleInteractiveApproval(msg);
5752
5871
  break;
5872
+ case "UserQuestionRequired":
5873
+ this.spinner.stop();
5874
+ await this.handleInteractiveUserQuestion(msg);
5875
+ break;
5753
5876
  case "TurnCompleted":
5754
5877
  if (this.reasoningStarted) {
5755
5878
  console.log(style.dim(`
@@ -5828,6 +5951,18 @@ class CliRepl {
5828
5951
  this.session.resolveApproval(msg.approvalId, false);
5829
5952
  }
5830
5953
  }
5954
+ async handleInteractiveUserQuestion(msg) {
5955
+ try {
5956
+ const answer = await promptUserQuestion({
5957
+ question: msg.question,
5958
+ options: msg.options
5959
+ });
5960
+ this.session.resolveUserQuestion(msg.questionId, answer);
5961
+ this.spinner.start(`Processing response and continuing...`);
5962
+ } catch {
5963
+ this.session.resolveUserQuestion(msg.questionId, "");
5964
+ }
5965
+ }
5831
5966
  async start() {
5832
5967
  const creds = new CredentialsStore().load();
5833
5968
  const accountUser = creds?.user?.username || creds?.user?.email || (creds?.accessToken ? "Authenticated" : undefined);
@@ -5967,7 +6102,7 @@ async function main() {
5967
6102
  } else if (arg === "--mcp") {
5968
6103
  mcpConfigFile = args[++i];
5969
6104
  } else if (arg === "--version" || arg === "-v" || arg === "version") {
5970
- console.log(`pikaa v0.2.4`);
6105
+ console.log(`pikaa v0.2.5`);
5971
6106
  process.exit(0);
5972
6107
  } else if (arg === "--help" || arg === "-h") {
5973
6108
  printCliHelp();
package/dist/index.js CHANGED
@@ -658,14 +658,14 @@ var writeFileTool = {
658
658
  // src/tools/handlers/request-user-input.ts
659
659
  var requestUserInputTool = {
660
660
  name: "request_user_input",
661
- description: "Ask the user a clarifying question or present options when instructions are ambiguous.",
661
+ description: "Ask the user a clarifying question or present multiple-choice options when instructions are ambiguous. You can include recommendations prefixing choices with '(Recommended)'.",
662
662
  parameters: {
663
663
  type: "object",
664
664
  properties: {
665
665
  question: { type: "string", description: "The specific question to ask the user." },
666
666
  options: {
667
667
  type: "array",
668
- description: "Optional list of choices for the user to select from.",
668
+ description: "Optional list of choices for the user to select from (e.g. ['(Recommended) Option A', 'Option B']).",
669
669
  items: { type: "string", description: "Choice text" }
670
670
  }
671
671
  },
@@ -674,19 +674,31 @@ var requestUserInputTool = {
674
674
  async execute(args, ctx) {
675
675
  const question = String(args.question || "");
676
676
  const options = Array.isArray(args.options) ? args.options : [];
677
+ if (ctx.requestInput) {
678
+ const answer = await ctx.requestInput(question, options);
679
+ return {
680
+ output: `User responded: "${answer}"`
681
+ };
682
+ }
677
683
  if (ctx.requestApproval) {
678
684
  const description = options.length > 0 ? `${question}
679
- Options: ${options.map((o, idx) => `${idx + 1}. ${o}`).join(", ")}` : question;
685
+ Options:
686
+ ${options.map((o, idx) => ` [${idx + 1}] ${o}`).join(`
687
+ `)}` : question;
680
688
  const answered = await ctx.requestApproval(description);
681
689
  return {
682
690
  output: answered ? "User confirmed to proceed." : "User declined or requested alternative."
683
691
  };
684
692
  }
685
693
  return {
686
- output: `Question sent to user: '${question}'. Waiting for input.`
694
+ output: `Question presented to user: '${question}' with options [${options.join(", ")}].`
687
695
  };
688
696
  }
689
697
  };
698
+ var askQuestionTool = {
699
+ ...requestUserInputTool,
700
+ name: "ask_question"
701
+ };
690
702
  // src/search/engine.ts
691
703
  import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2, existsSync as existsSync4 } from "fs";
692
704
  import { resolve as resolve4, relative, join, extname } from "path";
@@ -1345,6 +1357,7 @@ function createDefaultTools(options = {}) {
1345
1357
  router2.register(writeFileTool);
1346
1358
  router2.register(listDirTool);
1347
1359
  router2.register(requestUserInputTool);
1360
+ router2.register(askQuestionTool);
1348
1361
  for (const tool of createFileSearchTools()) {
1349
1362
  router2.register(tool);
1350
1363
  }
@@ -1445,6 +1458,7 @@ function buildSystemPrompt(params) {
1445
1458
  "- NEVER create temporary scripts, scratch files, or chunk files (e.g. `_tmp_*.ps1`, `_tmp_*.txt`, `split_*.py`) in the workspace to manipulate, split, or read files.",
1446
1459
  "- NEVER execute shell or PowerShell scripts as a workaround for reading, writing, or editing text files.",
1447
1460
  "- Use the `shell` tool ONLY for running tests, build targets, package installations, or checking environment/git status.",
1461
+ "- When user requirements are ambiguous or require architectural decisions, use `request_user_input` or `ask_question` to present 2-4 clear options. Prefix your recommended choice with `(Recommended)` (e.g. `['(Recommended) Option A', 'Option B']`).",
1448
1462
  "- You may be in a dirty git worktree. NEVER revert existing changes made by the user.",
1449
1463
  "- NEVER use destructive commands like `git reset --hard` or `git checkout --`.",
1450
1464
  "- Be concise, direct, and act surgically. Write clean, correct code with minimal necessary modifications."
@@ -1672,6 +1686,15 @@ async function runTurn(session, turnContext, input) {
1672
1686
  description,
1673
1687
  command
1674
1688
  });
1689
+ },
1690
+ requestInput: async (question, options) => {
1691
+ const questionId = `quest_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
1692
+ return session.requestUserQuestion({
1693
+ questionId,
1694
+ turnId,
1695
+ question,
1696
+ options
1697
+ });
1675
1698
  }
1676
1699
  });
1677
1700
  const functionOutputItem = {
@@ -1809,6 +1832,7 @@ class Session {
1809
1832
  status = "idle";
1810
1833
  eventListeners = [];
1811
1834
  pendingApprovals = new Map;
1835
+ pendingUserQuestions = new Map;
1812
1836
  submissionResolvers = [];
1813
1837
  submissionQueue = [];
1814
1838
  isTerminated = false;
@@ -1924,6 +1948,35 @@ class Session {
1924
1948
  resolver(approved);
1925
1949
  }
1926
1950
  }
1951
+ requestUserQuestion(params) {
1952
+ this.emitEvent({
1953
+ type: "UserQuestionRequired",
1954
+ questionId: params.questionId,
1955
+ turnId: params.turnId,
1956
+ question: params.question,
1957
+ options: params.options
1958
+ });
1959
+ this.emitEvent({
1960
+ type: "StatusChanged",
1961
+ status: "waiting_user_input"
1962
+ });
1963
+ return new Promise((resolve6) => {
1964
+ this.pendingUserQuestions.set(params.questionId, (answer) => {
1965
+ this.emitEvent({
1966
+ type: "StatusChanged",
1967
+ status: "running"
1968
+ });
1969
+ resolve6(answer);
1970
+ });
1971
+ });
1972
+ }
1973
+ resolveUserQuestion(questionId, answer) {
1974
+ const resolver = this.pendingUserQuestions.get(questionId);
1975
+ if (resolver) {
1976
+ this.pendingUserQuestions.delete(questionId);
1977
+ resolver(answer);
1978
+ }
1979
+ }
1927
1980
  async submit(op) {
1928
1981
  const subId = `sub_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
1929
1982
  const submission = {
@@ -4127,6 +4180,7 @@ export {
4127
4180
  TurnContext,
4128
4181
  WorktreeManager,
4129
4182
  applyPatchTool,
4183
+ askQuestionTool,
4130
4184
  buildSystemPrompt,
4131
4185
  captureWorldState,
4132
4186
  compactHistory,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikaa-ai/pikaa",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "PIKAA CLI - AI coding agent that runs locally in your terminal.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",