mixdog 0.9.175 → 1.0.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/package.json +1 -1
- package/scripts/context-renewal-live-check.mjs +2 -1
- package/scripts/plan-runtime-release-tag.mjs +57 -0
- package/src/defaults/skills/computer-use/SKILL.md +13 -12
- package/src/defaults/skills/setup/SKILL.md +1 -4
- package/src/defaults/skills/setup/references/actions.md +0 -2
- package/src/defaults/skills/setup/references/surfaces.md +2 -3
- package/src/defaults/skills/xlsx/SKILL.md +1 -0
- package/src/rules/shared/70-delivery.md +3 -0
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +12 -12
- package/src/runtime/agent/orchestrator/config.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +20 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/registry-errors.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +3 -2
- package/src/runtime/agent/orchestrator/runtime-core/goal-tasks.mjs +25 -6
- package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +3 -2
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/result-classification.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +7 -3
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +27 -4
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/benign-exit.mjs +7 -3
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +39 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +13 -5
- package/src/runtime/computer-bridge/error-recovery.mjs +1 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +94 -23
- package/src/runtime/office/capabilities-catalog.mjs +2 -0
- package/src/runtime/office/capabilities-signatures.mjs +12 -6
- package/src/runtime/office/capabilities.mjs +30 -10
- package/src/runtime/office/core/office-actions-open.mjs +1 -6
- package/src/runtime/office/core/office-sessions.mjs +9 -0
- package/src/runtime/office/portable/portable-xlsx-operations.mjs +2 -49
- package/src/runtime/office/portable/xlsx-contract.mjs +37 -3
- package/src/runtime/office/tool-defs.mjs +2 -2
- package/src/runtime/shared/config-patch.mjs +2 -0
- package/src/runtime/shared/llm/cost.mjs +16 -4
- package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
- package/src/runtime/shared/llm/model-catalog.mjs +48 -0
- package/src/runtime/shared/llm/model-pricing-rates.mjs +11 -4
- package/src/runtime/shared/llm/usage-accounting.mjs +12 -11
- package/src/runtime/shared/llm/usage-context.mjs +7 -0
- package/src/runtime/shared/llm/usage-ledger-repair.mjs +3 -1
- package/src/runtime/shared/llm/usage-ledger.mjs +1 -1
- package/src/runtime/shared/tool-card-model/card-status.mjs +7 -5
- package/src/runtime/shared/tool-card-model/git-result.mjs +10 -0
- package/src/runtime/shared/tool-card-model/terminal-status.mjs +6 -2
- package/src/runtime/shared/tool-card-model.golden-cases.mjs +2 -1
- package/src/runtime/shared/tool-result-summary.mjs +1 -0
- package/src/runtime/shared/tool-status.mjs +35 -2
- package/src/runtime/shared/user-data-guard.mjs +1 -1
- package/src/runtime/tidy/report.mjs +2 -2
- package/src/runtime/tidy/tool.mjs +6 -1
- package/src/session-runtime/config-lifecycle.mjs +11 -1
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +1 -1
- package/src/session-runtime/native-web-search/tool-shape.mjs +16 -4
- package/src/session-runtime/provider-models/catalog-load.mjs +18 -3
- package/src/session-runtime/setup-tool/executor.mjs +0 -1
- package/src/session-runtime/setup-tool/settings-contract.mjs +2 -13
- package/src/tui/dist/index.mjs +89 -38
- package/src/tui/session/session-api/settings.mjs +16 -10
- package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -2
- package/src/workflows/default/WORKFLOW.md +0 -2
- package/src/workflows/headless/WORKFLOW.md +0 -2
package/package.json
CHANGED
|
@@ -125,7 +125,8 @@ try {
|
|
|
125
125
|
for (const spec of specs) {
|
|
126
126
|
const dataDir = path.join(sandbox, spec.name);
|
|
127
127
|
const db = path.join(dataDir, 'pgdata');
|
|
128
|
-
const
|
|
128
|
+
const runtimeKey = fs.readFileSync(path.join(dataDir, 'runtime', 'active-version'), 'utf8').trim();
|
|
129
|
+
const ctl = path.join(dataDir, 'runtime', `runtime-${runtimeKey}`, 'bin', 'pg_ctl.exe');
|
|
129
130
|
if (process.platform === 'win32' && fs.existsSync(ctl) && fs.existsSync(path.join(db, 'postmaster.pid'))) {
|
|
130
131
|
const stopped = spawnSync(ctl, ['-D', db, '-m', 'fast', '-w', 'stop'], { stdio: 'inherit' });
|
|
131
132
|
if (stopped.status !== 0) cleanupFailed = true;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Decides the release tag a memory runtime build publishes under.
|
|
2
|
+
//
|
|
3
|
+
// Published runtime assets are immutable: every app that shipped a manifest
|
|
4
|
+
// naming a tag verifies that tag's assets by size and sha256, so replacing an
|
|
5
|
+
// asset under a shipped tag breaks the next runtime install of all of those
|
|
6
|
+
// apps. A build that would replace an asset of a shipped tag (any tag at or
|
|
7
|
+
// below the bundled manifest's release_tag) moves to the next patch tag; the
|
|
8
|
+
// manifest sync then points new app builds at it.
|
|
9
|
+
//
|
|
10
|
+
// CLI: node scripts/plan-runtime-release-tag.mjs <requested-tag> <shipped-tag> <needed-assets-file> <published-assets-file>
|
|
11
|
+
// prints the tag to build. The asset files hold one asset name per line.
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
13
|
+
import { pathToFileURL } from 'node:url';
|
|
14
|
+
|
|
15
|
+
const TAG_RE = /^runtime-v(\d+)\.(\d+)\.(\d+)$/;
|
|
16
|
+
|
|
17
|
+
function parseTag(tag) {
|
|
18
|
+
const match = TAG_RE.exec(String(tag ?? '').trim());
|
|
19
|
+
if (!match) throw new Error(`invalid runtime release tag: ${tag}`);
|
|
20
|
+
return match.slice(1).map(Number);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function compareTags(left, right) {
|
|
24
|
+
const a = parseTag(left);
|
|
25
|
+
const b = parseTag(right);
|
|
26
|
+
for (let index = 0; index < 3; index += 1) {
|
|
27
|
+
if (a[index] !== b[index]) return a[index] - b[index];
|
|
28
|
+
}
|
|
29
|
+
return 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function planRuntimeReleaseTag({ requestedTag, shippedTag, neededAssets, publishedAssets }) {
|
|
33
|
+
const published = new Set(publishedAssets);
|
|
34
|
+
const replacesPublished = neededAssets.some((name) => published.has(name));
|
|
35
|
+
if (!replacesPublished || compareTags(requestedTag, shippedTag) > 0) return requestedTag;
|
|
36
|
+
const [major, minor, patch] = parseTag(shippedTag);
|
|
37
|
+
return `runtime-v${major}.${minor}.${patch + 1}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function readNames(path) {
|
|
41
|
+
return readFileSync(path, 'utf8')
|
|
42
|
+
.split(/\r?\n/)
|
|
43
|
+
.map((line) => line.trim())
|
|
44
|
+
.filter(Boolean);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
|
|
48
|
+
const [requestedTag, shippedTag, neededPath, publishedPath] = process.argv.slice(2);
|
|
49
|
+
process.stdout.write(
|
|
50
|
+
`${planRuntimeReleaseTag({
|
|
51
|
+
requestedTag,
|
|
52
|
+
shippedTag,
|
|
53
|
+
neededAssets: readNames(neededPath),
|
|
54
|
+
publishedAssets: readNames(publishedPath),
|
|
55
|
+
})}\n`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -115,18 +115,19 @@ GNOME with the Window Calls extension); prefer accessibility refs there.
|
|
|
115
115
|
of the shared desktop. If that surface is unavailable, use semantic refs or
|
|
116
116
|
report `pixel_unavailable`; do not substitute a screen grab.
|
|
117
117
|
- If the user intervenes, preserve their cursor and focus. Worker termination
|
|
118
|
-
and input cleanup must finish before resuming
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
or change the idle policy, on
|
|
118
|
+
and input cleanup must finish before resuming. The user's Resume on the
|
|
119
|
+
overlay continues a pause; an ordinary physical-input pause may also resume
|
|
120
|
+
after the host's configured quiet interval (renewed input resets it), while
|
|
121
|
+
explicit pauses, stops and uncertain cleanup never auto-resume. Stop (or
|
|
122
|
+
`Ctrl+Alt+Esc`) cancels the task, which then reports the stop as a failure.
|
|
123
|
+
A cleanup that could not be confirmed is retried before the next input
|
|
124
|
+
command; if it still fails, that command returns the reason — relay it to
|
|
125
|
+
the user. If target-local release remains unconfirmed, the user must
|
|
126
|
+
inspect/recover that window; restarting the host alone does not prove
|
|
127
|
+
release. Obtain a new observation after an interruption.
|
|
128
|
+
Use `wait_for_user` to keep the task waiting without sending input. Never
|
|
129
|
+
operate Resume, Stop or the recovery controls, or change the idle policy, on
|
|
130
|
+
the user's behalf.
|
|
130
131
|
While paused, only `list`, `diagnose` and `wait_for_user` are available.
|
|
131
132
|
Chat text alone does not clear the host. After `resumed`, capture fresh state;
|
|
132
133
|
after `timeout` or `cancelled`, no input is authorized. Never replay the
|
|
@@ -25,7 +25,7 @@ supported `setup` action, and read its result as the change receipt.
|
|
|
25
25
|
arguments and the transcript. Open the Providers surface and let the user
|
|
26
26
|
enter them.
|
|
27
27
|
- A mutation follows the active workflow's approval rules. Status, catalog,
|
|
28
|
-
definition
|
|
28
|
+
definition and MCP-config reads do not change persisted settings.
|
|
29
29
|
- Most changes affect new sessions. Report `appliedToCurrentSession` and
|
|
30
30
|
`appliesTo` exactly when the result supplies them.
|
|
31
31
|
- This skill does not own source edits, builds, installation deployment,
|
|
@@ -78,9 +78,6 @@ that a call returned without transport error.
|
|
|
78
78
|
- Read definitions before editing them; use skill-creator when authoring a
|
|
79
79
|
skill. Setup persists the supported definition fields. Workflow and agent
|
|
80
80
|
ids are stable identities, distinct from their display names.
|
|
81
|
-
- Read Instructions before replacing them and pass the returned text as
|
|
82
|
-
`expectedContent`. `projectPath:null` means Common Instructions; otherwise
|
|
83
|
-
use an exact registered Project root. Keep the returned backup.
|
|
84
81
|
- MCP servers and plugins are machine-global integrations whose visibility may
|
|
85
82
|
be scoped to selected Projects.
|
|
86
83
|
- User skills are machine-global. Enabled plugin skills and built-in skills are
|
|
@@ -68,8 +68,6 @@ Read this when choosing a `setup` mutation.
|
|
|
68
68
|
| Desktop theme, display language, side panels, or zoom | `set_appearance` |
|
|
69
69
|
| Register a Project and optionally set its display alias | `save_project` |
|
|
70
70
|
| Unregister a Project without deleting its files | `remove_project` |
|
|
71
|
-
| Read Project/Common Instructions before editing | `get_instructions` |
|
|
72
|
-
| Replace unchanged Project/Common Instructions and retain a backup | `set_instructions` |
|
|
73
71
|
| Revoke one linked device; re-pairing restores access | `revoke_linked_device` |
|
|
74
72
|
| Managed local model context allocation; null restores automatic | `set_local_context` |
|
|
75
73
|
|
|
@@ -38,8 +38,7 @@ An attached Desktop or TUI may navigate and return `opened:true`.
|
|
|
38
38
|
- Extensions → Plugin → Git & GitHub: Git/GitHub CLI installation and connection.
|
|
39
39
|
- Settings → Connection: web-app pairing and linked devices.
|
|
40
40
|
- Settings → System: update, keep-awake, and Doctor.
|
|
41
|
-
- Projects: Project registration, name,
|
|
42
|
-
Core Memories.
|
|
41
|
+
- Projects: Project registration, name, Project Memories, and Common Memory.
|
|
43
42
|
- Workflows: workflow packs, Main and Web Search defaults, and agent editing.
|
|
44
43
|
- Extensions → Plugin: Built-in cards and plugins.
|
|
45
44
|
- Extensions → Skill: skills and MCP servers.
|
|
@@ -51,7 +50,7 @@ the user to Desktop.
|
|
|
51
50
|
|
|
52
51
|
Setup can read and change Desktop appearance, keep-awake, usage pin,
|
|
53
52
|
Computer observe-only, Browser/Computer/voice installation and toggles,
|
|
54
|
-
Projects,
|
|
53
|
+
Projects, and linked-device revocation through the attached
|
|
55
54
|
Desktop. A receipt identifies `scope:desktop-host`; appearance is not applied
|
|
56
55
|
to the paired browser. No Desktop claimant means no confirmed change.
|
|
57
56
|
|
|
@@ -37,6 +37,7 @@ Excel proves the numbers and makes their meaning readable. Keep sourced inputs a
|
|
|
37
37
|
1. Settle the table first: headers, one row per record, consistent units, numeric cells as numbers (not text), dates as ISO strings (both backends store `2026-09-30` as the Excel date under `yyyy-mm-dd`, so date arithmetic is `=D5-DATE(2026,9,22)`, not `DATEVALUE`), percentages as fractions (`0.15`, formatted `0.0%`). A figure written as text is stored as the number Excel types from it, on both backends — `"1,234"` under `#,##0`, `"15%"` as 0.15 under `0%` — and any other text stays exactly as written (`"1-2"`, `"007"`, `"1e3"`, `"TRUE"` never become a date, a 7, a 1000, or a truth value). Keep the same `design.content` model as the deck or document in the same package.
|
|
38
38
|
2. For a report, design the reading order, sheet roles, column proportions, chart placement and intended screen/print use. Author with native cell, style, merge and chart operations by default. `compose_sheet` is an opt-in preset only when its table-plus-panel structure already fits; do not select it merely to save calls. Batch all known operations. Use `describe format:'xlsx' operation:<op>` when fields are unknown; cell styles belong in `properties`.
|
|
39
39
|
3. Add sheets with `add_sheet`, then native ranges and formulas targeting `sheet`. Use `set_formula` for derived cells and `define_name` for reused inputs. Check representative formulas against the intended ranges before extending a model; clean recalculation proves evaluation, not correct modelling. A design specimen is optional, not required for every workbook.
|
|
40
|
+
If `create` fails, stop dependent `open`, `render`, and `finalize` calls: a failed creation is not an artifact. Correct the reported inputs and resume only from a successful `create` result.
|
|
40
41
|
4. Raw tabular files (`.csv`, `.tsv`) take `set_range` with a 2D `values` array and `append_row`; they carry no styles, formulas, or multiple sheets.
|
|
41
42
|
5. `finalize` recalculates and returns current rendered pages for review; add `auditProfile:'financial-model'` for a decision model. Read the actual images as in `report-design.md`, with concrete keep/fix observations about hierarchy, charts and print layout. Correct material issues and rerender, rather than filling a pass checklist. Complete with `design:{ reviewed:true, reviewToken:<current token>, critique:[{ page:1, verdict:'pass', note:<page-specific observations> }, ...] }`. This records agent review, not user approval. CSV/TSV needs no visual approval. Resolve validation failures against their original baseline, never by reopening as a new baseline.
|
|
42
43
|
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
absolute path, optionally `:line[:column]` (`deliverables/report.docx`,
|
|
8
8
|
`src/app.ts:42`); never a bare name, a path relative to another folder, or a
|
|
9
9
|
`file://` URI.
|
|
10
|
+
- Deliver requested visual artifacts (SVG, diagrams, HTML pages) as saved files
|
|
11
|
+
and give their path; never paste their source as the answer unless the user
|
|
12
|
+
asks for the code.
|
|
10
13
|
|
|
11
14
|
<!-- tools: git -->
|
|
12
15
|
- A commit request includes selecting and staging its changes; a stage-only
|
|
@@ -4,6 +4,7 @@ import { countJsonNextCalls } from './tools/next-call-utils.mjs';
|
|
|
4
4
|
import { parseGrepContextHeader, splitGrepLinePrefix } from './tools/builtin/grep-formatting.mjs';
|
|
5
5
|
import { isReadOnlyNavigationMiss } from './session/result-classification.mjs';
|
|
6
6
|
import { nonNegativeInt } from '../../shared/numbers.mjs';
|
|
7
|
+
import { isUserControlCancellation } from '../../shared/tool-status.mjs';
|
|
7
8
|
import {
|
|
8
9
|
appendAgentTrace,
|
|
9
10
|
normalizeSessionId,
|
|
@@ -128,7 +129,7 @@ function traceAgentCompact({
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
const TOOL_ARG_KEYS = {
|
|
131
|
-
read: ['path', 'offset', 'limit', 'line', 'context'],
|
|
132
|
+
read: ['file_path', 'path', 'offset', 'limit', 'line', 'context'],
|
|
132
133
|
grep: ['pattern', 'path', 'glob', 'output_mode', 'head_limit', 'offset'],
|
|
133
134
|
glob: ['pattern', 'path', 'head_limit', 'offset', 'sort'],
|
|
134
135
|
find: ['query', 'path', 'head_limit'],
|
|
@@ -211,12 +212,11 @@ function summarizeToolArgs(toolName, args) {
|
|
|
211
212
|
for (const key of keys) {
|
|
212
213
|
if (Object.hasOwn(args, key)) out[key] = compactTraceArgValue(args[key], key);
|
|
213
214
|
}
|
|
214
|
-
|
|
215
|
+
// Arrays are clipped to 6 entries above; the count keeps batch-limit
|
|
216
|
+
// failures (more than 10 read/grep targets) diagnosable.
|
|
217
|
+
for (const countKey of ['edits', 'writes', 'file_path', 'path']) {
|
|
215
218
|
if (Array.isArray(args[countKey])) out[`${countKey}_count`] = args[countKey].length;
|
|
216
219
|
}
|
|
217
|
-
if (toolName === 'read' && Array.isArray(args.path)) {
|
|
218
|
-
out.path_count = args.path.length;
|
|
219
|
-
}
|
|
220
220
|
return Object.keys(out).length ? out : null;
|
|
221
221
|
}
|
|
222
222
|
|
|
@@ -410,21 +410,21 @@ function classifyToolFailure(resultText, toolName) {
|
|
|
410
410
|
if (/\[signal:\s*[^\]]+/i.test(leading)) return 'process/signal';
|
|
411
411
|
return 'command-exit';
|
|
412
412
|
}
|
|
413
|
+
// edit quotes the file around a miss (the nearest line and a current-file
|
|
414
|
+
// excerpt). That quoted text is file content, so a word in it such as
|
|
415
|
+
// "unreadable", "timeout" or "permission" must not pick the category.
|
|
416
|
+
if (toolName === 'edit' && /^edit failed \(old_string (?:not found|found \d+ times)\b/i.test(leading))
|
|
417
|
+
return 'runtime/failure';
|
|
413
418
|
if (/compacted-history placeholder/.test(text)) return 'expected-preflight';
|
|
414
419
|
if (/\[tool-input-validation\]/.test(text)) return 'schema/args';
|
|
415
420
|
// These are emitted validation/cancellation contracts, not keywords found
|
|
416
421
|
// somewhere in a quoted document, command output, or error preview.
|
|
417
|
-
if (
|
|
418
|
-
/^(?:Browser command interrupted by local user input\.|computer_session_aborted: queued command was cancelled before execution|computer_user_control_active:)/i.test(
|
|
419
|
-
leading
|
|
420
|
-
)
|
|
421
|
-
)
|
|
422
|
-
return 'expected-cancellation';
|
|
422
|
+
if (isUserControlCancellation(leading)) return 'expected-cancellation';
|
|
423
423
|
if (
|
|
424
424
|
/^The arguments provided to `[^`]+` are invalid JSON and could not be parsed:/i.test(leading) ||
|
|
425
425
|
/^new Goal tasks must omit ids; use updates for existing tasks$/i.test(leading) ||
|
|
426
426
|
/^goal task exceeds \d+ characters$/i.test(leading) ||
|
|
427
|
-
/^goal (?:tasks|updates) support at most \d+ entries
|
|
427
|
+
/^goal (?:tasks|updates) support at most \d+ entries(?:$|;)/i.test(leading) ||
|
|
428
428
|
/^goal arguments contain unknown fields:/i.test(leading) ||
|
|
429
429
|
/^Unsupported field for GitHub [\w.]+\.$/i.test(leading) ||
|
|
430
430
|
/^browser action "[^"]+" (?:does not accept input field|requires input\.|accepts only one input target form|fields must be inside input:|input\.[^\n]*(?:must be|requires|accepts at most))/i.test(
|
|
@@ -100,7 +100,9 @@ export function buildDefaultConfig(options = {}) {
|
|
|
100
100
|
providers['mixdog-local'] = { enabled: false };
|
|
101
101
|
return {
|
|
102
102
|
providers,
|
|
103
|
-
|
|
103
|
+
// The Maintainer also starts off: background upkeep spends model calls, so
|
|
104
|
+
// it runs only once the user turns it on and picks its model.
|
|
105
|
+
disabledAgents: [...DEFAULT_DISABLED_AGENT_IDS, 'maintainer'],
|
|
104
106
|
workflow: { active: 'default' },
|
|
105
107
|
orchestrationMode: 'none',
|
|
106
108
|
};
|
|
@@ -272,6 +274,11 @@ export function loadConfig(options = {}) {
|
|
|
272
274
|
recap: { enabled: true },
|
|
273
275
|
developer: {},
|
|
274
276
|
modules: {},
|
|
277
|
+
// No stored section at all is a brand-new profile: its built-ins start
|
|
278
|
+
// uninstalled. The defaults above carry presets/recap/modules, which
|
|
279
|
+
// would otherwise read as a pre-section profile and be grandfathered.
|
|
280
|
+
// A stored section that failed to parse proves nothing and keeps that path.
|
|
281
|
+
...(hasKeys(sectionRaw) ? {} : { builtins: {} }),
|
|
275
282
|
};
|
|
276
283
|
}
|
|
277
284
|
/** In-lock patch of `skills.disabled` only (avoids whole-config lost-update). */
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from './retry-classifier.mjs';
|
|
22
22
|
import { PROVIDER_FIRST_BYTE_TIMEOUT_MS, createTimeoutSignal } from '../stall-policy.mjs';
|
|
23
23
|
import { noteFastModeCapacityError } from './anthropic-fast-mode.mjs';
|
|
24
|
+
import { noteRequestServiceTier } from '../../../shared/llm/usage-context.mjs';
|
|
24
25
|
import { notifyCurrentAnthropicRateLimit } from './admission-scheduler.mjs';
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -103,6 +104,7 @@ export function createAnthropicApiTransport({ client, label, opts, useModel, par
|
|
|
103
104
|
const requestStreamingResponse = () =>
|
|
104
105
|
withRetry(
|
|
105
106
|
async ({ signal: attemptSignal }) => {
|
|
107
|
+
noteRequestServiceTier(params.speed);
|
|
106
108
|
const res = await client.messages.create(params, requestOptions(attemptSignal)).asResponse();
|
|
107
109
|
if (!res.ok) {
|
|
108
110
|
const text = await res.text().catch(() => '');
|
|
@@ -156,7 +158,10 @@ export function createAnthropicApiTransport({ client, label, opts, useModel, par
|
|
|
156
158
|
|
|
157
159
|
const requestNonStreamingMessage = (nonStreamingParams, signal) =>
|
|
158
160
|
withRetry(
|
|
159
|
-
async ({ signal: attemptSignal }) =>
|
|
161
|
+
async ({ signal: attemptSignal }) => {
|
|
162
|
+
noteRequestServiceTier(nonStreamingParams.speed);
|
|
163
|
+
return client.messages.create(nonStreamingParams, requestOptions(attemptSignal));
|
|
164
|
+
},
|
|
160
165
|
{
|
|
161
166
|
...retryPolicy(signal, `${label} Anthropic non-streaming fallback`, `${label}-nonstreaming-request`),
|
|
162
167
|
onRetry: ({ attempt, maxAttempts, lastErr, delayMs }) =>
|
|
@@ -12,6 +12,7 @@ import { traceAgentFetch } from '../agent-trace.mjs';
|
|
|
12
12
|
import { createAbortController } from '../../../shared/abort-controller.mjs';
|
|
13
13
|
import { notifyCurrentAnthropicRateLimit } from './admission-scheduler.mjs';
|
|
14
14
|
import { noteFastModeCapacityError } from './anthropic-fast-mode.mjs';
|
|
15
|
+
import { noteRequestServiceTier } from '../../../shared/llm/usage-context.mjs';
|
|
15
16
|
import {
|
|
16
17
|
ANTHROPIC_RETRY_BACKOFF_MS,
|
|
17
18
|
ANTHROPIC_RETRY_JITTER_RATIO,
|
|
@@ -66,6 +67,7 @@ export function createAnthropicOAuthRequest({
|
|
|
66
67
|
// provider-visible cache breakpoint off the cached one — the
|
|
67
68
|
// exact COLD-turn bug this change fixes. Order is fixed:
|
|
68
69
|
// build → sanitize (once) → mark → prepare image bytes → JSON.stringify.
|
|
70
|
+
noteRequestServiceTier(requestBody?.speed);
|
|
69
71
|
const response = await postMessages({
|
|
70
72
|
accessToken,
|
|
71
73
|
requestBody,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { getAgentApiKey } from '../../../shared/config.mjs';
|
|
3
|
+
import { noteRequestServiceTier } from '../../../shared/llm/usage-context.mjs';
|
|
3
4
|
import {
|
|
4
5
|
knownToolNamesFromOpenAITools,
|
|
5
6
|
parseToolCalls,
|
|
@@ -510,6 +511,15 @@ function selectParameterizedCursorVariant(group, requested, sendOpts = {}) {
|
|
|
510
511
|
};
|
|
511
512
|
}
|
|
512
513
|
|
|
514
|
+
// Fast is a RequestedModel parameter on parameterized models and an id
|
|
515
|
+
// suffix on raw variants.
|
|
516
|
+
function cursorSelectionIsFast(selection) {
|
|
517
|
+
if (selection.parameters?.length) {
|
|
518
|
+
return selection.parameters.some((parameter) => parameter.id === 'fast' && parameter.value === 'true');
|
|
519
|
+
}
|
|
520
|
+
return parseCursorVariantId(selection.modelId).fast;
|
|
521
|
+
}
|
|
522
|
+
|
|
513
523
|
function selectCursorVariant(group, { effort = null, fast = false } = {}) {
|
|
514
524
|
if (!Array.isArray(group) || group.length === 0) return null;
|
|
515
525
|
const normalizedEffort =
|
|
@@ -680,11 +690,13 @@ class CursorProviderBase {
|
|
|
680
690
|
sendOpts.sessionId || sendOpts.providerCacheKey || sendOpts.promptCacheKey || `cursor-call:${randomUUID()}`
|
|
681
691
|
);
|
|
682
692
|
const openAiTools = tools?.length ? toOpenAITools(tools) : undefined;
|
|
683
|
-
const dispatch = () =>
|
|
684
|
-
|
|
693
|
+
const dispatch = () => {
|
|
694
|
+
noteRequestServiceTier(cursorSelectionIsFast(cursorSelection) ? 'fast' : '');
|
|
695
|
+
return this._dispatchChat(
|
|
685
696
|
this._chatBody(messages, { cursorSelection, sessionScope, openAiTools, toolChoice: sendOpts.toolChoice }),
|
|
686
697
|
{ runtime, accessToken, signal, sendOpts, openAiTools }
|
|
687
698
|
);
|
|
699
|
+
};
|
|
688
700
|
let assembled;
|
|
689
701
|
try {
|
|
690
702
|
assembled = await dispatch();
|
|
@@ -702,9 +714,15 @@ class CursorProviderBase {
|
|
|
702
714
|
assembled = await dispatch();
|
|
703
715
|
}
|
|
704
716
|
const rawUsage = assembled.rawUsage;
|
|
717
|
+
// A pinned raw variant (effort/Fast suffix) bills as its catalog model;
|
|
718
|
+
// the Fast part is carried by the request's tier, not the id.
|
|
719
|
+
const pinned = parseCursorVariantId(canonicalCursorModelId(String(model || '').trim()));
|
|
720
|
+
const pricingModel =
|
|
721
|
+
pinned.baseId !== pinned.id && this._cursorCatalog?.groups?.has(pinned.baseId) ? pinned.baseId : undefined;
|
|
705
722
|
return {
|
|
706
723
|
content: assembled.content || '',
|
|
707
724
|
model: model || assembled.model || 'auto',
|
|
725
|
+
...(pricingModel ? { pricingModel } : {}),
|
|
708
726
|
toolCalls: assembled.toolCalls,
|
|
709
727
|
stopReason: assembled.stopReason,
|
|
710
728
|
...(assembled.reasoningContent ? { reasoningContent: assembled.reasoningContent } : {}),
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
resolveProviderCacheKey,
|
|
40
40
|
} from '../agent-runtime/cache-strategy.mjs';
|
|
41
41
|
import { preconnect } from '../../../shared/llm/http-agent.mjs';
|
|
42
|
+
import { noteRequestServiceTier } from '../../../shared/llm/usage-context.mjs';
|
|
42
43
|
import { sendViaHttpSse, _envFlag } from './openai-oauth-http-sse.mjs';
|
|
43
44
|
import { warmCodexClientVersion } from './codex-client-meta.mjs';
|
|
44
45
|
import { CODEX_BACKEND_ORIGIN } from './openai-codex-endpoints.mjs';
|
|
@@ -209,6 +210,8 @@ export class OpenAIOAuthProvider {
|
|
|
209
210
|
const authState = { tokens: await _authP };
|
|
210
211
|
await _verP;
|
|
211
212
|
const body = await _bodyP;
|
|
213
|
+
// Every WS/HTTP attempt of this send carries this body's tier unchanged.
|
|
214
|
+
noteRequestServiceTier(body.service_tier);
|
|
212
215
|
// poolKey != cacheKey by design. poolKey isolates socket/delta state per
|
|
213
216
|
// session. cacheKey is body.prompt_cache_key and affects prompt-cache
|
|
214
217
|
// routing only; Codex handshake identity comes from sendOpts and remains
|
|
@@ -23,6 +23,7 @@ import { applyOpenAIDirectCachePolicy, openAiDirectSupportsFast } from './openai
|
|
|
23
23
|
import { streamCallbacks } from './lib/send-callbacks.mjs';
|
|
24
24
|
import { getAgentApiKey } from '../../../shared/provider-api-key.mjs';
|
|
25
25
|
import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
|
|
26
|
+
import { noteRequestServiceTier } from '../../../shared/llm/usage-context.mjs';
|
|
26
27
|
import { resolveProviderCacheKey, resolveProviderPromptCacheLane } from '../agent-runtime/cache-strategy.mjs';
|
|
27
28
|
|
|
28
29
|
function applyOpenAIDirectFastTier(body, model, opts) {
|
|
@@ -122,6 +123,7 @@ export class OpenAIDirectProvider {
|
|
|
122
123
|
// so a model can opt into Fast even when the OAuth catalog does not
|
|
123
124
|
// advertise a Fast tier for its OAuth endpoint.
|
|
124
125
|
applyOpenAIDirectFastTier(body, useModel, opts);
|
|
126
|
+
noteRequestServiceTier(body.service_tier);
|
|
125
127
|
// Keep public response storage and model-specific cache options out of
|
|
126
128
|
// the shared OAuth payload. Storage opt-out still forces full frames in
|
|
127
129
|
// _computeDelta; retained output items make those frames self-contained.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Error code for a provider init that has no enabled provider to register.
|
|
2
|
+
// The registry refuses such an init to protect its live instances; readers
|
|
3
|
+
// that legitimately run before any provider exists (the model catalog on a
|
|
4
|
+
// fresh install) treat it as "nothing to load" instead of a failure.
|
|
5
|
+
export const NO_ENABLED_PROVIDERS = 'PROVIDERS_NONE_ENABLED';
|
|
6
|
+
|
|
7
|
+
export function noEnabledProvidersError(message) {
|
|
8
|
+
return Object.assign(new Error(message), { code: NO_ENABLED_PROVIDERS });
|
|
9
|
+
}
|
|
@@ -4,6 +4,7 @@ import { refreshCatalog as refreshMetadataCatalog } from './model-catalog.mjs';
|
|
|
4
4
|
import { wrapProviderAdmission } from './admission-scheduler.mjs';
|
|
5
5
|
import { createAccountPoolProvider } from './account-pool.mjs';
|
|
6
6
|
import { isInclusiveProvider } from '../../../shared/llm/cost.mjs';
|
|
7
|
+
import { noEnabledProvidersError } from './registry-errors.mjs';
|
|
7
8
|
// OpenAI-compat provider names are self-declared by openai-compat-presets.mjs via
|
|
8
9
|
// OPENAI_COMPAT_PRESETS. No parallel list maintained here.
|
|
9
10
|
const providers = new Map();
|
|
@@ -223,7 +224,7 @@ async function _initProvidersUnsynchronized(config, signal = null) {
|
|
|
223
224
|
// here preserves whatever was already registered.
|
|
224
225
|
const entries = Object.entries(config || {});
|
|
225
226
|
if (entries.length === 0) {
|
|
226
|
-
throw
|
|
227
|
+
throw noEnabledProvidersError('[provider] initProviders called with empty config — refusing to clear registry');
|
|
227
228
|
}
|
|
228
229
|
const next = new Map();
|
|
229
230
|
const nextSignatures = new Map();
|
|
@@ -258,7 +259,7 @@ async function _initProvidersUnsynchronized(config, signal = null) {
|
|
|
258
259
|
// Second guard: every entry was disabled. Same reasoning — keep the
|
|
259
260
|
// existing registry rather than going dark.
|
|
260
261
|
if (next.size === 0) {
|
|
261
|
-
throw
|
|
262
|
+
throw noEnabledProvidersError('[provider] all providers disabled in config — refusing to clear registry');
|
|
262
263
|
}
|
|
263
264
|
// OAuth preservation guard. anthropic-oauth / openai-oauth are NOT stored
|
|
264
265
|
// in mixdog-config.json — buildDefaultConfig injects them at runtime by
|
|
@@ -6,11 +6,13 @@ import { clean } from '../../../shared/clean.mjs';
|
|
|
6
6
|
// dropped on normalize rather than rejected.
|
|
7
7
|
const TASK_ENTRY_FIELDS = ['id', 'text', 'status', 'kind'];
|
|
8
8
|
|
|
9
|
-
// Dropped rows remain in the record but are no longer requested work.
|
|
9
|
+
// Dropped rows remain in the record but are no longer requested work. Every
|
|
10
|
+
// row, completed and dropped included, still fills one of the capped slots.
|
|
10
11
|
export function goalTaskProgress(tasks) {
|
|
11
12
|
return {
|
|
12
13
|
tasksCompleted: tasks.filter((task) => task?.status === 'completed').length,
|
|
13
14
|
tasksTotal: tasks.filter((task) => task?.status !== 'dropped').length,
|
|
15
|
+
taskSlotsUsed: tasks.length,
|
|
14
16
|
};
|
|
15
17
|
}
|
|
16
18
|
|
|
@@ -34,7 +36,18 @@ export function optionalGoalTaskChanges({ tasks, updates } = {}) {
|
|
|
34
36
|
export function normalizeGoalTasks(input, previous = [], { strict = false } = {}) {
|
|
35
37
|
if (input == null) return previous.map((task) => ({ ...task }));
|
|
36
38
|
if (!Array.isArray(input)) throw new Error('goal tasks must be an array');
|
|
37
|
-
if (input.length > MAX_GOAL_TASKS)
|
|
39
|
+
if (input.length > MAX_GOAL_TASKS) {
|
|
40
|
+
// Settled rows count toward the cap; set_tasks may omit them (only
|
|
41
|
+
// unfinished tasks must be retained), which is how a long Goal makes room.
|
|
42
|
+
const settled = previous.some((task) => GOAL_TASK_SETTLED.includes(task?.status));
|
|
43
|
+
throw new Error(
|
|
44
|
+
`goal tasks support at most ${MAX_GOAL_TASKS} entries${
|
|
45
|
+
settled
|
|
46
|
+
? '; completed and dropped tasks count, so to add more send set_tasks with only the unfinished tasks plus the new ones'
|
|
47
|
+
: ''
|
|
48
|
+
}`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
38
51
|
const previousByText = new Map(previous.map((task) => [clean(task?.text), task]));
|
|
39
52
|
const reservedIds = new Set([...previous, ...input].map((task) => clean(task?.id)).filter(Boolean));
|
|
40
53
|
let nextId = 1;
|
|
@@ -78,12 +91,18 @@ function taskInputRetains(entry, task) {
|
|
|
78
91
|
}
|
|
79
92
|
|
|
80
93
|
export function patchGoalTasks(previous, { updates, tasks } = {}) {
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
if (!Array.isArray(
|
|
94
|
+
const requested = updates ?? [];
|
|
95
|
+
const entries = tasks ?? [];
|
|
96
|
+
if (!Array.isArray(requested) || !Array.isArray(entries)) throw new Error('goal updates and tasks must be arrays');
|
|
97
|
+
const byId = new Map(previous.map((task) => [task.id, { ...task }]));
|
|
98
|
+
// A new-task entry naming an id this Goal already has can only mean that
|
|
99
|
+
// task, so it is applied as the task's update. An id the Goal does not have
|
|
100
|
+
// is still refused below.
|
|
101
|
+
const addressesExisting = (entry) => byId.has(clean(entry?.id));
|
|
102
|
+
const patches = [...requested, ...entries.filter(addressesExisting)];
|
|
103
|
+
const additions = entries.filter((entry) => !addressesExisting(entry));
|
|
84
104
|
if (!patches.length && !additions.length) throw new Error('goal update_tasks requires updates or new tasks');
|
|
85
105
|
if (patches.length > MAX_GOAL_TASKS) throw new Error(`goal updates support at most ${MAX_GOAL_TASKS} entries`);
|
|
86
|
-
const byId = new Map(previous.map((task) => [task.id, { ...task }]));
|
|
87
106
|
const seen = new Set();
|
|
88
107
|
for (const patch of patches) {
|
|
89
108
|
const id = clean(patch?.id);
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// No filesystem, no runtime coupling — values in, text out.
|
|
4
4
|
|
|
5
5
|
import { goalTaskProgress } from './goal-tasks.mjs';
|
|
6
|
+
import { MAX_GOAL_TASKS } from './goal-tool-defs.mjs';
|
|
6
7
|
|
|
7
8
|
function escapeGoalPromptText(value) {
|
|
8
9
|
return String(value ?? '')
|
|
@@ -214,7 +215,7 @@ export function goalStateReminder(goal, { reason = '' } = {}) {
|
|
|
214
215
|
}
|
|
215
216
|
if (reason === 'deadline-reached' && goal.status === 'active') return '';
|
|
216
217
|
const tasks = Array.isArray(goal.tasks) ? goal.tasks : [];
|
|
217
|
-
const { tasksCompleted, tasksTotal } = goalTaskProgress(tasks);
|
|
218
|
+
const { tasksCompleted, tasksTotal, taskSlotsUsed } = goalTaskProgress(tasks);
|
|
218
219
|
// Event-specific steering: what the model could not have learned from its own
|
|
219
220
|
// tool results. Standing rules stay in the cached tool description.
|
|
220
221
|
let lead = 'Current durable Goal snapshot:';
|
|
@@ -233,7 +234,7 @@ export function goalStateReminder(goal, { reason = '' } = {}) {
|
|
|
233
234
|
...(goal.status === 'blocked' ? [BLOCKED_LINE] : []),
|
|
234
235
|
'',
|
|
235
236
|
`Objective: ${escapeGoalPromptText(goal.objective)}`,
|
|
236
|
-
`Status: ${escapeGoalPromptText(goal.status)} · tasks ${tasksCompleted}/${tasksTotal}`,
|
|
237
|
+
`Status: ${escapeGoalPromptText(goal.status)} · tasks ${tasksCompleted}/${tasksTotal} · task slots used ${taskSlotsUsed}/${MAX_GOAL_TASKS}`,
|
|
237
238
|
...(goal.blocker ? [`Waiting or stop reason: ${escapeGoalPromptText(goal.blocker)}`] : []),
|
|
238
239
|
...(goal.revision ? [`Revision: ${goal.revision}`] : []),
|
|
239
240
|
...goalTimeLines(goal),
|
|
@@ -22,6 +22,7 @@ function toolReply(ctx, id, goal, { full = false, previousIds = null } = {}) {
|
|
|
22
22
|
status: goal.status,
|
|
23
23
|
tasksCompleted: goal.tasksCompleted,
|
|
24
24
|
tasksTotal: goal.tasksTotal,
|
|
25
|
+
taskSlotsUsed: goal.taskSlotsUsed,
|
|
25
26
|
tasksUpdatedAt: goal.tasksUpdatedAt,
|
|
26
27
|
timeUsedMs: goal.timeUsedMs,
|
|
27
28
|
};
|
|
@@ -61,7 +61,7 @@ const ZERO_MATCH_PREFIXES = [
|
|
|
61
61
|
const READ_ONLY_NAVIGATION_TOOLS = new Set(['find', 'glob', 'grep', 'list', 'read', 'code_graph']);
|
|
62
62
|
|
|
63
63
|
const NAVIGATION_MISS_RE =
|
|
64
|
-
/\b(?:enoent|enotdir)\b|path does not exist|directory does not exist|no such (?:file|path)|not found at this path|file not found in
|
|
64
|
+
/\b(?:enoent|enotdir)\b|path does not exist|directory does not exist|no such (?:file|path)|not found at this path|file not found\b|symbol "[^"\n]*" not found in /i;
|
|
65
65
|
|
|
66
66
|
export function isReadOnlyNavigationMiss(toolName, result) {
|
|
67
67
|
if (!READ_ONLY_NAVIGATION_TOOLS.has(String(toolName || '').toLowerCase())) return false;
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
* session runtime.
|
|
4
4
|
*/
|
|
5
5
|
import { stripShellExitHeader, toolErrorDisplay } from './tool-result-text.mjs';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
hasBenignExitOutcome,
|
|
8
|
+
normalizeToolTerminalStatus,
|
|
9
|
+
toolResultTerminalStatus,
|
|
10
|
+
} from '../../../../shared/tool-status.mjs';
|
|
7
11
|
import { isReadOnlyNavigationMiss } from '../result-classification.mjs';
|
|
8
12
|
import { formatAggregateDetail, summarizeToolResult, toolLoadingTargets } from '../../../../shared/tool-surface.mjs';
|
|
9
13
|
import { normalizeToolName } from '../../../../shared/tool-primitives.mjs';
|
|
@@ -43,7 +47,7 @@ export function shellCommandExitCode(text) {
|
|
|
43
47
|
export function toolCallOutcome(message, rawText) {
|
|
44
48
|
if (normalizeToolName(message?.toolName || message?.name) === 'git') {
|
|
45
49
|
const exitCode = gitResultExitCode(rawText);
|
|
46
|
-
if (exitCode !== null) return { isCallError: false, isExitError:
|
|
50
|
+
if (exitCode !== null) return { isCallError: false, isExitError: !hasBenignExitOutcome(rawText), exitCode };
|
|
47
51
|
if (gitResultError(rawText)) return { isCallError: true, isExitError: false, exitCode: null };
|
|
48
52
|
}
|
|
49
53
|
const exitCode = shellCommandExitCode(rawText);
|
|
@@ -51,7 +55,7 @@ export function toolCallOutcome(message, rawText) {
|
|
|
51
55
|
// Every completed shell result carries `[exit code: N]` — including 0.
|
|
52
56
|
// A non-zero code is a command failure unless execution marked a known
|
|
53
57
|
// successful no-match/no-change outcome.
|
|
54
|
-
const benignExit = exitCode !== 0 &&
|
|
58
|
+
const benignExit = exitCode !== 0 && hasBenignExitOutcome(rawText);
|
|
55
59
|
return { isCallError: false, isExitError: exitCode !== 0 && !benignExit, exitCode };
|
|
56
60
|
}
|
|
57
61
|
if (isReadOnlyNavigationMiss(message?.toolName || message?.name, rawText)) {
|