@danypops/papyrus 0.27.7 → 0.27.8
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.
|
@@ -99,6 +99,18 @@ export interface AskQuestionParams {
|
|
|
99
99
|
* the same live ask run concurrently, each opening its own picker for the same question.
|
|
100
100
|
*/
|
|
101
101
|
onUpdate?: AgentToolUpdateCallback;
|
|
102
|
+
/**
|
|
103
|
+
* The tool call's OWN abort signal (execute()'s 3rd parameter) -- fires only if this specific
|
|
104
|
+
* tool call is genuinely interrupted (e.g. the human pressed Ctrl+C on the whole agent
|
|
105
|
+
* operation). Deliberately NOT `ExtensionContext.signal`: that one tracks "is the agent
|
|
106
|
+
* currently streaming a model response" and settles/aborts within a second or two of the
|
|
107
|
+
* assistant's tool_call message finishing generation -- which is normal, unrelated bookkeeping
|
|
108
|
+
* that happens long before a human actually answers a slow interactive prompt. A live-observed
|
|
109
|
+
* bug (the picker silently self-cancelling ~7-10s after opening, well before the human
|
|
110
|
+
* finished deciding, with their real answer then arriving disconnected as a stray follow-up)
|
|
111
|
+
* traced back to listening on the wrong signal here.
|
|
112
|
+
*/
|
|
113
|
+
signal?: AbortSignal;
|
|
102
114
|
}
|
|
103
115
|
|
|
104
116
|
export interface AskAnswer {
|
|
@@ -1200,9 +1212,9 @@ async function askQuestionBlocking(
|
|
|
1200
1212
|
let hasAnnouncedHide = false;
|
|
1201
1213
|
let response: AskResponse | null;
|
|
1202
1214
|
try {
|
|
1203
|
-
diag("factory about to construct AskComponent", { hasSignal:
|
|
1215
|
+
diag("factory about to construct AskComponent", { hasSignal: params.signal !== undefined, signalAlreadyAborted: params.signal?.aborted ?? null, hasExplicitTimeout: params.timeout !== undefined });
|
|
1204
1216
|
const factory = (tui: TUI, theme: Theme, keybindings: KeybindingsManager, done: (result: AskResponse | null) => void) => {
|
|
1205
|
-
if (
|
|
1217
|
+
if (params.signal) params.signal.addEventListener("abort", () => { diag("tool call signal fired abort -- resolving null"); done(null); }, { once: true });
|
|
1206
1218
|
if (params.timeout && params.timeout > 0) setTimeout(() => { diag("explicit params.timeout expired -- resolving null", { timeout: params.timeout }); done(null); }, params.timeout);
|
|
1207
1219
|
return new AskComponent(params.question, normalizedContext, options, allowMultiple, allowFreeform, allowComment, displayMode, tui, theme, keybindings, shortcuts, done);
|
|
1208
1220
|
};
|
|
@@ -39,7 +39,7 @@ function text(message: string, details: unknown = {}) {
|
|
|
39
39
|
* is available, never throws -- an unanswered live prompt still leaves the round it already
|
|
40
40
|
* recorded intact.
|
|
41
41
|
*/
|
|
42
|
-
async function liveAnswer(ctx: ExtensionContext, discussion: Artifact, onUpdate: AgentToolUpdateCallback | undefined): Promise<{ content: string; selected?: string[] } | undefined> {
|
|
42
|
+
async function liveAnswer(ctx: ExtensionContext, discussion: Artifact, onUpdate: AgentToolUpdateCallback | undefined, signal: AbortSignal | undefined): Promise<{ content: string; selected?: string[] } | undefined> {
|
|
43
43
|
if (!ctx.hasUI) return undefined;
|
|
44
44
|
const pending = (() => { try { return readDiscussionExtra(discussion.extra); } catch { return undefined; } })();
|
|
45
45
|
const question = `Reply to "${discussion.title}":`;
|
|
@@ -49,10 +49,11 @@ async function liveAnswer(ctx: ExtensionContext, discussion: Artifact, onUpdate:
|
|
|
49
49
|
options: pending.pendingOptions.map((title) => ({ title })),
|
|
50
50
|
allowMultiple: pending.pendingOptionsMode === "multi",
|
|
51
51
|
onUpdate,
|
|
52
|
+
signal,
|
|
52
53
|
key: discussion.id,
|
|
53
54
|
});
|
|
54
55
|
}
|
|
55
|
-
return askQuestion(ctx, { question, onUpdate, key: discussion.id });
|
|
56
|
+
return askQuestion(ctx, { question, onUpdate, signal, key: discussion.id });
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
/**
|
|
@@ -705,7 +706,7 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
705
706
|
executionMode: "sequential",
|
|
706
707
|
renderCall(args, theme) { return renderPapyrusToolCall("Discuss", args, theme); },
|
|
707
708
|
renderResult(result, options, theme, context) { return renderPapyrusToolResult(result, options, theme, context); },
|
|
708
|
-
async execute(_id, rawParams,
|
|
709
|
+
async execute(_id, rawParams, signal, onUpdate, ctx) {
|
|
709
710
|
try {
|
|
710
711
|
const params: Record<string, unknown> = { ...rawParams };
|
|
711
712
|
const action = params.action;
|
|
@@ -722,7 +723,7 @@ export function registerDomainTools(pi: ExtensionAPI): void {
|
|
|
722
723
|
? text(`Opened discussion ${artifactLine(result.discussion)}`, createArtifactDetails("discuss.open", result.discussion))
|
|
723
724
|
: text(`Round ${result.rounds[0]?.roundNumber} added to "${result.discussion.title}"`, createArtifactDetails("discuss.reply", result.discussion));
|
|
724
725
|
if (params.live !== true) return fallback;
|
|
725
|
-
const answer = await liveAnswer(ctx, result.discussion, onUpdate);
|
|
726
|
+
const answer = await liveAnswer(ctx, result.discussion, onUpdate, signal);
|
|
726
727
|
if (!answer) return fallback;
|
|
727
728
|
const answered = await callService<Record<string, unknown>, DiscussionAndRounds>("discuss.reply", {
|
|
728
729
|
id: result.discussion.id, actor: "human", content: answer.content, ...(answer.selected ? { selected: answer.selected } : {}), source: "discuss-live",
|
package/package.json
CHANGED