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.
Files changed (122) hide show
  1. package/README.md +24 -6
  2. package/assets/agents/gentle-ai-worker.md +5 -1
  3. package/assets/agents/sdd-apply.md +9 -7
  4. package/assets/agents/sdd-archive.md +42 -23
  5. package/assets/agents/sdd-proposal.md +2 -2
  6. package/assets/agents/sdd-remediate.md +4 -4
  7. package/assets/agents/sdd-research.md +20 -48
  8. package/assets/agents/sdd-tasks.md +5 -5
  9. package/assets/agents/sdd-verify.md +6 -28
  10. package/assets/chains/sdd-full.chain.md +4 -22
  11. package/assets/chains/sdd-verify.chain.md +3 -12
  12. package/assets/orchestrator-delegation.md +33 -3
  13. package/assets/orchestrator-memory.md +20 -7
  14. package/assets/orchestrator.md +5 -3
  15. package/assets/sdd-orchestrator-workflow.md +25 -58
  16. package/assets/support/sdd-status-contract.md +9 -12
  17. package/docs/gentle-shell.md +14 -4
  18. package/docs/readme-reference.md +171 -32
  19. package/extensions/codegraph-tools.ts +2 -0
  20. package/extensions/gentle-agents.ts +281 -361
  21. package/extensions/gentle-ai.ts +588 -117
  22. package/extensions/gentle-shell.ts +74 -30
  23. package/extensions/pi-pretty.ts +63 -14
  24. package/extensions/quiet-tools.ts +1 -2
  25. package/extensions/startup-banner.ts +10 -9
  26. package/lib/agent-home.ts +8 -0
  27. package/lib/agent-profile-pin.ts +336 -0
  28. package/lib/agent-profiles.ts +28 -8
  29. package/lib/agents-config.ts +24 -2
  30. package/lib/agents-history.ts +3 -97
  31. package/lib/agents-keys.ts +27 -0
  32. package/lib/agents-protocol.ts +2 -15
  33. package/lib/agents-runner.ts +67 -111
  34. package/lib/agents-session-transport.ts +691 -0
  35. package/lib/command-palette-catalog.ts +87 -0
  36. package/lib/command-palette.ts +346 -0
  37. package/lib/native-choice-list.ts +5 -0
  38. package/lib/native-review-cli.ts +10 -97
  39. package/lib/review-publication-gate.ts +11 -1
  40. package/lib/review-repository.ts +1 -1
  41. package/lib/review-snapshot.ts +1 -0
  42. package/lib/review-transaction.ts +4 -2
  43. package/lib/sdd-preflight.ts +2 -1
  44. package/lib/sdd-research-capabilities.ts +18 -152
  45. package/lib/sdd-status.ts +7 -779
  46. package/lib/session-change-capture.ts +2 -1
  47. package/lib/session-changes.ts +8 -1
  48. package/lib/shell-bar.ts +21 -12
  49. package/lib/shell-card.ts +8 -12
  50. package/lib/shell-changes.ts +3 -2
  51. package/lib/shell-prompt.ts +25 -8
  52. package/lib/shell-sidebar-banner.ts +2 -2
  53. package/lib/shell-sidebar-layout.ts +5 -2
  54. package/lib/windows-session-transport.ts +877 -0
  55. package/package.json +3 -3
  56. package/runtime/native-review-cli.mjs +9 -96
  57. package/runtime/windows-session-transport.ps1 +791 -0
  58. package/scripts/test-packed-runner.mjs +1668 -20
  59. package/scripts/verify-package-files.mjs +0 -1
  60. package/tests/agent-home.test.ts +52 -0
  61. package/tests/agent-profiles.test.ts +30 -1
  62. package/tests/agents-config.test.ts +44 -0
  63. package/tests/agents-history.test.ts +12 -24
  64. package/tests/agents-runner.test.ts +307 -58
  65. package/tests/agents-session-transport-process.test.ts +249 -0
  66. package/tests/agents-session-transport.test.ts +823 -0
  67. package/tests/artifact-language.test.ts +10 -7
  68. package/tests/command-palette.test.ts +378 -0
  69. package/tests/delegated-key-learnings-contract.test.ts +2 -2
  70. package/tests/fixtures/agents-session-transport-process.mjs +108 -0
  71. package/tests/fixtures/legacy/sdd-research-v2.5.0.md +54 -0
  72. package/tests/fixtures/windows-session-bootstrap.ps1 +129 -0
  73. package/tests/fixtures/windows-session-compile.ps1 +110 -0
  74. package/tests/gentle-agents.test.ts +849 -356
  75. package/tests/gentle-ai.test.ts +472 -4
  76. package/tests/gentle-shell.test.ts +201 -8
  77. package/tests/native-choice-list.test.ts +13 -0
  78. package/tests/native-review-cli.test.ts +0 -33
  79. package/tests/odd-routing-contract.test.ts +208 -0
  80. package/tests/orchestrator-budget.test.ts +17 -2
  81. package/tests/package-manifest.test.ts +115 -27
  82. package/tests/persona-single-channel.test.ts +3 -3
  83. package/tests/pi-pretty.test.ts +45 -0
  84. package/tests/profile-pin.test.ts +370 -0
  85. package/tests/quiet-tool-rendering.test.ts +32 -5
  86. package/tests/review-contract-prompt.test.ts +9 -0
  87. package/tests/review-controller.test.ts +0 -44
  88. package/tests/review-session-standing-permission-ipc.test.ts +427 -13
  89. package/tests/runtime-harness.mjs +4 -4
  90. package/tests/sdd-agent-tools.test.ts +15 -36
  91. package/tests/sdd-archive-replay.test.ts +82 -0
  92. package/tests/sdd-classical-continuation.test.ts +74 -0
  93. package/tests/sdd-execution-routing-contract.test.ts +18 -2
  94. package/tests/sdd-managed-runtime-settlement.test.ts +42 -330
  95. package/tests/sdd-native-managed-uptake.test.ts +11 -21
  96. package/tests/sdd-no-attempts-contract.test.ts +15 -0
  97. package/tests/sdd-odd-integration.test.ts +33 -0
  98. package/tests/sdd-optional-research.test.ts +124 -0
  99. package/tests/sdd-planning-routing-contract.test.ts +1 -1
  100. package/tests/sdd-preflight-rpc-input.test.ts +125 -0
  101. package/tests/sdd-preflight.test.ts +1 -1
  102. package/tests/sdd-research-capabilities.test.ts +20 -162
  103. package/tests/sdd-selection-transport.test.ts +180 -88
  104. package/tests/sdd-status.test.ts +5 -778
  105. package/tests/sdd-task-truth.test.ts +43 -0
  106. package/tests/session-change-capture.test.ts +20 -2
  107. package/tests/session-changes.test.ts +11 -0
  108. package/tests/shell-bar.test.ts +21 -0
  109. package/tests/shell-card.test.ts +8 -6
  110. package/tests/shell-changes.test.ts +8 -0
  111. package/tests/shell-prompt.test.ts +41 -7
  112. package/tests/shell-sidebar-banner.test.ts +4 -4
  113. package/tests/shell-sidebar-layout.test.ts +97 -13
  114. package/tests/startup-banner.test.ts +55 -2
  115. package/tests/windows-hidden-processes.test.ts +303 -0
  116. package/tests/windows-session-bootstrap.test.ts +1772 -0
  117. package/tests/windows-session-compile.test.ts +170 -0
  118. package/tests/windows-session-transport.test.ts +754 -0
  119. package/assets/agents/sdd-sync.md +0 -146
  120. package/lib/openspec-guardrails.ts +0 -99
  121. package/tests/native-sdd-attempt-authority.test.ts +0 -240
  122. 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
