@vellumai/assistant 0.12.2-dev.202609211913.b0ee78c → 0.12.2-dev.202609212016.fb4c53e
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/Dockerfile +63 -0
- package/docs/desktop-browser-cli.md +6 -4
- package/openapi.yaml +189 -1
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +10 -26
- package/src/__tests__/context-search-agent-runner.test.ts +259 -0
- package/src/__tests__/server-history-render.test.ts +51 -0
- package/src/api/events/tool-result.ts +61 -0
- package/src/api/index.ts +8 -0
- package/src/cli/commands/browser.help.ts +4 -5
- package/src/cli/commands/browser.ts +0 -1
- package/src/daemon/conversation-tool-setup.ts +1 -1
- package/src/desktop/desktop-automation-lease.test.ts +9 -72
- package/src/desktop/desktop-automation-lease.ts +3 -40
- package/src/desktop/desktop-computer-use.test.ts +2 -3
- package/src/desktop/desktop-dependencies.ts +41 -354
- package/src/desktop/desktop-stream-bridge.test.ts +11 -122
- package/src/desktop/desktop-stream-bridge.ts +6 -8
- package/src/desktop/virtual-desktop-platform.test.ts +5 -9
- package/src/ipc/__tests__/browser-ipc.test.ts +5 -6
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +77 -21
- package/src/plugins/defaults/memory/context-search/format.ts +51 -7
- package/src/plugins/defaults/memory/context-search/search.ts +2 -10
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +1 -0
- package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +11 -4
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +9 -5
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +5 -0
- package/src/plugins/defaults/memory/src/memory-item-routes.ts +2 -1
- package/src/plugins/defaults/memory/tools.test.ts +19 -1
- package/src/plugins/defaults/memory/tools.ts +8 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +61 -1
- package/src/providers/openai/chat-completions-provider.ts +51 -14
- package/src/runtime/AGENTS.md +1 -1
- package/src/runtime/routes/desktop-setup-routes.test.ts +9 -18
- package/src/runtime/routes/desktop-setup-routes.ts +4 -6
- package/src/desktop/desktop-dependencies.test.ts +0 -131
package/Dockerfile
CHANGED
|
@@ -101,6 +101,69 @@ RUN apt-get update && apt-get install -y \
|
|
|
101
101
|
xdg-utils \
|
|
102
102
|
&& rm -rf /var/lib/apt/lists/*
|
|
103
103
|
|
|
104
|
+
# Desktop system packages persist through container replacement as image layers.
|
|
105
|
+
RUN set -eu; \
|
|
106
|
+
wezterm_version=20240203-110809-5046fc22; \
|
|
107
|
+
case "$(dpkg --print-architecture)" in \
|
|
108
|
+
amd64) wezterm_suffix=Debian12.deb; wezterm_sha256=d3a5c97093fbc0a87e8f9616e44efc4c9503cfc495fd1cfe4ffeff88578d15f8 ;; \
|
|
109
|
+
arm64) wezterm_suffix=Debian12.arm64.deb; wezterm_sha256=351f8791c6e9561d687afa63834a2132de84800dad6e7c3d035cc6518d6c3744 ;; \
|
|
110
|
+
*) echo "Unsupported desktop architecture" >&2; exit 1 ;; \
|
|
111
|
+
esac; \
|
|
112
|
+
curl --fail --location --proto '=https' --proto-redir '=https' \
|
|
113
|
+
--retry 2 --max-time 300 --output /tmp/wezterm.deb \
|
|
114
|
+
"https://github.com/wezterm/wezterm/releases/download/${wezterm_version}/wezterm-${wezterm_version}.${wezterm_suffix}"; \
|
|
115
|
+
printf '%s /tmp/wezterm.deb\n' "$wezterm_sha256" | sha256sum --check -; \
|
|
116
|
+
apt-get update; \
|
|
117
|
+
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
|
118
|
+
adwaita-icon-theme \
|
|
119
|
+
at-spi2-core \
|
|
120
|
+
bamfdaemon \
|
|
121
|
+
dbus-daemon \
|
|
122
|
+
dbus-x11 \
|
|
123
|
+
feh \
|
|
124
|
+
fonts-liberation \
|
|
125
|
+
gnome-mines \
|
|
126
|
+
gvfs \
|
|
127
|
+
libcurl4 \
|
|
128
|
+
libegl1 \
|
|
129
|
+
libgl1-mesa-dri \
|
|
130
|
+
libgtk-3-0 \
|
|
131
|
+
libvulkan1 \
|
|
132
|
+
openbox \
|
|
133
|
+
plank \
|
|
134
|
+
python3 \
|
|
135
|
+
python3-dbus \
|
|
136
|
+
scrot \
|
|
137
|
+
thunar \
|
|
138
|
+
tigervnc-common \
|
|
139
|
+
tigervnc-standalone-server \
|
|
140
|
+
tumbler \
|
|
141
|
+
xauth \
|
|
142
|
+
xcompmgr \
|
|
143
|
+
xdotool \
|
|
144
|
+
xfonts-base \
|
|
145
|
+
xterm \
|
|
146
|
+
/tmp/wezterm.deb; \
|
|
147
|
+
wezterm --version; \
|
|
148
|
+
rm -rf /tmp/wezterm.deb /var/lib/apt/lists/* /var/cache/apt/archives/*.deb
|
|
149
|
+
|
|
150
|
+
# Extract Chrome without running package scripts or registering its repository.
|
|
151
|
+
RUN set -eu; \
|
|
152
|
+
chrome_version=153.0.8010.36-1; \
|
|
153
|
+
chrome_arch="$(dpkg --print-architecture)"; \
|
|
154
|
+
case "$chrome_arch" in \
|
|
155
|
+
amd64) chrome_sha256=9bb44e33031c2f2857cf36b4343051a12f93058e4b781e3c76313df87f6c8d32 ;; \
|
|
156
|
+
arm64) chrome_sha256=1fcf6ec51a9d52e26ff1ad807225f725be55069ad2b68c5d69361a2746665188 ;; \
|
|
157
|
+
*) echo "Unsupported Chrome architecture" >&2; exit 1 ;; \
|
|
158
|
+
esac; \
|
|
159
|
+
curl --fail --location --proto '=https' --proto-redir '=https' \
|
|
160
|
+
--retry 2 --max-time 300 --output /tmp/chrome.deb \
|
|
161
|
+
"https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${chrome_version}_${chrome_arch}.deb"; \
|
|
162
|
+
printf '%s /tmp/chrome.deb\n' "$chrome_sha256" | sha256sum --check -; \
|
|
163
|
+
dpkg-deb --extract /tmp/chrome.deb /; \
|
|
164
|
+
/opt/google/chrome/chrome --version; \
|
|
165
|
+
rm /tmp/chrome.deb
|
|
166
|
+
|
|
104
167
|
# Copy bun binary from builder instead of re-installing
|
|
105
168
|
COPY --from=builder /root/.bun/bin/bun /usr/local/bin/bun
|
|
106
169
|
RUN ln -sf /usr/local/bin/bun /usr/local/bin/bunx
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Virtual desktop browser CLI
|
|
2
2
|
|
|
3
|
-
`assistant browser --virtual-desktop` uses the browser CLI's shared operation handlers against the Chrome process owned by `DesktopSessionManager`. It requires a platform-hosted container, the `assistant-desktop` flag,
|
|
3
|
+
`assistant browser --virtual-desktop` uses the browser CLI's shared operation handlers against the Chrome process owned by `DesktopSessionManager`. It requires a platform-hosted container, the `assistant-desktop` flag, image-provided desktop components and an identified guardian conversation. Availability is checked before starting or reusing control and after asynchronous startup; losing readiness or the feature flag cancels active control. The first eligible browser command starts Chrome and executes its action without downloading or installing dependencies. Opening the picture-in-picture panel checks readiness and starts the viewer directly. The persistent profile remains `data/desktop-profile`.
|
|
4
|
+
|
|
5
|
+
The assistant Dockerfile installs desktop system packages, pinned WezTerm and pinned Google Chrome before Bun and application source. Architecture-specific SHA-256 checksums verify both downloads. Chrome is extracted to `/opt/google/chrome` without running package scripts or registering its repository. Missing components require replacing the assistant image. Runtime code only checks readiness. `POST /v1/desktop/setup` remains a read-only compatibility endpoint for released clients; current clients only use GET.
|
|
4
6
|
|
|
5
7
|
Automatic browser selection in platform-hosted web conversations uses the streamed Chrome when the desktop flag is enabled and the actor is an identified guardian. The macOS and Windows apps keep their existing browser selection and fallback behavior; they use the streamed desktop only with explicit `--virtual-desktop`. Their shared renderer reports a `web` transport, so selection also reads the active turn's frozen `clientOs`; it does not change transport identity or host capability checks. Explicit `--virtual-desktop`, `--browser-mode`, `--target-client-id` and active-tab requests override the default. Existing personal-browser sessions and tab pins remain on their selected browser. Tab commands use the same streamed-browser default as page commands. `--browser-mode local` selects assistant-side Playwright, not the user's Chrome.
|
|
6
8
|
|
|
@@ -16,7 +18,7 @@ flowchart LR
|
|
|
16
18
|
Chrome --> Stream[Existing desktop stream]
|
|
17
19
|
```
|
|
18
20
|
|
|
19
|
-
The Terminal dock launcher opens [WezTerm](https://wezterm.org/), with a plain dark native title bar, minimize/maximize/close controls, a new-tab button and split panes. Use Ctrl+Shift+T for a tab, Ctrl+Shift+D to split side by side, and Ctrl+Shift+E to split top and bottom. The desktop seeds an editable `data/desktop-panel/wezterm/wezterm.lua` configuration with software rendering and a dark theme. The managed launcher keeps its stable `data/desktop-panel/applications/xterm.desktop` path so existing dock pins remain valid.
|
|
21
|
+
The Terminal dock launcher opens [WezTerm](https://wezterm.org/), with a plain dark native title bar, minimize/maximize/close controls, a new-tab button and split panes. Use Ctrl+Shift+T for a tab, Ctrl+Shift+D to split side by side, and Ctrl+Shift+E to split top and bottom. The desktop seeds an editable `data/desktop-panel/wezterm/wezterm.lua` configuration with software rendering and a dark theme. The managed launcher keeps its stable `data/desktop-panel/applications/xterm.desktop` path so existing dock pins remain valid. The image includes an exact-version, SHA-256-verified WezTerm package for Linux x64 or ARM64, plus its rendering libraries.
|
|
20
22
|
|
|
21
23
|
Chrome and WezTerm use explicit desktop window classes shared with their launcher entries. The desktop session bus and Plank share `XDG_DATA_HOME` so the D-Bus-activated window matcher can resolve the generated launchers and group running windows under their pinned icons.
|
|
22
24
|
|
|
@@ -34,9 +36,9 @@ The client records key and mouse presses before dispatch. On release it opens a
|
|
|
34
36
|
|
|
35
37
|
`--use-active-tab` and personal browser targeting are rejected with `--virtual-desktop`. Download waiting is unsupported. Browser operations are bounded to two minutes and share the desktop lease's action budget and idle expiry.
|
|
36
38
|
|
|
37
|
-
Validation: focused client tests exercise shared snapshot/click behavior, namespace isolation, stale references, target changes, cancellation and uncertain-input cleanup. Lease tests cover browser ownership, cancellation and cleanup independently of native input. The Linux smoke script exercises real Chrome, the CLI and visible pointer feedback.
|
|
39
|
+
Validation: focused client tests exercise shared snapshot/click behavior, namespace isolation, stale references, target changes, cancellation and uncertain-input cleanup. Lease tests cover browser ownership, cancellation and cleanup independently of native input. The Linux smoke script exercises real Chrome, the CLI and visible pointer feedback. For image replacement coverage, run `scripts/smoke-desktop-browser-cli.ts` in two disposable containers with the same workspace volume and networking disabled from first use. Both runs require baked components to be ready before navigation and assert that no installation progress is emitted. Each run needs a temporary `ASSISTANT_IPC_SOCKET_DIR`.
|
|
38
40
|
|
|
39
|
-
The desktop header icon pulses in the assistant's avatar accent color while a browser automation lease is active, including between browser commands. It shares the progress indicator's accent and neutral fallback. Reduced-motion clients show a solid accent. Setup status exposes the optional `automationActive` field; `desktop_activity_changed` events refresh it on acquisition and cancellation or release.
|
|
41
|
+
The desktop header icon pulses in the assistant's avatar accent color while a browser automation lease is active, including between browser commands. It shares the progress indicator's accent and neutral fallback. Reduced-motion clients show a solid accent. Setup status exposes the optional `automationActive` field; `desktop_activity_changed` events refresh it on acquisition and cancellation or release. The indicator reads status, and reconnects refetch the current lease state.
|
|
40
42
|
|
|
41
43
|
Desktop streaming checks the current in-memory gateway feature flags during connection startup. Once connected, frame and drain callbacks do not check feature flags or load workspace configuration. Turning the flag off prevents new connections; an existing stream continues until it closes or the desktop stops.
|
|
42
44
|
|
package/openapi.yaml
CHANGED
|
@@ -12012,7 +12012,7 @@ paths:
|
|
|
12012
12012
|
additionalProperties: false
|
|
12013
12013
|
post:
|
|
12014
12014
|
operationId: desktop_setup_post
|
|
12015
|
-
summary:
|
|
12015
|
+
summary: Check virtual desktop readiness (legacy POST)
|
|
12016
12016
|
tags:
|
|
12017
12017
|
- desktop
|
|
12018
12018
|
responses:
|
|
@@ -21363,6 +21363,100 @@ paths:
|
|
|
21363
21363
|
- redirectCount
|
|
21364
21364
|
- durationMs
|
|
21365
21365
|
additionalProperties: false
|
|
21366
|
+
recall:
|
|
21367
|
+
type: object
|
|
21368
|
+
properties:
|
|
21369
|
+
query:
|
|
21370
|
+
type: string
|
|
21371
|
+
depth:
|
|
21372
|
+
type: string
|
|
21373
|
+
enum:
|
|
21374
|
+
- fast
|
|
21375
|
+
- standard
|
|
21376
|
+
- deep
|
|
21377
|
+
sources:
|
|
21378
|
+
type: array
|
|
21379
|
+
items:
|
|
21380
|
+
type: string
|
|
21381
|
+
enum:
|
|
21382
|
+
- memory
|
|
21383
|
+
- conversations
|
|
21384
|
+
- workspace
|
|
21385
|
+
answer:
|
|
21386
|
+
type: string
|
|
21387
|
+
evidence:
|
|
21388
|
+
type: array
|
|
21389
|
+
items:
|
|
21390
|
+
type: object
|
|
21391
|
+
properties:
|
|
21392
|
+
source:
|
|
21393
|
+
type: string
|
|
21394
|
+
enum:
|
|
21395
|
+
- memory
|
|
21396
|
+
- conversations
|
|
21397
|
+
- workspace
|
|
21398
|
+
title:
|
|
21399
|
+
type: string
|
|
21400
|
+
locator:
|
|
21401
|
+
type: string
|
|
21402
|
+
excerpt:
|
|
21403
|
+
type: string
|
|
21404
|
+
timestampMs:
|
|
21405
|
+
type: number
|
|
21406
|
+
path:
|
|
21407
|
+
type: string
|
|
21408
|
+
conversationId:
|
|
21409
|
+
type: string
|
|
21410
|
+
messageId:
|
|
21411
|
+
type: string
|
|
21412
|
+
required:
|
|
21413
|
+
- source
|
|
21414
|
+
- title
|
|
21415
|
+
- locator
|
|
21416
|
+
- excerpt
|
|
21417
|
+
additionalProperties: false
|
|
21418
|
+
searchedSources:
|
|
21419
|
+
type: array
|
|
21420
|
+
items:
|
|
21421
|
+
type: object
|
|
21422
|
+
properties:
|
|
21423
|
+
source:
|
|
21424
|
+
type: string
|
|
21425
|
+
enum:
|
|
21426
|
+
- memory
|
|
21427
|
+
- conversations
|
|
21428
|
+
- workspace
|
|
21429
|
+
status:
|
|
21430
|
+
type: string
|
|
21431
|
+
enum:
|
|
21432
|
+
- searched
|
|
21433
|
+
- degraded
|
|
21434
|
+
evidenceCount:
|
|
21435
|
+
type: number
|
|
21436
|
+
error:
|
|
21437
|
+
type: string
|
|
21438
|
+
required:
|
|
21439
|
+
- source
|
|
21440
|
+
- status
|
|
21441
|
+
- evidenceCount
|
|
21442
|
+
additionalProperties: false
|
|
21443
|
+
required:
|
|
21444
|
+
- query
|
|
21445
|
+
- depth
|
|
21446
|
+
- sources
|
|
21447
|
+
- evidence
|
|
21448
|
+
- searchedSources
|
|
21449
|
+
additionalProperties: false
|
|
21450
|
+
remember:
|
|
21451
|
+
type: object
|
|
21452
|
+
properties:
|
|
21453
|
+
facts:
|
|
21454
|
+
type: array
|
|
21455
|
+
items:
|
|
21456
|
+
type: string
|
|
21457
|
+
required:
|
|
21458
|
+
- facts
|
|
21459
|
+
additionalProperties: false
|
|
21366
21460
|
additionalProperties: false
|
|
21367
21461
|
scopeOptions:
|
|
21368
21462
|
type: array
|
|
@@ -21883,6 +21977,100 @@ paths:
|
|
|
21883
21977
|
- redirectCount
|
|
21884
21978
|
- durationMs
|
|
21885
21979
|
additionalProperties: false
|
|
21980
|
+
recall:
|
|
21981
|
+
type: object
|
|
21982
|
+
properties:
|
|
21983
|
+
query:
|
|
21984
|
+
type: string
|
|
21985
|
+
depth:
|
|
21986
|
+
type: string
|
|
21987
|
+
enum:
|
|
21988
|
+
- fast
|
|
21989
|
+
- standard
|
|
21990
|
+
- deep
|
|
21991
|
+
sources:
|
|
21992
|
+
type: array
|
|
21993
|
+
items:
|
|
21994
|
+
type: string
|
|
21995
|
+
enum:
|
|
21996
|
+
- memory
|
|
21997
|
+
- conversations
|
|
21998
|
+
- workspace
|
|
21999
|
+
answer:
|
|
22000
|
+
type: string
|
|
22001
|
+
evidence:
|
|
22002
|
+
type: array
|
|
22003
|
+
items:
|
|
22004
|
+
type: object
|
|
22005
|
+
properties:
|
|
22006
|
+
source:
|
|
22007
|
+
type: string
|
|
22008
|
+
enum:
|
|
22009
|
+
- memory
|
|
22010
|
+
- conversations
|
|
22011
|
+
- workspace
|
|
22012
|
+
title:
|
|
22013
|
+
type: string
|
|
22014
|
+
locator:
|
|
22015
|
+
type: string
|
|
22016
|
+
excerpt:
|
|
22017
|
+
type: string
|
|
22018
|
+
timestampMs:
|
|
22019
|
+
type: number
|
|
22020
|
+
path:
|
|
22021
|
+
type: string
|
|
22022
|
+
conversationId:
|
|
22023
|
+
type: string
|
|
22024
|
+
messageId:
|
|
22025
|
+
type: string
|
|
22026
|
+
required:
|
|
22027
|
+
- source
|
|
22028
|
+
- title
|
|
22029
|
+
- locator
|
|
22030
|
+
- excerpt
|
|
22031
|
+
additionalProperties: false
|
|
22032
|
+
searchedSources:
|
|
22033
|
+
type: array
|
|
22034
|
+
items:
|
|
22035
|
+
type: object
|
|
22036
|
+
properties:
|
|
22037
|
+
source:
|
|
22038
|
+
type: string
|
|
22039
|
+
enum:
|
|
22040
|
+
- memory
|
|
22041
|
+
- conversations
|
|
22042
|
+
- workspace
|
|
22043
|
+
status:
|
|
22044
|
+
type: string
|
|
22045
|
+
enum:
|
|
22046
|
+
- searched
|
|
22047
|
+
- degraded
|
|
22048
|
+
evidenceCount:
|
|
22049
|
+
type: number
|
|
22050
|
+
error:
|
|
22051
|
+
type: string
|
|
22052
|
+
required:
|
|
22053
|
+
- source
|
|
22054
|
+
- status
|
|
22055
|
+
- evidenceCount
|
|
22056
|
+
additionalProperties: false
|
|
22057
|
+
required:
|
|
22058
|
+
- query
|
|
22059
|
+
- depth
|
|
22060
|
+
- sources
|
|
22061
|
+
- evidence
|
|
22062
|
+
- searchedSources
|
|
22063
|
+
additionalProperties: false
|
|
22064
|
+
remember:
|
|
22065
|
+
type: object
|
|
22066
|
+
properties:
|
|
22067
|
+
facts:
|
|
22068
|
+
type: array
|
|
22069
|
+
items:
|
|
22070
|
+
type: string
|
|
22071
|
+
required:
|
|
22072
|
+
- facts
|
|
22073
|
+
additionalProperties: false
|
|
21886
22074
|
additionalProperties: false
|
|
21887
22075
|
scopeOptions:
|
|
21888
22076
|
type: array
|
package/package.json
CHANGED
|
@@ -14,26 +14,18 @@ import { registerBrowserCommand } from "../src/cli/commands/browser.js";
|
|
|
14
14
|
import { DesktopAutomationLease } from "../src/desktop/desktop-automation-lease.js";
|
|
15
15
|
import {
|
|
16
16
|
desktopChromePath,
|
|
17
|
-
|
|
17
|
+
desktopDependencies,
|
|
18
18
|
} from "../src/desktop/desktop-dependencies.js";
|
|
19
19
|
import { DesktopSessionManager } from "../src/desktop/desktop-session-manager.js";
|
|
20
20
|
import { getAssistantSocketPath } from "../src/ipc/socket-path.js";
|
|
21
21
|
|
|
22
|
-
if (
|
|
23
|
-
process.platform !== "linux" ||
|
|
24
|
-
!process.argv[2] ||
|
|
25
|
-
!process.env.ASSISTANT_IPC_SOCKET_DIR
|
|
26
|
-
) {
|
|
22
|
+
if (process.platform !== "linux" || !process.env.ASSISTANT_IPC_SOCKET_DIR) {
|
|
27
23
|
throw new Error(
|
|
28
|
-
"Run in disposable Linux
|
|
24
|
+
"Run in a disposable Linux assistant image with a temporary ASSISTANT_IPC_SOCKET_DIR.",
|
|
29
25
|
);
|
|
30
26
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
if (coldInstall) {
|
|
34
|
-
assert.equal(desktopDependencyInstaller.getStatus().state, "required");
|
|
35
|
-
assert.equal(Bun.which("Xtigervnc"), null);
|
|
36
|
-
}
|
|
27
|
+
const executable = desktopChromePath();
|
|
28
|
+
assert.equal(desktopDependencies.getStatus().state, "ready");
|
|
37
29
|
const directory = await mkdtemp(join(tmpdir(), "desktop-browser-cli-"));
|
|
38
30
|
const manager = new DesktopSessionManager({
|
|
39
31
|
resolveChromePath: async () => executable,
|
|
@@ -44,9 +36,7 @@ const manager = new DesktopSessionManager({
|
|
|
44
36
|
const control = new DesktopAutomationLease({
|
|
45
37
|
notify: async () => {},
|
|
46
38
|
enabled: () => true,
|
|
47
|
-
ready: () =>
|
|
48
|
-
!coldInstall || desktopDependencyInstaller.getStatus().state === "ready",
|
|
49
|
-
ensureReady: (signal) => desktopDependencyInstaller.ensureReady(signal),
|
|
39
|
+
ready: () => desktopDependencies.getStatus().state === "ready",
|
|
50
40
|
manager: () => manager,
|
|
51
41
|
});
|
|
52
42
|
const context = {
|
|
@@ -182,16 +172,10 @@ try {
|
|
|
182
172
|
"--allow-private-network",
|
|
183
173
|
);
|
|
184
174
|
assert.equal(navigations, 1);
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
"Installation progress must reach the viewer",
|
|
190
|
-
);
|
|
191
|
-
console.error(
|
|
192
|
-
"PASS: one CLI navigate installed desktop and Chrome from scratch, then loaded the requested page once",
|
|
193
|
-
);
|
|
194
|
-
}
|
|
175
|
+
assert.equal(setupNotifications, 0);
|
|
176
|
+
console.error(
|
|
177
|
+
"PASS: baked desktop opened the requested page without installation",
|
|
178
|
+
);
|
|
195
179
|
const snapshot = await cli("snapshot");
|
|
196
180
|
await cli(
|
|
197
181
|
"type",
|
|
@@ -9,6 +9,7 @@ import { tmpdir } from "node:os";
|
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
11
|
|
|
12
|
+
import { RecallMetadataSchema } from "../api/events/tool-result.js";
|
|
12
13
|
import type { AssistantConfig } from "../config/schema.js";
|
|
13
14
|
import type { Provider, ProviderResponse } from "../providers/types.js";
|
|
14
15
|
|
|
@@ -943,3 +944,261 @@ describe("runAgenticRecall", () => {
|
|
|
943
944
|
});
|
|
944
945
|
});
|
|
945
946
|
});
|
|
947
|
+
|
|
948
|
+
describe("runAgenticRecall activity", () => {
|
|
949
|
+
beforeEach(() => {
|
|
950
|
+
configuredProvider = null;
|
|
951
|
+
getConfiguredProviderCallSites.length = 0;
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
const launchAdapter = () =>
|
|
955
|
+
makeAdapter({
|
|
956
|
+
"launch notes": [
|
|
957
|
+
makeEvidence("workspace:launch", {
|
|
958
|
+
excerpt: " Alice chose\nFriday. ",
|
|
959
|
+
timestampMs: 1_700_000_000_000,
|
|
960
|
+
}),
|
|
961
|
+
makeEvidence("workspace:retro"),
|
|
962
|
+
],
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
test("carries the finish answer alone and the cited evidence", async () => {
|
|
966
|
+
configuredProvider = makeProvider([
|
|
967
|
+
toolResponse("finish_recall", {
|
|
968
|
+
answer: "Alice chose Friday.",
|
|
969
|
+
confidence: "high",
|
|
970
|
+
citation_ids: ["workspace:launch"],
|
|
971
|
+
}),
|
|
972
|
+
]);
|
|
973
|
+
|
|
974
|
+
const result = await runAgenticRecall(
|
|
975
|
+
{ query: "launch notes", sources: ["workspace"], depth: "fast" },
|
|
976
|
+
makeContext(),
|
|
977
|
+
{ searchOptions: { adapters: [launchAdapter()] } },
|
|
978
|
+
);
|
|
979
|
+
|
|
980
|
+
expect(result.activity).toEqual({
|
|
981
|
+
query: "launch notes",
|
|
982
|
+
depth: "fast",
|
|
983
|
+
sources: ["workspace"],
|
|
984
|
+
answer: "Alice chose Friday.",
|
|
985
|
+
evidence: [
|
|
986
|
+
{
|
|
987
|
+
source: "workspace",
|
|
988
|
+
title: "workspace:launch title",
|
|
989
|
+
locator: "workspace:launch.md",
|
|
990
|
+
excerpt: "Alice chose Friday.",
|
|
991
|
+
timestampMs: 1_700_000_000_000,
|
|
992
|
+
},
|
|
993
|
+
],
|
|
994
|
+
searchedSources: [
|
|
995
|
+
{ source: "workspace", status: "searched", evidenceCount: 2 },
|
|
996
|
+
],
|
|
997
|
+
});
|
|
998
|
+
expect(RecallMetadataSchema.parse(result.activity)).toEqual(
|
|
999
|
+
result.activity,
|
|
1000
|
+
);
|
|
1001
|
+
});
|
|
1002
|
+
|
|
1003
|
+
test("lists the available evidence the text appends on a low-confidence answer", async () => {
|
|
1004
|
+
configuredProvider = makeProvider([
|
|
1005
|
+
toolResponse("finish_recall", {
|
|
1006
|
+
answer: "Probably Friday.",
|
|
1007
|
+
confidence: "low",
|
|
1008
|
+
citation_ids: ["workspace:launch"],
|
|
1009
|
+
}),
|
|
1010
|
+
]);
|
|
1011
|
+
|
|
1012
|
+
const result = await runAgenticRecall(
|
|
1013
|
+
{ query: "launch notes", sources: ["workspace"] },
|
|
1014
|
+
makeContext(),
|
|
1015
|
+
{ searchOptions: { adapters: [launchAdapter()] } },
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
expect(result.content).toContain("Available evidence:");
|
|
1019
|
+
expect(result.activity.answer).toBe("Probably Friday.");
|
|
1020
|
+
expect(result.activity.evidence.map((item) => item.title)).toEqual([
|
|
1021
|
+
"workspace:launch title",
|
|
1022
|
+
"workspace:retro title",
|
|
1023
|
+
]);
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
test("has no answer when recall falls back to listing what it found", async () => {
|
|
1027
|
+
const result = await runAgenticRecall(
|
|
1028
|
+
{ query: "launch notes", sources: ["workspace"] },
|
|
1029
|
+
makeContext(),
|
|
1030
|
+
{ searchOptions: { adapters: [launchAdapter()] } },
|
|
1031
|
+
);
|
|
1032
|
+
|
|
1033
|
+
expect(result.debug.mode).toBe("deterministic_fallback");
|
|
1034
|
+
expect(result.activity.answer).toBeUndefined();
|
|
1035
|
+
expect(result.activity.depth).toBe("standard");
|
|
1036
|
+
expect(result.activity.evidence.map((item) => item.title)).toEqual([
|
|
1037
|
+
"workspace:launch title",
|
|
1038
|
+
"workspace:retro title",
|
|
1039
|
+
]);
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
test("carries the file or conversation each item was found in", async () => {
|
|
1043
|
+
const result = await runAgenticRecall(
|
|
1044
|
+
{ query: "launch notes", sources: ["workspace", "conversations"] },
|
|
1045
|
+
makeContext(),
|
|
1046
|
+
{
|
|
1047
|
+
searchOptions: {
|
|
1048
|
+
adapters: [
|
|
1049
|
+
makeAdapter({
|
|
1050
|
+
"launch notes": [
|
|
1051
|
+
makeEvidence("workspace:launch", {
|
|
1052
|
+
metadata: { path: "notes/launch.md", lineNumber: 4 },
|
|
1053
|
+
}),
|
|
1054
|
+
makeEvidence("workspace:unreadable", {
|
|
1055
|
+
metadata: { path: "notes/gone.md", inspectError: true },
|
|
1056
|
+
}),
|
|
1057
|
+
],
|
|
1058
|
+
}),
|
|
1059
|
+
makeAdapter(
|
|
1060
|
+
{
|
|
1061
|
+
"launch notes": [
|
|
1062
|
+
makeEvidence("conversations:c1:m1", {
|
|
1063
|
+
source: "conversations",
|
|
1064
|
+
metadata: {
|
|
1065
|
+
conversationId: "c1",
|
|
1066
|
+
messageId: "m1",
|
|
1067
|
+
role: "user",
|
|
1068
|
+
},
|
|
1069
|
+
}),
|
|
1070
|
+
],
|
|
1071
|
+
},
|
|
1072
|
+
[],
|
|
1073
|
+
"conversations",
|
|
1074
|
+
),
|
|
1075
|
+
],
|
|
1076
|
+
},
|
|
1077
|
+
},
|
|
1078
|
+
);
|
|
1079
|
+
|
|
1080
|
+
const byTitle = Object.fromEntries(
|
|
1081
|
+
result.activity.evidence.map((item) => [item.title, item]),
|
|
1082
|
+
);
|
|
1083
|
+
expect(byTitle["workspace:launch title"]?.path).toBe("notes/launch.md");
|
|
1084
|
+
expect(byTitle["workspace:unreadable title"]?.path).toBeUndefined();
|
|
1085
|
+
expect(byTitle["conversations:c1:m1 title"]?.conversationId).toBe("c1");
|
|
1086
|
+
expect(byTitle["conversations:c1:m1 title"]?.messageId).toBe("m1");
|
|
1087
|
+
expect(byTitle["conversations:c1:m1 title"]?.path).toBeUndefined();
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
test("reports a source that degrades on a follow-up search", async () => {
|
|
1091
|
+
configuredProvider = makeProvider([
|
|
1092
|
+
toolResponse("search_sources", {
|
|
1093
|
+
query: "decision notes",
|
|
1094
|
+
sources: ["workspace"],
|
|
1095
|
+
}),
|
|
1096
|
+
toolResponse("finish_recall", {
|
|
1097
|
+
answer: "Friday.",
|
|
1098
|
+
confidence: "medium",
|
|
1099
|
+
citation_ids: ["workspace:seed"],
|
|
1100
|
+
}),
|
|
1101
|
+
]);
|
|
1102
|
+
|
|
1103
|
+
const result = await runAgenticRecall(
|
|
1104
|
+
{ query: "launch notes", sources: ["workspace"] },
|
|
1105
|
+
makeContext(),
|
|
1106
|
+
{
|
|
1107
|
+
searchOptions: {
|
|
1108
|
+
adapters: [
|
|
1109
|
+
{
|
|
1110
|
+
source: "workspace",
|
|
1111
|
+
async search(query) {
|
|
1112
|
+
if (query === "decision notes") {
|
|
1113
|
+
throw new Error("index unavailable");
|
|
1114
|
+
}
|
|
1115
|
+
return { evidence: [makeEvidence("workspace:seed")] };
|
|
1116
|
+
},
|
|
1117
|
+
},
|
|
1118
|
+
],
|
|
1119
|
+
},
|
|
1120
|
+
},
|
|
1121
|
+
);
|
|
1122
|
+
|
|
1123
|
+
expect(result.activity.searchedSources).toEqual([
|
|
1124
|
+
{
|
|
1125
|
+
source: "workspace",
|
|
1126
|
+
status: "degraded",
|
|
1127
|
+
evidenceCount: 1,
|
|
1128
|
+
error: "index unavailable",
|
|
1129
|
+
},
|
|
1130
|
+
]);
|
|
1131
|
+
expect(result.content).toContain(
|
|
1132
|
+
"Degraded sources: workspace (index unavailable).",
|
|
1133
|
+
);
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
test("clears a degradation that a follow-up search recovers from", async () => {
|
|
1137
|
+
configuredProvider = makeProvider([
|
|
1138
|
+
toolResponse("search_sources", {
|
|
1139
|
+
query: "decision notes",
|
|
1140
|
+
sources: ["workspace"],
|
|
1141
|
+
}),
|
|
1142
|
+
toolResponse("finish_recall", {
|
|
1143
|
+
answer: "Friday.",
|
|
1144
|
+
confidence: "medium",
|
|
1145
|
+
citation_ids: ["workspace:decision"],
|
|
1146
|
+
}),
|
|
1147
|
+
]);
|
|
1148
|
+
|
|
1149
|
+
const result = await runAgenticRecall(
|
|
1150
|
+
{ query: "launch notes", sources: ["workspace"] },
|
|
1151
|
+
makeContext(),
|
|
1152
|
+
{
|
|
1153
|
+
searchOptions: {
|
|
1154
|
+
adapters: [
|
|
1155
|
+
{
|
|
1156
|
+
source: "workspace",
|
|
1157
|
+
async search(query) {
|
|
1158
|
+
if (query === "launch notes") {
|
|
1159
|
+
throw new Error("index warming up");
|
|
1160
|
+
}
|
|
1161
|
+
return { evidence: [makeEvidence("workspace:decision")] };
|
|
1162
|
+
},
|
|
1163
|
+
},
|
|
1164
|
+
],
|
|
1165
|
+
},
|
|
1166
|
+
},
|
|
1167
|
+
);
|
|
1168
|
+
|
|
1169
|
+
expect(result.activity.searchedSources).toEqual([
|
|
1170
|
+
{ source: "workspace", status: "searched", evidenceCount: 1 },
|
|
1171
|
+
]);
|
|
1172
|
+
expect(result.content).not.toContain("Degraded sources");
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
test("reports a degraded source and no evidence when nothing is found", async () => {
|
|
1176
|
+
const result = await runAgenticRecall(
|
|
1177
|
+
{ query: "launch notes", sources: ["workspace"] },
|
|
1178
|
+
makeContext(),
|
|
1179
|
+
{
|
|
1180
|
+
searchOptions: {
|
|
1181
|
+
adapters: [
|
|
1182
|
+
{
|
|
1183
|
+
source: "workspace",
|
|
1184
|
+
async search() {
|
|
1185
|
+
throw new Error("index unavailable");
|
|
1186
|
+
},
|
|
1187
|
+
},
|
|
1188
|
+
],
|
|
1189
|
+
},
|
|
1190
|
+
},
|
|
1191
|
+
);
|
|
1192
|
+
|
|
1193
|
+
expect(result.content).toContain("No reliable results found.");
|
|
1194
|
+
expect(result.activity.evidence).toEqual([]);
|
|
1195
|
+
expect(result.activity.searchedSources).toEqual([
|
|
1196
|
+
{
|
|
1197
|
+
source: "workspace",
|
|
1198
|
+
status: "degraded",
|
|
1199
|
+
evidenceCount: 0,
|
|
1200
|
+
error: "index unavailable",
|
|
1201
|
+
},
|
|
1202
|
+
]);
|
|
1203
|
+
});
|
|
1204
|
+
});
|