@vellumai/assistant 0.5.11 → 0.5.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/Dockerfile +42 -9
- package/docs/architecture/integrations.md +34 -32
- package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
- package/node_modules/@vellumai/ces-contracts/src/index.ts +7 -0
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +5 -0
- package/node_modules/@vellumai/credential-storage/src/index.ts +1 -1
- package/openapi.yaml +87 -9
- package/package.json +1 -1
- package/src/__tests__/catalog-cache.test.ts +164 -0
- package/src/__tests__/catalog-search.test.ts +61 -0
- package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
- package/src/__tests__/conversation-error.test.ts +3 -2
- package/src/__tests__/credential-security-invariants.test.ts +9 -15
- package/src/__tests__/credential-vault-unit.test.ts +32 -34
- package/src/__tests__/credential-vault.test.ts +25 -33
- package/src/__tests__/credentials-cli.test.ts +3 -3
- package/src/__tests__/daemon-credential-client.test.ts +2 -2
- package/src/__tests__/first-greeting.test.ts +7 -0
- package/src/__tests__/host-bash-proxy.test.ts +79 -0
- package/src/__tests__/host-cu-proxy.test.ts +90 -0
- package/src/__tests__/host-file-proxy.test.ts +89 -0
- package/src/__tests__/integration-status.test.ts +5 -5
- package/src/__tests__/list-messages-attachments.test.ts +171 -0
- package/src/__tests__/mcp-abort-signal.test.ts +205 -0
- package/src/__tests__/messaging-send-tool.test.ts +5 -5
- package/src/__tests__/navigate-settings-tab.test.ts +6 -2
- package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
- package/src/__tests__/oauth-cli.test.ts +126 -119
- package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/onboarding-template-contract.test.ts +2 -2
- package/src/__tests__/platform.test.ts +3 -168
- package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
- package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
- package/src/__tests__/skill-feature-flags.test.ts +8 -0
- package/src/__tests__/skill-secret-handling-guard.test.ts +212 -0
- package/src/__tests__/skills-uninstall.test.ts +2 -2
- package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
- package/src/__tests__/slack-share-routes.test.ts +5 -5
- package/src/__tests__/system-prompt.test.ts +39 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -1
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
- package/src/cli/AGENTS.md +47 -7
- package/src/cli/commands/browser-relay.ts +2 -17
- package/src/cli/commands/contacts.ts +6 -4
- package/src/cli/commands/conversations.ts +13 -1
- package/src/cli/commands/credential-execution.ts +16 -1
- package/src/cli/commands/credentials.ts +2 -8
- package/src/cli/commands/oauth/__tests__/connect.test.ts +29 -108
- package/src/cli/commands/oauth/__tests__/disconnect.test.ts +13 -87
- package/src/cli/commands/oauth/__tests__/mode.test.ts +22 -69
- package/src/cli/commands/oauth/__tests__/ping.test.ts +20 -79
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
- package/src/cli/commands/oauth/__tests__/status.test.ts +12 -40
- package/src/cli/commands/oauth/__tests__/token.test.ts +3 -50
- package/src/cli/commands/oauth/apps.ts +63 -44
- package/src/cli/commands/oauth/connect.ts +187 -155
- package/src/cli/commands/oauth/disconnect.ts +27 -75
- package/src/cli/commands/oauth/index.ts +36 -46
- package/src/cli/commands/oauth/mode.ts +22 -34
- package/src/cli/commands/oauth/ping.ts +19 -45
- package/src/cli/commands/oauth/providers.ts +569 -62
- package/src/cli/commands/oauth/request.ts +36 -48
- package/src/cli/commands/oauth/shared.ts +1 -19
- package/src/cli/commands/oauth/status.ts +14 -25
- package/src/cli/commands/oauth/token.ts +25 -34
- package/src/cli/commands/platform/__tests__/connect.test.ts +224 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +237 -0
- package/src/cli/commands/platform/__tests__/status.test.ts +246 -0
- package/src/cli/commands/platform/connect.ts +104 -0
- package/src/cli/commands/platform/disconnect.ts +118 -0
- package/src/cli/commands/{platform.ts → platform/index.ts} +108 -38
- package/src/cli/commands/sequence.ts +5 -4
- package/src/cli/commands/shotgun.ts +16 -0
- package/src/cli/commands/skills.ts +173 -41
- package/src/cli/commands/usage.ts +5 -11
- package/src/cli/lib/daemon-credential-client.ts +22 -38
- package/src/cli/program.ts +1 -1
- package/src/config/assistant-feature-flags.ts +3 -7
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
- package/src/config/bundled-skills/conversations/SKILL.md +20 -0
- package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
- package/src/config/bundled-skills/gmail/SKILL.md +13 -13
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
- package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +7 -7
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
- package/src/config/bundled-skills/settings/TOOLS.json +5 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +4 -2
- package/src/config/bundled-tool-registry.ts +5 -0
- package/src/config/feature-flag-registry.json +2 -2
- package/src/credential-execution/client.ts +15 -3
- package/src/daemon/conversation-agent-loop.ts +2 -0
- package/src/daemon/conversation-error.ts +36 -6
- package/src/daemon/conversation-messaging.ts +9 -0
- package/src/daemon/conversation-runtime-assembly.ts +33 -0
- package/src/daemon/conversation-surfaces.ts +120 -14
- package/src/daemon/conversation.ts +5 -0
- package/src/daemon/first-greeting.ts +6 -1
- package/src/daemon/handlers/skills.ts +148 -3
- package/src/daemon/host-bash-proxy.ts +16 -0
- package/src/daemon/host-cu-proxy.ts +16 -0
- package/src/daemon/host-file-proxy.ts +16 -0
- package/src/daemon/lifecycle.ts +56 -5
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/message-types/guardian-actions.ts +2 -0
- package/src/daemon/message-types/host-bash.ts +6 -1
- package/src/daemon/message-types/host-cu.ts +6 -1
- package/src/daemon/message-types/host-file.ts +6 -1
- package/src/daemon/message-types/integrations.ts +0 -1
- package/src/daemon/server.ts +29 -2
- package/src/hooks/cli.ts +74 -0
- package/src/inbound/platform-callback-registration.ts +7 -12
- package/src/index.ts +0 -12
- package/src/mcp/client.ts +6 -1
- package/src/mcp/manager.ts +2 -1
- package/src/memory/conversation-crud.ts +92 -3
- package/src/memory/conversation-key-store.ts +26 -0
- package/src/memory/conversation-queries.ts +6 -6
- package/src/memory/db-init.ts +16 -0
- package/src/memory/journal-memory.ts +8 -2
- package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
- package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
- package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
- package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
- package/src/memory/migrations/index.ts +4 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/oauth.ts +11 -0
- package/src/messaging/provider.ts +13 -12
- package/src/messaging/providers/gmail/adapter.ts +44 -35
- package/src/messaging/providers/slack/adapter.ts +63 -33
- package/src/messaging/providers/telegram-bot/adapter.ts +6 -8
- package/src/messaging/providers/whatsapp/adapter.ts +6 -8
- package/src/notifications/adapters/telegram.ts +78 -2
- package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
- package/src/oauth/byo-connection.test.ts +22 -24
- package/src/oauth/connect-orchestrator.ts +37 -76
- package/src/oauth/connect-types.ts +7 -65
- package/src/oauth/connection-resolver.test.ts +13 -13
- package/src/oauth/connection-resolver.ts +3 -4
- package/src/oauth/identity-verifier.ts +177 -0
- package/src/oauth/oauth-store.ts +228 -3
- package/src/oauth/platform-connection.test.ts +56 -6
- package/src/oauth/platform-connection.ts +8 -1
- package/src/oauth/seed-providers.ts +247 -34
- package/src/permissions/checker.ts +127 -1
- package/src/prompts/journal-context.ts +4 -1
- package/src/prompts/system-prompt.ts +54 -9
- package/src/prompts/templates/BOOTSTRAP.md +16 -5
- package/src/providers/anthropic/client.ts +2 -33
- package/src/runtime/guardian-action-service.ts +7 -2
- package/src/runtime/http-server.ts +12 -18
- package/src/runtime/http-types.ts +8 -1
- package/src/runtime/migrations/rebind-secrets-screen.ts +2 -2
- package/src/runtime/routes/conversation-management-routes.ts +31 -0
- package/src/runtime/routes/conversation-routes.ts +79 -4
- package/src/runtime/routes/guardian-action-routes.ts +15 -2
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
- package/src/runtime/routes/integrations/slack/share.ts +1 -1
- package/src/runtime/routes/oauth-apps.ts +2 -1
- package/src/runtime/routes/secret-routes.ts +45 -15
- package/src/runtime/routes/settings-routes.ts +12 -19
- package/src/runtime/routes/skills-routes.ts +45 -4
- package/src/schedule/integration-status.ts +2 -2
- package/src/security/ces-rpc-credential-backend.ts +19 -16
- package/src/security/oauth-completion-page.ts +153 -0
- package/src/security/oauth2.ts +3 -17
- package/src/security/secure-keys.ts +207 -7
- package/src/security/token-manager.ts +3 -6
- package/src/signals/bash.ts +6 -1
- package/src/skills/catalog-cache.ts +44 -0
- package/src/skills/catalog-search.ts +18 -0
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/credentials/post-connect-hooks.ts +1 -1
- package/src/tools/credentials/vault.ts +34 -45
- package/src/tools/host-terminal/host-shell.ts +16 -3
- package/src/tools/mcp/mcp-tool-factory.ts +2 -1
- package/src/tools/skills/sandbox-runner.ts +16 -3
- package/src/tools/terminal/shell.ts +16 -3
- package/src/util/logger.ts +11 -1
- package/src/util/platform.ts +1 -91
- package/src/util/sentry-log-stream.ts +51 -0
- package/src/watcher/providers/github.ts +2 -2
- package/src/watcher/providers/gmail.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +1 -1
- package/src/watcher/providers/linear.ts +2 -2
- package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
- package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/cli/commands/oauth/connections.ts +0 -255
- package/src/oauth/provider-behaviors.ts +0 -634
|
@@ -117,12 +117,12 @@ describe("Slack messaging token resolution", () => {
|
|
|
117
117
|
expect(await slackProvider.isConnected!()).toBe(true);
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
-
test("returns true when only
|
|
120
|
+
test("returns true when only slack has active OAuth connection (backwards compat)", async () => {
|
|
121
121
|
// No bot token
|
|
122
122
|
getSecureKeyAsyncMock.mockImplementation(async () => null);
|
|
123
123
|
// But OAuth provider is connected
|
|
124
124
|
isProviderConnectedMock.mockImplementation(async (service: string) =>
|
|
125
|
-
service === "
|
|
125
|
+
service === "slack" ? true : false,
|
|
126
126
|
);
|
|
127
127
|
|
|
128
128
|
expect(await slackProvider.isConnected!()).toBe(true);
|
|
@@ -139,7 +139,7 @@ describe("Slack messaging token resolution", () => {
|
|
|
139
139
|
// ── slackProvider.resolveConnection() ───────────────────────────────────
|
|
140
140
|
|
|
141
141
|
describe("slackProvider.resolveConnection()", () => {
|
|
142
|
-
test("returns
|
|
142
|
+
test("returns undefined when Socket Mode credentials exist (token cached internally)", async () => {
|
|
143
143
|
getSecureKeyAsyncMock.mockImplementation(async (key: string) =>
|
|
144
144
|
key === "credential/slack_channel/bot_token"
|
|
145
145
|
? "xoxb-socket-token"
|
|
@@ -147,20 +147,20 @@ describe("Slack messaging token resolution", () => {
|
|
|
147
147
|
);
|
|
148
148
|
|
|
149
149
|
const result = await slackProvider.resolveConnection!();
|
|
150
|
-
expect(result).
|
|
150
|
+
expect(result).toBeUndefined();
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
test("returns
|
|
153
|
+
test("returns undefined even without a slack_channel connection row (token-only resilience)", async () => {
|
|
154
154
|
getSecureKeyAsyncMock.mockImplementation(async (key: string) =>
|
|
155
155
|
key === "credential/slack_channel/bot_token" ? "xoxb-token-only" : null,
|
|
156
156
|
);
|
|
157
|
-
// No connection row — resolveConnection should still return
|
|
157
|
+
// No connection row — resolveConnection should still return undefined (token cached internally)
|
|
158
158
|
|
|
159
159
|
const result = await slackProvider.resolveConnection!();
|
|
160
|
-
expect(result).
|
|
160
|
+
expect(result).toBeUndefined();
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
-
test("returns OAuthConnection when only OAuth
|
|
163
|
+
test("returns OAuthConnection when only OAuth slack credentials exist (backwards compat)", async () => {
|
|
164
164
|
getSecureKeyAsyncMock.mockImplementation(async () => null);
|
|
165
165
|
const oauthConn = {
|
|
166
166
|
accessToken: "xoxp-oauth-token",
|
|
@@ -169,16 +169,15 @@ describe("Slack messaging token resolution", () => {
|
|
|
169
169
|
|
|
170
170
|
const result = await slackProvider.resolveConnection!();
|
|
171
171
|
expect(result).toBe(oauthConn);
|
|
172
|
-
expect(resolveOAuthConnectionMock).toHaveBeenCalledWith(
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
);
|
|
172
|
+
expect(resolveOAuthConnectionMock).toHaveBeenCalledWith("slack", {
|
|
173
|
+
account: undefined,
|
|
174
|
+
});
|
|
176
175
|
});
|
|
177
176
|
|
|
178
177
|
test("throws when no credentials exist at all (no Socket Mode, no OAuth)", async () => {
|
|
179
178
|
getSecureKeyAsyncMock.mockImplementation(async () => null);
|
|
180
179
|
resolveOAuthConnectionMock.mockImplementation(async () => {
|
|
181
|
-
throw new Error("No OAuth connection found for
|
|
180
|
+
throw new Error("No OAuth connection found for slack");
|
|
182
181
|
});
|
|
183
182
|
|
|
184
183
|
await expect(slackProvider.resolveConnection!()).rejects.toThrow(
|
|
@@ -190,13 +189,13 @@ describe("Slack messaging token resolution", () => {
|
|
|
190
189
|
// ── getProviderConnection() integration ─────────────────────────────────
|
|
191
190
|
|
|
192
191
|
describe("getProviderConnection()", () => {
|
|
193
|
-
test("returns
|
|
192
|
+
test("returns undefined for Slack when Socket Mode credentials exist (token cached internally)", async () => {
|
|
194
193
|
getSecureKeyAsyncMock.mockImplementation(async (key: string) =>
|
|
195
194
|
key === "credential/slack_channel/bot_token" ? "xoxb-conn-token" : null,
|
|
196
195
|
);
|
|
197
196
|
|
|
198
197
|
const result = await getProviderConnection(slackProvider);
|
|
199
|
-
expect(result).
|
|
198
|
+
expect(result).toBeUndefined();
|
|
200
199
|
});
|
|
201
200
|
|
|
202
201
|
test("returns OAuthConnection for Slack when only OAuth credentials exist (backwards compat)", async () => {
|
|
@@ -221,9 +220,9 @@ describe("Slack messaging token resolution", () => {
|
|
|
221
220
|
);
|
|
222
221
|
});
|
|
223
222
|
|
|
224
|
-
test(
|
|
223
|
+
test("Telegram returns undefined (no resolveConnection, uses isConnected path — regression check)", async () => {
|
|
225
224
|
// Telegram has isConnected but no resolveConnection.
|
|
226
|
-
// When isConnected returns true, getProviderConnection returns
|
|
225
|
+
// When isConnected returns true, getProviderConnection returns undefined
|
|
227
226
|
getSecureKeyAsyncMock.mockImplementation(async (key: string) => {
|
|
228
227
|
if (key === "credential/telegram/bot_token") return "bot-token";
|
|
229
228
|
if (key === "credential/telegram/webhook_secret") return "secret";
|
|
@@ -234,7 +233,7 @@ describe("Slack messaging token resolution", () => {
|
|
|
234
233
|
);
|
|
235
234
|
|
|
236
235
|
const result = await getProviderConnection(telegramBotMessagingProvider);
|
|
237
|
-
expect(result).
|
|
236
|
+
expect(result).toBeUndefined();
|
|
238
237
|
});
|
|
239
238
|
|
|
240
239
|
test("Gmail still calls resolveOAuthConnection (no resolveConnection, no isConnected — regression check)", async () => {
|
|
@@ -247,10 +246,9 @@ describe("Slack messaging token resolution", () => {
|
|
|
247
246
|
|
|
248
247
|
const result = await getProviderConnection(gmailMessagingProvider);
|
|
249
248
|
expect(result).toBe(oauthConn);
|
|
250
|
-
expect(resolveOAuthConnectionMock).toHaveBeenCalledWith(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
);
|
|
249
|
+
expect(resolveOAuthConnectionMock).toHaveBeenCalledWith("google", {
|
|
250
|
+
account: undefined,
|
|
251
|
+
});
|
|
254
252
|
});
|
|
255
253
|
});
|
|
256
254
|
|
|
@@ -264,7 +262,7 @@ describe("Slack messaging token resolution", () => {
|
|
|
264
262
|
);
|
|
265
263
|
// Gmail connected via OAuth
|
|
266
264
|
isProviderConnectedMock.mockImplementation(async (service: string) =>
|
|
267
|
-
service === "
|
|
265
|
+
service === "google" ? true : false,
|
|
268
266
|
);
|
|
269
267
|
|
|
270
268
|
await expect(resolveProvider()).rejects.toThrow(
|
|
@@ -285,7 +283,7 @@ describe("Slack messaging token resolution", () => {
|
|
|
285
283
|
test("auto-selects Gmail when it is the only connected provider (no Slack credentials)", async () => {
|
|
286
284
|
getSecureKeyAsyncMock.mockImplementation(async () => null);
|
|
287
285
|
isProviderConnectedMock.mockImplementation(async (service: string) =>
|
|
288
|
-
service === "
|
|
286
|
+
service === "google" ? true : false,
|
|
289
287
|
);
|
|
290
288
|
|
|
291
289
|
const provider = await resolveProvider();
|
|
@@ -111,7 +111,7 @@ describe("handleListSlackChannels", () => {
|
|
|
111
111
|
});
|
|
112
112
|
|
|
113
113
|
test("returns channels sorted by type then name", async () => {
|
|
114
|
-
connectionByProvider["
|
|
114
|
+
connectionByProvider["slack"] = { id: "conn-slack-1" };
|
|
115
115
|
secureKeyValues.set(
|
|
116
116
|
"oauth_connection/conn-slack-1/access_token",
|
|
117
117
|
"xoxb-test",
|
|
@@ -192,7 +192,7 @@ describe("handleShareToSlackChannel", () => {
|
|
|
192
192
|
});
|
|
193
193
|
|
|
194
194
|
test("returns 400 for malformed JSON", async () => {
|
|
195
|
-
connectionByProvider["
|
|
195
|
+
connectionByProvider["slack"] = { id: "conn-slack-1" };
|
|
196
196
|
secureKeyValues.set(
|
|
197
197
|
"oauth_connection/conn-slack-1/access_token",
|
|
198
198
|
"xoxb-test",
|
|
@@ -207,7 +207,7 @@ describe("handleShareToSlackChannel", () => {
|
|
|
207
207
|
});
|
|
208
208
|
|
|
209
209
|
test("returns 400 when missing required fields", async () => {
|
|
210
|
-
connectionByProvider["
|
|
210
|
+
connectionByProvider["slack"] = { id: "conn-slack-1" };
|
|
211
211
|
secureKeyValues.set(
|
|
212
212
|
"oauth_connection/conn-slack-1/access_token",
|
|
213
213
|
"xoxb-test",
|
|
@@ -220,7 +220,7 @@ describe("handleShareToSlackChannel", () => {
|
|
|
220
220
|
});
|
|
221
221
|
|
|
222
222
|
test("returns 404 when app not found", async () => {
|
|
223
|
-
connectionByProvider["
|
|
223
|
+
connectionByProvider["slack"] = { id: "conn-slack-1" };
|
|
224
224
|
secureKeyValues.set(
|
|
225
225
|
"oauth_connection/conn-slack-1/access_token",
|
|
226
226
|
"xoxb-test",
|
|
@@ -232,7 +232,7 @@ describe("handleShareToSlackChannel", () => {
|
|
|
232
232
|
});
|
|
233
233
|
|
|
234
234
|
test("posts message and returns success", async () => {
|
|
235
|
-
connectionByProvider["
|
|
235
|
+
connectionByProvider["slack"] = { id: "conn-slack-1" };
|
|
236
236
|
secureKeyValues.set(
|
|
237
237
|
"oauth_connection/conn-slack-1/access_token",
|
|
238
238
|
"xoxb-test",
|
|
@@ -24,6 +24,7 @@ mock.module("../util/platform.js", () => ({
|
|
|
24
24
|
getWorkspaceConfigPath: () => join(TEST_DIR, "config.json"),
|
|
25
25
|
getWorkspaceSkillsDir: () => join(TEST_DIR, "skills"),
|
|
26
26
|
getWorkspaceHooksDir: () => join(TEST_DIR, "hooks"),
|
|
27
|
+
getConversationsDir: () => join(TEST_DIR, "conversations"),
|
|
27
28
|
getWorkspacePromptPath: (file: string) => join(TEST_DIR, file),
|
|
28
29
|
ensureDataDir: () => {},
|
|
29
30
|
getPidPath: () => join(TEST_DIR, "vellum.pid"),
|
|
@@ -560,4 +561,42 @@ describe("ensurePromptFiles", () => {
|
|
|
560
561
|
const bootstrapPath = join(TEST_DIR, "BOOTSTRAP.md");
|
|
561
562
|
expect(existsSync(bootstrapPath)).toBe(false);
|
|
562
563
|
});
|
|
564
|
+
|
|
565
|
+
test("auto-deletes stale BOOTSTRAP.md when prior conversations exist", () => {
|
|
566
|
+
// Simulate a non-first-run workspace: core files + BOOTSTRAP.md still present
|
|
567
|
+
writeFileSync(join(TEST_DIR, "IDENTITY.md"), "My identity");
|
|
568
|
+
writeFileSync(join(TEST_DIR, "SOUL.md"), "My soul");
|
|
569
|
+
writeFileSync(join(TEST_DIR, "USER.md"), "My user");
|
|
570
|
+
writeFileSync(join(TEST_DIR, "BOOTSTRAP.md"), "# Stale bootstrap");
|
|
571
|
+
|
|
572
|
+
// Create a conversations directory with at least one entry
|
|
573
|
+
const convDir = join(TEST_DIR, "conversations");
|
|
574
|
+
mkdirSync(convDir, { recursive: true });
|
|
575
|
+
writeFileSync(join(convDir, "conv-001.json"), "{}");
|
|
576
|
+
|
|
577
|
+
ensurePromptFiles();
|
|
578
|
+
|
|
579
|
+
expect(existsSync(join(TEST_DIR, "BOOTSTRAP.md"))).toBe(false);
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
test("keeps BOOTSTRAP.md on first run even if conversations dir exists", () => {
|
|
583
|
+
// First run: no core files exist, BOOTSTRAP.md should be created and kept
|
|
584
|
+
const convDir = join(TEST_DIR, "conversations");
|
|
585
|
+
mkdirSync(convDir, { recursive: true });
|
|
586
|
+
writeFileSync(join(convDir, "conv-001.json"), "{}");
|
|
587
|
+
|
|
588
|
+
ensurePromptFiles();
|
|
589
|
+
|
|
590
|
+
expect(existsSync(join(TEST_DIR, "BOOTSTRAP.md"))).toBe(true);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
test("keeps BOOTSTRAP.md when no conversations exist yet", () => {
|
|
594
|
+
// Non-first-run but no conversations — user hasn't chatted yet
|
|
595
|
+
writeFileSync(join(TEST_DIR, "IDENTITY.md"), "My identity");
|
|
596
|
+
writeFileSync(join(TEST_DIR, "BOOTSTRAP.md"), "# Bootstrap");
|
|
597
|
+
|
|
598
|
+
ensurePromptFiles();
|
|
599
|
+
|
|
600
|
+
expect(existsSync(join(TEST_DIR, "BOOTSTRAP.md"))).toBe(true);
|
|
601
|
+
});
|
|
563
602
|
});
|
|
@@ -205,7 +205,7 @@ function makeSystemPrompt(size: "small" | "production" = "small"): string {
|
|
|
205
205
|
"### OAuth Setup",
|
|
206
206
|
"Most integrations use OAuth for authentication.",
|
|
207
207
|
"Guide the user through the OAuth flow when setting up a new integration:",
|
|
208
|
-
"1. Navigate to Settings >
|
|
208
|
+
"1. Navigate to Settings > Models & Services",
|
|
209
209
|
"2. Click 'Connect' for the desired service",
|
|
210
210
|
"3. Authorize in the browser popup",
|
|
211
211
|
"4. Confirm the connection is active",
|
|
@@ -284,20 +284,21 @@ describe("011-backfill-installation-id migration", () => {
|
|
|
284
284
|
expect(parsed.assistants[0].installationId).toBe("sqlite-id");
|
|
285
285
|
});
|
|
286
286
|
|
|
287
|
-
test("
|
|
287
|
+
test("ignores BASE_DATA_DIR and always reads lockfile from homedir", () => {
|
|
288
288
|
process.env.BASE_DATA_DIR = "/custom-base";
|
|
289
289
|
getMemoryCheckpointFn.mockReturnValue("sqlite-id");
|
|
290
290
|
|
|
291
|
-
|
|
291
|
+
// Lockfile under BASE_DATA_DIR should be ignored — the migration
|
|
292
|
+
// always reads from homedir() (per-user, not per-instance).
|
|
292
293
|
setupFs({
|
|
293
|
-
[
|
|
294
|
+
[LOCK_PATH]: makeLockfile([{ assistantId: "my-assistant" }]),
|
|
294
295
|
});
|
|
295
296
|
|
|
296
297
|
backfillInstallationIdMigration.run(WORKSPACE_DIR);
|
|
297
298
|
|
|
298
299
|
expect(writeFileSyncFn).toHaveBeenCalledTimes(1);
|
|
299
300
|
const [path] = writeFileSyncFn.mock.calls[0] as [string, string];
|
|
300
|
-
expect(path).toBe(
|
|
301
|
+
expect(path).toBe(LOCK_PATH);
|
|
301
302
|
});
|
|
302
303
|
|
|
303
304
|
test("preserves other assistants in lockfile when writing", () => {
|
package/src/cli/AGENTS.md
CHANGED
|
@@ -59,6 +59,46 @@ does and how to use it.
|
|
|
59
59
|
4. **Use Commander's `.addHelpText("after", ...)`** for extended help. Don't
|
|
60
60
|
cram everything into `.description()`.
|
|
61
61
|
|
|
62
|
+
### No Redundant Command Lists in `addHelpText`
|
|
63
|
+
|
|
64
|
+
Commander already renders a `Commands:` section from registered subcommands.
|
|
65
|
+
Never duplicate that list in `.addHelpText("after", ...)`. The `addHelpText`
|
|
66
|
+
block is for **supplementary context only** — domain notes, key concepts, and
|
|
67
|
+
examples. Repeating command names and descriptions wastes vertical space and
|
|
68
|
+
creates a maintenance burden (two places to update when a subcommand changes).
|
|
69
|
+
|
|
70
|
+
**Bad:**
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
oauth.addHelpText(
|
|
74
|
+
"after",
|
|
75
|
+
`
|
|
76
|
+
The oauth command group manages the full OAuth lifecycle:
|
|
77
|
+
|
|
78
|
+
connect Initiate an OAuth flow for a provider
|
|
79
|
+
disconnect Disconnect an OAuth provider
|
|
80
|
+
...
|
|
81
|
+
`,
|
|
82
|
+
);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Good:**
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
oauth.addHelpText(
|
|
89
|
+
"after",
|
|
90
|
+
`
|
|
91
|
+
Providers are seeded on startup for built-in integrations. Apps and connections
|
|
92
|
+
are created during the OAuth authorization flow or can be managed manually via
|
|
93
|
+
their respective subcommands.
|
|
94
|
+
|
|
95
|
+
Examples:
|
|
96
|
+
$ assistant oauth connect google --open-browser
|
|
97
|
+
$ assistant oauth status google
|
|
98
|
+
`,
|
|
99
|
+
);
|
|
100
|
+
```
|
|
101
|
+
|
|
62
102
|
### ID and Key Arguments
|
|
63
103
|
|
|
64
104
|
Options that accept IDs, keys, or opaque identifiers must include a short note
|
|
@@ -79,12 +119,12 @@ users and AI agents have no way to know what to pass.
|
|
|
79
119
|
|
|
80
120
|
Common discovery patterns:
|
|
81
121
|
|
|
82
|
-
| Argument type | Discovery command
|
|
83
|
-
| ------------- |
|
|
84
|
-
| Provider key | `assistant oauth providers list`
|
|
85
|
-
| Connection ID | `assistant oauth
|
|
86
|
-
| OAuth app ID | `assistant oauth apps list`
|
|
87
|
-
| Contact ID | `assistant contacts list`
|
|
122
|
+
| Argument type | Discovery command |
|
|
123
|
+
| ------------- | ----------------------------------- |
|
|
124
|
+
| Provider key | `assistant oauth providers list` |
|
|
125
|
+
| Connection ID | `assistant oauth status <provider>` |
|
|
126
|
+
| OAuth app ID | `assistant oauth apps list` |
|
|
127
|
+
| Contact ID | `assistant contacts list` |
|
|
88
128
|
|
|
89
129
|
### Error Messages
|
|
90
130
|
|
|
@@ -104,7 +144,7 @@ throw new Error("Connection not found");
|
|
|
104
144
|
|
|
105
145
|
```ts
|
|
106
146
|
throw new Error(
|
|
107
|
-
`Connection "${id}" not found. Run 'assistant oauth
|
|
147
|
+
`Connection "${id}" not found. Run 'assistant oauth status <provider>' to see available connections.`,
|
|
108
148
|
);
|
|
109
149
|
```
|
|
110
150
|
|
|
@@ -124,12 +124,6 @@ uses a dedicated user data directory at ~/Library/Application Support/Google/Chr
|
|
|
124
124
|
and defaults to port 9222. Commands are routed through a Chrome extension
|
|
125
125
|
relay that bridges the assistant to open Chrome tabs.
|
|
126
126
|
|
|
127
|
-
Subgroups:
|
|
128
|
-
relay Send commands to Chrome tabs via the browser extension relay
|
|
129
|
-
launch Launch or connect to a Chrome CDP instance
|
|
130
|
-
minimize Minimize the Chrome CDP window
|
|
131
|
-
restore Restore the Chrome CDP window
|
|
132
|
-
|
|
133
127
|
Examples:
|
|
134
128
|
$ assistant browser chrome launch
|
|
135
129
|
$ assistant browser chrome launch --start-url "https://example.com" --port 9333
|
|
@@ -150,17 +144,8 @@ Examples:
|
|
|
150
144
|
`
|
|
151
145
|
Routes commands to Chrome tabs through the browser extension relay. The relay
|
|
152
146
|
connects the assistant to a Chrome extension that can inspect and control
|
|
153
|
-
browser tabs.
|
|
154
|
-
|
|
155
|
-
Available subcommands:
|
|
156
|
-
find-tab Find a tab matching a URL pattern
|
|
157
|
-
new-tab Open a new tab with a URL
|
|
158
|
-
navigate Navigate an existing tab to a new URL
|
|
159
|
-
evaluate Execute JavaScript in a tab
|
|
160
|
-
get-cookies Fetch cookies for a domain
|
|
161
|
-
set-cookie Set a cookie
|
|
162
|
-
screenshot Capture a screenshot of a tab
|
|
163
|
-
status Check browser extension relay connection status
|
|
147
|
+
browser tabs. Commands support URL glob patterns for tab discovery and
|
|
148
|
+
JavaScript evaluation with stdin piping for long scripts.
|
|
164
149
|
|
|
165
150
|
Examples:
|
|
166
151
|
$ assistant browser chrome relay find-tab --url "*://*.amazon.com/*"
|
|
@@ -132,7 +132,7 @@ Examples:
|
|
|
132
132
|
"after",
|
|
133
133
|
`
|
|
134
134
|
Arguments:
|
|
135
|
-
id UUID of the contact to retrieve
|
|
135
|
+
id UUID of the contact to retrieve. Run 'assistant contacts list' to find IDs.
|
|
136
136
|
|
|
137
137
|
Returns the full contact record including role, display name, and all
|
|
138
138
|
channel memberships (phone numbers, Telegram IDs, email addresses, etc.).
|
|
@@ -174,7 +174,8 @@ Examples:
|
|
|
174
174
|
"after",
|
|
175
175
|
`
|
|
176
176
|
Arguments:
|
|
177
|
-
keepId UUID of the surviving contact that will absorb the other
|
|
177
|
+
keepId UUID of the surviving contact that will absorb the other.
|
|
178
|
+
Run 'assistant contacts list' to find IDs.
|
|
178
179
|
mergeId UUID of the contact to be merged and deleted
|
|
179
180
|
|
|
180
181
|
All channel memberships, conversation history, and metadata from mergeId
|
|
@@ -331,7 +332,8 @@ Examples:
|
|
|
331
332
|
"after",
|
|
332
333
|
`
|
|
333
334
|
Arguments:
|
|
334
|
-
channelId UUID of the contact channel to update
|
|
335
|
+
channelId UUID of the contact channel to update. Run 'assistant contacts get <contactId>'
|
|
336
|
+
to see a contact's channel IDs.
|
|
335
337
|
|
|
336
338
|
Updates the access-control fields on an existing channel. At least one of
|
|
337
339
|
--status or --policy must be provided.
|
|
@@ -590,7 +592,7 @@ Examples:
|
|
|
590
592
|
"after",
|
|
591
593
|
`
|
|
592
594
|
Arguments:
|
|
593
|
-
inviteId UUID of the invite to revoke
|
|
595
|
+
inviteId UUID of the invite to revoke. Run 'assistant contacts invites list' to find IDs.
|
|
594
596
|
|
|
595
597
|
Revokes an active invite so it can no longer be redeemed. Already-redeemed
|
|
596
598
|
channel memberships are not affected. Returns the updated invite record.
|
|
@@ -12,6 +12,7 @@ import { ensureDaemonRunning } from "../../daemon/lifecycle.js";
|
|
|
12
12
|
import { formatJson, formatMarkdown } from "../../export/formatter.js";
|
|
13
13
|
import {
|
|
14
14
|
clearAll as clearAllConversations,
|
|
15
|
+
countConversationsByScheduleJobId,
|
|
15
16
|
createConversation,
|
|
16
17
|
getConversation,
|
|
17
18
|
getMessages,
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
loadOrCreateSigningKey,
|
|
30
31
|
mintDaemonDeliveryToken,
|
|
31
32
|
} from "../../runtime/auth/token-service.js";
|
|
33
|
+
import { deleteSchedule } from "../../schedule/schedule-store.js";
|
|
32
34
|
import { timeAgo } from "../../util/time.js";
|
|
33
35
|
import { initializeDb } from "../db.js";
|
|
34
36
|
import { log } from "../logger.js";
|
|
@@ -119,7 +121,7 @@ Arguments:
|
|
|
119
121
|
conversationId Optional conversation ID (or unique prefix). Defaults to the
|
|
120
122
|
most recent conversation. Supports prefix matching — e.g.
|
|
121
123
|
"abc123" matches the first conversation whose ID starts with
|
|
122
|
-
"abc123".
|
|
124
|
+
"abc123". Run 'assistant conversations list' to find IDs.
|
|
123
125
|
|
|
124
126
|
Two output formats are available:
|
|
125
127
|
md Markdown conversation transcript (default). Human-readable rendering
|
|
@@ -275,6 +277,7 @@ Examples:
|
|
|
275
277
|
`
|
|
276
278
|
Arguments:
|
|
277
279
|
conversationId Conversation ID (or unique prefix). Supports prefix matching.
|
|
280
|
+
Run 'assistant conversations list' to find IDs.
|
|
278
281
|
|
|
279
282
|
Permanently wipes the conversation and reverts all memory changes it caused:
|
|
280
283
|
restores superseded memory items, deletes conversation summaries, and cancels
|
|
@@ -353,6 +356,15 @@ Examples:
|
|
|
353
356
|
return;
|
|
354
357
|
}
|
|
355
358
|
|
|
359
|
+
// Cancel the associated schedule job (if any) before wiping —
|
|
360
|
+
// but only when this is the last conversation referencing it.
|
|
361
|
+
if (
|
|
362
|
+
conversation.scheduleJobId &&
|
|
363
|
+
countConversationsByScheduleJobId(conversation.scheduleJobId) <= 1
|
|
364
|
+
) {
|
|
365
|
+
deleteSchedule(conversation.scheduleJobId);
|
|
366
|
+
}
|
|
367
|
+
|
|
356
368
|
// Daemon not running — safe to wipe directly (no in-memory state).
|
|
357
369
|
const result = wipeConversation(conversation.id);
|
|
358
370
|
|
|
@@ -129,6 +129,20 @@ export function registerCredentialExecutionCommand(program: Command): void {
|
|
|
129
129
|
)
|
|
130
130
|
.option("--json", "Machine-readable compact JSON output");
|
|
131
131
|
|
|
132
|
+
ce.addHelpText(
|
|
133
|
+
"after",
|
|
134
|
+
`
|
|
135
|
+
The Credential Execution Service (CES) mediates all secret-bearing operations.
|
|
136
|
+
Grants authorize specific credential handles for constrained purposes, and
|
|
137
|
+
audit records log each credentialed operation. Neither grants nor audit records
|
|
138
|
+
ever contain raw secret values — only sanitized metadata.
|
|
139
|
+
|
|
140
|
+
Examples:
|
|
141
|
+
$ assistant credential-execution grants list
|
|
142
|
+
$ assistant credential-execution grants revoke <grantId>
|
|
143
|
+
$ assistant credential-execution audit list`,
|
|
144
|
+
);
|
|
145
|
+
|
|
132
146
|
// -------------------------------------------------------------------------
|
|
133
147
|
// grants
|
|
134
148
|
// -------------------------------------------------------------------------
|
|
@@ -215,7 +229,8 @@ that grant for credentialed operations. The grant is permanently removed
|
|
|
215
229
|
from CES state.
|
|
216
230
|
|
|
217
231
|
Arguments:
|
|
218
|
-
grantId The stable grant identifier (UUID)
|
|
232
|
+
grantId The stable grant identifier (UUID). Run 'assistant credential-execution
|
|
233
|
+
grants list' to find grant IDs.
|
|
219
234
|
|
|
220
235
|
Examples:
|
|
221
236
|
$ assistant credential-execution grants revoke 7a3b1c2d-4e5f-6789-abcd-ef0123456789
|
|
@@ -397,10 +397,7 @@ Examples:
|
|
|
397
397
|
credential
|
|
398
398
|
.command("set <value>")
|
|
399
399
|
.description("Store a secret and create or update its metadata")
|
|
400
|
-
.requiredOption(
|
|
401
|
-
"--service <service>",
|
|
402
|
-
"Service namespace (e.g. integration:google)",
|
|
403
|
-
)
|
|
400
|
+
.requiredOption("--service <service>", "Service namespace (e.g. google)")
|
|
404
401
|
.requiredOption("--field <field>", "Field name (e.g. client_secret)")
|
|
405
402
|
.option("--label <label>", 'Human-friendly label (e.g. "prod", "work")')
|
|
406
403
|
.option("--description <description>", "What this credential is used for")
|
|
@@ -511,10 +508,7 @@ Examples:
|
|
|
511
508
|
`${service}:${field}`,
|
|
512
509
|
);
|
|
513
510
|
if (secretResult === "error") {
|
|
514
|
-
writeError(
|
|
515
|
-
cmd,
|
|
516
|
-
"Failed to delete credential from secure storage",
|
|
517
|
-
);
|
|
511
|
+
writeError(cmd, "Failed to delete credential from secure storage");
|
|
518
512
|
process.exitCode = 1;
|
|
519
513
|
return;
|
|
520
514
|
}
|