gentle-pi 3.7.0 → 4.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 +37 -6
- package/assets/agents/gentle-ai-explore.md +4 -4
- package/assets/agents/gentle-ai-verify.md +6 -4
- package/assets/agents/gentle-ai-worker.md +7 -9
- package/assets/orchestrator-delegation.md +40 -41
- package/assets/orchestrator-memory.md +1 -22
- package/assets/orchestrator-skills.md +1 -1
- package/assets/orchestrator.md +12 -24
- package/assets/support/strict-tdd-verify.md +4 -266
- package/assets/support/strict-tdd.md +8 -360
- package/bin/gentle-shell.mjs +254 -29
- package/docs/delegated-verification.md +26 -1
- package/docs/gentle-agents-activity.md +24 -0
- package/docs/gentle-shell.md +92 -28
- package/docs/native-authority-architecture.md +2 -2
- package/docs/prompt-history.md +280 -0
- package/docs/readme-reference.md +112 -206
- package/docs/telemetry.md +1 -1
- package/docs/yolo-mode.md +86 -0
- package/extensions/child-context.ts +26 -0
- package/extensions/child-safety.ts +23 -0
- package/extensions/gentle-agents.ts +429 -262
- package/extensions/gentle-ai.ts +781 -683
- package/extensions/gentle-shell.ts +1375 -88
- package/extensions/gentle-stats.ts +101 -0
- package/extensions/gentle-todo.ts +18 -7
- package/extensions/history/atomic-write.ts +38 -0
- package/extensions/history/hide-prompts.ts +183 -0
- package/extensions/history/index.ts +1419 -0
- package/extensions/history/load-shared-history.ts +39 -0
- package/extensions/history/selector-helpers.ts +538 -0
- package/extensions/history/session-scan.ts +233 -0
- package/extensions/history/store.ts +1119 -0
- package/extensions/nan-provider.ts +6 -0
- package/extensions/quiet-tools.ts +179 -87
- package/extensions/resume-hint.ts +60 -0
- package/extensions/skill-registry.ts +16 -12
- package/extensions/startup-banner.ts +60 -31
- package/lib/agent-assets.ts +604 -0
- package/lib/agent-profile-pin.ts +12 -0
- package/lib/agents-message-delivery.ts +181 -0
- package/lib/agents-protocol.ts +60 -0
- package/lib/agents-runner.ts +96 -98
- package/lib/agents-view.ts +26 -3
- package/lib/agents-widget.ts +16 -9
- package/lib/append-system-prompt.ts +21 -0
- package/lib/bounded-writer-admission.ts +147 -0
- package/lib/card-style-policy.ts +60 -0
- package/lib/child-context-files.ts +166 -0
- package/lib/codemode-renderer.ts +185 -0
- package/lib/command-palette-catalog.ts +3 -9
- package/lib/command-palette.ts +25 -14
- package/lib/destructive-command-guard.ts +144 -0
- package/lib/gentle-ai-elapsed-store.ts +87 -0
- package/lib/gentle-ai-renderer.ts +196 -39
- package/lib/gentle-shell-launcher.ts +24 -13
- package/lib/gentle-shell-resume-hint.ts +176 -0
- package/lib/history-capture-policy.ts +95 -0
- package/lib/model-routing-authority.ts +5 -1
- package/lib/nan-provider.ts +227 -0
- package/lib/native-review-cli.ts +49 -108
- package/lib/odd-phase-inference.ts +231 -0
- package/lib/odd-phase.ts +141 -0
- package/lib/overlay-repaint.ts +26 -0
- package/lib/pi-tui-keys.ts +53 -0
- package/lib/review-candidate-view-owner.ts +67 -17
- package/lib/review-candidate-view.ts +112 -25
- package/lib/review-reminder-receipt.ts +48 -8
- package/lib/review-risk-assessment.ts +156 -11
- package/lib/review-sidebar-state.ts +223 -0
- package/lib/selection-engine.ts +515 -0
- package/lib/session-messaging-grants.ts +135 -0
- package/lib/session-worktree-registry.ts +14 -2
- package/lib/shell-bar.ts +179 -24
- package/lib/shell-card.ts +287 -18
- package/lib/shell-changes-view.ts +2 -1
- package/lib/shell-prompt.ts +102 -6
- package/lib/shell-sidebar-layout.ts +78 -14
- package/lib/shell-sidebar.ts +15 -1
- package/lib/shell-todo.ts +23 -13
- package/lib/shell-usage-view.ts +9 -4
- package/lib/shell-usage.ts +66 -10
- package/lib/stats-collector.ts +381 -0
- package/lib/stats-view.ts +431 -0
- package/lib/theme-customization.ts +52 -0
- package/lib/vim-editor-adapter.ts +379 -0
- package/lib/vim-normal-engine.ts +154 -0
- package/lib/vim-operator-engine.ts +416 -0
- package/lib/vim-policy.ts +49 -0
- package/lib/vim-visual-engine.ts +107 -0
- package/lib/visual-customization-policy.ts +108 -0
- package/lib/visual-customize-view.ts +330 -0
- package/lib/visual-profiles.ts +228 -0
- package/lib/yolo-session-policy.ts +240 -0
- package/package.json +20 -8
- package/runtime/gentle-shell-launcher.mjs +23 -12
- package/runtime/gentle-shell-resume-hint.mjs +177 -0
- package/runtime/native-review-cli.mjs +49 -108
- package/runtime/review-risk-assessment.mjs +154 -9
- package/scripts/build-runtime-modules.mjs +1 -0
- package/scripts/gentle-ai-installer.mjs +14 -13
- package/scripts/mirror-odd-routing.mjs +2 -2
- package/scripts/run-test-suite.mjs +76 -0
- package/scripts/test-packed-runner.mjs +31 -14
- package/scripts/verify-package-files.mjs +11 -22
- package/skills/branch-pr/SKILL.md +24 -52
- package/skills/chained-pr/SKILL.md +31 -15
- package/skills/chained-pr/references/chaining-details.md +31 -20
- package/skills/gentle-ai/SKILL.md +9 -15
- package/skills/issue-creation/SKILL.md +8 -2
- package/skills/issue-creation/references/delegated-workflow-actions.md +19 -0
- package/skills/work-unit-commits/SKILL.md +4 -3
- package/tests/agent-profiles.test.ts +18 -0
- package/tests/agents-fake-child.ts +2 -2
- package/tests/agents-message-delivery.test.ts +106 -0
- package/tests/agents-protocol.test.ts +40 -0
- package/tests/agents-runner.test.ts +378 -89
- package/tests/agents-view-thread-identity.test.ts +169 -0
- package/tests/agents-view.test.ts +8 -2
- package/tests/agents-widget.test.ts +154 -15
- package/tests/append-system-prompt-route.test.ts +160 -0
- package/tests/append-system-prompt.test.ts +46 -0
- package/tests/artifact-language.test.ts +19 -213
- package/tests/ask-user-question.test.ts +44 -1
- package/tests/asset-installation-runtime.test.ts +5 -16
- package/tests/autonomous-guard.test.ts +69 -1
- package/tests/bounded-writer-admission.test.ts +95 -0
- package/tests/branch-pr-skill.test.ts +43 -0
- package/tests/card-style-policy.test.ts +55 -0
- package/tests/chained-pr-skill.test.ts +124 -0
- package/tests/child-context-files.test.ts +255 -0
- package/tests/child-safety.test.ts +82 -0
- package/tests/codemode-rendering.test.ts +491 -0
- package/tests/command-palette.test.ts +39 -3
- package/tests/delegated-key-learnings-contract.test.ts +0 -76
- package/tests/destructive-command-guard.test.ts +84 -0
- package/tests/devbinary/native-review-parity.devtest.ts +170 -2
- package/tests/devbinary/non-git-subagent-bootstrap.devtest.ts +193 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +7 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T08-00-00-000Z_ddd.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/2026-09-30T09-00-00-000Z_eee.jsonl +3 -0
- package/tests/fixtures/stats/sessions/--work-alpha--/run-1/session.jsonl +2 -0
- package/tests/fixtures/stats/sessions/--work-beta--/2026-09-01T12-00-00-000Z_ccc.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-28T10-00-00-000Z_aaa.jsonl +2 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-alpha--/2026-09-29T23-00-00-000Z_bbb.jsonl +4 -0
- package/tests/fixtures/stats/user-pi/sessions/--work-gamma--/2026-09-20T09-00-00-000Z_fff.jsonl +3 -0
- package/tests/generic-agent-tools.test.ts +54 -0
- package/tests/gentle-agents.test.ts +1047 -252
- package/tests/gentle-ai-binary.test.ts +3 -3
- package/tests/gentle-ai-elapsed-store.test.ts +68 -0
- package/tests/gentle-ai-installer.test.ts +68 -54
- package/tests/gentle-ai-renderer.test.ts +487 -8
- package/tests/gentle-ai.test.ts +288 -65
- package/tests/gentle-card-text.ts +2 -1
- package/tests/gentle-shell-bin.test.ts +651 -114
- package/tests/gentle-shell-launcher.test.ts +99 -52
- package/tests/gentle-shell-resume-hint.test.ts +270 -0
- package/tests/gentle-shell.test.ts +3839 -187
- package/tests/gentle-stats.test.ts +152 -0
- package/tests/gentle-theme.test.ts +4 -1
- package/tests/gentle-todo.test.ts +80 -8
- package/tests/history-atomic-write.test.ts +57 -0
- package/tests/history-capture-policy.test.ts +102 -0
- package/tests/history-command-registration.test.ts +164 -0
- package/tests/history-dedupe-entries.test.ts +123 -0
- package/tests/history-delete-backfill.test.ts +190 -0
- package/tests/history-delete-confirm.test.ts +460 -0
- package/tests/history-dispatch.test.ts +180 -0
- package/tests/history-drain-hidden.test.ts +110 -0
- package/tests/history-drain-order.test.ts +98 -0
- package/tests/history-expanded-globals.test.ts +62 -0
- package/tests/history-gc.test.ts +832 -0
- package/tests/history-header-layout.test.ts +265 -0
- package/tests/history-hide-prompts.test.ts +275 -0
- package/tests/history-lazy-windowing.test.ts +508 -0
- package/tests/history-legacy-migrate-v2.test.ts +297 -0
- package/tests/history-load-shared-history.test.ts +53 -0
- package/tests/history-max-results-cap.test.ts +76 -0
- package/tests/history-multi-reader.test.ts +203 -0
- package/tests/history-off-path.test.ts +170 -0
- package/tests/history-openflow-integration.test.ts +173 -0
- package/tests/history-overlay-margin.test.ts +326 -0
- package/tests/history-preview-layout.test.ts +93 -0
- package/tests/history-registry.test.ts +143 -0
- package/tests/history-scope-delete.test.ts +411 -0
- package/tests/history-search-caret-keys.test.ts +142 -0
- package/tests/history-seed-bootstrap.test.ts +170 -0
- package/tests/history-seed-regen.test.ts +129 -0
- package/tests/history-selector-windowing.test.ts +94 -0
- package/tests/history-session-scan-directory.test.ts +87 -0
- package/tests/history-session-scan-extract.test.ts +583 -0
- package/tests/history-session-writer.test.ts +351 -0
- package/tests/history-store-paths.test.ts +79 -0
- package/tests/history-tombstone-exact.test.ts +139 -0
- package/tests/history-wheel-mouse.test.ts +242 -0
- package/tests/inprocess-reviewer.test.ts +29 -19
- package/tests/issue-creation-skill.test.ts +61 -0
- package/tests/model-routing-authority.test.ts +16 -0
- package/tests/nan-provider.test.ts +471 -0
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-cli.test.ts +6 -120
- package/tests/native-review-parity-runtime.test.ts +100 -3
- package/tests/odd-integration.test.ts +67 -0
- package/tests/odd-phase-inference.test.ts +213 -0
- package/tests/odd-phase-loader.test.ts +253 -0
- package/tests/odd-phase.test.ts +307 -0
- package/tests/odd-routing-canonical-ratchet.test.ts +11 -6
- package/tests/odd-routing-contract.test.ts +86 -35
- package/tests/orchestrator-budget.test.ts +14 -39
- package/tests/orchestrator-rdd-ownership.test.ts +3 -3
- package/tests/overlay-repaint.test.ts +74 -0
- package/tests/package-manifest.test.ts +252 -115
- package/tests/packed-runner-owned-path.test.ts +46 -0
- package/tests/persona-single-channel.test.ts +6 -6
- package/tests/provider-defect-handoff.test.ts +3 -11
- package/tests/quiet-bash-runtime.test.ts +76 -0
- package/tests/quiet-tool-rendering.test.ts +409 -184
- package/tests/rdd-aware-verification-contract.test.ts +76 -1
- package/tests/rdd-status-line.test.ts +9 -4
- package/tests/resume-hint-extension.test.ts +122 -0
- package/tests/review-agent-end-preflight.test.ts +176 -12
- package/tests/review-candidate-owner-retry.test.ts +22 -1
- package/tests/review-candidate-view.test.ts +298 -0
- package/tests/review-contract-prompt.test.ts +108 -43
- package/tests/review-controller-lock-status.test.ts +0 -1
- package/tests/review-controller-native-routing.test.ts +611 -5
- package/tests/review-controller-workspace-root.test.ts +163 -4
- package/tests/review-host-relay-routing.test.ts +338 -2
- package/tests/review-integration-v2-forward.test.ts +200 -0
- package/tests/review-ledger-contract.test.ts +10 -34
- package/tests/review-reminder-receipt.test.ts +47 -1
- package/tests/review-risk-assessment.test.ts +498 -6
- package/tests/review-sidebar-state.test.ts +402 -0
- package/tests/run-test-suite.test.ts +124 -0
- package/tests/runtime-harness.mjs +145 -786
- package/tests/runtime-metrics-children.test.ts +16 -23
- package/tests/selection-engine.test.ts +421 -0
- package/tests/session-messaging-grants.test.ts +255 -0
- package/tests/session-worktree-registry.test.ts +77 -0
- package/tests/shell-bar.test.ts +382 -1
- package/tests/shell-card.test.ts +353 -1
- package/tests/shell-changes-view.test.ts +52 -0
- package/tests/shell-prompt.test.ts +94 -2
- package/tests/shell-sidebar-layout.test.ts +325 -21
- package/tests/shell-sidebar-scroll-benchmark.test.ts +255 -0
- package/tests/shell-todo.test.ts +87 -1
- package/tests/shell-usage-view.test.ts +27 -0
- package/tests/shell-usage.test.ts +73 -0
- package/tests/skill-registry.test.ts +50 -1
- package/tests/startup-banner.test.ts +130 -2
- package/tests/stats-collector.test.ts +195 -0
- package/tests/stats-view.test.ts +202 -0
- package/tests/telemetry-trigger.test.ts +81 -20
- package/tests/theme-customization.test.ts +72 -0
- package/tests/vim-editor-adapter-host-resolution.test.ts +37 -0
- package/tests/vim-editor-adapter.test.ts +804 -0
- package/tests/vim-normal-engine.test.ts +101 -0
- package/tests/vim-operator-engine.test.ts +215 -0
- package/tests/vim-policy.test.ts +19 -0
- package/tests/vim-visual-engine.test.ts +52 -0
- package/tests/visual-customization-policy.test.ts +110 -0
- package/tests/visual-customize-view.test.ts +418 -0
- package/tests/visual-profiles.test.ts +87 -0
- package/tests/yolo-customize.test.ts +256 -0
- package/tests/yolo-mode-runtime.test.ts +161 -0
- package/tests/yolo-mode.test.ts +261 -0
- package/tests/yolo-session-policy.test.ts +59 -0
- package/themes/Gentle.json +2 -1
- package/themes/Gentleman-Cute.json +2 -1
- package/themes/Gentleman-Sexy.json +2 -1
- package/assets/agents/sdd-apply.md +0 -159
- package/assets/agents/sdd-archive.md +0 -228
- package/assets/agents/sdd-design.md +0 -49
- package/assets/agents/sdd-explore.md +0 -48
- package/assets/agents/sdd-init.md +0 -56
- package/assets/agents/sdd-onboard.md +0 -52
- package/assets/agents/sdd-proposal.md +0 -64
- package/assets/agents/sdd-remediate.md +0 -37
- package/assets/agents/sdd-research.md +0 -49
- package/assets/agents/sdd-spec.md +0 -192
- package/assets/agents/sdd-status.md +0 -54
- package/assets/agents/sdd-tasks.md +0 -108
- package/assets/agents/sdd-verify.md +0 -124
- package/assets/chains/sdd-full.chain.md +0 -83
- package/assets/chains/sdd-plan.chain.md +0 -56
- package/assets/chains/sdd-verify.chain.md +0 -43
- package/assets/sdd-orchestrator-workflow.md +0 -319
- package/assets/support/sdd-status-contract.md +0 -77
- package/docs/assets/diagrams/sdd-cycle.svg +0 -14
- package/extensions/sdd-init.ts +0 -816
- package/lib/openspec-deltas.ts +0 -156
- package/lib/sdd-preflight.ts +0 -1066
- package/lib/sdd-research-capabilities.ts +0 -94
- package/lib/sdd-status.ts +0 -26
- package/tests/fixtures/legacy/sdd-research-v2.5.0.md +0 -54
- package/tests/fixtures/native-review-cli/v2.1.3/bind-sdd.json +0 -25
- package/tests/fixtures/v0.10.7/assets/agents/sdd-apply.md +0 -132
- package/tests/openspec-deltas.test.ts +0 -209
- package/tests/sdd-agent-tools.test.ts +0 -156
- package/tests/sdd-archive-replay.test.ts +0 -82
- package/tests/sdd-classical-continuation.test.ts +0 -74
- package/tests/sdd-execution-routing-contract.test.ts +0 -44
- package/tests/sdd-managed-runtime-settlement.test.ts +0 -155
- package/tests/sdd-native-managed-uptake.test.ts +0 -243
- package/tests/sdd-no-attempts-contract.test.ts +0 -15
- package/tests/sdd-odd-integration.test.ts +0 -33
- package/tests/sdd-optional-research.test.ts +0 -124
- package/tests/sdd-planning-routing-contract.test.ts +0 -45
- package/tests/sdd-preflight-rpc-input.test.ts +0 -125
- package/tests/sdd-preflight.test.ts +0 -541
- package/tests/sdd-research-capabilities.test.ts +0 -114
- package/tests/sdd-research-live.test.ts +0 -241
- package/tests/sdd-selection-transport.test.ts +0 -653
- package/tests/sdd-status.test.ts +0 -9
- package/tests/sdd-task-truth.test.ts +0 -43
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { AgentToolResult, ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { AgentToolResult, ExtensionAPI, ExtensionFactory, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import {
|
|
3
|
-
createBashTool,
|
|
4
3
|
createEditTool,
|
|
5
4
|
createFindTool,
|
|
6
5
|
createGrepTool,
|
|
@@ -13,11 +12,18 @@ import { Text, type Component } from "@earendil-works/pi-tui";
|
|
|
13
12
|
import { homedir } from "node:os";
|
|
14
13
|
import { isAbsolute } from "node:path";
|
|
15
14
|
import { resolveGentleAiDevBinaryOverride, type GentleAiDevBinaryOverride } from "../lib/gentle-ai-binary.ts";
|
|
15
|
+
import { GentleAiElapsedTimingLedger } from "../lib/gentle-ai-elapsed-store.ts";
|
|
16
16
|
import { quietToolsEnabled } from "../lib/quiet-tools-config.ts";
|
|
17
|
+
import { registerCompactCodemode } from "../lib/codemode-renderer.ts";
|
|
17
18
|
import { getGentleAiRenderState, renderGentleAiLifecycleCall, renderGentleAiResult, type GentleAiRenderContext } from "../lib/gentle-ai-renderer.ts";
|
|
18
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
CARD_TONE, cardAwaitingResult, cardBottom, cardInnerWidth, cardLine, cardRunningLine, cardTopRows, floatRows, markCardResult,
|
|
21
|
+
type CardRowContext, type CardTheme,
|
|
22
|
+
} from "../lib/shell-card.ts";
|
|
23
|
+
import { sanitizeTerminalText, stripAnsi } from "../lib/terminal-theme.ts";
|
|
19
24
|
|
|
20
25
|
type QuietToolName = "read" | "bash" | "grep" | "find" | "ls" | "edit" | "write";
|
|
26
|
+
type RegisteredToolName = Exclude<QuietToolName, "bash">;
|
|
21
27
|
type ThemeLike = {
|
|
22
28
|
bold(value: string): string;
|
|
23
29
|
fg(color: string, value: string): string;
|
|
@@ -25,13 +31,23 @@ type ThemeLike = {
|
|
|
25
31
|
|
|
26
32
|
const TOOL_CREATORS = {
|
|
27
33
|
read: createReadToolDefinition,
|
|
28
|
-
bash: createBashTool,
|
|
29
34
|
grep: createGrepTool,
|
|
30
35
|
find: createFindTool,
|
|
31
36
|
ls: createLsTool,
|
|
32
37
|
edit: createEditTool,
|
|
33
38
|
write: createWriteTool,
|
|
34
|
-
} satisfies Record<
|
|
39
|
+
} satisfies Record<RegisteredToolName, (cwd: string) => any>;
|
|
40
|
+
|
|
41
|
+
// Caller-owned identities: shared notices and lower widgets retain their glyphs.
|
|
42
|
+
const TOOL_GLYPH = {
|
|
43
|
+
read: "≡",
|
|
44
|
+
bash: "$",
|
|
45
|
+
grep: "⌕",
|
|
46
|
+
find: "⌖",
|
|
47
|
+
ls: "☷",
|
|
48
|
+
edit: "✎",
|
|
49
|
+
write: "+",
|
|
50
|
+
} as const satisfies Record<QuietToolName, string>;
|
|
35
51
|
|
|
36
52
|
const COLLAPSED_COUNT_LABELS: Partial<Record<QuietToolName, string>> = {
|
|
37
53
|
grep: "matches",
|
|
@@ -49,17 +65,17 @@ const EMPTY_RESULT_MESSAGES: Partial<Record<QuietToolName, string[]>> = {
|
|
|
49
65
|
bash: ["(no output)"],
|
|
50
66
|
};
|
|
51
67
|
|
|
52
|
-
const toolCache = new Map<string, Record<
|
|
68
|
+
const toolCache = new Map<string, Record<RegisteredToolName, any>>();
|
|
53
69
|
|
|
54
|
-
function createBuiltInTools(cwd: string): Record<
|
|
70
|
+
function createBuiltInTools(cwd: string): Record<RegisteredToolName, any> {
|
|
55
71
|
return Object.fromEntries(
|
|
56
|
-
(Object.entries(TOOL_CREATORS) as [
|
|
72
|
+
(Object.entries(TOOL_CREATORS) as [RegisteredToolName, (cwd: string) => any][]).map(
|
|
57
73
|
([name, createTool]) => [name, createTool(cwd)],
|
|
58
74
|
),
|
|
59
|
-
) as Record<
|
|
75
|
+
) as Record<RegisteredToolName, any>;
|
|
60
76
|
}
|
|
61
77
|
|
|
62
|
-
function getBuiltInTools(cwd: string): Record<
|
|
78
|
+
function getBuiltInTools(cwd: string): Record<RegisteredToolName, any> {
|
|
63
79
|
let tools = toolCache.get(cwd);
|
|
64
80
|
if (!tools) {
|
|
65
81
|
tools = createBuiltInTools(cwd);
|
|
@@ -167,7 +183,7 @@ function isGitCommand(args: Record<string, unknown> | undefined): boolean {
|
|
|
167
183
|
return /^(?:env\s+\S+=\S+\s+|command\s+|\w+=\S+\s+)*git(?:\s|$)/.test(command);
|
|
168
184
|
}
|
|
169
185
|
|
|
170
|
-
export type GentleAiRoutineCommand = "
|
|
186
|
+
export type GentleAiRoutineCommand = "review";
|
|
171
187
|
|
|
172
188
|
const GENTLE_AI_EXECUTABLE = String.raw`(?:gentle-ai(?:\.exe)?|(?:\.{1,2}[\\/]|(?:[A-Za-z]:)?(?:[\\/][^\\/\r\n]+)*[\\/])\.gentle-ai[\\/]v\d+\.\d+\.\d+[\\/]gentle-ai(?:\.exe)?)`;
|
|
173
189
|
const GENTLE_AI_COMMAND_ARGUMENTS = new RegExp(`^${GENTLE_AI_EXECUTABLE}$`);
|
|
@@ -238,8 +254,6 @@ function shellTokens(command: string): ShellTokenization {
|
|
|
238
254
|
function isAssignment(token: string): boolean {
|
|
239
255
|
return /^[A-Za-z_][A-Za-z0-9_]*=/.test(token);
|
|
240
256
|
}
|
|
241
|
-
const SDD_ATTEMPT_VERBS = new Set(["grant"]);
|
|
242
|
-
|
|
243
257
|
const REVIEW_DIRECT_OPERATIONS = new Set([
|
|
244
258
|
"capabilities",
|
|
245
259
|
"start",
|
|
@@ -304,22 +318,6 @@ function displayToken(token: string): string {
|
|
|
304
318
|
return token.replace(/-/g, " ");
|
|
305
319
|
}
|
|
306
320
|
|
|
307
|
-
function authorizationRootCount(tokens: string[]): number {
|
|
308
|
-
let count = 0;
|
|
309
|
-
for (let index = 0; index < tokens.length; index += 1) {
|
|
310
|
-
const token = tokens[index]!;
|
|
311
|
-
if (token === "--authorization-root") {
|
|
312
|
-
const value = tokens[index + 1];
|
|
313
|
-
if (value !== undefined && !value.startsWith("-")) count += 1;
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
if (token.startsWith("--authorization-root=") && token.slice("--authorization-root=".length).length > 0) {
|
|
317
|
-
count += 1;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return count;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
321
|
function validateGate(tokens: string[]): string | undefined {
|
|
324
322
|
const gateFlag = tokens.findIndex((token) => token === "--gate" || token.startsWith("--gate="));
|
|
325
323
|
if (gateFlag < 0) return undefined;
|
|
@@ -332,7 +330,7 @@ function validateGate(tokens: string[]): string | undefined {
|
|
|
332
330
|
/**
|
|
333
331
|
* Matches only supported routine Gentle AI CLI calls, including bounded
|
|
334
332
|
* package-local paths, not arbitrary shell output that merely mentions
|
|
335
|
-
* gentle-ai.
|
|
333
|
+
* gentle-ai. Review commands otherwise emit machine-readable RDD data.
|
|
336
334
|
*/
|
|
337
335
|
export function isGentleAiDirectCommand(args: Record<string, unknown> | undefined, commandArguments = GENTLE_AI_COMMAND_ARGUMENTS): boolean {
|
|
338
336
|
return gentleAiCommandTokens(args, commandArguments) !== undefined;
|
|
@@ -341,24 +339,12 @@ export function isGentleAiDirectCommand(args: Record<string, unknown> | undefine
|
|
|
341
339
|
export function gentleAiRoutineCommand(args: Record<string, unknown> | undefined, commandArguments = GENTLE_AI_COMMAND_ARGUMENTS): GentleAiRoutineCommand | undefined {
|
|
342
340
|
const tokens = gentleAiCommandTokens(args, commandArguments);
|
|
343
341
|
if (!tokens) return undefined;
|
|
344
|
-
if (tokens[0] === "sdd-status") return "sdd-status";
|
|
345
|
-
if (tokens[0] === "sdd-continue") return "sdd-continue";
|
|
346
|
-
if (tokens[0] === "sdd-attempt" && SDD_ATTEMPT_VERBS.has(tokens[1] ?? "")) return "sdd-attempt";
|
|
347
342
|
if (tokens[0] === "review") return "review";
|
|
348
343
|
return undefined;
|
|
349
344
|
}
|
|
350
345
|
|
|
351
|
-
function gentleAiOperationPathFrom(tokens: string[]): string {
|
|
352
|
-
if (tokens[0]
|
|
353
|
-
if (tokens[0] === "sdd-continue") return "sdd continue";
|
|
354
|
-
if (tokens[0] === "sdd-attempt") {
|
|
355
|
-
const verb = tokens[1] ?? "";
|
|
356
|
-
if (!SDD_ATTEMPT_VERBS.has(verb)) return "sdd attempt";
|
|
357
|
-
const rootCount = authorizationRootCount(tokens);
|
|
358
|
-
return rootCount > 0
|
|
359
|
-
? `sdd attempt grant · ${rootCount} root${rootCount === 1 ? "" : "s"}`
|
|
360
|
-
: "sdd attempt grant";
|
|
361
|
-
}
|
|
346
|
+
function gentleAiOperationPathFrom(tokens: string[]): string | undefined {
|
|
347
|
+
if (tokens[0]?.startsWith("sdd-")) return undefined;
|
|
362
348
|
if (tokens[0] === "version") return "version";
|
|
363
349
|
if (tokens[0] !== "review") return "command";
|
|
364
350
|
|
|
@@ -385,11 +371,6 @@ export function gentleAiOperationPath(args: Record<string, unknown> | undefined,
|
|
|
385
371
|
return tokens ? gentleAiOperationPathFrom(tokens) : undefined;
|
|
386
372
|
}
|
|
387
373
|
|
|
388
|
-
export function isGentleAiGrantCommand(args: Record<string, unknown> | undefined, commandArguments = GENTLE_AI_COMMAND_ARGUMENTS): boolean {
|
|
389
|
-
const tokens = gentleAiCommandTokens(args, commandArguments);
|
|
390
|
-
return tokens?.[0] === "sdd-attempt" && tokens[1] === "grant";
|
|
391
|
-
}
|
|
392
|
-
|
|
393
374
|
interface ToolResultFormatOptions {
|
|
394
375
|
expanded: boolean;
|
|
395
376
|
isError?: boolean;
|
|
@@ -501,7 +482,7 @@ function formatToolCall(toolName: QuietToolName, args: Record<string, unknown>,
|
|
|
501
482
|
case "bash": {
|
|
502
483
|
const command = safeText(asString(args.command, "..."));
|
|
503
484
|
const timeout = typeof args.timeout === "number" ? theme.fg("muted", ` (timeout ${args.timeout}s)`) : "";
|
|
504
|
-
return `${theme.fg("toolTitle", theme.bold(
|
|
485
|
+
return `${theme.fg("toolTitle", theme.bold(`bash $ ${command}`))}${timeout}`;
|
|
505
486
|
}
|
|
506
487
|
case "grep": {
|
|
507
488
|
let text = `${theme.fg("toolTitle", theme.bold("grep"))} ${theme.fg("accent", `/${safeText(asString(args.pattern))}/`)} in ${safeText(shortenPath(args.path) || ".")}`;
|
|
@@ -574,6 +555,78 @@ class BoundedRows implements Component {
|
|
|
574
555
|
invalidate(): void {}
|
|
575
556
|
}
|
|
576
557
|
|
|
558
|
+
// Tool card: every quiet tool call is a rounded petal card, like the Gentle AI
|
|
559
|
+
// card, so the call and its output read as one block apart from assistant
|
|
560
|
+
// prose. pi does not paint tool backgrounds for renderShell "self", so the
|
|
561
|
+
// frame tone carries the status the painted Box would otherwise show. The call
|
|
562
|
+
// owns the top rule; the result draws the sides and always closes the frame.
|
|
563
|
+
// Until the first result exists, the call closes the frame itself.
|
|
564
|
+
type ToolTone = typeof CARD_TONE.WARNING | typeof CARD_TONE.SUCCESS | typeof CARD_TONE.ERROR;
|
|
565
|
+
|
|
566
|
+
function toolTone(pending: boolean, failed: boolean): ToolTone {
|
|
567
|
+
if (failed) return CARD_TONE.ERROR;
|
|
568
|
+
return pending ? CARD_TONE.WARNING : CARD_TONE.SUCCESS;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
class ToolCardTop implements Component {
|
|
572
|
+
private readonly header: () => string;
|
|
573
|
+
private readonly glyph: string;
|
|
574
|
+
private readonly tone: ToolTone;
|
|
575
|
+
private readonly theme: CardTheme;
|
|
576
|
+
private readonly hint: string | undefined;
|
|
577
|
+
private readonly row: CardRowContext | undefined;
|
|
578
|
+
|
|
579
|
+
constructor(header: () => string, tone: ToolTone, theme: CardTheme, glyph: string, hint?: string, row?: CardRowContext) {
|
|
580
|
+
this.header = header;
|
|
581
|
+
this.glyph = glyph;
|
|
582
|
+
this.tone = tone;
|
|
583
|
+
this.theme = theme;
|
|
584
|
+
this.hint = hint;
|
|
585
|
+
this.row = row;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/** Renders `╭─ <icon> <call> ── <hint> ╮`, reserving applicable hint space before wrapping the call. A call that does not fit the rule (a long or multi-line command) continues on card rows below it, so every line of the command stays visible. */
|
|
589
|
+
render(width: number): string[] {
|
|
590
|
+
const target = Math.max(0, Math.floor(width));
|
|
591
|
+
if (target === 0) return [];
|
|
592
|
+
const running = this.row !== undefined && cardAwaitingResult(this.row);
|
|
593
|
+
return floatRows(this.tone, this.theme, target, (inner) => ({
|
|
594
|
+
head: cardTopRows({ title: this.header(), glyph: this.glyph, body: [], tone: this.tone }, this.theme, inner, this.hint),
|
|
595
|
+
body: running ? [cardRunningLine(this.tone, this.theme, inner)] : undefined,
|
|
596
|
+
bottom: running ? cardBottom(this.tone, this.theme, inner) : undefined,
|
|
597
|
+
}));
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
invalidate(): void {}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
class ToolCardBody implements Component {
|
|
604
|
+
private readonly inner: () => Component;
|
|
605
|
+
private readonly tone: ToolTone;
|
|
606
|
+
private readonly theme: CardTheme;
|
|
607
|
+
|
|
608
|
+
constructor(inner: () => Component, tone: ToolTone, theme: CardTheme) {
|
|
609
|
+
this.inner = inner;
|
|
610
|
+
this.tone = tone;
|
|
611
|
+
this.theme = theme;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/** Renders the inner component between the card sides and closes the frame, even when the result has no rows. */
|
|
615
|
+
render(width: number): string[] {
|
|
616
|
+
const target = Math.max(0, Math.floor(width));
|
|
617
|
+
if (target === 0) return [];
|
|
618
|
+
return floatRows(this.tone, this.theme, target, (inner) => ({
|
|
619
|
+
body: this.inner().render(cardInnerWidth(inner)).map((line) => cardLine(line.trimEnd(), this.tone, this.theme, inner)),
|
|
620
|
+
bottom: cardBottom(this.tone, this.theme, inner),
|
|
621
|
+
afterHeading: true,
|
|
622
|
+
}));
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
invalidate(): void {
|
|
626
|
+
// Content is rebuilt with the current theme at render time.
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
577
630
|
function shouldRenderPreviewTail(
|
|
578
631
|
toolName: QuietToolName,
|
|
579
632
|
text: string,
|
|
@@ -596,13 +649,6 @@ function hasImageContent(result: AgentToolResult<unknown>): boolean {
|
|
|
596
649
|
return result.content.some((content) => content.type === "image");
|
|
597
650
|
}
|
|
598
651
|
|
|
599
|
-
function hasExpandableContent(toolName: QuietToolName, result: AgentToolResult<unknown>, text: string): boolean {
|
|
600
|
-
if (isEmptyResultMessage(toolName, text)) return false;
|
|
601
|
-
if (text.length > 0 || hasImageContent(result)) return true;
|
|
602
|
-
const diff = detailsRecord(result).diff;
|
|
603
|
-
return toolName === "edit" && typeof diff === "string" && diff.length > 0;
|
|
604
|
-
}
|
|
605
|
-
|
|
606
652
|
function sanitizedRenderContext(context: ToolRenderContextLike | undefined): ToolRenderContextLike {
|
|
607
653
|
if (!context) return { args: {} };
|
|
608
654
|
return {
|
|
@@ -639,17 +685,21 @@ function gentleAiRenderTransition(
|
|
|
639
685
|
return { directResult: false };
|
|
640
686
|
}
|
|
641
687
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
688
|
+
/** Rendering-only factory; Bash renderers are not attached to production native Bash. */
|
|
689
|
+
export function createQuietToolRenderer(
|
|
690
|
+
toolName: QuietToolName,
|
|
691
|
+
resolveOverride: GentleAiDevBinaryOverrideResolver = () => undefined,
|
|
692
|
+
timing: () => GentleAiElapsedTimingLedger | undefined = () => undefined,
|
|
693
|
+
officialRenderResult?: ToolDefinition["renderResult"],
|
|
694
|
+
): Pick<ToolDefinition, "renderShell" | "renderCall" | "renderResult"> {
|
|
695
|
+
const commandArguments = () => createGentleAiCommandArguments(resolveQuietToolsDevBinaryPath(resolveOverride));
|
|
696
|
+
const withElapsedTiming = (context: GentleAiRenderContext): GentleAiRenderContext => {
|
|
697
|
+
const ledger = timing();
|
|
698
|
+
return ledger ? { ...context, elapsedTiming: ledger } : context;
|
|
699
|
+
};
|
|
645
700
|
|
|
646
|
-
|
|
647
|
-
...registrationTool,
|
|
701
|
+
return {
|
|
648
702
|
renderShell: "self",
|
|
649
|
-
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
650
|
-
const runtimeTool = getBuiltInTools(ctx.cwd)[toolName];
|
|
651
|
-
return runtimeTool.execute(toolCallId, params, signal, onUpdate, ctx);
|
|
652
|
-
},
|
|
653
703
|
renderCall(args, theme, context) {
|
|
654
704
|
const callArgs = args as Record<string, unknown>;
|
|
655
705
|
const renderContext = sanitizedRenderContext(context as ToolRenderContextLike | undefined);
|
|
@@ -658,13 +708,18 @@ function registerQuietTool(pi: ExtensionAPI, toolName: QuietToolName, commandArg
|
|
|
658
708
|
: undefined;
|
|
659
709
|
if (operationPath) {
|
|
660
710
|
const detail = renderContext.expanded === true && typeof callArgs.command === "string" ? `$ ${callArgs.command}` : undefined;
|
|
661
|
-
return renderGentleAiLifecycleCall(operationPath, theme, renderContext as GentleAiRenderContext, detail);
|
|
711
|
+
return renderGentleAiLifecycleCall(operationPath, theme, withElapsedTiming(renderContext as GentleAiRenderContext), detail);
|
|
662
712
|
}
|
|
663
|
-
|
|
713
|
+
const tone = toolTone(renderContext.isPartial !== false, renderContext.isError === true);
|
|
714
|
+
// Same place and wording as the Gentle AI card: the expand key rides the top rule once the call finished.
|
|
715
|
+
const finished = renderContext.isPartial === false && (renderContext.executionStarted === true || renderContext.isError === true);
|
|
716
|
+
const hint = finished ? stripAnsi(keyHint("app.tools.expand", renderContext.expanded === true ? "to collapse" : "to expand")) : undefined;
|
|
717
|
+
return new ToolCardTop(() => formatToolCall(toolName, callArgs, theme), tone, theme, TOOL_GLYPH[toolName], hint, renderContext);
|
|
664
718
|
},
|
|
665
719
|
/** Builds the card component for this render pass; collapsed cards delegate to the wrapped-line cache keyed by the tool result object. */
|
|
666
720
|
renderResult(result, options, theme, context) {
|
|
667
721
|
const renderContext = context as ToolRenderContextLike | undefined;
|
|
722
|
+
markCardResult(renderContext?.state);
|
|
668
723
|
const cacheKey = typeof result === "object" && result !== null ? result : undefined;
|
|
669
724
|
const safeResult = sanitizedResult(result);
|
|
670
725
|
const text = safeText(extractTextContent(safeResult));
|
|
@@ -676,49 +731,86 @@ function registerQuietTool(pi: ExtensionAPI, toolName: QuietToolName, commandArg
|
|
|
676
731
|
{ result: true, isPartial: options.isPartial },
|
|
677
732
|
).directResult;
|
|
678
733
|
if (directResult) {
|
|
679
|
-
return renderGentleAiResult(safeResult, { expanded: options.expanded, isPartial: options.isPartial, isError }, theme, renderContext as GentleAiRenderContext
|
|
734
|
+
return renderGentleAiResult(safeResult, { expanded: options.expanded, isPartial: options.isPartial, isError }, theme, renderContext ? withElapsedTiming(renderContext as GentleAiRenderContext) : undefined);
|
|
680
735
|
}
|
|
736
|
+
const resultTone = toolTone(options.isPartial === true, isError);
|
|
737
|
+
const carded = (component: () => Component): Component => new ToolCardBody(component, resultTone, theme);
|
|
681
738
|
if (options.isPartial) {
|
|
682
|
-
if (options.expanded) return new Text(`${theme.fg("warning", partialLabel(toolName, text))}\n${theme.fg("muted", text)}`, 0, 0);
|
|
739
|
+
if (options.expanded) return carded(() => new Text(`${theme.fg("warning", partialLabel(toolName, text))}\n${theme.fg("muted", text)}`, 0, 0));
|
|
683
740
|
const visible = lastOutputLines(text, PREVIEW_LINE_LIMIT);
|
|
684
|
-
return new BoundedRows([
|
|
741
|
+
return carded(() => new BoundedRows([
|
|
685
742
|
{ text: theme.fg("warning", partialLabel(toolName, text)), rows: 1 },
|
|
686
743
|
...(visible ? [{ text: theme.fg("muted", visible), rows: PREVIEW_LINE_LIMIT, tail: true }] : []),
|
|
687
|
-
], cacheKey);
|
|
744
|
+
], cacheKey));
|
|
688
745
|
}
|
|
689
746
|
if (options.expanded && toolName === "read" && hasImageContent(safeResult) && officialRenderResult) {
|
|
690
|
-
return officialRenderResult(
|
|
747
|
+
return carded(() => officialRenderResult(
|
|
691
748
|
safeResult,
|
|
692
749
|
options,
|
|
693
750
|
theme,
|
|
694
751
|
sanitizedRenderContext(renderContext) as any,
|
|
695
|
-
);
|
|
752
|
+
));
|
|
696
753
|
}
|
|
697
|
-
|
|
754
|
+
let output = formatToolResultOutput(toolName, safeResult, {
|
|
698
755
|
expanded: options.expanded,
|
|
699
756
|
isError,
|
|
700
757
|
args: renderContext?.args,
|
|
701
758
|
});
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
if (options.expanded
|
|
759
|
+
// Counts alone hide the result: retain their useful totals alongside actual rows.
|
|
760
|
+
if (!options.expanded && !isError && output && COLLAPSED_COUNT_LABELS[toolName]) {
|
|
761
|
+
output += `\n${firstLines(text, PREVIEW_LINE_LIMIT - 1)}`;
|
|
762
|
+
}
|
|
763
|
+
if (!options.expanded && !isError && toolName === "edit") {
|
|
764
|
+
const diff = detailsRecord(safeResult).diff;
|
|
765
|
+
if (typeof diff === "string") output += `\n${firstLines(safeText(diff).split("\n").filter((line) => /^[+-]/.test(line)).join("\n"), PREVIEW_LINE_LIMIT - 1)}`;
|
|
766
|
+
}
|
|
767
|
+
const color = isError ? "error" : options.expanded ? "toolOutput" : "muted";
|
|
768
|
+
if (options.expanded) return carded(() => new Text(output ? theme.fg(color, output.replace(/^\n/, "")) : "", 0, 0));
|
|
707
769
|
if (output) {
|
|
708
770
|
const tail = shouldRenderPreviewTail(toolName, text, isError, renderContext?.args);
|
|
709
|
-
return new BoundedRows([
|
|
771
|
+
return carded(() => new BoundedRows([
|
|
710
772
|
{ text: theme.fg(color, output.replace(/^\n/, "")), rows: PREVIEW_LINE_LIMIT, tail },
|
|
711
|
-
|
|
712
|
-
], cacheKey);
|
|
773
|
+
], cacheKey));
|
|
713
774
|
}
|
|
714
|
-
|
|
775
|
+
// The card top rule carries the expand key, so an empty result only closes the frame.
|
|
776
|
+
return carded(() => new Text("", 0, 0));
|
|
715
777
|
},
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function registerQuietTool(pi: ExtensionAPI, toolName: RegisteredToolName, resolveOverride: GentleAiDevBinaryOverrideResolver, timing: () => GentleAiElapsedTimingLedger | undefined): void {
|
|
782
|
+
const registrationTool = getBuiltInTools(process.cwd())[toolName];
|
|
783
|
+
pi.registerTool({
|
|
784
|
+
...registrationTool,
|
|
785
|
+
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
786
|
+
return getBuiltInTools(ctx.cwd)[toolName].execute(toolCallId, params, signal, onUpdate, ctx);
|
|
787
|
+
},
|
|
788
|
+
...createQuietToolRenderer(toolName, resolveOverride, timing, registrationTool.renderResult),
|
|
716
789
|
});
|
|
717
790
|
}
|
|
718
791
|
|
|
719
|
-
export default function quietTools(pi: ExtensionAPI, resolveOverride: GentleAiDevBinaryOverrideResolver = () => resolveGentleAiDevBinaryOverride()):
|
|
792
|
+
export default function quietTools(pi: ExtensionAPI, resolveOverride: GentleAiDevBinaryOverrideResolver = () => resolveGentleAiDevBinaryOverride()): ReturnType<ExtensionFactory> {
|
|
720
793
|
if (!quietToolsEnabled()) return;
|
|
721
|
-
|
|
722
|
-
|
|
794
|
+
let elapsedTiming: GentleAiElapsedTimingLedger | undefined;
|
|
795
|
+
pi.on("session_start", (_event, ctx) => {
|
|
796
|
+
elapsedTiming = new GentleAiElapsedTimingLedger(ctx.sessionManager, pi);
|
|
797
|
+
});
|
|
798
|
+
// Only bash calls that render as gentle-ai cards carry a durable duration;
|
|
799
|
+
// every other quiet tool keeps its plain renderer and writes no entries.
|
|
800
|
+
const recordGentleTiming = (event: { toolCallId: string; toolName: string; args?: unknown }, endedAt?: number): void => {
|
|
801
|
+
const ledger = elapsedTiming;
|
|
802
|
+
if (!ledger || event.toolName !== "bash" || !isGentleAiDirectCommand(event.args as Record<string, unknown> | undefined, createGentleAiCommandArguments(resolveQuietToolsDevBinaryPath(resolveOverride)))) return;
|
|
803
|
+
try {
|
|
804
|
+
if (endedAt === undefined) ledger.recordStart(event.toolCallId, Date.now());
|
|
805
|
+
else ledger.recordEnd(event.toolCallId, endedAt);
|
|
806
|
+
} catch { /* Timing persistence is best-effort and never breaks the tool event. */ }
|
|
807
|
+
};
|
|
808
|
+
pi.on("tool_execution_start", (event) => recordGentleTiming(event));
|
|
809
|
+
pi.on("tool_execution_end", (event) => recordGentleTiming(event, Date.now()));
|
|
810
|
+
const withElapsedTiming = (context: GentleAiRenderContext): GentleAiRenderContext =>
|
|
811
|
+
elapsedTiming ? { ...context, elapsedTiming } : context;
|
|
812
|
+
for (const toolName of Object.keys(TOOL_CREATORS) as RegisteredToolName[]) {
|
|
813
|
+
registerQuietTool(pi, toolName, resolveOverride, () => elapsedTiming);
|
|
723
814
|
}
|
|
815
|
+
return registerCompactCodemode(pi);
|
|
724
816
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
3
|
+
import {
|
|
4
|
+
isResumeHandoffPath,
|
|
5
|
+
RESUME_HANDOFF_ENV,
|
|
6
|
+
resumeHandoffFromSession,
|
|
7
|
+
serializeResumeHandoff,
|
|
8
|
+
} from "../lib/gentle-shell-resume-hint.ts";
|
|
9
|
+
|
|
10
|
+
// Hands the quitting session to bin/gentle-shell.mjs, which prints a
|
|
11
|
+
// gentle-shell resume command below pi's "pi --session <id>" exit hint (see
|
|
12
|
+
// lib/gentle-shell-resume-hint.ts). Inert unless the launcher set the env var.
|
|
13
|
+
|
|
14
|
+
// Process-wide, not per extension instance: /reload re-runs this factory after
|
|
15
|
+
// the env var was already claimed, and must keep using the same handoff path
|
|
16
|
+
// instead of going inert.
|
|
17
|
+
const STATE_KEY = Symbol.for("gentle-pi.resume-hint");
|
|
18
|
+
type StateHolder = { [STATE_KEY]?: string };
|
|
19
|
+
|
|
20
|
+
function claimHandoffPath(env: NodeJS.ProcessEnv): string | undefined {
|
|
21
|
+
const holder = globalThis as StateHolder;
|
|
22
|
+
const path = env[RESUME_HANDOFF_ENV];
|
|
23
|
+
// Claim the handoff for this process only: subagents and tool shells
|
|
24
|
+
// inherit process.env, and their own shutdowns must not overwrite it.
|
|
25
|
+
delete env[RESUME_HANDOFF_ENV];
|
|
26
|
+
if (holder[STATE_KEY] === undefined && path && isResumeHandoffPath(path)) holder[STATE_KEY] = path;
|
|
27
|
+
return holder[STATE_KEY];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Test-only: forget the claimed handoff path. */
|
|
31
|
+
export function resetResumeHintState(): void {
|
|
32
|
+
delete (globalThis as StateHolder)[STATE_KEY];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default function resumeHint(pi: ExtensionAPI, env: NodeJS.ProcessEnv = process.env): void {
|
|
36
|
+
const handoffPath = claimHandoffPath(env);
|
|
37
|
+
if (!handoffPath) return;
|
|
38
|
+
|
|
39
|
+
pi.on("session_shutdown", (event, ctx) => {
|
|
40
|
+
if (event.reason !== "quit" || ctx.mode !== "tui") return;
|
|
41
|
+
const sessionManager = ctx.sessionManager;
|
|
42
|
+
const handoff = resumeHandoffFromSession({
|
|
43
|
+
sessionId: sessionManager.getSessionId(),
|
|
44
|
+
sessionDir: sessionManager.getSessionDir(),
|
|
45
|
+
sessionFile: sessionManager.getSessionFile(),
|
|
46
|
+
cwd: sessionManager.getCwd(),
|
|
47
|
+
launchCwd: process.cwd(),
|
|
48
|
+
agentDir: getAgentDir(),
|
|
49
|
+
fileExists: existsSync,
|
|
50
|
+
});
|
|
51
|
+
if (!handoff) return;
|
|
52
|
+
try {
|
|
53
|
+
// "wx": create only. The launcher's private dir starts empty, so an
|
|
54
|
+
// existing file (or a planted symlink) means it is not ours to write.
|
|
55
|
+
writeFileSync(handoffPath, serializeResumeHandoff(handoff), { encoding: "utf8", flag: "wx" });
|
|
56
|
+
} catch {
|
|
57
|
+
// Best effort: without a handoff the launcher prints nothing extra.
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
@@ -19,7 +19,6 @@ const CACHE_REL_PATH = ".atl/.skill-registry.cache.json";
|
|
|
19
19
|
const SECTION_MARKER = "## Skills";
|
|
20
20
|
const EXCLUDE_NAMES = new Set(["_shared", "skill-registry"]);
|
|
21
21
|
const EXCLUDE_PREFIXES = ["sdd-"];
|
|
22
|
-
const ATL_IGNORE_ENTRY = ".atl/";
|
|
23
22
|
const WATCH_DEBOUNCE_MS = 500;
|
|
24
23
|
const REGISTRY_SCHEMA_VERSION = 7;
|
|
25
24
|
const NO_SKILL_REGISTRY_FLAG = "no-skill-registry";
|
|
@@ -315,19 +314,23 @@ interface RegenResult {
|
|
|
315
314
|
}
|
|
316
315
|
|
|
317
316
|
async function ensureAtlIgnored(cwd: string): Promise<void> {
|
|
318
|
-
const
|
|
319
|
-
|
|
317
|
+
const atlDir = join(cwd, ".atl");
|
|
318
|
+
const gitignorePath = join(atlDir, ".gitignore");
|
|
320
319
|
if (await pathExists(gitignorePath)) {
|
|
321
|
-
existing = await readFile(gitignorePath, "utf8");
|
|
320
|
+
const existing = await readFile(gitignorePath, "utf8");
|
|
321
|
+
const ignoreRules = existing
|
|
322
|
+
.split("\n")
|
|
323
|
+
.map((line) => line.trim())
|
|
324
|
+
.filter((line) => line !== "" && !line.startsWith("#"));
|
|
325
|
+
if (ignoreRules.at(-1) === "*") {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
329
|
+
await writeFile(gitignorePath, `${existing}${prefix}*\n`);
|
|
330
|
+
return;
|
|
322
331
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
.map((line) => line.trim())
|
|
326
|
-
.some((line) => line === ".atl" || line === ATL_IGNORE_ENTRY);
|
|
327
|
-
if (hasAtlIgnore) return;
|
|
328
|
-
const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
329
|
-
const header = existing.includes("# Local Pi runtime state") ? "" : "# Local Pi runtime state\n";
|
|
330
|
-
await writeFile(gitignorePath, `${existing}${prefix}${header}${ATL_IGNORE_ENTRY}\n`);
|
|
332
|
+
await mkdir(atlDir, { recursive: true });
|
|
333
|
+
await writeFile(gitignorePath, "*\n");
|
|
331
334
|
}
|
|
332
335
|
|
|
333
336
|
function isGeneratedLegacyProjectRegistry(source: string): boolean {
|
|
@@ -534,6 +537,7 @@ export const __testing = {
|
|
|
534
537
|
parseFrontmatter,
|
|
535
538
|
renderRegistry,
|
|
536
539
|
regenerateRegistry,
|
|
540
|
+
ensureAtlIgnored,
|
|
537
541
|
shouldSkipSkillRegistryStartup,
|
|
538
542
|
shouldSkipDuplicateExtensionLoad,
|
|
539
543
|
startSkillRegistryWatcher,
|