@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,288 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Assemble the runtime context the advisor needs to make grounded
|
|
3
|
-
* recommendations — the same situational awareness the executing agent has:
|
|
4
|
-
* - the tools available to it this turn,
|
|
5
|
-
* - the skills it can load,
|
|
6
|
-
* - the loaded workspace / project context, NOW.md, PKB, and open documents,
|
|
7
|
-
* - and relevant memory pulled through the recall search.
|
|
8
|
-
*
|
|
9
|
-
* The advisor already receives the agent's transcript and system prompt; this
|
|
10
|
-
* adds the situational context that lives *outside* the prompt (tools and
|
|
11
|
-
* skills are passed to the model as a separate catalog, not inlined) plus a
|
|
12
|
-
* fresh, task-focused memory recall.
|
|
13
|
-
*
|
|
14
|
-
* Personal-memory surfaces are gated to the same policy the main agent's
|
|
15
|
-
* memory injectors apply: the recall search honors `canAccessMemory` (like the
|
|
16
|
-
* `recall` tool), and NOW.md / PKB honor `isPersonalMemoryAllowed` (plus the
|
|
17
|
-
* scratchpad-injection toggle for NOW.md). The advisor tool is low-risk and can
|
|
18
|
-
* run on remote/trusted-contact turns, so without these gates it could forward
|
|
19
|
-
* private content the main agent itself would not receive.
|
|
20
|
-
*
|
|
21
|
-
* Every section is best-effort: each source is wrapped so a failure or empty
|
|
22
|
-
* result drops just that section, never the consult. Daemon- and memory-side
|
|
23
|
-
* modules are pulled in via dynamic `import()` so this plugin module — loaded
|
|
24
|
-
* at bootstrap through `defaults/index.ts` — never forms a static import cycle
|
|
25
|
-
* with them. The result is a single string injected into the advisor's system
|
|
26
|
-
* prompt (see `buildAdvisorSystem`), or `null` when nothing could be gathered.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import type { ChannelId } from "../../../channels/types.js";
|
|
30
|
-
import type { TrustContext } from "../../../daemon/trust-context.js";
|
|
31
|
-
import type { Message } from "../../../providers/types.js";
|
|
32
|
-
import type { TrustClass } from "../../../runtime/actor-trust-resolver.js";
|
|
33
|
-
|
|
34
|
-
export interface AdvisorContextSources {
|
|
35
|
-
conversationId: string;
|
|
36
|
-
workingDir: string;
|
|
37
|
-
/** The live tool set the executor sees this turn (`ToolContext.allowedToolNames`). */
|
|
38
|
-
allowedToolNames?: ReadonlySet<string>;
|
|
39
|
-
/**
|
|
40
|
-
* Trust class of the turn's actor, from the per-turn `ToolContext.trustClass`
|
|
41
|
-
* snapshot. Gates the memory recall and (with {@link sourceChannel}) the
|
|
42
|
-
* personal-memory surfaces.
|
|
43
|
-
*/
|
|
44
|
-
trustClass: TrustClass;
|
|
45
|
-
/**
|
|
46
|
-
* Channel the turn originates on, from the per-turn `ToolContext.executionChannel`
|
|
47
|
-
* snapshot. Combined with {@link trustClass} to evaluate personal-memory
|
|
48
|
-
* access exactly as the injectors do, off the same per-turn snapshot rather
|
|
49
|
-
* than the mutable live conversation trust.
|
|
50
|
-
*/
|
|
51
|
-
sourceChannel?: string;
|
|
52
|
-
/** The captured transcript, used to derive the recall query. */
|
|
53
|
-
transcript: ReadonlyArray<Message>;
|
|
54
|
-
signal?: AbortSignal;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** Cap a block so the assembled context never balloons the consult prompt. */
|
|
58
|
-
function truncate(text: string, max: number): string {
|
|
59
|
-
const trimmed = text.trim();
|
|
60
|
-
return trimmed.length <= max ? trimmed : `${trimmed.slice(0, max)}…`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** First sentence (or a capped prefix) of a tool/skill description. */
|
|
64
|
-
function summarize(description: string | undefined, max = 160): string {
|
|
65
|
-
if (!description) return "";
|
|
66
|
-
const firstSentence = description.split(/(?<=[.!?])\s/)[0] ?? description;
|
|
67
|
-
return truncate(firstSentence, max);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Pull the most recent user-authored text to seed the memory recall query. */
|
|
71
|
-
export function deriveRecallQuery(
|
|
72
|
-
transcript: ReadonlyArray<Message>,
|
|
73
|
-
): string | null {
|
|
74
|
-
for (let i = transcript.length - 1; i >= 0; i--) {
|
|
75
|
-
const message = transcript[i];
|
|
76
|
-
if (message.role !== "user") continue;
|
|
77
|
-
const text = message.content
|
|
78
|
-
.map((block) => (block.type === "text" ? block.text : ""))
|
|
79
|
-
.join(" ")
|
|
80
|
-
.trim();
|
|
81
|
-
if (text.length > 0) return truncate(text, 500);
|
|
82
|
-
}
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** `## Available tools` — the live tool set the agent can act with this turn. */
|
|
87
|
-
async function buildToolsSection(
|
|
88
|
-
allowedToolNames: ReadonlySet<string> | undefined,
|
|
89
|
-
): Promise<string | null> {
|
|
90
|
-
if (!allowedToolNames || allowedToolNames.size === 0) return null;
|
|
91
|
-
try {
|
|
92
|
-
const { getTool } = await import("../../../tools/registry.js");
|
|
93
|
-
const lines: string[] = [];
|
|
94
|
-
for (const name of [...allowedToolNames].sort()) {
|
|
95
|
-
// The advisor advises; it never recommends consulting itself.
|
|
96
|
-
if (name === "advisor") continue;
|
|
97
|
-
const summary = summarize(getTool(name)?.description);
|
|
98
|
-
lines.push(summary ? `- ${name} — ${summary}` : `- ${name}`);
|
|
99
|
-
}
|
|
100
|
-
if (lines.length === 0) return null;
|
|
101
|
-
return `## Available tools (what the agent can do)\n${lines.join("\n")}`;
|
|
102
|
-
} catch {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** `## Available skills` — the skills the agent can load via `skill_load`. */
|
|
108
|
-
async function buildSkillsSection(): Promise<string | null> {
|
|
109
|
-
try {
|
|
110
|
-
const { loadSkillCatalog } = await import("../../../config/skills.js");
|
|
111
|
-
const catalog = loadSkillCatalog();
|
|
112
|
-
if (catalog.length === 0) return null;
|
|
113
|
-
const lines = catalog.slice(0, 60).map((skill) => {
|
|
114
|
-
const summary = summarize(skill.description);
|
|
115
|
-
const when = skill.activationHints?.length
|
|
116
|
-
? ` (use when: ${truncate(skill.activationHints.join("; "), 120)})`
|
|
117
|
-
: "";
|
|
118
|
-
const label = skill.displayName || skill.name || skill.id;
|
|
119
|
-
return `- ${label} (${skill.id})${summary ? ` — ${summary}` : ""}${when}`;
|
|
120
|
-
});
|
|
121
|
-
const more =
|
|
122
|
-
catalog.length > 60 ? `\n- …and ${catalog.length - 60} more` : "";
|
|
123
|
-
return `## Available skills (load with skill_load)\n${lines.join("\n")}${more}`;
|
|
124
|
-
} catch {
|
|
125
|
-
return null;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Whether personal-memory surfaces (NOW.md, PKB) may be exposed to the advisor
|
|
131
|
-
* — the same `isPersonalMemoryAllowed` gate the runtime memory injectors apply.
|
|
132
|
-
*
|
|
133
|
-
* Derived from the per-turn trust snapshot (`ToolContext.trustClass` /
|
|
134
|
-
* `executionChannel`, threaded in via {@link AdvisorContextSources}), NOT the
|
|
135
|
-
* live `findConversation().trustContext`: that conversation state is mutable
|
|
136
|
-
* and a concurrent guardian/meta command could flip it to guardian mid-flight,
|
|
137
|
-
* granting a remote/non-guardian turn access its own snapshot was never given.
|
|
138
|
-
* Fail-closed: if the gate can't be resolved, returns false.
|
|
139
|
-
*/
|
|
140
|
-
async function personalMemoryAllowedForAdvisor(
|
|
141
|
-
trustClass: TrustClass,
|
|
142
|
-
sourceChannel: string | undefined,
|
|
143
|
-
): Promise<boolean> {
|
|
144
|
-
try {
|
|
145
|
-
const { isPersonalMemoryAllowed } =
|
|
146
|
-
await import("../../../daemon/trust-context.js");
|
|
147
|
-
// `isPersonalMemoryAllowed` reads only `sourceChannel` + `trustClass`; build
|
|
148
|
-
// a minimal trust context from the per-turn snapshot. The channel may be
|
|
149
|
-
// absent (local/internal turns), which the gate treats as non-remote.
|
|
150
|
-
const snapshot = {
|
|
151
|
-
sourceChannel: sourceChannel as ChannelId | undefined,
|
|
152
|
-
trustClass,
|
|
153
|
-
} as TrustContext;
|
|
154
|
-
return isPersonalMemoryAllowed(snapshot);
|
|
155
|
-
} catch {
|
|
156
|
-
return false;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/** `## Workspace & project context` — the loaded environment around the agent. */
|
|
161
|
-
async function buildWorkspaceSection(
|
|
162
|
-
sources: AdvisorContextSources,
|
|
163
|
-
): Promise<string | null> {
|
|
164
|
-
const { conversationId } = sources;
|
|
165
|
-
const parts: string[] = [];
|
|
166
|
-
|
|
167
|
-
// The `<workspace>` directory listing is not personal memory — the agent's
|
|
168
|
-
// own file tools already operate in this cwd — so it is surfaced ungated, the
|
|
169
|
-
// same way the workspace-context injector does.
|
|
170
|
-
try {
|
|
171
|
-
const { resolveWorkspaceTopLevelContext } =
|
|
172
|
-
await import("../../../daemon/conversation-workspace.js");
|
|
173
|
-
const workspace = resolveWorkspaceTopLevelContext(conversationId);
|
|
174
|
-
if (workspace) parts.push(truncate(workspace, 2500));
|
|
175
|
-
} catch {
|
|
176
|
-
/* best-effort */
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// NOW.md and PKB are personal-memory surfaces. Gate them behind the same
|
|
180
|
-
// `isPersonalMemoryAllowed` policy (and, for NOW.md, the scratchpad-injection
|
|
181
|
-
// toggle) the runtime injectors use, evaluated off the per-turn trust
|
|
182
|
-
// snapshot, so a low-risk advisor consult cannot forward private content the
|
|
183
|
-
// main agent would never receive.
|
|
184
|
-
if (
|
|
185
|
-
await personalMemoryAllowedForAdvisor(
|
|
186
|
-
sources.trustClass,
|
|
187
|
-
sources.sourceChannel,
|
|
188
|
-
)
|
|
189
|
-
) {
|
|
190
|
-
try {
|
|
191
|
-
const [{ readNowScratchpad }, { getConfig }] = await Promise.all([
|
|
192
|
-
import("../../../daemon/now-scratchpad.js"),
|
|
193
|
-
import("../../../config/loader.js"),
|
|
194
|
-
]);
|
|
195
|
-
if (getConfig().memory.retrieval.scratchpadInjection.enabled) {
|
|
196
|
-
const now = readNowScratchpad();
|
|
197
|
-
if (now) parts.push(`NOW.md scratchpad:\n${truncate(now, 1500)}`);
|
|
198
|
-
}
|
|
199
|
-
} catch {
|
|
200
|
-
/* best-effort */
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
const { readPkbContext } = await import("../../../memory/pkb/context.js");
|
|
205
|
-
const pkb = readPkbContext();
|
|
206
|
-
if (pkb) parts.push(truncate(pkb, 1500));
|
|
207
|
-
} catch {
|
|
208
|
-
/* best-effort */
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
try {
|
|
213
|
-
const { buildActiveDocuments } =
|
|
214
|
-
await import("../../../daemon/conversation-runtime-assembly.js");
|
|
215
|
-
const docs = buildActiveDocuments(conversationId);
|
|
216
|
-
if (docs && docs.length > 0) {
|
|
217
|
-
const titles = docs
|
|
218
|
-
.slice(0, 20)
|
|
219
|
-
.map((doc) => `- ${doc.title} (${doc.wordCount} words)`)
|
|
220
|
-
.join("\n");
|
|
221
|
-
parts.push(`Open documents:\n${titles}`);
|
|
222
|
-
}
|
|
223
|
-
} catch {
|
|
224
|
-
/* best-effort */
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
if (parts.length === 0) return null;
|
|
228
|
-
return `## Workspace & project context\n${parts.join("\n\n")}`;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/** `## Relevant memory (recall)` — a fresh, task-focused recall search. */
|
|
232
|
-
async function buildMemorySection(
|
|
233
|
-
sources: AdvisorContextSources,
|
|
234
|
-
): Promise<string | null> {
|
|
235
|
-
try {
|
|
236
|
-
const { resolveCapabilities } =
|
|
237
|
-
await import("../../../runtime/capabilities.js");
|
|
238
|
-
// Recall reads sensitive local context; honor the same trust gate the
|
|
239
|
-
// `recall` tool applies. Non-guardian turns get no fresh recall here.
|
|
240
|
-
if (!resolveCapabilities(sources.trustClass).canAccessMemory) return null;
|
|
241
|
-
|
|
242
|
-
const query = deriveRecallQuery(sources.transcript);
|
|
243
|
-
if (!query) return null;
|
|
244
|
-
|
|
245
|
-
const [{ runDeterministicRecallSearch }, { getConfig }] = await Promise.all(
|
|
246
|
-
[
|
|
247
|
-
import("../../../memory/context-search/search.js"),
|
|
248
|
-
import("../../../config/loader.js"),
|
|
249
|
-
],
|
|
250
|
-
);
|
|
251
|
-
|
|
252
|
-
const { evidence } = await runDeterministicRecallSearch(
|
|
253
|
-
{ query, max_results: 8 },
|
|
254
|
-
{
|
|
255
|
-
workingDir: sources.workingDir,
|
|
256
|
-
conversationId: sources.conversationId,
|
|
257
|
-
config: getConfig(),
|
|
258
|
-
signal: sources.signal,
|
|
259
|
-
},
|
|
260
|
-
);
|
|
261
|
-
if (evidence.length === 0) return null;
|
|
262
|
-
|
|
263
|
-
const lines = evidence.slice(0, 8).map((item) => {
|
|
264
|
-
const excerpt = truncate(item.excerpt, 220);
|
|
265
|
-
return `- [${item.source}] ${item.title} (${item.locator}): ${excerpt}`;
|
|
266
|
-
});
|
|
267
|
-
return `## Relevant memory (recall: "${truncate(query, 120)}")\n${lines.join("\n")}`;
|
|
268
|
-
} catch {
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Gather the advisor's runtime context block, or `null` if nothing is
|
|
275
|
-
* available. Sections run concurrently; each is independently best-effort.
|
|
276
|
-
*/
|
|
277
|
-
export async function buildAdvisorContext(
|
|
278
|
-
sources: AdvisorContextSources,
|
|
279
|
-
): Promise<string | null> {
|
|
280
|
-
const sections = await Promise.all([
|
|
281
|
-
buildToolsSection(sources.allowedToolNames),
|
|
282
|
-
buildSkillsSection(),
|
|
283
|
-
buildWorkspaceSection(sources),
|
|
284
|
-
buildMemorySection(sources),
|
|
285
|
-
]);
|
|
286
|
-
const present = sections.filter((s): s is string => s !== null);
|
|
287
|
-
return present.length > 0 ? present.join("\n\n") : null;
|
|
288
|
-
}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `post-model-call` hook: snapshot the transcript the executor just saw. Fires
|
|
3
|
-
* after the model returns its message (which may carry the `advisor` tool_use)
|
|
4
|
-
* but before tools run, so when `advisor.execute()` runs an instant later it
|
|
5
|
-
* reads exactly this snapshot.
|
|
6
|
-
*
|
|
7
|
-
* Pure observer: never mutates `content` or the continue/stop `decision`. Gated
|
|
8
|
-
* to the user-facing reply, so background/subagent/compaction calls — and the
|
|
9
|
-
* advisor's own `inference`-call-site sub-call — are ignored.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type { HookFunction, PostModelCallContext } from "@vellumai/plugin-api";
|
|
13
|
-
|
|
14
|
-
import { recordMessages } from "../advisor-state-store.js";
|
|
15
|
-
|
|
16
|
-
const postModelCall: HookFunction<PostModelCallContext> = async (ctx) => {
|
|
17
|
-
if (ctx.callSite !== "mainAgent") return;
|
|
18
|
-
if (ctx.error) return;
|
|
19
|
-
// `ctx.messages` is the pre-reply history; the turn the model just produced —
|
|
20
|
-
// including any text/plan it wrote before the `advisor` tool_use — lives in
|
|
21
|
-
// `ctx.content` and is not yet in `messages`. Append it (cloned) so the
|
|
22
|
-
// advisor reviews the full current transcript, not just the prior history.
|
|
23
|
-
// `transcript.ts` strips the pending tool_use from this final assistant turn.
|
|
24
|
-
const messages =
|
|
25
|
-
ctx.content.length > 0
|
|
26
|
-
? [
|
|
27
|
-
...ctx.messages,
|
|
28
|
-
{ role: "assistant" as const, content: [...ctx.content] },
|
|
29
|
-
]
|
|
30
|
-
: ctx.messages;
|
|
31
|
-
recordMessages(ctx.conversationId, messages);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export default postModelCall;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `pre-model-call` hook: for the user-facing reply (`mainAgent`), capture the
|
|
3
|
-
* executor's system prompt (steering stripped) so the advisor can be given it
|
|
4
|
-
* as context, and inject the advisor steering so the model reaches for the
|
|
5
|
-
* tool. Idempotent via the steering marker.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type { HookFunction, PreModelCallContext } from "@vellumai/plugin-api";
|
|
9
|
-
|
|
10
|
-
import { advisorEnabledForProfile } from "../advisor-gate.js";
|
|
11
|
-
import { recordSystemPrompt } from "../advisor-state-store.js";
|
|
12
|
-
import { ADVISOR_CONFIG } from "../config.js";
|
|
13
|
-
import { appendSteering, stripSteering } from "../steering.js";
|
|
14
|
-
|
|
15
|
-
const preModelCall: HookFunction<PreModelCallContext> = async (ctx) => {
|
|
16
|
-
if (ctx.callSite !== "mainAgent") return;
|
|
17
|
-
|
|
18
|
-
recordSystemPrompt(ctx.conversationId, stripSteering(ctx.systemPrompt));
|
|
19
|
-
|
|
20
|
-
// Steer the model to consult only when the steering is globally on AND the
|
|
21
|
-
// chat profile this turn routes to has the advisor enabled (default on).
|
|
22
|
-
if (
|
|
23
|
-
ADVISOR_CONFIG.steeringEnabled &&
|
|
24
|
-
advisorEnabledForProfile(ctx.modelProfile)
|
|
25
|
-
) {
|
|
26
|
-
ctx.systemPrompt = appendSteering(ctx.systemPrompt);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export default preModelCall;
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `user-prompt-submit` hook: seed the per-conversation capture at the start of a
|
|
3
|
-
* user turn with the inbound history, so an advisor call on the very first model
|
|
4
|
-
* turn still has context even before `post-model-call` snapshots the running
|
|
5
|
-
* transcript.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import type {
|
|
9
|
-
HookFunction,
|
|
10
|
-
UserPromptSubmitContext,
|
|
11
|
-
} from "@vellumai/plugin-api";
|
|
12
|
-
|
|
13
|
-
import { seedCapture } from "../advisor-state-store.js";
|
|
14
|
-
|
|
15
|
-
const userPromptSubmit: HookFunction<UserPromptSubmitContext> = async (ctx) => {
|
|
16
|
-
seedCapture(ctx.conversationId, ctx.latestMessages);
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export default userPromptSubmit;
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "default-advisor",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "First-party default plugin that adds an `advisor` tool: a no-argument tool the model calls mid-task to consult a stronger inference profile on the full conversation transcript for strategic guidance, routed through the assistant's own inference.",
|
|
5
|
-
"private": true,
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"type": "module",
|
|
8
|
-
"engines": {
|
|
9
|
-
"node": ">=20.12.0"
|
|
10
|
-
},
|
|
11
|
-
"peerDependencies": {
|
|
12
|
-
"@vellumai/plugin-api": "^0.8.0"
|
|
13
|
-
}
|
|
14
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `advisor` tool — a no-argument tool the model calls to consult a stronger
|
|
3
|
-
* model for strategic guidance. The model supplies no input; the plugin reads
|
|
4
|
-
* the transcript captured by the lifecycle hooks and runs the consult, routed
|
|
5
|
-
* through the assistant's own inference.
|
|
6
|
-
*
|
|
7
|
-
* Default export = the tool definition. `defaults/index.ts` finalizes it and
|
|
8
|
-
* attaches it to the advisor plugin's `tools` array, which `bootstrapPlugins`
|
|
9
|
-
* registers into the model-visible tool catalog.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import type {
|
|
13
|
-
ToolContext,
|
|
14
|
-
ToolDefinition,
|
|
15
|
-
ToolExecutionResult,
|
|
16
|
-
} from "@vellumai/plugin-api";
|
|
17
|
-
import { RiskLevel } from "@vellumai/plugin-api";
|
|
18
|
-
|
|
19
|
-
import { advisorEnabledForProfile } from "../advisor-gate.js";
|
|
20
|
-
import { getCapture } from "../advisor-state-store.js";
|
|
21
|
-
import { consultAdvisor } from "../consult.js";
|
|
22
|
-
import { buildAdvisorContext } from "../context-pack.js";
|
|
23
|
-
|
|
24
|
-
const advisorTool: ToolDefinition = {
|
|
25
|
-
name: "advisor",
|
|
26
|
-
description:
|
|
27
|
-
"Consult a stronger advisor model to shape your plan and get strategic guidance. " +
|
|
28
|
-
"Takes NO parameters — your full conversation (the task, every tool call, and every " +
|
|
29
|
-
"result) is forwarded automatically, along with your available tools and skills, the " +
|
|
30
|
-
"workspace/project context, and relevant memory. Call it BEFORE you start building: it " +
|
|
31
|
-
"can lay out a plan when you don't have one yet, or review and sharpen the plan you've " +
|
|
32
|
-
"already drafted. Also call it when you're stuck, when weighing a change in approach, and " +
|
|
33
|
-
"once before declaring a task complete. It runs on its own — if you call it alongside " +
|
|
34
|
-
"other tools, those are held back until you've seen its guidance. Give its guidance " +
|
|
35
|
-
"serious weight.",
|
|
36
|
-
input_schema: { type: "object", properties: {}, additionalProperties: false },
|
|
37
|
-
// Read-only advice; low risk so the consult isn't gated behind a prompt.
|
|
38
|
-
defaultRiskLevel: RiskLevel.Low,
|
|
39
|
-
// Runs alone in its turn: the loop defers any sibling tool calls so the model
|
|
40
|
-
// incorporates the advisor's guidance before acting on anything else.
|
|
41
|
-
exclusive: true,
|
|
42
|
-
async execute(
|
|
43
|
-
_input: Record<string, unknown>,
|
|
44
|
-
ctx: ToolContext,
|
|
45
|
-
): Promise<ToolExecutionResult> {
|
|
46
|
-
// Defense-in-depth: the steering is already gated per profile, but a model
|
|
47
|
-
// could still call the tool. Honor the chat profile this turn runs under —
|
|
48
|
-
// the per-turn override (per-conversation / profile-session) when present,
|
|
49
|
-
// else the workspace active profile.
|
|
50
|
-
if (!advisorEnabledForProfile(ctx.overrideProfile ?? null)) {
|
|
51
|
-
return {
|
|
52
|
-
content: "(advisor is disabled for the active profile)",
|
|
53
|
-
isError: false,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
const capture = getCapture(ctx.conversationId);
|
|
58
|
-
const messages = capture?.messages ?? [];
|
|
59
|
-
// Gather the agent's situational context (tools, skills, workspace,
|
|
60
|
-
// memory) so the advisor reasons with the same awareness the agent has.
|
|
61
|
-
// Best-effort: a failure here must not block the consult.
|
|
62
|
-
const runtimeContext = await buildAdvisorContext({
|
|
63
|
-
conversationId: ctx.conversationId,
|
|
64
|
-
workingDir: ctx.workingDir,
|
|
65
|
-
allowedToolNames: ctx.allowedToolNames,
|
|
66
|
-
// Per-turn trust snapshot — gates personal-memory surfaces off the same
|
|
67
|
-
// values the executor captured for this invocation, not live state.
|
|
68
|
-
trustClass: ctx.trustClass,
|
|
69
|
-
sourceChannel: ctx.executionChannel,
|
|
70
|
-
transcript: messages,
|
|
71
|
-
signal: ctx.signal,
|
|
72
|
-
}).catch(() => null);
|
|
73
|
-
const advice = await consultAdvisor({
|
|
74
|
-
systemPrompt: capture?.systemPrompt ?? null,
|
|
75
|
-
messages,
|
|
76
|
-
runtimeContext,
|
|
77
|
-
signal: ctx.signal,
|
|
78
|
-
// Stream the advisor's guidance live as it generates: each delta is
|
|
79
|
-
// surfaced as a `tool_output_chunk` for this tool call and rendered in
|
|
80
|
-
// the tool-detail drawer. The complete text is still returned below.
|
|
81
|
-
onText: (chunk) => ctx.onOutput?.(chunk),
|
|
82
|
-
});
|
|
83
|
-
return { content: advice, isError: false };
|
|
84
|
-
} catch (err) {
|
|
85
|
-
// Degrade like the advisor tool: never fail the turn over a consult.
|
|
86
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
87
|
-
return { content: `(advisor unavailable: ${reason})`, isError: false };
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
export default advisorTool;
|