@vellumai/assistant 0.11.8-dev.202609022016.7bd8f87 → 0.11.8-dev.202609030046.e26b25a

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 (47) hide show
  1. package/Dockerfile +8 -48
  2. package/docker-entrypoint.sh +5 -1
  3. package/docker-kata-apt-env.sh +3 -0
  4. package/docker-kata-apt-shims.sh +127 -0
  5. package/docker-kata-apt-wrapper.sh +45 -0
  6. package/docker-kata-chroot-exec.sh +35 -0
  7. package/docker-kata-pip.sh +8 -2
  8. package/package.json +1 -1
  9. package/src/__tests__/attachment-stored-path-annotation.test.ts +80 -0
  10. package/src/__tests__/attachments-store.test.ts +27 -0
  11. package/src/__tests__/attachments.test.ts +43 -0
  12. package/src/__tests__/docker-kata-apt-shims.test.ts +135 -0
  13. package/src/__tests__/media-resolve-image-validation.test.ts +77 -0
  14. package/src/__tests__/process-message-display-content.test.ts +4 -9
  15. package/src/__tests__/terminal-tools.test.ts +9 -0
  16. package/src/agent/attachments.ts +13 -2
  17. package/src/config/feature-flag-registry.json +1 -1
  18. package/src/daemon/conversation-messaging.ts +88 -25
  19. package/src/daemon/port-oversized-content.test.ts +117 -0
  20. package/src/daemon/port-oversized-content.ts +110 -0
  21. package/src/live-voice/__tests__/live-voice-photo.test.ts +11 -11
  22. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +10 -2
  23. package/src/messaging/providers/channel-transport.ts +28 -0
  24. package/src/messaging/providers/slack/send.test.ts +58 -0
  25. package/src/messaging/providers/slack/transport.ts +28 -2
  26. package/src/messaging/providers/telegram-bot/send.test.ts +159 -0
  27. package/src/messaging/providers/telegram-bot/send.ts +93 -0
  28. package/src/messaging/providers/telegram-bot/transport.ts +71 -0
  29. package/src/providers/anthropic/client.ts +21 -24
  30. package/src/providers/content-block-size.test.ts +82 -0
  31. package/src/providers/content-block-size.ts +99 -0
  32. package/src/providers/file-block-text.test.ts +64 -0
  33. package/src/providers/file-block-text.ts +28 -0
  34. package/src/providers/gemini/client.ts +12 -7
  35. package/src/providers/media-resolve.ts +14 -0
  36. package/src/providers/model-catalog.ts +20 -20
  37. package/src/providers/openai/chat-completions-provider.ts +9 -25
  38. package/src/providers/openai/responses-provider.ts +11 -18
  39. package/src/runtime/AGENTS.md +4 -0
  40. package/src/runtime/{slack-reply-session.test.ts → channel-reply-session.test.ts} +212 -163
  41. package/src/runtime/{slack-reply-session.ts → channel-reply-session.ts} +77 -77
  42. package/src/runtime/channel-retry-sweep.ts +1 -1
  43. package/src/runtime/finalize-event-delivery.ts +4 -4
  44. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +28 -4
  45. package/src/runtime/routes/inbound-stages/background-dispatch.ts +5 -7
  46. package/src/tools/terminal/safe-env.ts +4 -0
  47. package/docker-kata-pip-chroot.sh +0 -22
package/Dockerfile CHANGED
@@ -186,53 +186,11 @@ RUN printf '%s\n' \
186
186
  >> /home/assistant/.bashrc && \
187
187
  chown assistant:assistant /home/assistant/.bashrc
188
188
 
