@vellumai/assistant 0.5.7 → 0.5.9
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/Dockerfile +2 -1
- package/docker-entrypoint.sh +9 -0
- package/docs/architecture/memory.md +13 -11
- package/eslint.config.mjs +0 -31
- package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
- package/node_modules/@vellumai/ces-contracts/src/rpc.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/approval-cascade.test.ts +0 -1
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/ces-rpc-credential-backend.test.ts +3 -3
- package/src/__tests__/ces-startup-timeout.test.ts +40 -0
- package/src/__tests__/config-schema-cmd.test.ts +0 -1
- package/src/__tests__/config-schema.test.ts +2 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +2 -4
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +15 -1
- package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
- package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
- package/src/__tests__/conversation-queue.test.ts +0 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +227 -0
- package/src/__tests__/conversation-slash-queue.test.ts +0 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
- package/src/__tests__/credential-execution-client.test.ts +5 -2
- package/src/__tests__/credential-execution-feature-gates.test.ts +31 -16
- package/src/__tests__/credential-execution-managed-contract.test.ts +2 -2
- package/src/__tests__/credential-security-e2e.test.ts +1 -1
- package/src/__tests__/credential-security-invariants.test.ts +2 -5
- package/src/__tests__/credentials-cli.test.ts +4 -3
- package/src/__tests__/daemon-credential-client.test.ts +123 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
- package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
- package/src/__tests__/journal-context.test.ts +335 -0
- package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
- package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
- package/src/__tests__/memory-recall-quality.test.ts +48 -17
- package/src/__tests__/memory-regressions.test.ts +408 -363
- package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
- package/src/__tests__/non-member-access-request.test.ts +2 -2
- package/src/__tests__/notification-decision-strategy.test.ts +71 -0
- package/src/__tests__/oauth-cli.test.ts +5 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
- package/src/__tests__/provider-error-scenarios.test.ts +0 -267
- package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
- package/src/__tests__/relay-server.test.ts +1 -2
- package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
- package/src/__tests__/secret-onetime-send.test.ts +1 -1
- package/src/__tests__/secure-keys.test.ts +18 -15
- package/src/__tests__/skill-memory.test.ts +17 -3
- package/src/__tests__/stale-approval-dedup.test.ts +171 -0
- package/src/__tests__/stt-hints.test.ts +437 -0
- package/src/__tests__/task-memory-cleanup.test.ts +14 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
- package/src/__tests__/voice-quality.test.ts +58 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +5 -3
- package/src/acp/agent-process.ts +9 -1
- package/src/agent/loop.ts +1 -1
- package/src/approvals/guardian-request-resolvers.ts +164 -38
- package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
- package/src/calls/call-controller.ts +9 -5
- package/src/calls/fish-audio-client.ts +26 -14
- package/src/calls/stt-hints.ts +189 -0
- package/src/calls/tts-text-sanitizer.ts +61 -0
- package/src/calls/twilio-routes.ts +32 -4
- package/src/calls/voice-quality.ts +15 -3
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/avatar.ts +2 -2
- package/src/cli/commands/credentials.ts +110 -94
- package/src/cli/commands/doctor.ts +2 -2
- package/src/cli/commands/keys.ts +7 -7
- package/src/cli/commands/memory.ts +1 -1
- package/src/cli/commands/oauth/connections.ts +11 -29
- package/src/cli/commands/oauth/platform.ts +389 -43
- package/src/cli/lib/daemon-credential-client.ts +284 -0
- package/src/cli.ts +1 -1
- package/src/config/bundled-skills/AGENTS.md +34 -0
- package/src/config/bundled-skills/acp/SKILL.md +10 -0
- package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
- package/src/config/bundled-skills/settings/SKILL.md +15 -2
- package/src/config/bundled-skills/settings/TOOLS.json +46 -1
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
- package/src/config/bundled-skills/slack/SKILL.md +1 -1
- package/src/config/bundled-tool-registry.ts +4 -0
- package/src/config/defaults.ts +0 -2
- package/src/config/env-registry.ts +4 -4
- package/src/config/env.ts +14 -1
- package/src/config/feature-flag-registry.json +1 -1
- package/src/config/loader.ts +8 -11
- package/src/config/schema.ts +5 -16
- package/src/config/schemas/calls.ts +17 -0
- package/src/config/schemas/inference.ts +2 -2
- package/src/config/schemas/journal.ts +16 -0
- package/src/config/schemas/memory-processing.ts +2 -2
- package/src/config/types.ts +1 -0
- package/src/contacts/contact-store.ts +2 -2
- package/src/credential-execution/executable-discovery.ts +1 -1
- package/src/credential-execution/startup-timeout.ts +36 -0
- package/src/daemon/approval-generators.ts +3 -9
- package/src/daemon/conversation-agent-loop.ts +6 -0
- package/src/daemon/conversation-error.ts +13 -1
- package/src/daemon/conversation-memory.ts +1 -2
- package/src/daemon/conversation-process.ts +18 -1
- package/src/daemon/conversation-runtime-assembly.ts +61 -1
- package/src/daemon/conversation-surfaces.ts +30 -1
- package/src/daemon/conversation.ts +20 -9
- package/src/daemon/guardian-action-generators.ts +3 -9
- package/src/daemon/lifecycle.ts +18 -11
- package/src/daemon/message-types/conversations.ts +1 -0
- package/src/daemon/server.ts +2 -3
- package/src/memory/app-store.ts +31 -0
- package/src/memory/db-init.ts +4 -0
- package/src/memory/indexer.ts +19 -10
- package/src/memory/items-extractor.ts +315 -322
- package/src/memory/job-handlers/summarization.ts +26 -16
- package/src/memory/jobs-store.ts +33 -1
- package/src/memory/journal-memory.ts +214 -0
- package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/retriever.test.ts +37 -25
- package/src/memory/retriever.ts +24 -49
- package/src/memory/schema/memory-core.ts +2 -0
- package/src/memory/search/formatting.ts +7 -44
- package/src/memory/search/staleness.ts +4 -0
- package/src/memory/search/tier-classifier.ts +10 -2
- package/src/memory/search/types.ts +2 -5
- package/src/memory/task-memory-cleanup.ts +4 -3
- package/src/notifications/adapters/slack.ts +168 -6
- package/src/notifications/broadcaster.ts +1 -0
- package/src/notifications/copy-composer.ts +59 -2
- package/src/notifications/signal.ts +2 -0
- package/src/notifications/types.ts +2 -0
- package/src/prompts/journal-context.ts +133 -0
- package/src/prompts/persona-resolver.ts +80 -24
- package/src/prompts/system-prompt.ts +30 -0
- package/src/prompts/templates/NOW.md +26 -0
- package/src/prompts/templates/SOUL.md +20 -0
- package/src/prompts/update-bulletin-format.ts +0 -2
- package/src/providers/provider-send-message.ts +3 -32
- package/src/providers/registry.ts +2 -139
- package/src/providers/types.ts +1 -1
- package/src/runtime/access-request-helper.ts +4 -0
- package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
- package/src/runtime/auth/route-policy.ts +2 -0
- package/src/runtime/gateway-client.ts +47 -4
- package/src/runtime/guardian-decision-types.ts +45 -4
- package/src/runtime/http-server.ts +5 -2
- package/src/runtime/routes/access-request-decision.ts +2 -2
- package/src/runtime/routes/app-management-routes.ts +2 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
- package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
- package/src/runtime/routes/channel-readiness-routes.ts +9 -4
- package/src/runtime/routes/debug-routes.ts +12 -9
- package/src/runtime/routes/guardian-approval-interception.ts +168 -11
- package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
- package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
- package/src/runtime/routes/identity-routes.ts +1 -1
- package/src/runtime/routes/inbound-message-handler.ts +31 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
- package/src/runtime/routes/integrations/twilio.ts +52 -10
- package/src/runtime/routes/memory-item-routes.test.ts +3 -3
- package/src/runtime/routes/memory-item-routes.ts +25 -11
- package/src/runtime/routes/secret-routes.ts +141 -10
- package/src/runtime/routes/tts-routes.ts +11 -1
- package/src/security/ces-credential-client.ts +18 -9
- package/src/security/ces-rpc-credential-backend.ts +4 -3
- package/src/security/credential-backend.ts +10 -4
- package/src/security/secure-keys.ts +21 -4
- package/src/skills/catalog-install.ts +4 -36
- package/src/skills/inline-command-expansions.ts +7 -7
- package/src/skills/skill-memory.ts +1 -0
- package/src/subagent/manager.ts +2 -5
- package/src/tools/acp/spawn.ts +78 -1
- package/src/tools/credentials/vault.ts +5 -3
- package/src/tools/memory/definitions.ts +3 -2
- package/src/tools/memory/handlers.ts +10 -7
- package/src/tools/sensitive-output-placeholders.ts +2 -2
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/util/browser.ts +15 -0
- package/src/util/platform.ts +1 -1
- package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +4 -4
- package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -1
- package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +4 -4
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/workspace/provider-commit-message-generator.ts +12 -21
- package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
- package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
- package/src/memory/search/lexical.ts +0 -48
- package/src/providers/failover.ts +0 -186
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getProviderKeyAsync } from "../security/secure-keys.js";
|
|
2
|
-
import {
|
|
2
|
+
import { ProviderNotConfiguredError } from "../util/errors.js";
|
|
3
3
|
import { AnthropicProvider } from "./anthropic/client.js";
|
|
4
|
-
import { FailoverProvider, type ProviderHealthStatus } from "./failover.js";
|
|
5
4
|
import { FireworksProvider } from "./fireworks/client.js";
|
|
6
5
|
import { GeminiProvider } from "./gemini/client.js";
|
|
7
6
|
import {
|
|
@@ -17,8 +16,6 @@ import type { Provider } from "./types.js";
|
|
|
17
16
|
|
|
18
17
|
const providers = new Map<string, Provider>();
|
|
19
18
|
const routingSources = new Map<string, "user-key" | "managed-proxy">();
|
|
20
|
-
let cachedFailoverProvider: FailoverProvider | null = null;
|
|
21
|
-
let cachedFailoverKey: string | null = null;
|
|
22
19
|
|
|
23
20
|
export function registerProvider(name: string, provider: Provider): void {
|
|
24
21
|
providers.set(name, provider);
|
|
@@ -27,95 +24,11 @@ export function registerProvider(name: string, provider: Provider): void {
|
|
|
27
24
|
export function getProvider(name: string): Provider {
|
|
28
25
|
const provider = providers.get(name);
|
|
29
26
|
if (!provider) {
|
|
30
|
-
throw new
|
|
31
|
-
`Provider "${name}" not found. Available: ${listProviders().join(", ")}`,
|
|
32
|
-
);
|
|
27
|
+
throw new ProviderNotConfiguredError(name, listProviders());
|
|
33
28
|
}
|
|
34
29
|
return provider;
|
|
35
30
|
}
|
|
36
31
|
|
|
37
|
-
export interface ProviderSelection {
|
|
38
|
-
/** Ordered list of available provider names */
|
|
39
|
-
availableProviders: string[];
|
|
40
|
-
/** The selected (effective) primary provider name, or null if none available */
|
|
41
|
-
selectedPrimary: string | null;
|
|
42
|
-
/** Whether the effective primary differs from the requested primary */
|
|
43
|
-
usedFallbackPrimary: boolean;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Resolve provider selection from requested primary and provider order.
|
|
48
|
-
* Dedupes [requestedPrimary, ...providerOrder], filtered to initialized providers.
|
|
49
|
-
* Returns null selectedPrimary when no providers are available.
|
|
50
|
-
*/
|
|
51
|
-
export function resolveProviderSelection(
|
|
52
|
-
requestedPrimary: string,
|
|
53
|
-
providerOrder: string[],
|
|
54
|
-
): ProviderSelection {
|
|
55
|
-
const ordered: string[] = [];
|
|
56
|
-
const seen = new Set<string>();
|
|
57
|
-
|
|
58
|
-
for (const name of [requestedPrimary, ...providerOrder]) {
|
|
59
|
-
if (seen.has(name)) continue;
|
|
60
|
-
seen.add(name);
|
|
61
|
-
if (providers.has(name)) {
|
|
62
|
-
ordered.push(name);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (ordered.length === 0) {
|
|
67
|
-
return {
|
|
68
|
-
availableProviders: [],
|
|
69
|
-
selectedPrimary: null,
|
|
70
|
-
usedFallbackPrimary: false,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return {
|
|
75
|
-
availableProviders: ordered,
|
|
76
|
-
selectedPrimary: ordered[0],
|
|
77
|
-
usedFallbackPrimary: ordered[0] !== requestedPrimary,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Build a provider that tries the effective primary provider first, then falls
|
|
83
|
-
* back to others in the configured order. If the requested primary is not
|
|
84
|
-
* available, automatically selects the first available provider from the
|
|
85
|
-
* deduped [primaryName, ...providerOrder] list (fail-open).
|
|
86
|
-
*
|
|
87
|
-
* Throws ConfigError only when NO providers are available at all.
|
|
88
|
-
* Caches the FailoverProvider instance so health state persists across calls.
|
|
89
|
-
*/
|
|
90
|
-
export function getFailoverProvider(
|
|
91
|
-
primaryName: string,
|
|
92
|
-
providerOrder: string[],
|
|
93
|
-
): Provider {
|
|
94
|
-
const selection = resolveProviderSelection(primaryName, providerOrder);
|
|
95
|
-
|
|
96
|
-
if (!selection.selectedPrimary) {
|
|
97
|
-
throw new ProviderNotConfiguredError(primaryName, listProviders());
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const orderedProviders: Provider[] = selection.availableProviders.map(
|
|
101
|
-
(name) => providers.get(name)!,
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
if (orderedProviders.length === 1) {
|
|
105
|
-
return orderedProviders[0];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Cache key from effective ordered providers (not raw input strings)
|
|
109
|
-
const cacheKey = selection.availableProviders.join(",");
|
|
110
|
-
if (cachedFailoverProvider && cachedFailoverKey === cacheKey) {
|
|
111
|
-
return cachedFailoverProvider;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
cachedFailoverProvider = new FailoverProvider(orderedProviders);
|
|
115
|
-
cachedFailoverKey = cacheKey;
|
|
116
|
-
return cachedFailoverProvider;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
32
|
export function listProviders(): string[] {
|
|
120
33
|
return Array.from(providers.keys());
|
|
121
34
|
}
|
|
@@ -151,7 +64,6 @@ export interface ProvidersConfig {
|
|
|
151
64
|
provider: string;
|
|
152
65
|
};
|
|
153
66
|
};
|
|
154
|
-
providerOrder?: string[];
|
|
155
67
|
timeouts?: { providerStreamTimeoutSec?: number };
|
|
156
68
|
}
|
|
157
69
|
|
|
@@ -172,53 +84,6 @@ function resolveModel(config: ProvidersConfig, providerName: string): string {
|
|
|
172
84
|
return getProviderDefaultModel(providerName);
|
|
173
85
|
}
|
|
174
86
|
|
|
175
|
-
export interface ProviderDebugStatus {
|
|
176
|
-
configuredPrimary: string;
|
|
177
|
-
activePrimary: string | null;
|
|
178
|
-
usedFallback: boolean;
|
|
179
|
-
registeredProviders: string[];
|
|
180
|
-
failoverHealth: ProviderHealthStatus[] | null;
|
|
181
|
-
overallHealth: "healthy" | "degraded" | "down";
|
|
182
|
-
routingSources: Record<string, "user-key" | "managed-proxy">;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function getProviderDebugStatus(
|
|
186
|
-
configuredProvider: string,
|
|
187
|
-
providerOrder: string[],
|
|
188
|
-
): ProviderDebugStatus {
|
|
189
|
-
const registered = listProviders();
|
|
190
|
-
const selection = resolveProviderSelection(configuredProvider, providerOrder);
|
|
191
|
-
|
|
192
|
-
let failoverHealth: ProviderHealthStatus[] | null = null;
|
|
193
|
-
if (cachedFailoverProvider) {
|
|
194
|
-
failoverHealth = cachedFailoverProvider.getHealthStatus();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
let overallHealth: "healthy" | "degraded" | "down" = "down";
|
|
198
|
-
if (registered.length > 0 && selection.selectedPrimary) {
|
|
199
|
-
if (!failoverHealth) {
|
|
200
|
-
overallHealth = "healthy";
|
|
201
|
-
} else {
|
|
202
|
-
const healthyCount = failoverHealth.filter((h) => h.healthy).length;
|
|
203
|
-
if (healthyCount === failoverHealth.length) {
|
|
204
|
-
overallHealth = "healthy";
|
|
205
|
-
} else if (healthyCount > 0) {
|
|
206
|
-
overallHealth = "degraded";
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return {
|
|
212
|
-
configuredPrimary: configuredProvider,
|
|
213
|
-
activePrimary: selection.selectedPrimary,
|
|
214
|
-
usedFallback: selection.usedFallbackPrimary,
|
|
215
|
-
registeredProviders: registered,
|
|
216
|
-
failoverHealth,
|
|
217
|
-
overallHealth,
|
|
218
|
-
routingSources: Object.fromEntries(routingSources),
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
|
|
222
87
|
/**
|
|
223
88
|
* Resolve provider credentials using mode-aware logic.
|
|
224
89
|
* In "managed" mode, routes through the platform proxy.
|
|
@@ -273,8 +138,6 @@ export async function initializeProviders(
|
|
|
273
138
|
): Promise<void> {
|
|
274
139
|
providers.clear();
|
|
275
140
|
routingSources.clear();
|
|
276
|
-
cachedFailoverProvider = null;
|
|
277
|
-
cachedFailoverKey = null;
|
|
278
141
|
|
|
279
142
|
const streamTimeoutMs =
|
|
280
143
|
(config.timeouts?.providerStreamTimeoutSec ?? 300) * 1000;
|
package/src/providers/types.ts
CHANGED
|
@@ -55,6 +55,8 @@ export interface AccessRequestParams {
|
|
|
55
55
|
actorDisplayName?: string;
|
|
56
56
|
actorUsername?: string;
|
|
57
57
|
previousMemberStatus?: Exclude<ChannelStatus, "unverified">;
|
|
58
|
+
/** Preview of the requester's original message, shown to the guardian. */
|
|
59
|
+
messagePreview?: string;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
export type AccessRequestResult =
|
|
@@ -90,6 +92,7 @@ export function notifyGuardianOfAccessRequest(
|
|
|
90
92
|
actorDisplayName,
|
|
91
93
|
actorUsername,
|
|
92
94
|
previousMemberStatus,
|
|
95
|
+
messagePreview,
|
|
93
96
|
} = params;
|
|
94
97
|
|
|
95
98
|
if (!actorExternalId) {
|
|
@@ -244,6 +247,7 @@ export function notifyGuardianOfAccessRequest(
|
|
|
244
247
|
guardianBindingChannel,
|
|
245
248
|
guardianResolutionSource,
|
|
246
249
|
previousMemberStatus: previousMemberStatus ?? null,
|
|
250
|
+
messagePreview: messagePreview ?? null,
|
|
247
251
|
},
|
|
248
252
|
dedupeKey: `access-request:${canonicalRequest.id}`,
|
|
249
253
|
onConversationCreated: (info) => {
|
|
@@ -16,6 +16,10 @@ import { readFileSync } from "node:fs";
|
|
|
16
16
|
import { basename, resolve } from "node:path";
|
|
17
17
|
import { describe, expect, test } from "bun:test";
|
|
18
18
|
|
|
19
|
+
// Cross-package import: gateway duplicates the epoch constant and both must
|
|
20
|
+
// stay in sync. Importing directly is more reliable than regex-extracting.
|
|
21
|
+
import { CURRENT_POLICY_EPOCH as GATEWAY_POLICY_EPOCH } from "../../../../../gateway/src/auth/policy.js";
|
|
22
|
+
import { CURRENT_POLICY_EPOCH } from "../policy.js";
|
|
19
23
|
import { resolveScopeProfile } from "../scopes.js";
|
|
20
24
|
import type { Scope, ScopeProfile } from "../types.js";
|
|
21
25
|
|
|
@@ -337,56 +341,11 @@ describe("scope profile contract", () => {
|
|
|
337
341
|
// ---------------------------------------------------------------------------
|
|
338
342
|
|
|
339
343
|
describe("CURRENT_POLICY_EPOCH sync", () => {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
*/
|
|
345
|
-
|
|
346
|
-
const EPOCH_FILES = [
|
|
347
|
-
{
|
|
348
|
-
label: "assistant",
|
|
349
|
-
path: resolve(PROJECT_ROOT, "assistant/src/runtime/auth/policy.ts"),
|
|
350
|
-
},
|
|
351
|
-
{
|
|
352
|
-
label: "gateway",
|
|
353
|
-
path: resolve(PROJECT_ROOT, "gateway/src/auth/policy.ts"),
|
|
354
|
-
},
|
|
355
|
-
];
|
|
356
|
-
|
|
357
|
-
function extractEpoch(filePath: string): number {
|
|
358
|
-
const src = readFileSync(filePath, "utf-8");
|
|
359
|
-
const match = src.match(
|
|
360
|
-
/export\s+const\s+CURRENT_POLICY_EPOCH\s*=\s*(\d+)/,
|
|
361
|
-
);
|
|
362
|
-
if (!match) {
|
|
363
|
-
throw new Error(`Could not find CURRENT_POLICY_EPOCH in ${filePath}`);
|
|
364
|
-
}
|
|
365
|
-
return parseInt(match[1], 10);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
test("all non-skill packages export the same CURRENT_POLICY_EPOCH value", () => {
|
|
369
|
-
const values = EPOCH_FILES.map((f) => ({
|
|
370
|
-
label: f.label,
|
|
371
|
-
epoch: extractEpoch(f.path),
|
|
372
|
-
}));
|
|
373
|
-
|
|
374
|
-
const canonical = values[0];
|
|
375
|
-
const mismatches = values.filter((v) => v.epoch !== canonical.epoch);
|
|
376
|
-
|
|
377
|
-
if (mismatches.length > 0) {
|
|
378
|
-
const summary = values
|
|
379
|
-
.map((v) => ` - ${v.label}: ${v.epoch}`)
|
|
380
|
-
.join("\n");
|
|
381
|
-
const message = [
|
|
382
|
-
"CURRENT_POLICY_EPOCH is out of sync across packages:",
|
|
383
|
-
"",
|
|
384
|
-
summary,
|
|
385
|
-
"",
|
|
386
|
-
"All locations must have the same value.",
|
|
344
|
+
test("assistant and gateway export the same CURRENT_POLICY_EPOCH value", () => {
|
|
345
|
+
expect(
|
|
346
|
+
CURRENT_POLICY_EPOCH,
|
|
347
|
+
`CURRENT_POLICY_EPOCH mismatch: assistant=${CURRENT_POLICY_EPOCH}, gateway=${GATEWAY_POLICY_EPOCH}. ` +
|
|
387
348
|
"The canonical source is assistant/src/runtime/auth/policy.ts.",
|
|
388
|
-
|
|
389
|
-
expect(mismatches, message).toEqual([]);
|
|
390
|
-
}
|
|
349
|
+
).toBe(GATEWAY_POLICY_EPOCH);
|
|
391
350
|
});
|
|
392
351
|
});
|
|
@@ -259,6 +259,8 @@ const ACTOR_ENDPOINTS: Array<{ endpoint: string; scopes: Scope[] }> = [
|
|
|
259
259
|
|
|
260
260
|
// Secrets
|
|
261
261
|
{ endpoint: "secrets", scopes: ["settings.write"] },
|
|
262
|
+
{ endpoint: "secrets:GET", scopes: ["settings.read"] },
|
|
263
|
+
{ endpoint: "secrets/read", scopes: ["settings.write"] },
|
|
262
264
|
|
|
263
265
|
// Pairing (authenticated)
|
|
264
266
|
{ endpoint: "pairing/register", scopes: ["settings.write"] },
|
|
@@ -6,6 +6,24 @@ import type { RuntimeAttachmentMetadata } from "./http-types.js";
|
|
|
6
6
|
|
|
7
7
|
const log = getLogger("gateway-client");
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Error thrown when the gateway returns a non-OK response for channel delivery.
|
|
11
|
+
* Carries the optional `userMessage` field from the gateway so callers can
|
|
12
|
+
* surface actionable error text to end-users.
|
|
13
|
+
*/
|
|
14
|
+
export class ChannelDeliveryError extends Error {
|
|
15
|
+
readonly statusCode: number;
|
|
16
|
+
/** A user-facing error message from the gateway, if available. */
|
|
17
|
+
readonly userMessage?: string;
|
|
18
|
+
|
|
19
|
+
constructor(statusCode: number, body: string, userMessage?: string) {
|
|
20
|
+
super(`Channel reply delivery failed (${statusCode}): ${body}`);
|
|
21
|
+
this.name = "ChannelDeliveryError";
|
|
22
|
+
this.statusCode = statusCode;
|
|
23
|
+
this.userMessage = userMessage;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
9
27
|
const DELIVERY_TIMEOUT_MS = 30_000;
|
|
10
28
|
const MANAGED_OUTBOUND_SEND_PATH =
|
|
11
29
|
"/v1/internal/managed-gateway/outbound-send/";
|
|
@@ -37,6 +55,8 @@ export interface ChannelReplyPayload {
|
|
|
37
55
|
useBlocks?: boolean;
|
|
38
56
|
/** When provided, add or remove an emoji reaction on a message. */
|
|
39
57
|
reaction?: { action: "add" | "remove"; name: string; messageTs: string };
|
|
58
|
+
/** When provided, set or clear the Slack Assistants API thread status. */
|
|
59
|
+
assistantThreadStatus?: { channel: string; threadTs: string; status: string };
|
|
40
60
|
}
|
|
41
61
|
|
|
42
62
|
export interface ChannelDeliveryResult {
|
|
@@ -81,13 +101,36 @@ export async function deliverChannelReply(
|
|
|
81
101
|
|
|
82
102
|
if (!response.ok) {
|
|
83
103
|
const body = await response.text().catch(() => "<unreadable>");
|
|
104
|
+
|
|
105
|
+
// Try to extract userMessage from JSON error responses (e.g. from the
|
|
106
|
+
// Slack delivery endpoint) so callers can surface actionable errors.
|
|
107
|
+
let userMessage: string | undefined;
|
|
108
|
+
try {
|
|
109
|
+
const parsed = JSON.parse(body) as { userMessage?: string };
|
|
110
|
+
if (typeof parsed.userMessage === "string") {
|
|
111
|
+
userMessage = parsed.userMessage;
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
// Body wasn't JSON — that's fine, userMessage stays undefined.
|
|
115
|
+
}
|
|
116
|
+
|
|
84
117
|
log.error(
|
|
85
|
-
{
|
|
118
|
+
{
|
|
119
|
+
status: response.status,
|
|
120
|
+
body,
|
|
121
|
+
callbackUrl,
|
|
122
|
+
chatId: payload.chatId,
|
|
123
|
+
...(userMessage && { userMessage }),
|
|
124
|
+
},
|
|
86
125
|
"Channel reply delivery failed",
|
|
87
126
|
);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
127
|
+
if (userMessage) {
|
|
128
|
+
log.warn(
|
|
129
|
+
{ chatId: payload.chatId, userMessage },
|
|
130
|
+
"Gateway returned actionable error for user",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
throw new ChannelDeliveryError(response.status, body, userMessage);
|
|
91
134
|
}
|
|
92
135
|
|
|
93
136
|
const result: ChannelDeliveryResult = { ok: true };
|
|
@@ -38,6 +38,8 @@ export interface GuardianDecisionAction {
|
|
|
38
38
|
action: string;
|
|
39
39
|
/** Human-readable label for the action. */
|
|
40
40
|
label: string;
|
|
41
|
+
/** Short explanation shown in rich-UI legends (Telegram, Slack). */
|
|
42
|
+
description?: string;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
// ---------------------------------------------------------------------------
|
|
@@ -46,14 +48,27 @@ export interface GuardianDecisionAction {
|
|
|
46
48
|
|
|
47
49
|
/** Canonical set of all guardian decision actions with their labels. */
|
|
48
50
|
export const GUARDIAN_DECISION_ACTIONS = {
|
|
49
|
-
approve_once: {
|
|
50
|
-
|
|
51
|
+
approve_once: {
|
|
52
|
+
action: "approve_once",
|
|
53
|
+
label: "Approve once",
|
|
54
|
+
description: "This tool, this call only",
|
|
55
|
+
},
|
|
56
|
+
approve_10m: {
|
|
57
|
+
action: "approve_10m",
|
|
58
|
+
label: "Allow 10 min",
|
|
59
|
+
description: "All tools for 10 minutes",
|
|
60
|
+
},
|
|
51
61
|
approve_conversation: {
|
|
52
62
|
action: "approve_conversation",
|
|
53
63
|
label: "Allow conversation",
|
|
64
|
+
description: "All tools for this conversation",
|
|
65
|
+
},
|
|
66
|
+
approve_always: {
|
|
67
|
+
action: "approve_always",
|
|
68
|
+
label: "Approve always",
|
|
69
|
+
description: "This tool, permanently",
|
|
54
70
|
},
|
|
55
|
-
|
|
56
|
-
reject: { action: "reject", label: "Reject" },
|
|
71
|
+
reject: { action: "reject", label: "Reject", description: "Deny this call" },
|
|
57
72
|
} as const satisfies Record<string, GuardianDecisionAction>;
|
|
58
73
|
|
|
59
74
|
/**
|
|
@@ -89,6 +104,32 @@ export function buildDecisionActions(opts?: {
|
|
|
89
104
|
];
|
|
90
105
|
}
|
|
91
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Build a compact legend string explaining each action, for rich-UI channels
|
|
109
|
+
* (Telegram, Slack) where buttons are shown but their scope isn't obvious.
|
|
110
|
+
*
|
|
111
|
+
* Accepts either `GuardianDecisionAction[]` or action ID strings and looks up
|
|
112
|
+
* descriptions from the canonical constants.
|
|
113
|
+
*/
|
|
114
|
+
export function buildActionLegend(
|
|
115
|
+
actionIds: readonly (string | { action?: string; id?: string })[],
|
|
116
|
+
): string {
|
|
117
|
+
const lookup = GUARDIAN_DECISION_ACTIONS as Record<
|
|
118
|
+
string,
|
|
119
|
+
GuardianDecisionAction | undefined
|
|
120
|
+
>;
|
|
121
|
+
const lines = actionIds
|
|
122
|
+
.map((a) => {
|
|
123
|
+
const id = typeof a === "string" ? a : (a.action ?? a.id ?? "");
|
|
124
|
+
const canonical = lookup[id];
|
|
125
|
+
return canonical?.description
|
|
126
|
+
? `• *${canonical.label}* — ${canonical.description}`
|
|
127
|
+
: null;
|
|
128
|
+
})
|
|
129
|
+
.filter(Boolean);
|
|
130
|
+
return lines.length > 0 ? lines.join("\n") : "";
|
|
131
|
+
}
|
|
132
|
+
|
|
92
133
|
/**
|
|
93
134
|
* Build the plain-text fallback instruction string that matches the given
|
|
94
135
|
* set of decision actions. Ensures the text always includes parser-compatible
|
|
@@ -145,8 +145,11 @@ import { handleGuardianRefresh } from "./routes/guardian-refresh-routes.js";
|
|
|
145
145
|
import { hostBashRouteDefinitions } from "./routes/host-bash-routes.js";
|
|
146
146
|
import { hostCuRouteDefinitions } from "./routes/host-cu-routes.js";
|
|
147
147
|
import { hostFileRouteDefinitions } from "./routes/host-file-routes.js";
|
|
148
|
-
import {
|
|
149
|
-
|
|
148
|
+
import {
|
|
149
|
+
handleHealth,
|
|
150
|
+
handleReadyz,
|
|
151
|
+
identityRouteDefinitions,
|
|
152
|
+
} from "./routes/identity-routes.js";
|
|
150
153
|
import { slackChannelRouteDefinitions } from "./routes/integrations/slack/channel.js";
|
|
151
154
|
import { slackShareRouteDefinitions } from "./routes/integrations/slack/share.js";
|
|
152
155
|
import { telegramRouteDefinitions } from "./routes/integrations/telegram.js";
|
|
@@ -116,7 +116,7 @@ export async function deliverVerificationCodeToGuardian(params: {
|
|
|
116
116
|
}): Promise<DeliveryResult> {
|
|
117
117
|
const text =
|
|
118
118
|
`You approved access for ${params.requesterIdentifier}. ` +
|
|
119
|
-
`Give them this verification code:
|
|
119
|
+
`Give them this verification code: \`${params.verificationCode}\`. ` +
|
|
120
120
|
`The code expires in 10 minutes.`;
|
|
121
121
|
|
|
122
122
|
try {
|
|
@@ -189,7 +189,7 @@ export async function deliverVerificationCodeToRequester(params: {
|
|
|
189
189
|
}): Promise<DeliveryResult> {
|
|
190
190
|
const text =
|
|
191
191
|
`Great news — your access request was approved! ` +
|
|
192
|
-
`Your verification code is:
|
|
192
|
+
`Your verification code is: \`${params.verificationCode}\`. ` +
|
|
193
193
|
`Reply with it here to complete verification. The code expires in 10 minutes.`;
|
|
194
194
|
|
|
195
195
|
const target = resolveRequesterTarget(params);
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
getApp,
|
|
37
37
|
getAppDirPath,
|
|
38
38
|
getAppPreview,
|
|
39
|
+
inlineDistAssets,
|
|
39
40
|
isMultifileApp,
|
|
40
41
|
listApps,
|
|
41
42
|
queryAppRecords,
|
|
@@ -684,7 +685,7 @@ export function appManagementRouteDefinitions(): RouteDefinition[] {
|
|
|
684
685
|
}
|
|
685
686
|
}
|
|
686
687
|
if (existsSync(distIndex)) {
|
|
687
|
-
html = readFileSync(distIndex, "utf-8");
|
|
688
|
+
html = inlineDistAssets(appDir, readFileSync(distIndex, "utf-8"));
|
|
688
689
|
} else {
|
|
689
690
|
html = `<p>App compilation failed. Edit a source file to trigger a rebuild.</p>`;
|
|
690
691
|
}
|