@vellumai/assistant 0.9.0 → 0.9.1-staging.1
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 +18 -34
- package/bun.lock +7 -8
- package/docs/activation-funnel-telemetry.md +4 -4
- package/docs/architecture/security.md +29 -28
- package/docs/stt-provider-onboarding.md +3 -5
- package/docs/workflows-testing.md +13 -44
- package/docs/workflows.md +3 -5
- package/node_modules/@vellumai/ces-client/src/__tests__/ces-client.test.ts +47 -0
- package/node_modules/@vellumai/ces-client/src/rpc-client.ts +28 -5
- package/node_modules/@vellumai/environments/src/seeds.ts +2 -5
- package/node_modules/@vellumai/gateway-client/src/index.ts +17 -6
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +119 -0
- package/node_modules/@vellumai/gateway-client/src/types.ts +15 -84
- package/openapi.yaml +135 -59
- package/package.json +2 -1
- package/scripts/sync-llm-catalog.ts +6 -15
- package/scripts/sync-web-search-catalog.ts +3 -11
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +14 -26
- package/src/__tests__/agent-loop-compaction-strip.test.ts +240 -0
- package/src/__tests__/agent-loop-output-hooks.test.ts +69 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +25 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -3
- package/src/__tests__/app-dir-path-guard.test.ts +0 -1
- package/src/__tests__/assistant-feature-flag-guard.test.ts +1 -4
- package/src/__tests__/assistant-feature-flag-guardrails.test.ts +0 -2
- package/src/__tests__/avatar-identity-sync.test.ts +2 -27
- package/src/__tests__/btw-routes.test.ts +6 -8
- package/src/__tests__/checker.test.ts +0 -3
- package/src/__tests__/config-loader-backfill.test.ts +103 -6
- package/src/__tests__/config-watcher.test.ts +0 -18
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +22 -0
- package/src/__tests__/credential-broker.test.ts +449 -1
- package/src/__tests__/credential-execution-tools.test.ts +0 -1
- package/src/__tests__/credential-prompt-route.test.ts +3 -4
- package/src/__tests__/credential-routes.test.ts +360 -0
- package/src/__tests__/credential-security-invariants.test.ts +2 -13
- package/src/__tests__/dynamic-page-surface.test.ts +101 -1
- package/src/__tests__/fixtures/credential-security-fixtures.ts +2 -33
- package/src/__tests__/gateway-only-guard.test.ts +3 -7
- package/src/__tests__/identity-routes.test.ts +0 -189
- package/src/__tests__/inbound-invite-redemption.test.ts +4 -4
- package/src/__tests__/invite-redemption-service.test.ts +4 -4
- package/src/__tests__/llm-callsite-catalog.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +0 -22
- package/src/__tests__/llm-resolver.test.ts +49 -24
- package/src/__tests__/oauth-provider-seed-logos.test.ts +4 -6
- package/src/__tests__/onboarding-persona-write.test.ts +1 -1
- package/src/__tests__/persona-resolver.test.ts +11 -14
- package/src/__tests__/plugin-api-model-profiles.test.ts +178 -0
- package/src/__tests__/registry.test.ts +2 -7
- package/src/__tests__/schedule-routes-workflow-validation.test.ts +1 -10
- package/src/__tests__/schedule-routes.test.ts +0 -30
- package/src/__tests__/schedule-tools.test.ts +2 -18
- package/src/__tests__/skill-execute-input.test.ts +46 -1
- package/src/__tests__/skill-runtime-path.test.ts +2 -3
- package/src/__tests__/subagent-tools.test.ts +116 -0
- package/src/__tests__/surface-completion-nudge-hook.test.ts +367 -0
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1 -29
- package/src/__tests__/token-manager.test.ts +519 -0
- package/src/__tests__/tool-executor.test.ts +0 -79
- package/src/__tests__/trusted-contact-multichannel.test.ts +3 -3
- package/src/__tests__/trusted-contact-verification.test.ts +6 -6
- package/src/__tests__/voice-invite-redemption.test.ts +2 -2
- package/src/__tests__/web-search-catalog-parity.test.ts +6 -25
- package/src/__tests__/workspace-greetings.test.ts +152 -0
- package/src/agent/loop.ts +25 -5
- package/src/api/README.md +6 -6
- package/src/api/responses/conversation-message.ts +2 -4
- package/src/api/responses/home.ts +0 -4
- package/src/approvals/guardian-request-resolvers.ts +2 -2
- package/src/calls/relay-access-wait.ts +1 -1
- package/src/calls/voice-session-bridge.ts +2 -2
- package/src/cli/commands/plugins.ts +143 -2
- package/src/cli/lib/__tests__/diff-plugin.test.ts +443 -0
- package/src/cli/lib/__tests__/merge-plugin-tree.test.ts +313 -0
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +253 -2
- package/src/cli/lib/diff-plugin.ts +346 -0
- package/src/cli/lib/install-from-github.ts +105 -17
- package/src/cli/lib/merge-plugin-tree.ts +228 -0
- package/src/cli/lib/plugin-fingerprint.ts +14 -0
- package/src/cli/lib/upgrade-plugin.ts +270 -10
- package/src/cli/program.ts +0 -2
- package/src/config/bundled-skills/subagent/SKILL.md +4 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -0
- package/src/config/bundled-skills/workflows/SKILL.md +0 -1
- package/src/config/bundled-tool-registry.ts +2 -7
- package/src/config/call-site-defaults.ts +12 -2
- package/src/config/feature-flag-registry.json +1 -17
- package/src/config/inference-profile-validation.ts +26 -0
- package/src/config/loader.ts +4 -0
- package/src/config/profile-order.ts +28 -0
- package/src/config/schemas/elevenlabs.ts +0 -1
- package/src/config/schemas/platform.ts +0 -8
- package/src/config/seed-inference-profiles.ts +25 -20
- package/src/contacts/contact-store.ts +87 -96
- package/src/contacts/contacts-write.ts +5 -21
- package/src/context/compactor.ts +2 -2
- package/src/credential-execution/process-manager.ts +55 -14
- package/src/credential-execution/prompted-credential.ts +2 -3
- package/src/daemon/config-watcher.ts +0 -4
- package/src/daemon/conversation-agent-loop.ts +15 -4
- package/src/daemon/conversation-slash.ts +2 -23
- package/src/daemon/conversation-tool-setup.ts +11 -3
- package/src/daemon/conversation.ts +2 -0
- package/src/daemon/handlers/config-channels.ts +20 -16
- package/src/daemon/handlers/config-slack-channel.ts +2 -3
- package/src/daemon/lifecycle.ts +0 -7
- package/src/daemon/message-types/conversations.ts +3 -3
- package/src/daemon/message-types/sync.ts +0 -1
- package/src/daemon/orphan-reaper.test.ts +0 -19
- package/src/daemon/orphan-reaper.ts +2 -24
- package/src/daemon/server.ts +0 -10
- package/src/home/relationship-state.ts +2 -4
- package/src/memory/__tests__/memory-retrospective-job.test.ts +195 -401
- package/src/memory/bookmark-crud.ts +1 -2
- package/src/memory/db-init.ts +7 -17
- package/src/memory/embedding-backend.ts +23 -0
- package/src/memory/embedding-billing-breaker.ts +96 -0
- package/src/memory/jobs-store.ts +25 -13
- package/src/memory/jobs-worker.ts +52 -0
- package/src/memory/memory-retrospective-constants.ts +4 -4
- package/src/memory/memory-retrospective-job.ts +19 -227
- package/src/memory/migrations/291-contact-channels-renormalize-addresses.ts +62 -0
- package/src/memory/migrations/__tests__/291-contact-channels-renormalize-addresses.test.ts +311 -0
- package/src/memory/migrations/__tests__/run-migrations.test.ts +52 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/migrations/run-migrations.ts +41 -0
- package/src/memory/migrations/validate-migration-state.ts +1 -1
- package/src/memory/schema/contacts.ts +0 -4
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/notifications/adapters/shared.ts +29 -0
- package/src/notifications/adapters/slack.ts +5 -32
- package/src/notifications/adapters/telegram.ts +2 -20
- package/src/notifications/broadcaster.ts +10 -1
- package/src/notifications/home-feed-side-effect.ts +4 -3
- package/src/notifications/notification-utils.ts +17 -19
- package/src/notifications/types.ts +7 -0
- package/src/oauth/AGENTS.md +5 -24
- package/src/plugin-api/constants.ts +1 -1
- package/src/plugin-api/index.ts +6 -1
- package/src/plugin-api/model-profiles.ts +33 -0
- package/src/plugin-api/types.ts +50 -2
- package/src/plugins/defaults/index.ts +25 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/maintain-job.test.ts +54 -2
- package/src/plugins/defaults/memory-v3-shadow/maintain-job.ts +107 -7
- package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +276 -0
- package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +22 -0
- package/src/plugins/defaults/surface-completion-nudge/nudge-state-store.ts +46 -0
- package/src/plugins/defaults/surface-completion-nudge/package.json +14 -0
- package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +1 -1
- package/src/prompts/persona-resolver.ts +2 -2
- package/src/runtime/AGENTS.md +0 -1
- package/src/runtime/actor-trust-resolver.ts +12 -44
- package/src/runtime/btw-sidechain.ts +3 -6
- package/src/runtime/channel-approval-types.ts +18 -45
- package/src/runtime/channel-invite-transports/telegram.ts +4 -4
- package/src/runtime/channel-verification-service.ts +4 -3
- package/src/runtime/invite-redemption-service.ts +3 -3
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +218 -1
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -2
- package/src/runtime/routes/assets/vellum-design-system.css +1959 -0
- package/src/runtime/routes/btw-routes.ts +1 -27
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +2 -2
- package/src/runtime/routes/credential-routes.ts +40 -16
- package/src/runtime/routes/empty-state-greeting-cache.ts +1 -2
- package/src/runtime/routes/identity-routes.ts +1 -296
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +171 -5
- package/src/runtime/routes/schedule-routes.ts +0 -22
- package/src/runtime/routes/workflow-routes.test.ts +4 -43
- package/src/runtime/routes/workflow-routes.ts +0 -28
- package/src/runtime/routes/workspace-greetings.ts +55 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -11
- package/src/schedule/inference-profile.ts +2 -14
- package/src/subagent/manager.ts +6 -0
- package/src/subagent/types.ts +6 -0
- package/src/tools/AGENTS.md +3 -3
- package/src/tools/browser/browser-execution.ts +1 -1
- package/src/tools/network/web-search-error.ts +1 -1
- package/src/tools/permission-checker.ts +1 -1
- package/src/tools/schedule/create.ts +3 -9
- package/src/tools/schedule/update.ts +2 -10
- package/src/tools/side-effects.ts +2 -17
- package/src/tools/skills/execute.ts +34 -0
- package/src/tools/subagent/spawn.ts +34 -9
- package/src/tools/tool-approval-handler.ts +1 -3
- package/src/tools/tool-manifest.ts +0 -2
- package/src/tools/ui-surface/definitions.ts +39 -1
- package/src/tools/workflows/run-workflow.test.ts +8 -18
- package/src/util/platform.ts +2 -2
- package/src/workflows/capabilities.ts +2 -3
- package/src/workflows/run-manager.test.ts +0 -25
- package/src/workflows/run-manager.ts +2 -24
- package/src/__tests__/app-control-no-global-cgevent.test.ts +0 -98
- package/src/__tests__/credential-security-e2e.test.ts +0 -362
- package/src/__tests__/credential-vault-unit.test.ts +0 -1528
- package/src/__tests__/credential-vault.test.ts +0 -1706
- package/src/__tests__/identity-intro-cache.test.ts +0 -315
- package/src/__tests__/secret-onetime-send.test.ts +0 -182
- package/src/cli/commands/__tests__/task.test.ts +0 -914
- package/src/cli/commands/task.ts +0 -771
- package/src/config/bundled-skills/personal-page/SKILL.md +0 -57
- package/src/config/bundled-skills/personal-page/TOOLS.json +0 -27
- package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +0 -17
- package/src/config/preloaded-apps/personal-page/src/components/About.tsx +0 -22
- package/src/config/preloaded-apps/personal-page/src/components/App.tsx +0 -16
- package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +0 -77
- package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +0 -57
- package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +0 -28
- package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +0 -234
- package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +0 -48
- package/src/config/preloaded-apps/personal-page/src/components/media.ts +0 -16
- package/src/config/preloaded-apps/personal-page/src/index.html +0 -20
- package/src/config/preloaded-apps/personal-page/src/main.tsx +0 -7
- package/src/config/preloaded-apps/personal-page/src/profile-data.ts +0 -82
- package/src/config/preloaded-apps/personal-page/src/styles.css +0 -759
- package/src/memory/__tests__/preloaded-apps.test.ts +0 -85
- package/src/memory/preloaded-apps.ts +0 -116
- package/src/runtime/routes/identity-intro-cache.ts +0 -172
- package/src/tools/credentials/vault.ts +0 -712
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Unit tests for the identity intro cache (identity-intro-cache.ts).
|
|
3
|
-
*
|
|
4
|
-
* Validates TTL-based expiration, round-trip get/set behavior, and parsing
|
|
5
|
-
* workspace-authored greeting sections.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
9
|
-
|
|
10
|
-
// ---------------------------------------------------------------------------
|
|
11
|
-
// Mocks — must be defined before importing the module under test
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
// Suppress logger output
|
|
15
|
-
mock.module("../util/logger.js", () => ({
|
|
16
|
-
getLogger: () =>
|
|
17
|
-
new Proxy({} as Record<string, unknown>, {
|
|
18
|
-
get: () => () => {},
|
|
19
|
-
}),
|
|
20
|
-
}));
|
|
21
|
-
|
|
22
|
-
// In-memory checkpoint store
|
|
23
|
-
const checkpointStore = new Map<string, string>();
|
|
24
|
-
|
|
25
|
-
mock.module("../memory/checkpoints.js", () => ({
|
|
26
|
-
getMemoryCheckpoint: (key: string) => checkpointStore.get(key) ?? null,
|
|
27
|
-
setMemoryCheckpoint: (key: string, value: string) => {
|
|
28
|
-
checkpointStore.set(key, value);
|
|
29
|
-
},
|
|
30
|
-
}));
|
|
31
|
-
|
|
32
|
-
// Simulated workspace file contents
|
|
33
|
-
const workspaceFiles: Record<string, string> = {};
|
|
34
|
-
|
|
35
|
-
mock.module("node:fs", () => ({
|
|
36
|
-
existsSync: (path: string) => {
|
|
37
|
-
const name = path.split("/").pop() ?? "";
|
|
38
|
-
return name in workspaceFiles;
|
|
39
|
-
},
|
|
40
|
-
readFileSync: (path: string, _encoding: string) => {
|
|
41
|
-
const name = path.split("/").pop() ?? "";
|
|
42
|
-
if (name in workspaceFiles) return workspaceFiles[name];
|
|
43
|
-
throw new Error(`ENOENT: ${path}`);
|
|
44
|
-
},
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
// Imports (after mocks)
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
|
|
51
|
-
import {
|
|
52
|
-
getCachedIntro,
|
|
53
|
-
parseGreetingsSection,
|
|
54
|
-
readWorkspaceGreetings,
|
|
55
|
-
readWorkspaceIdentityIntro,
|
|
56
|
-
setCachedIntro,
|
|
57
|
-
} from "../runtime/routes/identity-intro-cache.js";
|
|
58
|
-
|
|
59
|
-
// ---------------------------------------------------------------------------
|
|
60
|
-
// Helpers
|
|
61
|
-
// ---------------------------------------------------------------------------
|
|
62
|
-
|
|
63
|
-
afterEach(() => {
|
|
64
|
-
checkpointStore.clear();
|
|
65
|
-
for (const key of Object.keys(workspaceFiles)) {
|
|
66
|
-
delete workspaceFiles[key];
|
|
67
|
-
}
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
// Tests
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
|
|
74
|
-
describe("identity intro cache", () => {
|
|
75
|
-
test("reads explicit intro from IDENTITY.md before SOUL.md", () => {
|
|
76
|
-
workspaceFiles["IDENTITY.md"] = [
|
|
77
|
-
"# Identity",
|
|
78
|
-
"",
|
|
79
|
-
"## Identity Intro",
|
|
80
|
-
"Nova here.",
|
|
81
|
-
].join("\n");
|
|
82
|
-
workspaceFiles["SOUL.md"] = [
|
|
83
|
-
"# Soul",
|
|
84
|
-
"",
|
|
85
|
-
"## Identity Intro",
|
|
86
|
-
"Soul fallback.",
|
|
87
|
-
].join("\n");
|
|
88
|
-
|
|
89
|
-
expect(readWorkspaceIdentityIntro()).toBe("Nova here.");
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test("falls back to SOUL.md identity intro for legacy workspaces", () => {
|
|
93
|
-
workspaceFiles["SOUL.md"] = [
|
|
94
|
-
"# Soul",
|
|
95
|
-
"",
|
|
96
|
-
"## Identity Intro",
|
|
97
|
-
"Soul fallback.",
|
|
98
|
-
].join("\n");
|
|
99
|
-
|
|
100
|
-
expect(readWorkspaceIdentityIntro()).toBe("Soul fallback.");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("returns null when cache is empty", () => {
|
|
104
|
-
expect(getCachedIntro()).toBeNull();
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
test("round-trip: set then get returns cached greetings array", () => {
|
|
108
|
-
workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
|
|
109
|
-
workspaceFiles["SOUL.md"] = "Be playful.";
|
|
110
|
-
|
|
111
|
-
setCachedIntro(["Hey, I'm Atlas.", "What's up?"]);
|
|
112
|
-
const cached = getCachedIntro();
|
|
113
|
-
expect(cached).not.toBeNull();
|
|
114
|
-
expect(cached!.greetings).toEqual(["Hey, I'm Atlas.", "What's up?"]);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("returns null when cache is expired (TTL exceeded)", () => {
|
|
118
|
-
workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
|
|
119
|
-
|
|
120
|
-
setCachedIntro(["Hello!"]);
|
|
121
|
-
|
|
122
|
-
const fourHoursAndOneMinuteAgo = String(
|
|
123
|
-
Date.now() - (4 * 60 + 1) * 60 * 1000,
|
|
124
|
-
);
|
|
125
|
-
checkpointStore.set("identity:intro:cached_at", fourHoursAndOneMinuteAgo);
|
|
126
|
-
|
|
127
|
-
expect(getCachedIntro()).toBeNull();
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
test("returns cached greetings when within TTL (3 hours 59 minutes ago)", () => {
|
|
131
|
-
workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
|
|
132
|
-
|
|
133
|
-
setCachedIntro(["Hello!"]);
|
|
134
|
-
|
|
135
|
-
const threeHoursAndFiftyNineMinutesAgo = String(
|
|
136
|
-
Date.now() - (3 * 60 + 59) * 60 * 1000,
|
|
137
|
-
);
|
|
138
|
-
checkpointStore.set(
|
|
139
|
-
"identity:intro:cached_at",
|
|
140
|
-
threeHoursAndFiftyNineMinutesAgo,
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
const cached = getCachedIntro();
|
|
144
|
-
expect(cached).not.toBeNull();
|
|
145
|
-
expect(cached!.greetings).toEqual(["Hello!"]);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test("keeps cached greetings when IDENTITY.md changes", () => {
|
|
149
|
-
workspaceFiles["IDENTITY.md"] = "- **Name:** Atlas";
|
|
150
|
-
setCachedIntro(["I'm Atlas!"]);
|
|
151
|
-
|
|
152
|
-
workspaceFiles["IDENTITY.md"] = "- **Name:** Nova";
|
|
153
|
-
|
|
154
|
-
expect(getCachedIntro()?.greetings).toEqual(["I'm Atlas!"]);
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
test("keeps cached greetings when SOUL.md changes", () => {
|
|
158
|
-
workspaceFiles["SOUL.md"] = "Be playful.";
|
|
159
|
-
setCachedIntro(["Hey there!"]);
|
|
160
|
-
|
|
161
|
-
workspaceFiles["SOUL.md"] = "Be serious and formal.";
|
|
162
|
-
|
|
163
|
-
expect(getCachedIntro()?.greetings).toEqual(["Hey there!"]);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test("handles missing workspace files gracefully", () => {
|
|
167
|
-
// No files exist — should still work.
|
|
168
|
-
setCachedIntro(["Hello!"]);
|
|
169
|
-
const cached = getCachedIntro();
|
|
170
|
-
expect(cached).not.toBeNull();
|
|
171
|
-
expect(cached!.greetings).toEqual(["Hello!"]);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test("handles legacy single-string cache value", () => {
|
|
175
|
-
// Simulate a cache entry written by an older daemon version
|
|
176
|
-
checkpointStore.set("identity:intro:greetings", "Legacy greeting");
|
|
177
|
-
checkpointStore.set("identity:intro:cached_at", String(Date.now()));
|
|
178
|
-
|
|
179
|
-
const cached = getCachedIntro();
|
|
180
|
-
expect(cached).not.toBeNull();
|
|
181
|
-
expect(cached!.greetings).toEqual(["Legacy greeting"]);
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
test("returns null when greetings checkpoint is missing", () => {
|
|
185
|
-
checkpointStore.set("identity:intro:cached_at", String(Date.now()));
|
|
186
|
-
expect(getCachedIntro()).toBeNull();
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test("returns null when timestamp checkpoint is missing", () => {
|
|
190
|
-
checkpointStore.set("identity:intro:greetings", '["Hello"]');
|
|
191
|
-
expect(getCachedIntro()).toBeNull();
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
describe("parseGreetingsSection", () => {
|
|
196
|
-
test("parses bullet list from ## Greetings section", () => {
|
|
197
|
-
const content = [
|
|
198
|
-
"# Soul",
|
|
199
|
-
"",
|
|
200
|
-
"## Greetings",
|
|
201
|
-
"- Hey there, friend!",
|
|
202
|
-
"- What's on your mind?",
|
|
203
|
-
"- Ready to roll!",
|
|
204
|
-
"",
|
|
205
|
-
"## Other Section",
|
|
206
|
-
"Some other content.",
|
|
207
|
-
].join("\n");
|
|
208
|
-
|
|
209
|
-
expect(parseGreetingsSection(content)).toEqual([
|
|
210
|
-
"Hey there, friend!",
|
|
211
|
-
"What's on your mind?",
|
|
212
|
-
"Ready to roll!",
|
|
213
|
-
]);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test("handles asterisk bullets", () => {
|
|
217
|
-
const content = ["## Greetings", "* Hello!", "* Hi there."].join("\n");
|
|
218
|
-
|
|
219
|
-
expect(parseGreetingsSection(content)).toEqual(["Hello!", "Hi there."]);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
test("handles plus and numbered bullets", () => {
|
|
223
|
-
const content = [
|
|
224
|
-
"## Greetings",
|
|
225
|
-
"+ Welcome back.",
|
|
226
|
-
"1. Ready when you are.",
|
|
227
|
-
"2) What are we building today?",
|
|
228
|
-
].join("\n");
|
|
229
|
-
|
|
230
|
-
expect(parseGreetingsSection(content)).toEqual([
|
|
231
|
-
"Welcome back.",
|
|
232
|
-
"Ready when you are.",
|
|
233
|
-
"What are we building today?",
|
|
234
|
-
]);
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
test("only starts at a level-two Greetings heading", () => {
|
|
238
|
-
const content = [
|
|
239
|
-
"# Greetings",
|
|
240
|
-
"- Not the section contract.",
|
|
241
|
-
"",
|
|
242
|
-
"## Greetings",
|
|
243
|
-
"- The real greeting.",
|
|
244
|
-
].join("\n");
|
|
245
|
-
|
|
246
|
-
expect(parseGreetingsSection(content)).toEqual(["The real greeting."]);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
test("returns null when section is missing", () => {
|
|
250
|
-
const content = ["# Soul", "## Personality", "Be friendly."].join("\n");
|
|
251
|
-
|
|
252
|
-
expect(parseGreetingsSection(content)).toBeNull();
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
test("returns null when section is empty", () => {
|
|
256
|
-
const content = ["## Greetings", "", "## Next Section"].join("\n");
|
|
257
|
-
|
|
258
|
-
expect(parseGreetingsSection(content)).toBeNull();
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test("ignores non-bullet lines in section", () => {
|
|
262
|
-
const content = [
|
|
263
|
-
"## Greetings",
|
|
264
|
-
"Some intro text that's not a bullet",
|
|
265
|
-
"- Actual greeting",
|
|
266
|
-
"Another non-bullet line",
|
|
267
|
-
"- Second greeting",
|
|
268
|
-
].join("\n");
|
|
269
|
-
|
|
270
|
-
expect(parseGreetingsSection(content)).toEqual([
|
|
271
|
-
"Actual greeting",
|
|
272
|
-
"Second greeting",
|
|
273
|
-
]);
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
test("allows nested headings and stops at the next same-or-higher heading", () => {
|
|
277
|
-
const content = [
|
|
278
|
-
"## Greetings",
|
|
279
|
-
"- First",
|
|
280
|
-
"- Second",
|
|
281
|
-
"### Sub-heading",
|
|
282
|
-
"- Third",
|
|
283
|
-
"## Other Section",
|
|
284
|
-
"- Should not appear",
|
|
285
|
-
].join("\n");
|
|
286
|
-
|
|
287
|
-
expect(parseGreetingsSection(content)).toEqual([
|
|
288
|
-
"First",
|
|
289
|
-
"Second",
|
|
290
|
-
"Third",
|
|
291
|
-
]);
|
|
292
|
-
});
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
describe("readWorkspaceGreetings", () => {
|
|
296
|
-
test("reads greetings from SOUL.md", () => {
|
|
297
|
-
workspaceFiles["SOUL.md"] = [
|
|
298
|
-
"# Soul",
|
|
299
|
-
"## Greetings",
|
|
300
|
-
"- Hey!",
|
|
301
|
-
"- What's up?",
|
|
302
|
-
].join("\n");
|
|
303
|
-
|
|
304
|
-
expect(readWorkspaceGreetings()).toEqual(["Hey!", "What's up?"]);
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
test("returns null when SOUL.md has no greetings section", () => {
|
|
308
|
-
workspaceFiles["SOUL.md"] = "# Soul\nBe friendly.";
|
|
309
|
-
expect(readWorkspaceGreetings()).toBeNull();
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
test("returns null when SOUL.md does not exist", () => {
|
|
313
|
-
expect(readWorkspaceGreetings()).toBeNull();
|
|
314
|
-
});
|
|
315
|
-
});
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// Mocks — declared before importing modules under test
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
const mockConfig = {
|
|
8
|
-
secretDetection: {
|
|
9
|
-
enabled: true,
|
|
10
|
-
allowOneTimeSend: false,
|
|
11
|
-
},
|
|
12
|
-
timeouts: { permissionTimeoutSec: 300 },
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
function setMockNestedValue(
|
|
16
|
-
obj: Record<string, unknown>,
|
|
17
|
-
path: string,
|
|
18
|
-
value: unknown,
|
|
19
|
-
): void {
|
|
20
|
-
const keys = path.split(".");
|
|
21
|
-
let current = obj;
|
|
22
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
23
|
-
const key = keys[i];
|
|
24
|
-
if (current[key] == null || typeof current[key] !== "object") {
|
|
25
|
-
current[key] = {};
|
|
26
|
-
}
|
|
27
|
-
current = current[key] as Record<string, unknown>;
|
|
28
|
-
}
|
|
29
|
-
current[keys[keys.length - 1]] = value;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
mock.module("../config/loader.js", () => ({
|
|
33
|
-
getConfig: () => mockConfig,
|
|
34
|
-
loadConfig: () => mockConfig,
|
|
35
|
-
loadRawConfig: () => ({}),
|
|
36
|
-
saveRawConfig: () => {},
|
|
37
|
-
setNestedValue: setMockNestedValue,
|
|
38
|
-
invalidateConfigCache: () => {},
|
|
39
|
-
}));
|
|
40
|
-
|
|
41
|
-
mock.module("../util/logger.js", () => ({
|
|
42
|
-
getLogger: () =>
|
|
43
|
-
new Proxy({} as Record<string, unknown>, {
|
|
44
|
-
get: () => () => {},
|
|
45
|
-
}),
|
|
46
|
-
}));
|
|
47
|
-
|
|
48
|
-
// Track credential store writes
|
|
49
|
-
const storedKeys = new Map<string, string>();
|
|
50
|
-
mock.module("../security/secure-keys.js", () => {
|
|
51
|
-
const syncSet = (key: string, value: string) => {
|
|
52
|
-
storedKeys.set(key, value);
|
|
53
|
-
return true;
|
|
54
|
-
};
|
|
55
|
-
const syncDelete = (key: string) => {
|
|
56
|
-
if (storedKeys.has(key)) {
|
|
57
|
-
storedKeys.delete(key);
|
|
58
|
-
return "deleted" as const;
|
|
59
|
-
}
|
|
60
|
-
return "not-found" as const;
|
|
61
|
-
};
|
|
62
|
-
return {
|
|
63
|
-
getSecureKeyAsync: async (key: string) => storedKeys.get(key) ?? undefined,
|
|
64
|
-
getSecureKeyResultAsync: async (account: string) => ({
|
|
65
|
-
value: storedKeys.get(account),
|
|
66
|
-
unreachable: false,
|
|
67
|
-
}),
|
|
68
|
-
setSecureKeyAsync: async (key: string, value: string) =>
|
|
69
|
-
syncSet(key, value),
|
|
70
|
-
deleteSecureKeyAsync: async (key: string) => syncDelete(key),
|
|
71
|
-
listSecureKeysAsync: async () => ({ accounts: [], unreachable: false }),
|
|
72
|
-
getProviderKeyAsync: async () => undefined,
|
|
73
|
-
getMaskedProviderKey: async () => null,
|
|
74
|
-
};
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
mock.module("./metadata-store.js", () => ({
|
|
78
|
-
upsertCredentialMetadata: () => {},
|
|
79
|
-
deleteCredentialMetadata: () => {},
|
|
80
|
-
getCredentialMetadata: () => null,
|
|
81
|
-
}));
|
|
82
|
-
|
|
83
|
-
mock.module("./policy-validate.js", () => ({
|
|
84
|
-
validatePolicyInput: () => ({ valid: true, errors: [] }),
|
|
85
|
-
toPolicyFromInput: () => ({
|
|
86
|
-
allowedTools: [],
|
|
87
|
-
allowedDomains: [],
|
|
88
|
-
usageDescription: undefined,
|
|
89
|
-
}),
|
|
90
|
-
}));
|
|
91
|
-
|
|
92
|
-
import { credentialKey } from "../security/credential-key.js";
|
|
93
|
-
|
|
94
|
-
const { credentialStoreTool } = await import("../tools/credentials/vault.js");
|
|
95
|
-
|
|
96
|
-
describe("one-time send override", () => {
|
|
97
|
-
beforeEach(() => {
|
|
98
|
-
storedKeys.clear();
|
|
99
|
-
mockConfig.secretDetection.allowOneTimeSend = false;
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
test("transient_send is rejected when allowOneTimeSend is disabled", async () => {
|
|
103
|
-
const context = {
|
|
104
|
-
workingDir: "/tmp",
|
|
105
|
-
conversationId: "c1",
|
|
106
|
-
trustClass: "guardian" as const,
|
|
107
|
-
requestSecret: async () => ({
|
|
108
|
-
value: "v1",
|
|
109
|
-
delivery: "transient_send" as const,
|
|
110
|
-
}),
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const result = await credentialStoreTool.execute(
|
|
114
|
-
{ action: "prompt", service: "svc", field: "key", label: "Key" },
|
|
115
|
-
context,
|
|
116
|
-
);
|
|
117
|
-
expect(result.isError).toBe(true);
|
|
118
|
-
expect(result.content).toContain("not enabled");
|
|
119
|
-
// Value must NOT be stored in credential store
|
|
120
|
-
expect(storedKeys.has(credentialKey("svc", "key"))).toBe(false);
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("transient_send succeeds when allowOneTimeSend is enabled", async () => {
|
|
124
|
-
mockConfig.secretDetection.allowOneTimeSend = true;
|
|
125
|
-
const context = {
|
|
126
|
-
workingDir: "/tmp",
|
|
127
|
-
conversationId: "c1",
|
|
128
|
-
trustClass: "guardian" as const,
|
|
129
|
-
requestSecret: async () => ({
|
|
130
|
-
value: "v1",
|
|
131
|
-
delivery: "transient_send" as const,
|
|
132
|
-
}),
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const result = await credentialStoreTool.execute(
|
|
136
|
-
{ action: "prompt", service: "svc", field: "key", label: "Key" },
|
|
137
|
-
context,
|
|
138
|
-
);
|
|
139
|
-
expect(result.isError).toBe(false);
|
|
140
|
-
expect(result.content).toContain("NOT saved");
|
|
141
|
-
// Value must NOT be stored in credential store
|
|
142
|
-
expect(storedKeys.has(credentialKey("svc", "key"))).toBe(false);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test("store delivery always persists to credential store regardless of allowOneTimeSend", async () => {
|
|
146
|
-
mockConfig.secretDetection.allowOneTimeSend = true;
|
|
147
|
-
const context = {
|
|
148
|
-
workingDir: "/tmp",
|
|
149
|
-
conversationId: "c1",
|
|
150
|
-
trustClass: "guardian" as const,
|
|
151
|
-
requestSecret: async () => ({ value: "v1", delivery: "store" as const }),
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const result = await credentialStoreTool.execute(
|
|
155
|
-
{ action: "prompt", service: "svc", field: "key", label: "Key" },
|
|
156
|
-
context,
|
|
157
|
-
);
|
|
158
|
-
expect(result.isError).toBe(false);
|
|
159
|
-
expect(result.content).toContain("stored");
|
|
160
|
-
expect(storedKeys.has(credentialKey("svc", "key"))).toBe(true);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
test("transient_send response content never contains the secret value", async () => {
|
|
164
|
-
mockConfig.secretDetection.allowOneTimeSend = true;
|
|
165
|
-
const secretVal = ["nv", "sh", "1"].join("");
|
|
166
|
-
const context = {
|
|
167
|
-
workingDir: "/tmp",
|
|
168
|
-
conversationId: "c1",
|
|
169
|
-
trustClass: "guardian" as const,
|
|
170
|
-
requestSecret: async () => ({
|
|
171
|
-
value: secretVal,
|
|
172
|
-
delivery: "transient_send" as const,
|
|
173
|
-
}),
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const result = await credentialStoreTool.execute(
|
|
177
|
-
{ action: "prompt", service: "svc", field: "key", label: "Key" },
|
|
178
|
-
context,
|
|
179
|
-
);
|
|
180
|
-
expect(result.content).not.toContain(secretVal);
|
|
181
|
-
});
|
|
182
|
-
});
|