gentle-pi 2.7.0 → 3.0.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 +24 -6
- package/assets/agents/gentle-ai-worker.md +5 -1
- package/assets/agents/sdd-apply.md +9 -7
- package/assets/agents/sdd-archive.md +42 -23
- package/assets/agents/sdd-proposal.md +2 -2
- package/assets/agents/sdd-remediate.md +4 -4
- package/assets/agents/sdd-research.md +20 -48
- package/assets/agents/sdd-tasks.md +5 -5
- package/assets/agents/sdd-verify.md +6 -28
- package/assets/chains/sdd-full.chain.md +4 -22
- package/assets/chains/sdd-verify.chain.md +3 -12
- package/assets/orchestrator-delegation.md +33 -3
- package/assets/orchestrator-memory.md +20 -7
- package/assets/orchestrator.md +5 -3
- package/assets/sdd-orchestrator-workflow.md +25 -58
- package/assets/support/sdd-status-contract.md +9 -12
- package/docs/gentle-shell.md +14 -4
- package/docs/readme-reference.md +171 -32
- package/extensions/codegraph-tools.ts +2 -0
- package/extensions/gentle-agents.ts +281 -361
- package/extensions/gentle-ai.ts +588 -117
- package/extensions/gentle-shell.ts +74 -30
- package/extensions/pi-pretty.ts +63 -14
- package/extensions/quiet-tools.ts +1 -2
- package/extensions/startup-banner.ts +10 -9
- package/lib/agent-home.ts +8 -0
- package/lib/agent-profile-pin.ts +336 -0
- package/lib/agent-profiles.ts +28 -8
- package/lib/agents-config.ts +24 -2
- package/lib/agents-history.ts +3 -97
- package/lib/agents-keys.ts +27 -0
- package/lib/agents-protocol.ts +2 -15
- package/lib/agents-runner.ts +67 -111
- package/lib/agents-session-transport.ts +691 -0
- package/lib/command-palette-catalog.ts +87 -0
- package/lib/command-palette.ts +346 -0
- package/lib/native-choice-list.ts +5 -0
- package/lib/native-review-cli.ts +10 -97
- package/lib/review-publication-gate.ts +11 -1
- package/lib/review-repository.ts +1 -1
- package/lib/review-snapshot.ts +1 -0
- package/lib/review-transaction.ts +4 -2
- package/lib/sdd-preflight.ts +2 -1
- package/lib/sdd-research-capabilities.ts +18 -152
- package/lib/sdd-status.ts +7 -779
- package/lib/session-change-capture.ts +2 -1
- package/lib/session-changes.ts +8 -1
- package/lib/shell-bar.ts +21 -12
- package/lib/shell-card.ts +8 -12
- package/lib/shell-changes.ts +3 -2
- package/lib/shell-prompt.ts +25 -8
- package/lib/shell-sidebar-banner.ts +2 -2
- package/lib/shell-sidebar-layout.ts +5 -2
- package/lib/windows-session-transport.ts +877 -0
- package/package.json +3 -3
- package/runtime/native-review-cli.mjs +9 -96
- package/runtime/windows-session-transport.ps1 +791 -0
- package/scripts/test-packed-runner.mjs +1668 -20
- package/scripts/verify-package-files.mjs +0 -1
- package/tests/agent-home.test.ts +52 -0
- package/tests/agent-profiles.test.ts +30 -1
- package/tests/agents-config.test.ts +44 -0
- package/tests/agents-history.test.ts +12 -24
- package/tests/agents-runner.test.ts +307 -58
- package/tests/agents-session-transport-process.test.ts +249 -0
- package/tests/agents-session-transport.test.ts +823 -0
- package/tests/artifact-language.test.ts +10 -7
- package/tests/command-palette.test.ts +378 -0
- package/tests/delegated-key-learnings-contract.test.ts +2 -2
- package/tests/fixtures/agents-session-transport-process.mjs +108 -0
- package/tests/fixtures/legacy/sdd-research-v2.5.0.md +54 -0
- package/tests/fixtures/windows-session-bootstrap.ps1 +129 -0
- package/tests/fixtures/windows-session-compile.ps1 +110 -0
- package/tests/gentle-agents.test.ts +849 -356
- package/tests/gentle-ai.test.ts +472 -4
- package/tests/gentle-shell.test.ts +201 -8
- package/tests/native-choice-list.test.ts +13 -0
- package/tests/native-review-cli.test.ts +0 -33
- package/tests/odd-routing-contract.test.ts +208 -0
- package/tests/orchestrator-budget.test.ts +17 -2
- package/tests/package-manifest.test.ts +115 -27
- package/tests/persona-single-channel.test.ts +3 -3
- package/tests/pi-pretty.test.ts +45 -0
- package/tests/profile-pin.test.ts +370 -0
- package/tests/quiet-tool-rendering.test.ts +32 -5
- package/tests/review-contract-prompt.test.ts +9 -0
- package/tests/review-controller.test.ts +0 -44
- package/tests/review-session-standing-permission-ipc.test.ts +427 -13
- package/tests/runtime-harness.mjs +4 -4
- package/tests/sdd-agent-tools.test.ts +15 -36
- package/tests/sdd-archive-replay.test.ts +82 -0
- package/tests/sdd-classical-continuation.test.ts +74 -0
- package/tests/sdd-execution-routing-contract.test.ts +18 -2
- package/tests/sdd-managed-runtime-settlement.test.ts +42 -330
- package/tests/sdd-native-managed-uptake.test.ts +11 -21
- package/tests/sdd-no-attempts-contract.test.ts +15 -0
- package/tests/sdd-odd-integration.test.ts +33 -0
- package/tests/sdd-optional-research.test.ts +124 -0
- package/tests/sdd-planning-routing-contract.test.ts +1 -1
- package/tests/sdd-preflight-rpc-input.test.ts +125 -0
- package/tests/sdd-preflight.test.ts +1 -1
- package/tests/sdd-research-capabilities.test.ts +20 -162
- package/tests/sdd-selection-transport.test.ts +180 -88
- package/tests/sdd-status.test.ts +5 -778
- package/tests/sdd-task-truth.test.ts +43 -0
- package/tests/session-change-capture.test.ts +20 -2
- package/tests/session-changes.test.ts +11 -0
- package/tests/shell-bar.test.ts +21 -0
- package/tests/shell-card.test.ts +8 -6
- package/tests/shell-changes.test.ts +8 -0
- package/tests/shell-prompt.test.ts +41 -7
- package/tests/shell-sidebar-banner.test.ts +4 -4
- package/tests/shell-sidebar-layout.test.ts +97 -13
- package/tests/startup-banner.test.ts +55 -2
- package/tests/windows-hidden-processes.test.ts +303 -0
- package/tests/windows-session-bootstrap.test.ts +1772 -0
- package/tests/windows-session-compile.test.ts +170 -0
- package/tests/windows-session-transport.test.ts +754 -0
- package/assets/agents/sdd-sync.md +0 -146
- package/lib/openspec-guardrails.ts +0 -99
- package/tests/native-sdd-attempt-authority.test.ts +0 -240
- package/tests/openspec-guardrails.test.ts +0 -71
|
@@ -10,8 +10,11 @@ import { CHANGE_STATUS, renderChangesWidget, type ChangedFile, type ChangesModel
|
|
|
10
10
|
import { WorktreeChangesView } from "../lib/shell-changes-view.ts";
|
|
11
11
|
import { SessionWorktreeRegistry, resolveSessionWorktree, worktreeGitEnvironment, type WorktreeResolver } from "../lib/session-worktree-registry.ts";
|
|
12
12
|
import { CARD_TONE, renderCard, type Card, type CardTheme } from "../lib/shell-card.ts";
|
|
13
|
+
import { CommandPalette, commandsKey, type CommandPaletteResult } from "../lib/command-palette.ts";
|
|
14
|
+
import { buildCommandPaletteGroups } from "../lib/command-palette-catalog.ts";
|
|
15
|
+
import { agentsViewKey } from "../lib/agents-keys.ts";
|
|
13
16
|
import { GentleAiDevBinaryOverrideError, resolveGentleAiDevBinaryOverride } from "../lib/gentle-ai-binary.ts";
|
|
14
|
-
import { framePromptLines, PROMPT_HINT, PROMPT_STATE, withPromptHint, type PromptState } from "../lib/shell-prompt.ts";
|
|
17
|
+
import { framePromptLines, PROMPT_HINT, PROMPT_STATE, SHELL_PULSE_MS, withPromptHint, type PromptState } from "../lib/shell-prompt.ts";
|
|
15
18
|
import { accountIdFromToken, CODEX_PROVIDER, CODEX_USAGE_URL, parseCodexUsage, parseUsageHeaders, UsageStore, type ProviderUsage } from "../lib/shell-usage.ts";
|
|
16
19
|
import { UsageView } from "../lib/shell-usage-view.ts";
|
|
17
20
|
import { sidebarPart } from "../lib/shell-sidebar.ts";
|
|
@@ -182,8 +185,6 @@ interface PromptEditorDeps {
|
|
|
182
185
|
|
|
183
186
|
const PROMPT_FRAME_ROLE = "border";
|
|
184
187
|
|
|
185
|
-
const PETAL_PULSE_MS = 160;
|
|
186
|
-
|
|
187
188
|
export class GentlePromptEditor extends CustomEditor {
|
|
188
189
|
private promptState: PromptState = PROMPT_STATE.IDLE;
|
|
189
190
|
private tick = 0;
|
|
@@ -202,7 +203,7 @@ export class GentlePromptEditor extends CustomEditor {
|
|
|
202
203
|
this.pulse = setInterval(() => {
|
|
203
204
|
this.tick += 1;
|
|
204
205
|
this.deps.requestRender();
|
|
205
|
-
},
|
|
206
|
+
}, SHELL_PULSE_MS);
|
|
206
207
|
this.pulse.unref();
|
|
207
208
|
}
|
|
208
209
|
this.deps.requestRender();
|
|
@@ -234,9 +235,14 @@ export class GentlePromptEditor extends CustomEditor {
|
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
237
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
238
|
+
// Stable across extension module reloads; never infer ownership from a name.
|
|
239
|
+
const PROMPT_OWNER = Symbol.for("gentle-pi.prompt-owner");
|
|
240
|
+
type PromptFactory = NonNullable<ReturnType<ExtensionContext["ui"]["getEditorComponent"]>> & { [PROMPT_OWNER]?: boolean };
|
|
241
|
+
|
|
242
|
+
function installPrompt(ctx: ExtensionContext, onCreated: (prompt: GentlePromptEditor) => void): boolean {
|
|
243
|
+
const previous = ctx.ui.getEditorComponent() as PromptFactory | undefined;
|
|
244
|
+
if (previous && !previous[PROMPT_OWNER]) return false;
|
|
245
|
+
const factory: PromptFactory = (tui, theme, keybindings) => {
|
|
240
246
|
const prompt = new GentlePromptEditor(tui, theme, keybindings, {
|
|
241
247
|
fg: (color, text) => ctx.ui.theme.fg(color as Parameters<typeof ctx.ui.theme.fg>[0], text),
|
|
242
248
|
bold: (text) => ctx.ui.theme.bold(text),
|
|
@@ -245,7 +251,10 @@ function installPrompt(ctx: ExtensionContext, onCreated: (prompt: GentlePromptEd
|
|
|
245
251
|
});
|
|
246
252
|
onCreated(prompt);
|
|
247
253
|
return prompt;
|
|
248
|
-
}
|
|
254
|
+
};
|
|
255
|
+
factory[PROMPT_OWNER] = true;
|
|
256
|
+
ctx.ui.setEditorComponent(factory);
|
|
257
|
+
return true;
|
|
249
258
|
}
|
|
250
259
|
|
|
251
260
|
const CHANGES_WIDGET_KEY = "gentle-shell-changes";
|
|
@@ -253,6 +262,7 @@ const CHANGES_COMMAND_NAME = "gentle:changes";
|
|
|
253
262
|
const CHANGES_SHORTCUT_DEFAULT = "alt+g";
|
|
254
263
|
const CHANGES_POLL_DEFAULT_MS = 2000;
|
|
255
264
|
const GIT_TIMEOUT_MS = 5000;
|
|
265
|
+
const COMMANDS_COMMAND_NAME = "gentle:commands";
|
|
256
266
|
const OVERLAY_HEIGHT_RATIO = 0.8;
|
|
257
267
|
const OVERLAY_MIN_ROWS = 8;
|
|
258
268
|
|
|
@@ -318,7 +328,7 @@ function changesPollMs(env: NodeJS.ProcessEnv): number {
|
|
|
318
328
|
}
|
|
319
329
|
|
|
320
330
|
function changesFingerprint(model: ChangesModel): string {
|
|
321
|
-
return model.files.map((file) => `${file.path}:${file.status}:${file.added}:${file.deleted}:${file.diffRevision ?? ""}:${file.countsUnavailable ?? ""}`).join("|");
|
|
331
|
+
return [model.notice ?? "", ...model.files.map((file) => `${file.path}:${file.status}:${file.added}:${file.deleted}:${file.diffRevision ?? ""}:${file.countsUnavailable ?? ""}`)].join("|");
|
|
322
332
|
}
|
|
323
333
|
|
|
324
334
|
interface OverlayDeps {
|
|
@@ -366,6 +376,29 @@ async function showChangesOverlay(ctx: ExtensionContext, deps: OverlayDeps): Pro
|
|
|
366
376
|
}
|
|
367
377
|
}
|
|
368
378
|
|
|
379
|
+
// The command palette is a curated, grouped menu (Configuration, Session,
|
|
380
|
+
// Diagnostics, SDD, Skills), not a raw listing of every registered
|
|
381
|
+
// extension command: buildCommandPaletteGroups keeps only the catalog
|
|
382
|
+
// entries that are actually registered, so a missing extension never shows
|
|
383
|
+
// a dead row. Selecting an entry runs it exactly as if the user had typed
|
|
384
|
+
// the underlying slash command.
|
|
385
|
+
async function showCommandPalette(pi: ExtensionAPI, ctx: ExtensionContext, env: NodeJS.ProcessEnv): Promise<void> {
|
|
386
|
+
if (!ctx.hasUI) return;
|
|
387
|
+
const groups = buildCommandPaletteGroups(pi.getCommands(), {
|
|
388
|
+
"gentle:changes": changesShortcut(env),
|
|
389
|
+
"gentle:agents": agentsViewKey(env),
|
|
390
|
+
});
|
|
391
|
+
if (groups.length === 0) {
|
|
392
|
+
ctx.ui.notify("No Gentle commands are registered.", "info");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const result = await ctx.ui.custom<CommandPaletteResult>(
|
|
396
|
+
(tui, theme, _keybindings, done) => new CommandPalette(groups, done, theme, () => Math.max(0, tui.terminal.rows)),
|
|
397
|
+
{ overlay: true, overlayOptions: { anchor: "center", width: "70%", minWidth: 60, maxHeight: "85%" } },
|
|
398
|
+
);
|
|
399
|
+
if (result?.type === "run") pi.sendUserMessage(`/${result.name}`, { expandPromptTemplates: true });
|
|
400
|
+
}
|
|
401
|
+
|
|
369
402
|
function showChanges(ctx: ExtensionContext, model: ChangesModel): void {
|
|
370
403
|
if (model.files.length === 0) {
|
|
371
404
|
ctx.ui.setWidget(CHANGES_WIDGET_KEY, undefined);
|
|
@@ -378,20 +411,6 @@ function showChanges(ctx: ExtensionContext, model: ChangesModel): void {
|
|
|
378
411
|
return renderChangesWidget(model, theme, width);
|
|
379
412
|
},
|
|
380
413
|
invalidate() {},
|
|
381
|
-
}, {
|
|
382
|
-
render(width: number) {
|
|
383
|
-
const noun = model.files.length === 1 ? "file" : "files";
|
|
384
|
-
return renderCard({
|
|
385
|
-
title: "Changes",
|
|
386
|
-
tone: CARD_TONE.INFO,
|
|
387
|
-
body: [
|
|
388
|
-
`${model.files.length} ${noun} · ${theme.fg("success", `+${model.added}`)} ${theme.fg("error", `−${model.deleted}`)}`,
|
|
389
|
-
"",
|
|
390
|
-
theme.fg("muted", `/${CHANGES_COMMAND_NAME}`),
|
|
391
|
-
],
|
|
392
|
-
}, theme, width, { expanded: true });
|
|
393
|
-
},
|
|
394
|
-
invalidate() {},
|
|
395
414
|
}),
|
|
396
415
|
{ placement: "belowEditor" },
|
|
397
416
|
);
|
|
@@ -533,7 +552,8 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
533
552
|
};
|
|
534
553
|
const unsubscribeWorktrees = pi.events.on(SESSION_CHANGE_EVENT, (data) => {
|
|
535
554
|
if (!currentContext || !registry || (data as { sessionId?: string } | undefined)?.sessionId !== registry.sessionId) return;
|
|
536
|
-
|
|
555
|
+
const notice = (data as { notice?: string } | undefined)?.notice;
|
|
556
|
+
if (notice) { if (changes) changes.notice = notice; currentContext.ui.notify(notice, "warning"); }
|
|
537
557
|
void refreshChanges(currentContext);
|
|
538
558
|
});
|
|
539
559
|
pi.registerTool({
|
|
@@ -565,7 +585,10 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
565
585
|
// part for: model, effort, context, cost, session name and extension
|
|
566
586
|
// statuses. The digest is what keeps the fullscreen memo honest, and it
|
|
567
587
|
// rebuilds the model exactly as the narrow bottom bar does every frame.
|
|
568
|
-
const footerModel = () =>
|
|
588
|
+
const footerModel = (): ShellBarModel => ({
|
|
589
|
+
...buildShellBarModel(pi, ctx, footerData, { dirty: tracker.model.files.length, usage: usage.get(ctx.model?.provider ?? ""), profile: deps.activeProfile() }),
|
|
590
|
+
changes: { files: tracker.model.files.length, added: tracker.model.added, deleted: tracker.model.deleted, notice: tracker.model.notice },
|
|
591
|
+
});
|
|
569
592
|
const part = sidebarPart(tui, "footer", bottom, {
|
|
570
593
|
digest: () => JSON.stringify(footerModel()),
|
|
571
594
|
render: (width) => renderShellSidebarBar(footerModel(), theme, width),
|
|
@@ -575,11 +598,13 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
575
598
|
return { ...part, dispose() { uninstall(); part.dispose(); } };
|
|
576
599
|
});
|
|
577
600
|
void refreshUsage(ctx, true);
|
|
578
|
-
installPrompt(ctx, (created) => {
|
|
601
|
+
const ownsPrompt = installPrompt(ctx, (created) => {
|
|
602
|
+
prompt?.dispose();
|
|
579
603
|
prompt = created;
|
|
580
604
|
});
|
|
581
|
-
//
|
|
582
|
-
|
|
605
|
+
// Hide native feedback only when our petal replaces it. Native transcript
|
|
606
|
+
// thinking blocks remain Pi-owned; this changes only the supported loader UI.
|
|
607
|
+
if (ownsPrompt) ctx.ui.setWorkingVisible(false);
|
|
583
608
|
const notice = deps.devBinary();
|
|
584
609
|
ctx.ui.setWidget(
|
|
585
610
|
DEV_BINARY_WIDGET_KEY,
|
|
@@ -591,7 +616,13 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
591
616
|
shown = "";
|
|
592
617
|
applyChanges(ctx, tracker.model);
|
|
593
618
|
});
|
|
594
|
-
pi.on("session_shutdown", () => {
|
|
619
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
620
|
+
prompt?.dispose();
|
|
621
|
+
prompt = undefined;
|
|
622
|
+
if ((ctx.ui.getEditorComponent() as PromptFactory | undefined)?.[PROMPT_OWNER]) {
|
|
623
|
+
ctx.ui.setEditorComponent(undefined);
|
|
624
|
+
ctx.ui.setWorkingVisible(true);
|
|
625
|
+
}
|
|
595
626
|
registry?.close();
|
|
596
627
|
registry = undefined;
|
|
597
628
|
changes = undefined;
|
|
@@ -619,13 +650,26 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
619
650
|
handler: async (ctx) => openChanges(ctx),
|
|
620
651
|
});
|
|
621
652
|
}
|
|
653
|
+
pi.registerCommand(COMMANDS_COMMAND_NAME, {
|
|
654
|
+
description: "Open the command palette: a curated, grouped menu of Gentle commands.",
|
|
655
|
+
handler: async (_args, ctx) => showCommandPalette(pi, ctx, env),
|
|
656
|
+
});
|
|
657
|
+
const commandsShortcut = commandsKey(env);
|
|
658
|
+
if (commandsShortcut) {
|
|
659
|
+
pi.registerShortcut(commandsShortcut as Parameters<ExtensionAPI["registerShortcut"]>[0], {
|
|
660
|
+
description: "Open the command palette",
|
|
661
|
+
handler: async (ctx) => showCommandPalette(pi, ctx, env),
|
|
662
|
+
});
|
|
663
|
+
}
|
|
622
664
|
pi.on("agent_start", (_event, ctx) => {
|
|
623
665
|
prompt?.setWorking(true);
|
|
624
666
|
// The dev-binary card is a startup notice: it leaves with the first prompt.
|
|
625
667
|
if (ctx.hasUI) ctx.ui.setWidget(DEV_BINARY_WIDGET_KEY, undefined);
|
|
626
668
|
});
|
|
627
|
-
pi.on("
|
|
669
|
+
pi.on("agent_settled", () => {
|
|
628
670
|
prompt?.setWorking(false);
|
|
671
|
+
});
|
|
672
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
629
673
|
await refreshChanges(ctx);
|
|
630
674
|
void refreshUsage(ctx, false);
|
|
631
675
|
});
|
package/extensions/pi-pretty.ts
CHANGED
|
@@ -1,29 +1,78 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { shellEnabled } from "../lib/shell-bar.ts";
|
|
4
3
|
import {
|
|
5
4
|
mergeDisabledTools,
|
|
6
5
|
PI_PRETTY_SUPPRESSED_TOOL_NAMES,
|
|
7
6
|
quietToolsEnabled,
|
|
8
7
|
} from "../lib/quiet-tools-config.ts";
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
fileURLToPath(new URL("../package.json", import.meta.url)),
|
|
12
|
-
);
|
|
13
|
-
const requireFromRealPackage = createRequire(packageJsonPath);
|
|
14
|
-
const piPrettyModule = requireFromRealPackage("@heyhuynhgiabuu/pi-pretty");
|
|
9
|
+
type PiPrettyExtension = (pi: unknown, deps?: unknown) => unknown;
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
typeof piPrettyModule === "function"
|
|
18
|
-
? piPrettyModule
|
|
19
|
-
: piPrettyModule.default;
|
|
11
|
+
let piPrettyExtensionPromise: Promise<PiPrettyExtension> | undefined;
|
|
20
12
|
|
|
21
|
-
|
|
13
|
+
async function loadPiPrettyExtension(): Promise<PiPrettyExtension> {
|
|
14
|
+
return piPrettyExtensionPromise ??= import("@heyhuynhgiabuu/pi-pretty").then(
|
|
15
|
+
(piPrettyModule) => {
|
|
16
|
+
const moduleCandidate: unknown = piPrettyModule;
|
|
17
|
+
const extension =
|
|
18
|
+
typeof moduleCandidate === "function"
|
|
19
|
+
? moduleCandidate
|
|
20
|
+
: piPrettyModule.default;
|
|
21
|
+
if (typeof extension !== "function") {
|
|
22
|
+
throw new TypeError("pi-pretty must export an extension function");
|
|
23
|
+
}
|
|
24
|
+
return extension as PiPrettyExtension;
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default async function gentlePiPrettyExtension(
|
|
30
|
+
pi: unknown,
|
|
31
|
+
deps?: unknown,
|
|
32
|
+
bundled?: PiPrettyExtension,
|
|
33
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
34
|
+
): Promise<unknown> {
|
|
22
35
|
if (quietToolsEnabled()) {
|
|
23
36
|
process.env.PRETTY_DISABLE_TOOLS = mergeDisabledTools(
|
|
24
37
|
process.env.PRETTY_DISABLE_TOOLS,
|
|
25
38
|
PI_PRETTY_SUPPRESSED_TOOL_NAMES,
|
|
26
39
|
);
|
|
27
40
|
}
|
|
28
|
-
|
|
41
|
+
const extension = bundled ?? await loadPiPrettyExtension();
|
|
42
|
+
if (!shellEnabled(env)) return extension(pi, deps);
|
|
43
|
+
const api = pi as ExtensionAPI;
|
|
44
|
+
// Scope interception to this bundled dependency, never to the shared host UI.
|
|
45
|
+
// Its tools/autocomplete/status APIs pass through; prompt ownership stays with
|
|
46
|
+
// Gentle Shell (or a genuine third-party editor). This also blocks stale restores.
|
|
47
|
+
const owned = new Set(["setEditorComponent", "setWorkingIndicator", "setWorkingMessage", "setWorkingVisible"]);
|
|
48
|
+
const delegated = new Proxy(api, {
|
|
49
|
+
get(target, key) {
|
|
50
|
+
if (key !== "on") return Reflect.get(target, key);
|
|
51
|
+
return (event: Parameters<ExtensionAPI["on"]>[0], handler: (event: unknown, ctx: ExtensionContext) => unknown) => {
|
|
52
|
+
api.on(event, ((event: unknown, ctx: ExtensionContext) => {
|
|
53
|
+
if (ctx.mode !== "tui") return handler(event, ctx);
|
|
54
|
+
const ui = new Proxy(ctx.ui, {
|
|
55
|
+
get(target, key) {
|
|
56
|
+
return owned.has(String(key)) ? () => {} : Reflect.get(target, key);
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
return handler(event, new Proxy(ctx, { get: (target, key) => key === "ui" ? ui : Reflect.get(target, key) }));
|
|
60
|
+
}) as never);
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
const result = await extension(delegated, deps);
|
|
65
|
+
// GentlePromptEditor already carries the live working state in its frame.
|
|
66
|
+
// Hide Pi's separate loader row to avoid repeating Thinking above the input;
|
|
67
|
+
// transcript thinking blocks remain untouched as historical reasoning markers.
|
|
68
|
+
api.on("session_start", (_event, ctx) => {
|
|
69
|
+
if (ctx.mode === "tui") ctx.ui.setWorkingVisible(false);
|
|
70
|
+
});
|
|
71
|
+
api.on("session_shutdown", (_event, ctx) => {
|
|
72
|
+
if (ctx.mode !== "tui") return;
|
|
73
|
+
ctx.ui.setWorkingVisible(true);
|
|
74
|
+
ctx.ui.setWorkingIndicator();
|
|
75
|
+
ctx.ui.setWorkingMessage();
|
|
76
|
+
});
|
|
77
|
+
return result;
|
|
29
78
|
}
|
|
@@ -238,7 +238,7 @@ function shellTokens(command: string): ShellTokenization {
|
|
|
238
238
|
function isAssignment(token: string): boolean {
|
|
239
239
|
return /^[A-Za-z_][A-Za-z0-9_]*=/.test(token);
|
|
240
240
|
}
|
|
241
|
-
const SDD_ATTEMPT_VERBS = new Set(["
|
|
241
|
+
const SDD_ATTEMPT_VERBS = new Set(["grant"]);
|
|
242
242
|
|
|
243
243
|
const REVIEW_DIRECT_OPERATIONS = new Set([
|
|
244
244
|
"capabilities",
|
|
@@ -354,7 +354,6 @@ function gentleAiOperationPathFrom(tokens: string[]): string {
|
|
|
354
354
|
if (tokens[0] === "sdd-attempt") {
|
|
355
355
|
const verb = tokens[1] ?? "";
|
|
356
356
|
if (!SDD_ATTEMPT_VERBS.has(verb)) return "sdd attempt";
|
|
357
|
-
if (verb !== "grant") return `sdd attempt ${verb}`;
|
|
358
357
|
const rootCount = authorizationRootCount(tokens);
|
|
359
358
|
return rootCount > 0
|
|
360
359
|
? `sdd attempt grant · ${rootCount} root${rootCount === 1 ? "" : "s"}`
|
|
@@ -29,14 +29,14 @@ const BANNER_PALETTES: Record<BannerColor, { rose: [number, number, number]; lab
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
const TEXT_LOGO = [
|
|
32
|
-
" ▄▄▄▀▀▀▀▀██ ▄▄▀▄▄
|
|
33
|
-
" ▄▄█▀▀▒▒▒▒▒▄▄█▀▒ ▄██ ▄▄█▀█▄█▀▒▒
|
|
34
|
-
" ▄▄██▀▒▒▒▒▒▄▄▄▀▀▒▒▒▒ ▄▄▄ ▀▀▀▀██▀▀▀▀▀███▀█▄▀▀▒▒▒▒
|
|
35
|
-
" ▄██▀▒▒▒▒ ▒▒▄▄█ ▄▄▄▀██ ▄▄▄▀▀▀▄ ▄██▀▒▒▒▒▄██▀▀▀▒▄▄███
|
|
36
|
-
" ██▀▒▒▒
|
|
37
|
-
" ▀█▄▄▄▄▄▀▀▀█▄▄███▄▒▀▀▀▀▀▀▒▀▀▒▒▀▀▀▀▒██▄▄▀▀▀ ▀█▄▀▀▀
|
|
38
|
-
" ▒▄▄▄█▀▀▀█▄█▀▀▒▒▒▒ ▒▒▒▒▒▒ ▒▒ ▒▒▒▒ ▒▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒
|
|
39
|
-
" ▄▄▀▀ ▒▒▒▒▄██▀▒▒▒▒
|
|
32
|
+
" ▄▄▄▀▀▀▀▀██ ▄▄▀▄▄ ▄▄█▀▀▀██ ▄▄▀██ ▄▄▀▄▄ ▄▄▀▄▄",
|
|
33
|
+
" ▄▄█▀▀▒▒▒▒▒▄▄█▀▒ ▄██ ▄▄█▀█▄█▀▒▒ ▄█▀▀▒▒▒▄█▀▒ ▄██▄█▀▒ ▄▄█▀█▄█▀▄▄█▀█▄█▀▒",
|
|
34
|
+
" ▄▄██▀▒▒▒▒▒▄▄▄▀▀▒▒▒▒ ▄▄▄ ▀▀▀▀██▀▀▀▀▀███▀█▄▀▀▒▒▒▒ ██▄▄▒▒▒▒▒▒▒ ▄██▀▀▒▒▒ ▄██▀█▄▀▀▄██▀█▄▀▀▒▒▒",
|
|
35
|
+
" ▄██▀▒▒▒▒ ▒▒▄▄█ ▄▄▄▀██ ▄▄▄▀▀▀▄ ▄██▀▒▒▒▒▄██▀▀▀▒▄▄███ ▀▀▀██▄▄ ▄██▀▒▄▄▀██ ▄▄▀▀██ ██▀▀▀▒▒▒██▀▀▀▒▒▒",
|
|
36
|
+
" ██▀▒▒▒ ▄▄▄███▀▄██▀▀▀▄▄██▀▀▄█▀▄▄██▀▒▒▒▄▄██▀▒▒▄██▀▀▀▄▄ ▄▄ ▀██▄▄██▀▄██▀██▒ ▄██▄▄▀▒ █▀▒▒▒ █▀▒▒▒",
|
|
37
|
+
" ▀█▄▄▄▄▄▀▀▀█▄▄███▄▒▀▀▀▀▀▀▒▀▀▒▒▀▀▀▀▒██▄▄▀▀▀ ▀█▄▀▀▀ ▀▀▀▀▀▒▒ ▀█▄▄▄▄█▀▀▒▒▀▀▒▒▀▀▒ ██▄▄▀▀█▄▄▄▄▀▀█▄▄▀▀▀▄▄▀█▄▄▀▀▀",
|
|
38
|
+
" ▒▄▄▄█▀▀▀█▄█▀▀▒▒▒▒ ▒▒▒▒▒▒ ▒▒ ▒▒▒▒ ▒▒▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒ ▒▀▀▀▀▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒",
|
|
39
|
+
" ▄▄▀▀ ▒▒▒▒▄██▀▒▒▒▒",
|
|
40
40
|
" ▄█ ▒▒▒▄▄██▀▀▒▒▒▒",
|
|
41
41
|
" ▀▀▀▀▀▀▒▒▒▒▒▒",
|
|
42
42
|
" ▒▒▒▒▒▒",
|
|
@@ -183,7 +183,8 @@ function buildLetterSpans(bounds: Span, weights: number[]): Span[] {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
const LOGO_BOUNDS = computeLogoBounds(TEXT_LOGO);
|
|
186
|
-
|
|
186
|
+
// Variable-width script regions, including the gap before Shell and shadows.
|
|
187
|
+
const LETTER_WEIGHTS = [22, 9, 8, 9, 8, 9, 10, 13, 8, 8, 13]; // G E N T L E S H E L L
|
|
187
188
|
const LETTER_SPANS = buildLetterSpans(LOGO_BOUNDS, LETTER_WEIGHTS);
|
|
188
189
|
|
|
189
190
|
function letterIndexAtX(x: number): number {
|
package/lib/agent-home.ts
CHANGED
|
@@ -6,3 +6,11 @@ import { join } from "node:path";
|
|
|
6
6
|
export function resolveGentlePiAgentHome(env: NodeJS.ProcessEnv = process.env): string {
|
|
7
7
|
return env.GENTLE_PI_AGENT_HOME || env.PI_CODING_AGENT_DIR || join(homedir(), ".pi", "agent");
|
|
8
8
|
}
|
|
9
|
+
|
|
10
|
+
// The Gentle AI config directory outside `~/.pi/agent`: everything the extension
|
|
11
|
+
// commands own lives here. It is defined once because the launch-time profile pin
|
|
12
|
+
// resolver reads the same profiles store the `/gentle:profiles` panel writes, and
|
|
13
|
+
// two spellings of the override would silently read two different stores.
|
|
14
|
+
export function gentlePiConfigHome(env: NodeJS.ProcessEnv = process.env): string {
|
|
15
|
+
return env.GENTLE_PI_CONFIG_HOME || join(homedir(), ".pi", "gentle-ai");
|
|
16
|
+
}
|