mixdog 0.9.166 → 0.9.167
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/audit-usage-ledger.mjs +86 -0
- package/scripts/generate-voice-runtime-manifest.mjs +22 -17
- package/scripts/memory-chunk-quality.mjs +128 -0
- package/scripts/memory-layer2-check.mjs +133 -0
- package/scripts/model-catalog-audit.mjs +6 -0
- package/scripts/native-tool-download.mjs +14 -0
- package/scripts/prepare-native-assets.mjs +2 -7
- package/scripts/tool-failures.mjs +7 -2
- package/scripts/verify-release-assets.mjs +48 -90
- package/src/defaults/skills/browser-use/SKILL.md +33 -18
- package/src/defaults/skills/computer-use/SKILL.md +16 -8
- package/src/lib/keychain-cjs.cjs +0 -1
- package/src/lib/rules-builder.cjs +4 -1
- package/src/rules/agent/40-cycle1-agent.md +21 -13
- package/src/rules/lead/01-general.md +8 -3
- package/src/rules/shared/10-tool-workflow.md +20 -11
- package/src/rules/shared/30-exploration.md +11 -4
- package/src/rules/shared/40-editing.md +2 -3
- package/src/rules/shared/50-execution.md +6 -3
- package/src/rules/shared/60-verification.md +3 -2
- package/src/rules/shared/70-delivery.md +0 -4
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +51 -45
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +3 -4
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +44 -7
- package/src/runtime/agent/orchestrator/agent-trace.mjs +9 -0
- package/src/runtime/agent/orchestrator/config.mjs +9 -4
- package/src/runtime/agent/orchestrator/context/collect.mjs +25 -1055
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +272 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +296 -0
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +523 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +13 -14
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +214 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +220 -116
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +74 -44
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +191 -29
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +13 -12
- package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +1 -9
- package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +48 -25
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +28 -2
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +1 -8
- package/src/runtime/agent/orchestrator/providers/lib/action-input-contract.mjs +32 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +21 -6
- package/src/runtime/agent/orchestrator/providers/lib/grok-tool-schema.mjs +61 -13
- package/src/runtime/agent/orchestrator/providers/model-catalog-projection.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +123 -118
- package/src/runtime/agent/orchestrator/providers/model-pricing-rates.mjs +54 -0
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +34 -3
- package/src/runtime/agent/orchestrator/providers/openai-codex-endpoints.mjs +21 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-catalog.mjs +154 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +322 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +243 -702
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-startup-prewarm.mjs +221 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/provider-catalog-cache.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/provider-model-identities.mjs +40 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +120 -710
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +36 -15
- package/src/runtime/agent/orchestrator/session/cache/util.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +17 -4
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +36 -31
- package/src/runtime/agent/orchestrator/session/compact.mjs +4 -5
- package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +6 -5
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +8 -6
- package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +121 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/loop/diagnostics.mjs +74 -0
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +44 -17
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +266 -0
- package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +111 -0
- package/src/runtime/agent/orchestrator/session/loop/request-projection.mjs +99 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +91 -0
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +20 -11
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +3 -5
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +9 -3
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +39 -0
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +21 -3
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +2 -3
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +25 -25
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +62 -10
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +17 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +61 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +13 -9
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +12 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +8 -3
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +69 -6
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +14 -17
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +1 -1
- package/src/runtime/bridge-discovery.mjs +8 -0
- package/src/runtime/browser-bridge/action-schema.mjs +37 -43
- package/src/runtime/browser-bridge/client.mjs +111 -99
- package/src/runtime/browser-bridge/input-fields.mjs +187 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +12 -227
- package/src/runtime/computer-bridge/action-schema.mjs +2 -46
- package/src/runtime/computer-bridge/client.mjs +69 -77
- package/src/runtime/computer-bridge/core-actions.mjs +7 -5
- package/src/runtime/computer-bridge/error-recovery.mjs +41 -37
- package/src/runtime/computer-bridge/tool-defs.mjs +1 -1
- package/src/runtime/github/commands.mjs +4 -1
- package/src/runtime/github/contract.mjs +1 -1
- package/src/runtime/github/tool.mjs +2 -2
- package/src/runtime/media/adapters/antigravity-image.mjs +72 -0
- package/src/runtime/media/adapters/gemini-image.mjs +20 -11
- package/src/runtime/media/auth.mjs +12 -0
- package/src/runtime/media/catalog.mjs +30 -10
- package/src/runtime/media/jobs.mjs +4 -0
- package/src/runtime/media/lanes.mjs +40 -31
- package/src/runtime/memory/index.mjs +2 -9
- package/src/runtime/memory/lib/compact-handoff.mjs +26 -18
- package/src/runtime/memory/lib/memory-chunk-audit.mjs +41 -0
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +388 -0
- package/src/runtime/memory/lib/memory-cycle1.mjs +95 -394
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +29 -4
- package/src/runtime/memory/lib/memory-recall-store.mjs +8 -12
- package/src/runtime/memory/lib/memory-retrievers.mjs +8 -11
- package/src/runtime/memory/lib/memory.mjs +2 -0
- package/src/runtime/memory/lib/query-handlers.mjs +98 -144
- package/src/runtime/memory/lib/query-ranking.mjs +23 -9
- package/src/runtime/memory/lib/recall-format.mjs +37 -27
- package/src/runtime/memory/lib/session-ingest-runtime.mjs +14 -8
- package/src/runtime/memory/lib/session-ingest.mjs +45 -84
- package/src/runtime/memory/lib/transcript-ingest.mjs +19 -21
- package/src/runtime/memory/tool-defs.mjs +2 -2
- package/src/runtime/office/authoring/pptx-author-session.mjs +3 -7
- package/src/runtime/office/core/office-actions-batch.mjs +2 -8
- package/src/runtime/office/core/office-actions-open.mjs +124 -0
- package/src/runtime/office/core/office-actions-read.mjs +127 -0
- package/src/runtime/office/core/office-core.mjs +80 -1
- package/src/runtime/office/core/office-sessions.mjs +116 -138
- package/src/runtime/office/index.mjs +18 -271
- package/src/runtime/office/pdf/pdf-analysis.mjs +2 -2
- package/src/runtime/office/pdf/pdf-limits.mjs +2 -0
- package/src/runtime/office/tool-defs.mjs +7 -6
- package/src/runtime/shared/agent-route-config.mjs +8 -8
- package/src/runtime/shared/child-spawn-gate.mjs +10 -13
- package/src/runtime/shared/config.mjs +2 -7
- package/src/runtime/shared/env.mjs +3 -3
- package/src/runtime/shared/err-text.mjs +1 -1
- package/src/runtime/shared/extension-scopes.mjs +3 -2
- package/src/runtime/shared/heap-cap.mjs +10 -6
- package/src/runtime/shared/llm/cost.mjs +35 -10
- package/src/runtime/shared/llm/usage-accounting.mjs +11 -1
- package/src/runtime/shared/llm/usage-context.mjs +6 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +5 -0
- package/src/runtime/shared/llm/usage-ledger-repair.mjs +109 -0
- package/src/runtime/shared/llm/usage-ledger.mjs +59 -20
- package/src/runtime/shared/llm/usage-pricing-refresh.mjs +26 -0
- package/src/runtime/shared/llm/usage-rollup.mjs +17 -15
- package/src/runtime/shared/native-asset.mjs +3 -4
- package/src/runtime/shared/object.mjs +4 -0
- package/src/runtime/shared/pg-schema-db.mjs +21 -0
- package/src/runtime/shared/plugin-metadata.mjs +3 -1
- package/src/runtime/shared/resource-admission.mjs +1 -6
- package/src/runtime/shared/schedules-db.mjs +8 -24
- package/src/runtime/shared/schema-value-error.d.mts +2 -0
- package/src/runtime/shared/schema-value-error.mjs +68 -0
- package/src/runtime/shared/singleton-owner.mjs +11 -24
- package/src/runtime/shared/staged-update.mjs +1 -10
- package/src/runtime/shared/tool-card-model.mjs +8 -5
- package/src/runtime/shared/tool-execution-contract.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +1 -5
- package/src/runtime/shared/user-data-guard.mjs +1 -4
- package/src/runtime/shared/webhooks-db.mjs +8 -24
- package/src/runtime/web-search/index.mjs +32 -8
- package/src/runtime/web-search/tool-defs.mjs +4 -4
- package/src/session-runtime/config-lifecycle.mjs +34 -0
- package/src/session-runtime/context-status.mjs +19 -0
- package/src/session-runtime/cwd-plugins.mjs +12 -29
- package/src/session-runtime/goal-tasks.mjs +2 -2
- package/src/session-runtime/goal-tool-defs.mjs +3 -2
- package/src/session-runtime/internal-tool-executor.mjs +7 -0
- package/src/session-runtime/lifecycle-api.mjs +5 -11
- package/src/session-runtime/notification-bus.mjs +41 -0
- package/src/session-runtime/prewarm.mjs +22 -0
- package/src/session-runtime/runtime-bootstrap.mjs +90 -0
- package/src/session-runtime/runtime-core.mjs +88 -202
- package/src/session-runtime/runtime-feature-gates.mjs +37 -0
- package/src/session-runtime/session-lifecycle.mjs +5 -5
- package/src/session-runtime/session-turn-api.mjs +1 -12
- package/src/session-runtime/setup-tool/executor.mjs +13 -5
- package/src/session-runtime/setup-tool/tool-defs.mjs +52 -46
- package/src/session-runtime/tool-defs.mjs +73 -4
- package/src/session-runtime/usage-stats-api.mjs +4 -1
- package/src/session-runtime/warmup-schedulers.mjs +8 -2
- package/src/session-runtime/workflow.mjs +14 -0
- package/src/standalone/channel-session-router.mjs +3 -1
- package/src/standalone/channel-worker.mjs +18 -9
- package/src/standalone/daemon-crash-capture.mjs +534 -0
- package/src/standalone/daemon-telemetry.mjs +175 -0
- package/src/standalone/daemon.mjs +31 -21
- package/src/standalone/session-client.mjs +31 -23
- package/src/standalone/usage-dashboard-model.mjs +2 -0
- package/src/standalone/usage-stats-hours.mjs +18 -11
- package/src/standalone/usage-stats-model.mjs +39 -28
- package/src/standalone/usage-stats-period.mjs +43 -45
- package/src/tui/app/use-transcript-window.mjs +1 -1
- package/src/tui/components/StatusLine.jsx +13 -42
- package/src/tui/dist/index.mjs +256 -239
- package/src/tui/session/session-api-ext.mjs +44 -41
- package/src/tui/session/session-api.mjs +4 -0
- package/src/tui/session/tool-card-results.mjs +25 -78
- package/src/tui/session/tool-result-status.mjs +94 -1
- package/src/tui/session/turn-aggregate-cards.mjs +17 -54
- package/src/ui/statusline-format.mjs +6 -26
- package/src/workflows/default/WORKFLOW.md +5 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
// Skill discovery, cache, resource loading, envelopes, manifests, and the
|
|
2
|
+
// Skill meta-tool. Extracted from collect.mjs so role-prompt assembly no
|
|
3
|
+
// longer shares a god-file with the catalog.
|
|
4
|
+
|
|
5
|
+
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
6
|
+
import { basename, dirname, join } from 'path';
|
|
7
|
+
import { maxMtimeRecursive } from '../cache-mtime.mjs';
|
|
8
|
+
import { mixdogHome, resolvePluginData, mixdogRoot } from '../../../shared/plugin-paths.mjs';
|
|
9
|
+
import { pluginSkillsRoots } from '../../../shared/plugin-manifest.mjs';
|
|
10
|
+
import { parseSkillDocument } from '../../../shared/skill-document.mjs';
|
|
11
|
+
import { readSkillToolDependencies, skillToolDependenciesRoot } from '../../../shared/skill-tool-dependencies.mjs';
|
|
12
|
+
import { loadConfig, normalizeSkillsConfig } from '../config.mjs';
|
|
13
|
+
import { builtinFeatureActive, withGrandfatheredBuiltins } from '../../../../session-runtime/builtin-features.mjs';
|
|
14
|
+
import { extensionScopesFromConfig, skillAllowedForCwd } from '../../../shared/extension-scopes.mjs';
|
|
15
|
+
import { currentSkillContext, latestSkillBodies, skillMessageText } from './skill-state.mjs';
|
|
16
|
+
import { compactPromptManifestText } from './deferred-tools.mjs';
|
|
17
|
+
|
|
18
|
+
function skillsDisabled() {
|
|
19
|
+
return /^(?:1|true|on|yes)$/i.test(String(process.env.MIXDOG_DISABLE_SKILLS || ''));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// --- mixdog asset roots (standalone CLI owns its own paths; never .claude) ---
|
|
23
|
+
// Skills are machine-global under <mixdogData>/skills. Project-local skill
|
|
24
|
+
// directories are intentionally outside the runtime resolution chain.
|
|
25
|
+
export function mixdogGlobalDir(kind) {
|
|
26
|
+
try {
|
|
27
|
+
return join(resolvePluginData(), kind);
|
|
28
|
+
} catch {
|
|
29
|
+
return join(process.env.MIXDOG_DATA_DIR || join(mixdogHome(), 'data'), kind);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function mixdogAssetDirs(_projectDir, kind) {
|
|
33
|
+
return [mixdogGlobalDir(kind)];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The package's own bundle (src/defaults/) is a plugin root like any other:
|
|
37
|
+
* `plugin.json` + `skills/<name>/SKILL.md`, read in place through the same
|
|
38
|
+
* manifest resolver as installed plugins, never copied — a release updates it
|
|
39
|
+
* immediately, and a user-global or plugin skill of the same name shadows it.
|
|
40
|
+
* It is not in the registry: the Built-in settings panel owns its UI.
|
|
41
|
+
*/
|
|
42
|
+
function builtinPluginRoot() {
|
|
43
|
+
return join(mixdogRoot(), 'defaults');
|
|
44
|
+
}
|
|
45
|
+
function builtinSkillDirs() {
|
|
46
|
+
return pluginSkillsRoots(builtinPluginRoot()).filter((dir) => existsSync(dir));
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Absolute path to the plugin registry file, or null when the data dir is
|
|
50
|
+
* unresolvable. Included in the skills mtime gate so plugin add/remove
|
|
51
|
+
* (which rewrites registry.json) invalidates the cached skill list even
|
|
52
|
+
* when no surviving skills dir got a newer mtime.
|
|
53
|
+
*/
|
|
54
|
+
function pluginRegistryPath() {
|
|
55
|
+
try {
|
|
56
|
+
return join(resolvePluginData(), 'plugins', 'registry.json');
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Read `<resolvePluginData()>/plugins/registry.json` (safe JSON parse, ignore
|
|
63
|
+
* errors) and yield every existing skill root of each enabled registered
|
|
64
|
+
* plugin: `<root>/skills` plus the manifest `skills` path, from the same
|
|
65
|
+
* resolver the Plugins panel counts with, so the two never disagree.
|
|
66
|
+
*/
|
|
67
|
+
function pluginSkillDirs() {
|
|
68
|
+
const registryPath = pluginRegistryPath();
|
|
69
|
+
if (!registryPath)
|
|
70
|
+
return [];
|
|
71
|
+
let registry;
|
|
72
|
+
try {
|
|
73
|
+
registry = JSON.parse(readFileSync(registryPath, 'utf-8'));
|
|
74
|
+
} catch {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
if (!registry || !Array.isArray(registry.plugins))
|
|
78
|
+
return [];
|
|
79
|
+
const dirs = [];
|
|
80
|
+
for (const entry of registry.plugins) {
|
|
81
|
+
if (entry?.enabled === false)
|
|
82
|
+
continue;
|
|
83
|
+
const root = entry && typeof entry.root === 'string' ? entry.root : null;
|
|
84
|
+
if (!root || !existsSync(root))
|
|
85
|
+
continue;
|
|
86
|
+
const plugin = String(entry.id || entry.name || '') || null;
|
|
87
|
+
for (const skillsDir of pluginSkillsRoots(root)) {
|
|
88
|
+
if (existsSync(skillsDir))
|
|
89
|
+
dirs.push({ dir: skillsDir, plugin });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return dirs;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function readSafe(path) {
|
|
96
|
+
try {
|
|
97
|
+
if (!existsSync(path))
|
|
98
|
+
return null;
|
|
99
|
+
const content = readFileSync(path, 'utf-8').trim();
|
|
100
|
+
return content || null;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const _CACHE_MAP_CAP = 16;
|
|
108
|
+
|
|
109
|
+
export function capMapSize(map, max = _CACHE_MAP_CAP) {
|
|
110
|
+
if (map.size > max) map.delete(map.keys().next().value);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function mtimeWithTtl(cache, key, paths, ttlMs) {
|
|
114
|
+
const cached = cache.get(key);
|
|
115
|
+
if (cached && Date.now() - cached.checkedAt < ttlMs) return cached.mtime;
|
|
116
|
+
const mtime = maxMtimeRecursive(paths);
|
|
117
|
+
cache.set(key, { mtime, checkedAt: Date.now() });
|
|
118
|
+
capMapSize(cache);
|
|
119
|
+
return mtime;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Collect available skills (frontmatter only — token efficient).
|
|
124
|
+
* Full content is read on demand when a skill is loaded.
|
|
125
|
+
*/
|
|
126
|
+
export function collectSkills(cwd) {
|
|
127
|
+
if (skillsDisabled()) return [];
|
|
128
|
+
void cwd;
|
|
129
|
+
const skills = [];
|
|
130
|
+
// Plugin-provided skills load after user-global ones and built-ins last,
|
|
131
|
+
// so the user keeps precedence; `seen` below dedupes by frontmatter name.
|
|
132
|
+
// Each entry remembers its owner so a plugin's skills can be shown and
|
|
133
|
+
// toggled with the plugin instead of as loose entries.
|
|
134
|
+
const sources = [
|
|
135
|
+
...mixdogAssetDirs(null, 'skills').map((dir) => ({ dir, source: 'global', plugin: null })),
|
|
136
|
+
...pluginSkillDirs().map(({ dir, plugin }) => ({ dir, source: 'plugin', plugin })),
|
|
137
|
+
...builtinSkillDirs().map((dir) => ({ dir, source: 'builtin', plugin: null })),
|
|
138
|
+
];
|
|
139
|
+
const seen = new Set();
|
|
140
|
+
for (const { dir, source, plugin } of sources) {
|
|
141
|
+
if (!existsSync(dir))
|
|
142
|
+
continue;
|
|
143
|
+
try {
|
|
144
|
+
const files = readdirSync(dir, { recursive: true });
|
|
145
|
+
for (const f of files) {
|
|
146
|
+
if (basename(String(f)) !== 'SKILL.md')
|
|
147
|
+
continue;
|
|
148
|
+
const filePath = join(dir, String(f));
|
|
149
|
+
const content = readSafe(filePath);
|
|
150
|
+
if (!content)
|
|
151
|
+
continue;
|
|
152
|
+
let skill;
|
|
153
|
+
try {
|
|
154
|
+
skill = parseSkillDocument(content);
|
|
155
|
+
} catch {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
// Agent Skills requires the manifest name to match its folder.
|
|
159
|
+
if (basename(dirname(filePath)) !== skill.name)
|
|
160
|
+
continue;
|
|
161
|
+
if (seen.has(skill.name))
|
|
162
|
+
continue;
|
|
163
|
+
seen.add(skill.name);
|
|
164
|
+
skills.push({
|
|
165
|
+
name: skill.name,
|
|
166
|
+
description: skill.description,
|
|
167
|
+
whenToUse: skill.whenToUse,
|
|
168
|
+
filePath,
|
|
169
|
+
source,
|
|
170
|
+
plugin,
|
|
171
|
+
requires: requiredFeatures(skill.frontmatter),
|
|
172
|
+
...readSkillToolDependencies(filePath, skill.frontmatter),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch { /* ignore */ }
|
|
177
|
+
}
|
|
178
|
+
return skills;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function normalizeSkillNameKey(name) {
|
|
182
|
+
return String(name || '').trim().toLowerCase();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* `metadata.requires` in the frontmatter names the built-in features a skill
|
|
187
|
+
* depends on (`office`, `git`, `memory`, `webSearch`). A skill that describes
|
|
188
|
+
* how to drive a tool must not be offered while that tool is uninstalled or
|
|
189
|
+
* switched off, or the model is pointed at a call that cannot succeed.
|
|
190
|
+
*/
|
|
191
|
+
function requiredFeatures(frontmatter) {
|
|
192
|
+
const metadata = frontmatter?.metadata;
|
|
193
|
+
const raw = metadata && typeof metadata === 'object' ? metadata.requires : null;
|
|
194
|
+
const list = Array.isArray(raw) ? raw : raw == null ? [] : String(raw).split(',');
|
|
195
|
+
return list.map((value) => String(value || '').trim()).filter(Boolean);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function missingFeature(skill, config) {
|
|
199
|
+
const requires = Array.isArray(skill?.requires) ? skill.requires : [];
|
|
200
|
+
return requires.find((feature) => !builtinFeatureActive(config, feature)) || null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Config for feature gating. A profile that predates the `builtins` section
|
|
204
|
+
* is grandfathered as installed exactly like the daemon does at adoption, so
|
|
205
|
+
* the skill list and the tool surface agree. */
|
|
206
|
+
function featureConfig(config = null) {
|
|
207
|
+
return withGrandfatheredBuiltins(config || loadConfig({ secrets: false }));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function getDisabledSkillNameSet(config = null) {
|
|
211
|
+
const cfg = config || loadConfig({ secrets: false });
|
|
212
|
+
const keys = normalizeSkillsConfig(cfg.skills).disabled
|
|
213
|
+
.map((n) => normalizeSkillNameKey(n))
|
|
214
|
+
.filter(Boolean);
|
|
215
|
+
return new Set(keys);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Feature-owned bundle entries follow their parent, including when an older
|
|
219
|
+
// profile still has their former individual OFF preference. User overrides
|
|
220
|
+
// and standalone built-ins retain their independent preference.
|
|
221
|
+
function followsBuiltinFeature(skill) {
|
|
222
|
+
return skill?.source === 'builtin' && Array.isArray(skill.requires) && skill.requires.length > 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function isSkillDisabled(name, config = null) {
|
|
226
|
+
const n = normalizeSkillNameKey(name);
|
|
227
|
+
if (!n) return false;
|
|
228
|
+
const skill = collectSkillsCached(null).find((entry) => normalizeSkillNameKey(entry.name) === n);
|
|
229
|
+
if (!followsBuiltinFeature(skill) && getDisabledSkillNameSet(config).has(n)) return true;
|
|
230
|
+
return Boolean(skill && missingFeature(skill, featureConfig(config)));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** The built-in feature a skill needs that is not active, or null. */
|
|
234
|
+
export function skillMissingFeature(name, config = null) {
|
|
235
|
+
const n = normalizeSkillNameKey(name);
|
|
236
|
+
const skill = collectSkillsCached(null).find((entry) => normalizeSkillNameKey(entry.name) === n);
|
|
237
|
+
return skill ? missingFeature(skill, featureConfig(config)) : null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Skills a session may see: not switched off, feature available, and — when
|
|
242
|
+
* `cwd` is given — inside the skill's (or its plugin's) project scope. Without
|
|
243
|
+
* a cwd only global skills pass, since a scoped skill belongs to a project.
|
|
244
|
+
*/
|
|
245
|
+
export function filterSkillsExcludingDisabled(skills, config = null, cwd = null) {
|
|
246
|
+
if (skillsDisabled()) return [];
|
|
247
|
+
const cfg = featureConfig(config);
|
|
248
|
+
const disabled = getDisabledSkillNameSet(cfg);
|
|
249
|
+
const scopes = extensionScopesFromConfig(cfg);
|
|
250
|
+
return (Array.isArray(skills) ? skills : []).filter((s) => {
|
|
251
|
+
const key = normalizeSkillNameKey(s?.name);
|
|
252
|
+
return key && (followsBuiltinFeature(s) || !disabled.has(key))
|
|
253
|
+
&& !missingFeature(s, cfg) && skillAllowedForCwd(scopes, s, cwd);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
export function collectPromptSkillsCached(cwd, config = null) {
|
|
258
|
+
return filterSkillsExcludingDisabled(collectSkillsCached(cwd), config, cwd);
|
|
259
|
+
}
|
|
260
|
+
// --- Skill cache (mtime-based, keyed by cwd) ---
|
|
261
|
+
const _skillsCache = new Map();
|
|
262
|
+
const _mtimeCache = new Map();
|
|
263
|
+
const _MTIME_TTL_MS = 2000;
|
|
264
|
+
|
|
265
|
+
export function collectSkillsCached(cwd) {
|
|
266
|
+
if (skillsDisabled()) return [];
|
|
267
|
+
void cwd;
|
|
268
|
+
const key = 'global';
|
|
269
|
+
// Same mixdog-owned dirs collectSkills() reads, used as the freshness gate.
|
|
270
|
+
const skillsDirs = mixdogAssetDirs(null, 'skills');
|
|
271
|
+
skillsDirs.push(skillToolDependenciesRoot());
|
|
272
|
+
skillsDirs.push(...pluginSkillDirs().map(({ dir }) => dir), ...builtinSkillDirs());
|
|
273
|
+
// registry.json itself gates plugin add/remove: removal deletes the
|
|
274
|
+
// plugin's skills dir (so no dir mtime advances), but saveRegistry()
|
|
275
|
+
// always rewrites this file. maxMtimeRecursive stats plain files directly.
|
|
276
|
+
const registryPath = pluginRegistryPath();
|
|
277
|
+
if (registryPath)
|
|
278
|
+
skillsDirs.push(registryPath);
|
|
279
|
+
const mtime = mtimeWithTtl(_mtimeCache, key, skillsDirs, _MTIME_TTL_MS);
|
|
280
|
+
const entry = _skillsCache.get(key);
|
|
281
|
+
if (entry && entry.mtime >= mtime) {
|
|
282
|
+
return entry.value;
|
|
283
|
+
}
|
|
284
|
+
const skills = collectSkills(cwd);
|
|
285
|
+
_skillsCache.set(key, { value: skills, mtime });
|
|
286
|
+
capMapSize(_skillsCache);
|
|
287
|
+
return skills;
|
|
288
|
+
}
|
|
289
|
+
export function invalidateSkillsCache(cwd) {
|
|
290
|
+
void cwd;
|
|
291
|
+
_skillsCache.clear();
|
|
292
|
+
_mtimeCache.clear();
|
|
293
|
+
}
|
|
294
|
+
/** Drop only the mtime TTL so the next call re-stats the skill roots while the
|
|
295
|
+
* cached list stays — lets a test prove the gate itself notices an edit. */
|
|
296
|
+
export function invalidateSkillsMtimeGate() {
|
|
297
|
+
_mtimeCache.clear();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Load full skill content plus its on-disk directory (for base-dir + ${MIXDOG_SKILL_DIR}).
|
|
302
|
+
*/
|
|
303
|
+
export function loadSkillResource(name, cwd) {
|
|
304
|
+
const skillName = String(name || '').trim();
|
|
305
|
+
if (!skillName)
|
|
306
|
+
return null;
|
|
307
|
+
const skills = collectSkillsCached(cwd);
|
|
308
|
+
const skill = skills.find(s => s.name === skillName);
|
|
309
|
+
if (!skill)
|
|
310
|
+
return null;
|
|
311
|
+
const content = readSafe(skill.filePath);
|
|
312
|
+
if (content == null)
|
|
313
|
+
return null;
|
|
314
|
+
try {
|
|
315
|
+
return {
|
|
316
|
+
content: parseSkillDocument(content).body,
|
|
317
|
+
dir: dirname(skill.filePath),
|
|
318
|
+
filePath: skill.filePath,
|
|
319
|
+
source: skill.source || 'global',
|
|
320
|
+
toolDependencies: skill.toolDependencies || [],
|
|
321
|
+
declaredToolDependencies: skill.declaredToolDependencies || [],
|
|
322
|
+
dependencySource: skill.dependencySource || 'none',
|
|
323
|
+
dependencyIssues: skill.dependencyIssues || [],
|
|
324
|
+
};
|
|
325
|
+
} catch {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function escapeSkillXmlText(value) {
|
|
331
|
+
return String(value || '').replace(/[<>&]/g, (ch) => ({ '<': '<', '>': '>', '&': '&' }[ch]));
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Wrap loaded SKILL.md body for Skill tool results (runtime + loop).
|
|
336
|
+
*/
|
|
337
|
+
export function buildSkillResultEnvelope(name, content, skillDir) {
|
|
338
|
+
const escapedName = escapeSkillXmlText(name);
|
|
339
|
+
let body = String(content == null ? '' : content);
|
|
340
|
+
let dirLine = '';
|
|
341
|
+
if (skillDir) {
|
|
342
|
+
const normDir = String(skillDir).replace(/\\/g, '/');
|
|
343
|
+
body = body.replace(/\$\{MIXDOG_SKILL_DIR\}/g, normDir);
|
|
344
|
+
const escapedDir = escapeSkillXmlText(normDir);
|
|
345
|
+
dirLine = `<base-dir>${escapedDir}</base-dir>\n`;
|
|
346
|
+
}
|
|
347
|
+
return `<skill>\n<name>${escapedName}</name>\n${dirLine}${body}\n</skill>`;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Short, model-visible tool_result stub for a loaded skill. The full SKILL.md
|
|
352
|
+
* body is delivered separately as ONE injected user message (newMessages),
|
|
353
|
+
* never in the tool_result — so the body appears exactly once.
|
|
354
|
+
*/
|
|
355
|
+
function buildSkillStub(name, source) {
|
|
356
|
+
// The stub doubles as the transcript's provenance marker: a built-in skill
|
|
357
|
+
// (packaged with a built-in feature) loads silently, while user and plugin
|
|
358
|
+
// skills surface as a card. `source` mirrors collectSkills() entries.
|
|
359
|
+
const kind = source === 'builtin' ? 'built-in skill' : 'skill';
|
|
360
|
+
return `Loaded ${kind}: ${String(name || '').trim()}`;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Is this skill's body still present in the conversation the model will see?
|
|
365
|
+
*
|
|
366
|
+
* The check is deliberately about the LIVE transcript, not about whether the
|
|
367
|
+
* skill was ever loaded. The live turn supersedes the saved transcript after
|
|
368
|
+
* compaction. Match the full current body, not just its name, so edited skills
|
|
369
|
+
* and skills absent from the rebuilt context are delivered again.
|
|
370
|
+
*/
|
|
371
|
+
export function skillBodyPresentInSession(session, body) {
|
|
372
|
+
if (!session || !body) return false;
|
|
373
|
+
const requested = latestSkillBodies([{ role: 'user', content: body }])[0];
|
|
374
|
+
if (!requested) return false;
|
|
375
|
+
const current = latestSkillBodies(currentSkillContext(session))
|
|
376
|
+
.find(entry => entry.name === requested.name);
|
|
377
|
+
return !!current && skillMessageText(current.message.content).trimStart() === body;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Build the Skill tool-result envelope used by BOTH the agent-loop viewSkill
|
|
382
|
+
* path and the runtime skillToolContent path so behavior matches across main
|
|
383
|
+
* + agent sessions:
|
|
384
|
+
* - result = short stub (`Loaded skill: <name>`), no body.
|
|
385
|
+
* - newMessages = exactly ONE role:'user' message carrying the full
|
|
386
|
+
* buildSkillResultEnvelope output (<base-dir> + body).
|
|
387
|
+
* The injected user message is flagged `meta:'skill'` so compaction's
|
|
388
|
+
* "latest human prompt" selection does not mistake the skill body for the
|
|
389
|
+
* human's request.
|
|
390
|
+
*
|
|
391
|
+
* Passing `session` makes an unchanged repeat load body-free. Tool
|
|
392
|
+
* dependencies are still reported, so a
|
|
393
|
+
* repeat call re-arms the skill's deferred tools exactly as before.
|
|
394
|
+
*/
|
|
395
|
+
export function buildSkillToolEnvelope(name, content, skillDir, {
|
|
396
|
+
source = 'global', toolDependencies = [], dependencyIssues = [],
|
|
397
|
+
} = {}, session = null) {
|
|
398
|
+
const body = buildSkillResultEnvelope(name, content, skillDir);
|
|
399
|
+
const alreadyLoaded = skillBodyPresentInSession(session, body);
|
|
400
|
+
return {
|
|
401
|
+
__toolEnvelope: true,
|
|
402
|
+
result: alreadyLoaded
|
|
403
|
+
? `${buildSkillStub(name, source)} (already active; instructions unchanged — follow them without calling Skill again)`
|
|
404
|
+
: buildSkillStub(name, source),
|
|
405
|
+
...(toolDependencies.length ? { skillToolDependencies: toolDependencies } : {}),
|
|
406
|
+
...(dependencyIssues.length ? { skillDependencyIssues: dependencyIssues } : {}),
|
|
407
|
+
newMessages: alreadyLoaded ? [] : [
|
|
408
|
+
{ role: 'user', content: body, meta: 'skill' },
|
|
409
|
+
],
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Only selection triggers enter the model's listing. Descriptions belong to
|
|
414
|
+
// the UI; operating instructions arrive in the body through Skill().
|
|
415
|
+
const SKILL_MANIFEST_TRIGGER_MAX = 250;
|
|
416
|
+
const SKILL_MANIFEST_TRIGGER_MIN = 60;
|
|
417
|
+
// Whole-manifest ceiling (~1% of a 200k-token window at 4 chars/token).
|
|
418
|
+
const SKILL_MANIFEST_CHAR_BUDGET = 8_000;
|
|
419
|
+
|
|
420
|
+
function compactSkillManifestText(value, max = SKILL_MANIFEST_TRIGGER_MAX) {
|
|
421
|
+
return compactPromptManifestText(value, max);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function skillManifestToolNames(skill) {
|
|
425
|
+
const dependencies = Array.isArray(skill?.toolDependencies) ? skill.toolDependencies : [];
|
|
426
|
+
return [...new Set(dependencies
|
|
427
|
+
.filter((entry) => ['tool', 'mcp'].includes(entry?.type)
|
|
428
|
+
&& /^[A-Za-z0-9_.:-]+$/.test(String(entry?.value || '')))
|
|
429
|
+
.map((entry) => entry.type === 'mcp' ? `mcp:${entry.value}` : entry.value))];
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Build the compact skill manifest shown to the model.
|
|
434
|
+
* Full SKILL.md content is still loaded only through Skill(name).
|
|
435
|
+
*/
|
|
436
|
+
export function buildSkillManifest(skills, { limit = 80, charBudget = SKILL_MANIFEST_CHAR_BUDGET } = {}) {
|
|
437
|
+
if (skillsDisabled()) return '';
|
|
438
|
+
const list = (Array.isArray(skills) ? skills : [])
|
|
439
|
+
.map((skill) => ({
|
|
440
|
+
name: String(skill?.name || '').trim(),
|
|
441
|
+
trigger: String(skill?.whenToUse || '').trim(),
|
|
442
|
+
linkedTools: skillManifestToolNames(skill),
|
|
443
|
+
}))
|
|
444
|
+
.filter((skill) => skill.name)
|
|
445
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
446
|
+
if (!list.length) return '';
|
|
447
|
+
const max = Math.max(1, Number(limit) || 80);
|
|
448
|
+
const visible = list.slice(0, max);
|
|
449
|
+
// Names are never truncated; the shared budget only shrinks triggers,
|
|
450
|
+
// evenly, down to the readable floor.
|
|
451
|
+
const budget = Math.max(1_000, Number(charBudget) || SKILL_MANIFEST_CHAR_BUDGET);
|
|
452
|
+
const toolSuffix = (skill) => skill.linkedTools.length ? ` [tools: ${skill.linkedTools.join(', ')}]` : '';
|
|
453
|
+
const nameOverhead = visible.reduce((sum, skill) => sum + skill.name.length + toolSuffix(skill).length + 4, 0);
|
|
454
|
+
const perEntry = Math.floor((budget - nameOverhead) / visible.length);
|
|
455
|
+
const triggerCap = Math.min(
|
|
456
|
+
SKILL_MANIFEST_TRIGGER_MAX,
|
|
457
|
+
Math.max(SKILL_MANIFEST_TRIGGER_MIN, Number.isFinite(perEntry) ? perEntry : SKILL_MANIFEST_TRIGGER_MAX),
|
|
458
|
+
);
|
|
459
|
+
for (const skill of visible) skill.trigger = compactSkillManifestText(skill.trigger, triggerCap);
|
|
460
|
+
const lines = [
|
|
461
|
+
'# available-skills',
|
|
462
|
+
'Selection triggers and linked tools for Skill({"name":"<skill-name>"}). mcp:<server> denotes that server’s tools.',
|
|
463
|
+
'<available_skills>',
|
|
464
|
+
...visible.map((skill) => (skill.trigger ? `- ${skill.name}: ${skill.trigger}` : `- ${skill.name}`) + toolSuffix(skill)),
|
|
465
|
+
...(list.length > visible.length ? [`- ... ${list.length - visible.length} more skills omitted`] : []),
|
|
466
|
+
'</available_skills>',
|
|
467
|
+
];
|
|
468
|
+
return lines.join('\n');
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Build the fixed skill loader meta-tool.
|
|
473
|
+
* A tiny stable schema keeps provider cache keys steady; concrete skill
|
|
474
|
+
* listings/content are resolved at call time.
|
|
475
|
+
*
|
|
476
|
+
* The structure is constant regardless of how many skills are in scope.
|
|
477
|
+
* Non-agent sessions only expose the loader when a skill exists; agent
|
|
478
|
+
* sessions always expose it so the schema shape stays fixed. Memoise so
|
|
479
|
+
* every createSession doesn't rebuild
|
|
480
|
+
* identical objects (trivial work, but the allocation noise shows up in
|
|
481
|
+
* repeated Pool C fan-out).
|
|
482
|
+
*/
|
|
483
|
+
let _skillToolDefsCache = null;
|
|
484
|
+
/**
|
|
485
|
+
* @param {Array} skills — discovered skill frontmatter list (may be empty)
|
|
486
|
+
* @param {object} [opts]
|
|
487
|
+
* @param {boolean} [opts.ownerIsAgentSession=false]
|
|
488
|
+
* Agent sessions ALWAYS include the meta-tool regardless of the current
|
|
489
|
+
* cwd's skill inventory — the concrete skill list is resolved at tool-call
|
|
490
|
+
* time (cwd-scoped) so the tool schema stays bit-identical across roles /
|
|
491
|
+
* cwds and the provider cache shard does not fragment.
|
|
492
|
+
* Non-agent sessions keep the historical "empty when skills.length===0"
|
|
493
|
+
* behaviour.
|
|
494
|
+
*/
|
|
495
|
+
export function buildSkillToolDefs(skills, { ownerIsAgentSession = false } = {}) {
|
|
496
|
+
if (skillsDisabled()) return [];
|
|
497
|
+
if (!ownerIsAgentSession && !skills.length) return [];
|
|
498
|
+
if (_skillToolDefsCache) return _skillToolDefsCache;
|
|
499
|
+
_skillToolDefsCache = [
|
|
500
|
+
{
|
|
501
|
+
name: 'Skill',
|
|
502
|
+
title: 'Skill',
|
|
503
|
+
annotations: {
|
|
504
|
+
title: 'Skill',
|
|
505
|
+
readOnlyHint: true,
|
|
506
|
+
destructiveHint: false,
|
|
507
|
+
idempotentHint: true,
|
|
508
|
+
openWorldHint: false,
|
|
509
|
+
agentHidden: false,
|
|
510
|
+
},
|
|
511
|
+
description: 'Load or refresh an available skill’s SKILL.md before task actions when its body is missing or needs an update. Reuse a body already in context for matching requests; a later turn or repeated mention is not a reason to call Skill again.',
|
|
512
|
+
inputSchema: {
|
|
513
|
+
type: 'object',
|
|
514
|
+
properties: {
|
|
515
|
+
name: { type: 'string', description: 'Exact name from available-skills.' },
|
|
516
|
+
},
|
|
517
|
+
required: ['name'],
|
|
518
|
+
additionalProperties: false,
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
];
|
|
522
|
+
return _skillToolDefsCache;
|
|
523
|
+
}
|
|
@@ -17,6 +17,11 @@ export function isAccountQuotaError(error) {
|
|
|
17
17
|
.includes(String(typedErrorCode(error) || error?.error?.type || '').toLowerCase());
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// Burst throttles reopen within seconds. A 429 whose Retry-After points
|
|
21
|
+
// minutes or days ahead is a closed subscription window, whichever scoped
|
|
22
|
+
// limit the usage endpoint attributes it to.
|
|
23
|
+
const QUOTA_WINDOW_RETRY_AFTER_MS = 60_000;
|
|
24
|
+
|
|
20
25
|
export function createAccountPoolProvider(providerName, create) {
|
|
21
26
|
if (!ACCOUNT_PROVIDERS.includes(providerName) || hasExplicitProviderAuthBinding(providerName)) return create();
|
|
22
27
|
const instances = new Map();
|
|
@@ -86,8 +91,9 @@ export function createAccountPoolProvider(providerName, create) {
|
|
|
86
91
|
return result;
|
|
87
92
|
} catch (error) {
|
|
88
93
|
if (options.signal?.aborted) throw error;
|
|
89
|
-
let exhausted = isAccountQuotaError(error);
|
|
90
94
|
const status = Number(error?.status || error?.httpStatus || error?.response?.status || 0);
|
|
95
|
+
const delay = retryAfterMsFromError(error);
|
|
96
|
+
let exhausted = isAccountQuotaError(error) || (status === 429 && delay > QUOTA_WINDOW_RETRY_AFTER_MS);
|
|
91
97
|
// Anthropic and other subscriptions may use the same 429 code for
|
|
92
98
|
// burst throttling and exhausted subscription windows. Ask the native
|
|
93
99
|
// usage endpoint rather than interpreting a human-readable message.
|
|
@@ -96,7 +102,6 @@ export function createAccountPoolProvider(providerName, create) {
|
|
|
96
102
|
exhausted = providerAccountExhausted(readProviderAccountPool(providerName).accounts.find((entry) => entry.id === row.id));
|
|
97
103
|
}
|
|
98
104
|
if (options.signal?.aborted || !exhausted) throw error;
|
|
99
|
-
const delay = retryAfterMsFromError(error);
|
|
100
105
|
blockProviderAccount(providerName, row.id, Date.now() + (delay > 0 ? delay : 5 * 60_000));
|
|
101
106
|
if (pool.auto === false || emitted || error.unsafeToRetry || error.liveTextEmitted || error.emittedToolCall) throw error;
|
|
102
107
|
lastError = error;
|
|
@@ -263,6 +263,17 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
263
263
|
let model = '';
|
|
264
264
|
let toolCalls = [];
|
|
265
265
|
let usage = { inputTokens: 0, outputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, raw: null };
|
|
266
|
+
const updateUsage = (raw) => {
|
|
267
|
+
// Final usage can revise any slot. Omitted fields preserve the earlier
|
|
268
|
+
// report; explicit zero replaces it. Update before terminal early exits.
|
|
269
|
+
if (raw.input_tokens != null) usage.inputTokens = raw.input_tokens;
|
|
270
|
+
if (raw.output_tokens != null) usage.outputTokens = raw.output_tokens;
|
|
271
|
+
if (raw.cache_read_input_tokens != null) usage.cachedTokens = raw.cache_read_input_tokens;
|
|
272
|
+
if (raw.cache_creation_input_tokens != null) usage.cacheWriteTokens = raw.cache_creation_input_tokens;
|
|
273
|
+
usage.raw = { ...(usage.raw || {}), ...raw };
|
|
274
|
+
// Input excludes cache; all three slots contribute to prompt volume.
|
|
275
|
+
usage.promptTokens = usage.inputTokens + usage.cachedTokens + usage.cacheWriteTokens;
|
|
276
|
+
};
|
|
266
277
|
let stopReason = null;
|
|
267
278
|
let stopDetails;
|
|
268
279
|
const fallbackEvents = [];
|
|
@@ -565,10 +576,7 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
565
576
|
try { onStreamDelta?.('semantic'); } catch {}
|
|
566
577
|
if (event.message.model) model = event.message.model;
|
|
567
578
|
if (event.message.usage) {
|
|
568
|
-
|
|
569
|
-
usage.cachedTokens = event.message.usage.cache_read_input_tokens || 0;
|
|
570
|
-
usage.cacheWriteTokens = event.message.usage.cache_creation_input_tokens || 0;
|
|
571
|
-
usage.raw = { ...event.message.usage };
|
|
579
|
+
updateUsage(event.message.usage);
|
|
572
580
|
}
|
|
573
581
|
}
|
|
574
582
|
|
|
@@ -835,8 +843,7 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
835
843
|
};
|
|
836
844
|
}
|
|
837
845
|
if (event.usage) {
|
|
838
|
-
|
|
839
|
-
usage.raw = { ...(usage.raw || {}), ...event.usage };
|
|
846
|
+
updateUsage(event.usage);
|
|
840
847
|
}
|
|
841
848
|
// A terminal stop_reason while ANY tool input is still
|
|
842
849
|
// streaming ends the turn truncated, immediately: the
|
|
@@ -881,14 +888,6 @@ export async function parseSSEStream(response, signal, abortStream, onStreamDelt
|
|
|
881
888
|
// out of streamLoop the moment the message ends.
|
|
882
889
|
break streamLoop;
|
|
883
890
|
}
|
|
884
|
-
// Unified prompt volume — what the model actually ingested.
|
|
885
|
-
// Anthropic splits input into three billable slots (uncached
|
|
886
|
-
// input + cache_read + cache_create); keep them separate for
|
|
887
|
-
// cost math but also expose the sum so cross-provider logs
|
|
888
|
-
// have a consistent `promptTokens` meaning.
|
|
889
|
-
usage.promptTokens = (usage.inputTokens || 0)
|
|
890
|
-
+ (usage.cachedTokens || 0)
|
|
891
|
-
+ (usage.cacheWriteTokens || 0);
|
|
892
891
|
} catch (err) {
|
|
893
892
|
if (err?.code === 'EANTHROPIC_SSE_ERROR') throw err;
|
|
894
893
|
/* skip malformed events */
|