mixdog 1.1.0 → 1.1.2
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 +13 -6
- package/package.json +1 -2
- package/scripts/latest-guard.mjs +63 -0
- package/scripts/lib/run-node-tests.mjs +103 -27
- package/scripts/lib/test-failure-summary.mjs +7 -2
- package/scripts/merge-mac-update-feed.mjs +107 -0
- package/scripts/mobile-release.mjs +210 -0
- package/scripts/post-release-check.mjs +353 -0
- package/scripts/publish-asset-release.mjs +43 -0
- package/scripts/release-guard.mjs +63 -0
- package/scripts/release-paths.mjs +39 -2
- package/scripts/test.mjs +29 -7
- package/src/defaults/shipped-history.json +2 -0
- package/src/defaults/skills/computer-use/SKILL.md +1 -1
- package/src/defaults/skills/setup/references/actions.md +1 -0
- package/src/headless-role.mjs +2 -2
- package/src/lib/keychain-cjs.cjs +582 -17
- package/src/lib/rules-builder.cjs +0 -23
- package/src/rules/lead/LEAD.md +2 -2
- package/src/runtime/agent/orchestrator/config-storage.mjs +32 -4
- package/src/runtime/agent/orchestrator/config.mjs +18 -0
- package/src/runtime/agent/orchestrator/context/instruction-files.mjs +130 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/lib/open-login-url.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +254 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-profile.mjs +218 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-tokens.mjs +239 -0
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +233 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth-login.mjs +247 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth-tokens.mjs +225 -0
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +542 -0
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +14 -6
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs +99 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +4 -8
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +2 -7
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +4 -0
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +15 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +1 -5
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +12 -14
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +11 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-call-ids.mjs +90 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +19 -11
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +1 -5
- package/src/runtime/agent/orchestrator/session/read-dedup.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/turn-effort.mjs +18 -0
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +11 -8
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +9 -4
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +245 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/command-preflight.mjs +13 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +49 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +9 -7
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +61 -8
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +28 -3
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +73 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +17 -6
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +23 -5
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +93 -43
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +32 -6
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +9 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +12 -15
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +124 -91
- package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +19 -4
- package/src/runtime/channels/index.mjs +1 -5
- package/src/runtime/channels/lib/runtime-paths.mjs +8 -23
- package/src/runtime/channels/lib/tool-dispatch.mjs +1 -13
- package/src/runtime/channels/lib/worker-ipc.mjs +2 -2
- package/src/runtime/channels/lib/worker-main.mjs +4 -34
- package/src/runtime/computer-bridge/client.mjs +25 -1
- package/src/runtime/computer-bridge/linux-action-capabilities.mjs +84 -0
- package/src/runtime/computer-bridge/linux-readiness.d.mts +52 -0
- package/src/runtime/computer-bridge/linux-readiness.mjs +411 -0
- package/src/runtime/computer-bridge/test-linux-display.mjs +5 -0
- package/src/runtime/effort-judge/compact-tokenizer.mjs +13 -8
- package/src/runtime/effort-judge/judge-input.mjs +5 -2
- package/src/runtime/effort-judge/model-install.mjs +9 -3
- package/src/runtime/effort-judge/model-manifest.json +7 -7
- package/src/runtime/memory/lib/embedding-worker.mjs +9 -3
- package/src/runtime/memory/lib/pg/supervisor.mjs +3 -0
- package/src/runtime/shared/config.mjs +50 -2
- package/src/runtime/shared/developer-options.mjs +7 -0
- package/src/runtime/shared/instruction-files-mode.mjs +9 -0
- package/src/runtime/shared/llm/model-catalog.mjs +2 -1
- package/src/runtime/shared/llm/openai-daybreak.mjs +79 -0
- package/src/runtime/shared/pristine-execution-contract.json +9 -0
- package/src/runtime/shared/pristine-execution.mjs +1 -1
- package/src/runtime/shared/provider-accounts.mjs +2 -0
- package/src/runtime/shared/resource-admission.mjs +10 -3
- package/src/runtime/shared/schedule-model-ref.mjs +11 -0
- package/src/runtime/shared/semver.d.mts +1 -0
- package/src/runtime/shared/semver.mjs +57 -0
- package/src/runtime/shared/session-approval-wait.mjs +35 -0
- package/src/runtime/shared/shell-display.mjs +4 -2
- package/src/runtime/shared/tool-surface.mjs +2 -2
- package/src/runtime/shared/turn-worktree-snapshot.mjs +11 -10
- package/src/runtime/shared/update-checker.mjs +2 -31
- package/src/session-runtime/boot/apis.mjs +5 -0
- package/src/session-runtime/boot/begin.mjs +0 -2
- package/src/session-runtime/boot/tools.mjs +5 -4
- package/src/session-runtime/model-recency.mjs +3 -0
- package/src/session-runtime/provider-auth/login.mjs +10 -10
- package/src/session-runtime/provider-auth/setup.mjs +22 -2
- package/src/session-runtime/provider-models/row-hydration.mjs +12 -4
- package/src/session-runtime/provider-readiness.mjs +4 -0
- package/src/session-runtime/resource-mcp-api.mjs +7 -3
- package/src/session-runtime/runtime-bootstrap.mjs +0 -4
- package/src/session-runtime/runtime-facade.mjs +5 -0
- package/src/session-runtime/runtime-paths.mjs +0 -1
- package/src/session-runtime/schedule-session-run.mjs +3 -1
- package/src/session-runtime/services/agent-tool/index-write-queue.mjs +55 -14
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +15 -3
- package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +2 -0
- package/src/session-runtime/services/agent-tool/worker-index/approval-wait.mjs +97 -0
- package/src/session-runtime/services/agent-tool/worker-index/mutation-batch.mjs +4 -2
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -2
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +3 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +58 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +69 -15
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +2 -0
- package/src/session-runtime/services/channel-admin.mjs +4 -2
- package/src/session-runtime/services/channel-worker.mjs +1 -1
- package/src/session-runtime/services/provider-admin.mjs +80 -17
- package/src/session-runtime/services/seeds.mjs +7 -10
- package/src/session-runtime/services/usage-dashboard-model.mjs +6 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +15 -0
- package/src/session-runtime/settings-profile-api.mjs +27 -2
- package/src/session-runtime/setup-tool/executor.mjs +5 -0
- package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
- package/src/session-runtime/starter-guide-state.mjs +62 -0
- package/src/session-runtime/tool-policy-refresh.mjs +3 -0
- package/src/session-runtime/webhook-session-run.mjs +3 -1
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -2
- package/src/standalone/channel-binding.mjs +1 -2
- package/src/standalone/channel-transport/call-dispatch.mjs +4 -10
- package/src/standalone/daemon-channels-loader.mjs +2 -3
- package/src/standalone/local-session-runtime.mjs +2 -78
- package/src/standalone/session-protocol.mjs +2 -0
- package/src/tui/app/model-options.mjs +13 -1
- package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +10 -4
- package/src/tui/app/provider-setup/oauth-actions.mjs +5 -0
- package/src/tui/dist/index.mjs +84 -34
- package/src/tui/session/context-state.mjs +2 -0
- package/src/tui/session/live-share/owner.mjs +17 -0
- package/src/tui/session/oauth-flows.mjs +2 -0
- package/src/tui/session/session-api/extensions.mjs +12 -8
- package/src/tui/session/session-api/integrations.mjs +9 -8
- package/src/tui/session/session-api/routes.mjs +2 -2
- package/src/tui/session/session-api/settings.mjs +1 -0
- package/src/tui/session/session-api/shared.mjs +10 -6
- package/src/tui/session/tool-card-results/grouped-result.mjs +11 -2
- package/src/tui/session/transcript-spill/spill-dir.mjs +22 -6
- package/src/tui/spinner-meta.mjs +26 -10
- package/src/runtime/channels/tool-defs.mjs +0 -17
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
// Linux Computer Use prerequisites, probed per capability BEFORE a task starts
|
|
2
|
+
// so a missing permission or helper yields one actionable message naming what
|
|
3
|
+
// is missing instead of a failure or hang mid-task. Probes never prompt: they
|
|
4
|
+
// read the environment, test file access and PATH, and ask D-Bus read-only
|
|
5
|
+
// questions. The wording mirrors the native hints in native/mixdog-computer
|
|
6
|
+
// (uinput.rs PERMISSION_HINT, compositor.rs supported, apps.rs TESSERACT_HINT /
|
|
7
|
+
// CLIPBOARD_HINT, atspi.rs connect).
|
|
8
|
+
import { spawn } from 'node:child_process';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { isForegroundWindowCapture, requiredCapabilities } from './linux-action-capabilities.mjs';
|
|
12
|
+
|
|
13
|
+
export const UINPUT_HINT =
|
|
14
|
+
'input_unavailable: Wayland input needs write access to /dev/uinput; add the udev rule `KERNEL=="uinput", GROUP="input", MODE="0660", OPTIONS+="static_node=uinput"`, add your user to the input group, then sign in again';
|
|
15
|
+
export const INPUT_GROUP_SECURITY_NOTE =
|
|
16
|
+
'Security note: membership of the input group lets every program you run read and inject keyboard and mouse input system-wide. Grant it only on a machine and account you trust.';
|
|
17
|
+
export const GNOME_EXTENSION_UUID = 'mixdog-windows@mixdog.io';
|
|
18
|
+
/** The oldest helper interface this app can use (2 added Screenshot and StopRequested, 3 the in-shell indicator). */
|
|
19
|
+
export const MIN_WINDOW_HELPER_VERSION = 3;
|
|
20
|
+
export const WINDOW_CONTROL_HINT =
|
|
21
|
+
'wayland_window_control_unavailable: this compositor offers no window control to other programs; on GNOME the Mixdog Windows extension (mixdog-windows@mixdog.io) provides it and must be enabled and loaded';
|
|
22
|
+
export const WINDOW_HELPER_RELOGIN_HINT =
|
|
23
|
+
'wayland_window_control_unavailable: the Mixdog Windows GNOME extension is installed and enabled but GNOME Shell has not loaded it yet; the user must sign out and sign in again once';
|
|
24
|
+
export const WINDOW_HELPER_ENABLE_HINT =
|
|
25
|
+
'wayland_window_control_unavailable: the Mixdog Windows GNOME extension is not enabled; it could not be enabled automatically (run gnome-extensions enable mixdog-windows@mixdog.io)';
|
|
26
|
+
export const WINDOW_CONTROL_UNSUPPORTED_HINT =
|
|
27
|
+
'wayland_window_control_unavailable: this desktop offers no window control to other programs (supported: GNOME with the Mixdog Windows extension, sway, Hyprland, or an X11 session); screen, pointer, keyboard and clipboard still work';
|
|
28
|
+
export const ACCESSIBILITY_HINT =
|
|
29
|
+
'accessibility_unavailable: the AT-SPI bus is not running; install at-spi2-core';
|
|
30
|
+
export const TESSERACT_HINT =
|
|
31
|
+
'ocr_unavailable: install Tesseract (for example `sudo apt install tesseract-ocr tesseract-ocr-kor`) to read text from pixels';
|
|
32
|
+
export const CLIPBOARD_HINT =
|
|
33
|
+
'clipboard_unavailable: install wl-clipboard (`wl-copy`/`wl-paste`) for clipboard access on Wayland';
|
|
34
|
+
export const DISPLAY_HINT =
|
|
35
|
+
'display_unavailable: no graphical session to drive; run inside a desktop session with DISPLAY (X11) or WAYLAND_DISPLAY set';
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* User-facing English phrases, one per fix a capability can ask for. They are
|
|
39
|
+
* the keys of the UI catalogs (apps/desktop/src/renderer/locales), so a UI that
|
|
40
|
+
* renders a capability's `fix` looks the phrase up there; `{{package}}` is the
|
|
41
|
+
* capability's `package`. The tool result text for the model stays English.
|
|
42
|
+
*/
|
|
43
|
+
export const FIX_MESSAGES = Object.freeze({
|
|
44
|
+
allow_input:
|
|
45
|
+
'Allow Mixdog to send keyboard and mouse input: grant access to /dev/uinput (add your user to the input group), then sign in again.',
|
|
46
|
+
relogin: 'Sign out and sign in again to finish setting up window control for Mixdog.',
|
|
47
|
+
enable_window_helper:
|
|
48
|
+
'Enable the Mixdog Windows extension so Mixdog can list and control windows (gnome-extensions enable mixdog-windows@mixdog.io).',
|
|
49
|
+
install_package: 'Install {{package}} to enable this Computer Use feature.',
|
|
50
|
+
});
|
|
51
|
+
export const REFUSAL_MESSAGE = 'Computer Use cannot run this action because a required capability is not ready.';
|
|
52
|
+
|
|
53
|
+
/** Stable code of the whole refusal; the per-capability codes are the native ones. */
|
|
54
|
+
export const CAPABILITY_UNAVAILABLE_CODE = 'computer_capability_unavailable';
|
|
55
|
+
|
|
56
|
+
const READY = Object.freeze({ state: 'ready' });
|
|
57
|
+
|
|
58
|
+
function onPath(name, env) {
|
|
59
|
+
return String(env.PATH || '')
|
|
60
|
+
.split(path.delimiter)
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
.some((dir) => {
|
|
63
|
+
try {
|
|
64
|
+
fs.accessSync(path.join(dir, name), fs.constants.X_OK);
|
|
65
|
+
return true;
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// A read-only gdbus call: its stdout, or null when it fails or times out.
|
|
73
|
+
function gdbusOutput(env, args, timeoutMs = 2000) {
|
|
74
|
+
return new Promise((resolve) => {
|
|
75
|
+
let child;
|
|
76
|
+
try {
|
|
77
|
+
child = spawn('gdbus', ['call', '--session', ...args], { env, stdio: ['ignore', 'pipe', 'ignore'], shell: false, windowsHide: true });
|
|
78
|
+
} catch {
|
|
79
|
+
resolve(null);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
let out = '';
|
|
83
|
+
child.stdout.on('data', (chunk) => {
|
|
84
|
+
out += chunk;
|
|
85
|
+
});
|
|
86
|
+
const timer = setTimeout(() => {
|
|
87
|
+
try {
|
|
88
|
+
child.kill('SIGKILL');
|
|
89
|
+
} catch {}
|
|
90
|
+
resolve(null);
|
|
91
|
+
}, timeoutMs);
|
|
92
|
+
child.once('error', () => {
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
resolve(null);
|
|
95
|
+
});
|
|
96
|
+
child.once('close', (status) => {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
resolve(status === 0 ? out : null);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const gdbusSucceeds = async (env, args, timeoutMs) => (await gdbusOutput(env, args, timeoutMs)) !== null;
|
|
104
|
+
|
|
105
|
+
// Asks our own interface for its Version property: read-only, never prompts.
|
|
106
|
+
// The version the shell actually loaded, or null when nothing answers.
|
|
107
|
+
async function gnomeWindowHelperVersion(env) {
|
|
108
|
+
const out = await gdbusOutput(env, [
|
|
109
|
+
'--dest', 'org.gnome.Shell',
|
|
110
|
+
'--object-path', '/io/mixdog/Windows',
|
|
111
|
+
'--method', 'org.freedesktop.DBus.Properties.Get',
|
|
112
|
+
'io.mixdog.Windows', 'Version',
|
|
113
|
+
]);
|
|
114
|
+
const version = /uint32 (\d+)/.exec(out ?? '');
|
|
115
|
+
return version ? Number(version[1]) : null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function gsettingsOutput(env, args, timeoutMs = 2000) {
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
let child;
|
|
121
|
+
try {
|
|
122
|
+
child = spawn('gsettings', args, { env, stdio: ['ignore', 'pipe', 'ignore'], shell: false, windowsHide: true });
|
|
123
|
+
} catch {
|
|
124
|
+
resolve('');
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
let out = '';
|
|
128
|
+
child.stdout.on('data', (chunk) => {
|
|
129
|
+
out += chunk;
|
|
130
|
+
});
|
|
131
|
+
const timer = setTimeout(() => {
|
|
132
|
+
try {
|
|
133
|
+
child.kill('SIGKILL');
|
|
134
|
+
} catch {}
|
|
135
|
+
resolve('');
|
|
136
|
+
}, timeoutMs);
|
|
137
|
+
child.once('error', () => {
|
|
138
|
+
clearTimeout(timer);
|
|
139
|
+
resolve('');
|
|
140
|
+
});
|
|
141
|
+
child.once('close', () => {
|
|
142
|
+
clearTimeout(timer);
|
|
143
|
+
resolve(out);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Where GNOME Shell looks for our extension: system-wide (the .deb) or per user. */
|
|
149
|
+
export function gnomeExtensionDirs(env = process.env) {
|
|
150
|
+
const home = env.HOME || '';
|
|
151
|
+
const data = env.XDG_DATA_HOME || (home ? path.join(home, '.local', 'share') : '');
|
|
152
|
+
return [
|
|
153
|
+
...(data ? [path.join(data, 'gnome-shell', 'extensions', GNOME_EXTENSION_UUID)] : []),
|
|
154
|
+
path.join('/usr/share/gnome-shell/extensions', GNOME_EXTENSION_UUID),
|
|
155
|
+
];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Whether our extension is on disk and listed as enabled (not whether the shell loaded it). */
|
|
159
|
+
async function gnomeWindowHelperStatus(env) {
|
|
160
|
+
const installed = gnomeExtensionDirs(env).some((dir) => fs.existsSync(path.join(dir, 'metadata.json')));
|
|
161
|
+
if (!installed) return { installed: false, enabled: false };
|
|
162
|
+
const list = await gsettingsOutput(env, ['get', 'org.gnome.shell', 'enabled-extensions']);
|
|
163
|
+
const disabledAll = /true/.test(await gsettingsOutput(env, ['get', 'org.gnome.shell', 'disable-user-extensions']));
|
|
164
|
+
return { installed, enabled: list.includes(`'${GNOME_EXTENSION_UUID}'`) && !disabledAll };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function accessibilityBusReachable(env) {
|
|
168
|
+
return gdbusSucceeds(env, [
|
|
169
|
+
'--dest', 'org.a11y.Bus',
|
|
170
|
+
'--object-path', '/org/a11y/bus',
|
|
171
|
+
'--method', 'org.a11y.Bus.GetAddress',
|
|
172
|
+
]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// zbus (the native client) reaches the session bus through this address or the
|
|
176
|
+
// per-user socket; without either there is nothing to ask.
|
|
177
|
+
function sessionBusPresent(env) {
|
|
178
|
+
if (env.DBUS_SESSION_BUS_ADDRESS) return true;
|
|
179
|
+
try {
|
|
180
|
+
return Boolean(env.XDG_RUNTIME_DIR) && fs.existsSync(path.join(env.XDG_RUNTIME_DIR, 'bus'));
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Same rule as native platform::linux::wayland_session(). */
|
|
187
|
+
export function detectSession(env) {
|
|
188
|
+
const wayland = env.WAYLAND_DISPLAY !== undefined && (env.XDG_SESSION_TYPE === undefined || env.XDG_SESSION_TYPE === 'wayland');
|
|
189
|
+
return wayland ? 'wayland' : 'x11';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function detectDesktop(env, session) {
|
|
193
|
+
if (session === 'wayland') {
|
|
194
|
+
if (env.SWAYSOCK !== undefined) return 'sway';
|
|
195
|
+
if (env.HYPRLAND_INSTANCE_SIGNATURE !== undefined) return 'hyprland';
|
|
196
|
+
}
|
|
197
|
+
const desktop = String(env.XDG_CURRENT_DESKTOP || '');
|
|
198
|
+
if (/gnome/i.test(desktop)) return 'gnome';
|
|
199
|
+
if (/kde|plasma/i.test(desktop)) return 'kde';
|
|
200
|
+
if (/sway/i.test(desktop)) return 'sway';
|
|
201
|
+
if (/hyprland/i.test(desktop)) return 'hyprland';
|
|
202
|
+
return 'other';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const needsPackage = (name, extra = {}) => ({ state: 'needs_setup', fix: 'install_package', package: name, ...extra });
|
|
206
|
+
|
|
207
|
+
async function windowsCapability({ env, session, hasCommand, windowHelperVersion, windowHelperStatus }) {
|
|
208
|
+
if (session === 'x11') return READY;
|
|
209
|
+
// Same order as native Compositor::detect().
|
|
210
|
+
if (env.SWAYSOCK !== undefined) return hasCommand('swaymsg') ? READY : needsPackage('sway');
|
|
211
|
+
if (env.HYPRLAND_INSTANCE_SIGNATURE !== undefined) return hasCommand('hyprctl') ? READY : needsPackage('hyprland');
|
|
212
|
+
if (/gnome/i.test(String(env.XDG_CURRENT_DESKTOP || ''))) {
|
|
213
|
+
// Without gdbus the extension cannot be probed (the native client talks to
|
|
214
|
+
// D-Bus itself), so this is unknown rather than missing.
|
|
215
|
+
if (!hasCommand('gdbus')) return { state: 'unknown' };
|
|
216
|
+
const loaded = await windowHelperVersion();
|
|
217
|
+
// A shell that loaded an older copy keeps running it until the next sign-in.
|
|
218
|
+
if (loaded !== null) return loaded >= MIN_WINDOW_HELPER_VERSION ? READY : { state: 'needs_setup', fix: 'relogin' };
|
|
219
|
+
// Not answering: either the shell has not loaded a freshly enabled
|
|
220
|
+
// extension yet (a sign-in fixes that) or it is not enabled at all.
|
|
221
|
+
const status = await windowHelperStatus();
|
|
222
|
+
return { state: 'needs_setup', fix: status.installed && status.enabled ? 'relogin' : 'enable_window_helper' };
|
|
223
|
+
}
|
|
224
|
+
return { state: 'unsupported' };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function accessibilityCapability({ env, hasCommand, hasSessionBus, accessibilityReachable }) {
|
|
228
|
+
if (!hasSessionBus() || !hasCommand('gdbus')) return { state: 'unknown' };
|
|
229
|
+
return (await accessibilityReachable()) ? READY : needsPackage('at-spi2-core');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Per-capability readiness of this Linux session. Capabilities report
|
|
234
|
+
* 'unknown' when they cannot be probed without side effects; only
|
|
235
|
+
* 'needs_setup' and 'unsupported' ever block an action.
|
|
236
|
+
* @returns {Promise<{
|
|
237
|
+
* session: 'wayland'|'x11',
|
|
238
|
+
* desktop: 'gnome'|'kde'|'sway'|'hyprland'|'other',
|
|
239
|
+
* capabilities: Record<string, {state: 'ready'|'needs_setup'|'unsupported'|'unknown', fix?: string, package?: string, optional?: boolean}>
|
|
240
|
+
* }>}
|
|
241
|
+
*/
|
|
242
|
+
export async function checkLinuxComputerReadiness({
|
|
243
|
+
env = process.env,
|
|
244
|
+
canWrite = (file) => {
|
|
245
|
+
try {
|
|
246
|
+
fs.accessSync(file, fs.constants.W_OK);
|
|
247
|
+
return true;
|
|
248
|
+
} catch {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
hasCommand = (name) => onPath(name, env),
|
|
253
|
+
hasSessionBus = () => sessionBusPresent(env),
|
|
254
|
+
windowHelperVersion = () => gnomeWindowHelperVersion(env),
|
|
255
|
+
windowHelperStatus = () => gnomeWindowHelperStatus(env),
|
|
256
|
+
accessibilityReachable = () => accessibilityBusReachable(env),
|
|
257
|
+
} = {}) {
|
|
258
|
+
const session = detectSession(env);
|
|
259
|
+
const desktop = detectDesktop(env, session);
|
|
260
|
+
const optional = (capability) => ({ ...capability, optional: true });
|
|
261
|
+
if (session === 'x11' && !env.DISPLAY) {
|
|
262
|
+
const none = { state: 'unsupported' };
|
|
263
|
+
return {
|
|
264
|
+
session,
|
|
265
|
+
desktop,
|
|
266
|
+
capabilities: {
|
|
267
|
+
capture: none, windows: none, accessibility: none, semanticActions: none,
|
|
268
|
+
pointer: none, keyboard: none, clipboard: optional(none), ocr: optional(none),
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const wayland = session === 'wayland';
|
|
273
|
+
const windows = await windowsCapability({ env, session, hasCommand, windowHelperVersion, windowHelperStatus });
|
|
274
|
+
const accessibility = await accessibilityCapability({ env, hasCommand, hasSessionBus, accessibilityReachable });
|
|
275
|
+
const input = !wayland || canWrite('/dev/uinput') ? READY : { state: 'needs_setup', fix: 'allow_input' };
|
|
276
|
+
const clipboard = !wayland || (hasCommand('wl-copy') && hasCommand('wl-paste')) ? READY : needsPackage('wl-clipboard');
|
|
277
|
+
return {
|
|
278
|
+
session,
|
|
279
|
+
desktop,
|
|
280
|
+
capabilities: {
|
|
281
|
+
// GNOME Wayland pixels come from our shell extension (no portal, never a
|
|
282
|
+
// prompt), so they are ready exactly when the helper is. Other Wayland
|
|
283
|
+
// desktops use the desktop portal, which only prompts when used and so
|
|
284
|
+
// cannot be probed.
|
|
285
|
+
capture: wayland ? (desktop === 'gnome' ? windows : { state: 'unknown' }) : READY,
|
|
286
|
+
windows,
|
|
287
|
+
accessibility,
|
|
288
|
+
// AT-SPI actions act on an accessible found through a window handle.
|
|
289
|
+
semanticActions: windows.state === 'ready' ? accessibility : windows,
|
|
290
|
+
pointer: input,
|
|
291
|
+
keyboard: input,
|
|
292
|
+
clipboard: optional(clipboard),
|
|
293
|
+
ocr: optional(hasCommand('tesseract') ? READY : needsPackage('tesseract-ocr')),
|
|
294
|
+
},
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function capabilityIssue(name, readiness) {
|
|
299
|
+
if (readiness.capabilities.capture.state === 'unsupported') return { code: 'display_unavailable', message: DISPLAY_HINT };
|
|
300
|
+
// Semantic actions fail on whichever of their two prerequisites is missing.
|
|
301
|
+
const gnomeWayland = readiness.session === 'wayland' && readiness.desktop === 'gnome';
|
|
302
|
+
const target = name === 'semanticActions' ? (readiness.capabilities.windows.state === 'ready' ? 'accessibility' : 'windows') : name === 'capture' && gnomeWayland ? 'windows' : name;
|
|
303
|
+
const capability = readiness.capabilities[target];
|
|
304
|
+
switch (target) {
|
|
305
|
+
case 'windows':
|
|
306
|
+
if (capability.fix === 'relogin') return { code: 'wayland_window_control_unavailable', message: WINDOW_HELPER_RELOGIN_HINT };
|
|
307
|
+
if (capability.fix === 'enable_window_helper') return { code: 'wayland_window_control_unavailable', message: WINDOW_HELPER_ENABLE_HINT };
|
|
308
|
+
if (capability.fix === 'install_package') {
|
|
309
|
+
return {
|
|
310
|
+
code: 'wayland_window_control_unavailable',
|
|
311
|
+
message: `wayland_window_control_unavailable: the ${readiness.desktop} window tool is missing from PATH; install ${capability.package}`,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return { code: 'wayland_window_control_unavailable', message: WINDOW_CONTROL_UNSUPPORTED_HINT };
|
|
315
|
+
case 'accessibility':
|
|
316
|
+
return { code: 'accessibility_unavailable', message: ACCESSIBILITY_HINT };
|
|
317
|
+
case 'pointer':
|
|
318
|
+
case 'keyboard':
|
|
319
|
+
return { code: 'input_unavailable', message: `${UINPUT_HINT}. ${INPUT_GROUP_SECURITY_NOTE}` };
|
|
320
|
+
case 'clipboard':
|
|
321
|
+
return { code: 'clipboard_unavailable', message: CLIPBOARD_HINT };
|
|
322
|
+
case 'ocr':
|
|
323
|
+
return { code: 'ocr_unavailable', message: TESSERACT_HINT };
|
|
324
|
+
default:
|
|
325
|
+
return { code: `${target}_unavailable`, message: `${target}_unavailable: ${target} is not available on this session` };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** The required capabilities that are not usable, with their stable codes. */
|
|
330
|
+
export function missingCapabilities(readiness, command) {
|
|
331
|
+
const missing = [];
|
|
332
|
+
for (const name of requiredCapabilities(command)) {
|
|
333
|
+
const capability = readiness.capabilities[name];
|
|
334
|
+
if (!capability || capability.state === 'ready' || capability.state === 'unknown') continue;
|
|
335
|
+
const { fix, package: pkg } = capability;
|
|
336
|
+
missing.push({
|
|
337
|
+
capability: name,
|
|
338
|
+
state: capability.state,
|
|
339
|
+
...(fix ? { fix } : {}),
|
|
340
|
+
...(pkg ? { package: pkg } : {}),
|
|
341
|
+
...capabilityIssue(name, readiness),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return missing;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const CACHE_MS = 3000;
|
|
348
|
+
let cached = null;
|
|
349
|
+
|
|
350
|
+
/** Probed at most once per few seconds so a fix is noticed on the next call. */
|
|
351
|
+
export function getLinuxComputerReadiness({ check = checkLinuxComputerReadiness, now = Date.now } = {}) {
|
|
352
|
+
if (!cached || now() - cached.at > CACHE_MS) {
|
|
353
|
+
const entry = { at: now(), result: Promise.resolve().then(() => check()) };
|
|
354
|
+
cached = entry;
|
|
355
|
+
entry.result.catch(() => {
|
|
356
|
+
if (cached === entry) cached = null;
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
return cached.result;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Null when the command can run; otherwise `{code, missing, message}` — the
|
|
364
|
+
* model-facing text names every missing capability and says nothing ran.
|
|
365
|
+
*/
|
|
366
|
+
export async function linuxComputerPreflight({ command, platform = process.platform, check, now } = {}) {
|
|
367
|
+
if (platform !== 'linux') return null;
|
|
368
|
+
const readiness = await getLinuxComputerReadiness({ check, now });
|
|
369
|
+
const missing = missingCapabilities(readiness, command);
|
|
370
|
+
if (missing.length === 0) return null;
|
|
371
|
+
const details = new Map();
|
|
372
|
+
for (const item of missing) {
|
|
373
|
+
const entry = details.get(item.message) ?? { code: item.code, message: item.message, capabilities: [] };
|
|
374
|
+
entry.capabilities.push(item.capability);
|
|
375
|
+
details.set(item.message, entry);
|
|
376
|
+
}
|
|
377
|
+
const lines = [...details.values()].map((entry) => `- ${entry.message} [${entry.capabilities.join(', ')}]`);
|
|
378
|
+
if (String(command?.action) === 'capture' && missing.some((item) => item.capability === 'windows') && !missing.some((item) => item.capability === 'capture')) {
|
|
379
|
+
lines.push('Screenshots that need no window still work: capture with mode="vision" and a screen index (screen=0).');
|
|
380
|
+
}
|
|
381
|
+
return {
|
|
382
|
+
code: CAPABILITY_UNAVAILABLE_CODE,
|
|
383
|
+
missing,
|
|
384
|
+
message: `${CAPABILITY_UNAVAILABLE_CODE}: Computer Use cannot run this action on this Linux session (${[...new Set(missing.map((item) => item.capability))].join(', ')}) — nothing was executed.\n${lines.join('\n')}`,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export const CAPTURE_FALLBACK_NOTE =
|
|
389
|
+
'Note: window control is not available on this Linux session, so this is a full-screen capture of screen 0 instead of the foreground window.';
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* A default capture resolves the foreground window; without window control it
|
|
393
|
+
* becomes a full-screen capture instead of a refusal. Null when no fallback applies.
|
|
394
|
+
*/
|
|
395
|
+
export async function linuxCaptureFallback({ command, platform = process.platform, check, now } = {}) {
|
|
396
|
+
if (platform !== 'linux' || !isForegroundWindowCapture(command)) return null;
|
|
397
|
+
const { capabilities } = await getLinuxComputerReadiness({ check, now });
|
|
398
|
+
const { state } = capabilities.windows;
|
|
399
|
+
if (state !== 'needs_setup' && state !== 'unsupported') return null;
|
|
400
|
+
// A screen capture the session cannot take either is a refusal, not a fallback.
|
|
401
|
+
if (capabilities.capture.state === 'needs_setup' || capabilities.capture.state === 'unsupported') return null;
|
|
402
|
+
const { action, session_id, include_ocr } = command;
|
|
403
|
+
return {
|
|
404
|
+
command: { action, mode: 'vision', screen: 0, ...(session_id ? { session_id } : {}), ...(include_ocr ? { include_ocr } : {}) },
|
|
405
|
+
note: CAPTURE_FALLBACK_NOTE,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export function resetLinuxComputerPreflightCache() {
|
|
410
|
+
cached = null;
|
|
411
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Test-only: the Linux Computer Use preflight refuses to run without a graphical session, and CI runners are headless. A DISPLAY makes the preflight see an X11 session (no uinput/window-control blockers) so the client tests exercise the bridge, not the preflight (covered by linux-readiness.test.mjs).
|
|
2
|
+
process.env.DISPLAY ||= ':99';
|
|
3
|
+
// The AT-SPI probe asks the session bus; a CI bus must not turn into a missing-accessibility refusal.
|
|
4
|
+
delete process.env.DBUS_SESSION_BUS_ADDRESS;
|
|
5
|
+
process.env.XDG_RUNTIME_DIR = '/nonexistent-mixdog-test';
|
|
@@ -23,16 +23,14 @@ function build(dir) {
|
|
|
23
23
|
const json = JSON.parse(readFileSync(join(dir, 'tokenizer.json'), 'utf8'));
|
|
24
24
|
const model = json.model;
|
|
25
25
|
const pre = json.pre_tokenizer;
|
|
26
|
-
const spaceToMeta =
|
|
26
|
+
const spaceToMeta =
|
|
27
|
+
json.normalizer?.type === 'Replace' && json.normalizer.pattern?.String === ' ' && json.normalizer.content === SPACE;
|
|
27
28
|
// metaspace: pieces start at every '▁' (mmBERT). whole: a split on ' ' after
|
|
28
29
|
// the normalizer turned every ' ' into '▁' never fires, so BPE sees the
|
|
29
30
|
// whole segment (EmbeddingGemma).
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
: pre?.type === 'Split' && pre.pattern?.String === ' ' && spaceToMeta
|
|
34
|
-
? 'whole'
|
|
35
|
-
: null;
|
|
31
|
+
let split = null;
|
|
32
|
+
if (pre?.type === 'Metaspace' && pre.split !== false) split = 'metaspace';
|
|
33
|
+
else if (pre?.type === 'Split' && pre.pattern?.String === ' ' && spaceToMeta) split = 'whole';
|
|
36
34
|
if (model?.type !== 'BPE' || !spaceToMeta || !split || model.continuing_subword_prefix || model.end_of_word_suffix) {
|
|
37
35
|
throw new Error('unsupported tokenizer.json layout for the compact encoder');
|
|
38
36
|
}
|
|
@@ -68,7 +66,14 @@ function build(dir) {
|
|
|
68
66
|
const prefix = template.slice(0, seqAt).flatMap(specialIds);
|
|
69
67
|
const suffix = template.slice(seqAt + 1).flatMap(specialIds);
|
|
70
68
|
const meta = Buffer.from(
|
|
71
|
-
JSON.stringify({
|
|
69
|
+
JSON.stringify({
|
|
70
|
+
unk: vocab[model.unk_token] ?? -1,
|
|
71
|
+
split,
|
|
72
|
+
prepend: split === 'metaspace' ? pre.prepend_scheme || 'always' : 'never',
|
|
73
|
+
added,
|
|
74
|
+
prefix,
|
|
75
|
+
suffix,
|
|
76
|
+
}),
|
|
72
77
|
'utf8'
|
|
73
78
|
);
|
|
74
79
|
const [size, mtime] = sourceStamp(dir);
|
|
@@ -29,7 +29,10 @@ export function cleanJudgeText(text) {
|
|
|
29
29
|
.trim();
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const head = (text, n) =>
|
|
32
|
+
const head = (text, n) =>
|
|
33
|
+
Array.from(String(text || ''))
|
|
34
|
+
.slice(0, n)
|
|
35
|
+
.join('');
|
|
33
36
|
const tail = (text, n) => {
|
|
34
37
|
const cps = Array.from(String(text || ''));
|
|
35
38
|
return cps.slice(Math.max(0, cps.length - n)).join('');
|
|
@@ -89,7 +92,7 @@ export function packJudgeIds(tokenizer, { request, prevRequest, prev }, maxToken
|
|
|
89
92
|
: [];
|
|
90
93
|
const [first, rest] = splitReply(cleanJudgeText(tail(prev, 1000)));
|
|
91
94
|
const top = first ? tokenizer.encode(`previous reply: ${first}`).slice(0, PACK_BUDGET.replyHead) : [];
|
|
92
|
-
|
|
95
|
+
const left = Math.max(0, room - req.length - q.length - top.length);
|
|
93
96
|
const end = rest && left > 0 ? tokenizer.encode(rest) : [];
|
|
94
97
|
const endIds = end.slice(Math.max(0, end.length - left));
|
|
95
98
|
return [...tokenizer.prefix, ...req, ...q, ...top, ...endIds, ...tokenizer.suffix].slice(0, maxTokens);
|
|
@@ -21,7 +21,9 @@ export function effortJudgeManifest() {
|
|
|
21
21
|
if (
|
|
22
22
|
!manifest.release ||
|
|
23
23
|
!files.length ||
|
|
24
|
-
files.some(
|
|
24
|
+
files.some(
|
|
25
|
+
([, f]) => !validSha256(f.sha256) || !Number.isSafeInteger(f.size) || f.size <= 0 || !/^https:\/\//.test(f.url)
|
|
26
|
+
)
|
|
25
27
|
) {
|
|
26
28
|
throw new Error('effort judge model manifest is invalid');
|
|
27
29
|
}
|
|
@@ -43,7 +45,10 @@ export function effortJudgeInstallCurrent(dir, manifest = effortJudgeManifest())
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
/** Download what is missing or different, then stamp the release. */
|
|
46
|
-
export async function installEffortJudgeModel(
|
|
48
|
+
export async function installEffortJudgeModel(
|
|
49
|
+
dir,
|
|
50
|
+
{ fetchFn = globalThis.fetch, manifest = effortJudgeManifest() } = {}
|
|
51
|
+
) {
|
|
47
52
|
mkdirSync(dir, { recursive: true });
|
|
48
53
|
let replaced = false;
|
|
49
54
|
for (const [name, file] of Object.entries(manifest.files)) {
|
|
@@ -58,7 +63,8 @@ export async function installEffortJudgeModel(dir, { fetchFn = globalThis.fetch,
|
|
|
58
63
|
fetchFn,
|
|
59
64
|
});
|
|
60
65
|
const actual = await sha256File(partial);
|
|
61
|
-
if (actual !== file.sha256)
|
|
66
|
+
if (actual !== file.sha256)
|
|
67
|
+
throw new Error(`effort judge ${name}: sha256 mismatch (expected ${file.sha256}, got ${actual})`);
|
|
62
68
|
// From the first replaced file until the new stamp, the folder is a mix of
|
|
63
69
|
// releases: without a stamp it reads as not installed, so an interrupted
|
|
64
70
|
// install is never loaded.
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
|
-
"release": "effort-judge-
|
|
3
|
+
"release": "effort-judge-v6",
|
|
4
4
|
"files": {
|
|
5
5
|
"model.onnx": {
|
|
6
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-
|
|
7
|
-
"sha256": "
|
|
8
|
-
"size":
|
|
6
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-v6/model.onnx",
|
|
7
|
+
"sha256": "5abed811aaa44690f8d6292472aa8d930abae49f73a99265bb7fe874044df395",
|
|
8
|
+
"size": 153062716
|
|
9
9
|
},
|
|
10
10
|
"tokenizer.json": {
|
|
11
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-
|
|
11
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-v6/tokenizer.json",
|
|
12
12
|
"sha256": "22c064aaa468855d6211fe5c2ec8fdec595c97c55eb2ffd3b9fdf71a93b16292",
|
|
13
13
|
"size": 32170665
|
|
14
14
|
},
|
|
15
15
|
"calibration.json": {
|
|
16
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-
|
|
17
|
-
"sha256": "
|
|
16
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/effort-judge-v6/calibration.json",
|
|
17
|
+
"sha256": "cc52c28b19fa891cca8913c69871235d5f863bd224d9336ada7d0f8cfb0765f6",
|
|
18
18
|
"size": 157
|
|
19
19
|
}
|
|
20
20
|
}
|
|
@@ -395,9 +395,15 @@ async function loadExtractor() {
|
|
|
395
395
|
try {
|
|
396
396
|
os.setPriority(0, os.constants.priority.PRIORITY_NORMAL);
|
|
397
397
|
} catch (e) {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
398
|
+
// Unprivileged processes cannot raise priority back (EACCES/EPERM);
|
|
399
|
+
// on Linux that is expected, not a stuck state worth warning about.
|
|
400
|
+
const expectedOnLinux =
|
|
401
|
+
process.platform === 'linux' && /EACCES|EPERM/.test(`${e?.code} ${e?.message}`);
|
|
402
|
+
if (!expectedOnLinux) {
|
|
403
|
+
__mixdogMemoryLog(
|
|
404
|
+
`[embed-worker] WARN: process priority stuck below normal (restore failed: ${e?.message || e})\n`
|
|
405
|
+
);
|
|
406
|
+
}
|
|
401
407
|
}
|
|
402
408
|
}
|
|
403
409
|
}
|
|
@@ -799,4 +799,7 @@ export function stopPgForShutdownSync() {
|
|
|
799
799
|
try {
|
|
800
800
|
_pgProc.stopPgSync({ runtimeDir, pgdataDir });
|
|
801
801
|
} catch {}
|
|
802
|
+
// The postmaster is gone, so drop its advert too (same guarded clear as the
|
|
803
|
+
// async path: never another live owner's); otherwise pg.json outlives the run.
|
|
804
|
+
patchActiveInstance({ pg_port: null, pg_started_at: null, pg_pgdata: null, pg_runtime_dir: null }, { timeoutMs: 1000 });
|
|
802
805
|
}
|
|
@@ -31,7 +31,15 @@ const {
|
|
|
31
31
|
setSecret: _setSecret,
|
|
32
32
|
deleteSecret: _deleteSecret,
|
|
33
33
|
hasSecret: _hasSecret,
|
|
34
|
+
setSecretAsync: _setSecretAsyncImpl,
|
|
35
|
+
deleteSecretAsync: _deleteSecretAsyncImpl,
|
|
36
|
+
keychainStatus: _keychainStatus,
|
|
37
|
+
retryKeychain: _retryKeychain,
|
|
38
|
+
refreshKeychainStatus: _refreshKeychainStatus,
|
|
39
|
+
onKeychainRecovered: _onKeychainRecovered,
|
|
34
40
|
} = _require('../../lib/keychain-cjs.cjs');
|
|
41
|
+
const _setSecretAsync = _setSecretAsyncImpl ?? (async (account, value) => _setSecret(account, value));
|
|
42
|
+
const _deleteSecretAsync = _deleteSecretAsyncImpl ?? (async (account) => _deleteSecret(account));
|
|
35
43
|
|
|
36
44
|
// Resolved per call: the pristine-execution boundary retargets
|
|
37
45
|
// MIXDOG_resolvePluginData() at runtime, so an import-time capture would write into the
|
|
@@ -588,6 +596,21 @@ export function saveSecret(account, value) {
|
|
|
588
596
|
try {
|
|
589
597
|
_setSecret(account, value);
|
|
590
598
|
} catch (err) {
|
|
599
|
+
throw saveSecretError(account, err);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/** Non-blocking saveSecret: Linux secret-tool runs off the event loop. */
|
|
604
|
+
export async function saveSecretAsync(account, value) {
|
|
605
|
+
try {
|
|
606
|
+
await _setSecretAsync(account, value);
|
|
607
|
+
} catch (err) {
|
|
608
|
+
throw saveSecretError(account, err);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function saveSecretError(account, err) {
|
|
613
|
+
{
|
|
591
614
|
// On WSL/headless Linux (and any host missing a usable keychain provider,
|
|
592
615
|
// e.g. secret-tool/libsecret not installed or no running Secret Service) the OS
|
|
593
616
|
// write throws a cryptic provider error. Surface an actionable message that
|
|
@@ -599,11 +622,10 @@ export function saveSecret(account, value) {
|
|
|
599
622
|
const envHint = stdEnv ? `${stdEnv} (or ${envKey})` : envKey;
|
|
600
623
|
const e = new Error(
|
|
601
624
|
`[config] could not save secret to the OS keychain: ${err?.message ? err.message : err}\n` +
|
|
602
|
-
` No usable keychain provider on this host (common on WSL / headless Linux without libsecret).\n` +
|
|
603
625
|
` Set the ${envHint} environment variable instead — the runtime reads it directly.`
|
|
604
626
|
);
|
|
605
627
|
e.cause = err;
|
|
606
|
-
|
|
628
|
+
return e;
|
|
607
629
|
}
|
|
608
630
|
}
|
|
609
631
|
|
|
@@ -611,6 +633,32 @@ export function deleteSecret(account) {
|
|
|
611
633
|
_deleteSecret(account);
|
|
612
634
|
}
|
|
613
635
|
|
|
636
|
+
/** Non-blocking deleteSecret: Linux secret-tool runs off the event loop. */
|
|
637
|
+
export async function deleteSecretAsync(account) {
|
|
638
|
+
await _deleteSecretAsync(account);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** Retry a failed Linux keyring read; true when it just recovered. */
|
|
642
|
+
export async function retryKeychain(options) {
|
|
643
|
+
return typeof _retryKeychain === 'function' ? await _retryKeychain(options) : false;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/** Passively detect a keyring that locked after it was read and re-snapshot a
|
|
647
|
+
* stale cache (no unlock prompt). Resolves true when stored values changed. */
|
|
648
|
+
export async function refreshKeychainStatus(options) {
|
|
649
|
+
return typeof _refreshKeychainStatus === 'function' ? (await _refreshKeychainStatus(options)) === true : false;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/** Subscribe to a locked/failed Linux keyring becoming readable again. */
|
|
653
|
+
export function onKeychainRecovered(listener) {
|
|
654
|
+
return typeof _onKeychainRecovered === 'function' ? _onKeychainRecovered(listener) : () => {};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/** Keyring health for the UI: { state: ok|locked|missing|error|unknown, message }. */
|
|
658
|
+
export function getKeychainStatus() {
|
|
659
|
+
return typeof _keychainStatus === 'function' ? _keychainStatus() : { state: 'unknown', message: '' };
|
|
660
|
+
}
|
|
661
|
+
|
|
614
662
|
/**
|
|
615
663
|
* Whether a secret is stored in the OS keychain for `account` (ignores env).
|
|
616
664
|
* Lets the setup UI show "Set" WITHOUT ever sending the secret value to the
|
|
@@ -30,6 +30,13 @@ export const DEVELOPER_SECTIONS = Object.freeze([
|
|
|
30
30
|
warning: OAUTH_RISK_WARNING,
|
|
31
31
|
provider: 'cursor-oauth',
|
|
32
32
|
}),
|
|
33
|
+
Object.freeze({
|
|
34
|
+
id: 'mimoOAuth',
|
|
35
|
+
label: 'MiMo',
|
|
36
|
+
description: 'Show Xiaomi MiMo OAuth in Providers and the model picker.',
|
|
37
|
+
warning: OAUTH_RISK_WARNING,
|
|
38
|
+
provider: 'mimo-oauth',
|
|
39
|
+
}),
|
|
33
40
|
]),
|
|
34
41
|
}),
|
|
35
42
|
]);
|