@pikaa-ai/pikaa 0.2.3 → 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/bin/pikaa.js +6 -5
- package/dist/cli.js +163 -16
- package/dist/index.js +69 -11
- package/package.json +1 -1
package/bin/pikaa.js
CHANGED
|
@@ -105,14 +105,15 @@ async function downloadBinary(url, dest, redirects = 0) {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
async function main() {
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
launchBinary(existing);
|
|
108
|
+
// 1. If local bun and dist/cli.js are available in the package, run immediately
|
|
109
|
+
if (tryLaunchWithBun()) {
|
|
111
110
|
return;
|
|
112
111
|
}
|
|
113
112
|
|
|
114
|
-
//
|
|
115
|
-
|
|
113
|
+
// 2. Otherwise use existing native binary if present
|
|
114
|
+
const existing = findExistingBinary();
|
|
115
|
+
if (existing) {
|
|
116
|
+
launchBinary(existing);
|
|
116
117
|
return;
|
|
117
118
|
}
|
|
118
119
|
|
package/dist/cli.js
CHANGED
|
@@ -468,12 +468,17 @@ function buildSystemPrompt(params) {
|
|
|
468
468
|
const sections = [];
|
|
469
469
|
sections.push(params.basePrompt || "You are Groupy, an expert autonomous AI coding assistant. You think step-by-step, act surgically, and write clean, correct code.");
|
|
470
470
|
sections.push([
|
|
471
|
-
"##
|
|
472
|
-
"
|
|
473
|
-
"
|
|
474
|
-
"
|
|
475
|
-
"
|
|
476
|
-
"
|
|
471
|
+
"## Editing Constraints & Guidelines",
|
|
472
|
+
"- Use `apply_patch` for surgical single-file edits. TargetContent must match existing file content exactly.",
|
|
473
|
+
"- Use `write_file` for creating new files or when completely replacing the full content of a file.",
|
|
474
|
+
"- Use `read_file` to inspect files and `grep_search` / `find_files` to discover symbols and locate files across the project.",
|
|
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
|
+
"- NEVER execute shell or PowerShell scripts as a workaround for reading, writing, or editing text files.",
|
|
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']`).",
|
|
479
|
+
"- You may be in a dirty git worktree. NEVER revert existing changes made by the user.",
|
|
480
|
+
"- NEVER use destructive commands like `git reset --hard` or `git checkout --`.",
|
|
481
|
+
"- Be concise, direct, and act surgically. Write clean, correct code with minimal necessary modifications."
|
|
477
482
|
].join(`
|
|
478
483
|
`));
|
|
479
484
|
if (params.memoriesPrompt) {
|
|
@@ -640,6 +645,15 @@ async function runTurn(session, turnContext, input) {
|
|
|
640
645
|
description,
|
|
641
646
|
command
|
|
642
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
|
+
});
|
|
643
657
|
}
|
|
644
658
|
});
|
|
645
659
|
const functionOutputItem = {
|
|
@@ -769,7 +783,7 @@ class ExecPolicy {
|
|
|
769
783
|
}
|
|
770
784
|
initDefaultRules() {
|
|
771
785
|
this.addRule(/^(git\s+(status|log|diff|branch|show|rev-parse))/i, "allow", "Safe git query");
|
|
772
|
-
this.addRule(/^(ls|dir|cat|grep|rg|find|pwd|echo|head|tail|wc|which|where)\b/i, "allow", "Safe read-only shell command");
|
|
786
|
+
this.addRule(/^(ls|dir|cat|type|grep|rg|find|pwd|echo|head|tail|wc|which|where)\b/i, "allow", "Safe read-only shell command");
|
|
773
787
|
this.addRule(/^(bun\s+(test|--version|-v)|npm\s+(test|--version|-v)|node\s+-v)\b/i, "allow", "Testing & runtime check");
|
|
774
788
|
this.addRule(/^(rm|del|rmdir|format|mkfs)\b/i, "prompt", "Destructive file removal");
|
|
775
789
|
this.addRule(/^(git\s+(push|reset\s+--hard|clean\s+-fd|rebase))\b/i, "prompt", "Destructive git operation");
|
|
@@ -811,6 +825,7 @@ class Session {
|
|
|
811
825
|
status = "idle";
|
|
812
826
|
eventListeners = [];
|
|
813
827
|
pendingApprovals = new Map;
|
|
828
|
+
pendingUserQuestions = new Map;
|
|
814
829
|
submissionResolvers = [];
|
|
815
830
|
submissionQueue = [];
|
|
816
831
|
isTerminated = false;
|
|
@@ -926,6 +941,35 @@ class Session {
|
|
|
926
941
|
resolver(approved);
|
|
927
942
|
}
|
|
928
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
|
+
}
|
|
929
973
|
async submit(op) {
|
|
930
974
|
const subId = `sub_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
931
975
|
const submission = {
|
|
@@ -1252,14 +1296,14 @@ var writeFileTool = {
|
|
|
1252
1296
|
// src/tools/handlers/request-user-input.ts
|
|
1253
1297
|
var requestUserInputTool = {
|
|
1254
1298
|
name: "request_user_input",
|
|
1255
|
-
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)'.",
|
|
1256
1300
|
parameters: {
|
|
1257
1301
|
type: "object",
|
|
1258
1302
|
properties: {
|
|
1259
1303
|
question: { type: "string", description: "The specific question to ask the user." },
|
|
1260
1304
|
options: {
|
|
1261
1305
|
type: "array",
|
|
1262
|
-
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']).",
|
|
1263
1307
|
items: { type: "string", description: "Choice text" }
|
|
1264
1308
|
}
|
|
1265
1309
|
},
|
|
@@ -1268,19 +1312,31 @@ var requestUserInputTool = {
|
|
|
1268
1312
|
async execute(args, ctx) {
|
|
1269
1313
|
const question = String(args.question || "");
|
|
1270
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
|
+
}
|
|
1271
1321
|
if (ctx.requestApproval) {
|
|
1272
1322
|
const description = options.length > 0 ? `${question}
|
|
1273
|
-
Options:
|
|
1323
|
+
Options:
|
|
1324
|
+
${options.map((o, idx) => ` [${idx + 1}] ${o}`).join(`
|
|
1325
|
+
`)}` : question;
|
|
1274
1326
|
const answered = await ctx.requestApproval(description);
|
|
1275
1327
|
return {
|
|
1276
1328
|
output: answered ? "User confirmed to proceed." : "User declined or requested alternative."
|
|
1277
1329
|
};
|
|
1278
1330
|
}
|
|
1279
1331
|
return {
|
|
1280
|
-
output: `Question
|
|
1332
|
+
output: `Question presented to user: '${question}' with options [${options.join(", ")}].`
|
|
1281
1333
|
};
|
|
1282
1334
|
}
|
|
1283
1335
|
};
|
|
1336
|
+
var askQuestionTool = {
|
|
1337
|
+
...requestUserInputTool,
|
|
1338
|
+
name: "ask_question"
|
|
1339
|
+
};
|
|
1284
1340
|
// src/search/engine.ts
|
|
1285
1341
|
import { readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2, existsSync as existsSync4 } from "fs";
|
|
1286
1342
|
import { resolve as resolve4, relative, join, extname } from "path";
|
|
@@ -1939,6 +1995,7 @@ function createDefaultTools(options = {}) {
|
|
|
1939
1995
|
router2.register(writeFileTool);
|
|
1940
1996
|
router2.register(listDirTool);
|
|
1941
1997
|
router2.register(requestUserInputTool);
|
|
1998
|
+
router2.register(askQuestionTool);
|
|
1942
1999
|
for (const tool of createFileSearchTools()) {
|
|
1943
2000
|
router2.register(tool);
|
|
1944
2001
|
}
|
|
@@ -5395,6 +5452,72 @@ async function promptToolApproval(params) {
|
|
|
5395
5452
|
});
|
|
5396
5453
|
return decision;
|
|
5397
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
|
+
}
|
|
5398
5521
|
|
|
5399
5522
|
// src/cli/ui/markdown.ts
|
|
5400
5523
|
class MarkdownHighlighter {
|
|
@@ -5746,6 +5869,10 @@ class CliRepl {
|
|
|
5746
5869
|
this.spinner.stop();
|
|
5747
5870
|
await this.handleInteractiveApproval(msg);
|
|
5748
5871
|
break;
|
|
5872
|
+
case "UserQuestionRequired":
|
|
5873
|
+
this.spinner.stop();
|
|
5874
|
+
await this.handleInteractiveUserQuestion(msg);
|
|
5875
|
+
break;
|
|
5749
5876
|
case "TurnCompleted":
|
|
5750
5877
|
if (this.reasoningStarted) {
|
|
5751
5878
|
console.log(style.dim(`
|
|
@@ -5824,6 +5951,18 @@ class CliRepl {
|
|
|
5824
5951
|
this.session.resolveApproval(msg.approvalId, false);
|
|
5825
5952
|
}
|
|
5826
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
|
+
}
|
|
5827
5966
|
async start() {
|
|
5828
5967
|
const creds = new CredentialsStore().load();
|
|
5829
5968
|
const accountUser = creds?.user?.username || creds?.user?.email || (creds?.accessToken ? "Authenticated" : undefined);
|
|
@@ -5906,6 +6045,8 @@ async function main() {
|
|
|
5906
6045
|
let model = process.env.GROUPY_MODEL || process.env.OPENAI_MODEL || "groupy";
|
|
5907
6046
|
let baseUrl = process.env.GROUPY_BASE_URL || process.env.OPENAI_BASE_URL || savedCreds?.baseUrl;
|
|
5908
6047
|
let apiKey = process.env.GROUPY_API_KEY || process.env.OPENAI_API_KEY || savedCreds?.accessToken;
|
|
6048
|
+
let explicitBaseUrl;
|
|
6049
|
+
let explicitApiKey;
|
|
5909
6050
|
let cwd = process.cwd();
|
|
5910
6051
|
let role = "default";
|
|
5911
6052
|
let mcpConfigFile;
|
|
@@ -5930,7 +6071,7 @@ async function main() {
|
|
|
5930
6071
|
printWhoami2(credStore);
|
|
5931
6072
|
process.exit(0);
|
|
5932
6073
|
} else if (arg === "models" || arg === "--models") {
|
|
5933
|
-
await printAvailableModels(baseUrl || "
|
|
6074
|
+
await printAvailableModels(baseUrl || "https://api.groupy-hub.store/v1", apiKey);
|
|
5934
6075
|
process.exit(0);
|
|
5935
6076
|
} else if (arg === "list" || arg === "sessions" || arg === "--list") {
|
|
5936
6077
|
printSessionsList(storageManager);
|
|
@@ -5949,15 +6090,20 @@ async function main() {
|
|
|
5949
6090
|
} else if (arg === "--model" || arg === "-m") {
|
|
5950
6091
|
model = args[++i] || model;
|
|
5951
6092
|
} else if (arg === "--base-url" || arg === "-u") {
|
|
5952
|
-
|
|
6093
|
+
explicitBaseUrl = args[++i];
|
|
6094
|
+
baseUrl = explicitBaseUrl || baseUrl;
|
|
5953
6095
|
} else if (arg === "--api-key" || arg === "-k") {
|
|
5954
|
-
|
|
6096
|
+
explicitApiKey = args[++i];
|
|
6097
|
+
apiKey = explicitApiKey || apiKey;
|
|
5955
6098
|
} else if (arg === "--cwd" || arg === "-C") {
|
|
5956
6099
|
cwd = resolve13(args[++i] || cwd);
|
|
5957
6100
|
} else if (arg === "--role" || arg === "-r") {
|
|
5958
6101
|
role = args[++i] || role;
|
|
5959
6102
|
} else if (arg === "--mcp") {
|
|
5960
6103
|
mcpConfigFile = args[++i];
|
|
6104
|
+
} else if (arg === "--version" || arg === "-v" || arg === "version") {
|
|
6105
|
+
console.log(`pikaa v0.2.5`);
|
|
6106
|
+
process.exit(0);
|
|
5961
6107
|
} else if (arg === "--help" || arg === "-h") {
|
|
5962
6108
|
printCliHelp();
|
|
5963
6109
|
process.exit(0);
|
|
@@ -5966,8 +6112,8 @@ async function main() {
|
|
|
5966
6112
|
}
|
|
5967
6113
|
}
|
|
5968
6114
|
const modelClient = new ModelClient({
|
|
5969
|
-
baseUrl,
|
|
5970
|
-
apiKey,
|
|
6115
|
+
baseUrl: explicitBaseUrl,
|
|
6116
|
+
apiKey: explicitApiKey,
|
|
5971
6117
|
defaultModel: model
|
|
5972
6118
|
});
|
|
5973
6119
|
const tools = createDefaultTools({ skillsLoader, memoryStore, worktreeManager });
|
|
@@ -6223,6 +6369,7 @@ ${style.bold("OPTIONS:")}
|
|
|
6223
6369
|
-C, --cwd <path> Working directory for agent operations (default: current dir)
|
|
6224
6370
|
-r, --role <role> Initial agent role (default, reviewer, researcher, tester, planner)
|
|
6225
6371
|
--mcp <path> Path to MCP server configuration JSON file
|
|
6372
|
+
-v, --version Show CLI version
|
|
6226
6373
|
-h, --help Show this help message
|
|
6227
6374
|
|
|
6228
6375
|
${style.bold("EXAMPLES:")}
|
package/dist/index.js
CHANGED
|
@@ -453,7 +453,7 @@ class ExecPolicy {
|
|
|
453
453
|
}
|
|
454
454
|
initDefaultRules() {
|
|
455
455
|
this.addRule(/^(git\s+(status|log|diff|branch|show|rev-parse))/i, "allow", "Safe git query");
|
|
456
|
-
this.addRule(/^(ls|dir|cat|grep|rg|find|pwd|echo|head|tail|wc|which|where)\b/i, "allow", "Safe read-only shell command");
|
|
456
|
+
this.addRule(/^(ls|dir|cat|type|grep|rg|find|pwd|echo|head|tail|wc|which|where)\b/i, "allow", "Safe read-only shell command");
|
|
457
457
|
this.addRule(/^(bun\s+(test|--version|-v)|npm\s+(test|--version|-v)|node\s+-v)\b/i, "allow", "Testing & runtime check");
|
|
458
458
|
this.addRule(/^(rm|del|rmdir|format|mkfs)\b/i, "prompt", "Destructive file removal");
|
|
459
459
|
this.addRule(/^(git\s+(push|reset\s+--hard|clean\s+-fd|rebase))\b/i, "prompt", "Destructive git operation");
|
|
@@ -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:
|
|
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
|
|
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
|
}
|
|
@@ -1438,12 +1451,17 @@ function buildSystemPrompt(params) {
|
|
|
1438
1451
|
const sections = [];
|
|
1439
1452
|
sections.push(params.basePrompt || "You are Groupy, an expert autonomous AI coding assistant. You think step-by-step, act surgically, and write clean, correct code.");
|
|
1440
1453
|
sections.push([
|
|
1441
|
-
"##
|
|
1442
|
-
"
|
|
1443
|
-
"
|
|
1444
|
-
"
|
|
1445
|
-
"
|
|
1446
|
-
"
|
|
1454
|
+
"## Editing Constraints & Guidelines",
|
|
1455
|
+
"- Use `apply_patch` for surgical single-file edits. TargetContent must match existing file content exactly.",
|
|
1456
|
+
"- Use `write_file` for creating new files or when completely replacing the full content of a file.",
|
|
1457
|
+
"- Use `read_file` to inspect files and `grep_search` / `find_files` to discover symbols and locate files across the project.",
|
|
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.",
|
|
1459
|
+
"- NEVER execute shell or PowerShell scripts as a workaround for reading, writing, or editing text files.",
|
|
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']`).",
|
|
1462
|
+
"- You may be in a dirty git worktree. NEVER revert existing changes made by the user.",
|
|
1463
|
+
"- NEVER use destructive commands like `git reset --hard` or `git checkout --`.",
|
|
1464
|
+
"- Be concise, direct, and act surgically. Write clean, correct code with minimal necessary modifications."
|
|
1447
1465
|
].join(`
|
|
1448
1466
|
`));
|
|
1449
1467
|
if (params.memoriesPrompt) {
|
|
@@ -1668,6 +1686,15 @@ async function runTurn(session, turnContext, input) {
|
|
|
1668
1686
|
description,
|
|
1669
1687
|
command
|
|
1670
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
|
+
});
|
|
1671
1698
|
}
|
|
1672
1699
|
});
|
|
1673
1700
|
const functionOutputItem = {
|
|
@@ -1805,6 +1832,7 @@ class Session {
|
|
|
1805
1832
|
status = "idle";
|
|
1806
1833
|
eventListeners = [];
|
|
1807
1834
|
pendingApprovals = new Map;
|
|
1835
|
+
pendingUserQuestions = new Map;
|
|
1808
1836
|
submissionResolvers = [];
|
|
1809
1837
|
submissionQueue = [];
|
|
1810
1838
|
isTerminated = false;
|
|
@@ -1920,6 +1948,35 @@ class Session {
|
|
|
1920
1948
|
resolver(approved);
|
|
1921
1949
|
}
|
|
1922
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
|
+
}
|
|
1923
1980
|
async submit(op) {
|
|
1924
1981
|
const subId = `sub_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
|
|
1925
1982
|
const submission = {
|
|
@@ -4123,6 +4180,7 @@ export {
|
|
|
4123
4180
|
TurnContext,
|
|
4124
4181
|
WorktreeManager,
|
|
4125
4182
|
applyPatchTool,
|
|
4183
|
+
askQuestionTool,
|
|
4126
4184
|
buildSystemPrompt,
|
|
4127
4185
|
captureWorldState,
|
|
4128
4186
|
compactHistory,
|