@vellumai/assistant 0.5.4 → 0.5.6
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 +17 -27
- package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +42 -0
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +113 -0
- package/src/__tests__/config-schema.test.ts +2 -2
- package/src/__tests__/context-window-manager.test.ts +78 -0
- package/src/__tests__/conversation-title-service.test.ts +30 -1
- package/src/__tests__/credential-security-invariants.test.ts +2 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +207 -0
- package/src/__tests__/memory-regressions.test.ts +8 -30
- package/src/__tests__/openai-whisper.test.ts +93 -0
- package/src/__tests__/require-fresh-approval.test.ts +4 -0
- package/src/__tests__/slack-messaging-token-resolution.test.ts +319 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +4 -0
- package/src/__tests__/volume-security-guard.test.ts +155 -0
- package/src/cli/commands/conversations.ts +0 -18
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +16 -37
- package/src/config/env-registry.ts +9 -0
- package/src/config/env.ts +8 -2
- package/src/config/feature-flag-registry.json +8 -8
- package/src/config/schema.ts +0 -12
- package/src/config/schemas/memory.ts +0 -4
- package/src/config/schemas/platform.ts +1 -1
- package/src/config/schemas/security.ts +4 -0
- package/src/context/window-manager.ts +53 -2
- package/src/credential-execution/managed-catalog.ts +5 -15
- package/src/daemon/conversation-agent-loop.ts +0 -60
- package/src/daemon/conversation-memory.ts +0 -117
- package/src/daemon/conversation-runtime-assembly.ts +0 -2
- package/src/daemon/daemon-control.ts +7 -0
- package/src/daemon/handlers/conversations.ts +0 -11
- package/src/daemon/lifecycle.ts +10 -47
- package/src/daemon/providers-setup.ts +2 -1
- package/src/followups/followup-store.ts +5 -2
- package/src/hooks/manager.ts +7 -0
- package/src/instrument.ts +33 -1
- package/src/memory/conversation-crud.ts +0 -236
- package/src/memory/conversation-title-service.ts +26 -10
- package/src/memory/db-init.ts +5 -13
- package/src/memory/embedding-local.ts +11 -5
- package/src/memory/indexer.ts +15 -106
- package/src/memory/job-handlers/conversation-starters.ts +24 -36
- package/src/memory/job-handlers/embedding.ts +0 -79
- package/src/memory/job-utils.ts +1 -1
- package/src/memory/jobs-store.ts +0 -8
- package/src/memory/jobs-worker.ts +0 -20
- package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
- package/src/memory/migrations/index.ts +1 -3
- package/src/memory/qdrant-client.ts +4 -6
- package/src/memory/schema/conversations.ts +0 -3
- package/src/memory/schema/index.ts +0 -2
- package/src/messaging/draft-store.ts +2 -2
- package/src/messaging/provider.ts +9 -0
- package/src/messaging/providers/slack/adapter.ts +29 -2
- package/src/oauth/connection-resolver.test.ts +22 -18
- package/src/oauth/connection-resolver.ts +92 -7
- package/src/oauth/platform-connection.test.ts +78 -69
- package/src/oauth/platform-connection.ts +12 -19
- package/src/permissions/defaults.ts +3 -3
- package/src/permissions/trust-client.ts +332 -0
- package/src/permissions/trust-store-interface.ts +105 -0
- package/src/permissions/trust-store.ts +531 -39
- package/src/platform/client.test.ts +148 -0
- package/src/platform/client.ts +71 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +190 -0
- package/src/providers/speech-to-text/openai-whisper.ts +68 -0
- package/src/providers/speech-to-text/resolve.ts +9 -0
- package/src/providers/speech-to-text/types.ts +17 -0
- package/src/runtime/auth/route-policy.ts +14 -0
- package/src/runtime/auth/token-service.ts +133 -0
- package/src/runtime/http-server.ts +4 -2
- package/src/runtime/routes/conversation-management-routes.ts +0 -36
- package/src/runtime/routes/conversation-query-routes.ts +44 -2
- package/src/runtime/routes/conversation-routes.ts +2 -1
- package/src/runtime/routes/inbound-message-handler.ts +27 -3
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +16 -1
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +287 -0
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +122 -0
- package/src/runtime/routes/log-export-routes.ts +1 -0
- package/src/runtime/routes/memory-item-routes.test.ts +221 -3
- package/src/runtime/routes/memory-item-routes.ts +124 -2
- package/src/runtime/routes/secret-routes.ts +4 -1
- package/src/runtime/routes/upgrade-broadcast-routes.ts +151 -0
- package/src/schedule/schedule-store.ts +0 -21
- package/src/security/ces-credential-client.ts +173 -0
- package/src/security/secure-keys.ts +65 -22
- package/src/signals/bash.ts +3 -0
- package/src/signals/cancel.ts +3 -0
- package/src/signals/confirm.ts +3 -0
- package/src/signals/conversation-undo.ts +3 -0
- package/src/signals/event-stream.ts +7 -0
- package/src/signals/shotgun.ts +3 -0
- package/src/signals/trust-rule.ts +3 -0
- package/src/skills/inline-command-render.ts +5 -1
- package/src/skills/inline-command-runner.ts +30 -2
- package/src/telemetry/usage-telemetry-reporter.test.ts +23 -36
- package/src/telemetry/usage-telemetry-reporter.ts +21 -19
- package/src/tools/memory/handlers.ts +1 -129
- package/src/tools/permission-checker.ts +18 -0
- package/src/tools/skills/load.ts +9 -2
- package/src/util/device-id.ts +70 -7
- package/src/util/logger.ts +35 -9
- package/src/util/platform.ts +29 -5
- package/src/util/xml.ts +8 -0
- package/src/workspace/heartbeat-service.ts +5 -24
- package/src/workspace/migrations/migrate-to-workspace-volume.ts +113 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/archive-recall.test.ts +0 -560
- package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
- package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
- package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
- package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
- package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
- package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
- package/src/__tests__/memory-brief-time.test.ts +0 -285
- package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
- package/src/__tests__/memory-chunk-archive.test.ts +0 -400
- package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
- package/src/__tests__/memory-episode-archive.test.ts +0 -370
- package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
- package/src/__tests__/memory-observation-archive.test.ts +0 -375
- package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
- package/src/__tests__/memory-reducer-job.test.ts +0 -538
- package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
- package/src/__tests__/memory-reducer-store.test.ts +0 -728
- package/src/__tests__/memory-reducer-types.test.ts +0 -707
- package/src/__tests__/memory-reducer.test.ts +0 -704
- package/src/__tests__/memory-simplified-config.test.ts +0 -281
- package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
- package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
- package/src/config/schemas/memory-simplified.ts +0 -101
- package/src/memory/archive-recall.ts +0 -516
- package/src/memory/archive-store.ts +0 -400
- package/src/memory/brief-formatting.ts +0 -33
- package/src/memory/brief-open-loops.ts +0 -266
- package/src/memory/brief-time.ts +0 -162
- package/src/memory/brief.ts +0 -75
- package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
- package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
- package/src/memory/migrations/185-memory-brief-state.ts +0 -52
- package/src/memory/migrations/186-memory-archive.ts +0 -109
- package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
- package/src/memory/reducer-scheduler.ts +0 -242
- package/src/memory/reducer-store.ts +0 -271
- package/src/memory/reducer-types.ts +0 -106
- package/src/memory/reducer.ts +0 -467
- package/src/memory/schema/memory-archive.ts +0 -121
- package/src/memory/schema/memory-brief.ts +0 -55
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { describe, expect, test } from "bun:test";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Guard test: assistant source code must not directly access files in the
|
|
7
|
+
* `protected/` directory (`trust.json`, `keys.enc`, `store.key`,
|
|
8
|
+
* `actor-token-signing-key`). In containerized (Docker) mode these files
|
|
9
|
+
* live outside the assistant's data volume and are managed by the gateway.
|
|
10
|
+
*
|
|
11
|
+
* All access must go through the appropriate abstraction layer:
|
|
12
|
+
* - Trust rules: trust-store.ts / trust-client.ts (file vs gateway backend)
|
|
13
|
+
* - Credentials: encrypted-store.ts / ces-credential-client.ts
|
|
14
|
+
* - Signing keys: secure-keys.ts / credential-backend.ts
|
|
15
|
+
*
|
|
16
|
+
* Only the abstraction-layer files themselves (and tests) are allowed to
|
|
17
|
+
* reference the raw file paths / helper functions.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Allowed files — abstraction layers that legitimately access protected/ files
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
const ALLOWED_FILES = new Set([
|
|
25
|
+
// Trust store backends
|
|
26
|
+
"assistant/src/permissions/trust-store.ts",
|
|
27
|
+
"assistant/src/permissions/trust-client.ts",
|
|
28
|
+
"assistant/src/permissions/trust-store-interface.ts",
|
|
29
|
+
// Credential / encrypted store backends
|
|
30
|
+
"assistant/src/security/encrypted-store.ts",
|
|
31
|
+
"assistant/src/security/secure-keys.ts",
|
|
32
|
+
"assistant/src/security/credential-backend.ts",
|
|
33
|
+
"assistant/src/security/ces-credential-client.ts",
|
|
34
|
+
// Token service owns the signing key lifecycle
|
|
35
|
+
"assistant/src/runtime/auth/token-service.ts",
|
|
36
|
+
// CLI commands that run outside Docker (doctor diagnostics, trust management)
|
|
37
|
+
"assistant/src/cli/commands/doctor.ts",
|
|
38
|
+
"assistant/src/cli/commands/trust.ts",
|
|
39
|
+
// Auth middleware documentation comment (not a file access)
|
|
40
|
+
"assistant/src/runtime/auth/middleware.ts",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Patterns that indicate direct access to protected directory files
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Each entry is a `git grep -E` pattern and a human-readable description
|
|
49
|
+
* for the error message.
|
|
50
|
+
*/
|
|
51
|
+
const GUARDED_PATTERNS: Array<{ pattern: string; description: string }> = [
|
|
52
|
+
{
|
|
53
|
+
pattern: "protected/trust\\.json",
|
|
54
|
+
description: "direct reference to protected/trust.json",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
pattern: "protected/keys\\.enc",
|
|
58
|
+
description: "direct reference to protected/keys.enc",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
pattern: "protected/store\\.key",
|
|
62
|
+
description: "direct reference to protected/store.key",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
pattern: "actor-token-signing-key",
|
|
66
|
+
description: "direct reference to actor-token-signing-key file",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
pattern: "\\bgetTrustPath\\b",
|
|
70
|
+
description: "use of getTrustPath() (trust-store internal)",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
pattern: "\\bgetStoreKeyPath\\b",
|
|
74
|
+
description: "use of getStoreKeyPath() (encrypted-store internal)",
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Helpers
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
function getRepoRoot(): string {
|
|
83
|
+
return join(process.cwd(), "..");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isTestFile(filePath: string): boolean {
|
|
87
|
+
return (
|
|
88
|
+
filePath.includes("/__tests__/") ||
|
|
89
|
+
filePath.endsWith(".test.ts") ||
|
|
90
|
+
filePath.endsWith(".test.js") ||
|
|
91
|
+
filePath.endsWith(".spec.ts") ||
|
|
92
|
+
filePath.endsWith(".spec.js")
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Tests
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
describe("volume security: protected directory access guard", () => {
|
|
101
|
+
for (const { pattern, description } of GUARDED_PATTERNS) {
|
|
102
|
+
test(`no ${description} outside allowed files`, () => {
|
|
103
|
+
const repoRoot = getRepoRoot();
|
|
104
|
+
|
|
105
|
+
let grepOutput = "";
|
|
106
|
+
try {
|
|
107
|
+
grepOutput = execFileSync(
|
|
108
|
+
"git",
|
|
109
|
+
[
|
|
110
|
+
"grep",
|
|
111
|
+
"-lE",
|
|
112
|
+
pattern,
|
|
113
|
+
"--",
|
|
114
|
+
"assistant/src/**/*.ts",
|
|
115
|
+
"assistant/src/*.ts",
|
|
116
|
+
],
|
|
117
|
+
{ encoding: "utf-8", cwd: repoRoot },
|
|
118
|
+
).trim();
|
|
119
|
+
} catch (err) {
|
|
120
|
+
// Exit code 1 means no matches — happy path
|
|
121
|
+
if ((err as { status?: number }).status === 1) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
throw err;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const files = grepOutput.split("\n").filter((f) => f.length > 0);
|
|
128
|
+
const violations = files.filter(
|
|
129
|
+
(f) => !isTestFile(f) && !ALLOWED_FILES.has(f),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if (violations.length > 0) {
|
|
133
|
+
const message = [
|
|
134
|
+
`Found assistant source files with ${description}.`,
|
|
135
|
+
"",
|
|
136
|
+
"In containerized (Docker) mode, the protected/ directory is not",
|
|
137
|
+
"accessible to the assistant. All access to protected files must go",
|
|
138
|
+
"through the abstraction layers:",
|
|
139
|
+
" - Trust rules: trust-store.ts / trust-client.ts",
|
|
140
|
+
" - Credentials: encrypted-store.ts / ces-credential-client.ts",
|
|
141
|
+
" - Signing keys: secure-keys.ts / credential-backend.ts",
|
|
142
|
+
"",
|
|
143
|
+
"If this file is a new abstraction backend, add it to ALLOWED_FILES",
|
|
144
|
+
"in this guard test. Otherwise, use the appropriate abstraction layer",
|
|
145
|
+
"or gate the access behind !getIsContainerized().",
|
|
146
|
+
"",
|
|
147
|
+
"Violations:",
|
|
148
|
+
...violations.map((f) => ` - ${f}`),
|
|
149
|
+
].join("\n");
|
|
150
|
+
|
|
151
|
+
expect(violations, message).toEqual([]);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
});
|
|
@@ -375,24 +375,6 @@ Examples:
|
|
|
375
375
|
targetId: summaryId,
|
|
376
376
|
});
|
|
377
377
|
}
|
|
378
|
-
for (const obsId of result.deletedObservationIds) {
|
|
379
|
-
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
380
|
-
targetType: "observation",
|
|
381
|
-
targetId: obsId,
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
for (const chunkId of result.deletedChunkIds) {
|
|
385
|
-
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
386
|
-
targetType: "chunk",
|
|
387
|
-
targetId: chunkId,
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
for (const episodeId of result.deletedEpisodeIds) {
|
|
391
|
-
enqueueMemoryJob("delete_qdrant_vectors", {
|
|
392
|
-
targetType: "episode",
|
|
393
|
-
targetId: episodeId,
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
378
|
|
|
397
379
|
log.info(
|
|
398
380
|
`Wiped conversation "${conversation.title ?? "Untitled"}". ` +
|
|
@@ -136,6 +136,7 @@ export async function getProviderConnection(
|
|
|
136
136
|
provider: MessagingProvider,
|
|
137
137
|
account?: string,
|
|
138
138
|
): Promise<OAuthConnection | string> {
|
|
139
|
+
if (provider.resolveConnection) return provider.resolveConnection(account);
|
|
139
140
|
if (await provider.isConnected?.()) return "";
|
|
140
141
|
return resolveOAuthConnection(provider.credentialService, { account });
|
|
141
142
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { tmpdir } from "node:os";
|
|
11
11
|
import { extname, join } from "node:path";
|
|
12
12
|
|
|
13
|
+
import { OpenAIWhisperProvider } from "../../../../providers/speech-to-text/openai-whisper.js";
|
|
13
14
|
import { getProviderKeyAsync } from "../../../../security/secure-keys.js";
|
|
14
15
|
import type {
|
|
15
16
|
ToolContext,
|
|
@@ -168,12 +169,19 @@ async function transcribeViaApi(
|
|
|
168
169
|
apiKey: string,
|
|
169
170
|
context: ToolContext,
|
|
170
171
|
): Promise<string> {
|
|
172
|
+
const provider = new OpenAIWhisperProvider(apiKey);
|
|
171
173
|
const duration = await getAudioDuration(audioPath);
|
|
172
174
|
const fileSize = Bun.file(audioPath).size;
|
|
173
175
|
|
|
174
176
|
// If small enough, send directly
|
|
175
177
|
if (fileSize <= WHISPER_API_MAX_BYTES) {
|
|
176
|
-
|
|
178
|
+
const audioBuffer = await readFile(audioPath);
|
|
179
|
+
const result = await provider.transcribe(
|
|
180
|
+
audioBuffer,
|
|
181
|
+
"audio/wav",
|
|
182
|
+
AbortSignal.timeout(API_REQUEST_TIMEOUT_MS),
|
|
183
|
+
);
|
|
184
|
+
return result.text;
|
|
177
185
|
}
|
|
178
186
|
|
|
179
187
|
// Split into chunks for large files
|
|
@@ -199,8 +207,13 @@ async function transcribeViaApi(
|
|
|
199
207
|
for (let i = 0; i < chunks.length; i++) {
|
|
200
208
|
if (context.signal?.aborted) throw new Error("Cancelled");
|
|
201
209
|
context.onOutput?.(` Transcribing chunk ${i + 1}/${chunks.length}...\n`);
|
|
202
|
-
const
|
|
203
|
-
|
|
210
|
+
const audioBuffer = await readFile(chunks[i]);
|
|
211
|
+
const result = await provider.transcribe(
|
|
212
|
+
audioBuffer,
|
|
213
|
+
"audio/wav",
|
|
214
|
+
AbortSignal.timeout(API_REQUEST_TIMEOUT_MS),
|
|
215
|
+
);
|
|
216
|
+
if (result.text) parts.push(result.text);
|
|
204
217
|
}
|
|
205
218
|
|
|
206
219
|
return parts.join(" ");
|
|
@@ -213,40 +226,6 @@ async function transcribeViaApi(
|
|
|
213
226
|
}
|
|
214
227
|
}
|
|
215
228
|
|
|
216
|
-
async function whisperApiRequest(
|
|
217
|
-
audioPath: string,
|
|
218
|
-
apiKey: string,
|
|
219
|
-
): Promise<string> {
|
|
220
|
-
const audioData = await readFile(audioPath);
|
|
221
|
-
const formData = new FormData();
|
|
222
|
-
formData.append(
|
|
223
|
-
"file",
|
|
224
|
-
new Blob([audioData], { type: "audio/wav" }),
|
|
225
|
-
"audio.wav",
|
|
226
|
-
);
|
|
227
|
-
formData.append("model", "whisper-1");
|
|
228
|
-
|
|
229
|
-
const response = await fetch(
|
|
230
|
-
"https://api.openai.com/v1/audio/transcriptions",
|
|
231
|
-
{
|
|
232
|
-
method: "POST",
|
|
233
|
-
headers: { Authorization: `Bearer ${apiKey}` },
|
|
234
|
-
body: formData,
|
|
235
|
-
signal: AbortSignal.timeout(API_REQUEST_TIMEOUT_MS),
|
|
236
|
-
},
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
if (!response.ok) {
|
|
240
|
-
const body = await response.text().catch(() => "");
|
|
241
|
-
throw new Error(
|
|
242
|
-
`Whisper API error (${response.status}): ${body.slice(0, 300)}`,
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const result = (await response.json()) as { text?: string };
|
|
247
|
-
return result.text?.trim() ?? "";
|
|
248
|
-
}
|
|
249
|
-
|
|
250
229
|
// ---------------------------------------------------------------------------
|
|
251
230
|
// Local mode - whisper.cpp
|
|
252
231
|
// ---------------------------------------------------------------------------
|
|
@@ -54,6 +54,15 @@ export function getIsContainerized(): boolean {
|
|
|
54
54
|
return flag("IS_CONTAINERIZED");
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* WORKSPACE_DIR — string, default: undefined
|
|
59
|
+
* When set, overrides the default workspace directory. Used in containerized
|
|
60
|
+
* deployments where the workspace is a separate volume.
|
|
61
|
+
*/
|
|
62
|
+
export function getWorkspaceDirOverride(): string | undefined {
|
|
63
|
+
return str("WORKSPACE_DIR");
|
|
64
|
+
}
|
|
65
|
+
|
|
57
66
|
// ── Known env var names ──────────────────────────────────────────────────────
|
|
58
67
|
|
|
59
68
|
/**
|
package/src/config/env.ts
CHANGED
|
@@ -51,9 +51,15 @@ export function getGatewayPort(): number {
|
|
|
51
51
|
return int("GATEWAY_PORT", DEFAULT_GATEWAY_PORT);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
/**
|
|
54
|
+
/**
|
|
55
|
+
* Resolve the gateway base URL for internal service-to-service calls.
|
|
56
|
+
*
|
|
57
|
+
* In containerized deployments the gateway runs in a separate container,
|
|
58
|
+
* reachable via `GATEWAY_INTERNAL_URL` (e.g. `http://gateway:7822`).
|
|
59
|
+
* Falls back to `http://127.0.0.1:<GATEWAY_PORT>` for local deployments.
|
|
60
|
+
*/
|
|
55
61
|
export function getGatewayInternalBaseUrl(): string {
|
|
56
|
-
return `http://127.0.0.1:${getGatewayPort()}`;
|
|
62
|
+
return str("GATEWAY_INTERNAL_URL") ?? `http://127.0.0.1:${getGatewayPort()}`;
|
|
57
63
|
}
|
|
58
64
|
|
|
59
65
|
// ── Ingress ──────────────────────────────────────────────────────────────────
|
|
@@ -25,14 +25,6 @@
|
|
|
25
25
|
"description": "Show the Contacts tab in Settings for viewing and managing contacts",
|
|
26
26
|
"defaultEnabled": true
|
|
27
27
|
},
|
|
28
|
-
{
|
|
29
|
-
"id": "custom-inference-provider",
|
|
30
|
-
"scope": "macos",
|
|
31
|
-
"key": "custom_inference_provider_enabled",
|
|
32
|
-
"label": "Custom Inference Provider",
|
|
33
|
-
"description": "Allow selecting a specific LLM provider and model for inference in Your Own mode",
|
|
34
|
-
"defaultEnabled": false
|
|
35
|
-
},
|
|
36
28
|
{
|
|
37
29
|
"id": "email-channel",
|
|
38
30
|
"scope": "assistant",
|
|
@@ -288,6 +280,14 @@
|
|
|
288
280
|
"label": "Inline Skill Command Expansion",
|
|
289
281
|
"description": "Enable secure inline skill command expansion via !`command` syntax, with version-pinned approval and sandboxed execution at skill load time",
|
|
290
282
|
"defaultEnabled": true
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"id": "channel-voice-transcription",
|
|
286
|
+
"scope": "assistant",
|
|
287
|
+
"key": "feature_flags.channel-voice-transcription.enabled",
|
|
288
|
+
"label": "Channel Voice Transcription",
|
|
289
|
+
"description": "Auto-transcribe voice/audio messages received from channels (Telegram, WhatsApp) before processing",
|
|
290
|
+
"defaultEnabled": true
|
|
291
291
|
}
|
|
292
292
|
]
|
|
293
293
|
}
|
package/src/config/schema.ts
CHANGED
|
@@ -106,18 +106,6 @@ export {
|
|
|
106
106
|
MemoryDynamicBudgetConfigSchema,
|
|
107
107
|
MemoryRetrievalConfigSchema,
|
|
108
108
|
} from "./schemas/memory-retrieval.js";
|
|
109
|
-
export type {
|
|
110
|
-
MemorySimplifiedArchiveRecallConfig,
|
|
111
|
-
MemorySimplifiedBriefConfig,
|
|
112
|
-
MemorySimplifiedConfig,
|
|
113
|
-
MemorySimplifiedReducerConfig,
|
|
114
|
-
} from "./schemas/memory-simplified.js";
|
|
115
|
-
export {
|
|
116
|
-
MemorySimplifiedArchiveRecallConfigSchema,
|
|
117
|
-
MemorySimplifiedBriefConfigSchema,
|
|
118
|
-
MemorySimplifiedConfigSchema,
|
|
119
|
-
MemorySimplifiedReducerConfigSchema,
|
|
120
|
-
} from "./schemas/memory-simplified.js";
|
|
121
109
|
export type {
|
|
122
110
|
MemoryEmbeddingsConfig,
|
|
123
111
|
MemorySegmentationConfig,
|
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
MemorySummarizationConfigSchema,
|
|
11
11
|
} from "./memory-processing.js";
|
|
12
12
|
import { MemoryRetrievalConfigSchema } from "./memory-retrieval.js";
|
|
13
|
-
import { MemorySimplifiedConfigSchema } from "./memory-simplified.js";
|
|
14
13
|
import {
|
|
15
14
|
MemoryEmbeddingsConfigSchema,
|
|
16
15
|
MemorySegmentationConfigSchema,
|
|
@@ -46,9 +45,6 @@ export const MemoryConfigSchema = z
|
|
|
46
45
|
summarization: MemorySummarizationConfigSchema.default(
|
|
47
46
|
MemorySummarizationConfigSchema.parse({}),
|
|
48
47
|
),
|
|
49
|
-
simplified: MemorySimplifiedConfigSchema.default(
|
|
50
|
-
MemorySimplifiedConfigSchema.parse({}),
|
|
51
|
-
),
|
|
52
48
|
})
|
|
53
49
|
.describe(
|
|
54
50
|
"Long-term memory system — stores, retrieves, and manages persistent knowledge across conversations",
|
|
@@ -43,7 +43,7 @@ export const DaemonConfigSchema = z
|
|
|
43
43
|
.number({ error: "daemon.titleGenerationMaxTokens must be a number" })
|
|
44
44
|
.int("daemon.titleGenerationMaxTokens must be an integer")
|
|
45
45
|
.positive("daemon.titleGenerationMaxTokens must be a positive integer")
|
|
46
|
-
.default(
|
|
46
|
+
.default(50)
|
|
47
47
|
.describe(
|
|
48
48
|
"Maximum number of tokens for auto-generated conversation titles",
|
|
49
49
|
),
|
|
@@ -77,6 +77,10 @@ export const PermissionsConfigSchema = z
|
|
|
77
77
|
.describe(
|
|
78
78
|
"Permission mode — 'strict' requires explicit approval for all operations, 'workspace' allows operations within the workspace",
|
|
79
79
|
),
|
|
80
|
+
dangerouslySkipPermissions: z
|
|
81
|
+
.boolean({ error: "permissions.dangerouslySkipPermissions must be a boolean" })
|
|
82
|
+
.default(false)
|
|
83
|
+
.describe("Auto-accept all permission prompts without asking"),
|
|
80
84
|
})
|
|
81
85
|
.describe("Permission enforcement mode for tool operations");
|
|
82
86
|
|
|
@@ -538,12 +538,12 @@ export class ContextWindowManager {
|
|
|
538
538
|
}
|
|
539
539
|
|
|
540
540
|
const keepTurns = lo;
|
|
541
|
-
const
|
|
541
|
+
const rawKeepFromIndex =
|
|
542
542
|
keepTurns === 0
|
|
543
543
|
? messages.length
|
|
544
544
|
: (userTurnStarts[userTurnStarts.length - keepTurns] ??
|
|
545
545
|
messages.length);
|
|
546
|
-
|
|
546
|
+
const keepFromIndex = adjustForToolPairs(messages, rawKeepFromIndex);
|
|
547
547
|
return { keepFromIndex, keepTurns };
|
|
548
548
|
}
|
|
549
549
|
|
|
@@ -703,6 +703,57 @@ function isToolResultOnly(message: Message): boolean {
|
|
|
703
703
|
);
|
|
704
704
|
}
|
|
705
705
|
|
|
706
|
+
/**
|
|
707
|
+
* Walk the keep boundary backward to ensure tool_use/tool_result pairs are
|
|
708
|
+
* never split across the compaction boundary. If the first kept message is
|
|
709
|
+
* a user message containing tool_result blocks whose matching tool_use blocks
|
|
710
|
+
* live in the preceding (compacted-away) assistant message, include that
|
|
711
|
+
* assistant message in the kept set.
|
|
712
|
+
*/
|
|
713
|
+
function adjustForToolPairs(
|
|
714
|
+
messages: Message[],
|
|
715
|
+
keepFromIndex: number,
|
|
716
|
+
): number {
|
|
717
|
+
let idx = keepFromIndex;
|
|
718
|
+
while (idx > 0) {
|
|
719
|
+
const msg = messages[idx];
|
|
720
|
+
if (!msg || msg.role !== "user") break;
|
|
721
|
+
|
|
722
|
+
// Collect tool_use_ids referenced by tool_results in this user message
|
|
723
|
+
const referencedIds = new Set<string>();
|
|
724
|
+
for (const block of msg.content) {
|
|
725
|
+
if ((block.type === "tool_result" || block.type === "web_search_tool_result") && "tool_use_id" in block) {
|
|
726
|
+
referencedIds.add((block as { tool_use_id: string }).tool_use_id);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
if (referencedIds.size === 0) break;
|
|
730
|
+
|
|
731
|
+
// Check if the preceding assistant message contains matching tool_uses
|
|
732
|
+
const prev = messages[idx - 1];
|
|
733
|
+
if (!prev || prev.role !== "assistant") break;
|
|
734
|
+
|
|
735
|
+
const hasOrphanedPair = prev.content.some(
|
|
736
|
+
(block) =>
|
|
737
|
+
(block.type === "tool_use" || block.type === "server_tool_use") &&
|
|
738
|
+
"id" in block &&
|
|
739
|
+
referencedIds.has((block as { id: string }).id),
|
|
740
|
+
);
|
|
741
|
+
if (!hasOrphanedPair) break;
|
|
742
|
+
|
|
743
|
+
// Include the assistant message
|
|
744
|
+
idx--;
|
|
745
|
+
|
|
746
|
+
// The assistant message may itself be preceded by a tool_result user
|
|
747
|
+
// message that pairs with an even earlier assistant — continue the check
|
|
748
|
+
if (idx > 0 && messages[idx - 1]?.role === "user") {
|
|
749
|
+
idx--;
|
|
750
|
+
} else {
|
|
751
|
+
break;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
return idx;
|
|
755
|
+
}
|
|
756
|
+
|
|
706
757
|
export function getSummaryFromContextMessage(
|
|
707
758
|
message: Message | undefined,
|
|
708
759
|
): string | null {
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import { platformOAuthHandle } from "@vellumai/ces-contracts";
|
|
13
13
|
|
|
14
|
-
import {
|
|
15
|
-
import { resolveManagedProxyContext } from "../providers/managed-proxy/context.js";
|
|
14
|
+
import { VellumPlatformClient } from "../platform/client.js";
|
|
16
15
|
import { getLogger } from "../util/logger.js";
|
|
17
16
|
|
|
18
17
|
const log = getLogger("managed-catalog");
|
|
@@ -79,25 +78,18 @@ export interface FetchManagedCatalogResult {
|
|
|
79
78
|
* error message that never contains secret material.
|
|
80
79
|
*/
|
|
81
80
|
export async function fetchManagedCatalog(): Promise<FetchManagedCatalogResult> {
|
|
82
|
-
const
|
|
81
|
+
const client = await VellumPlatformClient.create();
|
|
83
82
|
|
|
84
|
-
if (!
|
|
83
|
+
if (!client || !client.platformAssistantId) {
|
|
85
84
|
return { ok: true, descriptors: [] };
|
|
86
85
|
}
|
|
87
86
|
|
|
88
|
-
const
|
|
89
|
-
if (!assistantId) {
|
|
90
|
-
log.warn("PLATFORM_ASSISTANT_ID not set; cannot fetch managed catalog");
|
|
91
|
-
return { ok: true, descriptors: [] };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const url = `${ctx.platformBaseUrl}/v1/assistants/${encodeURIComponent(assistantId)}/oauth/managed/catalog/`;
|
|
87
|
+
const path = `/v1/assistants/${encodeURIComponent(client.platformAssistantId)}/oauth/managed/catalog/`;
|
|
95
88
|
|
|
96
89
|
try {
|
|
97
|
-
const response = await fetch(
|
|
90
|
+
const response = await client.fetch(path, {
|
|
98
91
|
method: "GET",
|
|
99
92
|
headers: {
|
|
100
|
-
Authorization: `Api-Key ${ctx.assistantApiKey}`,
|
|
101
93
|
Accept: "application/json",
|
|
102
94
|
},
|
|
103
95
|
});
|
|
@@ -139,8 +131,6 @@ export async function fetchManagedCatalog(): Promise<FetchManagedCatalogResult>
|
|
|
139
131
|
return { ok: true, descriptors };
|
|
140
132
|
} catch (err) {
|
|
141
133
|
const message = err instanceof Error ? err.message : String(err);
|
|
142
|
-
// Ensure the error message does not leak secrets — strip any URL params
|
|
143
|
-
// that might contain tokens (defensive, since we use Api-Key header).
|
|
144
134
|
const safeMessage = message.replace(
|
|
145
135
|
/Api-Key\s+\S+/gi,
|
|
146
136
|
"Api-Key [REDACTED]",
|
|
@@ -33,7 +33,6 @@ import {
|
|
|
33
33
|
} from "../instrument.js";
|
|
34
34
|
import { commitAppTurnChanges } from "../memory/app-git-service.js";
|
|
35
35
|
import { getApp, listAppFiles, resolveAppDir } from "../memory/app-store.js";
|
|
36
|
-
import { insertCompactionEpisode } from "../memory/archive-store.js";
|
|
37
36
|
import {
|
|
38
37
|
addMessage,
|
|
39
38
|
deleteMessageById,
|
|
@@ -514,12 +513,6 @@ export async function runAgentLoopImpl(
|
|
|
514
513
|
compacted.summaryText,
|
|
515
514
|
ctx.contextCompactedMessageCount,
|
|
516
515
|
);
|
|
517
|
-
dualWriteCompactionEpisode(
|
|
518
|
-
ctx.conversationId,
|
|
519
|
-
ctx.memoryPolicy.scopeId,
|
|
520
|
-
compacted.summaryText,
|
|
521
|
-
compacted.summaryOutputTokens,
|
|
522
|
-
);
|
|
523
516
|
onEvent({
|
|
524
517
|
type: "context_compacted",
|
|
525
518
|
previousEstimatedInputTokens: compacted.previousEstimatedInputTokens,
|
|
@@ -787,12 +780,6 @@ export async function runAgentLoopImpl(
|
|
|
787
780
|
step.compactionResult.summaryText,
|
|
788
781
|
ctx.contextCompactedMessageCount,
|
|
789
782
|
);
|
|
790
|
-
dualWriteCompactionEpisode(
|
|
791
|
-
ctx.conversationId,
|
|
792
|
-
ctx.memoryPolicy.scopeId,
|
|
793
|
-
step.compactionResult.summaryText,
|
|
794
|
-
step.compactionResult.summaryOutputTokens,
|
|
795
|
-
);
|
|
796
783
|
onEvent({
|
|
797
784
|
type: "context_compacted",
|
|
798
785
|
previousEstimatedInputTokens:
|
|
@@ -977,12 +964,6 @@ export async function runAgentLoopImpl(
|
|
|
977
964
|
midLoopCompact.summaryText,
|
|
978
965
|
ctx.contextCompactedMessageCount,
|
|
979
966
|
);
|
|
980
|
-
dualWriteCompactionEpisode(
|
|
981
|
-
ctx.conversationId,
|
|
982
|
-
ctx.memoryPolicy.scopeId,
|
|
983
|
-
midLoopCompact.summaryText,
|
|
984
|
-
midLoopCompact.summaryOutputTokens,
|
|
985
|
-
);
|
|
986
967
|
onEvent({
|
|
987
968
|
type: "context_compacted",
|
|
988
969
|
previousEstimatedInputTokens:
|
|
@@ -1179,12 +1160,6 @@ export async function runAgentLoopImpl(
|
|
|
1179
1160
|
step.compactionResult.summaryText,
|
|
1180
1161
|
ctx.contextCompactedMessageCount,
|
|
1181
1162
|
);
|
|
1182
|
-
dualWriteCompactionEpisode(
|
|
1183
|
-
ctx.conversationId,
|
|
1184
|
-
ctx.memoryPolicy.scopeId,
|
|
1185
|
-
step.compactionResult.summaryText,
|
|
1186
|
-
step.compactionResult.summaryOutputTokens,
|
|
1187
|
-
);
|
|
1188
1163
|
onEvent({
|
|
1189
1164
|
type: "context_compacted",
|
|
1190
1165
|
previousEstimatedInputTokens:
|
|
@@ -1292,12 +1267,6 @@ export async function runAgentLoopImpl(
|
|
|
1292
1267
|
emergencyCompact.summaryText,
|
|
1293
1268
|
ctx.contextCompactedMessageCount,
|
|
1294
1269
|
);
|
|
1295
|
-
dualWriteCompactionEpisode(
|
|
1296
|
-
ctx.conversationId,
|
|
1297
|
-
ctx.memoryPolicy.scopeId,
|
|
1298
|
-
emergencyCompact.summaryText,
|
|
1299
|
-
emergencyCompact.summaryOutputTokens,
|
|
1300
|
-
);
|
|
1301
1270
|
onEvent({
|
|
1302
1271
|
type: "context_compacted",
|
|
1303
1272
|
previousEstimatedInputTokens:
|
|
@@ -1402,12 +1371,6 @@ export async function runAgentLoopImpl(
|
|
|
1402
1371
|
emergencyCompact.summaryText,
|
|
1403
1372
|
ctx.contextCompactedMessageCount,
|
|
1404
1373
|
);
|
|
1405
|
-
dualWriteCompactionEpisode(
|
|
1406
|
-
ctx.conversationId,
|
|
1407
|
-
ctx.memoryPolicy.scopeId,
|
|
1408
|
-
emergencyCompact.summaryText,
|
|
1409
|
-
emergencyCompact.summaryOutputTokens,
|
|
1410
|
-
);
|
|
1411
1374
|
onEvent({
|
|
1412
1375
|
type: "context_compacted",
|
|
1413
1376
|
previousEstimatedInputTokens:
|
|
@@ -1873,26 +1836,3 @@ function collapseRawResponses(rawResponses?: unknown[]): unknown | undefined {
|
|
|
1873
1836
|
if (!rawResponses || rawResponses.length === 0) return undefined;
|
|
1874
1837
|
return rawResponses.length === 1 ? rawResponses[0] : rawResponses;
|
|
1875
1838
|
}
|
|
1876
|
-
|
|
1877
|
-
/**
|
|
1878
|
-
* Dual-write a compaction summary as an archive episode so it becomes
|
|
1879
|
-
* searchable via vector recall. Called after each successful compaction
|
|
1880
|
-
* that produces a new summary.
|
|
1881
|
-
*/
|
|
1882
|
-
function dualWriteCompactionEpisode(
|
|
1883
|
-
conversationId: string,
|
|
1884
|
-
scopeId: string,
|
|
1885
|
-
summaryText: string,
|
|
1886
|
-
summaryOutputTokens: number,
|
|
1887
|
-
): void {
|
|
1888
|
-
const now = Date.now();
|
|
1889
|
-
insertCompactionEpisode({
|
|
1890
|
-
conversationId,
|
|
1891
|
-
scopeId,
|
|
1892
|
-
title: truncate(summaryText, 120, ""),
|
|
1893
|
-
summary: summaryText,
|
|
1894
|
-
tokenEstimate: summaryOutputTokens,
|
|
1895
|
-
startAt: now,
|
|
1896
|
-
endAt: now,
|
|
1897
|
-
});
|
|
1898
|
-
}
|