@vellumai/assistant 0.10.3 → 0.10.4-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/openapi.yaml +73 -56
- package/package.json +1 -1
- package/src/__tests__/actor-trust-resolver-address-fallback.test.ts +83 -31
- package/src/__tests__/assistant-stream-state.test.ts +3 -76
- package/src/__tests__/background-workers-disk-pressure.test.ts +4 -2
- package/src/__tests__/channel-approval-routes.test.ts +21 -26
- package/src/__tests__/channel-delivery-store.test.ts +28 -0
- package/src/__tests__/channel-guardian.test.ts +82 -32
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +11 -19
- package/src/__tests__/channel-reply-delivery.test.ts +6 -2
- package/src/__tests__/compaction-ledger-store.test.ts +128 -0
- package/src/__tests__/config-loader-backfill.test.ts +148 -0
- package/src/__tests__/consult-deadline.test.ts +60 -0
- package/src/__tests__/contact-store-interaction-info.test.ts +156 -0
- package/src/__tests__/contact-store-user-file.test.ts +7 -10
- package/src/__tests__/contacts-relay-reads.test.ts +6 -9
- package/src/__tests__/contacts-write.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -2
- package/src/__tests__/conversation-agent-loop.test.ts +98 -7
- package/src/__tests__/conversation-attention-telegram.test.ts +9 -11
- package/src/__tests__/conversation-error.test.ts +18 -0
- package/src/__tests__/conversation-fork-crud.test.ts +354 -24
- package/src/__tests__/conversation-title-service.test.ts +222 -201
- package/src/__tests__/db-compaction-events-migration.test.ts +129 -0
- package/src/__tests__/delete-propagation.test.ts +5 -3
- package/src/__tests__/dm-backfill.test.ts +6 -4
- package/src/__tests__/emit-signal-routing-intent.test.ts +2 -6
- package/src/__tests__/guardian-binding-drift-heal.test.ts +43 -23
- package/src/__tests__/guardian-dispatch.test.ts +50 -5
- package/src/__tests__/guardian-routing-state.test.ts +6 -10
- package/src/__tests__/helpers/channel-test-adapter.ts +45 -12
- package/src/__tests__/helpers/create-guardian-binding.ts +15 -23
- package/src/__tests__/helpers/mock-logger.ts +1 -0
- package/src/__tests__/helpers/seed-contact-channel.ts +96 -0
- package/src/__tests__/inbound-invite-redemption.test.ts +87 -10
- package/src/__tests__/invite-redemption-service.test.ts +273 -53
- package/src/__tests__/invite-routes-http.test.ts +34 -0
- package/src/__tests__/invite-service-ipc.test.ts +65 -2
- package/src/__tests__/list-messages-page-latest.test.ts +173 -4
- package/src/__tests__/mcp-config-secret-boundary.test.ts +3 -0
- package/src/__tests__/non-member-access-request.test.ts +15 -13
- package/src/__tests__/onboarding-persona-write.test.ts +52 -22
- package/src/__tests__/persist-onboarding-artifacts.test.ts +1 -0
- package/src/__tests__/persona-resolver.test.ts +75 -45
- package/src/__tests__/plugin-bootstrap.test.ts +13 -5
- package/src/__tests__/plugin-disabled-state.test.ts +190 -0
- package/src/__tests__/provider-usage-tracking.test.ts +1 -1
- package/src/__tests__/reaction-intercept-cold-cache-warm.test.ts +135 -0
- package/src/__tests__/reaction-intercept-member-verdict-warm.test.ts +158 -0
- package/src/__tests__/reaction-persistence.test.ts +51 -4
- package/src/__tests__/relay-server.test.ts +88 -31
- package/src/__tests__/runtime-attachment-metadata.test.ts +9 -11
- package/src/__tests__/settings-routes.test.ts +32 -0
- package/src/__tests__/slack-block-formatting.test.ts +1 -38
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +13 -36
- package/src/__tests__/stt-hints.test.ts +6 -3
- package/src/__tests__/subagent-fork-prompt-role.test.ts +195 -0
- package/src/__tests__/subagent-fork-spawn.test.ts +6 -7
- package/src/__tests__/subagent-role-registry.test.ts +17 -4
- package/src/__tests__/subagent-spawn-and-await.test.ts +546 -0
- package/src/__tests__/subagent-tools.test.ts +398 -3
- package/src/__tests__/thread-backfill.test.ts +3 -3
- package/src/__tests__/tool-preview-lifecycle.test.ts +26 -10
- package/src/__tests__/tool-start-timestamp.test.ts +4 -3
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +37 -51
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -2
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +9 -7
- package/src/__tests__/trusted-contact-multichannel.test.ts +16 -7
- package/src/__tests__/trusted-contact-verification.test.ts +79 -54
- package/src/__tests__/voice-guardian-cold-cache-warm.test.ts +137 -0
- package/src/__tests__/voice-invite-redemption.test.ts +183 -20
- package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +3 -3
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +2 -2
- package/src/__tests__/workspace-migration-112-remove-advisor-callsite-override.test.ts +170 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +196 -238
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +35 -47
- package/src/agent/loop-exclusive-tool.test.ts +19 -15
- package/src/agent/loop-native-web-search.test.ts +200 -0
- package/src/agent/loop.ts +108 -1
- package/src/api/responses/conversation-message.ts +9 -0
- package/src/approvals/guardian-request-resolvers.ts +16 -4
- package/src/calls/__tests__/relay-setup-router.test.ts +10 -18
- package/src/calls/guardian-dispatch.ts +14 -11
- package/src/calls/inbound-trust-reader.ts +7 -1
- package/src/calls/relay-access-wait.ts +6 -6
- package/src/calls/relay-server.ts +22 -2
- package/src/calls/relay-setup-router.ts +10 -10
- package/src/cli/commands/__tests__/conversations-slack.test.ts +1 -0
- package/src/cli/commands/contacts.ts +10 -7
- package/src/cli/commands/memory/__tests__/worker.test.ts +147 -17
- package/src/cli/commands/memory/worker.ts +97 -30
- package/src/cli/commands/plugins.ts +3 -146
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +17 -17
- package/src/cli/lib/__tests__/publish-plugin.test.ts +98 -0
- package/src/cli/lib/publish-plugin.ts +231 -1
- package/src/config/__tests__/sync-gated-profiles.test.ts +5 -7
- package/src/config/bundled-skills/subagent/SKILL.md +16 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +5 -4
- package/src/config/call-site-defaults.ts +0 -6
- package/src/config/llm-resolver.ts +0 -3
- package/src/config/schemas/call-site-catalog.ts +0 -7
- package/src/config/schemas/heartbeat.ts +2 -5
- package/src/config/schemas/llm.ts +3 -12
- package/src/config/schemas/memory-lifecycle.ts +1 -1
- package/src/config/seed-inference-profiles.ts +76 -35
- package/src/config/sync-gated-profiles.ts +0 -3
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +7 -8
- package/src/contacts/__tests__/member-write-relay.test.ts +35 -11
- package/src/contacts/contact-store.ts +27 -237
- package/src/contacts/contacts-write.ts +18 -58
- package/src/contacts/gateway-channel-read.ts +51 -0
- package/src/contacts/member-write-relay.ts +25 -31
- package/src/contacts/types.ts +3 -15
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +0 -44
- package/src/daemon/conversation-agent-loop-handlers.ts +29 -10
- package/src/daemon/conversation-agent-loop.ts +68 -61
- package/src/daemon/conversation-error.ts +7 -10
- package/src/daemon/conversation-tool-setup.ts +0 -10
- package/src/daemon/conversation.ts +10 -0
- package/src/daemon/external-plugins-bootstrap.ts +8 -2
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +0 -1
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -2
- package/src/daemon/handlers/__tests__/config-channels.test.ts +9 -14
- package/src/daemon/handlers/config-channels.ts +14 -29
- package/src/daemon/lifecycle.ts +16 -4
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/heartbeat/heartbeat-service.ts +5 -0
- package/src/home/relationship-state-writer.ts +5 -0
- package/src/memory/__tests__/embedding-cache.test.ts +136 -0
- package/src/memory/compaction-ledger-store.ts +107 -0
- package/src/memory/conversation-crud.ts +136 -61
- package/src/memory/conversation-title-service.ts +173 -24
- package/src/memory/embedding-backend.ts +8 -1
- package/src/memory/embedding-cache.ts +139 -0
- package/src/memory/jobs-worker.ts +75 -29
- package/src/memory/memory-retrospective-job.ts +5 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +27 -5
- package/src/memory/migrations/302-create-compaction-events.ts +107 -0
- package/src/memory/migrations/303-add-conversation-creation-seq.ts +33 -0
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +79 -6
- package/src/memory/schema/contacts.ts +6 -2
- package/src/memory/schema/conversations.ts +39 -0
- package/src/memory/steps.ts +1090 -367
- package/src/memory/worker-control.ts +104 -18
- package/src/memory/worker-process.ts +17 -0
- package/src/messaging/channel-binding-metadata.ts +31 -0
- package/src/messaging/channel-binding-schema.ts +51 -0
- package/src/messaging/providers/__tests__/callback-routing.test.ts +45 -0
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +195 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +11 -0
- package/src/messaging/providers/a2a/deliver.ts +5 -1
- package/src/messaging/providers/a2a/transport.ts +10 -0
- package/src/messaging/providers/callback-routing.ts +48 -0
- package/src/messaging/providers/channel-transport.ts +55 -0
- package/src/messaging/providers/index.ts +65 -241
- package/src/messaging/providers/slack/binding-metadata.ts +62 -0
- package/src/messaging/providers/slack/transport.ts +92 -0
- package/src/messaging/providers/telegram-bot/transport.ts +51 -0
- package/src/messaging/providers/whatsapp/transport.ts +38 -0
- package/src/notifications/__tests__/broadcaster.test.ts +0 -8
- package/src/notifications/__tests__/connected-channels.test.ts +8 -36
- package/src/notifications/__tests__/destination-resolver.test.ts +12 -117
- package/src/notifications/destination-resolver.ts +7 -23
- package/src/notifications/emit-signal.ts +5 -11
- package/src/plugins/defaults/index.ts +0 -35
- package/src/plugins/defaults/memory-v3-shadow/__tests__/dense.test.ts +11 -0
- package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +243 -2
- package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +167 -14
- package/src/plugins/disabled-state.ts +31 -0
- package/src/plugins/registry.ts +55 -12
- package/src/prompts/persona-resolver.ts +43 -11
- package/src/providers/call-site-routing.ts +41 -0
- package/src/providers/provider-send-message.ts +6 -0
- package/src/providers/ratelimit.ts +6 -0
- package/src/providers/registry.ts +1 -1
- package/src/providers/retry.ts +6 -0
- package/src/providers/types.ts +13 -0
- package/src/providers/usage-tracking.ts +6 -0
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +30 -27
- package/src/runtime/__tests__/local-principal-trust.test.ts +16 -18
- package/src/runtime/__tests__/member-verdict-cache.test.ts +119 -0
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +115 -168
- package/src/runtime/access-request-helper.ts +1 -2
- package/src/runtime/actor-trust-resolver.ts +44 -17
- package/src/runtime/anchored-guardian.test.ts +7 -54
- package/src/runtime/anchored-guardian.ts +4 -53
- package/src/runtime/assistant-stream-state.ts +12 -74
- package/src/runtime/channel-reply-delivery.ts +3 -8
- package/src/runtime/guardian-vellum-migration.ts +18 -16
- package/src/runtime/invite-redemption-service.ts +25 -10
- package/src/runtime/local-actor-identity.test.ts +108 -0
- package/src/runtime/local-actor-identity.ts +27 -20
- package/src/runtime/member-verdict-cache.ts +0 -0
- package/src/runtime/routes/__tests__/contact-routes.test.ts +100 -7
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +1 -2
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +2 -1
- package/src/runtime/routes/contact-routes.ts +40 -25
- package/src/runtime/routes/conversation-list-routes.ts +1 -29
- package/src/runtime/routes/conversation-routes.ts +27 -7
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -10
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +19 -0
- package/src/runtime/routes/settings-routes.ts +8 -3
- package/src/runtime/services/conversation-serializer.ts +6 -49
- package/src/runtime/slack-block-formatting.ts +0 -15
- package/src/runtime/trust-verdict-consumer.ts +36 -41
- package/src/subagent/__tests__/consult-prompt.test.ts +35 -0
- package/src/{plugins/defaults/advisor/__tests__/transcript.test.ts → subagent/__tests__/consult-transcript.test.ts} +47 -10
- package/src/{plugins/defaults/advisor/steering.ts → subagent/consult-prompt.ts} +17 -39
- package/src/{plugins/defaults/advisor/transcript.ts → subagent/consult-transcript.ts} +18 -8
- package/src/subagent/index.ts +1 -1
- package/src/subagent/manager.ts +245 -33
- package/src/subagent/types.ts +8 -1
- package/src/tools/registry.ts +10 -3
- package/src/tools/subagent/consult-deadline.ts +49 -0
- package/src/tools/subagent/spawn.ts +234 -5
- package/src/util/logger.ts +9 -0
- package/src/util/platform.ts +14 -0
- package/src/workspace/migrations/031-drop-user-md.ts +232 -148
- package/src/workspace/migrations/112-remove-advisor-callsite-override.ts +64 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/plugins/defaults/advisor/__tests__/advisor-gate.test.ts +0 -56
- package/src/plugins/defaults/advisor/__tests__/advisor-state-store.test.ts +0 -43
- package/src/plugins/defaults/advisor/__tests__/agent-loop-integration.test.ts +0 -137
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -314
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
- package/src/plugins/defaults/advisor/__tests__/hooks.test.ts +0 -138
- package/src/plugins/defaults/advisor/advisor-gate.ts +0 -29
- package/src/plugins/defaults/advisor/advisor-state-store.ts +0 -94
- package/src/plugins/defaults/advisor/config.ts +0 -21
- package/src/plugins/defaults/advisor/consult.ts +0 -197
- package/src/plugins/defaults/advisor/context-pack.ts +0 -288
- package/src/plugins/defaults/advisor/hooks/post-model-call.ts +0 -34
- package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +0 -30
- package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +0 -19
- package/src/plugins/defaults/advisor/package.json +0 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +0 -92
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import type {
|
|
4
|
-
Message,
|
|
5
|
-
PostModelCallContext,
|
|
6
|
-
PreModelCallContext,
|
|
7
|
-
UserPromptSubmitContext,
|
|
8
|
-
} from "@vellumai/plugin-api";
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
getCapture,
|
|
12
|
-
resetAdvisorStateForTests,
|
|
13
|
-
} from "../advisor-state-store.js";
|
|
14
|
-
import postModelCall from "../hooks/post-model-call.js";
|
|
15
|
-
import preModelCall from "../hooks/pre-model-call.js";
|
|
16
|
-
import userPromptSubmit from "../hooks/user-prompt-submit.js";
|
|
17
|
-
import { STEERING_MARKER } from "../steering.js";
|
|
18
|
-
|
|
19
|
-
const logger = { info() {}, warn() {}, error() {}, debug() {} };
|
|
20
|
-
const userMsg = (t: string): Message => ({
|
|
21
|
-
role: "user",
|
|
22
|
-
content: [{ type: "text", text: t }],
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
beforeEach(() => {
|
|
26
|
-
resetAdvisorStateForTests();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe("pre-model-call hook", () => {
|
|
30
|
-
test("records the original system prompt and injects steering on mainAgent", async () => {
|
|
31
|
-
const ctx = {
|
|
32
|
-
conversationId: "c1",
|
|
33
|
-
callSite: "mainAgent",
|
|
34
|
-
systemPrompt: "BASE PROMPT",
|
|
35
|
-
deferAssistantOutput: false,
|
|
36
|
-
logger,
|
|
37
|
-
} as unknown as PreModelCallContext;
|
|
38
|
-
|
|
39
|
-
await preModelCall(ctx);
|
|
40
|
-
|
|
41
|
-
expect(ctx.systemPrompt).toContain(STEERING_MARKER);
|
|
42
|
-
expect(ctx.systemPrompt?.startsWith("BASE PROMPT")).toBe(true);
|
|
43
|
-
expect(getCapture("c1")?.systemPrompt).toBe("BASE PROMPT");
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("ignores non-mainAgent calls", async () => {
|
|
47
|
-
const ctx = {
|
|
48
|
-
conversationId: "bg",
|
|
49
|
-
callSite: "inference",
|
|
50
|
-
systemPrompt: "BG",
|
|
51
|
-
deferAssistantOutput: false,
|
|
52
|
-
logger,
|
|
53
|
-
} as unknown as PreModelCallContext;
|
|
54
|
-
|
|
55
|
-
await preModelCall(ctx);
|
|
56
|
-
expect(ctx.systemPrompt).toBe("BG");
|
|
57
|
-
expect(getCapture("bg")).toBeUndefined();
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe("post-model-call hook", () => {
|
|
62
|
-
const base = {
|
|
63
|
-
content: [],
|
|
64
|
-
stopReason: "end_turn",
|
|
65
|
-
decision: "stop" as const,
|
|
66
|
-
logger,
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
test("snapshots the transcript on mainAgent", async () => {
|
|
70
|
-
const messages = [userMsg("hi")];
|
|
71
|
-
await postModelCall({
|
|
72
|
-
...base,
|
|
73
|
-
conversationId: "c1",
|
|
74
|
-
callSite: "mainAgent",
|
|
75
|
-
messages,
|
|
76
|
-
} as unknown as PostModelCallContext);
|
|
77
|
-
expect(getCapture("c1")?.messages).toEqual(messages);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test("appends the model's current turn (ctx.content) to the snapshot", async () => {
|
|
81
|
-
const messages = [userMsg("task")];
|
|
82
|
-
const assistantTurn: Message = {
|
|
83
|
-
role: "assistant",
|
|
84
|
-
content: [
|
|
85
|
-
{ type: "text", text: "thinking about it" },
|
|
86
|
-
{ type: "tool_use", id: "t1", name: "advisor", input: {} },
|
|
87
|
-
],
|
|
88
|
-
};
|
|
89
|
-
await postModelCall({
|
|
90
|
-
...base,
|
|
91
|
-
content: assistantTurn.content,
|
|
92
|
-
conversationId: "c4",
|
|
93
|
-
callSite: "mainAgent",
|
|
94
|
-
messages,
|
|
95
|
-
} as unknown as PostModelCallContext);
|
|
96
|
-
const captured = getCapture("c4")?.messages;
|
|
97
|
-
expect(captured).toHaveLength(2);
|
|
98
|
-
expect(captured?.[1]).toEqual(assistantTurn);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
test("ignores the advisor's own inference sub-call (recursion safety) and errors", async () => {
|
|
102
|
-
await postModelCall({
|
|
103
|
-
...base,
|
|
104
|
-
conversationId: "c2",
|
|
105
|
-
callSite: "inference",
|
|
106
|
-
messages: [userMsg("hi")],
|
|
107
|
-
} as unknown as PostModelCallContext);
|
|
108
|
-
expect(getCapture("c2")).toBeUndefined();
|
|
109
|
-
|
|
110
|
-
await postModelCall({
|
|
111
|
-
...base,
|
|
112
|
-
conversationId: "c3",
|
|
113
|
-
callSite: "mainAgent",
|
|
114
|
-
messages: [userMsg("hi")],
|
|
115
|
-
error: new Error("boom"),
|
|
116
|
-
stopReason: null,
|
|
117
|
-
} as unknown as PostModelCallContext);
|
|
118
|
-
expect(getCapture("c3")).toBeUndefined();
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe("user-prompt-submit hook", () => {
|
|
123
|
-
test("seeds the capture with the inbound history", async () => {
|
|
124
|
-
const messages = [userMsg("task")];
|
|
125
|
-
await userPromptSubmit({
|
|
126
|
-
conversationId: "c1",
|
|
127
|
-
latestMessages: messages,
|
|
128
|
-
originalMessages: messages,
|
|
129
|
-
prompt: "task",
|
|
130
|
-
userMessageId: "u1",
|
|
131
|
-
requestId: "r1",
|
|
132
|
-
modelProfileKey: null,
|
|
133
|
-
isNonInteractive: false,
|
|
134
|
-
logger,
|
|
135
|
-
} as unknown as UserPromptSubmitContext);
|
|
136
|
-
expect(getCapture("c1")?.messages).toEqual(messages);
|
|
137
|
-
});
|
|
138
|
-
});
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-chat-profile gate for the advisor.
|
|
3
|
-
*
|
|
4
|
-
* The advisor is active or not depending on the chat profile the turn routes
|
|
5
|
-
* to (`ProfileEntry.advisorEnabled`). Default-on: the advisor runs unless a
|
|
6
|
-
* profile sets `advisorEnabled: false` explicitly — absent/null both mean on,
|
|
7
|
-
* preserving the prior always-on behavior for profiles that never set it.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { resolveDefaultProfileKey } from "../../../config/llm-resolver.js";
|
|
11
|
-
import { getConfig } from "../../../config/loader.js";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Whether the advisor is enabled for the chat profile this turn uses.
|
|
15
|
-
*
|
|
16
|
-
* `modelProfile` is the call's already-resolved profile — `PreModelCallContext.modelProfile`
|
|
17
|
-
* from the steering hook, or `ToolContext.overrideProfile` from the tool. Pass
|
|
18
|
-
* `null` when no per-turn override applies, in which case the workspace active
|
|
19
|
-
* profile — or the `mainAgent` call-site default — applies.
|
|
20
|
-
*/
|
|
21
|
-
export function advisorEnabledForProfile(modelProfile: string | null): boolean {
|
|
22
|
-
const { llm } = getConfig();
|
|
23
|
-
const key =
|
|
24
|
-
modelProfile ??
|
|
25
|
-
llm.activeProfile ??
|
|
26
|
-
resolveDefaultProfileKey("mainAgent", llm);
|
|
27
|
-
const entry = key ? llm.profiles[key] : undefined;
|
|
28
|
-
return entry?.advisorEnabled !== false;
|
|
29
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-conversation capture store for the advisor plugin.
|
|
3
|
-
*
|
|
4
|
-
* A tool's `execute` does not receive the conversation transcript — only
|
|
5
|
-
* lifecycle hooks do. So the hooks snapshot what the executor saw (the system
|
|
6
|
-
* prompt from `pre-model-call`, the running transcript from `post-model-call`)
|
|
7
|
-
* into this module-level store, keyed by conversation id, and the `advisor`
|
|
8
|
-
* tool reads the latest snapshot when the model calls it. This reproduces the
|
|
9
|
-
* advisor-tool property that the model calls the tool with no arguments and the
|
|
10
|
-
* full context is supplied for it.
|
|
11
|
-
*
|
|
12
|
-
* Bounded by a small LRU so a long-lived daemon does not accumulate state for
|
|
13
|
-
* every conversation it has ever served.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import type { Message } from "../../../providers/types.js";
|
|
17
|
-
|
|
18
|
-
export interface AdvisorCapture {
|
|
19
|
-
/** The executor's system prompt (steering stripped), or null if unseen. */
|
|
20
|
-
systemPrompt: string | null;
|
|
21
|
-
/** The transcript the executor most recently saw on a `mainAgent` call. */
|
|
22
|
-
messages: Message[];
|
|
23
|
-
/** Last-write timestamp, used for LRU eviction. */
|
|
24
|
-
updatedAt: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const MAX_CONVERSATIONS = 200;
|
|
28
|
-
const store = new Map<string, AdvisorCapture>();
|
|
29
|
-
|
|
30
|
-
/** Move an entry to most-recently-used and evict the oldest past the cap. */
|
|
31
|
-
function bump(conversationId: string, entry: AdvisorCapture): void {
|
|
32
|
-
entry.updatedAt = Date.now();
|
|
33
|
-
store.delete(conversationId);
|
|
34
|
-
store.set(conversationId, entry);
|
|
35
|
-
while (store.size > MAX_CONVERSATIONS) {
|
|
36
|
-
const oldest = store.keys().next().value;
|
|
37
|
-
if (oldest === undefined) break;
|
|
38
|
-
store.delete(oldest);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function ensure(conversationId: string): AdvisorCapture {
|
|
43
|
-
return (
|
|
44
|
-
store.get(conversationId) ?? {
|
|
45
|
-
systemPrompt: null,
|
|
46
|
-
messages: [],
|
|
47
|
-
updatedAt: Date.now(),
|
|
48
|
-
}
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Seed the capture at the start of a user turn with the inbound history, so an
|
|
54
|
-
* advisor call on the very first model turn still has context even before
|
|
55
|
-
* `post-model-call` snapshots the running transcript.
|
|
56
|
-
*/
|
|
57
|
-
export function seedCapture(
|
|
58
|
-
conversationId: string,
|
|
59
|
-
messages: ReadonlyArray<Message>,
|
|
60
|
-
): void {
|
|
61
|
-
const entry = ensure(conversationId);
|
|
62
|
-
entry.messages = [...messages];
|
|
63
|
-
bump(conversationId, entry);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** Record the executor's system prompt (steering already stripped). */
|
|
67
|
-
export function recordSystemPrompt(
|
|
68
|
-
conversationId: string,
|
|
69
|
-
systemPrompt: string | null,
|
|
70
|
-
): void {
|
|
71
|
-
const entry = ensure(conversationId);
|
|
72
|
-
entry.systemPrompt = systemPrompt;
|
|
73
|
-
bump(conversationId, entry);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Snapshot the transcript the executor just saw (before tools run). */
|
|
77
|
-
export function recordMessages(
|
|
78
|
-
conversationId: string,
|
|
79
|
-
messages: ReadonlyArray<Message>,
|
|
80
|
-
): void {
|
|
81
|
-
const entry = ensure(conversationId);
|
|
82
|
-
entry.messages = [...messages];
|
|
83
|
-
bump(conversationId, entry);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** The latest capture for a conversation, or `undefined` if none recorded. */
|
|
87
|
-
export function getCapture(conversationId: string): AdvisorCapture | undefined {
|
|
88
|
-
return store.get(conversationId);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Test-only: clear all captured state. */
|
|
92
|
-
export function resetAdvisorStateForTests(): void {
|
|
93
|
-
store.clear();
|
|
94
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Static behavioral configuration for the advisor.
|
|
3
|
-
*
|
|
4
|
-
* The advisor *model* is no longer hardcoded here: it routes through the
|
|
5
|
-
* dedicated `advisor` call site, whose default profile (`quality-optimized`)
|
|
6
|
-
* lives in CALL_SITE_DEFAULTS and is overridden per workspace by
|
|
7
|
-
* `llm.advisorProfile`. Whether the advisor is active is decided per chat
|
|
8
|
-
* profile by `ProfileEntry.advisorEnabled` (default on).
|
|
9
|
-
*/
|
|
10
|
-
export const ADVISOR_CONFIG = {
|
|
11
|
-
// No advisor-specific output cap: the consult omits `max_tokens` (so the
|
|
12
|
-
// resolver applies the profile's normal output budget) and the request text
|
|
13
|
-
// carries no word limit. The advisor decides its own length.
|
|
14
|
-
/** Abort the consult if the sub-call runs longer than this. */
|
|
15
|
-
timeoutMs: 60_000,
|
|
16
|
-
/**
|
|
17
|
-
* Global kill-switch for the steering injection. The per-chat-profile
|
|
18
|
-
* `advisorEnabled` toggle gates it further; this stays as a hard off switch.
|
|
19
|
-
*/
|
|
20
|
-
steeringEnabled: true,
|
|
21
|
-
} as const;
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Run one advisor consult.
|
|
3
|
-
*
|
|
4
|
-
* Routed entirely through the assistant's own inference: `getConfiguredProvider`
|
|
5
|
-
* resolves the general-purpose `inference` call site (with the advisor profile
|
|
6
|
-
* applied as an `overrideProfile`) to a provider/model/credentials from the
|
|
7
|
-
* configured profiles — managed-proxy or BYOK, no separate API key. The consult
|
|
8
|
-
* then runs a tool-less, capped one-shot completion through `provider.sendMessage`
|
|
9
|
-
* and returns the text.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { getConfig } from "../../../config/loader.js";
|
|
13
|
-
import type { LLMCallSite } from "../../../config/schemas/llm.js";
|
|
14
|
-
import {
|
|
15
|
-
extractAllText,
|
|
16
|
-
getConfiguredProvider,
|
|
17
|
-
userMessage,
|
|
18
|
-
} from "../../../providers/provider-send-message.js";
|
|
19
|
-
import type {
|
|
20
|
-
Message,
|
|
21
|
-
ProviderEvent,
|
|
22
|
-
ToolDefinition,
|
|
23
|
-
} from "../../../providers/types.js";
|
|
24
|
-
import { ADVISOR_CONFIG } from "./config.js";
|
|
25
|
-
import { advisorRequestText, buildAdvisorSystem } from "./steering.js";
|
|
26
|
-
import { toAdvisorMessages } from "./transcript.js";
|
|
27
|
-
|
|
28
|
-
// Dedicated advisor call site. Its default profile (`quality-optimized`) lives
|
|
29
|
-
// in CALL_SITE_DEFAULTS; a workspace overrides which profile the advisor runs
|
|
30
|
-
// on via `llm.advisorProfile`, which we float above the call-site layers.
|
|
31
|
-
const ADVISOR_CALL_SITE: LLMCallSite = "advisor";
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* The single tool the consult may attach: a `web_search`-named tool that
|
|
35
|
-
* provider-native search (Anthropic/OpenAI) substitutes for its server-side
|
|
36
|
-
* web tool. Only passed when `provider.supportsNativeWebSearch` is true, so the
|
|
37
|
-
* provider runs the search itself and returns results inline — no agent loop,
|
|
38
|
-
* which keeps the consult a one-shot completion.
|
|
39
|
-
*/
|
|
40
|
-
const ADVISOR_WEB_SEARCH_TOOL: ToolDefinition = {
|
|
41
|
-
name: "web_search",
|
|
42
|
-
description:
|
|
43
|
-
"Search the web for current information to ground your guidance.",
|
|
44
|
-
input_schema: {
|
|
45
|
-
type: "object",
|
|
46
|
-
properties: {
|
|
47
|
-
query: { type: "string", description: "The search query." },
|
|
48
|
-
},
|
|
49
|
-
required: ["query"],
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Resolve the routing override for the advisor consult. When the workspace has
|
|
55
|
-
* set `llm.advisorProfile`, force it above the call-site layers so it is
|
|
56
|
-
* authoritative; otherwise return `{}` so the `advisor` call site resolves to
|
|
57
|
-
* its default profile.
|
|
58
|
-
*/
|
|
59
|
-
function advisorOverride(): {
|
|
60
|
-
overrideProfile?: string;
|
|
61
|
-
forceOverrideProfile?: boolean;
|
|
62
|
-
} {
|
|
63
|
-
const advisorProfile = getConfig().llm.advisorProfile;
|
|
64
|
-
return advisorProfile
|
|
65
|
-
? { overrideProfile: advisorProfile, forceOverrideProfile: true }
|
|
66
|
-
: {};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface ConsultParams {
|
|
70
|
-
systemPrompt: string | null;
|
|
71
|
-
messages: ReadonlyArray<Message>;
|
|
72
|
-
/**
|
|
73
|
-
* The agent's runtime context (available tools and skills, workspace/project
|
|
74
|
-
* context, recalled memory), gathered by the tool from its `ToolContext`.
|
|
75
|
-
* Embedded in the advisor's system prompt so its advice is grounded in what
|
|
76
|
-
* the agent can actually do. Omitted/null when nothing could be gathered.
|
|
77
|
-
*/
|
|
78
|
-
runtimeContext?: string | null;
|
|
79
|
-
signal?: AbortSignal;
|
|
80
|
-
/**
|
|
81
|
-
* Optional sink for the advisor's live activity as it generates: incremental
|
|
82
|
-
* advice text, the reasoning summary (when surfaced), and a note per web
|
|
83
|
-
* search. Wiring this to the tool's `onOutput` surfaces the consult live as
|
|
84
|
-
* `tool_output_chunk` while the advisor is still working; the complete
|
|
85
|
-
* guidance is still returned as the resolved string. See `advisorActivitySink`.
|
|
86
|
-
*/
|
|
87
|
-
onText?: (chunk: string) => void;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** Combine the caller's signal with a consult timeout. */
|
|
91
|
-
function withTimeout(signal: AbortSignal | undefined, ms: number): AbortSignal {
|
|
92
|
-
const timeout = AbortSignal.timeout(ms);
|
|
93
|
-
return signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Build the streaming sink for a consult: forward the advisor's live activity
|
|
98
|
-
* to `onText` so the tool-output drawer streams throughout the consult instead
|
|
99
|
-
* of sitting silent until the final advice lands.
|
|
100
|
-
*
|
|
101
|
-
* The consult searches the web (up to 5×) and reasons over full context before
|
|
102
|
-
* writing its guidance. Forwarding the visible advice text alone would leave
|
|
103
|
-
* the drawer blank for that whole prefix, so the sink also surfaces the
|
|
104
|
-
* reasoning summary (when the model emits one) and a one-line note per web
|
|
105
|
-
* search — a success note with the query, or a failure note when the search
|
|
106
|
-
* errors. The complete guidance is still returned by `consultAdvisor`; the
|
|
107
|
-
* renderer swaps it in once the tool result arrives.
|
|
108
|
-
*/
|
|
109
|
-
function advisorActivitySink(
|
|
110
|
-
onText: (chunk: string) => void,
|
|
111
|
-
): (event: ProviderEvent) => void {
|
|
112
|
-
return (event) => {
|
|
113
|
-
switch (event.type) {
|
|
114
|
-
case "text_delta":
|
|
115
|
-
if (event.text) onText(event.text);
|
|
116
|
-
break;
|
|
117
|
-
case "thinking_delta":
|
|
118
|
-
if (event.thinking) onText(event.thinking);
|
|
119
|
-
break;
|
|
120
|
-
case "server_tool_start":
|
|
121
|
-
if (event.name === "web_search") onText("\n🔎 Searching the web…\n");
|
|
122
|
-
break;
|
|
123
|
-
case "server_tool_complete": {
|
|
124
|
-
const rawQuery = event.resolvedInput?.["query"];
|
|
125
|
-
const query = typeof rawQuery === "string" ? rawQuery.trim() : "";
|
|
126
|
-
if (event.isError) {
|
|
127
|
-
// A failed search (e.g. `query_too_long`, `max_uses_exceeded`) must
|
|
128
|
-
// not be announced as a success — the advisor proceeds without it.
|
|
129
|
-
onText(
|
|
130
|
-
query
|
|
131
|
-
? `\n⚠️ Web search failed: ${query}\n`
|
|
132
|
-
: "\n⚠️ Web search failed.\n",
|
|
133
|
-
);
|
|
134
|
-
} else if (query) {
|
|
135
|
-
onText(`\n🔎 Searched: ${query}\n`);
|
|
136
|
-
}
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
default:
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Returns the advisor's guidance text, or a short benign notice when the
|
|
147
|
-
* advisor can't run. Callers should surface the string as a non-error tool
|
|
148
|
-
* result so the executor continues regardless.
|
|
149
|
-
*/
|
|
150
|
-
export async function consultAdvisor(params: ConsultParams): Promise<string> {
|
|
151
|
-
const history = toAdvisorMessages(params.messages);
|
|
152
|
-
if (history.length === 0) {
|
|
153
|
-
return "(advisor: no conversation context is available yet)";
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const override = advisorOverride();
|
|
157
|
-
|
|
158
|
-
const provider = await getConfiguredProvider(ADVISOR_CALL_SITE, override);
|
|
159
|
-
if (!provider) {
|
|
160
|
-
return "(advisor unavailable: no inference provider is configured)";
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Append the consult instruction as the final user turn, then run a
|
|
164
|
-
// completion through the resolved provider. No `max_tokens` is set, so the
|
|
165
|
-
// resolver applies the profile's normal output budget rather than an
|
|
166
|
-
// advisor-specific cap.
|
|
167
|
-
const messages: Message[] = [...history, userMessage(advisorRequestText())];
|
|
168
|
-
|
|
169
|
-
// Give the advisor live web access when — and only when — the resolved
|
|
170
|
-
// provider runs search server-side (provider-native). Passing a `web_search`
|
|
171
|
-
// tool to a non-native provider would surface a client tool call this
|
|
172
|
-
// one-shot consult cannot execute, so we gate strictly on the capability and
|
|
173
|
-
// otherwise keep the consult tool-less.
|
|
174
|
-
const webEnabled = provider.supportsNativeWebSearch === true;
|
|
175
|
-
|
|
176
|
-
const { onText } = params;
|
|
177
|
-
const response = await provider.sendMessage(messages, {
|
|
178
|
-
systemPrompt: buildAdvisorSystem(
|
|
179
|
-
params.systemPrompt,
|
|
180
|
-
params.runtimeContext,
|
|
181
|
-
),
|
|
182
|
-
...(webEnabled ? { tools: [ADVISOR_WEB_SEARCH_TOOL] } : {}),
|
|
183
|
-
// Stream the consult's activity live (advice text, reasoning summary, and a
|
|
184
|
-
// note per web search) so the drawer isn't blank while the advisor searches
|
|
185
|
-
// and reasons before writing its guidance. See `advisorActivitySink`.
|
|
186
|
-
onEvent: onText ? advisorActivitySink(onText) : undefined,
|
|
187
|
-
config: {
|
|
188
|
-
callSite: ADVISOR_CALL_SITE,
|
|
189
|
-
...override,
|
|
190
|
-
tool_choice: webEnabled ? { type: "auto" } : { type: "none" },
|
|
191
|
-
},
|
|
192
|
-
signal: withTimeout(params.signal, ADVISOR_CONFIG.timeoutMs),
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const advice = extractAllText(response).trim();
|
|
196
|
-
return advice.length > 0 ? advice : "(advisor returned no guidance)";
|
|
197
|
-
}
|