paseo-acp-agy 1.2.14 → 1.2.16
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/acp-server.d.ts +3 -0
- package/dist/acp-server.js +85 -5
- package/dist/paseo-patcher.js +57 -0
- package/dist/protocol.d.ts +1 -1
- package/dist/tool-formatter.js +19 -7
- package/package.json +1 -1
package/dist/acp-server.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export declare class ACPServer extends EventEmitter {
|
|
|
10
10
|
private skipNarration;
|
|
11
11
|
private rl;
|
|
12
12
|
private isRunning;
|
|
13
|
+
private pendingRequests;
|
|
14
|
+
private nextRequestId;
|
|
13
15
|
constructor(options?: {
|
|
14
16
|
input?: Readable;
|
|
15
17
|
output?: Writable;
|
|
@@ -22,6 +24,7 @@ export declare class ACPServer extends EventEmitter {
|
|
|
22
24
|
private sendNotification;
|
|
23
25
|
private sendSuccess;
|
|
24
26
|
private sendError;
|
|
27
|
+
sendRequest<T = any>(method: string, params: unknown): Promise<T>;
|
|
25
28
|
private publishCommands;
|
|
26
29
|
private publishUsageUpdate;
|
|
27
30
|
private sessionState;
|
package/dist/acp-server.js
CHANGED
|
@@ -29,6 +29,8 @@ export class ACPServer extends EventEmitter {
|
|
|
29
29
|
skipNarration;
|
|
30
30
|
rl = null;
|
|
31
31
|
isRunning = false;
|
|
32
|
+
pendingRequests = new Map();
|
|
33
|
+
nextRequestId = 1;
|
|
32
34
|
constructor(options = {}) {
|
|
33
35
|
super();
|
|
34
36
|
this.input = options.input || process.stdin;
|
|
@@ -98,6 +100,13 @@ export class ACPServer extends EventEmitter {
|
|
|
98
100
|
};
|
|
99
101
|
this.send(response);
|
|
100
102
|
}
|
|
103
|
+
sendRequest(method, params) {
|
|
104
|
+
return new Promise((resolve, reject) => {
|
|
105
|
+
const id = this.nextRequestId++;
|
|
106
|
+
this.pendingRequests.set(id, { resolve, reject });
|
|
107
|
+
this.send({ jsonrpc: "2.0", id, method, params });
|
|
108
|
+
});
|
|
109
|
+
}
|
|
101
110
|
publishCommands(sessionId) {
|
|
102
111
|
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
103
112
|
sessionId,
|
|
@@ -198,6 +207,20 @@ export class ACPServer extends EventEmitter {
|
|
|
198
207
|
const isNotification = id === undefined || id === null;
|
|
199
208
|
const params = (parsed.params || {});
|
|
200
209
|
logger.debug("Received ACP message", { method, isNotification, id });
|
|
210
|
+
// Handle responses to requests initiated by the server (e.g. session/request_permission)
|
|
211
|
+
if (id !== undefined && !method) {
|
|
212
|
+
const pending = this.pendingRequests.get(id);
|
|
213
|
+
if (pending) {
|
|
214
|
+
this.pendingRequests.delete(id);
|
|
215
|
+
if (parsed.error) {
|
|
216
|
+
pending.reject(parsed.error);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
pending.resolve(parsed.result);
|
|
220
|
+
}
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
201
224
|
try {
|
|
202
225
|
switch (method) {
|
|
203
226
|
case ACP_METHODS.INITIALIZE: {
|
|
@@ -559,13 +582,70 @@ export class ACPServer extends EventEmitter {
|
|
|
559
582
|
rawInput: toolCall.rawInput,
|
|
560
583
|
},
|
|
561
584
|
});
|
|
562
|
-
if (
|
|
563
|
-
|
|
585
|
+
if (toolName === "ask_question") {
|
|
586
|
+
const rawParams = step.tool_info.parameters || {};
|
|
587
|
+
const rawQuestions = Array.isArray(rawParams.questions)
|
|
588
|
+
? rawParams.questions
|
|
589
|
+
: [];
|
|
590
|
+
const options = [];
|
|
591
|
+
for (let qIdx = 0; qIdx < rawQuestions.length; qIdx++) {
|
|
592
|
+
const q = rawQuestions[qIdx];
|
|
593
|
+
const opts = Array.isArray(q.options) ? q.options : [];
|
|
594
|
+
for (let optIdx = 0; optIdx < opts.length; optIdx++) {
|
|
595
|
+
const opt = opts[optIdx];
|
|
596
|
+
const clean = typeof opt === "string" ? opt.replace(/^\(Recommended\)\s*/i, "").trim() : String(opt);
|
|
597
|
+
const isRec = typeof opt === "string" && (opt.includes("(Recommended)") || opt.includes("(Recomendado)"));
|
|
598
|
+
options.push({
|
|
599
|
+
optionId: `q${qIdx + 1}_opt${optIdx + 1}`,
|
|
600
|
+
name: `${clean}${isRec ? " (Recomendado)" : ""}`,
|
|
601
|
+
kind: "allow_once",
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
options.push({
|
|
606
|
+
optionId: "cancel",
|
|
607
|
+
name: "Cancelar",
|
|
608
|
+
kind: "reject_once",
|
|
609
|
+
});
|
|
610
|
+
const promptText = rawQuestions.map((q) => q.question).filter(Boolean).join(" / ") || "Pergunta do Assistente";
|
|
611
|
+
this.sendRequest("session/request_permission", {
|
|
564
612
|
sessionId: session.id,
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
613
|
+
toolCall: {
|
|
614
|
+
toolCallId: toolCall.toolCallId,
|
|
615
|
+
title: toolCall.title,
|
|
616
|
+
kind: "question",
|
|
617
|
+
content: [{ type: "text", text: promptText }],
|
|
618
|
+
rawInput: toolCall.rawInput,
|
|
568
619
|
},
|
|
620
|
+
options,
|
|
621
|
+
questions: rawQuestions,
|
|
622
|
+
}).then((permRes) => {
|
|
623
|
+
logger.info("Interactive question answered in Paseo", { permRes });
|
|
624
|
+
const outcome = permRes?.outcome;
|
|
625
|
+
let answerText = "";
|
|
626
|
+
if (outcome?._meta?.answers && typeof outcome._meta.answers === "object") {
|
|
627
|
+
answerText = Object.entries(outcome._meta.answers)
|
|
628
|
+
.map(([k, v]) => `• **${k}**: ${v}`)
|
|
629
|
+
.join("\n");
|
|
630
|
+
}
|
|
631
|
+
else if (outcome?.optionId && outcome.optionId !== "cancel") {
|
|
632
|
+
const matched = options.find((o) => o.optionId === outcome.optionId);
|
|
633
|
+
answerText = matched ? matched.name : outcome.optionId;
|
|
634
|
+
}
|
|
635
|
+
if (answerText) {
|
|
636
|
+
this.sendNotification(ACP_METHODS.SESSION_UPDATE, {
|
|
637
|
+
sessionId: session.id,
|
|
638
|
+
update: {
|
|
639
|
+
sessionUpdate: "agent_message_chunk",
|
|
640
|
+
content: {
|
|
641
|
+
type: "text",
|
|
642
|
+
text: `\n\n> 💡 **Opção Selecionada:**\n${answerText}\n\n`,
|
|
643
|
+
},
|
|
644
|
+
},
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
}).catch((err) => {
|
|
648
|
+
logger.debug("Interactive question cancelled or dismissed", { error: err.message });
|
|
569
649
|
});
|
|
570
650
|
}
|
|
571
651
|
}
|
package/dist/paseo-patcher.js
CHANGED
|
@@ -1195,6 +1195,63 @@ ${braceIndent}}`;
|
|
|
1195
1195
|
}
|
|
1196
1196
|
}
|
|
1197
1197
|
}
|
|
1198
|
+
// Patch mapPermissionRequest for native QuestionFormCard support in Paseo UI
|
|
1199
|
+
if (!acpCode.includes("snapshot?.kind === \"question\" || params?.kind === \"question\"")) {
|
|
1200
|
+
const oldMapPermRegex = /function\s+mapPermissionRequest\s*\([^)]*\)\s*\{[\s\S]*?const\s+kind\s*=\s*snapshot\.kind\s*===\s*"switch_mode"\s*\?\s*"mode"\s*:\s*"tool";/m;
|
|
1201
|
+
const newMapPerm = `function mapPermissionRequest(provider, requestId, params, snapshot) {
|
|
1202
|
+
if (snapshot?.kind === "question" || params?.kind === "question" || (params?.questions && Array.isArray(params.questions)) || (snapshot?.rawInput?.questions && Array.isArray(snapshot?.rawInput?.questions))) {
|
|
1203
|
+
const rawQ = params.questions || snapshot?.rawInput?.questions || [];
|
|
1204
|
+
const questions = rawQ.map((q, idx) => ({
|
|
1205
|
+
header: q.header || q.title || ("Question " + (idx + 1)),
|
|
1206
|
+
question: q.question || q.prompt || "Escolha uma opção:",
|
|
1207
|
+
options: (q.options || []).map((opt) => typeof opt === "string" ? {
|
|
1208
|
+
label: opt.replace(/^\\(Recommended\\)\\s*/i, "").replace(/^\\(Recomendado\\)\\s*/i, "").trim(),
|
|
1209
|
+
description: (opt.includes("(Recommended)") || opt.includes("(Recomendado)")) ? "Recomendado" : undefined
|
|
1210
|
+
} : { label: opt.label || String(opt), description: opt.description }),
|
|
1211
|
+
multiSelect: q.multiSelect === true || q.is_multi_select === true || q.multiple === true,
|
|
1212
|
+
allowOther: true,
|
|
1213
|
+
allowEmpty: false,
|
|
1214
|
+
}));
|
|
1215
|
+
return {
|
|
1216
|
+
id: requestId,
|
|
1217
|
+
provider,
|
|
1218
|
+
name: "question",
|
|
1219
|
+
kind: "question",
|
|
1220
|
+
title: params.toolCall?.title ?? snapshot?.title ?? "Pergunta do Assistente",
|
|
1221
|
+
input: { questions },
|
|
1222
|
+
actions: (params.options || []).map((option) => ({
|
|
1223
|
+
id: option.optionId,
|
|
1224
|
+
label: option.name,
|
|
1225
|
+
behavior: option.kind.startsWith("allow") ? "allow" : "deny",
|
|
1226
|
+
})),
|
|
1227
|
+
metadata: {
|
|
1228
|
+
toolCallId: params.toolCall?.toolCallId,
|
|
1229
|
+
rawRequest: params,
|
|
1230
|
+
options: params.options,
|
|
1231
|
+
},
|
|
1232
|
+
};
|
|
1233
|
+
}
|
|
1234
|
+
const kind = snapshot.kind === "switch_mode" ? "mode" : "tool";`;
|
|
1235
|
+
if (oldMapPermRegex.test(acpCode)) {
|
|
1236
|
+
acpCode = acpCode.replace(oldMapPermRegex, newMapPerm);
|
|
1237
|
+
acpModified = true;
|
|
1238
|
+
changes.push(`Patched ${acpFile} for interactive QuestionFormCard support`);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
// Patch respondToPermission for QuestionFormCard updatedInput.answers
|
|
1242
|
+
if (!acpCode.includes("response.updatedInput?.answers")) {
|
|
1243
|
+
const oldRespondMatch = acpCode.match(/this\.pendingPermissions\.delete\(requestId\);[\s\S]*?pending\.resolve\([\s\S]*?\);\s*\r?\n\s*this\.pushEvent/m);
|
|
1244
|
+
if (oldRespondMatch) {
|
|
1245
|
+
const newRespond = `this.pendingPermissions.delete(requestId);
|
|
1246
|
+
const outcome = response.updatedInput?.answers
|
|
1247
|
+
? { outcome: "selected", optionId: selectedOption?.optionId || "form_submit", _meta: { answers: response.updatedInput.answers } }
|
|
1248
|
+
: (selectedOption ? { outcome: { outcome: "selected", optionId: selectedOption.optionId } } : { outcome: { outcome: "cancelled" } });
|
|
1249
|
+
pending.resolve(response.updatedInput?.answers ? { outcome } : (selectedOption ? { outcome: { outcome: "selected", optionId: selectedOption.optionId } } : { outcome: { outcome: "cancelled" } }));
|
|
1250
|
+
this.pushEvent`;
|
|
1251
|
+
acpCode = acpCode.replace(oldRespondMatch[0], newRespond);
|
|
1252
|
+
acpModified = true;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1198
1255
|
if (acpModified) {
|
|
1199
1256
|
fs.writeFileSync(acpFile, acpCode, "utf-8");
|
|
1200
1257
|
changes.push(`Patched ${acpFile} for context-window token telemetry and usage updates`);
|
package/dist/protocol.d.ts
CHANGED
|
@@ -135,7 +135,7 @@ export interface SessionInfo {
|
|
|
135
135
|
updatedAt?: string | null;
|
|
136
136
|
_meta?: Record<string, unknown> | null;
|
|
137
137
|
}
|
|
138
|
-
export type ToolKind = "read" | "edit" | "execute" | "fetch" | "search" | "other";
|
|
138
|
+
export type ToolKind = "read" | "edit" | "execute" | "fetch" | "search" | "question" | "other";
|
|
139
139
|
export declare function mapToolNameToKind(toolName: string): ToolKind;
|
|
140
140
|
export declare function extractPromptText(promptInput: unknown): string;
|
|
141
141
|
export interface ModelDefinition {
|
package/dist/tool-formatter.js
CHANGED
|
@@ -102,7 +102,7 @@ export function normalizeToolCall(toolName, rawParams = {}, stepIndex) {
|
|
|
102
102
|
return {
|
|
103
103
|
toolCallId,
|
|
104
104
|
title: "❓ Pergunta do Assistente",
|
|
105
|
-
kind: "
|
|
105
|
+
kind: "question",
|
|
106
106
|
rawInput: normalizedParams,
|
|
107
107
|
questionCard: questionCard || undefined,
|
|
108
108
|
};
|
|
@@ -187,24 +187,36 @@ export function extractPlanEntries(text) {
|
|
|
187
187
|
}
|
|
188
188
|
/**
|
|
189
189
|
* Formats Antigravity's ask_question structure into a clear, responsive markdown card.
|
|
190
|
+
* Supports multiple questions in a single call and single/multi-choice selection modes.
|
|
190
191
|
*/
|
|
191
192
|
function formatQuestionCard(questions) {
|
|
192
193
|
if (!questions || questions.length === 0)
|
|
193
194
|
return "";
|
|
195
|
+
const validQuestions = questions.filter((q) => q && typeof q === "object");
|
|
196
|
+
const total = validQuestions.length;
|
|
194
197
|
const cards = [];
|
|
195
|
-
for (
|
|
196
|
-
|
|
197
|
-
continue;
|
|
198
|
+
for (let i = 0; i < total; i++) {
|
|
199
|
+
const q = validQuestions[i];
|
|
198
200
|
const prompt = q.question || "Escolha uma opção:";
|
|
199
201
|
const options = Array.isArray(q.options) ? q.options : [];
|
|
200
|
-
|
|
202
|
+
const isMulti = Boolean(q.is_multi_select);
|
|
203
|
+
const titlePrefix = total > 1
|
|
204
|
+
? `### ❓ Pergunta ${i + 1} de ${total}`
|
|
205
|
+
: `### ❓ Pergunta do Assistente`;
|
|
206
|
+
let card = `${titlePrefix}\n> **${prompt}**\n\n`;
|
|
201
207
|
if (options.length > 0) {
|
|
202
|
-
|
|
208
|
+
if (isMulti) {
|
|
209
|
+
card += `*☑️ Múltipla escolha (você pode selecionar mais de uma opção, ex: 1, 3):*\n`;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
card += `*🔘 Escolha única (selecione uma das opções abaixo):*\n`;
|
|
213
|
+
}
|
|
203
214
|
card += options
|
|
204
215
|
.map((opt, idx) => {
|
|
205
216
|
const isRec = opt.includes("(Recommended)") || opt.includes("(Recomendado)");
|
|
206
217
|
const clean = opt.replace(/^\(Recommended\)\s*/i, "").replace(/^\(Recomendado\)\s*/i, "");
|
|
207
|
-
|
|
218
|
+
const mark = isMulti ? "- [ ]" : "-";
|
|
219
|
+
return `${mark} **[${idx + 1}]** ${clean}${isRec ? " *(Recomendado)*" : ""}`;
|
|
208
220
|
})
|
|
209
221
|
.join("\n");
|
|
210
222
|
}
|