@vellumai/assistant 0.12.2-staging.1 → 0.12.2-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/node_modules/@vellumai/slack-text/src/index.ts +13 -8
- package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
- package/openapi.yaml +35 -1
- package/package.json +1 -1
- package/src/__tests__/always-loaded-tools-guard.test.ts +5 -5
- package/src/__tests__/conversation-runtime-assembly.test.ts +28 -0
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
- package/src/__tests__/credential-routes.test.ts +38 -0
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/cu-unified-flow.test.ts +6 -2
- package/src/__tests__/host-cu-proxy.test.ts +69 -12
- package/src/__tests__/oauth-commands-routes.test.ts +55 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
- package/src/__tests__/subagent-tool-gate-mode.test.ts +51 -0
- package/src/__tests__/ui-channel-variants.test.ts +1 -56
- package/src/acp/__tests__/acp-claude-oauth.test.ts +257 -6
- package/src/acp/__tests__/acp-credentials.test.ts +13 -0
- package/src/acp/__tests__/claude-token-refresh.test.ts +257 -0
- package/src/acp/__tests__/prepare-agent-env.test.ts +60 -1
- package/src/acp/acp-claude-oauth.ts +328 -14
- package/src/acp/acp-credentials.ts +19 -0
- package/src/acp/claude-token-refresh.ts +150 -0
- package/src/acp/prepare-agent-env.ts +21 -6
- package/src/calls/__tests__/voice-control-protocol.test.ts +62 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +19 -0
- package/src/calls/voice-control-protocol.ts +102 -0
- package/src/calls/voice-session-bridge.ts +33 -26
- package/src/calls/voice-triage-escalate.ts +3 -3
- package/src/cli/commands/oauth/status.ts +5 -0
- package/src/config/bundled-skills/computer-use/SKILL.md +13 -4
- package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
- package/src/config/feature-flag-registry.json +9 -1
- package/src/config/loader.ts +1 -0
- package/src/config/schemas/services.ts +10 -0
- package/src/daemon/conversation-runtime-assembly.ts +18 -6
- package/src/daemon/conversation-surfaces.ts +6 -1
- package/src/daemon/conversation-tool-setup.ts +8 -16
- package/src/daemon/host-cu-proxy.ts +43 -5
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +121 -0
- package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
- package/src/live-voice/__tests__/live-voice-progress.test.ts +79 -0
- package/src/live-voice/__tests__/protocol.test.ts +39 -0
- package/src/live-voice/__tests__/session-controls.test.ts +79 -0
- package/src/live-voice/live-voice-session.ts +85 -7
- package/src/live-voice/protocol.ts +60 -0
- package/src/live-voice/session-controls.ts +111 -0
- package/src/oauth/__tests__/seed-providers-managed.test.ts +95 -0
- package/src/oauth/connection-resolver.test.ts +27 -0
- package/src/oauth/connection-resolver.ts +25 -1
- package/src/oauth/provider-serializer.ts +9 -0
- package/src/oauth/seed-providers.ts +110 -2
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +74 -4
- package/src/runtime/routes/acp-claude-auth-routes.ts +11 -3
- package/src/runtime/routes/app-management-routes.ts +17 -2
- package/src/runtime/routes/credential-routes.ts +1 -4
- package/src/runtime/routes/oauth-commands-routes.ts +14 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/tools/computer-use/definitions.ts +1 -1
- package/src/tools/ui-surface/channel-variants.ts +10 -59
- package/src/watch/watch-retro.ts +6 -8
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* has landed.
|
|
19
19
|
* POST /v1/acp/claude/auth/exchange — complete a manual/cloud flow: accept the
|
|
20
20
|
* pasted `code#state` (or a raw code + state), exchange it, and store the
|
|
21
|
-
* Claude OAuth token.
|
|
21
|
+
* Claude OAuth token set (access token plus any refresh token and expiry).
|
|
22
22
|
*
|
|
23
23
|
* `handleStartAuth` picks loopback vs. manual: a containerized host, or a client
|
|
24
24
|
* that sets `preferManual` because its browser can't reach the daemon's
|
|
@@ -126,7 +126,11 @@ async function handleStartLocalAuth(): Promise<StartResponse> {
|
|
|
126
126
|
// can react. Runs in the background — the web client opens `authorize_url`.
|
|
127
127
|
void flow.completion
|
|
128
128
|
.then(async (result) => {
|
|
129
|
-
await storeAcpClaudeToken(
|
|
129
|
+
await storeAcpClaudeToken({
|
|
130
|
+
accessToken: result.tokens.accessToken,
|
|
131
|
+
refreshToken: result.tokens.refreshToken,
|
|
132
|
+
expiresIn: result.tokens.expiresIn,
|
|
133
|
+
});
|
|
130
134
|
pendingFlows.mark(flow.state, "connected");
|
|
131
135
|
log.info("ACP Claude local OAuth flow connected");
|
|
132
136
|
})
|
|
@@ -216,7 +220,11 @@ async function handleExchange(args: RouteHandlerArgs): Promise<{ ok: true }> {
|
|
|
216
220
|
pending.codeVerifier,
|
|
217
221
|
state,
|
|
218
222
|
);
|
|
219
|
-
await storeAcpClaudeToken(
|
|
223
|
+
await storeAcpClaudeToken({
|
|
224
|
+
accessToken: result.tokens.accessToken,
|
|
225
|
+
refreshToken: result.tokens.refreshToken,
|
|
226
|
+
expiresIn: result.tokens.expiresIn,
|
|
227
|
+
});
|
|
220
228
|
} catch (err) {
|
|
221
229
|
const message = err instanceof Error ? err.message : String(err);
|
|
222
230
|
pendingFlows.mark(state, "error", { error: message });
|
|
@@ -181,7 +181,7 @@ function workspaceAppItem(a: AppDefinition): AppListItem {
|
|
|
181
181
|
description: a.description,
|
|
182
182
|
icon: a.icon,
|
|
183
183
|
createdAt: a.createdAt,
|
|
184
|
-
updatedAt: a.updatedAt,
|
|
184
|
+
updatedAt: appContentUpdatedAt(getAppDirPath(a.id), a.updatedAt),
|
|
185
185
|
version: a.version ?? "1.0.0",
|
|
186
186
|
contentId: computeContentId(a.name),
|
|
187
187
|
origin: "workspace",
|
|
@@ -203,13 +203,28 @@ function pluginAppItem(app: EnumeratedApp): AppListItem {
|
|
|
203
203
|
id: app.id,
|
|
204
204
|
name: app.name,
|
|
205
205
|
createdAt: mtime,
|
|
206
|
-
updatedAt: mtime,
|
|
206
|
+
updatedAt: appContentUpdatedAt(app.sourcePath, mtime),
|
|
207
207
|
version: "1.0.0",
|
|
208
208
|
contentId: computeContentId(app.name),
|
|
209
209
|
origin: `plugin:${pluginName}`,
|
|
210
210
|
};
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
/** The newest persisted metadata or compiled-output revision for an app. */
|
|
214
|
+
function appContentUpdatedAt(
|
|
215
|
+
sourcePath: string,
|
|
216
|
+
metadataUpdatedAt: number,
|
|
217
|
+
): number {
|
|
218
|
+
try {
|
|
219
|
+
return Math.max(
|
|
220
|
+
metadataUpdatedAt,
|
|
221
|
+
statSync(join(sourcePath, "dist")).mtimeMs,
|
|
222
|
+
);
|
|
223
|
+
} catch {
|
|
224
|
+
return metadataUpdatedAt;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
213
228
|
function getAppDataResult(
|
|
214
229
|
method: string,
|
|
215
230
|
appId: string,
|
|
@@ -34,7 +34,6 @@ import { credentialKey } from "../../security/credential-key.js";
|
|
|
34
34
|
import {
|
|
35
35
|
deleteSecureKeyAsync,
|
|
36
36
|
getActiveBackendInfoAsync,
|
|
37
|
-
getSecureKeyAsync,
|
|
38
37
|
getSecureKeyResultAsync,
|
|
39
38
|
} from "../../security/secure-keys.js";
|
|
40
39
|
import {
|
|
@@ -561,9 +560,7 @@ async function handleCredentialsDelete({ body }: RouteHandlerArgs) {
|
|
|
561
560
|
|
|
562
561
|
const key = credentialKey(service, field);
|
|
563
562
|
const affectedConnections = assertCredentialNotInUse(key, force === true);
|
|
564
|
-
const
|
|
565
|
-
const deleteResult =
|
|
566
|
-
existing != null ? await deleteSecureKeyAsync(key) : "not-found";
|
|
563
|
+
const deleteResult = await deleteSecureKeyAsync(key);
|
|
567
564
|
|
|
568
565
|
if (deleteResult === "error") {
|
|
569
566
|
throw new InternalError(
|
|
@@ -66,6 +66,7 @@ interface PlatformConnectionEntry {
|
|
|
66
66
|
id: string;
|
|
67
67
|
account_label?: string;
|
|
68
68
|
scopes_granted?: string[];
|
|
69
|
+
provider_params?: Record<string, string> | null;
|
|
69
70
|
status?: string;
|
|
70
71
|
}
|
|
71
72
|
|
|
@@ -574,6 +575,9 @@ async function handleStatus({ queryParams = {} }: RouteHandlerArgs) {
|
|
|
574
575
|
account: c.account_label ?? null,
|
|
575
576
|
grantedScopes: c.scopes_granted ?? [],
|
|
576
577
|
status: c.status ?? "ACTIVE",
|
|
578
|
+
// Values the provider scopes the connection by (QuickBooks' realm id),
|
|
579
|
+
// so a caller can address resources the proxy's base URL does not.
|
|
580
|
+
providerParams: c.provider_params ?? {},
|
|
577
581
|
}));
|
|
578
582
|
|
|
579
583
|
return {
|
|
@@ -1060,6 +1064,8 @@ async function handleManagedConnect({ body = {} }: RouteHandlerArgs) {
|
|
|
1060
1064
|
provider: string;
|
|
1061
1065
|
scopes?: string[];
|
|
1062
1066
|
redirect_after_connect?: string;
|
|
1067
|
+
/** Per-tenant providers (Shopify): the customer's own host. */
|
|
1068
|
+
tenant_host?: string;
|
|
1063
1069
|
};
|
|
1064
1070
|
|
|
1065
1071
|
if (!b.provider) {
|
|
@@ -1076,6 +1082,13 @@ async function handleManagedConnect({ body = {} }: RouteHandlerArgs) {
|
|
|
1076
1082
|
}
|
|
1077
1083
|
reqBody.redirect_after_connect =
|
|
1078
1084
|
b.redirect_after_connect ?? "/account/oauth/complete";
|
|
1085
|
+
// Only forwarded when present: the platform validates it against the
|
|
1086
|
+
// provider's pattern and rejects per-tenant providers that omit it.
|
|
1087
|
+
const tenantHost =
|
|
1088
|
+
typeof b.tenant_host === "string" ? b.tenant_host.trim() : "";
|
|
1089
|
+
if (tenantHost) {
|
|
1090
|
+
reqBody.tenant_host = tenantHost;
|
|
1091
|
+
}
|
|
1079
1092
|
|
|
1080
1093
|
const response = await client.fetch(startPath, {
|
|
1081
1094
|
method: "POST",
|
|
@@ -1122,6 +1135,7 @@ async function handleManagedConnectPoll({
|
|
|
1122
1135
|
id: e.id,
|
|
1123
1136
|
account_label: e.account_label ?? null,
|
|
1124
1137
|
scopes_granted: e.scopes_granted ?? [],
|
|
1138
|
+
provider_params: e.provider_params ?? {},
|
|
1125
1139
|
})),
|
|
1126
1140
|
};
|
|
1127
1141
|
}
|
|
@@ -397,6 +397,13 @@ export const oauthProviderSummarySchema = z.object({
|
|
|
397
397
|
supports_managed_mode: z.boolean(),
|
|
398
398
|
managed_service_is_paid: z.boolean(),
|
|
399
399
|
feature_flag: z.string().nullable(),
|
|
400
|
+
tenant_host: z
|
|
401
|
+
.object({
|
|
402
|
+
pattern: z.string(),
|
|
403
|
+
label: z.string(),
|
|
404
|
+
placeholder: z.string(),
|
|
405
|
+
})
|
|
406
|
+
.nullable(),
|
|
400
407
|
acts_as: z.enum(["user", "assistant"]),
|
|
401
408
|
});
|
|
402
409
|
|
|
@@ -569,7 +569,7 @@ export const computerUseObserveTool = {
|
|
|
569
569
|
include_screenshot: {
|
|
570
570
|
type: "boolean",
|
|
571
571
|
description:
|
|
572
|
-
"Force a screenshot with this observation. The accessibility tree is returned every step; ask for pixels whenever the tree is not enough to act on, such as a canvas, a game, a custom-drawn view, a window with few or unlabeled controls, or a question about layout.",
|
|
572
|
+
"Force a screenshot with this observation. The accessibility tree is returned every step and actions always return a screenshot of their result; ask for pixels on an observation whenever the tree is not enough to act on, such as a canvas, a game, a custom-drawn view, a window with few or unlabeled controls, or a question about layout.",
|
|
573
573
|
},
|
|
574
574
|
full_tree: {
|
|
575
575
|
type: "boolean",
|
|
@@ -1,68 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Conversation-stable wire projections of the UI surface tool definitions.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Activation-rail conversations additionally get the `activation_moment`
|
|
9
|
-
* telemetry param, which no other conversation pays schema tokens for (the
|
|
10
|
-
* emit path is gated on `isActivationSession` regardless).
|
|
4
|
+
* `ui_show`, `ui_update`, and `ui_dismiss` keep one definition across every
|
|
5
|
+
* channel and across live vs clientless turns. Channel renderers enforce the
|
|
6
|
+
* surfaces they can display at execution. Clientless calls persist surface
|
|
7
|
+
* content for the next capable client that opens the conversation.
|
|
11
8
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
9
|
+
* `activation_moment` is the remaining schema projection. It applies only to
|
|
10
|
+
* activation-rail conversations, whose marker is written before the first
|
|
11
|
+
* tool resolution, so the extra property is stable for that conversation's
|
|
12
|
+
* lifetime. It must not vary by channel or turn presence.
|
|
16
13
|
*/
|
|
17
14
|
|
|
18
15
|
import type { ToolDefinition } from "../../providers/types.js";
|
|
19
16
|
import { ACTIVATION_MOMENT_PARAMS } from "../../telemetry/activation-funnel.js";
|
|
20
|
-
import { TASK_PROGRESS_TEMPLATE_SHAPE } from "./surface-shape-docs.js";
|
|
21
|
-
|
|
22
|
-
const SLACK_UI_SHOW_DESCRIPTION = `Show a live task-progress card for the current work — the only surface this channel renders. data: { template: "task_progress", templateData: ${TASK_PROGRESS_TEMPLATE_SHAPE} }. Show it early on multi-step turns and advance step statuses via ui_update as work progresses.`;
|
|
23
|
-
|
|
24
|
-
const SLACK_UI_SHOW_INPUT_SCHEMA = {
|
|
25
|
-
type: "object",
|
|
26
|
-
properties: {
|
|
27
|
-
surface_type: {
|
|
28
|
-
type: "string",
|
|
29
|
-
enum: ["card"],
|
|
30
|
-
description: "Always card on this channel",
|
|
31
|
-
},
|
|
32
|
-
title: {
|
|
33
|
-
type: "string",
|
|
34
|
-
description: "Optional surface title",
|
|
35
|
-
},
|
|
36
|
-
data: {
|
|
37
|
-
type: "object",
|
|
38
|
-
description:
|
|
39
|
-
'Card data: { template: "task_progress", templateData: { title, status, steps } }',
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
required: ["surface_type", "data"],
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Swap UI surface tool definitions for channel-appropriate variants. Returns
|
|
47
|
-
* the input array unchanged (same identity) for channels without a variant.
|
|
48
|
-
*/
|
|
49
|
-
export function projectUiToolsForChannel(
|
|
50
|
-
definitions: ToolDefinition[],
|
|
51
|
-
channel: string | undefined,
|
|
52
|
-
): ToolDefinition[] {
|
|
53
|
-
if (channel !== "slack") {
|
|
54
|
-
return definitions;
|
|
55
|
-
}
|
|
56
|
-
return definitions.map((def) =>
|
|
57
|
-
def.name === "ui_show"
|
|
58
|
-
? {
|
|
59
|
-
...def,
|
|
60
|
-
description: SLACK_UI_SHOW_DESCRIPTION,
|
|
61
|
-
input_schema: SLACK_UI_SHOW_INPUT_SCHEMA,
|
|
62
|
-
}
|
|
63
|
-
: def,
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
17
|
|
|
67
18
|
const ACTIVATION_MOMENT_PROPERTY = {
|
|
68
19
|
type: "string",
|
|
@@ -73,7 +24,7 @@ const ACTIVATION_MOMENT_PROPERTY = {
|
|
|
73
24
|
|
|
74
25
|
/**
|
|
75
26
|
* Add the optional `activation_moment` param to ui_show's schema. Applied
|
|
76
|
-
* only for activation-rail conversations (`isActivationSession`)
|
|
27
|
+
* only for activation-rail conversations (`isActivationSession`). The rail
|
|
77
28
|
* bootstrap prompt instructs the tagging, and the daemon's emit path reads
|
|
78
29
|
* the tag from tool input independently of this schema.
|
|
79
30
|
*/
|
package/src/watch/watch-retro.ts
CHANGED
|
@@ -61,14 +61,12 @@ const WATCH_RETRO_WAKE_SOURCE = "watch-retro";
|
|
|
61
61
|
* under the `watch_retro` template, which the client draws as a paged card: the
|
|
62
62
|
* record on the first page, one question per page after it.
|
|
63
63
|
*
|
|
64
|
-
* **The daemon appends the card; the model
|
|
65
|
-
* is `clientless
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* retrospective's `skill_card` is appended by the daemon rather than requested
|
|
71
|
-
* by the model.
|
|
64
|
+
* **The daemon appends the card; the model calls `watch_retro_report`.** This
|
|
65
|
+
* wake is `clientless`. `ui_show` stays on the wire so background surfaces can
|
|
66
|
+
* persist, but this flow still reports through `watch_retro_report` so the
|
|
67
|
+
* daemon can append the card after the turn ends. The memory retrospective's
|
|
68
|
+
* `skill_card` is appended the same way: the daemon writes the surface, the
|
|
69
|
+
* model does not request it.
|
|
72
70
|
*
|
|
73
71
|
* **The append waits for the turn to end.** A `ui_surface` row written mid-turn
|
|
74
72
|
* can land between a persisted `tool_use` and its `tool_result`, an ordering
|