@vellumai/assistant 0.4.37 → 0.4.41
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 +3 -3
- package/README.md +13 -13
- package/bun.lock +80 -24
- package/docs/architecture/integrations.md +126 -128
- package/docs/runbook-trusted-contacts.md +1 -1
- package/docs/trusted-contact-access.md +12 -12
- package/package.json +3 -1
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +0 -14
- package/src/__tests__/app-bundler.test.ts +209 -0
- package/src/__tests__/app-compiler.test.ts +279 -0
- package/src/__tests__/app-executors.test.ts +293 -483
- package/src/__tests__/app-migration.test.ts +148 -0
- package/src/__tests__/app-routes-csp.test.ts +202 -0
- package/src/__tests__/avatar-e2e.test.ts +452 -0
- package/src/__tests__/avatar-generator.test.ts +193 -0
- package/src/__tests__/avatar-router.test.ts +186 -0
- package/src/__tests__/browser-download-timeout.test.ts +28 -0
- package/src/__tests__/bundled-skill-retrieval-guard.test.ts +9 -9
- package/src/__tests__/call-domain.test.ts +3 -7
- package/src/__tests__/credential-security-e2e.test.ts +19 -12
- package/src/__tests__/credentials-cli.test.ts +30 -4
- package/src/__tests__/guardian-verify-setup-skill-regression.test.ts +1 -1
- package/src/__tests__/handlers-slack-config.test.ts +0 -72
- package/src/__tests__/handlers-telegram-config.test.ts +19 -12
- package/src/__tests__/handlers-twitter-config.test.ts +105 -48
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/integration-status.test.ts +15 -5
- package/src/__tests__/integrations-cli.test.ts +1 -1
- package/src/__tests__/invite-redemption-service.test.ts +62 -7
- package/src/__tests__/ipc-snapshot.test.ts +0 -8
- package/src/__tests__/managed-avatar-client.test.ts +280 -0
- package/src/__tests__/mcp-cli.test.ts +3 -3
- package/src/__tests__/oauth-cli.test.ts +203 -0
- package/src/__tests__/relay-server.test.ts +3 -3
- package/src/__tests__/secret-onetime-send.test.ts +19 -12
- package/src/__tests__/secure-keys.test.ts +78 -0
- package/src/__tests__/session-messaging-secret-redirect.test.ts +3 -0
- package/src/__tests__/slack-channel-config.test.ts +23 -16
- package/src/__tests__/slack-share-routes.test.ts +263 -0
- package/src/__tests__/sms-messaging-provider.test.ts +3 -1
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +7 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +10 -10
- package/src/__tests__/twilio-config.test.ts +15 -36
- package/src/__tests__/twilio-provider.test.ts +4 -0
- package/src/__tests__/twitter-auth-handler.test.ts +27 -14
- package/src/__tests__/twitter-cli-error-shaping.test.ts +1 -1
- package/src/__tests__/twitter-cli-routing.test.ts +38 -53
- package/src/__tests__/twitter-oauth-client.test.ts +18 -47
- package/src/__tests__/voice-invite-redemption.test.ts +27 -3
- package/src/amazon/cart.ts +1 -1
- package/src/amazon/client.ts +89 -7
- package/src/approvals/guardian-request-resolvers.ts +2 -2
- package/src/bundler/app-bundler.ts +77 -32
- package/src/bundler/app-compiler.ts +195 -0
- package/src/bundler/manifest.ts +1 -1
- package/src/bundler/package-resolver.ts +185 -0
- package/src/calls/call-domain.ts +4 -14
- package/src/calls/relay-server.ts +2 -2
- package/src/calls/twilio-config.ts +5 -24
- package/src/calls/twilio-rest.ts +19 -5
- package/src/cli/amazon.ts +74 -249
- package/src/cli/audit.ts +2 -2
- package/src/cli/autonomy.ts +9 -9
- package/src/cli/channels.ts +5 -5
- package/src/cli/completions.ts +27 -27
- package/src/cli/config.ts +14 -14
- package/src/cli/contacts.ts +27 -27
- package/src/cli/credentials.ts +28 -28
- package/src/cli/dev.ts +2 -2
- package/src/cli/doctor.ts +2 -2
- package/src/cli/email.ts +82 -82
- package/src/cli/influencer.ts +13 -13
- package/src/cli/integrations.ts +19 -144
- package/src/cli/keys.ts +10 -10
- package/src/cli/map.ts +4 -4
- package/src/cli/mcp.ts +17 -17
- package/src/cli/memory.ts +18 -18
- package/src/cli/notifications.ts +13 -13
- package/src/cli/oauth.ts +77 -0
- package/src/cli/program.ts +2 -0
- package/src/cli/sequence.ts +27 -27
- package/src/cli/sessions.ts +12 -12
- package/src/cli/trust.ts +8 -8
- package/src/cli/twitter.ts +124 -70
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/agentmail/SKILL.md +34 -34
- package/src/config/bundled-skills/amazon/SKILL.md +54 -54
- package/src/config/bundled-skills/app-builder/SKILL.md +137 -3
- package/src/config/bundled-skills/app-builder/tools/app-create.ts +10 -4
- package/src/config/bundled-skills/configure-settings/SKILL.md +18 -18
- package/src/config/bundled-skills/contacts/SKILL.md +12 -12
- package/src/config/bundled-skills/doordash/lib/client.ts +7 -9
- package/src/config/bundled-skills/email-setup/SKILL.md +4 -4
- package/src/config/bundled-skills/frontend-design/icon.svg +16 -0
- package/src/config/bundled-skills/google-oauth-setup/SKILL.md +143 -162
- package/src/config/bundled-skills/guardian-verify-setup/SKILL.md +4 -4
- package/src/config/bundled-skills/influencer/SKILL.md +13 -13
- package/src/config/bundled-skills/mcp-setup/SKILL.md +11 -11
- package/src/config/bundled-skills/phone-calls/SKILL.md +48 -54
- package/src/config/bundled-skills/public-ingress/SKILL.md +6 -6
- package/src/config/bundled-skills/slack-app-setup/SKILL.md +1 -1
- package/src/config/bundled-skills/sms-setup/SKILL.md +3 -3
- package/src/config/bundled-skills/telegram-setup/SKILL.md +2 -2
- package/src/config/bundled-skills/twilio-setup/SKILL.md +136 -225
- package/src/config/bundled-skills/twitter/SKILL.md +68 -44
- package/src/config/bundled-skills/voice-setup/SKILL.md +2 -2
- package/src/config/core-schema.ts +26 -0
- package/src/config/env.ts +4 -0
- package/src/config/feature-flag-registry.json +9 -1
- package/src/config/schema.ts +8 -0
- package/src/config/system-prompt.ts +6 -3
- package/src/config/templates/BOOTSTRAP.md +7 -5
- package/src/contacts/contacts-write.ts +5 -1
- package/src/daemon/handlers/apps.ts +31 -4
- package/src/daemon/handlers/config-ingress.ts +3 -3
- package/src/daemon/handlers/config-integrations.ts +120 -49
- package/src/daemon/handlers/config-slack-channel.ts +26 -7
- package/src/daemon/handlers/config-slack.ts +1 -54
- package/src/daemon/handlers/config-telegram.ts +28 -10
- package/src/daemon/handlers/config.ts +1 -4
- package/src/daemon/handlers/twitter-auth.ts +11 -4
- package/src/daemon/ipc-contract/apps.ts +0 -13
- package/src/daemon/ipc-contract-inventory.json +0 -2
- package/src/daemon/lifecycle.ts +8 -1
- package/src/daemon/session-messaging.ts +2 -2
- package/src/daemon/tool-side-effects.ts +30 -0
- package/src/email/providers/agentmail.ts +1 -1
- package/src/email/providers/index.ts +1 -1
- package/src/email/service.ts +1 -1
- package/src/gallery/default-gallery.ts +538 -0
- package/src/gallery/gallery-manifest.ts +5 -1
- package/src/influencer/client.ts +8 -6
- package/src/mcp/client.ts +1 -1
- package/src/media/avatar-router.ts +99 -0
- package/src/media/avatar-types.ts +60 -0
- package/src/media/managed-avatar-client.ts +189 -0
- package/src/memory/app-migration.ts +114 -0
- package/src/memory/app-store.ts +11 -0
- package/src/memory/qdrant-client.ts +1 -1
- package/src/messaging/providers/slack/client.ts +12 -2
- package/src/messaging/providers/sms/adapter.ts +6 -10
- package/src/migrations/data-layout.ts +8 -1
- package/src/oauth/token-persistence.ts +9 -6
- package/src/runtime/assistant-scope.ts +5 -0
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/channel-readiness-service.ts +9 -4
- package/src/runtime/gateway-internal-client.ts +11 -3
- package/src/runtime/http-server.ts +2 -0
- package/src/runtime/invite-redemption-service.ts +23 -13
- package/src/runtime/middleware/twilio-validation.ts +2 -2
- package/src/runtime/routes/app-routes.ts +131 -3
- package/src/runtime/routes/inbound-stages/verification-intercept.ts +3 -3
- package/src/runtime/routes/integration-routes.ts +2 -2
- package/src/runtime/routes/slack-share-routes.ts +235 -0
- package/src/runtime/routes/twilio-routes.ts +47 -34
- package/src/schedule/integration-status.ts +2 -3
- package/src/security/token-manager.ts +11 -3
- package/src/tools/apps/executors.ts +116 -8
- package/src/tools/browser/browser-manager.ts +30 -2
- package/src/tools/browser/chrome-cdp.ts +31 -3
- package/src/tools/credentials/vault.ts +9 -7
- package/src/tools/executor.ts +4 -0
- package/src/tools/system/avatar-generator.ts +55 -34
- package/src/twitter/client.ts +1 -1
- package/src/twitter/oauth-client.ts +31 -43
- package/src/twitter/router.ts +25 -23
- package/src/util/platform.ts +5 -0
- package/src/slack/slack-webhook.ts +0 -66
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { mkdirSync, renameSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { dirname, join } from "node:path";
|
|
3
4
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
mapGeminiError,
|
|
8
|
-
} from "../../media/gemini-image-service.js";
|
|
5
|
+
import { routedGenerateAvatar } from "../../media/avatar-router.js";
|
|
6
|
+
import { ManagedAvatarError } from "../../media/avatar-types.js";
|
|
7
|
+
import { mapGeminiError } from "../../media/gemini-image-service.js";
|
|
9
8
|
import { RiskLevel } from "../../permissions/types.js";
|
|
10
9
|
import type { ToolDefinition } from "../../providers/types.js";
|
|
11
10
|
import { getLogger } from "../../util/logger.js";
|
|
@@ -66,21 +65,8 @@ export const setAvatarTool: Tool = {
|
|
|
66
65
|
};
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
const config = getConfig();
|
|
70
|
-
const apiKey = config.apiKeys.gemini ?? process.env.GEMINI_API_KEY;
|
|
71
|
-
if (!apiKey) {
|
|
72
|
-
return {
|
|
73
|
-
content:
|
|
74
|
-
"No Gemini API key configured. Please add your Gemini API key in Settings → Models & Services, or set the GEMINI_API_KEY environment variable.",
|
|
75
|
-
isError: true,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
68
|
try {
|
|
80
|
-
log.info(
|
|
81
|
-
{ description: description.trim() },
|
|
82
|
-
"Generating avatar via Gemini",
|
|
83
|
-
);
|
|
69
|
+
log.info({ description: description.trim() }, "Generating avatar");
|
|
84
70
|
|
|
85
71
|
const prompt =
|
|
86
72
|
`Create an avatar image based on this description: ${description.trim()}\n\n` +
|
|
@@ -89,29 +75,31 @@ export const setAvatarTool: Tool = {
|
|
|
89
75
|
"Circular or rounded composition filling the canvas. " +
|
|
90
76
|
"Subtle background color (not white or transparent).";
|
|
91
77
|
|
|
92
|
-
const result = await
|
|
93
|
-
|
|
94
|
-
mode: "generate",
|
|
95
|
-
model: config.imageGenModel,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
if (result.images.length === 0) {
|
|
78
|
+
const result = await routedGenerateAvatar(prompt);
|
|
79
|
+
if (!result.imageBase64) {
|
|
99
80
|
return {
|
|
100
|
-
content: "Error:
|
|
81
|
+
content: "Error: No image data returned. Please try again.",
|
|
101
82
|
isError: true,
|
|
102
83
|
};
|
|
103
84
|
}
|
|
104
|
-
|
|
105
|
-
const image = result.images[0];
|
|
106
|
-
const pngBuffer = Buffer.from(image.dataBase64, "base64");
|
|
85
|
+
const pngBuffer = Buffer.from(result.imageBase64, "base64");
|
|
107
86
|
|
|
108
87
|
const avatarPath = getAvatarPath();
|
|
109
88
|
const avatarDir = dirname(avatarPath);
|
|
110
89
|
|
|
90
|
+
const tmpPath = `${avatarPath}.${randomUUID()}.tmp`;
|
|
111
91
|
mkdirSync(avatarDir, { recursive: true });
|
|
112
|
-
writeFileSync(
|
|
92
|
+
writeFileSync(tmpPath, pngBuffer);
|
|
93
|
+
renameSync(tmpPath, avatarPath);
|
|
113
94
|
|
|
114
|
-
log.info(
|
|
95
|
+
log.info(
|
|
96
|
+
{
|
|
97
|
+
avatarPath,
|
|
98
|
+
pathUsed: result.pathUsed,
|
|
99
|
+
correlationId: result.correlationId,
|
|
100
|
+
},
|
|
101
|
+
"Avatar saved successfully",
|
|
102
|
+
);
|
|
115
103
|
|
|
116
104
|
// Side-effect hook in tool-side-effects.ts broadcasts avatar_updated to all clients.
|
|
117
105
|
|
|
@@ -120,9 +108,42 @@ export const setAvatarTool: Tool = {
|
|
|
120
108
|
isError: false,
|
|
121
109
|
};
|
|
122
110
|
} catch (error) {
|
|
111
|
+
if (error instanceof ManagedAvatarError) {
|
|
112
|
+
if (error.statusCode === 429) {
|
|
113
|
+
log.warn(
|
|
114
|
+
{ correlationId: error.correlationId },
|
|
115
|
+
"Avatar generation rate limited",
|
|
116
|
+
);
|
|
117
|
+
return {
|
|
118
|
+
content:
|
|
119
|
+
"Avatar generation is rate limited. Please wait and try again.",
|
|
120
|
+
isError: true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (error.statusCode === 503) {
|
|
124
|
+
log.warn(
|
|
125
|
+
{ correlationId: error.correlationId },
|
|
126
|
+
"Avatar generation service unavailable",
|
|
127
|
+
);
|
|
128
|
+
return {
|
|
129
|
+
content: "Avatar generation service is temporarily unavailable.",
|
|
130
|
+
isError: true,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
const detail =
|
|
134
|
+
error.message || "Avatar generation failed. Please try again.";
|
|
135
|
+
log.error(
|
|
136
|
+
{ error: detail, correlationId: error.correlationId },
|
|
137
|
+
"Managed avatar generation failed",
|
|
138
|
+
);
|
|
139
|
+
return {
|
|
140
|
+
content: `Avatar generation failed: ${detail}`,
|
|
141
|
+
isError: true,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
123
145
|
const message = mapGeminiError(error);
|
|
124
146
|
log.error({ error: message }, "Avatar generation failed");
|
|
125
|
-
|
|
126
147
|
return {
|
|
127
148
|
content: `Avatar generation failed: ${message}`,
|
|
128
149
|
isError: true,
|
package/src/twitter/client.ts
CHANGED
|
@@ -93,7 +93,7 @@ async function findTwitterTab(): Promise<string> {
|
|
|
93
93
|
const res = await fetch(`${CDP_BASE}/json/list`).catch(() => null);
|
|
94
94
|
if (!res?.ok) {
|
|
95
95
|
throw new SessionExpiredError(
|
|
96
|
-
"Chrome CDP not available. Run `
|
|
96
|
+
"Chrome CDP not available. Run `assistant twitter refresh` first.",
|
|
97
97
|
);
|
|
98
98
|
}
|
|
99
99
|
const targets = (await res.json()) as Array<{
|
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OAuth-backed Twitter API client.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Accepts an OAuth2 Bearer token as a parameter and uses it to execute
|
|
5
5
|
* Twitter API v2 operations directly, without requiring a browser session.
|
|
6
6
|
* Currently supports post and reply; all other operations fall back to the
|
|
7
7
|
* browser-based CDP client.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { getSecureKey } from "../security/secure-keys.js";
|
|
11
|
-
import { withValidToken } from "../security/token-manager.js";
|
|
12
|
-
|
|
13
10
|
const TWITTER_API_BASE = "https://api.x.com/2";
|
|
14
|
-
const SERVICE = "integration:twitter";
|
|
15
11
|
|
|
16
12
|
/** Operations that the OAuth client can handle natively. */
|
|
17
13
|
const SUPPORTED_OPERATIONS = new Set(["post", "reply"]);
|
|
@@ -45,55 +41,47 @@ export class UnsupportedOAuthOperationError extends Error {
|
|
|
45
41
|
/**
|
|
46
42
|
* Post a tweet (or reply) using OAuth2 Bearer token authentication.
|
|
47
43
|
*
|
|
48
|
-
* The
|
|
49
|
-
*
|
|
44
|
+
* The caller is responsible for providing a valid token (e.g. via
|
|
45
|
+
* `assistant oauth token twitter`).
|
|
50
46
|
*/
|
|
51
47
|
export async function oauthPostTweet(
|
|
52
48
|
text: string,
|
|
53
|
-
opts
|
|
49
|
+
opts: { inReplyToTweetId?: string; oauthToken: string },
|
|
54
50
|
): Promise<OAuthPostResult> {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
51
|
+
const body: Record<string, unknown> = { text };
|
|
52
|
+
if (opts.inReplyToTweetId) {
|
|
53
|
+
body.reply = { in_reply_to_tweet_id: opts.inReplyToTweetId };
|
|
54
|
+
}
|
|
60
55
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
56
|
+
const res = await fetch(`${TWITTER_API_BASE}/tweets`, {
|
|
57
|
+
method: "POST",
|
|
58
|
+
headers: {
|
|
59
|
+
Authorization: `Bearer ${opts.oauthToken}`,
|
|
60
|
+
"Content-Type": "application/json",
|
|
61
|
+
},
|
|
62
|
+
body: JSON.stringify(body),
|
|
63
|
+
});
|
|
69
64
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
(err as Error & { status: number }).status = res.status;
|
|
77
|
-
throw err;
|
|
78
|
-
}
|
|
65
|
+
if (!res.ok) {
|
|
66
|
+
const errorBody = await res.text().catch(() => "");
|
|
67
|
+
throw new Error(
|
|
68
|
+
`Twitter API error (${res.status}): ${errorBody.slice(0, 500)}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
79
71
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
});
|
|
72
|
+
const json = (await res.json()) as { data: { id: string; text: string } };
|
|
73
|
+
return {
|
|
74
|
+
tweetId: json.data.id,
|
|
75
|
+
text: json.data.text,
|
|
76
|
+
};
|
|
86
77
|
}
|
|
87
78
|
|
|
88
79
|
/**
|
|
89
|
-
* Check whether OAuth
|
|
90
|
-
*
|
|
91
|
-
* handle refresh if it's expired).
|
|
80
|
+
* Check whether an OAuth token is available.
|
|
81
|
+
* When the caller provides a token string, OAuth is available.
|
|
92
82
|
*/
|
|
93
|
-
export function oauthIsAvailable(): boolean {
|
|
94
|
-
return
|
|
95
|
-
getSecureKey("credential:integration:twitter:access_token") !== undefined
|
|
96
|
-
);
|
|
83
|
+
export function oauthIsAvailable(oauthToken: string | undefined): boolean {
|
|
84
|
+
return oauthToken != null && oauthToken.length > 0;
|
|
97
85
|
}
|
|
98
86
|
|
|
99
87
|
/**
|
package/src/twitter/router.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Strategy router for Twitter operations.
|
|
3
|
-
* Selects OAuth or browser path based on
|
|
3
|
+
* Selects OAuth or browser path based on the caller-provided strategy.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { loadRawConfig } from "../config/loader.js";
|
|
7
6
|
import type { PostTweetResult } from "./client.js";
|
|
8
7
|
import {
|
|
9
8
|
postTweet as browserPostTweet,
|
|
@@ -29,30 +28,23 @@ export interface RoutedError {
|
|
|
29
28
|
alternativeSetupHint?: string;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
function getPreferredStrategy(): TwitterStrategy {
|
|
33
|
-
try {
|
|
34
|
-
const raw = loadRawConfig();
|
|
35
|
-
const strategy = raw.twitterOperationStrategy as string | undefined;
|
|
36
|
-
if (strategy === "oauth" || strategy === "browser") return strategy;
|
|
37
|
-
} catch {
|
|
38
|
-
/* fall through */
|
|
39
|
-
}
|
|
40
|
-
return "auto";
|
|
41
|
-
}
|
|
42
|
-
|
|
43
31
|
export async function routedPostTweet(
|
|
44
32
|
text: string,
|
|
45
|
-
opts
|
|
33
|
+
opts: {
|
|
34
|
+
inReplyToTweetId?: string;
|
|
35
|
+
strategy: TwitterStrategy;
|
|
36
|
+
oauthToken?: string;
|
|
37
|
+
},
|
|
46
38
|
): Promise<RoutedResult<PostTweetResult>> {
|
|
47
|
-
const strategy =
|
|
48
|
-
const operation = opts
|
|
39
|
+
const strategy = opts.strategy;
|
|
40
|
+
const operation = opts.inReplyToTweetId ? "reply" : "post";
|
|
49
41
|
|
|
50
42
|
if (strategy === "oauth") {
|
|
51
43
|
// User explicitly wants OAuth
|
|
52
|
-
if (!oauthIsAvailable()) {
|
|
44
|
+
if (!oauthIsAvailable(opts.oauthToken)) {
|
|
53
45
|
throw Object.assign(
|
|
54
46
|
new Error(
|
|
55
|
-
"OAuth is not configured. Provide your X developer credentials here in the chat to set up OAuth, or switch to browser strategy: `
|
|
47
|
+
"OAuth is not configured. Provide your X developer credentials here in the chat to set up OAuth, or switch to browser strategy: `assistant config set twitter.operationStrategy browser`.",
|
|
56
48
|
),
|
|
57
49
|
{
|
|
58
50
|
pathUsed: "oauth" as const,
|
|
@@ -60,7 +52,10 @@ export async function routedPostTweet(
|
|
|
60
52
|
},
|
|
61
53
|
);
|
|
62
54
|
}
|
|
63
|
-
const result = await oauthPostTweet(text,
|
|
55
|
+
const result = await oauthPostTweet(text, {
|
|
56
|
+
inReplyToTweetId: opts.inReplyToTweetId,
|
|
57
|
+
oauthToken: opts.oauthToken!,
|
|
58
|
+
});
|
|
64
59
|
return {
|
|
65
60
|
result: {
|
|
66
61
|
tweetId: result.tweetId,
|
|
@@ -74,7 +69,9 @@ export async function routedPostTweet(
|
|
|
74
69
|
if (strategy === "browser") {
|
|
75
70
|
// User explicitly wants browser
|
|
76
71
|
try {
|
|
77
|
-
const result = await browserPostTweet(text,
|
|
72
|
+
const result = await browserPostTweet(text, {
|
|
73
|
+
inReplyToTweetId: opts.inReplyToTweetId,
|
|
74
|
+
});
|
|
78
75
|
return { result, pathUsed: "browser" };
|
|
79
76
|
} catch (err) {
|
|
80
77
|
if (err instanceof SessionExpiredError) {
|
|
@@ -89,9 +86,12 @@ export async function routedPostTweet(
|
|
|
89
86
|
|
|
90
87
|
// auto strategy: try OAuth first if available and supported, fallback to browser
|
|
91
88
|
let oauthError: Error | undefined;
|
|
92
|
-
if (oauthIsAvailable() && oauthSupportsOperation(operation)) {
|
|
89
|
+
if (oauthIsAvailable(opts.oauthToken) && oauthSupportsOperation(operation)) {
|
|
93
90
|
try {
|
|
94
|
-
const result = await oauthPostTweet(text,
|
|
91
|
+
const result = await oauthPostTweet(text, {
|
|
92
|
+
inReplyToTweetId: opts.inReplyToTweetId,
|
|
93
|
+
oauthToken: opts.oauthToken!,
|
|
94
|
+
});
|
|
95
95
|
return {
|
|
96
96
|
result: {
|
|
97
97
|
tweetId: result.tweetId,
|
|
@@ -108,7 +108,9 @@ export async function routedPostTweet(
|
|
|
108
108
|
|
|
109
109
|
// Fallback to browser
|
|
110
110
|
try {
|
|
111
|
-
const result = await browserPostTweet(text,
|
|
111
|
+
const result = await browserPostTweet(text, {
|
|
112
|
+
inReplyToTweetId: opts.inReplyToTweetId,
|
|
113
|
+
});
|
|
112
114
|
return { result, pathUsed: "browser" };
|
|
113
115
|
} catch (err) {
|
|
114
116
|
if (err instanceof SessionExpiredError) {
|
package/src/util/platform.ts
CHANGED
|
@@ -85,6 +85,11 @@ export function readLockfile(): Record<string, unknown> | null {
|
|
|
85
85
|
* inbound call path resolves phone numbers to config keys (typically
|
|
86
86
|
* "self"). This function maps any known lockfile assistant ID to "self"
|
|
87
87
|
* so both sides use a consistent DB key.
|
|
88
|
+
*
|
|
89
|
+
* Multi-instance safety: each daemon process runs with a scoped
|
|
90
|
+
* BASE_DATA_DIR, so readLockfile() only sees the lockfile for this
|
|
91
|
+
* instance. The mapping to "self" is correct because each daemon is
|
|
92
|
+
* single-tenant — it only manages its own instance's data.
|
|
88
93
|
*/
|
|
89
94
|
export function normalizeAssistantId(assistantId: string): string {
|
|
90
95
|
if (assistantId === "self") return "self";
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { ProviderError } from "../util/errors.js";
|
|
2
|
-
import { getLogger } from "../util/logger.js";
|
|
3
|
-
|
|
4
|
-
const log = getLogger("slack-webhook");
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Post a rich Block Kit message to a Slack Incoming Webhook URL.
|
|
8
|
-
*
|
|
9
|
-
* Uses the Block Kit format so the message renders nicely in Slack with
|
|
10
|
-
* a header, description section, and context footer.
|
|
11
|
-
*/
|
|
12
|
-
export async function postToSlackWebhook(
|
|
13
|
-
webhookUrl: string,
|
|
14
|
-
appName: string,
|
|
15
|
-
appDescription: string,
|
|
16
|
-
appIcon: string,
|
|
17
|
-
): Promise<void> {
|
|
18
|
-
const blocks = [
|
|
19
|
-
{
|
|
20
|
-
type: "header",
|
|
21
|
-
text: {
|
|
22
|
-
type: "plain_text",
|
|
23
|
-
text: `${appIcon} ${appName}`,
|
|
24
|
-
emoji: true,
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
type: "section",
|
|
29
|
-
text: {
|
|
30
|
-
type: "mrkdwn",
|
|
31
|
-
text: appDescription || "_No description_",
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
type: "context",
|
|
36
|
-
elements: [
|
|
37
|
-
{
|
|
38
|
-
type: "mrkdwn",
|
|
39
|
-
text: `Shared from Vellum Assistant`,
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
},
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
const payload = {
|
|
46
|
-
blocks,
|
|
47
|
-
text: `${appIcon} ${appName}: ${appDescription || "No description"}`,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
log.info({ appName }, "Posting app to Slack webhook");
|
|
51
|
-
|
|
52
|
-
const response = await fetch(webhookUrl, {
|
|
53
|
-
method: "POST",
|
|
54
|
-
headers: { "Content-Type": "application/json" },
|
|
55
|
-
body: JSON.stringify(payload),
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
if (!response.ok) {
|
|
59
|
-
const body = await response.text();
|
|
60
|
-
throw new ProviderError(
|
|
61
|
-
`Slack webhook returned ${response.status}: ${body}`,
|
|
62
|
-
"slack",
|
|
63
|
-
response.status,
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
}
|