@vellumai/assistant 0.11.10 → 0.11.11-staging.1
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/AGENTS.md +1 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/plan-credit.test.ts +51 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/plan-credit.ts +66 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/plan-credit.test.ts +51 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/plan-credit.ts +66 -0
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/__tests__/plan-credit.test.ts +51 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/plan-credit.ts +66 -0
- package/openapi.yaml +152 -55
- package/package.json +1 -1
- package/src/__tests__/anthropic-provider.test.ts +60 -0
- package/src/__tests__/config-get-vision-flag.test.ts +36 -0
- package/src/__tests__/conversation-error.test.ts +38 -0
- package/src/__tests__/copy-composer-tc-templates.test.ts +2 -2
- package/src/__tests__/guardian-action-service-scope.test.ts +53 -0
- package/src/__tests__/headless-browser-mode.test.ts +63 -13
- package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
- package/src/__tests__/heartbeat-service.test.ts +118 -27
- package/src/__tests__/host-app-control-proxy.test.ts +2 -2
- package/src/__tests__/host-app-control-routes.test.ts +41 -0
- package/src/__tests__/host-bash-routes.test.ts +30 -1
- package/src/__tests__/host-browser-routes.test.ts +34 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +29 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +29 -0
- package/src/__tests__/host-proxy-base.test.ts +1 -0
- package/src/__tests__/host-transfer-routes-targeted.test.ts +86 -0
- package/src/__tests__/llm-schema.test.ts +32 -0
- package/src/__tests__/mcp-config-secret-boundary.test.ts +2 -0
- package/src/__tests__/memory-config-file-parity.test.ts +18 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +15 -0
- package/src/__tests__/messaging-send-tool.test.ts +159 -13
- package/src/__tests__/messaging-shared.test.ts +19 -0
- package/src/__tests__/notification-events-source-context-probe.test.ts +211 -0
- package/src/__tests__/openai-responses-prompt-cache.test.ts +40 -6
- package/src/__tests__/outlook-messaging-provider.test.ts +9 -0
- package/src/__tests__/provider-catalog-visibility.test.ts +2 -2
- package/src/__tests__/schedule-routes.test.ts +30 -1
- package/src/__tests__/scheduler-result-notification.test.ts +209 -0
- package/src/__tests__/skills-install-extract.test.ts +26 -0
- package/src/__tests__/skills-install-staging.test.ts +11 -2
- package/src/api/constants/personality-sliders.test.ts +21 -0
- package/src/api/constants/personality-sliders.ts +64 -0
- package/src/api/index.ts +8 -0
- package/src/archive/ustar-size.test.ts +32 -0
- package/src/archive/ustar-size.ts +23 -0
- package/src/background-wake/next-wake.test.ts +13 -0
- package/src/background-wake/next-wake.ts +4 -2
- package/src/calls/voice-session-bridge.ts +26 -0
- package/src/cli/__tests__/catalog-search-help.test.ts +1 -0
- package/src/cli/commands/__tests__/plugins-search.test.ts +81 -0
- package/src/cli/commands/config.help.ts +3 -1
- package/src/cli/commands/memory/worker.ts +1 -1
- package/src/cli/commands/platform/__tests__/credits-format.test.ts +94 -5
- package/src/cli/commands/platform/__tests__/credits.test.ts +10 -0
- package/src/cli/commands/platform/credits-format.ts +57 -0
- package/src/cli/commands/platform/index.help.ts +19 -3
- package/src/cli/commands/plugins.help.ts +4 -1
- package/src/cli/commands/plugins.ts +2 -1
- package/src/cli/lib/bundled-marketplace.json +1 -1
- package/src/cli/lib/install-from-platform.ts +8 -10
- package/src/config/bundled-skills/messaging/SKILL.md +2 -0
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -4
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +85 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +6 -0
- package/src/config/bundled-skills/schedule/SKILL.md +9 -1
- package/src/config/bundled-skills/screen-annotation/SKILL.md +87 -20
- package/src/config/bundled-skills/screen-annotation/TOOLS.json +21 -5
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +67 -0
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/input-modalities.test.ts +71 -0
- package/src/config/input-modalities.ts +95 -0
- package/src/config/schemas/heartbeat.ts +3 -3
- package/src/config/schemas/llm.ts +13 -3
- package/src/config/schemas/memory-storage.test.ts +39 -0
- package/src/config/schemas/memory-storage.ts +21 -0
- package/src/context/token-estimator.ts +2 -2
- package/src/daemon/__tests__/embedding-reconcile.test.ts +77 -0
- package/src/daemon/__tests__/lifecycle-embedding-reconcile.test.ts +3 -0
- package/src/daemon/conversation-error.ts +20 -7
- package/src/daemon/embedding-reconcile.ts +64 -1
- package/src/daemon/handlers/config-embeddings.ts +31 -0
- package/src/daemon/host-app-control-proxy.ts +2 -0
- package/src/daemon/host-bash-proxy.ts +1 -0
- package/src/daemon/host-cu-proxy.ts +6 -6
- package/src/daemon/host-file-proxy.ts +6 -6
- package/src/daemon/host-proxy-base.ts +10 -9
- package/src/daemon/host-transfer-proxy.ts +21 -24
- package/src/daemon/mcp-reload-service.ts +6 -0
- package/src/daemon/providers-setup.ts +5 -26
- package/src/heartbeat/heartbeat-service.ts +72 -13
- package/src/live-voice/__tests__/live-voice-photo.test.ts +72 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +81 -0
- package/src/live-voice/live-voice-photo.ts +68 -4
- package/src/live-voice/live-voice-session.ts +36 -2
- package/src/live-voice/protocol.ts +98 -1
- package/src/mcp/__tests__/reload-signal-emission.test.ts +54 -0
- package/src/mcp/__tests__/reload-signal.test.ts +57 -0
- package/src/mcp/__tests__/startup.test.ts +150 -0
- package/src/mcp/reload-signal.ts +50 -0
- package/src/mcp/startup.ts +101 -0
- package/src/messaging/providers/outlook/adapter.ts +2 -13
- package/src/messaging/providers/outlook/client.ts +13 -1
- package/src/messaging/providers/outlook/types.ts +3 -0
- package/src/notifications/__tests__/decision-engine.test.ts +79 -6
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +27 -0
- package/src/notifications/__tests__/schedule-result-producer.test.ts +397 -0
- package/src/notifications/copy-composer.ts +33 -11
- package/src/notifications/decision-engine.ts +48 -11
- package/src/notifications/events-store.ts +68 -2
- package/src/notifications/home-feed-side-effect.ts +15 -3
- package/src/notifications/schedule-result-producer.ts +275 -0
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/seed-providers.ts +3 -2
- package/src/persistence/conversation-crud.ts +38 -0
- package/src/persistence/embeddings/embedding-backend.test.ts +162 -0
- package/src/persistence/embeddings/embedding-backend.ts +148 -11
- package/src/persistence/embeddings/embedding-openai.test.ts +113 -0
- package/src/persistence/embeddings/embedding-openai.ts +55 -4
- package/src/persistence/embeddings/embedding-types.test.ts +24 -0
- package/src/persistence/embeddings/embedding-types.ts +25 -1
- package/src/persistence/job-utils.ts +6 -2
- package/src/plugin-api/index.ts +2 -1
- package/src/plugin-api/vision-support.test.ts +81 -0
- package/src/plugin-api/vision-support.ts +57 -10
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +8 -0
- package/src/plugins/defaults/memory/jobs/__tests__/embed-concept-page.test.ts +1 -0
- package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +11 -3
- package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +20 -0
- package/src/plugins/defaults/memory/src/memory-worker-routes.ts +3 -1
- package/src/plugins/defaults/memory/substrate/__tests__/reembed-job.test.ts +1 -0
- package/src/plugins/defaults/memory/v2/__tests__/backfill-jobs.test.ts +1 -0
- package/src/plugins/defaults/memory/v3/__tests__/section-dense-store.test.ts +82 -0
- package/src/plugins/defaults/memory/v3/section-dense-store.ts +9 -27
- package/src/providers/__tests__/provider-secret-catalog.test.ts +4 -0
- package/src/providers/__tests__/xml-tool-call-salvage.test.ts +172 -0
- package/src/providers/anthropic/client.ts +9 -4
- package/src/providers/inline-audio-support.test.ts +34 -0
- package/src/providers/inline-audio-support.ts +27 -0
- package/src/providers/model-catalog.ts +22 -2
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +50 -0
- package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +51 -0
- package/src/providers/openai/__tests__/chat-completions-provider-xml-salvage.test.ts +291 -0
- package/src/providers/openai/api-error-normalization.ts +18 -3
- package/src/providers/openai/chat-completions-provider.ts +113 -13
- package/src/providers/openai/responses-provider.ts +10 -8
- package/src/providers/provider-secret-catalog.ts +11 -1
- package/src/providers/vellum/client.ts +14 -1
- package/src/providers/vellum/personality-directions.test.ts +163 -0
- package/src/providers/vellum/personality-directions.ts +120 -0
- package/src/providers/xml-tool-call-salvage.ts +212 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +42 -4
- package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -6
- package/src/runtime/auth/__tests__/route-policy.test.ts +65 -0
- package/src/runtime/auth/same-actor.ts +29 -5
- package/src/runtime/background-job-runner.ts +38 -29
- package/src/runtime/guardian-action-service.ts +20 -1
- package/src/runtime/migrations/vbundle-validator.ts +3 -3
- package/src/runtime/pending-interactions.ts +4 -5
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +24 -0
- package/src/runtime/routes/__tests__/platform-credits-routes.test.ts +99 -5
- package/src/runtime/routes/conversation-query-routes.ts +53 -11
- package/src/runtime/routes/document-comments-routes.ts +8 -2
- package/src/runtime/routes/guardian-action-routes.ts +7 -0
- package/src/runtime/routes/heartbeat-routes.ts +41 -53
- package/src/runtime/routes/host-app-control-routes.ts +10 -36
- package/src/runtime/routes/host-bash-routes.ts +11 -45
- package/src/runtime/routes/host-browser-routes.ts +24 -0
- package/src/runtime/routes/host-cu-routes.ts +11 -45
- package/src/runtime/routes/host-file-routes.ts +11 -44
- package/src/runtime/routes/host-proxy-result-binding.ts +57 -0
- package/src/runtime/routes/host-transfer-routes.ts +33 -25
- package/src/runtime/routes/index.ts +0 -2
- package/src/runtime/routes/integrations/a2a.ts +12 -3
- package/src/runtime/routes/integrations/vercel.ts +13 -3
- package/src/runtime/routes/platform-routes.ts +121 -24
- package/src/runtime/routes/schedule-routes.ts +15 -1
- package/src/schedule/__tests__/schedule-timezone.test.ts +21 -3
- package/src/schedule/__tests__/worker-mcp-tools.test.ts +88 -0
- package/src/schedule/schedule-timezone.ts +25 -0
- package/src/schedule/scheduler.ts +19 -0
- package/src/schedule/worker.ts +119 -2
- package/src/skills/catalog-install.ts +40 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +3 -0
- package/src/tools/browser/browser-execution.ts +117 -53
- package/src/tools/browser/browser-status-constants.ts +6 -6
- package/src/tools/capability-offer.test.ts +87 -0
- package/src/tools/capability-offer.ts +93 -0
- package/src/tools/terminal/__tests__/safe-env.test.ts +17 -0
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/util/provider-error-patterns.ts +34 -0
- package/src/watcher/__tests__/engine.test.ts +5 -2
- package/src/watcher/engine.ts +7 -4
- package/src/runtime/routes/__tests__/sight-frame-routes.test.ts +0 -487
- package/src/runtime/routes/sight-frame-routes.ts +0 -136
|
@@ -103,7 +103,7 @@ export interface SameActorLiveArgs {
|
|
|
103
103
|
export interface SameActorPersistedArgs {
|
|
104
104
|
sourceActorPrincipalId: string | undefined;
|
|
105
105
|
targetActorPrincipalId: string | undefined;
|
|
106
|
-
targetClientId
|
|
106
|
+
targetClientId?: string;
|
|
107
107
|
op: SameActorOp;
|
|
108
108
|
/**
|
|
109
109
|
* Fill-if-missing fallback for dev-bypass deployments: when the PERSISTED
|
|
@@ -136,12 +136,15 @@ function isLive(
|
|
|
136
136
|
function detectRejection(
|
|
137
137
|
args: SameActorLiveArgs | SameActorPersistedArgs,
|
|
138
138
|
): RejectionReason | undefined {
|
|
139
|
-
const { sourceActorPrincipalId,
|
|
139
|
+
const { sourceActorPrincipalId, op } = args;
|
|
140
|
+
const targetClientId = args.targetClientId;
|
|
140
141
|
const targetActorPrincipalId = isLive(args)
|
|
141
|
-
? args.hub.getActorPrincipalIdForClient(targetClientId)
|
|
142
|
+
? args.hub.getActorPrincipalIdForClient(args.targetClientId)
|
|
142
143
|
: (args.targetActorPrincipalId ??
|
|
143
|
-
(isHttpAuthDisabled()
|
|
144
|
-
? args.hubForMissingTarget?.getActorPrincipalIdForClient(
|
|
144
|
+
(isHttpAuthDisabled() && targetClientId
|
|
145
|
+
? args.hubForMissingTarget?.getActorPrincipalIdForClient(
|
|
146
|
+
targetClientId,
|
|
147
|
+
)
|
|
145
148
|
: undefined));
|
|
146
149
|
|
|
147
150
|
let reason: RejectionReason | undefined;
|
|
@@ -261,6 +264,27 @@ export function pickSameUserAutoResolve(args: {
|
|
|
261
264
|
return { kind: "ambiguous" };
|
|
262
265
|
}
|
|
263
266
|
|
|
267
|
+
/**
|
|
268
|
+
* Actor principal persisted on a host-proxy pending interaction.
|
|
269
|
+
*
|
|
270
|
+
* Targeted dispatch snapshots the target client's actor. Untargeted dispatch
|
|
271
|
+
* snapshots the turn's source actor so result routes can still reject a
|
|
272
|
+
* different principal even when no `targetClientId` was bound.
|
|
273
|
+
*/
|
|
274
|
+
export function snapshotHostProxyActorPrincipalId(args: {
|
|
275
|
+
hub: Pick<AssistantEventHub, "getActorPrincipalIdForClient">;
|
|
276
|
+
targetClientId?: string;
|
|
277
|
+
sourceActorPrincipalId?: string;
|
|
278
|
+
}): string | undefined {
|
|
279
|
+
if (args.targetClientId != null) {
|
|
280
|
+
return (
|
|
281
|
+
args.hub.getActorPrincipalIdForClient(args.targetClientId) ??
|
|
282
|
+
args.sourceActorPrincipalId
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
return args.sourceActorPrincipalId;
|
|
286
|
+
}
|
|
287
|
+
|
|
264
288
|
/**
|
|
265
289
|
* Standard error result for proxies when {@link pickSameUserAutoResolve}
|
|
266
290
|
* returns `ambiguous`. Asks the caller to specify `target_client_id`.
|
|
@@ -171,17 +171,23 @@ export interface RunBackgroundJobOptions {
|
|
|
171
171
|
*/
|
|
172
172
|
allowPreFirstUserMessage?: boolean;
|
|
173
173
|
/**
|
|
174
|
-
* Optional prompt-injection mitigation. When set, the runner
|
|
175
|
-
*
|
|
174
|
+
* Optional prompt-injection mitigation. When set, the runner seeds the
|
|
175
|
+
* conversation with a user/assistant/user sandwich BEFORE invoking
|
|
176
|
+
* `processMessage`:
|
|
176
177
|
*
|
|
177
|
-
* 1. `user` role: `preamble`
|
|
178
|
-
* 2. `assistant` role: `content`
|
|
178
|
+
* 1. `user` role: `preamble` - static, trusted instructions.
|
|
179
|
+
* 2. `assistant` role: `content` - attacker-controllable payload (the LLM
|
|
179
180
|
* treats it as its own past output, not as user instructions).
|
|
180
|
-
* 3. `user` role: `postamble`
|
|
181
|
+
* 3. `user` role: `postamble` - static, trusted action prompt.
|
|
181
182
|
*
|
|
182
|
-
* `
|
|
183
|
-
*
|
|
184
|
-
* the
|
|
183
|
+
* An empty `prompt` is a signal that the sandwich already carries the
|
|
184
|
+
* seed. In that case the runner persists only the preamble and untrusted
|
|
185
|
+
* content, then invokes `processMessage` with the postamble.
|
|
186
|
+
* `persistUserMessage` requires content or attachments, so the kickoff
|
|
187
|
+
* cannot be empty.
|
|
188
|
+
*
|
|
189
|
+
* A non-empty `prompt` still seeds all three sandwich messages, then
|
|
190
|
+
* invokes `processMessage` with that kicker.
|
|
185
191
|
*
|
|
186
192
|
* Used by the watcher engine to ingest external provider events safely:
|
|
187
193
|
* a malicious Linear title or Gmail subject reaches the model only in
|
|
@@ -324,28 +330,29 @@ export async function runBackgroundJob(
|
|
|
324
330
|
// messages. The LLM treats assistant-role content as its own prior
|
|
325
331
|
// output, not as user instructions, so a malicious payload (e.g. a
|
|
326
332
|
// crafted Linear title) cannot override the postamble's action prompt.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
);
|
|
334
|
-
await addMessage(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
333
|
+
const sandwich = opts.assistantSandwich;
|
|
334
|
+
let kickoffPrompt = opts.prompt;
|
|
335
|
+
let kickoffFromPostamble = false;
|
|
336
|
+
if (sandwich) {
|
|
337
|
+
await addMessage(conversation.id, "user", sandwich.preamble, {
|
|
338
|
+
skipIndexing: true,
|
|
339
|
+
});
|
|
340
|
+
await addMessage(conversation.id, "assistant", sandwich.content, {
|
|
341
|
+
skipIndexing: true,
|
|
342
|
+
});
|
|
343
|
+
if (opts.prompt.trim() === "") {
|
|
344
|
+
// persistUserMessage rejects empty content. The sandwich already
|
|
345
|
+
// carries the seed, so the trusted postamble is the kickoff.
|
|
346
|
+
kickoffFromPostamble = true;
|
|
347
|
+
kickoffPrompt = sandwich.postamble;
|
|
348
|
+
} else {
|
|
349
|
+
await addMessage(conversation.id, "user", sandwich.postamble, {
|
|
350
|
+
skipIndexing: true,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
346
353
|
}
|
|
347
354
|
|
|
348
|
-
const work = processMessage(conversation.id,
|
|
355
|
+
const work = processMessage(conversation.id, kickoffPrompt, {
|
|
349
356
|
trustContext: opts.trustContext,
|
|
350
357
|
callSite: opts.callSite,
|
|
351
358
|
...(opts.overrideProfile
|
|
@@ -355,7 +362,9 @@ export async function runBackgroundJob(
|
|
|
355
362
|
...(opts.cronRunId ? { cronRunId: opts.cronRunId } : {}),
|
|
356
363
|
...(opts.allowedTools ? { allowedTools: opts.allowedTools } : {}),
|
|
357
364
|
...(opts.toolGateMode ? { toolGateMode: opts.toolGateMode } : {}),
|
|
358
|
-
...(opts.skipPromptIndexing
|
|
365
|
+
...(opts.skipPromptIndexing || kickoffFromPostamble
|
|
366
|
+
? { skipUserMessageIndexing: true }
|
|
367
|
+
: {}),
|
|
359
368
|
});
|
|
360
369
|
// Absorb late rejections: if the timeout wins the race, `work` keeps
|
|
361
370
|
// running and may eventually reject — swallow so it doesn't surface as
|
|
@@ -64,6 +64,16 @@ export type ProcessGuardianDecisionResult =
|
|
|
64
64
|
ok: true;
|
|
65
65
|
applied: false;
|
|
66
66
|
reason: string;
|
|
67
|
+
/**
|
|
68
|
+
* Whether the decision was recorded before the decline. Present only
|
|
69
|
+
* for the two resolver failures: `true` under `resolver_failed` (the
|
|
70
|
+
* decision committed and its follow-through failed), `false` under
|
|
71
|
+
* `decision_not_persisted` (nothing was written; the guardian can
|
|
72
|
+
* retry). A client reads this rather than the reason so an older
|
|
73
|
+
* daemon, which reports both under `resolver_failed` and omits the
|
|
74
|
+
* field, is not misread as having committed.
|
|
75
|
+
*/
|
|
76
|
+
committed?: boolean;
|
|
67
77
|
resolverFailureReason?: string;
|
|
68
78
|
requestId?: string;
|
|
69
79
|
}
|
|
@@ -138,10 +148,19 @@ export async function processGuardianDecision(
|
|
|
138
148
|
// 4. Map the canonical result
|
|
139
149
|
if (decisionResult.applied) {
|
|
140
150
|
if (decisionResult.resolverFailed) {
|
|
151
|
+
// Two failures share the primitive's `resolverFailed` flag and a
|
|
152
|
+
// client has to tell them apart: a decision that committed and whose
|
|
153
|
+
// follow-through then failed is settled (another attempt can only
|
|
154
|
+
// come back already resolved), while one whose persist never landed
|
|
155
|
+
// is still pending and is the guardian's to retry. The primitive
|
|
156
|
+
// marks the difference by omitting `decidedAction` when nothing was
|
|
157
|
+
// committed (see `decisionPersistFailure`).
|
|
158
|
+
const committed = decisionResult.decidedAction !== undefined;
|
|
141
159
|
return {
|
|
142
160
|
ok: true,
|
|
143
161
|
applied: false,
|
|
144
|
-
reason: "resolver_failed",
|
|
162
|
+
reason: committed ? "resolver_failed" : "decision_not_persisted",
|
|
163
|
+
committed,
|
|
145
164
|
resolverFailureReason: decisionResult.resolverFailureReason,
|
|
146
165
|
requestId: decisionResult.requestId,
|
|
147
166
|
};
|
|
@@ -19,6 +19,8 @@ import { gunzipSync } from "node:zlib";
|
|
|
19
19
|
|
|
20
20
|
import { z } from "zod";
|
|
21
21
|
|
|
22
|
+
import { parseUstarSizeField } from "../../archive/ustar-size.js";
|
|
23
|
+
|
|
22
24
|
// ---------------------------------------------------------------------------
|
|
23
25
|
// Manifest schema (v1)
|
|
24
26
|
// ---------------------------------------------------------------------------
|
|
@@ -263,9 +265,7 @@ function parseTar(buffer: Uint8Array): TarEntry[] {
|
|
|
263
265
|
// File type (byte 156)
|
|
264
266
|
const typeFlag = String.fromCharCode(header[156]);
|
|
265
267
|
|
|
266
|
-
|
|
267
|
-
const sizeStr = decodeNullTerminated(header, 124, 12);
|
|
268
|
-
const size = parseInt(sizeStr, 8) || 0;
|
|
268
|
+
const size = parseUstarSizeField(header);
|
|
269
269
|
|
|
270
270
|
// Calculate data blocks
|
|
271
271
|
const dataBlocks = Math.ceil(size / BLOCK_SIZE);
|
|
@@ -110,11 +110,10 @@ export interface PendingInteraction {
|
|
|
110
110
|
/** When set, the host_bash request should be routed to this specific client. */
|
|
111
111
|
targetClientId?: string;
|
|
112
112
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* submission, which would otherwise 403 a legitimate result.
|
|
113
|
+
* Actor principal captured at registration. Targeted requests snapshot the
|
|
114
|
+
* target client's actor so a brief SSE reconnect does not 403 a legitimate
|
|
115
|
+
* result. Untargeted requests snapshot the turn's source actor so a
|
|
116
|
+
* different principal cannot forge stdout/stderr/exitCode.
|
|
118
117
|
*/
|
|
119
118
|
targetActorPrincipalId?: string;
|
|
120
119
|
|
|
@@ -23,6 +23,11 @@ mock.module("../../../heartbeat/heartbeat-service.js", () => ({
|
|
|
23
23
|
},
|
|
24
24
|
}));
|
|
25
25
|
|
|
26
|
+
mock.module("../../../schedule/schedule-timezone.js", () => ({
|
|
27
|
+
resolveScheduleTimezone: (explicit: string | null | undefined) =>
|
|
28
|
+
explicit ?? "America/Los_Angeles",
|
|
29
|
+
}));
|
|
30
|
+
|
|
26
31
|
// ─── Setup ─────────────────────────────────────────────────────────────────
|
|
27
32
|
|
|
28
33
|
let workspaceDir: string;
|
|
@@ -112,3 +117,22 @@ describe("setHeartbeatConfig handler", () => {
|
|
|
112
117
|
});
|
|
113
118
|
});
|
|
114
119
|
});
|
|
120
|
+
|
|
121
|
+
describe("getHeartbeatConfig handler", () => {
|
|
122
|
+
test("returns the resolved timezone separately from the stored override", async () => {
|
|
123
|
+
writeFileSync(
|
|
124
|
+
configPath,
|
|
125
|
+
JSON.stringify({ heartbeat: { enabled: true } }, null, 2) + "\n",
|
|
126
|
+
);
|
|
127
|
+
invalidateConfigCache();
|
|
128
|
+
|
|
129
|
+
const handler = findHandler("getHeartbeatConfig");
|
|
130
|
+
const result = (await handler({})) as {
|
|
131
|
+
timezone: string | null;
|
|
132
|
+
effectiveTimezone: string | null;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
expect(result.timezone).toBeNull();
|
|
136
|
+
expect(result.effectiveTimezone).toBe("America/Los_Angeles");
|
|
137
|
+
});
|
|
138
|
+
});
|
|
@@ -34,6 +34,8 @@ const creditsHandler = ROUTES.find(
|
|
|
34
34
|
)!.handler;
|
|
35
35
|
|
|
36
36
|
const SUMMARY_URL = "https://platform.test/v1/organizations/billing/summary/";
|
|
37
|
+
const SUBSCRIPTION_URL =
|
|
38
|
+
"https://platform.test/v1/organizations/billing/subscription/";
|
|
37
39
|
|
|
38
40
|
const FULL_SUMMARY = {
|
|
39
41
|
settled_balance_usd: "50.00",
|
|
@@ -46,21 +48,34 @@ const FULL_SUMMARY = {
|
|
|
46
48
|
daily_limit_snoozed: true,
|
|
47
49
|
low_balance_threshold_usd: "5.00",
|
|
48
50
|
low_balance_warning: true,
|
|
51
|
+
available_usage_balance: "9.10",
|
|
52
|
+
total_usage_balance: "20.00",
|
|
53
|
+
credits_expiring_soon_usd: "9.10",
|
|
54
|
+
next_credit_expiry_at: "2026-10-01T00:00:00Z",
|
|
49
55
|
};
|
|
50
56
|
|
|
51
57
|
const realFetch = globalThis.fetch;
|
|
52
58
|
let fetchedUrls: string[] = [];
|
|
53
59
|
|
|
54
|
-
/**
|
|
55
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Stub globalThis.fetch: the summary URL answers with `summary`, the
|
|
62
|
+
* subscription URL with `subscription` (a plan, or a 500 when "error").
|
|
63
|
+
*/
|
|
64
|
+
function stubFetch(
|
|
65
|
+
summary: unknown,
|
|
66
|
+
subscription: { plan_id: "base" | "pro" } | "error" = { plan_id: "pro" },
|
|
67
|
+
): void {
|
|
56
68
|
globalThis.fetch = (async (input: string | URL | Request) => {
|
|
57
|
-
|
|
69
|
+
const url =
|
|
58
70
|
typeof input === "string"
|
|
59
71
|
? input
|
|
60
72
|
: input instanceof URL
|
|
61
73
|
? input.toString()
|
|
62
|
-
: input.url
|
|
63
|
-
);
|
|
74
|
+
: input.url;
|
|
75
|
+
fetchedUrls.push(url);
|
|
76
|
+
const isSubscription = url === SUBSCRIPTION_URL;
|
|
77
|
+
const status = isSubscription && subscription === "error" ? 500 : 200;
|
|
78
|
+
const body = isSubscription ? subscription : summary;
|
|
64
79
|
return new Response(JSON.stringify(body), {
|
|
65
80
|
status,
|
|
66
81
|
headers: { "Content-Type": "application/json" },
|
|
@@ -97,6 +112,78 @@ describe("platform_credits", () => {
|
|
|
97
112
|
daily_limit_snoozed: true,
|
|
98
113
|
low_balance_threshold: 5,
|
|
99
114
|
low_balance_warning: true,
|
|
115
|
+
plan_credit_remaining: 9.1,
|
|
116
|
+
plan_credit_total: 20,
|
|
117
|
+
plan_credit_used_fraction: 0.545,
|
|
118
|
+
plan_credits_spent: false,
|
|
119
|
+
extra_credit_remaining: 33.07,
|
|
120
|
+
credits_expiring_soon: 9.1,
|
|
121
|
+
next_credit_expiry_at: "2026-10-01T00:00:00Z",
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("marks plan credit spent once the grants are used up", async () => {
|
|
126
|
+
stubFetch({ ...FULL_SUMMARY, available_usage_balance: "0.00" });
|
|
127
|
+
|
|
128
|
+
expect(await creditsHandler({})).toMatchObject({
|
|
129
|
+
plan_credit_used_fraction: 1,
|
|
130
|
+
plan_credits_spent: true,
|
|
131
|
+
extra_credit_remaining: 42.17,
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("treats a Pro plan whose grants total nothing as fully spent", async () => {
|
|
136
|
+
stubFetch(
|
|
137
|
+
{
|
|
138
|
+
...FULL_SUMMARY,
|
|
139
|
+
available_usage_balance: "0.00",
|
|
140
|
+
total_usage_balance: "0.00",
|
|
141
|
+
},
|
|
142
|
+
{ plan_id: "pro" },
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
expect(await creditsHandler({})).toMatchObject({
|
|
146
|
+
plan_credit_total: 0,
|
|
147
|
+
plan_credit_used_fraction: 1,
|
|
148
|
+
plan_credits_spent: true,
|
|
149
|
+
extra_credit_remaining: 42.17,
|
|
150
|
+
});
|
|
151
|
+
expect(fetchedUrls).toEqual([SUMMARY_URL, SUBSCRIPTION_URL]);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("gives a base plan with no grants no reading, and clamps a refund overshoot", async () => {
|
|
155
|
+
stubFetch(
|
|
156
|
+
{
|
|
157
|
+
...FULL_SUMMARY,
|
|
158
|
+
available_usage_balance: "0.00",
|
|
159
|
+
total_usage_balance: "0.00",
|
|
160
|
+
},
|
|
161
|
+
{ plan_id: "base" },
|
|
162
|
+
);
|
|
163
|
+
expect(await creditsHandler({})).toMatchObject({
|
|
164
|
+
plan_credit_used_fraction: null,
|
|
165
|
+
plan_credits_spent: null,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
stubFetch({ ...FULL_SUMMARY, available_usage_balance: "25.00" });
|
|
169
|
+
expect(await creditsHandler({})).toMatchObject({
|
|
170
|
+
plan_credit_used_fraction: 0,
|
|
171
|
+
plan_credits_spent: false,
|
|
172
|
+
extra_credit_remaining: 17.17,
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("still reads the ratio when the subscription fetch fails, but not the zero-grant case", async () => {
|
|
177
|
+
stubFetch(FULL_SUMMARY, "error");
|
|
178
|
+
expect(await creditsHandler({})).toMatchObject({
|
|
179
|
+
plan_credit_used_fraction: 0.545,
|
|
180
|
+
plan_credits_spent: false,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
stubFetch({ ...FULL_SUMMARY, total_usage_balance: "0.00" }, "error");
|
|
184
|
+
expect(await creditsHandler({})).toMatchObject({
|
|
185
|
+
plan_credit_used_fraction: null,
|
|
186
|
+
plan_credits_spent: null,
|
|
100
187
|
});
|
|
101
188
|
});
|
|
102
189
|
|
|
@@ -116,6 +203,13 @@ describe("platform_credits", () => {
|
|
|
116
203
|
daily_limit_snoozed: false,
|
|
117
204
|
low_balance_threshold: null,
|
|
118
205
|
low_balance_warning: false,
|
|
206
|
+
plan_credit_remaining: null,
|
|
207
|
+
plan_credit_total: null,
|
|
208
|
+
plan_credit_used_fraction: null,
|
|
209
|
+
plan_credits_spent: null,
|
|
210
|
+
extra_credit_remaining: null,
|
|
211
|
+
credits_expiring_soon: null,
|
|
212
|
+
next_credit_expiry_at: null,
|
|
119
213
|
});
|
|
120
214
|
});
|
|
121
215
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* GET /v1/model — current model info
|
|
7
7
|
* PUT /v1/model/image-gen — set image-gen model
|
|
8
8
|
* GET /v1/config/embeddings — current embedding config
|
|
9
|
-
* PUT /v1/config/embeddings
|
|
9
|
+
* PUT /v1/config/embeddings - set embedding provider/model/baseUrl
|
|
10
10
|
* GET /v1/config — full raw workspace config
|
|
11
11
|
* PATCH /v1/config — deep-merge partial config
|
|
12
12
|
* PUT /v1/config/llm/profiles/:name — replace an inference profile
|
|
@@ -29,6 +29,12 @@ import {
|
|
|
29
29
|
LatencyBreakdownSchema,
|
|
30
30
|
LLMRequestLogEntrySchema,
|
|
31
31
|
} from "../../api/responses/llm-request-log-entry.js";
|
|
32
|
+
import {
|
|
33
|
+
catalogEntryFor,
|
|
34
|
+
type InputModalities,
|
|
35
|
+
modalitiesOf,
|
|
36
|
+
resolveModalityOverride,
|
|
37
|
+
} from "../../config/input-modalities.js";
|
|
32
38
|
import {
|
|
33
39
|
deepMergeOverwrite,
|
|
34
40
|
fillContextDefaultsForMissingKeys,
|
|
@@ -85,6 +91,7 @@ import {
|
|
|
85
91
|
CONFIG_RELOAD_DEBOUNCE_MS,
|
|
86
92
|
log,
|
|
87
93
|
} from "../../daemon/handlers/shared.js";
|
|
94
|
+
import { rescheduleHeartbeatIfTimezoneChanged } from "../../heartbeat/heartbeat-service.js";
|
|
88
95
|
import {
|
|
89
96
|
getAssistantMessageIdsInTurn,
|
|
90
97
|
getConversation,
|
|
@@ -97,6 +104,7 @@ import {
|
|
|
97
104
|
} from "../../persistence/conversation-types.js";
|
|
98
105
|
import { getDb } from "../../persistence/db-connection.js";
|
|
99
106
|
import { clearEmbeddingBackendCache } from "../../persistence/embeddings/embedding-backend.js";
|
|
107
|
+
import { resolveOpenAICompatibleBaseUrl } from "../../persistence/embeddings/embedding-openai.js";
|
|
100
108
|
import { getLlmRequestLogSource } from "../../persistence/llm-request-log-source.js";
|
|
101
109
|
import { type LogRow } from "../../persistence/llm-request-log-store.js";
|
|
102
110
|
import { getMemoryRecallLogByMessageIds } from "../../plugins/defaults/memory/memory-recall-log-store.js";
|
|
@@ -112,7 +120,6 @@ import {
|
|
|
112
120
|
listConnections,
|
|
113
121
|
VELLUM_MANAGED_CONNECTION_NAME,
|
|
114
122
|
} from "../../providers/inference/connections.js";
|
|
115
|
-
import { PROVIDER_CATALOG } from "../../providers/model-catalog.js";
|
|
116
123
|
import { initializeProviders } from "../../providers/registry.js";
|
|
117
124
|
import { MANAGED_ROUTABLE_PROVIDERS } from "../../providers/vellum-model-routing.js";
|
|
118
125
|
import { credentialKey } from "../../security/credential-key.js";
|
|
@@ -457,9 +464,11 @@ async function handleSetEmbeddingConfig({ body }: RouteHandlerArgs) {
|
|
|
457
464
|
if (!body || typeof body !== "object") {
|
|
458
465
|
throw new BadRequestError("Request body is required");
|
|
459
466
|
}
|
|
460
|
-
const { provider, model } = body as {
|
|
467
|
+
const { provider, model, baseUrl, dimensions } = body as {
|
|
461
468
|
provider?: string;
|
|
462
469
|
model?: string;
|
|
470
|
+
baseUrl?: string;
|
|
471
|
+
dimensions?: number | null;
|
|
463
472
|
};
|
|
464
473
|
if (!provider || typeof provider !== "string") {
|
|
465
474
|
throw new BadRequestError("Missing required field: provider");
|
|
@@ -472,8 +481,32 @@ async function handleSetEmbeddingConfig({ body }: RouteHandlerArgs) {
|
|
|
472
481
|
if (model !== undefined && typeof model !== "string") {
|
|
473
482
|
throw new BadRequestError("Field 'model' must be a string");
|
|
474
483
|
}
|
|
484
|
+
if (baseUrl !== undefined && typeof baseUrl !== "string") {
|
|
485
|
+
throw new BadRequestError("Field 'baseUrl' must be a string");
|
|
486
|
+
}
|
|
487
|
+
if (
|
|
488
|
+
typeof baseUrl === "string" &&
|
|
489
|
+
baseUrl !== "" &&
|
|
490
|
+
!resolveOpenAICompatibleBaseUrl(baseUrl)
|
|
491
|
+
) {
|
|
492
|
+
throw new BadRequestError("Field 'baseUrl' must be an http(s) URL");
|
|
493
|
+
}
|
|
494
|
+
if (
|
|
495
|
+
dimensions !== undefined &&
|
|
496
|
+
dimensions !== null &&
|
|
497
|
+
(typeof dimensions !== "number" ||
|
|
498
|
+
!Number.isInteger(dimensions) ||
|
|
499
|
+
dimensions <= 0)
|
|
500
|
+
) {
|
|
501
|
+
throw new BadRequestError(
|
|
502
|
+
"Field 'dimensions' must be a positive integer or null",
|
|
503
|
+
);
|
|
504
|
+
}
|
|
475
505
|
try {
|
|
476
|
-
return await setEmbeddingConfig(provider, model, getModelSetContext()
|
|
506
|
+
return await setEmbeddingConfig(provider, model, getModelSetContext(), {
|
|
507
|
+
baseUrl,
|
|
508
|
+
dimensions,
|
|
509
|
+
});
|
|
477
510
|
} catch (err) {
|
|
478
511
|
const message = err instanceof Error ? err.message : String(err);
|
|
479
512
|
throw new InternalError(`Failed to set embedding config: ${message}`);
|
|
@@ -1064,9 +1097,10 @@ export function normalizeManagedProfileWrites(patch: unknown): void {
|
|
|
1064
1097
|
* Annotate each profile in `config.llm.profiles` with wire-only flags
|
|
1065
1098
|
* (`WIRE_ONLY_PROFILE_KEYS`) — never persisted to disk:
|
|
1066
1099
|
*
|
|
1067
|
-
* - `supportsVision`: resolved from the model catalog
|
|
1068
|
-
*
|
|
1069
|
-
*
|
|
1100
|
+
* - `supportsVision`: resolved from the model catalog, with a profile
|
|
1101
|
+
* `inputModalities.image` override winning when set. Unknown (provider,
|
|
1102
|
+
* model) pairs with no override default to `true` (fail-open) so image
|
|
1103
|
+
* upload remains available for custom / unlisted models.
|
|
1070
1104
|
* - `invariant`: `true` for managed-source entries of the managed profile
|
|
1071
1105
|
* names (`INVARIANT_PROFILE_NAMES`); absent otherwise. Source-gated to
|
|
1072
1106
|
* match `assertInvariantProfilesPreserved` — a user-owned profile sharing
|
|
@@ -1101,9 +1135,13 @@ function enrichProfilesForWire(config: unknown): void {
|
|
|
1101
1135
|
continue;
|
|
1102
1136
|
}
|
|
1103
1137
|
|
|
1104
|
-
const
|
|
1105
|
-
const
|
|
1106
|
-
|
|
1138
|
+
const catalogModel = catalogEntryFor(provider, model);
|
|
1139
|
+
const catalogVision = catalogModel?.supportsVision;
|
|
1140
|
+
const imageOverride = modalitiesOf({
|
|
1141
|
+
inputModalities: entry.inputModalities as InputModalities | null,
|
|
1142
|
+
})?.image;
|
|
1143
|
+
entry.supportsVision =
|
|
1144
|
+
resolveModalityOverride(imageOverride, catalogVision) ?? true;
|
|
1107
1145
|
}
|
|
1108
1146
|
}
|
|
1109
1147
|
|
|
@@ -1471,6 +1509,7 @@ export async function commitConfigWrite(
|
|
|
1471
1509
|
|
|
1472
1510
|
clearEmbeddingBackendCache();
|
|
1473
1511
|
invalidateConfigCache();
|
|
1512
|
+
rescheduleHeartbeatIfTimezoneChanged(preWrite, raw);
|
|
1474
1513
|
// Reinitialize providers so the live registry reflects the new config.
|
|
1475
1514
|
// Suppress disk writes inside loadConfig() — we just wrote the raw config
|
|
1476
1515
|
// and the first-launch seed path would overwrite it with full defaults.
|
|
@@ -2344,11 +2383,14 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
2344
2383
|
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
2345
2384
|
},
|
|
2346
2385
|
summary: "Set embedding config",
|
|
2347
|
-
description:
|
|
2386
|
+
description:
|
|
2387
|
+
"Change the embedding provider, model, and optional custom endpoint.",
|
|
2348
2388
|
tags: ["config"],
|
|
2349
2389
|
requestBody: z.object({
|
|
2350
2390
|
provider: z.string(),
|
|
2351
2391
|
model: z.string().optional(),
|
|
2392
|
+
baseUrl: z.string().optional(),
|
|
2393
|
+
dimensions: z.number().int().positive().nullable().optional(),
|
|
2352
2394
|
}),
|
|
2353
2395
|
handler: handleSetEmbeddingConfig,
|
|
2354
2396
|
},
|
|
@@ -208,7 +208,10 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
208
208
|
operationId: "updateDocumentComment",
|
|
209
209
|
endpoint: "documents/:id/comments/:commentId",
|
|
210
210
|
method: "PATCH",
|
|
211
|
-
policy:
|
|
211
|
+
policy: {
|
|
212
|
+
requiredScopes: ["settings.write"],
|
|
213
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
214
|
+
},
|
|
212
215
|
summary: "Update a document comment",
|
|
213
216
|
description: "Update the status or content of a comment.",
|
|
214
217
|
tags: ["documents"],
|
|
@@ -273,7 +276,10 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
273
276
|
operationId: "deleteDocumentComment",
|
|
274
277
|
endpoint: "documents/:id/comments/:commentId",
|
|
275
278
|
method: "DELETE",
|
|
276
|
-
policy:
|
|
279
|
+
policy: {
|
|
280
|
+
requiredScopes: ["settings.write"],
|
|
281
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
282
|
+
},
|
|
277
283
|
summary: "Delete a document comment",
|
|
278
284
|
description: "Permanently delete a comment.",
|
|
279
285
|
tags: ["documents"],
|
|
@@ -116,6 +116,7 @@ async function handleGuardianActionDecision({
|
|
|
116
116
|
return {
|
|
117
117
|
applied: false,
|
|
118
118
|
reason: result.reason,
|
|
119
|
+
...(result.committed !== undefined ? { committed: result.committed } : {}),
|
|
119
120
|
...(result.resolverFailureReason
|
|
120
121
|
? { resolverFailureReason: result.resolverFailureReason }
|
|
121
122
|
: {}),
|
|
@@ -271,6 +272,12 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
271
272
|
.string()
|
|
272
273
|
.optional()
|
|
273
274
|
.describe("Decline reason (present only when applied is false)"),
|
|
275
|
+
committed: z
|
|
276
|
+
.boolean()
|
|
277
|
+
.optional()
|
|
278
|
+
.describe(
|
|
279
|
+
"Whether the decision was recorded before the decline. Present only for a resolver failure: true under resolver_failed (the decision committed and its follow-through failed), false under decision_not_persisted (nothing was written; retry is safe). Absent on daemons that report both under resolver_failed.",
|
|
280
|
+
),
|
|
274
281
|
replyText: z
|
|
275
282
|
.string()
|
|
276
283
|
.optional()
|