@vellumai/assistant 0.10.2-dev.202606251104.36cd100 → 0.10.2-dev.202606251257.2eba8a4

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.
Files changed (41) hide show
  1. package/package.json +1 -1
  2. package/src/__tests__/plugin-bootstrap.test.ts +5 -5
  3. package/src/__tests__/plugin-route-contribution.test.ts +2 -2
  4. package/src/__tests__/plugin-tool-contribution.test.ts +2 -2
  5. package/src/__tests__/plugin-types.test.ts +2 -2
  6. package/src/daemon/external-plugins-bootstrap.ts +5 -5
  7. package/src/hooks/hook-loader.ts +13 -13
  8. package/src/plugin-api/index.ts +6 -6
  9. package/src/plugin-api/types.ts +9 -9
  10. package/src/plugins/defaults/advisor/hooks/post-model-call.ts +2 -2
  11. package/src/plugins/defaults/advisor/hooks/pre-model-call.ts +2 -2
  12. package/src/plugins/defaults/advisor/hooks/user-prompt-submit.ts +2 -2
  13. package/src/plugins/defaults/empty-response/hooks/post-model-call.ts +2 -2
  14. package/src/plugins/defaults/empty-response/hooks/stop.ts +2 -2
  15. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +2 -2
  16. package/src/plugins/defaults/history-repair/hooks/post-model-call.ts +2 -2
  17. package/src/plugins/defaults/history-repair/hooks/stop.ts +2 -2
  18. package/src/plugins/defaults/history-repair/hooks/user-prompt-submit.ts +2 -2
  19. package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +2 -2
  20. package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +2 -2
  21. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +2 -2
  22. package/src/plugins/defaults/image-recovery/hooks/stop.ts +2 -2
  23. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +2 -2
  24. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +2 -2
  25. package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +2 -2
  26. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +2 -2
  27. package/src/plugins/defaults/memory-v3-shadow/hooks/post-compact.ts +2 -2
  28. package/src/plugins/defaults/memory-v3-shadow/hooks/user-prompt-submit.ts +2 -2
  29. package/src/plugins/defaults/surface-completion-nudge/hooks/post-model-call.ts +2 -2
  30. package/src/plugins/defaults/surface-completion-nudge/hooks/stop.ts +2 -2
  31. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +2 -2
  32. package/src/plugins/defaults/title-generate/hooks/stop.ts +2 -2
  33. package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +2 -2
  34. package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +2 -2
  35. package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +2 -2
  36. package/src/plugins/external-plugin-loader.ts +2 -2
  37. package/src/plugins/mtime-cache.ts +5 -8
  38. package/src/plugins/pipeline.ts +2 -2
  39. package/src/plugins/registry.ts +5 -5
  40. package/src/plugins/types.ts +7 -7
  41. package/src/tools/types.ts +114 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.10.2-dev.202606251104.36cd100",
3
+ "version": "0.10.2-dev.202606251257.2eba8a4",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -2,7 +2,7 @@
2
2
  * Tests for plugin bootstrap (PR 14).
3
3
  *
4
4
  * Covers:
5
- * - A noop `init()` fires with a valid `PluginInitContext` that exposes every
5
+ * - A noop `init()` fires with a valid `InitContext` that exposes every
6
6
  * documented field.
7
7
  * - Version-mismatch registration fails with an error that names the plugin
