gentle-pi 2.4.0 → 2.6.0
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 +292 -25
- package/assets/agents/gentle-ai-worker.md +13 -0
- package/assets/agents/jd-fix-agent.md +18 -0
- package/assets/agents/jd-judge-a.md +1 -1
- package/assets/agents/jd-judge-b.md +1 -1
- package/assets/agents/sdd-apply.md +7 -5
- package/assets/agents/sdd-archive.md +5 -3
- package/assets/agents/sdd-design.md +4 -0
- package/assets/agents/sdd-explore.md +4 -0
- package/assets/agents/sdd-init.md +4 -0
- package/assets/agents/sdd-onboard.md +4 -0
- package/assets/agents/sdd-proposal.md +4 -0
- package/assets/agents/sdd-remediate.md +37 -0
- package/assets/agents/sdd-research.md +26 -3
- package/assets/agents/sdd-spec.md +4 -0
- package/assets/agents/sdd-status.md +9 -75
- package/assets/agents/sdd-sync.md +4 -0
- package/assets/agents/sdd-tasks.md +4 -0
- package/assets/agents/sdd-verify.md +5 -3
- package/assets/chains/sdd-full.chain.md +4 -0
- package/assets/chains/sdd-plan.chain.md +4 -0
- package/assets/chains/sdd-verify.chain.md +4 -0
- package/assets/migrations/managed-assets-v2.5.0.json +7 -0
- package/assets/orchestrator-delegation.md +39 -11
- package/assets/orchestrator.md +5 -5
- package/assets/sdd-orchestrator-workflow.md +54 -21
- package/assets/support/sdd-status-contract.md +34 -90
- package/contracts/telemetry/runtime-aggregate-v1.schema.json +65 -0
- package/docs/delegated-verification.md +25 -0
- package/docs/telemetry.md +94 -0
- package/docs/windows-startup-console-visibility.md +18 -0
- package/extensions/ask-user-choice.ts +159 -25
- package/extensions/codegraph-tools.ts +95 -5
- package/extensions/gentle-agents.ts +1337 -0
- package/extensions/gentle-ai.ts +2916 -386
- package/extensions/gentle-shell.ts +650 -0
- package/extensions/gentle-todo.ts +234 -0
- package/extensions/quiet-tools.ts +2 -1
- package/extensions/runtime-metrics.ts +130 -0
- package/extensions/sdd-init.ts +2 -2
- package/extensions/startup-banner.ts +52 -75
- package/lib/agent-profiles.ts +550 -0
- package/lib/agents-completion-delivery.ts +72 -0
- package/lib/agents-config.ts +315 -0
- package/lib/agents-history.ts +88 -0
- package/lib/agents-messaging.ts +187 -0
- package/lib/agents-protocol.ts +501 -0
- package/lib/agents-runner.ts +1012 -0
- package/lib/agents-thread-view.ts +57 -0
- package/lib/agents-transcript.ts +87 -0
- package/lib/agents-view-layout.ts +40 -0
- package/lib/agents-view.ts +914 -0
- package/lib/agents-widget.ts +241 -0
- package/lib/gentle-ai-binary.ts +3 -1
- package/lib/gentle-ai-renderer.ts +143 -25
- package/lib/native-choice-list.ts +194 -0
- package/lib/native-fullscreen-interaction.ts +47 -0
- package/lib/native-pointer-region.ts +164 -0
- package/lib/native-review-cli.ts +371 -13
- package/lib/orchestrator-presence.ts +337 -0
- package/lib/profiles-orchestrator.ts +203 -0
- package/lib/review-candidate-view-owner.ts +427 -0
- package/lib/review-candidate-view.ts +150 -48
- package/lib/review-consent-component.ts +247 -0
- package/lib/review-consent-ui.ts +110 -0
- package/lib/review-host-relay.ts +28 -0
- package/lib/review-integration-v2.ts +243 -11
- package/lib/review-last-event-controller.ts +8 -4
- package/lib/review-relay-contract.ts +11 -0
- package/lib/review-reminder-receipt.ts +74 -0
- package/lib/review-repository.ts +2 -2
- package/lib/review-risk-assessment.ts +339 -0
- package/lib/review-session-standing-permission-ipc.ts +309 -0
- package/lib/review-session-standing-permission.ts +240 -0
- package/lib/runtime-metrics-children.ts +199 -0
- package/lib/runtime-metrics-delivery.ts +68 -0
- package/lib/runtime-metrics-native.ts +166 -0
- package/lib/runtime-metrics-pi-identity.ts +113 -0
- package/lib/runtime-metrics-policy.ts +51 -0
- package/lib/runtime-metrics.ts +255 -0
- package/lib/sdd-preflight.ts +362 -81
- package/lib/sdd-research-capabilities.ts +228 -0
- package/lib/sdd-status.ts +29 -7
- package/lib/session-worktree-registry.ts +118 -0
- package/lib/shell-bar.ts +184 -0
- package/lib/shell-card.ts +133 -0
- package/lib/shell-changes-view.ts +530 -0
- package/lib/shell-changes.ts +290 -0
- package/lib/shell-gauge.ts +40 -0
- package/lib/shell-prompt.ts +115 -0
- package/lib/shell-sidebar-banner.ts +11 -0
- package/lib/shell-sidebar-layout.ts +213 -0
- package/lib/shell-sidebar.ts +41 -0
- package/lib/shell-todo.ts +297 -0
- package/lib/shell-usage-view.ts +76 -0
- package/lib/shell-usage.ts +246 -0
- package/lib/telemetry-trigger.ts +153 -0
- package/package.json +8 -5
- package/runtime/gentle-ai-binary.mjs +3 -1
- package/runtime/native-review-cli.mjs +370 -12
- package/runtime/review-integration-v2.mjs +243 -11
- package/runtime/review-relay-contract.mjs +11 -0
- package/runtime/review-risk-assessment.mjs +340 -0
- package/runtime/telemetry-trigger.mjs +154 -0
- package/scripts/build-runtime-modules.mjs +11 -1
- package/scripts/check-types.mjs +125 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/install-gentle-ai.mjs +12 -0
- package/scripts/install-tui-mode-setting.mjs +114 -0
- package/scripts/test-packed-runner.mjs +38 -2
- package/scripts/types-baseline.json +99 -0
- package/scripts/verify-package-files.mjs +8 -2
- package/skills/_shared/review-ledger-contract.md +20 -2
- package/skills/issue-creation/SKILL.md +3 -3
- package/skills/judgment-day/SKILL.md +17 -3
- package/skills/judgment-day/references/prompts-and-formats.md +14 -3
- package/tests/agent-profiles.test.ts +722 -0
- package/tests/agents-completion-delivery.test.ts +94 -0
- package/tests/agents-config.test.ts +205 -0
- package/tests/agents-fake-child.ts +66 -0
- package/tests/agents-grouping.test.ts +179 -0
- package/tests/agents-history.test.ts +54 -0
- package/tests/agents-integration.test.ts +100 -0
- package/tests/agents-messaging.test.ts +94 -0
- package/tests/agents-protocol.test.ts +198 -0
- package/tests/agents-queries.test.ts +190 -0
- package/tests/agents-responsive.test.ts +43 -0
- package/tests/agents-runner-process.test.ts +111 -0
- package/tests/agents-runner.test.ts +959 -0
- package/tests/agents-thread-view.test.ts +45 -0
- package/tests/agents-transcript.test.ts +30 -0
- package/tests/agents-view.test.ts +685 -0
- package/tests/agents-widget.test.ts +141 -0
- package/tests/artifact-language.test.ts +25 -2
- package/tests/ask-user-choice.test.ts +325 -5
- package/tests/asset-installation-runtime.test.ts +108 -0
- package/tests/autonomous-guard.test.ts +116 -1
- package/tests/codegraph-tools.test.ts +112 -2
- package/tests/delegated-key-learnings-contract.test.ts +1 -1
- package/tests/devbinary/native-review-parity.devtest.ts +110 -0
- package/tests/feature-request-form.test.ts +67 -0
- package/tests/fixtures/agents-messaging-child.mjs +5 -0
- package/tests/fixtures/agents-process-child.mjs +23 -0
- package/tests/fixtures/runtime-metrics-native-batches.json +6 -0
- package/tests/gentle-agents.test.ts +2168 -0
- package/tests/gentle-ai-binary.test.ts +7 -2
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/gentle-ai-renderer.test.ts +103 -0
- package/tests/gentle-ai.test.ts +971 -15
- package/tests/gentle-card-text.ts +35 -0
- package/tests/gentle-shell.test.ts +818 -0
- package/tests/gentle-todo.test.ts +226 -0
- package/tests/install-tui-mode-setting.test.ts +324 -0
- package/tests/issue-creation-skill.test.ts +22 -0
- package/tests/model-routing-authority.test.ts +12 -0
- package/tests/native-choice-list.test.ts +202 -0
- package/tests/native-fullscreen-interaction.test.ts +125 -0
- package/tests/native-pointer-region.test.ts +245 -0
- package/tests/native-review-capability-contract.test.ts +27 -1
- package/tests/native-review-cli.test.ts +317 -3
- package/tests/native-review-consent.test.ts +91 -0
- package/tests/native-review-parity-runtime.test.ts +8 -2
- package/tests/native-review-parity.test.ts +43 -29
- package/tests/native-sdd-attempt-authority.test.ts +7 -2
- package/tests/orchestrator-budget.test.ts +69 -0
- package/tests/orchestrator-presence.test.ts +389 -0
- package/tests/orchestrator-rdd-ownership.test.ts +9 -0
- package/tests/package-manifest.test.ts +243 -7
- package/tests/profiles-orchestrator.test.ts +208 -0
- package/tests/quiet-tool-rendering.test.ts +97 -37
- package/tests/rdd-aware-verification-contract.test.ts +226 -0
- package/tests/rdd-status-line.test.ts +286 -0
- package/tests/review-agent-end-preflight.test.ts +332 -24
- package/tests/review-candidate-view.test.ts +751 -7
- package/tests/review-consent-ui.test.ts +352 -0
- package/tests/review-contract-prompt.test.ts +17 -0
- package/tests/review-controller-native-recovery.test.ts +29 -4
- package/tests/review-controller-native-routing.test.ts +884 -7
- package/tests/review-controller-workspace-root.test.ts +45 -2
- package/tests/review-controller.test.ts +26 -1
- package/tests/review-host-relay-restart-parity.test.ts +142 -1
- package/tests/review-host-relay-routing.test.ts +384 -8
- package/tests/review-host-relay.test.ts +29 -0
- package/tests/review-integration-v2-forward.test.ts +44 -0
- package/tests/review-integration-v2.test.ts +276 -0
- package/tests/review-last-event-closure.test.ts +112 -3
- package/tests/review-ledger-contract.test.ts +61 -6
- package/tests/review-relay-contract.test.ts +26 -0
- package/tests/review-reminder-receipt.test.ts +62 -0
- package/tests/review-repository.test.ts +28 -1
- package/tests/review-risk-assessment.test.ts +626 -0
- package/tests/review-session-standing-permission-controller.test.ts +656 -0
- package/tests/review-session-standing-permission-ipc.test.ts +233 -0
- package/tests/review-session-standing-permission-runtime.test.ts +212 -0
- package/tests/review-session-standing-permission.test.ts +156 -0
- package/tests/runtime-harness.mjs +447 -39
- package/tests/runtime-metrics-children.test.ts +206 -0
- package/tests/runtime-metrics-delivery.test.ts +85 -0
- package/tests/runtime-metrics-extension.test.ts +187 -0
- package/tests/runtime-metrics-native.test.ts +209 -0
- package/tests/runtime-metrics-pi-identity.test.ts +113 -0
- package/tests/runtime-metrics-policy.test.ts +62 -0
- package/tests/runtime-metrics.test.ts +184 -0
- package/tests/sdd-agent-tools.test.ts +10 -1
- package/tests/sdd-execution-routing-contract.test.ts +28 -0
- package/tests/sdd-managed-runtime-settlement.test.ts +331 -0
- package/tests/sdd-native-managed-uptake.test.ts +253 -0
- package/tests/sdd-planning-routing-contract.test.ts +45 -0
- package/tests/sdd-preflight.test.ts +252 -8
- package/tests/sdd-research-capabilities.test.ts +256 -0
- package/tests/sdd-research-live.test.ts +241 -0
- package/tests/sdd-selection-transport.test.ts +504 -0
- package/tests/sdd-status.test.ts +51 -0
- package/tests/session-worktree-registry.test.ts +135 -0
- package/tests/shell-bar.test.ts +176 -0
- package/tests/shell-card.test.ts +139 -0
- package/tests/shell-changes-view.test.ts +609 -0
- package/tests/shell-changes.test.ts +350 -0
- package/tests/shell-prompt.test.ts +140 -0
- package/tests/shell-sidebar-banner.test.ts +23 -0
- package/tests/shell-sidebar-layout.test.ts +387 -0
- package/tests/shell-sidebar.test.ts +50 -0
- package/tests/shell-todo.test.ts +259 -0
- package/tests/shell-usage-view.test.ts +62 -0
- package/tests/shell-usage.test.ts +197 -0
- package/tests/startup-banner.test.ts +126 -0
- package/tests/telemetry-trigger.test.ts +351 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import { Container,
|
|
3
|
+
import { Container, Input, isKeyRelease, matchesKey, Text, type KeybindingsManager, type TuiMouseEvent } from "@earendil-works/pi-tui";
|
|
4
4
|
import { type Static, Type } from "typebox";
|
|
5
|
+
import { NativeChoiceList } from "../lib/native-choice-list.ts";
|
|
6
|
+
import { createNativeFullscreenInteraction } from "../lib/native-fullscreen-interaction.ts";
|
|
5
7
|
|
|
6
8
|
const CHOICE_TOOL_NAME = "ask_user_choice";
|
|
7
9
|
const ASK_USER_CHOICE_BLOCKED_EVENT = "gentle-pi:ask-user-choice:blocked";
|
|
@@ -23,6 +25,9 @@ const ChoiceParamsSchema = Type.Object(
|
|
|
23
25
|
maxItems: 4,
|
|
24
26
|
description: "Two to four ordered closed options",
|
|
25
27
|
}),
|
|
28
|
+
allowCustomResponse: Type.Optional(Type.Boolean({
|
|
29
|
+
description: "Opt in to an Other… free-text response. Never enable for provider-owned consent prompts, maintenance authorizations, or any exact opaque-token decision.",
|
|
30
|
+
})),
|
|
26
31
|
},
|
|
27
32
|
{ additionalProperties: false },
|
|
28
33
|
);
|
|
@@ -40,9 +45,108 @@ interface ChoiceDetails {
|
|
|
40
45
|
question: string;
|
|
41
46
|
options: ChoiceOption[];
|
|
42
47
|
selection?: ChoiceSelection;
|
|
48
|
+
customResponse?: string;
|
|
43
49
|
cancelled?: true;
|
|
44
50
|
}
|
|
45
51
|
|
|
52
|
+
type ChoiceResult = ChoiceSelection | { customResponse: string };
|
|
53
|
+
|
|
54
|
+
class CustomResponseEditor extends Container {
|
|
55
|
+
private readonly input = new Input({ prompt: "> ", placeholder: "Type your response" });
|
|
56
|
+
private readonly validationText = new Text("", 1, 0);
|
|
57
|
+
private readonly keybindings: KeybindingsManager | undefined;
|
|
58
|
+
private readonly onSubmit: (value: string) => void;
|
|
59
|
+
private readonly onCancel: () => void;
|
|
60
|
+
|
|
61
|
+
constructor(
|
|
62
|
+
keybindings: KeybindingsManager | undefined,
|
|
63
|
+
onSubmit: (value: string) => void,
|
|
64
|
+
onCancel: () => void,
|
|
65
|
+
) {
|
|
66
|
+
super();
|
|
67
|
+
this.keybindings = keybindings;
|
|
68
|
+
this.onSubmit = onSubmit;
|
|
69
|
+
this.onCancel = onCancel;
|
|
70
|
+
this.input.focused = true;
|
|
71
|
+
this.addChild(new Text("Custom response", 1, 0));
|
|
72
|
+
this.addChild(this.input);
|
|
73
|
+
this.addChild(this.validationText);
|
|
74
|
+
this.addChild(new Text("Submit to continue • Cancel to return to choices", 1, 0));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
handleInput(data: string): void {
|
|
78
|
+
if (isKeyRelease(data)) return;
|
|
79
|
+
if (this.matches(data, "tui.select.cancel")) {
|
|
80
|
+
this.onCancel();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (this.matches(data, "tui.input.submit")) {
|
|
84
|
+
const value = this.input.getValue();
|
|
85
|
+
if (value.trim().length > 0) this.onSubmit(value);
|
|
86
|
+
else this.validationText.setText("Response cannot be empty.");
|
|
87
|
+
this.invalidate();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (this.matches(data, "tui.editor.deleteCharBackward")) {
|
|
91
|
+
this.input.handleInput(data);
|
|
92
|
+
this.validationText.setText("");
|
|
93
|
+
this.invalidate();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.input.handleInput(data);
|
|
97
|
+
this.validationText.setText("");
|
|
98
|
+
this.invalidate();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private matches(
|
|
102
|
+
data: string,
|
|
103
|
+
binding: "tui.select.cancel" | "tui.input.submit" | "tui.editor.deleteCharBackward",
|
|
104
|
+
): boolean {
|
|
105
|
+
if (this.keybindings?.matches) return this.keybindings.matches(data, binding);
|
|
106
|
+
const key = binding === "tui.select.cancel" ? "escape"
|
|
107
|
+
: binding === "tui.input.submit" ? "enter" : "backspace";
|
|
108
|
+
return matchesKey(data, key);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
class ChoiceModeView extends Container {
|
|
113
|
+
private editing = false;
|
|
114
|
+
private readonly list: NativeChoiceList<{ id: string; label: string; description: string }>;
|
|
115
|
+
private readonly editor: CustomResponseEditor;
|
|
116
|
+
|
|
117
|
+
constructor(
|
|
118
|
+
list: NativeChoiceList<{ id: string; label: string; description: string }>,
|
|
119
|
+
editor: CustomResponseEditor,
|
|
120
|
+
) {
|
|
121
|
+
super();
|
|
122
|
+
this.list = list;
|
|
123
|
+
this.editor = editor;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
showEditor(): void {
|
|
127
|
+
this.editing = true;
|
|
128
|
+
this.invalidate();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
showList(): void {
|
|
132
|
+
this.editing = false;
|
|
133
|
+
this.invalidate();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
handleInput(data: string): void {
|
|
137
|
+
if (this.editing) this.editor.handleInput(data);
|
|
138
|
+
else this.list.handleInput(data);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
override handleMouse(event: TuiMouseEvent) {
|
|
142
|
+
return this.editing ? undefined : this.list.handleMouse(event);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
override render(width: number): string[] {
|
|
146
|
+
return (this.editing ? this.editor : this.list).render(width);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
46
150
|
function reconcileToolAvailability(pi: ExtensionAPI, interactiveTui: boolean): void {
|
|
47
151
|
const active = pi.getActiveTools();
|
|
48
152
|
const isActive = active.includes(CHOICE_TOOL_NAME);
|
|
@@ -60,10 +164,12 @@ function resultDetails(params: ChoiceParams): ChoiceDetails {
|
|
|
60
164
|
export default function askUserChoice(pi: ExtensionAPI): void {
|
|
61
165
|
pi.registerTool({
|
|
62
166
|
name: CHOICE_TOOL_NAME,
|
|
167
|
+
renderShell: "self",
|
|
63
168
|
label: "Ask User Choice",
|
|
64
|
-
description: "Ask one
|
|
169
|
+
description: "Ask one single-select question with two to four ordered options. A custom response is available only when allowCustomResponse is explicitly enabled.",
|
|
65
170
|
promptGuidelines: [
|
|
66
|
-
"Use ask_user_choice only for one exactly representable
|
|
171
|
+
"Use ask_user_choice only for one exactly representable single-select question with 2-4 ordered options. Enable allowCustomResponse only when free text is safe and intended.",
|
|
172
|
+
"Never enable allowCustomResponse for provider-owned consent prompts, maintenance authorizations, or any decision that requires an exact opaque token.",
|
|
67
173
|
],
|
|
68
174
|
parameters: ChoiceParamsSchema,
|
|
69
175
|
executionMode: "sequential",
|
|
@@ -72,41 +178,62 @@ export default function askUserChoice(pi: ExtensionAPI): void {
|
|
|
72
178
|
throw new Error("ask_user_choice is unavailable outside the interactive TUI");
|
|
73
179
|
}
|
|
74
180
|
|
|
75
|
-
const items
|
|
76
|
-
|
|
181
|
+
const items = params.options.map((option, index) => ({
|
|
182
|
+
id: `choice-${index}`,
|
|
77
183
|
label: option.label,
|
|
78
184
|
description: option.description,
|
|
79
185
|
}));
|
|
80
|
-
|
|
186
|
+
const customItemId = "choice-custom-response";
|
|
187
|
+
if (params.allowCustomResponse === true) {
|
|
188
|
+
items.push({ id: customItemId, label: "Other…", description: "Provide a custom response" });
|
|
189
|
+
}
|
|
190
|
+
let selection: ChoiceResult | undefined;
|
|
81
191
|
try {
|
|
82
192
|
pi.events.emit(ASK_USER_CHOICE_BLOCKED_EVENT, { active: true });
|
|
83
|
-
selection = await ctx.ui.custom<
|
|
84
|
-
const
|
|
85
|
-
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
86
|
-
container.addChild(new Text(theme.fg("accent", theme.bold(params.question)), 1, 0));
|
|
87
|
-
const list = new SelectList(items, items.length, {
|
|
193
|
+
selection = await ctx.ui.custom<ChoiceResult | undefined>((tui, theme, keybindings, done) => {
|
|
194
|
+
const list = new NativeChoiceList(items, {
|
|
88
195
|
selectedPrefix: (text) => theme.fg("accent", text),
|
|
89
196
|
selectedText: (text) => theme.fg("accent", text),
|
|
90
197
|
description: (text) => theme.fg("muted", text),
|
|
91
|
-
|
|
92
|
-
|
|
198
|
+
hoverBackground: (text) => theme.bg("toolPendingBg", text),
|
|
199
|
+
}, keybindings);
|
|
200
|
+
let completed = false;
|
|
201
|
+
const finish = (result: ChoiceResult | undefined) => {
|
|
202
|
+
if (completed) return;
|
|
203
|
+
completed = true;
|
|
204
|
+
list.setDisabled(true);
|
|
205
|
+
done(result);
|
|
206
|
+
};
|
|
207
|
+
let view: ChoiceModeView;
|
|
208
|
+
const editor = new CustomResponseEditor(
|
|
209
|
+
keybindings,
|
|
210
|
+
(value) => finish({ customResponse: value }),
|
|
211
|
+
() => view.showList(),
|
|
212
|
+
);
|
|
213
|
+
view = new ChoiceModeView(list, editor);
|
|
214
|
+
const container = createNativeFullscreenInteraction({
|
|
215
|
+
keyboardTarget: view,
|
|
216
|
+
requestRender: () => tui.requestRender(),
|
|
217
|
+
mouseObserver: list.createMouseObserver(() => tui.requestRender()),
|
|
93
218
|
});
|
|
94
219
|
list.onSelect = (item) => {
|
|
95
|
-
|
|
96
|
-
|
|
220
|
+
if (item.id === customItemId) {
|
|
221
|
+
view.showEditor();
|
|
222
|
+
tui.requestRender();
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
const index = items.indexOf(item);
|
|
226
|
+
const option = params.options[index];
|
|
227
|
+
if (option) finish({ value: option.value, label: option.label, index: index + 1 });
|
|
228
|
+
}
|
|
97
229
|
};
|
|
98
|
-
list.onCancel = () =>
|
|
99
|
-
container.addChild(
|
|
230
|
+
list.onCancel = () => finish(undefined);
|
|
231
|
+
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
232
|
+
container.addChild(new Text(theme.fg("accent", theme.bold(params.question)), 1, 0));
|
|
233
|
+
container.addChild(view);
|
|
100
234
|
container.addChild(new Text(theme.fg("dim", "↑↓ navigate • Enter select • Esc cancel"), 1, 0));
|
|
101
235
|
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
102
|
-
return
|
|
103
|
-
render: (width) => container.render(width),
|
|
104
|
-
invalidate: () => container.invalidate(),
|
|
105
|
-
handleInput: (data) => {
|
|
106
|
-
list.handleInput(data);
|
|
107
|
-
tui.requestRender();
|
|
108
|
-
},
|
|
109
|
-
};
|
|
236
|
+
return container;
|
|
110
237
|
});
|
|
111
238
|
} finally {
|
|
112
239
|
pi.events.emit(ASK_USER_CHOICE_BLOCKED_EVENT, { active: false });
|
|
@@ -118,6 +245,12 @@ export default function askUserChoice(pi: ExtensionAPI): void {
|
|
|
118
245
|
details: { ...resultDetails(params), cancelled: true },
|
|
119
246
|
};
|
|
120
247
|
}
|
|
248
|
+
if ("customResponse" in selection) {
|
|
249
|
+
return {
|
|
250
|
+
content: [{ type: "text", text: `User responded: ${selection.customResponse}` }],
|
|
251
|
+
details: { ...resultDetails(params), customResponse: selection.customResponse },
|
|
252
|
+
};
|
|
253
|
+
}
|
|
121
254
|
return {
|
|
122
255
|
content: [{ type: "text", text: `User selected: ${selection.index}. ${selection.label} (value: ${selection.value})` }],
|
|
123
256
|
details: { ...resultDetails(params), selection },
|
|
@@ -141,6 +274,7 @@ export default function askUserChoice(pi: ExtensionAPI): void {
|
|
|
141
274
|
if (details?.selection) {
|
|
142
275
|
return new Text(theme.fg("success", `✓ ${details.selection.index}. ${details.selection.label}`), 0, 0);
|
|
143
276
|
}
|
|
277
|
+
if (details?.customResponse !== undefined) return new Text(theme.fg("success", "✓ Custom response"), 0, 0);
|
|
144
278
|
return new Text(theme.fg("warning", "Cancelled"), 0, 0);
|
|
145
279
|
},
|
|
146
280
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execFile, execFileSync } from "node:child_process";
|
|
2
|
-
import { lstatSync, realpathSync } from "node:fs";
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
|
|
@@ -177,18 +177,108 @@ function codeGraphFailureMessage(status: CodeGraphStatus): string {
|
|
|
177
177
|
: `CodeGraph failed to run. ${FALLBACK_INSTRUCTIONS}`;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
function isEnoent(error: unknown): boolean {
|
|
181
|
+
return (
|
|
182
|
+
typeof error === "object" &&
|
|
183
|
+
error !== null &&
|
|
184
|
+
"code" in error &&
|
|
185
|
+
(error as { code?: unknown }).code === "ENOENT"
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Find `codegraph.cmd` shims in Windows PATH order. */
|
|
190
|
+
function* codeGraphCmdPathsOnPath(): Iterable<string> {
|
|
191
|
+
const pathEnv = process.env.Path ?? process.env.PATH ?? "";
|
|
192
|
+
for (const entry of pathEnv.split(";")) {
|
|
193
|
+
const dir = entry.length >= 2 && entry.startsWith('"') && entry.endsWith('"')
|
|
194
|
+
? entry.slice(1, -1)
|
|
195
|
+
: entry;
|
|
196
|
+
if (!dir) continue;
|
|
197
|
+
const candidate = join(dir, "codegraph.cmd");
|
|
198
|
+
if (existsSync(candidate)) yield candidate;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Resolve the actual JS entry of the `@colbymchenry/codegraph` package from the
|
|
204
|
+
* npm global bin dir (sibling of the `codegraph.cmd` shim) and run it with the
|
|
205
|
+
* current Node executable. No shell involvement, so args are never interpreted.
|
|
206
|
+
*/
|
|
207
|
+
export function codeGraphNodeScript(cmdPath: string): string | undefined {
|
|
208
|
+
const binDir = dirname(cmdPath);
|
|
209
|
+
const pkgRoot = join(binDir, "node_modules", "@colbymchenry", "codegraph");
|
|
210
|
+
const pkgJsonPath = join(pkgRoot, "package.json");
|
|
211
|
+
if (!existsSync(pkgJsonPath)) return undefined;
|
|
212
|
+
try {
|
|
213
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8")) as { bin?: unknown };
|
|
214
|
+
const bin = pkg.bin;
|
|
215
|
+
const target =
|
|
216
|
+
typeof bin === "string"
|
|
217
|
+
? bin
|
|
218
|
+
: bin &&
|
|
219
|
+
typeof bin === "object" &&
|
|
220
|
+
"codegraph" in bin &&
|
|
221
|
+
typeof (bin as Record<string, unknown>).codegraph === "string"
|
|
222
|
+
? (bin as Record<string, string>).codegraph
|
|
223
|
+
: undefined;
|
|
224
|
+
if (typeof target !== "string" || !target) return undefined;
|
|
225
|
+
const script = join(pkgRoot, target);
|
|
226
|
+
return statSync(script).isFile() ? script : undefined;
|
|
227
|
+
} catch {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function* codeGraphNodeScriptsOnPath(): Iterable<string> {
|
|
233
|
+
for (const cmdPath of codeGraphCmdPathsOnPath()) {
|
|
234
|
+
const script = codeGraphNodeScript(cmdPath);
|
|
235
|
+
if (script) yield script;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Resolve the first valid CodeGraph npm entry point in Windows PATH order. */
|
|
240
|
+
export function findCodeGraphNodeScriptOnPath(): string | undefined {
|
|
241
|
+
return codeGraphNodeScriptsOnPath().next().value;
|
|
242
|
+
}
|
|
243
|
+
|
|
180
244
|
const runCodeGraphCommand: CodeGraphRunner = async (args, options) => {
|
|
181
|
-
const
|
|
245
|
+
const runOptions = {
|
|
182
246
|
cwd: options.cwd,
|
|
183
247
|
signal: options.signal,
|
|
184
248
|
maxBuffer: options.maxBuffer,
|
|
185
|
-
}
|
|
186
|
-
|
|
249
|
+
};
|
|
250
|
+
let unavailableError: unknown;
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
const result = await execFileAsync("codegraph", [...args], runOptions);
|
|
254
|
+
return { stdout: result.stdout, stderr: result.stderr };
|
|
255
|
+
} catch (error) {
|
|
256
|
+
// Windows npm installs only shims (codegraph.cmd/.ps1 and a shell script)
|
|
257
|
+
// with no codegraph.exe, so plain execFile (CreateProcess, no shell)
|
|
258
|
+
// always fails with ENOENT even when the shim is on PATH.
|
|
259
|
+
if (process.platform !== "win32" || !isEnoent(error)) throw error;
|
|
260
|
+
unavailableError = error;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Resolve the real package script from each npm global bin directory in PATH
|
|
264
|
+
// order and run it through the current Node executable. This remains
|
|
265
|
+
// shell-free, so argument boundaries are never interpreted by a shell.
|
|
266
|
+
for (const script of codeGraphNodeScriptsOnPath()) {
|
|
267
|
+
try {
|
|
268
|
+
const result = await execFileAsync(process.execPath, [script, ...args], runOptions);
|
|
269
|
+
return { stdout: result.stdout, stderr: result.stderr };
|
|
270
|
+
} catch (innerError) {
|
|
271
|
+
if (!isEnoent(innerError)) throw innerError;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
throw unavailableError;
|
|
187
276
|
};
|
|
188
277
|
|
|
189
278
|
export function createCodeGraphTool(runner: CodeGraphRunner = runCodeGraphCommand) {
|
|
190
279
|
return {
|
|
191
280
|
name: "codegraph",
|
|
281
|
+
renderShell: "self" as const,
|
|
192
282
|
label: "CodeGraph",
|
|
193
283
|
description:
|
|
194
284
|
"Initialize, search, or explore the CodeGraph index for the current Pi workspace only. This tool never accepts a project path or shell command.",
|