one-code-extension 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -5
- package/agents/general-purpose.md +2 -2
- package/docs/guide/README.md +2 -1
- package/docs/guide/bring-your-claude-code-setup.md +60 -9
- package/docs/guide/configuration.md +48 -14
- package/docs/guide/differences-from-claude-code.md +40 -28
- package/docs/guide/doctor.md +20 -13
- package/docs/guide/hooks.md +18 -3
- package/docs/guide/installation.md +26 -4
- package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
- package/docs/guide/providers-and-models.md +137 -54
- package/docs/guide/reference.md +27 -15
- package/docs/guide/sessions-and-context.md +69 -16
- package/docs/guide/skills-plugins-and-mcp.md +57 -20
- package/docs/guide/subagents-and-workflows.md +95 -29
- package/docs/guide/tasks-and-background-work.md +101 -26
- package/docs/guide/terminal-interface.md +24 -12
- package/docs/guide/tools.md +46 -5
- package/docs/guide/troubleshooting.md +13 -9
- package/docs/guide/windows.md +58 -31
- package/extensions/artifacts/gallery.ts +227 -0
- package/extensions/artifacts/index.ts +288 -0
- package/extensions/artifacts/store.ts +570 -0
- package/extensions/artifacts/viewer.ts +199 -0
- package/extensions/ask-user/description.ts +67 -0
- package/extensions/ask-user/dialogs.ts +80 -0
- package/extensions/ask-user/index.ts +37 -33
- package/extensions/ask-user/questions.ts +32 -7
- package/extensions/ask-user/widget.ts +45 -25
- package/extensions/auto-mode/actions.ts +6 -0
- package/extensions/auto-mode/cache.ts +88 -0
- package/extensions/auto-mode/classifier-prompt.ts +5 -4
- package/extensions/auto-mode/classifier.ts +290 -65
- package/extensions/auto-mode/config.ts +72 -20
- package/extensions/auto-mode/decision-log.ts +2 -0
- package/extensions/auto-mode/defaults.ts +1 -1
- package/extensions/auto-mode/denials.ts +124 -0
- package/extensions/auto-mode/git-checkout-programs.ts +187 -0
- package/extensions/auto-mode/git-status-meta.ts +159 -0
- package/extensions/auto-mode/history.ts +189 -0
- package/extensions/auto-mode/instructions.ts +58 -52
- package/extensions/auto-mode/model-select.ts +159 -134
- package/extensions/auto-mode/paths.ts +57 -5
- package/extensions/auto-mode/pause.ts +57 -2
- package/extensions/auto-mode/prompt.ts +37 -21
- package/extensions/auto-mode/read-only-options.ts +724 -0
- package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
- package/extensions/auto-mode/rules.ts +18 -1
- package/extensions/auto-mode/safety-floor.ts +361 -18
- package/extensions/auto-mode/setup-run.ts +20 -14
- package/extensions/auto-mode/shell-analysis.ts +764 -490
- package/extensions/auto-mode/shell-parse.ts +885 -0
- package/extensions/auto-mode/system-note.ts +33 -0
- package/extensions/auto-mode/transcript.ts +80 -51
- package/extensions/background/cron.ts +642 -0
- package/extensions/background/index.ts +614 -248
- package/extensions/background/loop-fire.ts +173 -0
- package/extensions/background/loop-preamble.ts +59 -0
- package/extensions/background/loop-skill.ts +163 -0
- package/extensions/background/monitor-batch.ts +53 -0
- package/extensions/background/noop-fold.ts +128 -0
- package/extensions/background/switch-guard.ts +56 -0
- package/extensions/background/wakeup.ts +178 -55
- package/extensions/bash/background.ts +53 -17
- package/extensions/bash/child.ts +7 -0
- package/extensions/bash/description.ts +188 -0
- package/extensions/bash/guards.ts +15 -67
- package/extensions/bash/index.ts +66 -35
- package/extensions/branding/compaction-keep.ts +63 -0
- package/extensions/branding/declined.ts +22 -0
- package/extensions/branding/index.ts +133 -9
- package/extensions/branding/prompt-editor.ts +53 -6
- package/extensions/branding/queued-edit.ts +31 -0
- package/extensions/branding/replaced-builtins.ts +68 -0
- package/extensions/branding/startup.ts +33 -71
- package/extensions/btw/index.ts +250 -78
- package/extensions/btw/panel.ts +228 -100
- package/extensions/btw/prompt.ts +41 -4
- package/extensions/cache-marks/index.ts +42 -0
- package/extensions/claude-compat/index.ts +7 -11
- package/extensions/claude-context/index.ts +436 -63
- package/extensions/compaction/cut.ts +157 -0
- package/extensions/compaction/index.ts +219 -75
- package/extensions/compaction/kept-thinking.ts +62 -0
- package/extensions/compaction/prompt.ts +15 -12
- package/extensions/context-budget/budget.ts +16 -2
- package/extensions/context-budget/index.ts +35 -15
- package/extensions/context-management/index.ts +36 -28
- package/extensions/doctor/build.ts +59 -8
- package/extensions/doctor/builtins.ts +84 -0
- package/extensions/doctor/cli.ts +7 -1
- package/extensions/doctor/compat.ts +88 -23
- package/extensions/doctor/dependencies.ts +41 -5
- package/extensions/doctor/fix-prompt.ts +9 -2
- package/extensions/doctor/index.ts +102 -58
- package/extensions/doctor/models.ts +102 -63
- package/extensions/doctor/presets.ts +42 -27
- package/extensions/doctor/report.ts +19 -2
- package/extensions/doctor/viewer.ts +49 -27
- package/extensions/effort/index.ts +141 -25
- package/extensions/effort/settings.ts +21 -0
- package/extensions/effort/slider.ts +24 -20
- package/extensions/exit/index.ts +32 -1
- package/extensions/file-tracker/index.ts +420 -42
- package/extensions/file-tracker/replay.ts +137 -24
- package/extensions/file-tracker/restore.ts +66 -0
- package/extensions/file-tracker/results.ts +66 -0
- package/extensions/file-tracker/shell-reads.ts +211 -0
- package/extensions/file-tracker/tracker.ts +45 -15
- package/extensions/footer/footer-line.ts +45 -24
- package/extensions/footer/index.ts +47 -2
- package/extensions/footer/pr.ts +34 -5
- package/extensions/hooks/compaction.ts +7 -0
- package/extensions/hooks/executor.ts +32 -30
- package/extensions/hooks/index.ts +209 -58
- package/extensions/hooks/matcher.ts +3 -0
- package/extensions/hooks/prompt-context.ts +59 -0
- package/extensions/hooks/protocol.ts +41 -3
- package/extensions/hooks/settings.ts +50 -19
- package/extensions/hooks/subagent-bridge.ts +2 -0
- package/extensions/hooks/trust.ts +26 -12
- package/extensions/idle-compact/index.ts +119 -0
- package/extensions/idle-compact/policy.ts +143 -0
- package/extensions/init/index.ts +11 -10
- package/extensions/init/prompt.ts +25 -32
- package/extensions/interrupted/index.ts +13 -1
- package/extensions/kept-thinking/index.ts +27 -0
- package/extensions/ledger/index.ts +46 -0
- package/extensions/ledger/wire.ts +154 -0
- package/extensions/lib/agent-cron.ts +146 -0
- package/extensions/lib/agent-loader.ts +18 -3
- package/extensions/lib/agent-view.ts +85 -0
- package/extensions/lib/anthropic-auth.ts +98 -0
- package/extensions/lib/anthropic-payload.ts +82 -1
- package/extensions/lib/anthropic-server-call.ts +230 -0
- package/extensions/lib/anthropic-server-tools.ts +485 -0
- package/extensions/lib/app-launch.mjs +94 -0
- package/extensions/lib/argument-hints.ts +190 -0
- package/extensions/lib/atomic-write.ts +6 -2
- package/extensions/lib/auto-memory.ts +75 -0
- package/extensions/lib/bash-parser.ts +84 -0
- package/extensions/lib/btw-fork.ts +93 -0
- package/extensions/lib/child-extensions.ts +52 -0
- package/extensions/lib/child-writes.ts +45 -0
- package/extensions/lib/claude-context.ts +515 -100
- package/extensions/lib/claude-external-includes.ts +68 -0
- package/extensions/lib/claude-id.ts +22 -0
- package/extensions/lib/claude-rules.ts +323 -0
- package/extensions/lib/claude-settings.ts +76 -4
- package/extensions/lib/compaction-boundary.ts +36 -0
- package/extensions/lib/compaction-keep.mjs +83 -0
- package/extensions/lib/config-mode.ts +112 -0
- package/extensions/lib/consent-dialogs.ts +63 -0
- package/extensions/lib/consent-preview.ts +5 -1
- package/extensions/lib/consent-stores.ts +48 -0
- package/extensions/lib/context-facts.ts +97 -0
- package/extensions/lib/covered-messages.ts +23 -0
- package/extensions/lib/deferred.ts +359 -25
- package/extensions/lib/dock-rows.ts +60 -0
- package/extensions/lib/empty-tool-result.ts +27 -0
- package/extensions/lib/environment-block.ts +76 -0
- package/extensions/lib/fetch-timeout.ts +24 -6
- package/extensions/{system-prompt → lib}/git-status.ts +51 -13
- package/extensions/lib/git.ts +75 -13
- package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
- package/extensions/lib/grammars/README.md +2 -0
- package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
- package/extensions/lib/guide-docs.ts +45 -0
- package/extensions/lib/headless-output.ts +46 -0
- package/extensions/lib/hook-gate.ts +7 -7
- package/extensions/lib/interrupt.ts +20 -4
- package/extensions/lib/key-input.ts +455 -0
- package/extensions/lib/keys.ts +14 -1
- package/extensions/lib/ledger/fold-memo.ts +49 -0
- package/extensions/lib/ledger/fold.ts +265 -0
- package/extensions/lib/ledger/layout.ts +72 -0
- package/extensions/lib/ledger/plan.ts +102 -0
- package/extensions/lib/ledger/records.ts +276 -0
- package/extensions/lib/ledger/state.ts +47 -0
- package/extensions/lib/ledger/validate.ts +141 -0
- package/extensions/lib/ledger/wire.ts +632 -0
- package/extensions/lib/local-command.ts +71 -19
- package/extensions/lib/mcp-overrides.ts +1 -1
- package/extensions/lib/mcp-share.ts +46 -0
- package/extensions/lib/memory-content.ts +146 -0
- package/extensions/lib/memory-index.ts +18 -0
- package/extensions/lib/memory.ts +134 -123
- package/extensions/lib/model-catalog/huggingface.json +178 -0
- package/extensions/lib/model-catalog/models-dev.json +18533 -0
- package/extensions/lib/model-catalog/openrouter.json +5050 -0
- package/extensions/lib/model-catalog-data.ts +513 -0
- package/extensions/lib/model-catalog.ts +462 -0
- package/extensions/lib/model-choice-warnings.ts +137 -0
- package/extensions/lib/model-pairings.ts +152 -0
- package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
- package/extensions/lib/model-policy.ts +112 -15
- package/extensions/lib/model-tier.ts +283 -355
- package/extensions/lib/model-unusable.ts +1 -1
- package/extensions/lib/newer-model.ts +100 -0
- package/extensions/lib/notices.ts +87 -0
- package/extensions/lib/notifications.ts +357 -73
- package/extensions/lib/one-code-settings.ts +74 -8
- package/extensions/lib/open-browser.ts +63 -0
- package/extensions/lib/openrouter-generation.ts +94 -0
- package/extensions/lib/operation-deadline.ts +30 -0
- package/extensions/lib/original-command.ts +4 -4
- package/extensions/lib/package-version.ts +1 -1
- package/extensions/lib/panel-focus.ts +134 -0
- package/extensions/lib/paths.ts +17 -2
- package/extensions/lib/permission-gate.ts +72 -6
- package/extensions/lib/persisted-output.ts +60 -8
- package/extensions/lib/pi-ai-estimate.ts +19 -3
- package/extensions/lib/pi-version.ts +38 -2
- package/extensions/lib/plan-mode-channels.ts +42 -5
- package/extensions/lib/plugins.ts +26 -17
- package/extensions/lib/powershell-parse-script.ts +208 -0
- package/extensions/lib/powershell-parser.ts +344 -0
- package/extensions/lib/previous-mark.ts +101 -0
- package/extensions/lib/process-state.ts +48 -0
- package/extensions/lib/process-tree.ts +132 -8
- package/extensions/lib/prompt-options.ts +82 -0
- package/extensions/lib/queued-delivery.ts +44 -0
- package/extensions/lib/reminders.ts +370 -358
- package/extensions/lib/replaced-builtins.mjs +199 -0
- package/extensions/lib/replay-call.ts +70 -0
- package/extensions/lib/request-replay.ts +547 -0
- package/extensions/lib/scratchpad.ts +81 -14
- package/extensions/lib/send-now-channel.ts +7 -0
- package/extensions/lib/session-model-tier.ts +51 -0
- package/extensions/lib/session-work.ts +64 -0
- package/extensions/lib/settings-channels.ts +4 -4
- package/extensions/lib/shell-exit-semantics.ts +170 -0
- package/extensions/lib/shell-quote.ts +11 -0
- package/extensions/lib/shell-result.ts +70 -0
- package/extensions/lib/shell-spawn.ts +70 -6
- package/extensions/lib/shell-tool.ts +75 -25
- package/extensions/lib/side-call-cache.ts +124 -0
- package/extensions/lib/side-call-run.ts +96 -0
- package/extensions/lib/side-call-usage.ts +38 -0
- package/extensions/lib/side-call.ts +24 -1
- package/extensions/lib/single-flight.ts +18 -0
- package/extensions/lib/skill-body.ts +42 -0
- package/extensions/lib/skill-scan.ts +63 -24
- package/extensions/lib/spill-file.ts +140 -0
- package/extensions/lib/subagent-handback.ts +112 -0
- package/extensions/lib/system-role.ts +156 -0
- package/extensions/lib/terminal-text.ts +114 -0
- package/extensions/lib/text-width.ts +47 -16
- package/extensions/lib/timer-ops.ts +20 -0
- package/extensions/lib/tool-additions.ts +129 -0
- package/extensions/lib/tool-call-corruption.ts +134 -0
- package/extensions/lib/tool-path.ts +66 -0
- package/extensions/lib/tool-variants.ts +69 -0
- package/extensions/lib/tui-render.ts +255 -27
- package/extensions/lib/turn-budget-layout.ts +159 -0
- package/extensions/lib/update-check.mjs +15 -2
- package/extensions/lib/usage-bus.ts +1 -1
- package/extensions/lib/user-denial.ts +38 -0
- package/extensions/lib/workspace-channel.ts +16 -0
- package/extensions/lib/worktree-channel.ts +31 -3
- package/extensions/lib/worktree-isolation.ts +40 -10
- package/extensions/lsp/client.ts +8 -9
- package/extensions/lsp/index.ts +76 -12
- package/extensions/lsp/keep-alive.ts +1 -1
- package/extensions/lsp/trust.ts +119 -0
- package/extensions/lsp/watcher.ts +1 -1
- package/extensions/mcp/announce.ts +90 -43
- package/extensions/mcp/client.ts +161 -48
- package/extensions/mcp/config.ts +132 -8
- package/extensions/mcp/discovery.ts +19 -0
- package/extensions/mcp/index.ts +220 -121
- package/extensions/mcp/oauth/browser.ts +38 -17
- package/extensions/mcp/oauth/flow.ts +25 -17
- package/extensions/mcp/oauth/provider.ts +14 -8
- package/extensions/mcp/oauth/store.ts +34 -16
- package/extensions/mcp/panel/keys.ts +12 -11
- package/extensions/mcp/panel/render.ts +9 -6
- package/extensions/mcp/schema.ts +104 -5
- package/extensions/mcp/trust.ts +164 -73
- package/extensions/memory/entries.ts +23 -14
- package/extensions/memory/index.ts +134 -28
- package/extensions/memory/open-external.ts +33 -18
- package/extensions/memory/panel.ts +135 -26
- package/extensions/model-default/index.ts +86 -19
- package/extensions/newer-model/index.ts +31 -0
- package/extensions/notebook/description.ts +18 -0
- package/extensions/notebook/index.ts +6 -5
- package/extensions/notebook/notebook.ts +1 -1
- package/extensions/permissions/index.ts +1426 -398
- package/extensions/permissions/matcher.ts +425 -74
- package/extensions/permissions/modes.ts +11 -4
- package/extensions/permissions/panel/host.ts +130 -0
- package/extensions/permissions/panel/keys.ts +48 -0
- package/extensions/permissions/panel/render.ts +450 -0
- package/extensions/permissions/panel/state.ts +550 -0
- package/extensions/permissions/powershell-cmdlets.ts +198 -0
- package/extensions/permissions/powershell-paths.ts +197 -0
- package/extensions/permissions/powershell-rules.ts +207 -280
- package/extensions/permissions/powershell-tree.ts +557 -0
- package/extensions/permissions/project-trust.ts +27 -11
- package/extensions/permissions/protected-paths.ts +14 -2
- package/extensions/permissions/session-grant.ts +6 -0
- package/extensions/permissions/settings.ts +166 -31
- package/extensions/permissions/subagent-gate.ts +25 -0
- package/extensions/permissions/workspace.ts +78 -0
- package/extensions/plan-mode/index.ts +276 -92
- package/extensions/plan-mode/reminder.ts +89 -21
- package/extensions/plan-mode/run-state.ts +26 -0
- package/extensions/plan-mode/texts.ts +177 -0
- package/extensions/plan-mode/viewer.ts +92 -27
- package/extensions/plugins/counts.ts +3 -3
- package/extensions/plugins/index.ts +64 -46
- package/extensions/plugins/install/install.ts +16 -0
- package/extensions/plugins/marketplace/sync.ts +9 -3
- package/extensions/plugins/marketplace/types.ts +3 -1
- package/extensions/plugins/panel/keys.ts +21 -24
- package/extensions/plugins/panel/render.ts +12 -6
- package/extensions/plugins/shell-expand.ts +107 -0
- package/extensions/plugins/template.ts +32 -15
- package/extensions/powershell/child.ts +7 -0
- package/extensions/powershell/description.ts +38 -6
- package/extensions/powershell/guards.ts +1 -1
- package/extensions/powershell/index.ts +14 -7
- package/extensions/recap/index.ts +73 -71
- package/extensions/recap/prompt.ts +2 -2
- package/extensions/recap/scheduler.ts +3 -5
- package/extensions/search-tools/grep-merge.ts +39 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/send-now/chord.ts +62 -0
- package/extensions/send-now/index.ts +133 -0
- package/extensions/session-title/index.ts +3 -0
- package/extensions/session-title/title.ts +4 -1
- package/extensions/skill/announce.ts +30 -0
- package/extensions/skill/code-review.ts +97 -0
- package/extensions/skill/index.ts +469 -132
- package/extensions/skill/invoke.ts +118 -75
- package/extensions/skill/listing.ts +133 -9
- package/extensions/skill/panel/keys.ts +15 -16
- package/extensions/skill/panel/render.ts +5 -2
- package/extensions/skill/panel/state.ts +12 -1
- package/extensions/subagents/agent-args.ts +39 -0
- package/extensions/subagents/agent-description.ts +132 -0
- package/extensions/subagents/agents.ts +146 -31
- package/extensions/subagents/default-model.ts +2 -3
- package/extensions/subagents/delegation-steer.ts +4 -4
- package/extensions/subagents/fork-cache.ts +82 -0
- package/extensions/subagents/guide-agent.ts +189 -0
- package/extensions/subagents/hand-back-review.ts +3 -0
- package/extensions/subagents/index.ts +1594 -852
- package/extensions/subagents/live-runs.ts +26 -6
- package/extensions/subagents/model-select.ts +38 -25
- package/extensions/subagents/outcome.ts +5 -2
- package/extensions/subagents/panel-keys.ts +73 -31
- package/extensions/subagents/panel-render.ts +120 -96
- package/extensions/subagents/panel-widget.ts +60 -89
- package/extensions/subagents/parent-mode.ts +50 -0
- package/extensions/subagents/pending-claim.ts +18 -1
- package/extensions/subagents/runner.ts +135 -80
- package/extensions/subagents/runs.ts +44 -5
- package/extensions/subagents/send-to-main-tool.ts +1 -1
- package/extensions/subagents/session-turns.ts +32 -8
- package/extensions/subagents/shell-panel.ts +36 -178
- package/extensions/subagents/tasks-dialog.ts +170 -0
- package/extensions/subagents/viewport-mirror.ts +95 -0
- package/extensions/subagents/worktree.ts +57 -13
- package/extensions/system-prompt/environment.ts +19 -19
- package/extensions/system-prompt/idle-turn.ts +65 -0
- package/extensions/system-prompt/index.ts +165 -47
- package/extensions/system-prompt/template.ts +34 -51
- package/extensions/system-prompt/tiers/common.ts +23 -64
- package/extensions/system-prompt/tiers/frontier.ts +32 -11
- package/extensions/system-prompt/tiers/low.ts +67 -45
- package/extensions/system-prompt/tiers/mid.ts +96 -38
- package/extensions/system-reminder/index.ts +204 -70
- package/extensions/tasks/descriptions.ts +190 -0
- package/extensions/tasks/index.ts +120 -41
- package/extensions/tasks/store.ts +83 -16
- package/extensions/tool-call-corruption/child.ts +7 -0
- package/extensions/tool-call-corruption/index.ts +237 -0
- package/extensions/tool-search/index.ts +366 -144
- package/extensions/tool-style/index.ts +68 -10
- package/extensions/tool-style/write-description.ts +40 -0
- package/extensions/tool-style/write-render.ts +237 -0
- package/extensions/turn-duration/index.ts +7 -3
- package/extensions/turn-duration/line.ts +47 -4
- package/extensions/web/backends.ts +6 -9
- package/extensions/web/description.ts +50 -0
- package/extensions/web/index.ts +118 -53
- package/extensions/web/native-call.ts +15 -0
- package/extensions/web/native-results.ts +41 -0
- package/extensions/web-fetch/description.ts +42 -0
- package/extensions/web-fetch/extract.ts +53 -6
- package/extensions/web-fetch/index.ts +125 -76
- package/extensions/web-fetch/page-content.ts +19 -0
- package/extensions/web-fetch/response.ts +49 -0
- package/extensions/web-fetch/summarize.ts +9 -10
- package/extensions/workflow/agent-session.ts +108 -18
- package/extensions/workflow/args.ts +24 -0
- package/extensions/workflow/description.ts +67 -0
- package/extensions/workflow/globals.ts +17 -3
- package/extensions/workflow/index.ts +157 -162
- package/extensions/workflow/records.ts +1 -0
- package/extensions/workflow/run-manager.ts +33 -6
- package/extensions/workflow/saved-workflows.ts +3 -2
- package/extensions/workflow/types.ts +22 -1
- package/extensions/workflow/viewer.ts +54 -24
- package/extensions/workflow/widget.ts +20 -0
- package/extensions/worktree/descriptions.ts +91 -0
- package/extensions/worktree/guards.ts +316 -54
- package/extensions/worktree/index.ts +162 -44
- package/extensions/worktree/powershell-guards.ts +167 -0
- package/extensions/worktree/rewrite.ts +14 -16
- package/package.json +21 -7
- package/skills/artifact-design/SKILL.md +85 -0
- package/skills/code-review/SKILL.md +20 -20
- package/skills/code-review/cells/arguments-and-flags.md +19 -0
- package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
- package/skills/code-review/cells/high-8-inline.md +122 -0
- package/skills/code-review/cells/low-1-pass-max4.md +27 -0
- package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
- package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
- package/skills/code-review/cells/medium-3x5-verify.md +132 -0
- package/skills/code-review/cells/minimal-prompt.md +32 -0
- package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
- package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
- package/skills/fewer-permission-prompts/SKILL.md +1 -1
- package/skills/loop/SKILL.md +11 -0
- package/skills/security-review/SKILL.md +2 -5
- package/skills/simplify/SKILL.md +7 -5
- package/skills/workflow-authoring/SKILL.md +143 -0
- package/themes/onecode-light.json +1 -1
- package/themes/onecode.json +1 -1
- package/types/app.d.ts +58 -0
- package/types/community-packages.d.ts +26 -1
- package/extensions/auto-mode/recoverability.ts +0 -229
- package/extensions/lib/capability-index.ts +0 -466
- package/extensions/lib/model-facts.generated.json +0 -2675
- package/extensions/lib/model-facts.ts +0 -162
- package/extensions/tool-search/announce.ts +0 -42
|
@@ -3,22 +3,26 @@
|
|
|
3
3
|
* command registration (pure fs). (The /plugins Installed tab builds its rows
|
|
4
4
|
* from discoverPlugins directly and no longer calls this.)
|
|
5
5
|
*
|
|
6
|
-
* Scans the SAME directories
|
|
7
|
-
*
|
|
8
|
-
* `<cwd>/.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* Scans the SAME directories pi loads, in pi's order — `<agentDir>/skills`
|
|
7
|
+
* (pi's own), then the ones claude-compat feeds it: `~/.claude/skills`,
|
|
8
|
+
* `~/.agents/skills`, `<cwd>/.claude/skills`, `<cwd>/.agents/skills`, and the
|
|
9
|
+
* bundled catalog last — and merges the plugin skills the caller resolved.
|
|
10
|
+
* As in pi, a name loads once (the first wins, so the bundled catalog loses a
|
|
11
|
+
* collision) and a file reached twice through a link counts once, so the
|
|
12
|
+
* banner, `/skills` and `/doctor` count the skills pi actually loads. Rows carry the SKILL.md path (for the ~token
|
|
11
13
|
* size estimate) and the scope used by skill-overrides keys. Before the first
|
|
12
14
|
* turn this scan is the only skill list One Code has (pi resolves skills per
|
|
13
15
|
* turn), so a root missing here is a skill with no `/name` command at startup.
|
|
14
16
|
*/
|
|
15
17
|
|
|
16
|
-
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
18
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
17
19
|
import { dirname, join } from "node:path";
|
|
18
20
|
import { fileURLToPath } from "node:url";
|
|
19
21
|
import type { PluginSkill } from "./plugins.ts";
|
|
20
22
|
import type { SkillScope } from "./skill-overrides.ts";
|
|
21
|
-
import {
|
|
23
|
+
import { claudeSourcesOn, projectConfigDir, userConfigDir } from "./config-mode.ts";
|
|
24
|
+
import { claudeUserDir, isPathAtOrUnder, tryRealpath } from "./paths.ts";
|
|
25
|
+
import { parseFrontmatterLoosely } from "./frontmatter.ts";
|
|
22
26
|
|
|
23
27
|
/** The skill catalog shipped in this package: `<package>/skills` (Claude Code's self-contained built-in skills). */
|
|
24
28
|
export const BUNDLED_SKILLS_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "skills");
|
|
@@ -31,16 +35,38 @@ export interface ScannedSkill {
|
|
|
31
35
|
|
|
32
36
|
/**
|
|
33
37
|
* The one scope-classification rule for a skill path — user skills live under
|
|
34
|
-
* `~/.claude/skills`
|
|
38
|
+
* `~/.claude/skills`, `~/.agents/skills` and `<agentDir>/skills`; everything else is project
|
|
35
39
|
* scope. Both the scanner and the skill extension classify through this, so a
|
|
36
40
|
* skill never shows different scopes in /plugins vs the skill tool.
|
|
37
41
|
*/
|
|
38
42
|
export function scopeForPath(path: string, home: string, agentDir: string): SkillScope {
|
|
39
|
-
|
|
40
|
-
return "project";
|
|
43
|
+
const roots = [join(claudeUserDir(home), "skills"), join(home, ".agents", "skills"), join(agentDir, "skills")];
|
|
44
|
+
return roots.some((root) => isPathAtOrUnder(path, root)) ? "user" : "project";
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
/**
|
|
48
|
+
* The skill folders One Code adds to pi's own (`<agentDir>/skills`, which pi
|
|
49
|
+
* scans itself), in load order: Claude Code's two only in Claude-compatible
|
|
50
|
+
* mode (lib/config-mode.ts), the cross-tool `.agents` pair in both. `claudeDir`
|
|
51
|
+
* defaults to `~/.claude` (`CLAUDE_CONFIG_DIR`); `~/.agents` is never moved.
|
|
52
|
+
*/
|
|
53
|
+
export function skillSourceDirs(cwd: string, home: string, claudeDir: string = claudeUserDir(home)): Array<{ dir: string; scope: SkillScope }> {
|
|
54
|
+
const claude = claudeSourcesOn();
|
|
55
|
+
const dirs: Array<{ dir: string; scope: SkillScope } | false> = [
|
|
56
|
+
claude && { dir: join(claudeDir, "skills"), scope: "user" },
|
|
57
|
+
{ dir: join(home, ".agents", "skills"), scope: "user" },
|
|
58
|
+
claude && { dir: join(cwd, ".claude", "skills"), scope: "project" },
|
|
59
|
+
{ dir: join(cwd, ".agents", "skills"), scope: "project" },
|
|
60
|
+
];
|
|
61
|
+
return dirs.filter((d): d is { dir: string; scope: SkillScope } => d !== false);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** The prompt-template folders: `~/.claude/commands` and `.claude/commands`, or their `.onecode` twins in independent mode. */
|
|
65
|
+
export function commandDirs(cwd: string, home: string, claudeDir: string = claudeUserDir(home)): string[] {
|
|
66
|
+
return [join(claudeSourcesOn() ? claudeDir : userConfigDir(home), "commands"), join(projectConfigDir(cwd), "commands")];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function scanDir(dir: string, scope: SkillScope, into: Map<string, ScannedSkill>, seenFiles: Set<string>): void {
|
|
44
70
|
if (!existsSync(dir)) return;
|
|
45
71
|
let entries: string[];
|
|
46
72
|
try {
|
|
@@ -51,9 +77,18 @@ function scanDir(dir: string, scope: SkillScope, into: Map<string, ScannedSkill>
|
|
|
51
77
|
for (const entry of entries) {
|
|
52
78
|
const skillFile = join(dir, entry, "SKILL.md");
|
|
53
79
|
// existsSync follows symlinked skill directories, which isDirectory() would miss.
|
|
54
|
-
if (existsSync(skillFile)
|
|
55
|
-
|
|
80
|
+
if (!existsSync(skillFile)) continue;
|
|
81
|
+
let name = entry;
|
|
82
|
+
try {
|
|
83
|
+
const declared = parseFrontmatterLoosely(readFileSync(skillFile, "utf-8")).frontmatter.name;
|
|
84
|
+
if (typeof declared === "string" && declared) name = declared;
|
|
85
|
+
} catch {
|
|
86
|
+
continue;
|
|
56
87
|
}
|
|
88
|
+
const real = tryRealpath(skillFile) ?? skillFile;
|
|
89
|
+
if (into.has(name) || seenFiles.has(real)) continue;
|
|
90
|
+
seenFiles.add(real);
|
|
91
|
+
into.set(name, { name, path: skillFile, scope });
|
|
57
92
|
}
|
|
58
93
|
}
|
|
59
94
|
|
|
@@ -65,12 +100,10 @@ export function scanSkills(
|
|
|
65
100
|
bundledSkillsDir?: string,
|
|
66
101
|
): ScannedSkill[] {
|
|
67
102
|
const skills = new Map<string, ScannedSkill>();
|
|
68
|
-
|
|
69
|
-
scanDir(join(
|
|
70
|
-
|
|
71
|
-
scanDir(
|
|
72
|
-
scanDir(join(agentDir, "skills"), "user", skills);
|
|
73
|
-
if (bundledSkillsDir) scanDir(bundledSkillsDir, scopeForPath(bundledSkillsDir, home, agentDir), skills);
|
|
103
|
+
const seenFiles = new Set<string>();
|
|
104
|
+
scanDir(join(agentDir, "skills"), "user", skills, seenFiles);
|
|
105
|
+
for (const { dir, scope } of skillSourceDirs(cwd, home)) scanDir(dir, scope, skills, seenFiles);
|
|
106
|
+
if (bundledSkillsDir) scanDir(bundledSkillsDir, scopeForPath(bundledSkillsDir, home, agentDir), skills, seenFiles);
|
|
74
107
|
for (const skill of pluginSkills) {
|
|
75
108
|
skills.set(`plugin:${skill.name}`, { name: skill.name, path: skill.path, scope: "plugin" });
|
|
76
109
|
}
|
|
@@ -80,14 +113,20 @@ export function scanSkills(
|
|
|
80
113
|
/**
|
|
81
114
|
* Names of the `.claude/commands` prompt templates (`<name>.md`) pi will expose
|
|
82
115
|
* as `/<name>` — the same dirs claude-compat feeds pi (`~/.claude/commands`,
|
|
83
|
-
* `<cwd>/.claude/commands
|
|
116
|
+
* `<cwd>/.claude/commands`, or their `.onecode` twins in independent mode)
|
|
117
|
+
* plus pi's own `<agentDir>/prompts`. pi resolves
|
|
84
118
|
* templates per turn, after `session_start`, so a bare skill command registered
|
|
85
119
|
* then would silently shadow a same-named template for the whole session;
|
|
86
120
|
* this pre-scan lets the registration skip those names.
|
|
87
121
|
*/
|
|
88
122
|
export function promptTemplateNames(cwd: string, home: string, agentDir: string): string[] {
|
|
89
|
-
|
|
90
|
-
|
|
123
|
+
return [...new Set(promptTemplateFiles(cwd, home, agentDir).map((file) => file.name))];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Every prompt-template file pi will resolve, with the command name each gives. */
|
|
127
|
+
export function promptTemplateFiles(cwd: string, home: string, agentDir: string): { name: string; path: string }[] {
|
|
128
|
+
const files: { name: string; path: string }[] = [];
|
|
129
|
+
for (const dir of [...commandDirs(cwd, home), join(agentDir, "prompts")]) {
|
|
91
130
|
if (!existsSync(dir)) continue;
|
|
92
131
|
let entries: string[];
|
|
93
132
|
try {
|
|
@@ -95,9 +134,9 @@ export function promptTemplateNames(cwd: string, home: string, agentDir: string)
|
|
|
95
134
|
} catch {
|
|
96
135
|
continue;
|
|
97
136
|
}
|
|
98
|
-
for (const entry of entries) if (entry.endsWith(".md"))
|
|
137
|
+
for (const entry of entries) if (entry.endsWith(".md")) files.push({ name: entry.slice(0, -3), path: join(dir, entry) });
|
|
99
138
|
}
|
|
100
|
-
return
|
|
139
|
+
return files;
|
|
101
140
|
}
|
|
102
141
|
|
|
103
142
|
/** Rough token estimate from the SKILL.md byte size (~4 bytes/token). */
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep pi's foreground shell spill file readable.
|
|
3
|
+
*
|
|
4
|
+
* When a foreground `bash` or `powershell` run prints more than pi keeps, pi
|
|
5
|
+
* writes the whole output to `<os.tmpdir()>/pi-<shell>-<16 hex>.log` and ends
|
|
6
|
+
* the text with `[… Full output: <path>]`, on success and on a failure: a
|
|
7
|
+
* non-zero exit is an error result from pi 0.99 on and a thrown error before
|
|
8
|
+
* it, and a timeout or an abort is always thrown. `os.tmpdir()` is outside
|
|
9
|
+
* every root the permission gate treats as readable, so the "full output is
|
|
10
|
+
* one read away" promise held only where outside reads skip the gate. So the
|
|
11
|
+
* file is moved into the session's persisted-results dir (a readable root)
|
|
12
|
+
* and the path in the text is rewritten to match.
|
|
13
|
+
*
|
|
14
|
+
* Only a file pi produced is moved: the path must sit directly in the temp
|
|
15
|
+
* dir and carry pi's name shape, so a command that prints a look-alike line
|
|
16
|
+
* cannot make the harness move some other file.
|
|
17
|
+
*
|
|
18
|
+
* The output is also cut Claude Code's way instead of pi's. pi
|
|
19
|
+
* keeps the last 2,000 lines or 50 KB and drops the head, so a batched
|
|
20
|
+
* command lost its first commands' output. Claude Code keeps the head: the
|
|
21
|
+
* whole output up to its inline cap, past it the persisted block naming the
|
|
22
|
+
* file, with the first 2 KB as the preview. pi's spill file holds the whole
|
|
23
|
+
* output, so the block names it and nothing is copied.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { copyFileSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync } from "node:fs";
|
|
27
|
+
import { tmpdir } from "node:os";
|
|
28
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
29
|
+
import { persistedFilePreview, persistIfLarge } from "./persisted-output.ts";
|
|
30
|
+
|
|
31
|
+
const MARKER = "Full output: ";
|
|
32
|
+
const PI_SPILL_NAME = /^pi-[a-z]+-[0-9a-f]{16}\.log$/;
|
|
33
|
+
/** The note pi ends a cut output with: `\n\n[Showing lines …` or `\n\n[Showing last …`. */
|
|
34
|
+
const PI_NOTE = "\n\n[Showing ";
|
|
35
|
+
/** pi's status after the output of a run that did not exit 0. */
|
|
36
|
+
const PI_STATUS = /(?:^|\n\n)Command (?:exited with code -?\d+|timed out after \S+ seconds|aborted|terminated without an exit code)$/;
|
|
37
|
+
|
|
38
|
+
/** How a shell's output is cut: whole up to `inlineMax` bytes, past it persisted as `<id>.txt` when pi did not spill it. */
|
|
39
|
+
export interface OutputBound {
|
|
40
|
+
inlineMax: number;
|
|
41
|
+
id: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** The spill path pi named at the end of `text`, when it is one pi produced in `tmp`. Pure. */
|
|
45
|
+
export function piSpillPath(text: string, tmp: string = tmpdir()): string | undefined {
|
|
46
|
+
const at = text.lastIndexOf(MARKER);
|
|
47
|
+
if (at === -1) return undefined;
|
|
48
|
+
const close = text.indexOf("]", at + MARKER.length);
|
|
49
|
+
if (close === -1) return undefined;
|
|
50
|
+
const path = text.slice(at + MARKER.length, close);
|
|
51
|
+
if (!PI_SPILL_NAME.test(basename(path))) return undefined;
|
|
52
|
+
if (resolve(dirname(path)) !== resolve(tmp)) return undefined;
|
|
53
|
+
return path;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Move `from` into `dir`, keeping its name; the new path, or undefined when it could not move. */
|
|
57
|
+
function moveInto(from: string, dir: string): string | undefined {
|
|
58
|
+
const to = join(dir, basename(from));
|
|
59
|
+
try {
|
|
60
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
61
|
+
try {
|
|
62
|
+
renameSync(from, to);
|
|
63
|
+
} catch {
|
|
64
|
+
// A different filesystem: copy, then drop the original.
|
|
65
|
+
copyFileSync(from, to);
|
|
66
|
+
unlinkSync(from);
|
|
67
|
+
}
|
|
68
|
+
return to;
|
|
69
|
+
} catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* `text` with its spill file moved and pi's tail cut replaced by Claude
|
|
76
|
+
* Code's head-first one. A spill file that cannot be moved keeps pi's text;
|
|
77
|
+
* one that cannot be read, or a text not in pi's shape, keeps pi's text with
|
|
78
|
+
* only the path rewritten.
|
|
79
|
+
*/
|
|
80
|
+
export function boundSpillIn(text: string, resultsDir: string, bound: OutputBound, failed = true, tmp: string = tmpdir()): string {
|
|
81
|
+
// pi appends a status only to a run that did not exit 0; a successful
|
|
82
|
+
// command's own last line can look like one.
|
|
83
|
+
const status = failed ? PI_STATUS.exec(text) : null;
|
|
84
|
+
const end = status ? status.index : text.length;
|
|
85
|
+
const output = text.slice(0, end);
|
|
86
|
+
const from = piSpillPath(output, tmp);
|
|
87
|
+
if (!from) return persistIfLarge(output, { dir: resultsDir, id: bound.id, maxBytes: bound.inlineMax }) + text.slice(end);
|
|
88
|
+
const to = moveInto(from, resultsDir);
|
|
89
|
+
if (!to) return text;
|
|
90
|
+
// pi's note ends the output; anything else keeps pi's text.
|
|
91
|
+
const whole = output.includes(PI_NOTE) && output.endsWith(`${from}]`) ? wholeOutput(to, bound.inlineMax) : undefined;
|
|
92
|
+
return whole === undefined ? text.split(from).join(to) : whole + text.slice(end);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** The spill file inline while it fits `inlineMax`, past it the persisted block; undefined when unreadable. */
|
|
96
|
+
function wholeOutput(path: string, inlineMax: number): string | undefined {
|
|
97
|
+
try {
|
|
98
|
+
return statSync(path).size <= inlineMax ? readFileSync(path, "utf8") : persistedFilePreview(path);
|
|
99
|
+
} catch {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
type TextResult = { content: Array<{ type: string; text?: string }>; details?: unknown };
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Run a foreground shell execute and keep its spill file readable: on a
|
|
108
|
+
* returned result the text and `details.fullOutputPath` are rewritten, and a
|
|
109
|
+
* thrown error is rethrown with its message rewritten, the output cut
|
|
110
|
+
* Claude Code's way (`bound`).
|
|
111
|
+
*/
|
|
112
|
+
export async function keepSpillReadable<R extends TextResult>(run: () => Promise<R>, resultsDir: string, bound: OutputBound): Promise<R> {
|
|
113
|
+
const rewrite = (text: string, failed: boolean) => boundSpillIn(text, resultsDir, bound, failed);
|
|
114
|
+
let result: R;
|
|
115
|
+
try {
|
|
116
|
+
result = await run();
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (error instanceof Error) {
|
|
119
|
+
const message = rewrite(error.message, true);
|
|
120
|
+
if (message !== error.message) {
|
|
121
|
+
const moved = new Error(message);
|
|
122
|
+
moved.stack = error.stack;
|
|
123
|
+
throw moved;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
const details = result.details as { fullOutputPath?: unknown } | undefined;
|
|
129
|
+
const text = result.content.find((block) => block.type === "text" && typeof block.text === "string");
|
|
130
|
+
if (!text?.text) return result;
|
|
131
|
+
const rewritten = rewrite(text.text, (result as { isError?: boolean }).isError === true);
|
|
132
|
+
if (rewritten === text.text) return result;
|
|
133
|
+
const from = piSpillPath(text.text);
|
|
134
|
+
const to = from ? join(resultsDir, basename(from)) : undefined;
|
|
135
|
+
return {
|
|
136
|
+
...result,
|
|
137
|
+
content: result.content.map((block) => (block === text ? { ...block, text: rewritten } : block)),
|
|
138
|
+
details: details && typeof details === "object" && to ? { ...details, fullOutputPath: to } : result.details,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code's `SubagentHandback`: the tool every child session (an Agent
|
|
3
|
+
* run, a fork, a workflow agent) returns its result through, and the closing
|
|
4
|
+
* reminder that tells it so (decisions/subagents-workflows.md, "Every child
|
|
5
|
+
* hands back through SubagentHandback"). Claude Code gives it to Opus and
|
|
6
|
+
* Sonnet children only; One Code gives it to every child on every model.
|
|
7
|
+
*
|
|
8
|
+
* The tool records the report in the run's `HandbackSlot` and ends the run
|
|
9
|
+
* (`terminate`, so no further request goes out after a batch of hand-backs).
|
|
10
|
+
* The runner then reports the recorded text as the child's result, and falls
|
|
11
|
+
* back to the child's final message when the tool was never called.
|
|
12
|
+
*
|
|
13
|
+
* The tool keeps Claude Code's exact name, like `Agent` and `SendMessage`,
|
|
14
|
+
* because the harness frames that carry its report already name it ("its
|
|
15
|
+
* SubagentHandback call", lib/notifications.ts). It is harness plumbing: the
|
|
16
|
+
* child permission and hook gates never gate it, and an agent file's `tools`
|
|
17
|
+
* allowlist keeps it.
|
|
18
|
+
*
|
|
19
|
+
* In a fork the tool is deferred: a fork sends the parent's tool list so it
|
|
20
|
+
* reads the parent's prompt cache (subagents/fork-cache.ts), and an eager tool
|
|
21
|
+
* of its own would be dropped from that list. A deferred one reaches the
|
|
22
|
+
* request through a `tool_search` load, as the fork's `SendMessage` does.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
26
|
+
import { Type } from "typebox";
|
|
27
|
+
import { DEFER_CHANNEL } from "./deferred.ts";
|
|
28
|
+
import { REMINDER_CHANNEL, type ReminderPayload } from "./reminders.ts";
|
|
29
|
+
|
|
30
|
+
export const SUBAGENT_HANDBACK = "SubagentHandback";
|
|
31
|
+
|
|
32
|
+
/** Claude Code's description of the tool. */
|
|
33
|
+
export const SUBAGENT_HANDBACK_DESCRIPTION =
|
|
34
|
+
"Deliver your final report to the agent that spawned you (your caller). Use it once, for that hand-off only: when your work is complete, call SubagentHandback({message: <your full report>}). The call ends your run, so do everything else first and put everything your caller needs in that one report. It is not a messaging channel: do not use it for progress updates or questions.\n\nOnly a report delivered through SubagentHandback reaches your caller; plain text you write at the end of your run is NOT delivered. There is no recipient parameter: the report can only go to your caller.";
|
|
35
|
+
|
|
36
|
+
/** Claude Code's closing reminder on the child's task message. */
|
|
37
|
+
export const SUBAGENT_HANDBACK_REMINDER =
|
|
38
|
+
"Your final report is delivered through SubagentHandback: when your work is complete, call SubagentHandback({message: <your full report>}). The call ends your run, so make it your last step. Only a SubagentHandback call reaches your caller as your result; plain text you write at the end is not delivered.";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* One Code's line before the reminder in a fork, where the tool is deferred
|
|
42
|
+
* (the fork sends its parent's tool list): it names the load step, which the
|
|
43
|
+
* frozen deferred listing it inherits does not.
|
|
44
|
+
*/
|
|
45
|
+
export const SUBAGENT_HANDBACK_LOAD_FIRST =
|
|
46
|
+
'SubagentHandback is deferred here: load it first with tool_search (query "select:SubagentHandback").';
|
|
47
|
+
|
|
48
|
+
/** Claude Code's result texts for the call. */
|
|
49
|
+
export const HANDBACK_DELIVERED = "Report delivered to your caller.";
|
|
50
|
+
export const HANDBACK_ALREADY_DELIVERED =
|
|
51
|
+
"Nothing was sent: your report was already delivered (SubagentHandback delivers one report). Use SendMessage for anything further, then stop.";
|
|
52
|
+
export const HANDBACK_EMPTY = "message must not be empty: pass your full report as `message`.";
|
|
53
|
+
|
|
54
|
+
/** Where a run keeps the report its child handed back. */
|
|
55
|
+
export interface HandbackSlot {
|
|
56
|
+
/** Record the report; false when this turn already delivered one. */
|
|
57
|
+
recordHandback(message: string): boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** A slot for a one-turn run (a workflow agent): the first report wins. */
|
|
61
|
+
export function oneTurnHandbackSlot(): HandbackSlot & { report?: string } {
|
|
62
|
+
const slot: HandbackSlot & { report?: string } = {
|
|
63
|
+
report: undefined,
|
|
64
|
+
recordHandback(message) {
|
|
65
|
+
if (slot.report !== undefined) return false;
|
|
66
|
+
slot.report = message;
|
|
67
|
+
return true;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
return slot;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function subagentHandbackTool(slot: HandbackSlot): ToolDefinition {
|
|
74
|
+
return {
|
|
75
|
+
name: SUBAGENT_HANDBACK,
|
|
76
|
+
label: "Hand back",
|
|
77
|
+
description: SUBAGENT_HANDBACK_DESCRIPTION,
|
|
78
|
+
parameters: Type.Object({ message: Type.String({ description: "Your full report for your caller" }) }, { additionalProperties: false }) as never,
|
|
79
|
+
async execute(_toolCallId: string, params: unknown) {
|
|
80
|
+
const message = (params as { message?: unknown } | undefined)?.message;
|
|
81
|
+
// Fail loud (decisions/tools.md): an empty report would end the run with nothing for the caller.
|
|
82
|
+
if (typeof message !== "string" || !message.trim()) {
|
|
83
|
+
return { content: [{ type: "text" as const, text: HANDBACK_EMPTY }], details: {}, isError: true };
|
|
84
|
+
}
|
|
85
|
+
if (!slot.recordHandback(message)) {
|
|
86
|
+
return { content: [{ type: "text" as const, text: HANDBACK_ALREADY_DELIVERED }], details: {}, isError: true };
|
|
87
|
+
}
|
|
88
|
+
return { content: [{ type: "text" as const, text: HANDBACK_DELIVERED }], details: { handback: true }, terminate: true };
|
|
89
|
+
},
|
|
90
|
+
} as ToolDefinition;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The inline extension a child session loads for the hand-back: the tool,
|
|
95
|
+
* deferred in a fork, and the closing reminder. The reminder is a standing
|
|
96
|
+
* block (`sticky-append`) switched on at session start, so it rides the
|
|
97
|
+
* child's task message and every later message of the run (a resident's
|
|
98
|
+
* next turns hand back too), and never an inherited message of a fork.
|
|
99
|
+
*/
|
|
100
|
+
export function subagentHandbackExtension(slot: HandbackSlot, options: { deferred?: boolean } = {}) {
|
|
101
|
+
return (pi: ExtensionAPI) => {
|
|
102
|
+
pi.registerTool(subagentHandbackTool(slot));
|
|
103
|
+
if (options.deferred) pi.events.emit(DEFER_CHANNEL, { name: SUBAGENT_HANDBACK, keywords: ["handback", "report", "result", "final", "caller"] });
|
|
104
|
+
pi.on("session_start", () => {
|
|
105
|
+
pi.events.emit(REMINDER_CHANNEL, {
|
|
106
|
+
text: options.deferred ? `${SUBAGENT_HANDBACK_LOAD_FIRST}\n${SUBAGENT_HANDBACK_REMINDER}` : SUBAGENT_HANDBACK_REMINDER,
|
|
107
|
+
key: "subagent-handback",
|
|
108
|
+
placement: "sticky-append",
|
|
109
|
+
} satisfies ReminderPayload);
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which models get Claude Code's mid-conversation `role: "system"` message,
|
|
3
|
+
* and in which wire shape (pure). One function for the system-prompt builder,
|
|
4
|
+
* the reminder hook and the tool hook, so all three agree on every request.
|
|
5
|
+
*
|
|
6
|
+
* The gate is pi's model catalog flag `compat.supportsMidConvoSystemMessages`
|
|
7
|
+
* alone, no list of our own: pi owns the per-model facts and adds models as it
|
|
8
|
+
* confirms them (`working-docs/decisions/tools.md`, "Claude Code's request
|
|
9
|
+
* shape, on every model pi flags for it"). Haiku before 5 is never in it:
|
|
10
|
+
* Anthropic rejects the role there (findings §52); Haiku 5.5 takes it
|
|
11
|
+
* (findings §67). Every unflagged model keeps the context in the first user
|
|
12
|
+
* message.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { parseClaudeId } from "./claude-id.ts";
|
|
16
|
+
|
|
17
|
+
/** The wire shape a system message takes: Anthropic `system`, OpenAI Responses `input`, or Chat Completions `messages`. */
|
|
18
|
+
export type SystemRoleLayout = "anthropic" | "responses" | "completions";
|
|
19
|
+
|
|
20
|
+
/** The fields of a pi `Model` the gate reads. */
|
|
21
|
+
export interface SystemRoleModel {
|
|
22
|
+
id: string;
|
|
23
|
+
api: string;
|
|
24
|
+
compat?: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** pi-ai's Responses APIs: requests carry `input` items and flat `{ type, name }` tools. */
|
|
28
|
+
export const RESPONSES_APIS: ReadonlySet<string> = new Set(["openai-responses", "azure-openai-responses", "openai-codex-responses"]);
|
|
29
|
+
|
|
30
|
+
/** The wire shape of a pi API, for the three shapes One Code rewrites; undefined for the rest (Google, Bedrock). */
|
|
31
|
+
export function wireShape(api: string): SystemRoleLayout | undefined {
|
|
32
|
+
if (api === "anthropic-messages") return "anthropic";
|
|
33
|
+
if (RESPONSES_APIS.has(api)) return "responses";
|
|
34
|
+
if (api === "openai-completions") return "completions";
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The payload field that holds the conversation for a wire shape. */
|
|
39
|
+
export function messagesKey(shape: SystemRoleLayout): "input" | "messages" {
|
|
40
|
+
return shape === "responses" ? "input" : "messages";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function systemRoleLayout(model: SystemRoleModel | undefined): SystemRoleLayout | undefined {
|
|
44
|
+
if (!model) return undefined;
|
|
45
|
+
const compat = model.compat as { supportsMidConvoSystemMessages?: unknown } | undefined;
|
|
46
|
+
if (compat?.supportsMidConvoSystemMessages !== true) return undefined;
|
|
47
|
+
if (model.api === "anthropic-messages" && isHaikuBeforeFive(model.id)) return undefined;
|
|
48
|
+
return wireShape(model.api);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A Haiku older than Haiku 5 in any provider's spelling; the old `claude-3-5-haiku` order parses as none. */
|
|
52
|
+
function isHaikuBeforeFive(id: string): boolean {
|
|
53
|
+
const claude = parseClaudeId(id);
|
|
54
|
+
return claude ? claude.family === "haiku" && claude.major < 5 : id.includes("haiku");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** One context block that moves: its text as injected on the user message, and its text inside the system message. */
|
|
58
|
+
export interface MovedBlock {
|
|
59
|
+
/** The block exactly as the user message carries it (`<system-reminder>` frame and suffix included). */
|
|
60
|
+
framed: string;
|
|
61
|
+
/** The unwrapped text, as Claude Code's system message joins it. */
|
|
62
|
+
inner: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The role a system message takes on the OpenAI APIs: pi's own rule per API
|
|
67
|
+
* (`developer` for a reasoning model that accepts it, else `system`).
|
|
68
|
+
*/
|
|
69
|
+
export function instructionRole(layout: SystemRoleLayout, model: { reasoning?: boolean; compat?: unknown }): "system" | "developer" {
|
|
70
|
+
if (layout === "anthropic") return "system";
|
|
71
|
+
const developer = (model.compat as { supportsDeveloperRole?: boolean } | undefined)?.supportsDeveloperRole;
|
|
72
|
+
const accepts = layout === "responses" ? developer !== false : developer === true;
|
|
73
|
+
return model.reasoning && accepts ? "developer" : "system";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A wire message and content part, as far as the payload rewrites read them. */
|
|
77
|
+
export type WireMessage = { role?: unknown; type?: unknown; content?: unknown; output?: unknown; call_id?: unknown; tool_call_id?: unknown };
|
|
78
|
+
export type WirePart = { type?: unknown; text?: unknown; content?: unknown; tool_use_id?: unknown; cache_control?: unknown };
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The parts with those `drop` accepts removed, keeping pi's cache mark: when a
|
|
82
|
+
* removed part carried it, it moves to the new last part. Undefined when every
|
|
83
|
+
* part would go (a message cannot be left empty) or nothing is removed.
|
|
84
|
+
*/
|
|
85
|
+
export function withoutParts(parts: readonly WirePart[], drop: (part: WirePart, index: number) => boolean): WirePart[] | undefined {
|
|
86
|
+
let mark: unknown;
|
|
87
|
+
const kept = parts.filter((part, index) => {
|
|
88
|
+
if (!drop(part, index)) return true;
|
|
89
|
+
if (part?.cache_control !== undefined) mark = part.cache_control;
|
|
90
|
+
return false;
|
|
91
|
+
});
|
|
92
|
+
if (kept.length === parts.length || kept.length === 0) return undefined;
|
|
93
|
+
if (mark === undefined || kept[kept.length - 1].cache_control !== undefined) return kept;
|
|
94
|
+
return [...kept.slice(0, -1), { ...kept[kept.length - 1], cache_control: mark }];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The value stored under a pi call id for the id a request carries: the same
|
|
99
|
+
* id, the Responses call id before pi's `|item` suffix, or the id pi-ai's
|
|
100
|
+
* Anthropic conversion rewrote (any other character to `_`, at most 64), as it
|
|
101
|
+
* does for a call made on another provider before a model switch.
|
|
102
|
+
*/
|
|
103
|
+
export function byWireCallId<V>(stored: ReadonlyMap<string, V>, wireId: string): V | undefined {
|
|
104
|
+
if (stored.has(wireId)) return stored.get(wireId);
|
|
105
|
+
for (const [id, value] of stored) {
|
|
106
|
+
if (id.split("|")[0] === wireId || id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64) === wireId) return value;
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** A system message with `text` in the API's own shape. */
|
|
112
|
+
export function systemMessage(layout: SystemRoleLayout, role: "system" | "developer", text: string): WireMessage {
|
|
113
|
+
return layout === "anthropic" ? { role: "system", content: [{ type: "text", text }] } : { role, content: text };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The payload with the moved context blocks lifted off the user message that
|
|
118
|
+
* carries them and joined, unwrapped and a blank line apart, into one system
|
|
119
|
+
* message placed right after it, in the API's own shape (Claude Code's layout,
|
|
120
|
+
* `decisions/tools.md`). Blocks are found by their exact text, never by
|
|
121
|
+
* position: a fork's request drops the parent's first message, and then nothing
|
|
122
|
+
* moves. The cache mark is reseated afterwards (anthropic-payload.ts
|
|
123
|
+
* reseatMessageMark). Undefined when nothing changes.
|
|
124
|
+
*/
|
|
125
|
+
export function withSystemRoleContext(
|
|
126
|
+
payload: Record<string, unknown>,
|
|
127
|
+
layout: SystemRoleLayout,
|
|
128
|
+
moved: readonly MovedBlock[],
|
|
129
|
+
role: "system" | "developer",
|
|
130
|
+
): Record<string, unknown> | undefined {
|
|
131
|
+
if (moved.length === 0) return undefined;
|
|
132
|
+
const key = messagesKey(layout);
|
|
133
|
+
const messages = payload[key];
|
|
134
|
+
if (!Array.isArray(messages)) return undefined;
|
|
135
|
+
const wanted = new Set(moved.map((block) => block.framed));
|
|
136
|
+
const index = messages.findIndex(
|
|
137
|
+
(message: WireMessage) => message?.role === "user" && Array.isArray(message.content) && (message.content as WirePart[]).some((part) => wanted.has(part?.text as string)),
|
|
138
|
+
);
|
|
139
|
+
if (index === -1) return undefined;
|
|
140
|
+
|
|
141
|
+
const carrier = messages[index] as WireMessage & { content: WirePart[] };
|
|
142
|
+
const found = new Set<string>();
|
|
143
|
+
const kept = carrier.content.filter((part) => {
|
|
144
|
+
const text = part?.text;
|
|
145
|
+
if (typeof text !== "string" || !wanted.has(text) || found.has(text)) return true;
|
|
146
|
+
found.add(text);
|
|
147
|
+
return false;
|
|
148
|
+
});
|
|
149
|
+
const text = moved
|
|
150
|
+
.filter((block) => found.has(block.framed))
|
|
151
|
+
.map((block) => block.inner)
|
|
152
|
+
.join("\n\n");
|
|
153
|
+
|
|
154
|
+
const user: WireMessage = { ...carrier, content: kept };
|
|
155
|
+
return { ...payload, [key]: [...messages.slice(0, index), user, systemMessage(layout, role, text), ...messages.slice(index + 1)] };
|
|
156
|
+
}
|