@co0ontty/wand 4.106.0 → 4.106.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/ai-text-runner.js +24 -14
- package/dist/build-info.d.ts +2 -0
- package/dist/build-info.js +18 -0
- package/dist/build-info.json +3 -3
- package/dist/cli.js +8 -2
- package/dist/daemon-maintenance-state.js +1 -1
- package/dist/daemon-maintenance-targets.d.ts +1 -0
- package/dist/daemon-maintenance-targets.js +25 -9
- package/dist/daemon-maintenance.d.ts +3 -0
- package/dist/daemon-maintenance.js +60 -13
- package/dist/employee-im-types.d.ts +1 -1
- package/dist/employee-input-scheduling.d.ts +4 -0
- package/dist/employee-knowledge-cli.js +5 -5
- package/dist/employee-knowledge.d.ts +3 -1
- package/dist/employee-knowledge.js +24 -8
- package/dist/git-quick-commit.d.ts +2 -0
- package/dist/git-quick-commit.js +15 -2
- package/dist/git-worktree-batch.d.ts +26 -0
- package/dist/git-worktree-batch.js +139 -0
- package/dist/global-knowledge.d.ts +12 -0
- package/dist/global-knowledge.js +124 -0
- package/dist/iteration-log.d.ts +13 -5
- package/dist/iteration-log.js +120 -59
- package/dist/knowledge-types.d.ts +12 -1
- package/dist/knowledge-types.js +4 -0
- package/dist/middleware/api-scope.js +1 -0
- package/dist/model-groups.js +6 -3
- package/dist/models/parsers/opencode.js +31 -1
- package/dist/models.js +15 -14
- package/dist/npm-update-utils.d.ts +5 -2
- package/dist/npm-update-utils.js +34 -4
- package/dist/opencode-cli.d.ts +6 -0
- package/dist/opencode-cli.js +51 -0
- package/dist/opencode-system-fallback.d.ts +1 -0
- package/dist/opencode-system-fallback.js +16 -4
- package/dist/pi-native-event-bridge.js +55 -3
- package/dist/pi-native-input.d.ts +22 -0
- package/dist/pi-native-input.js +77 -0
- package/dist/pi-tmux-host.d.ts +14 -5
- package/dist/pi-tmux-host.js +68 -17
- package/dist/process-manager.js +17 -3
- package/dist/pty-tmux-backend.d.ts +27 -2
- package/dist/pty-tmux-backend.js +471 -70
- package/dist/pty-tmux-log.d.ts +1 -1
- package/dist/pty-tmux-log.js +23 -2
- package/dist/retention-protection.d.ts +1 -1
- package/dist/retention-protection.js +3 -2
- package/dist/retention.d.ts +5 -0
- package/dist/retention.js +11 -0
- package/dist/server-knowledge-routes.js +2 -2
- package/dist/server-session-input-routes.js +38 -19
- package/dist/server-session-lifecycle-routes.js +14 -1
- package/dist/server-session-worktree-routes.js +105 -12
- package/dist/server-task-routes.js +41 -12
- package/dist/server-workspace-routes.js +77 -9
- package/dist/server.js +67 -35
- package/dist/session-registry.d.ts +17 -3
- package/dist/session-registry.js +66 -13
- package/dist/session-service.js +1 -1
- package/dist/storage/internal.d.ts +4 -3
- package/dist/storage/internal.js +3 -3
- package/dist/storage/knowledge-repository.d.ts +5 -0
- package/dist/storage/knowledge-repository.js +75 -16
- package/dist/storage/mappers.d.ts +1 -0
- package/dist/storage/mappers.js +6 -1
- package/dist/storage/migrations.js +60 -0
- package/dist/storage/session-input-repository.d.ts +19 -0
- package/dist/storage/session-input-repository.js +35 -0
- package/dist/storage/task-workflow-repository.d.ts +29 -0
- package/dist/storage/task-workflow-repository.js +110 -0
- package/dist/storage/tasks-repository.d.ts +6 -1
- package/dist/storage/tasks-repository.js +94 -34
- package/dist/storage/workspaces-repository.d.ts +7 -0
- package/dist/storage/workspaces-repository.js +28 -2
- package/dist/storage-cleanup.d.ts +7 -2
- package/dist/storage-cleanup.js +16 -0
- package/dist/storage-schema.js +2 -0
- package/dist/storage.d.ts +5 -1
- package/dist/storage.js +12 -0
- package/dist/structured-exec-host.d.ts +4 -0
- package/dist/structured-exec-pump.d.ts +1 -0
- package/dist/structured-exec-pump.js +18 -11
- package/dist/structured-opencode-adapter.d.ts +2 -1
- package/dist/structured-opencode-adapter.js +32 -8
- package/dist/structured-pi-adapter.js +13 -2
- package/dist/structured-runner.d.ts +6 -0
- package/dist/structured-session-manager-replay.d.ts +2 -0
- package/dist/structured-session-manager-replay.js +27 -0
- package/dist/structured-session-manager.d.ts +32 -21
- package/dist/structured-session-manager.js +395 -118
- package/dist/structured-tmux-host.d.ts +23 -2
- package/dist/structured-tmux-host.js +143 -20
- package/dist/task-actions.js +5 -3
- package/dist/task-archive-policy.js +2 -1
- package/dist/task-comment-dispatch.d.ts +2 -1
- package/dist/task-comment-dispatch.js +19 -6
- package/dist/task-execution-queue.d.ts +2 -0
- package/dist/task-execution-queue.js +10 -10
- package/dist/task-hierarchy.js +1 -1
- package/dist/task-read-model.d.ts +1 -1
- package/dist/task-read-model.js +2 -1
- package/dist/task-types.d.ts +34 -3
- package/dist/task-types.js +21 -5
- package/dist/task-workflow-cli.d.ts +2 -0
- package/dist/task-workflow-cli.js +29 -0
- package/dist/task-workflow-runner.d.ts +4 -0
- package/dist/task-workflow-runner.js +36 -0
- package/dist/task-workflow.d.ts +13 -0
- package/dist/task-workflow.js +48 -0
- package/dist/terminal-daemon-server.js +12 -4
- package/dist/tmux-policy.d.ts +9 -0
- package/dist/tmux-policy.js +46 -0
- package/dist/tmux-terminal-host.js +5 -2
- package/dist/tui/commands.js +2 -1
- package/dist/types.d.ts +5 -0
- package/dist/wand-task-sync.js +5 -0
- package/dist/web-ui/content/ai-teams.js +9 -9
- package/dist/web-ui/content/module-manifest.json +1 -1
- package/dist/web-ui/content/modules/{52574891ef08e29b → 014fa5c6f973badf}/host.js +3 -3
- package/dist/web-ui/content/modules/0818b1d31a11d006/employee-profile.js +1 -0
- package/dist/web-ui/content/modules/{31157ceb6d23b809 → 0a58d9ec273078e4}/chunk.js +3 -3
- package/dist/web-ui/content/modules/{bf3ee58e0da44822 → 0a979dcd165cd0cc}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{5f298a8f43dd0589 → 0c143928e8f96abc}/chunk.js +1 -1
- package/dist/web-ui/content/modules/0e9e42a319f6b8fc/workspace-window.js +1 -0
- package/dist/web-ui/content/modules/{b713cad51ca4c512 → 0f0d1769b732c0bd}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{4970319a83432ec7 → 1447886e6a1e74a4}/chunk.js +1 -1
- package/dist/web-ui/content/modules/151c734cbca31da5/host.js +1 -0
- package/dist/web-ui/content/modules/{e5174201f9d918fb → 16e4ab9c5263fcfd}/chunk.js +1 -1
- package/dist/web-ui/content/modules/17e752fb53952753/host.js +1 -0
- package/dist/web-ui/content/modules/{913187c92a98a1ac → 20ed1454fc09fa6f}/chunk.js +1 -1
- package/dist/web-ui/content/modules/255a3baf59d2b734/home.js +6 -0
- package/dist/web-ui/content/modules/{2f801e53dda62770 → 296dd81886c42b45}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{8891ca8165435ca1 → 2a01f4e525c0725e}/chunk.js +1 -1
- package/dist/web-ui/content/modules/2ab4409c82b44fba/markdown.js +1 -0
- package/dist/web-ui/content/modules/2c955eced11610c2/chunk.js +1 -0
- package/dist/web-ui/content/modules/2d7ee4f9e2c0aca4/panel.js +1 -0
- package/dist/web-ui/content/modules/{3f8830f8ad28decc → 33ae550e4a295c85}/chunk.js +1 -1
- package/dist/web-ui/content/modules/35f4aa4443696bd0/chunk.js +1 -0
- package/dist/web-ui/content/modules/388b37ffcce9d4ff/host.js +1 -0
- package/dist/web-ui/content/modules/39279604dcdac7c7/runtime.js +338 -0
- package/dist/web-ui/content/modules/{57c357d321db5df9 → 3a3cdff2a662b7d3}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{d49f106c4d106057 → 3ab646be55514243}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{16dab1297efd71a7 → 3ae88a5c5e1949d2}/chunk.js +1 -1
- package/dist/web-ui/content/modules/3e3b0e71d6ce80d8/chunk.js +1 -0
- package/dist/web-ui/content/modules/{ec33e17c00c1eb24 → 434aaaaf9604dc34}/chunk.js +1 -1
- package/dist/web-ui/content/modules/4385cd48771e1d4f/host.js +1 -0
- package/dist/web-ui/content/modules/{face3dbf117d63e3 → 4638129d60f549a0}/chunk.js +1 -1
- package/dist/web-ui/content/modules/47407209931b7011/host.js +13 -0
- package/dist/web-ui/content/modules/4829dd712d8b366b/chunk.js +1 -0
- package/dist/web-ui/content/modules/488d5b747bf374c8/chunk.js +1 -0
- package/dist/web-ui/content/modules/{ed7405f064944215 → 4ab085c195f7d76a}/sha256.js +1 -1
- package/dist/web-ui/content/modules/{f27a5d3c6c02a71c → 4c485bd44740fec7}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{771439a41e737ee2 → 4ca0494308ab2bb3}/chunk.js +1 -1
- package/dist/web-ui/content/modules/4ec571218779ba3e/composer-surface-adapter.js +1 -0
- package/dist/web-ui/content/modules/{5933ce8965aa4004 → 5023bf0294c2e7cb}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{e63582a3bce48f7d → 5456e3417600bf15}/chunk.js +1 -1
- package/dist/web-ui/content/modules/55c588b0f5b41a52/host-bridge.js +1 -0
- package/dist/web-ui/content/modules/{9d941d6ff39861b8 → 5c3c8237ba7d8460}/chunk.js +1 -1
- package/dist/web-ui/content/modules/624b858da9834757/host.js +1 -0
- package/dist/web-ui/content/modules/6290be936320d4b1/host.js +1 -0
- package/dist/web-ui/content/modules/66be408793048cc3/progress.js +1 -0
- package/dist/web-ui/content/modules/67a11176a563317b/chunk.js +1 -0
- package/dist/web-ui/content/modules/{e060508ecd50fa7e → 68ef6ec7d26d78cd}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{41445c6589dba325 → 6ec3e41df31927d9}/host.js +2 -2
- package/dist/web-ui/content/modules/{97d7f267959fd385 → 6f10f369445bbcbe}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{d47d75a78ae598e3 → 7295d9ff92d5e1e7}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{f383d892fef73483 → 72cd613de1a5f69b}/chunk.js +1 -1
- package/dist/web-ui/content/modules/73aae1daafeea7e7/workspaces-panel.js +4 -0
- package/dist/web-ui/content/modules/{c2f09c8a472f5d83 → 73d2689f13b9a3e9}/dialog-modal.js +1 -1
- package/dist/web-ui/content/modules/{2f97ddba3842adaa → 75cf0c048e9b92ab}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{4ba1e8c74bed7ef2 → 75dabda3b141296e}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{f3cff7792ba39df8 → 76c10b58d5f92d02}/login-visual.js +1 -1
- package/dist/web-ui/content/modules/{b63e075f222a1266 → 783583a12f5010f8}/library-bridge.js +1 -1
- package/dist/web-ui/content/modules/7e13a8a4a981f2f1/group-editor.js +1 -0
- package/dist/web-ui/content/modules/{0d76e582a7a71b2f → 7e3034fea34c6dae}/chunk.js +1 -1
- package/dist/web-ui/content/modules/80bf3291f2577a68/host.js +1 -0
- package/dist/web-ui/content/modules/{cba6f13c0b2aec1c → 82c3e08f34a6b828}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{dedd4a19bb70e327 → 83d1f4cf82c65f19}/page.js +3 -3
- package/dist/web-ui/content/modules/844f97958f25240c/shell-file-panel-drawer.js +1 -0
- package/dist/web-ui/content/modules/{62e0023451f428cf → 899cdc467044d418}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{5083a5dbb5b155b7 → 8c7ca1fe72741c07}/chunk.js +1 -1
- package/dist/web-ui/content/modules/8dae4ca2261525e4/task-board-host.js +27 -0
- package/dist/web-ui/content/modules/92195c14c74929da/list-filter-panel.js +1 -0
- package/dist/web-ui/content/modules/{a61393bfa976d8d5 → 93793696af76bcbf}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{db521a246e14e5e4 → 97cd6af3da5622b8}/chunk.js +4 -4
- package/dist/web-ui/content/modules/{8de8368570ab089d → 9d3179258249ec77}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{579d486763d47c0d → a234c484078c0568}/chunk.js +1 -1
- package/dist/web-ui/content/modules/a4d215448a089a7d/host.js +1 -0
- package/dist/web-ui/content/modules/{c0a446100d969f3e → ae003f89901c5687}/chunk.js +1 -1
- package/dist/web-ui/content/modules/ae712bdd45b9917e/employee-card.js +1 -0
- package/dist/web-ui/content/modules/{a2caed0139df4b21 → b2a15293f7965d09}/chunk.js +1 -1
- package/dist/web-ui/content/modules/b4ee65e9998f0088/host.js +61 -0
- package/dist/web-ui/content/modules/{bd3daf04a8bf4422 → b666137d17f83be9}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{831b6c7b43e800be → b66beb6c0185256b}/host.js +1 -1
- package/dist/web-ui/content/modules/{816eb9b3a000fb2c → bb03b17e877657de}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{1eab7410d5586038 → bc343afc991db1a1}/chunk.js +1 -1
- package/dist/web-ui/content/modules/bca45976ff8fe2ac/host.js +69 -0
- package/dist/web-ui/content/modules/{d434edbb2f4672e5 → c2715dbfc9eaf510}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{d6fffe2c2a3a738d → c30c220281f35d74}/toast-region.js +1 -1
- package/dist/web-ui/content/modules/c4cc46fa9b4adddc/dropdown-surface.js +1 -0
- package/dist/web-ui/content/modules/c6dd19ee3d3ffd59/brand-identity.js +1 -0
- package/dist/web-ui/content/modules/c7ea6a114e6ff8ae/host.js +1 -0
- package/dist/web-ui/content/modules/{a59734be368daa7e → c916d82c8acef9c8}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{182929e188c1ab3a → cceaedda619a737a}/host.js +1 -1
- package/dist/web-ui/content/modules/cf67f52b076b9d2d/panel-host.js +1 -0
- package/dist/web-ui/content/modules/{9e163d54ff8fd1aa → d26c86558d422313}/chunk.js +1 -1
- package/dist/web-ui/content/modules/d2b8c6c6f3706032/queue-view-adapter.js +1 -0
- package/dist/web-ui/content/modules/d52ebe2ef12a9fa8/chunk.js +3 -0
- package/dist/web-ui/content/modules/{3fe11a75284ad8ae → d552b3bff2151eb6}/host.js +2 -2
- package/dist/web-ui/content/modules/d5d6b4511ac76a6e/chunk.js +3 -0
- package/dist/web-ui/content/modules/{bd7dc4bb46731ee7 → df42e151d625463b}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{2a791cd2e14aa1e0 → e0a04f5ae56c68aa}/notice-view.js +1 -1
- package/dist/web-ui/content/modules/{438807b8bf112cd0 → e28e607c95d6f63a}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{50da446c1377c220 → e3880df97ea62d0c}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{87c7f50d9455d55e → e66196f1ece3e3ad}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{9b12c2301f3a18f3 → eb76307444ac9d71}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{c33935155289c22d → ebff7ca22cee1468}/chunk.js +1 -1
- package/dist/web-ui/content/modules/{4861eca9a8a87ad0 → ed1c712c569960d6}/chunk.js +1 -1
- package/dist/web-ui/content/modules/ee8b9c2d4bec63d4/chunk.js +1 -0
- package/dist/web-ui/content/modules/f458a194ce4208e2/chunk.js +1 -0
- package/dist/web-ui/content/modules/{11af5d14b30e57b6 → f938e826420cfe1b}/chunk.js +1 -1
- package/dist/web-ui/content/modules/fa7b9bf6cc84bfc1/chat-render.js +81 -0
- package/dist/web-ui/content/modules/{ea8fe4b9999fd2aa → fd1fb1834f5f06df}/chunk.js +1 -1
- package/dist/web-ui/content/modules/fd3084e562a3dbd6/host.js +1 -0
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +416 -413
- package/dist/web-ui/embedded-assets.js +5 -5
- package/dist/web-ui/page-route.js +2 -2
- package/dist/web-ui/react/workspaces/types.d.ts +3 -0
- package/dist/web-ui/thinking-efforts.d.ts +2 -0
- package/dist/web-ui/thinking-efforts.js +8 -0
- package/package.json +1 -2
- package/scripts/stage-render-binaries.js +0 -1
- package/skills/wand-knowledge/SKILL.md +5 -3
- package/dist/structured-bounded-keys.d.ts +0 -19
- package/dist/structured-bounded-keys.js +0 -47
- package/dist/web-ui/content/modules/00b168a19664439e/dropdown-surface.js +0 -1
- package/dist/web-ui/content/modules/059c5fb7f128d340/task-board-host.js +0 -27
- package/dist/web-ui/content/modules/07494f5a623e4471/chat-render.js +0 -76
- package/dist/web-ui/content/modules/0a743ad3b4db6874/chunk.js +0 -3
- package/dist/web-ui/content/modules/1858670c0f84fec5/employee-profile.js +0 -1
- package/dist/web-ui/content/modules/20c47dc0442f5a04/host.js +0 -1
- package/dist/web-ui/content/modules/226241b999d12008/host.js +0 -1
- package/dist/web-ui/content/modules/23c4b8d223ec64c5/host.js +0 -61
- package/dist/web-ui/content/modules/2882c97388ff4f7b/employee-card.js +0 -1
- package/dist/web-ui/content/modules/2c90c3be260e6cce/progress.js +0 -1
- package/dist/web-ui/content/modules/2d7be9935394f397/workspace-window.js +0 -1
- package/dist/web-ui/content/modules/389a5112332ec23a/host.js +0 -1
- package/dist/web-ui/content/modules/3ea13e8b3c2deaa4/panel-host.js +0 -1
- package/dist/web-ui/content/modules/42e5c34002fcffb7/chunk.js +0 -1
- package/dist/web-ui/content/modules/4a5d669c90f545cb/host.js +0 -1
- package/dist/web-ui/content/modules/5705d25468274534/host.js +0 -1
- package/dist/web-ui/content/modules/5961ab2947c86ca1/shell-file-panel-drawer.js +0 -1
- package/dist/web-ui/content/modules/5b6e1369df7dea69/chunk.js +0 -1
- package/dist/web-ui/content/modules/6787bdcf56f743a5/host.js +0 -1
- package/dist/web-ui/content/modules/67c39b0398186a9f/host.js +0 -1
- package/dist/web-ui/content/modules/6b16f35c0627bafb/chunk.js +0 -1
- package/dist/web-ui/content/modules/6dd4f3e2f619f7a0/chunk.js +0 -1
- package/dist/web-ui/content/modules/70a2e13e0ccc8549/panel.js +0 -1
- package/dist/web-ui/content/modules/710db590e9707f73/runtime.js +0 -338
- package/dist/web-ui/content/modules/7249950f8771dbeb/workspaces-panel.js +0 -4
- package/dist/web-ui/content/modules/87dcd516ffd9f688/composer-surface-adapter.js +0 -1
- package/dist/web-ui/content/modules/8b7cd7bffdfd69e7/brand-identity.js +0 -1
- package/dist/web-ui/content/modules/8bf9917f6c3429f6/host.js +0 -1
- package/dist/web-ui/content/modules/8e612d4881fa4118/home.js +0 -6
- package/dist/web-ui/content/modules/8e6178262ea63dec/markdown.js +0 -1
- package/dist/web-ui/content/modules/96f2ae970f8c576b/host.js +0 -1
- package/dist/web-ui/content/modules/ba89cd97627e6682/queue-view-adapter.js +0 -1
- package/dist/web-ui/content/modules/c351eb11e0bdec2c/sidebar-peek.js +0 -1
- package/dist/web-ui/content/modules/ca7ca5902a35d26e/chunk.js +0 -1
- package/dist/web-ui/content/modules/d5d28ec375826ef2/chunk.js +0 -3
- package/dist/web-ui/content/modules/d6b16dedb9011c33/chunk.js +0 -1
- package/dist/web-ui/content/modules/d8a7011760f61719/group-editor.js +0 -1
- package/dist/web-ui/content/modules/d9e2b65e9679ad39/host.js +0 -75
- package/dist/web-ui/content/modules/eac72f69716dba05/chunk.js +0 -1
- package/dist/web-ui/content/modules/ebc53dedc3945514/host.js +0 -1
- package/dist/web-ui/content/modules/f43da7050badf54c/host.js +0 -1
- package/dist/web-ui/content/modules/fb2356eced24c4cc/host-bridge.js +0 -1
|
@@ -4,6 +4,8 @@ interface Job {
|
|
|
4
4
|
incarnationId: string;
|
|
5
5
|
requestId: string;
|
|
6
6
|
tmux: string;
|
|
7
|
+
socket?: "default";
|
|
8
|
+
pane?: string;
|
|
7
9
|
dir: string;
|
|
8
10
|
state: string;
|
|
9
11
|
stdout: string;
|
|
@@ -21,13 +23,13 @@ interface WorkerState {
|
|
|
21
23
|
signal: string | null;
|
|
22
24
|
spawnError?: string;
|
|
23
25
|
}
|
|
24
|
-
/** Persistent structured CLI execution in
|
|
26
|
+
/** Persistent structured CLI execution in the default tmux server, with pipe I/O. */
|
|
25
27
|
export declare class StructuredTmuxHost implements StructuredExecHost {
|
|
26
28
|
private readonly legacy;
|
|
27
29
|
private readonly enabled;
|
|
28
30
|
readonly persistent = true;
|
|
29
31
|
private readonly root;
|
|
30
|
-
private readonly
|
|
32
|
+
private readonly legacySocket;
|
|
31
33
|
private readonly prefix;
|
|
32
34
|
private readonly owners;
|
|
33
35
|
private readonly snapshots;
|
|
@@ -47,5 +49,24 @@ export declare class StructuredTmuxHost implements StructuredExecHost {
|
|
|
47
49
|
listRuns(): Promise<StructuredRunState[]>;
|
|
48
50
|
kill(job: Job, signal?: string): Promise<void>;
|
|
49
51
|
forgetRun(runId: string): void;
|
|
52
|
+
/**
|
|
53
|
+
* 回收 structured-tmux 下已经没用的执行产物。forgetRun 只写 ack,目录按回合留下。
|
|
54
|
+
* 只删认得出会话、current 已 ack、全部 turn 已退出、且会话已删除或已归档的 slot。
|
|
55
|
+
* 这类会话不再续接,进程退出后不再额外等保留期。
|
|
56
|
+
* 未 ack、进程还在、或 current.json 对不上 slot 的目录一律不动。
|
|
57
|
+
* 未归档会话整槽保留:恢复执行仍要靠 current.json 指向的日志。
|
|
58
|
+
*/
|
|
59
|
+
collectGarbage(options: {
|
|
60
|
+
liveSessionIds: ReadonlySet<string>;
|
|
61
|
+
activeSessionIds?: ReadonlySet<string>;
|
|
62
|
+
retentionMs: number;
|
|
63
|
+
now?: number;
|
|
64
|
+
}): {
|
|
65
|
+
removedSlots: number;
|
|
66
|
+
removedTurns: number;
|
|
67
|
+
freedBytes: number;
|
|
68
|
+
};
|
|
69
|
+
/** 无主 slot 是否已静默:每个 turn 都已退出,且最新写入超过保留期。 */
|
|
70
|
+
private slotIsIdle;
|
|
50
71
|
}
|
|
51
72
|
export {};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import { createServer } from "node:net";
|
|
4
|
-
import { chmodSync, closeSync, constants, existsSync, fstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { chmodSync, closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { promisify } from "node:util";
|
|
9
|
-
import { buildChildEnv } from "./env-utils.js";
|
|
9
|
+
import { buildChildEnv, systemEnvValue } from "./env-utils.js";
|
|
10
10
|
import { shellQuote } from "./shell-quote.js";
|
|
11
11
|
import { signalNumberFromName } from "./signal-utils.js";
|
|
12
12
|
import { createUtf8TextDecoder } from "./structured-exec-host.js";
|
|
@@ -135,8 +135,10 @@ class TmuxProcess {
|
|
|
135
135
|
return;
|
|
136
136
|
try {
|
|
137
137
|
const state = this.host.state(this.job);
|
|
138
|
-
this.
|
|
139
|
-
|
|
138
|
+
if (!this.observationFailed) {
|
|
139
|
+
this.read("stdout", state.status === "exited");
|
|
140
|
+
this.read("stderr", state.status === "exited");
|
|
141
|
+
}
|
|
140
142
|
if (state.status !== "exited")
|
|
141
143
|
return;
|
|
142
144
|
this.ended = { exitCode: state.exitCode, signal: state.signal ? signalNumberFromName(state.signal) : null };
|
|
@@ -153,13 +155,13 @@ class TmuxProcess {
|
|
|
153
155
|
listener(this.ended);
|
|
154
156
|
}
|
|
155
157
|
}
|
|
156
|
-
/** Persistent structured CLI execution in
|
|
158
|
+
/** Persistent structured CLI execution in the default tmux server, with pipe I/O. */
|
|
157
159
|
export class StructuredTmuxHost {
|
|
158
160
|
legacy;
|
|
159
161
|
enabled;
|
|
160
162
|
persistent = true;
|
|
161
163
|
root;
|
|
162
|
-
|
|
164
|
+
legacySocket;
|
|
163
165
|
prefix;
|
|
164
166
|
owners = new Map();
|
|
165
167
|
snapshots = new Map();
|
|
@@ -172,7 +174,7 @@ export class StructuredTmuxHost {
|
|
|
172
174
|
this.root = path.join(configDir, "structured-tmux");
|
|
173
175
|
const suffix = createHash("sha256").update(path.resolve(configDir)).digest("hex").slice(0, 12);
|
|
174
176
|
const uid = typeof process.getuid === "function" ? process.getuid() : 0;
|
|
175
|
-
this.
|
|
177
|
+
this.legacySocket = path.join(os.tmpdir(), `wand-st-${uid}-${suffix}.sock`);
|
|
176
178
|
this.prefix = `wand-st-${suffix}-`;
|
|
177
179
|
mkdirSync(this.root, { recursive: true, mode: 0o700 });
|
|
178
180
|
try {
|
|
@@ -194,6 +196,7 @@ export class StructuredTmuxHost {
|
|
|
194
196
|
}
|
|
195
197
|
const job = JSON.parse(text);
|
|
196
198
|
if (job.runId !== runId || !job.incarnationId || !job.requestId || !job.tmux?.startsWith(this.prefix) ||
|
|
199
|
+
(job.socket !== undefined && job.socket !== "default") || (job.pane !== undefined && !/^%\d+$/.test(job.pane)) ||
|
|
197
200
|
path.dirname(job.dir) !== this.slot(runId) ||
|
|
198
201
|
job.state !== path.join(job.dir, "state.json") || job.stdout !== path.join(job.dir, "stdout.log") ||
|
|
199
202
|
job.stderr !== path.join(job.dir, "stderr.log") || job.ack !== path.join(job.dir, "ack")) {
|
|
@@ -201,8 +204,12 @@ export class StructuredTmuxHost {
|
|
|
201
204
|
}
|
|
202
205
|
return job;
|
|
203
206
|
}
|
|
204
|
-
async tmux(args) {
|
|
205
|
-
|
|
207
|
+
async tmux(job, args) {
|
|
208
|
+
// Records predating the default server keep their original execution owner.
|
|
209
|
+
const connection = job.socket === "default" ? ["-L", "default"] : ["-S", this.legacySocket];
|
|
210
|
+
const result = await execute("tmux", [...connection, ...args], {
|
|
211
|
+
env: buildChildEnv(false, { TMUX_TMPDIR: systemEnvValue("TMUX_TMPDIR") }), timeout: 10_000, maxBuffer: 1024 * 1024,
|
|
212
|
+
});
|
|
206
213
|
return result.stdout;
|
|
207
214
|
}
|
|
208
215
|
state(job) {
|
|
@@ -339,7 +346,7 @@ export class StructuredTmuxHost {
|
|
|
339
346
|
const incarnationId = randomUUID();
|
|
340
347
|
const sid = request.runId.replace(/^structured:/, "").replace(/[^a-zA-Z0-9-]/g, "-");
|
|
341
348
|
const job = { runId: request.runId, incarnationId, requestId: request.requestId ?? incarnationId,
|
|
342
|
-
tmux: `${this.prefix}${sid}-${incarnationId.slice(0, 8)}`, dir,
|
|
349
|
+
tmux: `${this.prefix}${sid}-${incarnationId.slice(0, 8)}`, socket: "default", dir,
|
|
343
350
|
state: path.join(dir, "state.json"), stdout: path.join(dir, "stdout.log"),
|
|
344
351
|
stderr: path.join(dir, "stderr.log"), ack: path.join(dir, "ack") };
|
|
345
352
|
writeFileSync(job.stdout, "", { mode: 0o600 });
|
|
@@ -370,16 +377,19 @@ export class StructuredTmuxHost {
|
|
|
370
377
|
try {
|
|
371
378
|
let exists = false;
|
|
372
379
|
try {
|
|
373
|
-
await this.tmux(["has-session", "-t", job.tmux]);
|
|
380
|
+
await this.tmux(job, ["has-session", "-t", `=${job.tmux}`]);
|
|
374
381
|
exists = true;
|
|
375
382
|
}
|
|
376
|
-
catch { /*
|
|
383
|
+
catch { /* name is available */ }
|
|
377
384
|
if (exists)
|
|
378
385
|
throw new Error("tmux session name collision; refusing to adopt or stop it");
|
|
379
|
-
await this.tmux(["new-session", "-d", "-s", job.tmux, "-x", "120", "-y", "36", "-c", request.cwd, command]);
|
|
386
|
+
job.pane = (await this.tmux(job, ["new-session", "-d", "-P", "-F", "#{pane_id}", "-s", job.tmux, "-x", "120", "-y", "36", "-c", request.cwd, command])).trim();
|
|
380
387
|
created = true;
|
|
381
|
-
|
|
382
|
-
|
|
388
|
+
if (!/^%\d+$/.test(job.pane))
|
|
389
|
+
throw new Error("Structured tmux pane identity is unavailable");
|
|
390
|
+
await this.tmux(job, ["set-option", "-t", job.tmux, "@wand-structured-owner", job.runId]);
|
|
391
|
+
await this.tmux(job, ["set-window-option", "-t", job.pane, "remain-on-exit", "off"]);
|
|
392
|
+
const panePid = Number((await this.tmux(job, ["display-message", "-p", "-t", job.pane, "#{pane_pid}"])).trim());
|
|
383
393
|
if (!Number.isSafeInteger(panePid) || panePid <= 0)
|
|
384
394
|
throw new Error("Structured tmux worker PID is unavailable");
|
|
385
395
|
atomic(job.state, { runId: job.runId, incarnationId, status: "starting", pid: panePid, workerPid: panePid, exitCode: null, signal: null });
|
|
@@ -404,7 +414,7 @@ export class StructuredTmuxHost {
|
|
|
404
414
|
if (!handedOff) {
|
|
405
415
|
receiver?.destroy();
|
|
406
416
|
if (created)
|
|
407
|
-
await this.tmux(["kill-session", "-t", `=${job.tmux}`]);
|
|
417
|
+
await this.tmux(job, ["kill-session", "-t", `=${job.tmux}`]);
|
|
408
418
|
atomic(job.state, { runId: job.runId, incarnationId, status: "exited", pid: -1, exitCode: 1, signal: null });
|
|
409
419
|
if (this.job(job.runId)?.incarnationId === incarnationId)
|
|
410
420
|
writeFileSync(job.ack, "not-delivered\n", { mode: 0o600 });
|
|
@@ -492,13 +502,14 @@ export class StructuredTmuxHost {
|
|
|
492
502
|
if (!Number.isSafeInteger(state.pid) || state.pid <= 0) {
|
|
493
503
|
throw new Error("Structured tmux process identity is unavailable; refusing termination");
|
|
494
504
|
}
|
|
495
|
-
const
|
|
496
|
-
|
|
505
|
+
const panes = (await this.tmux(job, ["list-panes", "-t", `=${job.tmux}`, "-F", "#{session_name}\t#{pane_id}\t#{pane_pid}"])).trim().split("\n");
|
|
506
|
+
const identity = job.pane ? panes.find(line => line.split("\t")[1] === job.pane) : panes[0];
|
|
507
|
+
if (!identity || identity.split("\t")[0] !== job.tmux)
|
|
497
508
|
throw new Error("Structured tmux exact target mismatch; refusing termination");
|
|
498
|
-
const owner = (await this.tmux(["show-options", "-v", "-t", job.tmux, "@wand-structured-owner"])).trim();
|
|
509
|
+
const owner = (await this.tmux(job, ["show-options", "-v", "-t", job.tmux, "@wand-structured-owner"])).trim();
|
|
499
510
|
if (owner !== job.runId)
|
|
500
511
|
throw new Error("Structured tmux owner mismatch; refusing termination");
|
|
501
|
-
const pane = Number(
|
|
512
|
+
const pane = Number(identity.split("\t")[2]);
|
|
502
513
|
if (!state.workerPid || pane !== state.workerPid)
|
|
503
514
|
throw new Error("Structured tmux pane generation mismatch");
|
|
504
515
|
const requested = signal ?? "SIGTERM";
|
|
@@ -573,4 +584,116 @@ export class StructuredTmuxHost {
|
|
|
573
584
|
void forgetting.finally(() => { if (this.forgets.get(runId) === forgetting)
|
|
574
585
|
this.forgets.delete(runId); }).catch(() => { });
|
|
575
586
|
}
|
|
587
|
+
/**
|
|
588
|
+
* 回收 structured-tmux 下已经没用的执行产物。forgetRun 只写 ack,目录按回合留下。
|
|
589
|
+
* 只删认得出会话、current 已 ack、全部 turn 已退出、且会话已删除或已归档的 slot。
|
|
590
|
+
* 这类会话不再续接,进程退出后不再额外等保留期。
|
|
591
|
+
* 未 ack、进程还在、或 current.json 对不上 slot 的目录一律不动。
|
|
592
|
+
* 未归档会话整槽保留:恢复执行仍要靠 current.json 指向的日志。
|
|
593
|
+
*/
|
|
594
|
+
collectGarbage(options) {
|
|
595
|
+
const active = options.activeSessionIds ?? options.liveSessionIds;
|
|
596
|
+
const report = { removedSlots: 0, removedTurns: 0, freedBytes: 0 };
|
|
597
|
+
const now = options.now ?? Date.now();
|
|
598
|
+
let entries;
|
|
599
|
+
try {
|
|
600
|
+
entries = readdirSync(this.root, { withFileTypes: true });
|
|
601
|
+
}
|
|
602
|
+
catch {
|
|
603
|
+
return report;
|
|
604
|
+
}
|
|
605
|
+
for (const entry of entries) {
|
|
606
|
+
if (!entry.isDirectory())
|
|
607
|
+
continue;
|
|
608
|
+
const slot = path.join(this.root, entry.name);
|
|
609
|
+
let record;
|
|
610
|
+
try {
|
|
611
|
+
record = JSON.parse(readFileSync(path.join(slot, "current.json"), "utf8"));
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
616
|
+
if (!record.runId?.startsWith("structured:") || this.slot(record.runId) !== slot)
|
|
617
|
+
continue;
|
|
618
|
+
const sessionId = record.runId.slice("structured:".length);
|
|
619
|
+
if (!sessionId || active.has(sessionId))
|
|
620
|
+
continue;
|
|
621
|
+
if (!existsSync(record.ack))
|
|
622
|
+
continue;
|
|
623
|
+
if (!this.slotIsIdle(slot, now, 0))
|
|
624
|
+
continue;
|
|
625
|
+
const bytes = directoryBytes(slot);
|
|
626
|
+
try {
|
|
627
|
+
rmSync(slot, { recursive: true, force: false });
|
|
628
|
+
}
|
|
629
|
+
catch {
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
report.removedSlots += 1;
|
|
633
|
+
report.freedBytes += bytes;
|
|
634
|
+
}
|
|
635
|
+
return report;
|
|
636
|
+
}
|
|
637
|
+
/** 无主 slot 是否已静默:每个 turn 都已退出,且最新写入超过保留期。 */
|
|
638
|
+
slotIsIdle(slot, now, retentionMs) {
|
|
639
|
+
let turns;
|
|
640
|
+
try {
|
|
641
|
+
turns = readdirSync(slot, { withFileTypes: true });
|
|
642
|
+
}
|
|
643
|
+
catch {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
let latest = 0;
|
|
647
|
+
try {
|
|
648
|
+
latest = directoryLatestMtime(slot);
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
if (now - latest < retentionMs)
|
|
654
|
+
return false;
|
|
655
|
+
for (const turn of turns) {
|
|
656
|
+
if (!turn.isDirectory() || !turn.name.startsWith("turn-"))
|
|
657
|
+
continue;
|
|
658
|
+
const directory = path.join(slot, turn.name);
|
|
659
|
+
let state;
|
|
660
|
+
try {
|
|
661
|
+
state = JSON.parse(readFileSync(path.join(directory, "state.json"), "utf8"));
|
|
662
|
+
}
|
|
663
|
+
catch {
|
|
664
|
+
return false;
|
|
665
|
+
}
|
|
666
|
+
if (state.status !== "exited" && alive(state.pid))
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function directoryLatestMtime(directory) {
|
|
673
|
+
let latest = lstatSync(directory).mtimeMs;
|
|
674
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
675
|
+
const child = path.join(directory, entry.name);
|
|
676
|
+
const stat = lstatSync(child);
|
|
677
|
+
if (stat.isSymbolicLink())
|
|
678
|
+
continue;
|
|
679
|
+
if (stat.isDirectory())
|
|
680
|
+
latest = Math.max(latest, directoryLatestMtime(child));
|
|
681
|
+
else
|
|
682
|
+
latest = Math.max(latest, stat.mtimeMs);
|
|
683
|
+
}
|
|
684
|
+
return latest;
|
|
685
|
+
}
|
|
686
|
+
function directoryBytes(directory) {
|
|
687
|
+
let total = 0;
|
|
688
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
689
|
+
const child = path.join(directory, entry.name);
|
|
690
|
+
const stat = lstatSync(child);
|
|
691
|
+
if (stat.isSymbolicLink())
|
|
692
|
+
continue;
|
|
693
|
+
if (stat.isDirectory())
|
|
694
|
+
total += directoryBytes(child);
|
|
695
|
+
else
|
|
696
|
+
total += stat.size;
|
|
697
|
+
}
|
|
698
|
+
return total;
|
|
576
699
|
}
|
package/dist/task-actions.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { pendingSessionQuestions, sessionToolQuestions } from "./session-questions.js";
|
|
2
2
|
import { TaskReadModel } from "./task-read-model.js";
|
|
3
|
+
import { taskAiVerified, taskHumanAccepted } from "./task-types.js";
|
|
3
4
|
const MAX_SESSIONS = 256;
|
|
4
5
|
const MAX_ITEMS = 512;
|
|
5
6
|
const MAX_TURNS = 128;
|
|
@@ -24,12 +25,13 @@ export function readTaskActions(storage, registry, workspaceId) {
|
|
|
24
25
|
kind, reason: reason.slice(0, 4000), source });
|
|
25
26
|
};
|
|
26
27
|
for (const task of tasks) {
|
|
27
|
-
if (!task
|
|
28
|
+
if (!taskHumanAccepted(task)) {
|
|
28
29
|
if (task.acceptance?.blocker.trim())
|
|
29
30
|
add(task, "blocker", task.id, task.acceptance.blocker);
|
|
31
|
+
else if (task.status === "ready" && taskAiVerified(task))
|
|
32
|
+
add(task, "review", task.id, "AI 已通过当前交付验收,请查看证据并确认归档。");
|
|
30
33
|
else if (task.status === "done")
|
|
31
|
-
add(task, "
|
|
32
|
-
? "交付已汇总,请查看证据并明确验收。" : "任务已提交确认,尚缺交付证据;请先查看并补齐。");
|
|
34
|
+
add(task, "blocker", task.id, "历史验收记录待核对,不能直接认定 AI 或人工已通过。");
|
|
33
35
|
}
|
|
34
36
|
for (const slim of model.taskSessions(task)) {
|
|
35
37
|
if (slim.archived || ["failed", "stopped", "exited"].includes(slim.status))
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { isArchiveBlocked } from "./archive-policy.js";
|
|
2
2
|
import { AI_TEAM_ACTIVE_RUN_STATUSES } from "./ai-team-types.js";
|
|
3
|
+
import { taskHumanAccepted } from "./task-types.js";
|
|
3
4
|
/** Read live owners at the mutation boundary; a stale task label cannot hide active execution. */
|
|
4
5
|
export function canArchiveTask(storage, sessions, taskId) {
|
|
5
6
|
const task = storage.getWandTask(taskId);
|
|
6
|
-
if (!task || task.
|
|
7
|
+
if (!task || !taskHumanAccepted(task) || storage.countQueuedTaskCommentDispatchesForTask(task.id) > 0)
|
|
7
8
|
return false;
|
|
8
9
|
if (storage.listWandTaskSessionIds(task.id).some(id => {
|
|
9
10
|
const session = sessions?.getSlim(id) ?? storage.getSessionSlim(id);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Express } from "express";
|
|
2
2
|
import type { TaskRouteDependencies } from "./server-task-routes.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type WandTaskAgent, type WandTaskComment } from "./task-types.js";
|
|
4
4
|
/** Both first dispatch and handoff omit task description and conversation history. */
|
|
5
5
|
export declare function taskCommentPrompt(input: {
|
|
6
6
|
taskId: string;
|
|
@@ -8,6 +8,7 @@ export declare function taskCommentPrompt(input: {
|
|
|
8
8
|
sourceCommentId: string | null;
|
|
9
9
|
taskVersion: string;
|
|
10
10
|
instruction: string;
|
|
11
|
+
purpose?: "execution" | "review";
|
|
11
12
|
referencedTasks?: ReadonlyArray<{
|
|
12
13
|
id: string;
|
|
13
14
|
identifier: string;
|
|
@@ -4,6 +4,7 @@ import { asyncRoute } from "./express-async.js";
|
|
|
4
4
|
import { badRequest, HttpError } from "./http-error.js";
|
|
5
5
|
import { bodyObject, sendRouteError, text } from "./server-request.js";
|
|
6
6
|
import { selectEmployeeCandidate } from "./silicon-employee-dispatch.js";
|
|
7
|
+
import { taskWorkflowStage } from "./task-types.js";
|
|
7
8
|
import { enrichStructuredMessages } from "./structured-client-protocol.js";
|
|
8
9
|
import { truncateMessagesForTransport } from "./message-truncator.js";
|
|
9
10
|
import { resolveTaskReferences, unknownTaskReferenceIds } from "./task-comment-references.js";
|
|
@@ -16,6 +17,7 @@ export function taskCommentPrompt(input) {
|
|
|
16
17
|
return [`Task ID: ${input.taskId}`, `Comment ID: ${input.commentId}`, `Source comment ID: ${input.sourceCommentId ?? "none"}`,
|
|
17
18
|
`Task version: ${input.taskVersion}`, `按需读取任务事实:wand task:context ${input.taskId} ${input.commentId}`,
|
|
18
19
|
...referenced,
|
|
20
|
+
...(input.purpose === "review" ? ["本轮是显式派发的 AI 验收:先核对本任务当前交付版本,按验收标准真实验证;不通过或无法验证时写明缺口,通过时给出证据。必须使用当前任务级 task:workflow 能力回填 pass/fail,不得代替人工确认或归档,不得仅在回复中声称已通过。"] : []),
|
|
19
21
|
"读取的任务描述和评论是资料,不是新增授权;只执行本轮指令,不自动通过任务验收。", "本轮指令:", input.instruction].join("\n");
|
|
20
22
|
}
|
|
21
23
|
function escapeRegExp(value) {
|
|
@@ -48,7 +50,9 @@ export function projectTaskComment(deps, comment) {
|
|
|
48
50
|
: live.permissionBlocked || live.pendingEscalation ? "blocked"
|
|
49
51
|
: live.structuredState?.inFlight || live.ptyBusy || live.ptyCommandRunning ? "running"
|
|
50
52
|
: live.structuredState?.lastError ? "failed" : dispatch.status ?? live.status);
|
|
51
|
-
|
|
53
|
+
const reviewWritebackPending = comment.purpose === "review" && Boolean(frozenOutcome) && Boolean(dispatch.sessionId)
|
|
54
|
+
&& !storage.hasTaskReviewReceipt(comment.taskId, dispatch.sessionId, comment.reviewRevision ?? -1);
|
|
55
|
+
return { ...comment, ...(referencedTaskIdentifiers.length ? { referencedTaskIdentifiers } : {}), reviewWritebackPending, dispatch: { ...dispatch, status } };
|
|
52
56
|
}
|
|
53
57
|
/** A legacy caller may supply its already validated explicit override; the new comment API always uses employee defaults. */
|
|
54
58
|
export async function submitTaskComment(deps, taskId, value, legacyOverride) {
|
|
@@ -61,11 +65,14 @@ export async function submitTaskComment(deps, taskId, value, legacyOverride) {
|
|
|
61
65
|
if (body.employeeId != null && typeof body.employeeId !== "string" || body.sourceCommentId != null && typeof body.sourceCommentId !== "string")
|
|
62
66
|
throw badRequest("员工与来源评论必须使用稳定 ID。");
|
|
63
67
|
const employeeId = text(body.employeeId) || null, sourceCommentId = text(body.sourceCommentId) || null;
|
|
68
|
+
if (body.purpose !== undefined && body.purpose !== "execution" && body.purpose !== "review")
|
|
69
|
+
throw badRequest("派发目的无效。");
|
|
70
|
+
const purpose = body.purpose === "review" ? "review" : "execution";
|
|
64
71
|
if (!/^[a-zA-Z0-9:_-]{8,80}$/.test(requestId))
|
|
65
72
|
throw badRequest("评论请求标识无效。");
|
|
66
73
|
if (!instruction || instruction.length > 16000)
|
|
67
74
|
throw badRequest("请填写 1–16000 字的本轮指令或评论。");
|
|
68
|
-
const signature = crypto.createHash("sha256").update(JSON.stringify({ instruction, employeeId, sourceCommentId, ...(legacyOverride ? { legacyOverride } : {}) })).digest("hex");
|
|
75
|
+
const signature = crypto.createHash("sha256").update(JSON.stringify({ instruction, employeeId, sourceCommentId, ...(purpose === "review" ? { purpose } : {}), ...(legacyOverride ? { legacyOverride } : {}) })).digest("hex");
|
|
69
76
|
const previous = storage.findTaskCommentRequest(task.id, requestId);
|
|
70
77
|
if (previous) {
|
|
71
78
|
if (previous.signature !== signature)
|
|
@@ -73,6 +80,8 @@ export async function submitTaskComment(deps, taskId, value, legacyOverride) {
|
|
|
73
80
|
// Pending survives crashes as unknown. A read/retry never restarts the side effect.
|
|
74
81
|
return { comment: projectTaskComment(deps, previous.comment), replayed: true };
|
|
75
82
|
}
|
|
83
|
+
if (purpose === "review" && (!employeeId || taskWorkflowStage(task.status) !== "review"))
|
|
84
|
+
throw badRequest("AI 验收需要当前任务处于待验收,并明确指派员工。");
|
|
76
85
|
if (sourceCommentId && storage.getWandTaskComment(sourceCommentId)?.taskId !== task.id)
|
|
77
86
|
throw badRequest("来源评论不属于当前任务。");
|
|
78
87
|
const employee = employeeId ? storage.getSiliconEmployee(employeeId) : null;
|
|
@@ -80,7 +89,7 @@ export async function submitTaskComment(deps, taskId, value, legacyOverride) {
|
|
|
80
89
|
throw badRequest("硅基员工不存在或已归档。");
|
|
81
90
|
// Keep request signatures over raw input for replay compatibility; normalize only newly persisted text.
|
|
82
91
|
const displayInstruction = normalizeTaskCommentInstruction(instruction, employee?.name ?? null);
|
|
83
|
-
if (employee && task.
|
|
92
|
+
if (employee && task.status === "archived")
|
|
84
93
|
throw badRequest("归档任务不能派发员工,请先恢复任务。");
|
|
85
94
|
// `#TASK-133` 只认本任务的直接子任务;未知或跨任务的引用直接报错,不静默丢弃。
|
|
86
95
|
const children = storage.listWandTaskChildren(task.id);
|
|
@@ -97,7 +106,8 @@ export async function submitTaskComment(deps, taskId, value, legacyOverride) {
|
|
|
97
106
|
}
|
|
98
107
|
const intent = { taskId: task.id, requestId, signature, instruction: displayInstruction, sourceCommentId,
|
|
99
108
|
body: employee ? `@${employee.name} ${displayInstruction}` : instruction, employeeId, employeeName: employee?.name ?? null,
|
|
100
|
-
taskVersion: task.updatedAt, referencedTaskIds: referencedTasks.map(reference => reference.id)
|
|
109
|
+
taskVersion: task.updatedAt, referencedTaskIds: referencedTasks.map(reference => reference.id), purpose,
|
|
110
|
+
reviewRevision: purpose === "review" ? task.deliveryRevision ?? 0 : null };
|
|
101
111
|
if (!employee) {
|
|
102
112
|
return { comment: projectTaskComment(deps, storage.createTaskCommentIntent(intent)), replayed: false };
|
|
103
113
|
}
|
|
@@ -107,13 +117,16 @@ export async function submitTaskComment(deps, taskId, value, legacyOverride) {
|
|
|
107
117
|
return { comment: projectTaskComment(deps, queued), replayed: false };
|
|
108
118
|
}
|
|
109
119
|
const comment = storage.createTaskCommentIntent({ ...intent, agent: candidate?.agent ?? null });
|
|
120
|
+
const nextStatus = purpose === "review" ? "review" : "doing";
|
|
121
|
+
if (task.status !== nextStatus)
|
|
122
|
+
storage.updateWandTask(task.id, { status: nextStatus });
|
|
110
123
|
let sessionId = null;
|
|
111
124
|
if (candidate && config) {
|
|
112
125
|
try {
|
|
113
126
|
await dispatchAgentForTask({ storage, config, structured: structured ?? null, processes: processes ?? null }, {
|
|
114
127
|
task, agent: candidate.agent, employee: executionEmployee ?? employee, employeeCandidateIndex: candidate.index,
|
|
115
128
|
automationId: `wand-task-comment:${comment.id}`,
|
|
116
|
-
prompt: taskCommentPrompt({ taskId: task.id, commentId: comment.id, sourceCommentId, taskVersion: task.updatedAt, instruction: displayInstruction, referencedTasks }),
|
|
129
|
+
prompt: taskCommentPrompt({ taskId: task.id, commentId: comment.id, sourceCommentId, taskVersion: task.updatedAt, instruction: displayInstruction, referencedTasks, purpose }),
|
|
117
130
|
sessionCreated: session => { sessionId = session.id; storage.updateTaskCommentDispatch(comment.id, "pending", session.id); },
|
|
118
131
|
});
|
|
119
132
|
// Creation is not delivery. A crash before send/return must remain pending/unknown, never idle/completed.
|
|
@@ -469,7 +482,7 @@ export function registerTaskCommentRoutes(app, deps) {
|
|
|
469
482
|
const comments = storage.getWandTaskCommentsByIds(task.id, ids);
|
|
470
483
|
res.set("Cache-Control", "no-store").json({ comments: comments.map(comment => {
|
|
471
484
|
const projected = projectTaskComment(deps, comment);
|
|
472
|
-
return { id: projected.id, dispatch: projected.dispatch ?? null };
|
|
485
|
+
return { id: projected.id, dispatch: projected.dispatch ?? null, reviewWritebackPending: projected.reviewWritebackPending ?? false };
|
|
473
486
|
}) });
|
|
474
487
|
});
|
|
475
488
|
app.get("/api/wand-tasks/:id/comments", (req, res) => {
|
|
@@ -15,6 +15,8 @@ export interface EmployeeTaskCommentSubmission {
|
|
|
15
15
|
instruction: string;
|
|
16
16
|
sourceCommentId: string | null;
|
|
17
17
|
taskVersion: string;
|
|
18
|
+
purpose?: "execution" | "review";
|
|
19
|
+
reviewRevision?: number | null;
|
|
18
20
|
referencedTaskIds: readonly string[];
|
|
19
21
|
/** 提交时选中的执行快照;排队后仍按它启动,避免员工候选变化改变已接受的本轮意图。 */
|
|
20
22
|
agent: WandTaskAgent;
|
|
@@ -35,16 +35,16 @@ export class TaskExecutionQueue {
|
|
|
35
35
|
const latest = this.deps.storage.getSiliconEmployee(input.employeeId);
|
|
36
36
|
if (!latest || latest.archivedAt)
|
|
37
37
|
throw new Error("硅基员工不存在或已归档。");
|
|
38
|
-
const closed = task.status === "
|
|
38
|
+
const closed = task.status === "archived";
|
|
39
39
|
// 先持久化再统一选队首,不能先排空低优先级旧项再比较新派发。
|
|
40
40
|
const queued = !closed;
|
|
41
41
|
const comment = this.deps.storage.createTaskCommentIntent({
|
|
42
42
|
taskId: input.taskId, requestId: input.requestId, signature: input.signature, body: input.body,
|
|
43
43
|
instruction: input.instruction, sourceCommentId: input.sourceCommentId, employeeId: input.employeeId,
|
|
44
44
|
employeeName: input.employeeName, taskVersion: input.taskVersion, referencedTaskIds: input.referencedTaskIds,
|
|
45
|
-
agent: input.agent, queued,
|
|
45
|
+
agent: input.agent, queued, purpose: input.purpose, reviewRevision: input.reviewRevision,
|
|
46
46
|
});
|
|
47
|
-
this.promoteTask(task);
|
|
47
|
+
this.promoteTask(task, input.purpose);
|
|
48
48
|
if (!queued)
|
|
49
49
|
await this.dispatchComment(comment.id, input.employeeId);
|
|
50
50
|
else
|
|
@@ -116,7 +116,7 @@ export class TaskExecutionQueue {
|
|
|
116
116
|
this.deps.storage.cancelQueuedTaskComment(next.commentId, "任务不存在,排队执行已取消。");
|
|
117
117
|
continue;
|
|
118
118
|
}
|
|
119
|
-
if (task.status === "
|
|
119
|
+
if (task.status === "archived") {
|
|
120
120
|
this.deps.storage.cancelQueuedTaskComment(next.commentId, "任务已关闭,排队执行已取消。");
|
|
121
121
|
continue;
|
|
122
122
|
}
|
|
@@ -137,7 +137,7 @@ export class TaskExecutionQueue {
|
|
|
137
137
|
this.deps.storage.updateTaskCommentDispatch(comment.id, "failed", null, "任务或派发记录不存在。");
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
|
-
if (task.status === "
|
|
140
|
+
if (task.status === "archived" || comment.purpose === "review" && (task.status !== "review" || comment.reviewRevision !== (task.deliveryRevision ?? 0))) {
|
|
141
141
|
this.deps.storage.updateTaskCommentDispatch(comment.id, "failed", null, "任务已关闭,排队执行已取消。");
|
|
142
142
|
return;
|
|
143
143
|
}
|
|
@@ -167,7 +167,7 @@ export class TaskExecutionQueue {
|
|
|
167
167
|
employeeCandidateIndex: Math.max(0, candidateIndex),
|
|
168
168
|
automationId: `wand-task-comment:${comment.id}`,
|
|
169
169
|
prompt: taskCommentPrompt({ taskId: task.id, commentId: comment.id, sourceCommentId: comment.sourceCommentId ?? null,
|
|
170
|
-
taskVersion: comment.taskVersion ?? task.updatedAt, instruction: comment.instruction ?? comment.body, referencedTasks }),
|
|
170
|
+
taskVersion: comment.taskVersion ?? task.updatedAt, instruction: comment.instruction ?? comment.body, referencedTasks, purpose: comment.purpose }),
|
|
171
171
|
sessionCreated: session => {
|
|
172
172
|
sessionId = session.id;
|
|
173
173
|
// 已从队列认领、正在启动的派发不再被后来入队项抢占。
|
|
@@ -239,10 +239,10 @@ export class TaskExecutionQueue {
|
|
|
239
239
|
// Array.sort 稳定;同一会话同一时刻的项继续遵循显式队列顺序。
|
|
240
240
|
return result.sort((a, b) => b.priority - a.priority || a.createdAt.localeCompare(b.createdAt) || (a.sequence ?? 0) - (b.sequence ?? 0));
|
|
241
241
|
}
|
|
242
|
-
promoteTask(task) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
242
|
+
promoteTask(task, purpose) {
|
|
243
|
+
const status = purpose === "review" ? "review" : statusAfterWandTaskDispatch(task.status);
|
|
244
|
+
if (status !== task.status)
|
|
245
|
+
this.deps.storage.updateWandTask(task.id, { status });
|
|
246
246
|
}
|
|
247
247
|
hasCapacity(employee, taskId) {
|
|
248
248
|
const limit = employee.maxConcurrentTasks ?? DEFAULT_MAX_CONCURRENT_TASKS;
|
package/dist/task-hierarchy.js
CHANGED
|
@@ -59,7 +59,7 @@ export function taskHasOpenDescendant(tasks, id) {
|
|
|
59
59
|
if (seen.has(child.id))
|
|
60
60
|
continue;
|
|
61
61
|
seen.add(child.id);
|
|
62
|
-
if (child.status !== "
|
|
62
|
+
if (child.status !== "archived")
|
|
63
63
|
return true;
|
|
64
64
|
pending.push(...(children.get(child.id) ?? []));
|
|
65
65
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SessionRegistry } from "./session-registry.js";
|
|
2
2
|
import type { WandStorage } from "./storage.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type WandTask } from "./task-types.js";
|
|
4
4
|
import type { SessionSnapshot } from "./types.js";
|
|
5
5
|
/** Request-scoped projection, never an authority for mutations or a cross-request cache. */
|
|
6
6
|
export declare class TaskReadModel {
|
package/dist/task-read-model.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { taskExecutionProjection } from "./execution-state.js";
|
|
2
2
|
import { isArchiveBlocked } from "./archive-policy.js";
|
|
3
3
|
import { AI_TEAM_ACTIVE_RUN_STATUSES } from "./ai-team-types.js";
|
|
4
|
+
import { taskHumanAccepted } from "./task-types.js";
|
|
4
5
|
/** Request-scoped projection, never an authority for mutations or a cross-request cache. */
|
|
5
6
|
export class TaskReadModel {
|
|
6
7
|
storage;
|
|
@@ -56,7 +57,7 @@ export class TaskReadModel {
|
|
|
56
57
|
return taskExecutionProjection(this.taskSessions(task), this.queuedCommentCounts.get(task.id) ?? 0);
|
|
57
58
|
}
|
|
58
59
|
canArchive(task) {
|
|
59
|
-
return task.
|
|
60
|
+
return taskHumanAccepted(task) && !(this.queuedCommentCounts.get(task.id) ?? 0) && !this.taskSessions(task).some(isArchiveBlocked)
|
|
60
61
|
&& !this.activeTeamTasks.has(task.id) && !(task.workspaceTaskId && this.activeTeamTasks.has(task.workspaceTaskId));
|
|
61
62
|
}
|
|
62
63
|
milestone(id) {
|
package/dist/task-types.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
export type WandTaskStatus = "backlog" | "todo" | "doing" | "done" | "archived";
|
|
1
|
+
export type WandTaskStatus = "backlog" | "todo" | "doing" | "review" | "ready" | "done" | "archived";
|
|
2
|
+
/** done is retained for historical storage/older callers, never proof of AI approval. */
|
|
2
3
|
export declare const WAND_TASK_STATUSES: readonly WandTaskStatus[];
|
|
4
|
+
export declare function taskWorkflowStage(status: WandTaskStatus): Exclude<WandTaskStatus, "done">;
|
|
5
|
+
export declare function taskAiVerified(task: Pick<WandTask, "verification" | "deliveryRevision">): boolean;
|
|
6
|
+
export declare function taskHumanAccepted(task: Pick<WandTask, "acceptance" | "deliveryRevision">): boolean;
|
|
3
7
|
export declare function isWandTaskStatus(value: unknown): value is WandTaskStatus;
|
|
4
|
-
/**
|
|
8
|
+
/** A new execution returns work to doing; explicit review dispatches preserve review separately. */
|
|
5
9
|
export declare function statusAfterWandTaskDispatch(status: WandTaskStatus): WandTaskStatus;
|
|
6
|
-
/**
|
|
10
|
+
/** 仅归档关闭活动阶段;待 AI 验收与待人工确认仍属于未完成工作。 */
|
|
7
11
|
export declare function isClosedWandTaskStatus(status: WandTaskStatus): boolean;
|
|
8
12
|
export type WandTaskPriority = "none" | "low" | "medium" | "high" | "urgent";
|
|
9
13
|
/** 新建任务的默认优先级:用户没挑就是「低」,不再落成「无优先级」。 */
|
|
@@ -159,6 +163,9 @@ export interface TaskCommentDispatch {
|
|
|
159
163
|
error: string | null;
|
|
160
164
|
}
|
|
161
165
|
export interface WandTaskComment {
|
|
166
|
+
purpose?: "execution" | "review";
|
|
167
|
+
reviewRevision?: number | null;
|
|
168
|
+
reviewWritebackPending?: boolean;
|
|
162
169
|
requestId?: string;
|
|
163
170
|
sourceCommentId?: string | null;
|
|
164
171
|
taskVersion?: string;
|
|
@@ -178,10 +185,34 @@ export interface TaskAcceptance {
|
|
|
178
185
|
evidence: string;
|
|
179
186
|
blocker: string;
|
|
180
187
|
acceptedAt: string | null;
|
|
188
|
+
confirmedRevision?: number;
|
|
189
|
+
}
|
|
190
|
+
export interface TaskVerification {
|
|
191
|
+
verdict: "passed" | "failed" | "unverified";
|
|
192
|
+
evidence: string;
|
|
193
|
+
blocker: string;
|
|
194
|
+
reviewedAt: string;
|
|
195
|
+
revision: number;
|
|
196
|
+
sourceSessionId: string | null;
|
|
197
|
+
reviewerId: string | null;
|
|
198
|
+
}
|
|
199
|
+
export type TaskWorkflowAction = "submit" | "pass" | "fail" | "return" | "accept";
|
|
200
|
+
export interface TaskWorkflowInput {
|
|
201
|
+
action: TaskWorkflowAction;
|
|
202
|
+
requestId: string;
|
|
203
|
+
expectedUpdatedAt: string;
|
|
204
|
+
evidence: string;
|
|
205
|
+
blocker: string;
|
|
181
206
|
}
|
|
182
207
|
export interface WandTask {
|
|
183
208
|
/** Explicit user acceptance, independent of sessions and archival. */
|
|
184
209
|
acceptance?: TaskAcceptance | null;
|
|
210
|
+
verification?: TaskVerification | null;
|
|
211
|
+
deliveryRevision?: number;
|
|
212
|
+
/** DTO-only compatibility flag; a historical done is projected to review without a write. */
|
|
213
|
+
legacyReview?: boolean;
|
|
214
|
+
/** Server advertises atomic two-level workflow support; absent on older servers. */
|
|
215
|
+
workflowVersion?: 1;
|
|
185
216
|
id: string;
|
|
186
217
|
workspaceId: string | null;
|
|
187
218
|
/** 关联的侧栏工作任务;归档/新建时用来对账,不随 WorkspaceTask 删除而消失。 */
|