mixdog 0.9.157 → 0.9.158
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/README.md +30 -13
- package/package.json +2 -2
- package/scripts/build-runtime-linux.sh +7 -0
- package/scripts/build-runtime-macos.sh +7 -0
- package/scripts/lib/stage-postgres-runtime-windows.ps1 +13 -2
- package/scripts/smoke-runtime-negative.sh +3 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +234 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-interactions.mjs +80 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +75 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +234 -93
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +19 -7
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +75 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +79 -8
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +17 -2
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +23 -3
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +49 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/glob-static-prefix.mjs +121 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +6 -14
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +4 -5
- package/src/runtime/computer-bridge/tool-defs.mjs +2 -0
- package/src/runtime/memory/data/runtime-manifest.json +17 -17
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +5 -3
- package/src/runtime/memory/lib/memory-cycle3.mjs +5 -5
- package/src/runtime/memory/lib/runtime-fetcher.mjs +16 -6
- package/src/runtime/office/capabilities.mjs +3 -1
- package/src/runtime/office/design-pptx-plan.mjs +210 -124
- package/src/runtime/office/design-pptx-stack.mjs +50 -0
- package/src/runtime/office/design-system.mjs +10 -0
- package/src/runtime/office/image-layout.mjs +81 -0
- package/src/runtime/office/office-com-host.ps1 +178 -22
- package/src/runtime/office/office-com-session-host.ps1 +32 -5
- package/src/runtime/office/portable-pptx-core.mjs +11 -1
- package/src/runtime/office/portable-pptx-shapes.mjs +32 -6
- package/src/runtime/office/portable-slide-shapes.mjs +14 -1
- package/src/runtime/office/portable-validation.mjs +23 -4
- package/src/runtime/office/text-metrics.mjs +96 -2
- package/src/runtime/office/tool-defs.mjs +5 -1
- package/src/session-runtime/builtin-features.mjs +105 -0
- package/src/session-runtime/config-lifecycle.mjs +6 -1
- package/src/session-runtime/context-status.mjs +14 -13
- package/src/session-runtime/runtime-core.mjs +22 -14
- package/src/session-runtime/settings-api.mjs +49 -2
- package/src/standalone/session-protocol.mjs +2 -0
- package/src/tui/dist/index.mjs +6 -12
- package/src/tui/lib/voice-setup.mjs +13 -20
- package/src/tui/session/context-state.mjs +7 -1
- package/src/tui/session/session-api-ext.mjs +10 -5
- package/src/tui/session/session-api.mjs +18 -0
package/README.md
CHANGED
|
@@ -45,31 +45,35 @@ workflow setup.
|
|
|
45
45
|
|
|
46
46
|
Terminal-Bench 2.1 — same model, same 89 tasks, same official verifier, with
|
|
47
47
|
only the harness changed. Against the native CLI of each model family, Mixdog
|
|
48
|
-
|
|
48
|
+
delivers the same results at the same speed — on a fraction of the context,
|
|
49
|
+
for far less cost.
|
|
49
50
|
|
|
50
51
|
### GPT-5.6 Sol xhigh — Mixdog vs Codex CLI
|
|
51
52
|
|
|
52
53
|

|
|
53
54
|
|
|
54
|
-
- **
|
|
55
|
-
- **
|
|
56
|
-
- **
|
|
57
|
-
|
|
55
|
+
- **39%** lower priced cost — $0.476 vs $0.782 per trial
|
|
56
|
+
- **46%** smaller median final context — 18.5k vs 34.3k tokens
|
|
57
|
+
- **86.5%** (385/445) vs Codex CLI's **86.1%** (383/445) — full `k=5` on both
|
|
58
|
+
sides, pass@5 **96.6%** vs 95.5%
|
|
59
|
+
- Matched speed — 415s vs 437s wall time per trial
|
|
58
60
|
|
|
59
61
|
### Claude Opus 5 — Mixdog vs Claude Code
|
|
60
62
|
|
|
61
63
|

