pi-ui-extend 0.1.1
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 +307 -0
- package/bin/pix.mjs +219 -0
- package/dist/app/app.d.ts +96 -0
- package/dist/app/app.js +871 -0
- package/dist/app/blink-controller.d.ts +23 -0
- package/dist/app/blink-controller.js +82 -0
- package/dist/app/cli.d.ts +8 -0
- package/dist/app/cli.js +83 -0
- package/dist/app/clipboard.d.ts +1 -0
- package/dist/app/clipboard.js +24 -0
- package/dist/app/command-controller.d.ts +18 -0
- package/dist/app/command-controller.js +58 -0
- package/dist/app/command-host.d.ts +44 -0
- package/dist/app/command-host.js +1 -0
- package/dist/app/command-model-actions.d.ts +12 -0
- package/dist/app/command-model-actions.js +176 -0
- package/dist/app/command-navigation-actions.d.ts +19 -0
- package/dist/app/command-navigation-actions.js +267 -0
- package/dist/app/command-registry.d.ts +32 -0
- package/dist/app/command-registry.js +225 -0
- package/dist/app/command-runtime.d.ts +5 -0
- package/dist/app/command-runtime.js +32 -0
- package/dist/app/command-session-actions.d.ts +20 -0
- package/dist/app/command-session-actions.js +295 -0
- package/dist/app/constants.d.ts +52 -0
- package/dist/app/constants.js +103 -0
- package/dist/app/conversation-entry-renderer.d.ts +21 -0
- package/dist/app/conversation-entry-renderer.js +81 -0
- package/dist/app/conversation-shell-renderer.d.ts +5 -0
- package/dist/app/conversation-shell-renderer.js +43 -0
- package/dist/app/conversation-tool-renderer.d.ts +16 -0
- package/dist/app/conversation-tool-renderer.js +216 -0
- package/dist/app/conversation-viewport.d.ts +55 -0
- package/dist/app/conversation-viewport.js +252 -0
- package/dist/app/dcp-stats.d.ts +2 -0
- package/dist/app/dcp-stats.js +116 -0
- package/dist/app/editor-layout-renderer.d.ts +31 -0
- package/dist/app/editor-layout-renderer.js +211 -0
- package/dist/app/editor-panels.d.ts +4 -0
- package/dist/app/editor-panels.js +130 -0
- package/dist/app/extension-actions-controller.d.ts +22 -0
- package/dist/app/extension-actions-controller.js +60 -0
- package/dist/app/extension-event-bus.d.ts +3 -0
- package/dist/app/extension-event-bus.js +23 -0
- package/dist/app/extension-ui-controller.d.ts +57 -0
- package/dist/app/extension-ui-controller.js +532 -0
- package/dist/app/file-link-opener.d.ts +2 -0
- package/dist/app/file-link-opener.js +66 -0
- package/dist/app/file-links.d.ts +10 -0
- package/dist/app/file-links.js +117 -0
- package/dist/app/guards.d.ts +3 -0
- package/dist/app/guards.js +9 -0
- package/dist/app/icons.d.ts +37 -0
- package/dist/app/icons.js +97 -0
- package/dist/app/id.d.ts +1 -0
- package/dist/app/id.js +4 -0
- package/dist/app/image-click-targets.d.ts +5 -0
- package/dist/app/image-click-targets.js +32 -0
- package/dist/app/image-opener.d.ts +2 -0
- package/dist/app/image-opener.js +64 -0
- package/dist/app/input-action-controller.d.ts +47 -0
- package/dist/app/input-action-controller.js +209 -0
- package/dist/app/input-controller.d.ts +60 -0
- package/dist/app/input-controller.js +425 -0
- package/dist/app/input-paste-handler.d.ts +27 -0
- package/dist/app/input-paste-handler.js +146 -0
- package/dist/app/menu-items-controller.d.ts +32 -0
- package/dist/app/menu-items-controller.js +182 -0
- package/dist/app/message-content.d.ts +8 -0
- package/dist/app/message-content.js +115 -0
- package/dist/app/model-ref.d.ts +13 -0
- package/dist/app/model-ref.js +50 -0
- package/dist/app/model-usage-controller.d.ts +35 -0
- package/dist/app/model-usage-controller.js +99 -0
- package/dist/app/model-usage-status.d.ts +125 -0
- package/dist/app/model-usage-status.js +749 -0
- package/dist/app/mouse-controller.d.ts +182 -0
- package/dist/app/mouse-controller.js +968 -0
- package/dist/app/native-modifiers.d.ts +3 -0
- package/dist/app/native-modifiers.js +60 -0
- package/dist/app/nerd-font-controller.d.ts +11 -0
- package/dist/app/nerd-font-controller.js +90 -0
- package/dist/app/popup-action-controller.d.ts +44 -0
- package/dist/app/popup-action-controller.js +278 -0
- package/dist/app/popup-menu-controller.d.ts +183 -0
- package/dist/app/popup-menu-controller.js +1070 -0
- package/dist/app/prompt-enhancer-controller.d.ts +40 -0
- package/dist/app/prompt-enhancer-controller.js +215 -0
- package/dist/app/queued-message-controller.d.ts +62 -0
- package/dist/app/queued-message-controller.js +377 -0
- package/dist/app/render-controller.d.ts +41 -0
- package/dist/app/render-controller.js +378 -0
- package/dist/app/render-text.d.ts +19 -0
- package/dist/app/render-text.js +67 -0
- package/dist/app/request-history.d.ts +23 -0
- package/dist/app/request-history.js +131 -0
- package/dist/app/runtime.d.ts +39 -0
- package/dist/app/runtime.js +195 -0
- package/dist/app/screen-selection.d.ts +6 -0
- package/dist/app/screen-selection.js +9 -0
- package/dist/app/screen-styler.d.ts +34 -0
- package/dist/app/screen-styler.js +168 -0
- package/dist/app/scroll-controller.d.ts +51 -0
- package/dist/app/scroll-controller.js +207 -0
- package/dist/app/session-event-controller.d.ts +69 -0
- package/dist/app/session-event-controller.js +338 -0
- package/dist/app/session-history.d.ts +23 -0
- package/dist/app/session-history.js +164 -0
- package/dist/app/session-lifecycle-controller.d.ts +55 -0
- package/dist/app/session-lifecycle-controller.js +116 -0
- package/dist/app/session-search.d.ts +24 -0
- package/dist/app/session-search.js +215 -0
- package/dist/app/shell-command.d.ts +27 -0
- package/dist/app/shell-command.js +176 -0
- package/dist/app/shell-controller.d.ts +28 -0
- package/dist/app/shell-controller.js +124 -0
- package/dist/app/slash-commands.d.ts +6 -0
- package/dist/app/slash-commands.js +75 -0
- package/dist/app/startup-checks.d.ts +8 -0
- package/dist/app/startup-checks.js +59 -0
- package/dist/app/startup-info.d.ts +3 -0
- package/dist/app/startup-info.js +176 -0
- package/dist/app/status-controller.d.ts +35 -0
- package/dist/app/status-controller.js +105 -0
- package/dist/app/status-line-renderer.d.ts +68 -0
- package/dist/app/status-line-renderer.js +453 -0
- package/dist/app/subagents-files.d.ts +10 -0
- package/dist/app/subagents-files.js +193 -0
- package/dist/app/subagents-model.d.ts +23 -0
- package/dist/app/subagents-model.js +224 -0
- package/dist/app/subagents-widget-controller.d.ts +43 -0
- package/dist/app/subagents-widget-controller.js +311 -0
- package/dist/app/tab-line-renderer.d.ts +26 -0
- package/dist/app/tab-line-renderer.js +222 -0
- package/dist/app/tabs-controller.d.ts +100 -0
- package/dist/app/tabs-controller.js +885 -0
- package/dist/app/terminal-controller.d.ts +40 -0
- package/dist/app/terminal-controller.js +135 -0
- package/dist/app/terminal-edit-shortcuts.d.ts +23 -0
- package/dist/app/terminal-edit-shortcuts.js +138 -0
- package/dist/app/terminal-output-buffer.d.ts +20 -0
- package/dist/app/terminal-output-buffer.js +52 -0
- package/dist/app/toast-controller.d.ts +13 -0
- package/dist/app/toast-controller.js +40 -0
- package/dist/app/toast-renderer.d.ts +9 -0
- package/dist/app/toast-renderer.js +79 -0
- package/dist/app/todo-model.d.ts +22 -0
- package/dist/app/todo-model.js +179 -0
- package/dist/app/todo-widget-controller.d.ts +21 -0
- package/dist/app/todo-widget-controller.js +59 -0
- package/dist/app/tool-block-renderer.d.ts +26 -0
- package/dist/app/tool-block-renderer.js +439 -0
- package/dist/app/types.d.ts +550 -0
- package/dist/app/types.js +1 -0
- package/dist/app/update.d.ts +36 -0
- package/dist/app/update.js +315 -0
- package/dist/app/voice-controller.d.ts +52 -0
- package/dist/app/voice-controller.js +600 -0
- package/dist/app/workspace-actions-controller.d.ts +40 -0
- package/dist/app/workspace-actions-controller.js +215 -0
- package/dist/app/workspace-undo.d.ts +44 -0
- package/dist/app/workspace-undo.js +215 -0
- package/dist/config.d.ts +62 -0
- package/dist/config.js +527 -0
- package/dist/context-progress-bar.d.ts +17 -0
- package/dist/context-progress-bar.js +48 -0
- package/dist/default-pix-config.d.ts +1 -0
- package/dist/default-pix-config.js +375 -0
- package/dist/fuzzy.d.ts +23 -0
- package/dist/fuzzy.js +165 -0
- package/dist/input-editor-files.d.ts +15 -0
- package/dist/input-editor-files.js +62 -0
- package/dist/input-editor.d.ts +186 -0
- package/dist/input-editor.js +835 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +12 -0
- package/dist/markdown-format.d.ts +22 -0
- package/dist/markdown-format.js +542 -0
- package/dist/sdk.d.ts +3 -0
- package/dist/sdk.js +1 -0
- package/dist/syntax-highlight.d.ts +22 -0
- package/dist/syntax-highlight.js +528 -0
- package/dist/terminal-width.d.ts +6 -0
- package/dist/terminal-width.js +245 -0
- package/dist/theme.d.ts +56 -0
- package/dist/theme.js +118 -0
- package/dist/tool-renderers/apply-patch.d.ts +2 -0
- package/dist/tool-renderers/apply-patch.js +36 -0
- package/dist/tool-renderers/ast.d.ts +2 -0
- package/dist/tool-renderers/ast.js +5 -0
- package/dist/tool-renderers/compress.d.ts +2 -0
- package/dist/tool-renderers/compress.js +126 -0
- package/dist/tool-renderers/index.d.ts +3 -0
- package/dist/tool-renderers/index.js +44 -0
- package/dist/tool-renderers/question.d.ts +2 -0
- package/dist/tool-renderers/question.js +88 -0
- package/dist/tool-renderers/read.d.ts +2 -0
- package/dist/tool-renderers/read.js +36 -0
- package/dist/tool-renderers/repo.d.ts +2 -0
- package/dist/tool-renderers/repo.js +13 -0
- package/dist/tool-renderers/shell.d.ts +3 -0
- package/dist/tool-renderers/shell.js +27 -0
- package/dist/tool-renderers/skill.d.ts +2 -0
- package/dist/tool-renderers/skill.js +132 -0
- package/dist/tool-renderers/subagents.d.ts +2 -0
- package/dist/tool-renderers/subagents.js +51 -0
- package/dist/tool-renderers/todo.d.ts +2 -0
- package/dist/tool-renderers/todo.js +9 -0
- package/dist/tool-renderers/types.d.ts +42 -0
- package/dist/tool-renderers/types.js +1 -0
- package/dist/tool-renderers/utils.d.ts +31 -0
- package/dist/tool-renderers/utils.js +230 -0
- package/dist/tool-renderers/web.d.ts +3 -0
- package/dist/tool-renderers/web.js +9 -0
- package/dist/tool-renderers/write.d.ts +2 -0
- package/dist/tool-renderers/write.js +42 -0
- package/dist/ui.d.ts +56 -0
- package/dist/ui.js +94 -0
- package/docs/release.md +81 -0
- package/extensions/question/contract.ts +100 -0
- package/extensions/question/index.ts +34 -0
- package/extensions/question/render.ts +28 -0
- package/extensions/question/result.ts +86 -0
- package/extensions/question/tool-description.ts +11 -0
- package/extensions/question/tui.ts +629 -0
- package/extensions/question/types.ts +123 -0
- package/extensions/session-title/config.ts +169 -0
- package/extensions/session-title/index.ts +459 -0
- package/extensions/terminal-bell/index.ts +315 -0
- package/external/pi-tools-suite/README.md +242 -0
- package/external/pi-tools-suite/index.ts +1 -0
- package/external/pi-tools-suite/licenses/ollama-pi-web-search.MIT +21 -0
- package/external/pi-tools-suite/licenses/opencode-mystatus-MIT.txt +21 -0
- package/external/pi-tools-suite/package.json +53 -0
- package/external/pi-tools-suite/src/antigravity-auth/auth-store.ts +194 -0
- package/external/pi-tools-suite/src/antigravity-auth/commands.ts +80 -0
- package/external/pi-tools-suite/src/antigravity-auth/constants.ts +26 -0
- package/external/pi-tools-suite/src/antigravity-auth/headers.ts +20 -0
- package/external/pi-tools-suite/src/antigravity-auth/index.ts +104 -0
- package/external/pi-tools-suite/src/antigravity-auth/models.ts +86 -0
- package/external/pi-tools-suite/src/antigravity-auth/oauth.ts +305 -0
- package/external/pi-tools-suite/src/antigravity-auth/payload.ts +423 -0
- package/external/pi-tools-suite/src/antigravity-auth/status.ts +78 -0
- package/external/pi-tools-suite/src/antigravity-auth/stream.ts +302 -0
- package/external/pi-tools-suite/src/antigravity-auth/types.ts +113 -0
- package/external/pi-tools-suite/src/ast-grep/index.ts +6 -0
- package/external/pi-tools-suite/src/ast-grep/render.ts +70 -0
- package/external/pi-tools-suite/src/ast-grep/schema.ts +109 -0
- package/external/pi-tools-suite/src/ast-grep/tool.ts +345 -0
- package/external/pi-tools-suite/src/ast-grep/types.ts +55 -0
- package/external/pi-tools-suite/src/ast-grep/utils.ts +65 -0
- package/external/pi-tools-suite/src/async-subagents/async-subagents.sample.jsonc +222 -0
- package/external/pi-tools-suite/src/async-subagents/commands.ts +518 -0
- package/external/pi-tools-suite/src/async-subagents/constants.ts +11 -0
- package/external/pi-tools-suite/src/async-subagents/core/agent-strategy.ts +74 -0
- package/external/pi-tools-suite/src/async-subagents/core/attachment-bridge.ts +133 -0
- package/external/pi-tools-suite/src/async-subagents/core/cleanup.ts +66 -0
- package/external/pi-tools-suite/src/async-subagents/core/concurrency.ts +90 -0
- package/external/pi-tools-suite/src/async-subagents/core/config.ts +819 -0
- package/external/pi-tools-suite/src/async-subagents/core/log-limits.ts +166 -0
- package/external/pi-tools-suite/src/async-subagents/core/model-fallback.ts +133 -0
- package/external/pi-tools-suite/src/async-subagents/core/paths.ts +47 -0
- package/external/pi-tools-suite/src/async-subagents/core/pi-invocation.ts +35 -0
- package/external/pi-tools-suite/src/async-subagents/core/presets.ts +67 -0
- package/external/pi-tools-suite/src/async-subagents/core/process.ts +15 -0
- package/external/pi-tools-suite/src/async-subagents/core/prompt.ts +66 -0
- package/external/pi-tools-suite/src/async-subagents/core/registry.ts +224 -0
- package/external/pi-tools-suite/src/async-subagents/core/retry.ts +191 -0
- package/external/pi-tools-suite/src/async-subagents/core/routing.ts +259 -0
- package/external/pi-tools-suite/src/async-subagents/core/sessions.ts +138 -0
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +688 -0
- package/external/pi-tools-suite/src/async-subagents/core/state.ts +281 -0
- package/external/pi-tools-suite/src/async-subagents/core/stop.ts +131 -0
- package/external/pi-tools-suite/src/async-subagents/core/structured-result.ts +237 -0
- package/external/pi-tools-suite/src/async-subagents/core/tool-guard.ts +34 -0
- package/external/pi-tools-suite/src/async-subagents/core/types.ts +150 -0
- package/external/pi-tools-suite/src/async-subagents/core/ultrawork-auto.ts +184 -0
- package/external/pi-tools-suite/src/async-subagents/core/utils.ts +11 -0
- package/external/pi-tools-suite/src/async-subagents/format.ts +41 -0
- package/external/pi-tools-suite/src/async-subagents/index.ts +422 -0
- package/external/pi-tools-suite/src/async-subagents/lib.ts +88 -0
- package/external/pi-tools-suite/src/async-subagents/live.ts +10 -0
- package/external/pi-tools-suite/src/async-subagents/polling.ts +83 -0
- package/external/pi-tools-suite/src/async-subagents/render.ts +230 -0
- package/external/pi-tools-suite/src/async-subagents/subagent-overlay.ts +77 -0
- package/external/pi-tools-suite/src/async-subagents/tasks.ts +120 -0
- package/external/pi-tools-suite/src/async-subagents/tools/cleanup.ts +99 -0
- package/external/pi-tools-suite/src/async-subagents/tools/result.ts +179 -0
- package/external/pi-tools-suite/src/async-subagents/tools/spawn.ts +372 -0
- package/external/pi-tools-suite/src/async-subagents/tools/status.ts +60 -0
- package/external/pi-tools-suite/src/async-subagents/tools/stop.ts +79 -0
- package/external/pi-tools-suite/src/async-subagents/tools/subagents.ts +152 -0
- package/external/pi-tools-suite/src/async-subagents/tools/wait.ts +67 -0
- package/external/pi-tools-suite/src/async-subagents/types.ts +45 -0
- package/external/pi-tools-suite/src/async-subagents/ui.ts +5 -0
- package/external/pi-tools-suite/src/compress/commands.ts +440 -0
- package/external/pi-tools-suite/src/compress/compress-tool.ts +368 -0
- package/external/pi-tools-suite/src/compress/compression-blocks.ts +524 -0
- package/external/pi-tools-suite/src/compress/config.ts +310 -0
- package/external/pi-tools-suite/src/compress/dcp-tui-filter.ts +498 -0
- package/external/pi-tools-suite/src/compress/index.ts +397 -0
- package/external/pi-tools-suite/src/compress/prompts.ts +269 -0
- package/external/pi-tools-suite/src/compress/pruner-candidates.ts +176 -0
- package/external/pi-tools-suite/src/compress/pruner-compression-blocks.ts +260 -0
- package/external/pi-tools-suite/src/compress/pruner-message-ids.ts +147 -0
- package/external/pi-tools-suite/src/compress/pruner-metadata.ts +268 -0
- package/external/pi-tools-suite/src/compress/pruner-nudge.ts +315 -0
- package/external/pi-tools-suite/src/compress/pruner-tools.ts +263 -0
- package/external/pi-tools-suite/src/compress/pruner-types.ts +25 -0
- package/external/pi-tools-suite/src/compress/pruner.ts +92 -0
- package/external/pi-tools-suite/src/compress/state.ts +486 -0
- package/external/pi-tools-suite/src/compress/ui.ts +308 -0
- package/external/pi-tools-suite/src/config.ts +176 -0
- package/external/pi-tools-suite/src/context-usage.ts +31 -0
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +355 -0
- package/external/pi-tools-suite/src/index.ts +46 -0
- package/external/pi-tools-suite/src/lib/lsp.ts +62 -0
- package/external/pi-tools-suite/src/lib/project.ts +42 -0
- package/external/pi-tools-suite/src/lib/tool-args.ts +137 -0
- package/external/pi-tools-suite/src/lsp/_shared/config.ts +156 -0
- package/external/pi-tools-suite/src/lsp/_shared/glob.ts +60 -0
- package/external/pi-tools-suite/src/lsp/_shared/output.ts +102 -0
- package/external/pi-tools-suite/src/lsp/_shared/paths.ts +138 -0
- package/external/pi-tools-suite/src/lsp/_shared/runner.ts +64 -0
- package/external/pi-tools-suite/src/lsp/_shared/template.ts +23 -0
- package/external/pi-tools-suite/src/lsp/_shared/trust.ts +116 -0
- package/external/pi-tools-suite/src/lsp/_shared/types.ts +98 -0
- package/external/pi-tools-suite/src/lsp/async.ts +29 -0
- package/external/pi-tools-suite/src/lsp/child-process.ts +81 -0
- package/external/pi-tools-suite/src/lsp/client.ts +340 -0
- package/external/pi-tools-suite/src/lsp/constants.ts +9 -0
- package/external/pi-tools-suite/src/lsp/diagnostics-store.ts +64 -0
- package/external/pi-tools-suite/src/lsp/documents.ts +24 -0
- package/external/pi-tools-suite/src/lsp/index.ts +31 -0
- package/external/pi-tools-suite/src/lsp/lsp-utils.ts +37 -0
- package/external/pi-tools-suite/src/lsp/manager.ts +190 -0
- package/external/pi-tools-suite/src/lsp/mutation-events.ts +78 -0
- package/external/pi-tools-suite/src/lsp/renderer.ts +1 -0
- package/external/pi-tools-suite/src/lsp/tool-result.ts +6 -0
- package/external/pi-tools-suite/src/lsp/tsserver.ts +107 -0
- package/external/pi-tools-suite/src/lsp/types.ts +15 -0
- package/external/pi-tools-suite/src/model-tools/apply-patch.ts +590 -0
- package/external/pi-tools-suite/src/model-tools/index.ts +430 -0
- package/external/pi-tools-suite/src/model-tools/path-utils.ts +6 -0
- package/external/pi-tools-suite/src/model-tools/tool-args.ts +11 -0
- package/external/pi-tools-suite/src/prompt-commands/index.ts +349 -0
- package/external/pi-tools-suite/src/repo-discovery/index.ts +384 -0
- package/external/pi-tools-suite/src/repo-discovery/project.ts +7 -0
- package/external/pi-tools-suite/src/startup-section.ts +13 -0
- package/external/pi-tools-suite/src/terminal-bell/index.ts +309 -0
- package/external/pi-tools-suite/src/todo/index.ts +201 -0
- package/external/pi-tools-suite/src/todo/state/auto-clear.ts +13 -0
- package/external/pi-tools-suite/src/todo/state/invariants.ts +21 -0
- package/external/pi-tools-suite/src/todo/state/persistence.ts +94 -0
- package/external/pi-tools-suite/src/todo/state/replay.ts +38 -0
- package/external/pi-tools-suite/src/todo/state/selectors.ts +49 -0
- package/external/pi-tools-suite/src/todo/state/state-reducer.ts +362 -0
- package/external/pi-tools-suite/src/todo/state/state.ts +18 -0
- package/external/pi-tools-suite/src/todo/state/store.ts +52 -0
- package/external/pi-tools-suite/src/todo/state/task-graph.ts +57 -0
- package/external/pi-tools-suite/src/todo/todo.ts +487 -0
- package/external/pi-tools-suite/src/todo/tool/response-envelope.ts +143 -0
- package/external/pi-tools-suite/src/todo/tool/types.ts +188 -0
- package/external/pi-tools-suite/src/todo/view/format.ts +18 -0
- package/external/pi-tools-suite/src/todo/view/labels.ts +13 -0
- package/external/pi-tools-suite/src/tool-descriptions.ts +369 -0
- package/external/pi-tools-suite/src/usage/index.ts +152 -0
- package/external/pi-tools-suite/src/usage/lib/copilot.ts +535 -0
- package/external/pi-tools-suite/src/usage/lib/google.ts +478 -0
- package/external/pi-tools-suite/src/usage/lib/openai.ts +268 -0
- package/external/pi-tools-suite/src/usage/lib/types.ts +114 -0
- package/external/pi-tools-suite/src/usage/lib/utils.ts +134 -0
- package/external/pi-tools-suite/src/usage/lib/zhipu.ts +228 -0
- package/external/pi-tools-suite/src/web-search/index.ts +397 -0
- package/package.json +89 -0
- package/skills/context7/SKILL.md +69 -0
- package/skills/context7/scripts/context7.sh +73 -0
- package/skills/handoff/SKILL.md +15 -0
- package/skills/pdf/LICENSE.txt +30 -0
- package/skills/pdf/SKILL.md +314 -0
- package/skills/pdf/forms.md +294 -0
- package/skills/pdf/reference.md +612 -0
- package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
- package/skills/pdf/scripts/check_fillable_fields.py +11 -0
- package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
- package/skills/pdf/scripts/create_validation_image.py +37 -0
- package/skills/pdf/scripts/extract_form_field_info.py +122 -0
- package/skills/pdf/scripts/extract_form_structure.py +115 -0
- package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
- package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/skills/playwright-cli/SKILL.md +388 -0
- package/skills/playwright-cli/references/element-attributes.md +23 -0
- package/skills/playwright-cli/references/playwright-tests.md +39 -0
- package/skills/playwright-cli/references/request-mocking.md +87 -0
- package/skills/playwright-cli/references/running-code.md +241 -0
- package/skills/playwright-cli/references/session-management.md +225 -0
- package/skills/playwright-cli/references/spec-driven-testing.md +305 -0
- package/skills/playwright-cli/references/storage-state.md +275 -0
- package/skills/playwright-cli/references/test-generation.md +134 -0
- package/skills/playwright-cli/references/tracing.md +139 -0
- package/skills/playwright-cli/references/video-recording.md +143 -0
- package/skills/simplify/SKILL.md +51 -0
- package/skills/skill-creator/LICENSE.txt +202 -0
- package/skills/skill-creator/SKILL.md +485 -0
- package/skills/skill-creator/agents/analyzer.md +274 -0
- package/skills/skill-creator/agents/comparator.md +202 -0
- package/skills/skill-creator/agents/grader.md +223 -0
- package/skills/skill-creator/assets/eval_review.html +146 -0
- package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/skills/skill-creator/references/schemas.md +430 -0
- package/skills/skill-creator/scripts/__init__.py +0 -0
- package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/skills/skill-creator/scripts/generate_report.py +326 -0
- package/skills/skill-creator/scripts/improve_description.py +247 -0
- package/skills/skill-creator/scripts/package_skill.py +136 -0
- package/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/skills/skill-creator/scripts/run_eval.py +310 -0
- package/skills/skill-creator/scripts/run_loop.py +328 -0
- package/skills/skill-creator/scripts/utils.py +47 -0
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { delimiter, isAbsolute, join } from "node:path";
|
|
5
|
+
|
|
6
|
+
const BELL = "\x07";
|
|
7
|
+
const DEFAULT_IDLE_DELAY_MS = 250;
|
|
8
|
+
const IDLE_RETRY_DELAY_MS = 100;
|
|
9
|
+
const MAX_IDLE_RETRIES = 40;
|
|
10
|
+
const SUBAGENTS_LIVE_COUNT_EVENT = "pi-tools-suite:async-subagents:live-count";
|
|
11
|
+
const DEFAULT_NOTIFICATION_TITLE = "Pi";
|
|
12
|
+
const DEFAULT_NOTIFICATION_MESSAGE = "Session stopped";
|
|
13
|
+
const DEFAULT_ASK_USER_NOTIFICATION_MESSAGE = "Waiting for your answer";
|
|
14
|
+
const DEFAULT_MAC_SOUND = "Glass";
|
|
15
|
+
|
|
16
|
+
const TERM_PROGRAM_BUNDLE_IDS: Record<string, string> = {
|
|
17
|
+
Apple_Terminal: "com.apple.Terminal",
|
|
18
|
+
iTerm: "com.googlecode.iterm2",
|
|
19
|
+
"iTerm.app": "com.googlecode.iterm2",
|
|
20
|
+
WezTerm: "com.github.wez.wezterm",
|
|
21
|
+
WarpTerminal: "dev.warp.Warp-Stable",
|
|
22
|
+
ghostty: "com.mitchellh.ghostty",
|
|
23
|
+
Ghostty: "com.mitchellh.ghostty",
|
|
24
|
+
kitty: "net.kovidgoyal.kitty",
|
|
25
|
+
Alacritty: "org.alacritty",
|
|
26
|
+
vscode: "com.microsoft.VSCode",
|
|
27
|
+
"vscode-insiders": "com.microsoft.VSCodeInsiders",
|
|
28
|
+
zed: "dev.zed.Zed",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type Timer = ReturnType<typeof setTimeout>;
|
|
32
|
+
|
|
33
|
+
type SubagentsLiveCountEvent = {
|
|
34
|
+
count?: unknown;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function parseDelayMs(value: string | undefined): number {
|
|
38
|
+
if (value === undefined || value.trim() === "") return DEFAULT_IDLE_DELAY_MS;
|
|
39
|
+
const parsed = Number(value);
|
|
40
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : DEFAULT_IDLE_DELAY_MS;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isTruthyEnv(value: string | undefined): boolean {
|
|
44
|
+
if (value === undefined) return false;
|
|
45
|
+
return ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function extensionDisabled(): boolean {
|
|
49
|
+
return isTruthyEnv(process.env.HEADLESS) || isTruthyEnv(process.env.PI_TERMINAL_BELL_DISABLED);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function canRingTerminal(): boolean {
|
|
53
|
+
if (process.env.PI_TERMINAL_BELL === "0") return false;
|
|
54
|
+
if (process.env.PI_TERMINAL_BELL_FORCE === "1") return true;
|
|
55
|
+
return Boolean(process.stdout.isTTY || process.stderr.isTTY);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function writeBell(): void {
|
|
59
|
+
const stream = process.stdout.isTTY || !process.stderr.isTTY ? process.stdout : process.stderr;
|
|
60
|
+
stream.write(BELL);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function soundEnabled(ctx: ExtensionContext): boolean {
|
|
64
|
+
if (process.env.PI_TERMINAL_BELL_SOUND === "0") return false;
|
|
65
|
+
if (process.env.PI_TERMINAL_BELL_SOUND === "1") return true;
|
|
66
|
+
return ctx.hasUI === true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function notificationsEnabled(ctx: ExtensionContext): boolean {
|
|
70
|
+
if (process.env.PI_TERMINAL_BELL_NOTIFY === "0") return false;
|
|
71
|
+
if (process.env.PI_TERMINAL_BELL_NOTIFY === "1") return true;
|
|
72
|
+
return ctx.hasUI === true;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function appleScriptString(value: string): string {
|
|
76
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function shellSingleQuote(value: string): string {
|
|
80
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function spawnDetached(command: string, args: string[]): void {
|
|
84
|
+
try {
|
|
85
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
86
|
+
child.on("error", () => {});
|
|
87
|
+
child.unref();
|
|
88
|
+
} catch {
|
|
89
|
+
// Best-effort user attention signal. Missing notification backends should not
|
|
90
|
+
// affect the agent loop or suppress the terminal bell.
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function findExecutable(command: string): string | undefined {
|
|
95
|
+
if (command.includes("/")) return existsSync(command) ? command : undefined;
|
|
96
|
+
for (const dir of (process.env.PATH ?? "").split(delimiter)) {
|
|
97
|
+
if (!dir) continue;
|
|
98
|
+
const candidate = join(dir, command);
|
|
99
|
+
if (existsSync(candidate)) return candidate;
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function resolveMacSoundPath(sound: string): string {
|
|
105
|
+
if (isAbsolute(sound)) return sound;
|
|
106
|
+
const fileName = sound.endsWith(".aiff") ? sound : `${sound}.aiff`;
|
|
107
|
+
return `/System/Library/Sounds/${fileName}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function detectMacActivationBundleId(): string | undefined {
|
|
111
|
+
const explicit = process.env.PI_TERMINAL_BELL_NOTIFY_ACTIVATE;
|
|
112
|
+
if (explicit === "0" || explicit === "false") return undefined;
|
|
113
|
+
if (explicit && explicit.trim() !== "") return explicit.trim();
|
|
114
|
+
|
|
115
|
+
// GUI apps that launch shells on macOS commonly export their own bundle id.
|
|
116
|
+
// This catches Terminal.app, iTerm2, Zed's terminal, VS Code terminals, etc.
|
|
117
|
+
const inheritedBundleId = process.env.__CFBundleIdentifier;
|
|
118
|
+
if (inheritedBundleId && inheritedBundleId.trim() !== "") return inheritedBundleId.trim();
|
|
119
|
+
|
|
120
|
+
const termProgram = process.env.TERM_PROGRAM;
|
|
121
|
+
if (!termProgram) return undefined;
|
|
122
|
+
return TERM_PROGRAM_BUNDLE_IDS[termProgram];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function playAttentionSound(ctx: ExtensionContext): void {
|
|
126
|
+
if (!soundEnabled(ctx)) return;
|
|
127
|
+
if (process.platform !== "darwin") return;
|
|
128
|
+
const sound = process.env.PI_TERMINAL_BELL_SOUND && process.env.PI_TERMINAL_BELL_SOUND !== "1"
|
|
129
|
+
? process.env.PI_TERMINAL_BELL_SOUND
|
|
130
|
+
: DEFAULT_MAC_SOUND;
|
|
131
|
+
const soundPath = resolveMacSoundPath(sound);
|
|
132
|
+
if (!existsSync(soundPath)) return;
|
|
133
|
+
spawnDetached("/usr/bin/afplay", [soundPath]);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function notifySessionStopped(
|
|
137
|
+
ctx: ExtensionContext,
|
|
138
|
+
macActivationBundleId: string | undefined,
|
|
139
|
+
message = process.env.PI_TERMINAL_BELL_NOTIFY_MESSAGE ?? DEFAULT_NOTIFICATION_MESSAGE,
|
|
140
|
+
): void {
|
|
141
|
+
if (!notificationsEnabled(ctx)) return;
|
|
142
|
+
const title = process.env.PI_TERMINAL_BELL_NOTIFY_TITLE ?? DEFAULT_NOTIFICATION_TITLE;
|
|
143
|
+
|
|
144
|
+
if (process.platform === "darwin") {
|
|
145
|
+
const terminalNotifier = findExecutable(process.env.PI_TERMINAL_BELL_NOTIFIER ?? "terminal-notifier");
|
|
146
|
+
if (terminalNotifier) {
|
|
147
|
+
const args = ["-title", title, "-message", message];
|
|
148
|
+
const activate = macActivationBundleId;
|
|
149
|
+
if (activate) {
|
|
150
|
+
args.push("-activate", activate);
|
|
151
|
+
args.push("-execute", `/usr/bin/open -b ${shellSingleQuote(activate)}`);
|
|
152
|
+
|
|
153
|
+
// Do not pass -sender by default. On recent macOS versions it can make the
|
|
154
|
+
// notification look like it came from the target app, but then clicking the
|
|
155
|
+
// “Show” button may be handled as that app's own notification instead of
|
|
156
|
+
// terminal-notifier's -activate/-execute action.
|
|
157
|
+
if (process.env.PI_TERMINAL_BELL_NOTIFY_SENDER === "1") {
|
|
158
|
+
args.push("-sender", activate);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
spawnDetached(terminalNotifier, args);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Bare osascript notifications are sent by Script Editor/osascript on macOS;
|
|
166
|
+
// clicking them can open Script Editor's file picker. Keep that backend opt-in
|
|
167
|
+
// and prefer terminal-notifier for clickable system notifications.
|
|
168
|
+
if (process.env.PI_TERMINAL_BELL_NOTIFY_OSASCRIPT !== "1") return;
|
|
169
|
+
spawnDetached("/usr/bin/osascript", [
|
|
170
|
+
"-e",
|
|
171
|
+
`display notification ${appleScriptString(message)} with title ${appleScriptString(title)}`,
|
|
172
|
+
]);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (process.platform === "linux") {
|
|
177
|
+
spawnDetached("notify-send", [title, message]);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const ASK_USER_TOOL_NAMES = new Set(["ask_user", "ask_user_question", "question"]);
|
|
182
|
+
|
|
183
|
+
function isAskUserToolName(toolName: string): boolean {
|
|
184
|
+
return ASK_USER_TOOL_NAMES.has(toolName);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function isSubagentsWaitTool(toolName: string, args: unknown): boolean {
|
|
188
|
+
if (toolName === "async_subagents_wait") return true;
|
|
189
|
+
if (toolName !== "subagents") return false;
|
|
190
|
+
if (!args || typeof args !== "object") return false;
|
|
191
|
+
const action = (args as { action?: unknown }).action;
|
|
192
|
+
return typeof action === "string" && action.trim().toLowerCase() === "wait";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function normalizeLiveCount(event: SubagentsLiveCountEvent): number | undefined {
|
|
196
|
+
if (typeof event.count !== "number" || !Number.isFinite(event.count)) return undefined;
|
|
197
|
+
return Math.max(0, Math.floor(event.count));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export default function terminalBell(pi: ExtensionAPI) {
|
|
201
|
+
if (extensionDisabled()) return;
|
|
202
|
+
|
|
203
|
+
let timer: Timer | undefined;
|
|
204
|
+
let lastCtx: ExtensionContext | undefined;
|
|
205
|
+
let deferredUntilSubagentsFinish = false;
|
|
206
|
+
let liveSubagentCount = 0;
|
|
207
|
+
const activeSubagentWaitToolCallIds = new Set<string>();
|
|
208
|
+
const notifiedAskUserToolCallIds = new Set<string>();
|
|
209
|
+
const idleDelayMs = parseDelayMs(process.env.PI_TERMINAL_BELL_DELAY_MS);
|
|
210
|
+
const macActivationBundleId = process.platform === "darwin" ? detectMacActivationBundleId() : undefined;
|
|
211
|
+
|
|
212
|
+
function clearTimer(): void {
|
|
213
|
+
if (!timer) return;
|
|
214
|
+
clearTimeout(timer);
|
|
215
|
+
timer = undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function hasSubagentWork(): boolean {
|
|
219
|
+
return liveSubagentCount > 0 || activeSubagentWaitToolCallIds.size > 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function notifyAttention(ctx: ExtensionContext, message?: string): void {
|
|
223
|
+
if (canRingTerminal()) writeBell();
|
|
224
|
+
playAttentionSound(ctx);
|
|
225
|
+
notifySessionStopped(ctx, macActivationBundleId, message);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function attemptBell(ctx: ExtensionContext, attempt: number): void {
|
|
229
|
+
timer = undefined;
|
|
230
|
+
|
|
231
|
+
if (!ctx.isIdle()) {
|
|
232
|
+
if (attempt < MAX_IDLE_RETRIES) scheduleBell(ctx, IDLE_RETRY_DELAY_MS, attempt + 1);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (ctx.hasPendingMessages()) return;
|
|
237
|
+
|
|
238
|
+
if (hasSubagentWork()) {
|
|
239
|
+
deferredUntilSubagentsFinish = true;
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
deferredUntilSubagentsFinish = false;
|
|
244
|
+
notifyAttention(ctx);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function scheduleBell(ctx: ExtensionContext, delayMs = idleDelayMs, attempt = 0): void {
|
|
248
|
+
lastCtx = ctx;
|
|
249
|
+
clearTimer();
|
|
250
|
+
timer = setTimeout(() => attemptBell(ctx, attempt), delayMs);
|
|
251
|
+
timer.unref?.();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function notifyAskUserWaiting(toolCallId: string, ctx: ExtensionContext): void {
|
|
255
|
+
if (notifiedAskUserToolCallIds.has(toolCallId)) return;
|
|
256
|
+
notifiedAskUserToolCallIds.add(toolCallId);
|
|
257
|
+
notifyAttention(ctx, process.env.PI_TERMINAL_BELL_ASK_USER_NOTIFY_MESSAGE ?? DEFAULT_ASK_USER_NOTIFICATION_MESSAGE);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
pi.events.on(SUBAGENTS_LIVE_COUNT_EVENT, (data: unknown) => {
|
|
261
|
+
const event = data && typeof data === "object" ? data as SubagentsLiveCountEvent : {};
|
|
262
|
+
const count = normalizeLiveCount(event);
|
|
263
|
+
if (count === undefined) return;
|
|
264
|
+
liveSubagentCount = count;
|
|
265
|
+
if (count === 0 && deferredUntilSubagentsFinish && lastCtx) {
|
|
266
|
+
scheduleBell(lastCtx);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
pi.on("agent_start", async () => {
|
|
271
|
+
clearTimer();
|
|
272
|
+
deferredUntilSubagentsFinish = false;
|
|
273
|
+
activeSubagentWaitToolCallIds.clear();
|
|
274
|
+
notifiedAskUserToolCallIds.clear();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
pi.on("tool_execution_start", async (event, ctx) => {
|
|
278
|
+
if (isSubagentsWaitTool(event.toolName, event.args)) {
|
|
279
|
+
activeSubagentWaitToolCallIds.add(event.toolCallId);
|
|
280
|
+
}
|
|
281
|
+
if (isAskUserToolName(event.toolName)) {
|
|
282
|
+
notifyAskUserWaiting(event.toolCallId, ctx);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
287
|
+
if (isAskUserToolName(event.toolName)) {
|
|
288
|
+
notifyAskUserWaiting(event.toolCallId, ctx);
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
pi.on("tool_execution_end", async (event) => {
|
|
293
|
+
activeSubagentWaitToolCallIds.delete(event.toolCallId);
|
|
294
|
+
notifiedAskUserToolCallIds.delete(event.toolCallId);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
298
|
+
scheduleBell(ctx);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
pi.on("session_shutdown", async () => {
|
|
302
|
+
clearTimer();
|
|
303
|
+
lastCtx = undefined;
|
|
304
|
+
deferredUntilSubagentsFinish = false;
|
|
305
|
+
liveSubagentCount = 0;
|
|
306
|
+
activeSubagentWaitToolCallIds.clear();
|
|
307
|
+
notifiedAskUserToolCallIds.clear();
|
|
308
|
+
});
|
|
309
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { autoClearCompletedTodos } from "./state/auto-clear.js";
|
|
3
|
+
import { loadPersistedPlan, syncPersistedPlan } from "./state/persistence.js";
|
|
4
|
+
import { replayFromBranch } from "./state/replay.js";
|
|
5
|
+
import { ACTIVE_STATUSES, selectVisibleTasks } from "./state/selectors.js";
|
|
6
|
+
import { getState, replaceState } from "./state/store.js";
|
|
7
|
+
import { publishTodoState, registerTodosCommand, registerTodoTool } from "./todo.js";
|
|
8
|
+
|
|
9
|
+
const TODO_NUDGE_LIMIT = 8;
|
|
10
|
+
const TODO_NUDGE_INITIAL_DELAY_MS = 5_000;
|
|
11
|
+
const TODO_NUDGE_IDLE_RETRY_DELAY_MS = 100;
|
|
12
|
+
const TODO_NUDGE_MAX_IDLE_ATTEMPTS = 40;
|
|
13
|
+
const ASK_USER_TOOL_NAMES = new Set(["ask_user", "ask_user_question", "question"]);
|
|
14
|
+
|
|
15
|
+
function isAskUserToolName(toolName: string): boolean {
|
|
16
|
+
return ASK_USER_TOOL_NAMES.has(toolName);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getUnfinishedTodoNudge(): { signature: string; message: string } | undefined {
|
|
20
|
+
const unfinished = selectVisibleTasks(getState()).filter((task) => ACTIVE_STATUSES.has(task.status));
|
|
21
|
+
if (unfinished.length === 0) return undefined;
|
|
22
|
+
|
|
23
|
+
const signature = JSON.stringify(
|
|
24
|
+
unfinished.map((task) => ({
|
|
25
|
+
id: task.id,
|
|
26
|
+
status: task.status,
|
|
27
|
+
subject: task.subject,
|
|
28
|
+
activeForm: task.activeForm,
|
|
29
|
+
blockedBy: task.blockedBy ?? [],
|
|
30
|
+
})),
|
|
31
|
+
);
|
|
32
|
+
const shown = unfinished.slice(0, TODO_NUDGE_LIMIT);
|
|
33
|
+
const hidden = unfinished.length - shown.length;
|
|
34
|
+
const lines = shown.map((task) => {
|
|
35
|
+
const activeForm = task.activeForm ? ` — ${task.activeForm}` : "";
|
|
36
|
+
const blockedBy = task.blockedBy && task.blockedBy.length > 0 ? ` (blocked by #${task.blockedBy.join(", #")})` : "";
|
|
37
|
+
return `- #${task.id} [${task.status}] ${task.subject}${activeForm}${blockedBy}`;
|
|
38
|
+
});
|
|
39
|
+
if (hidden > 0) lines.push(`- …and ${hidden} more unfinished todo item${hidden === 1 ? "" : "s"}.`);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
signature,
|
|
43
|
+
message: [
|
|
44
|
+
"Todo auto-nudge: unfinished todo items remain after your last response.",
|
|
45
|
+
"Continue working on them now. Pick exactly one pending/in_progress item, mark it in_progress if needed, make concrete progress, and update or complete todos immediately as work changes.",
|
|
46
|
+
"If the user added/removed/canceled requirements or changed goal/scope/priority/approach, or if discovered facts make the current plan stale/incomplete/impossible, synchronize todos first: update still-relevant items, defer/delete obsolete ones, add new tasks, and adjust blockers/order.",
|
|
47
|
+
"If progress is waiting on user-supplied data, clarification, or a decision, defer the affected plan/todos before your final response instead of leaving them pending/in_progress, so auto-nudge stops until the user replies.",
|
|
48
|
+
"For non-user blockers, leave the current item in_progress and create/update a blocker task instead of stopping.",
|
|
49
|
+
"",
|
|
50
|
+
...lines,
|
|
51
|
+
].join("\n"),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getPersistedPlanPrompt(path: string): string | undefined {
|
|
56
|
+
const unfinished = selectVisibleTasks(getState()).filter((task) => task.status !== "completed");
|
|
57
|
+
if (unfinished.length === 0) return undefined;
|
|
58
|
+
const lines = unfinished.slice(0, TODO_NUDGE_LIMIT).map((task) => {
|
|
59
|
+
const activeForm = task.activeForm ? ` — ${task.activeForm}` : "";
|
|
60
|
+
const priority = task.priority ? ` (${task.priority})` : "";
|
|
61
|
+
const blockedBy = task.blockedBy?.length ? ` (blocked by #${task.blockedBy.join(", #")})` : "";
|
|
62
|
+
return `- #${task.id} [${task.status}]${priority} ${task.subject}${activeForm}${blockedBy}`;
|
|
63
|
+
});
|
|
64
|
+
if (unfinished.length > TODO_NUDGE_LIMIT) lines.push(`- …and ${unfinished.length - TODO_NUDGE_LIMIT} more unfinished todo item(s).`);
|
|
65
|
+
return [
|
|
66
|
+
`Persisted todo plan loaded from ${path}.`,
|
|
67
|
+
"Ask the user which item(s) to continue before implementing. Offer a compact choice list based on these active tasks; do not dump the full plan unless asked.",
|
|
68
|
+
"After the user chooses a scope, run /todos scope <id...> so out-of-scope active tasks become deferred and stop triggering auto-nudges.",
|
|
69
|
+
"Before resuming implementation, synchronize the loaded plan if the user's current goal/scope/requirements changed or discovered facts make it stale/incomplete: update still-relevant items, defer/delete obsolete ones, add new tasks, and adjust blockers/order.",
|
|
70
|
+
"Use /todos persist off (or clear) when the plan should be discarded; when all visible tasks are completed, persistence will be disabled and the plan file removed automatically.",
|
|
71
|
+
"",
|
|
72
|
+
...lines,
|
|
73
|
+
].join("\n");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function emitPersistedPlanPrompt(pi: ExtensionAPI, ctx: ExtensionContext, prompt: string): void {
|
|
77
|
+
if (ctx.hasUI) {
|
|
78
|
+
pi.sendUserMessage(prompt);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
console.log(prompt);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default function (pi: ExtensionAPI) {
|
|
85
|
+
let lastNudgedSignature: string | undefined;
|
|
86
|
+
let nudgeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
87
|
+
const pendingAskUserToolCallIds = new Set<string>();
|
|
88
|
+
|
|
89
|
+
function clearNudgeTimer(): void {
|
|
90
|
+
if (!nudgeTimer) return;
|
|
91
|
+
clearTimeout(nudgeTimer);
|
|
92
|
+
nudgeTimer = undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function scheduleTodoNudge(ctx: ExtensionContext, attempt = 0): void {
|
|
96
|
+
clearNudgeTimer();
|
|
97
|
+
const delayMs = attempt === 0 ? TODO_NUDGE_INITIAL_DELAY_MS : TODO_NUDGE_IDLE_RETRY_DELAY_MS;
|
|
98
|
+
nudgeTimer = setTimeout(() => {
|
|
99
|
+
nudgeTimer = undefined;
|
|
100
|
+
try {
|
|
101
|
+
if (!ctx.isIdle()) {
|
|
102
|
+
if (attempt < TODO_NUDGE_MAX_IDLE_ATTEMPTS) scheduleTodoNudge(ctx, attempt + 1);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (ctx.hasPendingMessages()) return;
|
|
106
|
+
|
|
107
|
+
const nudge = getUnfinishedTodoNudge();
|
|
108
|
+
if (!nudge) {
|
|
109
|
+
lastNudgedSignature = undefined;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Avoid an infinite self-nudge loop when the previous nudge did not change
|
|
114
|
+
// the pending/in_progress todo snapshot.
|
|
115
|
+
if (nudge.signature === lastNudgedSignature) return;
|
|
116
|
+
lastNudgedSignature = nudge.signature;
|
|
117
|
+
|
|
118
|
+
// agent_end fires before Pi is fully back in idle dispatch. Sending as a
|
|
119
|
+
// normal user message on the next idle tick reliably starts a fresh turn;
|
|
120
|
+
// queueing followUp from inside agent_end can be too late to be drained.
|
|
121
|
+
pi.sendUserMessage(nudge.message);
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.warn(`rpiv-todo: failed to auto-nudge unfinished todos — ${(err as Error).message}`);
|
|
124
|
+
}
|
|
125
|
+
}, delayMs);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
registerTodoTool(pi, {
|
|
129
|
+
afterCommit: (state, ctx) => {
|
|
130
|
+
try {
|
|
131
|
+
const sync = syncPersistedPlan(ctx.cwd, state);
|
|
132
|
+
if (sync?.completed) console.log(`rpiv-todo: completed persisted plan and removed ${sync.path}`);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
console.warn(`rpiv-todo: failed to sync persisted plan — ${(err as Error).message}`);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
registerTodosCommand(pi);
|
|
139
|
+
|
|
140
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
141
|
+
const persisted = loadPersistedPlan(ctx.cwd);
|
|
142
|
+
const loaded = autoClearCompletedTodos(persisted?.state ?? replayFromBranch(ctx));
|
|
143
|
+
replaceState(loaded.state);
|
|
144
|
+
publishTodoState(pi as any, ctx);
|
|
145
|
+
if (persisted && loaded.cleared) syncPersistedPlan(ctx.cwd, loaded.state);
|
|
146
|
+
lastNudgedSignature = undefined;
|
|
147
|
+
if (persisted) {
|
|
148
|
+
const prompt = getPersistedPlanPrompt(persisted.path);
|
|
149
|
+
if (prompt) {
|
|
150
|
+
emitPersistedPlanPrompt(pi, ctx, prompt);
|
|
151
|
+
lastNudgedSignature = getUnfinishedTodoNudge()?.signature;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
pi.on("session_compact", async (_event, ctx) => {
|
|
157
|
+
replaceState(autoClearCompletedTodos(loadPersistedPlan(ctx.cwd)?.state ?? replayFromBranch(ctx)).state);
|
|
158
|
+
publishTodoState(pi as any, ctx);
|
|
159
|
+
lastNudgedSignature = undefined;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
pi.on("session_tree", async (_event, ctx) => {
|
|
163
|
+
replaceState(autoClearCompletedTodos(loadPersistedPlan(ctx.cwd)?.state ?? replayFromBranch(ctx)).state);
|
|
164
|
+
publishTodoState(pi as any, ctx);
|
|
165
|
+
lastNudgedSignature = undefined;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
pi.on("session_shutdown", async () => {
|
|
169
|
+
clearNudgeTimer();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Reads getTodos() at render time; do NOT call replayFromBranch here
|
|
173
|
+
// (branch is stale — message_end runs after tool_execution_end).
|
|
174
|
+
pi.on("tool_execution_start", async (event) => {
|
|
175
|
+
if (!isAskUserToolName(event.toolName)) return;
|
|
176
|
+
pendingAskUserToolCallIds.add(event.toolCallId);
|
|
177
|
+
clearNudgeTimer();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
pi.on("tool_execution_end", async (event) => {
|
|
181
|
+
if (isAskUserToolName(event.toolName)) pendingAskUserToolCallIds.delete(event.toolCallId);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
pi.on("agent_start", async () => {
|
|
185
|
+
pendingAskUserToolCallIds.clear();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
189
|
+
if (pendingAskUserToolCallIds.size > 0) {
|
|
190
|
+
clearNudgeTimer();
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const nudge = getUnfinishedTodoNudge();
|
|
195
|
+
if (!nudge) {
|
|
196
|
+
lastNudgedSignature = undefined;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
scheduleTodoNudge(ctx);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { EMPTY_STATE, type TaskState } from "./state.js";
|
|
2
|
+
|
|
3
|
+
export const AUTO_CLEAR_COMPLETED_MESSAGE = "All todos completed; cleared automatically.";
|
|
4
|
+
|
|
5
|
+
export function shouldAutoClearCompletedTodos(state: TaskState): boolean {
|
|
6
|
+
const visible = state.tasks.filter((task) => task.status !== "deleted");
|
|
7
|
+
return visible.length > 0 && visible.every((task) => task.status === "completed");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function autoClearCompletedTodos(state: TaskState): { state: TaskState; cleared: boolean; count: number } {
|
|
11
|
+
if (!shouldAutoClearCompletedTodos(state)) return { state, cleared: false, count: 0 };
|
|
12
|
+
return { state: { ...EMPTY_STATE, tasks: [] }, cleared: true, count: state.tasks.length };
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TaskStatus } from "../tool/types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Allowed forward transitions per source status. `completed` is one-way to
|
|
5
|
+
* `deleted` (never back to `in_progress`); `deleted` is terminal.
|
|
6
|
+
*
|
|
7
|
+
* Idempotent same→same is checked separately in `isTransitionValid` so this
|
|
8
|
+
* table only enumerates actual transitions.
|
|
9
|
+
*/
|
|
10
|
+
export const VALID_TRANSITIONS: Record<TaskStatus, ReadonlySet<TaskStatus>> = {
|
|
11
|
+
pending: new Set(["in_progress", "deferred", "completed", "deleted"]),
|
|
12
|
+
in_progress: new Set(["pending", "deferred", "completed", "deleted"]),
|
|
13
|
+
deferred: new Set(["pending", "in_progress", "completed", "deleted"]),
|
|
14
|
+
completed: new Set(["deleted"]),
|
|
15
|
+
deleted: new Set(),
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function isTransitionValid(from: TaskStatus, to: TaskStatus): boolean {
|
|
19
|
+
if (from === to) return true;
|
|
20
|
+
return VALID_TRANSITIONS[from].has(to);
|
|
21
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import type { Task } from "../tool/types.js";
|
|
4
|
+
import { EMPTY_STATE, type TaskState } from "./state.js";
|
|
5
|
+
|
|
6
|
+
export const TODO_PLAN_RELATIVE_PATH = join(".pi", "todo-plan.json");
|
|
7
|
+
|
|
8
|
+
interface PersistedTodoPlan {
|
|
9
|
+
version: 1;
|
|
10
|
+
enabled: true;
|
|
11
|
+
updatedAt: string;
|
|
12
|
+
nextId: number;
|
|
13
|
+
tasks: Task[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PersistedPlanLoadResult {
|
|
17
|
+
path: string;
|
|
18
|
+
state: TaskState;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function cloneTask(task: Task): Task {
|
|
22
|
+
return {
|
|
23
|
+
...task,
|
|
24
|
+
blockedBy: task.blockedBy ? [...task.blockedBy] : undefined,
|
|
25
|
+
tags: task.tags ? [...task.tags] : undefined,
|
|
26
|
+
metadata: task.metadata ? { ...task.metadata } : undefined,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeCwd(cwd: string | undefined): string {
|
|
31
|
+
return cwd && cwd.trim() ? cwd : process.cwd();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getTodoPlanPath(cwd: string | undefined): string {
|
|
35
|
+
return join(normalizeCwd(cwd), TODO_PLAN_RELATIVE_PATH);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function isPersistenceEnabled(cwd: string | undefined): boolean {
|
|
39
|
+
return existsSync(getTodoPlanPath(cwd));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function loadPersistedPlan(cwd: string | undefined): PersistedPlanLoadResult | undefined {
|
|
43
|
+
const path = getTodoPlanPath(cwd);
|
|
44
|
+
if (!existsSync(path)) return undefined;
|
|
45
|
+
try {
|
|
46
|
+
const raw = JSON.parse(readFileSync(path, "utf8")) as Partial<PersistedTodoPlan>;
|
|
47
|
+
if (raw.version !== 1 || raw.enabled !== true || !Array.isArray(raw.tasks)) return undefined;
|
|
48
|
+
return {
|
|
49
|
+
path,
|
|
50
|
+
state: {
|
|
51
|
+
tasks: raw.tasks.map(cloneTask),
|
|
52
|
+
nextId: typeof raw.nextId === "number" && Number.isFinite(raw.nextId) ? raw.nextId : EMPTY_STATE.nextId,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
} catch (err) {
|
|
56
|
+
console.warn(`rpiv-todo: failed to load persisted plan from ${path} — ${(err as Error).message}`);
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function savePersistedPlan(cwd: string | undefined, state: TaskState): string {
|
|
62
|
+
const path = getTodoPlanPath(cwd);
|
|
63
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
64
|
+
const payload: PersistedTodoPlan = {
|
|
65
|
+
version: 1,
|
|
66
|
+
enabled: true,
|
|
67
|
+
updatedAt: new Date().toISOString(),
|
|
68
|
+
nextId: state.nextId,
|
|
69
|
+
tasks: state.tasks.map(cloneTask),
|
|
70
|
+
};
|
|
71
|
+
const tmpPath = `${path}.${process.pid}.tmp`;
|
|
72
|
+
writeFileSync(tmpPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
73
|
+
renameSync(tmpPath, path);
|
|
74
|
+
return path;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function disablePersistence(cwd: string | undefined): string {
|
|
78
|
+
const path = getTodoPlanPath(cwd);
|
|
79
|
+
rmSync(path, { force: true });
|
|
80
|
+
return path;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function isPlanComplete(state: TaskState): boolean {
|
|
84
|
+
const visible = state.tasks.filter((task) => task.status !== "deleted");
|
|
85
|
+
return visible.length === 0 || visible.every((task) => task.status === "completed");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function syncPersistedPlan(cwd: string | undefined, state: TaskState): { path: string; completed: boolean } | undefined {
|
|
89
|
+
if (!isPersistenceEnabled(cwd)) return undefined;
|
|
90
|
+
if (isPlanComplete(state)) {
|
|
91
|
+
return { path: disablePersistence(cwd), completed: true };
|
|
92
|
+
}
|
|
93
|
+
return { path: savePersistedPlan(cwd, state), completed: false };
|
|
94
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { TaskDetails } from "../tool/types.js";
|
|
2
|
+
import { EMPTY_STATE, type TaskState } from "./state.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Discriminator for `details` envelopes that match the persisted `TaskDetails`
|
|
6
|
+
* shape. Defensive — branch entries from older or corrupt sessions are
|
|
7
|
+
* skipped silently.
|
|
8
|
+
*/
|
|
9
|
+
export function isTaskDetails(value: unknown): value is TaskDetails {
|
|
10
|
+
if (!value || typeof value !== "object") return false;
|
|
11
|
+
const v = value as Record<string, unknown>;
|
|
12
|
+
return Array.isArray(v.tasks) && typeof v.nextId === "number";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Walk the current branch in chronological order; the LAST `toolResult` whose
|
|
17
|
+
* `toolName === "todo"` and whose `details` shape matches `TaskDetails` wins
|
|
18
|
+
* (last-write-wins). When no matching entry exists, returns `EMPTY_STATE`.
|
|
19
|
+
*
|
|
20
|
+
* Pure of module state — `index.ts` writes the returned snapshot into the
|
|
21
|
+
* store after this returns. The function explicitly does NOT touch the store
|
|
22
|
+
* cell.
|
|
23
|
+
*/
|
|
24
|
+
export function replayFromBranch(ctx: { sessionManager: { getBranch(): Iterable<unknown> } }): TaskState {
|
|
25
|
+
let result: TaskState = { tasks: [...EMPTY_STATE.tasks], nextId: EMPTY_STATE.nextId };
|
|
26
|
+
for (const entry of ctx.sessionManager.getBranch()) {
|
|
27
|
+
const e = entry as { type?: string; message?: { role?: string; toolName?: string; details?: unknown } };
|
|
28
|
+
if (e.type !== "message") continue;
|
|
29
|
+
const msg = e.message;
|
|
30
|
+
if (!msg || msg.role !== "toolResult" || msg.toolName !== "todo") continue;
|
|
31
|
+
if (!isTaskDetails(msg.details)) continue;
|
|
32
|
+
result = {
|
|
33
|
+
tasks: msg.details.tasks.map((t) => ({ ...t })),
|
|
34
|
+
nextId: msg.details.nextId,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|