vellum 0.2.1 → 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 +133 -34
- package/src/__tests__/account-registry.test.ts +2 -1
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- 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 +4 -3
- 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 +130 -4
- 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-store.test.ts +216 -1
- package/src/__tests__/cli-discover.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +148 -7
- 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 +299 -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__/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 +62 -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 +5 -3
- 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 +8 -4
- package/src/__tests__/run-orchestrator.test.ts +4 -4
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -6
- package/src/__tests__/runtime-runs-http.test.ts +4 -4
- package/src/__tests__/runtime-runs.test.ts +4 -4
- 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 +71 -48
- 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 +218 -3
- 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 +186 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +13 -3
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/bundler/app-bundler.ts +12 -8
- package/src/calls/call-bridge.ts +95 -0
- package/src/calls/call-constants.ts +43 -5
- package/src/calls/call-domain.ts +276 -0
- package/src/calls/call-orchestrator.ts +43 -17
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-store.ts +192 -5
- package/src/calls/relay-server.ts +41 -4
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-provider.ts +10 -6
- package/src/calls/twilio-routes.ts +90 -76
- package/src/calls/types.ts +1 -1
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/core-commands.ts +776 -0
- package/src/cli/doordash.ts +251 -1
- 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 +82 -23
- 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/media-generate-image.ts +1 -23
- 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 +33 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +161 -1
- package/src/config/system-prompt.ts +61 -16
- package/src/config/templates/IDENTITY.md +7 -0
- package/src/config/types.ts +4 -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 +163 -5
- 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 -277
- 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 +384 -68
- package/src/daemon/ipc-contract-inventory.json +28 -4
- package/src/daemon/ipc-contract.ts +133 -37
- package/src/daemon/ipc-protocol.ts +7 -2
- package/src/daemon/lifecycle.ts +21 -0
- package/src/daemon/main.ts +10 -4
- package/src/daemon/ride-shotgun-handler.ts +74 -10
- package/src/daemon/server.ts +143 -26
- 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 +216 -2
- package/src/daemon/session-usage.ts +0 -2
- package/src/daemon/session.ts +114 -1404
- 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 -1153
- 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 +96 -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 +23 -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 +108 -20
- package/src/runtime/routes/attachment-routes.ts +2 -3
- package/src/runtime/routes/call-routes.ts +140 -0
- package/src/runtime/routes/channel-routes.ts +5 -10
- package/src/runtime/routes/conversation-routes.ts +5 -5
- package/src/runtime/routes/run-routes.ts +2 -2
- package/src/runtime/run-orchestrator.ts +9 -3
- 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 +17 -67
- package/src/tools/calls/call-start.ts +24 -85
- package/src/tools/calls/call-status.ts +35 -51
- 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 +20 -4
- 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/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 -8
- package/src/tools/tasks/task-delete.ts +60 -88
- package/src/tools/tasks/task-list.ts +31 -52
- package/src/tools/tasks/task-run.ts +72 -108
- package/src/tools/tasks/task-save.ts +33 -65
- package/src/tools/tasks/work-item-enqueue.ts +183 -215
- package/src/tools/tasks/work-item-list.ts +33 -63
- package/src/tools/tasks/work-item-remove.ts +45 -97
- package/src/tools/tasks/work-item-update.ts +91 -163
- package/src/tools/terminal/backends/native.ts +3 -1
- package/src/tools/tool-manifest.ts +0 -62
- package/src/tools/types.ts +6 -0
- package/src/tools/ui-surface/definitions.ts +3 -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 +27 -2
- package/src/workspace/commit-message-enrichment-service.ts +31 -7
- package/src/workspace/git-service.ts +87 -22
- package/src/workspace/provider-commit-message-generator.ts +242 -0
- package/src/workspace/turn-commit.ts +62 -3
- 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
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CDP-based auto-navigation for X.com.
|
|
3
|
+
*
|
|
4
|
+
* Drives Chrome through key X.com pages to trigger GraphQL API calls,
|
|
5
|
+
* so the NetworkRecorder captures the full API surface without manual browsing.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getLogger } from '../../util/logger.js';
|
|
9
|
+
|
|
10
|
+
const log = getLogger('x-auto-navigate');
|
|
11
|
+
|
|
12
|
+
const CDP_BASE = 'http://localhost:9222';
|
|
13
|
+
|
|
14
|
+
interface NavStep {
|
|
15
|
+
label: string;
|
|
16
|
+
url?: string;
|
|
17
|
+
clickSelector?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Minimal CDP client — connects to one page tab. */
|
|
21
|
+
class MiniCDP {
|
|
22
|
+
private ws: WebSocket | null = null;
|
|
23
|
+
private nextId = 1;
|
|
24
|
+
private callbacks = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void }>();
|
|
25
|
+
|
|
26
|
+
async connect(wsUrl: string): Promise<void> {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
const ws = new WebSocket(wsUrl);
|
|
29
|
+
ws.onopen = () => { this.ws = ws; resolve(); };
|
|
30
|
+
ws.onerror = (e) => reject(new Error(`CDP error: ${e}`));
|
|
31
|
+
ws.onclose = () => { this.ws = null; };
|
|
32
|
+
ws.onmessage = (event) => {
|
|
33
|
+
const msg = JSON.parse(String(event.data));
|
|
34
|
+
if (msg.id != null) {
|
|
35
|
+
const cb = this.callbacks.get(msg.id);
|
|
36
|
+
if (cb) {
|
|
37
|
+
this.callbacks.delete(msg.id);
|
|
38
|
+
msg.error ? cb.reject(new Error(msg.error.message)) : cb.resolve(msg.result);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async send(method: string, params?: Record<string, unknown>): Promise<unknown> {
|
|
46
|
+
if (!this.ws) throw new Error('Not connected');
|
|
47
|
+
const id = this.nextId++;
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
this.callbacks.set(id, { resolve, reject });
|
|
50
|
+
this.ws!.send(JSON.stringify({ id, method, params }));
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
close() { this.ws?.close(); }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Navigate Chrome through X.com pages to trigger GraphQL calls.
|
|
59
|
+
* The NetworkRecorder should already be attached and capturing.
|
|
60
|
+
*
|
|
61
|
+
* @param abortSignal Optional signal to stop navigation early.
|
|
62
|
+
* @returns List of step labels that completed successfully.
|
|
63
|
+
*/
|
|
64
|
+
export async function navigateXPages(abortSignal?: { aborted: boolean }): Promise<string[]> {
|
|
65
|
+
let wsUrl: string | null = null;
|
|
66
|
+
try {
|
|
67
|
+
const res = await fetch(`${CDP_BASE}/json/list`);
|
|
68
|
+
if (!res.ok) {
|
|
69
|
+
log.warn('CDP not available for auto-navigation');
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const targets = (await res.json()) as Array<{ type: string; url: string; webSocketDebuggerUrl: string }>;
|
|
73
|
+
const xTab = targets.find(
|
|
74
|
+
t => t.type === 'page' && (t.url.includes('x.com') || t.url.includes('twitter.com')),
|
|
75
|
+
);
|
|
76
|
+
wsUrl = xTab?.webSocketDebuggerUrl ?? targets.find(t => t.type === 'page')?.webSocketDebuggerUrl ?? null;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
log.warn({ err }, 'Failed to discover Chrome tabs');
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (!wsUrl) {
|
|
83
|
+
log.warn('No Chrome tab found for auto-navigation');
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const cdp = new MiniCDP();
|
|
88
|
+
try {
|
|
89
|
+
await cdp.connect(wsUrl);
|
|
90
|
+
} catch (err) {
|
|
91
|
+
log.warn({ err }, 'Failed to connect CDP for auto-navigation');
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
await cdp.send('Page.enable').catch(() => {});
|
|
96
|
+
const completed: string[] = [];
|
|
97
|
+
|
|
98
|
+
// Navigate to home first to discover the screen name
|
|
99
|
+
try {
|
|
100
|
+
await cdp.send('Page.navigate', { url: 'https://x.com/home' });
|
|
101
|
+
await sleep(3000);
|
|
102
|
+
} catch (err) {
|
|
103
|
+
log.warn({ err }, 'Failed to navigate to home');
|
|
104
|
+
cdp.close();
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Resolve screen name for profile-based URLs
|
|
109
|
+
let screenName: string | null = null;
|
|
110
|
+
try {
|
|
111
|
+
const result = await cdp.send('Runtime.evaluate', {
|
|
112
|
+
expression: `
|
|
113
|
+
(function() {
|
|
114
|
+
const link = document.querySelector('a[data-testid="AppTabBar_Profile_Link"]');
|
|
115
|
+
if (link) return link.getAttribute('href')?.replace('/', '') ?? null;
|
|
116
|
+
return null;
|
|
117
|
+
})()
|
|
118
|
+
`,
|
|
119
|
+
awaitPromise: false,
|
|
120
|
+
returnByValue: true,
|
|
121
|
+
}) as { result?: { value?: string | null } };
|
|
122
|
+
screenName = result?.result?.value ?? null;
|
|
123
|
+
} catch { /* ignore */ }
|
|
124
|
+
|
|
125
|
+
log.info({ screenName }, 'Detected screen name');
|
|
126
|
+
|
|
127
|
+
// Build steps with resolved URLs
|
|
128
|
+
const steps: NavStep[] = [
|
|
129
|
+
{ label: 'Home timeline', url: 'https://x.com/home' },
|
|
130
|
+
{ label: 'Profile', clickSelector: 'a[data-testid="AppTabBar_Profile_Link"]' },
|
|
131
|
+
{ label: 'Tweet detail', clickSelector: 'article[data-testid="tweet"] a[href*="/status/"]' },
|
|
132
|
+
{ label: 'Search', url: 'https://x.com/search?q=hello&src=typed_query' },
|
|
133
|
+
{ label: 'Bookmarks', url: 'https://x.com/i/bookmarks' },
|
|
134
|
+
{ label: 'Notifications', url: 'https://x.com/notifications' },
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
// Add profile-based URLs if we have the screen name
|
|
138
|
+
if (screenName) {
|
|
139
|
+
steps.push(
|
|
140
|
+
{ label: 'Likes', url: `https://x.com/${screenName}/likes` },
|
|
141
|
+
{ label: 'Followers', url: `https://x.com/${screenName}/followers` },
|
|
142
|
+
{ label: 'Following', url: `https://x.com/${screenName}/following` },
|
|
143
|
+
{ label: 'Media', url: `https://x.com/${screenName}/media` },
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const step of steps) {
|
|
148
|
+
if (abortSignal?.aborted) break;
|
|
149
|
+
|
|
150
|
+
log.info({ step: step.label }, 'Auto-navigate step starting');
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
if (step.url) {
|
|
154
|
+
await cdp.send('Page.navigate', { url: step.url });
|
|
155
|
+
await sleep(3000);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (step.clickSelector) {
|
|
159
|
+
await sleep(1500);
|
|
160
|
+
await clickInPage(cdp, step.clickSelector);
|
|
161
|
+
await sleep(2000);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Scroll to trigger lazy-loaded content
|
|
165
|
+
await cdp.send('Runtime.evaluate', {
|
|
166
|
+
expression: 'window.scrollBy(0, 800)',
|
|
167
|
+
awaitPromise: false,
|
|
168
|
+
}).catch(() => {});
|
|
169
|
+
|
|
170
|
+
await sleep(2000);
|
|
171
|
+
|
|
172
|
+
completed.push(step.label);
|
|
173
|
+
log.info({ step: step.label }, 'Auto-navigate step completed');
|
|
174
|
+
} catch (err) {
|
|
175
|
+
log.warn({ err, step: step.label }, 'Auto-navigate step failed');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
cdp.close();
|
|
180
|
+
log.info({ completed: completed.length, total: steps.length }, 'Auto-navigation finished');
|
|
181
|
+
return completed;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function clickInPage(cdp: MiniCDP, selector: string): Promise<boolean> {
|
|
185
|
+
try {
|
|
186
|
+
const result = await cdp.send('Runtime.evaluate', {
|
|
187
|
+
expression: `
|
|
188
|
+
(function() {
|
|
189
|
+
const el = document.querySelector(${JSON.stringify(selector)});
|
|
190
|
+
if (!el) return false;
|
|
191
|
+
el.scrollIntoView({ block: 'center' });
|
|
192
|
+
el.click();
|
|
193
|
+
return true;
|
|
194
|
+
})()
|
|
195
|
+
`,
|
|
196
|
+
awaitPromise: false,
|
|
197
|
+
returnByValue: true,
|
|
198
|
+
}) as { result?: { value?: boolean } };
|
|
199
|
+
return result?.result?.value === true;
|
|
200
|
+
} catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function sleep(ms: number): Promise<void> {
|
|
206
|
+
return new Promise(r => setTimeout(r, ms));
|
|
207
|
+
}
|
|
@@ -2,13 +2,7 @@ import { RiskLevel } from '../../permissions/types.js';
|
|
|
2
2
|
import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
|
|
3
3
|
import type { ToolDefinition } from '../../providers/types.js';
|
|
4
4
|
import { registerTool } from '../registry.js';
|
|
5
|
-
import {
|
|
6
|
-
import { getCallOrchestrator, unregisterCallOrchestrator } from '../../calls/call-state.js';
|
|
7
|
-
import { activeRelayConnections } from '../../calls/relay-server.js';
|
|
8
|
-
import { TwilioConversationRelayProvider } from '../../calls/twilio-provider.js';
|
|
9
|
-
import { getLogger } from '../../util/logger.js';
|
|
10
|
-
|
|
11
|
-
const log = getLogger('call-end');
|
|
5
|
+
import { cancelCall } from '../../calls/call-domain.js';
|
|
12
6
|
|
|
13
7
|
const definition: ToolDefinition = {
|
|
14
8
|
name: 'call_end',
|
|
@@ -47,70 +41,26 @@ class CallEndTool implements Tool {
|
|
|
47
41
|
|
|
48
42
|
const reason = input.reason as string | undefined;
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
const session = getCallSession(callSessionId);
|
|
52
|
-
if (!session) {
|
|
53
|
-
return { content: `Error: no call session found with ID ${callSessionId}`, isError: true };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (session.status === 'completed' || session.status === 'failed') {
|
|
57
|
-
return {
|
|
58
|
-
content: `Call session ${callSessionId} has already ended with status: ${session.status}`,
|
|
59
|
-
isError: false,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
log.info({ callSessionId, reason }, 'Ending call');
|
|
64
|
-
|
|
65
|
-
// Terminate the call via the provider API so Twilio hangs up,
|
|
66
|
-
// even if the relay WebSocket is not connected.
|
|
67
|
-
if (session.providerCallSid) {
|
|
68
|
-
try {
|
|
69
|
-
const provider = new TwilioConversationRelayProvider();
|
|
70
|
-
await provider.endCall(session.providerCallSid);
|
|
71
|
-
} catch (endErr) {
|
|
72
|
-
log.warn({ err: endErr, callSessionId, callSid: session.providerCallSid }, 'Failed to terminate call via provider API — proceeding with cleanup');
|
|
73
|
-
}
|
|
74
|
-
}
|
|
44
|
+
const result = await cancelCall({ callSessionId, reason });
|
|
75
45
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
relayConnection.destroy();
|
|
81
|
-
activeRelayConnections.delete(callSessionId);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Clean up orchestrator
|
|
85
|
-
const orchestrator = getCallOrchestrator(callSessionId);
|
|
86
|
-
if (orchestrator) {
|
|
87
|
-
orchestrator.destroy();
|
|
88
|
-
unregisterCallOrchestrator(callSessionId);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Update session status
|
|
92
|
-
updateCallSession(callSessionId, {
|
|
93
|
-
status: 'completed',
|
|
94
|
-
endedAt: Date.now(),
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
log.info({ callSessionId }, 'Call ended successfully');
|
|
98
|
-
|
|
99
|
-
const lines = [
|
|
100
|
-
'Call ended successfully.',
|
|
101
|
-
` Call Session ID: ${callSessionId}`,
|
|
102
|
-
` Status: completed`,
|
|
103
|
-
];
|
|
104
|
-
if (reason) {
|
|
105
|
-
lines.push(` Reason: ${reason}`);
|
|
46
|
+
if (!result.ok) {
|
|
47
|
+
// If the call already ended, report it as a non-error for the tool
|
|
48
|
+
if (result.status === 409) {
|
|
49
|
+
return { content: result.error, isError: false };
|
|
106
50
|
}
|
|
51
|
+
return { content: `Error: ${result.error}`, isError: true };
|
|
52
|
+
}
|
|
107
53
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
54
|
+
const lines = [
|
|
55
|
+
'Call ended successfully.',
|
|
56
|
+
` Call Session ID: ${callSessionId}`,
|
|
57
|
+
` Status: cancelled`,
|
|
58
|
+
];
|
|
59
|
+
if (reason) {
|
|
60
|
+
lines.push(` Reason: ${reason}`);
|
|
113
61
|
}
|
|
62
|
+
|
|
63
|
+
return { content: lines.join('\n'), isError: false };
|
|
114
64
|
}
|
|
115
65
|
}
|
|
116
66
|
|
|
@@ -2,15 +2,8 @@ import { RiskLevel } from '../../permissions/types.js';
|
|
|
2
2
|
import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
|
|
3
3
|
import type { ToolDefinition } from '../../providers/types.js';
|
|
4
4
|
import { registerTool } from '../registry.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { TwilioConversationRelayProvider } from '../../calls/twilio-provider.js';
|
|
8
|
-
import { getTwilioConfig } from '../../calls/twilio-config.js';
|
|
9
|
-
import { getLogger } from '../../util/logger.js';
|
|
10
|
-
|
|
11
|
-
const log = getLogger('call-start');
|
|
12
|
-
|
|
13
|
-
const E164_REGEX = /^\+\d+$/;
|
|
5
|
+
import { startCall } from '../../calls/call-domain.js';
|
|
6
|
+
import { getConfig } from '../../config/loader.js';
|
|
14
7
|
|
|
15
8
|
const definition: ToolDefinition = {
|
|
16
9
|
name: 'call_start',
|
|
@@ -47,87 +40,33 @@ class CallStartTool implements Tool {
|
|
|
47
40
|
}
|
|
48
41
|
|
|
49
42
|
async execute(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return { content: 'Error: phone_number is required and must be a string', isError: true };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (!E164_REGEX.test(phoneNumber)) {
|
|
56
|
-
return {
|
|
57
|
-
content: 'Error: phone_number must be in E.164 format (starts with + followed by digits, e.g. +14155551234)',
|
|
58
|
-
isError: true,
|
|
59
|
-
};
|
|
43
|
+
if (!getConfig().calls.enabled) {
|
|
44
|
+
return { content: 'Error: Calls feature is disabled via configuration. Set calls.enabled to true to use this feature.', isError: true };
|
|
60
45
|
}
|
|
61
46
|
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
47
|
+
const result = await startCall({
|
|
48
|
+
phoneNumber: input.phone_number as string,
|
|
49
|
+
task: input.task as string,
|
|
50
|
+
context: input.context as string | undefined,
|
|
51
|
+
conversationId: context.conversationId,
|
|
52
|
+
});
|
|
66
53
|
|
|
67
|
-
if (
|
|
68
|
-
return { content:
|
|
54
|
+
if (!result.ok) {
|
|
55
|
+
return { content: `Error: ${result.error}`, isError: true };
|
|
69
56
|
}
|
|
70
57
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
provider: 'twilio',
|
|
84
|
-
fromNumber: config.phoneNumber,
|
|
85
|
-
toNumber: phoneNumber,
|
|
86
|
-
task: callContext ? `${task}\n\nContext: ${callContext}` : task,
|
|
87
|
-
});
|
|
88
|
-
sessionId = session.id;
|
|
89
|
-
|
|
90
|
-
log.info({ callSessionId: session.id, to: phoneNumber, task }, 'Initiating outbound call');
|
|
91
|
-
|
|
92
|
-
const baseUrl = config.webhookBaseUrl.replace(/\/$/, '');
|
|
93
|
-
const { callSid } = await provider.initiateCall({
|
|
94
|
-
from: config.phoneNumber,
|
|
95
|
-
to: phoneNumber,
|
|
96
|
-
webhookUrl: `${baseUrl}/v1/calls/twilio/voice-webhook?callSessionId=${session.id}`,
|
|
97
|
-
statusCallbackUrl: `${baseUrl}/v1/calls/twilio/status`,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
updateCallSession(session.id, { providerCallSid: callSid });
|
|
101
|
-
|
|
102
|
-
log.info({ callSessionId: session.id, callSid }, 'Call initiated successfully');
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
content: [
|
|
106
|
-
'Call initiated successfully.',
|
|
107
|
-
` Call Session ID: ${session.id}`,
|
|
108
|
-
` Call SID: ${callSid}`,
|
|
109
|
-
` To: ${phoneNumber}`,
|
|
110
|
-
` Status: initiated`,
|
|
111
|
-
'',
|
|
112
|
-
'The AI voice assistant is now placing the call. Use call_status to check progress.',
|
|
113
|
-
].join('\n'),
|
|
114
|
-
isError: false,
|
|
115
|
-
};
|
|
116
|
-
} catch (err) {
|
|
117
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
118
|
-
log.error({ err, phoneNumber }, 'Failed to initiate call');
|
|
119
|
-
|
|
120
|
-
// Mark the session as failed so it doesn't stay in 'initiated' state
|
|
121
|
-
if (sessionId) {
|
|
122
|
-
updateCallSession(sessionId, {
|
|
123
|
-
status: 'failed',
|
|
124
|
-
endedAt: Date.now(),
|
|
125
|
-
lastError: msg,
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return { content: `Error initiating call: ${msg}`, isError: true };
|
|
130
|
-
}
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
'Call initiated successfully.',
|
|
61
|
+
` Call Session ID: ${result.session.id}`,
|
|
62
|
+
` Call SID: ${result.callSid}`,
|
|
63
|
+
` To: ${result.session.toNumber}`,
|
|
64
|
+
` Status: initiated`,
|
|
65
|
+
'',
|
|
66
|
+
'The AI voice assistant is now placing the call. Use call_status to check progress.',
|
|
67
|
+
].join('\n'),
|
|
68
|
+
isError: false,
|
|
69
|
+
};
|
|
131
70
|
}
|
|
132
71
|
}
|
|
133
72
|
|
|
@@ -2,10 +2,7 @@ import { RiskLevel } from '../../permissions/types.js';
|
|
|
2
2
|
import type { Tool, ToolContext, ToolExecutionResult } from '../types.js';
|
|
3
3
|
import type { ToolDefinition } from '../../providers/types.js';
|
|
4
4
|
import { registerTool } from '../registry.js';
|
|
5
|
-
import {
|
|
6
|
-
import { getLogger } from '../../util/logger.js';
|
|
7
|
-
|
|
8
|
-
const log = getLogger('call-status');
|
|
5
|
+
import { getCallStatus } from '../../calls/call-domain.js';
|
|
9
6
|
|
|
10
7
|
const definition: ToolDefinition = {
|
|
11
8
|
name: 'call_status',
|
|
@@ -35,62 +32,49 @@ class CallStatusTool implements Tool {
|
|
|
35
32
|
async execute(input: Record<string, unknown>, context: ToolContext): Promise<ToolExecutionResult> {
|
|
36
33
|
const callSessionId = input.call_session_id as string | undefined;
|
|
37
34
|
|
|
38
|
-
|
|
39
|
-
let session;
|
|
35
|
+
const result = getCallStatus(callSessionId, context.conversationId);
|
|
40
36
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
} else {
|
|
47
|
-
session = getActiveCallSessionForConversation(context.conversationId);
|
|
48
|
-
if (!session) {
|
|
49
|
-
return { content: 'No active call found in the current conversation.', isError: false };
|
|
50
|
-
}
|
|
37
|
+
if (!result.ok) {
|
|
38
|
+
// When no active call is found and no specific ID was requested, it's not an error
|
|
39
|
+
if (!callSessionId && result.error === 'No active call found in the current conversation') {
|
|
40
|
+
return { content: result.error, isError: false };
|
|
51
41
|
}
|
|
42
|
+
return { content: `Error: ${result.error}`, isError: true };
|
|
43
|
+
}
|
|
52
44
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
];
|
|
45
|
+
const { session } = result;
|
|
46
|
+
const lines = [
|
|
47
|
+
`Call Session: ${session.id}`,
|
|
48
|
+
` Status: ${session.status}`,
|
|
49
|
+
` To: ${session.toNumber}`,
|
|
50
|
+
` From: ${session.fromNumber}`,
|
|
51
|
+
];
|
|
61
52
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (session.task) {
|
|
67
|
-
lines.push(` Task: ${session.task}`);
|
|
68
|
-
}
|
|
53
|
+
if (session.providerCallSid) {
|
|
54
|
+
lines.push(` Call SID: ${session.providerCallSid}`);
|
|
55
|
+
}
|
|
69
56
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
lines.push(` Duration: ${durationSec}s`);
|
|
74
|
-
}
|
|
57
|
+
if (session.task) {
|
|
58
|
+
lines.push(` Task: ${session.task}`);
|
|
59
|
+
}
|
|
75
60
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
61
|
+
if (session.startedAt) {
|
|
62
|
+
const durationMs = (session.endedAt ?? Date.now()) - session.startedAt;
|
|
63
|
+
const durationSec = Math.round(durationMs / 1000);
|
|
64
|
+
lines.push(` Duration: ${durationSec}s`);
|
|
65
|
+
}
|
|
79
66
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
lines.push('');
|
|
84
|
-
lines.push(` Pending Question: ${pendingQuestion.questionText}`);
|
|
85
|
-
lines.push(` Question ID: ${pendingQuestion.id}`);
|
|
86
|
-
}
|
|
67
|
+
if (session.lastError) {
|
|
68
|
+
lines.push(` Last Error: ${session.lastError}`);
|
|
69
|
+
}
|
|
87
70
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return { content: `Error checking call status: ${msg}`, isError: true };
|
|
71
|
+
if (result.pendingQuestion) {
|
|
72
|
+
lines.push('');
|
|
73
|
+
lines.push(` Pending Question: ${result.pendingQuestion.questionText}`);
|
|
74
|
+
lines.push(` Question ID: ${result.pendingQuestion.id}`);
|
|
93
75
|
}
|
|
76
|
+
|
|
77
|
+
return { content: lines.join('\n'), isError: false };
|
|
94
78
|
}
|
|
95
79
|
}
|
|
96
80
|
|