@vellumai/assistant 0.4.11 → 0.4.13
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 +401 -385
- package/package.json +1 -1
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +75 -61
- package/src/__tests__/registry.test.ts +235 -187
- package/src/__tests__/secure-keys.test.ts +27 -0
- package/src/__tests__/session-agent-loop.test.ts +521 -256
- package/src/__tests__/session-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/session-tool-setup-app-refresh.test.ts +1 -0
- package/src/__tests__/session-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/session-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/skills.test.ts +334 -276
- package/src/__tests__/slack-skill.test.ts +124 -0
- package/src/__tests__/starter-task-flow.test.ts +7 -17
- package/src/agent/loop.ts +10 -3
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +449 -0
- package/src/config/bundled-skills/doordash/SKILL.md +171 -0
- package/src/config/bundled-skills/doordash/__tests__/doordash-client.test.ts +203 -0
- package/src/config/bundled-skills/doordash/__tests__/doordash-session.test.ts +164 -0
- package/src/config/bundled-skills/doordash/doordash-cli.ts +1193 -0
- package/src/config/bundled-skills/doordash/doordash-entry.ts +22 -0
- package/src/config/bundled-skills/doordash/lib/cart-queries.ts +787 -0
- package/src/config/bundled-skills/doordash/lib/client.ts +1071 -0
- package/src/config/bundled-skills/doordash/lib/order-queries.ts +85 -0
- package/src/config/bundled-skills/doordash/lib/queries.ts +28 -0
- package/src/config/bundled-skills/doordash/lib/query-extractor.ts +94 -0
- package/src/config/bundled-skills/doordash/lib/search-queries.ts +203 -0
- package/src/config/bundled-skills/doordash/lib/session.ts +93 -0
- package/src/config/bundled-skills/doordash/lib/shared/errors.ts +61 -0
- package/src/config/bundled-skills/doordash/lib/shared/ipc.ts +32 -0
- package/src/config/bundled-skills/doordash/lib/shared/network-recorder.ts +380 -0
- package/src/config/bundled-skills/doordash/lib/shared/platform.ts +35 -0
- package/src/config/bundled-skills/doordash/lib/shared/recording-store.ts +43 -0
- package/src/config/bundled-skills/doordash/lib/shared/recording-types.ts +49 -0
- package/src/config/bundled-skills/doordash/lib/shared/truncate.ts +6 -0
- package/src/config/bundled-skills/doordash/lib/store-queries.ts +246 -0
- package/src/config/bundled-skills/doordash/lib/types.ts +367 -0
- package/src/config/bundled-skills/google-calendar/SKILL.md +4 -5
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +41 -41
- package/src/config/bundled-skills/messaging/SKILL.md +59 -42
- package/src/config/bundled-skills/messaging/TOOLS.json +14 -92
- package/src/config/bundled-skills/messaging/tools/gmail-archive-by-query.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/gmail-batch-archive.ts +11 -2
- package/src/config/bundled-skills/messaging/tools/gmail-outreach-scan.ts +8 -1
- package/src/config/bundled-skills/messaging/tools/gmail-sender-digest.ts +12 -4
- package/src/config/bundled-skills/messaging/tools/gmail-unsubscribe.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +5 -1
- package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +5 -2
- package/src/config/bundled-skills/notion/SKILL.md +240 -0
- package/src/config/bundled-skills/notion-oauth-setup/SKILL.md +127 -0
- package/src/config/bundled-skills/oauth-setup/SKILL.md +144 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +76 -45
- package/src/config/bundled-skills/skills-catalog/SKILL.md +32 -29
- package/src/config/bundled-skills/slack/SKILL.md +49 -0
- package/src/config/bundled-skills/slack/TOOLS.json +167 -0
- package/src/config/bundled-skills/slack/tools/shared.ts +23 -0
- package/src/config/bundled-skills/{messaging → slack}/tools/slack-add-reaction.ts +2 -5
- package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +33 -0
- package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +75 -0
- package/src/config/bundled-skills/{messaging → slack}/tools/slack-delete-message.ts +2 -5
- package/src/config/bundled-skills/{messaging → slack}/tools/slack-leave-channel.ts +2 -5
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +193 -0
- package/src/config/{vellum-skills → bundled-skills}/sms-setup/SKILL.md +29 -22
- package/src/config/{vellum-skills → bundled-skills}/telegram-setup/SKILL.md +17 -14
- package/src/config/{vellum-skills → bundled-skills}/twilio-setup/SKILL.md +20 -5
- package/src/config/bundled-tool-registry.ts +292 -267
- package/src/config/schema.ts +1 -1
- package/src/daemon/handlers/skills.ts +334 -234
- package/src/daemon/ipc-contract/messages.ts +2 -0
- package/src/daemon/ipc-contract/surfaces.ts +2 -0
- package/src/daemon/lifecycle.ts +358 -221
- package/src/daemon/response-tier.ts +2 -0
- package/src/daemon/server.ts +453 -193
- package/src/daemon/session-agent-loop-handlers.ts +43 -2
- package/src/daemon/session-agent-loop.ts +3 -0
- package/src/daemon/session-lifecycle.ts +3 -0
- package/src/daemon/session-process.ts +1 -0
- package/src/daemon/session-surfaces.ts +22 -20
- package/src/daemon/session-tool-setup.ts +1 -0
- package/src/daemon/session.ts +5 -2
- package/src/messaging/outreach-classifier.ts +12 -5
- package/src/messaging/provider-types.ts +5 -0
- package/src/messaging/provider.ts +1 -1
- package/src/messaging/providers/gmail/adapter.ts +11 -5
- package/src/messaging/providers/gmail/client.ts +2 -0
- package/src/messaging/providers/slack/adapter.ts +1 -0
- package/src/messaging/providers/slack/client.ts +8 -0
- package/src/messaging/providers/slack/types.ts +5 -0
- package/src/runtime/http-errors.ts +33 -20
- package/src/runtime/http-server.ts +706 -291
- package/src/runtime/http-types.ts +26 -16
- package/src/runtime/routes/secret-routes.ts +57 -2
- package/src/runtime/routes/surface-action-routes.ts +66 -0
- package/src/runtime/routes/trust-rules-routes.ts +140 -0
- package/src/security/keychain-to-encrypted-migration.ts +59 -0
- package/src/security/secure-keys.ts +17 -0
- package/src/skills/frontmatter.ts +9 -7
- package/src/tools/apps/executors.ts +2 -1
- package/src/tools/tool-manifest.ts +44 -42
- package/src/tools/types.ts +9 -0
- package/src/__tests__/skill-mirror-parity.test.ts +0 -176
- package/src/config/vellum-skills/catalog.json +0 -63
- package/src/config/vellum-skills/chatgpt-import/tools/chatgpt-import.ts +0 -295
- package/src/skills/vellum-catalog-remote.ts +0 -166
- package/src/tools/skills/vellum-catalog.ts +0 -168
- /package/src/config/{vellum-skills → bundled-skills}/chatgpt-import/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/chatgpt-import/TOOLS.json +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/deploy-fullstack-vercel/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/document-writer/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/guardian-verify-setup/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/slack-oauth-setup/SKILL.md +0 -0
- /package/src/config/{vellum-skills → bundled-skills}/trusted-contacts/SKILL.md +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL queries for DoorDash checkout: order creation, delivery options, and payment methods.
|
|
3
|
+
* Each query is fully self-contained with all required fragment definitions.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// DROPOFF_OPTIONS_QUERY
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
export const DROPOFF_OPTIONS_QUERY = `
|
|
11
|
+
query dropoffOptions($cartId: ID, $addressId: ID) {
|
|
12
|
+
dropoffOptions(cartId: $cartId, addressId: $addressId) {
|
|
13
|
+
id displayString isDefault isEnabled placeholderText disabledMessage
|
|
14
|
+
proofOfDeliveryType __typename
|
|
15
|
+
}
|
|
16
|
+
}`;
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// CREATE_ORDER_FROM_CART_QUERY
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
export const CREATE_ORDER_FROM_CART_QUERY = `
|
|
23
|
+
mutation createOrderFromCart($cartId: ID!, $total: Int!, $sosDeliveryFee: Int!, $isPickupOrder: Boolean!, $verifiedAgeRequirement: Boolean!, $deliveryTime: String!, $menuOptions: [String], $stripeToken: String, $attributionData: String, $fulfillsOwnDeliveries: Boolean, $budgetId: String, $teamId: String, $giftOptions: GiftOptionsInput, $recipientShippingDetails: RecipientShippingDetails, $storeId: String, $tipAmounts: [TipAmount!], $paymentMethod: Int, $deliveryOptionType: DeliveryOptionType, $workOrderOptions: WorkOrderOptionsInput, $isCardPayment: Boolean, $clientFraudContext: PaymentClientFraudContextInput, $programId: String, $membershipId: String, $dropoffPreferences: String, $routineReorderDetails: RoutineReorderDetails, $supplementalPaymentDetailsList: [SupplementalPaymentDetails!], $monitoringContext: CreateOrderFromCartMonitoringContextInput, $rewardBalanceApplied: RewardBalanceDetailsInput, $deliveryOptionInfo: DeliveryOptionInfo, $hasAccessibilityRequirements: Boolean, $shouldApplyCredits: Boolean, $dasherPickupInstructions: String, $paymentMethodUuid: String, $paymentMethodType: PaymentMethodType, $deviceTimezone: String, $paymentMethodBrand: String, $submitPlatform: String) {
|
|
24
|
+
createOrderFromCart(
|
|
25
|
+
cartId: $cartId
|
|
26
|
+
total: $total
|
|
27
|
+
sosDeliveryFee: $sosDeliveryFee
|
|
28
|
+
isPickupOrder: $isPickupOrder
|
|
29
|
+
verifiedAgeRequirement: $verifiedAgeRequirement
|
|
30
|
+
deliveryTime: $deliveryTime
|
|
31
|
+
menuOptions: $menuOptions
|
|
32
|
+
stripeToken: $stripeToken
|
|
33
|
+
attributionData: $attributionData
|
|
34
|
+
fulfillsOwnDeliveries: $fulfillsOwnDeliveries
|
|
35
|
+
budgetId: $budgetId
|
|
36
|
+
teamId: $teamId
|
|
37
|
+
giftOptions: $giftOptions
|
|
38
|
+
recipientShippingDetails: $recipientShippingDetails
|
|
39
|
+
storeId: $storeId
|
|
40
|
+
tipAmounts: $tipAmounts
|
|
41
|
+
paymentMethod: $paymentMethod
|
|
42
|
+
deliveryOptionType: $deliveryOptionType
|
|
43
|
+
workOrderOptions: $workOrderOptions
|
|
44
|
+
isCardPayment: $isCardPayment
|
|
45
|
+
clientFraudContext: $clientFraudContext
|
|
46
|
+
programId: $programId
|
|
47
|
+
membershipId: $membershipId
|
|
48
|
+
dropoffPreferences: $dropoffPreferences
|
|
49
|
+
routineReorderDetails: $routineReorderDetails
|
|
50
|
+
supplementalPaymentDetailsList: $supplementalPaymentDetailsList
|
|
51
|
+
monitoringContext: $monitoringContext
|
|
52
|
+
rewardBalanceApplied: $rewardBalanceApplied
|
|
53
|
+
deliveryOptionInfo: $deliveryOptionInfo
|
|
54
|
+
hasAccessibilityRequirements: $hasAccessibilityRequirements
|
|
55
|
+
shouldApplyCredits: $shouldApplyCredits
|
|
56
|
+
dasherPickupInstructions: $dasherPickupInstructions
|
|
57
|
+
paymentMethodUuid: $paymentMethodUuid
|
|
58
|
+
paymentMethodType: $paymentMethodType
|
|
59
|
+
deviceTimezone: $deviceTimezone
|
|
60
|
+
paymentMethodBrand: $paymentMethodBrand
|
|
61
|
+
submitPlatform: $submitPlatform
|
|
62
|
+
) {
|
|
63
|
+
cartId
|
|
64
|
+
orderUuid
|
|
65
|
+
isFirstOrderCart
|
|
66
|
+
isFirstNewVerticalsOrderCart
|
|
67
|
+
__typename
|
|
68
|
+
}
|
|
69
|
+
}`;
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// PAYMENT_METHODS_QUERY
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
export const PAYMENT_METHODS_QUERY = `
|
|
76
|
+
query paymentMethodQuery {
|
|
77
|
+
getPaymentMethodList {
|
|
78
|
+
id
|
|
79
|
+
type
|
|
80
|
+
last4
|
|
81
|
+
isDefault
|
|
82
|
+
paymentMethodUuid
|
|
83
|
+
__typename
|
|
84
|
+
}
|
|
85
|
+
}`;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Barrel re-export of all DoorDash GraphQL queries.
|
|
3
|
+
* Individual query modules are split by domain:
|
|
4
|
+
* - search-queries.ts — search and homepage discovery
|
|
5
|
+
* - store-queries.ts — store pages, item details, retail feeds
|
|
6
|
+
* - cart-queries.ts — cart CRUD (add, remove, update, list, detail)
|
|
7
|
+
* - order-queries.ts — checkout, delivery options, payment methods
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
ADD_CART_ITEM_QUERY,
|
|
12
|
+
DETAILED_CART_QUERY,
|
|
13
|
+
LIST_CARTS_QUERY,
|
|
14
|
+
REMOVE_CART_ITEM_QUERY,
|
|
15
|
+
UPDATE_CART_ITEM_QUERY,
|
|
16
|
+
} from "./cart-queries.js";
|
|
17
|
+
export {
|
|
18
|
+
CREATE_ORDER_FROM_CART_QUERY,
|
|
19
|
+
DROPOFF_OPTIONS_QUERY,
|
|
20
|
+
PAYMENT_METHODS_QUERY,
|
|
21
|
+
} from "./order-queries.js";
|
|
22
|
+
export { HOME_PAGE_QUERY, SEARCH_QUERY } from "./search-queries.js";
|
|
23
|
+
export {
|
|
24
|
+
ITEM_PAGE_QUERY,
|
|
25
|
+
RETAIL_SEARCH_QUERY,
|
|
26
|
+
RETAIL_STORE_FEED_QUERY,
|
|
27
|
+
STORE_PAGE_QUERY,
|
|
28
|
+
} from "./store-queries.js";
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extracts GraphQL queries from a session recording and persists them
|
|
3
|
+
* to disk so the DoorDash client can use real, captured queries instead
|
|
4
|
+
* of stale static fallbacks.
|
|
5
|
+
*
|
|
6
|
+
* Captured queries are saved to ~/.vellum/workspace/data/doordash/captured-queries.json
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
|
|
12
|
+
import { getDataDir } from "./shared/platform.js";
|
|
13
|
+
import type { SessionRecording } from "./shared/recording-types.js";
|
|
14
|
+
|
|
15
|
+
export interface CapturedQuery {
|
|
16
|
+
operationName: string;
|
|
17
|
+
query: string;
|
|
18
|
+
exampleVariables: unknown;
|
|
19
|
+
capturedAt: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getCapturedQueriesPath(): string {
|
|
23
|
+
return join(getDataDir(), "doordash", "captured-queries.json");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Extract GraphQL queries from a session recording's network entries.
|
|
28
|
+
* Filters for /graphql/ URLs, parses postData, deduplicates by operation name
|
|
29
|
+
* (keeps last occurrence).
|
|
30
|
+
*/
|
|
31
|
+
export function extractQueries(recording: SessionRecording): CapturedQuery[] {
|
|
32
|
+
const byName = new Map<string, CapturedQuery>();
|
|
33
|
+
|
|
34
|
+
for (const entry of recording.networkEntries) {
|
|
35
|
+
const url = entry.request.url;
|
|
36
|
+
if (!url.includes("/graphql/") && !url.includes("/graphql?")) continue;
|
|
37
|
+
if (!entry.request.postData) continue;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const body = JSON.parse(entry.request.postData) as {
|
|
41
|
+
operationName?: string;
|
|
42
|
+
query?: string;
|
|
43
|
+
variables?: unknown;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
if (!body.operationName || !body.query) continue;
|
|
47
|
+
|
|
48
|
+
byName.set(body.operationName, {
|
|
49
|
+
operationName: body.operationName,
|
|
50
|
+
query: body.query,
|
|
51
|
+
exampleVariables: body.variables ?? null,
|
|
52
|
+
capturedAt: entry.timestamp,
|
|
53
|
+
});
|
|
54
|
+
} catch {
|
|
55
|
+
// Skip entries with unparseable postData
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Array.from(byName.values());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Merge new captured queries with existing ones on disk (newer wins),
|
|
64
|
+
* then write to disk.
|
|
65
|
+
*/
|
|
66
|
+
export function saveQueries(queries: CapturedQuery[]): string {
|
|
67
|
+
const existing = loadCapturedQueries();
|
|
68
|
+
|
|
69
|
+
for (const q of queries) {
|
|
70
|
+
const prev = existing[q.operationName];
|
|
71
|
+
if (!prev || q.capturedAt >= prev.capturedAt) {
|
|
72
|
+
existing[q.operationName] = q;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const filePath = getCapturedQueriesPath();
|
|
77
|
+
mkdirSync(join(filePath, ".."), { recursive: true });
|
|
78
|
+
writeFileSync(filePath, JSON.stringify(existing, null, 2), "utf-8");
|
|
79
|
+
return filePath;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Load captured queries from disk. Returns a map keyed by operation name.
|
|
84
|
+
*/
|
|
85
|
+
export function loadCapturedQueries(): Record<string, CapturedQuery> {
|
|
86
|
+
const filePath = getCapturedQueriesPath();
|
|
87
|
+
if (!existsSync(filePath)) return {};
|
|
88
|
+
try {
|
|
89
|
+
const data = readFileSync(filePath, "utf-8");
|
|
90
|
+
return JSON.parse(data) as Record<string, CapturedQuery>;
|
|
91
|
+
} catch {
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL queries for DoorDash search and homepage discovery.
|
|
3
|
+
* Each query is fully self-contained with all required fragment definitions.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// SEARCH_QUERY
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
export const SEARCH_QUERY = `
|
|
11
|
+
query autocompleteFacetFeed($query: String!, $serializedBundleGlobalSearchContext: String) {
|
|
12
|
+
autocompleteFacetFeed(
|
|
13
|
+
query: $query
|
|
14
|
+
serializedBundleGlobalSearchContext: $serializedBundleGlobalSearchContext
|
|
15
|
+
) {
|
|
16
|
+
...FacetFeedV2ResultFragment
|
|
17
|
+
__typename
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
fragment FacetFeedV2ResultFragment on FacetFeedV2Result {
|
|
22
|
+
body {
|
|
23
|
+
id
|
|
24
|
+
header { ...FacetV2Fragment __typename }
|
|
25
|
+
body { ...FacetV2Fragment __typename }
|
|
26
|
+
layout { omitFooter __typename }
|
|
27
|
+
__typename
|
|
28
|
+
}
|
|
29
|
+
page { ...FacetV2PageFragment __typename }
|
|
30
|
+
header { ...FacetV2Fragment __typename }
|
|
31
|
+
footer { ...FacetV2Fragment __typename }
|
|
32
|
+
custom logging __typename
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fragment FacetV2Fragment on FacetV2 {
|
|
36
|
+
...FacetV2BaseFragment
|
|
37
|
+
childrenMap { ...FacetV2BaseFragment __typename }
|
|
38
|
+
__typename
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fragment FacetV2BaseFragment on FacetV2 {
|
|
42
|
+
id childrenCount
|
|
43
|
+
component { id category __typename }
|
|
44
|
+
name
|
|
45
|
+
text {
|
|
46
|
+
title
|
|
47
|
+
titleTextAttributes { textStyle textColor __typename }
|
|
48
|
+
subtitle
|
|
49
|
+
subtitleTextAttributes { textStyle textColor __typename }
|
|
50
|
+
accessory
|
|
51
|
+
accessoryTextAttributes { textStyle textColor __typename }
|
|
52
|
+
description
|
|
53
|
+
descriptionTextAttributes { textStyle textColor __typename }
|
|
54
|
+
custom { key value __typename }
|
|
55
|
+
__typename
|
|
56
|
+
}
|
|
57
|
+
images {
|
|
58
|
+
main { ...FacetV2ImageFragment __typename }
|
|
59
|
+
icon { ...FacetV2ImageFragment __typename }
|
|
60
|
+
background { ...FacetV2ImageFragment __typename }
|
|
61
|
+
accessory { ...FacetV2ImageFragment __typename }
|
|
62
|
+
custom { key value { ...FacetV2ImageFragment __typename } __typename }
|
|
63
|
+
__typename
|
|
64
|
+
}
|
|
65
|
+
events { click { name data __typename } __typename }
|
|
66
|
+
style {
|
|
67
|
+
spacing background_color
|
|
68
|
+
border { color width style __typename }
|
|
69
|
+
sizeClass dlsType __typename
|
|
70
|
+
}
|
|
71
|
+
layout {
|
|
72
|
+
omitFooter
|
|
73
|
+
gridSpecs {
|
|
74
|
+
Mobile { ...FacetV2LayoutGridFragment __typename }
|
|
75
|
+
Phablet { ...FacetV2LayoutGridFragment __typename }
|
|
76
|
+
Tablet { ...FacetV2LayoutGridFragment __typename }
|
|
77
|
+
Desktop { ...FacetV2LayoutGridFragment __typename }
|
|
78
|
+
WideScreen { ...FacetV2LayoutGridFragment __typename }
|
|
79
|
+
UltraWideScreen { ...FacetV2LayoutGridFragment __typename }
|
|
80
|
+
__typename
|
|
81
|
+
}
|
|
82
|
+
dlsPadding { top right bottom left __typename }
|
|
83
|
+
__typename
|
|
84
|
+
}
|
|
85
|
+
custom logging __typename
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fragment FacetV2ImageFragment on FacetV2Image {
|
|
89
|
+
uri videoUri placeholder local style logging
|
|
90
|
+
events { click { name data __typename } __typename }
|
|
91
|
+
__typename
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
fragment FacetV2LayoutGridFragment on FacetV2LayoutGrid {
|
|
95
|
+
interRowSpacing interColumnSpacing minDimensionCount __typename
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
fragment FacetV2PageFragment on FacetV2Page {
|
|
99
|
+
next { name data __typename }
|
|
100
|
+
onLoad { name data __typename }
|
|
101
|
+
__typename
|
|
102
|
+
}`;
|
|
103
|
+
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
// HOME_PAGE_QUERY
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
export const HOME_PAGE_QUERY = `
|
|
109
|
+
query homePageFacetFeed($cursor: String, $filterQuery: String, $displayHeader: Boolean, $isDebug: Boolean, $cuisineFilterVerticalIds: String) {
|
|
110
|
+
homePageFacetFeed(
|
|
111
|
+
cursor: $cursor
|
|
112
|
+
filterQuery: $filterQuery
|
|
113
|
+
displayHeader: $displayHeader
|
|
114
|
+
isDebug: $isDebug
|
|
115
|
+
cuisineFilterVerticalIds: $cuisineFilterVerticalIds
|
|
116
|
+
) {
|
|
117
|
+
...FacetFeedV2ResultFragment
|
|
118
|
+
__typename
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fragment FacetFeedV2ResultFragment on FacetFeedV2Result {
|
|
123
|
+
body {
|
|
124
|
+
id
|
|
125
|
+
header { ...FacetV2Fragment __typename }
|
|
126
|
+
body { ...FacetV2Fragment __typename }
|
|
127
|
+
layout { omitFooter __typename }
|
|
128
|
+
__typename
|
|
129
|
+
}
|
|
130
|
+
page { ...FacetV2PageFragment __typename }
|
|
131
|
+
header { ...FacetV2Fragment __typename }
|
|
132
|
+
footer { ...FacetV2Fragment __typename }
|
|
133
|
+
custom logging __typename
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
fragment FacetV2Fragment on FacetV2 {
|
|
137
|
+
...FacetV2BaseFragment
|
|
138
|
+
childrenMap { ...FacetV2BaseFragment __typename }
|
|
139
|
+
__typename
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fragment FacetV2BaseFragment on FacetV2 {
|
|
143
|
+
id childrenCount
|
|
144
|
+
component { id category __typename }
|
|
145
|
+
name
|
|
146
|
+
text {
|
|
147
|
+
title
|
|
148
|
+
titleTextAttributes { textStyle textColor __typename }
|
|
149
|
+
subtitle
|
|
150
|
+
subtitleTextAttributes { textStyle textColor __typename }
|
|
151
|
+
accessory
|
|
152
|
+
accessoryTextAttributes { textStyle textColor __typename }
|
|
153
|
+
description
|
|
154
|
+
descriptionTextAttributes { textStyle textColor __typename }
|
|
155
|
+
custom { key value __typename }
|
|
156
|
+
__typename
|
|
157
|
+
}
|
|
158
|
+
images {
|
|
159
|
+
main { ...FacetV2ImageFragment __typename }
|
|
160
|
+
icon { ...FacetV2ImageFragment __typename }
|
|
161
|
+
background { ...FacetV2ImageFragment __typename }
|
|
162
|
+
accessory { ...FacetV2ImageFragment __typename }
|
|
163
|
+
custom { key value { ...FacetV2ImageFragment __typename } __typename }
|
|
164
|
+
__typename
|
|
165
|
+
}
|
|
166
|
+
events { click { name data __typename } __typename }
|
|
167
|
+
style {
|
|
168
|
+
spacing background_color
|
|
169
|
+
border { color width style __typename }
|
|
170
|
+
sizeClass dlsType __typename
|
|
171
|
+
}
|
|
172
|
+
layout {
|
|
173
|
+
omitFooter
|
|
174
|
+
gridSpecs {
|
|
175
|
+
Mobile { ...FacetV2LayoutGridFragment __typename }
|
|
176
|
+
Phablet { ...FacetV2LayoutGridFragment __typename }
|
|
177
|
+
Tablet { ...FacetV2LayoutGridFragment __typename }
|
|
178
|
+
Desktop { ...FacetV2LayoutGridFragment __typename }
|
|
179
|
+
WideScreen { ...FacetV2LayoutGridFragment __typename }
|
|
180
|
+
UltraWideScreen { ...FacetV2LayoutGridFragment __typename }
|
|
181
|
+
__typename
|
|
182
|
+
}
|
|
183
|
+
dlsPadding { top right bottom left __typename }
|
|
184
|
+
__typename
|
|
185
|
+
}
|
|
186
|
+
custom logging __typename
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
fragment FacetV2ImageFragment on FacetV2Image {
|
|
190
|
+
uri videoUri placeholder local style logging
|
|
191
|
+
events { click { name data __typename } __typename }
|
|
192
|
+
__typename
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
fragment FacetV2LayoutGridFragment on FacetV2LayoutGrid {
|
|
196
|
+
interRowSpacing interColumnSpacing minDimensionCount __typename
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
fragment FacetV2PageFragment on FacetV2Page {
|
|
200
|
+
next { name data __typename }
|
|
201
|
+
onLoad { name data __typename }
|
|
202
|
+
__typename
|
|
203
|
+
}`;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DoorDash session persistence.
|
|
3
|
+
* Stores/loads auth cookies from a recording or manual login.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
existsSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
unlinkSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
import { ConfigError } from "./shared/errors.js";
|
|
16
|
+
import { getDataDir } from "./shared/platform.js";
|
|
17
|
+
import type {
|
|
18
|
+
ExtractedCredential,
|
|
19
|
+
SessionRecording,
|
|
20
|
+
} from "./shared/recording-types.js";
|
|
21
|
+
|
|
22
|
+
export interface DoorDashSession {
|
|
23
|
+
cookies: ExtractedCredential[];
|
|
24
|
+
importedAt: string;
|
|
25
|
+
recordingId?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getSessionDir(): string {
|
|
29
|
+
return join(getDataDir(), "doordash");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getSessionPath(): string {
|
|
33
|
+
return join(getSessionDir(), "session.json");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function loadSession(): DoorDashSession | null {
|
|
37
|
+
const path = getSessionPath();
|
|
38
|
+
if (!existsSync(path)) return null;
|
|
39
|
+
try {
|
|
40
|
+
return JSON.parse(readFileSync(path, "utf-8")) as DoorDashSession;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function saveSession(session: DoorDashSession): void {
|
|
47
|
+
const dir = getSessionDir();
|
|
48
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
49
|
+
writeFileSync(getSessionPath(), JSON.stringify(session, null, 2));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function clearSession(): void {
|
|
53
|
+
const path = getSessionPath();
|
|
54
|
+
if (existsSync(path)) {
|
|
55
|
+
unlinkSync(path);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Import cookies from a Ride Shotgun recording file.
|
|
61
|
+
*/
|
|
62
|
+
export function importFromRecording(recordingPath: string): DoorDashSession {
|
|
63
|
+
if (!existsSync(recordingPath)) {
|
|
64
|
+
throw new ConfigError(`Recording not found: ${recordingPath}`);
|
|
65
|
+
}
|
|
66
|
+
const recording = JSON.parse(
|
|
67
|
+
readFileSync(recordingPath, "utf-8"),
|
|
68
|
+
) as SessionRecording;
|
|
69
|
+
if (!recording.cookies?.length) {
|
|
70
|
+
throw new ConfigError("Recording contains no cookies");
|
|
71
|
+
}
|
|
72
|
+
const session: DoorDashSession = {
|
|
73
|
+
cookies: recording.cookies,
|
|
74
|
+
importedAt: new Date().toISOString(),
|
|
75
|
+
recordingId: recording.id,
|
|
76
|
+
};
|
|
77
|
+
saveSession(session);
|
|
78
|
+
return session;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Build a Cookie header string from the session.
|
|
83
|
+
*/
|
|
84
|
+
export function getCookieHeader(session: DoorDashSession): string {
|
|
85
|
+
return session.cookies.map((c) => `${c.name}=${c.value}`).join("; ");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Get the CSRF token from session cookies.
|
|
90
|
+
*/
|
|
91
|
+
export function getCsrfToken(session: DoorDashSession): string | undefined {
|
|
92
|
+
return session.cookies.find((c) => c.name === "csrf_token")?.value;
|
|
93
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inlined error classes used by the DoorDash skill.
|
|
3
|
+
* Subset of assistant/src/util/errors.ts — kept minimal.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export enum ErrorCode {
|
|
7
|
+
PROVIDER_ERROR = "PROVIDER_ERROR",
|
|
8
|
+
CONFIG_ERROR = "CONFIG_ERROR",
|
|
9
|
+
RATE_LIMIT_ERROR = "RATE_LIMIT_ERROR",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class VellumError extends Error {
|
|
13
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
14
|
+
super(message, options);
|
|
15
|
+
this.name = "VellumError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class AssistantError extends VellumError {
|
|
20
|
+
constructor(
|
|
21
|
+
message: string,
|
|
22
|
+
public readonly code: ErrorCode,
|
|
23
|
+
options?: { cause?: unknown },
|
|
24
|
+
) {
|
|
25
|
+
super(message, options);
|
|
26
|
+
this.name = "AssistantError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class BackendError extends VellumError {
|
|
31
|
+
constructor(message: string, options?: ErrorOptions) {
|
|
32
|
+
super(message, options);
|
|
33
|
+
this.name = "BackendError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class ProviderError extends AssistantError {
|
|
38
|
+
constructor(
|
|
39
|
+
message: string,
|
|
40
|
+
public readonly provider: string,
|
|
41
|
+
public readonly statusCode?: number,
|
|
42
|
+
options?: { cause?: unknown },
|
|
43
|
+
) {
|
|
44
|
+
super(message, ErrorCode.PROVIDER_ERROR, options);
|
|
45
|
+
this.name = "ProviderError";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class RateLimitError extends BackendError {
|
|
50
|
+
constructor(message: string) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.name = "RateLimitError";
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class ConfigError extends AssistantError {
|
|
57
|
+
constructor(message: string, options?: { cause?: unknown }) {
|
|
58
|
+
super(message, ErrorCode.CONFIG_ERROR, options);
|
|
59
|
+
this.name = "ConfigError";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal IPC serialization/parsing for daemon communication.
|
|
3
|
+
* Inlined from assistant/src/daemon/ipc-protocol.ts (stripped of type dependencies).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function serialize(msg: Record<string, unknown>): string {
|
|
7
|
+
return JSON.stringify(msg) + "\n";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function createMessageParser() {
|
|
11
|
+
let buffer = "";
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
feed(data: string): Array<Record<string, unknown>> {
|
|
15
|
+
buffer += data;
|
|
16
|
+
const lines = buffer.split("\n");
|
|
17
|
+
buffer = lines.pop() ?? "";
|
|
18
|
+
const results: Array<Record<string, unknown>> = [];
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
const trimmed = line.trim();
|
|
21
|
+
if (trimmed) {
|
|
22
|
+
try {
|
|
23
|
+
results.push(JSON.parse(trimmed) as Record<string, unknown>);
|
|
24
|
+
} catch {
|
|
25
|
+
// Skip malformed messages
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return results;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|