mixdog 1.0.8 → 1.0.10
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/package.json +2 -1
- package/scripts/lib/run-node-tests.mjs +13 -0
- package/scripts/session-efficiency-diag.mjs +283 -0
- package/src/defaults/skills/computer-use/SKILL.md +3 -3
- package/src/defaults/skills/local-provider/SKILL.md +2 -1
- package/src/defaults/skills/setup/SKILL.md +4 -4
- package/src/defaults/skills/setup/references/actions.md +1 -3
- package/src/defaults/skills/setup/references/surfaces.md +2 -3
- package/src/headless-exec.mjs +1 -0
- package/src/rules/shared/70-delivery.md +2 -2
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +8 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +18 -3
- package/src/runtime/agent/orchestrator/providers/client-version-store.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +27 -4
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +163 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-models.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +23 -4
- package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +1 -28
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/provider-base-url.mjs +24 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +14 -4
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +16 -0
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +0 -20
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -2
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +12 -10
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +15 -3
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +23 -9
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +15 -6
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +22 -1
- package/src/runtime/channels/lib/voice-transcription.mjs +30 -4
- package/src/runtime/computer-bridge/action-schema.mjs +3 -3
- package/src/runtime/computer-bridge/actions.mjs +4 -0
- package/src/runtime/computer-bridge/bridge-env.test-support.mjs +22 -0
- package/src/runtime/computer-bridge/client.mjs +10 -183
- package/src/runtime/computer-bridge/core-actions.mjs +2 -1
- package/src/runtime/computer-bridge/error-recovery.mjs +46 -34
- package/src/runtime/computer-bridge/result-canonical.mjs +181 -0
- package/src/runtime/local-provider/asset-installer.mjs +20 -10
- package/src/runtime/local-provider/catalog.mjs +4 -7
- package/src/runtime/local-provider/data/manifest.json +14 -1
- package/src/runtime/local-provider/hardware.mjs +36 -5
- package/src/runtime/local-provider/resumable-installations.mjs +2 -2
- package/src/runtime/local-provider/server.mjs +1 -1
- package/src/runtime/memory/index.mjs +2 -4
- package/src/runtime/memory/lib/core-memory-file.mjs +3 -8
- package/src/runtime/memory/lib/core-memory-store.mjs +14 -61
- package/src/runtime/memory/lib/cycle-scheduler/backlog-probe.mjs +5 -9
- package/src/runtime/memory/lib/cycle1/cycle1-plan.mjs +7 -0
- package/src/runtime/memory/lib/cycle1/cycle1-rows.mjs +43 -16
- package/src/runtime/memory/lib/cycle1/cycle1-window.mjs +11 -2
- package/src/runtime/memory/lib/embedding-provider.mjs +2 -5
- package/src/runtime/memory/lib/embedding-reindex.mjs +1 -5
- package/src/runtime/memory/lib/http-router/lifecycle-routes.mjs +0 -1
- package/src/runtime/memory/lib/memory-action-handlers/maintenance-actions.mjs +1 -1
- package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +4 -22
- package/src/runtime/memory/lib/memory.mjs +79 -32
- package/src/runtime/memory/lib/query-maintenance-handlers.mjs +0 -2
- package/src/runtime/office/com/com-adapter.mjs +3 -0
- package/src/runtime/office/pdf/pdf-adapter.mjs +50 -14
- package/src/runtime/office/pdf/pdf-render.mjs +2 -6
- package/src/runtime/office/pdf/pdf-security.mjs +8 -2
- package/src/runtime/office/portable/font-provisioner.mjs +28 -0
- package/src/runtime/office/portable/portable-docx-edits.mjs +28 -1
- package/src/runtime/office/portable/portable-soffice.mjs +44 -7
- package/src/runtime/shared/llm/anthropic-betas.mjs +7 -1
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +1 -1
- package/src/runtime/shared/llm/default-context-window.mjs +42 -0
- package/src/runtime/shared/llm/model-catalog-projection.mjs +2 -0
- package/src/runtime/shared/llm/model-catalog.mjs +2 -0
- package/src/runtime/shared/model-alias.mjs +5 -0
- package/src/runtime/shared/plugin-paths.mjs +9 -2
- package/src/runtime/shared/worker-exec-argv.mjs +25 -0
- package/src/runtime/web-search/lib/formatter.mjs +43 -18
- package/src/session-runtime/automation-agents.mjs +88 -0
- package/src/session-runtime/automation-workflow.mjs +19 -6
- package/src/session-runtime/boot/apis.mjs +6 -1
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +14 -12
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +4 -25
- package/src/session-runtime/model-recency.mjs +10 -2
- package/src/session-runtime/model-route/set-route.mjs +7 -1
- package/src/session-runtime/provider-auth/login.mjs +26 -1
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +17 -2
- package/src/session-runtime/resource-api.mjs +8 -2
- package/src/session-runtime/resource-plugins-api.mjs +14 -2
- package/src/session-runtime/route-state.mjs +6 -2
- package/src/session-runtime/schedule-session-run.mjs +7 -1
- package/src/session-runtime/services/agent-tool/render.mjs +0 -5
- package/src/session-runtime/services/provider-admin.mjs +201 -3
- package/src/session-runtime/settings-builtin-tools-api.mjs +0 -16
- package/src/session-runtime/setup-tool/executor.mjs +7 -13
- package/src/session-runtime/setup-tool/tool-defs.mjs +2 -3
- package/src/session-runtime/tool-policy-refresh.mjs +0 -2
- package/src/session-runtime/webhook-session-run.mjs +10 -11
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +5 -0
- package/src/standalone/daemon-agent-control.mjs +8 -1
- package/src/standalone/daemon.mjs +4 -0
- package/src/standalone/session-protocol.mjs +4 -0
- package/src/tui/app/model-options.mjs +10 -1
- package/src/tui/dist/index.mjs +12 -3
- package/src/tui/session/agent-job-feed/notification-router.mjs +7 -0
- package/src/tui/session/live-share.mjs +16 -1
- package/src/tui/session/notification-plan.mjs +4 -0
- package/src/tui/session/session-api/integrations.mjs +12 -0
- package/src/session-runtime/bridge-first-use-gate.mjs +0 -72
|
@@ -21,7 +21,7 @@ import { CODEX_OAUTH_ORIGINATOR, codexModelsUrl } from './openai-codex-endpoints
|
|
|
21
21
|
import { _normalizeCodexModel, _markLatestCodex } from './openai-codex-model.mjs';
|
|
22
22
|
|
|
23
23
|
const CODEX_MODEL_CACHE_TTL_MS = 24 * 60 * 60_000;
|
|
24
|
-
const CODEX_MODEL_CACHE_SCHEMA_VERSION =
|
|
24
|
+
const CODEX_MODEL_CACHE_SCHEMA_VERSION = 6;
|
|
25
25
|
const CATALOG_FETCH_TIMEOUT_MS = 10_000;
|
|
26
26
|
|
|
27
27
|
// In-memory mirror of the on-disk catalog, same pattern as anthropic-oauth.
|
|
@@ -59,6 +59,12 @@ export function codexCatalogHas(id) {
|
|
|
59
59
|
return _mirror.some((m) => m.id === id);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/** The catalog's own answer, or null when it has none for this model. */
|
|
63
|
+
export function codexModelSupportsEffortUpdates(id) {
|
|
64
|
+
const supported = findCachedCodexModel(id)?.supportsReasoningEffortUpdates;
|
|
65
|
+
return typeof supported === 'boolean' ? supported : null;
|
|
66
|
+
}
|
|
67
|
+
|
|
62
68
|
export function codexModelSupportsServiceTier(id, serviceTier) {
|
|
63
69
|
return modelSupportsServiceTier(findCachedCodexModel(id), serviceTier);
|
|
64
70
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Keep saved custom endpoints and OpenAI-compatible clients under the same
|
|
2
|
+
// transport policy: credentials may travel over HTTP only on loopback.
|
|
3
|
+
export function assertSafeBaseURL(rawURL, providerName) {
|
|
4
|
+
let parsed;
|
|
5
|
+
try {
|
|
6
|
+
parsed = new URL(String(rawURL));
|
|
7
|
+
} catch {
|
|
8
|
+
throw new Error(`[provider:${providerName}] invalid baseURL`);
|
|
9
|
+
}
|
|
10
|
+
const scheme = parsed.protocol.toLowerCase();
|
|
11
|
+
if (scheme !== 'https:' && scheme !== 'http:') {
|
|
12
|
+
throw new Error(`[provider:${providerName}] baseURL scheme not allowed: ${parsed.protocol} (only http/https)`);
|
|
13
|
+
}
|
|
14
|
+
if (scheme === 'http:') {
|
|
15
|
+
const host = parsed.hostname.toLowerCase();
|
|
16
|
+
const isLocal = host === 'localhost' || host === '127.0.0.1' || host === '[::1]';
|
|
17
|
+
if (!isLocal) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`[provider:${providerName}] baseURL must use https for non-localhost host (got ${parsed.protocol}//${parsed.hostname})`
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return rawURL;
|
|
24
|
+
}
|
|
@@ -150,7 +150,16 @@ const PROVIDER_MODULES = {
|
|
|
150
150
|
'opencode-go': ['./opencode-go.mjs', 'OpenCodeGoProvider'],
|
|
151
151
|
};
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
// User-defined providers (cfg.type === 'custom') are keyed by a generated
|
|
154
|
+
// `custom-<uuid>` id and never collide with the built-in names.
|
|
155
|
+
function isCustomProviderConfig(name, cfg) {
|
|
156
|
+
return cfg?.type === 'custom' && String(name).startsWith('custom-');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function loadProviderCtor(name, signal = null, cfg = null) {
|
|
160
|
+
if (isCustomProviderConfig(name, cfg)) {
|
|
161
|
+
return loadProviderExport('custom-provider', './custom-provider.mjs', 'createCustomProvider', signal);
|
|
162
|
+
}
|
|
154
163
|
if (Object.hasOwn(PROVIDER_MODULES, name)) {
|
|
155
164
|
const [spec, exportName] = PROVIDER_MODULES[name];
|
|
156
165
|
return loadProviderExport(name, spec, exportName, signal);
|
|
@@ -161,7 +170,8 @@ async function loadProviderCtor(name, signal = null) {
|
|
|
161
170
|
throw new Error(`unknown enabled provider: ${name}`);
|
|
162
171
|
}
|
|
163
172
|
|
|
164
|
-
function instantiateProvider(name, Ctor, cfg) {
|
|
173
|
+
async function instantiateProvider(name, Ctor, cfg) {
|
|
174
|
+
if (isCustomProviderConfig(name, cfg)) return wrapProviderAdmission(await Ctor(name, cfg), name);
|
|
165
175
|
if (Object.hasOwn(OPENAI_COMPAT_PRESETS, name) && name !== 'opencode-go') {
|
|
166
176
|
return wrapProviderAdmission(new Ctor(name, cfg), name);
|
|
167
177
|
}
|
|
@@ -239,9 +249,9 @@ async function _initProvidersUnsynchronized(config, signal = null) {
|
|
|
239
249
|
return { name, inst: providers.get(name), sig };
|
|
240
250
|
}
|
|
241
251
|
try {
|
|
242
|
-
const Ctor = await loadProviderCtor(name, signal);
|
|
252
|
+
const Ctor = await loadProviderCtor(name, signal, cfg);
|
|
243
253
|
throwIfAborted(signal);
|
|
244
|
-
const inst = instantiateProvider(name, Ctor, cfg);
|
|
254
|
+
const inst = await instantiateProvider(name, Ctor, cfg);
|
|
245
255
|
return { name, inst, sig };
|
|
246
256
|
} catch (err) {
|
|
247
257
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -469,6 +469,22 @@ const CONTEXT_OVERFLOW_PATTERNS = [
|
|
|
469
469
|
/context[_ ]length[_ ]exceeded/i,
|
|
470
470
|
/prompt is too long/i,
|
|
471
471
|
/reduce the length of (?:the )?(?:messages|input|prompt)/i,
|
|
472
|
+
// Wordings other backends use for the same refusal, each tied to a context
|
|
473
|
+
// or input-length noun so a tokens-per-minute rate limit never matches.
|
|
474
|
+
/prompt too long/i, // z.ai, Ollama
|
|
475
|
+
/prompt exceeds max length/i, // z.ai CN endpoint
|
|
476
|
+
/input is too long for requested model/i, // Amazon Bedrock
|
|
477
|
+
/input token count.*exceeds the maximum/i, // Google
|
|
478
|
+
/maximum prompt length is \d+/i, // xAI
|
|
479
|
+
/exceeds (?:the )?maximum allowed input length/i, // OpenRouter relays
|
|
480
|
+
/is longer than the model'?s context length/i, // Together AI
|
|
481
|
+
/exceeds the available context size/i, // llama.cpp server
|
|
482
|
+
/greater than the context length/i, // LM Studio
|
|
483
|
+
/context window exceeds limit/i, // MiniMax
|
|
484
|
+
/exceeded model token limit/i, // Kimi
|
|
485
|
+
/too large for model with \d+ maximum context length/i, // Mistral
|
|
486
|
+
/range of input length should be/i, // DashScope / Qwen
|
|
487
|
+
/model_context_window_exceeded/i, // z.ai finish reason surfaced as text
|
|
472
488
|
];
|
|
473
489
|
|
|
474
490
|
/**
|
|
@@ -148,26 +148,6 @@ export function localGitToolsActive(configLike, { gitAvailable } = {}) {
|
|
|
148
148
|
);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
-
/** Capabilities that ask the user once per session before their first live
|
|
152
|
-
* call: the desktop and the browser are the user's, and one approval at the
|
|
153
|
-
* moment of first use is how the user learns the model reached for them. */
|
|
154
|
-
export const BRIDGE_FIRST_USE_IDS = Object.freeze(['browser', 'computer']);
|
|
155
|
-
|
|
156
|
-
/** On unless the profile turns it off for that capability;
|
|
157
|
-
* MIXDOG_BRIDGE_FIRST_USE_APPROVAL overrides per process (headless, bench). */
|
|
158
|
-
export function builtinFirstUseApproval(configLike, id) {
|
|
159
|
-
return (
|
|
160
|
-
featureEnvOverride('MIXDOG_BRIDGE_FIRST_USE_APPROVAL') ?? configLike?.builtins?.[id]?.firstUseApproval !== false
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export function setBuiltinFirstUseApprovalInConfig(configLike, id, enabled) {
|
|
165
|
-
const next = { ...(configLike || {}) };
|
|
166
|
-
next.builtins = { ...(next.builtins || {}) };
|
|
167
|
-
next.builtins[id] = { ...(next.builtins[id] || {}), firstUseApproval: enabled !== false };
|
|
168
|
-
return next;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
151
|
export function setBuiltinInstalledInConfig(configLike, id, installed = true) {
|
|
172
152
|
const next = { ...(configLike || {}) };
|
|
173
153
|
next.builtins = { ...(next.builtins || {}) };
|
|
@@ -14,7 +14,7 @@ export function makeResolveDefaultProvider(isKnownProvider) {
|
|
|
14
14
|
return function resolveDefaultProvider(config) {
|
|
15
15
|
const selectedMain = findPreset(config, config?.default);
|
|
16
16
|
const configured = clean(selectedMain?.provider);
|
|
17
|
-
if (configured && isKnownProvider(configured)) return configured;
|
|
17
|
+
if (configured && isKnownProvider(configured, config)) return configured;
|
|
18
18
|
return DEFAULT_PROVIDER;
|
|
19
19
|
};
|
|
20
20
|
}
|
|
@@ -124,7 +124,8 @@ export function isLikelyRawModelId(value) {
|
|
|
124
124
|
const model = clean(value);
|
|
125
125
|
if (!model || model.length > 160) return false;
|
|
126
126
|
if (/\s/.test(model)) return false;
|
|
127
|
-
|
|
127
|
+
// OpenRouter's rolling model aliases start with a literal "~".
|
|
128
|
+
return /^~?[A-Za-z0-9][A-Za-z0-9._:/@+-]*$/.test(model);
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
export function validateRequestedModelSelector(config, requested = {}) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { clean, hasOwn } from './session-text.mjs';
|
|
6
6
|
import { modelSupportsServiceTier } from '../providers/model-service-tiers.mjs';
|
|
7
7
|
import { openAiDirectSupportsFast } from '../providers/openai-direct-request.mjs';
|
|
8
|
+
import { supportsAnthropicFastMode } from '../../../shared/llm/anthropic-betas.mjs';
|
|
8
9
|
|
|
9
10
|
const FAST_CAPABLE_PROVIDERS = new Set([
|
|
10
11
|
'anthropic',
|
|
@@ -52,16 +53,11 @@ function geminiModelSupportsHostedWebSearch(model) {
|
|
|
52
53
|
function anthropicModelSupportsHostedWebSearch(model) {
|
|
53
54
|
const id = clean(model?.id || model).toLowerCase();
|
|
54
55
|
if (!id) return false;
|
|
55
|
-
const match = id.match(/^claude-(opus|sonnet|haiku)-(\d+)(?:[-.](\d+))?/);
|
|
56
|
+
const match = id.match(/^claude-(opus|sonnet|haiku|fable|mythos)-(\d+)(?:[-.](\d+))?/);
|
|
56
57
|
if (!match) return false;
|
|
57
58
|
return (Number(match[2]) || 0) >= 4;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
function anthropicModelMetaSupportsFast(model) {
|
|
61
|
-
const id = clean(model?.id || model).toLowerCase();
|
|
62
|
-
return /^claude-(opus|sonnet)/.test(id);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
61
|
export function fastCapableFor(provider, model, effort = null, modelParameters = {}) {
|
|
66
62
|
const p = clean(provider);
|
|
67
63
|
if (!FAST_CAPABLE_PROVIDERS.has(p)) return false;
|
|
@@ -82,7 +78,7 @@ export function fastCapableFor(provider, model, effort = null, modelParameters =
|
|
|
82
78
|
}
|
|
83
79
|
if (p === 'openai') return openAiDirectSupportsFast(model);
|
|
84
80
|
if (p === 'openai-oauth') return modelSupportsServiceTier(model, 'priority');
|
|
85
|
-
if (p === 'anthropic' || p === 'anthropic-oauth') return
|
|
81
|
+
if (p === 'anthropic' || p === 'anthropic-oauth') return supportsAnthropicFastMode(clean(model?.id || model));
|
|
86
82
|
return false;
|
|
87
83
|
}
|
|
88
84
|
|
|
@@ -127,9 +123,15 @@ export function saveModelSettings(cfgMod, route, { fastCapable = true, baseConfi
|
|
|
127
123
|
} else {
|
|
128
124
|
delete nextSetting.modelParameters;
|
|
129
125
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
126
|
+
// Only a window the user moved off the model's default is a setting. The
|
|
127
|
+
// pickers send the default stop with every selection; storing it froze each
|
|
128
|
+
// model at whatever its default was that day, so a later default never
|
|
129
|
+
// reached anyone who had already selected the model.
|
|
130
|
+
const requestedPercent = Number(route.contextPercent);
|
|
131
|
+
const contextPercent = Math.round(requestedPercent / 10) * 10;
|
|
132
|
+
const isDefault = contextPercent === Number(route.contextDefaultPercent);
|
|
133
|
+
if (requestedPercent >= 10 && requestedPercent <= 100 && !isDefault) {
|
|
134
|
+
nextSetting.contextPercent = contextPercent;
|
|
133
135
|
} else {
|
|
134
136
|
delete nextSetting.contextPercent;
|
|
135
137
|
}
|
|
@@ -119,7 +119,9 @@ function summarySendOpts(opts) {
|
|
|
119
119
|
effort: 'low',
|
|
120
120
|
effortConfiguration: undefined,
|
|
121
121
|
effortConfigurationEnabled: false,
|
|
122
|
-
|
|
122
|
+
// Fast mode is opt-in (it needs separate account credits); the summary
|
|
123
|
+
// follows the route/session choice and never enables it on its own.
|
|
124
|
+
fast: opts.fast ?? opts.sendOpts?.fast ?? false,
|
|
123
125
|
maxOutputTokens: opts.maxOutputTokens || SUMMARY_OUTPUT_TOKENS,
|
|
124
126
|
providerState: undefined,
|
|
125
127
|
onToolCall: undefined,
|
|
@@ -54,7 +54,7 @@ export async function resolveCompactionRoute({
|
|
|
54
54
|
const contextWindow =
|
|
55
55
|
sameModel && positiveInt(sessionRef?.contextWindow)
|
|
56
56
|
? positiveInt(sessionRef.contextWindow)
|
|
57
|
-
: resolveSessionContextMeta(selectedProvider, selectedModel).contextWindow;
|
|
57
|
+
: resolveSessionContextMeta(selectedProvider, selectedModel, {}, { wholeWindow: true }).contextWindow;
|
|
58
58
|
return {
|
|
59
59
|
provider: selectedProvider,
|
|
60
60
|
providerName,
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// resolution.
|
|
3
3
|
|
|
4
4
|
import { getModelMetadataSync } from '../../providers/model-catalog.mjs';
|
|
5
|
+
import { contextWindowRange } from '../../../../shared/llm/default-context-window.mjs';
|
|
5
6
|
import { positiveInt } from '../../../../shared/numbers.mjs';
|
|
6
7
|
|
|
7
8
|
// Family-pattern fallback used only when the provider and external catalogs
|
|
@@ -96,11 +97,12 @@ function providerRawContextWindow(info, catalogInfo) {
|
|
|
96
97
|
if (catalogWindow && fromCache !== catalogWindow) return catalogWindow;
|
|
97
98
|
return fromCache || null;
|
|
98
99
|
}
|
|
99
|
-
|
|
100
|
+
// `wholeWindow` asks for everything the model can take in (the summary route
|
|
101
|
+
// budgets its own input that way) instead of the window a session starts with.
|
|
102
|
+
export function resolveSessionContextMeta(provider, model, seed = {}, { wholeWindow = false } = {}) {
|
|
100
103
|
const info = typeof provider?.getCachedModelInfo === 'function' ? provider.getCachedModelInfo(model) : null;
|
|
101
104
|
const catalogInfo = getModelMetadataSync(model, providerNameOf(provider));
|
|
102
|
-
const
|
|
103
|
-
positiveInt(seed.selectedContextWindow) ||
|
|
105
|
+
const servedContextWindow =
|
|
104
106
|
providerRawContextWindow(info, catalogInfo) ||
|
|
105
107
|
positiveInt(catalogInfo?.contextWindow) ||
|
|
106
108
|
positiveInt(catalogInfo?.maxContextWindow) ||
|
|
@@ -110,6 +112,16 @@ export function resolveSessionContextMeta(provider, model, seed = {}) {
|
|
|
110
112
|
positiveInt(seed.raw_context_window) ||
|
|
111
113
|
positiveInt(seed.contextWindow) ||
|
|
112
114
|
guessContextWindow(model, providerNameOf(provider));
|
|
115
|
+
// A session that records no selection starts from the model's default
|
|
116
|
+
// window, the one the picker shows. A recorded percentage without its window
|
|
117
|
+
// (a session older than selectedContextWindow, a route saved before the
|
|
118
|
+
// catalog warmed) is still the user's choice and keeps the served window.
|
|
119
|
+
const recordsSelection = wholeWindow || boundedPercent(seed.contextPercent) !== null;
|
|
120
|
+
const requestedContextWindow =
|
|
121
|
+
positiveInt(seed.selectedContextWindow) ||
|
|
122
|
+
(recordsSelection
|
|
123
|
+
? servedContextWindow
|
|
124
|
+
: contextWindowRange({ provider: providerNameOf(provider), contextWindow: servedContextWindow }).defaultWindow);
|
|
113
125
|
// A managed runtime's allocated capacity also bounds restored selections
|
|
114
126
|
// and catalog metadata; raising a slider cannot allocate server memory.
|
|
115
127
|
const runtimeContextWindow = positiveInt(info?.runtimeContextWindow);
|
|
@@ -13,7 +13,12 @@ import { buildProviderCacheOpts, cacheCapabilityForProvider } from '../../agent-
|
|
|
13
13
|
import { normalizeAutoClearConfig, resolveAutoClearIdleMs } from '../../runtime-core/config-helpers.mjs';
|
|
14
14
|
import { _buildBaseRules } from './rules-cache.mjs';
|
|
15
15
|
import { composeSessionSystem, seedSessionMessages } from './session-prompt-composition.mjs';
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
_prepareResumeTools,
|
|
18
|
+
delegationDisabled,
|
|
19
|
+
honoredSchemaAllowlist,
|
|
20
|
+
resolveSessionToolSurface,
|
|
21
|
+
} from './session-tool-surface.mjs';
|
|
17
22
|
import { _forgetPreparedResume, _preparedResumeMatches, _readPreparedResume } from './prepared-resume-cache.mjs';
|
|
18
23
|
import {
|
|
19
24
|
filterModelEditToolNames,
|
|
@@ -175,6 +180,7 @@ export function createSession(opts) {
|
|
|
175
180
|
logSessionSurface(surface);
|
|
176
181
|
const contextMeta = resolveSessionContextMeta(provider, modelName, {
|
|
177
182
|
selectedContextWindow: opts.selectedContextWindow,
|
|
183
|
+
contextPercent: route.contextPercent,
|
|
178
184
|
});
|
|
179
185
|
const session = buildSessionRecord({
|
|
180
186
|
opts,
|
|
@@ -215,7 +221,10 @@ export function _refreshSessionRuleVariantsForModel(session, previousModel, prev
|
|
|
215
221
|
...(getHiddenAgent(session?.agent || null) ? ['Skill'] : []),
|
|
216
222
|
...(delegationDisabled(session, isAgentOwner(session)) ? ['agent'] : []),
|
|
217
223
|
];
|
|
218
|
-
const allowTools =
|
|
224
|
+
const allowTools = honoredSchemaAllowlist(session?.schemaAllowedTools, {
|
|
225
|
+
ownerIsAgent: isAgentOwner(session),
|
|
226
|
+
agent: session?.agent || null,
|
|
227
|
+
});
|
|
219
228
|
const previousRules = _buildBaseRules({
|
|
220
229
|
omitTools: [...deny, unusedModelEditToolName(previousModel)],
|
|
221
230
|
allowTools,
|
|
@@ -63,7 +63,11 @@ function buildShellEnvironmentContext(opts, ownerIsAgent, toolsForRouting) {
|
|
|
63
63
|
// four system blocks; environmentTailContext is the persisted env tail.
|
|
64
64
|
export function composeSessionSystem(opts, { profile, providerName, modelName, surface }) {
|
|
65
65
|
const { ownerIsAgent, resolvedAgent, skills } = surface;
|
|
66
|
-
|
|
66
|
+
// A tool-free hidden role (a one-shot classifier or title writer) carries
|
|
67
|
+
// only its own role rules: the shared tool policy and the worker conduct
|
|
68
|
+
// rules describe work it is offered no tool for.
|
|
69
|
+
const toolFreeRole = ownerIsAgent && surface.schemaAllowedTools?.length === 0;
|
|
70
|
+
const skipAgentRules = opts.skipAgentRules === true || toolFreeRole;
|
|
67
71
|
const injectedRules = skipAgentRules
|
|
68
72
|
? ''
|
|
69
73
|
: _buildBaseRules({
|
|
@@ -29,6 +29,19 @@ function resolveToolSpec(toolPreset, profile, ownerIsAgent) {
|
|
|
29
29
|
return Array.isArray(profile?.tools) ? profile.tools : toolPreset;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
// The schema allowlist a session honours. Agent-owned sessions share one
|
|
33
|
+
// schema, so an allowlist handed down by a caller never narrows them — except
|
|
34
|
+
// the profile the session's own hidden role declares (agents.json
|
|
35
|
+
// toolSchemaProfile). Without that exception a tool-free maintenance role was
|
|
36
|
+
// sent the whole Agent schema on every one-shot call while the dispatch gate
|
|
37
|
+
// refused each of those tools (usage ledger 2026-09-30..10-05: 4104 cycle1
|
|
38
|
+
// calls at ~8.5K prompt tokens, about two thirds of it the 12-tool schema).
|
|
39
|
+
export function honoredSchemaAllowlist(schemaAllowedTools, { ownerIsAgent = false, agent = null } = {}) {
|
|
40
|
+
if (!Array.isArray(schemaAllowedTools)) return null;
|
|
41
|
+
if (ownerIsAgent && !getHiddenAgent(agent)) return null;
|
|
42
|
+
return schemaAllowedTools;
|
|
43
|
+
}
|
|
44
|
+
|
|
32
45
|
// Every tool omitted by a caller schema allowlist is omitted from BP1 too.
|
|
33
46
|
// The model never receives guidance for a tool it cannot call, and a new
|
|
34
47
|
// process-wide built-in cannot leak into a narrow profile's prompt.
|
|
@@ -48,13 +61,17 @@ export function resolveSessionToolSurface(opts, { profile, toolPreset, modelName
|
|
|
48
61
|
const resolvedAgent = opts.agent || opts.role || profile?.taskType || null;
|
|
49
62
|
const hiddenAgent = getHiddenAgent(resolvedAgent);
|
|
50
63
|
const isRetrievalAgent = hiddenAgent?.kind === 'retrieval';
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
//
|
|
54
|
-
//
|
|
64
|
+
const hasCallerAllow = Array.isArray(opts.schemaAllowedTools);
|
|
65
|
+
const schemaAllowedTools = honoredSchemaAllowlist(opts.schemaAllowedTools, { ownerIsAgent, agent: resolvedAgent });
|
|
66
|
+
// Lead and Agent share the same cwd-scoped Skill inventory. A session that
|
|
67
|
+
// is offered no tool cannot load a skill, so it is not sent the manifest.
|
|
68
|
+
const skills = opts.skipSkills || schemaAllowedTools?.length === 0 ? [] : collectPromptSkillsCached(opts.cwd);
|
|
69
|
+
// BP1 shared tool policy ships to every role that is offered tools (Lead,
|
|
70
|
+
// workers, retrieval): its anti-spiral clauses (one anchor is enough, never
|
|
55
71
|
// repeat equivalent patterns/scopes, plausible hit → stop) are exactly
|
|
56
72
|
// what narrow retrieval roles need. Role docs
|
|
57
|
-
// override role-inapplicable entries.
|
|
73
|
+
// override role-inapplicable entries. A tool-free role is sent none of it
|
|
74
|
+
// (session-prompt-composition.mjs).
|
|
58
75
|
const sessionDeny = [
|
|
59
76
|
...(Array.isArray(opts.disallowedTools) ? opts.disallowedTools : []),
|
|
60
77
|
...(delegationDisabled(opts, ownerIsAgent) ? ['agent'] : []),
|
|
@@ -70,8 +87,6 @@ export function resolveSessionToolSurface(opts, { profile, toolPreset, modelName
|
|
|
70
87
|
cwd: opts.cwd || null,
|
|
71
88
|
});
|
|
72
89
|
|
|
73
|
-
const hasCallerAllow = Array.isArray(opts.schemaAllowedTools);
|
|
74
|
-
const schemaAllowedTools = ownerIsAgent || !hasCallerAllow ? null : opts.schemaAllowedTools;
|
|
75
90
|
const tools = finalizeSessionToolList(toolsForRouting, {
|
|
76
91
|
schemaAllowedTools,
|
|
77
92
|
disallowedTools: sessionDeny,
|
|
@@ -121,8 +136,7 @@ export function _prepareResumeTools(session, preset) {
|
|
|
121
136
|
toolSpec,
|
|
122
137
|
ownerIsAgent,
|
|
123
138
|
tools: finalizeSessionToolList(toolsForRouting, {
|
|
124
|
-
schemaAllowedTools:
|
|
125
|
-
!ownerIsAgent && Array.isArray(session.schemaAllowedTools) ? session.schemaAllowedTools : null,
|
|
139
|
+
schemaAllowedTools: honoredSchemaAllowlist(session.schemaAllowedTools, { ownerIsAgent, agent: session.agent }),
|
|
126
140
|
disallowedTools: [
|
|
127
141
|
...(Array.isArray(session.disallowedTools) ? session.disallowedTools : []),
|
|
128
142
|
...(delegationDisabled(session, ownerIsAgent) ? ['agent'] : []),
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
// Commands start in the foreground. Only work still running after the
|
|
2
|
-
//
|
|
2
|
+
// 15 s coordination budget is promoted to a tracked background task.
|
|
3
3
|
// Short commands therefore complete in the original tool turn, while longer
|
|
4
4
|
// work returns partial output plus task_id and finishes by notification.
|
|
5
|
-
|
|
5
|
+
// 15 s because a command promoted moments before it ends costs a whole
|
|
6
|
+
// follow-up request on `task wait`. Session logs (2026-09-30..10-05), the 260
|
|
7
|
+
// promoted commands with a recorded duration: 108 had ended within 10 s, 144
|
|
8
|
+
// within 15 s and only 152 within 20 s.
|
|
9
|
+
export const DEFAULT_SHELL_AUTO_BACKGROUND_MS = 15_000;
|
|
6
10
|
|
|
7
11
|
// JS timers (setTimeout) and PS WaitForExit(ms) are 32-bit: a delay above
|
|
8
12
|
// 2^31-1 wraps to a tiny/negative value and fires immediately. Clamp the
|
|
@@ -13,12 +17,17 @@ const TIMER_MAX_MS = 2_147_483_647;
|
|
|
13
17
|
|
|
14
18
|
// Main-agent blocking budget. A timeout is the command's total deadline, not
|
|
15
19
|
// permission to hold the conversation open for that whole duration: after
|
|
16
|
-
//
|
|
20
|
+
// 15 s a still-running command becomes a tracked background task and
|
|
17
21
|
// completion is pushed to the owner. MIXDOG_SHELL_AUTO_BACKGROUND_MS
|
|
18
22
|
// overrides; an explicit 0 disables. Gated on backgroundOnTimeout so
|
|
19
23
|
// disabled background tasks remain foreground.
|
|
20
|
-
// A per-call wait_ms
|
|
21
|
-
// foreground maximum.
|
|
24
|
+
// A per-call wait_ms extends that default for the call, capped by the
|
|
25
|
+
// foreground maximum; it never shortens it. A shorter window promotes a
|
|
26
|
+
// command that would have returned its result in the starting call and spends
|
|
27
|
+
// a whole follow-up request on `task wait`. Session logs (2026-09-30..10-05,
|
|
28
|
+
// 3062 shell calls): 329 passed a wait_ms under the default (53% of one model
|
|
29
|
+
// family's calls), 256 of those were promoted, and 108 of the 192 with a
|
|
30
|
+
// recorded duration had finished inside the default window (10 s then).
|
|
22
31
|
function autoBackgroundBudget(backgroundOnTimeout, timeout, waitMs, maxForegroundMs) {
|
|
23
32
|
const raw = process.env.MIXDOG_SHELL_AUTO_BACKGROUND_MS;
|
|
24
33
|
const parsed = Number(raw);
|
|
@@ -26,7 +35,7 @@ function autoBackgroundBudget(backgroundOnTimeout, timeout, waitMs, maxForegroun
|
|
|
26
35
|
raw != null && String(raw).trim() !== '' && Number.isFinite(parsed) && parsed >= 0
|
|
27
36
|
? Math.floor(parsed)
|
|
28
37
|
: DEFAULT_SHELL_AUTO_BACKGROUND_MS;
|
|
29
|
-
if (waitMs > 0) defaultMs = Math.min(waitMs, maxForegroundMs);
|
|
38
|
+
if (waitMs > 0) defaultMs = Math.max(defaultMs, Math.min(waitMs, maxForegroundMs));
|
|
30
39
|
if (!backgroundOnTimeout || defaultMs <= 0) return 0;
|
|
31
40
|
return timeout > 0 ? Math.min(defaultMs, timeout) : defaultMs;
|
|
32
41
|
}
|
|
@@ -146,7 +146,7 @@ export const BUILTIN_TOOLS = [
|
|
|
146
146
|
openWorldHint: true,
|
|
147
147
|
compressible: true,
|
|
148
148
|
},
|
|
149
|
-
description: `Run programs, builds, tests and computation. Never use shell commands or scripts for work covered by dedicated tools: cat/head/tail→read, ls→list, find→glob, filename lookup→find, grep/rg→grep, code structure→code_graph, file edits/writes (sed/awk/redirection)→edit/apply_patch, Git→git when that tool is on the surface. Tool names are not shell commands. ${_shellBackgroundDisabled ? 'Commands run in the foreground until completion.' : 'After a
|
|
149
|
+
description: `Run programs, builds, tests and computation. Never use shell commands or scripts for work covered by dedicated tools: cat/head/tail→read, ls→list, find→glob, filename lookup→find, grep/rg→grep, code structure→code_graph, file edits/writes (sed/awk/redirection)→edit/apply_patch, Git→git when that tool is on the surface. Tool names are not shell commands. ${_shellBackgroundDisabled ? 'Commands run in the foreground until completion.' : 'After a 15s foreground window (not a timeout; wait_ms extends it up to 120s for one call, e.g. a test suite), unfinished work continues under task_id; use task wait, not read polling. If no execution slot frees within the admission wait, the command is queued as a background task with a task_id.'}`,
|
|
150
150
|
inputSchema: {
|
|
151
151
|
type: 'object',
|
|
152
152
|
properties: {
|
|
@@ -155,14 +155,14 @@ export const BUILTIN_TOOLS = [
|
|
|
155
155
|
type: 'integer',
|
|
156
156
|
minimum: 0,
|
|
157
157
|
description:
|
|
158
|
-
'Hard kill deadline in ms (omit or 0 = none), separate from the
|
|
158
|
+
'Hard kill deadline in ms (omit or 0 = none), separate from the 15s foreground window; set one for throwaway probes so a slow check fails fast.',
|
|
159
159
|
},
|
|
160
160
|
wait_ms: {
|
|
161
161
|
type: 'integer',
|
|
162
|
-
minimum:
|
|
162
|
+
minimum: 15000,
|
|
163
163
|
maximum: 120000,
|
|
164
164
|
description:
|
|
165
|
-
'Foreground wait in ms before unfinished work continues as a background task (default
|
|
165
|
+
'Foreground wait in ms before unfinished work continues as a background task (default 15000); raise it for a command that should return its result in this call.',
|
|
166
166
|
},
|
|
167
167
|
},
|
|
168
168
|
required: ['command'],
|
|
@@ -439,6 +439,8 @@ class NativePatchServer {
|
|
|
439
439
|
|
|
440
440
|
#contractPromise = null;
|
|
441
441
|
|
|
442
|
+
#contractPending = false;
|
|
443
|
+
|
|
442
444
|
constructor(binPath) {
|
|
443
445
|
this.binPath = binPath;
|
|
444
446
|
// windowsHide: mixdog-patch.exe is a console binary; without this each spawn
|
|
@@ -484,6 +486,13 @@ class NativePatchServer {
|
|
|
484
486
|
this.#child.stdin.on('error', (err) => {
|
|
485
487
|
this.failTransport(err);
|
|
486
488
|
});
|
|
489
|
+
// A spawn that fails outright (an artifact without the execute bit, or one
|
|
490
|
+
// removed between the gate's read and the spawn) reports only through the
|
|
491
|
+
// child's own 'error' event; unheard, it crashed the host instead of
|
|
492
|
+
// failing the handshake closed.
|
|
493
|
+
this.#child.on('error', (err) => {
|
|
494
|
+
this.failTransport(err);
|
|
495
|
+
});
|
|
487
496
|
this.#child.on('exit', (code, signal) => {
|
|
488
497
|
this.exited = true;
|
|
489
498
|
const err = new Error(`native patch server exited code=${code} signal=${signal} stderr=${this.stderr}`);
|
|
@@ -537,15 +546,23 @@ class NativePatchServer {
|
|
|
537
546
|
// the caller may respawn and retry without any risk of double-applying.
|
|
538
547
|
// Contract handshake on THIS session, memoized per instance: N concurrent
|
|
539
548
|
// callers await one exchange, and a respawned session proves itself again.
|
|
549
|
+
/** True while the handshake runs: between its exchanges no waiter is queued, yet the handles must stay referenced. */
|
|
550
|
+
get contractPending() {
|
|
551
|
+
return this.#contractPending;
|
|
552
|
+
}
|
|
553
|
+
|
|
540
554
|
contract() {
|
|
541
555
|
if (!this.#contractPromise) {
|
|
556
|
+
this.#contractPending = true;
|
|
542
557
|
this.#contractPromise = verifyContractOverSession(this.#handshakeView()).then(
|
|
543
558
|
(ok) => {
|
|
559
|
+
this.#contractPending = false;
|
|
544
560
|
this.#contractVerified = ok === true;
|
|
545
561
|
if (!ok) this.markContractFailed();
|
|
546
562
|
return ok;
|
|
547
563
|
},
|
|
548
564
|
() => {
|
|
565
|
+
this.#contractPending = false;
|
|
549
566
|
this.#contractVerified = false;
|
|
550
567
|
this.markContractFailed();
|
|
551
568
|
return false;
|
|
@@ -877,7 +894,11 @@ export function scheduleNativePatchPrewarm() {
|
|
|
877
894
|
}
|
|
878
895
|
}
|
|
879
896
|
await getNativePatchServer().ping();
|
|
880
|
-
if (
|
|
897
|
+
if (
|
|
898
|
+
!nativePatchPersistent() &&
|
|
899
|
+
(_nativePatchServer?.waiters?.length || 0) === 0 &&
|
|
900
|
+
!_nativePatchServer?.contractPending
|
|
901
|
+
) {
|
|
881
902
|
_nativePatchServer?.unref();
|
|
882
903
|
}
|
|
883
904
|
if (nativePatchTraceEnabled()) {
|
|
@@ -98,6 +98,35 @@ async function probeAudioDurationSec(filePath) {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// Leading silence so a clipped onset still decodes as the first word. The
|
|
102
|
+
// bundled ffmpeg carries only aformat/anull/aresample
|
|
103
|
+
// (scripts/build-ffmpeg-runtime.sh), so the pad is spliced into the converted
|
|
104
|
+
// wav here: zero samples in front of its PCM data.
|
|
105
|
+
const ONSET_SILENCE_SECONDS = 0.3;
|
|
106
|
+
|
|
107
|
+
export function prependWavSilence(wavPath, seconds = ONSET_SILENCE_SECONDS) {
|
|
108
|
+
const wav = fs.readFileSync(wavPath);
|
|
109
|
+
let sampleRate = 0;
|
|
110
|
+
let blockAlign = 0;
|
|
111
|
+
for (let offset = 12; offset + 8 <= wav.length; ) {
|
|
112
|
+
const id = wav.toString('ascii', offset, offset + 4);
|
|
113
|
+
const size = wav.readUInt32LE(offset + 4);
|
|
114
|
+
if (id === 'fmt ') {
|
|
115
|
+
sampleRate = wav.readUInt32LE(offset + 12);
|
|
116
|
+
blockAlign = wav.readUInt16LE(offset + 20);
|
|
117
|
+
} else if (id === 'data') {
|
|
118
|
+
const silence = Buffer.alloc(Math.round(sampleRate * seconds) * blockAlign);
|
|
119
|
+
const out = Buffer.concat([wav.subarray(0, offset + 8), silence, wav.subarray(offset + 8)]);
|
|
120
|
+
out.writeUInt32LE(size + silence.length, offset + 4);
|
|
121
|
+
out.writeUInt32LE(out.length - 8, 4);
|
|
122
|
+
fs.writeFileSync(wavPath, out);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
offset += 8 + size + (size % 2);
|
|
126
|
+
}
|
|
127
|
+
throw new Error(`no PCM data chunk in ${wavPath}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
101
130
|
// One whisper-ready wav per attachment: a cached conversion is reused while its
|
|
102
131
|
// file still exists, and parallel callers for the same key share a single
|
|
103
132
|
// ffmpeg spawn instead of racing two conversions onto the same output path.
|
|
@@ -126,9 +155,6 @@ function createVoiceWavCache() {
|
|
|
126
155
|
const _ffmpegPromise = runCmd(ffmpegPath, [
|
|
127
156
|
'-i',
|
|
128
157
|
audioPath,
|
|
129
|
-
// Leading silence so a clipped onset still decodes as the first word.
|
|
130
|
-
'-af',
|
|
131
|
-
'adelay=300:all=1',
|
|
132
158
|
'-ar',
|
|
133
159
|
String(sampleRate),
|
|
134
160
|
'-ac',
|
|
@@ -137,7 +163,7 @@ function createVoiceWavCache() {
|
|
|
137
163
|
String(threadCount),
|
|
138
164
|
'-y',
|
|
139
165
|
wavPath,
|
|
140
|
-
]);
|
|
166
|
+
]).then(() => prependWavSilence(wavPath));
|
|
141
167
|
ffmpegInflight.set(_ffmpegKey, _ffmpegPromise);
|
|
142
168
|
try {
|
|
143
169
|
await _ffmpegPromise;
|
|
@@ -118,11 +118,11 @@ const captureProperties = {
|
|
|
118
118
|
include_ocr: {
|
|
119
119
|
type: 'boolean',
|
|
120
120
|
description:
|
|
121
|
-
'State/SOM automatically use offline
|
|
121
|
+
'State/SOM automatically use offline OS OCR when semantic targets are absent; true always runs OCR even when semantic targets exist. OCR shares max_elements. Zoom recognizes the crop itself, which is how enlarged text becomes readable.',
|
|
122
122
|
},
|
|
123
123
|
ocr_language: {
|
|
124
124
|
...ocrLanguage,
|
|
125
|
-
description: 'Installed
|
|
125
|
+
description: 'Installed OS OCR language tag, e.g. ko or en-US.',
|
|
126
126
|
},
|
|
127
127
|
// Image encoding (JPEG quality, downscale width) and the OCR word cap are
|
|
128
128
|
// host defaults: the element budget already bounds OCR, and a frame the
|
|
@@ -213,7 +213,7 @@ export const COMPUTER_INPUT_SCHEMA = {
|
|
|
213
213
|
...windowTarget,
|
|
214
214
|
ocr_language: {
|
|
215
215
|
...ocrLanguage,
|
|
216
|
-
description: 'Optional
|
|
216
|
+
description: 'Optional OS OCR language tag to verify, e.g. ko or en-US.',
|
|
217
217
|
},
|
|
218
218
|
}),
|
|
219
219
|
false
|
|
@@ -31,6 +31,10 @@ action('ocr_image', nativeRead);
|
|
|
31
31
|
action('ocr_status', nativeRead);
|
|
32
32
|
action('release_session', { retainNativeRefs: true });
|
|
33
33
|
action('release_cursor_theme', { retainNativeRefs: true });
|
|
34
|
+
// Handing held input, focus and pointer back changes no target's content, so a
|
|
35
|
+
// turn's end keeps the refs of its last observation for a follow-up turn.
|
|
36
|
+
action('release_held_input', { retainNativeRefs: true });
|
|
37
|
+
action('restore_input_state', { retainNativeRefs: true });
|
|
34
38
|
action('list_apps', { ...hostRead, ...replay, policy: 'list' });
|
|
35
39
|
action('list_installed_apps', { ...nativeRead, ...replay, policy: 'list' });
|
|
36
40
|
action('list_history', { ...hostRead, ...replay, policy: 'list' });
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
// Creates a temp directory and points the env var `name` at it until `cleanup()`.
|
|
6
|
+
export async function makeTempEnvDir(name, prefix) {
|
|
7
|
+
const directory = await mkdtemp(join(tmpdir(), prefix));
|
|
8
|
+
const previous = process.env[name];
|
|
9
|
+
process.env[name] = directory;
|
|
10
|
+
return {
|
|
11
|
+
directory,
|
|
12
|
+
async cleanup() {
|
|
13
|
+
if (previous === undefined) delete process.env[name];
|
|
14
|
+
else process.env[name] = previous;
|
|
15
|
+
await rm(directory, { recursive: true, force: true });
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function writeBridgeDiscovery(directory, fields) {
|
|
21
|
+
return writeFile(join(directory, 'computer-bridge.json'), JSON.stringify({ version: 1, ...fields }));
|
|
22
|
+
}
|