mixdog 1.2.0 → 1.2.1
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 +7 -5
- package/package.json +1 -1
- package/src/defaults/shipped-history.json +4 -0
- package/src/defaults/skills/pptx/SKILL.md +6 -6
- package/src/defaults/skills/pptx/references/review.md +6 -4
- package/src/defaults/skills/pptx/scripts/source-extract.mjs +1 -1
- package/src/defaults/skills/setup/references/actions.md +13 -3
- package/src/defaults/skills/visualize/SKILL.md +12 -9
- package/src/rules/shared/60-verification.md +3 -1
- package/src/rules/shared/70-delivery.md +4 -2
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +26 -5
- package/src/runtime/agent/orchestrator/config.mjs +10 -4
- package/src/runtime/agent/orchestrator/context/collect.mjs +2 -1
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +11 -5
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +74 -21
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +86 -8
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +5 -1
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +65 -7
- package/src/runtime/agent/orchestrator/mcp/client.mjs +128 -44
- package/src/runtime/agent/orchestrator/mcp/lazy-output-validator.mjs +25 -0
- package/src/runtime/agent/orchestrator/mcp/plugin-placeholders.mjs +54 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +108 -17
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +19 -11
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +337 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-tokens.mjs +159 -0
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +41 -28
- package/src/runtime/agent/orchestrator/providers/lib/gemini-model-catalog.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-bridge.mjs +1080 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +15 -0
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +35 -0
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +12 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +6 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +9 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/registry.mjs +35 -9
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +15 -55
- package/src/runtime/agent/orchestrator/runtime-core/goal-control.mjs +15 -2
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +267 -84
- package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +4 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-schema.mjs +21 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +54 -3
- package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +284 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +268 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/sqlite-gate.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +628 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +184 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +136 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +128 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +127 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +101 -0
- package/src/runtime/agent/orchestrator/session/explicit-skills.mjs +83 -5
- package/src/runtime/agent/orchestrator/session/imported-message.mjs +11 -0
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +6 -4
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +20 -12
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +17 -4
- package/src/runtime/agent/orchestrator/session/manager.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +817 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +19 -12
- package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +158 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +183 -374
- package/src/runtime/agent/orchestrator/session/synthetic-user-envelope.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +21 -2
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +38 -17
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +173 -22
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-guards.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +15 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +13 -10
- package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +17 -12
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/root-resolution.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/route.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/read-redirects.mjs +34 -30
- package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -0
- package/src/runtime/attachments/store.d.mts +3 -0
- package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +1 -1
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +16 -4
- package/src/runtime/channels/lib/worker-main.mjs +17 -14
- package/src/runtime/computer-bridge/linux-readiness.mjs +0 -2
- package/src/runtime/effort-judge/judge-client.mjs +56 -18
- package/src/runtime/memory/lib/memory.mjs +1 -1
- package/src/runtime/memory/lib/session-ingest.mjs +2 -2
- package/src/runtime/shared/atomic-file.mjs +25 -4
- package/src/runtime/shared/extension-scopes.mjs +5 -3
- package/src/runtime/shared/file-lock.mjs +37 -11
- package/src/runtime/shared/llm/anthropic-effort.mjs +14 -0
- package/src/runtime/shared/llm/model-catalog.mjs +0 -12
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +6 -0
- package/src/runtime/shared/plugin-manifest.mjs +18 -9
- package/src/runtime/shared/pristine-execution-contract.json +9 -2
- package/src/runtime/shared/pristine-execution.mjs +28 -4
- package/src/runtime/shared/provider-accounts.mjs +124 -20
- package/src/runtime/shared/provider-api-key.mjs +12 -0
- package/src/runtime/shared/shell-display.mjs +9 -3
- package/src/runtime/shared/skill-mentions.d.mts +10 -0
- package/src/runtime/shared/skill-mentions.mjs +133 -0
- package/src/runtime/shared/skill-script-runtime.mjs +264 -0
- package/src/runtime/shared/tool-primitives.mjs +35 -2
- package/src/runtime/shared/turn-worktree-snapshot.mjs +25 -5
- package/src/runtime/shared/worker-requests.mjs +15 -2
- package/src/session-runtime/automation-agents.mjs +8 -2
- package/src/session-runtime/boot/apis.mjs +55 -0
- package/src/session-runtime/boot/config.mjs +5 -2
- package/src/session-runtime/boot/facade.mjs +1 -0
- package/src/session-runtime/boot/infrastructure.mjs +29 -18
- package/src/session-runtime/conversation-import/discovery.mjs +698 -0
- package/src/session-runtime/conversation-import/service.mjs +369 -0
- package/src/session-runtime/cwd-plugins/cwd-apply.mjs +10 -5
- package/src/session-runtime/cwd-plugins/plugin-status.mjs +47 -42
- package/src/session-runtime/mcp-glue/connect.mjs +57 -10
- package/src/session-runtime/mcp-glue/plugin-servers.mjs +499 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +115 -30
- package/src/session-runtime/mcp-glue.mjs +22 -2
- package/src/session-runtime/new-session-config.mjs +3 -2
- package/src/session-runtime/notification-bus.mjs +9 -2
- package/src/session-runtime/provider-auth/accounts.mjs +33 -15
- package/src/session-runtime/provider-models/web-search-catalog.mjs +5 -1
- package/src/session-runtime/provider-readiness.mjs +16 -10
- package/src/session-runtime/resource-extension-sync.mjs +20 -5
- package/src/session-runtime/resource-mcp-api.mjs +101 -77
- package/src/session-runtime/resource-plugins-api.mjs +29 -41
- package/src/session-runtime/resource-scope-decorate.mjs +6 -8
- package/src/session-runtime/resource-skills-api.mjs +23 -2
- package/src/session-runtime/schedule-session-run.mjs +1 -1
- package/src/session-runtime/services/channel-session-router.mjs +4 -1
- package/src/session-runtime/services/hook-bus/command-handler.mjs +4 -1
- package/src/session-runtime/services/hook-bus/handlers.mjs +7 -3
- package/src/session-runtime/services/provider-admin.mjs +64 -18
- package/src/session-runtime/services/usage-dashboard-model.mjs +3 -0
- package/src/session-runtime/setup-tool/executor.mjs +12 -1
- package/src/session-runtime/setup-tool/extended-actions.mjs +1 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +1 -0
- package/src/session-runtime/setup-tool/tool-defs.mjs +7 -1
- package/src/session-runtime/skills-api.mjs +7 -3
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/webhook-session-run.mjs +16 -4
- package/src/skill-script-entry.mjs +36 -0
- package/src/standalone/daemon-stored-session-views.mjs +11 -3
- package/src/standalone/daemon.mjs +41 -4
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +6 -1
- package/src/tui/App.jsx +9 -1
- package/src/tui/app/app-view/floating-panel.jsx +9 -1
- package/src/tui/app/app-view.jsx +6 -0
- package/src/tui/app/extension-pickers/plugins-pickers.mjs +35 -16
- package/src/tui/app/model-options.mjs +2 -0
- package/src/tui/app/skill-mention-palette.mjs +88 -0
- package/src/tui/app/slash-palette.mjs +23 -7
- package/src/tui/app/transcript-row-estimate.mjs +11 -3
- package/src/tui/app/use-prompt-draft-flow.mjs +7 -2
- package/src/tui/app/use-prompt-state.mjs +5 -0
- package/src/tui/components/Message.jsx +22 -1
- package/src/tui/components/PromptInput.jsx +21 -3
- package/src/tui/components/SlashCommandPalette.jsx +12 -3
- package/src/tui/components/TranscriptItem.jsx +9 -1
- package/src/tui/components/prompt-input/key-handler.mjs +21 -8
- package/src/tui/components/user-message-skills.mjs +38 -0
- package/src/tui/dist/index.mjs +507 -171
- package/src/tui/session/queue-helpers.mjs +7 -2
- package/src/tui/session/session-api/extensions.mjs +4 -2
- package/src/tui/session/session-api/settings.mjs +7 -0
- package/src/tui/session/session-flow/drain.mjs +14 -4
- package/src/tui/session/tui-steering-persist.mjs +25 -9
- package/src/tui/session/turn.mjs +17 -0
- package/src/runtime/effort-judge/previous-human-request.mjs +0 -162
- package/src/runtime/effort-judge/verified-context-input.mjs +0 -10
|
@@ -20,12 +20,19 @@ const MODEL_FILES = ['model.onnx', 'tokenizer.json'];
|
|
|
20
20
|
// The worker runs one judgment at a time, so requests are sent one by one:
|
|
21
21
|
// each answer's deadline starts when it is sent, and the wait for earlier
|
|
22
22
|
// requests is bounded separately so a burst of turns queues instead of
|
|
23
|
-
// timing out behind each other.
|
|
23
|
+
// timing out behind each other. The next request is sent only once the worker
|
|
24
|
+
// has answered the previous one: an answer that missed its deadline still
|
|
25
|
+
// occupies the worker, and sending into it would start the next deadline
|
|
26
|
+
// while the worker is busy, failing it too.
|
|
27
|
+
// 500 ms covers the 18-layer judge's tail while parallel agents share the CPU.
|
|
24
28
|
// MIXDOG_EFFORT_JUDGE_TIMEOUT_MS raises it on a host whose CPUs are shared
|
|
25
29
|
// with heavy work (parallel benchmark containers).
|
|
26
30
|
const WARM_TIMEOUT_MS =
|
|
27
|
-
Number(process.env.MIXDOG_EFFORT_JUDGE_TIMEOUT_MS) > 0 ? Number(process.env.MIXDOG_EFFORT_JUDGE_TIMEOUT_MS) :
|
|
31
|
+
Number(process.env.MIXDOG_EFFORT_JUDGE_TIMEOUT_MS) > 0 ? Number(process.env.MIXDOG_EFFORT_JUDGE_TIMEOUT_MS) : 500;
|
|
28
32
|
const QUEUE_WAIT_MS = 1000;
|
|
33
|
+
// A worker that has not answered for this long is stuck; it is replaced so
|
|
34
|
+
// the queue does not stay blocked behind it.
|
|
35
|
+
const STUCK_MS = 10_000;
|
|
29
36
|
let queueTail = Promise.resolve();
|
|
30
37
|
// A turn that arrives while the judge is still loading (its first use, or the
|
|
31
38
|
// first turn after a model update) waits at most this long; loading takes about 2 s on a desktop CPU.
|
|
@@ -107,7 +114,7 @@ function settle(id, fn) {
|
|
|
107
114
|
const entry = pending.get(id);
|
|
108
115
|
if (!entry) return;
|
|
109
116
|
pending.delete(id);
|
|
110
|
-
|
|
117
|
+
entry.done();
|
|
111
118
|
fn(entry);
|
|
112
119
|
}
|
|
113
120
|
|
|
@@ -151,11 +158,27 @@ function ensureWorker(dir) {
|
|
|
151
158
|
return created;
|
|
152
159
|
}
|
|
153
160
|
|
|
161
|
+
// `answer` settles with the probabilities or fails at the deadline; `done`
|
|
162
|
+
// resolves once the worker has answered (or exited), when it can take the next request.
|
|
154
163
|
function request(target, { request: text, prev, prevRequest, step }) {
|
|
155
164
|
const id = ++msgId;
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
let finish;
|
|
166
|
+
const done = new Promise((resolve) => {
|
|
167
|
+
finish = resolve;
|
|
168
|
+
});
|
|
169
|
+
const answer = new Promise((resolve, reject) => {
|
|
170
|
+
const timer = setTimeout(() => reject(new Error('timeout')), WARM_TIMEOUT_MS);
|
|
171
|
+
const stuck = setTimeout(() => void target.terminate().catch(() => {}), STUCK_MS);
|
|
172
|
+
stuck.unref();
|
|
173
|
+
pending.set(id, {
|
|
174
|
+
resolve,
|
|
175
|
+
reject,
|
|
176
|
+
done: () => {
|
|
177
|
+
clearTimeout(timer);
|
|
178
|
+
clearTimeout(stuck);
|
|
179
|
+
finish();
|
|
180
|
+
},
|
|
181
|
+
});
|
|
159
182
|
// The worker formats the text the way its model was trained.
|
|
160
183
|
target.postMessage({
|
|
161
184
|
action: 'judge',
|
|
@@ -169,6 +192,9 @@ function request(target, { request: text, prev, prevRequest, step }) {
|
|
|
169
192
|
...(step ? { step } : {}),
|
|
170
193
|
});
|
|
171
194
|
});
|
|
195
|
+
// A caller that gave up while queued never reads its answer.
|
|
196
|
+
answer.catch(() => {});
|
|
197
|
+
return { answer, done };
|
|
172
198
|
}
|
|
173
199
|
|
|
174
200
|
// One install at a time (the settings install and the boot refresh can overlap).
|
|
@@ -257,19 +283,31 @@ export async function judgeTurn(input) {
|
|
|
257
283
|
if (!ready && !(await waitReady(COLD_WAIT_MS))) {
|
|
258
284
|
return { skipped: lastLoadError ? `load-error: ${lastLoadError}` : 'warming' };
|
|
259
285
|
}
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
if (
|
|
263
|
-
if (
|
|
264
|
-
|
|
265
|
-
try {
|
|
266
|
-
return { probs: await request(target, input), ms: Date.now() - startedAt };
|
|
267
|
-
} catch (error) {
|
|
268
|
-
return { skipped: String(error?.message || error) };
|
|
269
|
-
}
|
|
286
|
+
let abandoned = false;
|
|
287
|
+
const turn = queueTail.then(() => {
|
|
288
|
+
if (abandoned) return null;
|
|
289
|
+
if (worker !== target || !ready) return { exited: true };
|
|
290
|
+
return { startedAt: Date.now(), ...request(target, input) };
|
|
270
291
|
});
|
|
271
|
-
queueTail =
|
|
272
|
-
|
|
292
|
+
queueTail = turn.then((sent) => sent?.done);
|
|
293
|
+
let timer;
|
|
294
|
+
const sent = await Promise.race([
|
|
295
|
+
turn,
|
|
296
|
+
new Promise((resolve) => {
|
|
297
|
+
timer = setTimeout(resolve, QUEUE_WAIT_MS, null);
|
|
298
|
+
}),
|
|
299
|
+
]);
|
|
300
|
+
clearTimeout(timer);
|
|
301
|
+
if (!sent) {
|
|
302
|
+
abandoned = true;
|
|
303
|
+
return { skipped: 'busy' };
|
|
304
|
+
}
|
|
305
|
+
if (sent.exited) return { skipped: 'effort judge exited' };
|
|
306
|
+
try {
|
|
307
|
+
return { probs: await sent.answer, ms: Date.now() - sent.startedAt };
|
|
308
|
+
} catch (error) {
|
|
309
|
+
return { skipped: String(error?.message || error) };
|
|
310
|
+
}
|
|
273
311
|
}
|
|
274
312
|
|
|
275
313
|
// MIXDOG_EFFORT_DECISIONS_LOG redirects the log, e.g. for benchmark runs whose
|
|
@@ -161,7 +161,7 @@ async function _migrateRecallIndexesIfStale(db) {
|
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
// One-time cleanup: attachment-only placeholder rows ('(attachment)' user
|
|
164
|
-
// content
|
|
164
|
+
// content from the former channel provider) predate the
|
|
165
165
|
// shouldExcludeIngestMessage() ingest-time filter (session-ingest.mjs).
|
|
166
166
|
// Delete any already-persisted rows so they stop polluting recall/cycle1.
|
|
167
167
|
// Idempotent (no-op once cleaned); best-effort so a failure never blocks boot.
|
|
@@ -201,8 +201,8 @@ function isExcludedUserIngestText(m, text) {
|
|
|
201
201
|
if (metaSource === 'task-notification') return true;
|
|
202
202
|
// `Reference files:` synthetic user rows (manager.mjs isReferenceFilesMessage).
|
|
203
203
|
if (/^Reference files:\s*/i.test(trimmedStart)) return true;
|
|
204
|
-
// Attachment-only placeholder rows (
|
|
205
|
-
//
|
|
204
|
+
// Attachment-only placeholder rows (`"(attachment)"` fallback written by the
|
|
205
|
+
// former channel provider when a message carried no text, only files).
|
|
206
206
|
if (text.trim() === '(attachment)') return true;
|
|
207
207
|
// Compaction summary user rows (compact.mjs isSummaryMessage / SUMMARY_PREFIX).
|
|
208
208
|
if (metaSource === 'compact-summary') return true;
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from 'node:fs/promises';
|
|
22
22
|
import { dirname, basename, join } from 'node:path';
|
|
23
23
|
import { randomBytes } from 'node:crypto';
|
|
24
|
-
import { DEFAULT_BACKOFFS_MS, withFileLock, withFileLockSync } from './file-lock.mjs';
|
|
24
|
+
import { DEFAULT_BACKOFFS_MS, recordLockTiming, withFileLock, withFileLockSync } from './file-lock.mjs';
|
|
25
25
|
import {
|
|
26
26
|
enforceOwnerOnlyAclWin32 as _enforceOwnerOnlyAclWin32,
|
|
27
27
|
enforceOwnerOnlyAclWin32Async as _enforceOwnerOnlyAclWin32Async,
|
|
@@ -321,9 +321,30 @@ export async function writeFileAtomicAsync(filePath, data, opts = {}) {
|
|
|
321
321
|
};
|
|
322
322
|
// Acquire the file lock before occupying an I/O slot. Otherwise a waiter can
|
|
323
323
|
// exhaust the gate while the current lock owner needs it to finish its write.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
324
|
+
if (opts.lock !== true) return _runAdaptiveFileIo(run);
|
|
325
|
+
const lockPath = `${filePath}.lock`;
|
|
326
|
+
return withFileLock(
|
|
327
|
+
lockPath,
|
|
328
|
+
async () => {
|
|
329
|
+
const queuedAt = Date.now();
|
|
330
|
+
let startedAt = 0;
|
|
331
|
+
try {
|
|
332
|
+
return await _runAdaptiveFileIo(() => {
|
|
333
|
+
startedAt = Date.now();
|
|
334
|
+
return run();
|
|
335
|
+
});
|
|
336
|
+
} finally {
|
|
337
|
+
const now = Date.now();
|
|
338
|
+
// Recorded from this (lock-owner) context so the held warning can
|
|
339
|
+
// split the hold into gate wait vs. the write itself.
|
|
340
|
+
recordLockTiming(lockPath, {
|
|
341
|
+
ioGateWaitMs: (startedAt || now) - queuedAt,
|
|
342
|
+
writeMs: startedAt ? now - startedAt : 0,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
opts
|
|
347
|
+
);
|
|
327
348
|
}
|
|
328
349
|
|
|
329
350
|
export function writeJsonAtomicAsync(filePath, value, opts = {}) {
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { resolve, sep } from 'node:path';
|
|
17
17
|
import { clean } from './clean.mjs';
|
|
18
18
|
import { isPlainObject } from './object.mjs';
|
|
19
|
+
import { parseMcpToolName } from './tool-primitives.mjs';
|
|
19
20
|
|
|
20
21
|
export const EXTENSION_SCOPE_KINDS = Object.freeze(['skills', 'mcp', 'plugins']);
|
|
21
22
|
|
|
@@ -123,9 +124,10 @@ export function skillAllowedForCwd(scopes, skill, cwd) {
|
|
|
123
124
|
return true;
|
|
124
125
|
}
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
/** Server of an MCP tool definition or name (metadata first; see parseMcpToolName). */
|
|
128
|
+
export function mcpServerNameOfTool(toolOrName) {
|
|
129
|
+
const value = toolOrName && typeof toolOrName === 'object' ? toolOrName : clean(toolOrName);
|
|
130
|
+
return clean(parseMcpToolName(value)?.server);
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
/** New config with one entry replaced; `projects` null/empty removes it (global). */
|
|
@@ -177,16 +177,38 @@ function warnLockRateLimited(warnedAt, lockPath, line) {
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
function
|
|
180
|
+
function formatLockTiming(timing) {
|
|
181
|
+
return Object.entries(timing || {})
|
|
182
|
+
.filter(([, ms]) => Number.isFinite(ms))
|
|
183
|
+
.map(([name, ms]) => ` ${name}=${ms}ms`)
|
|
184
|
+
.join('');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Lets a lock owner's callback attribute its time (io gate wait, write) to the
|
|
188
|
+
// held-warning of the lock it runs under.
|
|
189
|
+
export function recordLockTiming(lockPath, fields) {
|
|
190
|
+
const lease = heldLockPaths.getStore()?.get(lockPath);
|
|
191
|
+
if (lease?.active) Object.assign(lease.timing, fields);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function reportLockWait(lockPath, waitedMs, mode, timing) {
|
|
181
195
|
if (waitedMs < LOCK_WAIT_WARN_MS) return;
|
|
182
|
-
warnLockRateLimited(
|
|
196
|
+
warnLockRateLimited(
|
|
197
|
+
lockWaitWarnedAt,
|
|
198
|
+
lockPath,
|
|
199
|
+
`[atomic-file] ${mode} lock wait ${waitedMs}ms:${formatLockTiming(timing)} ${lockPath}\n`
|
|
200
|
+
);
|
|
183
201
|
}
|
|
184
202
|
|
|
185
203
|
// A lock held this long starves every waiter (they time out at 2s), so the
|
|
186
204
|
// holder's duration is reported once released.
|
|
187
|
-
function reportLockHeld(lockPath, heldMs, mode) {
|
|
205
|
+
function reportLockHeld(lockPath, heldMs, mode, timing) {
|
|
188
206
|
if (heldMs < LOCK_HELD_WARN_MS) return;
|
|
189
|
-
warnLockRateLimited(
|
|
207
|
+
warnLockRateLimited(
|
|
208
|
+
lockHeldWarnedAt,
|
|
209
|
+
lockPath,
|
|
210
|
+
`[atomic-file] ${mode} lock held ${heldMs}ms:${formatLockTiming(timing)} ${lockPath}\n`
|
|
211
|
+
);
|
|
190
212
|
}
|
|
191
213
|
|
|
192
214
|
function* tryAcquireReclaimGuard(lockPath) {
|
|
@@ -387,7 +409,7 @@ export function sweepStagedLockFiles(lockPath, now = Date.now()) {
|
|
|
387
409
|
return removed;
|
|
388
410
|
}
|
|
389
411
|
|
|
390
|
-
function* acquireLock(lockPath, opts, mode, markHeld) {
|
|
412
|
+
function* acquireLock(lockPath, opts, mode, markHeld, timing = null) {
|
|
391
413
|
const timeoutMs = lockTimeoutMs(opts);
|
|
392
414
|
const staleMs = lockStaleMs(opts);
|
|
393
415
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -415,7 +437,9 @@ function* acquireLock(lockPath, opts, mode, markHeld) {
|
|
|
415
437
|
attempt += 1;
|
|
416
438
|
continue;
|
|
417
439
|
}
|
|
418
|
-
|
|
440
|
+
const osMs = Date.now() - waitStartedAt;
|
|
441
|
+
if (timing) timing.osAcquireMs = osMs;
|
|
442
|
+
reportLockWait(lockPath, osMs, mode, timing);
|
|
419
443
|
return;
|
|
420
444
|
}
|
|
421
445
|
throw timeoutError(lockPath, timeoutMs, lastError, yield* describeLockHolder(lockPath));
|
|
@@ -444,13 +468,15 @@ export function withFileLockSync(lockPath, fn, opts = {}) {
|
|
|
444
468
|
export async function withFileLock(lockPath, fn, opts = {}) {
|
|
445
469
|
if (heldLockPaths.getStore()?.get(lockPath)?.active) return fn();
|
|
446
470
|
if (lockTimeoutMs(opts) <= 0) return withOsFileLock(lockPath, fn, opts);
|
|
447
|
-
|
|
471
|
+
const enqueuedAt = Date.now();
|
|
472
|
+
return lockQueue(lockPath, () => withOsFileLock(lockPath, fn, opts, Date.now() - enqueuedAt));
|
|
448
473
|
}
|
|
449
474
|
|
|
450
|
-
async function withOsFileLock(lockPath, fn, opts = {}) {
|
|
451
|
-
|
|
475
|
+
async function withOsFileLock(lockPath, fn, opts = {}, queueWaitMs = 0) {
|
|
476
|
+
const timing = { queueWaitMs };
|
|
477
|
+
await runAsync(acquireLock(lockPath, opts, 'async', true, timing));
|
|
452
478
|
const heldSince = Date.now();
|
|
453
|
-
const lease = { active: false };
|
|
479
|
+
const lease = { active: false, timing };
|
|
454
480
|
try {
|
|
455
481
|
if (opts.secret === true) await enforceOwnerOnlyAclWin32Async(lockPath, { fresh: true });
|
|
456
482
|
// Async descendants inherit a reference to this lease, not permanent
|
|
@@ -464,7 +490,7 @@ async function withOsFileLock(lockPath, fn, opts = {}) {
|
|
|
464
490
|
try {
|
|
465
491
|
await runAsync(releaseLock(lockPath));
|
|
466
492
|
} finally {
|
|
467
|
-
reportLockHeld(lockPath, Date.now() - heldSince, 'async');
|
|
493
|
+
reportLockHeld(lockPath, Date.now() - heldSince, 'async', timing);
|
|
468
494
|
// Unmark only once the lock file is gone: a sync waiter arriving while
|
|
469
495
|
// the release is still pending must fail fast, not block its completion.
|
|
470
496
|
unmarkOsHeld(lockPath);
|
|
@@ -96,6 +96,20 @@ function _catalogLevels(model) {
|
|
|
96
96
|
return null;
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Effort levels the wire builder sends unchanged for `model` (reserved `max`
|
|
101
|
+
* excluded), from the same capability gates normalizeAnthropicEffortInput and
|
|
102
|
+
* applyAnthropicEffortToBody use: catalog record first, regex ladder for an
|
|
103
|
+
* absent record. A model without effort support, or whose catalog record
|
|
104
|
+
* declares none, accepts none.
|
|
105
|
+
*/
|
|
106
|
+
export function anthropicEffortLevelsFor(model) {
|
|
107
|
+
if (!modelSupportsEffort(model)) return [];
|
|
108
|
+
const levels = ['low', 'medium', 'high'];
|
|
109
|
+
if (modelSupportsXhighEffort(model)) levels.push('xhigh');
|
|
110
|
+
return levels;
|
|
111
|
+
}
|
|
112
|
+
|
|
99
113
|
export const EFFORT_BETA_HEADER = 'effort-2025-11-24';
|
|
100
114
|
|
|
101
115
|
export const LEGACY_EFFORT_BUDGET = Object.freeze({
|
|
@@ -904,7 +904,6 @@ export function pricingCatalogRevisionSync() {
|
|
|
904
904
|
}
|
|
905
905
|
|
|
906
906
|
let lastAuditedRevision = null;
|
|
907
|
-
let lastAudit = null;
|
|
908
907
|
function auditCachedModelPricing() {
|
|
909
908
|
const revision = pricingCatalogRevisionSync();
|
|
910
909
|
const rows = Object.entries(cachedProviderModelListsSync()).flatMap(([provider, models]) =>
|
|
@@ -919,17 +918,6 @@ function auditCachedModelPricing() {
|
|
|
919
918
|
);
|
|
920
919
|
}
|
|
921
920
|
lastAuditedRevision = revision;
|
|
922
|
-
lastAudit = { revision, rows, unpriced };
|
|
923
|
-
return lastAudit;
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
/** Catalog routes without a complete list price, from the latest audit (run on demand when none is cached). */
|
|
927
|
-
export function unpricedModelsSync() {
|
|
928
|
-
return (lastAudit ?? auditCachedModelPricing()).unpriced.map(({ provider, model, missingRates }) => ({
|
|
929
|
-
provider,
|
|
930
|
-
model,
|
|
931
|
-
missingRates: [...missingRates],
|
|
932
|
-
}));
|
|
933
921
|
}
|
|
934
922
|
|
|
935
923
|
/**
|
|
@@ -85,6 +85,12 @@ const PROVIDER_CATALOGS = Object.freeze({
|
|
|
85
85
|
// Effort tiers live in the wire id; a row without levels has no effort
|
|
86
86
|
// surface (resolveAntigravityWireModel drops the effort).
|
|
87
87
|
'antigravity-oauth': { relay: true, rosterFiles: ['antigravity-oauth-models.json'], rosterLadderFinal: true },
|
|
88
|
+
// api.commandcode.ai/provider/v1 relays other vendors' models.
|
|
89
|
+
'command-code-oauth': {
|
|
90
|
+
relay: true,
|
|
91
|
+
rosterFiles: ['command-code-oauth-models.json'],
|
|
92
|
+
rosterLimitsAuthoritative: true,
|
|
93
|
+
},
|
|
88
94
|
'cursor-oauth': { relay: true },
|
|
89
95
|
'cursor-api': { relay: true },
|
|
90
96
|
'opencode-go': { relay: true },
|
|
@@ -7,10 +7,19 @@
|
|
|
7
7
|
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
8
8
|
import { readJsonSafe } from './json-file.mjs';
|
|
9
9
|
|
|
10
|
-
/** `.codex-plugin/plugin.json
|
|
11
|
-
*
|
|
10
|
+
/** Manifest files in precedence order: `.codex-plugin/plugin.json`, a
|
|
11
|
+
* root-level `plugin.json`, then Claude Code's `.claude-plugin/plugin.json`. */
|
|
12
|
+
export const PLUGIN_MANIFEST_FILES = ['.codex-plugin/plugin.json', 'plugin.json', '.claude-plugin/plugin.json'];
|
|
13
|
+
|
|
14
|
+
/** First readable manifest in `PLUGIN_MANIFEST_FILES` order; a plugin without
|
|
15
|
+
* any is an empty manifest, never an error. A Claude Code manifest's `skills`
|
|
16
|
+
* is a plugin-relative path (e.g. `./.claude/skills/`). */
|
|
12
17
|
export function pluginManifest(root) {
|
|
13
|
-
|
|
18
|
+
for (const rel of PLUGIN_MANIFEST_FILES) {
|
|
19
|
+
const manifest = readJsonSafe(join(root, ...rel.split('/')));
|
|
20
|
+
if (manifest) return manifest;
|
|
21
|
+
}
|
|
22
|
+
return {};
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
/** Resolve a manifest-relative path, refusing absolute paths and anything
|
|
@@ -21,22 +30,22 @@ export function resolveContainedPluginPath(root, rel) {
|
|
|
21
30
|
const base = resolve(root);
|
|
22
31
|
const abs = resolve(base, trimmed);
|
|
23
32
|
const relToBase = relative(base, abs);
|
|
24
|
-
if (relToBase.
|
|
33
|
+
if (relToBase.split(/[\\/]/)[0] === '..' || isAbsolute(relToBase)) return null;
|
|
25
34
|
return abs;
|
|
26
35
|
}
|
|
27
36
|
|
|
28
|
-
/** Skill roots a plugin contributes: the conventional `<root>/skills/` plus
|
|
29
|
-
*
|
|
37
|
+
/** Skill roots a plugin contributes: the conventional `<root>/skills/` plus the
|
|
38
|
+
* manifest `skills` field, a path or a list of paths. Deduplicated, absolute,
|
|
39
|
+
* root-contained. */
|
|
30
40
|
export function pluginSkillsRoots(root) {
|
|
31
41
|
const manifest = pluginManifest(root);
|
|
32
42
|
const roots = new Set();
|
|
33
43
|
const add = (rel) => {
|
|
44
|
+
if (typeof rel !== 'string' || !rel.trim()) return;
|
|
34
45
|
const abs = resolveContainedPluginPath(root, rel);
|
|
35
46
|
if (abs) roots.add(abs);
|
|
36
47
|
};
|
|
37
48
|
add('./skills/');
|
|
38
|
-
|
|
39
|
-
add(manifest.skills.trim());
|
|
40
|
-
}
|
|
49
|
+
for (const rel of Array.isArray(manifest.skills) ? manifest.skills : [manifest.skills]) add(rel);
|
|
41
50
|
return [...roots];
|
|
42
51
|
}
|
|
@@ -66,11 +66,18 @@
|
|
|
66
66
|
},
|
|
67
67
|
"muse-code-oauth": {
|
|
68
68
|
"credentialFile": "muse-code-oauth.json",
|
|
69
|
-
"credentialPathEnv": "MUSE_CODE_OAUTH_CREDENTIALS_PATH"
|
|
69
|
+
"credentialPathEnv": "MUSE_CODE_OAUTH_CREDENTIALS_PATH",
|
|
70
|
+
"modelCatalogFile": "muse-code-oauth-models.json"
|
|
71
|
+
},
|
|
72
|
+
"command-code-oauth": {
|
|
73
|
+
"credentialFile": "command-code-oauth.json",
|
|
74
|
+
"credentialPathEnv": "COMMAND_CODE_OAUTH_CREDENTIALS_PATH",
|
|
75
|
+
"modelCatalogFile": "command-code-oauth-models.json"
|
|
70
76
|
},
|
|
71
77
|
"mimo-oauth": {
|
|
72
78
|
"credentialFile": "mimo-oauth.json",
|
|
73
|
-
"credentialPathEnv": "MIMO_OAUTH_CREDENTIALS_PATH"
|
|
79
|
+
"credentialPathEnv": "MIMO_OAUTH_CREDENTIALS_PATH",
|
|
80
|
+
"modelCatalogFile": "mimo-oauth-models.json"
|
|
74
81
|
},
|
|
75
82
|
"cursor-oauth": {
|
|
76
83
|
"credentialFile": "cursor-oauth.json",
|
|
@@ -8,6 +8,12 @@ import { ACCOUNT_PROVIDERS, providerAccountPath, readProviderAccountPool } from
|
|
|
8
8
|
import { AGENT_PROVIDER_ENV_ALIASES, getAgentApiKey } from './provider-api-key.mjs';
|
|
9
9
|
import { platformEntryKey } from './native-asset.mjs';
|
|
10
10
|
import { clean } from './clean.mjs';
|
|
11
|
+
import { MODEL_PROXY_ENV, modelProxyClientMode } from './model-proxy-mode.mjs';
|
|
12
|
+
import { installModelProxyChannel } from '../agent/orchestrator/providers/model-proxy-channel.mjs';
|
|
13
|
+
import {
|
|
14
|
+
ModelProxyBridgeChannel,
|
|
15
|
+
readModelProxyBridgeDiscovery,
|
|
16
|
+
} from '../agent/orchestrator/providers/model-proxy-bridge.mjs';
|
|
11
17
|
|
|
12
18
|
const contractPath = fileURLToPath(new URL('./pristine-execution-contract.json', import.meta.url));
|
|
13
19
|
|
|
@@ -205,6 +211,12 @@ export function createPristineExecutionBoundary({
|
|
|
205
211
|
|
|
206
212
|
const contract = PRISTINE_EXECUTION_CONTRACT;
|
|
207
213
|
const hostEnv = { ...env };
|
|
214
|
+
// Remote (SSH) mode: this server holds no model credentials, so the child
|
|
215
|
+
// runtime's model calls go through the daemon's model proxy bridge. Its
|
|
216
|
+
// endpoint is read from the HOST runtime root, before it is retargeted
|
|
217
|
+
// below; a missing or stale one fails closed (never a local credential).
|
|
218
|
+
const proxyMode = modelProxyClientMode(hostEnv);
|
|
219
|
+
const bridgeEndpoint = proxyMode ? readModelProxyBridgeDiscovery(hostEnv.MIXDOG_RUNTIME_ROOT) : null;
|
|
208
220
|
const originalEnv = new Map();
|
|
209
221
|
const touchedKeys = new Set();
|
|
210
222
|
const rememberEnv = (name) => {
|
|
@@ -227,6 +239,8 @@ export function createPristineExecutionBoundary({
|
|
|
227
239
|
mkdirSync(runtimeRoot, { recursive: true, mode: 0o700 });
|
|
228
240
|
let cleaned = false;
|
|
229
241
|
let restoreAuthBindings = () => {};
|
|
242
|
+
let bridgeChannel = null;
|
|
243
|
+
let restoreModelProxyChannel = () => {};
|
|
230
244
|
// rmSync backs off linearly (retryDelay × attempt), so 50 retries at 100ms
|
|
231
245
|
// is a ≈128s budget per attempt. Callers whose result is already complete
|
|
232
246
|
// (headless exec) pass a smaller budget and leave stragglers to the sweep.
|
|
@@ -238,6 +252,8 @@ export function createPristineExecutionBoundary({
|
|
|
238
252
|
else env[name] = value;
|
|
239
253
|
}
|
|
240
254
|
restoreAuthBindings();
|
|
255
|
+
restoreModelProxyChannel();
|
|
256
|
+
bridgeChannel?.dispose('the headless run finished');
|
|
241
257
|
const removal = { recursive: true, force: true, maxRetries: rootRemovalRetries, retryDelay: 100 };
|
|
242
258
|
// Windows releases file handles (pg.log, sockets) a beat after the owning
|
|
243
259
|
// process exits; rmSync retries absorb that EBUSY/EPERM window.
|
|
@@ -280,7 +296,7 @@ export function createPristineExecutionBoundary({
|
|
|
280
296
|
const oauth = contract.oauthProviders[selectedProvider];
|
|
281
297
|
const apiKeyEnv = contract.apiKeyProviders[selectedProvider];
|
|
282
298
|
let selectedApiKey = null;
|
|
283
|
-
if (apiKeyEnv) {
|
|
299
|
+
if (apiKeyEnv && !proxyMode) {
|
|
284
300
|
// Resolve before scrubbing so the selected provider may use its standard,
|
|
285
301
|
// MIXDOG_AGENT_*, alias, or one provider-scoped keychain account. No
|
|
286
302
|
// general config loader or all-provider credential scan is involved.
|
|
@@ -311,7 +327,15 @@ export function createPristineExecutionBoundary({
|
|
|
311
327
|
|
|
312
328
|
let authMode = 'provider-managed';
|
|
313
329
|
let catalogCount = 0;
|
|
314
|
-
if (
|
|
330
|
+
if (proxyMode) {
|
|
331
|
+
// No API key, OAuth account or credential binding: the PC behind the
|
|
332
|
+
// daemon runs every call. The runtime root stays the pristine one, for
|
|
333
|
+
// the services this run owns; only model calls reach the daemon.
|
|
334
|
+
setEnv(MODEL_PROXY_ENV, 'client');
|
|
335
|
+
bridgeChannel = new ModelProxyBridgeChannel(bridgeEndpoint);
|
|
336
|
+
restoreModelProxyChannel = installModelProxyChannel(bridgeChannel);
|
|
337
|
+
authMode = 'model-proxy-bridge';
|
|
338
|
+
} else if (oauth) {
|
|
315
339
|
// Sign-in writes the selected account of the host's provider-accounts
|
|
316
340
|
// pool; the single legacy file remains for hosts that predate the pool.
|
|
317
341
|
// An explicit credential path still overrides both. Read the pool from
|
|
@@ -340,8 +364,8 @@ export function createPristineExecutionBoundary({
|
|
|
340
364
|
[selectedProvider]: sourceCredential,
|
|
341
365
|
});
|
|
342
366
|
authMode = 'in-process-host-oauth-binding';
|
|
343
|
-
const sourceCatalog = join(sourceDataDir, oauth.modelCatalogFile);
|
|
344
|
-
if (
|
|
367
|
+
const sourceCatalog = oauth.modelCatalogFile && join(sourceDataDir, oauth.modelCatalogFile);
|
|
368
|
+
if (sourceCatalog && existsSync(sourceCatalog)) {
|
|
345
369
|
copyFileSync(sourceCatalog, join(dataDir, oauth.modelCatalogFile));
|
|
346
370
|
catalogCount = 1;
|
|
347
371
|
}
|