@code-yeongyu/senpi 2026.8.18-3 → 2026.8.19
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/CHANGELOG.md +52 -0
- package/dist/cli/args.d.ts +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +4 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/core/agent-session.d.ts +33 -4
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +293 -55
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +3 -0
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +20 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +182 -52
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/index.d.ts +3 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/index.js +18 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/index.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.d.ts +1 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.js +7 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/oauth-login.js.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/settings.d.ts +8 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/settings.d.ts.map +1 -1
- package/dist/core/extensions/builtin/claude-sdk-oauth/settings.js +18 -0
- package/dist/core/extensions/builtin/claude-sdk-oauth/settings.js.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/guardrails.d.ts +7 -0
- package/dist/core/extensions/builtin/cursor-cli-oauth/guardrails.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/guardrails.js +10 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/guardrails.js.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/index.js +12 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/index.js.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.d.ts +12 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.js +20 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/oauth-login.js.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/settings.d.ts +11 -0
- package/dist/core/extensions/builtin/cursor-cli-oauth/settings.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/settings.js +7 -2
- package/dist/core/extensions/builtin/cursor-cli-oauth/settings.js.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.d.ts.map +1 -1
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js +7 -2
- package/dist/core/extensions/builtin/cursor-cli-oauth/stream.js.map +1 -1
- package/dist/core/extensions/builtin/index.d.ts.map +1 -1
- package/dist/core/extensions/builtin/index.js +2 -0
- package/dist/core/extensions/builtin/index.js.map +1 -1
- package/dist/core/extensions/builtin/loop/command-registration.d.ts +7 -0
- package/dist/core/extensions/builtin/loop/command-registration.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/command-registration.js +13 -0
- package/dist/core/extensions/builtin/loop/command-registration.js.map +1 -0
- package/dist/core/extensions/builtin/loop/command.d.ts +41 -0
- package/dist/core/extensions/builtin/loop/command.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/command.js +281 -0
- package/dist/core/extensions/builtin/loop/command.js.map +1 -0
- package/dist/core/extensions/builtin/loop/cron-planner.d.ts +23 -0
- package/dist/core/extensions/builtin/loop/cron-planner.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/cron-planner.js +88 -0
- package/dist/core/extensions/builtin/loop/cron-planner.js.map +1 -0
- package/dist/core/extensions/builtin/loop/index.d.ts +134 -0
- package/dist/core/extensions/builtin/loop/index.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/index.js +742 -0
- package/dist/core/extensions/builtin/loop/index.js.map +1 -0
- package/dist/core/extensions/builtin/loop/loopfile.d.ts +40 -0
- package/dist/core/extensions/builtin/loop/loopfile.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/loopfile.js +102 -0
- package/dist/core/extensions/builtin/loop/loopfile.js.map +1 -0
- package/dist/core/extensions/builtin/loop/parse.d.ts +45 -0
- package/dist/core/extensions/builtin/loop/parse.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/parse.js +92 -0
- package/dist/core/extensions/builtin/loop/parse.js.map +1 -0
- package/dist/core/extensions/builtin/loop/scheduler.d.ts +225 -0
- package/dist/core/extensions/builtin/loop/scheduler.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/scheduler.js +708 -0
- package/dist/core/extensions/builtin/loop/scheduler.js.map +1 -0
- package/dist/core/extensions/builtin/loop/status.d.ts +24 -0
- package/dist/core/extensions/builtin/loop/status.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/status.js +113 -0
- package/dist/core/extensions/builtin/loop/status.js.map +1 -0
- package/dist/core/extensions/builtin/loop/store.d.ts +49 -0
- package/dist/core/extensions/builtin/loop/store.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/store.js +424 -0
- package/dist/core/extensions/builtin/loop/store.js.map +1 -0
- package/dist/core/extensions/builtin/loop/tick-prompt.d.ts +64 -0
- package/dist/core/extensions/builtin/loop/tick-prompt.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/tick-prompt.js +183 -0
- package/dist/core/extensions/builtin/loop/tick-prompt.js.map +1 -0
- package/dist/core/extensions/builtin/loop/tools.d.ts +122 -0
- package/dist/core/extensions/builtin/loop/tools.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/tools.js +155 -0
- package/dist/core/extensions/builtin/loop/tools.js.map +1 -0
- package/dist/core/extensions/builtin/loop/types.d.ts +206 -0
- package/dist/core/extensions/builtin/loop/types.d.ts.map +1 -0
- package/dist/core/extensions/builtin/loop/types.js +37 -0
- package/dist/core/extensions/builtin/loop/types.js.map +1 -0
- package/dist/core/extensions/index.d.ts +1 -1
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +10 -5
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +30 -3
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +5 -10
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/model-registry.d.ts +2 -0
- package/dist/core/model-registry.d.ts.map +1 -1
- package/dist/core/model-registry.js +4 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-runtime.d.ts +7 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +17 -0
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +8 -4
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/provider-composer.d.ts +8 -0
- package/dist/core/provider-composer.d.ts.map +1 -1
- package/dist/core/provider-composer.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +2 -1
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/retry-fallback/chains.d.ts +1 -0
- package/dist/core/retry-fallback/chains.d.ts.map +1 -1
- package/dist/core/retry-fallback/chains.js +3 -0
- package/dist/core/retry-fallback/chains.js.map +1 -1
- package/dist/core/retry-fallback/controller.d.ts +2 -0
- package/dist/core/retry-fallback/controller.d.ts.map +1 -1
- package/dist/core/retry-fallback/controller.js.map +1 -1
- package/dist/core/retry-fallback/expansion.d.ts +8 -0
- package/dist/core/retry-fallback/expansion.d.ts.map +1 -1
- package/dist/core/retry-fallback/expansion.js +2 -0
- package/dist/core/retry-fallback/expansion.js.map +1 -1
- package/dist/core/settings-manager.d.ts +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +14 -3
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +51 -33
- package/dist/core/skills.js.map +1 -1
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +17 -2
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/extensions/llama/index.d.ts.map +1 -1
- package/dist/extensions/llama/index.js +2 -0
- package/dist/extensions/llama/index.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +6 -6
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +4 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +4 -4
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +1 -1
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/editor-paste-transfer.d.ts +10 -1
- package/dist/modes/interactive/editor-paste-transfer.d.ts.map +1 -1
- package/dist/modes/interactive/editor-paste-transfer.js +27 -2
- package/dist/modes/interactive/editor-paste-transfer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +69 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +434 -63
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/tips/catalog/dag-tips.d.ts +7 -0
- package/dist/modes/interactive/tips/catalog/dag-tips.d.ts.map +1 -0
- package/dist/modes/interactive/tips/catalog/dag-tips.js +57 -0
- package/dist/modes/interactive/tips/catalog/dag-tips.js.map +1 -0
- package/dist/modes/interactive/tips/catalog/memory-tips.d.ts +7 -0
- package/dist/modes/interactive/tips/catalog/memory-tips.d.ts.map +1 -0
- package/dist/modes/interactive/tips/catalog/memory-tips.js +100 -0
- package/dist/modes/interactive/tips/catalog/memory-tips.js.map +1 -0
- package/dist/modes/interactive/tips/registry.d.ts.map +1 -1
- package/dist/modes/interactive/tips/registry.js +4 -0
- package/dist/modes/interactive/tips/registry.js.map +1 -1
- package/dist/utils/management-http.d.ts +5 -3
- package/dist/utils/management-http.d.ts.map +1 -1
- package/dist/utils/management-http.js +14 -10
- package/dist/utils/management-http.js.map +1 -1
- package/docs/compaction.md +17 -0
- package/docs/custom-provider.md +5 -2
- package/docs/extensions.md +11 -2
- package/docs/llama-cpp.md +2 -0
- package/docs/models.md +8 -2
- package/docs/providers.md +5 -3
- package/docs/settings.md +22 -2
- package/docs/skills.md +4 -3
- package/examples/extensions/border-status-editor.ts +1 -1
- package/examples/extensions/git-checkpoint.ts +2 -2
- package/examples/extensions/notify.ts +3 -1
- package/examples/extensions/subagent/index.ts +6 -1
- package/examples/extensions/titlebar-spinner.ts +1 -1
- package/examples/rpc-extension-ui.ts +1 -1
- package/examples/sdk/06-extensions.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +12 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts +3 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js +51 -17
- package/node_modules/@earendil-works/pi-agent-core/dist/agent-loop.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts +4 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js +5 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/skills.d.ts +3 -2
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/skills.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/skills.js +13 -3
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/skills.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js +14 -1
- package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/edit.js.map +1 -1
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/README.md +5 -3
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts +7 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js +21 -4
- package/node_modules/@earendil-works/pi-ai/dist/api/anthropic-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.d.ts +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js +7 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/azure-openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js +39 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/bedrock-converse-stream.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/gen/agent_pb.d.ts +22 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/gen/agent_pb.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/gen/agent_pb.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent/gen/agent_pb.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js +51 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/cursor-agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.d.ts +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js +14 -11
- package/node_modules/@earendil-works/pi-ai/dist/api/google-generative-ai.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.d.ts +5 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.js +16 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/google-shared.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.d.ts +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js +13 -10
- package/node_modules/@earendil-works/pi-ai/dist/api/google-vertex.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js +4 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/mistral-conversations.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js +4 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-codex-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js +45 -29
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-completions.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js +8 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/openai-responses.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/pi-messages.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.d.ts +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js +18 -9
- package/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.d.ts +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.js +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/device-code.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.js +22 -9
- package/node_modules/@earendil-works/pi-ai/dist/auth/oauth/github-copilot.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/anthropic.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/baseten.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/github-copilot.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode-go.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan-individual.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/xai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/xiaomi.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai-coding-cn.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/zai.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.js +1 -5
- package/node_modules/@earendil-works/pi-ai/dist/providers/xai.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +28 -4
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js +15 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/overflow.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/pi-user-agent.d.ts +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/pi-user-agent.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/pi-user-agent.js +7 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/pi-user-agent.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.d.ts +2 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.js +2 -0
- package/node_modules/@earendil-works/pi-ai/dist/utils/prompt-cache-ttl.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -3
- package/node_modules/@earendil-works/pi-pty/dist/loader.d.ts +6 -0
- package/node_modules/@earendil-works/pi-pty/dist/loader.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-pty/dist/loader.js +11 -0
- package/node_modules/@earendil-works/pi-pty/dist/loader.js.map +1 -1
- package/node_modules/@earendil-works/pi-pty/dist/quarantine.d.ts +15 -0
- package/node_modules/@earendil-works/pi-pty/dist/quarantine.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-pty/dist/quarantine.js +27 -0
- package/node_modules/@earendil-works/pi-pty/dist/quarantine.js.map +1 -0
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts +73 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js +173 -17
- package/node_modules/@earendil-works/pi-tui/dist/components/editor.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/text.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/components/text.js +5 -4
- package/node_modules/@earendil-works/pi-tui/dist/components/text.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.d.ts +69 -0
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/editor-component.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/image-markers.d.ts +46 -0
- package/node_modules/@earendil-works/pi-tui/dist/image-markers.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/image-markers.js +130 -0
- package/node_modules/@earendil-works/pi-tui/dist/image-markers.js.map +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts +1 -0
- package/node_modules/@earendil-works/pi-tui/dist/index.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/index.js +2 -0
- package/node_modules/@earendil-works/pi-tui/dist/index.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/paste-markers.d.ts +3 -0
- package/node_modules/@earendil-works/pi-tui/dist/paste-markers.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/paste-markers.js +23 -7
- package/node_modules/@earendil-works/pi-tui/dist/paste-markers.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js +5 -1
- package/node_modules/@earendil-works/pi-tui/dist/tui-alt-screen.js.map +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tick-prompt templates with cache-friendly sentinel expansion.
|
|
3
|
+
*
|
|
4
|
+
* `buildTickMessage` is pure: it reads no files, touches no clock, and mutates nothing.
|
|
5
|
+
* The caller resolves the loop file (see `loopfile.ts`) and hands the snapshot in; this
|
|
6
|
+
* module decides full-vs-reminder delivery, renders the tick text, and returns the
|
|
7
|
+
* updated sentinel delivery state to persist.
|
|
8
|
+
*
|
|
9
|
+
* Cache discipline: the long instruction blocks are delivered once (the "anchor") and
|
|
10
|
+
* later ticks send a short reminder that points back at that anchored message, so the
|
|
11
|
+
* cached message prefix stays stable across fires.
|
|
12
|
+
*/
|
|
13
|
+
const LOOP_FILE_HEADER = "# /loop tick - loop.md tasks";
|
|
14
|
+
const AUTONOMOUS_HEADER = "# Autonomous loop tick";
|
|
15
|
+
const DYNAMIC_PACING_SUFFIX = " (dynamic pacing)";
|
|
16
|
+
const FIXED_RESCHEDULE_RULE = [
|
|
17
|
+
"The fixed recurring schedule fires the next tick automatically - do not call `schedule_wakeup` from this tick.",
|
|
18
|
+
"If this tick errors, the schedule stays armed and the next occurrence still fires.",
|
|
19
|
+
].join("\n");
|
|
20
|
+
function dynamicRescheduleRule(reentryPrompt) {
|
|
21
|
+
return [
|
|
22
|
+
`As the last action of this turn, call \`schedule_wakeup\` with \`prompt: ${JSON.stringify(reentryPrompt)}\` so the loop stays alive; a turn that ends without it burns the single keepalive fallback and then the loop ends.`,
|
|
23
|
+
"To end the loop instead, call `schedule_wakeup` with `{ stop: true }` and a reason.",
|
|
24
|
+
"When the next step is gated on observable state, do not sleep or poll: watch terminal output with `monitor`, inspect it with `bash_output`, stop the watcher with `kill_bash`, and let task-notification wakeups resume this loop. With one of those armed, the scheduled delay is only a fallback heartbeat (1200-1800s is the normal idle range).",
|
|
25
|
+
"Set `noop: true` only when this tick found no actionable change, and notify the user only on changes worth acting on - not once per tick.",
|
|
26
|
+
].join("\n");
|
|
27
|
+
}
|
|
28
|
+
function modeRule(mode, reentryPrompt) {
|
|
29
|
+
return mode === "fixed" ? FIXED_RESCHEDULE_RULE : dynamicRescheduleRule(reentryPrompt);
|
|
30
|
+
}
|
|
31
|
+
function header(base, mode) {
|
|
32
|
+
return mode === "dynamic" ? `${base}${DYNAMIC_PACING_SUFFIX}` : base;
|
|
33
|
+
}
|
|
34
|
+
const AUTONOMOUS_FULL_BODY = [
|
|
35
|
+
"No loop-tasks file is configured, so run the standard autonomous maintenance pass for this tick:",
|
|
36
|
+
"",
|
|
37
|
+
"1. Inspect current state: active tasks, running terminal commands and monitors, and any delegated child work.",
|
|
38
|
+
"2. Make concrete progress where progress is possible; do not merely narrate.",
|
|
39
|
+
"3. Avoid repeating unchanged status - stay silent when nothing moved.",
|
|
40
|
+
"4. Surface only actionable state changes to the user, once per state change.",
|
|
41
|
+
].join("\n");
|
|
42
|
+
const AUTONOMOUS_REMINDER_BODY = "Continue the autonomous maintenance pass established by the most recent full autonomous loop instruction message in this conversation. Re-read that anchored message and perform the next applicable work.";
|
|
43
|
+
const LOOP_FILE_REMINDER_BODY = "Continue the loop.md tasks established by the most recent full loop.md instruction message in this conversation. Re-read that anchored message and perform the next applicable work.";
|
|
44
|
+
const LOOP_FILE_INTRO = "The user configured a loop-tasks file. Work through the tasks defined below; these are the instructions for this tick and every subsequent tick (the reminder on later fires refers back to this message).";
|
|
45
|
+
const LOOP_FILE_ABSENT_BODY = "The loop-tasks file used by this loop is currently absent. Perform the autonomous maintenance check for this tick. Keep the loop alive so a later tick can pick up the file if it reappears.";
|
|
46
|
+
function joinBlocks(blocks) {
|
|
47
|
+
return blocks.filter((block) => block.length > 0).join("\n\n");
|
|
48
|
+
}
|
|
49
|
+
function sentinelOf(payload) {
|
|
50
|
+
return payload.type === "sentinel" ? payload.sentinel : undefined;
|
|
51
|
+
}
|
|
52
|
+
function isLoopFileSentinel(sentinel) {
|
|
53
|
+
return sentinel === "<<loop.md>>" || sentinel === "<<loop.md-dynamic>>";
|
|
54
|
+
}
|
|
55
|
+
function fingerprintChanged(previous, snapshot) {
|
|
56
|
+
return (previous.path !== snapshot.path ||
|
|
57
|
+
previous.mtimeMs !== snapshot.mtimeMs ||
|
|
58
|
+
previous.size !== snapshot.size ||
|
|
59
|
+
previous.contentHash !== snapshot.contentHash);
|
|
60
|
+
}
|
|
61
|
+
function buildPromptTick(input, prompt) {
|
|
62
|
+
const text = joinBlocks([prompt, modeRule(input.mode, input.reentryPrompt)]);
|
|
63
|
+
return {
|
|
64
|
+
text,
|
|
65
|
+
delivery: "prompt",
|
|
66
|
+
details: {
|
|
67
|
+
loopId: input.loopId,
|
|
68
|
+
deliveryId: input.deliveryId,
|
|
69
|
+
mode: input.mode,
|
|
70
|
+
delivery: "prompt",
|
|
71
|
+
},
|
|
72
|
+
// A verbatim prompt tick carries no sentinel instructions, so nothing is anchored.
|
|
73
|
+
deliveryState: input.deliveryState,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function buildLoopFileTick(input, sentinel) {
|
|
77
|
+
const { deliveryState, loopFile, mode } = input;
|
|
78
|
+
const rule = modeRule(mode, input.reentryPrompt);
|
|
79
|
+
if (!loopFile.present) {
|
|
80
|
+
// Absence keeps the loop.md sentinel: a later tick must detect reappearance.
|
|
81
|
+
const text = joinBlocks([header("# /loop tick - loop.md absent", mode), LOOP_FILE_ABSENT_BODY, rule]);
|
|
82
|
+
return {
|
|
83
|
+
text,
|
|
84
|
+
delivery: "full",
|
|
85
|
+
details: {
|
|
86
|
+
loopId: input.loopId,
|
|
87
|
+
deliveryId: input.deliveryId,
|
|
88
|
+
mode,
|
|
89
|
+
delivery: "full",
|
|
90
|
+
sentinel,
|
|
91
|
+
},
|
|
92
|
+
deliveryState: {
|
|
93
|
+
...deliveryState,
|
|
94
|
+
lastLoopFileDelivered: null,
|
|
95
|
+
forceFullDelivery: false,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const previous = deliveryState.lastLoopFileDelivered;
|
|
100
|
+
const full = deliveryState.forceFullDelivery || previous === null || fingerprintChanged(previous, loopFile);
|
|
101
|
+
if (!full) {
|
|
102
|
+
return {
|
|
103
|
+
text: joinBlocks([LOOP_FILE_HEADER, LOOP_FILE_REMINDER_BODY, rule]),
|
|
104
|
+
delivery: "reminder",
|
|
105
|
+
details: {
|
|
106
|
+
loopId: input.loopId,
|
|
107
|
+
deliveryId: input.deliveryId,
|
|
108
|
+
mode,
|
|
109
|
+
delivery: "reminder",
|
|
110
|
+
sentinel,
|
|
111
|
+
loopFile: previous,
|
|
112
|
+
},
|
|
113
|
+
deliveryState,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const fingerprint = {
|
|
117
|
+
path: loopFile.path,
|
|
118
|
+
mtimeMs: loopFile.mtimeMs,
|
|
119
|
+
size: loopFile.size,
|
|
120
|
+
contentHash: loopFile.contentHash,
|
|
121
|
+
anchorDeliveryId: input.deliveryId,
|
|
122
|
+
};
|
|
123
|
+
const text = joinBlocks([LOOP_FILE_HEADER, LOOP_FILE_INTRO, `---\n${loopFile.content}\n---`, rule]);
|
|
124
|
+
return {
|
|
125
|
+
text,
|
|
126
|
+
delivery: "full",
|
|
127
|
+
details: {
|
|
128
|
+
loopId: input.loopId,
|
|
129
|
+
deliveryId: input.deliveryId,
|
|
130
|
+
mode,
|
|
131
|
+
delivery: "full",
|
|
132
|
+
sentinel,
|
|
133
|
+
loopFile: fingerprint,
|
|
134
|
+
},
|
|
135
|
+
deliveryState: {
|
|
136
|
+
...deliveryState,
|
|
137
|
+
lastLoopFileDelivered: fingerprint,
|
|
138
|
+
forceFullDelivery: false,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function buildAutonomousTick(input, sentinel) {
|
|
143
|
+
const { deliveryState, mode } = input;
|
|
144
|
+
const rule = modeRule(mode, input.reentryPrompt);
|
|
145
|
+
const full = deliveryState.forceFullDelivery || !deliveryState.autonomousPreambleDelivered;
|
|
146
|
+
const head = header(AUTONOMOUS_HEADER, mode);
|
|
147
|
+
const text = full
|
|
148
|
+
? joinBlocks([head, AUTONOMOUS_FULL_BODY, rule])
|
|
149
|
+
: joinBlocks([head, AUTONOMOUS_REMINDER_BODY, rule]);
|
|
150
|
+
return {
|
|
151
|
+
text,
|
|
152
|
+
delivery: full ? "full" : "reminder",
|
|
153
|
+
details: {
|
|
154
|
+
loopId: input.loopId,
|
|
155
|
+
deliveryId: input.deliveryId,
|
|
156
|
+
mode,
|
|
157
|
+
delivery: full ? "full" : "reminder",
|
|
158
|
+
sentinel,
|
|
159
|
+
},
|
|
160
|
+
deliveryState: full
|
|
161
|
+
? { ...deliveryState, autonomousPreambleDelivered: true, forceFullDelivery: false }
|
|
162
|
+
: deliveryState,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Render one tick message plus the delivery state to persist afterwards.
|
|
167
|
+
*
|
|
168
|
+
* Full delivery happens on the first sentinel delivery, when `forceFullDelivery` is set
|
|
169
|
+
* (accepted compaction or a missing anchor on resume), when the loop file's fingerprint
|
|
170
|
+
* changed, or when the file reappeared after absence. Otherwise the tick is a short
|
|
171
|
+
* reminder anchored to the last full message.
|
|
172
|
+
*/
|
|
173
|
+
export function buildTickMessage(input) {
|
|
174
|
+
if (input.payload.type === "prompt") {
|
|
175
|
+
return buildPromptTick(input, input.payload.prompt);
|
|
176
|
+
}
|
|
177
|
+
const sentinel = sentinelOf(input.payload);
|
|
178
|
+
if (sentinel === undefined) {
|
|
179
|
+
throw new Error("loop tick payload is a sentinel without a sentinel value");
|
|
180
|
+
}
|
|
181
|
+
return isLoopFileSentinel(sentinel) ? buildLoopFileTick(input, sentinel) : buildAutonomousTick(input, sentinel);
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=tick-prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tick-prompt.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/loop/tick-prompt.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Tick-prompt templates with cache-friendly sentinel expansion.\n *\n * `buildTickMessage` is pure: it reads no files, touches no clock, and mutates nothing.\n * The caller resolves the loop file (see `loopfile.ts`) and hands the snapshot in; this\n * module decides full-vs-reminder delivery, renders the tick text, and returns the\n * updated sentinel delivery state to persist.\n *\n * Cache discipline: the long instruction blocks are delivered once (the \"anchor\") and\n * later ticks send a short reminder that points back at that anchored message, so the\n * cached message prefix stays stable across fires.\n */\n\nimport type {\n\tDeliveryId,\n\tLoopFileFingerprint,\n\tLoopId,\n\tLoopPayload,\n\tLoopSentinel,\n\tSentinelDeliveryState,\n} from \"./types.ts\";\n\nexport type LoopMode = \"fixed\" | \"dynamic\";\n\n/** `prompt` covers a verbatim user prompt; sentinels alternate between full and reminder. */\nexport type TickDelivery = \"full\" | \"reminder\" | \"prompt\";\n\n/** Resolved loop-file state for this fire, produced by the caller's loop-file reader. */\nexport type LoopFileSnapshot =\n\t| { readonly present: false }\n\t| {\n\t\t\treadonly present: true;\n\t\t\treadonly path: string;\n\t\t\t/** Model-visible (already truncated) representation. */\n\t\t\treadonly content: string;\n\t\t\treadonly mtimeMs: number;\n\t\t\treadonly size: number;\n\t\t\t/** SHA-256 of the model-visible representation. */\n\t\t\treadonly contentHash: string;\n\t };\n\nexport interface TickPromptInput {\n\treadonly loopId: LoopId;\n\treadonly deliveryId: DeliveryId;\n\treadonly mode: LoopMode;\n\treadonly payload: LoopPayload;\n\t/** Canonical re-entry text, e.g. `/loop check the deploy` or `/loop`. */\n\treadonly reentryPrompt: string;\n\treadonly deliveryState: SentinelDeliveryState;\n\treadonly loopFile: LoopFileSnapshot;\n}\n\nexport interface LoopTickDetails {\n\treadonly loopId: LoopId;\n\treadonly deliveryId: DeliveryId;\n\treadonly mode: LoopMode;\n\treadonly delivery: TickDelivery;\n\treadonly sentinel?: LoopSentinel;\n\treadonly loopFile?: LoopFileFingerprint;\n}\n\nexport interface TickPromptResult {\n\treadonly text: string;\n\treadonly delivery: TickDelivery;\n\treadonly details: LoopTickDetails;\n\t/** Delivery state to persist after this tick is dispatched. */\n\treadonly deliveryState: SentinelDeliveryState;\n}\n\nconst LOOP_FILE_HEADER = \"# /loop tick - loop.md tasks\";\nconst AUTONOMOUS_HEADER = \"# Autonomous loop tick\";\nconst DYNAMIC_PACING_SUFFIX = \" (dynamic pacing)\";\n\nconst FIXED_RESCHEDULE_RULE = [\n\t\"The fixed recurring schedule fires the next tick automatically - do not call `schedule_wakeup` from this tick.\",\n\t\"If this tick errors, the schedule stays armed and the next occurrence still fires.\",\n].join(\"\\n\");\n\nfunction dynamicRescheduleRule(reentryPrompt: string): string {\n\treturn [\n\t\t`As the last action of this turn, call \\`schedule_wakeup\\` with \\`prompt: ${JSON.stringify(reentryPrompt)}\\` so the loop stays alive; a turn that ends without it burns the single keepalive fallback and then the loop ends.`,\n\t\t\"To end the loop instead, call `schedule_wakeup` with `{ stop: true }` and a reason.\",\n\t\t\"When the next step is gated on observable state, do not sleep or poll: watch terminal output with `monitor`, inspect it with `bash_output`, stop the watcher with `kill_bash`, and let task-notification wakeups resume this loop. With one of those armed, the scheduled delay is only a fallback heartbeat (1200-1800s is the normal idle range).\",\n\t\t\"Set `noop: true` only when this tick found no actionable change, and notify the user only on changes worth acting on - not once per tick.\",\n\t].join(\"\\n\");\n}\n\nfunction modeRule(mode: LoopMode, reentryPrompt: string): string {\n\treturn mode === \"fixed\" ? FIXED_RESCHEDULE_RULE : dynamicRescheduleRule(reentryPrompt);\n}\n\nfunction header(base: string, mode: LoopMode): string {\n\treturn mode === \"dynamic\" ? `${base}${DYNAMIC_PACING_SUFFIX}` : base;\n}\n\nconst AUTONOMOUS_FULL_BODY = [\n\t\"No loop-tasks file is configured, so run the standard autonomous maintenance pass for this tick:\",\n\t\"\",\n\t\"1. Inspect current state: active tasks, running terminal commands and monitors, and any delegated child work.\",\n\t\"2. Make concrete progress where progress is possible; do not merely narrate.\",\n\t\"3. Avoid repeating unchanged status - stay silent when nothing moved.\",\n\t\"4. Surface only actionable state changes to the user, once per state change.\",\n].join(\"\\n\");\n\nconst AUTONOMOUS_REMINDER_BODY =\n\t\"Continue the autonomous maintenance pass established by the most recent full autonomous loop instruction message in this conversation. Re-read that anchored message and perform the next applicable work.\";\n\nconst LOOP_FILE_REMINDER_BODY =\n\t\"Continue the loop.md tasks established by the most recent full loop.md instruction message in this conversation. Re-read that anchored message and perform the next applicable work.\";\n\nconst LOOP_FILE_INTRO =\n\t\"The user configured a loop-tasks file. Work through the tasks defined below; these are the instructions for this tick and every subsequent tick (the reminder on later fires refers back to this message).\";\n\nconst LOOP_FILE_ABSENT_BODY =\n\t\"The loop-tasks file used by this loop is currently absent. Perform the autonomous maintenance check for this tick. Keep the loop alive so a later tick can pick up the file if it reappears.\";\n\nfunction joinBlocks(blocks: readonly string[]): string {\n\treturn blocks.filter((block) => block.length > 0).join(\"\\n\\n\");\n}\n\nfunction sentinelOf(payload: LoopPayload): LoopSentinel | undefined {\n\treturn payload.type === \"sentinel\" ? payload.sentinel : undefined;\n}\n\nfunction isLoopFileSentinel(sentinel: LoopSentinel): boolean {\n\treturn sentinel === \"<<loop.md>>\" || sentinel === \"<<loop.md-dynamic>>\";\n}\n\nfunction fingerprintChanged(\n\tprevious: LoopFileFingerprint,\n\tsnapshot: Extract<LoopFileSnapshot, { present: true }>,\n): boolean {\n\treturn (\n\t\tprevious.path !== snapshot.path ||\n\t\tprevious.mtimeMs !== snapshot.mtimeMs ||\n\t\tprevious.size !== snapshot.size ||\n\t\tprevious.contentHash !== snapshot.contentHash\n\t);\n}\n\nfunction buildPromptTick(input: TickPromptInput, prompt: string): TickPromptResult {\n\tconst text = joinBlocks([prompt, modeRule(input.mode, input.reentryPrompt)]);\n\treturn {\n\t\ttext,\n\t\tdelivery: \"prompt\",\n\t\tdetails: {\n\t\t\tloopId: input.loopId,\n\t\t\tdeliveryId: input.deliveryId,\n\t\t\tmode: input.mode,\n\t\t\tdelivery: \"prompt\",\n\t\t},\n\t\t// A verbatim prompt tick carries no sentinel instructions, so nothing is anchored.\n\t\tdeliveryState: input.deliveryState,\n\t};\n}\n\nfunction buildLoopFileTick(input: TickPromptInput, sentinel: LoopSentinel): TickPromptResult {\n\tconst { deliveryState, loopFile, mode } = input;\n\tconst rule = modeRule(mode, input.reentryPrompt);\n\n\tif (!loopFile.present) {\n\t\t// Absence keeps the loop.md sentinel: a later tick must detect reappearance.\n\t\tconst text = joinBlocks([header(\"# /loop tick - loop.md absent\", mode), LOOP_FILE_ABSENT_BODY, rule]);\n\t\treturn {\n\t\t\ttext,\n\t\t\tdelivery: \"full\",\n\t\t\tdetails: {\n\t\t\t\tloopId: input.loopId,\n\t\t\t\tdeliveryId: input.deliveryId,\n\t\t\t\tmode,\n\t\t\t\tdelivery: \"full\",\n\t\t\t\tsentinel,\n\t\t\t},\n\t\t\tdeliveryState: {\n\t\t\t\t...deliveryState,\n\t\t\t\tlastLoopFileDelivered: null,\n\t\t\t\tforceFullDelivery: false,\n\t\t\t},\n\t\t};\n\t}\n\n\tconst previous = deliveryState.lastLoopFileDelivered;\n\tconst full = deliveryState.forceFullDelivery || previous === null || fingerprintChanged(previous, loopFile);\n\n\tif (!full) {\n\t\treturn {\n\t\t\ttext: joinBlocks([LOOP_FILE_HEADER, LOOP_FILE_REMINDER_BODY, rule]),\n\t\t\tdelivery: \"reminder\",\n\t\t\tdetails: {\n\t\t\t\tloopId: input.loopId,\n\t\t\t\tdeliveryId: input.deliveryId,\n\t\t\t\tmode,\n\t\t\t\tdelivery: \"reminder\",\n\t\t\t\tsentinel,\n\t\t\t\tloopFile: previous,\n\t\t\t},\n\t\t\tdeliveryState,\n\t\t};\n\t}\n\n\tconst fingerprint: LoopFileFingerprint = {\n\t\tpath: loopFile.path,\n\t\tmtimeMs: loopFile.mtimeMs,\n\t\tsize: loopFile.size,\n\t\tcontentHash: loopFile.contentHash,\n\t\tanchorDeliveryId: input.deliveryId,\n\t};\n\n\tconst text = joinBlocks([LOOP_FILE_HEADER, LOOP_FILE_INTRO, `---\\n${loopFile.content}\\n---`, rule]);\n\n\treturn {\n\t\ttext,\n\t\tdelivery: \"full\",\n\t\tdetails: {\n\t\t\tloopId: input.loopId,\n\t\t\tdeliveryId: input.deliveryId,\n\t\t\tmode,\n\t\t\tdelivery: \"full\",\n\t\t\tsentinel,\n\t\t\tloopFile: fingerprint,\n\t\t},\n\t\tdeliveryState: {\n\t\t\t...deliveryState,\n\t\t\tlastLoopFileDelivered: fingerprint,\n\t\t\tforceFullDelivery: false,\n\t\t},\n\t};\n}\n\nfunction buildAutonomousTick(input: TickPromptInput, sentinel: LoopSentinel): TickPromptResult {\n\tconst { deliveryState, mode } = input;\n\tconst rule = modeRule(mode, input.reentryPrompt);\n\tconst full = deliveryState.forceFullDelivery || !deliveryState.autonomousPreambleDelivered;\n\tconst head = header(AUTONOMOUS_HEADER, mode);\n\n\tconst text = full\n\t\t? joinBlocks([head, AUTONOMOUS_FULL_BODY, rule])\n\t\t: joinBlocks([head, AUTONOMOUS_REMINDER_BODY, rule]);\n\n\treturn {\n\t\ttext,\n\t\tdelivery: full ? \"full\" : \"reminder\",\n\t\tdetails: {\n\t\t\tloopId: input.loopId,\n\t\t\tdeliveryId: input.deliveryId,\n\t\t\tmode,\n\t\t\tdelivery: full ? \"full\" : \"reminder\",\n\t\t\tsentinel,\n\t\t},\n\t\tdeliveryState: full\n\t\t\t? { ...deliveryState, autonomousPreambleDelivered: true, forceFullDelivery: false }\n\t\t\t: deliveryState,\n\t};\n}\n\n/**\n * Render one tick message plus the delivery state to persist afterwards.\n *\n * Full delivery happens on the first sentinel delivery, when `forceFullDelivery` is set\n * (accepted compaction or a missing anchor on resume), when the loop file's fingerprint\n * changed, or when the file reappeared after absence. Otherwise the tick is a short\n * reminder anchored to the last full message.\n */\nexport function buildTickMessage(input: TickPromptInput): TickPromptResult {\n\tif (input.payload.type === \"prompt\") {\n\t\treturn buildPromptTick(input, input.payload.prompt);\n\t}\n\tconst sentinel = sentinelOf(input.payload);\n\tif (sentinel === undefined) {\n\t\tthrow new Error(\"loop tick payload is a sentinel without a sentinel value\");\n\t}\n\treturn isLoopFileSentinel(sentinel) ? buildLoopFileTick(input, sentinel) : buildAutonomousTick(input, sentinel);\n}\n"]}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `schedule_wakeup` model tool: the ONLY model-callable surface of the `/loop`
|
|
3
|
+
* extension. It lets a dynamic (self-paced) loop pick its own next delay, or end itself.
|
|
4
|
+
*
|
|
5
|
+
* Two deliberate shape decisions:
|
|
6
|
+
* - The TypeBox schema is a FLAT object with no root union. Several provider payload
|
|
7
|
+
* conversions rebuild tool schemas from top-level `properties` only, so a root
|
|
8
|
+
* `anyOf` would reach the model as an empty schema (same reasoning as
|
|
9
|
+
* `terminal/tools/monitor.ts`). Branch requirements are enforced in `execute`.
|
|
10
|
+
* - `delaySeconds` carries NO schema `minimum`/`maximum`. The contract clamps
|
|
11
|
+
* out-of-range integers instead of rejecting them, and schema bounds would make the
|
|
12
|
+
* clamp unreachable.
|
|
13
|
+
*
|
|
14
|
+
* Scheduler state is never mutated here: every effect goes through the injected
|
|
15
|
+
* `ScheduleWakeupSchedulerPort`.
|
|
16
|
+
*/
|
|
17
|
+
import { Type } from "typebox";
|
|
18
|
+
import type { ExtensionAPI } from "../../types.ts";
|
|
19
|
+
import type { EpochMs, LoopId, WakeupId } from "./types.ts";
|
|
20
|
+
/** Model-visible tool name. */
|
|
21
|
+
export declare const SCHEDULE_WAKEUP_TOOL = "schedule_wakeup";
|
|
22
|
+
/** Inclusive bounds the executor clamps `delaySeconds` into. */
|
|
23
|
+
export declare const MIN_WAKEUP_DELAY_SECONDS = 60;
|
|
24
|
+
export declare const MAX_WAKEUP_DELAY_SECONDS = 3600;
|
|
25
|
+
export declare const SCHEDULE_WAKEUP_DESCRIPTION = "Schedule when to resume work in /loop dynamic mode. Always pass the `prompt` argument unless stopping. Call this as the last action before ending a dynamic loop iteration so the loop remains alive; call with `stop: true` to end the active dynamic loop immediately.\n\n`delaySeconds` is clamped to 60-3600 seconds. For idle polling, normally use 1200-1800 seconds. Avoid choosing 300 seconds merely to poll: short polling repeatedly loses prompt-cache value on many API paths. When work is gated on observable terminal state, use `monitor` instead of sleeping or polling; inspect it with `bash_output` and stop the watcher with `kill_bash`. For delegated work, use the available `task` output and control tools and let task completion notifications wake the session. When a monitor or task notification is the primary wake source, the scheduled delay is only a fallback heartbeat.\n\nFor normal dynamic re-entry, set `prompt` to the complete original command, for example `/loop check the deploy`, preserving the user's text verbatim. Set `noop: true` only when this iteration found no actionable change; consecutive noop iterations are folded in the terminal view. Omit `noop` when stopping. Notify the user only when state changes in a way worth acting on, not once per tick.";
|
|
26
|
+
export declare const scheduleWakeupSchema: Type.TObject<{
|
|
27
|
+
delaySeconds: Type.TOptional<Type.TInteger>;
|
|
28
|
+
reason: Type.TString;
|
|
29
|
+
prompt: Type.TOptional<Type.TString>;
|
|
30
|
+
stop: Type.TOptional<Type.TBoolean>;
|
|
31
|
+
noop: Type.TOptional<Type.TBoolean>;
|
|
32
|
+
}>;
|
|
33
|
+
/** Runtime-validated parameter shape; the schema itself stays flat and permissive. */
|
|
34
|
+
export interface ScheduleWakeupParams {
|
|
35
|
+
readonly delaySeconds?: number;
|
|
36
|
+
readonly reason: string;
|
|
37
|
+
readonly prompt?: string;
|
|
38
|
+
readonly stop?: boolean;
|
|
39
|
+
readonly noop?: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface ScheduleWakeupScheduledDetails {
|
|
42
|
+
readonly ok: true;
|
|
43
|
+
readonly action: "scheduled";
|
|
44
|
+
readonly loopId: LoopId;
|
|
45
|
+
readonly wakeupId: WakeupId;
|
|
46
|
+
readonly replacedWakeupId?: WakeupId;
|
|
47
|
+
/** Integer the model asked for, before the clamp. */
|
|
48
|
+
readonly requestedDelaySeconds: number;
|
|
49
|
+
/** Effective delay after clamping to `[60, 3600]`. */
|
|
50
|
+
readonly delaySeconds: number;
|
|
51
|
+
readonly clamped: boolean;
|
|
52
|
+
readonly dueAt: EpochMs;
|
|
53
|
+
readonly reason: string;
|
|
54
|
+
/** Preserved verbatim after the non-blank check. */
|
|
55
|
+
readonly prompt: string;
|
|
56
|
+
readonly noop: boolean;
|
|
57
|
+
readonly noopStreak: number;
|
|
58
|
+
}
|
|
59
|
+
export interface ScheduleWakeupStoppedDetails {
|
|
60
|
+
readonly ok: true;
|
|
61
|
+
readonly action: "stopped";
|
|
62
|
+
readonly loopId: LoopId;
|
|
63
|
+
readonly terminalReason: "stopped";
|
|
64
|
+
readonly reason: string;
|
|
65
|
+
readonly endedAt: EpochMs;
|
|
66
|
+
/** Fields accepted for model compatibility but not applied. */
|
|
67
|
+
readonly ignoredFields: readonly ScheduleWakeupIgnorableField[];
|
|
68
|
+
}
|
|
69
|
+
export type ScheduleWakeupIgnorableField = "delaySeconds" | "prompt";
|
|
70
|
+
export type ScheduleWakeupDetails = ScheduleWakeupScheduledDetails | ScheduleWakeupStoppedDetails;
|
|
71
|
+
/**
|
|
72
|
+
* Which loop, if any, this tool call belongs to. `null` means no loop context at all;
|
|
73
|
+
* a `fixed` target means the call came from a fixed tick, whose schedule re-arms itself.
|
|
74
|
+
*/
|
|
75
|
+
export type ScheduleWakeupTarget = {
|
|
76
|
+
readonly kind: "dynamic" | "fixed";
|
|
77
|
+
readonly loopId: LoopId;
|
|
78
|
+
} | null;
|
|
79
|
+
export interface ScheduleWakeupRequest {
|
|
80
|
+
readonly loopId: LoopId;
|
|
81
|
+
readonly requestedDelaySeconds: number;
|
|
82
|
+
readonly delaySeconds: number;
|
|
83
|
+
readonly reason: string;
|
|
84
|
+
readonly prompt: string;
|
|
85
|
+
readonly noop: boolean;
|
|
86
|
+
}
|
|
87
|
+
export interface ScheduleWakeupOutcome {
|
|
88
|
+
readonly wakeupId: WakeupId;
|
|
89
|
+
/** Set when this schedule replaced a still-pending wakeup for the same loop. */
|
|
90
|
+
readonly replacedWakeupId?: WakeupId;
|
|
91
|
+
readonly dueAt: EpochMs;
|
|
92
|
+
readonly noopStreak: number;
|
|
93
|
+
}
|
|
94
|
+
export interface StopDynamicLoopRequest {
|
|
95
|
+
readonly loopId: LoopId;
|
|
96
|
+
readonly reason: string;
|
|
97
|
+
}
|
|
98
|
+
export interface StopDynamicLoopOutcome {
|
|
99
|
+
readonly endedAt: EpochMs;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Minimal scheduler surface this tool needs. Declared locally so the tool module stays
|
|
103
|
+
* independent of the scheduler implementation and testable with a fake.
|
|
104
|
+
*/
|
|
105
|
+
export interface ScheduleWakeupSchedulerPort {
|
|
106
|
+
/** Loop the current turn is attributed to, resolved at call time. */
|
|
107
|
+
getWakeupTarget(): ScheduleWakeupTarget;
|
|
108
|
+
scheduleWakeup(request: ScheduleWakeupRequest): Promise<ScheduleWakeupOutcome>;
|
|
109
|
+
stopDynamicLoop(request: StopDynamicLoopRequest): Promise<StopDynamicLoopOutcome>;
|
|
110
|
+
}
|
|
111
|
+
export interface LoopToolRegistrationDeps {
|
|
112
|
+
readonly scheduler: ScheduleWakeupSchedulerPort;
|
|
113
|
+
}
|
|
114
|
+
export declare const NO_ACTIVE_DYNAMIC_LOOP_ERROR = "schedule_wakeup can only be used while a dynamic /loop is active.";
|
|
115
|
+
export declare const FIXED_TICK_ERROR = "This is a fixed /loop tick; the recurring schedule re-arms automatically. Do not call schedule_wakeup.";
|
|
116
|
+
export declare const MISSING_PROMPT_ERROR = "prompt is required and must be non-empty unless stop is true.";
|
|
117
|
+
export declare const MISSING_DELAY_ERROR = "delaySeconds is required unless stop is true.";
|
|
118
|
+
export declare const NON_INTEGER_DELAY_ERROR = "delaySeconds must be a finite integer number of seconds.";
|
|
119
|
+
export declare const BLANK_REASON_ERROR = "reason is required and must be non-empty.";
|
|
120
|
+
export declare const NOOP_WITH_STOP_ERROR = "noop must be omitted when stop is true.";
|
|
121
|
+
export declare function registerLoopTools(pi: ExtensionAPI, deps: LoopToolRegistrationDeps): void;
|
|
122
|
+
//# sourceMappingURL=tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/loop/tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `schedule_wakeup` model tool: the ONLY model-callable surface of the `/loop`
|
|
3
|
+
* extension. It lets a dynamic (self-paced) loop pick its own next delay, or end itself.
|
|
4
|
+
*
|
|
5
|
+
* Two deliberate shape decisions:
|
|
6
|
+
* - The TypeBox schema is a FLAT object with no root union. Several provider payload
|
|
7
|
+
* conversions rebuild tool schemas from top-level `properties` only, so a root
|
|
8
|
+
* `anyOf` would reach the model as an empty schema (same reasoning as
|
|
9
|
+
* `terminal/tools/monitor.ts`). Branch requirements are enforced in `execute`.
|
|
10
|
+
* - `delaySeconds` carries NO schema `minimum`/`maximum`. The contract clamps
|
|
11
|
+
* out-of-range integers instead of rejecting them, and schema bounds would make the
|
|
12
|
+
* clamp unreachable.
|
|
13
|
+
*
|
|
14
|
+
* Scheduler state is never mutated here: every effect goes through the injected
|
|
15
|
+
* `ScheduleWakeupSchedulerPort`.
|
|
16
|
+
*/
|
|
17
|
+
import { Type } from "typebox";
|
|
18
|
+
/** Model-visible tool name. */
|
|
19
|
+
export const SCHEDULE_WAKEUP_TOOL = "schedule_wakeup";
|
|
20
|
+
/** Inclusive bounds the executor clamps `delaySeconds` into. */
|
|
21
|
+
export const MIN_WAKEUP_DELAY_SECONDS = 60;
|
|
22
|
+
export const MAX_WAKEUP_DELAY_SECONDS = 3600;
|
|
23
|
+
export const SCHEDULE_WAKEUP_DESCRIPTION = `Schedule when to resume work in /loop dynamic mode. Always pass the \`prompt\` argument unless stopping. Call this as the last action before ending a dynamic loop iteration so the loop remains alive; call with \`stop: true\` to end the active dynamic loop immediately.
|
|
24
|
+
|
|
25
|
+
\`delaySeconds\` is clamped to ${MIN_WAKEUP_DELAY_SECONDS}-${MAX_WAKEUP_DELAY_SECONDS} seconds. For idle polling, normally use 1200-1800 seconds. Avoid choosing 300 seconds merely to poll: short polling repeatedly loses prompt-cache value on many API paths. When work is gated on observable terminal state, use \`monitor\` instead of sleeping or polling; inspect it with \`bash_output\` and stop the watcher with \`kill_bash\`. For delegated work, use the available \`task\` output and control tools and let task completion notifications wake the session. When a monitor or task notification is the primary wake source, the scheduled delay is only a fallback heartbeat.
|
|
26
|
+
|
|
27
|
+
For normal dynamic re-entry, set \`prompt\` to the complete original command, for example \`/loop check the deploy\`, preserving the user's text verbatim. Set \`noop: true\` only when this iteration found no actionable change; consecutive noop iterations are folded in the terminal view. Omit \`noop\` when stopping. Notify the user only when state changes in a way worth acting on, not once per tick.`;
|
|
28
|
+
export const scheduleWakeupSchema = Type.Object({
|
|
29
|
+
delaySeconds: Type.Optional(Type.Integer({
|
|
30
|
+
description: `Dynamic loop delay in seconds. Values are clamped to ${MIN_WAKEUP_DELAY_SECONDS}-${MAX_WAKEUP_DELAY_SECONDS}.`,
|
|
31
|
+
})),
|
|
32
|
+
reason: Type.String({
|
|
33
|
+
minLength: 1,
|
|
34
|
+
description: "Why this wakeup or stop is appropriate. Must not be blank.",
|
|
35
|
+
}),
|
|
36
|
+
prompt: Type.Optional(Type.String({
|
|
37
|
+
description: "Prompt to dispatch when the wakeup fires. Required unless stop is true; preserve the original /loop command verbatim for normal dynamic re-entry.",
|
|
38
|
+
})),
|
|
39
|
+
stop: Type.Optional(Type.Boolean({
|
|
40
|
+
description: "End the active dynamic loop immediately instead of scheduling another wakeup.",
|
|
41
|
+
})),
|
|
42
|
+
noop: Type.Optional(Type.Boolean({
|
|
43
|
+
description: "True when this iteration observed no actionable change. Consecutive noop iterations are folded in the terminal view. Omit when stopping.",
|
|
44
|
+
})),
|
|
45
|
+
}, { additionalProperties: false });
|
|
46
|
+
export const NO_ACTIVE_DYNAMIC_LOOP_ERROR = `${SCHEDULE_WAKEUP_TOOL} can only be used while a dynamic /loop is active.`;
|
|
47
|
+
export const FIXED_TICK_ERROR = `This is a fixed /loop tick; the recurring schedule re-arms automatically. Do not call ${SCHEDULE_WAKEUP_TOOL}.`;
|
|
48
|
+
export const MISSING_PROMPT_ERROR = "prompt is required and must be non-empty unless stop is true.";
|
|
49
|
+
export const MISSING_DELAY_ERROR = "delaySeconds is required unless stop is true.";
|
|
50
|
+
export const NON_INTEGER_DELAY_ERROR = "delaySeconds must be a finite integer number of seconds.";
|
|
51
|
+
export const BLANK_REASON_ERROR = "reason is required and must be non-empty.";
|
|
52
|
+
export const NOOP_WITH_STOP_ERROR = "noop must be omitted when stop is true.";
|
|
53
|
+
function clampDelaySeconds(requested) {
|
|
54
|
+
return Math.min(MAX_WAKEUP_DELAY_SECONDS, Math.max(MIN_WAKEUP_DELAY_SECONDS, requested));
|
|
55
|
+
}
|
|
56
|
+
function resolveDynamicLoopId(scheduler) {
|
|
57
|
+
const target = scheduler.getWakeupTarget();
|
|
58
|
+
if (target === null)
|
|
59
|
+
throw new Error(NO_ACTIVE_DYNAMIC_LOOP_ERROR);
|
|
60
|
+
if (target.kind === "fixed")
|
|
61
|
+
throw new Error(FIXED_TICK_ERROR);
|
|
62
|
+
return target.loopId;
|
|
63
|
+
}
|
|
64
|
+
function requireReason(reason) {
|
|
65
|
+
const trimmed = typeof reason === "string" ? reason.trim() : "";
|
|
66
|
+
if (trimmed.length === 0)
|
|
67
|
+
throw new Error(BLANK_REASON_ERROR);
|
|
68
|
+
return trimmed;
|
|
69
|
+
}
|
|
70
|
+
function collectIgnoredFields(params) {
|
|
71
|
+
const ignored = [];
|
|
72
|
+
if (params.delaySeconds !== undefined)
|
|
73
|
+
ignored.push("delaySeconds");
|
|
74
|
+
if (params.prompt !== undefined)
|
|
75
|
+
ignored.push("prompt");
|
|
76
|
+
return ignored;
|
|
77
|
+
}
|
|
78
|
+
async function executeStop(scheduler, params) {
|
|
79
|
+
if (params.noop !== undefined)
|
|
80
|
+
throw new Error(NOOP_WITH_STOP_ERROR);
|
|
81
|
+
const reason = requireReason(params.reason);
|
|
82
|
+
const loopId = resolveDynamicLoopId(scheduler);
|
|
83
|
+
const ignoredFields = collectIgnoredFields(params);
|
|
84
|
+
const { endedAt } = await scheduler.stopDynamicLoop({ loopId, reason });
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: "text", text: `Stopped dynamic loop ${loopId}.` }],
|
|
87
|
+
details: {
|
|
88
|
+
ok: true,
|
|
89
|
+
action: "stopped",
|
|
90
|
+
loopId,
|
|
91
|
+
terminalReason: "stopped",
|
|
92
|
+
reason,
|
|
93
|
+
endedAt,
|
|
94
|
+
ignoredFields,
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async function executeSchedule(scheduler, params) {
|
|
99
|
+
const reason = requireReason(params.reason);
|
|
100
|
+
const requestedDelaySeconds = params.delaySeconds;
|
|
101
|
+
if (requestedDelaySeconds === undefined)
|
|
102
|
+
throw new Error(MISSING_DELAY_ERROR);
|
|
103
|
+
if (!Number.isInteger(requestedDelaySeconds))
|
|
104
|
+
throw new Error(NON_INTEGER_DELAY_ERROR);
|
|
105
|
+
const prompt = params.prompt;
|
|
106
|
+
if (prompt === undefined || prompt.trim().length === 0)
|
|
107
|
+
throw new Error(MISSING_PROMPT_ERROR);
|
|
108
|
+
const loopId = resolveDynamicLoopId(scheduler);
|
|
109
|
+
const delaySeconds = clampDelaySeconds(requestedDelaySeconds);
|
|
110
|
+
const clamped = delaySeconds !== requestedDelaySeconds;
|
|
111
|
+
const noop = params.noop === true;
|
|
112
|
+
const outcome = await scheduler.scheduleWakeup({
|
|
113
|
+
loopId,
|
|
114
|
+
requestedDelaySeconds,
|
|
115
|
+
delaySeconds,
|
|
116
|
+
reason,
|
|
117
|
+
prompt,
|
|
118
|
+
noop,
|
|
119
|
+
});
|
|
120
|
+
const text = clamped
|
|
121
|
+
? `Scheduled loop ${loopId} in ${delaySeconds}s; requested ${requestedDelaySeconds}s was clamped to the supported ${MIN_WAKEUP_DELAY_SECONDS}-${MAX_WAKEUP_DELAY_SECONDS}s range.`
|
|
122
|
+
: `Scheduled loop ${loopId} in ${delaySeconds}s.`;
|
|
123
|
+
return {
|
|
124
|
+
content: [{ type: "text", text }],
|
|
125
|
+
details: {
|
|
126
|
+
ok: true,
|
|
127
|
+
action: "scheduled",
|
|
128
|
+
loopId,
|
|
129
|
+
wakeupId: outcome.wakeupId,
|
|
130
|
+
...(outcome.replacedWakeupId === undefined ? {} : { replacedWakeupId: outcome.replacedWakeupId }),
|
|
131
|
+
requestedDelaySeconds,
|
|
132
|
+
delaySeconds,
|
|
133
|
+
clamped,
|
|
134
|
+
dueAt: outcome.dueAt,
|
|
135
|
+
reason,
|
|
136
|
+
prompt,
|
|
137
|
+
noop,
|
|
138
|
+
noopStreak: outcome.noopStreak,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
export function registerLoopTools(pi, deps) {
|
|
143
|
+
pi.registerTool({
|
|
144
|
+
name: SCHEDULE_WAKEUP_TOOL,
|
|
145
|
+
label: "Schedule Wakeup",
|
|
146
|
+
description: SCHEDULE_WAKEUP_DESCRIPTION,
|
|
147
|
+
parameters: scheduleWakeupSchema,
|
|
148
|
+
// Sequential so two concurrent model tool calls cannot race scheduler state.
|
|
149
|
+
executionMode: "sequential",
|
|
150
|
+
async execute(_toolCallId, params) {
|
|
151
|
+
return params.stop === true ? executeStop(deps.scheduler, params) : executeSchedule(deps.scheduler, params);
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../../../../../src/core/extensions/builtin/loop/tools.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The `schedule_wakeup` model tool: the ONLY model-callable surface of the `/loop`\n * extension. It lets a dynamic (self-paced) loop pick its own next delay, or end itself.\n *\n * Two deliberate shape decisions:\n * - The TypeBox schema is a FLAT object with no root union. Several provider payload\n * conversions rebuild tool schemas from top-level `properties` only, so a root\n * `anyOf` would reach the model as an empty schema (same reasoning as\n * `terminal/tools/monitor.ts`). Branch requirements are enforced in `execute`.\n * - `delaySeconds` carries NO schema `minimum`/`maximum`. The contract clamps\n * out-of-range integers instead of rejecting them, and schema bounds would make the\n * clamp unreachable.\n *\n * Scheduler state is never mutated here: every effect goes through the injected\n * `ScheduleWakeupSchedulerPort`.\n */\n\nimport { Type } from \"typebox\";\nimport type { AgentToolResult, ExtensionAPI } from \"../../types.ts\";\nimport type { EpochMs, LoopId, WakeupId } from \"./types.ts\";\n\n/** Model-visible tool name. */\nexport const SCHEDULE_WAKEUP_TOOL = \"schedule_wakeup\";\n\n/** Inclusive bounds the executor clamps `delaySeconds` into. */\nexport const MIN_WAKEUP_DELAY_SECONDS = 60;\nexport const MAX_WAKEUP_DELAY_SECONDS = 3600;\n\nexport const SCHEDULE_WAKEUP_DESCRIPTION = `Schedule when to resume work in /loop dynamic mode. Always pass the \\`prompt\\` argument unless stopping. Call this as the last action before ending a dynamic loop iteration so the loop remains alive; call with \\`stop: true\\` to end the active dynamic loop immediately.\n\n\\`delaySeconds\\` is clamped to ${MIN_WAKEUP_DELAY_SECONDS}-${MAX_WAKEUP_DELAY_SECONDS} seconds. For idle polling, normally use 1200-1800 seconds. Avoid choosing 300 seconds merely to poll: short polling repeatedly loses prompt-cache value on many API paths. When work is gated on observable terminal state, use \\`monitor\\` instead of sleeping or polling; inspect it with \\`bash_output\\` and stop the watcher with \\`kill_bash\\`. For delegated work, use the available \\`task\\` output and control tools and let task completion notifications wake the session. When a monitor or task notification is the primary wake source, the scheduled delay is only a fallback heartbeat.\n\nFor normal dynamic re-entry, set \\`prompt\\` to the complete original command, for example \\`/loop check the deploy\\`, preserving the user's text verbatim. Set \\`noop: true\\` only when this iteration found no actionable change; consecutive noop iterations are folded in the terminal view. Omit \\`noop\\` when stopping. Notify the user only when state changes in a way worth acting on, not once per tick.`;\n\nexport const scheduleWakeupSchema = Type.Object(\n\t{\n\t\tdelaySeconds: Type.Optional(\n\t\t\tType.Integer({\n\t\t\t\tdescription: `Dynamic loop delay in seconds. Values are clamped to ${MIN_WAKEUP_DELAY_SECONDS}-${MAX_WAKEUP_DELAY_SECONDS}.`,\n\t\t\t}),\n\t\t),\n\t\treason: Type.String({\n\t\t\tminLength: 1,\n\t\t\tdescription: \"Why this wakeup or stop is appropriate. Must not be blank.\",\n\t\t}),\n\t\tprompt: Type.Optional(\n\t\t\tType.String({\n\t\t\t\tdescription:\n\t\t\t\t\t\"Prompt to dispatch when the wakeup fires. Required unless stop is true; preserve the original /loop command verbatim for normal dynamic re-entry.\",\n\t\t\t}),\n\t\t),\n\t\tstop: Type.Optional(\n\t\t\tType.Boolean({\n\t\t\t\tdescription: \"End the active dynamic loop immediately instead of scheduling another wakeup.\",\n\t\t\t}),\n\t\t),\n\t\tnoop: Type.Optional(\n\t\t\tType.Boolean({\n\t\t\t\tdescription:\n\t\t\t\t\t\"True when this iteration observed no actionable change. Consecutive noop iterations are folded in the terminal view. Omit when stopping.\",\n\t\t\t}),\n\t\t),\n\t},\n\t{ additionalProperties: false },\n);\n\n/** Runtime-validated parameter shape; the schema itself stays flat and permissive. */\nexport interface ScheduleWakeupParams {\n\treadonly delaySeconds?: number;\n\treadonly reason: string;\n\treadonly prompt?: string;\n\treadonly stop?: boolean;\n\treadonly noop?: boolean;\n}\n\nexport interface ScheduleWakeupScheduledDetails {\n\treadonly ok: true;\n\treadonly action: \"scheduled\";\n\treadonly loopId: LoopId;\n\treadonly wakeupId: WakeupId;\n\treadonly replacedWakeupId?: WakeupId;\n\n\t/** Integer the model asked for, before the clamp. */\n\treadonly requestedDelaySeconds: number;\n\t/** Effective delay after clamping to `[60, 3600]`. */\n\treadonly delaySeconds: number;\n\treadonly clamped: boolean;\n\treadonly dueAt: EpochMs;\n\n\treadonly reason: string;\n\t/** Preserved verbatim after the non-blank check. */\n\treadonly prompt: string;\n\treadonly noop: boolean;\n\treadonly noopStreak: number;\n}\n\nexport interface ScheduleWakeupStoppedDetails {\n\treadonly ok: true;\n\treadonly action: \"stopped\";\n\treadonly loopId: LoopId;\n\treadonly terminalReason: \"stopped\";\n\treadonly reason: string;\n\treadonly endedAt: EpochMs;\n\t/** Fields accepted for model compatibility but not applied. */\n\treadonly ignoredFields: readonly ScheduleWakeupIgnorableField[];\n}\n\nexport type ScheduleWakeupIgnorableField = \"delaySeconds\" | \"prompt\";\n\nexport type ScheduleWakeupDetails = ScheduleWakeupScheduledDetails | ScheduleWakeupStoppedDetails;\n\n/**\n * Which loop, if any, this tool call belongs to. `null` means no loop context at all;\n * a `fixed` target means the call came from a fixed tick, whose schedule re-arms itself.\n */\nexport type ScheduleWakeupTarget = { readonly kind: \"dynamic\" | \"fixed\"; readonly loopId: LoopId } | null;\n\nexport interface ScheduleWakeupRequest {\n\treadonly loopId: LoopId;\n\treadonly requestedDelaySeconds: number;\n\treadonly delaySeconds: number;\n\treadonly reason: string;\n\treadonly prompt: string;\n\treadonly noop: boolean;\n}\n\nexport interface ScheduleWakeupOutcome {\n\treadonly wakeupId: WakeupId;\n\t/** Set when this schedule replaced a still-pending wakeup for the same loop. */\n\treadonly replacedWakeupId?: WakeupId;\n\treadonly dueAt: EpochMs;\n\treadonly noopStreak: number;\n}\n\nexport interface StopDynamicLoopRequest {\n\treadonly loopId: LoopId;\n\treadonly reason: string;\n}\n\nexport interface StopDynamicLoopOutcome {\n\treadonly endedAt: EpochMs;\n}\n\n/**\n * Minimal scheduler surface this tool needs. Declared locally so the tool module stays\n * independent of the scheduler implementation and testable with a fake.\n */\nexport interface ScheduleWakeupSchedulerPort {\n\t/** Loop the current turn is attributed to, resolved at call time. */\n\tgetWakeupTarget(): ScheduleWakeupTarget;\n\tscheduleWakeup(request: ScheduleWakeupRequest): Promise<ScheduleWakeupOutcome>;\n\tstopDynamicLoop(request: StopDynamicLoopRequest): Promise<StopDynamicLoopOutcome>;\n}\n\nexport interface LoopToolRegistrationDeps {\n\treadonly scheduler: ScheduleWakeupSchedulerPort;\n}\n\nexport const NO_ACTIVE_DYNAMIC_LOOP_ERROR = `${SCHEDULE_WAKEUP_TOOL} can only be used while a dynamic /loop is active.`;\nexport const FIXED_TICK_ERROR = `This is a fixed /loop tick; the recurring schedule re-arms automatically. Do not call ${SCHEDULE_WAKEUP_TOOL}.`;\nexport const MISSING_PROMPT_ERROR = \"prompt is required and must be non-empty unless stop is true.\";\nexport const MISSING_DELAY_ERROR = \"delaySeconds is required unless stop is true.\";\nexport const NON_INTEGER_DELAY_ERROR = \"delaySeconds must be a finite integer number of seconds.\";\nexport const BLANK_REASON_ERROR = \"reason is required and must be non-empty.\";\nexport const NOOP_WITH_STOP_ERROR = \"noop must be omitted when stop is true.\";\n\nfunction clampDelaySeconds(requested: number): number {\n\treturn Math.min(MAX_WAKEUP_DELAY_SECONDS, Math.max(MIN_WAKEUP_DELAY_SECONDS, requested));\n}\n\nfunction resolveDynamicLoopId(scheduler: ScheduleWakeupSchedulerPort): LoopId {\n\tconst target = scheduler.getWakeupTarget();\n\tif (target === null) throw new Error(NO_ACTIVE_DYNAMIC_LOOP_ERROR);\n\tif (target.kind === \"fixed\") throw new Error(FIXED_TICK_ERROR);\n\treturn target.loopId;\n}\n\nfunction requireReason(reason: string): string {\n\tconst trimmed = typeof reason === \"string\" ? reason.trim() : \"\";\n\tif (trimmed.length === 0) throw new Error(BLANK_REASON_ERROR);\n\treturn trimmed;\n}\n\nfunction collectIgnoredFields(params: ScheduleWakeupParams): ScheduleWakeupIgnorableField[] {\n\tconst ignored: ScheduleWakeupIgnorableField[] = [];\n\tif (params.delaySeconds !== undefined) ignored.push(\"delaySeconds\");\n\tif (params.prompt !== undefined) ignored.push(\"prompt\");\n\treturn ignored;\n}\n\nasync function executeStop(\n\tscheduler: ScheduleWakeupSchedulerPort,\n\tparams: ScheduleWakeupParams,\n): Promise<AgentToolResult<ScheduleWakeupDetails>> {\n\tif (params.noop !== undefined) throw new Error(NOOP_WITH_STOP_ERROR);\n\tconst reason = requireReason(params.reason);\n\tconst loopId = resolveDynamicLoopId(scheduler);\n\tconst ignoredFields = collectIgnoredFields(params);\n\tconst { endedAt } = await scheduler.stopDynamicLoop({ loopId, reason });\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text: `Stopped dynamic loop ${loopId}.` }],\n\t\tdetails: {\n\t\t\tok: true,\n\t\t\taction: \"stopped\",\n\t\t\tloopId,\n\t\t\tterminalReason: \"stopped\",\n\t\t\treason,\n\t\t\tendedAt,\n\t\t\tignoredFields,\n\t\t},\n\t};\n}\n\nasync function executeSchedule(\n\tscheduler: ScheduleWakeupSchedulerPort,\n\tparams: ScheduleWakeupParams,\n): Promise<AgentToolResult<ScheduleWakeupDetails>> {\n\tconst reason = requireReason(params.reason);\n\tconst requestedDelaySeconds = params.delaySeconds;\n\tif (requestedDelaySeconds === undefined) throw new Error(MISSING_DELAY_ERROR);\n\tif (!Number.isInteger(requestedDelaySeconds)) throw new Error(NON_INTEGER_DELAY_ERROR);\n\tconst prompt = params.prompt;\n\tif (prompt === undefined || prompt.trim().length === 0) throw new Error(MISSING_PROMPT_ERROR);\n\n\tconst loopId = resolveDynamicLoopId(scheduler);\n\tconst delaySeconds = clampDelaySeconds(requestedDelaySeconds);\n\tconst clamped = delaySeconds !== requestedDelaySeconds;\n\tconst noop = params.noop === true;\n\n\tconst outcome = await scheduler.scheduleWakeup({\n\t\tloopId,\n\t\trequestedDelaySeconds,\n\t\tdelaySeconds,\n\t\treason,\n\t\tprompt,\n\t\tnoop,\n\t});\n\n\tconst text = clamped\n\t\t? `Scheduled loop ${loopId} in ${delaySeconds}s; requested ${requestedDelaySeconds}s was clamped to the supported ${MIN_WAKEUP_DELAY_SECONDS}-${MAX_WAKEUP_DELAY_SECONDS}s range.`\n\t\t: `Scheduled loop ${loopId} in ${delaySeconds}s.`;\n\n\treturn {\n\t\tcontent: [{ type: \"text\" as const, text }],\n\t\tdetails: {\n\t\t\tok: true,\n\t\t\taction: \"scheduled\",\n\t\t\tloopId,\n\t\t\twakeupId: outcome.wakeupId,\n\t\t\t...(outcome.replacedWakeupId === undefined ? {} : { replacedWakeupId: outcome.replacedWakeupId }),\n\t\t\trequestedDelaySeconds,\n\t\t\tdelaySeconds,\n\t\t\tclamped,\n\t\t\tdueAt: outcome.dueAt,\n\t\t\treason,\n\t\t\tprompt,\n\t\t\tnoop,\n\t\t\tnoopStreak: outcome.noopStreak,\n\t\t},\n\t};\n}\n\nexport function registerLoopTools(pi: ExtensionAPI, deps: LoopToolRegistrationDeps): void {\n\tpi.registerTool({\n\t\tname: SCHEDULE_WAKEUP_TOOL,\n\t\tlabel: \"Schedule Wakeup\",\n\t\tdescription: SCHEDULE_WAKEUP_DESCRIPTION,\n\t\tparameters: scheduleWakeupSchema,\n\t\t// Sequential so two concurrent model tool calls cannot race scheduler state.\n\t\texecutionMode: \"sequential\",\n\t\tasync execute(_toolCallId, params): Promise<AgentToolResult<ScheduleWakeupDetails>> {\n\t\t\treturn params.stop === true ? executeStop(deps.scheduler, params) : executeSchedule(deps.scheduler, params);\n\t\t},\n\t});\n}\n"]}
|