- }, PETAL_PULSE_MS);
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
- function installPrompt(ctx: ExtensionContext, onCreated: (prompt: GentlePromptEditor) => void): void {
238
- if (ctx.ui.getEditorComponent()) return;
239
- ctx.ui.setEditorComponent((tui, theme, keybindings) => {
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
- if ((data as { notice?: string }).notice) currentContext.ui.notify((data as { notice: string }).notice, "warning");
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 = () => buildShellBarModel(pi, ctx, footerData, { dirty: tracker.model.files.length, usage: usage.get(ctx.model?.provider ?? ""), profile: deps.activeProfile() });
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
- // The petal already says the agent is working; pi's own "Working" row would say it twice.
582
- ctx.ui.setWorkingVisible(false);
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("agent_end", async (_event, ctx) => {
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
  });
@@ -1,29 +1,78 @@
1
- import { realpathSync } from "node:fs";
2
- import { createRequire } from "node:module";
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
- const packageJsonPath = realpathSync(
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
- const piPrettyExtension =
17
- typeof piPrettyModule === "function"
18
- ? piPrettyModule
19
- : piPrettyModule.default;
11
+ let piPrettyExtensionPromise: Promise<PiPrettyExtension> | undefined;
20
12
 
21
- export default async function gentlePiPrettyExtension(pi: unknown, deps?: unknown): Promise<unknown> {
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
- return piPrettyExtension(pi, deps);
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(["acquire", "settle", "grant"]);
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
- const LETTER_WEIGHTS = [14, 10, 11, 10, 9, 11, 6, 13, 12]; // G E N T L E - P I
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
+ }