@vellumai/assistant 0.8.9 → 0.8.10-dev.202606092238.d04fd59
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/AGENTS.md +1 -1
- package/examples/plugins/echo/README.md +3 -3
- package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +2 -0
- package/openapi.yaml +38 -12
- package/package.json +1 -1
- package/scripts/sync-llm-catalog.ts +2 -0
- package/src/__tests__/agent-loop-exit-reason.test.ts +19 -14
- package/src/__tests__/build-persisted-content.test.ts +75 -1
- package/src/__tests__/config-managed-gemini-defaults.test.ts +2 -46
- package/src/__tests__/context-overflow-reducer.test.ts +264 -124
- package/src/__tests__/context-window-manager-overflow-rung.test.ts +289 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +22 -22
- package/src/__tests__/conversation-agent-loop.test.ts +45 -37
- package/src/__tests__/conversation-fork-crud.test.ts +110 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +16 -20
- package/src/__tests__/conversation-runtime-assembly.test.ts +118 -5
- package/src/__tests__/conversation-surfaces-app-control.test.ts +4 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +91 -142
- package/src/__tests__/conversation-tool-setup-skill-attribution.test.ts +116 -0
- package/src/__tests__/conversation-tool-setup-test-helpers.ts +98 -0
- package/src/__tests__/corrected-target.test.ts +93 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -5
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/embedding-managed-proxy-selection.test.ts +6 -40
- package/src/__tests__/external-plugin-loader.test.ts +0 -1
- package/src/__tests__/guardian-routing-invariants.test.ts +91 -0
- package/src/__tests__/history-repair-hook.test.ts +2 -0
- package/src/__tests__/host-app-control-proxy.test.ts +45 -0
- package/src/__tests__/llm-catalog-parity.test.ts +4 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +29 -15
- package/src/__tests__/notification-decision-strategy.test.ts +3 -3
- package/src/__tests__/persistence-secret-redaction.test.ts +78 -0
- package/src/__tests__/plugin-bootstrap.test.ts +82 -73
- package/src/__tests__/plugin-tool-contribution.test.ts +7 -4
- package/src/__tests__/plugin-types.test.ts +0 -8
- package/src/__tests__/provider-catalog-visibility.test.ts +1 -9
- package/src/__tests__/retry-thinking-adaptive-only.test.ts +245 -0
- package/src/__tests__/server-history-render.test.ts +39 -0
- package/src/__tests__/skills.test.ts +123 -0
- package/src/__tests__/title-generate-hook.test.ts +2 -0
- package/src/__tests__/tool-audit-listener.test.ts +51 -0
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +32 -0
- package/src/__tests__/tool-start-timestamp.test.ts +218 -0
- package/src/agent/loop.ts +26 -23
- package/src/api/events/tool-result.ts +9 -0
- package/src/api/events/tool-use-start.ts +7 -0
- package/src/api/responses/conversation-message.ts +91 -14
- package/src/approvals/guardian-request-resolvers.ts +26 -0
- package/src/cli/commands/channel-verification-sessions.ts +6 -6
- package/src/cli/commands/inference-providers.ts +0 -8
- package/src/cli/commands/plugins.ts +2 -2
- package/src/cli/lib/__tests__/install-from-github.test.ts +174 -18
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +1 -1
- package/src/cli/lib/__tests__/plugin-details.test.ts +1 -1
- package/src/cli/lib/__tests__/plugin-marketplace.test.ts +1 -1
- package/src/cli/lib/__tests__/search-plugins.test.ts +11 -11
- package/src/cli/lib/install-from-github.ts +130 -19
- package/src/cli/lib/plugin-catalog-cache.ts +1 -1
- package/src/cli/lib/plugin-details.ts +4 -4
- package/src/cli/lib/plugin-marketplace.ts +2 -2
- package/src/cli/lib/search-plugins.ts +6 -6
- package/src/config/bundled-skills/skill-management/SKILL.md +89 -1
- package/src/config/env-registry.ts +12 -0
- package/src/config/feature-flag-registry.json +0 -80
- package/src/config/loader.ts +5 -22
- package/src/config/seed-inference-profiles.ts +1 -1
- package/src/config/skills.ts +101 -47
- package/src/context/strip-injections.ts +7 -4
- package/src/daemon/conversation-agent-loop-handlers.ts +102 -6
- package/src/daemon/conversation-agent-loop.ts +134 -232
- package/src/daemon/conversation-runtime-assembly.ts +44 -8
- package/src/daemon/conversation-tool-setup.ts +9 -1
- package/src/daemon/external-plugins-bootstrap.ts +33 -87
- package/src/daemon/handlers/config-channels.ts +11 -2
- package/src/daemon/handlers/shared.ts +9 -1
- package/src/daemon/handlers/skills.ts +6 -2
- package/src/daemon/host-app-control-proxy.ts +72 -57
- package/src/daemon/message-protocol.ts +0 -7
- package/src/daemon/message-types/skills.ts +4 -0
- package/src/events/tool-audit-listener.ts +7 -1
- package/src/memory/__tests__/tool-invocation-test-helpers.ts +69 -0
- package/src/memory/conversation-crud.ts +44 -1
- package/src/memory/db-init.ts +4 -0
- package/src/memory/embedding-backend.ts +5 -22
- package/src/memory/memory-retrospective-startup-cleanup.ts +1 -1
- package/src/memory/migrations/275-tool-invocations-add-skill-id.test.ts +81 -0
- package/src/memory/migrations/275-tool-invocations-add-skill-id.ts +20 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.test.ts +68 -0
- package/src/memory/migrations/276-tool-invocations-created-at-id-index.ts +20 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/tool-execution-events-store.test.ts +162 -0
- package/src/memory/tool-execution-events-store.ts +73 -0
- package/src/memory/tool-usage-store.ts +4 -3
- package/src/memory/v2/__tests__/injected-block-slugs.test.ts +51 -0
- package/src/memory/v2/activation-store.ts +50 -1
- package/src/memory/v2/injected-block-slugs.ts +30 -0
- package/src/notifications/__tests__/copy-composer.test.ts +244 -0
- package/src/notifications/access-request-copy.ts +298 -0
- package/src/notifications/adapters/slack.ts +3 -3
- package/src/notifications/adapters/telegram.ts +2 -1
- package/src/notifications/copy-composer.ts +49 -267
- package/src/notifications/decision-engine.ts +16 -35
- package/src/notifications/home-feed-side-effect.ts +1 -6
- package/src/platform/client.ts +1 -3
- package/src/platform/feature-gate.ts +3 -12
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +34 -6
- package/src/plugin-api/types.ts +19 -0
- package/src/plugins/defaults/compaction/compact.ts +39 -1
- package/src/plugins/defaults/compaction/context-overflow-reducer.ts +240 -32
- package/src/plugins/defaults/compaction/corrected-target.ts +53 -0
- package/src/plugins/defaults/compaction/window-manager.ts +136 -0
- package/src/plugins/defaults/index.ts +24 -0
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +68 -43
- package/src/plugins/defaults/memory-retrieval/hooks/{user-prompt-submit-temp.ts → user-prompt-submit.ts} +36 -67
- package/src/plugins/defaults/memory-retrieval/package.json +14 -0
- package/src/plugins/external-api.ts +1 -1
- package/src/plugins/types.ts +5 -51
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +1 -2
- package/src/providers/model-catalog.ts +57 -1
- package/src/providers/registry.ts +0 -8
- package/src/providers/retry.ts +35 -7
- package/src/runtime/routes/__tests__/acp-routes.test.ts +151 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +0 -62
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +4 -4
- package/src/runtime/routes/acp-routes.test.ts +106 -0
- package/src/runtime/routes/acp-routes.ts +248 -2
- package/src/runtime/routes/browser-tabs-routes.ts +1 -1
- package/src/runtime/routes/channel-verification-routes.ts +14 -5
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +0 -14
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +33 -34
- package/src/runtime/routes/inference-provider-connection-routes.ts +0 -45
- package/src/runtime/routes/plugins-routes.ts +3 -3
- package/src/runtime/verification-outbound-actions.ts +147 -2
- package/src/runtime/verification-templates.ts +29 -3
- package/src/telemetry/types.ts +25 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +141 -7
- package/src/telemetry/usage-telemetry-reporter.ts +63 -7
- package/src/tools/executor.ts +4 -1
- package/src/tools/skills/load.ts +11 -6
- package/src/tools/terminal/safe-env.ts +1 -0
- package/src/tools/types.ts +10 -0
- package/src/__tests__/plugin-skill-contribution.test.ts +0 -355
- package/src/daemon/message-types/browser.ts +0 -10
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +0 -187
- package/src/plugins/plugin-skill-contributions.ts +0 -292
package/AGENTS.md
CHANGED
|
@@ -84,7 +84,7 @@ The rule exists because test machinery and production code have **inverted invar
|
|
|
84
84
|
|
|
85
85
|
Concretely:
|
|
86
86
|
|
|
87
|
-
- **Test helpers** (e.g. `src/__tests__/*-test-helpers.ts`) use only node stdlib, `bun:test`, and sibling helpers.
|
|
87
|
+
- **Test helpers** (e.g. `src/__tests__/*-test-helpers.ts`) use only node stdlib, `bun:test`, and sibling helpers. Type-only imports (`import type`) of production types are permitted: they are erased at compile time and carry no import-time side effects, so the isolation invariant holds — prefer them over duplicating non-trivial production types structurally. If helpers need to manipulate shared state that production code also reads, both sides declare a typed slot under `globalThis.vellumAssistant.*` and read/write that slot independently. The slot shape is duplicated by design — the helper and the production module both reference the namespace, neither imports the other.
|
|
88
88
|
- **The test preload** (`src/__tests__/test-preload.ts`) is the strictest: it must not import from `src/` at all. Its only static imports are node stdlib, `bun:test`, and helpers in `src/__tests__/`. Importing from a source module risks running its import-time side effects before the workspace override is set.
|
|
89
89
|
- **The preload verifier** (`src/__tests__/test-preload-verifier.ts`) runs after the main preload and asserts the override took effect (`VELLUM_WORKSPACE_DIR` must resolve under `os.tmpdir()`).
|
|
90
90
|
- **Destructive ops** (e.g. `rmSync(dbPath, ...)`) in tests must call `assertNotLiveDb(path)` from `src/__tests__/assert-not-live-db.js` immediately before the destructive call. The check is a per-callsite belt to the preload-verifier suspenders.
|
|
@@ -16,8 +16,8 @@ to eyeball which hooks fire during a conversation.
|
|
|
16
16
|
|
|
17
17
|
For the full plugin authoring guide — manifest shape, every contribution
|
|
18
18
|
surface, hook patterns, and conventions — see
|
|
19
|
-
[`
|
|
20
|
-
[`simple-memory`](../../../../
|
|
19
|
+
[`plugins/README.md`](../../../../plugins/README.md).
|
|
20
|
+
[`simple-memory`](../../../../plugins/simple-memory/) is the
|
|
21
21
|
canonical reference implementation that exercises every wired surface.
|
|
22
22
|
|
|
23
23
|
## What it does
|
|
@@ -115,7 +115,7 @@ vellum restart
|
|
|
115
115
|
|
|
116
116
|
## Next steps
|
|
117
117
|
|
|
118
|
-
- Read [`
|
|
118
|
+
- Read [`plugins/README.md`](../../../../plugins/README.md)
|
|
119
119
|
for the full plugin authoring guide: manifest shape, every contribution
|
|
120
120
|
surface, hook patterns (observe / transform), tool contributions, and
|
|
121
121
|
conventions.
|
|
@@ -195,6 +195,8 @@ interface ToolLifecycleEventBase {
|
|
|
195
195
|
conversationId: string;
|
|
196
196
|
requestId?: string;
|
|
197
197
|
executionTarget?: ExecutionTarget;
|
|
198
|
+
/** Id of the skill whose `skill_execute` dispatch triggered this tool call. Absent for direct tool calls. */
|
|
199
|
+
skillId?: string;
|
|
198
200
|
}
|
|
199
201
|
|
|
200
202
|
export interface AllowlistOption {
|
package/openapi.yaml
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
openapi: 3.1.0
|
|
4
4
|
info:
|
|
5
5
|
title: Vellum Assistant API
|
|
6
|
-
version: 0.8.
|
|
6
|
+
version: 0.8.10
|
|
7
7
|
description: Auto-generated OpenAPI specification for the Vellum Assistant runtime HTTP server.
|
|
8
8
|
servers:
|
|
9
9
|
- url: http://127.0.0.1:7821
|
|
@@ -96,8 +96,11 @@ paths:
|
|
|
96
96
|
operationId: acp_by_id_steer_post
|
|
97
97
|
summary: Steer ACP session
|
|
98
98
|
description:
|
|
99
|
-
Send a steering instruction to an ACP session. Sessions no longer in memory (completed, or lost to a
|
|
100
|
-
restart) are transparently resumed from persisted history first, when the agent supports ACP session
|
|
99
|
+
"Send a steering instruction to an ACP session. Sessions no longer in memory (completed, or lost to a
|
|
100
|
+
daemon restart) are transparently resumed from persisted history first, when the agent supports ACP session
|
|
101
|
+
loading. Resuming a terminal session re-spawns the host agent, so it requires guardian approval: the route acks
|
|
102
|
+
immediately with approvalPending=true and performs the resume in the background once approved, streaming results
|
|
103
|
+
over SSE."
|
|
101
104
|
tags:
|
|
102
105
|
- acp
|
|
103
106
|
responses:
|
|
@@ -115,6 +118,11 @@ paths:
|
|
|
115
118
|
resumed:
|
|
116
119
|
description: True when the session was resumed from persisted history before steering.
|
|
117
120
|
type: boolean
|
|
121
|
+
approvalPending:
|
|
122
|
+
description:
|
|
123
|
+
True when the steer triggered a guardian-approval-gated resume that will run asynchronously; watch SSE for
|
|
124
|
+
the outcome.
|
|
125
|
+
type: boolean
|
|
118
126
|
required:
|
|
119
127
|
- acpSessionId
|
|
120
128
|
- steered
|
|
@@ -15937,6 +15945,8 @@ paths:
|
|
|
15937
15945
|
- assistant
|
|
15938
15946
|
content:
|
|
15939
15947
|
type: string
|
|
15948
|
+
deprecated: true
|
|
15949
|
+
description: "Deprecated: superseded by contentBlocks. Flat plain-text body (joined text segments)."
|
|
15940
15950
|
timestamp:
|
|
15941
15951
|
type: string
|
|
15942
15952
|
attachments:
|
|
@@ -16241,6 +16251,8 @@ paths:
|
|
|
16241
16251
|
- name
|
|
16242
16252
|
- input
|
|
16243
16253
|
additionalProperties: false
|
|
16254
|
+
deprecated: true
|
|
16255
|
+
description: "Deprecated: superseded by contentBlocks (the tool_use variant). Flat list of tool calls."
|
|
16244
16256
|
surfaces:
|
|
16245
16257
|
type: array
|
|
16246
16258
|
items:
|
|
@@ -16292,18 +16304,28 @@ paths:
|
|
|
16292
16304
|
- surfaceType
|
|
16293
16305
|
- data
|
|
16294
16306
|
additionalProperties: false
|
|
16307
|
+
deprecated: true
|
|
16308
|
+
description: "Deprecated: superseded by contentBlocks (the surface variant). Flat list of surfaces."
|
|
16295
16309
|
textSegments:
|
|
16296
16310
|
type: array
|
|
16297
16311
|
items:
|
|
16298
16312
|
type: string
|
|
16313
|
+
deprecated: true
|
|
16314
|
+
description: "Deprecated: superseded by contentBlocks. Text segments split by tool-call boundaries."
|
|
16299
16315
|
thinkingSegments:
|
|
16300
16316
|
type: array
|
|
16301
16317
|
items:
|
|
16302
16318
|
type: string
|
|
16319
|
+
deprecated: true
|
|
16320
|
+
description: "Deprecated: superseded by contentBlocks. Reasoning text extracted from thinking blocks."
|
|
16303
16321
|
contentOrder:
|
|
16304
16322
|
type: array
|
|
16305
16323
|
items:
|
|
16306
16324
|
type: string
|
|
16325
|
+
deprecated: true
|
|
16326
|
+
description:
|
|
16327
|
+
'Deprecated: superseded by contentBlocks. Positional "<type>:<index>" content ordering (e.g. "text:0",
|
|
16328
|
+
"thinking:1").'
|
|
16307
16329
|
contentBlocks:
|
|
16308
16330
|
type: array
|
|
16309
16331
|
items:
|
|
@@ -16326,6 +16348,10 @@ paths:
|
|
|
16326
16348
|
const: thinking
|
|
16327
16349
|
thinking:
|
|
16328
16350
|
type: string
|
|
16351
|
+
startedAt:
|
|
16352
|
+
type: number
|
|
16353
|
+
completedAt:
|
|
16354
|
+
type: number
|
|
16329
16355
|
required:
|
|
16330
16356
|
- type
|
|
16331
16357
|
- thinking
|
|
@@ -19449,11 +19475,11 @@ paths:
|
|
|
19449
19475
|
operationId: plugins_install_post
|
|
19450
19476
|
summary: Install a plugin
|
|
19451
19477
|
description:
|
|
19452
|
-
"Install a plugin by name from the canonical source — a whitelisted `
|
|
19453
|
-
|
|
19454
|
-
|
|
19455
|
-
|
|
19456
|
-
|
|
19478
|
+
"Install a plugin by name from the canonical source — a whitelisted `plugins/marketplace.json` entry, else
|
|
19479
|
+
the first-party `plugins/<name>/` convention. Always resolves against the curated default git ref (no
|
|
19480
|
+
caller-supplied ref): installing from an unreviewed revision would bypass the marketplace/first-party curation
|
|
19481
|
+
boundary and let attacker-controlled code be loaded. Materializes the plugin under
|
|
19482
|
+
`<workspaceDir>/plugins/<name>/`; the assistant must be restarted to load it. Mirrors the CLI's `assistant
|
|
19457
19483
|
plugins install <name>`. An already-installed name without `force` returns 409; a name that resolves to nothing
|
|
19458
19484
|
returns 404. Sibling to `POST /v1/skills/install`."
|
|
19459
19485
|
tags:
|
|
@@ -19515,9 +19541,9 @@ paths:
|
|
|
19515
19541
|
operationId: plugins_search_get
|
|
19516
19542
|
summary: Search the plugin catalog
|
|
19517
19543
|
description:
|
|
19518
|
-
List installable plugins from the canonical `vellum-ai/vellum-assistant` catalog at
|
|
19519
|
-
|
|
19520
|
-
|
|
19544
|
+
List installable plugins from the canonical `vellum-ai/vellum-assistant` catalog at `plugins/`. The query
|
|
19545
|
+
is an ECMAScript regex matched case-insensitively against the directory name (e.g. `memory`, `^simple`). Empty
|
|
19546
|
+
query returns every entry. Mirrors the CLI's `assistant plugins search`.
|
|
19521
19547
|
tags:
|
|
19522
19548
|
- plugins
|
|
19523
19549
|
responses:
|
|
@@ -19545,7 +19571,7 @@ paths:
|
|
|
19545
19571
|
path:
|
|
19546
19572
|
type: string
|
|
19547
19573
|
description:
|
|
19548
|
-
"Human-readable origin: a repo-relative path (`
|
|
19574
|
+
"Human-readable origin: a repo-relative path (`plugins/<name>`) for first-party plugins or a
|
|
19549
19575
|
`github:owner/repo@ref` locator for external ones."
|
|
19550
19576
|
description:
|
|
19551
19577
|
description: Short description, when known (external entries only today).
|
package/package.json
CHANGED
|
@@ -74,6 +74,8 @@ function projectModel(model: CatalogModel): Record<string, unknown> {
|
|
|
74
74
|
projected.longContextMode = model.longContextMode;
|
|
75
75
|
if (model.supportsThinking !== undefined)
|
|
76
76
|
projected.supportsThinking = model.supportsThinking;
|
|
77
|
+
if (model.adaptiveThinkingOnly !== undefined)
|
|
78
|
+
projected.adaptiveThinkingOnly = model.adaptiveThinkingOnly;
|
|
77
79
|
if (model.supportsCaching !== undefined)
|
|
78
80
|
projected.supportsCaching = model.supportsCaching;
|
|
79
81
|
if (model.supportsVision !== undefined)
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* Sites not exercised here (`aborted_via_error`) require deeper provider
|
|
14
14
|
* fakery and are best covered by integration tests.
|
|
15
15
|
*/
|
|
16
|
-
import { afterEach, describe, expect,
|
|
16
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
17
17
|
|
|
18
18
|
import type {
|
|
19
19
|
AgentEvent,
|
|
@@ -23,12 +23,14 @@ import type {
|
|
|
23
23
|
import { AgentLoop, isMaxTokensStopReason } from "../agent/loop.js";
|
|
24
24
|
import type { ContextWindowConfig } from "../config/types.js";
|
|
25
25
|
import type { TrustContext } from "../daemon/trust-context.js";
|
|
26
|
+
import { HOOKS } from "../plugin-api/constants.js";
|
|
26
27
|
import {
|
|
27
28
|
createContextWindowManager,
|
|
28
29
|
disposeContextWindowManager,
|
|
29
30
|
getContextWindowManager,
|
|
30
31
|
} from "../plugins/defaults/compaction/manager-store.js";
|
|
31
32
|
import type { PostCompactContext } from "../plugins/defaults/memory-retrieval/hooks/post-compact.js";
|
|
33
|
+
import { registerPlugin } from "../plugins/registry.js";
|
|
32
34
|
import type {
|
|
33
35
|
Message,
|
|
34
36
|
Provider,
|
|
@@ -37,22 +39,25 @@ import type {
|
|
|
37
39
|
ToolDefinition,
|
|
38
40
|
} from "../providers/types.js";
|
|
39
41
|
|
|
40
|
-
// The agent loop
|
|
41
|
-
// when it compacts in place.
|
|
42
|
-
// re-injection result without the
|
|
43
|
-
//
|
|
44
|
-
//
|
|
42
|
+
// The agent loop runs the default post-compaction re-injection through the
|
|
43
|
+
// `post-compact` hook chain when it compacts in place. Register a test hook on
|
|
44
|
+
// that chain so these unit tests can drive the re-injection result without the
|
|
45
|
+
// daemon-level injector. The hook writes the re-injected history back onto the
|
|
46
|
+
// context; tests assign `postCompactImpl` to observe the call or force a
|
|
47
|
+
// failure; when unset the hook leaves the history it was handed untouched.
|
|
45
48
|
let postCompactImpl:
|
|
46
49
|
| ((input: PostCompactContext) => Promise<Message[]>)
|
|
47
50
|
| null = null;
|
|
48
|
-
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
registerPlugin({
|
|
52
|
+
manifest: { name: "test-post-compact", version: "0.0.0" },
|
|
53
|
+
hooks: {
|
|
54
|
+
[HOOKS.POST_COMPACT]: async (input: PostCompactContext): Promise<void> => {
|
|
55
|
+
input.history = postCompactImpl
|
|
56
|
+
? await postCompactImpl(input)
|
|
57
|
+
: input.history;
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
56
61
|
|
|
57
62
|
// ---------------------------------------------------------------------------
|
|
58
63
|
// Helpers (mirrored from agent-loop.test.ts so this file is self-contained)
|
|
@@ -54,7 +54,10 @@ mock.module("../memory/llm-request-log-store.js", () => ({
|
|
|
54
54
|
}));
|
|
55
55
|
|
|
56
56
|
// ── Imports (after mocks) ─────────────────────────────────────────────────────
|
|
57
|
-
import {
|
|
57
|
+
import {
|
|
58
|
+
buildPersistedAssistantContent,
|
|
59
|
+
stampThinkingTiming,
|
|
60
|
+
} from "../daemon/conversation-agent-loop-handlers.js";
|
|
58
61
|
import type { ToolActivityMetadata } from "../daemon/message-types/web-activity.js";
|
|
59
62
|
import type { ContentBlock } from "../providers/types.js";
|
|
60
63
|
|
|
@@ -182,3 +185,74 @@ describe("buildPersistedAssistantContent — native activityMetadata", () => {
|
|
|
182
185
|
expect(block._activityMetadata).toBeUndefined();
|
|
183
186
|
});
|
|
184
187
|
});
|
|
188
|
+
|
|
189
|
+
describe("stampThinkingTiming", () => {
|
|
190
|
+
test("stamps internal timing onto thinking blocks by position", () => {
|
|
191
|
+
// GIVEN a turn that interleaves text and two thinking blocks AND the
|
|
192
|
+
// per-block timing captured while streaming (one entry per thinking block,
|
|
193
|
+
// in stream order)
|
|
194
|
+
const content = [
|
|
195
|
+
{ type: "thinking", thinking: "first", signature: "s1" },
|
|
196
|
+
{ type: "text", text: "answer" },
|
|
197
|
+
{ type: "thinking", thinking: "second", signature: "s2" },
|
|
198
|
+
] as unknown as ContentBlock[];
|
|
199
|
+
const timings = [
|
|
200
|
+
{ startedAt: 100, completedAt: 250 },
|
|
201
|
+
{ startedAt: 400, completedAt: 480 },
|
|
202
|
+
];
|
|
203
|
+
|
|
204
|
+
// WHEN the content is stamped before persistence
|
|
205
|
+
const stamped = stampThinkingTiming(content, timings) as unknown as Array<
|
|
206
|
+
Record<string, unknown>
|
|
207
|
+
>;
|
|
208
|
+
|
|
209
|
+
// THEN each thinking block carries the `_`-prefixed timing for its position
|
|
210
|
+
expect(stamped[0]).toMatchObject({
|
|
211
|
+
type: "thinking",
|
|
212
|
+
_startedAt: 100,
|
|
213
|
+
_completedAt: 250,
|
|
214
|
+
});
|
|
215
|
+
expect(stamped[2]).toMatchObject({
|
|
216
|
+
type: "thinking",
|
|
217
|
+
_startedAt: 400,
|
|
218
|
+
_completedAt: 480,
|
|
219
|
+
});
|
|
220
|
+
// AND the interleaved text block is left untouched
|
|
221
|
+
expect(stamped[1]).toEqual({ type: "text", text: "answer" });
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("leaves thinking blocks unstamped when no timing was captured", () => {
|
|
225
|
+
// GIVEN thinking content but an empty timing list (thinking streaming was
|
|
226
|
+
// disabled, so no per-block timing was recorded this turn)
|
|
227
|
+
const content = [
|
|
228
|
+
{ type: "thinking", thinking: "first", signature: "s1" },
|
|
229
|
+
] as unknown as ContentBlock[];
|
|
230
|
+
|
|
231
|
+
// WHEN the content is stamped with no timing
|
|
232
|
+
const stamped = stampThinkingTiming(content, []);
|
|
233
|
+
|
|
234
|
+
// THEN the original content is returned unchanged so the UI hides duration,
|
|
235
|
+
// exactly as a tool call with no timing
|
|
236
|
+
expect(stamped).toBe(content);
|
|
237
|
+
expect(stamped[0]).not.toHaveProperty("_startedAt");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("stamps only the thinking blocks that have a matching timing entry", () => {
|
|
241
|
+
// GIVEN two thinking blocks but only one captured timing entry (e.g. the
|
|
242
|
+
// second block opened after the timing array was already finalized)
|
|
243
|
+
const content = [
|
|
244
|
+
{ type: "thinking", thinking: "first", signature: "s1" },
|
|
245
|
+
{ type: "thinking", thinking: "second", signature: "s2" },
|
|
246
|
+
] as unknown as ContentBlock[];
|
|
247
|
+
const timings = [{ startedAt: 100, completedAt: 250 }];
|
|
248
|
+
|
|
249
|
+
// WHEN the content is stamped
|
|
250
|
+
const stamped = stampThinkingTiming(content, timings) as unknown as Array<
|
|
251
|
+
Record<string, unknown>
|
|
252
|
+
>;
|
|
253
|
+
|
|
254
|
+
// THEN the first block is stamped and the unmatched second block is left as-is
|
|
255
|
+
expect(stamped[0]).toMatchObject({ _startedAt: 100, _completedAt: 250 });
|
|
256
|
+
expect(stamped[1]).not.toHaveProperty("_startedAt");
|
|
257
|
+
});
|
|
258
|
+
});
|
|
@@ -57,17 +57,8 @@ mock.module("../util/logger.js", () => ({
|
|
|
57
57
|
getLogger: () => makeLoggerStub(),
|
|
58
58
|
}));
|
|
59
59
|
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
// Feature flag mock — controls whether managed-gemini-embeddings-enabled is on
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
|
|
64
|
-
let featureFlagEnabled = false;
|
|
65
|
-
|
|
66
60
|
mock.module("../config/assistant-feature-flags.js", () => ({
|
|
67
|
-
isAssistantFeatureFlagEnabled: (
|
|
68
|
-
if (key === "managed-gemini-embeddings-enabled") return featureFlagEnabled;
|
|
69
|
-
return true;
|
|
70
|
-
},
|
|
61
|
+
isAssistantFeatureFlagEnabled: () => true,
|
|
71
62
|
clearFeatureFlagOverridesCache: () => {},
|
|
72
63
|
initFeatureFlagOverrides: async () => {},
|
|
73
64
|
getAssistantFeatureFlagDefaults: () => ({}),
|
|
@@ -119,8 +110,6 @@ describe("managed Gemini embedding defaults (via loadConfig)", () => {
|
|
|
119
110
|
setStorePathForTesting(join(WORKSPACE_DIR, "keys.enc"));
|
|
120
111
|
invalidateConfigCache();
|
|
121
112
|
|
|
122
|
-
// Reset mock state
|
|
123
|
-
featureFlagEnabled = false;
|
|
124
113
|
originalIsPlatform = process.env.IS_PLATFORM;
|
|
125
114
|
delete process.env.IS_PLATFORM;
|
|
126
115
|
});
|
|
@@ -137,10 +126,9 @@ describe("managed Gemini embedding defaults (via loadConfig)", () => {
|
|
|
137
126
|
}
|
|
138
127
|
});
|
|
139
128
|
|
|
140
|
-
test("applies managed Gemini defaults when
|
|
129
|
+
test("applies managed Gemini defaults when IS_PLATFORM + provider auto", () => {
|
|
141
130
|
writeConfig({});
|
|
142
131
|
|
|
143
|
-
featureFlagEnabled = true;
|
|
144
132
|
process.env.IS_PLATFORM = "true";
|
|
145
133
|
|
|
146
134
|
const config = loadConfig();
|
|
@@ -162,22 +150,9 @@ describe("managed Gemini embedding defaults (via loadConfig)", () => {
|
|
|
162
150
|
expect(qdrantRaw.vectorSize).toBe(3072);
|
|
163
151
|
});
|
|
164
152
|
|
|
165
|
-
test("does NOT apply when feature flag is OFF", () => {
|
|
166
|
-
writeConfig({});
|
|
167
|
-
|
|
168
|
-
featureFlagEnabled = false;
|
|
169
|
-
process.env.IS_PLATFORM = "true";
|
|
170
|
-
|
|
171
|
-
const config = loadConfig();
|
|
172
|
-
|
|
173
|
-
expect(config.memory.embeddings.provider).toBe("auto");
|
|
174
|
-
expect(config.memory.qdrant.vectorSize).toBe(384);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
153
|
test("does NOT apply when IS_PLATFORM is not set", () => {
|
|
178
154
|
writeConfig({});
|
|
179
155
|
|
|
180
|
-
featureFlagEnabled = true;
|
|
181
156
|
delete process.env.IS_PLATFORM;
|
|
182
157
|
|
|
183
158
|
const config = loadConfig();
|
|
@@ -191,7 +166,6 @@ describe("managed Gemini embedding defaults (via loadConfig)", () => {
|
|
|
191
166
|
memory: { embeddings: { provider: "local" } },
|
|
192
167
|
});
|
|
193
168
|
|
|
194
|
-
featureFlagEnabled = true;
|
|
195
169
|
process.env.IS_PLATFORM = "true";
|
|
196
170
|
|
|
197
171
|
const config = loadConfig();
|
|
@@ -204,7 +178,6 @@ describe("managed Gemini embedding defaults (via loadConfig)", () => {
|
|
|
204
178
|
memory: { embeddings: { provider: "openai" } },
|
|
205
179
|
});
|
|
206
180
|
|
|
207
|
-
featureFlagEnabled = true;
|
|
208
181
|
process.env.IS_PLATFORM = "true";
|
|
209
182
|
|
|
210
183
|
const config = loadConfig();
|
|
@@ -219,7 +192,6 @@ describe("managed Gemini embedding defaults (via loadConfig)", () => {
|
|
|
219
192
|
},
|
|
220
193
|
});
|
|
221
194
|
|
|
222
|
-
featureFlagEnabled = true;
|
|
223
195
|
process.env.IS_PLATFORM = "true";
|
|
224
196
|
|
|
225
197
|
const config = loadConfig();
|
|
@@ -235,7 +207,6 @@ describe("managed Gemini embedding defaults (via loadConfig)", () => {
|
|
|
235
207
|
memory: { embeddings: { provider: "ollama" } },
|
|
236
208
|
});
|
|
237
209
|
|
|
238
|
-
featureFlagEnabled = true;
|
|
239
210
|
process.env.IS_PLATFORM = "true";
|
|
240
211
|
|
|
241
212
|
const config = loadConfig();
|
|
@@ -245,7 +216,6 @@ describe("managed Gemini embedding defaults (via loadConfig)", () => {
|
|
|
245
216
|
test("is idempotent — second loadConfig is a no-op after migration", () => {
|
|
246
217
|
writeConfig({});
|
|
247
218
|
|
|
248
|
-
featureFlagEnabled = true;
|
|
249
219
|
process.env.IS_PLATFORM = "true";
|
|
250
220
|
|
|
251
221
|
const config = loadConfig();
|
|
@@ -274,7 +244,6 @@ describe("managed Gemini embedding defaults (via loadConfig)", () => {
|
|
|
274
244
|
},
|
|
275
245
|
});
|
|
276
246
|
|
|
277
|
-
featureFlagEnabled = true;
|
|
278
247
|
process.env.IS_PLATFORM = "true";
|
|
279
248
|
|
|
280
249
|
const config = loadConfig();
|
|
@@ -295,22 +264,9 @@ describe("managed Gemini embedding defaults (via loadConfig)", () => {
|
|
|
295
264
|
expect(qdrantRaw.onDisk).toBe(false);
|
|
296
265
|
});
|
|
297
266
|
|
|
298
|
-
test("does NOT apply when both FF off and IS_PLATFORM not set", () => {
|
|
299
|
-
writeConfig({});
|
|
300
|
-
|
|
301
|
-
featureFlagEnabled = false;
|
|
302
|
-
delete process.env.IS_PLATFORM;
|
|
303
|
-
|
|
304
|
-
const config = loadConfig();
|
|
305
|
-
|
|
306
|
-
expect(config.memory.embeddings.provider).toBe("auto");
|
|
307
|
-
expect(config.memory.qdrant.vectorSize).toBe(384);
|
|
308
|
-
});
|
|
309
|
-
|
|
310
267
|
test("applies when IS_PLATFORM is '1'", () => {
|
|
311
268
|
writeConfig({});
|
|
312
269
|
|
|
313
|
-
featureFlagEnabled = true;
|
|
314
270
|
process.env.IS_PLATFORM = "1";
|
|
315
271
|
|
|
316
272
|
const config = loadConfig();
|