@hobin/developer 0.1.4 → 0.1.6
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/README.md +174 -39
- package/extensions/developer.ts +827 -123
- package/extensions/machine.ts +344 -0
- package/extensions/state.ts +242 -100
- package/extensions/tool-policy.ts +54 -25
- package/extensions/tui.ts +598 -155
- package/package.json +7 -2
- package/skills/abstraction-review/SKILL.md +6 -5
- package/skills/adversarial-eval/SKILL.md +7 -3
- package/skills/model/SKILL.md +13 -4
- package/skills/naming-judgment/SKILL.md +9 -3
- package/skills/schedule/SKILL.md +6 -3
- package/skills/signal/SKILL.md +6 -3
- package/skills/sketch/SKILL.md +29 -10
- package/skills/specify/SKILL.md +11 -2
- package/skills/verify/SKILL.md +10 -4
- package/skills/visualize/SKILL.md +5 -3
package/extensions/tui.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionCommandContext,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
KeybindingsManager,
|
|
5
|
+
Theme,
|
|
6
|
+
ThemeColor,
|
|
6
7
|
} from "@earendil-works/pi-coding-agent";
|
|
7
8
|
import {
|
|
8
9
|
Container,
|
|
9
10
|
matchesKey,
|
|
10
11
|
type SelectItem,
|
|
12
|
+
type SizeValue,
|
|
11
13
|
Text,
|
|
12
14
|
truncateToWidth,
|
|
13
15
|
visibleWidth,
|
|
@@ -16,13 +18,18 @@ import {
|
|
|
16
18
|
|
|
17
19
|
import {
|
|
18
20
|
protocolState,
|
|
21
|
+
type ChoiceResponseField,
|
|
22
|
+
type ChoiceResponseOption,
|
|
19
23
|
type DeveloperMode,
|
|
20
24
|
type DeveloperState,
|
|
25
|
+
type JudgmentStatus,
|
|
21
26
|
type PendingQuestion,
|
|
22
27
|
type ProtocolState,
|
|
23
28
|
} from "./state.ts";
|
|
24
29
|
|
|
25
|
-
export type DeveloperAction =
|
|
30
|
+
export type DeveloperAction =
|
|
31
|
+
| { kind: "command"; value: "status" | "questions" | DeveloperMode }
|
|
32
|
+
| { kind: "question"; questionId: string };
|
|
26
33
|
|
|
27
34
|
function modeName(mode: DeveloperMode): string {
|
|
28
35
|
if (mode === "on") return "adaptive";
|
|
@@ -31,7 +38,13 @@ function modeName(mode: DeveloperMode): string {
|
|
|
31
38
|
|
|
32
39
|
function protocolColor(value: ProtocolState): ThemeColor {
|
|
33
40
|
if (value === "blocked") return "error";
|
|
34
|
-
if (
|
|
41
|
+
if (
|
|
42
|
+
value === "needs-evidence" ||
|
|
43
|
+
value === "needs-answer" ||
|
|
44
|
+
value === "needs-routing" ||
|
|
45
|
+
value === "needs-verification"
|
|
46
|
+
)
|
|
47
|
+
return "warning";
|
|
35
48
|
if (value === "needs-judgment") return "accent";
|
|
36
49
|
return "dim";
|
|
37
50
|
}
|
|
@@ -42,6 +55,22 @@ function modeColor(mode: DeveloperMode): ThemeColor {
|
|
|
42
55
|
return "dim";
|
|
43
56
|
}
|
|
44
57
|
|
|
58
|
+
function judgmentColor(status: JudgmentStatus): ThemeColor {
|
|
59
|
+
if (status === "blocked") return "error";
|
|
60
|
+
if (status === "needs-evidence") return "warning";
|
|
61
|
+
if (status === "resolved") return "success";
|
|
62
|
+
return "muted";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function judgmentSummary(result: string): string {
|
|
66
|
+
return (
|
|
67
|
+
result
|
|
68
|
+
.split(/\r?\n/)
|
|
69
|
+
.map((line) => line.trim())
|
|
70
|
+
.find((line) => line && !line.startsWith("```")) ?? result
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
45
74
|
export function renderDeveloperFooter(state: DeveloperState, theme: Theme): string {
|
|
46
75
|
const currentProtocol = protocolState(state);
|
|
47
76
|
const target = state.activeRoute?.owner ?? "none";
|
|
@@ -64,13 +93,7 @@ export function developerActionItems(state: DeveloperState): SelectItem[] {
|
|
|
64
93
|
description: `${modeName(state.mode)} · ${currentProtocol} · ${state.pendingQuestions.length} open`,
|
|
65
94
|
},
|
|
66
95
|
];
|
|
67
|
-
|
|
68
|
-
items.push({
|
|
69
|
-
value: "questions",
|
|
70
|
-
label: "Revisit an open question",
|
|
71
|
-
description: `Choose from ${state.pendingQuestions.length} unresolved question(s) and prepare the editor`,
|
|
72
|
-
});
|
|
73
|
-
}
|
|
96
|
+
items.push(...pendingQuestionItems(state.pendingQuestions));
|
|
74
97
|
items.push(
|
|
75
98
|
{
|
|
76
99
|
value: "on",
|
|
@@ -80,7 +103,7 @@ export function developerActionItems(state: DeveloperState): SelectItem[] {
|
|
|
80
103
|
{
|
|
81
104
|
value: "strict",
|
|
82
105
|
label: state.mode === "strict" ? "Strict mode (active)" : "Strict mode",
|
|
83
|
-
description: "
|
|
106
|
+
description: "Allow bash on judgment routes; require direct for Pi built-in edit/write",
|
|
84
107
|
},
|
|
85
108
|
{
|
|
86
109
|
value: "off",
|
|
@@ -91,31 +114,84 @@ export function developerActionItems(state: DeveloperState): SelectItem[] {
|
|
|
91
114
|
return items;
|
|
92
115
|
}
|
|
93
116
|
|
|
117
|
+
function questionAction(owner: PendingQuestion["resolutionOwner"]): string {
|
|
118
|
+
if (owner === "user") return "enter to provide the required answer";
|
|
119
|
+
if (owner === "environment") return "enter to provide access or external evidence";
|
|
120
|
+
if (owner === "agent") return "enter to ask Pi to investigate";
|
|
121
|
+
return "enter to classify and resolve this legacy question";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function resolutionRequestLabel(owner: PendingQuestion["resolutionOwner"]): string {
|
|
125
|
+
if (owner === "user") return "Required answer or product decision:";
|
|
126
|
+
if (owner === "environment") return "External access, observation, or environment evidence:";
|
|
127
|
+
return "Evidence or investigation request for Pi:";
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const MAX_IMMEDIATE_REQUEST_BYTES = 16_000;
|
|
131
|
+
const MAX_IMMEDIATE_REQUEST_LINES = 1_000;
|
|
132
|
+
const ENABLE_MOUSE_SCROLL = "\u001b[?1000h\u001b[?1006h";
|
|
133
|
+
const DISABLE_MOUSE_SCROLL = "\u001b[?1006l\u001b[?1000l";
|
|
134
|
+
|
|
135
|
+
export type ImmediateQuestionDisposition = { kind: "answer"; request: string } | { kind: "defer" };
|
|
136
|
+
|
|
137
|
+
interface WritableTerminal {
|
|
138
|
+
write(data: string): void;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function enableMouseScroll(tui: { terminal?: WritableTerminal }): WritableTerminal | undefined {
|
|
142
|
+
const terminal = tui.terminal;
|
|
143
|
+
if (!terminal || typeof terminal.write !== "function") return undefined;
|
|
144
|
+
terminal.write(ENABLE_MOUSE_SCROLL);
|
|
145
|
+
return terminal;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function disableMouseScroll(terminal: WritableTerminal | undefined): void {
|
|
149
|
+
terminal?.write(DISABLE_MOUSE_SCROLL);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function mouseWheelDirection(data: string): -1 | 1 | undefined {
|
|
153
|
+
const match = /^\u001b\[<(\d+);\d+;\d+M$/.exec(data);
|
|
154
|
+
if (!match?.[1]) return undefined;
|
|
155
|
+
const button = Number(match[1]);
|
|
156
|
+
if ((button & 64) === 0 || (button & 3) > 1) return undefined;
|
|
157
|
+
return (button & 1) === 0 ? -1 : 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
94
160
|
export function pendingQuestionItems(questions: PendingQuestion[]): SelectItem[] {
|
|
95
|
-
return questions.map((question) =>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
161
|
+
return questions.map((question) => {
|
|
162
|
+
const action = questionAction(question.resolutionOwner);
|
|
163
|
+
return {
|
|
164
|
+
value: question.id,
|
|
165
|
+
label: question.question,
|
|
166
|
+
description: `${question.status} · ${question.resolutionOwner} · ${question.gate} · ${action}`,
|
|
167
|
+
};
|
|
168
|
+
});
|
|
100
169
|
}
|
|
101
170
|
|
|
102
171
|
interface SelectDialogOptions {
|
|
103
172
|
title: string;
|
|
104
173
|
subtitle: string;
|
|
105
174
|
items: SelectItem[];
|
|
106
|
-
width:
|
|
175
|
+
width: SizeValue;
|
|
176
|
+
minWidth: number;
|
|
107
177
|
maxVisible: number;
|
|
108
178
|
selectedLabelMaxLines: number;
|
|
109
179
|
selectedDescriptionMaxLines: number;
|
|
110
180
|
}
|
|
111
181
|
|
|
112
|
-
function boundedWrappedLines(
|
|
182
|
+
function boundedWrappedLines(
|
|
183
|
+
content: string,
|
|
184
|
+
width: number,
|
|
185
|
+
maxLines: number,
|
|
186
|
+
ellipsis: string,
|
|
187
|
+
): string[] {
|
|
113
188
|
const contentWidth = Math.max(1, width);
|
|
114
189
|
const wrapped = wrapTextWithAnsi(content, contentWidth);
|
|
115
190
|
const visible = wrapped.slice(0, Math.max(1, maxLines));
|
|
116
191
|
if (wrapped.length > visible.length && visible.length > 0) {
|
|
117
192
|
const last = visible.length - 1;
|
|
118
|
-
visible[last] =
|
|
193
|
+
visible[last] =
|
|
194
|
+
truncateToWidth(visible[last] ?? "", Math.max(1, contentWidth - 1), "") + ellipsis;
|
|
119
195
|
}
|
|
120
196
|
return visible;
|
|
121
197
|
}
|
|
@@ -136,6 +212,7 @@ class WrappedSelectList {
|
|
|
136
212
|
private readonly items: SelectItem[];
|
|
137
213
|
private readonly keybindings: KeybindingsManager;
|
|
138
214
|
private readonly maxVisible: number;
|
|
215
|
+
private readonly renderHeight: number;
|
|
139
216
|
private readonly selectedDescriptionMaxLines: number;
|
|
140
217
|
private readonly selectedLabelMaxLines: number;
|
|
141
218
|
private readonly theme: Theme;
|
|
@@ -158,6 +235,10 @@ class WrappedSelectList {
|
|
|
158
235
|
this.keybindings = keybindings;
|
|
159
236
|
this.selectedLabelMaxLines = options.selectedLabelMaxLines;
|
|
160
237
|
this.selectedDescriptionMaxLines = options.selectedDescriptionMaxLines;
|
|
238
|
+
this.renderHeight = Math.max(
|
|
239
|
+
1,
|
|
240
|
+
maxVisible - 1 + options.selectedLabelMaxLines + options.selectedDescriptionMaxLines + 1,
|
|
241
|
+
);
|
|
161
242
|
}
|
|
162
243
|
|
|
163
244
|
render(width: number): string[] {
|
|
@@ -166,7 +247,10 @@ class WrappedSelectList {
|
|
|
166
247
|
const lines: string[] = [];
|
|
167
248
|
const startIndex = Math.max(
|
|
168
249
|
0,
|
|
169
|
-
Math.min(
|
|
250
|
+
Math.min(
|
|
251
|
+
this.selectedIndex - Math.floor(this.maxVisible / 2),
|
|
252
|
+
this.items.length - this.maxVisible,
|
|
253
|
+
),
|
|
170
254
|
);
|
|
171
255
|
const endIndex = Math.min(startIndex + this.maxVisible, this.items.length);
|
|
172
256
|
|
|
@@ -202,18 +286,37 @@ class WrappedSelectList {
|
|
|
202
286
|
lines.push(
|
|
203
287
|
this.theme.fg(
|
|
204
288
|
"dim",
|
|
205
|
-
truncateToWidth(
|
|
289
|
+
truncateToWidth(
|
|
290
|
+
` (${this.selectedIndex + 1}/${this.items.length})`,
|
|
291
|
+
Math.max(1, width - 2),
|
|
292
|
+
"",
|
|
293
|
+
),
|
|
206
294
|
),
|
|
207
295
|
);
|
|
208
296
|
}
|
|
209
|
-
|
|
297
|
+
const visible = lines.slice(0, this.renderHeight);
|
|
298
|
+
while (visible.length < this.renderHeight) visible.push("");
|
|
299
|
+
return visible;
|
|
210
300
|
}
|
|
211
301
|
|
|
212
302
|
handleInput(data: string): void {
|
|
213
|
-
|
|
214
|
-
|
|
303
|
+
const wheelDirection = mouseWheelDirection(data);
|
|
304
|
+
if (wheelDirection !== undefined) {
|
|
305
|
+
this.moveSelection(wheelDirection * 3);
|
|
306
|
+
} else if (this.keybindings.matches(data, "tui.select.up")) {
|
|
307
|
+
this.selectedIndex =
|
|
308
|
+
this.selectedIndex === 0 ? this.items.length - 1 : this.selectedIndex - 1;
|
|
215
309
|
} else if (this.keybindings.matches(data, "tui.select.down")) {
|
|
216
|
-
this.selectedIndex =
|
|
310
|
+
this.selectedIndex =
|
|
311
|
+
this.selectedIndex === this.items.length - 1 ? 0 : this.selectedIndex + 1;
|
|
312
|
+
} else if (this.keybindings.matches(data, "tui.select.pageUp")) {
|
|
313
|
+
this.moveSelection(-Math.max(1, this.maxVisible - 1));
|
|
314
|
+
} else if (this.keybindings.matches(data, "tui.select.pageDown")) {
|
|
315
|
+
this.moveSelection(Math.max(1, this.maxVisible - 1));
|
|
316
|
+
} else if (matchesKey(data, "home")) {
|
|
317
|
+
this.selectedIndex = 0;
|
|
318
|
+
} else if (matchesKey(data, "end")) {
|
|
319
|
+
this.selectedIndex = Math.max(0, this.items.length - 1);
|
|
217
320
|
} else if (this.keybindings.matches(data, "tui.select.confirm")) {
|
|
218
321
|
const selected = this.items[this.selectedIndex];
|
|
219
322
|
if (selected) this.onSelect?.(selected);
|
|
@@ -222,72 +325,82 @@ class WrappedSelectList {
|
|
|
222
325
|
}
|
|
223
326
|
}
|
|
224
327
|
|
|
328
|
+
private moveSelection(delta: number): void {
|
|
329
|
+
this.selectedIndex = Math.max(0, Math.min(this.items.length - 1, this.selectedIndex + delta));
|
|
330
|
+
}
|
|
331
|
+
|
|
225
332
|
invalidate(): void {}
|
|
226
333
|
}
|
|
227
334
|
|
|
228
335
|
async function showSelectDialog(
|
|
229
|
-
ctx:
|
|
336
|
+
ctx: ExtensionContext,
|
|
230
337
|
options: SelectDialogOptions,
|
|
231
338
|
): Promise<string | undefined> {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
339
|
+
let mouseTerminal: WritableTerminal | undefined;
|
|
340
|
+
try {
|
|
341
|
+
const result = await ctx.ui.custom<string | null>(
|
|
342
|
+
(tui, theme, keybindings, done) => {
|
|
343
|
+
mouseTerminal = enableMouseScroll(tui);
|
|
344
|
+
const container = new Container();
|
|
345
|
+
const title = new Text("", 1, 0);
|
|
346
|
+
const subtitle = new Text("", 1, 0);
|
|
347
|
+
const hint = new Text("", 1, 0);
|
|
348
|
+
const updateText = () => {
|
|
349
|
+
title.setText(theme.fg("accent", theme.bold(`◆ ${options.title}`)));
|
|
350
|
+
subtitle.setText(theme.fg("muted", options.subtitle));
|
|
351
|
+
hint.setText(
|
|
352
|
+
theme.fg("dim", "wheel/↑↓ · PgUp/PgDn · Home/End · enter select · esc cancel"),
|
|
353
|
+
);
|
|
354
|
+
};
|
|
355
|
+
updateText();
|
|
356
|
+
|
|
357
|
+
const list = new WrappedSelectList(
|
|
358
|
+
options.items,
|
|
359
|
+
Math.min(options.items.length, options.maxVisible),
|
|
360
|
+
theme,
|
|
361
|
+
keybindings,
|
|
362
|
+
{
|
|
363
|
+
selectedLabelMaxLines: options.selectedLabelMaxLines,
|
|
364
|
+
selectedDescriptionMaxLines: options.selectedDescriptionMaxLines,
|
|
365
|
+
},
|
|
366
|
+
);
|
|
367
|
+
list.onSelect = (item) => done(item.value);
|
|
368
|
+
list.onCancel = () => done(null);
|
|
369
|
+
|
|
370
|
+
container.addChild(title);
|
|
371
|
+
container.addChild(subtitle);
|
|
372
|
+
container.addChild(list);
|
|
373
|
+
container.addChild(hint);
|
|
374
|
+
|
|
375
|
+
return {
|
|
376
|
+
render(width: number) {
|
|
377
|
+
return renderModalFrame(container, theme, width);
|
|
378
|
+
},
|
|
379
|
+
invalidate() {
|
|
380
|
+
updateText();
|
|
381
|
+
container.invalidate();
|
|
382
|
+
},
|
|
383
|
+
handleInput(data: string) {
|
|
384
|
+
list.handleInput(data);
|
|
385
|
+
tui.requestRender();
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
overlay: true,
|
|
391
|
+
overlayOptions: {
|
|
392
|
+
anchor: "center",
|
|
393
|
+
width: options.width,
|
|
394
|
+
minWidth: options.minWidth,
|
|
395
|
+
maxHeight: "88%",
|
|
396
|
+
margin: 1,
|
|
274
397
|
},
|
|
275
|
-
};
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
overlay: true,
|
|
279
|
-
overlayOptions: {
|
|
280
|
-
anchor: "center",
|
|
281
|
-
width: options.width,
|
|
282
|
-
maxHeight: Math.min(
|
|
283
|
-
options.maxVisible + options.selectedLabelMaxLines + options.selectedDescriptionMaxLines + 7,
|
|
284
|
-
24,
|
|
285
|
-
),
|
|
286
|
-
margin: 1,
|
|
287
398
|
},
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
399
|
+
);
|
|
400
|
+
return result ?? undefined;
|
|
401
|
+
} finally {
|
|
402
|
+
disableMouseScroll(mouseTerminal);
|
|
403
|
+
}
|
|
291
404
|
}
|
|
292
405
|
|
|
293
406
|
export async function showDeveloperActionSelector(
|
|
@@ -298,31 +411,278 @@ export async function showDeveloperActionSelector(
|
|
|
298
411
|
title: "Developer control",
|
|
299
412
|
subtitle: `Current · ${modeName(state.mode)} · ${protocolState(state)}`,
|
|
300
413
|
items: developerActionItems(state),
|
|
301
|
-
width:
|
|
414
|
+
width: "84%",
|
|
415
|
+
minWidth: 78,
|
|
302
416
|
maxVisible: 6,
|
|
303
|
-
selectedLabelMaxLines:
|
|
304
|
-
selectedDescriptionMaxLines:
|
|
417
|
+
selectedLabelMaxLines: 3,
|
|
418
|
+
selectedDescriptionMaxLines: 3,
|
|
305
419
|
});
|
|
306
|
-
if (result
|
|
307
|
-
|
|
420
|
+
if (!result) return undefined;
|
|
421
|
+
if (
|
|
422
|
+
result === "status" ||
|
|
423
|
+
result === "questions" ||
|
|
424
|
+
result === "on" ||
|
|
425
|
+
result === "strict" ||
|
|
426
|
+
result === "off"
|
|
427
|
+
) {
|
|
428
|
+
return { kind: "command", value: result };
|
|
429
|
+
}
|
|
430
|
+
if (state.pendingQuestions.some((question) => question.id === result)) {
|
|
431
|
+
return { kind: "question", questionId: result };
|
|
308
432
|
}
|
|
309
433
|
return undefined;
|
|
310
434
|
}
|
|
311
435
|
|
|
312
|
-
export
|
|
436
|
+
export function showPendingQuestionSelector(
|
|
313
437
|
ctx: ExtensionCommandContext,
|
|
314
438
|
questions: PendingQuestion[],
|
|
315
439
|
): Promise<string | undefined> {
|
|
316
|
-
if (questions.length === 0) return undefined;
|
|
440
|
+
if (questions.length === 0) return Promise.resolve(undefined);
|
|
317
441
|
return showSelectDialog(ctx, {
|
|
318
|
-
title: "
|
|
319
|
-
subtitle:
|
|
442
|
+
title: "Resolve an open Developer question",
|
|
443
|
+
subtitle:
|
|
444
|
+
"Enter opens an answer/evidence editor; the question closes after a resolved or not-applicable judgment",
|
|
320
445
|
items: pendingQuestionItems(questions),
|
|
321
|
-
width:
|
|
446
|
+
width: "92%",
|
|
447
|
+
minWidth: 88,
|
|
322
448
|
maxVisible: 10,
|
|
323
449
|
selectedLabelMaxLines: 5,
|
|
324
|
-
selectedDescriptionMaxLines:
|
|
450
|
+
selectedDescriptionMaxLines: 3,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
interface ChoiceResponseAnswer {
|
|
455
|
+
option: ChoiceResponseOption;
|
|
456
|
+
detail?: string;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function responseWithinLimits(
|
|
460
|
+
ctx: ExtensionContext,
|
|
461
|
+
request: string,
|
|
462
|
+
label = "Question response",
|
|
463
|
+
): boolean {
|
|
464
|
+
const requestBytes = new TextEncoder().encode(request).byteLength;
|
|
465
|
+
const requestLines = request.split(/\r?\n/).length;
|
|
466
|
+
if (requestBytes <= MAX_IMMEDIATE_REQUEST_BYTES && requestLines <= MAX_IMMEDIATE_REQUEST_LINES) {
|
|
467
|
+
return true;
|
|
468
|
+
}
|
|
469
|
+
ctx.ui.notify(
|
|
470
|
+
`${label} is too large (${requestBytes} bytes, ${requestLines} lines); shorten it before submitting.`,
|
|
471
|
+
"warning",
|
|
472
|
+
);
|
|
473
|
+
return false;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function choiceDetailInitial(field: ChoiceResponseField, option: ChoiceResponseOption): string {
|
|
477
|
+
return [
|
|
478
|
+
`Decision: ${field.prompt}`,
|
|
479
|
+
`Selected: ${option.value} — ${option.label}`,
|
|
480
|
+
"",
|
|
481
|
+
option.detailPrompt,
|
|
482
|
+
"",
|
|
483
|
+
"Required detail:",
|
|
484
|
+
].join("\n");
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async function editChoiceDetail(
|
|
488
|
+
ctx: ExtensionContext,
|
|
489
|
+
field: ChoiceResponseField,
|
|
490
|
+
option: ChoiceResponseOption,
|
|
491
|
+
): Promise<string | undefined> {
|
|
492
|
+
const initial = choiceDetailInitial(field, option);
|
|
493
|
+
while (true) {
|
|
494
|
+
const response = await ctx.ui.editor(`Add detail for ${field.id} · ${option.value}`, initial);
|
|
495
|
+
if (response === undefined) return undefined;
|
|
496
|
+
const detail = (
|
|
497
|
+
response.startsWith(initial) ? response.slice(initial.length) : response
|
|
498
|
+
).trim();
|
|
499
|
+
if (!detail) {
|
|
500
|
+
ctx.ui.notify("This choice requires a non-empty detail.", "warning");
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (!responseWithinLimits(ctx, detail, "Choice detail")) continue;
|
|
504
|
+
return detail;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function structuredResolutionRequest(
|
|
509
|
+
question: PendingQuestion,
|
|
510
|
+
answers: ReadonlyArray<ChoiceResponseAnswer | undefined>,
|
|
511
|
+
): string {
|
|
512
|
+
const fields = question.responseSpec?.fields ?? [];
|
|
513
|
+
const lines = [questionResolutionPrompt(question), "", "Structured answer:"];
|
|
514
|
+
for (const [index, field] of fields.entries()) {
|
|
515
|
+
const answer = answers[index];
|
|
516
|
+
if (!answer) continue;
|
|
517
|
+
lines.push(`- ${field.id}: ${answer.option.value} — ${answer.option.label}`);
|
|
518
|
+
if (answer.detail) lines.push(` Detail: ${answer.detail}`);
|
|
519
|
+
}
|
|
520
|
+
return lines.join("\n");
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
type ChoiceFieldAction =
|
|
524
|
+
| { kind: "answer"; answer: ChoiceResponseAnswer }
|
|
525
|
+
| { kind: "cancel" }
|
|
526
|
+
| { kind: "retry" };
|
|
527
|
+
|
|
528
|
+
type ChoiceReviewAction =
|
|
529
|
+
| { kind: "submit" }
|
|
530
|
+
| { kind: "edit"; fieldIndex: number }
|
|
531
|
+
| { kind: "back" };
|
|
532
|
+
|
|
533
|
+
async function selectChoiceAnswer(
|
|
534
|
+
ctx: ExtensionContext,
|
|
535
|
+
field: ChoiceResponseField,
|
|
536
|
+
fieldIndex: number,
|
|
537
|
+
fieldCount: number,
|
|
538
|
+
): Promise<ChoiceFieldAction> {
|
|
539
|
+
const selectedValue = await showSelectDialog(ctx, {
|
|
540
|
+
title: `Decision ${fieldIndex + 1}/${fieldCount} · ${field.id}`,
|
|
541
|
+
subtitle: field.description ?? field.prompt,
|
|
542
|
+
items: field.options.map((option) => ({
|
|
543
|
+
value: option.value,
|
|
544
|
+
label: `${option.value} · ${option.label}`,
|
|
545
|
+
description: option.description ?? field.prompt,
|
|
546
|
+
})),
|
|
547
|
+
width: "92%",
|
|
548
|
+
minWidth: 88,
|
|
549
|
+
maxVisible: Math.min(field.options.length, 12),
|
|
550
|
+
selectedLabelMaxLines: 3,
|
|
551
|
+
selectedDescriptionMaxLines: 4,
|
|
325
552
|
});
|
|
553
|
+
if (!selectedValue) return { kind: "cancel" };
|
|
554
|
+
const option = field.options.find((candidate) => candidate.value === selectedValue);
|
|
555
|
+
if (!option) return { kind: "retry" };
|
|
556
|
+
const detail = option.detailPrompt ? await editChoiceDetail(ctx, field, option) : undefined;
|
|
557
|
+
if (option.detailPrompt && detail === undefined) return { kind: "retry" };
|
|
558
|
+
return { kind: "answer", answer: { option, detail } };
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
async function reviewChoiceAnswers(
|
|
562
|
+
ctx: ExtensionContext,
|
|
563
|
+
question: PendingQuestion,
|
|
564
|
+
fields: ChoiceResponseField[],
|
|
565
|
+
answers: ReadonlyArray<ChoiceResponseAnswer | undefined>,
|
|
566
|
+
): Promise<ChoiceReviewAction> {
|
|
567
|
+
const action = await showSelectDialog(ctx, {
|
|
568
|
+
title: "Review structured answer",
|
|
569
|
+
subtitle: question.question,
|
|
570
|
+
items: [
|
|
571
|
+
{
|
|
572
|
+
value: "submit",
|
|
573
|
+
label: "Submit answers",
|
|
574
|
+
description: "Send these decisions to Pi; the question remains open until judgment",
|
|
575
|
+
},
|
|
576
|
+
...fields.map((field, index) => {
|
|
577
|
+
const answer = answers[index];
|
|
578
|
+
return {
|
|
579
|
+
value: `edit:${index}`,
|
|
580
|
+
label: `${field.id} · ${answer?.option.value ?? "missing"} — ${answer?.option.label ?? "Missing answer"}`,
|
|
581
|
+
description: answer?.detail ? `Detail: ${answer.detail}` : "Enter to change this answer",
|
|
582
|
+
};
|
|
583
|
+
}),
|
|
584
|
+
],
|
|
585
|
+
width: "92%",
|
|
586
|
+
minWidth: 88,
|
|
587
|
+
maxVisible: Math.min(fields.length + 1, 12),
|
|
588
|
+
selectedLabelMaxLines: 3,
|
|
589
|
+
selectedDescriptionMaxLines: 4,
|
|
590
|
+
});
|
|
591
|
+
if (action === "submit") return { kind: "submit" };
|
|
592
|
+
if (!action?.startsWith("edit:")) return { kind: "back" };
|
|
593
|
+
const fieldIndex = Number(action.slice("edit:".length));
|
|
594
|
+
if (!Number.isInteger(fieldIndex) || fieldIndex < 0 || fieldIndex >= fields.length) {
|
|
595
|
+
return { kind: "back" };
|
|
596
|
+
}
|
|
597
|
+
return { kind: "edit", fieldIndex };
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function previousChoiceField(
|
|
601
|
+
fieldIndex: number,
|
|
602
|
+
fieldCount: number,
|
|
603
|
+
returnToReview: boolean,
|
|
604
|
+
): number | undefined {
|
|
605
|
+
if (returnToReview) return fieldCount;
|
|
606
|
+
return fieldIndex === 0 ? undefined : fieldIndex - 1;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
async function collectChoiceResponse(
|
|
610
|
+
ctx: ExtensionContext,
|
|
611
|
+
question: PendingQuestion,
|
|
612
|
+
): Promise<string | undefined> {
|
|
613
|
+
const fields = question.responseSpec?.fields;
|
|
614
|
+
if (!fields || fields.length === 0) return undefined;
|
|
615
|
+
const answers: Array<ChoiceResponseAnswer | undefined> = [];
|
|
616
|
+
let fieldIndex = 0;
|
|
617
|
+
let returnToReview = false;
|
|
618
|
+
|
|
619
|
+
while (true) {
|
|
620
|
+
if (fieldIndex < fields.length) {
|
|
621
|
+
const field = fields[fieldIndex];
|
|
622
|
+
if (!field) return undefined;
|
|
623
|
+
const action = await selectChoiceAnswer(ctx, field, fieldIndex, fields.length);
|
|
624
|
+
if (action.kind === "retry") continue;
|
|
625
|
+
if (action.kind === "cancel") {
|
|
626
|
+
fieldIndex = previousChoiceField(fieldIndex, fields.length, returnToReview) ?? -1;
|
|
627
|
+
}
|
|
628
|
+
if (fieldIndex < 0) return undefined;
|
|
629
|
+
if (action.kind === "cancel") continue;
|
|
630
|
+
answers[fieldIndex] = action.answer;
|
|
631
|
+
fieldIndex = returnToReview ? fields.length : fieldIndex + 1;
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const action = await reviewChoiceAnswers(ctx, question, fields, answers);
|
|
636
|
+
if (action.kind === "submit") return structuredResolutionRequest(question, answers);
|
|
637
|
+
if (action.kind === "edit") {
|
|
638
|
+
fieldIndex = action.fieldIndex;
|
|
639
|
+
returnToReview = true;
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
returnToReview = false;
|
|
643
|
+
fieldIndex = fields.length - 1;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
export async function promptImmediateUserQuestion(
|
|
648
|
+
ctx: ExtensionContext,
|
|
649
|
+
question: PendingQuestion,
|
|
650
|
+
): Promise<ImmediateQuestionDisposition> {
|
|
651
|
+
while (true) {
|
|
652
|
+
const action = await showSelectDialog(ctx, {
|
|
653
|
+
title: "Developer needs a decision",
|
|
654
|
+
subtitle: question.question,
|
|
655
|
+
items: [
|
|
656
|
+
{
|
|
657
|
+
value: "answer",
|
|
658
|
+
label: "Answer now",
|
|
659
|
+
description:
|
|
660
|
+
"Review the full context and provide the decision required before direct work",
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
value: "defer",
|
|
664
|
+
label: "Leave open",
|
|
665
|
+
description: "Keep this question in Developer and answer it later",
|
|
666
|
+
},
|
|
667
|
+
],
|
|
668
|
+
width: "92%",
|
|
669
|
+
minWidth: 88,
|
|
670
|
+
maxVisible: 2,
|
|
671
|
+
selectedLabelMaxLines: 3,
|
|
672
|
+
selectedDescriptionMaxLines: 3,
|
|
673
|
+
});
|
|
674
|
+
if (action !== "answer") return { kind: "defer" };
|
|
675
|
+
|
|
676
|
+
const request = question.responseSpec
|
|
677
|
+
? await collectChoiceResponse(ctx, question)
|
|
678
|
+
: await ctx.ui.editor(
|
|
679
|
+
"Answer the new Developer question",
|
|
680
|
+
questionResolutionPrompt(question),
|
|
681
|
+
);
|
|
682
|
+
if (request === undefined) continue;
|
|
683
|
+
if (!responseWithinLimits(ctx, request)) continue;
|
|
684
|
+
return { kind: "answer", request };
|
|
685
|
+
}
|
|
326
686
|
}
|
|
327
687
|
|
|
328
688
|
export class DeveloperWidget {
|
|
@@ -346,19 +706,27 @@ export class DeveloperWidget {
|
|
|
346
706
|
);
|
|
347
707
|
}
|
|
348
708
|
for (const question of this.state.pendingQuestions.slice(0, 3)) {
|
|
709
|
+
const label = question.resolutionOwner === "user" ? "? answer" : "? evidence";
|
|
349
710
|
lines.push(
|
|
350
711
|
truncateToWidth(
|
|
351
|
-
`${this.theme.fg(question.status === "blocked" ? "error" : "warning",
|
|
712
|
+
`${this.theme.fg(question.status === "blocked" ? "error" : "warning", label)} ${this.theme.fg("dim", `· ${question.gate} ·`)} ${this.theme.fg("muted", question.question)}`,
|
|
352
713
|
width,
|
|
353
714
|
"…",
|
|
354
715
|
),
|
|
355
716
|
);
|
|
356
717
|
}
|
|
357
718
|
if (this.state.pendingQuestions.length > 3) {
|
|
358
|
-
lines.push(
|
|
719
|
+
lines.push(
|
|
720
|
+
this.theme.fg("dim", ` +${this.state.pendingQuestions.length - 3} more open questions`),
|
|
721
|
+
);
|
|
359
722
|
}
|
|
360
723
|
if (this.state.implementationFramingRequired) {
|
|
361
|
-
lines.push(
|
|
724
|
+
lines.push(
|
|
725
|
+
this.theme.fg("warning", "◇ gate · frame implementation before direct (sketch or signal)"),
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
if (this.state.rerouteRequired) {
|
|
729
|
+
lines.push(this.theme.fg("warning", "→ next · reroute from the latest direct landing"));
|
|
362
730
|
}
|
|
363
731
|
if (this.state.verificationRequired) {
|
|
364
732
|
lines.push(this.theme.fg("warning", "→ next · verify changed artifacts before completion"));
|
|
@@ -404,14 +772,24 @@ export class DeveloperStatusPanel {
|
|
|
404
772
|
this.onClose();
|
|
405
773
|
return;
|
|
406
774
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
775
|
+
const wheelDirection = mouseWheelDirection(data);
|
|
776
|
+
const pageSize = Math.max(1, this.viewportHeight - 6);
|
|
777
|
+
if (wheelDirection !== undefined) this.moveScroll(wheelDirection * 3);
|
|
778
|
+
else if (matchesKey(data, "up")) this.moveScroll(-1);
|
|
779
|
+
else if (matchesKey(data, "down")) this.moveScroll(1);
|
|
780
|
+
else if (matchesKey(data, "pageUp")) this.moveScroll(-pageSize);
|
|
781
|
+
else if (matchesKey(data, "pageDown")) this.moveScroll(pageSize);
|
|
782
|
+
else if (matchesKey(data, "home")) this.scrollOffset = 0;
|
|
783
|
+
else if (matchesKey(data, "end")) this.scrollOffset = this.maxScrollOffset;
|
|
784
|
+
else return;
|
|
411
785
|
this.invalidate();
|
|
412
786
|
this.requestRender();
|
|
413
787
|
}
|
|
414
788
|
|
|
789
|
+
private moveScroll(delta: number): void {
|
|
790
|
+
this.scrollOffset = Math.max(0, Math.min(this.maxScrollOffset, this.scrollOffset + delta));
|
|
791
|
+
}
|
|
792
|
+
|
|
415
793
|
render(width: number): string[] {
|
|
416
794
|
if (this.cachedLines && this.cachedWidth === width) return this.cachedLines;
|
|
417
795
|
|
|
@@ -419,8 +797,14 @@ export class DeveloperStatusPanel {
|
|
|
419
797
|
const innerWidth = Math.max(1, panelWidth - 2);
|
|
420
798
|
const rows: string[] = [];
|
|
421
799
|
const border = (text: string) => this.theme.fg("borderAccent", text);
|
|
422
|
-
const row = (content = "") =>
|
|
423
|
-
|
|
800
|
+
const row = (content = "") =>
|
|
801
|
+
`${border("│")}${truncateToWidth(content, innerWidth, "…", true)}${border("│")}`;
|
|
802
|
+
const addWrapped = (
|
|
803
|
+
label: string,
|
|
804
|
+
value: string,
|
|
805
|
+
color: ThemeColor = "muted",
|
|
806
|
+
maxLines = 2,
|
|
807
|
+
) => {
|
|
424
808
|
const labelText = `${label} ·`;
|
|
425
809
|
const plainPrefix = ` ${labelText} `;
|
|
426
810
|
const styledPrefix = ` ${this.theme.fg("dim", labelText)} `;
|
|
@@ -430,13 +814,15 @@ export class DeveloperStatusPanel {
|
|
|
430
814
|
if (wrapped.length > visible.length && visible.length > 0) {
|
|
431
815
|
const last = visible.length - 1;
|
|
432
816
|
visible[last] =
|
|
433
|
-
truncateToWidth(visible[last] ?? "", Math.max(1, contentWidth - 1), "") +
|
|
817
|
+
truncateToWidth(visible[last] ?? "", Math.max(1, contentWidth - 1), "") +
|
|
818
|
+
this.theme.fg("dim", "…");
|
|
434
819
|
}
|
|
435
820
|
rows.push(row(styledPrefix + (visible[0] ?? "")));
|
|
436
821
|
const hangingIndent = " ".repeat(visibleWidth(plainPrefix));
|
|
437
822
|
for (const line of visible.slice(1)) rows.push(row(hangingIndent + line));
|
|
438
823
|
};
|
|
439
|
-
const section = (title: string) =>
|
|
824
|
+
const section = (title: string) =>
|
|
825
|
+
rows.push(row(` ${this.theme.fg("accent", this.theme.bold(title))}`));
|
|
440
826
|
|
|
441
827
|
rows.push(border(`╭${"─".repeat(innerWidth)}╮`));
|
|
442
828
|
rows.push(row(` ${this.theme.fg("accent", this.theme.bold("◆ Developer status"))}`));
|
|
@@ -450,7 +836,8 @@ export class DeveloperStatusPanel {
|
|
|
450
836
|
`protocol ${this.theme.fg(protocolColor(currentProtocol), currentProtocol)}` +
|
|
451
837
|
this.theme.fg("dim", " · ") +
|
|
452
838
|
`target ${this.theme.fg("muted", state.activeRoute?.owner ?? "none")}`;
|
|
453
|
-
for (const line of wrapTextWithAnsi(summary, Math.max(1, innerWidth - 2)))
|
|
839
|
+
for (const line of wrapTextWithAnsi(summary, Math.max(1, innerWidth - 2)))
|
|
840
|
+
rows.push(row(` ${line}`));
|
|
454
841
|
rows.push(row());
|
|
455
842
|
|
|
456
843
|
section("Active route");
|
|
@@ -471,41 +858,60 @@ export class DeveloperStatusPanel {
|
|
|
471
858
|
} else {
|
|
472
859
|
for (const question of state.pendingQuestions.slice(0, 4)) {
|
|
473
860
|
addWrapped(
|
|
474
|
-
question.status
|
|
861
|
+
`${question.status} · ${question.resolutionOwner} · ${question.gate}`,
|
|
475
862
|
question.question,
|
|
476
863
|
question.status === "blocked" ? "error" : "warning",
|
|
477
864
|
2,
|
|
478
865
|
);
|
|
866
|
+
addWrapped("resolves when", question.resolutionCriteria, "dim", 2);
|
|
479
867
|
}
|
|
480
868
|
if (state.pendingQuestions.length > 4) {
|
|
481
|
-
addWrapped(
|
|
869
|
+
addWrapped(
|
|
870
|
+
"more",
|
|
871
|
+
`${state.pendingQuestions.length - 4} additional open questions`,
|
|
872
|
+
"dim",
|
|
873
|
+
1,
|
|
874
|
+
);
|
|
482
875
|
}
|
|
483
876
|
}
|
|
484
877
|
|
|
485
878
|
rows.push(row());
|
|
486
|
-
section(
|
|
487
|
-
if (state.
|
|
488
|
-
addWrapped(
|
|
489
|
-
"status",
|
|
490
|
-
state.lastJudgment.status,
|
|
491
|
-
protocolColor(
|
|
492
|
-
state.lastJudgment.status === "blocked"
|
|
493
|
-
? "blocked"
|
|
494
|
-
: state.lastJudgment.status === "needs-evidence"
|
|
495
|
-
? "needs-evidence"
|
|
496
|
-
: "idle",
|
|
497
|
-
),
|
|
498
|
-
1,
|
|
499
|
-
);
|
|
500
|
-
addWrapped("result", state.lastJudgment.result, "muted", 3);
|
|
501
|
-
addWrapped(
|
|
502
|
-
"evidence",
|
|
503
|
-
`${state.lastJudgment.basis.length} basis · ${state.lastJudgment.artifacts.length} artifacts`,
|
|
504
|
-
"dim",
|
|
505
|
-
1,
|
|
506
|
-
);
|
|
507
|
-
} else {
|
|
879
|
+
section(`Judgment history · ${state.judgmentHistory.length}`);
|
|
880
|
+
if (state.judgmentHistory.length === 0) {
|
|
508
881
|
addWrapped("state", "No judgment has been recorded on this branch.", "dim", 2);
|
|
882
|
+
} else {
|
|
883
|
+
const recentJudgments = state.judgmentHistory.slice(-10).toReversed();
|
|
884
|
+
for (const judgment of recentJudgments) {
|
|
885
|
+
const route = state.routeHistory.find(
|
|
886
|
+
(candidate) => candidate.routeId === judgment.routeId,
|
|
887
|
+
);
|
|
888
|
+
addWrapped(
|
|
889
|
+
`${judgment.owner} ${judgment.status}`,
|
|
890
|
+
`${judgment.question} → ${judgmentSummary(judgment.result)}`,
|
|
891
|
+
judgmentColor(judgment.status),
|
|
892
|
+
3,
|
|
893
|
+
);
|
|
894
|
+
if (route) addWrapped("route reason", route.reason, "dim", 2);
|
|
895
|
+
for (const alternative of route?.consideredAlternatives ?? []) {
|
|
896
|
+
addWrapped(`considered ${alternative.owner}`, alternative.reason, "dim", 2);
|
|
897
|
+
}
|
|
898
|
+
for (const update of judgment.questionUpdates ?? []) {
|
|
899
|
+
addWrapped(
|
|
900
|
+
`question ${update.status}`,
|
|
901
|
+
`${update.questionId} → ${update.result}`,
|
|
902
|
+
"accent",
|
|
903
|
+
2,
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (state.judgmentHistory.length > recentJudgments.length) {
|
|
908
|
+
addWrapped(
|
|
909
|
+
"earlier",
|
|
910
|
+
`${state.judgmentHistory.length - recentJudgments.length} earlier judgments`,
|
|
911
|
+
"dim",
|
|
912
|
+
1,
|
|
913
|
+
);
|
|
914
|
+
}
|
|
509
915
|
}
|
|
510
916
|
|
|
511
917
|
rows.push(row());
|
|
@@ -523,9 +929,10 @@ export class DeveloperStatusPanel {
|
|
|
523
929
|
this.maxScrollOffset = Math.max(0, body.length - bodyCapacity);
|
|
524
930
|
this.scrollOffset = Math.min(this.scrollOffset, this.maxScrollOffset);
|
|
525
931
|
const visibleBody = body.slice(this.scrollOffset, this.scrollOffset + bodyCapacity);
|
|
932
|
+
while (visibleBody.length < bodyCapacity) visibleBody.push(row());
|
|
526
933
|
const position =
|
|
527
934
|
body.length > bodyCapacity
|
|
528
|
-
?
|
|
935
|
+
? `wheel/↑↓ · PgUp/PgDn · Home/End · ${this.scrollOffset + 1}–${Math.min(body.length, this.scrollOffset + bodyCapacity)}/${body.length} · enter/esc close`
|
|
529
936
|
: "enter/esc close · /develop questions revisits open work";
|
|
530
937
|
const visibleRows = [
|
|
531
938
|
...header,
|
|
@@ -545,28 +952,64 @@ export class DeveloperStatusPanel {
|
|
|
545
952
|
}
|
|
546
953
|
}
|
|
547
954
|
|
|
548
|
-
export async function showDeveloperStatus(
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
maxHeight: 28,
|
|
562
|
-
margin: 1,
|
|
955
|
+
export async function showDeveloperStatus(
|
|
956
|
+
ctx: ExtensionCommandContext,
|
|
957
|
+
view: DeveloperStatusView,
|
|
958
|
+
): Promise<void> {
|
|
959
|
+
let mouseTerminal: WritableTerminal | undefined;
|
|
960
|
+
try {
|
|
961
|
+
await ctx.ui.custom<void>(
|
|
962
|
+
(tui, theme, _keybindings, done) => {
|
|
963
|
+
mouseTerminal = enableMouseScroll(tui);
|
|
964
|
+
return new DeveloperStatusPanel(view, theme, () => done(), {
|
|
965
|
+
viewportHeight: Math.max(10, Math.min(36, Math.floor(tui.terminal.rows * 0.88))),
|
|
966
|
+
requestRender: () => tui.requestRender(),
|
|
967
|
+
});
|
|
563
968
|
},
|
|
564
|
-
|
|
565
|
-
|
|
969
|
+
{
|
|
970
|
+
overlay: true,
|
|
971
|
+
overlayOptions: {
|
|
972
|
+
anchor: "center",
|
|
973
|
+
width: "90%",
|
|
974
|
+
minWidth: 72,
|
|
975
|
+
maxHeight: "88%",
|
|
976
|
+
margin: 1,
|
|
977
|
+
},
|
|
978
|
+
},
|
|
979
|
+
);
|
|
980
|
+
} finally {
|
|
981
|
+
disableMouseScroll(mouseTerminal);
|
|
982
|
+
}
|
|
566
983
|
}
|
|
567
984
|
|
|
568
|
-
export function
|
|
569
|
-
const
|
|
985
|
+
export function questionResolutionPrompt(question: PendingQuestion): string {
|
|
986
|
+
const requestLabel = resolutionRequestLabel(question.resolutionOwner);
|
|
987
|
+
const context = question.context
|
|
988
|
+
? ["", "Decision or evidence context:", question.context, ""]
|
|
989
|
+
: [];
|
|
990
|
+
return [
|
|
991
|
+
"Resolve this open Developer question.",
|
|
992
|
+
"",
|
|
993
|
+
`Question: ${question.question}`,
|
|
994
|
+
...context,
|
|
995
|
+
`Resolution owner: ${question.resolutionOwner}`,
|
|
996
|
+
`Gate: ${question.gate}`,
|
|
997
|
+
`Resolution criteria: ${question.resolutionCriteria}`,
|
|
998
|
+
"",
|
|
999
|
+
requestLabel,
|
|
1000
|
+
"",
|
|
1001
|
+
"Use the supplied answer as new evidence, or investigate with available tools when the owner is agent.",
|
|
1002
|
+
"Route the focused question, then record resolved/not-applicable with question_updates when it is settled; otherwise retain it with the specific remaining evidence gap.",
|
|
1003
|
+
].join("\n");
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
export function editQuestionResolutionRequest(
|
|
1007
|
+
ctx: ExtensionCommandContext,
|
|
1008
|
+
question: PendingQuestion,
|
|
1009
|
+
): Promise<string | undefined> {
|
|
1010
|
+
if (question.responseSpec) return collectChoiceResponse(ctx, question);
|
|
570
1011
|
const current = ctx.ui.getEditorText();
|
|
571
|
-
|
|
1012
|
+
const request = questionResolutionPrompt(question);
|
|
1013
|
+
const initial = current.trim() ? `${current.trimEnd()}\n\n${request}` : request;
|
|
1014
|
+
return ctx.ui.editor("Answer or investigate the selected Developer question", initial);
|
|
572
1015
|
}
|