@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
|
@@ -180,7 +180,7 @@ describe("redeemVoiceInviteCode", () => {
|
|
|
180
180
|
|
|
181
181
|
const channelResult = findContactChannel({
|
|
182
182
|
channelType: "phone",
|
|
183
|
-
|
|
183
|
+
address: phone,
|
|
184
184
|
});
|
|
185
185
|
|
|
186
186
|
expect(channelResult).not.toBeNull();
|
|
@@ -399,7 +399,7 @@ describe("redeemVoiceInviteCode", () => {
|
|
|
399
399
|
// Verify the redeemer's phone is now bound to Mom's contact
|
|
400
400
|
const contactResult = findContactChannel({
|
|
401
401
|
channelType: "phone",
|
|
402
|
-
|
|
402
|
+
address: phone,
|
|
403
403
|
});
|
|
404
404
|
expect(contactResult).not.toBeNull();
|
|
405
405
|
expect(contactResult!.contact.id).toBe(momContact.id);
|
|
@@ -8,20 +8,15 @@ import {
|
|
|
8
8
|
} from "../providers/search-provider-catalog.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* Parity guard: daemon TS catalog vs the
|
|
11
|
+
* Parity guard: daemon TS catalog vs the generated JSON copy.
|
|
12
12
|
*
|
|
13
13
|
* The daemon maintains its canonical search-provider catalog in
|
|
14
14
|
* `assistant/src/providers/search-provider-catalog.ts`.
|
|
15
|
-
* `assistant/scripts/sync-web-search-catalog.ts` writes
|
|
16
|
-
*
|
|
17
|
-
* - `meta/web-search-provider-catalog.json` — primary cross-package artifact.
|
|
18
|
-
* - `clients/shared/Resources/web-search-provider-catalog.json` — SwiftPM
|
|
19
|
-
* resource bundled into `VellumAssistantShared` (Swift cannot reach
|
|
20
|
-
* files outside a target's source directory).
|
|
15
|
+
* `assistant/scripts/sync-web-search-catalog.ts` writes
|
|
16
|
+
* `meta/web-search-provider-catalog.json` — the primary cross-package artifact.
|
|
21
17
|
*
|
|
22
18
|
* These tests enforce structural equality between the TS catalog and the
|
|
23
|
-
* meta/ copy
|
|
24
|
-
* mirror. CI fails when any of the three drift.
|
|
19
|
+
* meta/ copy. CI fails when they drift.
|
|
25
20
|
*/
|
|
26
21
|
|
|
27
22
|
interface JsonCatalogEntry {
|
|
@@ -46,11 +41,6 @@ const META_JSON_PATH = join(
|
|
|
46
41
|
"..",
|
|
47
42
|
"meta/web-search-provider-catalog.json",
|
|
48
43
|
);
|
|
49
|
-
const SWIFTPM_MIRROR_PATH = join(
|
|
50
|
-
process.cwd(),
|
|
51
|
-
"..",
|
|
52
|
-
"clients/shared/Resources/web-search-provider-catalog.json",
|
|
53
|
-
);
|
|
54
44
|
|
|
55
45
|
function loadJsonCatalog(): JsonCatalog {
|
|
56
46
|
return JSON.parse(readFileSync(META_JSON_PATH, "utf-8"));
|
|
@@ -73,7 +63,8 @@ function entryToPlain(
|
|
|
73
63
|
if (entry.apiKeyPrefix !== undefined) out.apiKeyPrefix = entry.apiKeyPrefix;
|
|
74
64
|
if (entry.envVar !== undefined) out.envVar = entry.envVar;
|
|
75
65
|
if (entry.secretKey !== undefined) out.secretKey = entry.secretKey;
|
|
76
|
-
if (entry.fallbackOrder !== undefined)
|
|
66
|
+
if (entry.fallbackOrder !== undefined)
|
|
67
|
+
out.fallbackOrder = entry.fallbackOrder;
|
|
77
68
|
if (entry.privacyPolicyUrl !== undefined) {
|
|
78
69
|
out.privacyPolicyUrl = entry.privacyPolicyUrl;
|
|
79
70
|
}
|
|
@@ -95,14 +86,4 @@ describe("web-search catalog parity (TS canonical vs meta/ JSON)", () => {
|
|
|
95
86
|
SEARCH_PROVIDER_CATALOG.map((p) => p.id),
|
|
96
87
|
);
|
|
97
88
|
});
|
|
98
|
-
|
|
99
|
-
test("SwiftPM mirror is byte-identical to meta/ copy", () => {
|
|
100
|
-
// The generator writes both files from the same serializer; this guard
|
|
101
|
-
// catches any case where one copy is regenerated without the other.
|
|
102
|
-
// Byte equality is required because SwiftPM bundles the resource verbatim
|
|
103
|
-
// and the meta/ JSON is consumed as a cross-package artifact.
|
|
104
|
-
const metaBytes = readFileSync(META_JSON_PATH);
|
|
105
|
-
const swiftPmBytes = readFileSync(SWIFTPM_MIRROR_PATH);
|
|
106
|
-
expect(swiftPmBytes.equals(metaBytes)).toBe(true);
|
|
107
|
-
});
|
|
108
89
|
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for workspace-authored greeting parsing.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
6
|
+
|
|
7
|
+
const workspaceFiles: Record<string, string> = {};
|
|
8
|
+
|
|
9
|
+
mock.module("node:fs", () => ({
|
|
10
|
+
existsSync: (path: string) => {
|
|
11
|
+
const name = path.split("/").pop() ?? "";
|
|
12
|
+
return name in workspaceFiles;
|
|
13
|
+
},
|
|
14
|
+
readFileSync: (path: string, _encoding: string) => {
|
|
15
|
+
const name = path.split("/").pop() ?? "";
|
|
16
|
+
if (name in workspaceFiles) return workspaceFiles[name];
|
|
17
|
+
throw new Error(`ENOENT: ${path}`);
|
|
18
|
+
},
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
parseGreetingsSection,
|
|
23
|
+
readWorkspaceGreetings,
|
|
24
|
+
} from "../runtime/routes/workspace-greetings.js";
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
for (const key of Object.keys(workspaceFiles)) {
|
|
28
|
+
delete workspaceFiles[key];
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("parseGreetingsSection", () => {
|
|
33
|
+
test("parses bullet list from ## Greetings section", () => {
|
|
34
|
+
const content = [
|
|
35
|
+
"# Soul",
|
|
36
|
+
"",
|
|
37
|
+
"## Greetings",
|
|
38
|
+
"- Hey there, friend!",
|
|
39
|
+
"- What's on your mind?",
|
|
40
|
+
"- Ready to roll!",
|
|
41
|
+
"",
|
|
42
|
+
"## Other Section",
|
|
43
|
+
"Some other content.",
|
|
44
|
+
].join("\n");
|
|
45
|
+
|
|
46
|
+
expect(parseGreetingsSection(content)).toEqual([
|
|
47
|
+
"Hey there, friend!",
|
|
48
|
+
"What's on your mind?",
|
|
49
|
+
"Ready to roll!",
|
|
50
|
+
]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("handles asterisk bullets", () => {
|
|
54
|
+
const content = ["## Greetings", "* Hello!", "* Hi there."].join("\n");
|
|
55
|
+
|
|
56
|
+
expect(parseGreetingsSection(content)).toEqual(["Hello!", "Hi there."]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("handles plus and numbered bullets", () => {
|
|
60
|
+
const content = [
|
|
61
|
+
"## Greetings",
|
|
62
|
+
"+ Welcome back.",
|
|
63
|
+
"1. Ready when you are.",
|
|
64
|
+
"2) What are we building today?",
|
|
65
|
+
].join("\n");
|
|
66
|
+
|
|
67
|
+
expect(parseGreetingsSection(content)).toEqual([
|
|
68
|
+
"Welcome back.",
|
|
69
|
+
"Ready when you are.",
|
|
70
|
+
"What are we building today?",
|
|
71
|
+
]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("only starts at a level-two Greetings heading", () => {
|
|
75
|
+
const content = [
|
|
76
|
+
"# Greetings",
|
|
77
|
+
"- Not the section contract.",
|
|
78
|
+
"",
|
|
79
|
+
"## Greetings",
|
|
80
|
+
"- The real greeting.",
|
|
81
|
+
].join("\n");
|
|
82
|
+
|
|
83
|
+
expect(parseGreetingsSection(content)).toEqual(["The real greeting."]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("returns null when section is missing", () => {
|
|
87
|
+
const content = ["# Soul", "## Personality", "Be friendly."].join("\n");
|
|
88
|
+
|
|
89
|
+
expect(parseGreetingsSection(content)).toBeNull();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("returns null when section is empty", () => {
|
|
93
|
+
const content = ["## Greetings", "", "## Next Section"].join("\n");
|
|
94
|
+
|
|
95
|
+
expect(parseGreetingsSection(content)).toBeNull();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("ignores non-bullet lines in section", () => {
|
|
99
|
+
const content = [
|
|
100
|
+
"## Greetings",
|
|
101
|
+
"Some intro text that's not a bullet",
|
|
102
|
+
"- Actual greeting",
|
|
103
|
+
"Another non-bullet line",
|
|
104
|
+
"- Second greeting",
|
|
105
|
+
].join("\n");
|
|
106
|
+
|
|
107
|
+
expect(parseGreetingsSection(content)).toEqual([
|
|
108
|
+
"Actual greeting",
|
|
109
|
+
"Second greeting",
|
|
110
|
+
]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("allows nested headings and stops at the next same-or-higher heading", () => {
|
|
114
|
+
const content = [
|
|
115
|
+
"## Greetings",
|
|
116
|
+
"- First",
|
|
117
|
+
"- Second",
|
|
118
|
+
"### Sub-heading",
|
|
119
|
+
"- Third",
|
|
120
|
+
"## Other Section",
|
|
121
|
+
"- Should not appear",
|
|
122
|
+
].join("\n");
|
|
123
|
+
|
|
124
|
+
expect(parseGreetingsSection(content)).toEqual([
|
|
125
|
+
"First",
|
|
126
|
+
"Second",
|
|
127
|
+
"Third",
|
|
128
|
+
]);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("readWorkspaceGreetings", () => {
|
|
133
|
+
test("reads greetings from SOUL.md", () => {
|
|
134
|
+
workspaceFiles["SOUL.md"] = [
|
|
135
|
+
"# Soul",
|
|
136
|
+
"## Greetings",
|
|
137
|
+
"- Hey!",
|
|
138
|
+
"- What's up?",
|
|
139
|
+
].join("\n");
|
|
140
|
+
|
|
141
|
+
expect(readWorkspaceGreetings()).toEqual(["Hey!", "What's up?"]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("returns null when SOUL.md has no greetings section", () => {
|
|
145
|
+
workspaceFiles["SOUL.md"] = "# Soul\nBe friendly.";
|
|
146
|
+
expect(readWorkspaceGreetings()).toBeNull();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("returns null when SOUL.md does not exist", () => {
|
|
150
|
+
expect(readWorkspaceGreetings()).toBeNull();
|
|
151
|
+
});
|
|
152
|
+
});
|
package/src/agent/loop.ts
CHANGED
|
@@ -761,8 +761,10 @@ export class AgentLoop {
|
|
|
761
761
|
/**
|
|
762
762
|
* Compact the running history in place when the budget gate trips.
|
|
763
763
|
*
|
|
764
|
-
* Calls the default compaction plugin
|
|
765
|
-
*
|
|
764
|
+
* Calls the default compaction plugin, then re-applies injections via the
|
|
765
|
+
* supplied hooks. The budget path summarizes the injected `history` (so the
|
|
766
|
+
* summary call reuses the agent's warm prefix cache); overflow recovery runs
|
|
767
|
+
* on the injection-stripped history. When `overflowSignal` is
|
|
766
768
|
* supplied the plugin routes through the manager's reduction ladder (which
|
|
767
769
|
* advances one rung per call and reports `exhausted` / `autoCompressApplied`
|
|
768
770
|
* / `injectionMode`); otherwise it runs ordinary forced compaction. Returns
|
|
@@ -795,8 +797,11 @@ export class AgentLoop {
|
|
|
795
797
|
startedAt,
|
|
796
798
|
messages: history,
|
|
797
799
|
});
|
|
798
|
-
//
|
|
799
|
-
//
|
|
800
|
+
// Injection-stripped history for overflow recovery's reduction ladder and
|
|
801
|
+
// the post-compaction re-injection base below. The budget-path summarizer
|
|
802
|
+
// instead runs on the injected `history` so its prompt prefix matches the
|
|
803
|
+
// agent's warm prefix cache and the summary call is a cache read, not a
|
|
804
|
+
// fresh cache write.
|
|
800
805
|
const rawHistory = stripInjectionsForCompaction(history);
|
|
801
806
|
// Record the history-stripped marker right after stripping, before the
|
|
802
807
|
// pipeline runs.
|
|
@@ -812,7 +817,7 @@ export class AgentLoop {
|
|
|
812
817
|
// routes the request through the reduction ladder when present.
|
|
813
818
|
const compactResult = await defaultCompact({
|
|
814
819
|
conversationId: this.conversationId,
|
|
815
|
-
messages: rawHistory,
|
|
820
|
+
messages: overflowSignal != null ? rawHistory : history,
|
|
816
821
|
signal,
|
|
817
822
|
force: true,
|
|
818
823
|
actorTrustClass: trust.trustClass,
|
|
@@ -1434,6 +1439,7 @@ export class AgentLoop {
|
|
|
1434
1439
|
conversationId: this.conversationId,
|
|
1435
1440
|
callSite: callSite ?? null,
|
|
1436
1441
|
systemPrompt: providerOptions.systemPrompt ?? null,
|
|
1442
|
+
modelProfile: effectiveOverrideProfile ?? null,
|
|
1437
1443
|
deferAssistantOutput: false,
|
|
1438
1444
|
logger: rlog,
|
|
1439
1445
|
};
|
|
@@ -1443,6 +1449,20 @@ export class AgentLoop {
|
|
|
1443
1449
|
);
|
|
1444
1450
|
providerOptions.systemPrompt =
|
|
1445
1451
|
finalPreModelCtx.systemPrompt ?? undefined;
|
|
1452
|
+
// Route this call to the hook's chosen inference profile. The
|
|
1453
|
+
// resolver layers `llm.profiles[overrideProfile]` at the top of
|
|
1454
|
+
// precedence for the user-facing call, so a model router can pick
|
|
1455
|
+
// the profile per message; clearing it drops any seeded override.
|
|
1456
|
+
const hookModelProfile = finalPreModelCtx.modelProfile?.trim();
|
|
1457
|
+
if (hookModelProfile) {
|
|
1458
|
+
providerConfig.overrideProfile = hookModelProfile;
|
|
1459
|
+
if (forceOverrideProfile) {
|
|
1460
|
+
providerConfig.forceOverrideProfile = true;
|
|
1461
|
+
}
|
|
1462
|
+
} else {
|
|
1463
|
+
delete providerConfig.overrideProfile;
|
|
1464
|
+
delete providerConfig.forceOverrideProfile;
|
|
1465
|
+
}
|
|
1446
1466
|
// The hook owns the policy (it sees `callSite`/conversation and
|
|
1447
1467
|
// self-gates); the loop honors whatever it decides.
|
|
1448
1468
|
deferAssistantOutput = finalPreModelCtx.deferAssistantOutput;
|
package/src/api/README.md
CHANGED
|
@@ -7,7 +7,7 @@ the wire contracts the assistant exposes: schemas, types, and pure helpers.
|
|
|
7
7
|
Internal assistant code imports the files in this directory via relative paths
|
|
8
8
|
(e.g. `../../api/events/open-url.js`). External consumers import the
|
|
9
9
|
materialized npm-style package `@vellumai/assistant-api`, regenerated into
|
|
10
|
-
`
|
|
10
|
+
`clients/web/node_modules/` by `clients/web/scripts/postinstall.ts`.
|
|
11
11
|
|
|
12
12
|
## Architecture
|
|
13
13
|
|
|
@@ -72,7 +72,7 @@ a migrated event is the import and the union membership.
|
|
|
72
72
|
|
|
73
73
|
### 3. Cut over web consumers
|
|
74
74
|
|
|
75
|
-
`
|
|
75
|
+
`clients/web/src/domains/chat/api/event-types.ts` no longer needs to list the
|
|
76
76
|
migrated event in its `AssistantEvent` union — `APIAssistantEvent` covers it.
|
|
77
77
|
Drop the per-event member, leaving the union to peel off legacy entries one
|
|
78
78
|
at a time as each event migrates.
|
|
@@ -85,7 +85,7 @@ import them straight from the canonical package.
|
|
|
85
85
|
### 4. Delete the legacy parser cases
|
|
86
86
|
|
|
87
87
|
Remove the matching `case "my_event":` blocks from
|
|
88
|
-
`
|
|
88
|
+
`clients/web/src/domains/chat/api/event-parser.ts`. Any per-event helper
|
|
89
89
|
(`parseFooBase`, etc.) goes with them.
|
|
90
90
|
|
|
91
91
|
### 5. Tests
|
|
@@ -110,15 +110,15 @@ Handler-level tests in the consuming domain modules typically need no change
|
|
|
110
110
|
Run before push, in order:
|
|
111
111
|
|
|
112
112
|
```bash
|
|
113
|
-
# In
|
|
113
|
+
# In clients/web — regenerate the @vellumai/assistant-api bundle
|
|
114
114
|
bun run scripts/postinstall.ts
|
|
115
115
|
|
|
116
116
|
# Type-check both packages
|
|
117
117
|
( cd assistant && bunx tsc --noEmit )
|
|
118
|
-
( cd
|
|
118
|
+
( cd clients/web && bunx tsc --noEmit )
|
|
119
119
|
|
|
120
120
|
# Targeted tests
|
|
121
|
-
( cd
|
|
121
|
+
( cd clients/web && bun test src/domains/chat/api/event-parser.test.ts )
|
|
122
122
|
|
|
123
123
|
# Lint + format the touched files
|
|
124
124
|
bunx eslint <files>
|
|
@@ -390,10 +390,8 @@ export const ConversationMessageSchema = z.object({
|
|
|
390
390
|
/**
|
|
391
391
|
* @deprecated Superseded by `contentBlocks`. Flat plain-text body (joined
|
|
392
392
|
* text segments). Redundant with `textSegments`/`contentOrder` for clients
|
|
393
|
-
* that render from the positional arrays (web, CLI)
|
|
394
|
-
*
|
|
395
|
-
* (its `HistoryReconstructionService` skips rows with empty text). The
|
|
396
|
-
* serializer always emits it — do not remove without updating that client.
|
|
393
|
+
* that render from the positional arrays (web, CLI). The serializer always
|
|
394
|
+
* emits it — do not remove without auditing clients that read it directly.
|
|
397
395
|
*/
|
|
398
396
|
content: z
|
|
399
397
|
.string()
|
|
@@ -12,10 +12,6 @@
|
|
|
12
12
|
* generator, and the web/CLI clients all derive from one source and cannot
|
|
13
13
|
* drift.
|
|
14
14
|
*
|
|
15
|
-
* A structurally compatible Swift mirror lives at
|
|
16
|
-
* `clients/shared/Network/FeedItem.swift`. Any change to the feed-item
|
|
17
|
-
* shape must be mirrored there.
|
|
18
|
-
*
|
|
19
15
|
* Canonical wire-contract source. Assistant code imports the types
|
|
20
16
|
* directly from this file via relative paths; external consumers
|
|
21
17
|
* (web client, gateway, evals) import via `@vellumai/assistant-api`.
|
|
@@ -373,7 +373,7 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
373
373
|
const requesterContactResult = requesterExternalUserId
|
|
374
374
|
? findContactChannel({
|
|
375
375
|
channelType: channel,
|
|
376
|
-
|
|
376
|
+
address: requesterExternalUserId,
|
|
377
377
|
})
|
|
378
378
|
: null;
|
|
379
379
|
const requesterDisplayName =
|
|
@@ -382,7 +382,7 @@ const accessRequestResolver: GuardianRequestResolver = {
|
|
|
382
382
|
const decidedByContactResult = decidedByExternalUserId
|
|
383
383
|
? findContactChannel({
|
|
384
384
|
channelType: channel,
|
|
385
|
-
|
|
385
|
+
address: decidedByExternalUserId,
|
|
386
386
|
})
|
|
387
387
|
: null;
|
|
388
388
|
const decidedByDisplayName =
|
|
@@ -553,8 +553,8 @@ export async function startVoiceTurn(
|
|
|
553
553
|
} else if (msg.type === "secret_request") {
|
|
554
554
|
if (usesLocalInteractiveApprovals) {
|
|
555
555
|
// Local live voice runs alongside the desktop client, which has a
|
|
556
|
-
// secret-entry UI
|
|
557
|
-
//
|
|
556
|
+
// secret-entry UI. Forward the broadcast and let the prompter's
|
|
557
|
+
// existing registration handle the response.
|
|
558
558
|
broadcastMessage(msg);
|
|
559
559
|
return;
|
|
560
560
|
}
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
import type { Command } from "commander";
|
|
11
11
|
|
|
12
12
|
import { confirmPrompt } from "../lib/confirm-prompt.js";
|
|
13
|
+
import {
|
|
14
|
+
diffPlugin,
|
|
15
|
+
type PluginDiffResult,
|
|
16
|
+
PluginDiffUnavailableError,
|
|
17
|
+
} from "../lib/diff-plugin.js";
|
|
13
18
|
import {
|
|
14
19
|
inspectPlugin,
|
|
15
20
|
type PluginInspection,
|
|
@@ -35,8 +40,13 @@ import {
|
|
|
35
40
|
uninstallPlugin,
|
|
36
41
|
} from "../lib/uninstall-plugin.js";
|
|
37
42
|
import {
|
|
43
|
+
DEFAULT_PLUGIN_UPGRADE_STRATEGY,
|
|
44
|
+
PLUGIN_UPGRADE_STRATEGIES,
|
|
45
|
+
PluginMergeBaselineError,
|
|
38
46
|
PluginNotUpgradableError,
|
|
39
47
|
type PluginUpgradeResult,
|
|
48
|
+
type PluginUpgradeStrategy,
|
|
49
|
+
PluginUpgradeStrategyUnsupportedError,
|
|
40
50
|
upgradePlugin,
|
|
41
51
|
} from "../lib/upgrade-plugin.js";
|
|
42
52
|
import { getCliLogger } from "../logger.js";
|
|
@@ -60,8 +70,12 @@ Examples:
|
|
|
60
70
|
$ assistant plugins list --json
|
|
61
71
|
$ assistant plugins inspect example
|
|
62
72
|
$ assistant plugins inspect example --json
|
|
73
|
+
$ assistant plugins diff example
|
|
74
|
+
$ assistant plugins diff example --json
|
|
63
75
|
$ assistant plugins upgrade example
|
|
64
76
|
$ assistant plugins upgrade example --dry-run
|
|
77
|
+
$ assistant plugins upgrade example --strategy ours
|
|
78
|
+
$ assistant plugins upgrade example --strategy theirs
|
|
65
79
|
$ assistant plugins search example
|
|
66
80
|
$ assistant plugins search "^example"
|
|
67
81
|
$ assistant plugins search example --json
|
|
@@ -212,6 +226,73 @@ Examples:
|
|
|
212
226
|
}
|
|
213
227
|
});
|
|
214
228
|
|
|
229
|
+
plugins
|
|
230
|
+
.command("diff <name>")
|
|
231
|
+
.description(
|
|
232
|
+
"Show a unified diff of local edits to an installed plugin against the commit it was installed at",
|
|
233
|
+
)
|
|
234
|
+
.option(
|
|
235
|
+
"--json",
|
|
236
|
+
"Emit the machine-readable diff result as JSON (files: { path, status, diff, binary, reconstructed }[]) instead of a unified diff",
|
|
237
|
+
)
|
|
238
|
+
.addHelpText(
|
|
239
|
+
"after",
|
|
240
|
+
`
|
|
241
|
+
Arguments:
|
|
242
|
+
name Install name (kebab-case directory under the workspace plugins dir);
|
|
243
|
+
run 'assistant plugins list' to see installed names.
|
|
244
|
+
|
|
245
|
+
The baseline is the exact commit the plugin was installed at (recorded in its
|
|
246
|
+
install-meta.json), re-materialized through the install pipeline — so an
|
|
247
|
+
install-time adapter transform never reads as a local change. To compare
|
|
248
|
+
against the marketplace's current pin instead, use 'plugins upgrade --dry-run'.
|
|
249
|
+
|
|
250
|
+
Examples:
|
|
251
|
+
$ assistant plugins diff example
|
|
252
|
+
$ assistant plugins diff example --json`,
|
|
253
|
+
)
|
|
254
|
+
.action(async (name: string, opts: { json?: boolean }) => {
|
|
255
|
+
try {
|
|
256
|
+
const result = await diffPlugin(
|
|
257
|
+
{ name },
|
|
258
|
+
{ fetch: globalThis.fetch.bind(globalThis) },
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
if (opts.json) {
|
|
262
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Logged after the JSON early-return: the CLI logger writes
|
|
267
|
+
// info to stdout, which would otherwise corrupt --json output.
|
|
268
|
+
log.info(
|
|
269
|
+
{
|
|
270
|
+
name: result.name,
|
|
271
|
+
clean: result.clean,
|
|
272
|
+
files: result.files.length,
|
|
273
|
+
},
|
|
274
|
+
"plugin diff",
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
for (const line of formatDiff(result)) {
|
|
278
|
+
console.log(line);
|
|
279
|
+
}
|
|
280
|
+
} catch (err) {
|
|
281
|
+
if (
|
|
282
|
+
err instanceof PluginNotInstalledError ||
|
|
283
|
+
err instanceof PluginDiffUnavailableError ||
|
|
284
|
+
err instanceof InvalidPluginNameError
|
|
285
|
+
) {
|
|
286
|
+
console.error(err.message);
|
|
287
|
+
process.exitCode = 1;
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
291
|
+
console.error(`Plugin diff failed: ${message}`);
|
|
292
|
+
process.exitCode = 1;
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
215
296
|
plugins
|
|
216
297
|
.command("search <query>")
|
|
217
298
|
.description(
|
|
@@ -327,12 +408,36 @@ Examples:
|
|
|
327
408
|
"--dry-run",
|
|
328
409
|
"Show what would change without modifying the install",
|
|
329
410
|
)
|
|
411
|
+
.option(
|
|
412
|
+
"--strategy <strategy>",
|
|
413
|
+
`How to reconcile local edits with the pin: ${PLUGIN_UPGRADE_STRATEGIES.join(", ")} (default: ${DEFAULT_PLUGIN_UPGRADE_STRATEGY})`,
|
|
414
|
+
)
|
|
330
415
|
.option("--json", "Emit machine-readable JSON instead of a summary")
|
|
331
416
|
.action(
|
|
332
|
-
async (
|
|
417
|
+
async (
|
|
418
|
+
name: string,
|
|
419
|
+
opts: { dryRun?: boolean; strategy?: string; json?: boolean },
|
|
420
|
+
) => {
|
|
421
|
+
const strategy = opts.strategy;
|
|
422
|
+
if (
|
|
423
|
+
strategy !== undefined &&
|
|
424
|
+
!(PLUGIN_UPGRADE_STRATEGIES as readonly string[]).includes(
|
|
425
|
+
strategy,
|
|
426
|
+
)
|
|
427
|
+
) {
|
|
428
|
+
console.error(
|
|
429
|
+
`Invalid --strategy "${strategy}". Expected one of: ${PLUGIN_UPGRADE_STRATEGIES.join(", ")}.`,
|
|
430
|
+
);
|
|
431
|
+
process.exitCode = 1;
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
333
434
|
try {
|
|
334
435
|
const result = await upgradePlugin(
|
|
335
|
-
{
|
|
436
|
+
{
|
|
437
|
+
name,
|
|
438
|
+
dryRun: opts.dryRun,
|
|
439
|
+
strategy: strategy as PluginUpgradeStrategy | undefined,
|
|
440
|
+
},
|
|
336
441
|
{ fetch: globalThis.fetch.bind(globalThis) },
|
|
337
442
|
);
|
|
338
443
|
|
|
@@ -360,6 +465,8 @@ Examples:
|
|
|
360
465
|
if (
|
|
361
466
|
err instanceof PluginNotInstalledError ||
|
|
362
467
|
err instanceof PluginNotUpgradableError ||
|
|
468
|
+
err instanceof PluginUpgradeStrategyUnsupportedError ||
|
|
469
|
+
err instanceof PluginMergeBaselineError ||
|
|
363
470
|
err instanceof InvalidPluginNameError
|
|
364
471
|
) {
|
|
365
472
|
console.error(err.message);
|
|
@@ -521,14 +628,48 @@ function formatUpgrade(result: PluginUpgradeResult): string[] {
|
|
|
521
628
|
result.fileCount === null
|
|
522
629
|
? ""
|
|
523
630
|
: `(${result.fileCount} file${result.fileCount === 1 ? "" : "s"}) `;
|
|
631
|
+
const mergeNote =
|
|
632
|
+
result.strategy === "ours" || result.strategy === "theirs"
|
|
633
|
+
? `Local edits were merged (--strategy ${result.strategy}).`
|
|
634
|
+
: null;
|
|
524
635
|
const lines = [
|
|
525
636
|
`Upgraded "${name}" ${move}`,
|
|
526
637
|
"",
|
|
527
638
|
`${count}→ ${result.target}`,
|
|
528
639
|
"Restart the assistant to pick up the upgrade.",
|
|
529
640
|
];
|
|
641
|
+
if (mergeNote) lines.push(mergeNote);
|
|
530
642
|
if (provenanceNote) lines.push(provenanceNote);
|
|
531
643
|
return lines;
|
|
532
644
|
}
|
|
533
645
|
}
|
|
534
646
|
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Render a diff result: a one-line "no local changes" when clean, otherwise a
|
|
650
|
+
* header naming the install-commit baseline followed by each file's unified
|
|
651
|
+
* diff (blank-line separated, mirroring how `git diff` stacks file patches).
|
|
652
|
+
*/
|
|
653
|
+
function formatDiff(result: PluginDiffResult): string[] {
|
|
654
|
+
const baseline = `${formatTimestamp(result.committedAt)} (${shortSha(result.commit)})`;
|
|
655
|
+
if (result.clean) {
|
|
656
|
+
return [
|
|
657
|
+
`"${result.name}" has no local changes (matches install commit ${baseline}).`,
|
|
658
|
+
];
|
|
659
|
+
}
|
|
660
|
+
const count = result.files.length;
|
|
661
|
+
const lines = [
|
|
662
|
+
`"${result.name}" — ${count} file${count === 1 ? "" : "s"} changed vs install commit ${baseline}`,
|
|
663
|
+
"",
|
|
664
|
+
];
|
|
665
|
+
for (const file of result.files) {
|
|
666
|
+
// A non-reconstructed baseline carries an explanatory marker, not a patch
|
|
667
|
+
// with `a/`–`b/` headers, so name the file it refers to.
|
|
668
|
+
if (!file.reconstructed) {
|
|
669
|
+
lines.push(`${file.path}: ${file.diff.trimEnd()}`, "");
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
lines.push(file.diff.trimEnd(), "");
|
|
673
|
+
}
|
|
674
|
+
return lines;
|
|
675
|
+
}
|