vellum 0.2.0 → 0.2.2
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 +15 -2
- package/bun.lock +5 -2
- package/package.json +4 -2
- package/scripts/capture-x-graphql.ts +562 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
- package/scripts/test.sh +5 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +161 -34
- package/src/__tests__/account-registry.test.ts +2 -1
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/app-bundler.test.ts +12 -33
- package/src/__tests__/asset-materialize-tool.test.ts +16 -15
- package/src/__tests__/asset-search-tool.test.ts +23 -22
- package/src/__tests__/attachments-store.test.ts +56 -127
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
- package/src/__tests__/browser-skill-endstate.test.ts +5 -8
- package/src/__tests__/call-bridge.test.ts +385 -0
- package/src/__tests__/call-constants.test.ts +40 -0
- package/src/__tests__/call-orchestrator.test.ts +454 -0
- package/src/__tests__/call-recovery.test.ts +518 -0
- package/src/__tests__/call-routes-http.test.ts +459 -0
- package/src/__tests__/call-state-machine.test.ts +143 -0
- package/src/__tests__/call-state.test.ts +133 -0
- package/src/__tests__/call-store.test.ts +691 -0
- package/src/__tests__/cli-discover.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
- package/src/__tests__/compaction.benchmark.test.ts +176 -0
- package/src/__tests__/computer-use-tools.test.ts +250 -0
- package/src/__tests__/config-schema.test.ts +348 -3
- package/src/__tests__/conflict-store.test.ts +2 -1
- package/src/__tests__/contacts-tools.test.ts +331 -0
- package/src/__tests__/conversation-store.test.ts +30 -32
- package/src/__tests__/credential-security-invariants.test.ts +4 -0
- package/src/__tests__/date-context.test.ts +373 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
- package/src/__tests__/doordash-session.test.ts +9 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
- package/src/__tests__/followup-tools.test.ts +303 -0
- package/src/__tests__/handlers-twitter-config.test.ts +718 -0
- package/src/__tests__/intent-routing.test.ts +64 -57
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
- package/src/__tests__/ipc-snapshot.test.ts +96 -28
- package/src/__tests__/llm-usage-store.test.ts +3 -8
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
- package/src/__tests__/playbook-tools.test.ts +342 -0
- package/src/__tests__/profile-compiler.test.ts +2 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
- package/src/__tests__/recurrence-engine.test.ts +69 -0
- package/src/__tests__/recurrence-types.test.ts +71 -0
- package/src/__tests__/registry.test.ts +17 -10
- package/src/__tests__/relay-server.test.ts +633 -0
- package/src/__tests__/reminder-store.test.ts +6 -3
- package/src/__tests__/reminder.test.ts +43 -77
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +222 -0
- package/src/__tests__/run-orchestrator.test.ts +7 -7
- package/src/__tests__/runtime-attachment-metadata.test.ts +19 -20
- package/src/__tests__/runtime-runs-http.test.ts +5 -23
- package/src/__tests__/runtime-runs.test.ts +11 -11
- package/src/__tests__/schedule-store.test.ts +482 -0
- package/src/__tests__/schedule-tools.test.ts +700 -0
- package/src/__tests__/scheduler-recurrence.test.ts +329 -0
- package/src/__tests__/server-history-render.test.ts +14 -13
- package/src/__tests__/session-error.test.ts +28 -0
- package/src/__tests__/session-init.benchmark.test.ts +462 -0
- package/src/__tests__/session-queue.test.ts +89 -16
- package/src/__tests__/session-runtime-assembly.test.ts +161 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
- package/src/__tests__/signup-e2e.test.ts +2 -1
- package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
- package/src/__tests__/skill-script-runner.test.ts +159 -0
- package/src/__tests__/speaker-identification.test.ts +52 -0
- package/src/__tests__/subagent-manager-notify.test.ts +42 -10
- package/src/__tests__/subagent-tools.test.ts +141 -41
- package/src/__tests__/task-compiler.test.ts +2 -1
- package/src/__tests__/task-runner.test.ts +2 -1
- package/src/__tests__/task-scheduler.test.ts +2 -1
- package/src/__tests__/task-tools.test.ts +49 -56
- package/src/__tests__/tool-audit-listener.test.ts +1 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
- package/src/__tests__/tool-executor.test.ts +13 -17
- package/src/__tests__/turn-commit.test.ts +273 -2
- package/src/__tests__/twilio-provider.test.ts +143 -0
- package/src/__tests__/twilio-routes.test.ts +789 -0
- package/src/__tests__/twitter-auth-handler.test.ts +581 -0
- package/src/__tests__/view-image-tool.test.ts +217 -0
- package/src/__tests__/workspace-git-service.test.ts +403 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +141 -2
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/bundler/app-bundler.ts +35 -14
- package/src/calls/call-bridge.ts +95 -0
- package/src/calls/call-constants.ts +48 -0
- package/src/calls/call-domain.ts +276 -0
- package/src/calls/call-orchestrator.ts +390 -0
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-state.ts +64 -0
- package/src/calls/call-store.ts +416 -0
- package/src/calls/relay-server.ts +335 -0
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-config.ts +34 -0
- package/src/calls/twilio-provider.ts +173 -0
- package/src/calls/twilio-routes.ts +250 -0
- package/src/calls/types.ts +37 -0
- package/src/calls/voice-provider.ts +14 -0
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/core-commands.ts +776 -0
- package/src/cli/doordash.ts +256 -25
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/map.ts +246 -0
- package/src/cli/twitter.ts +575 -0
- package/src/cli.ts +7 -5
- package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
- package/src/commands/cc-command-registry.ts +209 -0
- package/src/config/bundled-skills/contacts/SKILL.md +39 -0
- package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
- package/src/config/bundled-skills/document/SKILL.md +18 -0
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
- package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
- package/src/config/bundled-skills/doordash/SKILL.md +163 -0
- package/src/config/bundled-skills/followups/SKILL.md +32 -0
- package/src/config/bundled-skills/followups/TOOLS.json +100 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -24
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
- package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
- package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
- package/src/config/bundled-skills/reminder/SKILL.md +20 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
- package/src/config/bundled-skills/schedule/SKILL.md +74 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
- package/src/config/bundled-skills/subagent/SKILL.md +25 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
- package/src/config/bundled-skills/tasks/SKILL.md +28 -0
- package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
- package/src/config/bundled-skills/twitter/SKILL.md +134 -0
- package/src/config/bundled-skills/watcher/SKILL.md +27 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
- package/src/config/defaults.ts +44 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +218 -1
- package/src/config/system-prompt.ts +100 -6
- package/src/config/templates/IDENTITY.md +7 -0
- package/src/config/types.ts +5 -0
- package/src/contacts/contact-store.ts +4 -4
- package/src/daemon/assistant-attachments.ts +10 -0
- package/src/daemon/classifier.ts +3 -1
- package/src/daemon/computer-use-session.ts +3 -1
- package/src/daemon/date-context.ts +136 -0
- package/src/daemon/handlers/apps.ts +16 -1
- package/src/daemon/handlers/browser.ts +54 -0
- package/src/daemon/handlers/computer-use.ts +7 -1
- package/src/daemon/handlers/config.ts +192 -4
- package/src/daemon/handlers/diagnostics.ts +5 -1
- package/src/daemon/handlers/documents.ts +18 -29
- package/src/daemon/handlers/home-base.ts +5 -1
- package/src/daemon/handlers/index.ts +40 -271
- package/src/daemon/handlers/misc.ts +9 -1
- package/src/daemon/handlers/publish.ts +6 -1
- package/src/daemon/handlers/sessions.ts +65 -12
- package/src/daemon/handlers/shared.ts +36 -1
- package/src/daemon/handlers/signing.ts +37 -0
- package/src/daemon/handlers/skills.ts +20 -6
- package/src/daemon/handlers/subagents.ts +8 -3
- package/src/daemon/handlers/twitter-auth.ts +169 -0
- package/src/daemon/handlers/work-items.ts +495 -39
- package/src/daemon/ipc-contract-inventory.json +40 -4
- package/src/daemon/ipc-contract.ts +185 -37
- package/src/daemon/ipc-protocol.ts +7 -2
- package/src/daemon/lifecycle.ts +48 -5
- package/src/daemon/main.ts +10 -4
- package/src/daemon/ride-shotgun-handler.ts +74 -10
- package/src/daemon/server.ts +144 -29
- package/src/daemon/session-agent-loop.ts +887 -0
- package/src/daemon/session-attachments.ts +28 -5
- package/src/daemon/session-error.ts +24 -3
- package/src/daemon/session-lifecycle.ts +147 -0
- package/src/daemon/session-media-retry.ts +147 -0
- package/src/daemon/session-messaging.ts +145 -0
- package/src/daemon/session-notifiers.ts +164 -0
- package/src/daemon/session-process.ts +2 -2
- package/src/daemon/session-queue-manager.ts +1 -0
- package/src/daemon/session-runtime-assembly.ts +52 -0
- package/src/daemon/session-skill-tools.ts +124 -5
- package/src/daemon/session-slash.ts +3 -0
- package/src/daemon/session-surfaces.ts +77 -2
- package/src/daemon/session-tool-setup.ts +222 -2
- package/src/daemon/session-usage.ts +0 -2
- package/src/daemon/session.ts +114 -1365
- package/src/daemon/video-thumbnail.ts +60 -0
- package/src/doordash/client.ts +121 -27
- package/src/doordash/queries.ts +1 -2
- package/src/export/formatter.ts +3 -1
- package/src/followups/followup-store.ts +4 -2
- package/src/followups/types.ts +6 -0
- package/src/hooks/templates.ts +1 -1
- package/src/index.ts +32 -1151
- package/src/media/gemini-image-service.ts +1 -1
- package/src/memory/attachments-store.ts +28 -83
- package/src/memory/channel-delivery-store.ts +7 -21
- package/src/memory/clarification-resolver.ts +6 -5
- package/src/memory/contradiction-checker.ts +3 -2
- package/src/memory/conversation-key-store.ts +10 -29
- package/src/memory/conversation-store.ts +2 -1
- package/src/memory/db.ts +362 -2
- package/src/memory/entity-extractor.ts +6 -3
- package/src/memory/items-extractor.ts +5 -4
- package/src/memory/jobs-store.ts +3 -2
- package/src/memory/llm-usage-store.ts +1 -2
- package/src/memory/runs-store.ts +1 -2
- package/src/memory/schema.ts +65 -2
- package/src/messaging/style-analyzer.ts +3 -2
- package/src/messaging/thread-summarizer.ts +8 -12
- package/src/messaging/triage-engine.ts +4 -2
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/registry.ts +8 -0
- package/src/runtime/http-server.ts +277 -25
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/routes/attachment-routes.ts +5 -6
- package/src/runtime/routes/call-routes.ts +140 -0
- package/src/runtime/routes/channel-routes.ts +12 -19
- package/src/runtime/routes/conversation-routes.ts +5 -9
- package/src/runtime/routes/run-routes.ts +4 -8
- package/src/runtime/run-orchestrator.ts +39 -6
- package/src/schedule/recurrence-engine.ts +138 -0
- package/src/schedule/recurrence-types.ts +67 -0
- package/src/schedule/schedule-store.ts +102 -57
- package/src/schedule/scheduler.ts +9 -6
- package/src/security/oauth2.ts +29 -4
- package/src/security/secret-allowlist.ts +46 -0
- package/src/skills/clawhub.ts +1 -1
- package/src/subagent/manager.ts +40 -8
- package/src/swarm/backend-claude-code.ts +64 -9
- package/src/swarm/worker-prompts.ts +2 -1
- package/src/tasks/SPEC.md +34 -28
- package/src/tasks/ephemeral-permissions.ts +16 -7
- package/src/tasks/task-compiler.ts +5 -4
- package/src/tasks/task-runner.ts +10 -5
- package/src/tasks/task-scheduler.ts +1 -1
- package/src/tasks/tool-sanitizer.ts +36 -0
- package/src/tools/assets/search.ts +4 -4
- package/src/tools/browser/api-map.ts +220 -0
- package/src/tools/browser/auto-navigate.ts +270 -0
- package/src/tools/browser/browser-execution.ts +2 -1
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/browser/network-recorder.ts +5 -4
- package/src/tools/browser/x-auto-navigate.ts +207 -0
- package/src/tools/calls/call-end.ts +67 -0
- package/src/tools/calls/call-start.ts +73 -0
- package/src/tools/calls/call-status.ts +81 -0
- package/src/tools/claude-code/claude-code.ts +77 -11
- package/src/tools/contacts/contact-merge.ts +46 -78
- package/src/tools/contacts/contact-search.ts +35 -79
- package/src/tools/contacts/contact-upsert.ts +35 -108
- package/src/tools/credentials/vault.ts +21 -5
- package/src/tools/document/document-tool.ts +71 -144
- package/src/tools/executor.ts +129 -10
- package/src/tools/followups/followup_create.ts +46 -88
- package/src/tools/followups/followup_list.ts +34 -74
- package/src/tools/followups/followup_resolve.ts +31 -66
- package/src/tools/host-terminal/cli-discover.ts +2 -1
- package/src/tools/host-terminal/host-shell.ts +10 -0
- package/src/tools/memory/handlers.ts +5 -4
- package/src/tools/network/__tests__/web-search.test.ts +427 -0
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
- package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
- package/src/tools/network/web-fetch.ts +18 -6
- package/src/tools/playbooks/index.ts +4 -5
- package/src/tools/playbooks/playbook-create.ts +3 -47
- package/src/tools/playbooks/playbook-delete.ts +1 -25
- package/src/tools/playbooks/playbook-list.ts +1 -28
- package/src/tools/playbooks/playbook-update.ts +3 -51
- package/src/tools/registry.ts +2 -4
- package/src/tools/reminder/reminder.ts +5 -78
- package/src/tools/schedule/create.ts +69 -74
- package/src/tools/schedule/delete.ts +21 -47
- package/src/tools/schedule/list.ts +55 -74
- package/src/tools/schedule/update.ts +77 -84
- package/src/tools/subagent/abort.ts +29 -58
- package/src/tools/subagent/message.ts +30 -63
- package/src/tools/subagent/read.ts +53 -84
- package/src/tools/subagent/spawn.ts +43 -82
- package/src/tools/subagent/status.ts +42 -71
- package/src/tools/swarm/delegate.ts +2 -1
- package/src/tools/tasks/index.ts +8 -6
- package/src/tools/tasks/task-delete.ts +69 -56
- package/src/tools/tasks/task-list.ts +31 -52
- package/src/tools/tasks/task-run.ts +74 -102
- package/src/tools/tasks/task-save.ts +33 -65
- package/src/tools/tasks/work-item-enqueue.ts +192 -134
- package/src/tools/tasks/work-item-list.ts +33 -78
- package/src/tools/tasks/work-item-remove.ts +60 -0
- package/src/tools/tasks/work-item-update.ts +114 -0
- package/src/tools/terminal/backends/native.ts +3 -1
- package/src/tools/tool-manifest.ts +20 -74
- package/src/tools/types.ts +6 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tools/watch/screen-watch.ts +3 -1
- package/src/tools/watcher/create.ts +52 -98
- package/src/tools/watcher/delete.ts +20 -46
- package/src/tools/watcher/digest.ts +36 -70
- package/src/tools/watcher/list.ts +49 -79
- package/src/tools/watcher/update.ts +45 -91
- package/src/twitter/client.ts +690 -0
- package/src/twitter/session.ts +91 -0
- package/src/usage/types.ts +0 -1
- package/src/util/truncate.ts +6 -0
- package/src/watcher/providers/slack.ts +2 -1
- package/src/watcher/watcher-store.ts +3 -2
- package/src/work-items/work-item-store.ts +236 -2
- package/src/workspace/commit-message-enrichment-service.ts +284 -0
- package/src/workspace/commit-message-provider.ts +95 -0
- package/src/workspace/git-service.ts +272 -52
- package/src/workspace/heartbeat-service.ts +70 -13
- package/src/workspace/provider-commit-message-generator.ts +242 -0
- package/src/workspace/turn-commit.ts +100 -51
- package/src/tools/contacts/index.ts +0 -4
- package/src/tools/document/index.ts +0 -5
- package/src/tools/followups/index.ts +0 -3
- package/src/tools/subagent/index.ts +0 -5
- /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
package/README.md
CHANGED
|
@@ -104,7 +104,7 @@ assistant/
|
|
|
104
104
|
│ ├── home-base/ # Home Base app-link bootstrap
|
|
105
105
|
│ ├── hooks/ # Git-style lifecycle hooks
|
|
106
106
|
│ ├── media/ # Media processing and attachments
|
|
107
|
-
│ ├── schedule/ # Reminders and scheduling
|
|
107
|
+
│ ├── schedule/ # Reminders and recurrence scheduling (cron + RRULE)
|
|
108
108
|
│ ├── tasks/ # Task management
|
|
109
109
|
│ ├── workspace/ # Workspace file operations
|
|
110
110
|
│ ├── events/ # Domain event bus
|
|
@@ -122,7 +122,9 @@ assistant/
|
|
|
122
122
|
|
|
123
123
|
## Database
|
|
124
124
|
|
|
125
|
-
SQLite via Drizzle ORM, stored at `~/.vellum/workspace/data/db/assistant.db`. Key tables include conversations, messages, tool invocations, attachments, memory segments (with FTS5), memory items, entities, and
|
|
125
|
+
SQLite via Drizzle ORM, stored at `~/.vellum/workspace/data/db/assistant.db`. Key tables include conversations, messages, tool invocations, attachments, memory segments (with FTS5), memory items, entities, reminders, and recurrence schedules (cron + RRULE).
|
|
126
|
+
|
|
127
|
+
> **Compatibility note:** The recurrence schedule system supports both cron expressions and iCalendar RRULE syntax. The legacy field names `cron_expression` and `cronExpression` remain supported in API inputs. New code should use the `expression` field with an explicit `syntax` discriminator. See [`ARCHITECTURE.md`](../ARCHITECTURE.md) for details.
|
|
126
128
|
|
|
127
129
|
Run migrations:
|
|
128
130
|
|
|
@@ -145,6 +147,17 @@ docker run --rm -p 3001:3001 \
|
|
|
145
147
|
|
|
146
148
|
The image runs as non-root user `assistant` (uid 1001) and exposes port `3001`.
|
|
147
149
|
|
|
150
|
+
## Troubleshooting
|
|
151
|
+
|
|
152
|
+
### Invalid RRULE set expressions
|
|
153
|
+
|
|
154
|
+
If `schedule_create` rejects an RRULE expression, check the following:
|
|
155
|
+
|
|
156
|
+
- **Missing DTSTART** — Every RRULE expression must include a `DTSTART` line (e.g., `DTSTART:20250101T090000Z`).
|
|
157
|
+
- **No inclusion rule** — At least one `RRULE:` or `RDATE` line is required. An expression with only `EXDATE` or `EXRULE` lines and no inclusion has no occurrences to schedule.
|
|
158
|
+
- **Unsupported lines** — Only `DTSTART`, `RRULE:`, `RDATE`, `EXDATE`, and `EXRULE` prefixes are recognized. Any other line (e.g., `VTIMEZONE`, `VEVENT`) will be rejected.
|
|
159
|
+
- **Newline encoding** — When passing multi-line RRULE expressions through JSON, use literal `\n` between lines. The engine normalizes escaped newlines automatically.
|
|
160
|
+
|
|
148
161
|
## Development
|
|
149
162
|
|
|
150
163
|
```bash
|
package/bun.lock
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"@huggingface/transformers": "^3.8.1",
|
|
12
12
|
"@qdrant/js-client-rest": "^1.16.2",
|
|
13
13
|
"@sentry/node": "^10.38.0",
|
|
14
|
-
"@vellumai/cli": "0.1.
|
|
14
|
+
"@vellumai/cli": "0.1.5",
|
|
15
15
|
"@vellumai/vellum-gateway": "^0.1.7",
|
|
16
16
|
"agentmail": "^0.1.0",
|
|
17
17
|
"archiver": "^7.0.1",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"playwright": "^1.58.2",
|
|
29
29
|
"postgres": "^3.4.8",
|
|
30
30
|
"react": "^19.2.4",
|
|
31
|
+
"rrule": "^2.8.1",
|
|
31
32
|
"tldts": "^7.0.23",
|
|
32
33
|
"tree-sitter-bash": "0.25.1",
|
|
33
34
|
"uuid": "^11.1.0",
|
|
@@ -539,7 +540,7 @@
|
|
|
539
540
|
|
|
540
541
|
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.55.0", "", { "dependencies": { "@typescript-eslint/types": "8.55.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-AxNRwEie8Nn4eFS1FzDMJWIISMGoXMb037sgCBJ3UR6o0fQTzr2tqN9WT+DkWJPhIdQCfV7T6D387566VtnCJA=="],
|
|
541
542
|
|
|
542
|
-
"@vellumai/cli": ["@vellumai/cli@0.1.
|
|
543
|
+
"@vellumai/cli": ["@vellumai/cli@0.1.5", "", { "dependencies": { "ink": "^6.7.0", "react": "^19.2.4" }, "bin": { "vellum-cli": "src/index.ts" } }, "sha512-DTFm7wHjzaa5fhpGD/ZoZwz8NXFoaCla61YNdncmsnHgpWgxesjFl7mhOqhkWt8IVQkr5SAMouiGvm0+kSjSUg=="],
|
|
543
544
|
|
|
544
545
|
"@vellumai/vellum-gateway": ["@vellumai/vellum-gateway@0.1.7", "", { "dependencies": { "file-type": "^21.3.0", "pino": "^9.6.0", "pino-pretty": "^13.1.3", "zod": "^4.3.6" } }, "sha512-JJhCqhsnZ99+dazCUWtOac24p92WyCr3/bxYkfmTgk6yYz9zqH7O1DpjxTLOtjAohQnki4WXNFUCzu48LCtFsg=="],
|
|
545
546
|
|
|
@@ -1105,6 +1106,8 @@
|
|
|
1105
1106
|
|
|
1106
1107
|
"roarr": ["roarr@2.15.4", "", { "dependencies": { "boolean": "^3.0.1", "detect-node": "^2.0.4", "globalthis": "^1.0.1", "json-stringify-safe": "^5.0.1", "semver-compare": "^1.0.0", "sprintf-js": "^1.1.2" } }, "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A=="],
|
|
1107
1108
|
|
|
1109
|
+
"rrule": ["rrule@2.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-hM3dHSBMeaJ0Ktp7W38BJZ7O1zOgaFEsn41PDk+yHoEtfLV+PoJt9E9xAlZiWgf/iqEqionN0ebHFZIDAp+iGw=="],
|
|
1110
|
+
|
|
1108
1111
|
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
|
|
1109
1112
|
|
|
1110
1113
|
"safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vellum",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"vellum": "./src/index.ts"
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"typecheck": "bunx tsc --noEmit",
|
|
19
19
|
"test": "bash scripts/test.sh",
|
|
20
20
|
"test:stable": "EXCLUDE_EXPERIMENTAL=true bash scripts/test.sh",
|
|
21
|
+
"test:bench": "find src/__tests__ -maxdepth 1 -type f -name '*.benchmark.test.ts' -print0 | xargs -0 -P 1 -I {} bun test {}",
|
|
21
22
|
"test:filesystem-tools": "bash scripts/test-filesystem-tools.sh"
|
|
22
23
|
},
|
|
23
24
|
"dependencies": {
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
"@huggingface/transformers": "^3.8.1",
|
|
28
29
|
"@qdrant/js-client-rest": "^1.16.2",
|
|
29
30
|
"@sentry/node": "^10.38.0",
|
|
30
|
-
"@vellumai/cli": "0.1.
|
|
31
|
+
"@vellumai/cli": "0.1.5",
|
|
31
32
|
"@vellumai/vellum-gateway": "^0.1.7",
|
|
32
33
|
"agentmail": "^0.1.0",
|
|
33
34
|
"archiver": "^7.0.1",
|
|
@@ -44,6 +45,7 @@
|
|
|
44
45
|
"playwright": "^1.58.2",
|
|
45
46
|
"postgres": "^3.4.8",
|
|
46
47
|
"react": "^19.2.4",
|
|
48
|
+
"rrule": "^2.8.1",
|
|
47
49
|
"tldts": "^7.0.23",
|
|
48
50
|
"tree-sitter-bash": "0.25.1",
|
|
49
51
|
"uuid": "^11.1.0",
|
|
@@ -0,0 +1,562 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Capture X.com GraphQL API calls via Chrome CDP.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* 1. Make sure Chrome is running with CDP (vellum x refresh will do this)
|
|
7
|
+
* 2. Run: bun run scripts/capture-x-graphql.ts [--auto] [--all]
|
|
8
|
+
* 3. In --auto mode, Chrome is navigated automatically. Otherwise browse X manually.
|
|
9
|
+
* 4. Press Ctrl+C to stop (or wait for --auto to finish).
|
|
10
|
+
*
|
|
11
|
+
* Flags:
|
|
12
|
+
* --auto Automatically navigate Chrome through X.com pages via CDP
|
|
13
|
+
* --all Capture ALL GraphQL queries (skip relevance filter)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { mkdirSync, existsSync } from 'node:fs';
|
|
17
|
+
|
|
18
|
+
const CDP_BASE = 'http://localhost:9222';
|
|
19
|
+
const CAPTURE_DIR = '/tmp/x-graphql-capture';
|
|
20
|
+
|
|
21
|
+
// ─── Relevance filter ────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
const RELEVANT_QUERIES = new Set([
|
|
24
|
+
// Reads
|
|
25
|
+
'UserByScreenName', 'UserTweets', 'TweetDetail', 'TweetResultByRestId',
|
|
26
|
+
'SearchTimeline', 'Bookmarks', 'Likes', 'Favoriters',
|
|
27
|
+
'Followers', 'Following', 'HomeTimeline', 'HomeLatestTimeline',
|
|
28
|
+
'NotificationsTimeline', 'ListTimeline',
|
|
29
|
+
'UserMedia',
|
|
30
|
+
// Writes
|
|
31
|
+
'CreateTweet', 'DeleteTweet', 'FavoriteTweet',
|
|
32
|
+
'UnfavoriteTweet', 'CreateRetweet', 'DeleteRetweet', 'CreateBookmark',
|
|
33
|
+
'DeleteBookmark',
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
interface CapturedQuery {
|
|
39
|
+
queryName: string;
|
|
40
|
+
queryId: string;
|
|
41
|
+
method: string;
|
|
42
|
+
variables: unknown;
|
|
43
|
+
features?: unknown;
|
|
44
|
+
response?: unknown;
|
|
45
|
+
timestamp: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Minimal CDP WebSocket client ────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
class CDPClient {
|
|
51
|
+
private ws: WebSocket | null = null;
|
|
52
|
+
private nextId = 1;
|
|
53
|
+
private callbacks = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
|
|
54
|
+
private eventHandlers = new Map<string, Array<(params: Record<string, unknown>) => void>>();
|
|
55
|
+
|
|
56
|
+
async connect(wsUrl: string): Promise<void> {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const ws = new WebSocket(wsUrl);
|
|
59
|
+
ws.onopen = () => { this.ws = ws; resolve(); };
|
|
60
|
+
ws.onerror = (e) => reject(new Error(`CDP error: ${e}`));
|
|
61
|
+
ws.onclose = () => { this.ws = null; };
|
|
62
|
+
ws.onmessage = (event) => {
|
|
63
|
+
const msg = JSON.parse(String(event.data));
|
|
64
|
+
if (msg.id != null) {
|
|
65
|
+
const cb = this.callbacks.get(msg.id);
|
|
66
|
+
if (cb) { this.callbacks.delete(msg.id); msg.error ? cb.reject(new Error(msg.error.message)) : cb.resolve(msg.result); }
|
|
67
|
+
} else if (msg.method) {
|
|
68
|
+
for (const h of this.eventHandlers.get(msg.method) ?? []) h(msg.params ?? {});
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async send(method: string, params?: Record<string, unknown>): Promise<unknown> {
|
|
75
|
+
const id = this.nextId++;
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
this.callbacks.set(id, { resolve, reject });
|
|
78
|
+
this.ws!.send(JSON.stringify({ id, method, params }));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
on(event: string, handler: (params: Record<string, unknown>) => void) {
|
|
83
|
+
const list = this.eventHandlers.get(event) ?? [];
|
|
84
|
+
list.push(handler);
|
|
85
|
+
this.eventHandlers.set(event, list);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
close() { this.ws?.close(); }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ─── State ───────────────────────────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
const args = process.argv.slice(2);
|
|
94
|
+
const autoMode = args.includes('--auto');
|
|
95
|
+
const captureAll = args.includes('--all');
|
|
96
|
+
|
|
97
|
+
const captured: CapturedQuery[] = [];
|
|
98
|
+
const seenQueries = new Set<string>();
|
|
99
|
+
|
|
100
|
+
// Ensure capture directory exists
|
|
101
|
+
if (!existsSync(CAPTURE_DIR)) mkdirSync(CAPTURE_DIR, { recursive: true });
|
|
102
|
+
|
|
103
|
+
// ─── Auto-navigation steps ───────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
interface GuideStep {
|
|
106
|
+
label: string;
|
|
107
|
+
url?: string;
|
|
108
|
+
clickSelector?: string;
|
|
109
|
+
expectedQueries: string[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Resolve the logged-in user's screen name for profile-based URLs
|
|
113
|
+
async function getScreenName(): Promise<string | null> {
|
|
114
|
+
if (!navigationClient) return null;
|
|
115
|
+
try {
|
|
116
|
+
const result = await navigationClient.send('Runtime.evaluate', {
|
|
117
|
+
expression: `
|
|
118
|
+
(function() {
|
|
119
|
+
const link = document.querySelector('a[data-testid="AppTabBar_Profile_Link"]');
|
|
120
|
+
if (link) return link.getAttribute('href')?.replace('/', '') ?? null;
|
|
121
|
+
return null;
|
|
122
|
+
})()
|
|
123
|
+
`,
|
|
124
|
+
awaitPromise: false,
|
|
125
|
+
returnByValue: true,
|
|
126
|
+
}) as { result?: { value?: string | null } };
|
|
127
|
+
return result?.result?.value ?? null;
|
|
128
|
+
} catch {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const GUIDE_STEPS: GuideStep[] = [
|
|
134
|
+
{
|
|
135
|
+
label: 'Home timeline',
|
|
136
|
+
url: 'https://x.com/home',
|
|
137
|
+
expectedQueries: ['HomeTimeline', 'HomeLatestTimeline'],
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
label: 'Profile',
|
|
141
|
+
// URL set dynamically in runAutoMode after resolving screen name
|
|
142
|
+
clickSelector: 'a[data-testid="AppTabBar_Profile_Link"]',
|
|
143
|
+
expectedQueries: ['UserByScreenName', 'UserTweets'],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
label: 'Tweet detail',
|
|
147
|
+
clickSelector: 'article[data-testid="tweet"] a[href*="/status/"]',
|
|
148
|
+
expectedQueries: ['TweetDetail'],
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
label: 'Search',
|
|
152
|
+
url: 'https://x.com/search?q=hello&src=typed_query',
|
|
153
|
+
expectedQueries: ['SearchTimeline'],
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
label: 'Bookmarks',
|
|
157
|
+
url: 'https://x.com/i/bookmarks',
|
|
158
|
+
expectedQueries: ['Bookmarks'],
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
label: 'Notifications',
|
|
162
|
+
url: 'https://x.com/notifications',
|
|
163
|
+
expectedQueries: ['NotificationsTimeline'],
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
label: 'Likes',
|
|
167
|
+
// URL set dynamically
|
|
168
|
+
expectedQueries: ['Likes'],
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
label: 'Followers',
|
|
172
|
+
// URL set dynamically
|
|
173
|
+
expectedQueries: ['Followers'],
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
label: 'Following',
|
|
177
|
+
// URL set dynamically
|
|
178
|
+
expectedQueries: ['Following'],
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
label: 'Media',
|
|
182
|
+
// URL set dynamically
|
|
183
|
+
expectedQueries: ['UserMedia'],
|
|
184
|
+
},
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
// ─── Discover Chrome tabs ────────────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
const res = await fetch(`${CDP_BASE}/json/list`);
|
|
190
|
+
if (!res.ok) {
|
|
191
|
+
console.error('Chrome CDP not available. Run `vellum x refresh` first.');
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
const targets = (await res.json()) as Array<{ type: string; url: string; webSocketDebuggerUrl: string }>;
|
|
195
|
+
const pages = targets.filter(t => t.type === 'page');
|
|
196
|
+
|
|
197
|
+
if (pages.length === 0) {
|
|
198
|
+
console.error('No pages found in Chrome.');
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
console.log(`Found ${pages.length} tab(s). Attaching to all...`);
|
|
203
|
+
|
|
204
|
+
// ─── Pending request tracking ────────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
const pendingRequests = new Map<string, { url: string; queryName: string }>();
|
|
207
|
+
// Resolve callbacks for --auto mode: queryName → resolve function
|
|
208
|
+
const queryWaiters = new Map<string, () => void>();
|
|
209
|
+
|
|
210
|
+
function notifyQuerySeen(queryName: string) {
|
|
211
|
+
seenQueries.add(queryName);
|
|
212
|
+
const waiter = queryWaiters.get(queryName);
|
|
213
|
+
if (waiter) {
|
|
214
|
+
queryWaiters.delete(queryName);
|
|
215
|
+
waiter();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function waitForQuery(queryName: string, timeoutMs = 15000): Promise<boolean> {
|
|
220
|
+
if (seenQueries.has(queryName)) return Promise.resolve(true);
|
|
221
|
+
return new Promise(resolve => {
|
|
222
|
+
const timer = setTimeout(() => {
|
|
223
|
+
queryWaiters.delete(queryName);
|
|
224
|
+
resolve(false);
|
|
225
|
+
}, timeoutMs);
|
|
226
|
+
queryWaiters.set(queryName, () => {
|
|
227
|
+
clearTimeout(timer);
|
|
228
|
+
resolve(true);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function waitForAnyQuery(queryNames: string[], timeoutMs = 15000): Promise<boolean> {
|
|
234
|
+
if (queryNames.some(q => seenQueries.has(q))) return Promise.resolve(true);
|
|
235
|
+
return new Promise(resolve => {
|
|
236
|
+
const timer = setTimeout(() => {
|
|
237
|
+
for (const q of queryNames) queryWaiters.delete(q);
|
|
238
|
+
resolve(false);
|
|
239
|
+
}, timeoutMs);
|
|
240
|
+
for (const q of queryNames) {
|
|
241
|
+
queryWaiters.set(q, () => {
|
|
242
|
+
clearTimeout(timer);
|
|
243
|
+
for (const q2 of queryNames) queryWaiters.delete(q2);
|
|
244
|
+
resolve(true);
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// ─── Attach to all tabs ──────────────────────────────────────────────────────
|
|
251
|
+
|
|
252
|
+
// We'll keep a reference to one client that's on an x.com tab for navigation
|
|
253
|
+
let navigationClient: CDPClient | null = null;
|
|
254
|
+
let navigationWsUrl: string | null = null;
|
|
255
|
+
|
|
256
|
+
for (const page of pages) {
|
|
257
|
+
const client = new CDPClient();
|
|
258
|
+
await client.connect(page.webSocketDebuggerUrl);
|
|
259
|
+
await client.send('Network.enable');
|
|
260
|
+
|
|
261
|
+
// Track which client is on an x.com tab for navigation
|
|
262
|
+
if (page.url.includes('x.com') || page.url.includes('twitter.com')) {
|
|
263
|
+
navigationClient = client;
|
|
264
|
+
navigationWsUrl = page.webSocketDebuggerUrl;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
client.on('Network.requestWillBeSent', (params) => {
|
|
268
|
+
const req = params.request as Record<string, unknown> | undefined;
|
|
269
|
+
const url = (req?.url ?? params.url) as string | undefined;
|
|
270
|
+
if (!url?.includes('/i/api/graphql/')) return;
|
|
271
|
+
|
|
272
|
+
const match = url.match(/\/graphql\/([^/]+)\/([^?]+)/);
|
|
273
|
+
const queryId = match?.[1] ?? 'unknown';
|
|
274
|
+
const queryName = match?.[2] ?? 'unknown';
|
|
275
|
+
const method = (req?.method as string) ?? 'GET';
|
|
276
|
+
|
|
277
|
+
// Apply relevance filter
|
|
278
|
+
if (!captureAll && !RELEVANT_QUERIES.has(queryName)) return;
|
|
279
|
+
|
|
280
|
+
let variables: unknown = undefined;
|
|
281
|
+
let features: unknown = undefined;
|
|
282
|
+
|
|
283
|
+
if (method === 'POST' && req?.postData) {
|
|
284
|
+
try {
|
|
285
|
+
const body = JSON.parse(req.postData as string);
|
|
286
|
+
variables = body.variables;
|
|
287
|
+
features = body.features;
|
|
288
|
+
} catch { /* ignore */ }
|
|
289
|
+
} else if (method === 'GET') {
|
|
290
|
+
try {
|
|
291
|
+
const u = new URL(url);
|
|
292
|
+
const v = u.searchParams.get('variables');
|
|
293
|
+
if (v) variables = JSON.parse(v);
|
|
294
|
+
const f = u.searchParams.get('features');
|
|
295
|
+
if (f) features = JSON.parse(f);
|
|
296
|
+
} catch { /* ignore */ }
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
console.log(`\n>>> ${method} ${queryName} (${queryId})`);
|
|
300
|
+
if (variables) console.log(` variables: ${JSON.stringify(variables).slice(0, 200)}`);
|
|
301
|
+
|
|
302
|
+
pendingRequests.set(params.requestId as string, { url, queryName });
|
|
303
|
+
captured.push({ queryName, queryId, method, variables, features, timestamp: Date.now() });
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
client.on('Network.responseReceived', (params) => {
|
|
307
|
+
const requestId = params.requestId as string;
|
|
308
|
+
const pending = pendingRequests.get(requestId);
|
|
309
|
+
if (!pending) return;
|
|
310
|
+
|
|
311
|
+
const response = params.response as Record<string, unknown>;
|
|
312
|
+
const status = response.status as number;
|
|
313
|
+
console.log(` <<< ${status}`);
|
|
314
|
+
|
|
315
|
+
// Get full response body
|
|
316
|
+
client.send('Network.getResponseBody', { requestId }).then((result) => {
|
|
317
|
+
const body = (result as Record<string, unknown>).body as string;
|
|
318
|
+
try {
|
|
319
|
+
const json = JSON.parse(body);
|
|
320
|
+
// Attach full response to the captured entry
|
|
321
|
+
const entry = [...captured].reverse().find(e => e.queryName === pending.queryName && !e.response);
|
|
322
|
+
if (entry) {
|
|
323
|
+
entry.response = json;
|
|
324
|
+
|
|
325
|
+
// Write individual file
|
|
326
|
+
const filename = `${pending.queryName}-${entry.timestamp}.json`;
|
|
327
|
+
Bun.write(`${CAPTURE_DIR}/${filename}`, JSON.stringify({
|
|
328
|
+
queryName: entry.queryName,
|
|
329
|
+
queryId: entry.queryId,
|
|
330
|
+
method: entry.method,
|
|
331
|
+
variables: entry.variables,
|
|
332
|
+
features: entry.features,
|
|
333
|
+
response: json,
|
|
334
|
+
}, null, 2));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Notify waiters
|
|
338
|
+
notifyQuerySeen(pending.queryName);
|
|
339
|
+
} catch { /* ignore */ }
|
|
340
|
+
}).catch(() => { /* body not available */ });
|
|
341
|
+
|
|
342
|
+
pendingRequests.delete(requestId);
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// If no x.com tab found, use the first page for navigation
|
|
347
|
+
if (!navigationClient && pages.length > 0) {
|
|
348
|
+
navigationClient = new CDPClient();
|
|
349
|
+
await navigationClient.connect(pages[0].webSocketDebuggerUrl);
|
|
350
|
+
navigationWsUrl = pages[0].webSocketDebuggerUrl;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ─── CDP navigation helpers ──────────────────────────────────────────────────
|
|
354
|
+
|
|
355
|
+
async function navigateTo(url: string): Promise<void> {
|
|
356
|
+
if (!navigationClient) return;
|
|
357
|
+
await navigationClient.send('Page.navigate', { url });
|
|
358
|
+
// Wait for page to load
|
|
359
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function clickElement(selector: string): Promise<boolean> {
|
|
363
|
+
if (!navigationClient) return false;
|
|
364
|
+
try {
|
|
365
|
+
const result = await navigationClient.send('Runtime.evaluate', {
|
|
366
|
+
expression: `
|
|
367
|
+
(function() {
|
|
368
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
369
|
+
if (!el) return false;
|
|
370
|
+
el.scrollIntoView({ block: 'center' });
|
|
371
|
+
el.click();
|
|
372
|
+
return true;
|
|
373
|
+
})()
|
|
374
|
+
`,
|
|
375
|
+
awaitPromise: false,
|
|
376
|
+
returnByValue: true,
|
|
377
|
+
}) as { result?: { value?: boolean } };
|
|
378
|
+
return result?.result?.value === true;
|
|
379
|
+
} catch {
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
async function scrollDown(): Promise<void> {
|
|
385
|
+
if (!navigationClient) return;
|
|
386
|
+
try {
|
|
387
|
+
await navigationClient.send('Runtime.evaluate', {
|
|
388
|
+
expression: 'window.scrollBy(0, 800)',
|
|
389
|
+
awaitPromise: false,
|
|
390
|
+
});
|
|
391
|
+
} catch { /* ignore */ }
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ─── Auto mode ───────────────────────────────────────────────────────────────
|
|
395
|
+
|
|
396
|
+
async function runAutoMode() {
|
|
397
|
+
console.log('\n🚗 Auto mode: navigating Chrome through X.com...\n');
|
|
398
|
+
|
|
399
|
+
// Enable Page domain for navigation
|
|
400
|
+
if (navigationClient) {
|
|
401
|
+
await navigationClient.send('Page.enable').catch(() => {});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Navigate to home first to discover the screen name
|
|
405
|
+
await navigateTo('https://x.com/home');
|
|
406
|
+
const screenName = await getScreenName();
|
|
407
|
+
if (screenName) {
|
|
408
|
+
console.log(` Detected user: @${screenName}\n`);
|
|
409
|
+
// Fill in profile-based URLs
|
|
410
|
+
for (const step of GUIDE_STEPS) {
|
|
411
|
+
if (step.label === 'Likes') step.url = `https://x.com/${screenName}/likes`;
|
|
412
|
+
if (step.label === 'Followers') step.url = `https://x.com/${screenName}/followers`;
|
|
413
|
+
if (step.label === 'Following') step.url = `https://x.com/${screenName}/following`;
|
|
414
|
+
if (step.label === 'Media') step.url = `https://x.com/${screenName}/media`;
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
console.log(' Could not detect screen name — some steps will use click navigation\n');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const completedSteps: string[] = [];
|
|
421
|
+
const failedSteps: string[] = [];
|
|
422
|
+
|
|
423
|
+
for (const step of GUIDE_STEPS) {
|
|
424
|
+
const alreadySeen = step.expectedQueries.some(q => seenQueries.has(q));
|
|
425
|
+
if (alreadySeen) {
|
|
426
|
+
console.log(` ✓ ${step.label} (already captured)`);
|
|
427
|
+
completedSteps.push(step.label);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
process.stdout.write(` ⏳ ${step.label}...`);
|
|
432
|
+
|
|
433
|
+
// Navigate if URL provided
|
|
434
|
+
if (step.url) {
|
|
435
|
+
await navigateTo(step.url);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Click if selector provided
|
|
439
|
+
if (step.clickSelector) {
|
|
440
|
+
await new Promise(r => setTimeout(r, 1500)); // wait for page to settle
|
|
441
|
+
const clicked = await clickElement(step.clickSelector);
|
|
442
|
+
if (!clicked) {
|
|
443
|
+
// Try scrolling and clicking again
|
|
444
|
+
await scrollDown();
|
|
445
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
446
|
+
await clickElement(step.clickSelector);
|
|
447
|
+
}
|
|
448
|
+
await new Promise(r => setTimeout(r, 2000)); // wait for navigation
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Scroll to trigger lazy-loaded content
|
|
452
|
+
await scrollDown();
|
|
453
|
+
|
|
454
|
+
// Wait for any expected query
|
|
455
|
+
const seen = await waitForAnyQuery(step.expectedQueries, 10000);
|
|
456
|
+
|
|
457
|
+
if (seen) {
|
|
458
|
+
const captured = step.expectedQueries.filter(q => seenQueries.has(q));
|
|
459
|
+
console.log(`\r ✅ ${step.label} → ${captured.join(', ')}`);
|
|
460
|
+
completedSteps.push(step.label);
|
|
461
|
+
} else {
|
|
462
|
+
console.log(`\r ⚠️ ${step.label} → no queries captured (page may need manual interaction)`);
|
|
463
|
+
failedSteps.push(step.label);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
console.log(`\n🏁 Auto navigation complete: ${completedSteps.length}/${GUIDE_STEPS.length} steps succeeded`);
|
|
468
|
+
if (failedSteps.length > 0) {
|
|
469
|
+
console.log(` Missed: ${failedSteps.join(', ')}`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// Finish
|
|
473
|
+
printSummary();
|
|
474
|
+
process.exit(0);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ─── Summary ─────────────────────────────────────────────────────────────────
|
|
478
|
+
|
|
479
|
+
function printSummary() {
|
|
480
|
+
console.log(`\n\n${'='.repeat(60)}`);
|
|
481
|
+
console.log(` Captured ${captured.length} GraphQL requests`);
|
|
482
|
+
console.log(`${'='.repeat(60)}\n`);
|
|
483
|
+
|
|
484
|
+
// Dedupe by queryName
|
|
485
|
+
const seen = new Set<string>();
|
|
486
|
+
const unique = captured.filter(q => {
|
|
487
|
+
if (seen.has(q.queryName)) return false;
|
|
488
|
+
seen.add(q.queryName);
|
|
489
|
+
return true;
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
// Print table
|
|
493
|
+
const nameWidth = Math.max(25, ...unique.map(q => q.queryName.length));
|
|
494
|
+
const idWidth = 22;
|
|
495
|
+
const methodWidth = 6;
|
|
496
|
+
|
|
497
|
+
console.log(
|
|
498
|
+
` ${'Query'.padEnd(nameWidth)} ${'QueryID'.padEnd(idWidth)} ${'Method'.padEnd(methodWidth)} Variables`,
|
|
499
|
+
);
|
|
500
|
+
console.log(` ${'─'.repeat(nameWidth)} ${'─'.repeat(idWidth)} ${'─'.repeat(methodWidth)} ${'─'.repeat(30)}`);
|
|
501
|
+
|
|
502
|
+
for (const q of unique) {
|
|
503
|
+
const varKeys = q.variables && typeof q.variables === 'object'
|
|
504
|
+
? Object.keys(q.variables as Record<string, unknown>).join(', ')
|
|
505
|
+
: '—';
|
|
506
|
+
console.log(
|
|
507
|
+
` ${q.queryName.padEnd(nameWidth)} ${q.queryId.padEnd(idWidth)} ${q.method.padEnd(methodWidth)} ${varKeys}`,
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Gap analysis
|
|
512
|
+
if (!captureAll) {
|
|
513
|
+
const notSeen = [...RELEVANT_QUERIES].filter(q => !seenQueries.has(q));
|
|
514
|
+
if (notSeen.length > 0) {
|
|
515
|
+
console.log(`\n ⚠️ Not captured (${notSeen.length}):`);
|
|
516
|
+
for (const q of notSeen) {
|
|
517
|
+
console.log(` • ${q}`);
|
|
518
|
+
}
|
|
519
|
+
} else {
|
|
520
|
+
console.log('\n ✅ All relevant queries captured!');
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// Save summary
|
|
525
|
+
const summaryPath = `${CAPTURE_DIR}/summary.json`;
|
|
526
|
+
const summary = {
|
|
527
|
+
capturedAt: new Date().toISOString(),
|
|
528
|
+
totalRequests: captured.length,
|
|
529
|
+
uniqueQueries: unique.map(q => ({
|
|
530
|
+
queryName: q.queryName,
|
|
531
|
+
queryId: q.queryId,
|
|
532
|
+
method: q.method,
|
|
533
|
+
variableKeys: q.variables && typeof q.variables === 'object'
|
|
534
|
+
? Object.keys(q.variables as Record<string, unknown>)
|
|
535
|
+
: [],
|
|
536
|
+
})),
|
|
537
|
+
notCaptured: captureAll ? [] : [...RELEVANT_QUERIES].filter(q => !seenQueries.has(q)),
|
|
538
|
+
};
|
|
539
|
+
Bun.write(summaryPath, JSON.stringify(summary, null, 2));
|
|
540
|
+
console.log(`\n Summary saved to ${summaryPath}`);
|
|
541
|
+
console.log(` Individual captures in ${CAPTURE_DIR}/`);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
545
|
+
|
|
546
|
+
if (autoMode) {
|
|
547
|
+
console.log('\nRecording X.com GraphQL requests (auto mode)...');
|
|
548
|
+
console.log(`Filter: ${captureAll ? 'ALL queries' : `${RELEVANT_QUERIES.size} relevant queries`}`);
|
|
549
|
+
// Give network listeners a moment to settle, then start auto-navigation
|
|
550
|
+
setTimeout(() => runAutoMode(), 1000);
|
|
551
|
+
} else {
|
|
552
|
+
console.log('\nRecording X.com GraphQL requests...');
|
|
553
|
+
console.log(`Filter: ${captureAll ? 'ALL queries' : `${RELEVANT_QUERIES.size} relevant queries`}`);
|
|
554
|
+
console.log('Browse X in Chrome — visit a profile, scroll tweets, search.');
|
|
555
|
+
console.log('Press Ctrl+C to stop and dump results.\n');
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Ctrl+C handler
|
|
559
|
+
process.on('SIGINT', () => {
|
|
560
|
+
printSummary();
|
|
561
|
+
process.exit(0);
|
|
562
|
+
});
|
|
@@ -47,10 +47,11 @@ const SWIFT_OMIT_ALLOWLIST = new Set<string>([
|
|
|
47
47
|
// Watcher messages — not yet consumed by the macOS client
|
|
48
48
|
'watcher_escalation',
|
|
49
49
|
'watcher_notification',
|
|
50
|
+
// Agent heartbeat alerts — not yet consumed by the macOS client
|
|
51
|
+
'agent_heartbeat_alert',
|
|
50
52
|
// Browser handoff — not yet consumed by the macOS client
|
|
51
53
|
'browser_handoff_request',
|
|
52
54
|
// Work item messages — not yet consumed by the macOS client
|
|
53
|
-
'work_item_create_response',
|
|
54
55
|
'work_item_get_response',
|
|
55
56
|
'work_item_run_task_response',
|
|
56
57
|
'work_item_status_changed',
|