@lucascouts/claude-agent-acp-plus 0.4.0 → 0.5.1
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-agent.d.ts +131 -16
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +657 -164
- package/dist/elicitation.d.ts.map +1 -1
- package/dist/elicitation.js +13 -0
- package/dist/model-deprecation.d.ts +1 -1
- package/dist/model-deprecation.d.ts.map +1 -1
- package/dist/model-deprecation.js +9 -4
- package/dist/rewind-command.d.ts +15 -3
- package/dist/rewind-command.d.ts.map +1 -1
- package/dist/rewind-command.js +37 -6
- package/dist/thinking-option.d.ts +12 -8
- package/dist/thinking-option.d.ts.map +1 -1
- package/dist/thinking-option.js +12 -8
- package/dist/tools.d.ts +1 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +51 -55
- package/package.json +17 -10
- package/dist/ask-user-question-fallback.d.ts +0 -78
- package/dist/ask-user-question-fallback.d.ts.map +0 -1
- package/dist/ask-user-question-fallback.js +0 -104
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fallback for the built-in AskUserQuestion tool when the connected client does
|
|
3
|
-
* NOT advertise `elicitation.form`. Upstream disables the tool for such clients;
|
|
4
|
-
* this module lets the agent instead route each question through ACP's
|
|
5
|
-
* `session/request_permission` dialog. The path is gated by the
|
|
6
|
-
* `ACP_ASKUSERQUESTION_FALLBACK` env var so it can be turned off to restore
|
|
7
|
-
* byte-for-byte upstream behavior.
|
|
8
|
-
*
|
|
9
|
-
* Alongside the env gate this module provides the pure mapping/orchestration
|
|
10
|
-
* helpers (`questionPermissionOptions`, `handleAskUserQuestionViaPermission`);
|
|
11
|
-
* the ACP client is injected by the adapter that wires them in.
|
|
12
|
-
*/
|
|
13
|
-
import { randomUUID } from "node:crypto";
|
|
14
|
-
import { extractAskUserQuestions } from "./elicitation.js";
|
|
15
|
-
/**
|
|
16
|
-
* Whether the AskUserQuestion permission fallback is enabled for clients lacking
|
|
17
|
-
* `elicitation.form`. Defaults ON: only an explicit `0` or `false` (trimmed,
|
|
18
|
-
* case-insensitive) turns it off; any other value — unset, `1`, `true`, … —
|
|
19
|
-
* leaves it on.
|
|
20
|
-
*
|
|
21
|
-
* @param env Environment map to read `ACP_ASKUSERQUESTION_FALLBACK` from (pass
|
|
22
|
-
* `process.env`).
|
|
23
|
-
*/
|
|
24
|
-
export function askUserQuestionFallbackEnabled(env) {
|
|
25
|
-
const raw = env.ACP_ASKUSERQUESTION_FALLBACK;
|
|
26
|
-
if (raw === undefined) {
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
const normalized = raw.trim().toLowerCase();
|
|
30
|
-
return normalized !== "0" && normalized !== "false";
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Map one AskUserQuestion question to the options shown in ACP's
|
|
34
|
-
* `session/request_permission` dialog: one `allow_once` option per choice (in
|
|
35
|
-
* order) followed by a trailing `reject_once` "Skip this question" option.
|
|
36
|
-
*
|
|
37
|
-
* Each choice's `optionId` is its bare `label` — the value the tool records as
|
|
38
|
-
* the answer — so the description is never folded into the id; a non-empty
|
|
39
|
-
* `description` is instead appended to the human-readable `name` as
|
|
40
|
-
* `"<label> — <description>"`. The skip option is given a random `optionId`
|
|
41
|
-
* that cannot collide with any label, so the orchestrator can recognize a skip
|
|
42
|
-
* by id alone.
|
|
43
|
-
*
|
|
44
|
-
* @param question The single question whose choices become permission options.
|
|
45
|
-
*/
|
|
46
|
-
export function questionPermissionOptions(question) {
|
|
47
|
-
const options = question.options.map((option) => ({
|
|
48
|
-
kind: "allow_once",
|
|
49
|
-
optionId: option.label,
|
|
50
|
-
name: option.description ? `${option.label} — ${option.description}` : option.label,
|
|
51
|
-
}));
|
|
52
|
-
options.push({
|
|
53
|
-
kind: "reject_once",
|
|
54
|
-
name: "Skip this question",
|
|
55
|
-
optionId: randomUUID(),
|
|
56
|
-
});
|
|
57
|
-
return options;
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Route an AskUserQuestion tool call through ACP permission requests rather than
|
|
61
|
-
* a form elicitation, for clients that lack `elicitation.form`. Each question is
|
|
62
|
-
* asked sequentially via the injected `requestPermission`; the selected label is
|
|
63
|
-
* accumulated into `answers` (keyed by the question text), a skipped question is
|
|
64
|
-
* omitted, and a multi-select question naturally degrades to a single label
|
|
65
|
-
* since each option is single-select here.
|
|
66
|
-
*
|
|
67
|
-
* Mirrors the upstream form handler's contract: input with no parseable
|
|
68
|
-
* questions denies with the identical message, and an aborted signal or a
|
|
69
|
-
* cancelled request throws `"Tool use aborted"`, discarding every accumulated
|
|
70
|
-
* answer. On success it returns the original `toolInput` augmented with
|
|
71
|
-
* `answers` as `updatedInput`.
|
|
72
|
-
*
|
|
73
|
-
* Pure and injectable — the real ACP client is wired in by the adapter — so the
|
|
74
|
-
* orchestration can be exercised without a transport.
|
|
75
|
-
*
|
|
76
|
-
* @param toolInput Raw AskUserQuestion tool input (expected to carry `questions`).
|
|
77
|
-
* @param requestPermission Injected permission-request function, invoked once per
|
|
78
|
-
* question, resolving to the user's selection or a cancellation.
|
|
79
|
-
* @param signal Abort signal for the tool call; checked on entry and around each
|
|
80
|
-
* request.
|
|
81
|
-
*/
|
|
82
|
-
export async function handleAskUserQuestionViaPermission(toolInput, requestPermission, signal) {
|
|
83
|
-
if (signal.aborted) {
|
|
84
|
-
throw new Error("Tool use aborted");
|
|
85
|
-
}
|
|
86
|
-
const questions = extractAskUserQuestions(toolInput);
|
|
87
|
-
if (!questions) {
|
|
88
|
-
return { behavior: "deny", message: "AskUserQuestion called with no valid questions." };
|
|
89
|
-
}
|
|
90
|
-
const answers = {};
|
|
91
|
-
for (const question of questions) {
|
|
92
|
-
const options = questionPermissionOptions(question);
|
|
93
|
-
const skipOptionId = options.find((option) => option.kind === "reject_once")?.optionId;
|
|
94
|
-
const outcome = await requestPermission({ question, options });
|
|
95
|
-
if (outcome.outcome === "cancelled" || signal.aborted) {
|
|
96
|
-
throw new Error("Tool use aborted");
|
|
97
|
-
}
|
|
98
|
-
if (outcome.optionId === skipOptionId) {
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
answers[question.question] = outcome.optionId;
|
|
102
|
-
}
|
|
103
|
-
return { behavior: "allow", updatedInput: { ...toolInput, answers } };
|
|
104
|
-
}
|