@vellumai/assistant 0.4.48 → 0.4.49
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/ARCHITECTURE.md +2 -2
- package/README.md +2 -23
- package/docs/architecture/integrations.md +45 -41
- package/docs/architecture/keychain-broker.md +3 -3
- package/docs/runbook-trusted-contacts.md +3 -8
- package/hook-templates/debug-prompt-logger/hook.json +1 -1
- package/hook-templates/debug-prompt-logger/run.sh +1 -3
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +0 -1
- package/src/__tests__/anthropic-provider.test.ts +156 -0
- package/src/__tests__/approval-cascade.test.ts +810 -0
- package/src/__tests__/approval-primitive.test.ts +0 -1
- package/src/__tests__/approval-routes-http.test.ts +2 -0
- package/src/__tests__/assistant-attachments.test.ts +12 -34
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/channel-guardian.test.ts +0 -2
- package/src/__tests__/channel-readiness-routes.test.ts +15 -6
- package/src/__tests__/channel-readiness-service.test.ts +10 -9
- package/src/__tests__/checker.test.ts +9 -29
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-tools.test.ts +2 -19
- package/src/__tests__/config-watcher.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
- package/src/__tests__/context-image-dimensions.test.ts +332 -0
- package/src/__tests__/context-token-estimator.test.ts +196 -13
- package/src/__tests__/conversation-attention-store.test.ts +0 -1
- package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +144 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
- package/src/__tests__/credential-metadata-store.test.ts +64 -73
- package/src/__tests__/credential-security-invariants.test.ts +13 -7
- package/src/__tests__/credential-vault-unit.test.ts +280 -49
- package/src/__tests__/credential-vault.test.ts +138 -16
- package/src/__tests__/credentials-cli.test.ts +71 -0
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
- package/src/__tests__/ephemeral-permissions.test.ts +3 -3
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
- package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +0 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
- package/src/__tests__/heartbeat-service.test.ts +0 -1
- package/src/__tests__/host-cu-proxy.test.ts +629 -0
- package/src/__tests__/host-shell-tool.test.ts +27 -15
- package/src/__tests__/http-user-message-parity.test.ts +1 -0
- package/src/__tests__/ingress-url-consistency.test.ts +14 -21
- package/src/__tests__/integration-status.test.ts +32 -51
- package/src/__tests__/intent-routing.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +10 -9
- package/src/__tests__/keychain-broker-client.test.ts +11 -43
- package/src/__tests__/notification-routing-intent.test.ts +0 -1
- package/src/__tests__/oauth-cli.test.ts +373 -14
- package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
- package/src/__tests__/oauth-scope-policy.test.ts +4 -6
- package/src/__tests__/oauth-store.test.ts +756 -0
- package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
- package/src/__tests__/provider-error-scenarios.test.ts +0 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
- package/src/__tests__/public-ingress-urls.test.ts +15 -21
- package/src/__tests__/recording-handler.test.ts +3 -4
- package/src/__tests__/registry.test.ts +2 -2
- package/src/__tests__/runtime-events-sse.test.ts +55 -7
- package/src/__tests__/schedule-store.test.ts +0 -1
- package/src/__tests__/scheduler-recurrence.test.ts +0 -1
- package/src/__tests__/scoped-approval-grants.test.ts +0 -1
- package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
- package/src/__tests__/secret-ingress-handler.test.ts +0 -1
- package/src/__tests__/send-endpoint-busy.test.ts +21 -6
- package/src/__tests__/sequence-store.test.ts +0 -1
- package/src/__tests__/session-init.benchmark.test.ts +4 -5
- package/src/__tests__/skill-include-graph.test.ts +66 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
- package/src/__tests__/skill-load-tool.test.ts +149 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/skills-uninstall.test.ts +1 -1
- package/src/__tests__/skills.test.ts +3 -3
- package/src/__tests__/slack-channel-config.test.ts +67 -3
- package/src/__tests__/slack-share-routes.test.ts +17 -19
- package/src/__tests__/system-prompt.test.ts +0 -1
- package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
- package/src/__tests__/terminal-tools.test.ts +4 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/tool-approval-handler.test.ts +0 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +0 -1
- package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
- package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
- package/src/__tests__/trust-store.test.ts +1 -22
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
- package/src/__tests__/twilio-routes.test.ts +0 -16
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/agent/ax-tree-compaction.test.ts +235 -0
- package/src/agent/loop.ts +76 -130
- package/src/calls/call-domain.ts +1 -6
- package/src/calls/relay-server.ts +9 -13
- package/src/calls/twilio-config.ts +2 -7
- package/src/calls/twilio-routes.ts +1 -2
- package/src/calls/voice-ingress-preflight.ts +1 -1
- package/src/cli/commands/browser-relay.ts +18 -12
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/credentials.ts +101 -15
- package/src/cli/commands/oauth/apps.ts +255 -0
- package/src/cli/commands/oauth/connections.ts +299 -0
- package/src/cli/commands/oauth/index.ts +52 -0
- package/src/cli/commands/oauth/providers.ts +242 -0
- package/src/cli/commands/skills.ts +4 -338
- package/src/cli/program.ts +1 -5
- package/src/cli/reference.ts +1 -3
- package/src/config/assistant-feature-flags.ts +0 -3
- package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +22 -4
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
- package/src/config/bundled-skills/settings/SKILL.md +1 -1
- package/src/config/bundled-skills/settings/TOOLS.json +2 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
- package/src/config/env-registry.ts +14 -83
- package/src/config/env.ts +11 -50
- package/src/config/feature-flag-registry.json +16 -16
- package/src/config/loader.ts +0 -6
- package/src/config/schema.ts +3 -1
- package/src/config/skills.ts +21 -2
- package/src/context/image-dimensions.ts +229 -0
- package/src/context/token-estimator.ts +75 -12
- package/src/context/window-manager.ts +49 -10
- package/src/daemon/assistant-attachments.ts +1 -13
- package/src/daemon/handlers/config-ingress.ts +8 -33
- package/src/daemon/handlers/config-slack-channel.ts +49 -46
- package/src/daemon/handlers/config-telegram.ts +32 -16
- package/src/daemon/handlers/sessions.ts +10 -24
- package/src/daemon/handlers/shared.ts +0 -130
- package/src/daemon/host-cu-proxy.ts +401 -0
- package/src/daemon/lifecycle.ts +36 -68
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/computer-use.ts +2 -119
- package/src/daemon/message-types/host-cu.ts +19 -0
- package/src/daemon/message-types/messages.ts +3 -0
- package/src/daemon/server.ts +14 -21
- package/src/daemon/session-agent-loop-handlers.ts +2 -0
- package/src/daemon/session-attachments.ts +1 -2
- package/src/daemon/session-slash.ts +1 -1
- package/src/daemon/session-surfaces.ts +40 -28
- package/src/daemon/session-tool-setup.ts +2 -9
- package/src/daemon/session.ts +138 -15
- package/src/daemon/tool-side-effects.ts +2 -8
- package/src/daemon/watch-handler.ts +2 -2
- package/src/events/tool-metrics-listener.ts +2 -2
- package/src/hooks/manager.ts +1 -4
- package/src/inbound/public-ingress-urls.ts +7 -7
- package/src/logfire.ts +16 -5
- package/src/memory/conversation-key-store.ts +21 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/migrations/149-oauth-tables.ts +60 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/oauth.ts +65 -0
- package/src/messaging/provider.ts +4 -4
- package/src/messaging/providers/gmail/client.ts +82 -2
- package/src/messaging/providers/gmail/people-client.ts +10 -10
- package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
- package/src/messaging/providers/whatsapp/adapter.ts +11 -8
- package/src/messaging/registry.ts +2 -32
- package/src/notifications/copy-composer.ts +0 -5
- package/src/notifications/signal.ts +4 -5
- package/src/oauth/byo-connection.test.ts +126 -25
- package/src/oauth/byo-connection.ts +22 -6
- package/src/oauth/connect-orchestrator.ts +113 -57
- package/src/oauth/connect-types.ts +17 -23
- package/src/oauth/connection-resolver.ts +35 -11
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/manual-token-connection.ts +104 -0
- package/src/oauth/oauth-store.ts +496 -0
- package/src/oauth/platform-connection.test.ts +29 -0
- package/src/oauth/platform-connection.ts +6 -5
- package/src/oauth/provider-behaviors.ts +124 -0
- package/src/oauth/scope-policy.ts +9 -2
- package/src/oauth/seed-providers.ts +161 -0
- package/src/oauth/token-persistence.ts +74 -78
- package/src/permissions/checker.ts +3 -3
- package/src/permissions/defaults.ts +0 -1
- package/src/permissions/prompter.ts +10 -1
- package/src/permissions/trust-store.ts +13 -0
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
- package/src/prompts/system-prompt.ts +28 -40
- package/src/providers/anthropic/client.ts +133 -24
- package/src/providers/retry.ts +1 -27
- package/src/runtime/auth/route-policy.ts +0 -3
- package/src/runtime/channel-reply-delivery.ts +0 -40
- package/src/runtime/gateway-client.ts +0 -7
- package/src/runtime/http-server.ts +8 -6
- package/src/runtime/http-types.ts +2 -2
- package/src/runtime/middleware/twilio-validation.ts +1 -11
- package/src/runtime/pending-interactions.ts +14 -12
- package/src/runtime/routes/channel-delivery-routes.ts +0 -1
- package/src/runtime/routes/conversation-routes.ts +73 -19
- package/src/runtime/routes/events-routes.ts +21 -11
- package/src/runtime/routes/host-cu-routes.ts +97 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
- package/src/runtime/routes/integrations/slack/share.ts +6 -7
- package/src/runtime/routes/log-export-routes.ts +126 -8
- package/src/runtime/routes/settings-routes.ts +55 -48
- package/src/runtime/routes/surface-action-routes.ts +1 -1
- package/src/runtime/routes/watch-routes.ts +128 -0
- package/src/schedule/integration-status.ts +10 -9
- package/src/security/credential-key.ts +0 -156
- package/src/security/keychain-broker-client.ts +5 -6
- package/src/security/oauth2.ts +1 -1
- package/src/security/token-manager.ts +119 -46
- package/src/skills/catalog-install.ts +358 -0
- package/src/skills/include-graph.ts +32 -0
- package/src/telegram/bot-username.ts +2 -3
- package/src/tools/browser/network-recorder.ts +1 -1
- package/src/tools/browser/network-recording-types.ts +1 -1
- package/src/tools/computer-use/definitions.ts +46 -11
- package/src/tools/computer-use/registry.ts +4 -5
- package/src/tools/credentials/broker.ts +1 -2
- package/src/tools/credentials/metadata-store.ts +17 -121
- package/src/tools/credentials/vault.ts +94 -167
- package/src/tools/registry.ts +2 -7
- package/src/tools/skills/load.ts +62 -3
- package/src/tools/watch/watch-state.ts +0 -12
- package/src/util/logger.ts +7 -41
- package/src/util/platform.ts +9 -28
- package/src/watcher/providers/google-calendar.ts +2 -1
- package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
- package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
- package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
- package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
- package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
- package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
- package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
- package/src/cli/commands/dev.ts +0 -129
- package/src/cli/commands/map.ts +0 -391
- package/src/cli/commands/oauth.ts +0 -77
- package/src/config/bundled-skills/computer-use/tools/computer-use-request-control.ts +0 -16
- package/src/daemon/computer-use-session.ts +0 -1026
- package/src/daemon/ride-shotgun-handler.ts +0 -569
- package/src/oauth/provider-base-urls.ts +0 -21
- package/src/oauth/provider-profiles.ts +0 -192
- package/src/prompts/computer-use-prompt.ts +0 -98
- package/src/runtime/routes/computer-use-routes.ts +0 -641
- package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
- package/src/runtime/telegram-streaming-delivery.ts +0 -393
- package/src/tools/computer-use/request-computer-control.ts +0 -56
|
@@ -3,7 +3,6 @@ import { describe, expect, mock, test } from "bun:test";
|
|
|
3
3
|
mock.module("../util/logger.js", () => ({
|
|
4
4
|
getLogger: () =>
|
|
5
5
|
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
6
|
-
isDebug: () => false,
|
|
7
6
|
}));
|
|
8
7
|
|
|
9
8
|
// Only mock sleep so retries complete instantly; keep real retry logic.
|
|
@@ -15,7 +15,6 @@ import { describe, expect, mock, test } from "bun:test";
|
|
|
15
15
|
mock.module("../util/logger.js", () => ({
|
|
16
16
|
getLogger: () =>
|
|
17
17
|
new Proxy({} as Record<string, unknown>, { get: () => () => {} }),
|
|
18
|
-
isDebug: () => false,
|
|
19
18
|
}));
|
|
20
19
|
|
|
21
20
|
import { FailoverProvider } from "../providers/failover.js";
|
|
@@ -25,6 +25,7 @@ mock.module("../util/logger.js", () => ({
|
|
|
25
25
|
getLogger: () => makeLoggerStub(),
|
|
26
26
|
}));
|
|
27
27
|
|
|
28
|
+
import { setIngressPublicBaseUrl } from "../config/env.js";
|
|
28
29
|
import {
|
|
29
30
|
getOAuthCallbackUrl,
|
|
30
31
|
getPublicBaseUrl,
|
|
@@ -40,19 +41,12 @@ import {
|
|
|
40
41
|
// ---------------------------------------------------------------------------
|
|
41
42
|
|
|
42
43
|
describe("getPublicBaseUrl", () => {
|
|
43
|
-
let savedIngressEnv: string | undefined;
|
|
44
|
-
|
|
45
44
|
beforeEach(() => {
|
|
46
|
-
|
|
47
|
-
delete process.env.INGRESS_PUBLIC_BASE_URL;
|
|
45
|
+
setIngressPublicBaseUrl(undefined);
|
|
48
46
|
});
|
|
49
47
|
|
|
50
48
|
afterEach(() => {
|
|
51
|
-
|
|
52
|
-
process.env.INGRESS_PUBLIC_BASE_URL = savedIngressEnv;
|
|
53
|
-
} else {
|
|
54
|
-
delete process.env.INGRESS_PUBLIC_BASE_URL;
|
|
55
|
-
}
|
|
49
|
+
setIngressPublicBaseUrl(undefined);
|
|
56
50
|
});
|
|
57
51
|
|
|
58
52
|
test("returns ingress.publicBaseUrl when set", () => {
|
|
@@ -62,16 +56,16 @@ describe("getPublicBaseUrl", () => {
|
|
|
62
56
|
expect(result).toBe("https://ingress.example.com");
|
|
63
57
|
});
|
|
64
58
|
|
|
65
|
-
test("falls back to
|
|
66
|
-
|
|
59
|
+
test("falls back to module-level ingress state when ingress.publicBaseUrl is empty", () => {
|
|
60
|
+
setIngressPublicBaseUrl("https://ingress-env.example.com/");
|
|
67
61
|
const result = getPublicBaseUrl({
|
|
68
62
|
ingress: { publicBaseUrl: "" },
|
|
69
63
|
});
|
|
70
64
|
expect(result).toBe("https://ingress-env.example.com");
|
|
71
65
|
});
|
|
72
66
|
|
|
73
|
-
test("falls back to
|
|
74
|
-
|
|
67
|
+
test("falls back to module-level ingress state when config is empty", () => {
|
|
68
|
+
setIngressPublicBaseUrl("https://ingress-env.example.com");
|
|
75
69
|
const result = getPublicBaseUrl({});
|
|
76
70
|
expect(result).toBe("https://ingress-env.example.com");
|
|
77
71
|
});
|
|
@@ -118,8 +112,8 @@ describe("getPublicBaseUrl", () => {
|
|
|
118
112
|
expect(result).toBe("https://example.com");
|
|
119
113
|
});
|
|
120
114
|
|
|
121
|
-
test("falls back to
|
|
122
|
-
|
|
115
|
+
test("falls back to module-level state when enabled is undefined and no publicBaseUrl", () => {
|
|
116
|
+
setIngressPublicBaseUrl("https://env-fallback.example.com");
|
|
123
117
|
const result = getPublicBaseUrl({
|
|
124
118
|
ingress: { enabled: undefined, publicBaseUrl: "" },
|
|
125
119
|
});
|
|
@@ -140,22 +134,22 @@ describe("getPublicBaseUrl", () => {
|
|
|
140
134
|
expect(result).toBe("https://example.com");
|
|
141
135
|
});
|
|
142
136
|
|
|
143
|
-
test("skips whitespace-only ingress.publicBaseUrl and falls through to
|
|
144
|
-
|
|
137
|
+
test("skips whitespace-only ingress.publicBaseUrl and falls through to module state", () => {
|
|
138
|
+
setIngressPublicBaseUrl("https://ingress-env.example.com");
|
|
145
139
|
const result = getPublicBaseUrl({
|
|
146
140
|
ingress: { publicBaseUrl: " " },
|
|
147
141
|
});
|
|
148
142
|
expect(result).toBe("https://ingress-env.example.com");
|
|
149
143
|
});
|
|
150
144
|
|
|
151
|
-
test("normalizes trailing slashes from
|
|
152
|
-
|
|
145
|
+
test("normalizes trailing slashes from module-level ingress state", () => {
|
|
146
|
+
setIngressPublicBaseUrl("https://ingress-env.example.com///");
|
|
153
147
|
const result = getPublicBaseUrl({});
|
|
154
148
|
expect(result).toBe("https://ingress-env.example.com");
|
|
155
149
|
});
|
|
156
150
|
|
|
157
|
-
test("trims whitespace from
|
|
158
|
-
|
|
151
|
+
test("trims whitespace from module-level ingress state", () => {
|
|
152
|
+
setIngressPublicBaseUrl(" https://ingress-env.example.com ");
|
|
159
153
|
const result = getPublicBaseUrl({});
|
|
160
154
|
expect(result).toBe("https://ingress-env.example.com");
|
|
161
155
|
});
|
|
@@ -32,8 +32,9 @@ mock.module("../config/loader.js", () => ({
|
|
|
32
32
|
contextWindow: {
|
|
33
33
|
enabled: true,
|
|
34
34
|
maxInputTokens: 180000,
|
|
35
|
-
targetBudgetRatio: 0.
|
|
36
|
-
compactThreshold: 0.8,
|
|
35
|
+
targetBudgetRatio: 0.3,
|
|
36
|
+
compactThreshold: 0.8,
|
|
37
|
+
summaryBudgetRatio: 0.05,
|
|
37
38
|
},
|
|
38
39
|
}),
|
|
39
40
|
invalidateConfigCache: noop,
|
|
@@ -167,8 +168,6 @@ function createCtx(): {
|
|
|
167
168
|
|
|
168
169
|
const ctx: HandlerContext = {
|
|
169
170
|
sessions: new Map(),
|
|
170
|
-
cuSessions: new Map(),
|
|
171
|
-
cuObservationParseSequence: new Map(),
|
|
172
171
|
sharedRequestTimestamps: [],
|
|
173
172
|
debounceTimers: new DebouncerMap({ defaultDelayMs: 200 }),
|
|
174
173
|
suppressConfigReload: false,
|
|
@@ -510,7 +510,7 @@ describe("computer-use registration split", () => {
|
|
|
510
510
|
// Start each test from a completely empty registry so assertions are
|
|
511
511
|
// non-vacuous — the split functions must actually register tools.
|
|
512
512
|
|
|
513
|
-
test("registerComputerUseActionTools registers all
|
|
513
|
+
test("registerComputerUseActionTools registers all 11 CU action tools and nothing else", async () => {
|
|
514
514
|
const { registerComputerUseActionTools } =
|
|
515
515
|
await import("../tools/computer-use/registry.js");
|
|
516
516
|
|
|
@@ -520,7 +520,7 @@ describe("computer-use registration split", () => {
|
|
|
520
520
|
registerComputerUseActionTools();
|
|
521
521
|
|
|
522
522
|
const registered = getAllTools();
|
|
523
|
-
expect(registered).toHaveLength(
|
|
523
|
+
expect(registered).toHaveLength(11);
|
|
524
524
|
expect(registered.every((t) => t.name.startsWith("computer_use_"))).toBe(
|
|
525
525
|
true,
|
|
526
526
|
);
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Tests:
|
|
5
5
|
* - 401 unauthorized (missing bearer token)
|
|
6
|
-
* -
|
|
6
|
+
* - 200 when conversationKey is omitted (unfiltered subscription)
|
|
7
7
|
* - Happy path: stream receives a published AssistantEvent
|
|
8
|
+
* - Unfiltered: streams events from multiple conversations
|
|
8
9
|
*/
|
|
9
10
|
import { mkdtempSync, realpathSync, rmSync } from "node:fs";
|
|
10
11
|
import { tmpdir } from "node:os";
|
|
@@ -125,15 +126,13 @@ describe("SSE assistant-events endpoint", () => {
|
|
|
125
126
|
|
|
126
127
|
// ── Validation ────────────────────────────────────────────────────────────
|
|
127
128
|
|
|
128
|
-
test("
|
|
129
|
+
test("200 when conversationKey is omitted (unfiltered subscription)", async () => {
|
|
129
130
|
await startServer();
|
|
130
131
|
|
|
131
132
|
const res = await fetch(eventsUrl(), { headers: AUTH_HEADERS });
|
|
132
|
-
expect(res.status).toBe(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
};
|
|
136
|
-
expect(body.error.message).toContain("conversationKey");
|
|
133
|
+
expect(res.status).toBe(200);
|
|
134
|
+
expect(res.headers.get("content-type")).toContain("text/event-stream");
|
|
135
|
+
await res.body?.cancel();
|
|
137
136
|
|
|
138
137
|
await stopServer();
|
|
139
138
|
});
|
|
@@ -184,4 +183,53 @@ describe("SSE assistant-events endpoint", () => {
|
|
|
184
183
|
expect(frame).toContain(`"sessionId":"${conversationId}"`);
|
|
185
184
|
expect(frame).toContain('"type":"pong"');
|
|
186
185
|
});
|
|
186
|
+
|
|
187
|
+
// ── Unfiltered subscription ──────────────────────────────────────────────
|
|
188
|
+
|
|
189
|
+
test("streams all events when conversationKey is omitted", async () => {
|
|
190
|
+
// Subscribe without a conversationKey — should receive events from any session.
|
|
191
|
+
const ac = new AbortController();
|
|
192
|
+
const req = new Request("http://localhost/v1/events", {
|
|
193
|
+
signal: ac.signal,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const { AssistantEventHub } =
|
|
197
|
+
await import("../runtime/assistant-event-hub.js");
|
|
198
|
+
const testHub = new AssistantEventHub();
|
|
199
|
+
|
|
200
|
+
const { handleSubscribeAssistantEvents } =
|
|
201
|
+
await import("../runtime/routes/events-routes.js");
|
|
202
|
+
const response = handleSubscribeAssistantEvents(req, new URL(req.url), {
|
|
203
|
+
hub: testHub,
|
|
204
|
+
skipActorVerification: true,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(response.status).toBe(200);
|
|
208
|
+
|
|
209
|
+
const reader = response.body!.getReader();
|
|
210
|
+
|
|
211
|
+
// Consume the initial heartbeat.
|
|
212
|
+
const heartbeat = await reader.read();
|
|
213
|
+
expect(heartbeat.done).toBe(false);
|
|
214
|
+
expect(new TextDecoder().decode(heartbeat.value)).toBe(": heartbeat\n\n");
|
|
215
|
+
|
|
216
|
+
// Publish events with two different sessionIds.
|
|
217
|
+
const eventA = buildAssistantEvent("self", { type: "pong" }, "session-aaa");
|
|
218
|
+
const eventB = buildAssistantEvent("self", { type: "pong" }, "session-bbb");
|
|
219
|
+
await testHub.publish(eventA);
|
|
220
|
+
await testHub.publish(eventB);
|
|
221
|
+
|
|
222
|
+
// Read both frames from the stream.
|
|
223
|
+
const frameA = await reader.read();
|
|
224
|
+
expect(frameA.done).toBe(false);
|
|
225
|
+
const textA = new TextDecoder().decode(frameA.value);
|
|
226
|
+
expect(textA).toContain("session-aaa");
|
|
227
|
+
|
|
228
|
+
const frameB = await reader.read();
|
|
229
|
+
expect(frameB.done).toBe(false);
|
|
230
|
+
const textB = new TextDecoder().decode(frameB.value);
|
|
231
|
+
expect(textB).toContain("session-bbb");
|
|
232
|
+
|
|
233
|
+
ac.abort();
|
|
234
|
+
});
|
|
187
235
|
});
|
|
@@ -117,6 +117,7 @@ function makeCompletingSession(): Session {
|
|
|
117
117
|
updateClient: () => {},
|
|
118
118
|
setHostBashProxy: () => {},
|
|
119
119
|
setHostFileProxy: () => {},
|
|
120
|
+
setHostCuProxy: () => {},
|
|
120
121
|
hasAnyPendingConfirmation: () => false,
|
|
121
122
|
hasPendingConfirmation: () => false,
|
|
122
123
|
denyAllPendingConfirmations: () => {},
|
|
@@ -173,6 +174,7 @@ function makeHangingSession(): Session {
|
|
|
173
174
|
updateClient: () => {},
|
|
174
175
|
setHostBashProxy: () => {},
|
|
175
176
|
setHostFileProxy: () => {},
|
|
177
|
+
setHostCuProxy: () => {},
|
|
176
178
|
hasAnyPendingConfirmation: () => false,
|
|
177
179
|
hasPendingConfirmation: () => false,
|
|
178
180
|
denyAllPendingConfirmations: () => {},
|
|
@@ -257,6 +259,7 @@ function makePendingApprovalSession(
|
|
|
257
259
|
updateClient: () => {},
|
|
258
260
|
setHostBashProxy: () => {},
|
|
259
261
|
setHostFileProxy: () => {},
|
|
262
|
+
setHostCuProxy: () => {},
|
|
260
263
|
hasAnyPendingConfirmation: () => pending.size > 0,
|
|
261
264
|
hasPendingConfirmation: (candidateRequestId: string) =>
|
|
262
265
|
pending.has(candidateRequestId),
|
|
@@ -364,11 +367,17 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
|
|
|
364
367
|
interface: "macos",
|
|
365
368
|
}),
|
|
366
369
|
});
|
|
367
|
-
const body = (await res.json()) as {
|
|
370
|
+
const body = (await res.json()) as {
|
|
371
|
+
accepted: boolean;
|
|
372
|
+
messageId: string;
|
|
373
|
+
conversationId: string;
|
|
374
|
+
};
|
|
368
375
|
|
|
369
376
|
expect(res.status).toBe(202);
|
|
370
377
|
expect(body.accepted).toBe(true);
|
|
371
378
|
expect(body.messageId).toBeDefined();
|
|
379
|
+
expect(typeof body.conversationId).toBe("string");
|
|
380
|
+
expect(body.conversationId.length).toBeGreaterThan(0);
|
|
372
381
|
|
|
373
382
|
await stopServer();
|
|
374
383
|
});
|
|
@@ -485,8 +494,8 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
|
|
|
485
494
|
createCanonicalGuardianRequest({
|
|
486
495
|
id: requestId,
|
|
487
496
|
kind: "tool_approval",
|
|
488
|
-
sourceType: "
|
|
489
|
-
sourceChannel: "
|
|
497
|
+
sourceType: "voice",
|
|
498
|
+
sourceChannel: "slack",
|
|
490
499
|
conversationId,
|
|
491
500
|
toolName: "call_start",
|
|
492
501
|
status: "pending",
|
|
@@ -511,8 +520,8 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
|
|
|
511
520
|
body: JSON.stringify({
|
|
512
521
|
conversationKey,
|
|
513
522
|
content: "sure let's do that",
|
|
514
|
-
sourceChannel: "
|
|
515
|
-
interface: "
|
|
523
|
+
sourceChannel: "slack",
|
|
524
|
+
interface: "slack",
|
|
516
525
|
}),
|
|
517
526
|
});
|
|
518
527
|
const body = (await res.json()) as {
|
|
@@ -808,11 +817,17 @@ describe("POST /v1/messages — queue-if-busy and hub publishing", () => {
|
|
|
808
817
|
interface: "macos",
|
|
809
818
|
}),
|
|
810
819
|
});
|
|
811
|
-
const body2 = (await res2.json()) as {
|
|
820
|
+
const body2 = (await res2.json()) as {
|
|
821
|
+
accepted: boolean;
|
|
822
|
+
queued: boolean;
|
|
823
|
+
conversationId: string;
|
|
824
|
+
};
|
|
812
825
|
|
|
813
826
|
expect(res2.status).toBe(202);
|
|
814
827
|
expect(body2.accepted).toBe(true);
|
|
815
828
|
expect(body2.queued).toBe(true);
|
|
829
|
+
expect(typeof body2.conversationId).toBe("string");
|
|
830
|
+
expect(body2.conversationId.length).toBeGreaterThan(0);
|
|
816
831
|
|
|
817
832
|
await stopServer();
|
|
818
833
|
});
|
|
@@ -127,7 +127,6 @@ mock.module("../util/logger.js", () => ({
|
|
|
127
127
|
new Proxy({} as Record<string, unknown>, {
|
|
128
128
|
get: () => () => {},
|
|
129
129
|
}),
|
|
130
|
-
isDebug: () => false,
|
|
131
130
|
truncateForLog: (value: string, maxLen = 500) =>
|
|
132
131
|
value.length > maxLen ? value.slice(0, maxLen) + "..." : value,
|
|
133
132
|
initLogger: () => {},
|
|
@@ -342,7 +341,7 @@ describe("Session initialization benchmark", () => {
|
|
|
342
341
|
}
|
|
343
342
|
|
|
344
343
|
timings.sort((a, b) => a - b);
|
|
345
|
-
expect(median(timings)).toBeLessThan(
|
|
344
|
+
expect(median(timings)).toBeLessThan(15);
|
|
346
345
|
});
|
|
347
346
|
|
|
348
347
|
test("buildSystemPrompt assembles prompt under 50ms (median of 5)", () => {
|
|
@@ -384,9 +383,9 @@ describe("Session initialization benchmark", () => {
|
|
|
384
383
|
await initializeTools();
|
|
385
384
|
const definitions = getAllToolDefinitions();
|
|
386
385
|
|
|
387
|
-
// Sanity: we expect a meaningful number of core tools (at least
|
|
386
|
+
// Sanity: we expect a meaningful number of core tools (at least 18)
|
|
388
387
|
// but not an unreasonable explosion (under 200)
|
|
389
|
-
expect(definitions.length).toBeGreaterThanOrEqual(
|
|
388
|
+
expect(definitions.length).toBeGreaterThanOrEqual(18);
|
|
390
389
|
expect(definitions.length).toBeLessThan(200);
|
|
391
390
|
});
|
|
392
391
|
});
|
|
@@ -548,6 +547,6 @@ describe("End-to-end session creation benchmark", () => {
|
|
|
548
547
|
}
|
|
549
548
|
|
|
550
549
|
timings.sort((a, b) => a - b);
|
|
551
|
-
expect(median(timings)).toBeLessThan(
|
|
550
|
+
expect(median(timings)).toBeLessThan(15);
|
|
552
551
|
});
|
|
553
552
|
});
|
|
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import type { SkillSummary } from "../config/skills.js";
|
|
4
4
|
import {
|
|
5
|
+
collectAllMissing,
|
|
5
6
|
getImmediateChildren,
|
|
6
7
|
indexCatalogById,
|
|
7
8
|
traverseIncludes,
|
|
@@ -298,3 +299,68 @@ describe("validateIncludes — cycle detection", () => {
|
|
|
298
299
|
}
|
|
299
300
|
});
|
|
300
301
|
});
|
|
302
|
+
|
|
303
|
+
describe("collectAllMissing", () => {
|
|
304
|
+
test("returns empty set when skill has no includes", () => {
|
|
305
|
+
const catalog = [makeSkill("root")];
|
|
306
|
+
const index = indexCatalogById(catalog);
|
|
307
|
+
expect(collectAllMissing("root", index)).toEqual(new Set([]));
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
test("returns empty set when all includes are present", () => {
|
|
311
|
+
const catalog = [makeSkill("A", ["B"]), makeSkill("B")];
|
|
312
|
+
const index = indexCatalogById(catalog);
|
|
313
|
+
expect(collectAllMissing("A", index)).toEqual(new Set([]));
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test("returns immediate missing children", () => {
|
|
317
|
+
const catalog = [makeSkill("A", ["B", "C"]), makeSkill("C")];
|
|
318
|
+
const index = indexCatalogById(catalog);
|
|
319
|
+
expect(collectAllMissing("A", index)).toEqual(new Set(["B"]));
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("returns transitive missing children", () => {
|
|
323
|
+
const catalog = [makeSkill("A", ["B"]), makeSkill("B", ["C"])];
|
|
324
|
+
const index = indexCatalogById(catalog);
|
|
325
|
+
expect(collectAllMissing("A", index)).toEqual(new Set(["C"]));
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("returns multiple missing at different levels", () => {
|
|
329
|
+
// A→B→C, B present but C missing
|
|
330
|
+
const catalog1 = [makeSkill("A", ["B"]), makeSkill("B", ["C"])];
|
|
331
|
+
const index1 = indexCatalogById(catalog1);
|
|
332
|
+
expect(collectAllMissing("A", index1)).toEqual(new Set(["C"]));
|
|
333
|
+
|
|
334
|
+
// A includes B and C, both missing
|
|
335
|
+
const catalog2 = [makeSkill("A", ["B", "C"])];
|
|
336
|
+
const index2 = indexCatalogById(catalog2);
|
|
337
|
+
expect(collectAllMissing("A", index2)).toEqual(new Set(["B", "C"]));
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("handles diamond with missing leaf", () => {
|
|
341
|
+
const catalog = [
|
|
342
|
+
makeSkill("A", ["B", "C"]),
|
|
343
|
+
makeSkill("B", ["D"]),
|
|
344
|
+
makeSkill("C", ["D"]),
|
|
345
|
+
];
|
|
346
|
+
const index = indexCatalogById(catalog);
|
|
347
|
+
const result = collectAllMissing("A", index);
|
|
348
|
+
expect(result).toEqual(new Set(["D"]));
|
|
349
|
+
// Verify no duplicates (Set handles this, but confirm size)
|
|
350
|
+
expect(result.size).toBe(1);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("does not loop infinitely on cycles", () => {
|
|
354
|
+
const catalog = [makeSkill("A", ["B"]), makeSkill("B", ["A"])];
|
|
355
|
+
const index = indexCatalogById(catalog);
|
|
356
|
+
expect(collectAllMissing("A", index)).toEqual(new Set([]));
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("handles cycle with missing node", () => {
|
|
360
|
+
const catalog = [makeSkill("A", ["B"]), makeSkill("B", ["C"])];
|
|
361
|
+
// C is missing, and if C referenced B it would be a cycle — but C isn't in catalog
|
|
362
|
+
// So A→B→C, C missing
|
|
363
|
+
const index = indexCatalogById(catalog);
|
|
364
|
+
expect(collectAllMissing("A", index)).toEqual(new Set(["C"]));
|
|
365
|
+
});
|
|
366
|
+
});
|