189
- RUN printf '%s\n' \
190
- '#!/usr/bin/env sh' \
191
- 'set -eu' \
192
- '. /app/assistant/docker-kata-runtime-family.sh' \
193
- 'if ! vellum_is_kata_family_runtime; then' \
194
- ' exec /usr/bin/apt-get "$@"' \
195
- 'fi' \
196
- 'export DEBIAN_FRONTEND=noninteractive' \
197
- 'DATA_ROOT="${VELLUM_APT_DATA_ROOT:-/data/system}"' \
198
- '/app/assistant/docker-init-apt-root.sh' \
199
- 'if [ -x "${DATA_ROOT}/bin/sh" ] && [ -x "${DATA_ROOT}/usr/bin/apt-get" ] && [ -f "${DATA_ROOT}/.rootfs-initialized" ] && ! grep -qs " ${DATA_ROOT} .*noexec" /proc/mounts; then' \
200
- ' exec chroot "${DATA_ROOT}" /usr/bin/apt-get "$@"' \
201
- 'fi' \
202
- 'exec /usr/bin/apt-get "$@"' \
203
- > /usr/local/bin/apt-get && \
204
- chmod +x /usr/local/bin/apt-get && \
205
- printf '%s\n' \
206
- '#!/usr/bin/env sh' \
207
- 'set -eu' \
208
- '. /app/assistant/docker-kata-runtime-family.sh' \
209
- 'if ! vellum_is_kata_family_runtime; then' \
210
- ' exec /usr/bin/apt "$@"' \
211
- 'fi' \
212
- 'export DEBIAN_FRONTEND=noninteractive' \
213
- 'DATA_ROOT="${VELLUM_APT_DATA_ROOT:-/data/system}"' \
214
- '/app/assistant/docker-init-apt-root.sh' \
215
- 'if [ -x "${DATA_ROOT}/bin/sh" ] && [ -x "${DATA_ROOT}/usr/bin/apt" ] && [ -f "${DATA_ROOT}/.rootfs-initialized" ] && ! grep -qs " ${DATA_ROOT} .*noexec" /proc/mounts; then' \
216
- ' exec chroot "${DATA_ROOT}" /usr/bin/apt "$@"' \
217
- 'fi' \
218
- 'exec /usr/bin/apt "$@"' \
219
- > /usr/local/bin/apt && \
220
- chmod +x /usr/local/bin/apt && \
221
- printf '%s\n' \
222
- '#!/usr/bin/env sh' \
223
- 'set -eu' \
224
- '. /app/assistant/docker-kata-runtime-family.sh' \
225
- 'if ! vellum_is_kata_family_runtime; then' \
226
- ' exec /usr/bin/dpkg "$@"' \
227
- 'fi' \
228
- 'DATA_ROOT="${VELLUM_APT_DATA_ROOT:-/data/system}"' \
229
- '/app/assistant/docker-init-apt-root.sh' \
230
- 'if [ -x "${DATA_ROOT}/bin/sh" ] && [ -x "${DATA_ROOT}/usr/bin/dpkg" ] && [ -f "${DATA_ROOT}/.rootfs-initialized" ] && ! grep -qs " ${DATA_ROOT} .*noexec" /proc/mounts; then' \
231
- ' exec chroot "${DATA_ROOT}" /usr/bin/dpkg "$@"' \
232
- 'fi' \
233
- 'exec /usr/bin/dpkg "$@"' \
234
- > /usr/local/bin/dpkg && \
235
- chmod +x /usr/local/bin/dpkg
189
+ # apt/apt-get/dpkg must persist like pip: on Kata-family runtimes the wrapper
190
+ # routes package operations into the persistent apt chroot.
191
+ RUN ln -s /app/assistant/docker-kata-apt-wrapper.sh /usr/local/bin/apt-get && \
192
+ ln -s /app/assistant/docker-kata-apt-wrapper.sh /usr/local/bin/apt && \
193
+ ln -s /app/assistant/docker-kata-apt-wrapper.sh /usr/local/bin/dpkg
236
194
 
237
195
  # pip must persist like apt: on Kata-family runtimes the wrapper routes root
238
196
  # installs into the persistent apt chroot.
@@ -274,8 +232,10 @@ RUN chmod +x \
274
232
  /app/assistant/docker-entrypoint.sh \
275
233
  /app/assistant/docker-init-apt-root.sh \
276
234
  /app/assistant/docker-kata-apt-env.sh \
235
+ /app/assistant/docker-kata-apt-shims.sh \
236
+ /app/assistant/docker-kata-apt-wrapper.sh \
237
+ /app/assistant/docker-kata-chroot-exec.sh \
277
238
  /app/assistant/docker-kata-pip.sh \
278
- /app/assistant/docker-kata-pip-chroot.sh \
279
239
  /app/assistant/docker-kata-runtime-family.sh \
280
240
  /usr/local/bin/vellum-block-volume-common.sh \
281
241
  /usr/local/bin/vellum-block-volume-init.sh \
@@ -11,7 +11,11 @@ KATA_APT_INIT_PID=""
11
11
  if vellum_is_kata_family_runtime && [ -x /app/assistant/docker-init-apt-root.sh ]; then
12
12
  export VELLUM_APT_DATA_ROOT="${VELLUM_APT_DATA_ROOT:-/data/system}"
13
13
  # Warm the chroot used by Kata-family apt wrappers without blocking assistant readiness.
14
- /app/assistant/docker-init-apt-root.sh &
14
+ (
15
+ /app/assistant/docker-init-apt-root.sh
16
+ # Refresh persisted wrapper-script shims before commands use the overlay.
17
+ /app/assistant/docker-kata-apt-shims.sh || true
18
+ ) &
15
19
  KATA_APT_INIT_PID="$!"
16
20
  fi
17
21
 
@@ -36,6 +36,9 @@ _vellum_kata_prepend_library_path() {
36
36
  esac
37
37
  }
38
38
 