|
|
62
64
|
|
|
63
|
-
- **79/89** vs Claude Code's **77/89**
|
|
64
65
|
- **19%** lower priced cost — $104.29 vs $129.21 per run
|
|
65
66
|
- **28%** smaller median final context — 27.6k vs 38.2k tokens
|
|
66
|
-
- **
|
|
67
|
+
- **79/89** vs Claude Code's **77/89**
|
|
68
|
+
- **1.16×** faster — 610s vs 708s wall time per trial
|
|
67
69
|
|
|
68
|
-
Every run uses the official Harbor verifier, fast mode off, a 272k context
|
|
69
|
-
window
|
|
70
|
-
Terminal-Bench leaderboard requires
|
|
71
|
-
|
|
72
|
-
(`k=1`, 89 trials each).
|
|
70
|
+
Every run uses the official Harbor verifier, fast mode off, and a 272k context
|
|
71
|
+
window; task failures and agent timeouts are never retried. The Sol comparison
|
|
72
|
+
follows the protocol the official Terminal-Bench leaderboard requires on both
|
|
73
|
+
sides — all 89 tasks repeated five times (`k=5`, 445 trials each); the
|
|
74
|
+
Opus-side runs are single passes (`k=1`, 89 trials each). Speed is the full
|
|
75
|
+
trial wall clock, and cost values both sides at the same current API list
|
|
76
|
+
rates.
|
|
73
77
|
|
|
74
78
|
The leaderboard is not accepting community submissions, so every run here ships
|
|
75
79
|
its raw artifacts instead — Harbor verdicts, official verifier output, pinned
|
|
@@ -88,6 +92,13 @@ the harness, presets, and metric scripts that recompute each number above:
|
|
|
88
92
|
MCP, skills, hooks, and plugins.
|
|
89
93
|
- **Local memory** — semantic and lexical recall with project-scoped context
|
|
90
94
|
and multilingual retrieval.
|
|
95
|
+
- **Browser Use** — a logged-in Chromium pane the agent can drive: tabs,
|
|
96
|
+
forms, downloads, and snapshots, with one-time Chrome profile import
|
|
97
|
+
including cookies and passwords.
|
|
98
|
+
- **Computer Use** — agent control of the Windows desktop through screen
|
|
99
|
+
capture, accessibility, OCR, and a strict guarded input contract.
|
|
100
|
+
- **Office documents** — author and edit Word, Excel, and PowerPoint files
|
|
101
|
+
with model-authored design plans, charts, and assurance-checked output.
|
|
91
102
|
- **Encrypted remote access** — pair the installable web app with Desktop and
|
|
92
103
|
use Mixdog from a browser or phone over authenticated E2EE.
|
|
93
104
|
- **Desktop coding app** — agent panes, Monaco editor, Git, terminals, file
|
|
@@ -202,14 +213,20 @@ Mixdog Desktop runs the same agent runtime as the CLI:
|
|
|
202
213
|
- Git staging, commits, branches, and generated commit messages
|
|
203
214
|
- File explorer with previews, thumbnails, search, and drag-and-drop
|
|
204
215
|
- Integrated terminal tabs using the local system shell
|
|
216
|
+
- Browser Use pane with agent control and Chromium profile import
|
|
217
|
+
- Computer Use on Windows with guarded native input
|
|
218
|
+
- Office document authoring with rendered previews
|
|
205
219
|
- Image and video generation Studio with a persistent local gallery
|
|
206
220
|
- Visual workflow, agent, schedule, and webhook editors
|
|
207
221
|
- Voice dictation with an optional local transcription runtime
|
|
222
|
+
- Extensions hub with guided setup for Git, Memory, Browser Use, Computer
|
|
223
|
+
Use, Office, and voice
|
|
208
224
|
- Provider setup, usage, git identity, and remote pairing settings
|
|
209
225
|
|
|
210
226
|
The paired remote web app is installable on desktop and mobile browsers. It
|
|
211
227
|
uses an authenticated end-to-end encrypted connection before session state,
|
|
212
|
-
terminal data, files, or operation requests cross the relay
|
|
228
|
+
terminal data, files, or operation requests cross the relay, and adds mobile
|
|
229
|
+
share-target intake, push notifications, and remote Browser Use.
|
|
213
230
|
|
|
214
231
|
For desktop development:
|
|
215
232
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mixdog",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.158",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Standalone mixdog coding-agent CLI/TUI workspace.",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"test:session-transport": "node --test scripts/session-transport-test.mjs scripts/daemon-bootstrap-test.mjs src/runtime/shared/child-spawn-remote.test.mjs src/standalone/session-runtime-agent-control-client.test.mjs src/standalone/session-runtime-dispatch-cancel.test.mjs src/standalone/session-runtime-host-factory.test.mjs src/standalone/session-runtime-inline-host.test.mjs src/standalone/session-runtime-provider-cooldown.test.mjs src/standalone/session-runtime-shard-host.test.mjs src/standalone/session-runtime-shard-router.test.mjs",
|
|
73
73
|
"test:session": "node --test scripts/runtime-turn-contract-test.mjs scripts/session-save-fault-store-test.mjs scripts/turn-review-revert-test.mjs src/runtime/shared/tool-surface.test.mjs src/runtime/bridge-clients.test.mjs",
|
|
74
74
|
"test:quick": "npm run test:session && npm run test:session-transport && npm run test:desktop-main --prefix apps/desktop",
|
|
75
|
-
"test:office": "node --test scripts/run-office-live-tests.test.mjs src/runtime/office/office-runtime.test.mjs src/runtime/office/office-assurance.test.mjs src/runtime/office/portable-authoring.test.mjs && npm run test:office-approval --prefix apps/desktop",
|
|
75
|
+
"test:office": "node --test scripts/run-office-live-tests.test.mjs src/runtime/office/office-runtime.test.mjs src/runtime/office/office-assurance.test.mjs src/runtime/office/portable-authoring.test.mjs src/runtime/office/slide-quality.test.mjs && npm run test:office-approval --prefix apps/desktop",
|
|
76
76
|
"test:office:live": "node scripts/run-office-live-tests.mjs",
|
|
77
77
|
"test:media": "node --test src/runtime/media/store.test.mjs src/runtime/media/renditions.test.mjs src/runtime/media/adapters/codex-image.test.mjs",
|
|
78
78
|
"failures": "node scripts/tool-failures.mjs",
|
|
@@ -78,6 +78,7 @@ else
|
|
|
78
78
|
make -j"$(nproc)"
|
|
79
79
|
make install
|
|
80
80
|
make -C contrib/pgcrypto install
|
|
81
|
+
make -C contrib/pg_trgm install
|
|
81
82
|
fi
|
|
82
83
|
|
|
83
84
|
PG_CONFIG="$STAGE_DIR/bin/pg_config"
|
|
@@ -255,6 +256,10 @@ for attempt in 1 2 3; do
|
|
|
255
256
|
echo " vector extension version: $EXTV"
|
|
256
257
|
echo " distance query result: $DIST"
|
|
257
258
|
[[ "$EXTV" == "$PGVECTOR_VERSION" ]] || { echo "FAIL: extversion=$EXTV expected=$PGVECTOR_VERSION"; exit 1; }
|
|
259
|
+
"${RUN_AS[@]}" "$RUNTIME_DIR/bin/psql" -h 127.0.0.1 -p "$SMOKE_PORT" -U postgres -d postgres -c "CREATE EXTENSION pg_trgm;" > /dev/null
|
|
260
|
+
SIM="$("${RUN_AS[@]}" "$RUNTIME_DIR/bin/psql" -h 127.0.0.1 -p "$SMOKE_PORT" -U postgres -d postgres -tAc "SELECT similarity('mixdog','mixdig') > 0;")"
|
|
261
|
+
echo " pg_trgm similarity probe: $SIM"
|
|
262
|
+
[[ "$SIM" == "t" ]] || { echo "FAIL: pg_trgm similarity=$SIM"; exit 1; }
|
|
258
263
|
"${RUN_AS[@]}" "$RUNTIME_DIR/bin/pg_ctl" -D "$SMOKE_DATA" -m fast stop > /dev/null
|
|
259
264
|
)
|
|
260
265
|
attempt_rc=$?
|
|
@@ -323,6 +328,8 @@ for attempt in 1 2 3; do
|
|
|
323
328
|
"$EXTRACT_DIR/bin/psql" -h 127.0.0.1 -p "$EXTRACT_PORT" -U postgres -d postgres -tAc \
|
|
324
329
|
"SELECT extversion FROM pg_extension WHERE extname='vector';")"
|
|
325
330
|
[[ "$EXTV2" == "$PGVECTOR_VERSION" ]] || { echo "FAIL: extracted-smoke extversion=$EXTV2"; exit 1; }
|
|
331
|
+
"${RUN_AS[@]}" env -i HOME="$EXTRACT_DIR" PATH="/usr/bin:/bin" \
|
|
332
|
+
"$EXTRACT_DIR/bin/psql" -h 127.0.0.1 -p "$EXTRACT_PORT" -U postgres -d postgres -c "CREATE EXTENSION pg_trgm;" > /dev/null
|
|
326
333
|
"${RUN_AS[@]}" env -i HOME="$EXTRACT_DIR" PATH="/usr/bin:/bin" \
|
|
327
334
|
"$EXTRACT_DIR/bin/pg_ctl" -D "$EXTRACT_DATA" -m fast stop > /dev/null
|
|
328
335
|
)
|
|
@@ -73,6 +73,7 @@ else
|
|
|
73
73
|
make -j"$(sysctl -n hw.logicalcpu)"
|
|
74
74
|
make install
|
|
75
75
|
make -C contrib/pgcrypto install
|
|
76
|
+
make -C contrib/pg_trgm install
|
|
76
77
|
fi
|
|
77
78
|
|
|
78
79
|
PG_CONFIG="$STAGE_DIR/bin/pg_config"
|
|
@@ -165,6 +166,10 @@ DIST="$("$RUNTIME_DIR/bin/psql" -h 127.0.0.1 -p "$SMOKE_PORT" -U postgres -d pos
|
|
|
165
166
|
echo " vector extension version: $EXTV"
|
|
166
167
|
echo " distance query result: $DIST"
|
|
167
168
|
[[ "$EXTV" == "$PGVECTOR_VERSION" ]] || { echo "FAIL: extversion=$EXTV expected=$PGVECTOR_VERSION"; exit 1; }
|
|
169
|
+
"$RUNTIME_DIR/bin/psql" -h 127.0.0.1 -p "$SMOKE_PORT" -U postgres -d postgres -c "CREATE EXTENSION pg_trgm;" > /dev/null
|
|
170
|
+
SIM="$("$RUNTIME_DIR/bin/psql" -h 127.0.0.1 -p "$SMOKE_PORT" -U postgres -d postgres -tAc "SELECT similarity('mixdog','mixdig') > 0;")"
|
|
171
|
+
echo " pg_trgm similarity probe: $SIM"
|
|
172
|
+
[[ "$SIM" == "t" ]] || { echo "FAIL: pg_trgm similarity=$SIM"; exit 1; }
|
|
168
173
|
"$RUNTIME_DIR/bin/pg_ctl" -D "$SMOKE_DATA" -m fast stop > /dev/null
|
|
169
174
|
trap - EXIT
|
|
170
175
|
rm -rf "$SMOKE_DATA"
|
|
@@ -207,6 +212,8 @@ env -i HOME="$EXTRACT_DIR" PATH="/usr/bin:/bin" \
|
|
|
207
212
|
"$EXTRACT_DIR/bin/psql" -h 127.0.0.1 -p "$EXTRACT_PORT" -U postgres -d postgres -c "CREATE EXTENSION vector;" > /dev/null
|
|
208
213
|
EXTV2="$(env -i HOME="$EXTRACT_DIR" PATH="/usr/bin:/bin" "$EXTRACT_DIR/bin/psql" -h 127.0.0.1 -p "$EXTRACT_PORT" -U postgres -d postgres -tAc "SELECT extversion FROM pg_extension WHERE extname='vector';")"
|
|
209
214
|
[[ "$EXTV2" == "$PGVECTOR_VERSION" ]] || { echo "FAIL: extracted-smoke extversion=$EXTV2"; exit 1; }
|
|
215
|
+
env -i HOME="$EXTRACT_DIR" PATH="/usr/bin:/bin" \
|
|
216
|
+
"$EXTRACT_DIR/bin/psql" -h 127.0.0.1 -p "$EXTRACT_PORT" -U postgres -d postgres -c "CREATE EXTENSION pg_trgm;" > /dev/null
|
|
210
217
|
env -i HOME="$EXTRACT_DIR" PATH="/usr/bin:/bin" \
|
|
211
218
|
"$EXTRACT_DIR/bin/pg_ctl" -D "$EXTRACT_DATA" -m fast stop > /dev/null
|
|
212
219
|
trap - EXIT
|
|
@@ -64,6 +64,7 @@ function Stage-MixdogWindowsPgRuntime {
|
|
|
64
64
|
)
|
|
65
65
|
$ModuleSources = [ordered]@{
|
|
66
66
|
'dict_snowball.dll' = Join-Path $PgRoot 'lib\dict_snowball.dll'
|
|
67
|
+
'pg_trgm.dll' = Join-Path $PgRoot 'lib\pg_trgm.dll'
|
|
67
68
|
'plpgsql.dll' = Join-Path $PgRoot 'lib\plpgsql.dll'
|
|
68
69
|
'vector.dll' = $VectorDll
|
|
69
70
|
}
|
|
@@ -137,6 +138,15 @@ function Stage-MixdogWindowsPgRuntime {
|
|
|
137
138
|
}
|
|
138
139
|
$PlpgsqlAssets | Copy-Item -Destination $RuntimeExtensionDir -Force
|
|
139
140
|
|
|
141
|
+
# pg_trgm ships with the runtime: memory recall's hybrid trigram CTEs and
|
|
142
|
+
# transcript inserts depend on it (runtime-v0.4.0 omitted it and every
|
|
143
|
+
# memory write/recall failed with 'could not access file "$libdir/pg_trgm"').
|
|
144
|
+
$PgTrgmAssets = @(Get-ChildItem -Path "$SourceShare\extension\pg_trgm*" -File)
|
|
145
|
+
if ($PgTrgmAssets.Count -eq 0) {
|
|
146
|
+
throw "Required pg_trgm extension assets not found under $SourceShare\extension"
|
|
147
|
+
}
|
|
148
|
+
$PgTrgmAssets | Copy-Item -Destination $RuntimeExtensionDir -Force
|
|
149
|
+
|
|
140
150
|
$RuntimeBytes = (
|
|
141
151
|
Get-ChildItem -LiteralPath $RuntimeDir -File -Recurse |
|
|
142
152
|
Measure-Object -Property Length -Sum
|
|
@@ -174,7 +184,7 @@ function Assert-MixdogWindowsPgRuntime {
|
|
|
174
184
|
throw "Unexpected PostgreSQL executable set: $($ActualExecutables -join ', ')"
|
|
175
185
|
}
|
|
176
186
|
|
|
177
|
-
$ExpectedModules = @('dict_snowball.dll', 'plpgsql.dll', 'vector.dll')
|
|
187
|
+
$ExpectedModules = @('dict_snowball.dll', 'pg_trgm.dll', 'plpgsql.dll', 'vector.dll')
|
|
178
188
|
$ActualModules = @(
|
|
179
189
|
Get-ChildItem -LiteralPath "$RuntimeDir\lib" -File -Filter '*.dll' |
|
|
180
190
|
ForEach-Object Name |
|
|
@@ -195,7 +205,7 @@ function Assert-MixdogWindowsPgRuntime {
|
|
|
195
205
|
|
|
196
206
|
$UnexpectedExtensionAssets = @(
|
|
197
207
|
Get-ChildItem -LiteralPath "$RuntimeDir\share\extension" -Force |
|
|
198
|
-
Where-Object { $_.Name -notlike 'plpgsql*' -and $_.Name -notlike 'vector*' }
|
|
208
|
+
Where-Object { $_.Name -notlike 'pg_trgm*' -and $_.Name -notlike 'plpgsql*' -and $_.Name -notlike 'vector*' }
|
|
199
209
|
)
|
|
200
210
|
if ($UnexpectedExtensionAssets.Count -gt 0) {
|
|
201
211
|
throw "Unexpected extension assets leaked into runtime: $($UnexpectedExtensionAssets.Count)"
|
|
@@ -205,6 +215,7 @@ function Assert-MixdogWindowsPgRuntime {
|
|
|
205
215
|
"$RuntimeDir\share\extension\vector.control",
|
|
206
216
|
"$RuntimeDir\share\extension\vector--$PgVectorVersion.sql",
|
|
207
217
|
"$RuntimeDir\share\extension\plpgsql.control",
|
|
218
|
+
"$RuntimeDir\share\extension\pg_trgm.control",
|
|
208
219
|
"$RuntimeDir\share\postgres.bki"
|
|
209
220
|
)
|
|
210
221
|
foreach ($Path in $RequiredPaths) {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
set -euo pipefail
|
|
11
11
|
|
|
12
|
-
TAG="${TAG:-runtime-v0.4.
|
|
12
|
+
TAG="${TAG:-runtime-v0.4.1}"
|
|
13
13
|
OS="${OS:-$(uname -s | tr '[:upper:]' '[:lower:]')}"
|
|
14
14
|
ARCH="${ARCH:-$(uname -m | sed 's/x86_64/x64/')}"
|
|
15
15
|
PG_VER="16.4"
|
|
@@ -76,7 +76,8 @@ echo "==> ldd vector.so (under env -i):"
|
|
|
76
76
|
"${RUN_AS[@]}" env -i HOME="$WORK/fresh" PATH=/usr/bin:/bin "$PG_BIN/psql" -h 127.0.0.1 -p "$PORT" -U postgres -d postgres -c "CREATE EXTENSION vector;" > /dev/null
|
|
77
77
|
EXTV="$("${RUN_AS[@]}" env -i HOME="$WORK/fresh" PATH=/usr/bin:/bin "$PG_BIN/psql" -h 127.0.0.1 -p "$PORT" -U postgres -d postgres -tAc "SELECT extversion FROM pg_extension WHERE extname='vector';")"
|
|
78
78
|
[[ "$EXTV" == "$PGVECTOR_VER" ]] || { echo "FAIL: extversion=$EXTV"; exit 1; }
|
|
79
|
-
|
|
79
|
+
"${RUN_AS[@]}" env -i HOME="$WORK/fresh" PATH=/usr/bin:/bin "$PG_BIN/psql" -h 127.0.0.1 -p "$PORT" -U postgres -d postgres -c "CREATE EXTENSION pg_trgm;" > /dev/null
|
|
80
|
+
echo " PASS: fresh-extract boot + vector + pg_trgm extensions"
|
|
80
81
|
|
|
81
82
|
echo "==> Test 5: same tarball extracted twice → second initdb refuses (data-dir already initialized)"
|
|
82
83
|
mkdir -p "$WORK/twice"
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
const SCHEMA_ANNOTATIONS = new Set([
|
|
2
|
+
'$comment',
|
|
3
|
+
'$id',
|
|
4
|
+
'$schema',
|
|
5
|
+
'default',
|
|
6
|
+
'deprecated',
|
|
7
|
+
'description',
|
|
8
|
+
'examples',
|
|
9
|
+
'readOnly',
|
|
10
|
+
'title',
|
|
11
|
+
'writeOnly',
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
export const MAX_CONNECT_FRAME_BYTES = 64 * 1024 * 1024;
|
|
15
|
+
export const MAX_CHECKPOINT_BYTES = 48 * 1024 * 1024;
|
|
16
|
+
export const MAX_TOOL_TEXT_BYTES = 512 * 1024;
|
|
17
|
+
export const MAX_TOOL_MEDIA_BYTES = 16 * 1024 * 1024;
|
|
18
|
+
export const MAX_USER_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
19
|
+
export const MAX_CONVERSATION_BLOB_BYTES = 128 * 1024 * 1024;
|
|
20
|
+
export const MAX_CONVERSATION_BLOB_ENTRIES = 512;
|
|
21
|
+
export const MAX_INDIVIDUAL_BLOB_BYTES = 32 * 1024 * 1024;
|
|
22
|
+
|
|
23
|
+
function boundedInteger(value, fallback, { min = 0, max = Number.MAX_SAFE_INTEGER } = {}) {
|
|
24
|
+
const parsed = Number(value);
|
|
25
|
+
if (!Number.isFinite(parsed) || parsed < min) return fallback;
|
|
26
|
+
return Math.min(max, Math.floor(parsed));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function resolveCursorStreamTuning(env = process.env) {
|
|
30
|
+
return {
|
|
31
|
+
idleTimeoutMs: boundedInteger(env.MIXDOG_CURSOR_STREAM_IDLE_TIMEOUT_MS, 180_000),
|
|
32
|
+
parkTimeoutMs: boundedInteger(env.MIXDOG_CURSOR_STREAM_PARK_TIMEOUT_MS, 45_000, { min: 1_000 }),
|
|
33
|
+
maxRetries: boundedInteger(env.MIXDOG_CURSOR_STREAM_IDLE_MAX_RETRIES, 5, { max: 10 }),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function slimSchema(value, depth = 0) {
|
|
38
|
+
if (value == null || depth > 12) return value;
|
|
39
|
+
if (Array.isArray(value)) return value.map((entry) => slimSchema(entry, depth + 1));
|
|
40
|
+
if (typeof value !== 'object') return value;
|
|
41
|
+
const output = {};
|
|
42
|
+
for (const [key, child] of Object.entries(value)) {
|
|
43
|
+
if (SCHEMA_ANNOTATIONS.has(key) || child === undefined) continue;
|
|
44
|
+
if (key === 'additionalProperties' && child === true) continue;
|
|
45
|
+
if (key === 'required' && Array.isArray(child) && child.length === 0) continue;
|
|
46
|
+
output[key] = slimSchema(child, depth + 1);
|
|
47
|
+
}
|
|
48
|
+
return output;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function conciseDescription(value) {
|
|
52
|
+
const normalized = String(value || '').replace(/\s+/g, ' ').trim();
|
|
53
|
+
if (normalized.length <= 120) return normalized;
|
|
54
|
+
const sentence = normalized.match(/^.{24,117}?[.!?](?:\s|$)/)?.[0]?.trim();
|
|
55
|
+
return sentence || `${normalized.slice(0, 117)}...`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function prepareCursorToolDefinition(tool) {
|
|
59
|
+
const fn = tool?.function || tool || {};
|
|
60
|
+
const inputSchema = fn.parameters && typeof fn.parameters === 'object'
|
|
61
|
+
? slimSchema(fn.parameters)
|
|
62
|
+
: { type: 'object', properties: {} };
|
|
63
|
+
return {
|
|
64
|
+
name: fn.name,
|
|
65
|
+
description: conciseDescription(fn.description),
|
|
66
|
+
inputSchema,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function truncateUtf8(value, maxBytes) {
|
|
71
|
+
const text = String(value ?? '');
|
|
72
|
+
const encoded = Buffer.from(text, 'utf8');
|
|
73
|
+
if (encoded.length <= maxBytes) return text;
|
|
74
|
+
const notice = Buffer.from('\n\n[Mixdog truncated this Cursor tool result to stay within the wire limit.]');
|
|
75
|
+
const end = Math.max(0, maxBytes - notice.length);
|
|
76
|
+
const clipped = encoded.subarray(0, end).toString('utf8').replace(/\uFFFD$/u, '');
|
|
77
|
+
return `${clipped}${notice.toString('utf8')}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function capCursorToolResult(result) {
|
|
81
|
+
const media = [];
|
|
82
|
+
let mediaBytes = 0;
|
|
83
|
+
for (const image of Array.isArray(result?.media) ? result.media : []) {
|
|
84
|
+
const bytes = image?.data instanceof Uint8Array ? image.data : null;
|
|
85
|
+
if (!bytes || bytes.byteLength > MAX_TOOL_MEDIA_BYTES) continue;
|
|
86
|
+
if (mediaBytes + bytes.byteLength > MAX_TOOL_MEDIA_BYTES) break;
|
|
87
|
+
media.push(image);
|
|
88
|
+
mediaBytes += bytes.byteLength;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
...result,
|
|
92
|
+
content: truncateUtf8(result?.content, MAX_TOOL_TEXT_BYTES),
|
|
93
|
+
media,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function assertCursorUserImages(images) {
|
|
98
|
+
for (const image of images || []) {
|
|
99
|
+
const size = image?.data?.byteLength || 0;
|
|
100
|
+
if (size > MAX_USER_IMAGE_BYTES) {
|
|
101
|
+
const error = new Error(`Cursor image exceeds ${MAX_USER_IMAGE_BYTES} bytes`);
|
|
102
|
+
error.code = 'cursor_payload_too_large';
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function blobBytes(blobs) {
|
|
109
|
+
let total = 0;
|
|
110
|
+
for (const value of blobs.values()) total += value?.byteLength || 0;
|
|
111
|
+
return total;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function storeCursorBlob(blobs, key, value) {
|
|
115
|
+
const data = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
116
|
+
if (data.byteLength > MAX_INDIVIDUAL_BLOB_BYTES) {
|
|
117
|
+
const error = new Error(`Cursor blob exceeds ${MAX_INDIVIDUAL_BLOB_BYTES} bytes`);
|
|
118
|
+
error.code = 'cursor_payload_too_large';
|
|
119
|
+
throw error;
|
|
120
|
+
}
|
|
121
|
+
if (blobs.has(key)) blobs.delete(key);
|
|
122
|
+
blobs.set(key, data);
|
|
123
|
+
while (
|
|
124
|
+
blobs.size > MAX_CONVERSATION_BLOB_ENTRIES
|
|
125
|
+
|| blobBytes(blobs) > MAX_CONVERSATION_BLOB_BYTES
|
|
126
|
+
) {
|
|
127
|
+
const oldest = blobs.keys().next().value;
|
|
128
|
+
if (oldest === undefined || (oldest === key && blobs.size === 1)) break;
|
|
129
|
+
blobs.delete(oldest);
|
|
130
|
+
}
|
|
131
|
+
return data;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function createCursorByteQueue() {
|
|
135
|
+
let chunks = [];
|
|
136
|
+
let chunkIndex = 0;
|
|
137
|
+
let chunkOffset = 0;
|
|
138
|
+
let byteLength = 0;
|
|
139
|
+
|
|
140
|
+
const compact = () => {
|
|
141
|
+
if (chunkIndex < 64) return;
|
|
142
|
+
chunks = chunks.slice(chunkIndex);
|
|
143
|
+
chunkIndex = 0;
|
|
144
|
+
};
|
|
145
|
+
const copy = (count, consume) => {
|
|
146
|
+
if (count > byteLength) throw new Error('Cursor byte queue underflow');
|
|
147
|
+
const output = Buffer.allocUnsafe(count);
|
|
148
|
+
let written = 0;
|
|
149
|
+
let index = chunkIndex;
|
|
150
|
+
let offset = chunkOffset;
|
|
151
|
+
while (written < count) {
|
|
152
|
+
const chunk = chunks[index];
|
|
153
|
+
const take = Math.min(count - written, chunk.length - offset);
|
|
154
|
+
chunk.copy(output, written, offset, offset + take);
|
|
155
|
+
written += take;
|
|
156
|
+
offset += take;
|
|
157
|
+
if (offset === chunk.length) {
|
|
158
|
+
index += 1;
|
|
159
|
+
offset = 0;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (consume) {
|
|
163
|
+
chunkIndex = index;
|
|
164
|
+
chunkOffset = offset;
|
|
165
|
+
byteLength -= count;
|
|
166
|
+
compact();
|
|
167
|
+
}
|
|
168
|
+
return output;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
get byteLength() { return byteLength; },
|
|
173
|
+
append(value) {
|
|
174
|
+
const chunk = Buffer.from(value);
|
|
175
|
+
if (!chunk.length) return;
|
|
176
|
+
chunks.push(chunk);
|
|
177
|
+
byteLength += chunk.length;
|
|
178
|
+
},
|
|
179
|
+
peek(count) { return copy(count, false); },
|
|
180
|
+
read(count) { return copy(count, true); },
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function createCursorStreamWatchdog({ idleTimeoutMs, parkTimeoutMs, onTimeout }) {
|
|
185
|
+
let timer = null;
|
|
186
|
+
let stopped = false;
|
|
187
|
+
const arm = (timeoutMs, kind) => {
|
|
188
|
+
if (timer) clearTimeout(timer);
|
|
189
|
+
timer = null;
|
|
190
|
+
if (stopped || timeoutMs <= 0) return;
|
|
191
|
+
timer = setTimeout(() => onTimeout(kind), timeoutMs);
|
|
192
|
+
timer.unref?.();
|
|
193
|
+
};
|
|
194
|
+
return {
|
|
195
|
+
start() { arm(idleTimeoutMs, 'idle'); },
|
|
196
|
+
progress(kind) {
|
|
197
|
+
if (kind === 'work') arm(idleTimeoutMs, 'idle');
|
|
198
|
+
else if (kind === 'park') arm(parkTimeoutMs, 'park');
|
|
199
|
+
},
|
|
200
|
+
stop() {
|
|
201
|
+
stopped = true;
|
|
202
|
+
if (timer) clearTimeout(timer);
|
|
203
|
+
timer = null;
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function cursorInteractionProgress(update) {
|
|
209
|
+
if (!update) return 'none';
|
|
210
|
+
if (update.heartbeat) return 'liveness';
|
|
211
|
+
if (update.textDelta?.text || update.thinkingDelta?.text) return 'work';
|
|
212
|
+
if (
|
|
213
|
+
update.tokenDelta
|
|
214
|
+
|| update.toolCallStarted
|
|
215
|
+
|| update.toolCallCompleted
|
|
216
|
+
|| update.partialToolCall
|
|
217
|
+
|| update.toolCallDelta
|
|
218
|
+
|| update.thinkingCompleted
|
|
219
|
+
|| update.summary
|
|
220
|
+
|| update.summaryStarted
|
|
221
|
+
|| update.summaryCompleted
|
|
222
|
+
|| update.stepStarted
|
|
223
|
+
|| update.stepCompleted
|
|
224
|
+
|| update.turnEnded
|
|
225
|
+
) return 'work';
|
|
226
|
+
return 'none';
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function isRetryableCursorStreamError(error) {
|
|
230
|
+
const status = Number(error?.httpStatus || error?.status || 0);
|
|
231
|
+
if ([400, 401, 403, 404, 429].includes(status)) return false;
|
|
232
|
+
if (error?.name === 'AbortError') return false;
|
|
233
|
+
return status === 0 || status >= 500;
|
|
234
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const REJECT_REASON = 'This Cursor-side interaction is unavailable. Use the Mixdog tools instead.';
|
|
2
|
+
|
|
3
|
+
function response(name, value) {
|
|
4
|
+
return {
|
|
5
|
+
handled: true,
|
|
6
|
+
action: `${name}_rejected`,
|
|
7
|
+
message: { interactionResponse: value },
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function buildCursorInteractionResponse(query = {}) {
|
|
12
|
+
const id = Number(query.id) || 0;
|
|
13
|
+
if (query.webSearchRequestQuery) {
|
|
14
|
+
return response('web_search', {
|
|
15
|
+
id,
|
|
16
|
+
webSearchRequestResponse: { rejected: { reason: REJECT_REASON } },
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
if (query.exaSearchRequestQuery) {
|
|
20
|
+
return response('exa_search', {
|
|
21
|
+
id,
|
|
22
|
+
exaSearchRequestResponse: { rejected: { reason: REJECT_REASON } },
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
if (query.exaFetchRequestQuery) {
|
|
26
|
+
return response('exa_fetch', {
|
|
27
|
+
id,
|
|
28
|
+
exaFetchRequestResponse: { rejected: { reason: REJECT_REASON } },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (query.switchModeRequestQuery) {
|
|
32
|
+
return response('switch_mode', {
|
|
33
|
+
id,
|
|
34
|
+
switchModeRequestResponse: { rejected: { reason: REJECT_REASON } },
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (query.askQuestionInteractionQuery) {
|
|
38
|
+
return response('ask_question', {
|
|
39
|
+
id,
|
|
40
|
+
askQuestionInteractionResponse: { result: { rejected: { reason: REJECT_REASON } } },
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (query.createPlanRequestQuery) {
|
|
44
|
+
return response('create_plan', {
|
|
45
|
+
id,
|
|
46
|
+
createPlanRequestResponse: { result: { error: { error: REJECT_REASON } } },
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
if (query.setupVmEnvironmentArgs) {
|
|
50
|
+
return {
|
|
51
|
+
handled: true,
|
|
52
|
+
action: 'setup_vm_acknowledged',
|
|
53
|
+
message: { interactionResponse: { id, setupVmEnvironmentResult: { success: {} } } },
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (query.$unknown?.some((field) => field.no === 9)) {
|
|
57
|
+
return response('web_fetch', {
|
|
58
|
+
id,
|
|
59
|
+
webFetchRequestResponse: { rejected: { reason: REJECT_REASON } },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const field = query.$unknown?.[0]?.no;
|
|
63
|
+
return {
|
|
64
|
+
handled: false,
|
|
65
|
+
action: 'unsupported',
|
|
66
|
+
queryCase: field ? `field_${field}` : 'unknown',
|
|
67
|
+
message: null,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function buildCursorExecThrow(exec = {}, detail = 'Unsupported Cursor exec') {
|
|
72
|
+
return {
|
|
73
|
+
execClientControlMessage: {
|
|
74
|
+
throw: {
|
|
75
|
+
id: Number(exec.id) || 0,
|
|
76
|
+
error: `${detail}. Use a Mixdog tool instead.`,
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|