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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import { parseJsonText,
|
|
4
|
+
import { parseJsonText, updateJsonAtomic } from './atomic-file.mjs';
|
|
5
5
|
import { resolvePluginData } from './plugin-paths.mjs';
|
|
6
6
|
|
|
7
7
|
export const ACCOUNT_PROVIDERS = Object.freeze([
|
|
@@ -9,6 +9,7 @@ export const ACCOUNT_PROVIDERS = Object.freeze([
|
|
|
9
9
|
'anthropic-oauth',
|
|
10
10
|
'grok-oauth',
|
|
11
11
|
'muse-code-oauth',
|
|
12
|
+
'command-code-oauth',
|
|
12
13
|
'mimo-oauth',
|
|
13
14
|
'cursor-oauth',
|
|
14
15
|
'antigravity-oauth',
|
|
@@ -74,16 +75,39 @@ function normalizeLabels(pool) {
|
|
|
74
75
|
return { ...pool, accounts };
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
// How the roster picks an account. `manual` serves from the selected account
|
|
79
|
+
// only; `limit` keeps the selected account until it reaches a limit and then
|
|
80
|
+
// moves to the account with the most headroom; `headroom` starts every new
|
|
81
|
+
// session on the account with the most headroom and keeps it for that session.
|
|
82
|
+
export const ACCOUNT_POLICIES = Object.freeze(['manual', 'limit', 'headroom']);
|
|
83
|
+
|
|
84
|
+
// `auto` predates `policy` and is still written beside it, so a build that
|
|
85
|
+
// knows only `auto` reads the same switching on/off. Whenever the two
|
|
86
|
+
// disagree (an older build toggled `auto`), `auto` decides whether switching
|
|
87
|
+
// is on at all.
|
|
88
|
+
export function providerAccountPolicy(pool) {
|
|
89
|
+
if (pool?.auto === false) return 'manual';
|
|
90
|
+
return pool?.policy === 'headroom' ? 'headroom' : 'limit';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function normalizePool(pool) {
|
|
94
|
+
if (!pool) return pool;
|
|
95
|
+
const policy = providerAccountPolicy(pool);
|
|
96
|
+
return { ...normalizeLabels(pool), policy, auto: policy !== 'manual' };
|
|
97
|
+
}
|
|
98
|
+
|
|
77
99
|
export function readProviderAccountPool(provider, dataDir = resolvePluginData()) {
|
|
78
100
|
requireAccountProvider(provider);
|
|
79
|
-
return
|
|
101
|
+
return (
|
|
102
|
+
normalizePool(read(dataDir).providers[provider]) || { accounts: [], selectedId: null, auto: true, policy: 'limit' }
|
|
103
|
+
);
|
|
80
104
|
}
|
|
81
105
|
|
|
82
|
-
function update(provider, mutate) {
|
|
106
|
+
async function update(provider, mutate) {
|
|
83
107
|
requireAccountProvider(provider);
|
|
84
108
|
// Read failures must not be interpreted as an empty account roster.
|
|
85
109
|
read();
|
|
86
|
-
const result =
|
|
110
|
+
const result = await updateJsonAtomic(
|
|
87
111
|
file(),
|
|
88
112
|
(raw) => {
|
|
89
113
|
const state = validate(raw);
|
|
@@ -94,7 +118,7 @@ function update(provider, mutate) {
|
|
|
94
118
|
},
|
|
95
119
|
{ mode: 0o600, secret: true, fsyncDir: true }
|
|
96
120
|
);
|
|
97
|
-
return
|
|
121
|
+
return normalizePool(result.providers[provider]);
|
|
98
122
|
}
|
|
99
123
|
|
|
100
124
|
/**
|
|
@@ -117,7 +141,7 @@ export function newProviderAccountId() {
|
|
|
117
141
|
return randomUUID();
|
|
118
142
|
}
|
|
119
143
|
|
|
120
|
-
export function registerProviderAccount(provider, id, { label = '', includeDefault = false } = {}) {
|
|
144
|
+
export async function registerProviderAccount(provider, id, { label = '', includeDefault = false } = {}) {
|
|
121
145
|
requireAccountId(id);
|
|
122
146
|
return update(provider, (pool) => {
|
|
123
147
|
if (includeDefault && !pool.accounts.some((row) => row.id === 'default')) {
|
|
@@ -131,12 +155,12 @@ export function registerProviderAccount(provider, id, { label = '', includeDefau
|
|
|
131
155
|
});
|
|
132
156
|
}
|
|
133
157
|
|
|
134
|
-
export function changeProviderAccounts(provider, change) {
|
|
158
|
+
export async function changeProviderAccounts(provider, change) {
|
|
135
159
|
if (
|
|
136
160
|
!change ||
|
|
137
161
|
typeof change !== 'object' ||
|
|
138
162
|
Array.isArray(change) ||
|
|
139
|
-
Object.keys(change).some((key) => !['selectedId', 'order', 'auto', 'useCredits', 'rename'].includes(key))
|
|
163
|
+
Object.keys(change).some((key) => !['selectedId', 'order', 'auto', 'policy', 'useCredits', 'rename'].includes(key))
|
|
140
164
|
) {
|
|
141
165
|
throw new TypeError('Invalid provider account change.');
|
|
142
166
|
}
|
|
@@ -175,8 +199,19 @@ export function changeProviderAccounts(provider, change) {
|
|
|
175
199
|
}
|
|
176
200
|
if (change.auto !== undefined) {
|
|
177
201
|
if (typeof change.auto !== 'boolean') throw new TypeError('Automatic account switching must be boolean.');
|
|
202
|
+
// Turning switching on keeps a non-manual policy; on from manual is the
|
|
203
|
+
// classic switch-at-limit behaviour.
|
|
204
|
+
const current = providerAccountPolicy(pool);
|
|
205
|
+
if (!change.auto) pool.policy = 'manual';
|
|
206
|
+
else if (current === 'manual') pool.policy = 'limit';
|
|
207
|
+
else pool.policy = current;
|
|
178
208
|
pool.auto = change.auto;
|
|
179
209
|
}
|
|
210
|
+
if (change.policy !== undefined) {
|
|
211
|
+
if (!ACCOUNT_POLICIES.includes(change.policy)) throw new TypeError('Unknown account switching policy.');
|
|
212
|
+
pool.policy = change.policy;
|
|
213
|
+
pool.auto = change.policy !== 'manual';
|
|
214
|
+
}
|
|
180
215
|
if (change.useCredits !== undefined) {
|
|
181
216
|
if (typeof change.useCredits !== 'boolean') throw new TypeError('Credit use must be boolean.');
|
|
182
217
|
pool.useCredits = change.useCredits;
|
|
@@ -184,7 +219,24 @@ export function changeProviderAccounts(provider, change) {
|
|
|
184
219
|
});
|
|
185
220
|
}
|
|
186
221
|
|
|
187
|
-
|
|
222
|
+
// Selects `id` only while the selection is still `expectedSelectedId`, checked
|
|
223
|
+
// under the write's own lock, so a newer selection (a manual switch) wins. A
|
|
224
|
+
// given `expectedPolicy` must also still hold: a policy change while the
|
|
225
|
+
// request ran (e.g. to Manual) cancels the commit.
|
|
226
|
+
// Returns the pool when the selection moved, otherwise null.
|
|
227
|
+
export async function selectProviderAccountIf(provider, expectedSelectedId, id, expectedPolicy) {
|
|
228
|
+
requireAccountId(id);
|
|
229
|
+
let moved = false;
|
|
230
|
+
const pool = await update(provider, (pool) => {
|
|
231
|
+
if (pool.selectedId !== expectedSelectedId || !pool.accounts.some((row) => row.id === id)) return;
|
|
232
|
+
if (expectedPolicy && providerAccountPolicy(pool) !== expectedPolicy) return;
|
|
233
|
+
pool.selectedId = id;
|
|
234
|
+
moved = true;
|
|
235
|
+
});
|
|
236
|
+
return moved ? pool : null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function removeProviderAccount(provider, id) {
|
|
188
240
|
requireAccountId(id);
|
|
189
241
|
return update(provider, (pool) => {
|
|
190
242
|
pool.accounts = pool.accounts.filter((row) => row.id !== id);
|
|
@@ -192,7 +244,7 @@ export function removeProviderAccount(provider, id) {
|
|
|
192
244
|
});
|
|
193
245
|
}
|
|
194
246
|
|
|
195
|
-
export function recordProviderAccountUsage(provider, id, snapshot) {
|
|
247
|
+
export async function recordProviderAccountUsage(provider, id, snapshot) {
|
|
196
248
|
if (!snapshot || !Array.isArray(snapshot.quotaWindows)) return;
|
|
197
249
|
const windows = snapshot.quotaWindows.slice(0, 12).map((row) => ({
|
|
198
250
|
label: String(row.label || '').slice(0, 40),
|
|
@@ -200,7 +252,7 @@ export function recordProviderAccountUsage(provider, id, snapshot) {
|
|
|
200
252
|
resetAt: typeof row.resetAt === 'number' && Number.isFinite(row.resetAt) ? row.resetAt : null,
|
|
201
253
|
}));
|
|
202
254
|
if (!readProviderAccountPool(provider).accounts.some((row) => row.id === id)) return;
|
|
203
|
-
update(provider, (pool) => {
|
|
255
|
+
await update(provider, (pool) => {
|
|
204
256
|
const row = pool.accounts.find((entry) => entry.id === id);
|
|
205
257
|
if (!row) return;
|
|
206
258
|
row.usage = { windows, checkedAt: Date.now() };
|
|
@@ -225,7 +277,7 @@ const MAX_BLOCK_MS = 6 * 60 * 60_000;
|
|
|
225
277
|
// early can tell us anything.
|
|
226
278
|
// `options.quota` records that the block came from a typed quota refusal or an
|
|
227
279
|
// exhausted usage window, so later sends still count it as quota evidence.
|
|
228
|
-
export function blockProviderAccount(provider, id, until, model = '', source = 'inferred', options = {}) {
|
|
280
|
+
export async function blockProviderAccount(provider, id, until, model = '', source = 'inferred', options = {}) {
|
|
229
281
|
return update(provider, (pool) => {
|
|
230
282
|
const row = pool.accounts.find((entry) => entry.id === id);
|
|
231
283
|
if (!row) return;
|
|
@@ -245,7 +297,7 @@ export function blockProviderAccount(provider, id, until, model = '', source = '
|
|
|
245
297
|
// the refusal window and the meter recorded against the OLD subscription would
|
|
246
298
|
// otherwise outlive it and refuse a request the provider would have served.
|
|
247
299
|
// Both are dropped; the next usage reading refills them.
|
|
248
|
-
export function clearProviderAccountQuotaState(provider, id) {
|
|
300
|
+
export async function clearProviderAccountQuotaState(provider, id) {
|
|
249
301
|
return update(provider, (pool) => {
|
|
250
302
|
const row = pool.accounts.find((entry) => entry.id === id);
|
|
251
303
|
if (!row) return;
|
|
@@ -318,14 +370,53 @@ export function providerAccountsResetAt(rows, now = Date.now(), model = '', opti
|
|
|
318
370
|
return known.length ? Math.min(...known) : null;
|
|
319
371
|
}
|
|
320
372
|
|
|
373
|
+
function windowFull(row, window, now) {
|
|
374
|
+
return window.usedPct >= 100 && (window.resetAt ? window.resetAt > now : now - row.usage.checkedAt < 60_000);
|
|
375
|
+
}
|
|
376
|
+
|
|
321
377
|
export function providerAccountExhausted(row, now = Date.now(), model = '', options = {}) {
|
|
322
378
|
if (row?.blockedUntil > now && blockAppliesToModel(row, model)) return true;
|
|
323
379
|
if (options.useCredits === true) return false;
|
|
324
380
|
return (row?.usage?.windows || []).some(
|
|
325
|
-
(window) =>
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
381
|
+
(window) => quotaWindowAppliesToModel(window, model) && windowFull(row, window, now)
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function quotaWindowFamily(window) {
|
|
386
|
+
return FAMILY_QUOTA_WINDOW.exec(String(window?.label || '').trim())?.[1] || '';
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Which limit currently holds the account, independent of any one model:
|
|
390
|
+
// `{ scope: 'account' }` when every request is refused (an unscoped window is
|
|
391
|
+
// full, or a block with no narrower meter to attribute it to), `{ scope:
|
|
392
|
+
// 'model', families }` when only model-family meters ("7D Fable") or a block
|
|
393
|
+
// attributed to one are spent, so other models still run — else null.
|
|
394
|
+
export function providerAccountLimit(row, now = Date.now(), options = {}) {
|
|
395
|
+
if (!row) return null;
|
|
396
|
+
const windows = row.usage?.windows || [];
|
|
397
|
+
const blocked = row.blockedUntil > now;
|
|
398
|
+
const scope = String(row.blockedModel || '').trim();
|
|
399
|
+
const scopedWindows = windows.filter((window) => quotaWindowFamily(window));
|
|
400
|
+
// A block counts as family-scoped only when a family meter covers the model
|
|
401
|
+
// that earned it; any other refusal is treated as the account's.
|
|
402
|
+
const covering = scope ? scopedWindows.filter((window) => quotaWindowAppliesToModel(window, scope)) : [];
|
|
403
|
+
if (blocked && !covering.length) return { scope: 'account' };
|
|
404
|
+
const credits = options.useCredits === true;
|
|
405
|
+
if (!credits && windows.some((window) => !quotaWindowFamily(window) && windowFull(row, window, now))) {
|
|
406
|
+
return { scope: 'account' };
|
|
407
|
+
}
|
|
408
|
+
const families = new Set(blocked ? covering.map(quotaWindowFamily) : []);
|
|
409
|
+
if (!credits) {
|
|
410
|
+
for (const window of scopedWindows) if (windowFull(row, window, now)) families.add(quotaWindowFamily(window));
|
|
411
|
+
}
|
|
412
|
+
return families.size ? { scope: 'model', families: [...families] } : null;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// The account refuses `model` only through a model-scoped limit: it still
|
|
416
|
+
// serves other models, so it remains the provider's account of record.
|
|
417
|
+
export function providerAccountExhaustedForModelOnly(row, now = Date.now(), model = '', options = {}) {
|
|
418
|
+
return (
|
|
419
|
+
providerAccountExhausted(row, now, model, options) && providerAccountLimit(row, now, options)?.scope === 'model'
|
|
329
420
|
);
|
|
330
421
|
}
|
|
331
422
|
|
|
@@ -347,15 +438,28 @@ function accountHeadroom(row, now, model) {
|
|
|
347
438
|
return used.length ? 100 - Math.max(...used) : null;
|
|
348
439
|
}
|
|
349
440
|
|
|
350
|
-
|
|
441
|
+
// `sessionAccountId` is the account the requesting session already runs on
|
|
442
|
+
// (headroom policy only): kept while usable, so a session switches only on a
|
|
443
|
+
// limit. Without one, the session's first request takes the account with the
|
|
444
|
+
// most headroom.
|
|
445
|
+
export function chooseProviderAccount(
|
|
446
|
+
pool,
|
|
447
|
+
excluded = new Set(),
|
|
448
|
+
now = Date.now(),
|
|
449
|
+
model = '',
|
|
450
|
+
{ sessionAccountId } = {}
|
|
451
|
+
) {
|
|
351
452
|
const options = { useCredits: pool.useCredits === true };
|
|
352
453
|
const usable = (row) => row && !excluded.has(row.id) && !providerAccountExhausted(row, now, model, options);
|
|
353
454
|
const selected = pool.accounts.find((row) => row.id === pool.selectedId);
|
|
354
|
-
|
|
455
|
+
const policy = providerAccountPolicy(pool);
|
|
456
|
+
if (policy === 'manual') {
|
|
355
457
|
if (excluded.has(selected?.id)) return null;
|
|
356
458
|
return options.useCredits || usable(selected) ? selected || null : null;
|
|
357
459
|
}
|
|
358
|
-
|
|
460
|
+
const preferred =
|
|
461
|
+
policy === 'headroom' ? pool.accounts.find((row) => sessionAccountId && row.id === sessionAccountId) : selected;
|
|
462
|
+
if (usable(preferred)) return preferred;
|
|
359
463
|
// Falling back in roster order is blind: the account after the one that just
|
|
360
464
|
// refused may itself be nearly spent, so the request pays a second switch to
|
|
361
465
|
// learn what a recorded meter already knew. Ranking needs evidence for EVERY
|
|
@@ -69,6 +69,18 @@ export function getAgentApiKey(provider) {
|
|
|
69
69
|
return null;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/** The one API-key resolver for key-based providers: an explicit config key
|
|
73
|
+
* wins, otherwise the environment/keychain (getAgentApiKey). */
|
|
74
|
+
export function resolveProviderApiKey(provider, config) {
|
|
75
|
+
return config?.apiKey || getAgentApiKey(provider) || '';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Auth-error reload: the stored/environment key is re-read first so a
|
|
79
|
+
* rotated key beats the stale one captured in config. */
|
|
80
|
+
export function reloadProviderApiKey(provider, config) {
|
|
81
|
+
return getAgentApiKey(provider) || config?.apiKey || '';
|
|
82
|
+
}
|
|
83
|
+
|
|
72
84
|
/** One provider's key read on demand, warming no other account: the
|
|
73
85
|
* environment first (getAgentApiKey's precedence), then only this provider's
|
|
74
86
|
* keychain entry through the async path (no whole-keyring snapshot). */
|
|
@@ -11,9 +11,15 @@
|
|
|
11
11
|
export const POWERSHELL_UTF8_PREFIX =
|
|
12
12
|
'[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; $OutputEncoding=New-Object System.Text.UTF8Encoding $false;';
|
|
13
13
|
|
|
14
|
+
// The `mixdog-skill-node` definition PowerShell commands carry next
|
|
15
|
+
// (renderPowerShellSkillFunction) is the same kind of execution detail.
|
|
16
|
+
const SKILL_FUNCTION_LINE = /^[ \t]*function mixdog-skill-node \{[^\r\n]*\r?\n/;
|
|
17
|
+
|
|
14
18
|
export function displayShellCommand(value) {
|
|
15
19
|
const text = String(value ?? '');
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
let rest = text.trimStart();
|
|
21
|
+
const prefixed = rest.startsWith(POWERSHELL_UTF8_PREFIX);
|
|
22
|
+
if (prefixed) rest = rest.slice(POWERSHELL_UTF8_PREFIX.length).replace(/^[ \t]*\r?\n/, '');
|
|
23
|
+
const withoutFunction = rest.replace(SKILL_FUNCTION_LINE, '');
|
|
24
|
+
return prefixed || withoutFunction !== rest ? withoutFunction : text;
|
|
19
25
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface SkillMention {
|
|
2
|
+
name: string;
|
|
3
|
+
start: number;
|
|
4
|
+
end: number;
|
|
5
|
+
}
|
|
6
|
+
export function isReservedMentionName(name: unknown): boolean;
|
|
7
|
+
export function isInMarkdownCode(text: unknown, offset: number): boolean;
|
|
8
|
+
export function skillMentionCandidates(text: unknown): SkillMention[];
|
|
9
|
+
export function skillMentions(text: unknown, isSkill: (name: string) => boolean): SkillMention[];
|
|
10
|
+
export function mentionedSkillNames(text: unknown, isSkill: (name: string) => boolean): string[];
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Inline `$name` skill mentions: a skill can be named anywhere in a prompt.
|
|
2
|
+
// A candidate is `$` at the start of the text or after whitespace or an
|
|
3
|
+
// opening bracket/quote, followed by a skill-name token (the same
|
|
4
|
+
// lowercase-letters, digits and hyphens rule, 64 characters at most, that
|
|
5
|
+
// skill-document.mjs enforces). It selects a skill only when the token is
|
|
6
|
+
// exactly a known skill name, so `$HOME`, `$300` and unknown names stay prose.
|
|
7
|
+
// The token ends where no ASCII identifier character follows (`$pptx_env`,
|
|
8
|
+
// `$pptxUPPER` are not `pptx`); any other character, Korean particles
|
|
9
|
+
// included (`$pptx로`), ends it.
|
|
10
|
+
const MENTION = /(^|[\s([{"'`])\$([a-z0-9][a-z0-9-]{0,63})(?![A-Za-z0-9_-])/g;
|
|
11
|
+
|
|
12
|
+
// Common environment-variable names (compared case-insensitively) and
|
|
13
|
+
// numeric-only tokens are never inline mentions, even when a skill has that
|
|
14
|
+
// name: `$path` or `$300` in prose means the variable or the amount. Choosing
|
|
15
|
+
// such a skill explicitly (the `Skill:` header chip) still works.
|
|
16
|
+
const RESERVED_NAMES = new Set([
|
|
17
|
+
'path',
|
|
18
|
+
'home',
|
|
19
|
+
'user',
|
|
20
|
+
'shell',
|
|
21
|
+
'pwd',
|
|
22
|
+
'tmpdir',
|
|
23
|
+
'temp',
|
|
24
|
+
'tmp',
|
|
25
|
+
'lang',
|
|
26
|
+
'term',
|
|
27
|
+
'xdg_config_home',
|
|
28
|
+
'userprofile',
|
|
29
|
+
'appdata',
|
|
30
|
+
'localappdata',
|
|
31
|
+
'programfiles',
|
|
32
|
+
'systemroot',
|
|
33
|
+
'windir',
|
|
34
|
+
'comspec',
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
/** True when `name` can never be an inline mention (env var or number). */
|
|
38
|
+
export function isReservedMentionName(name) {
|
|
39
|
+
const value = String(name ?? '');
|
|
40
|
+
return /^[0-9]+$/.test(value) || RESERVED_NAMES.has(value.toLowerCase());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Markdown code is never a mention: fenced blocks (``` or ~~~, 3+ chars,
|
|
44
|
+
// optional info string, closed by a fence of the same char at least as long,
|
|
45
|
+
// else running to the end) and inline code spans (a backtick run closed by
|
|
46
|
+
// the next run of exactly the same length; an unmatched run is literal).
|
|
47
|
+
// Deliberate Mixdog difference: Codex (codex-rs/skills/src/mentions.rs) scans
|
|
48
|
+
// raw bytes with no code handling. Offsets stay those of the original text,
|
|
49
|
+
// so desktop highlighting lines up (and never lands inside code).
|
|
50
|
+
const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})(.*)$/;
|
|
51
|
+
const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
|
|
52
|
+
|
|
53
|
+
function inlineCodeRanges(source, from, to, ranges) {
|
|
54
|
+
const runs = [];
|
|
55
|
+
const re = /`+/g;
|
|
56
|
+
re.lastIndex = from;
|
|
57
|
+
for (let match = re.exec(source); match && match.index < to; match = re.exec(source)) {
|
|
58
|
+
runs.push({ start: match.index, end: Math.min(to, match.index + match[0].length) });
|
|
59
|
+
}
|
|
60
|
+
for (let index = 0; index < runs.length; index += 1) {
|
|
61
|
+
const length = runs[index].end - runs[index].start;
|
|
62
|
+
const close = runs.findIndex((run, at) => at > index && run.end - run.start === length);
|
|
63
|
+
if (close < 0) continue;
|
|
64
|
+
ranges.push([runs[index].start, runs[close].end]);
|
|
65
|
+
index = close;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** [start, end) offsets of Markdown code (fenced blocks and inline spans). */
|
|
70
|
+
function codeRanges(source) {
|
|
71
|
+
const ranges = [];
|
|
72
|
+
let open = null;
|
|
73
|
+
let proseStart = 0;
|
|
74
|
+
for (let at = 0; at <= source.length; ) {
|
|
75
|
+
const newline = source.indexOf('\n', at);
|
|
76
|
+
const end = newline < 0 ? source.length : newline;
|
|
77
|
+
const next = newline < 0 ? source.length + 1 : newline + 1;
|
|
78
|
+
const line = source.slice(at, end).replace(/\r$/, '');
|
|
79
|
+
if (!open) {
|
|
80
|
+
const fence = FENCE_OPEN.exec(line);
|
|
81
|
+
if (fence && !(fence[1][0] === '`' && fence[2].includes('`'))) {
|
|
82
|
+
inlineCodeRanges(source, proseStart, at, ranges);
|
|
83
|
+
open = { char: fence[1][0], length: fence[1].length, start: at };
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
const fence = FENCE_CLOSE.exec(line);
|
|
87
|
+
if (fence && fence[1][0] === open.char && fence[1].length >= open.length) {
|
|
88
|
+
ranges.push([open.start, Math.min(next, source.length)]);
|
|
89
|
+
proseStart = Math.min(next, source.length);
|
|
90
|
+
open = null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
at = next;
|
|
94
|
+
}
|
|
95
|
+
if (open) ranges.push([open.start, source.length]);
|
|
96
|
+
else inlineCodeRanges(source, proseStart, source.length, ranges);
|
|
97
|
+
return ranges;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** True when `offset` in `text` lies inside Markdown code (a fenced block or
|
|
101
|
+
* an inline code span) by the same rules mention recognition uses, so a
|
|
102
|
+
* composer can tell whether a `$` being typed can become a mention. */
|
|
103
|
+
export function isInMarkdownCode(text, offset) {
|
|
104
|
+
const source = typeof text === 'string' ? text : '';
|
|
105
|
+
if (!Number.isInteger(offset) || (!source.includes('`') && !source.includes('~~~'))) return false;
|
|
106
|
+
return codeRanges(source).some(([from, to]) => offset >= from && offset < to);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Every syntactically valid `$name` in `text` outside Markdown code, in
|
|
110
|
+
* order: `start` is the `$`. */
|
|
111
|
+
export function skillMentionCandidates(text) {
|
|
112
|
+
const source = typeof text === 'string' ? text : '';
|
|
113
|
+
const found = [];
|
|
114
|
+
let code = null;
|
|
115
|
+
for (const match of source.matchAll(MENTION)) {
|
|
116
|
+
if (isReservedMentionName(match[2])) continue;
|
|
117
|
+
const start = match.index + match[1].length;
|
|
118
|
+
code ??= source.includes('`') || source.includes('~~~') ? codeRanges(source) : [];
|
|
119
|
+
if (code.some(([from, to]) => start >= from && start < to)) continue;
|
|
120
|
+
found.push({ name: match[2], start, end: start + 1 + match[2].length });
|
|
121
|
+
}
|
|
122
|
+
return found;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Candidates whose name `isSkill(name)` accepts (all occurrences, in order). */
|
|
126
|
+
export function skillMentions(text, isSkill) {
|
|
127
|
+
return skillMentionCandidates(text).filter((mention) => isSkill(mention.name));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Distinct mentioned skill names in first-mention order. */
|
|
131
|
+
export function mentionedSkillNames(text, isSkill) {
|
|
132
|
+
return [...new Set(skillMentions(text, isSkill).map((mention) => mention.name))];
|
|
133
|
+
}
|