@vellumai/assistant 0.10.5-staging.1 → 0.10.5-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/openapi.yaml +81 -4
- package/package.json +1 -1
- package/src/__tests__/conversation-enabledplugins-route.test.ts +184 -0
- package/src/__tests__/conversation-queue.test.ts +48 -0
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +407 -0
- package/src/__tests__/list-messages-background-tool-completion.test.ts +6 -6
- package/src/__tests__/list-messages-queued.test.ts +25 -0
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +24 -0
- package/src/__tests__/title-generate-hook.test.ts +16 -0
- package/src/api/responses/conversation-message.ts +6 -6
- package/src/daemon/conversation-agent-loop.ts +8 -0
- package/src/daemon/conversation-process.ts +40 -14
- package/src/daemon/conversation.ts +2 -0
- package/src/daemon/wake-conversation-ops.ts +7 -8
- package/src/persistence/conversation-crud.ts +26 -1
- package/src/plugin-api/types.ts +7 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +5 -0
- package/src/runtime/routes/conversation-list-routes.ts +6 -0
- package/src/runtime/routes/conversation-management-routes.ts +71 -0
- package/src/runtime/routes/conversation-routes.ts +97 -65
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +13 -8
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +24 -9
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +19 -0
- package/src/runtime/services/conversation-serializer.ts +5 -0
- package/src/runtime/sync/resource-sync-events.ts +10 -0
|
@@ -205,13 +205,13 @@ describe("enforceIngressAcl — verdict-sourced member resolution", () => {
|
|
|
205
205
|
expect(findContactChannelCalls.length).toBe(0);
|
|
206
206
|
});
|
|
207
207
|
|
|
208
|
-
test("member-less guardian verdict
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
// stranger
|
|
214
|
-
// guardian
|
|
208
|
+
test("member-less guardian verdict fails safe: denied with no stranger-lane side effects", async () => {
|
|
209
|
+
// ATL-958 regression: the gateway proves guardian identity via a
|
|
210
|
+
// same-channel member row, so every guardian verdict carries one. A
|
|
211
|
+
// guardian verdict with NO member row is contradictory (e.g. a forged
|
|
212
|
+
// cross-channel classification) and must not be admitted — but it must
|
|
213
|
+
// not be routed through the stranger lane either, which would fire an
|
|
214
|
+
// access request carrying the claimed guardian identity.
|
|
215
215
|
const result = await enforceIngressAcl(
|
|
216
216
|
makeParams({
|
|
217
217
|
sourceMetadata: withVerdict({
|
|
@@ -222,10 +222,15 @@ describe("enforceIngressAcl — verdict-sourced member resolution", () => {
|
|
|
222
222
|
}),
|
|
223
223
|
);
|
|
224
224
|
|
|
225
|
-
expect(result.earlyResponse).
|
|
225
|
+
expect(result.earlyResponse).toMatchObject({
|
|
226
|
+
accepted: true,
|
|
227
|
+
denied: true,
|
|
228
|
+
reason: "not_a_member",
|
|
229
|
+
});
|
|
226
230
|
expect(result.resolvedMember).toBeNull();
|
|
227
231
|
expect(accessRequestCalls.length).toBe(0);
|
|
228
232
|
expect(deliverReplyCalls.length).toBe(0);
|
|
233
|
+
expect(createOutboundSessionCalls.length).toBe(0);
|
|
229
234
|
});
|
|
230
235
|
|
|
231
236
|
test("guardian verdict with an inactive (pending) member row is still admitted", async () => {
|
|
@@ -257,20 +257,35 @@ export async function enforceIngressAcl(
|
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
// ── Guardian short-circuit ──
|
|
260
|
-
// A verdict classified `guardian` is admitted even when
|
|
261
|
-
//
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
//
|
|
265
|
-
//
|
|
260
|
+
// A verdict classified `guardian` is admitted even when its same-channel
|
|
261
|
+
// member row is inactive (e.g. pending): the gateway classifies guardians
|
|
262
|
+
// by principal, so a guardian speaking on a channel where they hold no
|
|
263
|
+
// active guardian binding must not fall through the member-vs-stranger
|
|
264
|
+
// gates below — those would misroute the guardian into the stranger lane
|
|
265
|
+
// and fire an access request at the guardian themselves.
|
|
266
266
|
if (verdict.trustClass === "guardian") {
|
|
267
|
+
// The gateway proves guardian identity via a same-channel member row
|
|
268
|
+
// (the active binding address, or a row belonging to the guardian
|
|
269
|
+
// contact), so every guardian verdict carries a resolvable member row.
|
|
270
|
+
// A guardian verdict WITHOUT one is contradictory — cross-channel
|
|
271
|
+
// address collisions are not identity proofs and must never confer
|
|
272
|
+
// guardian capabilities. Fail safe: soft-deny with no stranger-lane
|
|
273
|
+
// side effects.
|
|
274
|
+
if (!resolvedMember) {
|
|
275
|
+
log.warn(
|
|
276
|
+
{ sourceChannel, externalUserId: canonicalSenderId },
|
|
277
|
+
"Ingress ACL: guardian verdict without a member row, denying fail-safe",
|
|
278
|
+
);
|
|
279
|
+
return failClosedDeny();
|
|
280
|
+
}
|
|
281
|
+
|
|
267
282
|
// The gateway never classifies a blocked/revoked same-channel row as
|
|
268
283
|
// guardian (explicit per-channel governance wins over the principal
|
|
269
284
|
// check), so a verdict claiming both is contradictory. Fail safe:
|
|
270
285
|
// soft-deny with no stranger-lane side effects.
|
|
271
286
|
if (
|
|
272
|
-
resolvedMember
|
|
273
|
-
resolvedMember
|
|
287
|
+
resolvedMember.status === "blocked" ||
|
|
288
|
+
resolvedMember.status === "revoked"
|
|
274
289
|
) {
|
|
275
290
|
log.warn(
|
|
276
291
|
{
|
|
@@ -288,7 +303,7 @@ export async function enforceIngressAcl(
|
|
|
288
303
|
// classification. Deny with the accurate policy_deny reason but none of
|
|
289
304
|
// the stranger-lane side effects — the canned "ask the guardian" reply
|
|
290
305
|
// would be addressed at the guardian themselves.
|
|
291
|
-
if (resolvedMember
|
|
306
|
+
if (resolvedMember.policy === "deny") {
|
|
292
307
|
log.info(
|
|
293
308
|
{ sourceChannel, externalUserId: canonicalSenderId },
|
|
294
309
|
"Ingress ACL: guardian member row carries policy deny, denying",
|
|
@@ -140,3 +140,22 @@ describe("serializeConversationSummary · surfaced groupId normalization", () =>
|
|
|
140
140
|
);
|
|
141
141
|
});
|
|
142
142
|
});
|
|
143
|
+
|
|
144
|
+
describe("serializeConversationSummary · enabledPlugins", () => {
|
|
145
|
+
test("serializes a non-empty plugin scope", () => {
|
|
146
|
+
const summary = serialize(
|
|
147
|
+
makeConversationRow({ enabledPlugins: ["plugin-a", "plugin-b"] }),
|
|
148
|
+
);
|
|
149
|
+
expect(summary.enabledPlugins).toEqual(["plugin-a", "plugin-b"]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("preserves an explicit empty scope (user cleared all optional plugins)", () => {
|
|
153
|
+
const summary = serialize(makeConversationRow({ enabledPlugins: [] }));
|
|
154
|
+
expect(summary.enabledPlugins).toEqual([]);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("omits the field when there is no per-chat restriction (null)", () => {
|
|
158
|
+
const summary = serialize(makeConversationRow({ enabledPlugins: null }));
|
|
159
|
+
expect("enabledPlugins" in summary).toBe(false);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
@@ -215,6 +215,11 @@ export function serializeConversationSummary(params: {
|
|
|
215
215
|
...(conversation.inferenceProfile != null
|
|
216
216
|
? { inferenceProfile: conversation.inferenceProfile }
|
|
217
217
|
: {}),
|
|
218
|
+
// Include when non-null so an explicit `[]` (user cleared all plugins) is
|
|
219
|
+
// preserved; `null`/default is omitted.
|
|
220
|
+
...(conversation.enabledPlugins != null
|
|
221
|
+
? { enabledPlugins: conversation.enabledPlugins }
|
|
222
|
+
: {}),
|
|
218
223
|
isProcessing,
|
|
219
224
|
};
|
|
220
225
|
}
|
|
@@ -175,6 +175,16 @@ export function publishConversationTitleChanged(
|
|
|
175
175
|
);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
export function publishConversationEnabledPluginsChanged(
|
|
179
|
+
conversationId: string,
|
|
180
|
+
originClientId?: string,
|
|
181
|
+
): void {
|
|
182
|
+
void publishSyncInvalidation(
|
|
183
|
+
[SYNC_TAGS.conversationsList, conversationMetadataSyncTag(conversationId)],
|
|
184
|
+
originClientId,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
178
188
|
export function publishConversationInferenceProfileChanged(
|
|
179
189
|
params: {
|
|
180
190
|
conversationId: string;
|