@danypops/papyrus 0.27.14 → 0.27.15
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.
|
@@ -56,6 +56,9 @@ export type AskDisplayMode = "overlay" | "inline";
|
|
|
56
56
|
export interface AskQuestionParams {
|
|
57
57
|
question: string;
|
|
58
58
|
context?: string;
|
|
59
|
+
/** Plain orientation line ("which discussion is this"), shown dim above the question -- not a
|
|
60
|
+
* labeled section like context. Typically the Discussion's own title. */
|
|
61
|
+
subtitle?: string;
|
|
59
62
|
options?: AskOption[];
|
|
60
63
|
allowMultiple?: boolean;
|
|
61
64
|
allowFreeform?: boolean;
|
|
@@ -621,6 +624,7 @@ class AskComponent extends Container {
|
|
|
621
624
|
constructor(
|
|
622
625
|
private question: string,
|
|
623
626
|
private context: string | undefined,
|
|
627
|
+
private subtitle: string | undefined,
|
|
624
628
|
private options: AskOption[],
|
|
625
629
|
private allowMultiple: boolean,
|
|
626
630
|
private allowFreeform: boolean,
|
|
@@ -781,6 +785,9 @@ class AskComponent extends Container {
|
|
|
781
785
|
"",
|
|
782
786
|
];
|
|
783
787
|
}
|
|
788
|
+
// Only meaningful when reached by escaping OUT of a real select list -- see showFreeformMode's
|
|
789
|
+
// identical guard for the non-overlay layout.
|
|
790
|
+
if (this.options.length === 0) return [];
|
|
784
791
|
return [...new Text(this.theme.fg("accent", this.theme.bold("Custom answer")), 1, 0).render(width), ""];
|
|
785
792
|
}
|
|
786
793
|
|
|
@@ -864,7 +871,11 @@ class AskComponent extends Container {
|
|
|
864
871
|
|
|
865
872
|
private updateStaticText(): void {
|
|
866
873
|
const theme = this.theme;
|
|
867
|
-
|
|
874
|
+
// Reuses the same slot for two different purposes: a plain "which discussion is this" subtitle
|
|
875
|
+
// normally, or "Optional comment" while in comment mode. A generic "Question" header above the
|
|
876
|
+
// real question text added nothing beyond what the question itself already says, and read
|
|
877
|
+
// confusingly like the question text WAS the header.
|
|
878
|
+
this.titleText.setText(this.mode === "comment" ? theme.fg("accent", theme.bold("Optional comment")) : this.subtitle ? theme.fg("dim", this.subtitle) : "");
|
|
868
879
|
this.questionText.setText(theme.fg("text", theme.bold(this.question)));
|
|
869
880
|
if (this.contextComponent && this.context) {
|
|
870
881
|
if (this.contextComponent instanceof Markdown) (this.contextComponent as Markdown).setText(`**Context:**\n${this.context}`);
|
|
@@ -985,8 +996,13 @@ class AskComponent extends Container {
|
|
|
985
996
|
const editor = this.ensureEditor();
|
|
986
997
|
this.setEditorText(this.freeformDraft);
|
|
987
998
|
(editor as any).focused = this._focused;
|
|
988
|
-
|
|
989
|
-
|
|
999
|
+
// Only meaningful when reached by escaping OUT of a real select list ("instead of these
|
|
1000
|
+
// options, here's a custom one") -- with no options at all there's nothing to contrast
|
|
1001
|
+
// against, so the label is pure noise.
|
|
1002
|
+
if (this.options.length > 0) {
|
|
1003
|
+
this.modeContainer.addChild(new Text(this.theme.fg("accent", this.theme.bold("Custom answer")), 1, 0));
|
|
1004
|
+
this.modeContainer.addChild(new Spacer(1));
|
|
1005
|
+
}
|
|
990
1006
|
this.modeContainer.addChild(editor);
|
|
991
1007
|
this.updateHelpText();
|
|
992
1008
|
this.invalidate();
|
|
@@ -1165,7 +1181,7 @@ async function askQuestionBlocking(
|
|
|
1165
1181
|
const factory = (tui: TUI, theme: Theme, keybindings: KeybindingsManager, done: (result: AskResponse | null) => void) => {
|
|
1166
1182
|
if (params.signal) params.signal.addEventListener("abort", () => done(null), { once: true });
|
|
1167
1183
|
if (params.timeout && params.timeout > 0) setTimeout(() => done(null), params.timeout);
|
|
1168
|
-
return new AskComponent(params.question, normalizedContext, options, allowMultiple, allowFreeform, allowComment, displayMode, tui, theme, keybindings, shortcuts, done);
|
|
1184
|
+
return new AskComponent(params.question, normalizedContext, params.subtitle, options, allowMultiple, allowFreeform, allowComment, displayMode, tui, theme, keybindings, shortcuts, done);
|
|
1169
1185
|
};
|
|
1170
1186
|
|
|
1171
1187
|
const overlayToggle = shortcuts.overlayToggle;
|
package/extension/src/discuss.ts
CHANGED
|
@@ -82,14 +82,15 @@ export async function showDiscussions(ctx: ExtensionCommandContext): Promise<voi
|
|
|
82
82
|
}
|
|
83
83
|
if (choice === "Reply") {
|
|
84
84
|
const pending = (() => { try { return readDiscussionExtra(discussion.extra); } catch { return undefined; } })();
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
//
|
|
85
|
+
// Same fix as the live discuss tool: the most recent round's own content IS the real
|
|
86
|
+
// question -- the title becomes a plain orientation subtitle, not a labeled-backwards
|
|
87
|
+
// "Context:" section under a generic "Reply to <title>:" wrapper.
|
|
88
88
|
const transcript = await callService<Record<string, unknown>, DiscussionAndRounds>("discuss.show", { id: discussion.id });
|
|
89
|
-
const
|
|
89
|
+
const question = transcript.rounds.at(-1)?.content?.trim() || `Reply to "${discussion.title}":`;
|
|
90
|
+
const subtitle = discussion.title;
|
|
90
91
|
const answer = pending?.pendingOptions && pending.pendingOptions.length > 0 && pending.pendingOptionsMode
|
|
91
|
-
? await askQuestion(commandCtx, { question,
|
|
92
|
-
: await askQuestion(commandCtx, { question,
|
|
92
|
+
? await askQuestion(commandCtx, { question, subtitle, options: pending.pendingOptions.map((title) => ({ title })), allowMultiple: pending.pendingOptionsMode === "multi" })
|
|
93
|
+
: await askQuestion(commandCtx, { question, subtitle });
|
|
93
94
|
if (!answer) return; // canceled
|
|
94
95
|
await callService("discuss.reply", { id: discussion.id, actor: ACTOR, content: answer.content, ...(answer.selected ? { selected: answer.selected } : {}), source: SOURCE });
|
|
95
96
|
commandCtx.ui.notify(answer.selected ? `Selected: ${answer.selected.join(", ")}` : "Reply added.", "info");
|
|
@@ -42,22 +42,23 @@ function text(message: string, details: unknown = {}) {
|
|
|
42
42
|
async function liveAnswer(ctx: ExtensionContext, discussion: Artifact, latestContent: string | undefined, 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
|
-
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
const
|
|
45
|
+
// The just-recorded round's own content IS the real question -- a generic "Reply to <title>:"
|
|
46
|
+
// wrapper as the primary question, with the real content demoted to "Context:", left a human
|
|
47
|
+
// staring at a labeled-backwards prompt (live-observed). The wrapper is now only a fallback for
|
|
48
|
+
// the degenerate case of empty content; the title becomes a plain orientation subtitle instead.
|
|
49
|
+
const question = latestContent?.trim() || `Reply to "${discussion.title}":`;
|
|
50
|
+
const subtitle = discussion.title;
|
|
50
51
|
if (pending?.pendingOptions && pending.pendingOptions.length > 0 && pending.pendingOptionsMode) {
|
|
51
52
|
return askQuestion(ctx, {
|
|
52
53
|
question,
|
|
53
|
-
|
|
54
|
+
subtitle,
|
|
54
55
|
options: pending.pendingOptions.map((title) => ({ title })),
|
|
55
56
|
allowMultiple: pending.pendingOptionsMode === "multi",
|
|
56
57
|
onUpdate,
|
|
57
58
|
signal,
|
|
58
59
|
});
|
|
59
60
|
}
|
|
60
|
-
return askQuestion(ctx, { question,
|
|
61
|
+
return askQuestion(ctx, { question, subtitle, onUpdate, signal });
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
/**
|
package/package.json
CHANGED