@vellumai/assistant 0.8.11 → 0.8.12-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +15 -17
- package/README.md +0 -6
- package/bun.lock +6 -122
- package/node_modules/@vellumai/gateway-client/bun.lock +1 -0
- package/node_modules/@vellumai/gateway-client/package.json +3 -1
- package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -1
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +87 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +3 -5
- package/openapi.yaml +633 -4
- package/package.json +1 -3
- package/src/__tests__/adaptive-thinking-repair.test.ts +185 -0
- package/src/__tests__/agent-loop-compaction-events.test.ts +7 -6
- package/src/__tests__/anthropic-provider.test.ts +129 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -1
- package/src/__tests__/btw-routes.test.ts +7 -34
- package/src/__tests__/checker.test.ts +6 -12
- package/src/__tests__/config-loader-backfill.test.ts +4 -2
- package/src/__tests__/config-loader-quarantine-notice.test.ts +167 -0
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
- package/src/__tests__/conversation-error.test.ts +2 -6
- package/src/__tests__/conversation-history-web-search.test.ts +8 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -1
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/disk-pressure-tools.test.ts +1 -1
- package/src/__tests__/exploration-drift-hook.test.ts +692 -0
- package/src/__tests__/filing-service.test.ts +8 -3
- package/src/__tests__/guardian-action-store.test.ts +0 -167
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +1 -1
- package/src/__tests__/heartbeat-disk-pressure.test.ts +4 -1
- package/src/__tests__/heartbeat-service.test.ts +5 -2
- package/src/__tests__/identity-intro-cache.test.ts +12 -5
- package/src/__tests__/identity-routes.test.ts +16 -57
- package/src/__tests__/injector-chain.test.ts +8 -3
- package/src/__tests__/injector-config-quarantine-notice.test.ts +115 -0
- package/src/__tests__/llm-catalog-parity.test.ts +16 -0
- package/src/__tests__/llm-usage-store.test.ts +11 -0
- package/src/__tests__/log-export-workspace.test.ts +468 -3
- package/src/__tests__/memory-v2-static-injector.test.ts +22 -0
- package/src/__tests__/model-intents.test.ts +1 -1
- package/src/__tests__/oauth-cli.test.ts +19 -8
- package/src/__tests__/openai-provider.test.ts +34 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +0 -1
- package/src/__tests__/recurrence-engine.test.ts +45 -0
- package/src/__tests__/schedule-routes.test.ts +34 -0
- package/src/__tests__/scheduler-disk-pressure.test.ts +1 -1
- package/src/__tests__/script-proxy-conversation-manager.test.ts +10 -5
- package/src/__tests__/secret-fixtures.ts +20 -0
- package/src/__tests__/skill-tool-factory.test.ts +49 -0
- package/src/__tests__/subagent-role-registry.test.ts +24 -1
- package/src/__tests__/subagent-tools.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +109 -11
- package/src/__tests__/tool-approval-handler.test.ts +85 -0
- package/src/__tests__/tool-audit-listener.test.ts +86 -0
- package/src/__tests__/tool-error-hook.test.ts +1 -0
- package/src/__tests__/tool-result-spool.test.ts +337 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +1 -0
- package/src/__tests__/validate-input.test.ts +95 -1
- package/src/__tests__/workspace-migration-098-remove-stale-updates-bulletin-file.test.ts +65 -0
- package/src/__tests__/workspace-migration-099-disable-cache-one-shot-callsites.test.ts +139 -0
- package/src/__tests__/workspace-migration-100-upgrade-quality-profile-to-fable-5.test.ts +174 -0
- package/src/__tests__/workspace-migration-101-upgrade-balanced-economy-to-minimax-m3.test.ts +162 -0
- package/src/__tests__/workspace-release-notes-feature-flag-guard.test.ts +45 -95
- package/src/acp/__tests__/agent-process.test.ts +315 -2
- package/src/acp/__tests__/prepare-agent-env.test.ts +79 -5
- package/src/acp/agent-process.ts +163 -34
- package/src/acp/prepare-agent-env.ts +55 -15
- package/src/agent/loop.ts +81 -24
- package/src/api/events/usage-progress.ts +28 -0
- package/src/api/index.ts +6 -0
- package/src/background-wake/wake-intent-hooks.test.ts +2 -0
- package/src/bundler/app-bundler.ts +25 -42
- package/src/bundler/app-compiler.ts +8 -0
- package/src/calls/call-controller.ts +1 -1
- package/src/cli/commands/plugins.ts +248 -15
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +318 -0
- package/src/cli/lib/__tests__/install-from-github.test.ts +16 -9
- package/src/cli/lib/__tests__/plugin-artifact.test.ts +183 -0
- package/src/cli/lib/__tests__/plugin-details.test.ts +158 -0
- package/src/cli/lib/__tests__/plugin-fingerprint.test.ts +245 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +307 -0
- package/src/cli/lib/inspect-plugin.ts +252 -0
- package/src/cli/lib/install-from-github.ts +214 -21
- package/src/cli/lib/list-installed-plugins.ts +17 -6
- package/src/cli/lib/plugin-artifact.ts +103 -0
- package/src/cli/lib/plugin-details.ts +18 -1
- package/src/cli/lib/plugin-fingerprint.ts +197 -0
- package/src/cli/lib/upgrade-plugin.ts +225 -0
- package/src/config/bundled-skills/subagent/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +8 -2
- package/src/config/call-site-defaults.ts +13 -2
- package/src/config/feature-flag-registry.json +8 -16
- package/src/config/loader.ts +52 -59
- package/src/config/schema.ts +0 -2
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +10 -0
- package/src/config/schemas/llm.ts +10 -0
- package/src/config/schemas/memory-v2.ts +13 -0
- package/src/config/schemas/memory-v3.ts +92 -0
- package/src/config/seed-inference-profiles.ts +4 -8
- package/src/context/post-turn-tool-result-truncation.ts +32 -18
- package/src/context/tool-result-spool.ts +104 -0
- package/src/credential-execution/feature-gates.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +41 -16
- package/src/daemon/conversation-error.ts +6 -15
- package/src/daemon/conversation.ts +9 -0
- package/src/daemon/disk-pressure-policy.ts +0 -1
- package/src/daemon/lifecycle.ts +1 -20
- package/src/daemon/message-types/conversations.ts +2 -15
- package/src/daemon/trust-context.ts +1 -1
- package/src/events/tool-audit-listener.ts +40 -9
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +1 -1
- package/src/home/__tests__/home-content-refresh.test.ts +114 -0
- package/src/home/__tests__/suggested-prompts.test.ts +86 -5
- package/src/home/home-content-refresh.ts +43 -31
- package/src/home/home-greeting-cache.ts +8 -1
- package/src/home/home-greeting.ts +13 -9
- package/src/home/suggested-prompts.ts +77 -24
- package/src/ipc/routes/trust-rules.test.ts +66 -72
- package/src/media/image-credentials.ts +2 -2
- package/src/memory/__tests__/compaction-log-store-clickhouse.test.ts +432 -0
- package/src/memory/{compaction-log-writer-clickhouse.ts → compaction-log-store-clickhouse.ts} +264 -55
- package/src/memory/conversation-attention-store.ts +1 -0
- package/src/memory/conversation-bootstrap.ts +18 -9
- package/src/memory/conversation-crud.ts +12 -2
- package/src/memory/conversation-title-service.ts +53 -9
- package/src/memory/delivery-channels.ts +0 -69
- package/src/memory/graph/extraction-job.ts +0 -15
- package/src/memory/guardian-action-store.ts +1 -376
- package/src/memory/llm-usage-store.ts +5 -1
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +2 -2
- package/src/memory/v2/__tests__/consolidation-job.test.ts +183 -2
- package/src/memory/v2/__tests__/injection.test.ts +70 -0
- package/src/memory/v2/__tests__/static-context.test.ts +12 -0
- package/src/memory/v2/consolidation-job.ts +93 -9
- package/src/memory/v2/injection.ts +53 -0
- package/src/memory/v2/prompts/consolidation.ts +1 -0
- package/src/memory/v2/static-context.ts +13 -1
- package/src/memory/v2/sweep-job.ts +1 -1
- package/src/memory/v2/types.ts +5 -0
- package/src/plugin-api/types.ts +7 -0
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +300 -0
- package/src/plugins/defaults/exploration-drift/package.json +15 -0
- package/src/plugins/defaults/index.ts +25 -0
- package/src/plugins/defaults/memory-retrieval/injectors.ts +132 -4
- package/src/plugins/defaults/memory-v3-shadow/__tests__/card.test.ts +92 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/fresh-set.test.ts +52 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +1 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/live-integration.test.ts +2 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/orchestrate.test.ts +136 -5
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +17 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +6 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +5 -1
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +68 -4
- package/src/plugins/defaults/memory-v3-shadow/card.ts +49 -5
- package/src/plugins/defaults/memory-v3-shadow/fresh-set.ts +59 -0
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +4 -2
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.test.ts +169 -0
- package/src/plugins/defaults/memory-v3-shadow/learned-edges.ts +178 -0
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +115 -26
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -9
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +144 -22
- package/src/plugins/defaults/memory-v3-shadow/types.ts +24 -6
- package/src/plugins/defaults/title-generate/hooks/stop.ts +13 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +16 -0
- package/src/prompts/cache-boundary.ts +17 -0
- package/src/prompts/sections.ts +50 -17
- package/src/prompts/system-prompt.ts +12 -4
- package/src/prompts/templates/system-sections.ts +22 -0
- package/src/providers/__tests__/unparseable-tool-args.test.ts +53 -0
- package/src/providers/anthropic/client.ts +74 -28
- package/src/providers/gemini/client.ts +5 -1
- package/src/providers/minimax/client.ts +9 -0
- package/src/providers/model-catalog.ts +28 -0
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/chat-completions-provider.ts +4 -2
- package/src/providers/openai/responses-provider.ts +7 -2
- package/src/providers/retry.ts +8 -0
- package/src/providers/types.ts +11 -0
- package/src/providers/unparseable-tool-args.ts +56 -0
- package/src/runtime/AGENTS.md +6 -0
- package/src/runtime/__tests__/agent-wake.test.ts +2 -2
- package/src/runtime/agent-wake.ts +5 -5
- package/src/runtime/background-job-runner.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +150 -3
- package/src/runtime/migrations/vbundle-import-analyzer.ts +29 -6
- package/src/runtime/migrations/vbundle-import-policy.ts +23 -0
- package/src/runtime/migrations/vbundle-importer.ts +9 -4
- package/src/runtime/migrations/vbundle-streaming-importer.ts +8 -3
- package/src/runtime/pre-first-message-gate.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +241 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +132 -0
- package/src/runtime/routes/__tests__/gateway-log-routes.test.ts +97 -185
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +17 -0
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +348 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +3 -3
- package/src/runtime/routes/btw-routes.ts +0 -14
- package/src/runtime/routes/conversation-compaction-routes.ts +86 -19
- package/src/runtime/routes/conversation-list-routes.ts +77 -5
- package/src/runtime/routes/conversation-management-routes.ts +54 -0
- package/src/runtime/routes/conversation-query-routes.ts +79 -4
- package/src/runtime/routes/gateway-log-routes.ts +14 -64
- package/src/runtime/routes/home-feed-routes.ts +10 -0
- package/src/runtime/routes/identity-intro-cache.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +76 -20
- package/src/runtime/routes/inbound-message-handler.ts +0 -36
- package/src/runtime/routes/log-export-routes.ts +143 -96
- package/src/runtime/routes/plugins-routes.ts +380 -0
- package/src/runtime/routes/redact-staged-export.ts +259 -0
- package/src/runtime/routes/schedule-routes.ts +19 -2
- package/src/runtime/routes/trust-rules-routes.ts +14 -67
- package/src/schedule/recurrence-engine.ts +34 -0
- package/src/schedule/scheduler.ts +1 -0
- package/src/security/redact-json.ts +61 -0
- package/src/skills/validate-input.ts +41 -1
- package/src/subagent/types.ts +26 -1
- package/src/telemetry/types.ts +15 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +6 -1
- package/src/telemetry/usage-telemetry-reporter.ts +1 -0
- package/src/tools/apps/executors.ts +1 -1
- package/src/tools/skills/skill-tool-factory.ts +19 -8
- package/src/tools/tool-approval-handler.ts +31 -0
- package/src/usage/types.ts +8 -1
- package/src/util/platform.ts +16 -0
- package/src/watcher/engine.ts +1 -0
- package/src/workspace/adaptive-thinking-repair.ts +113 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +70 -67
- package/src/workspace/migrations/098-remove-stale-updates-bulletin-file.ts +31 -0
- package/src/workspace/migrations/099-disable-cache-one-shot-callsites.ts +81 -0
- package/src/workspace/migrations/100-upgrade-quality-profile-to-fable-5.ts +86 -0
- package/src/workspace/migrations/101-upgrade-balanced-economy-to-minimax-m3.ts +70 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +0 -202
- package/src/__tests__/conversation-starters-cadence.test.ts +0 -161
- package/src/__tests__/guardian-action-followup-executor.test.ts +0 -322
- package/src/__tests__/guardian-action-followup-store.test.ts +0 -373
- package/src/__tests__/guardian-action-late-reply.test.ts +0 -1083
- package/src/__tests__/update-bulletin-job.test.ts +0 -292
- package/src/config/schemas/updates.ts +0 -14
- package/src/memory/__tests__/compaction-log-writer-clickhouse.test.ts +0 -227
- package/src/memory/conversation-starters-cadence.ts +0 -78
- package/src/prompts/update-bulletin-job.ts +0 -180
- package/src/runtime/guardian-action-followup-executor.ts +0 -306
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* connection is stubbed directly so no child process is spawned.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { describe, expect, test } from "bun:test";
|
|
9
|
+
import { describe, expect, mock, test } from "bun:test";
|
|
10
10
|
|
|
11
|
-
import type { InitializeResponse } from "@agentclientprotocol/sdk";
|
|
11
|
+
import type { AuthMethod, InitializeResponse } from "@agentclientprotocol/sdk";
|
|
12
12
|
|
|
13
13
|
import { AcpAgentProcess } from "../agent-process.js";
|
|
14
14
|
|
|
@@ -159,3 +159,316 @@ describe("AcpAgentProcess loadSession/resumeSession", () => {
|
|
|
159
159
|
).rejects.toThrow('ACP agent "test-agent" is not spawned');
|
|
160
160
|
});
|
|
161
161
|
});
|
|
162
|
+
|
|
163
|
+
describe("AcpAgentProcess auth_required retry", () => {
|
|
164
|
+
// spawnedEnv is injected the way spawn() builds it ({ ...process.env,
|
|
165
|
+
// ...config.env }), so the advertised var names use a VELLUM_TEST_ prefix
|
|
166
|
+
// guaranteed absent from the test process env. The structure mirrors
|
|
167
|
+
// codex-acp's real advertisement: an agent-driven browser login (no `type`,
|
|
168
|
+
// must never be auto-selected) followed by two env_var methods, in that
|
|
169
|
+
// order.
|
|
170
|
+
const CODEX_VAR = "VELLUM_TEST_FAKE_CODEX_API_KEY";
|
|
171
|
+
const OPENAI_VAR = "VELLUM_TEST_FAKE_OPENAI_API_KEY";
|
|
172
|
+
|
|
173
|
+
const codexLikeAuthMethods: AuthMethod[] = [
|
|
174
|
+
{ id: "chatgpt", name: "Login with ChatGPT" },
|
|
175
|
+
{
|
|
176
|
+
type: "env_var",
|
|
177
|
+
id: "codex-api-key",
|
|
178
|
+
name: "Use CODEX_API_KEY",
|
|
179
|
+
vars: [{ name: CODEX_VAR }],
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
type: "env_var",
|
|
183
|
+
id: "openai-api-key",
|
|
184
|
+
name: "Use OPENAI_API_KEY",
|
|
185
|
+
vars: [{ name: OPENAI_VAR }],
|
|
186
|
+
},
|
|
187
|
+
];
|
|
188
|
+
|
|
189
|
+
const authRequiredError = {
|
|
190
|
+
code: -32000,
|
|
191
|
+
message: "Authentication required",
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Creates a process whose connection is stubbed with mocks, then runs
|
|
196
|
+
* initialize() so the advertised authMethods are captured.
|
|
197
|
+
*/
|
|
198
|
+
async function setupAuthProcess(options: {
|
|
199
|
+
env?: Record<string, string>;
|
|
200
|
+
authMethods?: AuthMethod[];
|
|
201
|
+
/** Rejections to throw from successive newSession calls before succeeding. */
|
|
202
|
+
newSessionRejections?: unknown[];
|
|
203
|
+
loadSessionRejections?: unknown[];
|
|
204
|
+
resumeSessionRejections?: unknown[];
|
|
205
|
+
promptRejections?: unknown[];
|
|
206
|
+
}) {
|
|
207
|
+
const proc = new AcpAgentProcess(
|
|
208
|
+
"codex",
|
|
209
|
+
{ command: "echo", args: [], env: options.env },
|
|
210
|
+
() => {
|
|
211
|
+
throw new Error("client factory should not be called in this test");
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
function failThenSucceed(rejections: unknown[], result: unknown) {
|
|
216
|
+
let calls = 0;
|
|
217
|
+
return mock(() => {
|
|
218
|
+
const rejection = rejections[calls];
|
|
219
|
+
calls += 1;
|
|
220
|
+
return rejection != null
|
|
221
|
+
? Promise.reject(rejection)
|
|
222
|
+
: Promise.resolve(result);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const newSession = failThenSucceed(options.newSessionRejections ?? [], {
|
|
227
|
+
sessionId: "session-1",
|
|
228
|
+
});
|
|
229
|
+
const loadSession = failThenSucceed(
|
|
230
|
+
options.loadSessionRejections ?? [],
|
|
231
|
+
{},
|
|
232
|
+
);
|
|
233
|
+
const resumeSession = failThenSucceed(
|
|
234
|
+
options.resumeSessionRejections ?? [],
|
|
235
|
+
{},
|
|
236
|
+
);
|
|
237
|
+
const prompt = failThenSucceed(options.promptRejections ?? [], {
|
|
238
|
+
stopReason: "end_turn",
|
|
239
|
+
});
|
|
240
|
+
const authenticate = mock(() => Promise.resolve({}));
|
|
241
|
+
|
|
242
|
+
const internals = proc as unknown as {
|
|
243
|
+
connection: unknown;
|
|
244
|
+
spawnedEnv: NodeJS.ProcessEnv;
|
|
245
|
+
};
|
|
246
|
+
internals.connection = {
|
|
247
|
+
initialize: () =>
|
|
248
|
+
Promise.resolve({
|
|
249
|
+
protocolVersion: 1,
|
|
250
|
+
authMethods: options.authMethods ?? codexLikeAuthMethods,
|
|
251
|
+
}),
|
|
252
|
+
newSession,
|
|
253
|
+
loadSession,
|
|
254
|
+
resumeSession,
|
|
255
|
+
prompt,
|
|
256
|
+
authenticate,
|
|
257
|
+
};
|
|
258
|
+
internals.spawnedEnv = { ...process.env, ...options.env };
|
|
259
|
+
|
|
260
|
+
await proc.initialize();
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
proc,
|
|
264
|
+
newSession,
|
|
265
|
+
loadSession,
|
|
266
|
+
resumeSession,
|
|
267
|
+
prompt,
|
|
268
|
+
authenticate,
|
|
269
|
+
internals,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
test("createSession does not authenticate when newSession succeeds", async () => {
|
|
274
|
+
const { proc, newSession, authenticate } = await setupAuthProcess({
|
|
275
|
+
env: { [OPENAI_VAR]: "sk-test" },
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const sessionId = await proc.createSession("/tmp/project");
|
|
279
|
+
|
|
280
|
+
expect(sessionId).toBe("session-1");
|
|
281
|
+
expect(newSession).toHaveBeenCalledTimes(1);
|
|
282
|
+
expect(authenticate).not.toHaveBeenCalled();
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("auth_required with OPENAI-style key authenticates and retries once", async () => {
|
|
286
|
+
const { proc, newSession, authenticate } = await setupAuthProcess({
|
|
287
|
+
env: { [OPENAI_VAR]: "sk-test" },
|
|
288
|
+
newSessionRejections: [authRequiredError],
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const sessionId = await proc.createSession("/tmp/project");
|
|
292
|
+
|
|
293
|
+
expect(sessionId).toBe("session-1");
|
|
294
|
+
expect(authenticate).toHaveBeenCalledTimes(1);
|
|
295
|
+
expect(authenticate).toHaveBeenCalledWith({ methodId: "openai-api-key" });
|
|
296
|
+
expect(newSession).toHaveBeenCalledTimes(2);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("auth_required with both keys present selects the earlier advertised method", async () => {
|
|
300
|
+
// Both methods are satisfiable, so this pins advertised-order selection
|
|
301
|
+
// rather than only-satisfiable selection.
|
|
302
|
+
const { proc, authenticate } = await setupAuthProcess({
|
|
303
|
+
env: { [CODEX_VAR]: "sk-codex", [OPENAI_VAR]: "sk-openai" },
|
|
304
|
+
newSessionRejections: [authRequiredError],
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
await proc.createSession("/tmp/project");
|
|
308
|
+
|
|
309
|
+
expect(authenticate).toHaveBeenCalledTimes(1);
|
|
310
|
+
expect(authenticate).toHaveBeenCalledWith({ methodId: "codex-api-key" });
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("auth_required with no satisfiable env_var method throws an actionable error", async () => {
|
|
314
|
+
const { proc, newSession, authenticate } = await setupAuthProcess({
|
|
315
|
+
env: {},
|
|
316
|
+
newSessionRejections: [authRequiredError],
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const promise = proc.createSession("/tmp/project");
|
|
320
|
+
|
|
321
|
+
await expect(promise).rejects.toThrow(
|
|
322
|
+
'ACP agent "codex" requires authentication',
|
|
323
|
+
);
|
|
324
|
+
await expect(promise).rejects.toThrow("Login with ChatGPT");
|
|
325
|
+
await expect(promise).rejects.toThrow(CODEX_VAR);
|
|
326
|
+
await expect(promise).rejects.toThrow(OPENAI_VAR);
|
|
327
|
+
expect(authenticate).not.toHaveBeenCalled();
|
|
328
|
+
expect(newSession).toHaveBeenCalledTimes(1);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("a second auth_required after authenticating propagates without a retry loop", async () => {
|
|
332
|
+
const { proc, newSession, authenticate } = await setupAuthProcess({
|
|
333
|
+
env: { [OPENAI_VAR]: "sk-test" },
|
|
334
|
+
newSessionRejections: [authRequiredError, authRequiredError],
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
await expect(proc.createSession("/tmp/project")).rejects.toMatchObject({
|
|
338
|
+
code: -32000,
|
|
339
|
+
});
|
|
340
|
+
expect(authenticate).toHaveBeenCalledTimes(1);
|
|
341
|
+
expect(newSession).toHaveBeenCalledTimes(2);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
test("an absent optional var does not block satisfiability", async () => {
|
|
345
|
+
const { proc, authenticate } = await setupAuthProcess({
|
|
346
|
+
env: { [OPENAI_VAR]: "sk-test" },
|
|
347
|
+
authMethods: [
|
|
348
|
+
{
|
|
349
|
+
type: "env_var",
|
|
350
|
+
id: "openai-api-key",
|
|
351
|
+
name: "Use OPENAI_API_KEY",
|
|
352
|
+
vars: [
|
|
353
|
+
{ name: OPENAI_VAR },
|
|
354
|
+
{ name: "VELLUM_TEST_FAKE_OPTIONAL_VAR", optional: true },
|
|
355
|
+
],
|
|
356
|
+
},
|
|
357
|
+
],
|
|
358
|
+
newSessionRejections: [authRequiredError],
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
await proc.createSession("/tmp/project");
|
|
362
|
+
|
|
363
|
+
expect(authenticate).toHaveBeenCalledWith({ methodId: "openai-api-key" });
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("loadSession authenticates and retries on auth_required", async () => {
|
|
367
|
+
const { proc, loadSession, authenticate } = await setupAuthProcess({
|
|
368
|
+
env: { [OPENAI_VAR]: "sk-test" },
|
|
369
|
+
loadSessionRejections: [authRequiredError],
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
await proc.loadSession("session-1", "/tmp/project");
|
|
373
|
+
|
|
374
|
+
expect(authenticate).toHaveBeenCalledWith({ methodId: "openai-api-key" });
|
|
375
|
+
expect(loadSession).toHaveBeenCalledTimes(2);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test("resumeSession authenticates and retries on auth_required", async () => {
|
|
379
|
+
const { proc, resumeSession, authenticate } = await setupAuthProcess({
|
|
380
|
+
env: { [OPENAI_VAR]: "sk-test" },
|
|
381
|
+
resumeSessionRejections: [authRequiredError],
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
await proc.resumeSession("session-1", "/tmp/project");
|
|
385
|
+
|
|
386
|
+
expect(authenticate).toHaveBeenCalledWith({ methodId: "openai-api-key" });
|
|
387
|
+
expect(resumeSession).toHaveBeenCalledTimes(2);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
test("prompt authenticates and retries on auth_required, returning the response", async () => {
|
|
391
|
+
const { proc, prompt, authenticate } = await setupAuthProcess({
|
|
392
|
+
env: { [OPENAI_VAR]: "sk-test" },
|
|
393
|
+
promptRejections: [authRequiredError],
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
const response = await proc.prompt("session-1", "hello");
|
|
397
|
+
|
|
398
|
+
expect(response).toEqual({ stopReason: "end_turn" });
|
|
399
|
+
expect(authenticate).toHaveBeenCalledTimes(1);
|
|
400
|
+
expect(authenticate).toHaveBeenCalledWith({ methodId: "openai-api-key" });
|
|
401
|
+
expect(prompt).toHaveBeenCalledTimes(2);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("prompt auth_required with no satisfiable env_var method throws an actionable error", async () => {
|
|
405
|
+
const { proc, prompt, authenticate } = await setupAuthProcess({
|
|
406
|
+
env: {},
|
|
407
|
+
promptRejections: [authRequiredError],
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const promise = proc.prompt("session-1", "hello");
|
|
411
|
+
|
|
412
|
+
await expect(promise).rejects.toThrow(
|
|
413
|
+
'ACP agent "codex" requires authentication',
|
|
414
|
+
);
|
|
415
|
+
await expect(promise).rejects.toThrow("Login with ChatGPT");
|
|
416
|
+
await expect(promise).rejects.toThrow(CODEX_VAR);
|
|
417
|
+
await expect(promise).rejects.toThrow(OPENAI_VAR);
|
|
418
|
+
expect(authenticate).not.toHaveBeenCalled();
|
|
419
|
+
expect(prompt).toHaveBeenCalledTimes(1);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
test("a non-auth error propagates without authenticating", async () => {
|
|
423
|
+
const { proc, newSession, authenticate } = await setupAuthProcess({
|
|
424
|
+
env: { [OPENAI_VAR]: "sk-test" },
|
|
425
|
+
newSessionRejections: [{ code: -32603, message: "Internal error" }],
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
await expect(proc.createSession("/tmp/project")).rejects.toMatchObject({
|
|
429
|
+
code: -32603,
|
|
430
|
+
});
|
|
431
|
+
expect(authenticate).not.toHaveBeenCalled();
|
|
432
|
+
expect(newSession).toHaveBeenCalledTimes(1);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test("auth_required after the agent process exits throws the not-spawned error", async () => {
|
|
436
|
+
const { proc, authenticate, internals } = await setupAuthProcess({
|
|
437
|
+
env: { [OPENAI_VAR]: "sk-test" },
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Simulate handleProcessExit racing the auth retry: the agent dies as it
|
|
441
|
+
// rejects with auth_required, nulling the connection before the retry
|
|
442
|
+
// path runs. spawnedEnv stays satisfiable so only the connection guard
|
|
443
|
+
// can produce the failure.
|
|
444
|
+
(internals.connection as { newSession: unknown }).newSession = () => {
|
|
445
|
+
internals.connection = null;
|
|
446
|
+
return Promise.reject(authRequiredError);
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
await expect(proc.createSession("/tmp/project")).rejects.toThrow(
|
|
450
|
+
'ACP agent "codex" is not spawned',
|
|
451
|
+
);
|
|
452
|
+
expect(authenticate).not.toHaveBeenCalled();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
test("satisfiability uses the env captured at spawn, not live process.env", async () => {
|
|
456
|
+
const { proc, authenticate } = await setupAuthProcess({
|
|
457
|
+
env: { [OPENAI_VAR]: "sk-test" },
|
|
458
|
+
newSessionRejections: [authRequiredError],
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Drift process.env after "spawn": the earlier-advertised codex var
|
|
462
|
+
// appears, but the spawned child never saw it, so the openai method
|
|
463
|
+
// must still be selected.
|
|
464
|
+
process.env[CODEX_VAR] = "sk-codex-late";
|
|
465
|
+
try {
|
|
466
|
+
await proc.createSession("/tmp/project");
|
|
467
|
+
} finally {
|
|
468
|
+
delete process.env[CODEX_VAR];
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
expect(authenticate).toHaveBeenCalledTimes(1);
|
|
472
|
+
expect(authenticate).toHaveBeenCalledWith({ methodId: "openai-api-key" });
|
|
473
|
+
});
|
|
474
|
+
});
|
|
@@ -47,8 +47,7 @@ mock.module("../../tools/credentials/metadata-store.js", () => ({
|
|
|
47
47
|
const existing = metadataStore.get(key);
|
|
48
48
|
metadataStore.set(key, {
|
|
49
49
|
allowedTools: policy?.allowedTools ?? existing?.allowedTools ?? [],
|
|
50
|
-
usageDescription:
|
|
51
|
-
policy?.usageDescription ?? existing?.usageDescription,
|
|
50
|
+
usageDescription: policy?.usageDescription ?? existing?.usageDescription,
|
|
52
51
|
});
|
|
53
52
|
return {
|
|
54
53
|
credentialId: `cred-${key}`,
|
|
@@ -331,16 +330,91 @@ describe("prepareAgentEnv - gemini gating", () => {
|
|
|
331
330
|
});
|
|
332
331
|
});
|
|
333
332
|
|
|
334
|
-
describe("prepareAgentEnv
|
|
335
|
-
|
|
333
|
+
describe("prepareAgentEnv - codex-acp gating", () => {
|
|
334
|
+
function seedVaultOpenaiKey(key: string): void {
|
|
335
|
+
vaultStore.set("acp/openai_api_key", key);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function seedVaultCodexKey(key: string): void {
|
|
339
|
+
vaultStore.set("acp/codex_api_key", key);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
test("injects OPENAI_API_KEY from the vault via the broker when agent.env has no override", async () => {
|
|
343
|
+
seedVaultOpenaiKey("vault-fake-openai-AAA");
|
|
344
|
+
|
|
345
|
+
const prepared = await prepareAgentEnv({
|
|
346
|
+
command: "codex-acp",
|
|
347
|
+
args: [],
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
expect(prepared.env?.OPENAI_API_KEY).toBe("vault-fake-openai-AAA");
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("agent.env override wins over the vault entry and skips the broker (precedence pin)", async () => {
|
|
354
|
+
// Seed a vault value but no metadata: if the override path consulted the
|
|
355
|
+
// broker anyway, ensureAcpCredentialPolicy would create metadata here.
|
|
356
|
+
seedVaultOpenaiKey("vault-fake-openai-BBB");
|
|
357
|
+
|
|
358
|
+
const prepared = await prepareAgentEnv({
|
|
359
|
+
command: "codex-acp",
|
|
360
|
+
args: [],
|
|
361
|
+
env: { OPENAI_API_KEY: "config-fake-openai-CCC" },
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
expect(prepared.env?.OPENAI_API_KEY).toBe("config-fake-openai-CCC");
|
|
365
|
+
expect(metadataStore.has("acp/openai_api_key")).toBe(false);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
test("a vault miss for both fields does NOT throw and spawns with env unchanged (keys are optional)", async () => {
|
|
369
|
+
const prepared = await prepareAgentEnv({
|
|
370
|
+
command: "codex-acp",
|
|
371
|
+
args: [],
|
|
372
|
+
env: { NO_COLOR: "1" },
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
expect(prepared.env).toEqual({ NO_COLOR: "1" });
|
|
376
|
+
expect(prepared.env).not.toHaveProperty("OPENAI_API_KEY");
|
|
377
|
+
expect(prepared.env).not.toHaveProperty("CODEX_API_KEY");
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("injects CODEX_API_KEY independently of OPENAI_API_KEY", async () => {
|
|
381
|
+
seedVaultCodexKey("vault-fake-codex-DDD");
|
|
382
|
+
|
|
336
383
|
const prepared = await prepareAgentEnv({
|
|
337
384
|
command: "codex-acp",
|
|
338
385
|
args: [],
|
|
339
386
|
});
|
|
340
387
|
|
|
341
|
-
expect(prepared.env).
|
|
388
|
+
expect(prepared.env?.CODEX_API_KEY).toBe("vault-fake-codex-DDD");
|
|
389
|
+
expect(prepared.env).not.toHaveProperty("OPENAI_API_KEY");
|
|
342
390
|
});
|
|
343
391
|
|
|
392
|
+
test("injects both keys when both vault fields are present", async () => {
|
|
393
|
+
seedVaultOpenaiKey("vault-fake-openai-EEE");
|
|
394
|
+
seedVaultCodexKey("vault-fake-codex-FFF");
|
|
395
|
+
|
|
396
|
+
const prepared = await prepareAgentEnv({
|
|
397
|
+
command: "codex-acp",
|
|
398
|
+
args: [],
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
expect(prepared.env?.OPENAI_API_KEY).toBe("vault-fake-openai-EEE");
|
|
402
|
+
expect(prepared.env?.CODEX_API_KEY).toBe("vault-fake-codex-FFF");
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("gates on the resolved command BASENAME (custom agent id with full path still gets injection)", async () => {
|
|
406
|
+
seedVaultOpenaiKey("vault-fake-openai-GGG");
|
|
407
|
+
|
|
408
|
+
const prepared = await prepareAgentEnv({
|
|
409
|
+
command: "/data/.bun/bin/codex-acp",
|
|
410
|
+
args: [],
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
expect(prepared.env?.OPENAI_API_KEY).toBe("vault-fake-openai-GGG");
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
describe("prepareAgentEnv — non-claude commands", () => {
|
|
344
418
|
test("returns the config unchanged for an unrecognized command basename", async () => {
|
|
345
419
|
seedVaultToken("vault-HHH");
|
|
346
420
|
|