8
8
  * (the registry enforces this at `registerPlugin` time, so bootstrap never
@@ -29,7 +29,7 @@ import {
29
29
  registerPlugin,
30
30
  resetPluginRegistryForTests,
31
31
  } from "../plugins/registry.js";
32
- import { type Plugin, type PluginInitContext } from "../plugins/types.js";
32
+ import { type InitContext, type Plugin } from "../plugins/types.js";
33
33
  import { APP_VERSION } from "../version.js";
34
34
  import { setOverridesForTesting } from "./feature-flag-test-helpers.js";
35
35
 
@@ -51,7 +51,7 @@ function buildPlugin(
51
51
  name: string,
52
52
  extras: Partial<Omit<Plugin, "manifest" | "hooks">> & {
53
53
  hooks?: Plugin["hooks"];
54
- init?: (ctx: PluginInitContext) => Promise<void>;
54
+ init?: (ctx: InitContext) => Promise<void>;
55
55
  onShutdown?: () => Promise<void>;
56
56
  } = {},
57
57
  options: {
@@ -98,8 +98,8 @@ describe("plugin bootstrap", () => {
98
98
  await rm(TEST_WORKSPACE_DIR, { recursive: true, force: true });
99
99
  });
100
100
 
101
- test("noop plugin: init fires with a fully-populated PluginInitContext", async () => {
102
- let received: PluginInitContext | undefined;
101
+ test("noop plugin: init fires with a fully-populated InitContext", async () => {
102
+ let received: InitContext | undefined;
103
103
  const plugin: Plugin = buildPlugin("alpha", {
104
104
  async init(ctx) {
105
105
  received = ctx;
@@ -38,7 +38,7 @@ import {
38
38
  registerPlugin,
39
39
  resetPluginRegistryForTests,
40
40
  } from "../plugins/registry.js";
41
- import type { Plugin, PluginInitContext } from "../plugins/types.js";
41
+ import type { InitContext, Plugin } from "../plugins/types.js";
42
42
  import {
43
43
  matchSkillRoute,
44
44
  resetSkillRoutesForTests,
@@ -64,7 +64,7 @@ function buildPlugin(
64
64
  name: string,
65
65
  extras: Partial<Omit<Plugin, "manifest" | "hooks">> & {
66
66
  hooks?: Plugin["hooks"];
67
- init?: (ctx: PluginInitContext) => Promise<void>;
67
+ init?: (ctx: InitContext) => Promise<void>;
68
68
  onShutdown?: () => Promise<void>;
69
69
  } = {},
70
70
  ): Plugin {
@@ -33,7 +33,7 @@ import {
33
33
  registerPlugin,
34
34
  resetPluginRegistryForTests,
35
35
  } from "../plugins/registry.js";
36
- import type { Plugin, PluginInitContext } from "../plugins/types.js";
36
+ import type { InitContext, Plugin } from "../plugins/types.js";
37
37
  import {
38
38
  __clearRegistryForTesting,
39
39
  __resetRegistryForTesting,
@@ -82,7 +82,7 @@ function buildPlugin(
82
82
  name: string,
83
83
  extras: Partial<Omit<Plugin, "manifest" | "hooks">> & {
84
84
  hooks?: Plugin["hooks"];
85
- init?: (ctx: PluginInitContext) => Promise<void>;
85
+ init?: (ctx: InitContext) => Promise<void>;
86
86
  onShutdown?: () => Promise<void>;
87
87
  } = {},
88
88
  ): Plugin {
@@ -13,9 +13,9 @@ import { describe, expect, test } from "bun:test";
13
13
  import type { TrustContext } from "../daemon/trust-context.js";
14
14
  import { RiskLevel } from "../permissions/types.js";
15
15
  import {
16
+ type InitContext,
16
17
  type Plugin,
17
18
  PluginExecutionError,
18
- type PluginInitContext,
19
19
  type PluginManifest,
20
20
  type TurnContext,
21
21
  } from "../plugins/types.js";
@@ -57,7 +57,7 @@ describe("plugin core types", () => {
57
57
  const plugin = {
58
58
  manifest,
59
59
  hooks: {
60
- async init(ctx: PluginInitContext) {
60
+ async init(ctx: InitContext) {
61
61
  // Touch every field so refactors that rename any of them break here.
62
62
  void ctx.config;
63
63
  void ctx.logger;
@@ -24,7 +24,7 @@
24
24
  * (Zod schemas with `.parse()` are supported; anything else is passed
25
25
  * through untouched).
26
26
  * 5. Creates `<workspaceDir>/plugins-data/<plugin>/` on demand for per-plugin
27
- * writable state and exposes it via {@link PluginInitContext.pluginStorageDir}.
27
+ * writable state and exposes it via {@link InitContext.pluginStorageDir}.
28
28
  * 6. For each surviving plugin, registers its contributed tools and routes
29
29
  * into their global registries via {@link registerPluginTools} and
30
30
  * {@link registerSkillRoute}. Contributions land BEFORE `init()` so
@@ -64,7 +64,7 @@ import { getRegisteredPlugins, unregisterPlugin } from "../plugins/registry.js";
64
64
  import {
65
65
  type Plugin,
66
66
  PluginExecutionError,
67
- type PluginShutdownContext,
67
+ type ShutdownContext,
68
68
  } from "../plugins/types.js";
69
69
  import { loadUserPlugins } from "../plugins/user-loader.js";
70
70
  import {
@@ -222,8 +222,8 @@ export async function bootstrapPlugins(): Promise<void> {
222
222
  // Shutdown context is identical for every plugin in this boot — construct
223
223
  // once and reuse across the per-plugin teardown and the normal shutdown
224
224
  // hook below. Only `assistantVersion` is exposed today; future additions
225
- // live on {@link PluginShutdownContext}.
226
- const shutdownContext: PluginShutdownContext = {
225
+ // live on {@link ShutdownContext}.
226
+ const shutdownContext: ShutdownContext = {
227
227
  assistantVersion: APP_VERSION,
228
228
  };
229
229
 
@@ -407,7 +407,7 @@ async function initializePlugin(
407
407
  async function teardownPlugin(
408
408
  active: ActivePlugin,
409
409
  reason: string,
410
- shutdownContext: PluginShutdownContext,
410
+ shutdownContext: ShutdownContext,
411
411
  ): Promise<void> {
412
412
  const { plugin, routeHandles } = active;
413
413
  const name = plugin.manifest.name;
@@ -25,9 +25,9 @@ import { join } from "node:path";
25
25
  import { getConfig } from "../config/loader.js";
26
26
  import { HOOKS } from "../plugin-api/constants.js";
27
27
  import type {
28
- PluginHookFn,
29
- PluginInitContext,
30
- PluginShutdownContext,
28
+ HookFunction,
29
+ InitContext,
30
+ ShutdownContext,
31
31
  } from "../plugin-api/types.js";
32
32
  import { listSurfaceDir } from "../plugins/external-plugin-loader.js";
33
33
  import { getMtime, importWithTimeout } from "../plugins/surface-import.js";
@@ -53,7 +53,7 @@ export const WORKSPACE_HOOKS_OWNER = "__workspace__";
53
53
  * mtime changes, the hook is re-imported and the entry is replaced.
54
54
  */
55
55
  interface CachedHook {
56
- readonly hook: PluginHookFn;
56
+ readonly hook: HookFunction;
57
57
  /** mtimeMs of the source file this hook was imported from. */
58
58
  readonly sourceMtime: number;
59
59
  }
@@ -83,7 +83,7 @@ async function resolveCachedHook<TCtx>(
83
83
  ownerName: string,
84
84
  hookName: string,
85
85
  filePath: string,
86
- ): Promise<PluginHookFn<TCtx> | undefined> {
86
+ ): Promise<HookFunction<TCtx> | undefined> {
87
87
  const key = hookKey(ownerName, hookName);
88
88
  const currentMtime = getMtime(filePath);
89
89
 
@@ -94,7 +94,7 @@ async function resolveCachedHook<TCtx>(
94
94
  cached.sourceMtime === currentMtime &&
95
95
  currentMtime > 0
96
96
  ) {
97
- return cached.hook as PluginHookFn<TCtx>;
97
+ return cached.hook as HookFunction<TCtx>;
98
98
  }
99
99
 
100
100
  // Cache miss — re-import.
@@ -105,7 +105,7 @@ async function resolveCachedHook<TCtx>(
105
105
  }
106
106
 
107
107
  try {
108
- const hook = await importWithTimeout<PluginHookFn>(filePath);
108
+ const hook = await importWithTimeout<HookFunction>(filePath);
109
109
  if (hook === undefined || typeof hook !== "function") {
110
110
  log.error(
111
111
  { plugin: ownerName, hook: hookName, path: filePath },
@@ -114,7 +114,7 @@ async function resolveCachedHook<TCtx>(
114
114
  return undefined;
115
115
  }
116
116
  hookCache.set(key, { hook, sourceMtime: currentMtime });
117
- return hook as PluginHookFn<TCtx>;
117
+ return hook as HookFunction<TCtx>;
118
118
  } catch (err) {
119
119
  log.error(
120
120
  { err, plugin: ownerName, hook: hookName, path: filePath },
@@ -141,8 +141,8 @@ async function resolveCachedHook<TCtx>(
141
141
  export async function collectUserHooks<TCtx = unknown>(
142
142
  hookName: string,
143
143
  pluginDirs: Iterable<readonly [string, string]>,
144
- ): Promise<PluginHookFn<TCtx>[]> {
145
- const out: PluginHookFn<TCtx>[] = [];
144
+ ): Promise<HookFunction<TCtx>[]> {
145
+ const out: HookFunction<TCtx>[] = [];
146
146
 
147
147
  for (const [pluginDir, pluginName] of pluginDirs) {
148
148
  const hookFile = listSurfaceDir(join(pluginDir, "hooks")).find(
@@ -191,7 +191,7 @@ export async function preImportHooksDir(
191
191
  if (currentMtime === 0) continue;
192
192
 
193
193
  try {
194
- const hook = await importWithTimeout<PluginHookFn>(file.path);
194
+ const hook = await importWithTimeout<HookFunction>(file.path);
195
195
  if (hook !== undefined && typeof hook === "function") {
196
196
  hookCache.set(key, { hook, sourceMtime: currentMtime });
197
197
  }
@@ -233,7 +233,7 @@ export async function runInitHook(ownerName: string): Promise<void> {
233
233
  if (initHookEntry === undefined) return;
234
234
 
235
235
  try {
236
- const initContext: PluginInitContext = {
236
+ const initContext: InitContext = {
237
237
  config: getConfig().plugins?.[ownerName],
238
238
  logger: log.child({ plugin: ownerName }),
239
239
  pluginStorageDir: ensureHookStorageDir(ownerName),
@@ -256,7 +256,7 @@ export async function runInitHook(ownerName: string): Promise<void> {
256
256
  */
257
257
  export async function runShutdownHook(
258
258
  ownerName: string,
259
- context: PluginShutdownContext,
259
+ context: ShutdownContext,
260
260
  reason: string,
261
261
  ): Promise<void> {
262
262
  const shutdownHookEntry = hookCache.get(hookKey(ownerName, HOOKS.SHUTDOWN));
@@ -37,8 +37,8 @@
37
37
  * (optionally overriding the profile) and run inference through the
38
38
  * workspace's configured profiles and credentials — no plugin-supplied API key
39
39
  *
40
- * - {@link PluginInitContext} — passed to `init` hook at bootstrap
41
- * - {@link PluginShutdownContext} — passed to `shutdown` hook at teardown
40
+ * - {@link InitContext} — passed to `init` hook at bootstrap
41
+ * - {@link ShutdownContext} — passed to `shutdown` hook at teardown
42
42
  * - {@link UserPromptSubmitContext} — passed to `user-prompt-submit` hook,
43
43
  * fired immediately before the agent loop receives a user's prompt
44
44
  * - {@link PostCompactContext} — passed to `post-compact` hook, fired after
@@ -55,7 +55,7 @@
55
55
  * - {@link PostModelCallContext} — passed to `post-model-call` hook, fired at
56
56
  * every model-call outcome (a finalized reply or a provider rejection) to
57
57
  * transform content and decide whether to retry
58
- * - {@link PluginHookFn} — signature every lifecycle hook implements
58
+ * - {@link HookFunction} — signature every lifecycle hook implements
59
59
  * - {@link PluginLogger} — pino-compatible logger shape on the contexts
60
60
  * - {@link ToolDefinition} — author-facing tool spec (default-export shape
61
61
  * for both plugin tool files and workspace tool files)
@@ -98,16 +98,16 @@ export type {
98
98
  export type { LLMCallSite } from "../config/schemas/llm.js";
99
99
  export type {
100
100
  AgentLoopExitReason,
101
+ HookFunction,
102
+ InitContext,
101
103
  ModelProfileInfo,
102
- PluginHookFn,
103
- PluginInitContext,
104
104
  PluginLogger,
105
- PluginShutdownContext,
106
105
  PostCompactContext,
107
106
  PostModelCallContext,
108
107
  PostModelCallDecision,
109
108
  PostToolUseContext,
110
109
  PreModelCallContext,
110
+ ShutdownContext,
111
111
  StopContext,
112
112
  ToolContext,
113
113
  ToolDefinition,
@@ -57,15 +57,15 @@ export interface PluginLogger {
57
57
  * empty model that with `| null`, not `| undefined`.
58
58
  *
59
59
  * Each known hook key has a documented context shape:
60
- * - `init` — {@link PluginInitContext}
61
- * - `shutdown` — {@link PluginShutdownContext}
60
+ * - `init` — {@link InitContext}
61
+ * - `shutdown` — {@link ShutdownContext}
62
62
  * - `user-prompt-submit` — {@link UserPromptSubmitContext}
63
63
  * - `pre-model-call` — {@link PreModelCallContext}
64
64
  * - `post-tool-use` — {@link PostToolUseContext}
65
65
  * - `stop` — {@link StopContext}
66
66
  * - `post-model-call` — {@link PostModelCallContext}
67
67
  */
68
- export type PluginHookFn<TCtx = unknown> = (
68
+ export type HookFunction<TCtx = unknown> = (
69
69
  ctx: TCtx,
70
70
  ) => Promise<Partial<TCtx> | void>;
71
71
 
@@ -76,7 +76,7 @@ export type PluginHookFn<TCtx = unknown> = (
76
76
  * config, a pino-compatible logger scoped to the plugin, a per-plugin
77
77
  * writable data directory, and the assistant's version metadata.
78
78
  */
79
- export interface PluginInitContext {
79
+ export interface InitContext {
80
80
  /** Parsed config for this plugin (may be `unknown` until the manifest validates). */
81
81
  config: unknown;
82
82
  /** Pino-compatible child logger bound to `{ plugin: <name> }`. */
@@ -125,7 +125,7 @@ export interface ModelProfileInfo {
125
125
 
126
126
  /**
127
127
  * Context passed to the `shutdown` hook during daemon teardown. Kept
128
- * intentionally narrower than {@link PluginInitContext} — most teardown
128
+ * intentionally narrower than {@link InitContext} — most teardown
129
129
  * paths only need to know which assistant version they're shutting
130
130
  * down against (e.g. for version-conditional cleanup of state files
131
131
  * written by a previous boot).
@@ -135,7 +135,7 @@ export interface ModelProfileInfo {
135
135
  * stash a version stamp at init can compare against the same name on
136
136
  * tear-down without keeping their own copy.
137
137
  */
138
- export interface PluginShutdownContext {
138
+ export interface ShutdownContext {
139
139
  /** Assistant semver for compatibility checks inside the plugin. */
140
140
  assistantVersion: string;
141
141
  }
@@ -151,7 +151,7 @@ export interface PluginShutdownContext {
151
151
  *
152
152
  * The hook may transform `latestMessages` either by mutating it in place
153
153
  * (`push` / `splice` / `length = 0`) or by returning a new context with
154
- * a fresh `latestMessages` array — see {@link PluginHookFn}'s polymorphic
154
+ * a fresh `latestMessages` array — see {@link HookFunction}'s polymorphic
155
155
  * return shape. The daemon threads the final `latestMessages` value into
156
156
  * `agentLoop.run()` as the run-messages argument.
157
157
  *
@@ -244,7 +244,7 @@ export interface UserPromptSubmitContext {
244
244
  * their own injected context the same way.
245
245
  *
246
246
  * The hook re-injects by mutating `history` in place (or returning a new
247
- * context with a replacement `history`) — see {@link PluginHookFn}'s
247
+ * context with a replacement `history`) — see {@link HookFunction}'s
248
248
  * polymorphic return shape. The agent loop reads the settled `history` back off
249
249
  * the context and resumes the turn from it. Multiple plugins' hooks chain in
250
250
  * registration order, each seeing the previous plugin's edits.
@@ -301,7 +301,7 @@ export interface PostCompactContext {
301
301
  *
302
302
  * The hook may transform the result either by mutating `toolResponse` in
303
303
  * place (e.g. reassigning `toolResponse.content`) or by returning a new
304
- * context with a fresh `toolResponse` — see {@link PluginHookFn}'s
304
+ * context with a fresh `toolResponse` — see {@link HookFunction}'s
305
305
  * polymorphic return shape. The daemon threads the final `toolResponse`
306
306
  * into the provider-bound history.
307
307
  *
@@ -9,11 +9,11 @@
9
9
  * advisor's own `inference`-call-site sub-call — are ignored.
10
10
  */
11
11
 
12
- import type { PluginHookFn, PostModelCallContext } from "@vellumai/plugin-api";
12
+ import type { HookFunction, PostModelCallContext } from "@vellumai/plugin-api";
13
13
 
14
14
  import { recordMessages } from "../advisor-state-store.js";
15
15
 
16
- const postModelCall: PluginHookFn<PostModelCallContext> = async (ctx) => {
16
+ const postModelCall: HookFunction<PostModelCallContext> = async (ctx) => {
17
17
  if (ctx.callSite !== "mainAgent") return;
18
18
  if (ctx.error) return;
19
19
  // `ctx.messages` is the pre-reply history; the turn the model just produced —
@@ -5,14 +5,14 @@
5
5
  * tool. Idempotent via the steering marker.
6
6
  */
7
7
 
8
- import type { PluginHookFn, PreModelCallContext } from "@vellumai/plugin-api";
8
+ import type { HookFunction, PreModelCallContext } from "@vellumai/plugin-api";
9
9
 
10
10
  import { advisorEnabledForProfile } from "../advisor-gate.js";
11
11
  import { recordSystemPrompt } from "../advisor-state-store.js";
12
12
  import { ADVISOR_CONFIG } from "../config.js";
13
13
  import { appendSteering, stripSteering } from "../steering.js";
14
14
 
15
- const preModelCall: PluginHookFn<PreModelCallContext> = async (ctx) => {
15
+ const preModelCall: HookFunction<PreModelCallContext> = async (ctx) => {
16
16
  if (ctx.callSite !== "mainAgent") return;
17
17
 
18
18
  recordSystemPrompt(ctx.conversationId, stripSteering(ctx.systemPrompt));
@@ -6,13 +6,13 @@
6
6
  */
7
7
 
8
8
  import type {
9
- PluginHookFn,
9
+ HookFunction,
10
10
  UserPromptSubmitContext,
11
11
  } from "@vellumai/plugin-api";
12
12
 
13
13
  import { seedCapture } from "../advisor-state-store.js";
14
14
 
15
- const userPromptSubmit: PluginHookFn<UserPromptSubmitContext> = async (ctx) => {
15
+ const userPromptSubmit: HookFunction<UserPromptSubmitContext> = async (ctx) => {
16
16
  seedCapture(ctx.conversationId, ctx.latestMessages);
17
17
  };
18
18
 
@@ -46,7 +46,7 @@
46
46
  * ignores the decision — so the hook returns early for both.
47
47
  */
48
48
 
49
- import type { PluginHookFn, PostModelCallContext } from "@vellumai/plugin-api";
49
+ import type { HookFunction, PostModelCallContext } from "@vellumai/plugin-api";
50
50
 
51
51
  import type { ContentBlock, Message } from "../../../../providers/types.js";
52
52
  import {
@@ -112,7 +112,7 @@ function currentCycleMessages(
112
112
  return messages;
113
113
  }
114
114
 
115
- const postModelCall: PluginHookFn<PostModelCallContext> = async (ctx) => {
115
+ const postModelCall: HookFunction<PostModelCallContext> = async (ctx) => {
116
116
  // A provider rejection carries no turn content to assess (a recovery hook
117
117
  // owns the rejection); the sibling `stop` hook clears the mark when the turn
118
118
  // terminates.
@@ -11,11 +11,11 @@
11
11
  * refused).
12
12
  */
13
13
 
14
- import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
14
+ import type { HookFunction, StopContext } from "@vellumai/plugin-api";
15
15
 
16
16
  import { clearEmptyResponseNudged } from "../nudge-state-store.js";
17
17
 
18
- const stop: PluginHookFn<StopContext> = async (ctx) => {
18
+ const stop: HookFunction<StopContext> = async (ctx) => {
19
19
  clearEmptyResponseNudged(ctx.conversationId);
20
20
  };
21
21
 
@@ -55,7 +55,7 @@
55
55
  * per-tool-result hot path.
56
56
  */
57
57
 
58
- import type { PluginHookFn, PostToolUseContext } from "@vellumai/plugin-api";
58
+ import type { HookFunction, PostToolUseContext } from "@vellumai/plugin-api";
59
59
 
60
60
  import type { Message } from "../../../../providers/types.js";
61
61
 
@@ -233,7 +233,7 @@ function currentCallRepeatCount(
233
233
  return count;
234
234
  }
235
235
 
236
- const postToolUse: PluginHookFn<PostToolUseContext> = async (ctx) => {
236
+ const postToolUse: HookFunction<PostToolUseContext> = async (ctx) => {
237
237
  const usesById = toolUsesById(ctx.messages);
238
238
  const currentUse = usesById.get(ctx.toolResponse.tool_use_id);
239
239
  if (currentUse === undefined || !EXPLORATION_TOOL_NAMES.has(currentUse.name))
@@ -23,7 +23,7 @@
23
23
  * empty-response hook; only a provider rejection is this hook's to act on.
24
24
  */
25
25
 
26
- import type { PluginHookFn, PostModelCallContext } from "@vellumai/plugin-api";
26
+ import type { HookFunction, PostModelCallContext } from "@vellumai/plugin-api";
27
27
 
28
28
  import {
29
29
  isOrderingRepairAttempted,
@@ -31,7 +31,7 @@ import {
31
31
  } from "../repair-state-store.js";
32
32
  import { deepRepairHistory, isRepairableOrderingError } from "../terminal.js";
33
33
 
34
- const postModelCall: PluginHookFn<PostModelCallContext> = async (ctx) => {
34
+ const postModelCall: HookFunction<PostModelCallContext> = async (ctx) => {
35
35
  if (
36
36
  ctx.error &&
37
37
  isRepairableOrderingError(ctx.error.message) &&
@@ -11,11 +11,11 @@
11
11
  * retry the loop's per-run backstop refused).
12
12
  */
13
13
 
14
- import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
14
+ import type { HookFunction, StopContext } from "@vellumai/plugin-api";
15
15
 
16
16
  import { clearOrderingRepairAttempted } from "../repair-state-store.js";
17
17
 
18
- const stop: PluginHookFn<StopContext> = async (ctx) => {
18
+ const stop: HookFunction<StopContext> = async (ctx) => {
19
19
  clearOrderingRepairAttempted(ctx.conversationId);
20
20
  };
21
21
 
@@ -10,13 +10,13 @@
10
10
  */
11
11
 
12
12
  import type {
13
- PluginHookFn,
13
+ HookFunction,
14
14
  UserPromptSubmitContext,
15
15
  } from "@vellumai/plugin-api";
16
16
 
17
17
  import { repairHistory } from "../terminal.js";
18
18
 
19
- const userPromptSubmit: PluginHookFn<UserPromptSubmitContext> = async (ctx) => {
19
+ const userPromptSubmit: HookFunction<UserPromptSubmitContext> = async (ctx) => {
20
20
  const { messages, stats } = repairHistory(ctx.latestMessages);
21
21
  ctx.latestMessages = messages;
22
22
  if (
@@ -17,14 +17,14 @@
17
17
 
18
18
  import {
19
19
  doesSupportVision,
20
- type PluginHookFn,
20
+ type HookFunction,
21
21
  type PostToolUseContext,
22
22
  } from "@vellumai/plugin-api";
23
23
 
24
24
  import { captionImageBlocks } from "../src/caption-blocks.js";
25
25
  import { findVisionProfile } from "../src/vision-caption.js";
26
26
 
27
- const postToolUse: PluginHookFn<PostToolUseContext> = async (ctx) => {
27
+ const postToolUse: HookFunction<PostToolUseContext> = async (ctx) => {
28
28
  // Cheapest gate first: bail unless the tool actually returned an image,
29
29
  // before touching the model catalog or resolving a vision profile.
30
30
  const blocks = ctx.toolResponse.contentBlocks;
@@ -23,7 +23,7 @@
23
23
  */
24
24
 
25
25
  import {
26
- type PluginHookFn,
26
+ type HookFunction,
27
27
  type UserPromptSubmitContext,
28
28
  } from "@vellumai/plugin-api";
29
29
 
@@ -33,7 +33,7 @@ import {
33
33
  } from "../src/caption-blocks.js";
34
34
  import { findVisionProfile } from "../src/vision-caption.js";
35
35
 
36
- const userPromptSubmit: PluginHookFn<UserPromptSubmitContext> = async (ctx) => {
36
+ const userPromptSubmit: HookFunction<UserPromptSubmitContext> = async (ctx) => {
37
37
  // If the turn's model already supports vision, nothing to do.
38
38
  if (!needsImageFallback(ctx.modelProfileKey)) return;
39
39
 
@@ -24,7 +24,7 @@
24
24
  * empty-response hook; only a provider rejection is this hook's to act on.
25
25
  */
26
26
 
27
- import type { PluginHookFn, PostModelCallContext } from "@vellumai/plugin-api";
27
+ import type { HookFunction, PostModelCallContext } from "@vellumai/plugin-api";
28
28
 
29
29
  import { isImageDimensionsTooLargeError } from "../detect.js";
30
30
  import {
@@ -36,7 +36,7 @@ import {
36
36
  recoverOversizedImages,
37
37
  } from "../recover.js";
38
38
 
39
- const postModelCall: PluginHookFn<PostModelCallContext> = async (ctx) => {
39
+ const postModelCall: HookFunction<PostModelCallContext> = async (ctx) => {
40
40
  if (
41
41
  ctx.error &&
42
42
  isImageDimensionsTooLargeError(ctx.error.message) &&
@@ -11,11 +11,11 @@
11
11
  * retry the loop's per-run backstop refused).
12
12
  */
13
13
 
14
- import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
14
+ import type { HookFunction, StopContext } from "@vellumai/plugin-api";
15
15
 
16
16
  import { clearImageRecoveryAttempted } from "../image-recovery-state-store.js";
17
17
 
18
- const stop: PluginHookFn<StopContext> = async (ctx) => {
18
+ const stop: HookFunction<StopContext> = async (ctx) => {
19
19
  clearImageRecoveryAttempted(ctx.conversationId);
20
20
  };
21
21
 
@@ -30,7 +30,7 @@
30
30
  * reject.
31
31
  */
32
32
 
33
- import type { PluginHookFn, PostModelCallContext } from "@vellumai/plugin-api";
33
+ import type { HookFunction, PostModelCallContext } from "@vellumai/plugin-api";
34
34
 
35
35
  import { isMaxTokensStopReason } from "../../../../agent/loop.js";
36
36
  import {
@@ -45,7 +45,7 @@ import {
45
45
  export const MAX_TOKENS_CONTINUE_NUDGE_TEXT =
46
46
  "<system_notice>Your previous response was cut off because it reached the maximum output length. Continue exactly where you stopped — do not repeat content you already sent and do not start over.</system_notice>";
47
47
 
48
- const postModelCall: PluginHookFn<PostModelCallContext> = async (ctx) => {
48
+ const postModelCall: HookFunction<PostModelCallContext> = async (ctx) => {
49
49
  if (ctx.error) return;
50
50
  if (!isMaxTokensStopReason(ctx.stopReason)) return;
51
51
  if (ctx.callSite !== "mainAgent") return;
@@ -9,11 +9,11 @@
9
9
  * budget, no matter how the turn ended.
10
10
  */
11
11
 
12
- import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
12
+ import type { HookFunction, StopContext } from "@vellumai/plugin-api";
13
13
 
14
14
  import { clearMaxTokensContinueBudget } from "../continue-state-store.js";
15
15
 
16
- const stop: PluginHookFn<StopContext> = async (ctx) => {
16
+ const stop: HookFunction<StopContext> = async (ctx) => {
17
17
  clearMaxTokensContinueBudget(ctx.conversationId);
18
18
  };
19
19
 
@@ -38,7 +38,7 @@
38
38
  * {@link PostCompactContext}.
39
39
  */
40
40
 
41
- import type { PluginHookFn, PostCompactContext } from "@vellumai/plugin-api";
41
+ import type { HookFunction, PostCompactContext } from "@vellumai/plugin-api";
42
42
 
43
43
  import { getConfig } from "../../../../config/loader.js";
44
44
  import { findConversationOrSubagent } from "../../../../daemon/conversation-registry.js";
@@ -53,7 +53,7 @@ import {
53
53
  import { getLiveGraphMemory } from "../../../../memory/graph/conversation-graph-memory.js";
54
54
  import { stripTailInjectionsForReinjection } from "../tail-reinjection-strip.js";
55
55
 
56
- const postCompact: PluginHookFn<PostCompactContext> = async (ctx) => {
56
+ const postCompact: HookFunction<PostCompactContext> = async (ctx) => {
57
57
  const {
58
58
  history,
59
59
  requestId,
@@ -28,7 +28,7 @@
28
28
  */
29
29
 
30
30
  import type {
31
- PluginHookFn,
31
+ HookFunction,
32
32
  UserPromptSubmitContext,
33
33
  } from "@vellumai/plugin-api";
34
34
 
@@ -264,7 +264,7 @@ function persistInjectionBlocks(
264
264
  * slower turn. Cancellation still works via `ctx.signal`, which is threaded
265
265
  * into `prepareMemory`.
266
266
  */
267
- const userPromptSubmitMemoryRetrieval: PluginHookFn<
267
+ const userPromptSubmitMemoryRetrieval: HookFunction<
268
268
  UserPromptSubmitContext
269
269
  > = async (ctx) => {
270
270
  // The conversation-scoped retrieval state — graph handle, abort signal, and
@@ -7,8 +7,8 @@
7
7
  * `../injector.js` and re-apply it here. Until then this hook does nothing.
8
8
  */
9
9
 
10
- import type { PluginHookFn } from "@vellumai/plugin-api";
10
+ import type { HookFunction } from "@vellumai/plugin-api";
11
11
 
12
- const postCompact: PluginHookFn = async () => {};
12
+ const postCompact: HookFunction = async () => {};
13
13
 
14
14
  export default postCompact;
@@ -10,10 +10,10 @@
10
10
  */
11
11
 
12
12
  import type {
13
- PluginHookFn,
13
+ HookFunction,
14
14
  UserPromptSubmitContext,
15
15
  } from "@vellumai/plugin-api";
16
16
 
17
- const userPromptSubmit: PluginHookFn<UserPromptSubmitContext> = async () => {};
17
+ const userPromptSubmit: HookFunction<UserPromptSubmitContext> = async () => {};
18
18
 
19
19
  export default userPromptSubmit;
@@ -45,7 +45,7 @@
45
45
  * the `post-model-call` chain — later hooks see (and may override) its decision.
46
46
  */
47
47
 
48
- import type { PluginHookFn, PostModelCallContext } from "@vellumai/plugin-api";
48
+ import type { HookFunction, PostModelCallContext } from "@vellumai/plugin-api";
49
49
 
50
50
  import type { ContentBlock, Message } from "../../../../providers/types.js";
51
51
  import {
@@ -246,7 +246,7 @@ function hasDanglingProgressSurface(messages: ReadonlyArray<Message>): boolean {
246
246
  return false;
247
247
  }
248
248
 
249
- const postModelCall: PluginHookFn<PostModelCallContext> = async (ctx) => {
249
+ const postModelCall: HookFunction<PostModelCallContext> = async (ctx) => {
250
250
  // A provider rejection carries no turn content to assess (a recovery hook
251
251
  // owns the rejection).
252
252
  if (ctx.error) return;
@@ -11,11 +11,11 @@
11
11
  * refused).
12
12
  */
13
13
 
14
- import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
14
+ import type { HookFunction, StopContext } from "@vellumai/plugin-api";
15
15
 
16
16
  import { clearSurfaceCompletionNudged } from "../nudge-state-store.js";
17
17
 
18
- const stop: PluginHookFn<StopContext> = async (ctx) => {
18
+ const stop: HookFunction<StopContext> = async (ctx) => {
19
19
  clearSurfaceCompletionNudged(ctx.conversationId);
20
20
  };
21
21
 
@@ -45,7 +45,7 @@
45
45
  * below it (a new turn restarts counting low).
46
46
  */
47
47
 
48
- import type { PluginHookFn, PostToolUseContext } from "@vellumai/plugin-api";
48
+ import type { HookFunction, PostToolUseContext } from "@vellumai/plugin-api";
49
49
 
50
50
  import type { ContentBlock, Message } from "../../../../providers/types.js";
51
51
  import { isWeakOpenModel } from "../../../../providers/weak-open-model.js";
@@ -134,7 +134,7 @@ function scanTurn(messages: ReadonlyArray<Message>): {
134
134
  return { rounds, taskProgressShown };
135
135
  }
136
136
 
137
- const postToolUse: PluginHookFn<PostToolUseContext> = async (ctx) => {
137
+ const postToolUse: HookFunction<PostToolUseContext> = async (ctx) => {
138
138
  if (!isWeakOpenModel(ctx.model)) return;
139
139
 
140
140
  const { rounds, taskProgressShown } = scanTurn(ctx.messages);
@@ -18,7 +18,7 @@
18
18
  * stateless and means a mid-run array rewrite (compaction) can't invalidate it.
19
19
  */
20
20
 
21
- import type { PluginHookFn, StopContext } from "@vellumai/plugin-api";
21
+ import type { HookFunction, StopContext } from "@vellumai/plugin-api";
22
22
 
23
23
  import { getConfig } from "../../../../config/loader.js";
24
24
  import { getConversation } from "../../../../memory/conversation-crud.js";
@@ -65,7 +65,7 @@ function shouldRetryFallbackTitle(conversation: {
65
65
  );
66
66
  }
67
67
 
68
- const stop: PluginHookFn<StopContext> = async (ctx) => {
68
+ const stop: HookFunction<StopContext> = async (ctx) => {
69
69
  // Re-title only at a genuine successful turn end (the model returned a reply
70
70
  // with no tool calls). Any other terminal — a provider rejection, abort, or
71
71
  // an output-limit cutoff — produced no new topic to re-title from.
@@ -11,14 +11,14 @@
11
11
  */
12
12
 
13
13
  import type {
14
- PluginHookFn,
14
+ HookFunction,
15
15
  UserPromptSubmitContext,
16
16
  } from "@vellumai/plugin-api";
17
17
 
18
18
  import { getConversation } from "../../../../memory/conversation-crud.js";
19
19
  import { queueGenerateConversationTitle } from "../../../../memory/conversation-title-service.js";
20
20
 
21
- const userPromptSubmit: PluginHookFn<UserPromptSubmitContext> = async (ctx) => {
21
+ const userPromptSubmit: HookFunction<UserPromptSubmitContext> = async (ctx) => {
22
22
  // System conversations (background/scheduled) carry a deterministic title
23
23
  // from bootstrap. Their own job prompts arrive as non-interactive turns and
24
24
  // must not spend an LLM call on a title nobody reads — only a genuine user
@@ -22,7 +22,7 @@
22
22
  * result for the tool resets its streak.
23
23
  */
24
24
 
25
- import type { PluginHookFn, PostToolUseContext } from "@vellumai/plugin-api";
25
+ import type { HookFunction, PostToolUseContext } from "@vellumai/plugin-api";
26
26
 
27
27
  import type { Message } from "../../../../providers/types.js";
28
28
 
@@ -85,7 +85,7 @@ function priorConsecutiveErrors(
85
85
  return streak;
86
86
  }
87
87
 
88
- const postToolUse: PluginHookFn<PostToolUseContext> = async (ctx) => {
88
+ const postToolUse: HookFunction<PostToolUseContext> = async (ctx) => {
89
89
  if (ctx.toolResponse.is_error !== true) return;
90
90
 
91
91
  const namesById = toolNamesById(ctx.messages);
@@ -8,11 +8,11 @@
8
8
  * The hook mutates `toolResponse.content` in place.
9
9
  */
10
10
 
11
- import type { PluginHookFn, PostToolUseContext } from "@vellumai/plugin-api";
11
+ import type { HookFunction, PostToolUseContext } from "@vellumai/plugin-api";
12
12
 
13
13
  import { truncateToolResult } from "../terminal.js";
14
14
 
15
- const postToolUse: PluginHookFn<PostToolUseContext> = async (ctx) => {
15
+ const postToolUse: HookFunction<PostToolUseContext> = async (ctx) => {
16
16
  const { content, truncated } = truncateToolResult(
17
17
  ctx.toolResponse.content,
18
18
  ctx.maxInputTokens,
@@ -54,8 +54,8 @@ import type { Tool, ToolDefinition } from "../tools/types.js";
54
54
  import { getLogger } from "../util/logger.js";
55
55
  import { registerPlugin } from "./registry.js";
56
56
  import type {
57
+ HookFunction,
57
58
  Plugin,
58
- PluginHookFn,
59
59
  PluginHooks,
60
60
  PluginManifest,
61
61
  } from "./types.js";
@@ -195,7 +195,7 @@ async function loadHooks(
195
195
  if (files.length === 0) return undefined;
196
196
  const hooks: PluginHooks = {};
197
197
  for (const { name, path } of files) {
198
- const fn = await importDefault<PluginHookFn>(path);
198
+ const fn = await importDefault<HookFunction>(path);
199
199
  if (typeof fn !== "function") {
200
200
  throw new Error(
201
201
  `external plugin ${pluginName}: hooks/${name} default export must be a function (got ${typeof fn})`,
@@ -35,10 +35,7 @@ import {
35
35
  runShutdownHook,
36
36
  WORKSPACE_HOOKS_OWNER,
37
37
  } from "../hooks/hook-loader.js";
38
- import type {
39
- PluginHookFn,
40
- PluginShutdownContext,
41
- } from "../plugin-api/types.js";
38
+ import type { HookFunction, ShutdownContext } from "../plugin-api/types.js";
42
39
  import {
43
40
  registerPluginTools,
44
41
  unregisterPluginTools,
@@ -60,9 +57,9 @@ import {
60
57
  setSurfaceImportTimeout,
61
58
  } from "./surface-import.js";
62
59
 
63
- // Re-export for type compat — consumers that import PluginHookFn from
60
+ // Re-export for type compat — consumers that import HookFunction from
64
61
  // the mtime cache module still resolve.
65
- export type { PluginHookFn } from "./types.js";
62
+ export type { HookFunction } from "./types.js";
66
63
 
67
64
  const log = getLogger("plugin-mtime-cache");
68
65
 
@@ -184,7 +181,7 @@ const disabledPluginDirs = new Set<string>();
184
181
  */
185
182
  export async function getUserHooksFor<TCtx = unknown>(
186
183
  hookName: string,
187
- ): Promise<PluginHookFn<TCtx>[]> {
184
+ ): Promise<HookFunction<TCtx>[]> {
188
185
  await scanPlugins();
189
186
  return collectUserHooks<TCtx>(hookName, discoveredPluginDirs);
190
187
  }
@@ -439,7 +436,7 @@ const activatedPlugins: Array<{ name: string }> = [];
439
436
  */
440
437
  const activatedNames = new Set<string>();
441
438
 
442
- const shutdownContext: PluginShutdownContext = {
439
+ const shutdownContext: ShutdownContext = {
443
440
  assistantVersion: APP_VERSION,
444
441
  };
445
442
 
@@ -19,7 +19,7 @@
19
19
  import type { HookName } from "../plugin-api/constants.js";
20
20
  import { getLogger } from "../util/logger.js";
21
21
  import { getHooksFor } from "./registry.js";
22
- import type { PluginHookFn } from "./types.js";
22
+ import type { HookFunction } from "./types.js";
23
23
 
24
24
  // ─── Hook runner ────────────────────────────────────────────────────────────
25
25
 
@@ -116,7 +116,7 @@ export async function runHook<TCtx>(
116
116
  name: HookName,
117
117
  initialCtx: TCtx,
118
118
  ): Promise<TCtx> {
119
- let hooks: PluginHookFn<TCtx>[];
119
+ let hooks: HookFunction<TCtx>[];
120
120
  try {
121
121
  hooks = await getHooksFor<TCtx>(name);
122
122
  } catch (err) {
@@ -21,9 +21,9 @@
21
21
 
22
22
  import { getUserHooksFor } from "./mtime-cache.js";
23
23
  import {
24
+ type HookFunction,
24
25
  type Plugin,
25
26
  PluginExecutionError,
26
- type PluginHookFn,
27
27
  } from "./types.js";
28
28
 
29
29
  // ─── Internal state ──────────────────────────────────────────────────────────
@@ -151,20 +151,20 @@ export function getRegisteredPlugins(): Plugin[] {
151
151
  * files. First-party default plugin hooks are read synchronously from the
152
152
  * registry and prepended.
153
153
  *
154
- * The `TCtx` generic mirrors {@link PluginHookFn}'s — callers parameterize
154
+ * The `TCtx` generic mirrors {@link HookFunction}'s — callers parameterize
155
155
  * over the concrete context type their hook receives. Hooks that mutate
156
156
  * the context in place return `void`; hooks that return a new context
157
157
  * replace the threaded value for the next hook in the chain.
158
158
  */
159
159
  export async function getHooksFor<TCtx = unknown>(
160
160
  name: string,
161
- ): Promise<PluginHookFn<TCtx>[]> {
161
+ ): Promise<HookFunction<TCtx>[]> {
162
162
  // First-party defaults from the registry (synchronous).
163
- const defaultHooks: PluginHookFn<TCtx>[] = [];
163
+ const defaultHooks: HookFunction<TCtx>[] = [];
164
164
  for (const plugin of registeredPlugins.values()) {
165
165
  const hook = plugin.hooks?.[name];
166
166
  if (hook) {
167
- defaultHooks.push(hook as PluginHookFn<TCtx>);
167
+ defaultHooks.push(hook as HookFunction<TCtx>);
168
168
  }
169
169
  }
170
170
 
@@ -19,7 +19,7 @@ import type {
19
19
  } from "../daemon/conversation-runtime-assembly.js";
20
20
  import type { TrustContext } from "../daemon/trust-context.js";
21
21
  import type { ConversationGraphMemory } from "../memory/graph/conversation-graph-memory.js";
22
- import type { PluginHookFn } from "../plugin-api/types.js";
22
+ import type { HookFunction } from "../plugin-api/types.js";
23
23
  import type { Message } from "../providers/types.js";
24
24
  import type { SkillRoute } from "../runtime/skill-route-registry.js";
25
25
  import type { Tool } from "../tools/types.js";
@@ -67,9 +67,9 @@ export interface PluginManifest {
67
67
  // existing internal call sites keep working. Plugin authors import these from
68
68
  // `@vellumai/plugin-api`.
69
69
  export type {
70
- PluginHookFn,
71
- PluginInitContext,
72
- PluginShutdownContext,
70
+ HookFunction,
71
+ InitContext,
72
+ ShutdownContext,
73
73
  } from "../plugin-api/types.js";
74
74
 
75
75
  // ─── Memory-graph result ─────────────────────────────────────────────────────
@@ -343,14 +343,14 @@ export type PluginRouteRegistration = SkillRoute;
343
343
  * unknown keys are forward-compat scaffolding.
344
344
  *
345
345
  * See `assistant/src/daemon/external-plugins-bootstrap.ts` for the
346
- * full lifecycle, and {@link PluginHookFn} for the per-entry signature.
346
+ * full lifecycle, and {@link HookFunction} for the per-entry signature.
347
347
  */
348
348
  // The map stores hooks for arbitrary keys with arbitrary context shapes.
349
349
  // `any` (rather than `unknown`) is required so concrete plugin signatures
350
- // like `(ctx: PluginInitContext) => Promise<void>` and `() => Promise<void>`
350
+ // like `(ctx: InitContext) => Promise<void>` and `() => Promise<void>`
351
351
  // both assign in/out of slot entries under strict-function-types contravariance.
352
352
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
353
- export type PluginHooks = Record<string, PluginHookFn<any>>;
353
+ export type PluginHooks = Record<string, HookFunction<any>>;
354
354
 
355
355
  /**
356
356
  * A registered plugin. Every field besides `manifest` is optional — a plugin
@@ -249,6 +249,26 @@ export function stringifyToolInput(input: Record<string, unknown>): string {
249
249
  }
250
250
  }
251
251
 
252
+ /**
253
+ * Runtime context passed as the second argument to every tool's `execute`.
254
+ *
255
+ * The fields fall into two groups:
256
+ *
257
+ * - A small, stable core that we are comfortable exposing to any tool —
258
+ * including workspace- and plugin-authored tools via `@vellumai/plugin-api`:
259
+ * `conversationId`, `workingDir`, `requestId`, `signal`, `onOutput`,
260
+ * `assistantId`, `isInteractive`.
261
+ * - Everything tagged `@legacy` below: host-internal routing, permission,
262
+ * trust, requester-identity, proxy, and telemetry metadata that historically
263
+ * accreted on this single context. These are NOT a surface we want third-party
264
+ * tools to depend on; we are triaging them post-launch with the goal of
265
+ * moving them off the public context (or removing them) over time. Grep for
266
+ * `@legacy` to enumerate the set. Do not add new fields here — extend the
267
+ * stable core only when a field is genuinely safe and stable to expose.
268
+ *
269
+ * The daemon constructs and passes the full object to every tool at runtime; a
270
+ * tool that only reads the stable core is unaffected by the eventual cleanup.
271
+ */
252
272
  export interface ToolContext {
253
273
  /** Identifier of the conversation this tool invocation belongs to. */
254
274
  conversationId: string;
@@ -262,23 +282,44 @@ export interface ToolContext {
262
282
  onOutput?: (chunk: string) => void;
263
283
  /** Logical assistant scope for multi-assistant routing. */
264
284
  assistantId?: string;
265
- /** When set, the tool execution is part of a task run. Used to retrieve ephemeral permission rules. */
285
+ /** True when an interactive client is connected (not just a no-op callback). */
286
+ isInteractive?: boolean;
287
+ /**
288
+ * When set, the tool execution is part of a task run. Used to retrieve ephemeral permission rules.
289
+ * @legacy
290
+ */
266
291
  taskRunId?: string;
267
292
  /**
268
293
  * Model attribution snapshot for the conversation at invocation time
269
294
  * (provider/model/profile that issued this tool call). Used by tool
270
295
  * telemetry; never sent to the tool itself.
296
+ * @legacy
271
297
  */
272
298
  attribution?: UsageAttributionSnapshot | null;
273
- /** Optional callback for tool lifecycle events (start/prompt/deny/execute/error). */
299
+ /**
300
+ * Optional callback for tool lifecycle events (start/prompt/deny/execute/error).
301
+ * @legacy
302
+ */
274
303
  onToolLifecycleEvent?: ToolLifecycleEventHandler;
275
- /** Optional resolver for proxy tools - delegates execution to an external client. */
304
+ /**
305
+ * Optional resolver for proxy tools - delegates execution to an external client.
306
+ * @legacy
307
+ */
276
308
  proxyToolResolver?: ProxyToolResolver;
277
- /** When set, only tools in this set may execute. Tools outside the set are blocked with an error. */
309
+ /**
310
+ * When set, only tools in this set may execute. Tools outside the set are blocked with an error.
311
+ * @legacy
312
+ */
278
313
  allowedToolNames?: Set<string>;
279
- /** True when this turn is restricted to storage cleanup-safe tools. */
314
+ /**
315
+ * True when this turn is restricted to storage cleanup-safe tools.
316
+ * @legacy
317
+ */
280
318
  diskPressureCleanupModeActive?: boolean;
281
- /** Prompt the user for a secret value via native SecureField UI. */
319
+ /**
320
+ * Prompt the user for a secret value via native SecureField UI.
321
+ * @legacy
322
+ */
282
323
  requestSecret?: (params: {
283
324
  service: string;
284
325
  field: string;
@@ -289,11 +330,15 @@ export interface ToolContext {
289
330
  allowedTools?: string[];
290
331
  allowedDomains?: string[];
291
332
  }) => Promise<SecretPromptResult>;
292
- /** Optional callback to send a message to the connected client (e.g. open_url). */
333
+ /**
334
+ * Optional callback to send a message to the connected client (e.g. open_url).
335
+ * @legacy
336
+ */
293
337
  sendToClient?: (msg: { type: string; [key: string]: unknown }) => void;
294
- /** True when an interactive client is connected (not just a no-op callback). */
295
- isInteractive?: boolean;
296
- /** When true, tools with side effects should always prompt for confirmation. */
338
+ /**
339
+ * When true, tools with side effects should always prompt for confirmation.
340
+ * @legacy
341
+ */
297
342
  forcePromptSideEffects?: boolean;
298
343
  /**
299
344
  * When true, the tool requires a fresh interactive approval for every
@@ -304,26 +349,46 @@ export interface ToolContext {
304
349
  * temporary override options in the prompt UI. Used by
305
350
  * `manage_secure_command_tool` to ensure a human reviews each secure
306
351
  * bundle installation.
352
+ * @legacy
307
353
  */
308
354
  requireFreshApproval?: boolean;
309
- /** Approval callback for proxy policy decisions that require user confirmation. */
355
+ /**
356
+ * Approval callback for proxy policy decisions that require user confirmation.
357
+ * @legacy
358
+ */
310
359
  proxyApprovalCallback?: ProxyApprovalCallback;
311
- /** Optional principal identifier propagated to sub-tool confirmation flows. */
360
+ /**
361
+ * Optional principal identifier propagated to sub-tool confirmation flows.
362
+ * @legacy
363
+ */
312
364
  principal?: string;
313
365
  /**
314
366
  * Trust classification of the actor who initiated this tool invocation.
315
367
  * Determines permission level: guardians self-approve, trusted contacts
316
368
  * may escalate to guardian for approval, unknown actors are fail-closed.
317
369
  * See {@link TrustClass} in actor-trust-resolver.ts for value semantics.
370
+ * @legacy
318
371
  */
319
372
  trustClass: TrustClass;
320
- /** Channel through which the tool invocation originates (e.g. 'telegram', 'phone'). Used for scoped grant consumption. */
373
+ /**
374
+ * Channel through which the tool invocation originates (e.g. 'telegram', 'phone'). Used for scoped grant consumption.
375
+ * @legacy
376
+ */
321
377
  executionChannel?: string;
322
- /** Voice/call session ID, if the invocation originates from a call. Used for scoped grant consumption. */
378
+ /**
379
+ * Voice/call session ID, if the invocation originates from a call. Used for scoped grant consumption.
380
+ * @legacy
381
+ */
323
382
  callSessionId?: string;
324
- /** True when the tool invocation was triggered by a user clicking a surface action button (not a regular message). */
383
+ /**
384
+ * True when the tool invocation was triggered by a user clicking a surface action button (not a regular message).
385
+ * @legacy
386
+ */
325
387
  triggeredBySurfaceAction?: boolean;
326
- /** True when the user explicitly approved this tool invocation via the interactive permission prompt (not auto-approved by trust rules or temporary overrides). */
388
+ /**
389
+ * True when the user explicitly approved this tool invocation via the interactive permission prompt (not auto-approved by trust rules or temporary overrides).
390
+ * @legacy
391
+ */
327
392
  approvedViaPrompt?: boolean;
328
393
  /**
329
394
  * True when the invocation is inside a scheduled task run whose
@@ -331,21 +396,43 @@ export interface ToolContext {
331
396
  * Tools that normally require a surface-action click (e.g. bulk archive,
332
397
  * unsubscribe) may treat this as equivalent consent, since the user
333
398
  * already reviewed the tool list when the task was saved.
399
+ * @legacy
334
400
  */
335
401
  batchAuthorizedByTask?: boolean;
336
- /** External user ID of the requester (non-guardian actor). Used for scoped grant consumption. */
402
+ /**
403
+ * External user ID of the requester (non-guardian actor). Used for scoped grant consumption.
404
+ * @legacy
405
+ */
337
406
  requesterExternalUserId?: string;
338
- /** Chat ID of the requester (non-guardian actor). Used for tool grant request escalation notifications. */
407
+ /**
408
+ * Chat ID of the requester (non-guardian actor). Used for tool grant request escalation notifications.
409
+ * @legacy
410
+ */
339
411
  requesterChatId?: string;
340
- /** Human-readable identifier for the requester (e.g., @username). */
412
+ /**
413
+ * Human-readable identifier for the requester (e.g., @username).
414
+ * @legacy
415
+ */
341
416
  requesterIdentifier?: string;
342
- /** Preferred display name for the requester. */
417
+ /**
418
+ * Preferred display name for the requester.
419
+ * @legacy
420
+ */
343
421
  requesterDisplayName?: string;
344
- /** Slack channel ID for channel-scoped permission enforcement. When set, tools are checked against the channel's permission profile. */
422
+ /**
423
+ * Slack channel ID for channel-scoped permission enforcement. When set, tools are checked against the channel's permission profile.
424
+ * @legacy
425
+ */
345
426
  channelPermissionChannelId?: string;
346
- /** The tool_use block ID from the LLM response, used to correlate confirmation prompts with specific tool invocations. */
427
+ /**
428
+ * The tool_use block ID from the LLM response, used to correlate confirmation prompts with specific tool invocations.
429
+ * @legacy
430
+ */
347
431
  toolUseId?: string;
348
- /** True when the assistant is running as a platform-managed remote instance. Used to auto-approve sandboxed bash tools. */
432
+ /**
433
+ * True when the assistant is running as a platform-managed remote instance. Used to auto-approve sandboxed bash tools.
434
+ * @legacy
435
+ */
349
436
  isPlatformHosted?: boolean;
350
437
  /**
351
438
  * The interface ID of the connected client driving the current turn (e.g.
@@ -353,6 +440,7 @@ export interface ToolContext {
353
440
  * transport preference — for example, macOS-originated turns prefer the
354
441
  * user's real Chrome session via the paired extension before falling back
355
442
  * to cdp-inspect or local Playwright.
443
+ * @legacy
356
444
  */
357
445
  transportInterface?: InterfaceId;
358
446
  /**
@@ -363,6 +451,7 @@ export interface ToolContext {
363
451
  * has `inferenceProfile` set — the override only flows through the
364
452
  * in-memory `SubagentConfig.overrideProfile` chain. See
365
453
  * `executeSubagentSpawn` in tools/subagent/spawn.ts.
454
+ * @legacy
366
455
  */
367
456
  overrideProfile?: string;
368
457
  /**
@@ -371,6 +460,7 @@ export interface ToolContext {
371
460
  * default a spawned subagent's inference profile to the profile the invoking
372
461
  * turn resolved to, so subagents match whatever agent invoked them rather
373
462
  * than always falling back to the static `subagentSpawn` call-site default.
463
+ * @legacy
374
464
  */
375
465
  invokingCallSite?: LLMCallSite;
376
466
  /**
@@ -379,6 +469,7 @@ export interface ToolContext {
379
469
  * Used by host proxies to bind cross-client targeted execution to the same
380
470
  * authenticated user identity. May be undefined for legacy/internal flows
381
471
  * with no resolved actor identity.
472
+ * @legacy
382
473
  */
383
474
  sourceActorPrincipalId?: string;
384
475
  }