@vellumai/assistant 0.4.48 → 0.4.49
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/ARCHITECTURE.md +2 -2
- package/README.md +2 -23
- package/docs/architecture/integrations.md +45 -41
- package/docs/architecture/keychain-broker.md +3 -3
- package/docs/runbook-trusted-contacts.md +3 -8
- package/hook-templates/debug-prompt-logger/hook.json +1 -1
- package/hook-templates/debug-prompt-logger/run.sh +1 -3
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +0 -1
- package/src/__tests__/anthropic-provider.test.ts +156 -0
- package/src/__tests__/approval-cascade.test.ts +810 -0
- package/src/__tests__/approval-primitive.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-attachments.test.ts +12 -34
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/channel-guardian.test.ts +0 -2
- package/src/__tests__/channel-readiness-routes.test.ts +15 -6
- package/src/__tests__/channel-readiness-service.test.ts +10 -9
- package/src/__tests__/checker.test.ts +9 -29
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-tools.test.ts +2 -19
- package/src/__tests__/config-watcher.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-image-dimensions.test.ts +332 -0
- package/src/__tests__/context-token-estimator.test.ts +196 -13
- package/src/__tests__/conversation-attention-store.test.ts +0 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-metadata-store.test.ts +64 -73
- package/src/__tests__/credential-security-invariants.test.ts +13 -7
- package/src/__tests__/credential-vault-unit.test.ts +280 -49
- package/src/__tests__/credential-vault.test.ts +138 -16
- package/src/__tests__/credentials-cli.test.ts +71 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-cu-proxy.test.ts +629 -0
- package/src/__tests__/host-shell-tool.test.ts +27 -15
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/ingress-url-consistency.test.ts +14 -21
- package/src/__tests__/integration-status.test.ts +32 -51
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +10 -9
- package/src/__tests__/keychain-broker-client.test.ts +11 -43
- package/src/__tests__/notification-routing-intent.test.ts +0 -1
- package/src/__tests__/oauth-cli.test.ts +373 -14
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/oauth-store.test.ts +756 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +0 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +15 -21
- package/src/__tests__/recording-handler.test.ts +3 -4
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/runtime-events-sse.test.ts +55 -7
- package/src/__tests__/schedule-store.test.ts +0 -1
- package/src/__tests__/scheduler-recurrence.test.ts +0 -1
- package/src/__tests__/scoped-approval-grants.test.ts +0 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
- package/src/__tests__/secret-ingress-handler.test.ts +0 -1
- package/src/__tests__/send-endpoint-busy.test.ts +21 -6
- package/src/__tests__/sequence-store.test.ts +0 -1
- package/src/__tests__/session-init.benchmark.test.ts +4 -5
- package/src/__tests__/skill-include-graph.test.ts +66 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
- package/src/__tests__/skill-load-tool.test.ts +149 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/skills-uninstall.test.ts +1 -1
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-channel-config.test.ts +67 -3
- package/src/__tests__/slack-share-routes.test.ts +17 -19
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
- package/src/__tests__/terminal-tools.test.ts +4 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
- package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
- package/src/__tests__/trust-store.test.ts +1 -22
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/twilio-routes.test.ts +0 -16
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/agent/ax-tree-compaction.test.ts +235 -0
- package/src/agent/loop.ts +76 -130
- package/src/calls/call-domain.ts +1 -6
- package/src/calls/relay-server.ts +9 -13
- package/src/calls/twilio-config.ts +2 -7
- package/src/calls/twilio-routes.ts +1 -2
- package/src/calls/voice-ingress-preflight.ts +1 -1
- package/src/cli/commands/browser-relay.ts +18 -12
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/credentials.ts +101 -15
- package/src/cli/commands/oauth/apps.ts +255 -0
- package/src/cli/commands/oauth/connections.ts +299 -0
- package/src/cli/commands/oauth/index.ts +52 -0
- package/src/cli/commands/oauth/providers.ts +242 -0
- package/src/cli/commands/skills.ts +4 -338
- package/src/cli/program.ts +1 -5
- package/src/cli/reference.ts +1 -3
- package/src/config/assistant-feature-flags.ts +0 -3
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
- package/src/config/bundled-skills/settings/SKILL.md +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +2 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
- package/src/config/env-registry.ts +14 -83
- package/src/config/env.ts +11 -50
- package/src/config/feature-flag-registry.json +16 -16
- package/src/config/loader.ts +0 -6
- package/src/config/schema.ts +3 -1
- package/src/config/skills.ts +21 -2
- package/src/context/image-dimensions.ts +229 -0
- package/src/context/token-estimator.ts +75 -12
- package/src/context/window-manager.ts +49 -10
- package/src/daemon/assistant-attachments.ts +1 -13
- package/src/daemon/handlers/config-ingress.ts +8 -33
- package/src/daemon/handlers/config-slack-channel.ts +49 -46
- package/src/daemon/handlers/config-telegram.ts +32 -16
- package/src/daemon/handlers/sessions.ts +10 -24
- package/src/daemon/handlers/shared.ts +0 -130
- package/src/daemon/host-cu-proxy.ts +401 -0
- package/src/daemon/lifecycle.ts +36 -68
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/computer-use.ts +2 -119
- package/src/daemon/message-types/host-cu.ts +19 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/server.ts +14 -21
- package/src/daemon/session-agent-loop-handlers.ts +2 -0
- package/src/daemon/session-attachments.ts +1 -2
- package/src/daemon/session-slash.ts +1 -1
- package/src/daemon/session-surfaces.ts +40 -28
- package/src/daemon/session-tool-setup.ts +2 -9
- package/src/daemon/session.ts +138 -15
- package/src/daemon/tool-side-effects.ts +2 -8
- package/src/daemon/watch-handler.ts +2 -2
- package/src/events/tool-metrics-listener.ts +2 -2
- package/src/hooks/manager.ts +1 -4
- package/src/inbound/public-ingress-urls.ts +7 -7
- package/src/logfire.ts +16 -5
- package/src/memory/conversation-key-store.ts +21 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/149-oauth-tables.ts +60 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/oauth.ts +65 -0
- package/src/messaging/provider.ts +4 -4
- package/src/messaging/providers/gmail/client.ts +82 -2
- package/src/messaging/providers/gmail/people-client.ts +10 -10
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
- package/src/messaging/providers/whatsapp/adapter.ts +11 -8
- package/src/messaging/registry.ts +2 -32
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/signal.ts +4 -5
- package/src/oauth/byo-connection.test.ts +126 -25
- package/src/oauth/byo-connection.ts +22 -6
- package/src/oauth/connect-orchestrator.ts +113 -57
- package/src/oauth/connect-types.ts +17 -23
- package/src/oauth/connection-resolver.ts +35 -11
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +104 -0
- package/src/oauth/oauth-store.ts +496 -0
- package/src/oauth/platform-connection.test.ts +29 -0
- package/src/oauth/platform-connection.ts +6 -5
- package/src/oauth/provider-behaviors.ts +124 -0
- package/src/oauth/scope-policy.ts +9 -2
- package/src/oauth/seed-providers.ts +161 -0
- package/src/oauth/token-persistence.ts +74 -78
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +0 -1
- package/src/permissions/prompter.ts +10 -1
- package/src/permissions/trust-store.ts +13 -0
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
- package/src/prompts/system-prompt.ts +28 -40
- package/src/providers/anthropic/client.ts +133 -24
- package/src/providers/retry.ts +1 -27
- package/src/runtime/auth/route-policy.ts +0 -3
- package/src/runtime/channel-reply-delivery.ts +0 -40
- package/src/runtime/gateway-client.ts +0 -7
- package/src/runtime/http-server.ts +8 -6
- package/src/runtime/http-types.ts +2 -2
- package/src/runtime/middleware/twilio-validation.ts +1 -11
- package/src/runtime/pending-interactions.ts +14 -12
- package/src/runtime/routes/channel-delivery-routes.ts +0 -1
- package/src/runtime/routes/conversation-routes.ts +73 -19
- package/src/runtime/routes/events-routes.ts +21 -11
- package/src/runtime/routes/host-cu-routes.ts +97 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
- package/src/runtime/routes/integrations/slack/share.ts +6 -7
- package/src/runtime/routes/log-export-routes.ts +126 -8
- package/src/runtime/routes/settings-routes.ts +55 -48
- package/src/runtime/routes/surface-action-routes.ts +1 -1
- package/src/runtime/routes/watch-routes.ts +128 -0
- package/src/schedule/integration-status.ts +10 -9
- package/src/security/credential-key.ts +0 -156
- package/src/security/keychain-broker-client.ts +5 -6
- package/src/security/oauth2.ts +1 -1
- package/src/security/token-manager.ts +119 -46
- package/src/skills/catalog-install.ts +358 -0
- package/src/skills/include-graph.ts +32 -0
- package/src/telegram/bot-username.ts +2 -3
- package/src/tools/browser/network-recorder.ts +1 -1
- package/src/tools/browser/network-recording-types.ts +1 -1
- package/src/tools/computer-use/definitions.ts +46 -11
- package/src/tools/computer-use/registry.ts +4 -5
- package/src/tools/credentials/broker.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +17 -121
- package/src/tools/credentials/vault.ts +94 -167
- package/src/tools/registry.ts +2 -7
- package/src/tools/skills/load.ts +62 -3
- package/src/tools/watch/watch-state.ts +0 -12
- package/src/util/logger.ts +7 -41
- package/src/util/platform.ts +9 -28
- package/src/watcher/providers/google-calendar.ts +2 -1
- package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
- package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
- package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
- package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
- package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
- package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
- package/src/cli/commands/dev.ts +0 -129
- package/src/cli/commands/map.ts +0 -391
- package/src/cli/commands/oauth.ts +0 -77
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
- package/src/daemon/computer-use-session.ts +0 -1026
- package/src/daemon/ride-shotgun-handler.ts +0 -569
- package/src/oauth/provider-base-urls.ts +0 -21
- package/src/oauth/provider-profiles.ts +0 -192
- package/src/prompts/computer-use-prompt.ts +0 -98
- package/src/runtime/routes/computer-use-routes.ts +0 -641
- package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
- package/src/runtime/telegram-streaming-delivery.ts +0 -393
- package/src/tools/computer-use/request-computer-control.ts +0 -56
|
@@ -1,569 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
|
|
3
|
-
import { autoNavigate } from "../tools/browser/auto-navigate.js";
|
|
4
|
-
import {
|
|
5
|
-
type CdpSession,
|
|
6
|
-
ensureChromeWithCdp,
|
|
7
|
-
minimizeChromeWindow,
|
|
8
|
-
} from "../tools/browser/chrome-cdp.js";
|
|
9
|
-
import { NetworkRecorder } from "../tools/browser/network-recorder.js";
|
|
10
|
-
import type { SessionRecording } from "../tools/browser/network-recording-types.js";
|
|
11
|
-
import { saveRecording } from "../tools/browser/recording-store.js";
|
|
12
|
-
import type { WatchSession } from "../tools/watch/watch-state.js";
|
|
13
|
-
import {
|
|
14
|
-
fireWatchCompletionNotifier,
|
|
15
|
-
fireWatchStartNotifier,
|
|
16
|
-
registerWatchCompletionNotifier,
|
|
17
|
-
unregisterWatchCompletionNotifier,
|
|
18
|
-
watchSessions,
|
|
19
|
-
} from "../tools/watch/watch-state.js";
|
|
20
|
-
import { getLogger } from "../util/logger.js";
|
|
21
|
-
import type { HandlerContext } from "./handlers/shared.js";
|
|
22
|
-
import type { RideShotgunStart, RideShotgunStop } from "./message-protocol.js";
|
|
23
|
-
import { generateSummary, lastSummaryBySession } from "./watch-handler.js";
|
|
24
|
-
|
|
25
|
-
const log = getLogger("ride-shotgun-handler");
|
|
26
|
-
|
|
27
|
-
/** Active network recorders keyed by watchId. */
|
|
28
|
-
const activeRecorders = new Map<string, NetworkRecorder>();
|
|
29
|
-
|
|
30
|
-
/** Active CDP sessions keyed by watchId — tracks browser ownership for cleanup. */
|
|
31
|
-
const activeCdpSessions = new Map<string, CdpSession>();
|
|
32
|
-
|
|
33
|
-
/** Active progress interval timers keyed by watchId, cleared on session completion. */
|
|
34
|
-
const activeProgressIntervals = new Map<string, NodeJS.Timeout>();
|
|
35
|
-
|
|
36
|
-
/** Return domain-specific URL patterns that indicate a successful login. */
|
|
37
|
-
function getLoginSignals(_targetDomain?: string): string[] {
|
|
38
|
-
// DoorDash and general fallback
|
|
39
|
-
return [
|
|
40
|
-
"/graphql/postLoginQuery",
|
|
41
|
-
"/graphql/homePageFacetFeed",
|
|
42
|
-
"/graphql/getConsumerOrdersWithDetails",
|
|
43
|
-
];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Complete a session — finalize recording (if learn mode), generate summary, fire notifier.
|
|
48
|
-
* Shared by both the duration timeout and the early-stop handler.
|
|
49
|
-
*/
|
|
50
|
-
async function completeSession(session: WatchSession): Promise<void> {
|
|
51
|
-
if (session.status !== "active") return; // already completing/completed
|
|
52
|
-
|
|
53
|
-
session.status = "completing";
|
|
54
|
-
if (session.timeoutHandle) {
|
|
55
|
-
clearTimeout(session.timeoutHandle);
|
|
56
|
-
session.timeoutHandle = undefined;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Clear progress interval timer if one was registered for this session
|
|
60
|
-
const progressTimer = activeProgressIntervals.get(session.watchId);
|
|
61
|
-
if (progressTimer) {
|
|
62
|
-
clearInterval(progressTimer);
|
|
63
|
-
activeProgressIntervals.delete(session.watchId);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const { watchId, sessionId } = session;
|
|
67
|
-
log.info(
|
|
68
|
-
{ watchId, sessionId, observationCount: session.observations.length },
|
|
69
|
-
"Session completing...",
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
// In learn mode, stop recording and save — skip the LLM summary (not needed)
|
|
73
|
-
if (session.isLearnMode && session.recordingId) {
|
|
74
|
-
const hasRecorder = activeRecorders.has(watchId);
|
|
75
|
-
|
|
76
|
-
if (hasRecorder) {
|
|
77
|
-
session.savedRecordingPath = await finalizeLearnRecording(
|
|
78
|
-
watchId,
|
|
79
|
-
session,
|
|
80
|
-
session.recordingId,
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Clean up the CDP session — minimize if we launched Chrome, leave it alone otherwise
|
|
85
|
-
const cdpSession = activeCdpSessions.get(watchId);
|
|
86
|
-
if (cdpSession) {
|
|
87
|
-
activeCdpSessions.delete(watchId);
|
|
88
|
-
if (cdpSession.launchedByUs) {
|
|
89
|
-
try {
|
|
90
|
-
await minimizeChromeWindow(cdpSession.baseUrl);
|
|
91
|
-
log.info({ watchId }, "Minimized assistant-launched Chrome window");
|
|
92
|
-
} catch (err) {
|
|
93
|
-
log.debug({ err, watchId }, "Failed to minimize Chrome window");
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Use bootstrapFailureReason as the primary discriminator — hasRecorder
|
|
99
|
-
// alone can't distinguish "browser never launched" from "recorder failed
|
|
100
|
-
// after retries" since both leave activeRecorders empty.
|
|
101
|
-
const summary = session.bootstrapFailureReason
|
|
102
|
-
? `Learn session failed — ${session.bootstrapFailureReason}`
|
|
103
|
-
: session.savedRecordingPath
|
|
104
|
-
? "Learn session completed — recording saved."
|
|
105
|
-
: "Learn session completed — recording failed to save.";
|
|
106
|
-
|
|
107
|
-
lastSummaryBySession.set(sessionId, summary);
|
|
108
|
-
session.status = "completed";
|
|
109
|
-
log.info(
|
|
110
|
-
{
|
|
111
|
-
watchId,
|
|
112
|
-
sessionId,
|
|
113
|
-
hasRecorder,
|
|
114
|
-
bootstrapFailureReason: session.bootstrapFailureReason,
|
|
115
|
-
},
|
|
116
|
-
"Learn session complete — firing completion notifier",
|
|
117
|
-
);
|
|
118
|
-
fireWatchCompletionNotifier(sessionId, session);
|
|
119
|
-
log.info({ watchId, sessionId }, "Completion notifier fired");
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
await generateSummary(session);
|
|
124
|
-
session.status = "completed";
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export async function handleRideShotgunStart(
|
|
128
|
-
msg: RideShotgunStart,
|
|
129
|
-
ctx: HandlerContext,
|
|
130
|
-
): Promise<void> {
|
|
131
|
-
const watchId = randomUUID();
|
|
132
|
-
const sessionId = randomUUID();
|
|
133
|
-
const { durationSeconds, intervalSeconds } = msg;
|
|
134
|
-
const mode = msg.mode ?? "observe";
|
|
135
|
-
const targetDomain = msg.targetDomain;
|
|
136
|
-
const isLearnMode = mode === "learn";
|
|
137
|
-
const recordingId = isLearnMode ? randomUUID() : undefined;
|
|
138
|
-
|
|
139
|
-
const session: WatchSession = {
|
|
140
|
-
watchId,
|
|
141
|
-
sessionId,
|
|
142
|
-
focusArea: isLearnMode
|
|
143
|
-
? `Learn mode: recording network traffic and screen observations${
|
|
144
|
-
targetDomain ? ` for ${targetDomain}` : ""
|
|
145
|
-
}`
|
|
146
|
-
: "General workflow observation",
|
|
147
|
-
durationSeconds,
|
|
148
|
-
intervalSeconds,
|
|
149
|
-
observations: [],
|
|
150
|
-
commentaryCount: 0,
|
|
151
|
-
status: "active",
|
|
152
|
-
startedAt: Date.now(),
|
|
153
|
-
isRideShotgun: true,
|
|
154
|
-
isLearnMode,
|
|
155
|
-
targetDomain,
|
|
156
|
-
recordingId,
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
watchSessions.set(watchId, session);
|
|
160
|
-
log.debug(
|
|
161
|
-
{
|
|
162
|
-
watchId,
|
|
163
|
-
sessionId,
|
|
164
|
-
durationSeconds,
|
|
165
|
-
intervalSeconds,
|
|
166
|
-
mode,
|
|
167
|
-
targetDomain,
|
|
168
|
-
},
|
|
169
|
-
"Session created and stored in watchSessions map",
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
// In learn mode, ensure Chrome is available with CDP, then connect for network recording.
|
|
173
|
-
if (isLearnMode) {
|
|
174
|
-
const startRecording = async () => {
|
|
175
|
-
// Ensure Chrome is running with CDP — launches it if needed
|
|
176
|
-
let cdpSession: CdpSession;
|
|
177
|
-
try {
|
|
178
|
-
cdpSession = await ensureChromeWithCdp({
|
|
179
|
-
startUrl: targetDomain ? `https://${targetDomain}` : undefined,
|
|
180
|
-
});
|
|
181
|
-
// If session completed while we were awaiting Chrome, skip storing to avoid a stale map entry
|
|
182
|
-
if (session.status !== "active") {
|
|
183
|
-
log.info(
|
|
184
|
-
{ watchId, status: session.status },
|
|
185
|
-
"Session no longer active after CDP launch — skipping recording",
|
|
186
|
-
);
|
|
187
|
-
// If we launched Chrome, minimize it since completeSession already ran and won't find it
|
|
188
|
-
if (cdpSession.launchedByUs) {
|
|
189
|
-
try {
|
|
190
|
-
await minimizeChromeWindow(cdpSession.baseUrl);
|
|
191
|
-
log.info(
|
|
192
|
-
{ watchId },
|
|
193
|
-
"Minimized assistant-launched Chrome window (post-session)",
|
|
194
|
-
);
|
|
195
|
-
} catch (err) {
|
|
196
|
-
log.debug(
|
|
197
|
-
{ err, watchId },
|
|
198
|
-
"Failed to minimize Chrome window (post-session)",
|
|
199
|
-
);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
activeCdpSessions.set(watchId, cdpSession);
|
|
205
|
-
log.info(
|
|
206
|
-
{
|
|
207
|
-
watchId,
|
|
208
|
-
launchedByUs: cdpSession.launchedByUs,
|
|
209
|
-
baseUrl: cdpSession.baseUrl,
|
|
210
|
-
},
|
|
211
|
-
"CDP session established",
|
|
212
|
-
);
|
|
213
|
-
} catch (err) {
|
|
214
|
-
log.warn(
|
|
215
|
-
{ err, watchId },
|
|
216
|
-
"Failed to ensure Chrome with CDP — cannot start recording",
|
|
217
|
-
);
|
|
218
|
-
ctx.send({
|
|
219
|
-
type: "ride_shotgun_error",
|
|
220
|
-
watchId,
|
|
221
|
-
sessionId,
|
|
222
|
-
message:
|
|
223
|
-
"Failed to start browser — Chrome CDP could not be launched.",
|
|
224
|
-
});
|
|
225
|
-
// Fail-fast: complete the session immediately instead of waiting for timeout
|
|
226
|
-
session.bootstrapFailureReason = "browser could not be started.";
|
|
227
|
-
await completeSession(session);
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const cdpBaseUrl = cdpSession.baseUrl;
|
|
232
|
-
|
|
233
|
-
for (let attempt = 0; attempt < 10; attempt++) {
|
|
234
|
-
// Check if session is still active before each attempt
|
|
235
|
-
if (session.status !== "active") {
|
|
236
|
-
log.info(
|
|
237
|
-
{ watchId, attempt, status: session.status },
|
|
238
|
-
"Session no longer active — aborting recording start",
|
|
239
|
-
);
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
try {
|
|
243
|
-
const recorder = new NetworkRecorder(targetDomain, cdpBaseUrl);
|
|
244
|
-
recorder.loginSignals = getLoginSignals(targetDomain);
|
|
245
|
-
await recorder.startDirect();
|
|
246
|
-
// If session completed while we were connecting, stop immediately to avoid leak
|
|
247
|
-
if (session.status !== "active") {
|
|
248
|
-
log.info(
|
|
249
|
-
{ watchId, attempt },
|
|
250
|
-
"Session completed during CDP connect — stopping recorder to prevent leak",
|
|
251
|
-
);
|
|
252
|
-
await recorder.stop();
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
activeRecorders.set(watchId, recorder);
|
|
256
|
-
log.info(
|
|
257
|
-
{ watchId, targetDomain, attempt },
|
|
258
|
-
"Network recording started for learn session",
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
// Send periodic progress updates with network entry counts and idle detection
|
|
262
|
-
let lastNetworkEntryCount = 0;
|
|
263
|
-
let lastActivityTimestamp = Date.now();
|
|
264
|
-
let idleHintSent = false;
|
|
265
|
-
|
|
266
|
-
const progressInterval: NodeJS.Timeout = setInterval(() => {
|
|
267
|
-
if (session.status !== "active") {
|
|
268
|
-
clearInterval(progressInterval);
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const currentCount = recorder.entryCount;
|
|
273
|
-
|
|
274
|
-
// Track activity: reset idle timer when count changes
|
|
275
|
-
if (currentCount !== lastNetworkEntryCount) {
|
|
276
|
-
lastNetworkEntryCount = currentCount;
|
|
277
|
-
lastActivityTimestamp = Date.now();
|
|
278
|
-
// If we previously sent an idle hint, clear it now that activity resumed
|
|
279
|
-
if (idleHintSent) {
|
|
280
|
-
idleHintSent = false;
|
|
281
|
-
log.info(
|
|
282
|
-
{ watchId, currentCount },
|
|
283
|
-
"Activity resumed — clearing idleHint",
|
|
284
|
-
);
|
|
285
|
-
ctx.send({
|
|
286
|
-
type: "ride_shotgun_progress",
|
|
287
|
-
watchId,
|
|
288
|
-
message: `Recording network traffic...`,
|
|
289
|
-
networkEntryCount: currentCount,
|
|
290
|
-
statusMessage: "Recording network traffic...",
|
|
291
|
-
idleHint: false,
|
|
292
|
-
});
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Idle detection: if some initial activity happened and no new entries for 15s, hint once
|
|
298
|
-
const idleMs = Date.now() - lastActivityTimestamp;
|
|
299
|
-
let idleHint: boolean | undefined;
|
|
300
|
-
if (!idleHintSent && currentCount > 0 && idleMs >= 15_000) {
|
|
301
|
-
idleHint = true;
|
|
302
|
-
idleHintSent = true;
|
|
303
|
-
log.info(
|
|
304
|
-
{ watchId, currentCount, idleMs },
|
|
305
|
-
"Idle detected — sending idleHint",
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
ctx.send({
|
|
310
|
-
type: "ride_shotgun_progress",
|
|
311
|
-
watchId,
|
|
312
|
-
message: `Recording network traffic...`,
|
|
313
|
-
networkEntryCount: currentCount,
|
|
314
|
-
statusMessage: "Recording network traffic...",
|
|
315
|
-
...(idleHint !== undefined ? { idleHint } : {}),
|
|
316
|
-
});
|
|
317
|
-
}, 5000);
|
|
318
|
-
activeProgressIntervals.set(watchId, progressInterval);
|
|
319
|
-
|
|
320
|
-
if (msg.autoNavigate && targetDomain) {
|
|
321
|
-
const navDomain = msg.navigateDomain ?? targetDomain;
|
|
322
|
-
const abortSignal = { aborted: false };
|
|
323
|
-
const checkInterval = setInterval(() => {
|
|
324
|
-
if (session.status !== "active") {
|
|
325
|
-
abortSignal.aborted = true;
|
|
326
|
-
clearInterval(checkInterval);
|
|
327
|
-
}
|
|
328
|
-
}, 1000);
|
|
329
|
-
autoNavigate(navDomain, {
|
|
330
|
-
abortSignal,
|
|
331
|
-
onProgress: (progress) => {
|
|
332
|
-
// Send progress to connected client
|
|
333
|
-
if (progress.type === "visiting" && progress.url) {
|
|
334
|
-
const shortUrl = progress.url.replace(/^https?:\/\//, "");
|
|
335
|
-
ctx.send({
|
|
336
|
-
type: "ride_shotgun_progress",
|
|
337
|
-
watchId,
|
|
338
|
-
message: `[${progress.pageNumber || "?"}] ${shortUrl}`,
|
|
339
|
-
});
|
|
340
|
-
}
|
|
341
|
-
},
|
|
342
|
-
cdpBaseUrl,
|
|
343
|
-
})
|
|
344
|
-
.then((visited) => {
|
|
345
|
-
clearInterval(checkInterval);
|
|
346
|
-
log.info(
|
|
347
|
-
{ watchId, visitedPages: visited.length },
|
|
348
|
-
"Generic auto-navigation finished",
|
|
349
|
-
);
|
|
350
|
-
if (session.status === "active") {
|
|
351
|
-
completeSession(session);
|
|
352
|
-
}
|
|
353
|
-
})
|
|
354
|
-
.catch((err) => {
|
|
355
|
-
clearInterval(checkInterval);
|
|
356
|
-
log.warn({ err, watchId }, "Generic auto-navigation failed");
|
|
357
|
-
if (session.status === "active") {
|
|
358
|
-
completeSession(session);
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
} else if (msg.autoNavigate === false && targetDomain) {
|
|
362
|
-
// Manual mode: just record network traffic until timeout or early stop — no login detection shortcut.
|
|
363
|
-
} else {
|
|
364
|
-
// No targetDomain or targetDomain without explicit autoNavigate=false: use login detection
|
|
365
|
-
recorder.onLoginDetected = () => {
|
|
366
|
-
log.info(
|
|
367
|
-
{ watchId },
|
|
368
|
-
"Login detected — auto-stopping learn session",
|
|
369
|
-
);
|
|
370
|
-
completeSession(session);
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return;
|
|
375
|
-
} catch (err) {
|
|
376
|
-
if (attempt < 9) {
|
|
377
|
-
log.debug({ attempt, watchId }, "CDP not ready, retrying in 2s...");
|
|
378
|
-
await new Promise((r) => setTimeout(r, 2000));
|
|
379
|
-
} else {
|
|
380
|
-
log.warn(
|
|
381
|
-
{ err, watchId },
|
|
382
|
-
"Failed to start network recording after 10 attempts",
|
|
383
|
-
);
|
|
384
|
-
ctx.send({
|
|
385
|
-
type: "ride_shotgun_error",
|
|
386
|
-
watchId,
|
|
387
|
-
sessionId,
|
|
388
|
-
message: "Failed to start network recording after 10 attempts.",
|
|
389
|
-
});
|
|
390
|
-
session.bootstrapFailureReason =
|
|
391
|
-
"network recording could not be started after 10 attempts.";
|
|
392
|
-
await completeSession(session);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
};
|
|
397
|
-
// Don't block session start — record in background
|
|
398
|
-
startRecording();
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Set timeout for duration expiry
|
|
402
|
-
session.timeoutHandle = setTimeout(() => {
|
|
403
|
-
if (
|
|
404
|
-
session.isLearnMode &&
|
|
405
|
-
!activeRecorders.has(watchId) &&
|
|
406
|
-
!session.bootstrapFailureReason
|
|
407
|
-
) {
|
|
408
|
-
session.bootstrapFailureReason =
|
|
409
|
-
"session timed out before recording could start.";
|
|
410
|
-
}
|
|
411
|
-
completeSession(session);
|
|
412
|
-
}, durationSeconds * 1000);
|
|
413
|
-
|
|
414
|
-
// Register completion notifier to send summary back to client
|
|
415
|
-
registerWatchCompletionNotifier(
|
|
416
|
-
sessionId,
|
|
417
|
-
(_completedSession: WatchSession) => {
|
|
418
|
-
const summary = lastSummaryBySession.get(sessionId) ?? "";
|
|
419
|
-
const observationCount = _completedSession.observations.length;
|
|
420
|
-
|
|
421
|
-
log.info(
|
|
422
|
-
{
|
|
423
|
-
watchId,
|
|
424
|
-
sessionId,
|
|
425
|
-
observationCount,
|
|
426
|
-
summaryLength: summary.length,
|
|
427
|
-
},
|
|
428
|
-
"Completion notifier firing — sending ride_shotgun_result to client",
|
|
429
|
-
);
|
|
430
|
-
|
|
431
|
-
ctx.send({
|
|
432
|
-
type: "ride_shotgun_result",
|
|
433
|
-
sessionId,
|
|
434
|
-
watchId,
|
|
435
|
-
summary,
|
|
436
|
-
observationCount,
|
|
437
|
-
recordingId,
|
|
438
|
-
recordingPath: _completedSession.savedRecordingPath,
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
unregisterWatchCompletionNotifier(sessionId);
|
|
442
|
-
lastSummaryBySession.delete(sessionId);
|
|
443
|
-
log.debug(
|
|
444
|
-
{ watchId, sessionId, observationCount, recordingId },
|
|
445
|
-
"Ride shotgun result sent successfully",
|
|
446
|
-
);
|
|
447
|
-
},
|
|
448
|
-
);
|
|
449
|
-
|
|
450
|
-
// Fire start notifier
|
|
451
|
-
fireWatchStartNotifier(sessionId, session);
|
|
452
|
-
|
|
453
|
-
// Send watch_started so the Swift client knows the watchId/sessionId
|
|
454
|
-
ctx.send({
|
|
455
|
-
type: "watch_started",
|
|
456
|
-
sessionId,
|
|
457
|
-
watchId,
|
|
458
|
-
durationSeconds,
|
|
459
|
-
intervalSeconds,
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
log.info(
|
|
463
|
-
{ watchId, sessionId, durationSeconds, intervalSeconds, mode },
|
|
464
|
-
"Ride shotgun session started",
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
export async function handleRideShotgunStop(
|
|
469
|
-
msg: RideShotgunStop,
|
|
470
|
-
_ctx: HandlerContext,
|
|
471
|
-
): Promise<void> {
|
|
472
|
-
const { watchId } = msg;
|
|
473
|
-
const session = watchSessions.get(watchId);
|
|
474
|
-
if (!session) {
|
|
475
|
-
log.warn({ watchId }, "ride_shotgun_stop: session not found");
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
log.info({ watchId, sessionId: session.sessionId }, "Early stop requested");
|
|
479
|
-
await completeSession(session);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* Stop network recording, extract cookies, build and save the SessionRecording.
|
|
484
|
-
*/
|
|
485
|
-
async function finalizeLearnRecording(
|
|
486
|
-
watchId: string,
|
|
487
|
-
session: WatchSession,
|
|
488
|
-
recordingId: string,
|
|
489
|
-
): Promise<string | undefined> {
|
|
490
|
-
try {
|
|
491
|
-
const recorder = activeRecorders.get(watchId);
|
|
492
|
-
|
|
493
|
-
// Extract cookies before stopping (needs the CDP connection alive)
|
|
494
|
-
const cookies = recorder
|
|
495
|
-
? await recorder.extractCookies(session.targetDomain)
|
|
496
|
-
: [];
|
|
497
|
-
|
|
498
|
-
const networkEntries = recorder ? await recorder.stop() : [];
|
|
499
|
-
activeRecorders.delete(watchId);
|
|
500
|
-
|
|
501
|
-
// Save cookies to the encrypted credential store (keyed by target domain)
|
|
502
|
-
// so they don't need to be persisted in the plaintext recording file.
|
|
503
|
-
if (session.targetDomain && cookies.length > 0) {
|
|
504
|
-
const { credentialKey: credKey } =
|
|
505
|
-
await import("../security/credential-key.js");
|
|
506
|
-
const { setSecureKeyAsync } = await import("../security/secure-keys.js");
|
|
507
|
-
const { upsertCredentialMetadata } =
|
|
508
|
-
await import("../tools/credentials/metadata-store.js");
|
|
509
|
-
|
|
510
|
-
const service = session.targetDomain;
|
|
511
|
-
const field = "session:cookies";
|
|
512
|
-
const storageKey = credKey(service, field);
|
|
513
|
-
const stored = await setSecureKeyAsync(
|
|
514
|
-
storageKey,
|
|
515
|
-
JSON.stringify(cookies),
|
|
516
|
-
);
|
|
517
|
-
if (stored) {
|
|
518
|
-
try {
|
|
519
|
-
upsertCredentialMetadata(service, field, {});
|
|
520
|
-
} catch {
|
|
521
|
-
// Non-critical: metadata upsert is best-effort
|
|
522
|
-
}
|
|
523
|
-
log.info(
|
|
524
|
-
{ targetDomain: service, cookieCount: cookies.length },
|
|
525
|
-
"Cookies saved to credential store",
|
|
526
|
-
);
|
|
527
|
-
} else {
|
|
528
|
-
log.warn(
|
|
529
|
-
{ targetDomain: service },
|
|
530
|
-
"Failed to save cookies to credential store",
|
|
531
|
-
);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
const recording: SessionRecording = {
|
|
536
|
-
id: recordingId,
|
|
537
|
-
startedAt: session.startedAt,
|
|
538
|
-
endedAt: Date.now(),
|
|
539
|
-
targetDomain: session.targetDomain,
|
|
540
|
-
networkEntries,
|
|
541
|
-
cookies: [], // Cookies saved to credential store — never persisted in recording
|
|
542
|
-
observations: session.observations.map((obs) => ({
|
|
543
|
-
ocrText: obs.ocrText,
|
|
544
|
-
appName: obs.appName,
|
|
545
|
-
windowTitle: obs.windowTitle,
|
|
546
|
-
timestamp: obs.timestamp,
|
|
547
|
-
captureIndex: obs.captureIndex,
|
|
548
|
-
})),
|
|
549
|
-
};
|
|
550
|
-
|
|
551
|
-
const path = saveRecording(recording);
|
|
552
|
-
log.info(
|
|
553
|
-
{
|
|
554
|
-
recordingId,
|
|
555
|
-
networkEntries: networkEntries.length,
|
|
556
|
-
cookies: cookies.length,
|
|
557
|
-
observations: session.observations.length,
|
|
558
|
-
},
|
|
559
|
-
"Learn recording finalized and saved",
|
|
560
|
-
);
|
|
561
|
-
return path;
|
|
562
|
-
} catch (err) {
|
|
563
|
-
log.error(
|
|
564
|
-
{ err, watchId, recordingId },
|
|
565
|
-
"Failed to finalize learn recording",
|
|
566
|
-
);
|
|
567
|
-
return undefined;
|
|
568
|
-
}
|
|
569
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/** Default base URL per credential service. Used by the connection when no per-request override is provided. */
|
|
2
|
-
export const PROVIDER_BASE_URLS: Record<string, string> = {
|
|
3
|
-
"integration:gmail": "https://gmail.googleapis.com/gmail/v1/users/me",
|
|
4
|
-
"integration:slack": "https://slack.com/api",
|
|
5
|
-
"integration:twitter": "https://api.x.com",
|
|
6
|
-
"integration:notion": "https://api.notion.com",
|
|
7
|
-
"integration:linear": "https://api.linear.app",
|
|
8
|
-
"integration:github": "https://api.github.com",
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Alternative base URLs for providers that span multiple API hosts
|
|
13
|
-
* sharing one OAuth token. Callers pass these via `request({ baseUrl })`.
|
|
14
|
-
*/
|
|
15
|
-
export const GOOGLE_CALENDAR_BASE_URL =
|
|
16
|
-
"https://www.googleapis.com/calendar/v3";
|
|
17
|
-
export const GOOGLE_PEOPLE_BASE_URL = "https://people.googleapis.com/v1";
|
|
18
|
-
|
|
19
|
-
export function getProviderBaseUrl(providerKey: string): string | undefined {
|
|
20
|
-
return PROVIDER_BASE_URLS[providerKey];
|
|
21
|
-
}
|