@vellumai/assistant 0.12.6-staging.3 → 0.12.7-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/.env.example +2 -0
- package/ARCHITECTURE.md +14 -14
- package/docs/architecture/integrations.md +5 -5
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +2 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +8 -4
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +2 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +8 -4
- package/node_modules/@vellumai/gateway-client/src/__tests__/route-trust-class.test.ts +111 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -2
- package/node_modules/@vellumai/gateway-client/src/index.ts +9 -0
- package/node_modules/@vellumai/gateway-client/src/route-trust-class.ts +94 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +2 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +8 -4
- package/openapi.yaml +153 -5
- package/package.json +1 -1
- package/src/__tests__/channel-policy.test.ts +7 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +141 -2
- package/src/__tests__/identity-routes.test.ts +1 -0
- package/src/__tests__/list-messages-client-message-id.test.ts +43 -0
- package/src/__tests__/openai-provider.test.ts +27 -0
- package/src/__tests__/skill-execute-input.test.ts +11 -0
- package/src/__tests__/tool-approval-handler.test.ts +4 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +26 -0
- package/src/agent/loop-unparseable-tool-retry.test.ts +333 -0
- package/src/agent/loop.ts +133 -12
- package/src/calls/__tests__/call-controller.test.ts +273 -1
- package/src/calls/__tests__/media-stream-server-integration.test.ts +40 -0
- package/src/calls/__tests__/media-stream-stt-session.test.ts +33 -0
- package/src/calls/call-constants.ts +10 -0
- package/src/calls/call-controller.ts +359 -25
- package/src/calls/media-stream-server.ts +18 -0
- package/src/calls/media-stream-stt-session.ts +30 -9
- package/src/channels/config.ts +8 -0
- package/src/cli/lib/bundled-marketplace.json +39 -0
- package/src/config/composer-preferences.test.ts +97 -0
- package/src/config/composer-preferences.ts +95 -0
- package/src/config/feature-flag-registry.json +16 -8
- package/src/config/schemas/llm.ts +1 -0
- package/src/contacts/types.ts +1 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +18 -2
- package/src/daemon/conversation-agent-loop.ts +3 -0
- package/src/daemon/conversation-messaging.ts +4 -0
- package/src/daemon/conversation-process.ts +15 -0
- package/src/daemon/conversation-queue-manager.ts +2 -0
- package/src/daemon/conversation.ts +2 -0
- package/src/daemon/handlers/shared.ts +13 -2
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/process-message.ts +5 -0
- package/src/i18n/__tests__/i18n.test.ts +25 -0
- package/src/i18n/assistant-text.ts +18 -0
- package/src/i18n/index.ts +1 -0
- package/src/i18n/messages.ts +11 -0
- package/src/ipc/routes/__tests__/route-adapter.test.ts +40 -0
- package/src/ipc/routes/route-adapter.ts +9 -0
- package/src/notifications/README.md +1 -1
- package/src/notifications/signal.ts +4 -0
- package/src/persistence/conversation-key-store.ts +12 -11
- package/src/providers/__tests__/unparseable-tool-args.test.ts +25 -0
- package/src/providers/inference/adapter-factory.ts +6 -0
- package/src/providers/ionet/client.test.ts +143 -0
- package/src/providers/ionet/client.ts +37 -0
- package/src/providers/model-catalog.ts +40 -0
- package/src/providers/openai/chat-completions-provider.ts +33 -2
- package/src/providers/retry.ts +10 -4
- package/src/providers/unparseable-tool-args.ts +29 -5
- package/src/runtime/__tests__/http-router-trust.test.ts +360 -0
- package/src/runtime/__tests__/shared-principal-lookup.test.ts +346 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +51 -1
- package/src/runtime/auth/__tests__/route-policy.test.ts +85 -1
- package/src/runtime/auth/__tests__/scopes.test.ts +36 -0
- package/src/runtime/auth/route-policy.ts +44 -0
- package/src/runtime/auth/scopes.ts +15 -0
- package/src/runtime/auth/types.ts +1 -0
- package/src/runtime/channel-reply-delivery.ts +2 -0
- package/src/runtime/http-router.ts +61 -1
- package/src/runtime/http-types.ts +3 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +35 -10
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +48 -0
- package/src/runtime/migrations/vbundle-builder.ts +19 -3
- package/src/runtime/migrations/vbundle-streaming-importer.ts +14 -19
- package/src/runtime/migrations/vbundle-streaming-validator.ts +14 -7
- package/src/runtime/migrations/vbundle-validator.ts +54 -1
- package/src/runtime/routes/composer-settings-routes.test.ts +71 -0
- package/src/runtime/routes/composer-settings-routes.ts +76 -0
- package/src/runtime/routes/conversation-routes.ts +54 -14
- package/src/runtime/routes/identity-routes.ts +2 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/migration-routes.ts +3 -1
- package/src/runtime/routes/types.ts +4 -1
- package/src/runtime/shared-principal-lookup.ts +235 -0
- package/src/tools/host-terminal/host-shell.ts +4 -2
- package/src/tools/shared/shell-output.test.ts +42 -0
- package/src/tools/shared/shell-output.ts +27 -4
- package/src/tools/skills/execute.ts +10 -7
- package/src/tools/terminal/shell.ts +2 -0
- package/src/tools/tool-approval-handler.ts +9 -1
package/openapi.yaml
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
openapi: 3.1.0
|
|
4
4
|
info:
|
|
5
5
|
title: Vellum Assistant API
|
|
6
|
-
version: 0.12.
|
|
6
|
+
version: 0.12.7
|
|
7
7
|
description: Auto-generated OpenAPI specification for the Vellum Assistant runtime HTTP server.
|
|
8
8
|
servers:
|
|
9
9
|
- url: http://127.0.0.1:7821
|
|
@@ -4849,6 +4849,7 @@ paths:
|
|
|
4849
4849
|
- a2a
|
|
4850
4850
|
- discord
|
|
4851
4851
|
- plugin
|
|
4852
|
+
- vellum-shared
|
|
4852
4853
|
assistantId:
|
|
4853
4854
|
type: string
|
|
4854
4855
|
hasPendingChallenge:
|
|
@@ -5279,6 +5280,7 @@ paths:
|
|
|
5279
5280
|
- a2a
|
|
5280
5281
|
- discord
|
|
5281
5282
|
- plugin
|
|
5283
|
+
- vellum-shared
|
|
5282
5284
|
ready:
|
|
5283
5285
|
type: boolean
|
|
5284
5286
|
setupStatus:
|
|
@@ -5403,6 +5405,7 @@ paths:
|
|
|
5403
5405
|
- a2a
|
|
5404
5406
|
- discord
|
|
5405
5407
|
- plugin
|
|
5408
|
+
- vellum-shared
|
|
5406
5409
|
includeRemote:
|
|
5407
5410
|
description: Include remote checks (default true)
|
|
5408
5411
|
type: boolean
|
|
@@ -5434,6 +5437,7 @@ paths:
|
|
|
5434
5437
|
- a2a
|
|
5435
5438
|
- discord
|
|
5436
5439
|
- plugin
|
|
5440
|
+
- vellum-shared
|
|
5437
5441
|
ready:
|
|
5438
5442
|
type: boolean
|
|
5439
5443
|
setupStatus:
|
|
@@ -5567,6 +5571,7 @@ paths:
|
|
|
5567
5571
|
- a2a
|
|
5568
5572
|
- discord
|
|
5569
5573
|
- plugin
|
|
5574
|
+
- vellum-shared
|
|
5570
5575
|
description: Channel to send on
|
|
5571
5576
|
target:
|
|
5572
5577
|
type: object
|
|
@@ -5617,6 +5622,7 @@ paths:
|
|
|
5617
5622
|
- a2a
|
|
5618
5623
|
- discord
|
|
5619
5624
|
- plugin
|
|
5625
|
+
- vellum-shared
|
|
5620
5626
|
chatId:
|
|
5621
5627
|
type: string
|
|
5622
5628
|
threadId:
|
|
@@ -5835,6 +5841,135 @@ paths:
|
|
|
5835
5841
|
required:
|
|
5836
5842
|
- recorded
|
|
5837
5843
|
additionalProperties: false
|
|
5844
|
+
/v1/composer/settings:
|
|
5845
|
+
get:
|
|
5846
|
+
operationId: composer_settings_get
|
|
5847
|
+
summary: Get the current user's composer preferences
|
|
5848
|
+
tags:
|
|
5849
|
+
- settings
|
|
5850
|
+
responses:
|
|
5851
|
+
"200":
|
|
5852
|
+
description: Successful response
|
|
5853
|
+
content:
|
|
5854
|
+
application/json:
|
|
5855
|
+
schema:
|
|
5856
|
+
type: object
|
|
5857
|
+
properties:
|
|
5858
|
+
preferences:
|
|
5859
|
+
type: object
|
|
5860
|
+
properties:
|
|
5861
|
+
favoriteModeIds:
|
|
5862
|
+
default: []
|
|
5863
|
+
maxItems: 5
|
|
5864
|
+
type: array
|
|
5865
|
+
items:
|
|
5866
|
+
type: string
|
|
5867
|
+
minLength: 1
|
|
5868
|
+
maxLength: 200
|
|
5869
|
+
lastModeId:
|
|
5870
|
+
default: null
|
|
5871
|
+
anyOf:
|
|
5872
|
+
- type: string
|
|
5873
|
+
minLength: 1
|
|
5874
|
+
maxLength: 200
|
|
5875
|
+
- type: "null"
|
|
5876
|
+
lastAutonomy:
|
|
5877
|
+
default: null
|
|
5878
|
+
anyOf:
|
|
5879
|
+
- type: string
|
|
5880
|
+
enum:
|
|
5881
|
+
- none
|
|
5882
|
+
- low
|
|
5883
|
+
- medium
|
|
5884
|
+
- high
|
|
5885
|
+
- type: "null"
|
|
5886
|
+
required:
|
|
5887
|
+
- favoriteModeIds
|
|
5888
|
+
- lastModeId
|
|
5889
|
+
- lastAutonomy
|
|
5890
|
+
additionalProperties: false
|
|
5891
|
+
required:
|
|
5892
|
+
- preferences
|
|
5893
|
+
additionalProperties: false
|
|
5894
|
+
patch:
|
|
5895
|
+
operationId: composer_settings_patch
|
|
5896
|
+
summary: Update the current user's composer preferences
|
|
5897
|
+
tags:
|
|
5898
|
+
- settings
|
|
5899
|
+
requestBody:
|
|
5900
|
+
required: true
|
|
5901
|
+
content:
|
|
5902
|
+
application/json:
|
|
5903
|
+
schema:
|
|
5904
|
+
type: object
|
|
5905
|
+
properties:
|
|
5906
|
+
favoriteModeIds:
|
|
5907
|
+
maxItems: 5
|
|
5908
|
+
type: array
|
|
5909
|
+
items:
|
|
5910
|
+
type: string
|
|
5911
|
+
minLength: 1
|
|
5912
|
+
maxLength: 200
|
|
5913
|
+
lastModeId:
|
|
5914
|
+
anyOf:
|
|
5915
|
+
- type: string
|
|
5916
|
+
minLength: 1
|
|
5917
|
+
maxLength: 200
|
|
5918
|
+
- type: "null"
|
|
5919
|
+
lastAutonomy:
|
|
5920
|
+
anyOf:
|
|
5921
|
+
- type: string
|
|
5922
|
+
enum:
|
|
5923
|
+
- none
|
|
5924
|
+
- low
|
|
5925
|
+
- medium
|
|
5926
|
+
- high
|
|
5927
|
+
- type: "null"
|
|
5928
|
+
additionalProperties: false
|
|
5929
|
+
responses:
|
|
5930
|
+
"200":
|
|
5931
|
+
description: Successful response
|
|
5932
|
+
content:
|
|
5933
|
+
application/json:
|
|
5934
|
+
schema:
|
|
5935
|
+
type: object
|
|
5936
|
+
properties:
|
|
5937
|
+
preferences:
|
|
5938
|
+
type: object
|
|
5939
|
+
properties:
|
|
5940
|
+
favoriteModeIds:
|
|
5941
|
+
default: []
|
|
5942
|
+
maxItems: 5
|
|
5943
|
+
type: array
|
|
5944
|
+
items:
|
|
5945
|
+
type: string
|
|
5946
|
+
minLength: 1
|
|
5947
|
+
maxLength: 200
|
|
5948
|
+
lastModeId:
|
|
5949
|
+
default: null
|
|
5950
|
+
anyOf:
|
|
5951
|
+
- type: string
|
|
5952
|
+
minLength: 1
|
|
5953
|
+
maxLength: 200
|
|
5954
|
+
- type: "null"
|
|
5955
|
+
lastAutonomy:
|
|
5956
|
+
default: null
|
|
5957
|
+
anyOf:
|
|
5958
|
+
- type: string
|
|
5959
|
+
enum:
|
|
5960
|
+
- none
|
|
5961
|
+
- low
|
|
5962
|
+
- medium
|
|
5963
|
+
- high
|
|
5964
|
+
- type: "null"
|
|
5965
|
+
required:
|
|
5966
|
+
- favoriteModeIds
|
|
5967
|
+
- lastModeId
|
|
5968
|
+
- lastAutonomy
|
|
5969
|
+
additionalProperties: false
|
|
5970
|
+
required:
|
|
5971
|
+
- preferences
|
|
5972
|
+
additionalProperties: false
|
|
5838
5973
|
/v1/config:
|
|
5839
5974
|
get:
|
|
5840
5975
|
operationId: config_get
|
|
@@ -6025,6 +6160,7 @@ paths:
|
|
|
6025
6160
|
- minimax
|
|
6026
6161
|
- atlascloud
|
|
6027
6162
|
- baseten
|
|
6163
|
+
- ionet
|
|
6028
6164
|
- poolside
|
|
6029
6165
|
connectionName:
|
|
6030
6166
|
type: string
|
|
@@ -8073,6 +8209,7 @@ paths:
|
|
|
8073
8209
|
- a2a
|
|
8074
8210
|
- discord
|
|
8075
8211
|
- plugin
|
|
8212
|
+
- vellum-shared
|
|
8076
8213
|
description:
|
|
8077
8214
|
Filter by origin channel. When provided, only conversations with this exact origin_channel value are
|
|
8078
8215
|
returned. Omit to include all channels.
|
|
@@ -8237,6 +8374,7 @@ paths:
|
|
|
8237
8374
|
- a2a
|
|
8238
8375
|
- discord
|
|
8239
8376
|
- plugin
|
|
8377
|
+
- vellum-shared
|
|
8240
8378
|
- type: "null"
|
|
8241
8379
|
assistantAttention:
|
|
8242
8380
|
type: object
|
|
@@ -8607,6 +8745,7 @@ paths:
|
|
|
8607
8745
|
- a2a
|
|
8608
8746
|
- discord
|
|
8609
8747
|
- plugin
|
|
8748
|
+
- vellum-shared
|
|
8610
8749
|
- type: "null"
|
|
8611
8750
|
assistantAttention:
|
|
8612
8751
|
type: object
|
|
@@ -9821,6 +9960,7 @@ paths:
|
|
|
9821
9960
|
- a2a
|
|
9822
9961
|
- discord
|
|
9823
9962
|
- plugin
|
|
9963
|
+
- vellum-shared
|
|
9824
9964
|
- type: "null"
|
|
9825
9965
|
assistantAttention:
|
|
9826
9966
|
type: object
|
|
@@ -14470,6 +14610,8 @@ paths:
|
|
|
14470
14610
|
type: boolean
|
|
14471
14611
|
chatsSettings:
|
|
14472
14612
|
type: boolean
|
|
14613
|
+
composerSettings:
|
|
14614
|
+
type: boolean
|
|
14473
14615
|
required:
|
|
14474
14616
|
- memoryOptOut
|
|
14475
14617
|
- retryLastTurn
|
|
@@ -14706,6 +14848,8 @@ paths:
|
|
|
14706
14848
|
type: boolean
|
|
14707
14849
|
chatsSettings:
|
|
14708
14850
|
type: boolean
|
|
14851
|
+
composerSettings:
|
|
14852
|
+
type: boolean
|
|
14709
14853
|
required:
|
|
14710
14854
|
- memoryOptOut
|
|
14711
14855
|
- retryLastTurn
|
|
@@ -16973,8 +17117,8 @@ paths:
|
|
|
16973
17117
|
type: string
|
|
16974
17118
|
description:
|
|
16975
17119
|
"Filter by provider id. One of: anthropic, openai, gemini, ollama, fireworks, together, openrouter,
|
|
16976
|
-
vercel-ai-gateway, litellm, opencode, openai-compatible, minimax, atlascloud, baseten,
|
|
16977
|
-
vellum"
|
|
17120
|
+
vercel-ai-gateway, litellm, opencode, openai-compatible, minimax, atlascloud, baseten, ionet, poolside,
|
|
17121
|
+
typesafe, vellum"
|
|
16978
17122
|
responses:
|
|
16979
17123
|
"200":
|
|
16980
17124
|
description: Successful response
|
|
@@ -17246,8 +17390,8 @@ paths:
|
|
|
17246
17390
|
type: string
|
|
17247
17391
|
description:
|
|
17248
17392
|
"Filter by provider. One of: anthropic, openai, gemini, ollama, fireworks, together, openrouter,
|
|
17249
|
-
vercel-ai-gateway, litellm, opencode, openai-compatible, minimax, atlascloud, baseten,
|
|
17250
|
-
vellum, chatgpt"
|
|
17393
|
+
vercel-ai-gateway, litellm, opencode, openai-compatible, minimax, atlascloud, baseten, ionet, poolside,
|
|
17394
|
+
typesafe, vellum, chatgpt"
|
|
17251
17395
|
responses:
|
|
17252
17396
|
"200":
|
|
17253
17397
|
description: Successful response
|
|
@@ -25225,6 +25369,7 @@ paths:
|
|
|
25225
25369
|
- a2a
|
|
25226
25370
|
- discord
|
|
25227
25371
|
- plugin
|
|
25372
|
+
- vellum-shared
|
|
25228
25373
|
- scheduler
|
|
25229
25374
|
- watcher
|
|
25230
25375
|
sourceContextId:
|
|
@@ -39425,6 +39570,7 @@ components:
|
|
|
39425
39570
|
- minimax
|
|
39426
39571
|
- atlascloud
|
|
39427
39572
|
- baseten
|
|
39573
|
+
- ionet
|
|
39428
39574
|
- poolside
|
|
39429
39575
|
- typesafe
|
|
39430
39576
|
- vellum
|
|
@@ -39689,6 +39835,7 @@ components:
|
|
|
39689
39835
|
- minimax
|
|
39690
39836
|
- atlascloud
|
|
39691
39837
|
- baseten
|
|
39838
|
+
- ionet
|
|
39692
39839
|
- poolside
|
|
39693
39840
|
connectionName:
|
|
39694
39841
|
type: string
|
|
@@ -40077,6 +40224,7 @@ components:
|
|
|
40077
40224
|
- minimax
|
|
40078
40225
|
- atlascloud
|
|
40079
40226
|
- baseten
|
|
40227
|
+
- ionet
|
|
40080
40228
|
- poolside
|
|
40081
40229
|
- type: "null"
|
|
40082
40230
|
connectionName:
|
package/package.json
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
isNotificationDeliverable,
|
|
17
17
|
} from "../channels/config.js";
|
|
18
18
|
import { CHANNEL_IDS, type ChannelId } from "../channels/types.js";
|
|
19
|
+
import { getTransportForChannel } from "../messaging/providers/index.js";
|
|
19
20
|
|
|
20
21
|
describe("channel policy registry", () => {
|
|
21
22
|
// ── Exhaustiveness ────────────────────────────────────────────────────
|
|
@@ -133,6 +134,12 @@ describe("channel policy registry", () => {
|
|
|
133
134
|
expect(getConversationStrategy("phone")).toBe("not_deliverable");
|
|
134
135
|
});
|
|
135
136
|
|
|
137
|
+
test("vellum-shared has no transport and receives no notifications", () => {
|
|
138
|
+
expect(isNotificationDeliverable("vellum-shared")).toBe(false);
|
|
139
|
+
expect(getConversationStrategy("vellum-shared")).toBe("not_deliverable");
|
|
140
|
+
expect(getTransportForChannel("vellum-shared")).toBeUndefined();
|
|
141
|
+
});
|
|
142
|
+
|
|
136
143
|
test("deliverable channels include vellum and telegram", () => {
|
|
137
144
|
const deliverable = getDeliverableChannels();
|
|
138
145
|
expect(deliverable).toContain("vellum");
|
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
2
8
|
import { join } from "node:path";
|
|
3
|
-
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
9
|
+
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
|
|
4
10
|
|
|
5
11
|
import { createAssistantMessage } from "../agent/message-types.js";
|
|
6
12
|
import type { Conversation } from "../daemon/conversation.js";
|
|
7
13
|
import type { EnqueueMessageOptions } from "../daemon/conversation-messaging.js";
|
|
8
14
|
import { persistUserMessage } from "../daemon/conversation-messaging.js";
|
|
15
|
+
import * as gatewayClient from "../ipc/gateway-client.js";
|
|
9
16
|
import {
|
|
10
17
|
addMessage,
|
|
11
18
|
findMessageIdByClientMessageId,
|
|
@@ -18,11 +25,16 @@ import {
|
|
|
18
25
|
syncMessageToDisk,
|
|
19
26
|
} from "../persistence/conversation-disk-view.js";
|
|
20
27
|
import {
|
|
28
|
+
_resetFirstConversationSeenForTesting,
|
|
21
29
|
getConversationByKey,
|
|
22
30
|
getOrCreateConversation as getOrCreateConversationMapping,
|
|
23
31
|
} from "../persistence/conversation-key-store.js";
|
|
24
32
|
import { getDb } from "../persistence/db-connection.js";
|
|
25
33
|
import { initializeDb } from "../persistence/db-init.js";
|
|
34
|
+
import {
|
|
35
|
+
conversationKeys,
|
|
36
|
+
conversations,
|
|
37
|
+
} from "../persistence/schema/index.js";
|
|
26
38
|
import {
|
|
27
39
|
AssistantEventHub,
|
|
28
40
|
assistantEventHub,
|
|
@@ -30,6 +42,7 @@ import {
|
|
|
30
42
|
import type { AuthContext } from "../runtime/auth/types.js";
|
|
31
43
|
import * as pendingInteractions from "../runtime/pending-interactions.js";
|
|
32
44
|
import { handleSendMessage } from "../runtime/routes/conversation-routes.js";
|
|
45
|
+
import { assertNotLiveDb } from "./assert-not-live-db.js";
|
|
33
46
|
import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
|
|
34
47
|
import { callHandler } from "./helpers/call-route-handler.js";
|
|
35
48
|
import { mockUnownedModeSessions } from "./helpers/mock-conversation.js";
|
|
@@ -541,6 +554,132 @@ describe("POST /v1/messages — body.conversationId direct id lookup", () => {
|
|
|
541
554
|
);
|
|
542
555
|
}
|
|
543
556
|
|
|
557
|
+
test.each(["unavailable", "rejected"])(
|
|
558
|
+
"an autonomy write that is %s rolls back a minted chat before retry",
|
|
559
|
+
async (failure) => {
|
|
560
|
+
_resetFirstConversationSeenForTesting();
|
|
561
|
+
const bootstrapFiles = ["BOOTSTRAP.md", "BOOTSTRAP-REFERENCE.md"].map(
|
|
562
|
+
(name) => join(testDir, name),
|
|
563
|
+
);
|
|
564
|
+
for (const path of bootstrapFiles) {
|
|
565
|
+
writeFileSync(path, "Onboarding instructions");
|
|
566
|
+
}
|
|
567
|
+
const ipc = spyOn(gatewayClient, "ipcCall");
|
|
568
|
+
if (failure === "unavailable") {
|
|
569
|
+
ipc.mockResolvedValueOnce(undefined);
|
|
570
|
+
} else {
|
|
571
|
+
ipc.mockRejectedValueOnce(new Error("Gateway unavailable"));
|
|
572
|
+
}
|
|
573
|
+
const body = {
|
|
574
|
+
content: "Start a new chat",
|
|
575
|
+
sourceChannel: "vellum",
|
|
576
|
+
interface: "macos",
|
|
577
|
+
riskThreshold: "none",
|
|
578
|
+
};
|
|
579
|
+
try {
|
|
580
|
+
if (failure === "rejected") {
|
|
581
|
+
await expect(sendMessage(body)).rejects.toThrow(
|
|
582
|
+
"Gateway unavailable",
|
|
583
|
+
);
|
|
584
|
+
} else {
|
|
585
|
+
const failed = await sendMessage(body);
|
|
586
|
+
expect(failed.status).toBe(500);
|
|
587
|
+
}
|
|
588
|
+
expect(getDb().select().from(conversations).all()).toHaveLength(0);
|
|
589
|
+
expect(getDb().select().from(conversationKeys).all()).toHaveLength(0);
|
|
590
|
+
expect(conversationInstances.size).toBe(0);
|
|
591
|
+
expect(bootstrapFiles.every((path) => existsSync(path))).toBe(true);
|
|
592
|
+
|
|
593
|
+
ipc.mockResolvedValueOnce({ ok: true });
|
|
594
|
+
const retried = await sendMessage(body);
|
|
595
|
+
expect(retried.status).toBe(202);
|
|
596
|
+
const rows = getDb().select().from(conversations).all();
|
|
597
|
+
expect(rows).toHaveLength(1);
|
|
598
|
+
expect(getDb().select().from(conversationKeys).all()).toHaveLength(1);
|
|
599
|
+
await waitFor(() => {
|
|
600
|
+
const messages = readPersistedMessages(rows[0]!.id);
|
|
601
|
+
return messages.length === 2 ? messages : undefined;
|
|
602
|
+
});
|
|
603
|
+
expect(bootstrapFiles.every((path) => existsSync(path))).toBe(true);
|
|
604
|
+
getOrCreateConversationMapping("second-successful-chat");
|
|
605
|
+
expect(bootstrapFiles.some((path) => existsSync(path))).toBe(false);
|
|
606
|
+
} finally {
|
|
607
|
+
ipc.mockRestore();
|
|
608
|
+
for (const path of bootstrapFiles) {
|
|
609
|
+
assertNotLiveDb(path);
|
|
610
|
+
rmSync(path, { force: true });
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
},
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
test("an autonomy write failure preserves an existing chat and its messages", async () => {
|
|
617
|
+
const seeded = getOrCreateConversationMapping("existing-chat");
|
|
618
|
+
await addMessage(seeded.conversationId, "user", "Existing message");
|
|
619
|
+
const ipc = spyOn(gatewayClient, "ipcCall").mockResolvedValueOnce(
|
|
620
|
+
undefined,
|
|
621
|
+
);
|
|
622
|
+
try {
|
|
623
|
+
const response = await sendMessage({
|
|
624
|
+
conversationId: seeded.conversationId,
|
|
625
|
+
content: "Continue this chat",
|
|
626
|
+
sourceChannel: "vellum",
|
|
627
|
+
interface: "macos",
|
|
628
|
+
riskThreshold: "none",
|
|
629
|
+
});
|
|
630
|
+
expect(response.status).toBe(500);
|
|
631
|
+
expect(getConversation(seeded.conversationId)).not.toBeNull();
|
|
632
|
+
expect(readPersistedMessages(seeded.conversationId)).toHaveLength(1);
|
|
633
|
+
} finally {
|
|
634
|
+
ipc.mockRestore();
|
|
635
|
+
}
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
test("a minted chat with a concurrent message survives an autonomy write failure", async () => {
|
|
639
|
+
const ipc = spyOn(gatewayClient, "ipcCall").mockImplementationOnce(
|
|
640
|
+
async () => {
|
|
641
|
+
const row = getDb().select().from(conversations).all()[0]!;
|
|
642
|
+
await addMessage(row.id, "user", "Concurrent message");
|
|
643
|
+
return undefined;
|
|
644
|
+
},
|
|
645
|
+
);
|
|
646
|
+
try {
|
|
647
|
+
const response = await sendMessage({
|
|
648
|
+
content: "Start a new chat",
|
|
649
|
+
sourceChannel: "vellum",
|
|
650
|
+
interface: "macos",
|
|
651
|
+
riskThreshold: "none",
|
|
652
|
+
});
|
|
653
|
+
expect(response.status).toBe(500);
|
|
654
|
+
const rows = getDb().select().from(conversations).all();
|
|
655
|
+
expect(rows).toHaveLength(1);
|
|
656
|
+
expect(readPersistedMessages(rows[0]!.id)).toHaveLength(1);
|
|
657
|
+
} finally {
|
|
658
|
+
ipc.mockRestore();
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
test("an autonomy write failure preserves a keyed chat for retry or concurrent senders", async () => {
|
|
663
|
+
const ipc = spyOn(gatewayClient, "ipcCall").mockResolvedValueOnce(
|
|
664
|
+
undefined,
|
|
665
|
+
);
|
|
666
|
+
try {
|
|
667
|
+
const response = await sendMessage({
|
|
668
|
+
conversationKey: "shared-chat-key",
|
|
669
|
+
content: "Send to a keyed chat",
|
|
670
|
+
sourceChannel: "vellum",
|
|
671
|
+
interface: "macos",
|
|
672
|
+
riskThreshold: "none",
|
|
673
|
+
});
|
|
674
|
+
expect(response.status).toBe(500);
|
|
675
|
+
const mapping = getConversationByKey("shared-chat-key");
|
|
676
|
+
expect(mapping).not.toBeNull();
|
|
677
|
+
expect(getConversation(mapping!.conversationId)).not.toBeNull();
|
|
678
|
+
} finally {
|
|
679
|
+
ipc.mockRestore();
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
|
|
544
683
|
test("body.conversationId=<existing-id> scopes the send to that conversation", async () => {
|
|
545
684
|
// Pre-materialise a conversation via the key path, then send a message
|
|
546
685
|
// by its assistant-minted internal id.
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { beforeEach, describe, expect, test } from "bun:test";
|
|
10
10
|
|
|
11
|
+
import { MESSAGE_KEYS } from "../i18n/index.js";
|
|
11
12
|
import { setConfig } from "./helpers/set-config.js";
|
|
12
13
|
|
|
13
14
|
// Keep the memory system off so addMessage skips indexing side effects.
|
|
@@ -31,6 +32,7 @@ function resetTables() {
|
|
|
31
32
|
|
|
32
33
|
interface MessagePayload {
|
|
33
34
|
role: string;
|
|
35
|
+
textSegments?: string[];
|
|
34
36
|
clientMessageId?: string;
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -59,6 +61,47 @@ describe("handleListMessages clientMessageId", () => {
|
|
|
59
61
|
expect(body.messages[0].clientMessageId).toBe("nonce-abc");
|
|
60
62
|
});
|
|
61
63
|
|
|
64
|
+
test("localizes persisted assistant keys without rewriting legacy text", async () => {
|
|
65
|
+
const localizedConv = createConversation();
|
|
66
|
+
await addMessage(
|
|
67
|
+
localizedConv.id,
|
|
68
|
+
"assistant",
|
|
69
|
+
JSON.stringify([
|
|
70
|
+
{
|
|
71
|
+
type: "text",
|
|
72
|
+
text: MESSAGE_KEYS.AGENT_LOOP_UNPARSEABLE_TOOL_RETRY_STOP,
|
|
73
|
+
},
|
|
74
|
+
]),
|
|
75
|
+
{ skipIndexing: true },
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const localizedResponse = await handleListMessages({
|
|
79
|
+
queryParams: { conversationId: localizedConv.id },
|
|
80
|
+
headers: { "accept-language": "es" },
|
|
81
|
+
});
|
|
82
|
+
const localizedBody = localizedResponse as { messages: MessagePayload[] };
|
|
83
|
+
expect(localizedBody.messages[0]?.textSegments).toEqual([
|
|
84
|
+
"Me detuve después de varias llamadas de herramienta con formato incorrecto para evitar que esta conversación creciera indefinidamente. Inténtalo de nuevo o cambia de modelo si el problema continúa.",
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
const legacyConv = createConversation();
|
|
88
|
+
const legacy =
|
|
89
|
+
"I stopped after repeated malformed tool calls to keep this conversation from growing indefinitely. Please try again, or switch models if the problem continues.";
|
|
90
|
+
await addMessage(
|
|
91
|
+
legacyConv.id,
|
|
92
|
+
"assistant",
|
|
93
|
+
JSON.stringify([{ type: "text", text: legacy }]),
|
|
94
|
+
{ skipIndexing: true },
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const legacyResponse = await handleListMessages({
|
|
98
|
+
queryParams: { conversationId: legacyConv.id },
|
|
99
|
+
headers: { "accept-language": "es" },
|
|
100
|
+
});
|
|
101
|
+
const legacyBody = legacyResponse as { messages: MessagePayload[] };
|
|
102
|
+
expect(legacyBody.messages[0]?.textSegments).toEqual([legacy]);
|
|
103
|
+
});
|
|
104
|
+
|
|
62
105
|
test("omits clientMessageId when the row was persisted without one", async () => {
|
|
63
106
|
// GIVEN a user message persisted without a client nonce (e.g. a channel send)
|
|
64
107
|
const conv = createConversation();
|
|
@@ -1178,6 +1178,33 @@ describe("OpenAIProvider", () => {
|
|
|
1178
1178
|
});
|
|
1179
1179
|
});
|
|
1180
1180
|
|
|
1181
|
+
test("bounds malformed arguments in the diagnostic raw response only", async () => {
|
|
1182
|
+
const raw = '{"content":"' + "x".repeat(1_000);
|
|
1183
|
+
fakeChunks = [
|
|
1184
|
+
...toolCallChunks([{ id: "call_large_bad", name: "test", args: raw }]),
|
|
1185
|
+
usageChunk(10, 5),
|
|
1186
|
+
];
|
|
1187
|
+
|
|
1188
|
+
const result = await provider.sendMessage([userMsg("test")]);
|
|
1189
|
+
|
|
1190
|
+
expect(result.content[0]).toEqual({
|
|
1191
|
+
type: "tool_use",
|
|
1192
|
+
id: "call_large_bad",
|
|
1193
|
+
name: "test",
|
|
1194
|
+
input: { _raw: raw },
|
|
1195
|
+
});
|
|
1196
|
+
const rawResponse = result.rawResponse as {
|
|
1197
|
+
choices: Array<{
|
|
1198
|
+
message: {
|
|
1199
|
+
tool_calls: Array<{ function: { arguments: string } }>;
|
|
1200
|
+
};
|
|
1201
|
+
}>;
|
|
1202
|
+
};
|
|
1203
|
+
expect(
|
|
1204
|
+
rawResponse.choices[0]!.message.tool_calls[0]!.function.arguments,
|
|
1205
|
+
).toBe(raw.slice(0, 200) + "…");
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1181
1208
|
// -----------------------------------------------------------------------
|
|
1182
1209
|
// stream_options and model
|
|
1183
1210
|
// -----------------------------------------------------------------------
|
|
@@ -188,6 +188,17 @@ describe("recoverSkillExecuteEnvelope", () => {
|
|
|
188
188
|
expect(recoverSkillExecuteEnvelope(wrapped)).toBe(wrapped);
|
|
189
189
|
});
|
|
190
190
|
|
|
191
|
+
test("keeps recovered JSON wrapped when it lacks a string tool name", () => {
|
|
192
|
+
for (const raw of [
|
|
193
|
+
JSON.stringify({ input: { content: "hi" }, activity: "x" }),
|
|
194
|
+
JSON.stringify({ tool: 42, input: {}, activity: "x" }),
|
|
195
|
+
JSON.stringify(["document_update"]),
|
|
196
|
+
]) {
|
|
197
|
+
const wrapped = { _raw: raw };
|
|
198
|
+
expect(recoverSkillExecuteEnvelope(wrapped)).toBe(wrapped);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
191
202
|
test("passes a normal envelope through untouched", () => {
|
|
192
203
|
const envelope = {
|
|
193
204
|
tool: "document_update",
|
|
@@ -1357,6 +1357,10 @@ describe("ToolApprovalHandler / unparseable tool args gate", () => {
|
|
|
1357
1357
|
}
|
|
1358
1358
|
expect(result.result.content).not.toContain(raw);
|
|
1359
1359
|
expect(result.result.content).toContain("…");
|
|
1360
|
+
expect(auditCalls.error).toHaveLength(1);
|
|
1361
|
+
expect(auditCalls.error[0].input).toEqual({
|
|
1362
|
+
_raw: raw.slice(0, 200) + "…",
|
|
1363
|
+
});
|
|
1360
1364
|
});
|
|
1361
1365
|
|
|
1362
1366
|
test("legitimate input containing a _raw field among others is not rejected", async () => {
|
|
@@ -341,6 +341,32 @@ describe("tool preview lifecycle", () => {
|
|
|
341
341
|
expect((emitted as any).conversationId).toBe("test-session-id");
|
|
342
342
|
});
|
|
343
343
|
|
|
344
|
+
test("caps cumulative app preview payloads and stops forwarding growth", () => {
|
|
345
|
+
const collector = createEventCollector();
|
|
346
|
+
const deps = createMockDeps({ onEvent: collector.onEvent });
|
|
347
|
+
const accumulatedJson = '{"code":"' + "x".repeat(300_000);
|
|
348
|
+
|
|
349
|
+
handleInputJsonDelta(state, deps, {
|
|
350
|
+
type: "input_json_delta",
|
|
351
|
+
toolName: "app_create",
|
|
352
|
+
toolUseId: "toolu_large_app",
|
|
353
|
+
accumulatedJson,
|
|
354
|
+
});
|
|
355
|
+
handleInputJsonDelta(state, deps, {
|
|
356
|
+
type: "input_json_delta",
|
|
357
|
+
toolName: "app_create",
|
|
358
|
+
toolUseId: "toolu_large_app",
|
|
359
|
+
accumulatedJson: accumulatedJson + "more",
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
expect(collector.events).toHaveLength(1);
|
|
363
|
+
const emitted = collector.events[0] as Extract<
|
|
364
|
+
AssistantEvent,
|
|
365
|
+
{ type: "tool_input_delta" }
|
|
366
|
+
>;
|
|
367
|
+
expect(emitted.content).toBe(accumulatedJson.slice(0, 256_000));
|
|
368
|
+
});
|
|
369
|
+
|
|
344
370
|
test("a streaming ui_show visual announces its surface exactly once", () => {
|
|
345
371
|
const collector = createEventCollector();
|
|
346
372
|
const deps = createMockDeps({ onEvent: collector.onEvent });
|