@ashx-j/lunr 0.2.26 → 0.2.28
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 +25 -1
- package/LICENSE +22 -0
- package/dist/builtin-extensions/ashxj-thinking.d.ts +1 -1
- package/dist/builtin-extensions/ashxj-thinking.d.ts.map +1 -1
- package/dist/builtin-extensions/ashxj-thinking.js +1 -1
- package/dist/builtin-extensions/ashxj-thinking.js.map +1 -1
- package/dist/builtin-extensions/ashxj-tui.d.ts +11 -1
- package/dist/builtin-extensions/ashxj-tui.d.ts.map +1 -1
- package/dist/builtin-extensions/ashxj-tui.js +6 -10
- package/dist/builtin-extensions/ashxj-tui.js.map +1 -1
- package/dist/builtin-extensions/lunr-cron.d.ts +4 -5
- package/dist/builtin-extensions/lunr-cron.d.ts.map +1 -1
- package/dist/builtin-extensions/lunr-cron.js +94 -108
- package/dist/builtin-extensions/lunr-cron.js.map +1 -1
- package/dist/builtin-extensions/lunr-local-providers/local-servers.d.ts.map +1 -1
- package/dist/builtin-extensions/lunr-local-providers/local-servers.js +3 -1
- package/dist/builtin-extensions/lunr-local-providers/local-servers.js.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/accounting.d.ts.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/accounting.js +5 -2
- package/dist/builtin-extensions/narumiruna-pi-goal/src/accounting.js.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.d.ts.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.js +2 -2
- package/dist/builtin-extensions/narumiruna-pi-goal/src/commands.js.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.d.ts.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/goal.js.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.d.ts.map +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.js +1 -1
- package/dist/builtin-extensions/narumiruna-pi-goal/src/runtime.js.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-intercom/index.js +2 -2
- package/dist/builtin-extensions/pi-intercom/index.js.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/bemol-extension/index.d.ts +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/bemol-extension/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/bemol-extension/index.js +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/bemol-extension/index.js.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/index.js +1 -15
- package/dist/builtin-extensions/pi-lsp-extension/src/index.js.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/lsp-client.d.ts +6 -0
- package/dist/builtin-extensions/pi-lsp-extension/src/lsp-client.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/lsp-client.js +187 -132
- package/dist/builtin-extensions/pi-lsp-extension/src/lsp-client.js.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/lsp-manager.d.ts +8 -0
- package/dist/builtin-extensions/pi-lsp-extension/src/lsp-manager.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/lsp-manager.js +142 -112
- package/dist/builtin-extensions/pi-lsp-extension/src/lsp-manager.js.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/tools/code-rewrite.d.ts +12 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/tools/code-rewrite.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/tools/code-rewrite.js +14 -7
- package/dist/builtin-extensions/pi-lsp-extension/src/tools/code-rewrite.js.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/rewrite-engine.d.ts +11 -3
- package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/rewrite-engine.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/rewrite-engine.js +67 -41
- package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/rewrite-engine.js.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/search-engine.d.ts +4 -2
- package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/search-engine.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/search-engine.js +3 -0
- package/dist/builtin-extensions/pi-lsp-extension/src/tree-sitter/search-engine.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/commands.d.ts +2 -1
- package/dist/builtin-extensions/pi-mcp-adapter/commands.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/commands.js +6 -6
- package/dist/builtin-extensions/pi-mcp-adapter/commands.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/config.d.ts +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/config.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/config.js +11 -5
- package/dist/builtin-extensions/pi-mcp-adapter/config.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tool-executor.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tool-executor.js +2 -2
- package/dist/builtin-extensions/pi-mcp-adapter/direct-tool-executor.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/host-html-template.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/host-html-template.js +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/host-html-template.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/index.js +48 -40
- package/dist/builtin-extensions/pi-mcp-adapter/index.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/init.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/init.js +3 -1
- package/dist/builtin-extensions/pi-mcp-adapter/init.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/mcp-auth-flow.d.ts +12 -40
- package/dist/builtin-extensions/pi-mcp-adapter/mcp-auth-flow.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/mcp-auth-flow.js +166 -235
- package/dist/builtin-extensions/pi-mcp-adapter/mcp-auth-flow.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/mcp-callback-server.d.ts +5 -0
- package/dist/builtin-extensions/pi-mcp-adapter/mcp-callback-server.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/mcp-callback-server.js +43 -13
- package/dist/builtin-extensions/pi-mcp-adapter/mcp-callback-server.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/mcp-oauth-provider.d.ts +14 -3
- package/dist/builtin-extensions/pi-mcp-adapter/mcp-oauth-provider.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/mcp-oauth-provider.js +73 -21
- package/dist/builtin-extensions/pi-mcp-adapter/mcp-oauth-provider.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/proxy-modes.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/proxy-modes.js +4 -4
- package/dist/builtin-extensions/pi-mcp-adapter/proxy-modes.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/server-manager.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/server-manager.js +2 -2
- package/dist/builtin-extensions/pi-mcp-adapter/server-manager.js.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/state.d.ts +2 -0
- package/dist/builtin-extensions/pi-mcp-adapter/state.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-mcp-adapter/state.js.map +1 -1
- package/dist/builtin-extensions/pi-ollama-cloud/index.d.ts +1 -1
- package/dist/builtin-extensions/pi-ollama-cloud/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-ollama-cloud/index.js +1 -1
- package/dist/builtin-extensions/pi-ollama-cloud/index.js.map +1 -1
- package/dist/builtin-extensions/pi-ollama-cloud/models.d.ts +11 -5
- package/dist/builtin-extensions/pi-ollama-cloud/models.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-ollama-cloud/models.js +12 -32
- package/dist/builtin-extensions/pi-ollama-cloud/models.js.map +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/index.js +5 -3
- package/dist/builtin-extensions/pi-prompt-template-model/index.js.map +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/prompt-loader.d.ts +2 -1
- package/dist/builtin-extensions/pi-prompt-template-model/prompt-loader.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/prompt-loader.js +21 -12
- package/dist/builtin-extensions/pi-prompt-template-model/prompt-loader.js.map +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/tool-manager.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-prompt-template-model/tool-manager.js +7 -24
- package/dist/builtin-extensions/pi-prompt-template-model/tool-manager.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js +39 -77
- package/dist/builtin-extensions/pi-subagents/src/extension/index.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.d.ts +4 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.js +2 -0
- package/dist/builtin-extensions/pi-subagents/src/runs/background/chain-root-attachment.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts +6 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js +17 -31
- package/dist/builtin-extensions/pi-subagents/src/runs/background/notify.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js +7 -2
- package/dist/builtin-extensions/pi-subagents/src/runs/background/result-watcher.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts +5 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js +78 -42
- package/dist/builtin-extensions/pi-subagents/src/runs/background/subagent-runner.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js +8 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/chain-execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts +0 -3
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js +134 -28
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/execution.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js +26 -5
- package/dist/builtin-extensions/pi-subagents/src/runs/foreground/subagent-executor.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/model-fallback.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts +7 -8
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js +60 -32
- package/dist/builtin-extensions/pi-subagents/src/runs/shared/parallel-utils.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/session-tokens.d.ts +7 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/session-tokens.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/session-tokens.js +70 -38
- package/dist/builtin-extensions/pi-subagents/src/shared/session-tokens.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts +9 -0
- package/dist/builtin-extensions/pi-subagents/src/shared/types.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/types.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/utils.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/utils.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/shared/utils.js +15 -5
- package/dist/builtin-extensions/pi-subagents/src/shared/utils.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js +4 -3
- package/dist/builtin-extensions/pi-subagents/src/slash/prompt-workflows.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js +13 -62
- package/dist/builtin-extensions/pi-subagents/src/slash/slash-commands.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js +18 -9
- package/dist/builtin-extensions/pi-subagents/src/tui/render.js.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/review.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-subagents/src/watchdog/review.js +3 -3
- package/dist/builtin-extensions/pi-subagents/src/watchdog/review.js.map +1 -1
- package/dist/builtin-extensions/pi-tps.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-tps.js +28 -17
- package/dist/builtin-extensions/pi-tps.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/activity.d.ts +1 -0
- package/dist/builtin-extensions/pi-web-access/activity.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/activity.js +27 -5
- package/dist/builtin-extensions/pi-web-access/activity.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/bounded-body.d.ts +3 -0
- package/dist/builtin-extensions/pi-web-access/bounded-body.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-web-access/bounded-body.js +43 -0
- package/dist/builtin-extensions/pi-web-access/bounded-body.js.map +1 -0
- package/dist/builtin-extensions/pi-web-access/content-page.d.ts +24 -0
- package/dist/builtin-extensions/pi-web-access/content-page.d.ts.map +1 -0
- package/dist/builtin-extensions/pi-web-access/content-page.js +57 -0
- package/dist/builtin-extensions/pi-web-access/content-page.js.map +1 -0
- package/dist/builtin-extensions/pi-web-access/extract.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/extract.js +7 -2
- package/dist/builtin-extensions/pi-web-access/extract.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/github-extract.d.ts +0 -1
- package/dist/builtin-extensions/pi-web-access/github-extract.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/github-extract.js +147 -36
- package/dist/builtin-extensions/pi-web-access/github-extract.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/index.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/index.js +48 -73
- package/dist/builtin-extensions/pi-web-access/index.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/session-cleanup.d.ts +12 -4
- package/dist/builtin-extensions/pi-web-access/session-cleanup.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/session-cleanup.js +35 -12
- package/dist/builtin-extensions/pi-web-access/session-cleanup.js.map +1 -1
- package/dist/builtin-extensions/pi-web-access/storage.d.ts +9 -6
- package/dist/builtin-extensions/pi-web-access/storage.d.ts.map +1 -1
- package/dist/builtin-extensions/pi-web-access/storage.js +36 -24
- package/dist/builtin-extensions/pi-web-access/storage.js.map +1 -1
- package/dist/builtin-extensions/simple-pi-memory.d.ts +1 -1
- package/dist/builtin-extensions/simple-pi-memory.d.ts.map +1 -1
- package/dist/builtin-extensions/simple-pi-memory.js +1 -1
- package/dist/builtin-extensions/simple-pi-memory.js.map +1 -1
- package/dist/catalog/models.json +1 -1
- package/dist/catalog/providers/google-vertex.json +1 -1
- package/dist/catalog/providers/google.json +1 -1
- package/dist/catalog/publication.json +3 -3
- package/dist/cli/install-cli.d.ts.map +1 -1
- package/dist/cli/install-cli.js +38 -30
- package/dist/cli/install-cli.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts +13 -0
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +75 -11
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +46 -21
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +344 -126
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/browser/runtime.d.ts +2 -0
- package/dist/core/browser/runtime.d.ts.map +1 -1
- package/dist/core/browser/runtime.js +80 -45
- package/dist/core/browser/runtime.js.map +1 -1
- package/dist/core/browser/setup.d.ts.map +1 -1
- package/dist/core/browser/setup.js +12 -1
- package/dist/core/browser/setup.js.map +1 -1
- package/dist/core/cache-stats.d.ts +1 -1
- package/dist/core/cache-stats.d.ts.map +1 -1
- package/dist/core/cache-stats.js +1 -1
- package/dist/core/cache-stats.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +2 -1
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +1 -0
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +5 -11
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +38 -92
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/computer-use/adapter.d.ts.map +1 -1
- package/dist/core/computer-use/adapter.js +7 -2
- package/dist/core/computer-use/adapter.js.map +1 -1
- package/dist/core/computer-use/image.d.ts.map +1 -1
- package/dist/core/computer-use/image.js +17 -5
- package/dist/core/computer-use/image.js.map +1 -1
- package/dist/core/computer-use/schemas.d.ts.map +1 -1
- package/dist/core/computer-use/schemas.js +23 -6
- package/dist/core/computer-use/schemas.js.map +1 -1
- package/dist/core/computer-use/workflow.d.ts.map +1 -1
- package/dist/core/computer-use/workflow.js +459 -119
- package/dist/core/computer-use/workflow.js.map +1 -1
- package/dist/core/context-breakdown.d.ts +2 -2
- package/dist/core/context-breakdown.d.ts.map +1 -1
- package/dist/core/context-breakdown.js +22 -20
- package/dist/core/context-breakdown.js.map +1 -1
- package/dist/core/cron/jobs.d.ts +26 -8
- package/dist/core/cron/jobs.d.ts.map +1 -1
- package/dist/core/cron/jobs.js +357 -232
- package/dist/core/cron/jobs.js.map +1 -1
- package/dist/core/cron/origin-context.d.ts +1 -0
- package/dist/core/cron/origin-context.d.ts.map +1 -1
- package/dist/core/cron/origin-context.js.map +1 -1
- package/dist/core/cron/scheduler.d.ts +22 -17
- package/dist/core/cron/scheduler.d.ts.map +1 -1
- package/dist/core/cron/scheduler.js +153 -73
- package/dist/core/cron/scheduler.js.map +1 -1
- 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/runner.d.ts +1 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +8 -0
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/types.d.ts +11 -1
- 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 +4 -0
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +22 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/large-subagent-launch.d.ts +1 -1
- package/dist/core/large-subagent-launch.d.ts.map +1 -1
- package/dist/core/large-subagent-launch.js +7 -7
- package/dist/core/large-subagent-launch.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 +5 -0
- package/dist/core/model-registry.js.map +1 -1
- package/dist/core/model-runtime.d.ts +4 -1
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +40 -4
- 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.map +1 -1
- package/dist/core/permissions.d.ts +3 -3
- package/dist/core/permissions.d.ts.map +1 -1
- package/dist/core/permissions.js +11 -35
- package/dist/core/permissions.js.map +1 -1
- package/dist/core/plan-mode.d.ts +5 -13
- package/dist/core/plan-mode.d.ts.map +1 -1
- package/dist/core/plan-mode.js +126 -116
- package/dist/core/plan-mode.js.map +1 -1
- package/dist/core/project-trust.d.ts.map +1 -1
- package/dist/core/project-trust.js +1 -1
- package/dist/core/project-trust.js.map +1 -1
- package/dist/core/prompt-resource-bridge.d.ts +12 -0
- package/dist/core/prompt-resource-bridge.d.ts.map +1 -0
- package/dist/core/prompt-resource-bridge.js +9 -0
- package/dist/core/prompt-resource-bridge.js.map +1 -0
- package/dist/core/protected-targets.d.ts +6 -0
- package/dist/core/protected-targets.d.ts.map +1 -0
- package/dist/core/protected-targets.js +45 -0
- package/dist/core/protected-targets.js.map +1 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +7 -0
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/rollback.d.ts +19 -3
- package/dist/core/rollback.d.ts.map +1 -1
- package/dist/core/rollback.js +172 -56
- package/dist/core/rollback.js.map +1 -1
- package/dist/core/runtime-credentials.d.ts +3 -0
- package/dist/core/runtime-credentials.d.ts.map +1 -1
- package/dist/core/runtime-credentials.js +14 -1
- package/dist/core/runtime-credentials.js.map +1 -1
- package/dist/core/sdk.d.ts +3 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager.d.ts +11 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +111 -33
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/session-ownership.d.ts.map +1 -1
- package/dist/core/session-ownership.js +8 -3
- package/dist/core/session-ownership.js.map +1 -1
- package/dist/core/session-retention.d.ts.map +1 -1
- package/dist/core/session-retention.js +18 -7
- package/dist/core/session-retention.js.map +1 -1
- package/dist/core/session-startup-settings.d.ts +10 -0
- package/dist/core/session-startup-settings.d.ts.map +1 -0
- package/dist/core/session-startup-settings.js +70 -0
- package/dist/core/session-startup-settings.js.map +1 -0
- package/dist/core/subscriptions.d.ts +16 -7
- package/dist/core/subscriptions.d.ts.map +1 -1
- package/dist/core/subscriptions.js +96 -55
- package/dist/core/subscriptions.js.map +1 -1
- package/dist/core/trust-manager.d.ts +2 -1
- package/dist/core/trust-manager.d.ts.map +1 -1
- package/dist/core/trust-manager.js +6 -1
- package/dist/core/trust-manager.js.map +1 -1
- package/dist/core/update-check.d.ts.map +1 -1
- package/dist/core/update-check.js.map +1 -1
- package/dist/core/usage-accounting.d.ts +47 -0
- package/dist/core/usage-accounting.d.ts.map +1 -0
- package/dist/core/usage-accounting.js +205 -0
- package/dist/core/usage-accounting.js.map +1 -0
- package/dist/core/usage-adapters/kimi-coding.d.ts +2 -2
- package/dist/core/usage-adapters/kimi-coding.d.ts.map +1 -1
- package/dist/core/usage-adapters/kimi-coding.js +2 -2
- package/dist/core/usage-adapters/kimi-coding.js.map +1 -1
- package/dist/core/usage-adapters/openai-codex.d.ts +3 -2
- package/dist/core/usage-adapters/openai-codex.d.ts.map +1 -1
- package/dist/core/usage-adapters/openai-codex.js +3 -3
- package/dist/core/usage-adapters/openai-codex.js.map +1 -1
- package/dist/core/usage-adapters/xai.d.ts +2 -2
- package/dist/core/usage-adapters/xai.d.ts.map +1 -1
- package/dist/core/usage-adapters/xai.js +3 -3
- package/dist/core/usage-adapters/xai.js.map +1 -1
- package/dist/core/usage-adapters/zai.d.ts +2 -2
- package/dist/core/usage-adapters/zai.d.ts.map +1 -1
- package/dist/core/usage-adapters/zai.js +2 -2
- package/dist/core/usage-adapters/zai.js.map +1 -1
- package/dist/core/usage-history.d.ts +1 -18
- package/dist/core/usage-history.d.ts.map +1 -1
- package/dist/core/usage-history.js +46 -114
- package/dist/core/usage-history.js.map +1 -1
- package/dist/core/usage-service.d.ts +9 -2
- package/dist/core/usage-service.d.ts.map +1 -1
- package/dist/core/usage-service.js +95 -50
- package/dist/core/usage-service.js.map +1 -1
- package/dist/gateway/agent-bridge.d.ts +9 -1
- package/dist/gateway/agent-bridge.d.ts.map +1 -1
- package/dist/gateway/agent-bridge.js +91 -37
- package/dist/gateway/agent-bridge.js.map +1 -1
- package/dist/gateway/buttons.d.ts +8 -1
- package/dist/gateway/buttons.d.ts.map +1 -1
- package/dist/gateway/buttons.js +76 -18
- package/dist/gateway/buttons.js.map +1 -1
- package/dist/gateway/commands.d.ts +1 -0
- package/dist/gateway/commands.d.ts.map +1 -1
- package/dist/gateway/commands.js +10 -4
- package/dist/gateway/commands.js.map +1 -1
- package/dist/gateway/cron.d.ts +12 -11
- package/dist/gateway/cron.d.ts.map +1 -1
- package/dist/gateway/cron.js +65 -56
- package/dist/gateway/cron.js.map +1 -1
- package/dist/gateway/index.d.ts.map +1 -1
- package/dist/gateway/index.js +2 -1
- package/dist/gateway/index.js.map +1 -1
- package/dist/gateway/mobile-commands.d.ts +1 -0
- package/dist/gateway/mobile-commands.d.ts.map +1 -1
- package/dist/gateway/mobile-commands.js +69 -15
- package/dist/gateway/mobile-commands.js.map +1 -1
- package/dist/gateway/presenter.d.ts +12 -2
- package/dist/gateway/presenter.d.ts.map +1 -1
- package/dist/gateway/presenter.js +73 -38
- package/dist/gateway/presenter.js.map +1 -1
- package/dist/gateway/router.d.ts +2 -2
- package/dist/gateway/router.d.ts.map +1 -1
- package/dist/gateway/router.js +17 -2
- package/dist/gateway/router.js.map +1 -1
- package/dist/gateway/service.d.ts +2 -0
- package/dist/gateway/service.d.ts.map +1 -1
- package/dist/gateway/service.js +153 -0
- package/dist/gateway/service.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +86 -33
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +6 -4
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/context-view.d.ts.map +1 -1
- package/dist/modes/interactive/components/context-view.js +2 -2
- package/dist/modes/interactive/components/context-view.js.map +1 -1
- package/dist/modes/interactive/components/copyable-text.d.ts.map +1 -1
- package/dist/modes/interactive/components/copyable-text.js +3 -2
- package/dist/modes/interactive/components/copyable-text.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +1 -1
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +8 -12
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/usage-view.d.ts +5 -0
- package/dist/modes/interactive/components/usage-view.d.ts.map +1 -1
- package/dist/modes/interactive/components/usage-view.js +21 -6
- package/dist/modes/interactive/components/usage-view.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +3 -7
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +151 -131
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/smooth-streaming.d.ts.map +1 -1
- package/dist/modes/interactive/smooth-streaming.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +2 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/node-runtime/{adapter-VFG7WNPX.js → adapter-247RTYLS.js} +4 -5
- package/dist/node-runtime/agent-session-services-RTUGP3XR.js +54 -0
- package/dist/node-runtime/{brave-UWDA3I22.js → brave-2AFFAXNN.js} +1 -1
- package/dist/node-runtime/{builtin-extensions-I2HIFEMY.js → builtin-extensions-N4EUD6SB.js} +8 -7
- package/dist/node-runtime/{chunk-QH6YL7VK.js → chunk-252ZVPPF.js} +3 -3
- package/dist/node-runtime/{chunk-PCEW76PM.js → chunk-2OIPYKLZ.js} +6 -6
- package/dist/node-runtime/{chunk-XBWPBZBN.js → chunk-2SWW7KMW.js} +1 -1
- package/dist/node-runtime/{chunk-UQUCFAJ3.js → chunk-2VIW3IMF.js} +40 -7
- package/dist/node-runtime/{chunk-UOEDI7RP.js → chunk-4KNKQGHM.js} +2 -2
- package/dist/node-runtime/chunk-4QRUZ64T.js +1203 -0
- package/dist/node-runtime/{chunk-FQMM2EZ2.js → chunk-64XTQNLA.js} +29 -7
- package/dist/node-runtime/{chunk-SPVDBUOS.js → chunk-6O3IW3IA.js} +2 -2
- package/dist/node-runtime/{chunk-5ZQXRPMC.js → chunk-6ZZHGUSA.js} +2 -2
- package/dist/node-runtime/{chunk-WIF24I7G.js → chunk-APVM5NEU.js} +149 -144
- package/dist/node-runtime/{chunk-LWYIVUGV.js → chunk-B2IBAXNX.js} +48 -31
- package/dist/node-runtime/{chunk-M3LZLQW2.js → chunk-BDRGN3L2.js} +1 -0
- package/dist/node-runtime/{chunk-LJTIVM7I.js → chunk-BMRXLQWQ.js} +464 -160
- package/dist/node-runtime/{chunk-GJ2RDBRL.js → chunk-BYC6RKCN.js} +4 -2
- package/dist/node-runtime/{chunk-T4DA4XE7.js → chunk-DFMPRAQQ.js} +162 -82
- package/dist/node-runtime/{chunk-SN2KSMPQ.js → chunk-DQSCFU7R.js} +13 -7
- package/dist/node-runtime/{chunk-UVWCDQYF.js → chunk-E5DQGNJI.js} +455 -278
- package/dist/node-runtime/{chunk-4SBQAI4T.js → chunk-EBLAZVSA.js} +2058 -73
- package/dist/node-runtime/{chunk-CSFI6YXL.js → chunk-ES6PBSIE.js} +1 -1
- package/dist/node-runtime/{chunk-IF24A3FH.js → chunk-EVM653DD.js} +4 -4
- package/dist/node-runtime/{chunk-HTFRRDSF.js → chunk-F2SMQ6ES.js} +3 -2
- package/dist/node-runtime/chunk-F2V2F4ZG.js +114 -0
- package/dist/node-runtime/{chunk-IKU6Y7DL.js → chunk-FE3WZI7F.js} +9 -6
- package/dist/node-runtime/{chunk-6VYZ5UFP.js → chunk-FLGEEH27.js} +1 -1
- package/dist/node-runtime/{chunk-5EFIU3OM.js → chunk-FTKEZL27.js} +1 -1
- package/dist/node-runtime/{chunk-RGQAEOM5.js → chunk-FYIFPFXE.js} +23 -6
- package/dist/node-runtime/{chunk-HMBGROQK.js → chunk-GLDXBBL6.js} +3 -3
- package/dist/node-runtime/{chunk-KWA4OC2J.js → chunk-H6YBCWKG.js} +1 -1
- package/dist/node-runtime/{chunk-2MI5OPMN.js → chunk-HNNHHJAB.js} +68 -50
- package/dist/node-runtime/{chunk-I74LN5JG.js → chunk-HUD2RFJW.js} +2 -2
- package/dist/node-runtime/{chunk-XHIRAZ26.js → chunk-IAZSK7KK.js} +1 -1
- package/dist/node-runtime/{chunk-GYIXXQAO.js → chunk-ICKGAKWX.js} +23 -59
- package/dist/node-runtime/{chunk-X3PGBLQ7.js → chunk-IHHTWSXN.js} +9 -9
- package/dist/node-runtime/{chunk-DAPBPCBP.js → chunk-IPP5BIV5.js} +25 -3
- package/dist/node-runtime/{chunk-QFWG2RVL.js → chunk-IXG6F4NT.js} +11 -0
- package/dist/node-runtime/{chunk-L24TPBS4.js → chunk-JEK35V2M.js} +45 -31
- package/dist/node-runtime/{chunk-RDDAZOMU.js → chunk-JL4HYQXK.js} +1 -1
- package/dist/node-runtime/{chunk-2HMIRK7W.js → chunk-LHV3V67B.js} +5 -5
- package/dist/node-runtime/{chunk-UAEOTO52.js → chunk-MNQXDFPV.js} +6 -2
- package/dist/node-runtime/{chunk-MPKSOJBT.js → chunk-NHATCSOI.js} +4 -7
- package/dist/node-runtime/{chunk-RMQZU2YU.js → chunk-NNNR3QIY.js} +1 -1
- package/dist/node-runtime/{chunk-2KIPT4HJ.js → chunk-PWHTSQC4.js} +83 -21
- package/dist/node-runtime/{chunk-RWTKOC3B.js → chunk-QBTJG3UM.js} +1 -1
- package/dist/node-runtime/{chunk-RCR3XAQY.js → chunk-QT3F6UEV.js} +233 -169
- package/dist/node-runtime/{chunk-GS42T6MX.js → chunk-QW3AAHWN.js} +1 -1
- package/dist/node-runtime/{chunk-DVQSHCVP.js → chunk-R4WSB2MQ.js} +5 -5
- package/dist/node-runtime/{chunk-B4HP6ZWM.js → chunk-RHCGYWO7.js} +1 -1
- package/dist/node-runtime/{chunk-2CFDTH5B.js → chunk-RLGY2QIL.js} +45 -59
- package/dist/node-runtime/{chunk-CDVHZOOK.js → chunk-RY3FJ6WI.js} +29 -27
- package/dist/node-runtime/{chunk-KKUTQQV7.js → chunk-RYRGL7P4.js} +1 -1
- package/dist/node-runtime/{chunk-I54LGHWM.js → chunk-STMX7W5I.js} +3 -3
- package/dist/node-runtime/chunk-SWWHZ2RI.js +158 -0
- package/dist/node-runtime/{chunk-5Q4A37RB.js → chunk-TCT6PCUR.js} +32 -11
- package/dist/node-runtime/{chunk-P7XBVXGT.js → chunk-U3YVZ6UC.js} +18 -14
- package/dist/node-runtime/{chunk-D5UCKPBQ.js → chunk-UBWW2PID.js} +1 -1
- package/dist/node-runtime/{chunk-E75WFZ33.js → chunk-UNUZICC4.js} +3 -3
- package/dist/node-runtime/{chunk-RAK2QA2B.js → chunk-UYRKUKFE.js} +4 -4
- package/dist/node-runtime/{chunk-LWJXTA6Z.js → chunk-VSUEAI7O.js} +1377 -45
- package/dist/node-runtime/{chunk-NAY6UUQZ.js → chunk-WDIVLMOH.js} +1 -1
- package/dist/node-runtime/{chunk-E24EO34J.js → chunk-WPXUR3HE.js} +1 -1
- package/dist/node-runtime/{chunk-SLVOAQOY.js → chunk-WXYNALHD.js} +113 -1
- package/dist/node-runtime/{chunk-KJ6DWS3K.js → chunk-XEOCUEUJ.js} +121 -60
- package/dist/node-runtime/chunk-YD3IZAZ7.js +47 -0
- package/dist/node-runtime/chunk-YSB6URMT.js +41 -0
- package/dist/node-runtime/{chunk-YWOJIGFY.js → chunk-ZEVFBFNF.js} +95 -142
- package/dist/node-runtime/{chunk-5B6VKFEV.js → chunk-ZIQUS5QO.js} +1 -1
- package/dist/node-runtime/cli-runtime.js +4 -4
- package/dist/node-runtime/{command-FSBU2EVZ.js → command-YRT7I2BW.js} +5 -5
- package/dist/node-runtime/{commands-NAAL7KNT.js → commands-2ETOWWRA.js} +16 -16
- package/dist/node-runtime/{commands-EMBQQVVT.js → commands-D6A6H4TS.js} +11 -18
- package/dist/node-runtime/{config-QDD5PUE2.js → config-CU7M7BTK.js} +1 -1
- package/dist/node-runtime/{config-D4QUPAHX.js → config-DS7XLMEM.js} +2 -2
- package/dist/node-runtime/{direct-tool-executor-4D7UXYU4.js → direct-tool-executor-2QJXVQD2.js} +6 -11
- package/dist/node-runtime/{exa-LL64QZR2.js → exa-6VBZNPGE.js} +1 -1
- package/dist/node-runtime/{export-html-VM4J2MNR.js → export-html-DBQRRMAY.js} +4 -4
- package/dist/node-runtime/{extension-input-E7BJZ35H.js → extension-input-VBBMX7YV.js} +5 -5
- package/dist/node-runtime/{extension-selector-WU42XDTU.js → extension-selector-T5AOG5XB.js} +5 -5
- package/dist/node-runtime/{extract-EQJE7FZZ.js → extract-BFX2N35A.js} +3 -3
- package/dist/node-runtime/{gateway-DQM333YV.js → gateway-V6K2CDWN.js} +51 -42
- package/dist/node-runtime/{gemini-search-35OCRFLN.js → gemini-search-C66QHDT6.js} +2 -2
- package/dist/node-runtime/{gemini-url-context-UQY3PHHW.js → gemini-url-context-T7DEK73R.js} +3 -3
- package/dist/node-runtime/{github-extract-F64RZOEA.js → github-extract-HOGE3D6F.js} +107 -38
- package/dist/node-runtime/{goal-NJEE5CTU.js → goal-J7TYMOBK.js} +14 -8
- package/dist/node-runtime/index.js +51 -48
- package/dist/node-runtime/{init-P5AUV77I.js → init-JBQJIPYI.js} +3 -6
- package/dist/node-runtime/{install-cli-NN4YAU44.js → install-cli-INGHIVBY.js} +30 -25
- package/dist/node-runtime/interactive-mode-ASZ7APYP.js +75 -0
- package/dist/node-runtime/{interactive-view-NGFAMHPW.js → interactive-view-K33PM4OS.js} +8 -8
- package/dist/node-runtime/{lsp-manager-3ASUSO4T.js → lsp-manager-S554V5HR.js} +240 -155
- package/dist/node-runtime/{lunr-browser-IR7TL3J7.js → lunr-browser-H4FDOR3C.js} +7 -7
- package/dist/node-runtime/{lunr-computer-use-Y23CCDIJ.js → lunr-computer-use-VDP2QNRQ.js} +9 -9
- package/dist/node-runtime/{lunr-cron-USHC4LXH.js → lunr-cron-WD3YH4CR.js} +93 -80
- package/dist/node-runtime/main.js +43 -41
- package/dist/node-runtime/{mobile-commands-O5LCHL32.js → mobile-commands-ICUXFW5V.js} +12 -12
- package/dist/node-runtime/{model-runtime-OO5HFLWE.js → model-runtime-MZ2JMDV4.js} +4 -4
- package/dist/node-runtime/{openai-search-B3MSNCQA.js → openai-search-K72LJDYN.js} +1 -1
- package/dist/node-runtime/{package-manager-cli-Q7BL6KZO.js → package-manager-cli-CYM262SI.js} +20 -20
- package/dist/node-runtime/{parallel-AI2MNSCU.js → parallel-X724NCNU.js} +1 -1
- package/dist/node-runtime/{perplexity-QVJW3I35.js → perplexity-RDHQHVMK.js} +2 -2
- package/dist/node-runtime/{pi-intercom-QM53RLTS.js → pi-intercom-NHHEPBMI.js} +2 -2
- package/dist/node-runtime/{pi-mcp-adapter-3PHF4KZ4.js → pi-mcp-adapter-PP3GQJQ2.js} +98 -94
- package/dist/node-runtime/{pi-ollama-cloud-2F3Y2QNM.js → pi-ollama-cloud-4MNWIMFN.js} +11 -24
- package/dist/node-runtime/{pi-prompt-template-model-QWTCGLJU.js → pi-prompt-template-model-FTGELLS5.js} +32 -36
- package/dist/node-runtime/{pi-subagents-7IBSSGEW.js → pi-subagents-QPU3OXE4.js} +114 -167
- package/dist/node-runtime/{pi-web-access-WTGMNZ32.js → pi-web-access-Z3LP246C.js} +144 -108
- package/dist/node-runtime/{print-mode-XQ76CYMT.js → print-mode-2XHN5SUA.js} +3 -3
- package/dist/node-runtime/{process-registry-4FAKZQOL.js → process-registry-Q4QWIU33.js} +3 -3
- package/dist/node-runtime/{proxy-modes-5FAPSZEZ.js → proxy-modes-VEGC6QG5.js} +8 -15
- package/dist/node-runtime/rpc-entry.js +43 -41
- package/dist/node-runtime/{rpc-mode-O3XV4SID.js → rpc-mode-32DHJPEJ.js} +4 -4
- package/dist/node-runtime/{runtime-4VA6JKNF.js → runtime-EDUUGAQD.js} +56 -46
- package/dist/node-runtime/{runtime-EFPPD2UW.js → runtime-UGXM4NPQ.js} +2 -2
- package/dist/node-runtime/{runtime-bridges-7WVCLI76.js → runtime-bridges-DNKMNJCF.js} +7 -7
- package/dist/node-runtime/{service-MDQIPQ26.js → service-IHLGFLBT.js} +6 -4
- package/dist/node-runtime/{session-handoff-GWARVEWT.js → session-handoff-LYH53ESV.js} +4 -4
- package/dist/node-runtime/{session-manager-JB5J6XBN.js → session-manager-ZJ3N5Q74.js} +3 -3
- package/dist/node-runtime/{session-selector-GIM4DHQ2.js → session-selector-I73U3EIO.js} +7 -7
- package/dist/node-runtime/{settings-manager-DJDM5AHM.js → settings-manager-P2TKGYTR.js} +5 -5
- package/dist/node-runtime/{setup-JBKOLJ6F.js → setup-H7CCELIL.js} +15 -1
- package/dist/node-runtime/{setup-PFBWRNUI.js → setup-OLQ4GXWS.js} +17 -17
- package/dist/node-runtime/{src-IVPNZ2BX.js → src-6RV4PXKJ.js} +64 -36
- package/dist/node-runtime/{subagent-executor-LPZ4BHWE.js → subagent-executor-UI6SHG3X.js} +412 -288
- package/dist/node-runtime/{tavily-WEZFVFP7.js → tavily-KZBFYQB6.js} +1 -1
- package/dist/node-runtime/{theme-UOO5RVFN.js → theme-YLVZKKZD.js} +2 -2
- package/dist/node-runtime/{trust-manager-MMISNCYO.js → trust-manager-ZCU76D23.js} +2 -2
- package/dist/node-runtime/{update-cli-HNMOTNGX.js → update-cli-F3XXJANQ.js} +6 -6
- package/dist/node-runtime/{usage-service-LFB2SWYV.js → usage-service-CPAGBED5.js} +1 -1
- package/dist/node-runtime/{video-extract-OOZHR7SU.js → video-extract-UPKYIBW3.js} +3 -3
- package/dist/node-runtime/{workflow-OHPBEFO4.js → workflow-ICDI4N3J.js} +208 -78
- package/dist/node-runtime/{youtube-extract-FNPAUNHU.js → youtube-extract-77NT7FWH.js} +4 -4
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/exif-orientation.d.ts.map +1 -1
- package/dist/utils/exif-orientation.js +25 -10
- package/dist/utils/exif-orientation.js.map +1 -1
- package/dist/utils/image-resize-core.d.ts.map +1 -1
- package/dist/utils/image-resize-core.js +2 -0
- package/dist/utils/image-resize-core.js.map +1 -1
- package/dist/utils/paths.d.ts +4 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +33 -2
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/sanitize-terminal-text.d.ts +7 -0
- package/dist/utils/sanitize-terminal-text.d.ts.map +1 -0
- package/dist/utils/sanitize-terminal-text.js +67 -0
- package/dist/utils/sanitize-terminal-text.js.map +1 -0
- package/docs/browser.md +1 -1
- package/docs/custom-provider.md +3 -3
- package/docs/extensions.md +8 -4
- package/docs/features.md +25 -7
- package/docs/keybindings.md +1 -1
- package/docs/models.md +5 -5
- package/docs/packages.md +1 -1
- package/docs/providers.md +2 -2
- package/docs/rpc.md +7 -7
- package/docs/security.md +10 -5
- package/docs/sessions.md +2 -2
- package/docs/skills.md +1 -1
- package/docs/terminal-setup.md +7 -7
- package/docs/themes.md +1 -1
- package/docs/usage.md +1 -1
- package/examples/extensions/auto-commit-on-exit.ts +1 -1
- package/examples/extensions/bash-spawn-hook.ts +1 -1
- package/examples/extensions/built-in-tool-renderer.ts +1 -1
- package/examples/extensions/claude-rules.ts +1 -1
- package/examples/extensions/commands.ts +1 -1
- package/examples/extensions/custom-compaction.ts +1 -1
- package/examples/extensions/custom-provider-anthropic/index.ts +3 -3
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/index.ts +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/doom-overlay/index.ts +1 -1
- package/examples/extensions/git-merge-and-resolve.ts +2 -2
- package/examples/extensions/gondolin/index.ts +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/hidden-thinking-label.ts +1 -1
- package/examples/extensions/inline-bash.ts +2 -2
- package/examples/extensions/input-transform-streaming.ts +2 -2
- package/examples/extensions/input-transform.ts +2 -2
- package/examples/extensions/interactive-shell.ts +1 -1
- package/examples/extensions/kimi-deferred-tools.ts +1 -1
- package/examples/extensions/mac-system-theme.ts +1 -1
- package/examples/extensions/minimal-mode.ts +1 -1
- package/examples/extensions/modal-editor.ts +1 -1
- package/examples/extensions/model-status.ts +1 -1
- package/examples/extensions/notify.ts +3 -3
- package/examples/extensions/overlay-qa-tests.ts +1 -1
- package/examples/extensions/overlay-test.ts +1 -1
- package/examples/extensions/pirate.ts +1 -1
- package/examples/extensions/preset.ts +3 -3
- package/examples/extensions/project-trust.ts +4 -4
- package/examples/extensions/prompt-customizer.ts +1 -1
- package/examples/extensions/rainbow-editor.ts +1 -1
- package/examples/extensions/rpc-demo.ts +1 -1
- package/examples/extensions/sandbox/index.ts +7 -7
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/shutdown-command.ts +5 -5
- package/examples/extensions/ssh.ts +2 -2
- package/examples/extensions/subagent/README.md +3 -3
- package/examples/extensions/subagent/index.ts +2 -2
- package/examples/extensions/titlebar-spinner.ts +1 -1
- package/examples/extensions/tool-override.ts +1 -1
- package/examples/extensions/tools.ts +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/extensions/working-indicator.ts +1 -1
- package/examples/extensions/working-message-test.ts +1 -1
- package/examples/sdk/01-minimal.ts +1 -1
- package/examples/sdk/03-custom-prompt.ts +1 -1
- package/examples/sdk/04-skills.ts +1 -1
- package/examples/sdk/06-extensions.ts +2 -2
- package/examples/sdk/08-prompt-templates.ts +1 -1
- package/install-lock/package-lock.json +24 -24
- package/install-lock/package.json +2 -2
- package/npm-shrinkwrap.json +60 -24
- package/package.json +9 -9
- package/dist/modes/interactive/components/exit-card.d.ts +0 -22
- package/dist/modes/interactive/components/exit-card.d.ts.map +0 -1
- package/dist/modes/interactive/components/exit-card.js +0 -69
- package/dist/modes/interactive/components/exit-card.js.map +0 -1
- package/dist/node-runtime/agent-session-services-5YXS2PLF.js +0 -51
- package/dist/node-runtime/chunk-2IDTZAG5.js +0 -1415
- package/dist/node-runtime/chunk-3MISGG4S.js +0 -769
- package/dist/node-runtime/chunk-4HXHDDCF.js +0 -16
- package/dist/node-runtime/chunk-5B5QN3ZQ.js +0 -356
- package/dist/node-runtime/chunk-7IQGBKBJ.js +0 -2033
- package/dist/node-runtime/chunk-AUXVGBO4.js +0 -19
- package/dist/node-runtime/chunk-PTFBPO3C.js +0 -50
- package/dist/node-runtime/interactive-mode-YOHZVMKX.js +0 -74
- package/dist/node-runtime/mcp-auth-flow-QVLFVLEO.js +0 -33
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context-breakdown.js","sourceRoot":"","sources":["../../src/core/context-breakdown.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunr: Context window breakdown estimation for the /context command.\n *\n * Pure functions that estimate (chars/4, same heuristic as `estimateTokens` in\n * compaction.ts) what consumes the model's context window: the system prompt\n * (project context files are baked into it), the registered tool definitions,\n * and the live session messages split into user / assistant text / thinking /\n * tool calls / tool results / summaries.\n *\n * Pass the same message list the context actually contains (post-compaction,\n * that is `AgentSession.messages`) so the breakdown matches reality.\n */\n\nimport type { AgentMessage } from \"@ashx-j/lunr-agent\";\nimport type { AssistantMessage } from \"@ashx-j/lunr-ai/compat\";\nimport { estimateTokens } from \"./compaction/index.ts\";\n\nexport interface ContextBreakdownTool {\n\tname: string;\n\tdescription: string;\n\tparameters: unknown;\n}\n\nexport interface ContextBreakdownInput {\n\tsystemPrompt: string;\n\ttools: ReadonlyArray<ContextBreakdownTool>;\n\tmessages: ReadonlyArray<AgentMessage>;\n\tcontextWindow: number;\n\t/** Absolute path of the user-level AGENTS.md, used only to distinguish its UI label. */\n\tglobalAgentsPath?: string;\n\t/** Absolute path of the selected model's AGENTS.md, used only for its UI label. */\n\tmodelAgentsPath?: string;\n}\n\nexport interface ContextFileBreakdown {\n\t/** Display label for the context file (AGENTS.md, Global AGENTS.md, CLAUDE.md, …). */\n\tlabel: string;\n\t/** Path from the <project_instructions path=\"…\"> wrapper, when present. */\n\tpath?: string;\n\ttokens: number;\n}\n\nexport interface ContextBreakdown {\n\t/** System prompt minus <project_context> and the skills block. */\n\tsystemPrompt: number;\n\t/** Each AGENTS.md / context file from <project_instructions>. */\n\tcontextFiles: ContextFileBreakdown[];\n\t/** formatSkillsForPrompt section, if present. */\n\tskills: number;\n\t/** Serialized tool schemas (name + description + JSON parameter schema). */\n\ttoolDefinitions: number;\n\t/** User and extension (\"custom\") messages. */\n\tuser: number;\n\t/** Assistant text blocks. */\n\tassistantText: number;\n\t/** Assistant thinking blocks. */\n\tthinking: number;\n\t/** Assistant tool call blocks (name + serialized arguments). */\n\ttoolCalls: number;\n\t/** Tool result messages and bash execution output. */\n\ttoolResults: number;\n\t/** Compaction and branch summaries. */\n\tsummaries: number;\n\t/** Sum of all categories above. */\n\ttotal: number;\n\tcontextWindow: number;\n\t/** max(0, contextWindow - total) */\n\tfree: number;\n\t/** Per-category message counts (not token counts). */\n\tcounts: {\n\t\tuser: number;\n\t\tassistantText: number;\n\t\tthinking: number;\n\t\ttoolCalls: number;\n\t\ttoolResults: number;\n\t\tsummaries: number;\n\t};\n}\n\nconst CHARS_PER_TOKEN = 4;\nconst PROJECT_CONTEXT_RE = /<project_context>[\\s\\S]*?<\\/project_context>/;\nconst PROJECT_INSTRUCTIONS_RE = /<project_instructions(?:\\s+path=\"([^\"]*)\")?>([\\s\\S]*?)<\\/project_instructions>/g;\nconst SKILLS_BLOCK_RE =\n\t/\\n\\nThe following skills provide specialized instructions for specific tasks\\.[\\s\\S]*<\\/available_skills>/;\n\nfunction estimateChars(text: string): number {\n\treturn text.length === 0 ? 0 : Math.ceil(text.length / CHARS_PER_TOKEN);\n}\n\nfunction contextFileLabel(\n\tfilePath: string | undefined,\n\tglobalAgentsPath: string | undefined,\n\tmodelAgentsPath: string | undefined,\n): string {\n\tif (!filePath) return \"AGENTS.md\";\n\tconst normalized = filePath.replace(/\\\\/g, \"/\");\n\tif (globalAgentsPath && normalized.toLowerCase() === globalAgentsPath.replace(/\\\\/g, \"/\").toLowerCase()) {\n\t\treturn \"Global AGENTS.md\";\n\t}\n\tif (modelAgentsPath && normalized.toLowerCase() === modelAgentsPath.replace(/\\\\/g, \"/\").toLowerCase()) {\n\t\tconst parts = normalized.split(\"/\");\n\t\treturn `${parts.at(-2) ?? \"Model\"} AGENTS.md`;\n\t}\n\tconst slash = normalized.lastIndexOf(\"/\");\n\treturn slash === -1 ? normalized : normalized.slice(slash + 1);\n}\n\n/** Split a live system prompt into base / context-file / skills parts. */\nexport function splitSystemPromptSections(systemPrompt: string): {\n\tbase: string;\n\tcontextFiles: Array<{ path?: string; content: string }>;\n\tskills: string;\n} {\n\tlet remaining = systemPrompt;\n\tconst contextFiles: Array<{ path?: string; content: string }> = [];\n\tconst contextMatch = remaining.match(PROJECT_CONTEXT_RE);\n\tif (contextMatch) {\n\t\tconst block = contextMatch[0];\n\t\tPROJECT_INSTRUCTIONS_RE.lastIndex = 0;\n\t\tfor (const match of block.matchAll(PROJECT_INSTRUCTIONS_RE)) {\n\t\t\tcontextFiles.push({\n\t\t\t\tpath: match[1],\n\t\t\t\tcontent: match[2] ?? \"\",\n\t\t\t});\n\t\t}\n\t\tremaining = remaining.replace(block, \"\");\n\t}\n\tlet skills = \"\";\n\tconst skillsMatch = remaining.match(SKILLS_BLOCK_RE);\n\tif (skillsMatch) {\n\t\tskills = skillsMatch[0];\n\t\tremaining = remaining.replace(skills, \"\");\n\t}\n\treturn { base: remaining, contextFiles, skills };\n}\n\n/**\n * Estimate the token cost of the tool definitions sent to the model.\n * The wire format is roughly `{ name, description, parameters(JSON schema) }` per tool.\n */\nexport function estimateToolDefinitionTokens(tools: ReadonlyArray<ContextBreakdownTool>): number {\n\tlet chars = 0;\n\tfor (const tool of tools) {\n\t\tchars += tool.name.length + tool.description.length;\n\t\ttry {\n\t\t\tconst schema = JSON.stringify(tool.parameters);\n\t\t\tif (schema) chars += schema.length;\n\t\t} catch {\n\t\t\t// Unserializable schema — skip rather than fail the whole breakdown.\n\t\t}\n\t}\n\treturn Math.ceil(chars / CHARS_PER_TOKEN);\n}\n\n/**\n * Compute the estimated context window breakdown.\n * All numbers are chars/4 estimates, matching `estimateTokens`.\n */\nexport function computeContextBreakdown(input: ContextBreakdownInput): ContextBreakdown {\n\tconst sections = splitSystemPromptSections(input.systemPrompt);\n\tconst systemPrompt = estimateChars(sections.base);\n\tconst contextFiles = sections.contextFiles.map((file) => ({\n\t\tlabel: contextFileLabel(file.path, input.globalAgentsPath, input.modelAgentsPath),\n\t\tpath: file.path,\n\t\ttokens: estimateChars(file.content),\n\t}));\n\tconst skills = estimateChars(sections.skills);\n\tconst toolDefinitions = estimateToolDefinitionTokens(input.tools);\n\n\tlet user = 0;\n\tlet assistantText = 0;\n\tlet thinking = 0;\n\tlet toolCalls = 0;\n\tlet toolResults = 0;\n\tlet summaries = 0;\n\n\tconst counts = {\n\t\tuser: 0,\n\t\tassistantText: 0,\n\t\tthinking: 0,\n\t\ttoolCalls: 0,\n\t\ttoolResults: 0,\n\t\tsummaries: 0,\n\t};\n\n\tfor (const message of input.messages) {\n\t\tswitch (message.role) {\n\t\t\tcase \"assistant\": {\n\t\t\t\tlet textChars = 0;\n\t\t\t\tlet thinkingChars = 0;\n\t\t\t\tlet toolCallChars = 0;\n\t\t\t\tfor (const block of (message as AssistantMessage).content) {\n\t\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\t\ttextChars += block.text.length;\n\t\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\t\tthinkingChars += block.thinking.length;\n\t\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\t\ttoolCallChars += block.name.length + JSON.stringify(block.arguments).length;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tassistantText += Math.ceil(textChars / CHARS_PER_TOKEN);\n\t\t\t\tthinking += Math.ceil(thinkingChars / CHARS_PER_TOKEN);\n\t\t\t\ttoolCalls += Math.ceil(toolCallChars / CHARS_PER_TOKEN);\n\t\t\t\tif (textChars > 0) counts.assistantText++;\n\t\t\t\tif (thinkingChars > 0) counts.thinking++;\n\t\t\t\tif (toolCallChars > 0) counts.toolCalls++;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"user\":\n\t\t\tcase \"custom\":\n\t\t\t\tuser += estimateTokens(message);\n\t\t\t\tcounts.user++;\n\t\t\t\tbreak;\n\t\t\tcase \"toolResult\":\n\t\t\tcase \"bashExecution\":\n\t\t\t\ttoolResults += estimateTokens(message);\n\t\t\t\tcounts.toolResults++;\n\t\t\t\tbreak;\n\t\t\tcase \"branchSummary\":\n\t\t\tcase \"compactionSummary\":\n\t\t\t\tsummaries += estimateTokens(message);\n\t\t\t\tcounts.summaries++;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tconst contextFileTokens = contextFiles.reduce((sum, file) => sum + file.tokens, 0);\n\tconst total =\n\t\tsystemPrompt +\n\t\tcontextFileTokens +\n\t\tskills +\n\t\ttoolDefinitions +\n\t\tuser +\n\t\tassistantText +\n\t\tthinking +\n\t\ttoolCalls +\n\t\ttoolResults +\n\t\tsummaries;\n\treturn {\n\t\tsystemPrompt,\n\t\tcontextFiles,\n\t\tskills,\n\t\ttoolDefinitions,\n\t\tuser,\n\t\tassistantText,\n\t\tthinking,\n\t\ttoolCalls,\n\t\ttoolResults,\n\t\tsummaries,\n\t\ttotal,\n\t\tcontextWindow: input.contextWindow,\n\t\tfree: Math.max(0, input.contextWindow - total),\n\t\tcounts,\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"context-breakdown.js","sourceRoot":"","sources":["../../src/core/context-breakdown.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunr: Context window breakdown estimation for the /context command.\n *\n * Pure functions that estimate (shared text heuristic, as `estimateTokens` in\n * compaction.ts) what consumes the model's context window: the system prompt\n * (project context files are baked into it), the registered tool definitions,\n * and the live session messages split into user / assistant text / thinking /\n * tool calls / tool results / summaries.\n *\n * Pass the same message list the context actually contains (post-compaction,\n * that is `AgentSession.messages`) so the breakdown matches reality.\n */\n\nimport type { AgentMessage } from \"@ashx-j/lunr-agent\";\nimport type { AssistantMessage } from \"@ashx-j/lunr-ai/compat\";\nimport { estimateTextTokens } from \"@ashx-j/lunr-ai/compat\";\nimport { estimateTokens } from \"./compaction/index.ts\";\n\nexport interface ContextBreakdownTool {\n\tname: string;\n\tdescription: string;\n\tparameters: unknown;\n}\n\nexport interface ContextBreakdownInput {\n\tsystemPrompt: string;\n\ttools: ReadonlyArray<ContextBreakdownTool>;\n\tmessages: ReadonlyArray<AgentMessage>;\n\tcontextWindow: number;\n\t/** Absolute path of the user-level AGENTS.md, used only to distinguish its UI label. */\n\tglobalAgentsPath?: string;\n\t/** Absolute path of the selected model's AGENTS.md, used only for its UI label. */\n\tmodelAgentsPath?: string;\n}\n\nexport interface ContextFileBreakdown {\n\t/** Display label for the context file (AGENTS.md, Global AGENTS.md, CLAUDE.md, …). */\n\tlabel: string;\n\t/** Path from the <project_instructions path=\"…\"> wrapper, when present. */\n\tpath?: string;\n\ttokens: number;\n}\n\nexport interface ContextBreakdown {\n\t/** System prompt minus <project_context> and the skills block. */\n\tsystemPrompt: number;\n\t/** Each AGENTS.md / context file from <project_instructions>. */\n\tcontextFiles: ContextFileBreakdown[];\n\t/** formatSkillsForPrompt section, if present. */\n\tskills: number;\n\t/** Serialized tool schemas (name + description + JSON parameter schema). */\n\ttoolDefinitions: number;\n\t/** User and extension (\"custom\") messages. */\n\tuser: number;\n\t/** Assistant text blocks. */\n\tassistantText: number;\n\t/** Assistant thinking blocks. */\n\tthinking: number;\n\t/** Assistant tool call blocks (name + serialized arguments). */\n\ttoolCalls: number;\n\t/** Tool result messages and bash execution output. */\n\ttoolResults: number;\n\t/** Compaction and branch summaries. */\n\tsummaries: number;\n\t/** Sum of all categories above. */\n\ttotal: number;\n\tcontextWindow: number;\n\t/** max(0, contextWindow - total) */\n\tfree: number;\n\t/** Per-category message counts (not token counts). */\n\tcounts: {\n\t\tuser: number;\n\t\tassistantText: number;\n\t\tthinking: number;\n\t\ttoolCalls: number;\n\t\ttoolResults: number;\n\t\tsummaries: number;\n\t};\n}\n\nconst PROJECT_CONTEXT_RE = /<project_context>[\\s\\S]*?<\\/project_context>/;\nconst PROJECT_INSTRUCTIONS_RE = /<project_instructions(?:\\s+path=\"([^\"]*)\")?>([\\s\\S]*?)<\\/project_instructions>/g;\nconst SKILLS_BLOCK_RE =\n\t/\\n\\nThe following skills provide specialized instructions for specific tasks\\.[\\s\\S]*<\\/available_skills>/;\n\nfunction estimateChars(text: string): number {\n\treturn estimateTextTokens(text);\n}\n\nfunction contextFileLabel(\n\tfilePath: string | undefined,\n\tglobalAgentsPath: string | undefined,\n\tmodelAgentsPath: string | undefined,\n): string {\n\tif (!filePath) return \"AGENTS.md\";\n\tconst normalized = filePath.replace(/\\\\/g, \"/\");\n\tif (globalAgentsPath && normalized.toLowerCase() === globalAgentsPath.replace(/\\\\/g, \"/\").toLowerCase()) {\n\t\treturn \"Global AGENTS.md\";\n\t}\n\tif (modelAgentsPath && normalized.toLowerCase() === modelAgentsPath.replace(/\\\\/g, \"/\").toLowerCase()) {\n\t\tconst parts = normalized.split(\"/\");\n\t\treturn `${parts.at(-2) ?? \"Model\"} AGENTS.md`;\n\t}\n\tconst slash = normalized.lastIndexOf(\"/\");\n\treturn slash === -1 ? normalized : normalized.slice(slash + 1);\n}\n\n/** Split a live system prompt into base / context-file / skills parts. */\nexport function splitSystemPromptSections(systemPrompt: string): {\n\tbase: string;\n\tcontextFiles: Array<{ path?: string; content: string }>;\n\tskills: string;\n} {\n\tlet remaining = systemPrompt;\n\tconst contextFiles: Array<{ path?: string; content: string }> = [];\n\tconst contextMatch = remaining.match(PROJECT_CONTEXT_RE);\n\tif (contextMatch) {\n\t\tconst block = contextMatch[0];\n\t\tPROJECT_INSTRUCTIONS_RE.lastIndex = 0;\n\t\tfor (const match of block.matchAll(PROJECT_INSTRUCTIONS_RE)) {\n\t\t\tcontextFiles.push({\n\t\t\t\tpath: match[1],\n\t\t\t\tcontent: match[2] ?? \"\",\n\t\t\t});\n\t\t}\n\t\tremaining = remaining.replace(block, \"\");\n\t}\n\tlet skills = \"\";\n\tconst skillsMatch = remaining.match(SKILLS_BLOCK_RE);\n\tif (skillsMatch) {\n\t\tskills = skillsMatch[0];\n\t\tremaining = remaining.replace(skills, \"\");\n\t}\n\treturn { base: remaining, contextFiles, skills };\n}\n\n/**\n * Estimate the token cost of the tool definitions sent to the model.\n * The wire format is roughly `{ name, description, parameters(JSON schema) }` per tool.\n */\nexport function estimateToolDefinitionTokens(tools: ReadonlyArray<ContextBreakdownTool>): number {\n\tlet text = \"\";\n\tfor (const tool of tools) {\n\t\ttext += tool.name + tool.description;\n\t\ttry {\n\t\t\tconst schema = JSON.stringify(tool.parameters);\n\t\t\tif (schema) text += schema;\n\t\t} catch {\n\t\t\t// Unserializable schema — skip rather than fail the whole breakdown.\n\t\t}\n\t}\n\treturn estimateTextTokens(text);\n}\n\n/**\n * Compute the estimated context window breakdown.\n * All numbers use the shared text heuristic, matching `estimateTokens`.\n */\nexport function computeContextBreakdown(input: ContextBreakdownInput): ContextBreakdown {\n\tconst sections = splitSystemPromptSections(input.systemPrompt);\n\tconst systemPrompt = estimateChars(sections.base);\n\tconst contextFiles = sections.contextFiles.map((file) => ({\n\t\tlabel: contextFileLabel(file.path, input.globalAgentsPath, input.modelAgentsPath),\n\t\tpath: file.path,\n\t\ttokens: estimateChars(file.content),\n\t}));\n\tconst skills = estimateChars(sections.skills);\n\tconst toolDefinitions = estimateToolDefinitionTokens(input.tools);\n\n\tlet user = 0;\n\tlet assistantText = 0;\n\tlet thinking = 0;\n\tlet toolCalls = 0;\n\tlet toolResults = 0;\n\tlet summaries = 0;\n\n\tconst counts = {\n\t\tuser: 0,\n\t\tassistantText: 0,\n\t\tthinking: 0,\n\t\ttoolCalls: 0,\n\t\ttoolResults: 0,\n\t\tsummaries: 0,\n\t};\n\n\tfor (const message of input.messages) {\n\t\tif (message.role === \"bashExecution\" && message.excludeFromContext) continue;\n\t\tswitch (message.role) {\n\t\t\tcase \"assistant\": {\n\t\t\t\tlet text = \"\";\n\t\t\t\tlet thinkingText = \"\";\n\t\t\t\tlet toolCallText = \"\";\n\t\t\t\tfor (const block of (message as AssistantMessage).content) {\n\t\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\t\ttext += block.text;\n\t\t\t\t\t} else if (block.type === \"thinking\") {\n\t\t\t\t\t\tthinkingText += block.thinking;\n\t\t\t\t\t} else if (block.type === \"toolCall\") {\n\t\t\t\t\t\ttoolCallText += block.name + JSON.stringify(block.arguments);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tassistantText += estimateTextTokens(text);\n\t\t\t\tthinking += estimateTextTokens(thinkingText);\n\t\t\t\ttoolCalls += estimateTextTokens(toolCallText);\n\t\t\t\tif (text.length > 0) counts.assistantText++;\n\t\t\t\tif (thinkingText.length > 0) counts.thinking++;\n\t\t\t\tif (toolCallText.length > 0) counts.toolCalls++;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"user\":\n\t\t\tcase \"custom\":\n\t\t\t\tuser += estimateTokens(message);\n\t\t\t\tcounts.user++;\n\t\t\t\tbreak;\n\t\t\tcase \"toolResult\":\n\t\t\tcase \"bashExecution\":\n\t\t\t\ttoolResults += estimateTokens(message);\n\t\t\t\tcounts.toolResults++;\n\t\t\t\tbreak;\n\t\t\tcase \"branchSummary\":\n\t\t\tcase \"compactionSummary\":\n\t\t\t\tsummaries += estimateTokens(message);\n\t\t\t\tcounts.summaries++;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tconst contextFileTokens = contextFiles.reduce((sum, file) => sum + file.tokens, 0);\n\tconst total =\n\t\tsystemPrompt +\n\t\tcontextFileTokens +\n\t\tskills +\n\t\ttoolDefinitions +\n\t\tuser +\n\t\tassistantText +\n\t\tthinking +\n\t\ttoolCalls +\n\t\ttoolResults +\n\t\tsummaries;\n\treturn {\n\t\tsystemPrompt,\n\t\tcontextFiles,\n\t\tskills,\n\t\ttoolDefinitions,\n\t\tuser,\n\t\tassistantText,\n\t\tthinking,\n\t\ttoolCalls,\n\t\ttoolResults,\n\t\tsummaries,\n\t\ttotal,\n\t\tcontextWindow: input.contextWindow,\n\t\tfree: Math.max(0, input.contextWindow - total),\n\t\tcounts,\n\t};\n}\n"]}
|
package/dist/core/cron/jobs.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Jobs persist at `<agentDir>/cron/jobs.json` (`{ jobs: [...], updated_at }`),
|
|
5
5
|
* agentDir resolved via getAgentDir() (honors PI_CODING_AGENT_DIR, defaults
|
|
6
6
|
* ~/.lunr/agent). Writes are atomic (tmp file in the same dir + rename) and
|
|
7
|
-
* serialized
|
|
7
|
+
* serialized with a cross-process read-modify-write lock over fresh data.
|
|
8
8
|
*
|
|
9
9
|
* Per-run output goes to `<agentDir>/cron/output/<jobId>/<YYYY-MM-DD_HH-MM-SS>.md`
|
|
10
10
|
* with a retention cap of the newest 50 files per job.
|
|
@@ -22,6 +22,8 @@ export interface CronSchedule {
|
|
|
22
22
|
expr?: string;
|
|
23
23
|
}
|
|
24
24
|
export interface CronJobOrigin {
|
|
25
|
+
/** Requester identity, absent on legacy jobs. Never persist adapter role grants. */
|
|
26
|
+
userId?: string;
|
|
25
27
|
platform: string;
|
|
26
28
|
chatId: string;
|
|
27
29
|
threadId?: string;
|
|
@@ -53,6 +55,15 @@ export interface CronJob {
|
|
|
53
55
|
/** Job IDs whose latest output (8K chars cap each) is prepended to the prompt. */
|
|
54
56
|
contextFrom?: string[];
|
|
55
57
|
workdir?: string;
|
|
58
|
+
/** Unknown interrupted effects require an explicit run, rather than ordinary resume. */
|
|
59
|
+
interrupted?: boolean;
|
|
60
|
+
/** Durable claim written before dispatch. Interrupted claims require explicit retry. */
|
|
61
|
+
activeRun?: {
|
|
62
|
+
id: string;
|
|
63
|
+
scheduledAt: string | null;
|
|
64
|
+
startedAt: string;
|
|
65
|
+
advancedTo?: string | null;
|
|
66
|
+
};
|
|
56
67
|
}
|
|
57
68
|
export interface CreateJobInput {
|
|
58
69
|
prompt: string;
|
|
@@ -89,9 +100,11 @@ export declare const OUTPUT_RETENTION = 50;
|
|
|
89
100
|
export declare const CONTEXT_OUTPUT_CAP = 8000;
|
|
90
101
|
/**
|
|
91
102
|
* Override the base dir (defaults to the agent dir) and reset the in-memory
|
|
92
|
-
*
|
|
103
|
+
* transaction queue. Passing undefined restores the default. For tests.
|
|
93
104
|
*/
|
|
94
105
|
export declare function setCronBaseDir(dir: string | undefined): void;
|
|
106
|
+
/** Acquire the non-preemptive profile scheduler lease. A live PID is never displaced. */
|
|
107
|
+
export declare function acquireSchedulerLease(): Promise<(() => Promise<void>) | null>;
|
|
95
108
|
export interface ParsedSchedule {
|
|
96
109
|
schedule: CronSchedule;
|
|
97
110
|
scheduleDisplay: string;
|
|
@@ -136,14 +149,13 @@ export declare function removeJob(idOrName: string): Promise<CronJob>;
|
|
|
136
149
|
/**
|
|
137
150
|
* Jobs due to fire now. Skips disabled/paused/completed jobs and jobs without a
|
|
138
151
|
* nextRunAt. One-shots fire up to 120s late (flat recovery grace); beyond that
|
|
139
|
-
* they are marked state "error" and skipped. Recurring
|
|
140
|
-
*
|
|
141
|
-
* to their next future slot but still fire ONCE now (collapse backlog, no burst).
|
|
152
|
+
* they are marked state "error" and skipped. Recurring backlog fires once;
|
|
153
|
+
* claiming the occurrence advances it to the next future slot without a burst.
|
|
142
154
|
*/
|
|
143
155
|
export declare function getDueJobs(now?: Date): Promise<CronJob[]>;
|
|
144
156
|
/**
|
|
145
|
-
* Advance a recurring job to its next slot.
|
|
146
|
-
*
|
|
157
|
+
* Advance a recurring job to its next slot. Scheduler dispatch uses claimJobRun
|
|
158
|
+
* instead so one-shots and recurring work both retain a durable claim.
|
|
147
159
|
*/
|
|
148
160
|
export declare function advanceNextRun(jobId: string): Promise<CronJob>;
|
|
149
161
|
/**
|
|
@@ -154,7 +166,13 @@ export declare function advanceNextRun(jobId: string): Promise<CronJob>;
|
|
|
154
166
|
export declare function markJobRun(jobId: string, result: {
|
|
155
167
|
status: CronJobStatus;
|
|
156
168
|
error?: string;
|
|
157
|
-
|
|
169
|
+
runId?: string;
|
|
170
|
+
}): Promise<CronJob | null>;
|
|
171
|
+
/** Claim only a still-due, enabled occurrence from the fresh store. */
|
|
172
|
+
export declare function claimJobRun(jobId: string, now: Date, manual?: boolean): Promise<CronJob | null>;
|
|
173
|
+
/** Recover uncertain interrupted work without replaying its external effects. */
|
|
174
|
+
export declare function recoverInterruptedRuns(): Promise<void>;
|
|
175
|
+
export declare function deferJobRun(jobId: string, runId: string): Promise<void>;
|
|
158
176
|
/** Save a run's output and prune to the newest OUTPUT_RETENTION files. Returns the file path. */
|
|
159
177
|
export declare function saveJobOutput(jobId: string, content: string): Promise<string>;
|
|
160
178
|
/** Content of the newest output file for a job, or null when none exists. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../../src/core/cron/jobs.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * lunR: cron job model + persistent store (Phase 1 of the cron/gateway roadmap).\n *\n * Jobs persist at `<agentDir>/cron/jobs.json` (`{ jobs: [...], updated_at }`),\n * agentDir resolved via getAgentDir() (honors PI_CODING_AGENT_DIR, defaults\n * ~/.lunr/agent). Writes are atomic (tmp file in the same dir + rename) and\n * serialized through an in-process promise queue — single-process, no locks.\n *\n * Per-run output goes to `<agentDir>/cron/output/<jobId>/<YYYY-MM-DD_HH-MM-SS>.md`\n * with a retention cap of the newest 50 files per job.\n *\n * Tests inject a base dir via setCronBaseDir() so no real agent dir is touched.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { isAbsolute, join, normalize, resolve } from \"node:path\";\nimport { Cron } from \"croner\";\nimport { getAgentDir } from \"../../config.ts\";\nimport {\n\tchatPlatformDeliverBlockedMessage,\n\tdeliverMentionsChatPlatform,\n\tisFeatureEnabled,\n} from \"../install-features.ts\";\n\n// ---------------------------------------------------------------------------\n// Model\n// ---------------------------------------------------------------------------\n\nexport type CronScheduleKind = \"once\" | \"interval\" | \"cron\";\n\nexport interface CronSchedule {\n\tkind: CronScheduleKind;\n\t/** ISO timestamp, one-shots only. */\n\trunAt?: string;\n\t/** Interval length in minutes, interval kind only. */\n\tminutes?: number;\n\t/** 5-field cron expression, cron kind only. */\n\texpr?: string;\n}\n\nexport interface CronJobOrigin {\n\tplatform: string;\n\tchatId: string;\n\tthreadId?: string;\n\tchatType?: string;\n}\n\nexport type CronJobState = \"scheduled\" | \"paused\" | \"completed\" | \"error\";\nexport type CronJobStatus = \"ok\" | \"error\";\n\nexport interface CronJob {\n\tid: string;\n\tname: string;\n\tprompt: string;\n\tschedule: CronSchedule;\n\tscheduleDisplay: string;\n\trepeat: { times: number | null; completed: number };\n\tenabled: boolean;\n\tstate: CronJobState;\n\tcreatedAt: string;\n\tnextRunAt: string | null;\n\tlastRunAt: string | null;\n\tlastStatus: CronJobStatus | null;\n\tlastError: string | null;\n\tlastDeliveryError: string | null;\n\t/** \"local\" | \"origin\" | \"telegram[:chatId]\" | \"discord[:chatId]\" (comma-combinable). v1 TUI only uses \"local\". */\n\tdeliver: string;\n\torigin?: CronJobOrigin | null;\n\t/** Job IDs whose latest output (8K chars cap each) is prepended to the prompt. */\n\tcontextFrom?: string[];\n\tworkdir?: string;\n}\n\nexport interface CreateJobInput {\n\tprompt: string;\n\t/** Raw schedule input, parsed by parseSchedule(). */\n\tschedule: string;\n\tname?: string;\n\t/** Repetition cap; null = forever. One-shots are always times=1. */\n\ttimes?: number | null;\n\tdeliver?: string;\n\tcontextFrom?: string[];\n\tworkdir?: string;\n\torigin?: CronJobOrigin | null;\n}\n\nexport interface JobPatch {\n\tname?: string;\n\tprompt?: string;\n\t/** Raw schedule input; reparsed and re-anchors nextRunAt. */\n\tschedule?: string;\n\tdeliver?: string;\n\tenabled?: boolean;\n\ttimes?: number | null;\n\tcontextFrom?: string[];\n\tworkdir?: string;\n\torigin?: CronJobOrigin | null;\n\t/** Internal: manual-run trigger and delivery-error bookkeeping. */\n\tnextRunAt?: string | null;\n\tlastDeliveryError?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Flat recovery grace for one-shots: fire up to 120s late. */\nexport const ONE_SHOT_GRACE_MS = 120_000;\n/** Recurring grace window clamps: at least 120s, at most 2h. */\nconst MIN_GRACE_MS = 120_000;\nconst MAX_GRACE_MS = 7_200_000;\n/** Per-job output retention. */\nexport const OUTPUT_RETENTION = 50;\n/** Cap per upstream job output injected via contextFrom. */\nexport const CONTEXT_OUTPUT_CAP = 8000;\n\nconst MAX_NAME_LEN = 50;\n\n// ---------------------------------------------------------------------------\n// Paths + store state\n// ---------------------------------------------------------------------------\n\nlet baseDirOverride: string | undefined;\nlet cache: CronJob[] | undefined;\nlet writeQueue: Promise<void> = Promise.resolve();\n\n/**\n * Override the base dir (defaults to the agent dir) and reset the in-memory\n * cache + write queue. Passing undefined restores the default. For tests.\n */\nexport function setCronBaseDir(dir: string | undefined): void {\n\tbaseDirOverride = dir;\n\tcache = undefined;\n\twriteQueue = Promise.resolve();\n}\n\nfunction cronDir(): string {\n\treturn join(baseDirOverride ?? getAgentDir(), \"cron\");\n}\n\nfunction jobsFile(): string {\n\treturn join(cronDir(), \"jobs.json\");\n}\n\nfunction outputDir(jobId: string): string {\n\treturn join(cronDir(), \"output\", jobId);\n}\n\nfunction loadJobs(): CronJob[] {\n\tif (cache) return cache;\n\tconst file = jobsFile();\n\tif (!existsSync(file)) {\n\t\tcache = [];\n\t\treturn cache;\n\t}\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(file, \"utf-8\"));\n\t\tcache = Array.isArray(parsed?.jobs) ? (parsed.jobs as CronJob[]) : [];\n\t} catch {\n\t\tcache = [];\n\t}\n\treturn cache;\n}\n\n/** Queue an atomic write of the in-memory store. Rejects only for the caller; the chain self-heals. */\nfunction persist(): Promise<void> {\n\tconst snapshot = JSON.stringify({ jobs: cache ?? [], updated_at: new Date().toISOString() }, null, 2);\n\tconst p = writeQueue.then(() => {\n\t\tmkdirSync(cronDir(), { recursive: true });\n\t\tconst tmp = join(cronDir(), `.jobs.json.${process.pid}.${Date.now()}.tmp`);\n\t\twriteFileSync(tmp, snapshot, \"utf-8\");\n\t\trenameSync(tmp, jobsFile());\n\t});\n\twriteQueue = p.then(\n\t\t() => undefined,\n\t\t() => undefined,\n\t);\n\treturn p;\n}\n\n// ---------------------------------------------------------------------------\n// Schedule parsing\n// ---------------------------------------------------------------------------\n\nexport interface ParsedSchedule {\n\tschedule: CronSchedule;\n\tscheduleDisplay: string;\n}\n\nconst INTERVAL_RE = /^every\\s+(\\d+)\\s*([mhd])$/i;\nconst DURATION_RE = /^(\\d+)\\s*([mhd])$/i;\n\nfunction toMinutes(value: number, unit: string): number {\n\tconst u = unit.toLowerCase();\n\tconst factor = u === \"d\" ? 1440 : u === \"h\" ? 60 : 1;\n\treturn value * factor;\n}\n\n/**\n * Parse a schedule input:\n * - `every 30m` / `every 2h` / `every 1d` → interval (minutes; d = 1440)\n * - bare `30m` / `2h` / `1d` → one-shot at now + duration\n * - ISO timestamp → one-shot at that time\n * - 5-field cron expression (validated via croner; throws on invalid)\n */\nexport function parseSchedule(input: string, now: Date = new Date()): ParsedSchedule {\n\tconst text = input.trim().replace(/\\s+/g, \" \");\n\tif (!text) throw new Error(\"schedule is empty\");\n\n\tconst interval = INTERVAL_RE.exec(text);\n\tif (interval) {\n\t\tconst minutes = toMinutes(Number(interval[1]), interval[2]);\n\t\tif (minutes <= 0) throw new Error(`invalid interval: \"${text}\"`);\n\t\treturn {\n\t\t\tschedule: { kind: \"interval\", minutes },\n\t\t\tscheduleDisplay: `every ${Number(interval[1])}${interval[2].toLowerCase()}`,\n\t\t};\n\t}\n\n\tconst duration = DURATION_RE.exec(text);\n\tif (duration) {\n\t\tconst minutes = toMinutes(Number(duration[1]), duration[2]);\n\t\tif (minutes <= 0) throw new Error(`invalid duration: \"${text}\"`);\n\t\tconst runAt = new Date(now.getTime() + minutes * 60_000).toISOString();\n\t\treturn { schedule: { kind: \"once\", runAt }, scheduleDisplay: text };\n\t}\n\n\tif (/^\\d{4}-\\d{2}-\\d{2}/.test(text)) {\n\t\tconst ts = Date.parse(text);\n\t\tif (!Number.isNaN(ts)) {\n\t\t\tconst runAt = new Date(ts).toISOString();\n\t\t\treturn { schedule: { kind: \"once\", runAt }, scheduleDisplay: runAt };\n\t\t}\n\t}\n\n\tif (text.split(\" \").length === 5) {\n\t\ttry {\n\t\t\tnew Cron(text);\n\t\t} catch (err) {\n\t\t\tthrow new Error(`invalid cron expression \"${text}\": ${err instanceof Error ? err.message : String(err)}`);\n\t\t}\n\t\treturn { schedule: { kind: \"cron\", expr: text }, scheduleDisplay: text };\n\t}\n\n\tthrow new Error(\n\t\t`unrecognized schedule: \"${input}\" (expected \"every 30m\", \"2h\", an ISO timestamp, or a 5-field cron expression)`,\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Next-run computation\n// ---------------------------------------------------------------------------\n\n/**\n * Compute the next run time for a job:\n * - once: the stored runAt, recoverable within a 120s grace if never run; null after that or once run.\n * - interval: (lastRunAt ?? createdAt) + minutes.\n * - cron: croner nextRun after (lastRunAt ?? now).\n */\nexport function computeNextRun(job: CronJob, now: Date = new Date()): Date | null {\n\tconst s = job.schedule;\n\tif (s.kind === \"once\") {\n\t\tif (job.repeat.completed > 0) return null;\n\t\tif (!s.runAt) return null;\n\t\tconst runAt = new Date(s.runAt);\n\t\tif (Number.isNaN(runAt.getTime())) return null;\n\t\treturn now.getTime() - runAt.getTime() <= ONE_SHOT_GRACE_MS ? runAt : null;\n\t}\n\tif (s.kind === \"interval\") {\n\t\tconst base = job.lastRunAt ? new Date(job.lastRunAt) : new Date(job.createdAt);\n\t\treturn new Date(base.getTime() + (s.minutes ?? 0) * 60_000);\n\t}\n\tconst cron = new Cron(s.expr!);\n\treturn cron.nextRun(job.lastRunAt ? new Date(job.lastRunAt) : now);\n}\n\n/** Approximate schedule period in ms (recurring jobs only) for grace-window sizing. */\nfunction periodMs(job: CronJob, now: Date): number {\n\tif (job.schedule.kind === \"interval\") return (job.schedule.minutes ?? 0) * 60_000;\n\tif (job.schedule.kind === \"cron\") {\n\t\tconst cron = new Cron(job.schedule.expr!);\n\t\tconst a = cron.nextRun(now);\n\t\tif (!a) return MAX_GRACE_MS;\n\t\tconst b = cron.nextRun(a);\n\t\tif (!b) return MAX_GRACE_MS;\n\t\treturn Math.max(b.getTime() - a.getTime(), 1);\n\t}\n\treturn 0;\n}\n\n/** Next slot strictly after `after` for a recurring job. */\nfunction nextSlotAfter(job: CronJob, after: Date): Date | null {\n\tif (job.schedule.kind === \"interval\") {\n\t\tconst period = (job.schedule.minutes ?? 0) * 60_000;\n\t\tif (period <= 0) return null;\n\t\t// Step forward from the stored nextRunAt (or now) until strictly in the future of `after`.\n\t\tlet slot = job.nextRunAt ? new Date(job.nextRunAt).getTime() : new Date(job.createdAt).getTime() + period;\n\t\twhile (slot <= after.getTime()) slot += period;\n\t\treturn new Date(slot);\n\t}\n\tif (job.schedule.kind === \"cron\") {\n\t\treturn new Cron(job.schedule.expr!).nextRun(after);\n\t}\n\treturn null;\n}\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\nlet workdirRoots: string[] = [process.cwd(), homedir()];\nlet deliverValidator: ((deliver: string, origin?: CronJobOrigin | null) => string | undefined) | undefined;\n\n/**\n * Configure the allowed absolute roots for cron job workdirs. Relative workdirs\n * are resolved against process.cwd() and must fall under one of these roots.\n * Default roots are process.cwd() and the user's home directory.\n */\nexport function setCronWorkdirRoots(roots: string[]): void {\n\tworkdirRoots = roots.map((r) => normalize(resolve(r)));\n}\n\n/**\n * Configure a deliver-target validator. When set, createJob/updateJob reject\n * deliver strings that the validator does not approve. The gateway uses this\n * to prevent delivery to arbitrary chats.\n */\nexport function setCronDeliverValidator(\n\tfn: ((deliver: string, origin?: CronJobOrigin | null) => string | undefined) | undefined,\n): void {\n\tdeliverValidator = fn;\n}\n\n/** Test seam: restore default validation state. */\nexport function resetCronValidators(): void {\n\tworkdirRoots = [process.cwd(), homedir()];\n\tdeliverValidator = undefined;\n}\n\nfunction isPathUnderRoot(absPath: string, root: string): boolean {\n\tconst normRoot = normalize(root).replace(/\\\\$/g, \"\");\n\tconst normPath = normalize(absPath).replace(/\\\\$/g, \"\");\n\treturn normPath === normRoot || normPath.startsWith(`${normRoot}/`) || normPath.startsWith(`${normRoot}\\\\`);\n}\n\nfunction validateWorkdir(workdir: string): string | undefined {\n\tconst candidate = resolve(workdir);\n\tif (isAbsolute(workdir) && !workdirRoots.some((root) => isPathUnderRoot(candidate, root))) {\n\t\treturn `workdir ${workdir} is outside allowed roots`;\n\t}\n\tif (!workdirRoots.some((root) => isPathUnderRoot(candidate, root))) {\n\t\treturn `workdir ${workdir} resolves outside allowed roots`;\n\t}\n\treturn undefined;\n}\n\nfunction runDeliverValidation(deliver: string, origin?: CronJobOrigin | null): string | undefined {\n\tif (deliverMentionsChatPlatform(deliver) && !isFeatureEnabled(\"chat-platforms\")) {\n\t\treturn chatPlatformDeliverBlockedMessage();\n\t}\n\treturn deliverValidator?.(deliver, origin);\n}\n\nfunction deriveName(prompt: string): string {\n\tconst first = prompt.split(\"\\n\")[0].replace(/\\s+/g, \" \").trim();\n\treturn (first || \"cron job\").slice(0, MAX_NAME_LEN);\n}\n\nexport async function createJob(input: CreateJobInput): Promise<CronJob> {\n\tconst prompt = input.prompt.trim();\n\tif (!prompt) throw new Error(\"createJob: prompt is empty\");\n\tif (input.workdir) {\n\t\tconst err = validateWorkdir(input.workdir);\n\t\tif (err) throw new Error(`createJob: ${err}`);\n\t}\n\tif (input.deliver) {\n\t\tconst err = runDeliverValidation(input.deliver, input.origin);\n\t\tif (err) throw new Error(`createJob: ${err}`);\n\t}\n\tconst now = new Date();\n\tconst parsed = parseSchedule(input.schedule, now);\n\tconst isOneShot = parsed.schedule.kind === \"once\";\n\tconst times = isOneShot ? 1 : (input.times ?? null);\n\tif (times !== null && (!Number.isInteger(times) || times < 1)) {\n\t\tthrow new Error(`createJob: times must be a positive integer or null, got ${times}`);\n\t}\n\tconst job: CronJob = {\n\t\tid: randomUUID().replaceAll(\"-\", \"\").slice(0, 12),\n\t\tname: (input.name?.trim() || deriveName(prompt)).slice(0, MAX_NAME_LEN),\n\t\tprompt,\n\t\tschedule: parsed.schedule,\n\t\tscheduleDisplay: parsed.scheduleDisplay,\n\t\trepeat: { times, completed: 0 },\n\t\tenabled: true,\n\t\tstate: \"scheduled\",\n\t\tcreatedAt: now.toISOString(),\n\t\tnextRunAt: null,\n\t\tlastRunAt: null,\n\t\tlastStatus: null,\n\t\tlastError: null,\n\t\tlastDeliveryError: null,\n\t\tdeliver: input.deliver?.trim() || \"local\",\n\t\torigin: input.origin ?? null,\n\t\tcontextFrom: input.contextFrom,\n\t\tworkdir: input.workdir,\n\t};\n\tjob.nextRunAt = computeNextRun(job, now)?.toISOString() ?? null;\n\tloadJobs().push(job);\n\tawait persist();\n\treturn job;\n}\n\n/** Look up a job by id, then by unique name. Throws when not found or when the name is ambiguous. */\nexport function getJob(idOrName: string): CronJob {\n\tconst jobs = loadJobs();\n\tconst byId = jobs.find((j) => j.id === idOrName);\n\tif (byId) return byId;\n\tconst byName = jobs.filter((j) => j.name === idOrName);\n\tif (byName.length === 1) return byName[0];\n\tif (byName.length > 1) {\n\t\tthrow new Error(\n\t\t\t`cron job name \"${idOrName}\" is ambiguous (${byName.length} matches: ${byName.map((j) => j.id).join(\", \")})`,\n\t\t);\n\t}\n\tthrow new Error(`no cron job matches \"${idOrName}\"`);\n}\n\nexport function listJobs(): CronJob[] {\n\treturn [...loadJobs()];\n}\n\nexport async function updateJob(idOrName: string, patch: JobPatch): Promise<CronJob> {\n\tconst job = getJob(idOrName);\n\tif (patch.name !== undefined) job.name = patch.name.trim().slice(0, MAX_NAME_LEN) || job.name;\n\tif (patch.workdir !== undefined) {\n\t\tif (patch.workdir) {\n\t\t\tconst err = validateWorkdir(patch.workdir);\n\t\t\tif (err) throw new Error(`updateJob: ${err}`);\n\t\t}\n\t\tjob.workdir = patch.workdir;\n\t}\n\tif (patch.deliver !== undefined) {\n\t\tconst err = runDeliverValidation(patch.deliver, patch.origin ?? job.origin);\n\t\tif (err) throw new Error(`updateJob: ${err}`);\n\t\tjob.deliver = patch.deliver.trim() || \"local\";\n\t}\n\tif (patch.prompt !== undefined) {\n\t\tconst prompt = patch.prompt.trim();\n\t\tif (!prompt) throw new Error(\"updateJob: prompt is empty\");\n\t\tjob.prompt = prompt;\n\t}\n\tif (patch.schedule !== undefined) {\n\t\tconst parsed = parseSchedule(patch.schedule);\n\t\tjob.schedule = parsed.schedule;\n\t\tjob.scheduleDisplay = parsed.scheduleDisplay;\n\t\tif (parsed.schedule.kind === \"once\") job.repeat.times = 1;\n\t\tjob.nextRunAt = computeNextRun(job)?.toISOString() ?? null;\n\t}\n\tif (patch.enabled !== undefined) job.enabled = patch.enabled;\n\tif (patch.times !== undefined) {\n\t\tif (patch.times !== null && (!Number.isInteger(patch.times) || patch.times < 1)) {\n\t\t\tthrow new Error(`updateJob: times must be a positive integer or null, got ${patch.times}`);\n\t\t}\n\t\tif (job.schedule.kind !== \"once\") job.repeat.times = patch.times;\n\t}\n\tif (patch.contextFrom !== undefined) job.contextFrom = patch.contextFrom;\n\tif (patch.origin !== undefined) job.origin = patch.origin;\n\tif (patch.nextRunAt !== undefined) job.nextRunAt = patch.nextRunAt;\n\tif (patch.lastDeliveryError !== undefined) job.lastDeliveryError = patch.lastDeliveryError;\n\tawait persist();\n\treturn job;\n}\n\nexport async function pauseJob(idOrName: string): Promise<CronJob> {\n\tconst job = getJob(idOrName);\n\tif (job.state !== \"completed\") job.state = \"paused\";\n\tawait persist();\n\treturn job;\n}\n\nexport async function resumeJob(idOrName: string): Promise<CronJob> {\n\tconst job = getJob(idOrName);\n\tif (job.state === \"paused\") {\n\t\tjob.state = \"scheduled\";\n\t\tjob.nextRunAt = computeNextRun(job)?.toISOString() ?? null;\n\t}\n\tawait persist();\n\treturn job;\n}\n\nexport async function removeJob(idOrName: string): Promise<CronJob> {\n\tconst job = getJob(idOrName);\n\tconst jobs = loadJobs();\n\tjobs.splice(jobs.indexOf(job), 1);\n\tawait persist();\n\treturn job;\n}\n\n// ---------------------------------------------------------------------------\n// Scheduling\n// ---------------------------------------------------------------------------\n\n/**\n * Jobs due to fire now. Skips disabled/paused/completed jobs and jobs without a\n * nextRunAt. One-shots fire up to 120s late (flat recovery grace); beyond that\n * they are marked state \"error\" and skipped. Recurring jobs whose nextRunAt is\n * older than the grace window — min(max(period/2, 120s), 2h) — are fast-forwarded\n * to their next future slot but still fire ONCE now (collapse backlog, no burst).\n */\nexport async function getDueJobs(now: Date = new Date()): Promise<CronJob[]> {\n\tconst due: CronJob[] = [];\n\tlet changed = false;\n\tfor (const job of loadJobs()) {\n\t\tif (!job.enabled || job.state === \"paused\" || job.state === \"completed\") continue;\n\t\tif (!job.nextRunAt) continue;\n\t\tconst next = new Date(job.nextRunAt).getTime();\n\t\tif (Number.isNaN(next) || now.getTime() < next) continue;\n\n\t\tif (job.schedule.kind === \"once\") {\n\t\t\tif (now.getTime() - next <= ONE_SHOT_GRACE_MS) {\n\t\t\t\tdue.push(job);\n\t\t\t} else {\n\t\t\t\tjob.state = \"error\";\n\t\t\t\tjob.lastStatus = \"error\";\n\t\t\t\tjob.lastError = \"missed one-shot schedule beyond the 120s recovery grace\";\n\t\t\t\tjob.nextRunAt = null;\n\t\t\t\tchanged = true;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst grace = Math.min(Math.max(periodMs(job, now) / 2, MIN_GRACE_MS), MAX_GRACE_MS);\n\t\tif (now.getTime() - next > grace) {\n\t\t\tjob.nextRunAt = nextSlotAfter(job, now)?.toISOString() ?? null;\n\t\t\tchanged = true;\n\t\t}\n\t\tdue.push(job);\n\t}\n\tif (changed) await persist();\n\treturn due;\n}\n\n/**\n * Advance a recurring job to its next slot. Called by the scheduler BEFORE\n * execution so a crash mid-run loses at most that run (at-most-once).\n */\nexport async function advanceNextRun(jobId: string): Promise<CronJob> {\n\tconst job = getJob(jobId);\n\tif (job.schedule.kind !== \"once\") {\n\t\t// Advance to the first slot strictly after NOW: the job fires once for the\n\t\t// current slot, and the next fire is the next future slot (no catch-up burst).\n\t\tjob.nextRunAt = nextSlotAfter(job, new Date())?.toISOString() ?? computeNextRun(job)?.toISOString() ?? null;\n\t\tawait persist();\n\t}\n\treturn job;\n}\n\n/**\n * Record a finished run: lastRunAt/lastStatus/lastError, repeat.completed++,\n * then either complete the job (finite repeats exhausted, one-shots included)\n * or re-anchor nextRunAt off the previous scheduled slot (no drift).\n */\nexport async function markJobRun(jobId: string, result: { status: CronJobStatus; error?: string }): Promise<CronJob> {\n\tconst job = getJob(jobId);\n\tconst now = new Date();\n\tjob.lastRunAt = now.toISOString();\n\tjob.lastStatus = result.status;\n\tjob.lastError = result.error ?? null;\n\tjob.repeat.completed += 1;\n\tif (job.repeat.times !== null && job.repeat.completed >= job.repeat.times) {\n\t\tjob.state = \"completed\";\n\t\tjob.nextRunAt = null;\n\t} else if (job.schedule.kind === \"interval\") {\n\t\t// Anchor to the previous scheduled slot + period to avoid cumulative drift.\n\t\t// If advanceNextRun already moved nextRunAt into the future (scheduler path),\n\t\t// keep it; otherwise (manual run) step forward from now.\n\t\tconst current = job.nextRunAt ? new Date(job.nextRunAt) : now;\n\t\tif (current.getTime() > now.getTime()) {\n\t\t\t// Already advanced by the scheduler — keep the scheduled slot.\n\t\t\tjob.nextRunAt = current.toISOString();\n\t\t} else {\n\t\t\tjob.nextRunAt = nextSlotAfter(job, now)?.toISOString() ?? null;\n\t\t}\n\t} else if (job.schedule.kind === \"cron\") {\n\t\tjob.nextRunAt = new Cron(job.schedule.expr!).nextRun(now)?.toISOString() ?? null;\n\t}\n\tawait persist();\n\treturn job;\n}\n\n// ---------------------------------------------------------------------------\n// Per-run output\n// ---------------------------------------------------------------------------\n\nfunction formatStamp(d: Date): string {\n\tconst p = (n: number) => String(n).padStart(2, \"0\");\n\treturn `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}_${p(d.getHours())}-${p(d.getMinutes())}-${p(d.getSeconds())}`;\n}\n\nfunction parseStamp(stamp: string): Date {\n\tconst m = /^(\\d{4})-(\\d{2})-(\\d{2})_(\\d{2})-(\\d{2})-(\\d{2})/.exec(stamp);\n\tif (!m) return new Date();\n\treturn new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), Number(m[4]), Number(m[5]), Number(m[6]));\n}\n\nlet lastOutputStamp = \"\";\n\n/** Monotonic timestamp stamp: never collides within the process, keeps lexicographic = chronological order. */\nfunction nextOutputStamp(): string {\n\tlet d = new Date();\n\tlet stamp = formatStamp(d);\n\tif (stamp <= lastOutputStamp) {\n\t\td = new Date(parseStamp(lastOutputStamp).getTime() + 1000);\n\t\tstamp = formatStamp(d);\n\t}\n\tlastOutputStamp = stamp;\n\treturn stamp;\n}\n\nfunction listOutputFiles(jobId: string): string[] {\n\tconst dir = outputDir(jobId);\n\tif (!existsSync(dir)) return [];\n\treturn readdirSync(dir)\n\t\t.filter((f) => f.endsWith(\".md\"))\n\t\t.sort();\n}\n\n/** Save a run's output and prune to the newest OUTPUT_RETENTION files. Returns the file path. */\nexport async function saveJobOutput(jobId: string, content: string): Promise<string> {\n\tconst dir = outputDir(jobId);\n\tmkdirSync(dir, { recursive: true });\n\tconst file = join(dir, `${nextOutputStamp()}.md`);\n\twriteFileSync(file, content, \"utf-8\");\n\tconst files = listOutputFiles(jobId);\n\twhile (files.length > OUTPUT_RETENTION) {\n\t\tconst oldest = files.shift();\n\t\tif (oldest) rmSync(join(dir, oldest), { force: true });\n\t}\n\treturn file;\n}\n\n/** Content of the newest output file for a job, or null when none exists. */\nexport function getLatestJobOutput(jobId: string): string | null {\n\tconst files = listOutputFiles(jobId);\n\tconst latest = files[files.length - 1];\n\tif (!latest) return null;\n\ttry {\n\t\treturn readFileSync(join(outputDir(jobId), latest), \"utf-8\");\n\t} catch {\n\t\treturn null;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"jobs.d.ts","sourceRoot":"","sources":["../../../src/core/cron/jobs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AA8BH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;AAE5D,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,gBAAgB,CAAC;IACvB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC7B,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,GAAG,OAAO,CAAC;AAC1E,MAAM,MAAM,aAAa,GAAG,IAAI,GAAG,OAAO,CAAC;AAE3C,MAAM,WAAW,OAAO;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,YAAY,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,aAAa,GAAG,IAAI,CAAC;IACjC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kHAAkH;IAClH,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,wFAAwF;IACxF,SAAS,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CACtG;AAED,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,QAAQ;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9B,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAMD,+DAA+D;AAC/D,eAAO,MAAM,iBAAiB,SAAU,CAAC;AACzC,gCAAgC;AAChC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB,OAAO,CAAC;AAYvC;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAI5D;AA2DD,yFAAyF;AACzF,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAoEnF;AAMD,MAAM,WAAW,cAAc;IAC9B,QAAQ,EAAE,YAAY,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACxB;AAWD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,cAAc,CA0CnF;AAMD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,GAAE,IAAiB,GAAG,IAAI,GAAG,IAAI,CAehF;AAyBD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAEzD;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACtC,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,IAAI,KAAK,MAAM,GAAG,SAAS,CAAC,GAAG,SAAS,GACtF,IAAI,CAEN;AAED,mDAAmD;AACnD,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C;AAuCD,wBAAsB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CA2CvE;AAED,qGAAqG;AACrG,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAYhD;AAED,wBAAgB,QAAQ,IAAI,OAAO,EAAE,CAEpC;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,CAyCnF;AAED,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMjE;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAWlE;AAED,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOlE;AAMD;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,GAAG,GAAE,IAAiB,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAyB3E;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUpE;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAC/B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/D,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CA8BzB;AAED,uEAAuE;AACvE,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,UAAQ,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAWnG;AAED,iFAAiF;AACjF,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC,CAW5D;AAED,wBAAsB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAO7E;AAuCD,iGAAiG;AACjG,wBAAsB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAWnF;AAED,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAS/D","sourcesContent":["/**\n * lunR: cron job model + persistent store (Phase 1 of the cron/gateway roadmap).\n *\n * Jobs persist at `<agentDir>/cron/jobs.json` (`{ jobs: [...], updated_at }`),\n * agentDir resolved via getAgentDir() (honors PI_CODING_AGENT_DIR, defaults\n * ~/.lunr/agent). Writes are atomic (tmp file in the same dir + rename) and\n * serialized with a cross-process read-modify-write lock over fresh data.\n *\n * Per-run output goes to `<agentDir>/cron/output/<jobId>/<YYYY-MM-DD_HH-MM-SS>.md`\n * with a retention cap of the newest 50 files per job.\n *\n * Tests inject a base dir via setCronBaseDir() so no real agent dir is touched.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport {\n\texistsSync,\n\tmkdirSync,\n\treaddirSync,\n\treadFileSync,\n\trealpathSync,\n\trenameSync,\n\trmSync,\n\tstatSync,\n\twriteFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { isAbsolute, join, normalize, relative, resolve, sep } from \"node:path\";\nimport { Cron } from \"croner\";\nimport lockfile from \"proper-lockfile\";\nimport { getAgentDir } from \"../../config.ts\";\nimport { loadGatewayConfig } from \"../../gateway/config.ts\";\nimport {\n\tchatPlatformDeliverBlockedMessage,\n\tdeliverMentionsChatPlatform,\n\tisFeatureEnabled,\n} from \"../install-features.ts\";\n\n// ---------------------------------------------------------------------------\n// Model\n// ---------------------------------------------------------------------------\n\nexport type CronScheduleKind = \"once\" | \"interval\" | \"cron\";\n\nexport interface CronSchedule {\n\tkind: CronScheduleKind;\n\t/** ISO timestamp, one-shots only. */\n\trunAt?: string;\n\t/** Interval length in minutes, interval kind only. */\n\tminutes?: number;\n\t/** 5-field cron expression, cron kind only. */\n\texpr?: string;\n}\n\nexport interface CronJobOrigin {\n\t/** Requester identity, absent on legacy jobs. Never persist adapter role grants. */\n\tuserId?: string;\n\tplatform: string;\n\tchatId: string;\n\tthreadId?: string;\n\tchatType?: string;\n}\n\nexport type CronJobState = \"scheduled\" | \"paused\" | \"completed\" | \"error\";\nexport type CronJobStatus = \"ok\" | \"error\";\n\nexport interface CronJob {\n\tid: string;\n\tname: string;\n\tprompt: string;\n\tschedule: CronSchedule;\n\tscheduleDisplay: string;\n\trepeat: { times: number | null; completed: number };\n\tenabled: boolean;\n\tstate: CronJobState;\n\tcreatedAt: string;\n\tnextRunAt: string | null;\n\tlastRunAt: string | null;\n\tlastStatus: CronJobStatus | null;\n\tlastError: string | null;\n\tlastDeliveryError: string | null;\n\t/** \"local\" | \"origin\" | \"telegram[:chatId]\" | \"discord[:chatId]\" (comma-combinable). v1 TUI only uses \"local\". */\n\tdeliver: string;\n\torigin?: CronJobOrigin | null;\n\t/** Job IDs whose latest output (8K chars cap each) is prepended to the prompt. */\n\tcontextFrom?: string[];\n\tworkdir?: string;\n\t/** Unknown interrupted effects require an explicit run, rather than ordinary resume. */\n\tinterrupted?: boolean;\n\t/** Durable claim written before dispatch. Interrupted claims require explicit retry. */\n\tactiveRun?: { id: string; scheduledAt: string | null; startedAt: string; advancedTo?: string | null };\n}\n\nexport interface CreateJobInput {\n\tprompt: string;\n\t/** Raw schedule input, parsed by parseSchedule(). */\n\tschedule: string;\n\tname?: string;\n\t/** Repetition cap; null = forever. One-shots are always times=1. */\n\ttimes?: number | null;\n\tdeliver?: string;\n\tcontextFrom?: string[];\n\tworkdir?: string;\n\torigin?: CronJobOrigin | null;\n}\n\nexport interface JobPatch {\n\tname?: string;\n\tprompt?: string;\n\t/** Raw schedule input; reparsed and re-anchors nextRunAt. */\n\tschedule?: string;\n\tdeliver?: string;\n\tenabled?: boolean;\n\ttimes?: number | null;\n\tcontextFrom?: string[];\n\tworkdir?: string;\n\torigin?: CronJobOrigin | null;\n\t/** Internal: manual-run trigger and delivery-error bookkeeping. */\n\tnextRunAt?: string | null;\n\tlastDeliveryError?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Flat recovery grace for one-shots: fire up to 120s late. */\nexport const ONE_SHOT_GRACE_MS = 120_000;\n/** Per-job output retention. */\nexport const OUTPUT_RETENTION = 50;\n/** Cap per upstream job output injected via contextFrom. */\nexport const CONTEXT_OUTPUT_CAP = 8000;\n\nconst MAX_NAME_LEN = 50;\n\n// ---------------------------------------------------------------------------\n// Paths + store state\n// ---------------------------------------------------------------------------\n\nlet baseDirOverride: string | undefined;\nlet transactionJobs: CronJob[] | undefined;\nlet writeQueue: Promise<void> = Promise.resolve();\n\n/**\n * Override the base dir (defaults to the agent dir) and reset the in-memory\n * transaction queue. Passing undefined restores the default. For tests.\n */\nexport function setCronBaseDir(dir: string | undefined): void {\n\tbaseDirOverride = dir;\n\ttransactionJobs = undefined;\n\twriteQueue = Promise.resolve();\n}\n\nfunction cronDir(): string {\n\treturn join(baseDirOverride ?? getAgentDir(), \"cron\");\n}\n\nfunction jobsFile(): string {\n\treturn join(cronDir(), \"jobs.json\");\n}\n\nfunction outputDir(jobId: string): string {\n\treturn join(cronDir(), \"output\", jobId);\n}\n\nfunction loadJobs(): CronJob[] {\n\tif (transactionJobs) return transactionJobs;\n\tconst file = jobsFile();\n\tif (!existsSync(file)) return [];\n\tconst parsed = JSON.parse(readFileSync(file, \"utf-8\"));\n\tif (!Array.isArray(parsed?.jobs)) throw new Error(\"invalid cron job store\");\n\treturn parsed.jobs as CronJob[];\n}\n\n/** Each mutation reads the latest snapshot while holding the profile store lock. */\nfunction mutateJobs<T>(fn: () => T): Promise<T> {\n\tconst dir = cronDir();\n\tconst file = join(dir, \"jobs.json\");\n\tconst operation = writeQueue.then(async () => {\n\t\tmkdirSync(dir, { recursive: true });\n\t\tconst release = await lockfile.lock(file, {\n\t\t\trealpath: false,\n\t\t\tretries: { retries: 100, minTimeout: 10, maxTimeout: 100 },\n\t\t});\n\t\ttry {\n\t\t\ttransactionJobs = loadJobs();\n\t\t\tconst result = fn();\n\t\t\tconst tmp = join(dir, `.jobs.json.${randomUUID()}.tmp`);\n\t\t\ttry {\n\t\t\t\twriteFileSync(\n\t\t\t\t\ttmp,\n\t\t\t\t\tJSON.stringify({ jobs: transactionJobs, updated_at: new Date().toISOString() }, null, 2),\n\t\t\t\t);\n\t\t\t\trenameSync(tmp, file);\n\t\t\t} finally {\n\t\t\t\trmSync(tmp, { force: true });\n\t\t\t}\n\t\t\treturn structuredClone(result);\n\t\t} finally {\n\t\t\ttransactionJobs = undefined;\n\t\t\tawait release();\n\t\t}\n\t});\n\twriteQueue = operation.then(\n\t\t() => undefined,\n\t\t() => undefined,\n\t);\n\treturn operation;\n}\n\n/** Acquire the non-preemptive profile scheduler lease. A live PID is never displaced. */\nexport async function acquireSchedulerLease(): Promise<(() => Promise<void>) | null> {\n\tconst dir = cronDir();\n\tmkdirSync(dir, { recursive: true });\n\tconst file = join(dir, \"scheduler\");\n\tconst ownerFile = `${file}.owner.json`;\n\tconst staleMs = 120_000;\n\tif (existsSync(`${file}.lock`)) {\n\t\tlet owner: unknown;\n\t\ttry {\n\t\t\towner = JSON.parse(readFileSync(ownerFile, \"utf-8\"));\n\t\t\tif (\n\t\t\t\t!owner ||\n\t\t\t\ttypeof owner !== \"object\" ||\n\t\t\t\t!(\"pid\" in owner) ||\n\t\t\t\ttypeof owner.pid !== \"number\" ||\n\t\t\t\t!Number.isInteger(owner.pid) ||\n\t\t\t\towner.pid <= 0 ||\n\t\t\t\towner.pid > 2_147_483_647\n\t\t\t)\n\t\t\t\tthrow new Error(\"invalid scheduler owner PID\");\n\t\t} catch (error) {\n\t\t\t// A competing operator may still be publishing its owner record. Missing\n\t\t\t// or malformed metadata never proves that an existing owner is dead.\n\t\t\tif (!existsSync(`${file}.lock`)) return acquireSchedulerLease();\n\t\t\tif ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n\t\t\t\ttry {\n\t\t\t\t\tif (Date.now() - statSync(`${file}.lock`).mtimeMs < staleMs) return null;\n\t\t\t\t} catch (lockError) {\n\t\t\t\t\tif ((lockError as NodeJS.ErrnoException).code === \"ENOENT\") return acquireSchedulerLease();\n\t\t\t\t}\n\t\t\t}\n\t\t\tthrow new Error(\n\t\t\t\t`Cron scheduler ownership cannot be verified. Before repairing it, confirm every operator using this profile has stopped. ` +\n\t\t\t\t\t`Then remove ${file}.lock and ${ownerFile}, and restart scheduling. Never remove them while an operator may be running.`,\n\t\t\t\t{ cause: error },\n\t\t\t);\n\t\t}\n\t\ttry {\n\t\t\tprocess.kill(owner.pid, 0);\n\t\t\treturn null;\n\t\t} catch (error) {\n\t\t\tif ((error as NodeJS.ErrnoException).code !== \"ESRCH\") return null;\n\t\t}\n\t}\n\tlet release: () => Promise<void>;\n\ttry {\n\t\trelease = await lockfile.lock(file, { realpath: false, stale: staleMs });\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code === \"ELOCKED\") return null;\n\t\tthrow error;\n\t}\n\tconst ownerTmp = join(dir, `.scheduler.owner.json.${randomUUID()}.tmp`);\n\ttry {\n\t\twriteFileSync(ownerTmp, JSON.stringify({ pid: process.pid }));\n\t\trenameSync(ownerTmp, ownerFile);\n\t} catch (error) {\n\t\tawait release();\n\t\tthrow error;\n\t} finally {\n\t\trmSync(ownerTmp, { force: true });\n\t}\n\treturn async () => {\n\t\ttry {\n\t\t\trmSync(ownerFile, { force: true });\n\t\t} finally {\n\t\t\tawait release();\n\t\t}\n\t};\n}\n\n// ---------------------------------------------------------------------------\n// Schedule parsing\n// ---------------------------------------------------------------------------\n\nexport interface ParsedSchedule {\n\tschedule: CronSchedule;\n\tscheduleDisplay: string;\n}\n\nconst INTERVAL_RE = /^every\\s+(\\d+)\\s*([mhd])$/i;\nconst DURATION_RE = /^(\\d+)\\s*([mhd])$/i;\n\nfunction toMinutes(value: number, unit: string): number {\n\tconst u = unit.toLowerCase();\n\tconst factor = u === \"d\" ? 1440 : u === \"h\" ? 60 : 1;\n\treturn value * factor;\n}\n\n/**\n * Parse a schedule input:\n * - `every 30m` / `every 2h` / `every 1d` → interval (minutes; d = 1440)\n * - bare `30m` / `2h` / `1d` → one-shot at now + duration\n * - ISO timestamp → one-shot at that time\n * - 5-field cron expression (validated via croner; throws on invalid)\n */\nexport function parseSchedule(input: string, now: Date = new Date()): ParsedSchedule {\n\tconst text = input.trim().replace(/\\s+/g, \" \");\n\tif (!text) throw new Error(\"schedule is empty\");\n\n\tconst interval = INTERVAL_RE.exec(text);\n\tif (interval) {\n\t\tconst minutes = toMinutes(Number(interval[1]), interval[2]);\n\t\tif (minutes <= 0) throw new Error(`invalid interval: \"${text}\"`);\n\t\treturn {\n\t\t\tschedule: { kind: \"interval\", minutes },\n\t\t\tscheduleDisplay: `every ${Number(interval[1])}${interval[2].toLowerCase()}`,\n\t\t};\n\t}\n\n\tconst duration = DURATION_RE.exec(text);\n\tif (duration) {\n\t\tconst minutes = toMinutes(Number(duration[1]), duration[2]);\n\t\tif (minutes <= 0) throw new Error(`invalid duration: \"${text}\"`);\n\t\tconst runAt = new Date(now.getTime() + minutes * 60_000).toISOString();\n\t\treturn { schedule: { kind: \"once\", runAt }, scheduleDisplay: text };\n\t}\n\n\tif (/^\\d{4}-\\d{2}-\\d{2}/.test(text)) {\n\t\tconst ts = Date.parse(text);\n\t\tif (!Number.isNaN(ts)) {\n\t\t\tconst runAt = new Date(ts).toISOString();\n\t\t\treturn { schedule: { kind: \"once\", runAt }, scheduleDisplay: runAt };\n\t\t}\n\t}\n\n\tif (text.split(\" \").length === 5) {\n\t\ttry {\n\t\t\tnew Cron(text);\n\t\t} catch (err) {\n\t\t\tthrow new Error(`invalid cron expression \"${text}\": ${err instanceof Error ? err.message : String(err)}`);\n\t\t}\n\t\treturn { schedule: { kind: \"cron\", expr: text }, scheduleDisplay: text };\n\t}\n\n\tthrow new Error(\n\t\t`unrecognized schedule: \"${input}\" (expected \"every 30m\", \"2h\", an ISO timestamp, or a 5-field cron expression)`,\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Next-run computation\n// ---------------------------------------------------------------------------\n\n/**\n * Compute the next run time for a job:\n * - once: the stored runAt, recoverable within a 120s grace if never run; null after that or once run.\n * - interval: (lastRunAt ?? createdAt) + minutes.\n * - cron: croner nextRun after (lastRunAt ?? now).\n */\nexport function computeNextRun(job: CronJob, now: Date = new Date()): Date | null {\n\tconst s = job.schedule;\n\tif (s.kind === \"once\") {\n\t\tif (job.repeat.completed > 0) return null;\n\t\tif (!s.runAt) return null;\n\t\tconst runAt = new Date(s.runAt);\n\t\tif (Number.isNaN(runAt.getTime())) return null;\n\t\treturn now.getTime() - runAt.getTime() <= ONE_SHOT_GRACE_MS ? runAt : null;\n\t}\n\tif (s.kind === \"interval\") {\n\t\tconst base = job.lastRunAt ? new Date(job.lastRunAt) : new Date(job.createdAt);\n\t\treturn new Date(base.getTime() + (s.minutes ?? 0) * 60_000);\n\t}\n\tconst cron = new Cron(s.expr!);\n\treturn cron.nextRun(job.lastRunAt ? new Date(job.lastRunAt) : now);\n}\n\n/** Next slot strictly after `after` for a recurring job. */\nfunction nextSlotAfter(job: CronJob, after: Date): Date | null {\n\tif (job.schedule.kind === \"interval\") {\n\t\tconst period = (job.schedule.minutes ?? 0) * 60_000;\n\t\tif (period <= 0) return null;\n\t\t// Step forward from the stored nextRunAt (or now) until strictly in the future of `after`.\n\t\tlet slot = job.nextRunAt ? new Date(job.nextRunAt).getTime() : new Date(job.createdAt).getTime() + period;\n\t\twhile (slot <= after.getTime()) slot += period;\n\t\treturn new Date(slot);\n\t}\n\tif (job.schedule.kind === \"cron\") {\n\t\treturn new Cron(job.schedule.expr!).nextRun(after);\n\t}\n\treturn null;\n}\n\n// ---------------------------------------------------------------------------\n// Validation\n// ---------------------------------------------------------------------------\n\nlet workdirRoots: string[] = [process.cwd(), homedir()];\nlet deliverValidator: ((deliver: string, origin?: CronJobOrigin | null) => string | undefined) | undefined;\n\n/**\n * Configure the allowed absolute roots for cron job workdirs. Relative workdirs\n * are resolved against process.cwd() and must fall under one of these roots.\n * Default roots are process.cwd() and the user's home directory.\n */\nexport function setCronWorkdirRoots(roots: string[]): void {\n\tworkdirRoots = roots.map((r) => normalize(resolve(r)));\n}\n\n/**\n * Configure a deliver-target validator. When set, createJob/updateJob reject\n * deliver strings that the validator does not approve. The gateway uses this\n * to prevent delivery to arbitrary chats.\n */\nexport function setCronDeliverValidator(\n\tfn: ((deliver: string, origin?: CronJobOrigin | null) => string | undefined) | undefined,\n): void {\n\tdeliverValidator = fn;\n}\n\n/** Test seam: restore default validation state. */\nexport function resetCronValidators(): void {\n\tworkdirRoots = [process.cwd(), homedir()];\n\tdeliverValidator = undefined;\n}\n\nfunction isPathUnderRoot(absPath: string, root: string): boolean {\n\tconst rel = relative(root, absPath);\n\treturn rel === \"\" || (!rel.startsWith(`..${sep}`) && rel !== \"..\" && !isAbsolute(rel));\n}\n\nfunction validateWorkdir(workdir: string, origin?: CronJobOrigin | null): string | undefined {\n\tconst candidate = origin ? realpathSync(resolve(workdir)) : resolve(workdir);\n\tconst roots = origin\n\t\t? (loadGatewayConfig().projectRoots ?? []).flatMap((root) => {\n\t\t\t\ttry {\n\t\t\t\t\treturn [realpathSync(root)];\n\t\t\t\t} catch {\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t})\n\t\t: workdirRoots;\n\tif (isAbsolute(workdir) && !roots.some((root) => isPathUnderRoot(candidate, root))) {\n\t\treturn `workdir ${workdir} is outside allowed roots`;\n\t}\n\tif (!roots.some((root) => isPathUnderRoot(candidate, root))) {\n\t\treturn `workdir ${workdir} resolves outside allowed roots`;\n\t}\n\treturn undefined;\n}\n\nfunction runDeliverValidation(deliver: string, origin?: CronJobOrigin | null): string | undefined {\n\tif (deliverMentionsChatPlatform(deliver) && !isFeatureEnabled(\"chat-platforms\")) {\n\t\treturn chatPlatformDeliverBlockedMessage();\n\t}\n\treturn deliverValidator?.(deliver, origin);\n}\n\nfunction deriveName(prompt: string): string {\n\tconst first = prompt.split(\"\\n\")[0].replace(/\\s+/g, \" \").trim();\n\treturn (first || \"cron job\").slice(0, MAX_NAME_LEN);\n}\n\nexport async function createJob(input: CreateJobInput): Promise<CronJob> {\n\treturn mutateJobs(() => {\n\t\tconst prompt = input.prompt.trim();\n\t\tif (!prompt) throw new Error(\"createJob: prompt is empty\");\n\t\tif (input.workdir) {\n\t\t\tconst err = validateWorkdir(input.workdir, input.origin);\n\t\t\tif (err) throw new Error(`createJob: ${err}`);\n\t\t}\n\t\tif (input.deliver) {\n\t\t\tconst err = runDeliverValidation(input.deliver, input.origin);\n\t\t\tif (err) throw new Error(`createJob: ${err}`);\n\t\t}\n\t\tconst now = new Date();\n\t\tconst parsed = parseSchedule(input.schedule, now);\n\t\tconst isOneShot = parsed.schedule.kind === \"once\";\n\t\tconst times = isOneShot ? 1 : (input.times ?? null);\n\t\tif (times !== null && (!Number.isInteger(times) || times < 1)) {\n\t\t\tthrow new Error(`createJob: times must be a positive integer or null, got ${times}`);\n\t\t}\n\t\tconst job: CronJob = {\n\t\t\tid: randomUUID().replaceAll(\"-\", \"\").slice(0, 12),\n\t\t\tname: (input.name?.trim() || deriveName(prompt)).slice(0, MAX_NAME_LEN),\n\t\t\tprompt,\n\t\t\tschedule: parsed.schedule,\n\t\t\tscheduleDisplay: parsed.scheduleDisplay,\n\t\t\trepeat: { times, completed: 0 },\n\t\t\tenabled: true,\n\t\t\tstate: \"scheduled\",\n\t\t\tcreatedAt: now.toISOString(),\n\t\t\tnextRunAt: null,\n\t\t\tlastRunAt: null,\n\t\t\tlastStatus: null,\n\t\t\tlastError: null,\n\t\t\tlastDeliveryError: null,\n\t\t\tdeliver: input.deliver?.trim() || \"local\",\n\t\t\torigin: input.origin ?? null,\n\t\t\tcontextFrom: input.contextFrom,\n\t\t\tworkdir: input.workdir,\n\t\t};\n\t\tjob.nextRunAt = computeNextRun(job, now)?.toISOString() ?? null;\n\t\tloadJobs().push(job);\n\t\treturn job;\n\t});\n}\n\n/** Look up a job by id, then by unique name. Throws when not found or when the name is ambiguous. */\nexport function getJob(idOrName: string): CronJob {\n\tconst jobs = loadJobs();\n\tconst byId = jobs.find((j) => j.id === idOrName);\n\tif (byId) return byId;\n\tconst byName = jobs.filter((j) => j.name === idOrName);\n\tif (byName.length === 1) return byName[0];\n\tif (byName.length > 1) {\n\t\tthrow new Error(\n\t\t\t`cron job name \"${idOrName}\" is ambiguous (${byName.length} matches: ${byName.map((j) => j.id).join(\", \")})`,\n\t\t);\n\t}\n\tthrow new Error(`no cron job matches \"${idOrName}\"`);\n}\n\nexport function listJobs(): CronJob[] {\n\treturn [...loadJobs()];\n}\n\nexport async function updateJob(idOrName: string, patch: JobPatch): Promise<CronJob> {\n\treturn mutateJobs(() => {\n\t\tconst job = getJob(idOrName);\n\t\tif (patch.name !== undefined) job.name = patch.name.trim().slice(0, MAX_NAME_LEN) || job.name;\n\t\tif (patch.workdir !== undefined) {\n\t\t\tif (patch.workdir) {\n\t\t\t\tconst err = validateWorkdir(patch.workdir, patch.origin ?? job.origin);\n\t\t\t\tif (err) throw new Error(`updateJob: ${err}`);\n\t\t\t}\n\t\t\tjob.workdir = patch.workdir;\n\t\t}\n\t\tif (patch.deliver !== undefined) {\n\t\t\tconst err = runDeliverValidation(patch.deliver, patch.origin ?? job.origin);\n\t\t\tif (err) throw new Error(`updateJob: ${err}`);\n\t\t\tjob.deliver = patch.deliver.trim() || \"local\";\n\t\t}\n\t\tif (patch.prompt !== undefined) {\n\t\t\tconst prompt = patch.prompt.trim();\n\t\t\tif (!prompt) throw new Error(\"updateJob: prompt is empty\");\n\t\t\tjob.prompt = prompt;\n\t\t}\n\t\tif (patch.schedule !== undefined) {\n\t\t\tconst parsed = parseSchedule(patch.schedule);\n\t\t\tjob.schedule = parsed.schedule;\n\t\t\tjob.scheduleDisplay = parsed.scheduleDisplay;\n\t\t\tif (parsed.schedule.kind === \"once\") job.repeat.times = 1;\n\t\t\tjob.nextRunAt = computeNextRun(job)?.toISOString() ?? null;\n\t\t}\n\t\tif (patch.enabled !== undefined) job.enabled = patch.enabled;\n\t\tif (patch.times !== undefined) {\n\t\t\tif (patch.times !== null && (!Number.isInteger(patch.times) || patch.times < 1)) {\n\t\t\t\tthrow new Error(`updateJob: times must be a positive integer or null, got ${patch.times}`);\n\t\t\t}\n\t\t\tif (job.schedule.kind !== \"once\") job.repeat.times = patch.times;\n\t\t}\n\t\tif (patch.contextFrom !== undefined) job.contextFrom = patch.contextFrom;\n\t\tif (patch.origin !== undefined) job.origin = patch.origin;\n\t\tif (patch.nextRunAt !== undefined) job.nextRunAt = patch.nextRunAt;\n\t\tif (patch.lastDeliveryError !== undefined) job.lastDeliveryError = patch.lastDeliveryError;\n\t\treturn job;\n\t});\n}\n\nexport async function pauseJob(idOrName: string): Promise<CronJob> {\n\treturn mutateJobs(() => {\n\t\tconst job = getJob(idOrName);\n\t\tif (job.state !== \"completed\") job.state = \"paused\";\n\t\treturn job;\n\t});\n}\n\nexport async function resumeJob(idOrName: string): Promise<CronJob> {\n\treturn mutateJobs(() => {\n\t\tconst job = getJob(idOrName);\n\t\tif (job.interrupted)\n\t\t\tthrow new Error(\"interrupted run has unknown effects; inspect them and use cron run for a deliberate retry\");\n\t\tif (job.state === \"paused\") {\n\t\t\tjob.state = \"scheduled\";\n\t\t\tjob.nextRunAt = computeNextRun(job)?.toISOString() ?? null;\n\t\t}\n\t\treturn job;\n\t});\n}\n\nexport async function removeJob(idOrName: string): Promise<CronJob> {\n\treturn mutateJobs(() => {\n\t\tconst job = getJob(idOrName);\n\t\tconst jobs = loadJobs();\n\t\tjobs.splice(jobs.indexOf(job), 1);\n\t\treturn job;\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Scheduling\n// ---------------------------------------------------------------------------\n\n/**\n * Jobs due to fire now. Skips disabled/paused/completed jobs and jobs without a\n * nextRunAt. One-shots fire up to 120s late (flat recovery grace); beyond that\n * they are marked state \"error\" and skipped. Recurring backlog fires once;\n * claiming the occurrence advances it to the next future slot without a burst.\n */\nexport async function getDueJobs(now: Date = new Date()): Promise<CronJob[]> {\n\treturn mutateJobs(() => {\n\t\tconst due: CronJob[] = [];\n\t\tfor (const job of loadJobs()) {\n\t\t\tif (!job.enabled || job.state !== \"scheduled\" || job.activeRun) continue;\n\t\t\tif (!job.nextRunAt) continue;\n\t\t\tconst next = new Date(job.nextRunAt).getTime();\n\t\t\tif (Number.isNaN(next) || now.getTime() < next) continue;\n\n\t\t\tif (job.schedule.kind === \"once\") {\n\t\t\t\tif (now.getTime() - next <= ONE_SHOT_GRACE_MS) {\n\t\t\t\t\tdue.push(job);\n\t\t\t\t} else {\n\t\t\t\t\tjob.state = \"error\";\n\t\t\t\t\tjob.lastStatus = \"error\";\n\t\t\t\t\tjob.lastError = \"missed one-shot schedule beyond the 120s recovery grace\";\n\t\t\t\t\tjob.nextRunAt = null;\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tdue.push(job);\n\t\t}\n\t\treturn due;\n\t});\n}\n\n/**\n * Advance a recurring job to its next slot. Scheduler dispatch uses claimJobRun\n * instead so one-shots and recurring work both retain a durable claim.\n */\nexport async function advanceNextRun(jobId: string): Promise<CronJob> {\n\treturn mutateJobs(() => {\n\t\tconst job = getJob(jobId);\n\t\tif (job.schedule.kind !== \"once\") {\n\t\t\t// Advance to the first slot strictly after NOW: the job fires once for the\n\t\t\t// current slot, and the next fire is the next future slot (no catch-up burst).\n\t\t\tjob.nextRunAt = nextSlotAfter(job, new Date())?.toISOString() ?? computeNextRun(job)?.toISOString() ?? null;\n\t\t}\n\t\treturn job;\n\t});\n}\n\n/**\n * Record a finished run: lastRunAt/lastStatus/lastError, repeat.completed++,\n * then either complete the job (finite repeats exhausted, one-shots included)\n * or re-anchor nextRunAt off the previous scheduled slot (no drift).\n */\nexport async function markJobRun(\n\tjobId: string,\n\tresult: { status: CronJobStatus; error?: string; runId?: string },\n): Promise<CronJob | null> {\n\treturn mutateJobs(() => {\n\t\tconst job = loadJobs().find((job) => job.id === jobId);\n\t\tif (!job || (result.runId && job.activeRun?.id !== result.runId)) return null;\n\t\tdelete job.activeRun;\n\t\tdelete job.interrupted;\n\t\tconst now = new Date();\n\t\tjob.lastRunAt = now.toISOString();\n\t\tjob.lastStatus = result.status;\n\t\tjob.lastError = result.error ?? null;\n\t\tjob.repeat.completed += 1;\n\t\tif (job.repeat.times !== null && job.repeat.completed >= job.repeat.times) {\n\t\t\tjob.state = \"completed\";\n\t\t\tjob.nextRunAt = null;\n\t\t} else if (!result.runId && job.schedule.kind === \"interval\") {\n\t\t\t// Anchor to the previous scheduled slot + period to avoid cumulative drift.\n\t\t\t// If advanceNextRun already moved nextRunAt into the future (scheduler path),\n\t\t\t// keep it; otherwise (manual run) step forward from now.\n\t\t\tconst current = job.nextRunAt ? new Date(job.nextRunAt) : now;\n\t\t\tif (current.getTime() > now.getTime()) {\n\t\t\t\t// Already advanced by the scheduler — keep the scheduled slot.\n\t\t\t\tjob.nextRunAt = current.toISOString();\n\t\t\t} else {\n\t\t\t\tjob.nextRunAt = nextSlotAfter(job, now)?.toISOString() ?? null;\n\t\t\t}\n\t\t} else if (!result.runId && job.schedule.kind === \"cron\") {\n\t\t\tjob.nextRunAt = new Cron(job.schedule.expr!).nextRun(now)?.toISOString() ?? null;\n\t\t}\n\t\treturn job;\n\t});\n}\n\n/** Claim only a still-due, enabled occurrence from the fresh store. */\nexport async function claimJobRun(jobId: string, now: Date, manual = false): Promise<CronJob | null> {\n\treturn mutateJobs(() => {\n\t\tconst job = loadJobs().find((job) => job.id === jobId);\n\t\tif (!job || job.activeRun) return null;\n\t\tif (!manual && (!job.enabled || job.state !== \"scheduled\" || !job.nextRunAt || new Date(job.nextRunAt) > now))\n\t\t\treturn null;\n\t\tjob.activeRun = { id: randomUUID(), scheduledAt: job.nextRunAt, startedAt: now.toISOString() };\n\t\tjob.nextRunAt = job.schedule.kind === \"once\" ? null : (nextSlotAfter(job, now)?.toISOString() ?? null);\n\t\tjob.activeRun.advancedTo = job.nextRunAt;\n\t\treturn job;\n\t});\n}\n\n/** Recover uncertain interrupted work without replaying its external effects. */\nexport async function recoverInterruptedRuns(): Promise<void> {\n\tawait mutateJobs(() => {\n\t\tfor (const job of loadJobs()) {\n\t\t\tif (!job.activeRun) continue;\n\t\t\tjob.state = \"paused\";\n\t\t\tjob.lastStatus = \"error\";\n\t\t\tjob.lastError = \"interrupted run has unknown effects; use cron run to retry deliberately\";\n\t\t\tjob.interrupted = true;\n\t\t\tdelete job.activeRun;\n\t\t}\n\t});\n}\n\nexport async function deferJobRun(jobId: string, runId: string): Promise<void> {\n\tawait mutateJobs(() => {\n\t\tconst job = loadJobs().find((job) => job.id === jobId);\n\t\tif (job?.activeRun?.id !== runId) return;\n\t\tif (job.nextRunAt === job.activeRun.advancedTo) job.nextRunAt = job.activeRun.scheduledAt;\n\t\tdelete job.activeRun;\n\t});\n}\n\n// ---------------------------------------------------------------------------\n// Per-run output\n// ---------------------------------------------------------------------------\n\nfunction formatStamp(d: Date): string {\n\tconst p = (n: number) => String(n).padStart(2, \"0\");\n\treturn `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}_${p(d.getHours())}-${p(d.getMinutes())}-${p(d.getSeconds())}`;\n}\n\nfunction parseStamp(stamp: string): Date {\n\tconst m = /^(\\d{4})-(\\d{2})-(\\d{2})_(\\d{2})-(\\d{2})-(\\d{2})/.exec(stamp);\n\tif (!m) return new Date();\n\treturn new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), Number(m[4]), Number(m[5]), Number(m[6]));\n}\n\nlet lastOutputStamp = \"\";\n\n/** Monotonic timestamp stamp: never collides within the process, keeps lexicographic = chronological order. */\nfunction nextOutputStamp(): string {\n\tlet d = new Date();\n\tlet stamp = formatStamp(d);\n\tif (stamp <= lastOutputStamp) {\n\t\td = new Date(parseStamp(lastOutputStamp).getTime() + 1000);\n\t\tstamp = formatStamp(d);\n\t}\n\tlastOutputStamp = stamp;\n\treturn stamp;\n}\n\nfunction listOutputFiles(jobId: string): string[] {\n\tconst dir = outputDir(jobId);\n\tif (!existsSync(dir)) return [];\n\treturn readdirSync(dir)\n\t\t.filter((f) => f.endsWith(\".md\"))\n\t\t.sort();\n}\n\n/** Save a run's output and prune to the newest OUTPUT_RETENTION files. Returns the file path. */\nexport async function saveJobOutput(jobId: string, content: string): Promise<string> {\n\tconst dir = outputDir(jobId);\n\tmkdirSync(dir, { recursive: true });\n\tconst file = join(dir, `${nextOutputStamp()}.md`);\n\twriteFileSync(file, content, \"utf-8\");\n\tconst files = listOutputFiles(jobId);\n\twhile (files.length > OUTPUT_RETENTION) {\n\t\tconst oldest = files.shift();\n\t\tif (oldest) rmSync(join(dir, oldest), { force: true });\n\t}\n\treturn file;\n}\n\n/** Content of the newest output file for a job, or null when none exists. */\nexport function getLatestJobOutput(jobId: string): string | null {\n\tconst files = listOutputFiles(jobId);\n\tconst latest = files[files.length - 1];\n\tif (!latest) return null;\n\ttry {\n\t\treturn readFileSync(join(outputDir(jobId), latest), \"utf-8\");\n\t} catch {\n\t\treturn null;\n\t}\n}\n"]}
|