@desplega.ai/agent-swarm 1.49.0 → 1.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/openapi.json +2070 -728
- package/package.json +10 -1
- package/src/agentmail/handlers.ts +65 -10
- package/src/agentmail/templates.ts +111 -0
- package/src/be/db.ts +1233 -7
- package/src/be/migrations/014_prompt_templates.sql +33 -0
- package/src/be/migrations/015_workflow_workspace.sql +3 -0
- package/src/be/migrations/016_active_session_runner_session.sql +4 -0
- package/src/be/migrations/017_channel_activity_cursors.sql +6 -0
- package/src/be/migrations/018_fix_seed_double_version.sql +30 -0
- package/src/be/migrations/019_skills.sql +65 -0
- package/src/be/migrations/020_approval_requests.sql +41 -0
- package/src/be/seed.ts +62 -0
- package/src/be/skill-parser.ts +70 -0
- package/src/be/skill-sync.ts +106 -0
- package/src/commands/runner.ts +320 -132
- package/src/commands/templates.ts +172 -0
- package/src/github/handlers.ts +292 -77
- package/src/github/mentions-aliases.test.ts +73 -0
- package/src/github/mentions.test.ts +3 -3
- package/src/github/mentions.ts +32 -6
- package/src/github/templates.ts +398 -0
- package/src/gitlab/handlers.ts +63 -22
- package/src/gitlab/templates.ts +140 -0
- package/src/heartbeat/heartbeat.ts +19 -10
- package/src/heartbeat/templates.ts +30 -0
- package/src/http/active-sessions.ts +27 -0
- package/src/http/approval-requests.ts +247 -0
- package/src/http/config.ts +3 -3
- package/src/http/index.ts +9 -2
- package/src/http/poll.ts +135 -14
- package/src/http/prompt-templates.ts +412 -0
- package/src/http/schedules.ts +35 -0
- package/src/http/skills.ts +479 -0
- package/src/http/workflows.ts +8 -0
- package/src/linear/sync.ts +28 -4
- package/src/linear/templates.ts +47 -0
- package/src/prompts/base-prompt.ts +41 -490
- package/src/prompts/registry.ts +57 -0
- package/src/prompts/resolver.ts +296 -0
- package/src/prompts/session-templates.ts +604 -0
- package/src/providers/claude-adapter.ts +15 -2
- package/src/providers/pi-mono-extension.ts +5 -1
- package/src/scheduler/scheduler.ts +125 -91
- package/src/server.ts +44 -0
- package/src/slack/assistant.ts +7 -4
- package/src/slack/channel-activity.ts +177 -0
- package/src/slack/handlers.ts +21 -6
- package/src/slack/templates.ts +55 -0
- package/src/tests/approval-requests.test.ts +735 -0
- package/src/tests/artifact-sdk.test.ts +12 -12
- package/src/tests/base-prompt.test.ts +49 -49
- package/src/tests/channel-activity.test.ts +363 -0
- package/src/tests/heartbeat.test.ts +1 -0
- package/src/tests/linear-webhook.test.ts +7 -3
- package/src/tests/pool-session-logs.test.ts +199 -0
- package/src/tests/prompt-template-github.test.ts +682 -0
- package/src/tests/prompt-template-remaining.test.ts +504 -0
- package/src/tests/prompt-template-resolver.test.ts +621 -0
- package/src/tests/prompt-template-session.test.ts +363 -0
- package/src/tests/prompt-templates-db.test.ts +616 -0
- package/src/tests/self-improvement.test.ts +8 -7
- package/src/tests/skill-parser.test.ts +178 -0
- package/src/tests/skill-sync.test.ts +171 -0
- package/src/tests/slack-metadata-inheritance.test.ts +1 -1
- package/src/tests/slack-thread-followups.test.ts +1 -1
- package/src/tests/structured-output.test.ts +0 -4
- package/src/tests/tool-annotations.test.ts +2 -1
- package/src/tests/update-profile-agentid.test.ts +248 -0
- package/src/tests/update-profile-auth.test.ts +195 -0
- package/src/tests/workflow-async-v2.test.ts +126 -4
- package/src/tests/workflow-definition-validation.test.ts +76 -0
- package/src/tests/workflow-executors.test.ts +4 -2
- package/src/tests/workflow-retry-v2.test.ts +1 -1
- package/src/tests/workflow-schedule-trigger.test.ts +104 -0
- package/src/tests/workflow-workspace.test.ts +272 -0
- package/src/tools/prompt-templates/delete.ts +86 -0
- package/src/tools/prompt-templates/get.ts +89 -0
- package/src/tools/prompt-templates/index.ts +5 -0
- package/src/tools/prompt-templates/list.ts +95 -0
- package/src/tools/prompt-templates/preview.ts +84 -0
- package/src/tools/prompt-templates/set.ts +117 -0
- package/src/tools/request-human-input.ts +106 -0
- package/src/tools/skills/index.ts +11 -0
- package/src/tools/skills/skill-create.ts +105 -0
- package/src/tools/skills/skill-delete.ts +67 -0
- package/src/tools/skills/skill-get.ts +75 -0
- package/src/tools/skills/skill-install-remote.ts +152 -0
- package/src/tools/skills/skill-install.ts +101 -0
- package/src/tools/skills/skill-list.ts +77 -0
- package/src/tools/skills/skill-publish.ts +123 -0
- package/src/tools/skills/skill-search.ts +43 -0
- package/src/tools/skills/skill-sync-remote.ts +128 -0
- package/src/tools/skills/skill-uninstall.ts +60 -0
- package/src/tools/skills/skill-update.ts +128 -0
- package/src/tools/store-progress.ts +22 -4
- package/src/tools/task-action.ts +20 -0
- package/src/tools/templates.ts +53 -0
- package/src/tools/tool-config.ts +23 -0
- package/src/tools/update-profile.ts +106 -34
- package/src/tools/workflows/create-workflow.ts +19 -1
- package/src/tools/workflows/update-workflow.ts +16 -1
- package/src/types.ts +109 -2
- package/src/workflows/definition.ts +30 -12
- package/src/workflows/engine.ts +40 -14
- package/src/workflows/executors/agent-task.ts +14 -3
- package/src/workflows/executors/human-in-the-loop.ts +160 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/index.ts +1 -1
- package/src/workflows/recovery.ts +72 -0
- package/src/workflows/resume.ts +162 -12
- package/src/workflows/triggers.ts +31 -2
- package/src/workflows/version.ts +2 -0
- package/.claude/settings.json +0 -84
- package/.claude/settings.local.json +0 -117
- package/.dockerignore +0 -61
- package/.editorconfig +0 -15
- package/.entire/settings.json +0 -4
- package/.env.docker.example +0 -56
- package/.env.example +0 -78
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -78
- package/.github/ISSUE_TEMPLATE/community-template.yml +0 -77
- package/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -60
- package/.github/PULL_REQUEST_TEMPLATE/community-template.md +0 -29
- package/.github/workflows/ci.yml +0 -52
- package/.github/workflows/docker-and-deploy.yml +0 -132
- package/.github/workflows/merge-gate.yml +0 -233
- package/.opencode/plugins/entire.ts +0 -133
- package/.superset/config.json +0 -6
- package/.wts-config.json +0 -4
- package/.wts-setup.ts +0 -171
- package/CHANGELOG.md +0 -447
- package/CLAUDE.md +0 -521
- package/CONTRIBUTING.md +0 -315
- package/DEPLOYMENT.md +0 -622
- package/Dockerfile +0 -65
- package/Dockerfile.worker +0 -189
- package/MCP.md +0 -841
- package/UI.md +0 -40
- package/api-entrypoint.sh +0 -56
- package/assets/agent-swarm-logo-orange.png +0 -0
- package/assets/agent-swarm-logo.png +0 -0
- package/assets/agent-swarm.mp4 +0 -0
- package/assets/agent-swarm.png +0 -0
- package/biome.json +0 -39
- package/deploy/DEPLOY.md +0 -60
- package/deploy/agent-swarm.service +0 -17
- package/deploy/docker-push.ts +0 -30
- package/deploy/install.ts +0 -85
- package/deploy/prod-db.ts +0 -42
- package/deploy/uninstall.ts +0 -12
- package/deploy/update.ts +0 -21
- package/depot.json +0 -1
- package/docker-compose.example.yml +0 -350
- package/docker-compose.local.yml +0 -119
- package/docker-entrypoint.sh +0 -632
- package/docs-site/app/api/search/route.ts +0 -4
- package/docs-site/app/docs/[[...slug]]/page.tsx +0 -87
- package/docs-site/app/docs/layout.tsx +0 -12
- package/docs-site/app/globals.css +0 -24
- package/docs-site/app/layout.config.tsx +0 -34
- package/docs-site/app/layout.tsx +0 -119
- package/docs-site/app/llms-full.txt/route.ts +0 -11
- package/docs-site/app/llms.mdx/docs/[[...slug]]/route.ts +0 -24
- package/docs-site/app/llms.txt/route.ts +0 -8
- package/docs-site/app/page.tsx +0 -5
- package/docs-site/app/robots.ts +0 -13
- package/docs-site/app/sitemap.ts +0 -37
- package/docs-site/components/api-page.client.tsx +0 -4
- package/docs-site/components/api-page.tsx +0 -7
- package/docs-site/components/mdx/mermaid.tsx +0 -55
- package/docs-site/content/docs/(documentation)/architecture/agents.mdx +0 -117
- package/docs-site/content/docs/(documentation)/architecture/hooks.mdx +0 -77
- package/docs-site/content/docs/(documentation)/architecture/memory.mdx +0 -96
- package/docs-site/content/docs/(documentation)/architecture/meta.json +0 -4
- package/docs-site/content/docs/(documentation)/architecture/overview.mdx +0 -172
- package/docs-site/content/docs/(documentation)/concepts/epics.mdx +0 -98
- package/docs-site/content/docs/(documentation)/concepts/meta.json +0 -4
- package/docs-site/content/docs/(documentation)/concepts/scheduling.mdx +0 -136
- package/docs-site/content/docs/(documentation)/concepts/services.mdx +0 -104
- package/docs-site/content/docs/(documentation)/concepts/task-lifecycle.mdx +0 -148
- package/docs-site/content/docs/(documentation)/concepts/workflows.mdx +0 -209
- package/docs-site/content/docs/(documentation)/contributing.mdx +0 -158
- package/docs-site/content/docs/(documentation)/getting-started.mdx +0 -157
- package/docs-site/content/docs/(documentation)/guides/agentmail-integration.mdx +0 -79
- package/docs-site/content/docs/(documentation)/guides/deployment.mdx +0 -171
- package/docs-site/content/docs/(documentation)/guides/github-integration.mdx +0 -81
- package/docs-site/content/docs/(documentation)/guides/gitlab-integration.mdx +0 -93
- package/docs-site/content/docs/(documentation)/guides/linear-integration.mdx +0 -98
- package/docs-site/content/docs/(documentation)/guides/meta.json +0 -13
- package/docs-site/content/docs/(documentation)/guides/sentry-integration.mdx +0 -52
- package/docs-site/content/docs/(documentation)/guides/slack-integration.mdx +0 -179
- package/docs-site/content/docs/(documentation)/guides/x402-payments.mdx +0 -154
- package/docs-site/content/docs/(documentation)/index.mdx +0 -65
- package/docs-site/content/docs/(documentation)/meta.json +0 -19
- package/docs-site/content/docs/(documentation)/reference/cli.mdx +0 -241
- package/docs-site/content/docs/(documentation)/reference/environment-variables.mdx +0 -205
- package/docs-site/content/docs/(documentation)/reference/mcp-tools.mdx +0 -449
- package/docs-site/content/docs/(documentation)/reference/meta.json +0 -4
- package/docs-site/content/docs/api-reference/active-sessions.mdx +0 -9
- package/docs-site/content/docs/api-reference/agents.mdx +0 -9
- package/docs-site/content/docs/api-reference/channels.mdx +0 -9
- package/docs-site/content/docs/api-reference/config.mdx +0 -9
- package/docs-site/content/docs/api-reference/debug.mdx +0 -9
- package/docs-site/content/docs/api-reference/ecosystem.mdx +0 -9
- package/docs-site/content/docs/api-reference/epics.mdx +0 -9
- package/docs-site/content/docs/api-reference/index.mdx +0 -32
- package/docs-site/content/docs/api-reference/memory.mdx +0 -9
- package/docs-site/content/docs/api-reference/meta.json +0 -25
- package/docs-site/content/docs/api-reference/poll.mdx +0 -9
- package/docs-site/content/docs/api-reference/repos.mdx +0 -9
- package/docs-site/content/docs/api-reference/schedules.mdx +0 -9
- package/docs-site/content/docs/api-reference/session-data.mdx +0 -9
- package/docs-site/content/docs/api-reference/stats.mdx +0 -9
- package/docs-site/content/docs/api-reference/tasks.mdx +0 -9
- package/docs-site/content/docs/api-reference/trackers.mdx +0 -9
- package/docs-site/content/docs/api-reference/webhooks.mdx +0 -9
- package/docs-site/content/docs/api-reference/workflows.mdx +0 -9
- package/docs-site/content/docs/meta.json +0 -3
- package/docs-site/lib/get-llm-text.ts +0 -10
- package/docs-site/lib/openapi.ts +0 -23
- package/docs-site/lib/source.ts +0 -8
- package/docs-site/mdx-components.tsx +0 -13
- package/docs-site/next.config.mjs +0 -29
- package/docs-site/package.json +0 -35
- package/docs-site/pnpm-lock.yaml +0 -5407
- package/docs-site/postcss.config.mjs +0 -8
- package/docs-site/public/logo.png +0 -0
- package/docs-site/scripts/generate-docs.ts +0 -171
- package/docs-site/source.config.ts +0 -17
- package/docs-site/tsconfig.json +0 -46
- package/ecosystem.config.cjs +0 -66
- package/landing/next.config.ts +0 -14
- package/landing/package.json +0 -31
- package/landing/pnpm-lock.yaml +0 -1091
- package/landing/postcss.config.mjs +0 -8
- package/landing/public/apple-touch-icon.png +0 -0
- package/landing/public/favicon.ico +0 -0
- package/landing/public/logo.png +0 -0
- package/landing/public/og-image.png +0 -0
- package/landing/public/omghost-desplega.svg +0 -30
- package/landing/public/omghost-openfort.svg +0 -9
- package/landing/src/app/actions/waitlist.ts +0 -25
- package/landing/src/app/blog/openfort-hackathon/page.tsx +0 -863
- package/landing/src/app/blog/page.tsx +0 -162
- package/landing/src/app/blog/swarm-metrics/page.tsx +0 -685
- package/landing/src/app/examples/page.tsx +0 -174
- package/landing/src/app/examples/x402/page.tsx +0 -456
- package/landing/src/app/globals.css +0 -122
- package/landing/src/app/layout.tsx +0 -134
- package/landing/src/app/page.tsx +0 -27
- package/landing/src/app/robots.ts +0 -13
- package/landing/src/app/sitemap.ts +0 -44
- package/landing/src/components/architecture.tsx +0 -163
- package/landing/src/components/cta.tsx +0 -52
- package/landing/src/components/features.tsx +0 -160
- package/landing/src/components/footer.tsx +0 -100
- package/landing/src/components/hero.tsx +0 -217
- package/landing/src/components/how-it-works.tsx +0 -165
- package/landing/src/components/navbar.tsx +0 -147
- package/landing/src/components/waitlist.tsx +0 -110
- package/landing/src/components/why-choose.tsx +0 -149
- package/landing/src/components/workshops.tsx +0 -328
- package/landing/src/lib/utils.ts +0 -6
- package/landing/tsconfig.json +0 -41
- package/misc/transcripts/2026-03-09-pi-mono-e2e-verification.md +0 -154
- package/new-ui/CLAUDE.md +0 -92
- package/new-ui/README.md +0 -73
- package/new-ui/biome.json +0 -42
- package/new-ui/components.json +0 -21
- package/new-ui/index.html +0 -25
- package/new-ui/package.json +0 -49
- package/new-ui/pnpm-lock.yaml +0 -4845
- package/new-ui/public/logo.png +0 -0
- package/new-ui/src/api/client.ts +0 -814
- package/new-ui/src/api/hooks/index.ts +0 -64
- package/new-ui/src/api/hooks/use-agents.ts +0 -58
- package/new-ui/src/api/hooks/use-channels.ts +0 -115
- package/new-ui/src/api/hooks/use-config-api.ts +0 -46
- package/new-ui/src/api/hooks/use-costs.ts +0 -122
- package/new-ui/src/api/hooks/use-db-query.ts +0 -29
- package/new-ui/src/api/hooks/use-epics.ts +0 -75
- package/new-ui/src/api/hooks/use-repos.ts +0 -61
- package/new-ui/src/api/hooks/use-schedules.ts +0 -81
- package/new-ui/src/api/hooks/use-services.ts +0 -16
- package/new-ui/src/api/hooks/use-stats.ts +0 -27
- package/new-ui/src/api/hooks/use-tasks.ts +0 -89
- package/new-ui/src/api/hooks/use-workflows.ts +0 -109
- package/new-ui/src/api/types.ts +0 -549
- package/new-ui/src/app/App.tsx +0 -13
- package/new-ui/src/app/providers.tsx +0 -32
- package/new-ui/src/app/router.tsx +0 -52
- package/new-ui/src/components/layout/app-header.tsx +0 -47
- package/new-ui/src/components/layout/app-sidebar.tsx +0 -128
- package/new-ui/src/components/layout/breadcrumbs.tsx +0 -57
- package/new-ui/src/components/layout/config-guard.tsx +0 -22
- package/new-ui/src/components/layout/root-layout.tsx +0 -40
- package/new-ui/src/components/layout/swarm-switcher.tsx +0 -85
- package/new-ui/src/components/shared/command-menu.tsx +0 -131
- package/new-ui/src/components/shared/data-grid.tsx +0 -141
- package/new-ui/src/components/shared/empty-state.tsx +0 -24
- package/new-ui/src/components/shared/error-boundary.tsx +0 -72
- package/new-ui/src/components/shared/json-viewer.tsx +0 -47
- package/new-ui/src/components/shared/name-connection-modal.tsx +0 -99
- package/new-ui/src/components/shared/page-skeleton.tsx +0 -16
- package/new-ui/src/components/shared/session-log-viewer.tsx +0 -364
- package/new-ui/src/components/shared/stats-bar.tsx +0 -132
- package/new-ui/src/components/shared/status-badge.tsx +0 -131
- package/new-ui/src/components/shared/usage-summary.tsx +0 -179
- package/new-ui/src/components/ui/alert-dialog.tsx +0 -176
- package/new-ui/src/components/ui/alert.tsx +0 -60
- package/new-ui/src/components/ui/avatar.tsx +0 -96
- package/new-ui/src/components/ui/badge.tsx +0 -46
- package/new-ui/src/components/ui/button.tsx +0 -62
- package/new-ui/src/components/ui/card.tsx +0 -75
- package/new-ui/src/components/ui/command.tsx +0 -160
- package/new-ui/src/components/ui/dialog.tsx +0 -143
- package/new-ui/src/components/ui/dropdown-menu.tsx +0 -226
- package/new-ui/src/components/ui/input.tsx +0 -21
- package/new-ui/src/components/ui/label.tsx +0 -19
- package/new-ui/src/components/ui/progress.tsx +0 -26
- package/new-ui/src/components/ui/scroll-area.tsx +0 -54
- package/new-ui/src/components/ui/select.tsx +0 -175
- package/new-ui/src/components/ui/separator.tsx +0 -28
- package/new-ui/src/components/ui/sheet.tsx +0 -132
- package/new-ui/src/components/ui/sidebar.tsx +0 -691
- package/new-ui/src/components/ui/skeleton.tsx +0 -13
- package/new-ui/src/components/ui/sonner.tsx +0 -35
- package/new-ui/src/components/ui/switch.tsx +0 -33
- package/new-ui/src/components/ui/table.tsx +0 -92
- package/new-ui/src/components/ui/tabs.tsx +0 -79
- package/new-ui/src/components/ui/textarea.tsx +0 -18
- package/new-ui/src/components/ui/tooltip.tsx +0 -51
- package/new-ui/src/components/workflows/action-node.tsx +0 -53
- package/new-ui/src/components/workflows/condition-node.tsx +0 -50
- package/new-ui/src/components/workflows/graph-utils.ts +0 -124
- package/new-ui/src/components/workflows/json-tree.tsx +0 -189
- package/new-ui/src/components/workflows/node-styles.ts +0 -10
- package/new-ui/src/components/workflows/step-detail-sheet.tsx +0 -87
- package/new-ui/src/components/workflows/trigger-node.tsx +0 -41
- package/new-ui/src/components/workflows/workflow-graph.tsx +0 -65
- package/new-ui/src/hooks/use-auto-scroll.ts +0 -82
- package/new-ui/src/hooks/use-config.ts +0 -203
- package/new-ui/src/hooks/use-keyboard-shortcuts.ts +0 -41
- package/new-ui/src/hooks/use-mobile.ts +0 -19
- package/new-ui/src/hooks/use-theme.ts +0 -60
- package/new-ui/src/lib/config.ts +0 -188
- package/new-ui/src/lib/slugs.ts +0 -71
- package/new-ui/src/lib/utils.ts +0 -120
- package/new-ui/src/main.tsx +0 -11
- package/new-ui/src/pages/agents/[id]/page.tsx +0 -492
- package/new-ui/src/pages/agents/page.tsx +0 -134
- package/new-ui/src/pages/chat/page.tsx +0 -674
- package/new-ui/src/pages/config/page.tsx +0 -1109
- package/new-ui/src/pages/dashboard/page.tsx +0 -454
- package/new-ui/src/pages/debug/page.tsx +0 -275
- package/new-ui/src/pages/epics/[id]/page.tsx +0 -809
- package/new-ui/src/pages/epics/page.tsx +0 -321
- package/new-ui/src/pages/not-found/page.tsx +0 -18
- package/new-ui/src/pages/repos/page.tsx +0 -369
- package/new-ui/src/pages/schedules/[id]/page.tsx +0 -664
- package/new-ui/src/pages/schedules/page.tsx +0 -477
- package/new-ui/src/pages/services/page.tsx +0 -128
- package/new-ui/src/pages/tasks/[id]/page.tsx +0 -670
- package/new-ui/src/pages/tasks/page.tsx +0 -592
- package/new-ui/src/pages/usage/page.tsx +0 -195
- package/new-ui/src/pages/workflow-runs/[id]/page.tsx +0 -363
- package/new-ui/src/pages/workflows/[id]/page.tsx +0 -417
- package/new-ui/src/pages/workflows/page.tsx +0 -266
- package/new-ui/src/styles/ag-grid.css +0 -36
- package/new-ui/src/styles/globals.css +0 -213
- package/new-ui/test-results/.last-run.json +0 -4
- package/new-ui/tsconfig.app.json +0 -34
- package/new-ui/tsconfig.json +0 -4
- package/new-ui/tsconfig.node.json +0 -26
- package/new-ui/vercel.json +0 -4
- package/new-ui/vite.config.ts +0 -28
- package/plugin/README.md +0 -1
- package/plugin/build-pi-skills.ts +0 -233
- package/plugin/hooks/hooks.json +0 -71
- package/prek.toml +0 -75
- package/pyproject.toml +0 -9
- package/scripts/check-db-boundary.sh +0 -60
- package/scripts/e2e-docker-provider.ts +0 -820
- package/scripts/e2e-io-schemas-test.ts +0 -807
- package/scripts/e2e-provider-test.ts +0 -220
- package/scripts/e2e-workflow-redesign.sh +0 -229
- package/scripts/e2e-workflow-test.sh +0 -285
- package/scripts/e2e-workflow-test.ts +0 -857
- package/scripts/generate-mcp-docs.ts +0 -415
- package/scripts/generate-openapi.ts +0 -26
- package/scripts/measure-tool-tokens.ts +0 -118
- package/scripts/x402-e2e-test.ts +0 -195
- package/scripts/x402-test-server.ts +0 -236
- package/scripts/x402-testnet-e2e.ts +0 -668
- package/slack-manifest.json +0 -88
- package/templates-ui/README.md +0 -46
- package/templates-ui/components.json +0 -17
- package/templates-ui/eslint.config.mjs +0 -18
- package/templates-ui/next.config.ts +0 -7
- package/templates-ui/package.json +0 -35
- package/templates-ui/pnpm-lock.yaml +0 -4571
- package/templates-ui/postcss.config.mjs +0 -7
- package/templates-ui/public/file.svg +0 -1
- package/templates-ui/public/globe.svg +0 -1
- package/templates-ui/public/logo.png +0 -0
- package/templates-ui/public/next.svg +0 -1
- package/templates-ui/public/vercel.svg +0 -1
- package/templates-ui/public/window.svg +0 -1
- package/templates-ui/src/app/[category]/[name]/page.tsx +0 -89
- package/templates-ui/src/app/api/templates/[...slug]/route.ts +0 -52
- package/templates-ui/src/app/api/templates/route.ts +0 -18
- package/templates-ui/src/app/builder/page.tsx +0 -37
- package/templates-ui/src/app/globals.css +0 -94
- package/templates-ui/src/app/layout.tsx +0 -79
- package/templates-ui/src/app/page.tsx +0 -38
- package/templates-ui/src/app/robots.ts +0 -11
- package/templates-ui/src/app/sitemap.ts +0 -31
- package/templates-ui/src/components/compose-builder.tsx +0 -442
- package/templates-ui/src/components/compose-preview.tsx +0 -117
- package/templates-ui/src/components/file-preview.tsx +0 -77
- package/templates-ui/src/components/footer.tsx +0 -40
- package/templates-ui/src/components/header.tsx +0 -41
- package/templates-ui/src/components/template-card.tsx +0 -87
- package/templates-ui/src/components/template-detail.tsx +0 -125
- package/templates-ui/src/components/template-gallery.tsx +0 -263
- package/templates-ui/src/components/ui/badge.tsx +0 -36
- package/templates-ui/src/components/ui/button.tsx +0 -57
- package/templates-ui/src/components/ui/card.tsx +0 -76
- package/templates-ui/src/components/ui/separator.tsx +0 -31
- package/templates-ui/src/components/ui/tooltip.tsx +0 -32
- package/templates-ui/src/lib/compose-generator.ts +0 -241
- package/templates-ui/src/lib/templates.ts +0 -137
- package/templates-ui/src/lib/utils.ts +0 -6
- package/templates-ui/tsconfig.json +0 -34
- package/thoughts/research/2026-02-28-openfort-viem-x402-research.md +0 -679
- package/thoughts/research/2026-02-28-x402-payments-research.md +0 -686
- package/thoughts/researcher/plans/2026-02-20-agent-self-improvement-plan.md +0 -282
- package/thoughts/researcher/research/2026-02-20-agent-self-improvement.md +0 -492
- package/thoughts/shared/plans/.gitkeep +0 -0
- package/thoughts/shared/plans/2025-12-18-slack-integration.md +0 -1195
- package/thoughts/shared/plans/2025-12-19-agent-log-streaming.md +0 -732
- package/thoughts/shared/plans/2025-12-19-role-based-swarm-plugin.md +0 -361
- package/thoughts/shared/plans/2025-12-20-mobile-responsive-ui.md +0 -501
- package/thoughts/shared/plans/2025-12-20-startup-team-swarm.md +0 -560
- package/thoughts/shared/plans/2025-12-23-runner-level-polling.md +0 -934
- package/thoughts/shared/plans/2025-12-23-runner-session-logs.md +0 -1000
- package/thoughts/shared/plans/2025-12-23-worker-lead-spawn-triggers.md +0 -568
- package/thoughts/shared/plans/2026-01-09-inverse-teleport.md +0 -1516
- package/thoughts/shared/plans/2026-01-12-agent-rename-pm2-control.md +0 -1133
- package/thoughts/shared/plans/2026-01-12-github-app-integration.md +0 -380
- package/thoughts/shared/plans/2026-01-12-lead-inbox-model.md +0 -876
- package/thoughts/shared/plans/2026-01-12-ralph-wiggum-integration.md +0 -463
- package/thoughts/shared/plans/2026-01-13-agent-concurrency.md +0 -691
- package/thoughts/shared/plans/2026-01-13-github-assignment-handling.md +0 -690
- package/thoughts/shared/plans/2026-01-13-prevent-duplicate-trigger-processing.md +0 -1071
- package/thoughts/shared/plans/2026-01-14-fix-slack-thread-context.md +0 -507
- package/thoughts/shared/plans/2026-01-15-scheduled-tasks-implementation.md +0 -565
- package/thoughts/shared/plans/2026-01-15-usage-cost-tracking-ui.md +0 -1479
- package/thoughts/shared/plans/2026-01-16-epics-feature-implementation.md +0 -1230
- package/thoughts/shared/plans/2026-02-26-mcp-tool-context-reduction.md +0 -282
- package/thoughts/shared/plans/2026-03-02-claude-context-mode-integration.md +0 -328
- package/thoughts/shared/plans/2026-03-02-code-level-heartbeat.md +0 -224
- package/thoughts/shared/research/.gitkeep +0 -0
- package/thoughts/shared/research/2025-01-09-inverse-teleport-plan-review.md +0 -420
- package/thoughts/shared/research/2025-12-18-slack-integration.md +0 -442
- package/thoughts/shared/research/2025-12-19-agent-log-streaming.md +0 -339
- package/thoughts/shared/research/2025-12-19-agent-secrets-cli-research.md +0 -390
- package/thoughts/shared/research/2025-12-21-gemini-cli-integration.md +0 -376
- package/thoughts/shared/research/2025-12-22-runner-loop-architecture.md +0 -582
- package/thoughts/shared/research/2025-12-22-setup-experience-improvements.md +0 -264
- package/thoughts/shared/research/2026-01-13-lead-duplicate-trigger-processing.md +0 -223
- package/thoughts/shared/research/2026-01-14-lead-slack-thread-context.md +0 -277
- package/thoughts/shared/research/2026-01-15-ai-tracker-agent-swarm-integration.md +0 -376
- package/thoughts/shared/research/2026-01-15-auto-starting-processes-in-worker-containers.md +0 -787
- package/thoughts/shared/research/2026-01-15-scheduled-tasks.md +0 -390
- package/thoughts/shared/research/2026-01-16-epics-feature-research.md +0 -437
- package/thoughts/shared/research/2026-02-26-cliffy-mcp-tools.md +0 -159
- package/thoughts/shared/research/2026-03-03-database-migration-system-refactor.md +0 -337
- package/thoughts/swarm-researcher/plans/2026-02-23-openclaw-improvements-plan.md +0 -778
- package/thoughts/swarm-researcher/plans/2026-02-26-artifacts-localtunnel-plan.md +0 -1269
- package/thoughts/swarm-researcher/research/2026-02-23-openclaw-vs-agent-swarm-comparison.md +0 -411
- package/thoughts/swarm-researcher/research/2026-02-26-artifacts-localtunnel.md +0 -724
- package/thoughts/taras/brainstorms/2026-03-20-prompt-template-registry.md +0 -443
- package/thoughts/taras/brainstorms/2026-03-20-setup-cli-onboarding.md +0 -307
- package/thoughts/taras/plans/2026-01-22-agent-swarm-schemas.md +0 -98
- package/thoughts/taras/plans/2026-01-28-per-worker-claude-md.md +0 -617
- package/thoughts/taras/plans/2026-01-28-sentry-cli-integration.md +0 -214
- package/thoughts/taras/plans/2026-02-20-auto-improvement.md +0 -803
- package/thoughts/taras/plans/2026-02-20-env-management.md +0 -538
- package/thoughts/taras/plans/2026-02-20-memory-system.md +0 -882
- package/thoughts/taras/plans/2026-02-20-repos-knowledge.md +0 -806
- package/thoughts/taras/plans/2026-02-20-session-attach.md +0 -647
- package/thoughts/taras/plans/2026-02-20-worker-identity.md +0 -820
- package/thoughts/taras/plans/2026-02-25-feat-new-ui-visual-redesign-plan.md +0 -768
- package/thoughts/taras/plans/2026-03-04-fix-buildSystemPrompt-missing-fields.md +0 -77
- package/thoughts/taras/plans/2026-03-04-new-ui-missing-actions.md +0 -543
- package/thoughts/taras/plans/2026-03-06-one-time-scheduled-tasks.md +0 -373
- package/thoughts/taras/plans/2026-03-08-memory-self-improvement-enhancements.md +0 -512
- package/thoughts/taras/plans/2026-03-08-pi-mono-provider-implementation.md +0 -919
- package/thoughts/taras/plans/2026-03-09-templates-registry.md +0 -723
- package/thoughts/taras/plans/2026-03-10-task-working-directory.md +0 -371
- package/thoughts/taras/plans/2026-03-11-archil-per-agent-write-strategy.md +0 -621
- package/thoughts/taras/plans/2026-03-12-eliminate-inbox-route-to-tasks.md +0 -61
- package/thoughts/taras/plans/2026-03-12-slack-thread-followup-additive.md +0 -488
- package/thoughts/taras/plans/2026-03-13-slack-ai-improvements.md +0 -644
- package/thoughts/taras/plans/2026-03-16-route-wrapper-openapi.md +0 -636
- package/thoughts/taras/plans/2026-03-17-multi-api-config.md +0 -444
- package/thoughts/taras/plans/2026-03-18-agent-fs-integration.md +0 -591
- package/thoughts/taras/plans/2026-03-18-debug-db-explorer.md +0 -446
- package/thoughts/taras/plans/2026-03-18-workflow-redesign.md +0 -987
- package/thoughts/taras/plans/2026-03-19-compound-learnings.md +0 -403
- package/thoughts/taras/plans/2026-03-19-ticket-tracker-linear-integration.md +0 -860
- package/thoughts/taras/plans/2026-03-19-workflow-io-schemas-and-bugs.md +0 -899
- package/thoughts/taras/plans/2026-03-20-setup-cli-onboarding.md +0 -874
- package/thoughts/taras/plans/2026-03-20-workflow-structured-output-validation-workspace.md +0 -723
- package/thoughts/taras/research/2026-01-22-vercel-cli-integration.md +0 -287
- package/thoughts/taras/research/2026-01-27-excessive-polling-issue.md +0 -311
- package/thoughts/taras/research/2026-01-28-per-worker-claude-md.md +0 -383
- package/thoughts/taras/research/2026-01-28-sentry-cli-integration.md +0 -240
- package/thoughts/taras/research/2026-02-19-agent-native-swarm-architecture.md +0 -390
- package/thoughts/taras/research/2026-02-19-swarm-gaps-implementation.md +0 -594
- package/thoughts/taras/research/2026-02-25-dashboard-ui-design-best-practices.md +0 -825
- package/thoughts/taras/research/2026-02-26-task-detail-page-redesign.md +0 -393
- package/thoughts/taras/research/2026-03-03-new-ui-missing-actions.md +0 -168
- package/thoughts/taras/research/2026-03-05-pi-mono-provider-research.md +0 -230
- package/thoughts/taras/research/2026-03-06-workflow-engine-design.md +0 -445
- package/thoughts/taras/research/2026-03-08-drive-loop-concept.md +0 -375
- package/thoughts/taras/research/2026-03-08-pi-mono-deep-dive.md +0 -869
- package/thoughts/taras/research/2026-03-09-templates-registry.md +0 -373
- package/thoughts/taras/research/2026-03-10-agent-working-directory.md +0 -223
- package/thoughts/taras/research/2026-03-10-configurable-event-prompts.md +0 -339
- package/thoughts/taras/research/2026-03-11-archil-production-setup.md +0 -181
- package/thoughts/taras/research/2026-03-11-archil-shared-disk-write-strategies.md +0 -437
- package/thoughts/taras/research/2026-03-13-slack-ai-features.md +0 -258
- package/thoughts/taras/research/2026-03-16-openapi-docs-generation.md +0 -335
- package/thoughts/taras/research/2026-03-16-route-wrapper-openapi.md +0 -670
- package/thoughts/taras/research/2026-03-16-slack-thread-followups-e2e.md +0 -54
- package/thoughts/taras/research/2026-03-18-agent-fs-integration.md +0 -558
- package/thoughts/taras/research/2026-03-18-linear-integration-finalization.md +0 -526
- package/thoughts/taras/research/2026-03-18-workflow-redesign.md +0 -797
- package/thoughts/taras/research/2026-03-19-workflow-node-io-schemas-and-bugs.md +0 -563
- package/thoughts/taras/research/2026-03-19-workflow-structured-output-validation-workspace.md +0 -486
- package/thoughts/taras/research/2026-03-20-prompt-template-registry.md +0 -469
- package/tsconfig.json +0 -37
|
@@ -1,778 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
date: 2026-02-23
|
|
3
|
-
author: Researcher (16990304-76e4-4017-b991-f3e37b34cf73)
|
|
4
|
-
topic: "Implementation Plan: P1 + P2 Openclaw Improvements"
|
|
5
|
-
status: ready
|
|
6
|
-
related_research: thoughts/swarm-researcher/research/2026-02-23-openclaw-vs-agent-swarm-comparison.md
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# Implementation Plan: P1 + P2 Openclaw Improvements
|
|
10
|
-
|
|
11
|
-
## Overview
|
|
12
|
-
|
|
13
|
-
This plan covers all 6 improvements from the openclaw comparison research (3 P1, 3 P2). Items are ordered by implementation dependency and difficulty. Each item includes exact file paths, code insertion points, pseudocode, and testing strategy.
|
|
14
|
-
|
|
15
|
-
**Implementation order**: 2 → 6 → 3 → 4 → 1 → 5
|
|
16
|
-
|
|
17
|
-
Rationale: Start with the simplest items that have no dependencies, then build up to more complex features.
|
|
18
|
-
|
|
19
|
-
---
|
|
20
|
-
|
|
21
|
-
## Item 2 (P1): Active Session Heartbeat — Wire Existing Endpoint
|
|
22
|
-
|
|
23
|
-
**Goal**: Call the already-existing `heartbeatActiveSession()` endpoint from the PostToolUse hook so that `cleanupStaleSessions()` can actually detect stale sessions.
|
|
24
|
-
|
|
25
|
-
**Current state**: The endpoint `PUT /api/active-sessions/heartbeat/:taskId` and DB function `heartbeatActiveSession()` (db.ts:5567) exist but are never called. Sessions only get a heartbeat timestamp at creation time.
|
|
26
|
-
|
|
27
|
-
### Files to modify
|
|
28
|
-
|
|
29
|
-
**`src/hooks/hook.ts`** — PostToolUse handler (line ~771-847)
|
|
30
|
-
|
|
31
|
-
### Implementation
|
|
32
|
-
|
|
33
|
-
Add a heartbeat call inside the `PostToolUse` case, after the ping and agent info are available. The heartbeat should fire on every tool use, which naturally provides liveness data without extra overhead.
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
// Inside case "PostToolUse": (after line 771)
|
|
37
|
-
// Before existing logic, fire session heartbeat
|
|
38
|
-
if (agentInfo && !agentInfo.isLead) {
|
|
39
|
-
const taskFileData = await readTaskFile();
|
|
40
|
-
if (taskFileData?.taskId) {
|
|
41
|
-
try {
|
|
42
|
-
await fetch(`${getBaseUrl()}/api/active-sessions/heartbeat/${taskFileData.taskId}`, {
|
|
43
|
-
method: "PUT",
|
|
44
|
-
headers: mcpConfig!.headers,
|
|
45
|
-
});
|
|
46
|
-
} catch {
|
|
47
|
-
// Non-blocking — heartbeat failure should not interrupt agent work
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Insert this block at the very beginning of the `case "PostToolUse":` block (line 772, before the `if (agentInfo)` check). The heartbeat is fire-and-forget.
|
|
54
|
-
|
|
55
|
-
### Why at PostToolUse (not PreToolUse)
|
|
56
|
-
|
|
57
|
-
PreToolUse already does cancellation checks which add latency. Adding heartbeat there would compound. PostToolUse runs after the tool completes, so the latency doesn't block the agent.
|
|
58
|
-
|
|
59
|
-
### Testing
|
|
60
|
-
|
|
61
|
-
1. Start a worker, assign a task
|
|
62
|
-
2. Run `SELECT lastHeartbeatAt FROM active_sessions WHERE taskId = ?` — verify it updates on each tool call
|
|
63
|
-
3. Kill a worker mid-task, wait 30+ minutes, run `POST /api/active-sessions/cleanup` — verify stale session is deleted
|
|
64
|
-
|
|
65
|
-
### Estimated changes: ~15 lines in hook.ts
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## Item 6 (P2): Task Progress Deduplication
|
|
70
|
-
|
|
71
|
-
**Goal**: Skip duplicate `store-progress` calls where progress text is identical to the last update within a time window.
|
|
72
|
-
|
|
73
|
-
**Current state**: `updateTaskProgress()` (db.ts:1837) stores every progress update unconditionally and creates a `task_progress` log entry each time. Agents sometimes send repeated progress messages.
|
|
74
|
-
|
|
75
|
-
### Files to modify
|
|
76
|
-
|
|
77
|
-
1. **`src/be/db.ts`** — Migration + new column + helper
|
|
78
|
-
2. **`src/tools/store-progress.ts`** — Dedup check before calling updateTaskProgress
|
|
79
|
-
|
|
80
|
-
### Implementation
|
|
81
|
-
|
|
82
|
-
#### Step 1: DB Migration (db.ts)
|
|
83
|
-
|
|
84
|
-
Add a new migration in the migrations block (after the existing `ALTER TABLE` migrations, around line ~800):
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
// Migration: Add lastProgressText for deduplication
|
|
88
|
-
try {
|
|
89
|
-
database.run("ALTER TABLE agent_tasks ADD COLUMN lastProgressText TEXT");
|
|
90
|
-
} catch {}
|
|
91
|
-
try {
|
|
92
|
-
database.run("ALTER TABLE agent_tasks ADD COLUMN lastProgressAt TEXT");
|
|
93
|
-
} catch {}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
#### Step 2: Update `updateTaskProgress` (db.ts:1837)
|
|
97
|
-
|
|
98
|
-
Modify the function to also update `lastProgressText` and `lastProgressAt`:
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
101
|
-
export function updateTaskProgress(id: string, progress: string): AgentTask | null {
|
|
102
|
-
const row = getDb()
|
|
103
|
-
.prepare<AgentTaskRow, [string, string, string]>(
|
|
104
|
-
`UPDATE agent_tasks SET progress = ?, lastProgressText = ?, lastProgressAt = ?, lastUpdatedAt = ?
|
|
105
|
-
WHERE id = ? RETURNING *`
|
|
106
|
-
)
|
|
107
|
-
.get(progress, progress, new Date().toISOString(), new Date().toISOString(), id);
|
|
108
|
-
// ... rest unchanged
|
|
109
|
-
}
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
Wait — actually the current `taskQueries.setProgress()` prepared statement handles this. We need to modify that query. Let me look at the actual query.
|
|
113
|
-
|
|
114
|
-
The simplest approach: modify `store-progress.ts` to check before calling `updateTaskProgress`.
|
|
115
|
-
|
|
116
|
-
#### Step 2 (revised): Dedup in store-progress.ts (line ~103)
|
|
117
|
-
|
|
118
|
-
Before calling `updateTaskProgress`, check if the progress text matches the existing task's progress:
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
// Inside the transaction, before line 103:
|
|
122
|
-
if (progress) {
|
|
123
|
-
// Dedup: skip if same progress text was set within the last 5 minutes
|
|
124
|
-
const isDuplicate =
|
|
125
|
-
existingTask.progress === progress &&
|
|
126
|
-
existingTask.lastUpdatedAt &&
|
|
127
|
-
Date.now() - new Date(existingTask.lastUpdatedAt).getTime() < 5 * 60 * 1000;
|
|
128
|
-
|
|
129
|
-
if (!isDuplicate) {
|
|
130
|
-
const result = updateTaskProgress(taskId, progress);
|
|
131
|
-
if (result) updatedTask = result;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
This avoids any DB schema changes — we just compare against the existing `progress` field and `lastUpdatedAt` timestamp on the task.
|
|
137
|
-
|
|
138
|
-
### Testing
|
|
139
|
-
|
|
140
|
-
1. Call `store-progress` with progress "Working on X" twice in rapid succession
|
|
141
|
-
2. Verify only one `task_progress` log entry is created
|
|
142
|
-
3. Call again after 5+ minutes — verify it stores
|
|
143
|
-
4. Verify status changes (completed/failed) always go through regardless of dedup
|
|
144
|
-
|
|
145
|
-
### Estimated changes: ~10 lines in store-progress.ts (no DB migration needed)
|
|
146
|
-
|
|
147
|
-
---
|
|
148
|
-
|
|
149
|
-
## Item 3 (P1): Missed Schedule Recovery on Startup
|
|
150
|
-
|
|
151
|
-
**Goal**: When the scheduler starts, check for past-due scheduled tasks and execute them immediately instead of waiting for the next scheduled run.
|
|
152
|
-
|
|
153
|
-
**Current state**: `startScheduler()` (scheduler.ts:77) calls `processSchedules()` once on startup. `getDueScheduledTasks()` (db.ts:4242) already returns tasks where `nextRunAt <= now`, so past-due tasks ARE picked up on the first tick. However, if a task's `nextRunAt` is far in the past, `calculateNextRun()` will schedule the NEXT run from `now`, which is correct.
|
|
154
|
-
|
|
155
|
-
Wait — re-reading the code, the current `processSchedules()` ALREADY handles this because `getDueScheduledTasks()` returns `WHERE enabled = 1 AND nextRunAt IS NOT NULL AND nextRunAt <= ?` with `?` = now. So any past-due tasks are already picked up on the first tick.
|
|
156
|
-
|
|
157
|
-
**Actually needed**: The real gap is that `executeSchedule()` calls `calculateNextRun(schedule, new Date())` which calculates next run from NOW. For cron expressions, this works correctly. For intervals, this also works (next = now + interval). So the schedule recovery is already implicit.
|
|
158
|
-
|
|
159
|
-
**Remaining gap**: What if the scheduler was down and a schedule was due MULTIPLE times? E.g., an hourly schedule was down for 3 hours. Currently only one execution happens. Openclaw runs all missed past-due executions.
|
|
160
|
-
|
|
161
|
-
**Revised approach**: Add a `missedRunRecovery` option to `startScheduler()` that checks how many runs were missed and optionally catches up.
|
|
162
|
-
|
|
163
|
-
### Files to modify
|
|
164
|
-
|
|
165
|
-
**`src/scheduler/scheduler.ts`** — `startScheduler()` and new `recoverMissedSchedules()` function
|
|
166
|
-
|
|
167
|
-
### Implementation
|
|
168
|
-
|
|
169
|
-
Add a recovery function before the main poll loop:
|
|
170
|
-
|
|
171
|
-
```typescript
|
|
172
|
-
/**
|
|
173
|
-
* Recover missed scheduled task runs from downtime.
|
|
174
|
-
* Only fires ONE catch-up run per schedule (not N missed runs).
|
|
175
|
-
* Tags the task with "recovered" so it's distinguishable.
|
|
176
|
-
*/
|
|
177
|
-
async function recoverMissedSchedules(): Promise<void> {
|
|
178
|
-
const now = new Date();
|
|
179
|
-
const dueSchedules = getDueScheduledTasks();
|
|
180
|
-
|
|
181
|
-
for (const schedule of dueSchedules) {
|
|
182
|
-
// Only recover if nextRunAt is significantly in the past (> 1 tick)
|
|
183
|
-
if (!schedule.nextRunAt) continue;
|
|
184
|
-
const missedBy = now.getTime() - new Date(schedule.nextRunAt).getTime();
|
|
185
|
-
if (missedBy < 15000) continue; // Less than 15s — normal timing jitter
|
|
186
|
-
|
|
187
|
-
console.log(
|
|
188
|
-
`[Scheduler] Recovering missed schedule "${schedule.name}" ` +
|
|
189
|
-
`(was due ${Math.round(missedBy / 1000)}s ago)`
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
// Use transaction for atomicity
|
|
194
|
-
const tx = getDb().transaction(() => {
|
|
195
|
-
createTaskExtended(schedule.taskTemplate, {
|
|
196
|
-
creatorAgentId: schedule.createdByAgentId,
|
|
197
|
-
taskType: schedule.taskType,
|
|
198
|
-
tags: [...schedule.tags, "scheduled", `schedule:${schedule.name}`, "recovered"],
|
|
199
|
-
priority: schedule.priority,
|
|
200
|
-
agentId: schedule.targetAgentId,
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// Update nextRunAt to the next FUTURE run
|
|
204
|
-
const nextRun = calculateNextRun(schedule, now);
|
|
205
|
-
updateScheduledTask(schedule.id, {
|
|
206
|
-
lastRunAt: now.toISOString(),
|
|
207
|
-
nextRunAt: nextRun,
|
|
208
|
-
lastUpdatedAt: now.toISOString(),
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
tx();
|
|
212
|
-
} catch (err) {
|
|
213
|
-
console.error(`[Scheduler] Error recovering "${schedule.name}":`, err);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
Then call it in `startScheduler()` before the first `processSchedules()`:
|
|
220
|
-
|
|
221
|
-
```typescript
|
|
222
|
-
export function startScheduler(intervalMs = 10000): void {
|
|
223
|
-
if (schedulerInterval) {
|
|
224
|
-
console.log("[Scheduler] Already running");
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
console.log(`[Scheduler] Starting with ${intervalMs}ms polling interval`);
|
|
229
|
-
|
|
230
|
-
// Recover missed schedules from downtime (before normal processing)
|
|
231
|
-
void recoverMissedSchedules();
|
|
232
|
-
|
|
233
|
-
// Run immediately once, then start interval
|
|
234
|
-
void processSchedules();
|
|
235
|
-
// ...
|
|
236
|
-
}
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
**Note**: `recoverMissedSchedules()` and `processSchedules()` both call `getDueScheduledTasks()`, but `recoverMissedSchedules` updates `nextRunAt` for recovered schedules, so `processSchedules()` won't double-execute them.
|
|
240
|
-
|
|
241
|
-
### Testing
|
|
242
|
-
|
|
243
|
-
1. Create a schedule with `intervalMs: 60000` (1 minute)
|
|
244
|
-
2. Stop the scheduler, wait 5 minutes
|
|
245
|
-
3. Restart — verify exactly one "recovered" task is created
|
|
246
|
-
4. Verify the nextRunAt is set to a future time (not to the missed time)
|
|
247
|
-
|
|
248
|
-
### Estimated changes: ~40 lines in scheduler.ts
|
|
249
|
-
|
|
250
|
-
---
|
|
251
|
-
|
|
252
|
-
## Item 4 (P2): Exponential Backoff for Failed Scheduled Tasks
|
|
253
|
-
|
|
254
|
-
**Goal**: When a scheduled task's execution fails repeatedly, apply exponential backoff to prevent flooding and auto-disable after too many consecutive failures.
|
|
255
|
-
|
|
256
|
-
**Current state**: `executeSchedule()` (scheduler.ts:44) catches errors and logs them but takes no recovery action. The schedule fires again at the normal interval.
|
|
257
|
-
|
|
258
|
-
### Files to modify
|
|
259
|
-
|
|
260
|
-
1. **`src/be/db.ts`** — Migration for new columns + update helper
|
|
261
|
-
2. **`src/scheduler/scheduler.ts`** — Error tracking in `executeSchedule()`, backoff in `processSchedules()`
|
|
262
|
-
|
|
263
|
-
### Implementation
|
|
264
|
-
|
|
265
|
-
#### Step 1: DB Migration (db.ts)
|
|
266
|
-
|
|
267
|
-
Add after existing migrations:
|
|
268
|
-
|
|
269
|
-
```typescript
|
|
270
|
-
// Migration: Add error tracking columns to scheduled_tasks
|
|
271
|
-
try {
|
|
272
|
-
database.run("ALTER TABLE scheduled_tasks ADD COLUMN consecutiveErrors INTEGER DEFAULT 0");
|
|
273
|
-
} catch {}
|
|
274
|
-
try {
|
|
275
|
-
database.run("ALTER TABLE scheduled_tasks ADD COLUMN lastErrorAt TEXT");
|
|
276
|
-
} catch {}
|
|
277
|
-
try {
|
|
278
|
-
database.run("ALTER TABLE scheduled_tasks ADD COLUMN lastErrorMessage TEXT");
|
|
279
|
-
} catch {}
|
|
280
|
-
```
|
|
281
|
-
|
|
282
|
-
#### Step 2: Update `updateScheduledTask` signature
|
|
283
|
-
|
|
284
|
-
The existing `updateScheduledTask()` (db.ts:4155) already accepts a generic update object with dynamic keys. The new columns (`consecutiveErrors`, `lastErrorAt`, `lastErrorMessage`) need to be added to its field allowlist.
|
|
285
|
-
|
|
286
|
-
In db.ts, find the `updateScheduledTask` function and add the new fields to the allowed fields list:
|
|
287
|
-
|
|
288
|
-
```typescript
|
|
289
|
-
// In updateScheduledTask(), add to the allowed fields:
|
|
290
|
-
const allowedFields = [
|
|
291
|
-
// ... existing fields ...
|
|
292
|
-
"consecutiveErrors",
|
|
293
|
-
"lastErrorAt",
|
|
294
|
-
"lastErrorMessage",
|
|
295
|
-
];
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
Also update the `ScheduledTask` type in `types.ts` and the `rowToScheduledTask` mapper if it exists.
|
|
299
|
-
|
|
300
|
-
#### Step 3: Backoff logic in scheduler.ts
|
|
301
|
-
|
|
302
|
-
Define the backoff schedule:
|
|
303
|
-
|
|
304
|
-
```typescript
|
|
305
|
-
const ERROR_BACKOFF_MS = [
|
|
306
|
-
60_000, // 1 minute
|
|
307
|
-
300_000, // 5 minutes
|
|
308
|
-
900_000, // 15 minutes
|
|
309
|
-
1_800_000, // 30 minutes
|
|
310
|
-
3_600_000, // 1 hour (cap)
|
|
311
|
-
];
|
|
312
|
-
|
|
313
|
-
const MAX_CONSECUTIVE_ERRORS = 5; // Auto-disable after this many
|
|
314
|
-
|
|
315
|
-
function getBackoffMs(consecutiveErrors: number): number {
|
|
316
|
-
const idx = Math.min(consecutiveErrors - 1, ERROR_BACKOFF_MS.length - 1);
|
|
317
|
-
return ERROR_BACKOFF_MS[Math.max(0, idx)];
|
|
318
|
-
}
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
#### Step 4: Modify `executeSchedule()` to track errors
|
|
322
|
-
|
|
323
|
-
Wrap the existing `executeSchedule()` in error handling that updates the schedule on failure:
|
|
324
|
-
|
|
325
|
-
```typescript
|
|
326
|
-
async function executeSchedule(schedule: ScheduledTask): Promise<void> {
|
|
327
|
-
try {
|
|
328
|
-
const tx = getDb().transaction(() => {
|
|
329
|
-
const now = new Date().toISOString();
|
|
330
|
-
createTaskExtended(schedule.taskTemplate, {
|
|
331
|
-
creatorAgentId: schedule.createdByAgentId,
|
|
332
|
-
taskType: schedule.taskType,
|
|
333
|
-
tags: [...schedule.tags, "scheduled", `schedule:${schedule.name}`],
|
|
334
|
-
priority: schedule.priority,
|
|
335
|
-
agentId: schedule.targetAgentId,
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
const nextRun = calculateNextRun(schedule, new Date());
|
|
339
|
-
updateScheduledTask(schedule.id, {
|
|
340
|
-
lastRunAt: now,
|
|
341
|
-
nextRunAt: nextRun,
|
|
342
|
-
lastUpdatedAt: now,
|
|
343
|
-
// Reset error tracking on success
|
|
344
|
-
consecutiveErrors: 0,
|
|
345
|
-
lastErrorAt: null,
|
|
346
|
-
lastErrorMessage: null,
|
|
347
|
-
});
|
|
348
|
-
|
|
349
|
-
return nextRun;
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
const nextRun = tx();
|
|
353
|
-
console.log(`[Scheduler] Executed schedule "${schedule.name}", next run: ${nextRun}`);
|
|
354
|
-
} catch (err) {
|
|
355
|
-
const errorCount = (schedule.consecutiveErrors ?? 0) + 1;
|
|
356
|
-
const now = new Date();
|
|
357
|
-
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
358
|
-
|
|
359
|
-
console.error(
|
|
360
|
-
`[Scheduler] Error executing "${schedule.name}" (${errorCount} consecutive):`,
|
|
361
|
-
errorMsg
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
const updates: Record<string, unknown> = {
|
|
365
|
-
consecutiveErrors: errorCount,
|
|
366
|
-
lastErrorAt: now.toISOString(),
|
|
367
|
-
lastErrorMessage: errorMsg.slice(0, 500),
|
|
368
|
-
lastUpdatedAt: now.toISOString(),
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
if (errorCount >= MAX_CONSECUTIVE_ERRORS) {
|
|
372
|
-
// Auto-disable after too many consecutive failures
|
|
373
|
-
updates.enabled = 0;
|
|
374
|
-
console.warn(
|
|
375
|
-
`[Scheduler] Auto-disabled "${schedule.name}" after ${errorCount} consecutive errors`
|
|
376
|
-
);
|
|
377
|
-
} else {
|
|
378
|
-
// Apply backoff to nextRunAt
|
|
379
|
-
const backoff = getBackoffMs(errorCount);
|
|
380
|
-
updates.nextRunAt = new Date(now.getTime() + backoff).toISOString();
|
|
381
|
-
console.log(
|
|
382
|
-
`[Scheduler] Backing off "${schedule.name}" for ${backoff / 1000}s`
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
updateScheduledTask(schedule.id, updates);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
### Testing
|
|
392
|
-
|
|
393
|
-
1. Create a schedule with an invalid `taskTemplate` (or target a non-existent agent) to trigger failures
|
|
394
|
-
2. Observe `consecutiveErrors` incrementing and `nextRunAt` being pushed forward
|
|
395
|
-
3. After 5 failures, verify the schedule is auto-disabled (`enabled = 0`)
|
|
396
|
-
4. Fix the issue, re-enable the schedule, verify `consecutiveErrors` resets to 0 on success
|
|
397
|
-
|
|
398
|
-
### Estimated changes: ~60 lines in scheduler.ts, ~10 lines in db.ts
|
|
399
|
-
|
|
400
|
-
---
|
|
401
|
-
|
|
402
|
-
## Item 1 (P1): Tool Loop Detection
|
|
403
|
-
|
|
404
|
-
**Goal**: Detect when an agent is stuck in a repetitive tool call loop and intervene by blocking the tool call with an explanatory message.
|
|
405
|
-
|
|
406
|
-
**Current state**: No loop detection exists. Agents may repeat the same tool calls indefinitely (especially `Bash`, `Read`, or `Grep` with identical arguments) until context compaction or max-turns limits.
|
|
407
|
-
|
|
408
|
-
### Files to modify
|
|
409
|
-
|
|
410
|
-
1. **`src/hooks/tool-loop-detection.ts`** — New file with detection logic
|
|
411
|
-
2. **`src/hooks/hook.ts`** — PreToolUse handler integration
|
|
412
|
-
|
|
413
|
-
### Implementation
|
|
414
|
-
|
|
415
|
-
#### Step 1: New file `src/hooks/tool-loop-detection.ts`
|
|
416
|
-
|
|
417
|
-
```typescript
|
|
418
|
-
/**
|
|
419
|
-
* Tool loop detection for agent-swarm hooks.
|
|
420
|
-
*
|
|
421
|
-
* Tracks recent tool calls per session and detects repetitive patterns.
|
|
422
|
-
* Uses a file-based history to persist across hook invocations (hooks are
|
|
423
|
-
* separate Bun processes, not long-running).
|
|
424
|
-
*/
|
|
425
|
-
|
|
426
|
-
interface ToolCallRecord {
|
|
427
|
-
toolName: string;
|
|
428
|
-
argsHash: string;
|
|
429
|
-
timestamp: number;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
interface LoopDetectionResult {
|
|
433
|
-
blocked: boolean;
|
|
434
|
-
reason?: string;
|
|
435
|
-
severity?: "warning" | "critical";
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const HISTORY_DIR = "/tmp/agent-swarm-tool-history";
|
|
439
|
-
const MAX_HISTORY = 30; // Sliding window size
|
|
440
|
-
const REPEAT_WARNING_THRESHOLD = 8;
|
|
441
|
-
const REPEAT_CRITICAL_THRESHOLD = 15;
|
|
442
|
-
const PINGPONG_WARNING_THRESHOLD = 6;
|
|
443
|
-
const PINGPONG_CRITICAL_THRESHOLD = 12;
|
|
444
|
-
|
|
445
|
-
/**
|
|
446
|
-
* Simple hash of tool arguments for comparison.
|
|
447
|
-
* Uses JSON.stringify + a basic hash to avoid storing full args.
|
|
448
|
-
*/
|
|
449
|
-
function hashArgs(args: Record<string, unknown>): string {
|
|
450
|
-
const str = JSON.stringify(args, Object.keys(args).sort());
|
|
451
|
-
let hash = 0;
|
|
452
|
-
for (let i = 0; i < str.length; i++) {
|
|
453
|
-
const char = str.charCodeAt(i);
|
|
454
|
-
hash = ((hash << 5) - hash) + char;
|
|
455
|
-
hash |= 0; // Convert to 32bit integer
|
|
456
|
-
}
|
|
457
|
-
return hash.toString(36);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Load tool call history for a session.
|
|
462
|
-
*/
|
|
463
|
-
async function loadHistory(sessionKey: string): Promise<ToolCallRecord[]> {
|
|
464
|
-
try {
|
|
465
|
-
const file = Bun.file(`${HISTORY_DIR}/${sessionKey}.json`);
|
|
466
|
-
if (await file.exists()) {
|
|
467
|
-
return await file.json();
|
|
468
|
-
}
|
|
469
|
-
} catch {}
|
|
470
|
-
return [];
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* Save tool call history for a session.
|
|
475
|
-
*/
|
|
476
|
-
async function saveHistory(sessionKey: string, history: ToolCallRecord[]): Promise<void> {
|
|
477
|
-
try {
|
|
478
|
-
await Bun.$`mkdir -p ${HISTORY_DIR}`.quiet();
|
|
479
|
-
await Bun.write(
|
|
480
|
-
`${HISTORY_DIR}/${sessionKey}.json`,
|
|
481
|
-
JSON.stringify(history.slice(-MAX_HISTORY))
|
|
482
|
-
);
|
|
483
|
-
} catch {}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* Detect if the current tool call is part of a repetitive loop.
|
|
488
|
-
*/
|
|
489
|
-
export async function checkToolLoop(
|
|
490
|
-
sessionKey: string,
|
|
491
|
-
toolName: string,
|
|
492
|
-
toolInput: Record<string, unknown>
|
|
493
|
-
): Promise<LoopDetectionResult> {
|
|
494
|
-
const argsHash = hashArgs(toolInput);
|
|
495
|
-
const history = await loadHistory(sessionKey);
|
|
496
|
-
|
|
497
|
-
// Add current call to history
|
|
498
|
-
history.push({ toolName, argsHash, timestamp: Date.now() });
|
|
499
|
-
await saveHistory(sessionKey, history);
|
|
500
|
-
|
|
501
|
-
// Only check if we have enough history
|
|
502
|
-
if (history.length < REPEAT_WARNING_THRESHOLD) {
|
|
503
|
-
return { blocked: false };
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
// Strategy 1: Same tool + same args repeated
|
|
507
|
-
const key = `${toolName}:${argsHash}`;
|
|
508
|
-
let repeatCount = 0;
|
|
509
|
-
for (const record of history) {
|
|
510
|
-
if (`${record.toolName}:${record.argsHash}` === key) {
|
|
511
|
-
repeatCount++;
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
if (repeatCount >= REPEAT_CRITICAL_THRESHOLD) {
|
|
516
|
-
return {
|
|
517
|
-
blocked: true,
|
|
518
|
-
severity: "critical",
|
|
519
|
-
reason: `Tool "${toolName}" has been called ${repeatCount} times with identical arguments in the last ${MAX_HISTORY} calls. You are stuck in a loop. Try a completely different approach.`,
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
if (repeatCount >= REPEAT_WARNING_THRESHOLD) {
|
|
524
|
-
return {
|
|
525
|
-
blocked: false, // Warning only — don't block yet
|
|
526
|
-
severity: "warning",
|
|
527
|
-
reason: `Tool "${toolName}" has been called ${repeatCount} times with identical arguments. Consider trying a different approach.`,
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// Strategy 2: Ping-pong between two tool call patterns
|
|
532
|
-
if (history.length >= PINGPONG_WARNING_THRESHOLD) {
|
|
533
|
-
const recent = history.slice(-PINGPONG_CRITICAL_THRESHOLD);
|
|
534
|
-
const patterns = new Map<string, number>();
|
|
535
|
-
for (const r of recent) {
|
|
536
|
-
const p = `${r.toolName}:${r.argsHash}`;
|
|
537
|
-
patterns.set(p, (patterns.get(p) || 0) + 1);
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Check if exactly 2 patterns dominate
|
|
541
|
-
const sorted = [...patterns.entries()].sort((a, b) => b[1] - a[1]);
|
|
542
|
-
if (sorted.length >= 2) {
|
|
543
|
-
const [first, second] = sorted;
|
|
544
|
-
const dominance = first[1] + second[1];
|
|
545
|
-
if (dominance >= recent.length * 0.8) {
|
|
546
|
-
// Two patterns account for 80%+ of recent calls
|
|
547
|
-
const totalPingPong = first[1] + second[1];
|
|
548
|
-
if (totalPingPong >= PINGPONG_CRITICAL_THRESHOLD) {
|
|
549
|
-
return {
|
|
550
|
-
blocked: true,
|
|
551
|
-
severity: "critical",
|
|
552
|
-
reason: `Detected ping-pong loop: alternating between "${first[0].split(":")[0]}" and "${second[0].split(":")[0]}" for ${totalPingPong} calls. Break out of this pattern.`,
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
if (totalPingPong >= PINGPONG_WARNING_THRESHOLD) {
|
|
556
|
-
return {
|
|
557
|
-
blocked: false,
|
|
558
|
-
severity: "warning",
|
|
559
|
-
reason: `Possible ping-pong pattern detected between two tool calls. Consider a different approach.`,
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
return { blocked: false };
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* Clear tool call history for a session (call on session end).
|
|
571
|
-
*/
|
|
572
|
-
export async function clearToolHistory(sessionKey: string): Promise<void> {
|
|
573
|
-
try {
|
|
574
|
-
await Bun.$`rm -f ${HISTORY_DIR}/${sessionKey}.json`.quiet();
|
|
575
|
-
} catch {}
|
|
576
|
-
}
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
#### Step 2: Integrate in hook.ts PreToolUse handler
|
|
580
|
-
|
|
581
|
-
In hook.ts, add import at top:
|
|
582
|
-
|
|
583
|
-
```typescript
|
|
584
|
-
import { checkToolLoop, clearToolHistory } from "./tool-loop-detection";
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
In the `PreToolUse` case (after cancellation check, around line 755), add:
|
|
588
|
-
|
|
589
|
-
```typescript
|
|
590
|
-
// Tool loop detection (workers only, when processing a task)
|
|
591
|
-
if (agentInfo && !agentInfo.isLead && agentInfo.status === "busy") {
|
|
592
|
-
const taskFileData = await readTaskFile();
|
|
593
|
-
if (taskFileData?.taskId && msg.tool_name && msg.tool_input) {
|
|
594
|
-
const result = await checkToolLoop(
|
|
595
|
-
taskFileData.taskId,
|
|
596
|
-
msg.tool_name,
|
|
597
|
-
msg.tool_input as Record<string, unknown>
|
|
598
|
-
);
|
|
599
|
-
|
|
600
|
-
if (result.blocked) {
|
|
601
|
-
outputBlockResponse(
|
|
602
|
-
`🔄 LOOP DETECTED: ${result.reason} ` +
|
|
603
|
-
`Stop repeating this action and try a fundamentally different approach. ` +
|
|
604
|
-
`If you're truly stuck, use store-progress to report the blocker.`
|
|
605
|
-
);
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
if (result.severity === "warning" && result.reason) {
|
|
610
|
-
// Output as a non-blocking warning (just console.log, not a block response)
|
|
611
|
-
console.log(`⚠️ ${result.reason}`);
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
```
|
|
616
|
-
|
|
617
|
-
Also clear history on `SessionStart`:
|
|
618
|
-
|
|
619
|
-
```typescript
|
|
620
|
-
// In case "SessionStart":, after existing logic:
|
|
621
|
-
// Clear any stale tool loop history
|
|
622
|
-
const startTaskFile = await readTaskFile();
|
|
623
|
-
if (startTaskFile?.taskId) {
|
|
624
|
-
await clearToolHistory(startTaskFile.taskId);
|
|
625
|
-
}
|
|
626
|
-
```
|
|
627
|
-
|
|
628
|
-
### Design decisions
|
|
629
|
-
|
|
630
|
-
- **File-based history**: Hooks run as separate Bun processes (not a persistent server). State must persist across invocations → file-based storage in `/tmp`.
|
|
631
|
-
- **Session-scoped**: History is keyed by taskId, so each task has independent tracking.
|
|
632
|
-
- **Warning before blocking**: At 8 repeats, warn. At 15, block. This gives the agent a chance to self-correct.
|
|
633
|
-
- **Critical blocks**: Only the `outputBlockResponse` path actually stops the agent. Warnings are advisory.
|
|
634
|
-
|
|
635
|
-
### Testing
|
|
636
|
-
|
|
637
|
-
1. Create a test task that deliberately loops (e.g., a prompt that says "read the same file 20 times")
|
|
638
|
-
2. Observe warning at 8 repeats, block at 15
|
|
639
|
-
3. Verify ping-pong detection with alternating tool calls
|
|
640
|
-
4. Verify history is cleared on session start (no contamination between tasks)
|
|
641
|
-
|
|
642
|
-
### Estimated changes: ~160 lines in tool-loop-detection.ts, ~30 lines in hook.ts
|
|
643
|
-
|
|
644
|
-
---
|
|
645
|
-
|
|
646
|
-
## Item 5 (P2): Graceful Task Cancellation (Push-based)
|
|
647
|
-
|
|
648
|
-
**Goal**: When a task is cancelled, proactively signal the Claude subprocess instead of waiting for the next hook check.
|
|
649
|
-
|
|
650
|
-
**Current state**: Cancellation is detected via `isTaskCancelled()` in the `PreToolUse` and `UserPromptSubmit` hooks (hook.ts:748-768, 849-858). If the agent is in a long-running operation (subprocess, waiting, etc.), it won't notice until the next tool call.
|
|
651
|
-
|
|
652
|
-
### Files to modify
|
|
653
|
-
|
|
654
|
-
1. **`src/commands/runner.ts`** — Main loop: check for cancelled tasks and signal subprocess
|
|
655
|
-
2. **`src/http.ts`** — Add a `POST /api/tasks/:id/cancel` webhook/notification path (optional, for future)
|
|
656
|
-
|
|
657
|
-
### Implementation
|
|
658
|
-
|
|
659
|
-
#### Approach: Runner-side polling for cancellations
|
|
660
|
-
|
|
661
|
-
The runner's main loop already iterates every ~2s (PollIntervalMs). Add a cancellation check there.
|
|
662
|
-
|
|
663
|
-
In `runner.ts`, inside the main `while(true)` loop (after `checkCompletedProcesses` at line 2019), add:
|
|
664
|
-
|
|
665
|
-
```typescript
|
|
666
|
-
// Check for cancelled tasks and signal their subprocesses
|
|
667
|
-
for (const [taskId, task] of state.activeTasks) {
|
|
668
|
-
try {
|
|
669
|
-
const resp = await fetch(
|
|
670
|
-
`${apiUrl}/cancelled-tasks?taskId=${encodeURIComponent(taskId)}`,
|
|
671
|
-
{
|
|
672
|
-
headers: {
|
|
673
|
-
Authorization: `Bearer ${apiKey}`,
|
|
674
|
-
"X-Agent-ID": agentId,
|
|
675
|
-
},
|
|
676
|
-
}
|
|
677
|
-
);
|
|
678
|
-
if (resp.ok) {
|
|
679
|
-
const data = await resp.json() as { cancelled: Array<{ id: string }> };
|
|
680
|
-
if (data.cancelled?.some(t => t.id === taskId)) {
|
|
681
|
-
console.log(`[${role}] Task ${taskId.slice(0, 8)} was cancelled — sending SIGTERM to subprocess`);
|
|
682
|
-
task.process.kill("SIGTERM");
|
|
683
|
-
// Don't remove from activeTasks yet — checkCompletedProcesses will handle cleanup
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
} catch {
|
|
687
|
-
// Non-blocking — cancellation check is best-effort
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
This checks each active task against the cancellation endpoint. If cancelled, it sends SIGTERM to the Claude subprocess, which triggers Claude Code's graceful shutdown (Stop hook fires, task gets cleaned up).
|
|
693
|
-
|
|
694
|
-
#### Optimization: Batch check
|
|
695
|
-
|
|
696
|
-
To avoid N API calls (one per active task), we can batch by checking all at once. However, the current `/cancelled-tasks` endpoint only supports `?taskId=` for a single task. Two options:
|
|
697
|
-
|
|
698
|
-
**Option A (Simple)**: Keep per-task checks. With maxConcurrent typically being 1-2, this is fine.
|
|
699
|
-
|
|
700
|
-
**Option B (Better)**: Modify the `/cancelled-tasks` endpoint to support `?taskIds=id1,id2,id3`. But this requires HTTP endpoint changes.
|
|
701
|
-
|
|
702
|
-
**Recommendation**: Go with Option A for simplicity. The overhead of 1-2 extra API calls per 2s is negligible.
|
|
703
|
-
|
|
704
|
-
#### Important: Only check when tasks are running
|
|
705
|
-
|
|
706
|
-
```typescript
|
|
707
|
-
// Only check if there are active tasks (avoid unnecessary API calls)
|
|
708
|
-
if (state.activeTasks.size > 0) {
|
|
709
|
-
for (const [taskId, task] of state.activeTasks) {
|
|
710
|
-
// ... check cancellation
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
```
|
|
714
|
-
|
|
715
|
-
### Why SIGTERM (not SIGUSR1)
|
|
716
|
-
|
|
717
|
-
Claude Code's `--sigterm-timeout` flag controls how long it waits after SIGTERM before force-exiting. The Stop hook fires on SIGTERM, which gives the agent a chance to clean up. SIGUSR1 is not handled by Claude Code.
|
|
718
|
-
|
|
719
|
-
### Testing
|
|
720
|
-
|
|
721
|
-
1. Start a worker with a long-running task
|
|
722
|
-
2. Cancel the task via `cancel-task` MCP tool
|
|
723
|
-
3. Observe: within ~2 seconds, the subprocess receives SIGTERM
|
|
724
|
-
4. Verify the task is marked as cancelled/failed (via `ensureTaskFinished`)
|
|
725
|
-
|
|
726
|
-
### Estimated changes: ~25 lines in runner.ts
|
|
727
|
-
|
|
728
|
-
---
|
|
729
|
-
|
|
730
|
-
## Implementation Order Summary
|
|
731
|
-
|
|
732
|
-
| Order | Item | Effort | Dependencies |
|
|
733
|
-
|-------|------|--------|-------------|
|
|
734
|
-
| 1st | **Item 2**: Active Session Heartbeat | ~15 lines | None |
|
|
735
|
-
| 2nd | **Item 6**: Progress Deduplication | ~10 lines | None |
|
|
736
|
-
| 3rd | **Item 3**: Missed Schedule Recovery | ~40 lines | None |
|
|
737
|
-
| 4th | **Item 4**: Schedule Error Backoff | ~70 lines | Builds on Item 3's scheduler changes |
|
|
738
|
-
| 5th | **Item 1**: Tool Loop Detection | ~190 lines | None (but most complex) |
|
|
739
|
-
| 6th | **Item 5**: Push Cancellation | ~25 lines | None (but requires careful testing) |
|
|
740
|
-
|
|
741
|
-
**Total estimated new code**: ~350 lines across 5 files
|
|
742
|
-
|
|
743
|
-
---
|
|
744
|
-
|
|
745
|
-
## Rollout Strategy
|
|
746
|
-
|
|
747
|
-
### Phase 1: Low-risk improvements (Items 2, 6)
|
|
748
|
-
- Deploy together. Zero risk — heartbeat is fire-and-forget, dedup is a simple optimization.
|
|
749
|
-
- Verify heartbeat updates are flowing before proceeding.
|
|
750
|
-
|
|
751
|
-
### Phase 2: Scheduler improvements (Items 3, 4)
|
|
752
|
-
- Deploy together since Item 4 builds on Item 3.
|
|
753
|
-
- Test with a non-critical schedule first.
|
|
754
|
-
- Monitor `consecutiveErrors` column after deploy.
|
|
755
|
-
|
|
756
|
-
### Phase 3: Complex features (Items 1, 5)
|
|
757
|
-
- Deploy Item 1 (tool loop detection) first, monitor for false positives.
|
|
758
|
-
- Start with higher thresholds (e.g., 20 for critical) and lower once confident.
|
|
759
|
-
- Deploy Item 5 (push cancellation) last — test in staging by cancelling tasks manually.
|
|
760
|
-
|
|
761
|
-
### Monitoring
|
|
762
|
-
|
|
763
|
-
After deployment, track:
|
|
764
|
-
- Active session heartbeat frequency (should update every few seconds during active tasks)
|
|
765
|
-
- Progress dedup hit rate (log when a duplicate is skipped)
|
|
766
|
-
- Schedule recovery events (look for "recovered" tagged tasks)
|
|
767
|
-
- Schedule error backoff events (look for "Auto-disabled" log lines)
|
|
768
|
-
- Tool loop detections (warning/critical counts)
|
|
769
|
-
- Cancellation signal delivery latency (time between cancel API call and SIGTERM)
|
|
770
|
-
|
|
771
|
-
---
|
|
772
|
-
|
|
773
|
-
## Open Questions for Reviewer
|
|
774
|
-
|
|
775
|
-
1. **Tool loop thresholds**: Are 8/15 (warning/critical) appropriate, or should we start more conservative (e.g., 12/20)?
|
|
776
|
-
2. **Schedule recovery**: Should we run ALL missed executions (like openclaw) or just ONE catch-up per schedule (current plan)?
|
|
777
|
-
3. **Auto-disable threshold**: Is 5 consecutive errors the right threshold for auto-disabling a schedule?
|
|
778
|
-
4. **Cancellation signal**: Should we use SIGTERM (triggers full Stop hook) or a lighter signal? SIGTERM means the agent gets a chance to save progress.
|