39
+ # Shims for chroot wrapper scripts with hardcoded absolute paths must shadow
40
+ # the broken originals in the chroot bin dirs below (docker-kata-apt-shims.sh).
41
+ _vellum_kata_append_path "${VELLUM_APT_DATA_ROOT}/.host-shims"
39
42
  _vellum_kata_append_path "${VELLUM_APT_DATA_ROOT}/bin"
40
43
  _vellum_kata_append_path "${VELLUM_APT_DATA_ROOT}/usr/local/sbin"
41
44
  _vellum_kata_append_path "${VELLUM_APT_DATA_ROOT}/usr/local/bin"
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env sh
2
+ # Regenerates host-side shims for chroot-installed commands whose bin entry
3
+ # is a pure trampoline script exec'ing a hardcoded absolute path (Debian
4
+ # 7zip's /usr/bin/7z execs /usr/lib/7zip/7z, for example). Those targets only
5
+ # exist inside the chroot, so running the wrapper through the host PATH
6
+ # overlay fails; each shim execs the chroot-prefixed target instead.
7
+ # docker-kata-apt-env.sh and buildSanitizedEnv put the shim dir on PATH ahead
8
+ # of the chroot bin dirs so shims shadow the broken wrappers.
9
+ set -eu
10
+
11
+ DATA_ROOT="${VELLUM_APT_DATA_ROOT:-/data/system}"
12
+ SHIM_DIR="${DATA_ROOT}/.host-shims"
13
+ MARKER="# vellum-apt-shim:"
14
+ # dpkg-managed dirs that can hold trampolines (Debian policy keeps packages
15
+ # out of /usr/local; /bin and /sbin are usrmerge symlinks into these).
16
+ SCAN_DIRS="usr/bin usr/sbin usr/games"
17
+ # Every overlay bin dir in PATH priority order (usrmerge collapses bin/sbin
18
+ # into their usr counterparts): precedence must also see the /usr/local dirs
19
+ # that chroot pip installs populate, so a dpkg trampoline never shims over a
20
+ # higher-priority local command.
21
+ PRIORITY_DIRS="usr/bin usr/local/sbin usr/local/bin usr/sbin usr/games"
22
+
23
+ [ -d "${DATA_ROOT}/usr/bin" ] || exit 0
24
+ mkdir -p "${SHIM_DIR}"
25
+
26
+ # Scanning the bin dirs forks a few processes per file; skip it when the
27
+ # overlay state is unchanged (read-only invocations like `apt list` or
28
+ # `dpkg -l`). The stamp covers command names, links, effective modes, and small
29
+ # executable contents so reinstalls and pip changes invalidate stale shims.
30
+ STAMP_FILE="${SHIM_DIR}/.overlay-stamp"
31
+ STAMP="$(
32
+ {
33
+ md5sum "${DATA_ROOT}/var/lib/dpkg/status" 2>/dev/null
34
+ for d in ${PRIORITY_DIRS}; do
35
+ printf '%s\0' "${d}"
36
+ find "${DATA_ROOT}/${d}" -mindepth 1 -maxdepth 1 -printf '%P %y %m %l\0' 2>/dev/null | sort -z
37
+ find -L "${DATA_ROOT}/${d}" -mindepth 1 -maxdepth 1 -printf '%P %y %m\0' 2>/dev/null | sort -z
38
+ done
39
+ for d in ${SCAN_DIRS}; do
40
+ find -L "${DATA_ROOT}/${d}" -mindepth 1 -maxdepth 1 -type f -size -513c -perm /111 -print0 2>/dev/null |
41
+ sort -z |
42
+ xargs -0 -r md5sum -z
43
+ done
44
+ } | md5sum | cut -d' ' -f1
45
+ )"
46
+ if [ "${STAMP}" = "$(cat "${STAMP_FILE}" 2>/dev/null || true)" ]; then
47
+ exit 0
48
+ fi
49
+
50
+ # Prints the target of a pure trampoline: a tiny script whose body is nothing
51
+ # but the shebang, comments, blank lines, and a single `exec /abs/path "$@"`
52
+ # line. Wrappers that do anything else (export env vars, cd, pass extra
53
+ # flags) print nothing: a direct-exec shim would bypass that setup.
54
+ wrapper_target() {
55
+ [ -f "$1" ] && [ -x "$1" ] || return 0
56
+ [ "$(wc -c <"$1")" -le 512 ] || return 0
57
+ case "$(head -c 2 "$1" 2>/dev/null)" in
58
+ '#!') ;;
59
+ *) return 0 ;;
60
+ esac
61
+ body="$(sed -e '1d' -e '/^[[:space:]]*#/d' -e '/^[[:space:]]*$/d' "$1")"
62
+ [ -n "${body}" ] || return 0
63
+ [ "$(printf '%s' "${body}" | wc -l)" -eq 0 ] || return 0
64
+ printf '%s\n' "${body}" | sed -n 's/^[[:space:]]*exec[[:space:]][[:space:]]*\(\/[^"[:space:]][^"[:space:]]*\)[[:space:]][[:space:]]*"\$@"[[:space:]]*$/\1/p'
65
+ }
66
+
67
+ # Highest-priority overlay dir that holds an entry for this shim name.
68
+ shim_source() {
69
+ for d in ${PRIORITY_DIRS}; do
70
+ candidate="${DATA_ROOT}/${d}/$1"
71
+ if [ -f "${candidate}" ] && [ -x "${candidate}" ]; then
72
+ printf '%s\n' "${candidate}"
73
+ return 0
74
+ fi
75
+ done
76
+ }
77
+
78
+ generation_failed=0
79
+ for d in ${SCAN_DIRS}; do
80
+ for bin in "${DATA_ROOT}/${d}/"*; do
81
+ # Shims outrank every chroot bin dir on PATH, so only the
82
+ # highest-priority entry for a name may produce one: a trampoline in a
83
+ # lower dir must not shadow a real command above it.
84
+ [ "$(shim_source "${bin##*/}")" = "${bin}" ] || continue
85
+ target="$(wrapper_target "${bin}")"
86
+ [ -n "${target}" ] || continue
87
+ # Only shim when the hardcoded path is broken on the host but real in
88
+ # the chroot.
89
+ if [ -e "${target}" ] || [ ! -x "${DATA_ROOT}${target}" ]; then
90
+ continue
91
+ fi
92
+ shim="${SHIM_DIR}/${bin##*/}"
93
+ if ! tmp="$(mktemp "${SHIM_DIR}/.shim.XXXXXX")"; then
94
+ generation_failed=1
95
+ continue
96
+ fi
97
+ if printf '%s\n' \
98
+ '#!/bin/sh' \
99
+ "${MARKER} ${target}" \
100
+ "exec \"${DATA_ROOT}${target}\" \"\$@\"" \
101
+ >"${tmp}" &&
102
+ chmod 0755 "${tmp}" &&
103
+ mv -f "${tmp}" "${shim}"; then
104
+ :
105
+ else
106
+ rm -f "${tmp}"
107
+ generation_failed=1
108
+ fi
109
+ done
110
+ done
111
+
112
+ if [ "${generation_failed}" -ne 0 ]; then
113
+ exit 1
114
+ fi
115
+
116
+ # Drop shims that remain stale after desired replacements are installed.
117
+ for shim in "${SHIM_DIR}"/*; do
118
+ [ -f "${shim}" ] || continue
119
+ grep -qs "^${MARKER}" "${shim}" || continue
120
+ target="$(sed -n "s|^${MARKER} ||p" "${shim}" | sed -n '1p')"
121
+ src="$(shim_source "$(basename "${shim}")")"
122
+ if [ -z "${target}" ] || [ -z "${src}" ] || [ ! -x "${DATA_ROOT}${target}" ] || [ "$(wrapper_target "${src}")" != "${target}" ]; then
123
+ rm -f "${shim}"
124
+ fi
125
+ done
126
+
127
+ printf '%s\n' "${STAMP}" >"${STAMP_FILE}"
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env sh
2
+ # apt/apt-get/dpkg wrapper (symlinked from /usr/local/bin). On Kata-family
3
+ # runtimes, package operations are routed into the persistent apt chroot so
4
+ # they survive machine saves; the image rootfs is discarded on every save.
5
+ # The chroot runs through docker-kata-chroot-exec.sh in a private mount
6
+ # namespace with /dev, /dev/pts and /proc available, so apt can allocate a
7
+ # pty for term.log and maintainer scripts see a normal system.
8
+ set -eu
9
+
10
+ TOOL_NAME="$(basename "$0")"
11
+ case "${TOOL_NAME}" in
12
+ apt | apt-get | dpkg) ;;
13
+ *) TOOL_NAME="apt-get" ;;
14
+ esac
15
+
16
+ . /app/assistant/docker-kata-runtime-family.sh
17
+
18
+ if ! vellum_is_kata_family_runtime; then
19
+ exec "/usr/bin/${TOOL_NAME}" "$@"
20
+ fi
21
+
22
+ if [ "${TOOL_NAME}" != "dpkg" ]; then
23
+ export DEBIAN_FRONTEND=noninteractive
24
+ fi
25
+ DATA_ROOT="${VELLUM_APT_DATA_ROOT:-/data/system}"
26
+
27
+ /app/assistant/docker-init-apt-root.sh
28
+ if [ -x "${DATA_ROOT}/bin/sh" ] && [ -x "${DATA_ROOT}/usr/bin/${TOOL_NAME}" ] && [ -f "${DATA_ROOT}/.rootfs-initialized" ] && ! grep -qs " ${DATA_ROOT} .*noexec" /proc/mounts; then
29
+ rc=0
30
+ # Platform kata pods are privileged inside their per-assistant VM, so
31
+ # unshare -m works there; if this environment can't create a mount
32
+ # namespace, fall back to a bare chroot: installs still persist, but with
33
+ # no /dev/pts or /proc apt logs a pty warning and the caller's cwd is not
34
+ # restored.
35
+ if unshare -m true 2>/dev/null; then
36
+ unshare -m /app/assistant/docker-kata-chroot-exec.sh "${DATA_ROOT}" "${PWD}" "/usr/bin/${TOOL_NAME}" "$@" || rc=$?
37
+ else
38
+ chroot "${DATA_ROOT}" "/usr/bin/${TOOL_NAME}" "$@" || rc=$?
39
+ fi
40
+ # Packages can install wrapper scripts that hardcode absolute paths which
41
+ # only resolve inside the chroot; refresh host-side shims for those.
42
+ /app/assistant/docker-kata-apt-shims.sh || true
43
+ exit "${rc}"
44
+ fi
45
+ exec "/usr/bin/${TOOL_NAME}" "$@"
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env sh
2
+ # Runs a command inside the persistent apt chroot. Must be invoked under
3
+ # `unshare -m` (see docker-kata-pip.sh and docker-kata-apt-wrapper.sh): the
4
+ # bind mounts below live in a private mount namespace so they disappear with
5
+ # the process and are never visible to other processes; a lingering /data
6
+ # bind inside the chroot would create a path cycle (/data/system/data/system/...)
7
+ # for anything walking /data.
8
+ set -eu
9
+
10
+ DATA_ROOT="$1"
11
+ CALLER_CWD="$2"
12
+ shift 2
13
+
14
+ for dir in /workspace /data /tmp /var/tmp; do
15
+ [ -d "${dir}" ] || continue
16
+ mkdir -p "${DATA_ROOT}${dir}"
17
+ mount --bind "${dir}" "${DATA_ROOT}${dir}" 2>/dev/null || true
18
+ done
19
+
20
+ # /proc and /dev make the chroot behave like a normal system: apt needs a pty
21
+ # (posix_openpt via /dev/ptmx) to write /var/log/apt/term.log, and package
22
+ # maintainer scripts routinely read /proc. The recursive /dev bind carries the
23
+ # container's /dev/pts along; the devpts mount is a fallback for environments
24
+ # where it did not produce one.
25
+ mkdir -p "${DATA_ROOT}/proc" "${DATA_ROOT}/dev"
26
+ mount --bind /proc "${DATA_ROOT}/proc" 2>/dev/null || true
27
+ mount --rbind /dev "${DATA_ROOT}/dev" 2>/dev/null || true
28
+ if [ ! -e "${DATA_ROOT}/dev/pts/ptmx" ]; then
29
+ mkdir -p "${DATA_ROOT}/dev/pts" 2>/dev/null || true
30
+ mount -t devpts -o gid=5,mode=620,ptmxmode=666 devpts "${DATA_ROOT}/dev/pts" 2>/dev/null || true
31
+ fi
32
+
33
+ # chroot(1) always chdirs to /; restore the caller's cwd when it exists inside
34
+ # the chroot so relative paths keep working.
35
+ exec chroot "${DATA_ROOT}" /bin/sh -c 'cd "$1" 2>/dev/null || cd /; shift; exec "$@"' sh "${CALLER_CWD}" "$@"
@@ -38,10 +38,16 @@ if [ "$(id -u)" = "0" ]; then
38
38
  # unshare -m works there; if this environment can't create a mount
39
39
  # namespace, fall back to a bare chroot — index installs still persist,
40
40
  # only caller-path installs lose visibility.
41
+ rc=0
41
42
  if unshare -m true 2>/dev/null; then
42
- exec unshare -m /app/assistant/docker-kata-pip-chroot.sh "${DATA_ROOT}" "${PWD}" "/usr/bin/${PIP_NAME}" "$@"
43
+ unshare -m /app/assistant/docker-kata-chroot-exec.sh "${DATA_ROOT}" "${PWD}" "/usr/bin/${PIP_NAME}" "$@" || rc=$?
44
+ else
45
+ chroot "${DATA_ROOT}" /bin/sh -c 'cd "$1" 2>/dev/null || cd /; shift; exec "$@"' sh "${PWD}" "/usr/bin/${PIP_NAME}" "$@" || rc=$?
43
46
  fi
44
- exec chroot "${DATA_ROOT}" /bin/sh -c 'cd "$1" 2>/dev/null || cd /; shift; exec "$@"' sh "${PWD}" "/usr/bin/${PIP_NAME}" "$@"
47
+ # pip can add usr/local commands that outrank existing shims on the
48
+ # PATH overlay; refresh so a stale shim never shadows them.
49
+ /app/assistant/docker-kata-apt-shims.sh || true
50
+ exit "${rc}"
45
51
  fi
46
52
  fi
47
53
  echo "Warning: persistent pip root unavailable; falling back to the image pip (installs will not survive a save)" >&2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.11.8-dev.202609022016.7bd8f87",
3
+ "version": "0.11.8-dev.202609030046.e26b25a",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -8,6 +8,7 @@
8
8
  * stays bound to the oldest upload.
9
9
  */
10
10
  import { readFileSync } from "node:fs";
11
+ import { basename } from "node:path";
11
12
  import { beforeEach, describe, expect, test } from "bun:test";
12
13
 
13
14
  import { reinjectAttachmentPathAnnotations } from "../daemon/conversation-lifecycle.js";
@@ -119,6 +120,85 @@ describe("persistQueuedMessageBody stored path annotations", () => {
119
120
  expect(rebuiltBlock.text).toBe(annotation.text);
120
121
  });
121
122
 
123
+ test("offloaded oversized text records its stored path for history reload", async () => {
124
+ const conv = createConversation();
125
+ const ctx = makeCtx(conv.id);
126
+ const original = "x".repeat(8_000_001);
127
+
128
+ await persistQueuedMessageBody(ctx, { content: original });
129
+
130
+ const row = rawGet<{ metadata: string; content: string }>(
131
+ "test:offloadMessage",
132
+ "SELECT metadata, content FROM messages ORDER BY created_at DESC LIMIT 1",
133
+ );
134
+ const meta = JSON.parse(row!.metadata) as {
135
+ attachmentStoredPaths?: Record<string, string>;
136
+ };
137
+ const storedPaths = Object.values(meta.attachmentStoredPaths ?? {});
138
+ expect(storedPaths).toHaveLength(1);
139
+ const filename = basename(storedPaths[0]!);
140
+ expect(filename.startsWith("oversized-content-")).toBe(true);
141
+ expect(filename.endsWith(".txt")).toBe(true);
142
+ expect(readFileSync(storedPaths[0]!).toString("utf8")).toBe(original);
143
+
144
+ const annotation = lastAnnotationBlock(ctx);
145
+ expect(annotation.text).toBe(
146
+ `[Attachment "${filename}" is stored at: ${storedPaths[0]}]`,
147
+ );
148
+
149
+ const persisted = JSON.parse(row!.content) as ContentBlock[];
150
+ const rebuilt = reinjectAttachmentPathAnnotations(
151
+ persisted,
152
+ "user",
153
+ row!.metadata,
154
+ );
155
+ const rebuiltBlock = rebuilt.at(-1) as { type: "text"; text: string };
156
+ expect(rebuiltBlock.text).toBe(annotation.text);
157
+ });
158
+
159
+ test("two oversized messages in one conversation get distinct filenames", async () => {
160
+ const conv = createConversation();
161
+ const ctx = makeCtx(conv.id);
162
+
163
+ await persistQueuedMessageBody(ctx, { content: "a".repeat(8_000_001) });
164
+ await persistQueuedMessageBody(ctx, { content: "b".repeat(8_000_001) });
165
+
166
+ const first = (ctx.messages[0].content as ContentBlock[]).at(-1) as {
167
+ text: string;
168
+ };
169
+ const second = (ctx.messages[1].content as ContentBlock[]).at(-1) as {
170
+ text: string;
171
+ };
172
+ expect(first.text).toContain("oversized-content-");
173
+ expect(second.text).toContain("oversized-content-");
174
+ expect(first.text).not.toBe(second.text);
175
+ });
176
+
177
+ test("display-only offload is not named on the model-facing list", async () => {
178
+ const conv = createConversation();
179
+ const ctx = makeCtx(conv.id);
180
+ const display = "y".repeat(8_000_001);
181
+
182
+ await persistQueuedMessageBody(ctx, {
183
+ content: "fenced model copy",
184
+ displayContent: display,
185
+ });
186
+
187
+ const llmContent = ctx.messages[0].content as ContentBlock[];
188
+ expect(JSON.stringify(llmContent)).not.toContain("oversized-content-");
189
+ expect(JSON.stringify(llmContent)).toContain("fenced model copy");
190
+ expect(JSON.stringify(llmContent)).not.toContain(display);
191
+
192
+ const row = rawGet<{ metadata: string }>(
193
+ "test:displayOnlyOffloadMetadata",
194
+ "SELECT metadata FROM messages ORDER BY created_at DESC LIMIT 1",
195
+ );
196
+ const meta = JSON.parse(row!.metadata) as {
197
+ attachmentStoredPaths?: Record<string, string>;
198
+ };
199
+ expect(meta.attachmentStoredPaths).toBeUndefined();
200
+ });
201
+
122
202
  test("messages without attachments get no annotation block", async () => {
123
203
  const conv = createConversation();
124
204
  const ctx = makeCtx(conv.id);
@@ -2,6 +2,10 @@ import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { beforeEach, describe, expect, test } from "bun:test";
4
4
 
5
+ import {
6
+ offloadOversizedText,
7
+ OVERSIZED_CONTENT_FILENAME_PREFIX,
8
+ } from "../daemon/port-oversized-content.js";
5
9
  import {
6
10
  attachInlineAttachmentToMessage,
7
11
  AttachmentUploadError,
@@ -456,6 +460,29 @@ describe("createInlineAttachment (workspace_ref persistence)", () => {
456
460
  stored.id,
457
461
  ]);
458
462
  });
463
+
464
+ test("offloadOversizedText writes the original bytes next to the conversation", async () => {
465
+ const conv = createConversation();
466
+ const original = "x".repeat(32);
467
+ const result = await offloadOversizedText(
468
+ original,
469
+ {
470
+ conversationId: conv.id,
471
+ conversationCreatedAt: conv.createdAt,
472
+ },
473
+ 16,
474
+ );
475
+
476
+ expect(result.attachmentId).toBeDefined();
477
+ const filePath = getFilePathForAttachment(result.attachmentId!);
478
+ expect(filePath).toBeTruthy();
479
+ expect(result.filename).toBeDefined();
480
+ expect(filePath!.endsWith(result.filename!)).toBe(true);
481
+ expect(result.filename!.startsWith(`${OVERSIZED_CONTENT_FILENAME_PREFIX}-`)).toBe(
482
+ true,
483
+ );
484
+ expect(readFileSync(filePath!).toString("utf8")).toBe(original);
485
+ });
459
486
  });
460
487
 
461
488
  describe("attachInlineAttachmentToMessage filename collisions", () => {
@@ -127,6 +127,32 @@ describe("attachmentsToReferenceBlocks", () => {
127
127
  expect(JSON.stringify(blocks)).not.toContain("data");
128
128
  });
129
129
 
130
+ test("omits extracted_text on a video workspace_ref", () => {
131
+ const blocks = attachmentsToReferenceBlocks([
132
+ {
133
+ attachmentId: "att-vid",
134
+ filename: "clip.mp4",
135
+ mimeType: "video/mp4",
136
+ sizeBytes: 40_000_000,
137
+ extractedText: "this must not dump into the prompt",
138
+ },
139
+ ]);
140
+
141
+ expect(blocks).toEqual([
142
+ {
143
+ type: "file",
144
+ source: {
145
+ type: "workspace_ref",
146
+ media_type: "video/mp4",
147
+ attachmentId: "att-vid",
148
+ sizeBytes: 40_000_000,
149
+ filename: "clip.mp4",
150
+ },
151
+ },
152
+ ]);
153
+ expect(JSON.stringify(blocks)).not.toContain("extracted");
154
+ });
155
+
130
156
  test("builds a file workspace_ref with filename and extracted text", () => {
131
157
  const blocks = attachmentsToReferenceBlocks([
132
158
  {
@@ -208,6 +234,23 @@ describe("attachmentsToContentBlocks", () => {
208
234
  expect(blocks[0].type).toBe("image");
209
235
  });
210
236
 
237
+ test("omits extracted_text on an inline video file block", async () => {
238
+ const blocks = await attachmentsToContentBlocks([
239
+ {
240
+ filename: "clip.mp4",
241
+ mimeType: "video/mp4",
242
+ data: "AAAA",
243
+ extractedText: "must not dump",
244
+ },
245
+ ]);
246
+ expect(blocks).toHaveLength(1);
247
+ expect(blocks[0]).toMatchObject({
248
+ type: "file",
249
+ source: { type: "base64", media_type: "video/mp4", filename: "clip.mp4" },
250
+ });
251
+ expect(blocks[0]).not.toHaveProperty("extracted_text");
252
+ });
253
+
211
254
  test("creates file content block for non-image mime types", async () => {
212
255
  const blocks = await attachmentsToContentBlocks([
213
256
  { filename: "doc.pdf", mimeType: "application/pdf", data: "pdfdata" },
@@ -0,0 +1,135 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import {
3
+ chmodSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ mkdtempSync,
7
+ readFileSync,
8
+ rmSync,
9
+ writeFileSync,
10
+ } from "node:fs";
11
+ import { tmpdir } from "node:os";
12
+ import { basename, join } from "node:path";
13
+ import { afterEach, describe, expect, test } from "bun:test";
14
+
15
+ const scriptPath = join(
16
+ import.meta.dir,
17
+ "..",
18
+ "..",
19
+ "docker-kata-apt-shims.sh",
20
+ );
21
+ const testRoots: string[] = [];
22
+
23
+ afterEach(() => {
24
+ for (const root of testRoots.splice(0)) {
25
+ rmSync(root, { recursive: true, force: true });
26
+ }
27
+ });
28
+
29
+ function writeExecutable(path: string, contents: string): void {
30
+ writeFileSync(path, contents);
31
+ chmodSync(path, 0o755);
32
+ }
33
+
34
+ function createDataRoot(): string {
35
+ const dataRoot = mkdtempSync(join(tmpdir(), "kata-apt-shims-"));
36
+ testRoots.push(dataRoot);
37
+ for (const dir of [
38
+ "usr/bin",
39
+ "usr/sbin",
40
+ "usr/games",
41
+ "usr/local/bin",
42
+ "usr/local/sbin",
43
+ "var/lib/dpkg",
44
+ ]) {
45
+ mkdirSync(join(dataRoot, dir), { recursive: true });
46
+ }
47
+ writeFileSync(join(dataRoot, "var/lib/dpkg/status"), "unchanged\n");
48
+ return dataRoot;
49
+ }
50
+
51
+ function refreshShims(dataRoot: string, pathPrefix?: string): void {
52
+ execFileSync("/bin/sh", [scriptPath], {
53
+ env: {
54
+ ...process.env,
55
+ PATH: pathPrefix
56
+ ? `${pathPrefix}:${process.env.PATH ?? ""}`
57
+ : process.env.PATH,
58
+ VELLUM_APT_DATA_ROOT: dataRoot,
59
+ },
60
+ });
61
+ }
62
+
63
+ describe("docker-kata-apt-shims", () => {
64
+ test.skipIf(process.platform !== "linux")(
65
+ "refreshes a shim when a same-version reinstall retargets its trampoline",
66
+ () => {
67
+ const dataRoot = createDataRoot();
68
+
69
+ const suffix = basename(dataRoot);
70
+ const firstTarget = `/vellum-apt-shim-test/${suffix}-1`;
71
+ const secondTarget = `/vellum-apt-shim-test/${suffix}-2`;
72
+ const source = join(dataRoot, "usr/bin/example-tool");
73
+ const shim = join(dataRoot, ".host-shims/example-tool");
74
+
75
+ for (const target of [firstTarget, secondTarget]) {
76
+ const chrootTarget = `${dataRoot}${target}`;
77
+ mkdirSync(join(chrootTarget, ".."), { recursive: true });
78
+ writeExecutable(chrootTarget, "#!/bin/sh\nexit 0\n");
79
+ }
80
+
81
+ writeExecutable(source, `#!/bin/sh\nexec ${firstTarget} "$@"\n`);
82
+ refreshShims(dataRoot);
83
+ expect(readFileSync(shim, "utf8")).toContain(firstTarget);
84
+
85
+ writeExecutable(source, `#!/bin/sh\nexec ${secondTarget} "$@"\n`);
86
+ const fakeBin = join(dataRoot, "fake-bin");
87
+ const failedOnce = join(dataRoot, "mv-failed-once");
88
+ mkdirSync(fakeBin);
89
+ writeExecutable(
90
+ join(fakeBin, "mv"),
91
+ `#!/bin/sh
92
+ if [ ! -e "${failedOnce}" ]; then
93
+ touch "${failedOnce}"
94
+ exit 91
95
+ fi
96
+ [ -f "$3" ] || exit 92
97
+ exec /bin/mv "$@"
98
+ `,
99
+ );
100
+
101
+ expect(() => refreshShims(dataRoot, fakeBin)).toThrow();
102
+ expect(readFileSync(shim, "utf8")).toContain(firstTarget);
103
+ refreshShims(dataRoot, fakeBin);
104
+
105
+ const refreshedShim = readFileSync(shim, "utf8");
106
+ expect(refreshedShim).toContain(secondTarget);
107
+ expect(refreshedShim).not.toContain(firstTarget);
108
+ },
109
+ );
110
+
111
+ test.skipIf(process.platform !== "linux")(
112
+ "ignores non-executable entries and refreshes when they become runnable",
113
+ () => {
114
+ const dataRoot = createDataRoot();
115
+ const suffix = basename(dataRoot);
116
+ const target = `/vellum-apt-shim-test/${suffix}`;
117
+ const chrootTarget = `${dataRoot}${target}`;
118
+ const lowerSource = join(dataRoot, "usr/sbin/example-tool");
119
+ const higherSource = join(dataRoot, "usr/local/bin/example-tool");
120
+ const shim = join(dataRoot, ".host-shims/example-tool");
121
+
122
+ mkdirSync(join(chrootTarget, ".."), { recursive: true });
123
+ writeExecutable(chrootTarget, "#!/bin/sh\nexit 0\n");
124
+ writeExecutable(lowerSource, `#!/bin/sh\nexec ${target} "$@"\n`);
125
+ writeFileSync(higherSource, "#!/bin/sh\nexit 0\n", { mode: 0o644 });
126
+
127
+ refreshShims(dataRoot);
128
+ expect(readFileSync(shim, "utf8")).toContain(target);
129
+
130
+ chmodSync(higherSource, 0o755);
131
+ refreshShims(dataRoot);
132
+ expect(existsSync(shim)).toBe(false);
133
+ },
134
+ );
135
+ });