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
|
@@ -50,6 +50,7 @@ export class ModelProxyChannel {
|
|
|
50
50
|
attachPublisher(publish: (snapshot: ModelProxySnapshot) => void): () => void;
|
|
51
51
|
disconnect(reason?: string): void;
|
|
52
52
|
setClientPresent(present: boolean): void;
|
|
53
|
+
onStateChange(listener: () => void): () => void;
|
|
53
54
|
request(
|
|
54
55
|
call: Record<string, unknown>,
|
|
55
56
|
options?: { signal?: AbortSignal | null; callbacks?: Record<string, (...args: unknown[]) => unknown> }
|
|
@@ -65,3 +66,17 @@ export class ModelProxyChannel {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
export function getModelProxyChannel(): ModelProxyChannel;
|
|
69
|
+
|
|
70
|
+
/** The channel surface model-proxy providers use; the bridge child implements only this. */
|
|
71
|
+
export interface ModelProxyChannelLike {
|
|
72
|
+
readonly connected: boolean;
|
|
73
|
+
readonly generation: number;
|
|
74
|
+
whenConnected(timeoutMs: number): Promise<boolean>;
|
|
75
|
+
request(
|
|
76
|
+
call: Record<string, unknown>,
|
|
77
|
+
options?: { signal?: AbortSignal | null; callbacks?: Record<string, (...args: unknown[]) => unknown> }
|
|
78
|
+
): Promise<unknown>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Swap the process-wide channel; the returned function restores the previous one. */
|
|
82
|
+
export function installModelProxyChannel(channel: ModelProxyChannel | ModelProxyChannelLike): () => void;
|
|
@@ -243,8 +243,24 @@ export class ModelProxyChannel {
|
|
|
243
243
|
});
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
#stateListeners = new Set();
|
|
247
|
+
|
|
248
|
+
/** `listener()` runs after every presence/generation change (the model
|
|
249
|
+
* proxy bridge pushes it to its clients). Returns the unsubscribe. */
|
|
250
|
+
onStateChange(listener) {
|
|
251
|
+
this.#stateListeners.add(listener);
|
|
252
|
+
return () => this.#stateListeners.delete(listener);
|
|
253
|
+
}
|
|
254
|
+
|
|
246
255
|
#connectionChanged() {
|
|
247
256
|
this.#generation += 1;
|
|
257
|
+
for (const listener of [...this.#stateListeners]) {
|
|
258
|
+
try {
|
|
259
|
+
listener();
|
|
260
|
+
} catch {
|
|
261
|
+
/* a listener never breaks the transport that reported the change */
|
|
262
|
+
}
|
|
263
|
+
}
|
|
248
264
|
if (!this.connected) return;
|
|
249
265
|
for (const waiter of [...this.#connectionWaiters]) waiter(true);
|
|
250
266
|
}
|
|
@@ -519,3 +535,22 @@ export function getModelProxyChannel() {
|
|
|
519
535
|
globalThis[CHANNEL_KEY] ??= new ModelProxyChannel();
|
|
520
536
|
return globalThis[CHANNEL_KEY];
|
|
521
537
|
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Make `channel` the process-wide channel (a headless child talks through the
|
|
541
|
+
* daemon's model proxy bridge instead). The returned restore puts the previous
|
|
542
|
+
* channel back once, unless another install replaced this one since.
|
|
543
|
+
*/
|
|
544
|
+
export function installModelProxyChannel(channel) {
|
|
545
|
+
const hadPrevious = Object.hasOwn(globalThis, CHANNEL_KEY);
|
|
546
|
+
const previous = globalThis[CHANNEL_KEY];
|
|
547
|
+
globalThis[CHANNEL_KEY] = channel;
|
|
548
|
+
let restored = false;
|
|
549
|
+
return () => {
|
|
550
|
+
if (restored) return;
|
|
551
|
+
restored = true;
|
|
552
|
+
if (globalThis[CHANNEL_KEY] !== channel) return;
|
|
553
|
+
if (hadPrevious) globalThis[CHANNEL_KEY] = previous;
|
|
554
|
+
else delete globalThis[CHANNEL_KEY];
|
|
555
|
+
};
|
|
556
|
+
}
|
|
@@ -181,6 +181,17 @@ function mimoOAuthState() {
|
|
|
181
181
|
);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
function commandCodeOAuthState() {
|
|
185
|
+
const paths = [
|
|
186
|
+
boundProviderAuthPath('command-code-oauth') ||
|
|
187
|
+
process.env.COMMAND_CODE_OAUTH_CREDENTIALS_PATH ||
|
|
188
|
+
join(resolvePluginData(), 'command-code-oauth.json'),
|
|
189
|
+
];
|
|
190
|
+
return memoProbe('command-code-oauth', paths, () =>
|
|
191
|
+
resolveProbeState(paths, (docs) => docs.some((own) => typeof own?.apiKey === 'string' && !!own.apiKey.trim()))
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
184
195
|
function museCodeOAuthState() {
|
|
185
196
|
const paths = [
|
|
186
197
|
boundProviderAuthPath('muse-code-oauth') ||
|
|
@@ -255,6 +266,7 @@ const OAUTH_PROBE_STATES = new Map([
|
|
|
255
266
|
['openai-oauth', openAIOAuthState],
|
|
256
267
|
['grok-oauth', grokOAuthState],
|
|
257
268
|
['muse-code-oauth', museCodeOAuthState],
|
|
269
|
+
['command-code-oauth', commandCodeOAuthState],
|
|
258
270
|
['mimo-oauth', mimoOAuthState],
|
|
259
271
|
['cursor-oauth', cursorOAuthState],
|
|
260
272
|
['antigravity-oauth', antigravityOAuthState],
|
|
@@ -868,7 +868,8 @@ async function measureUsage(provider, providerObj, routeInfo) {
|
|
|
868
868
|
(provider === 'cursor-oauth' ||
|
|
869
869
|
provider === 'cursor-api' ||
|
|
870
870
|
provider === 'antigravity-oauth' ||
|
|
871
|
-
provider === 'muse-code-oauth'
|
|
871
|
+
provider === 'muse-code-oauth' ||
|
|
872
|
+
provider === 'command-code-oauth') &&
|
|
872
873
|
typeof providerObj?.getUsageSnapshot === 'function'
|
|
873
874
|
) {
|
|
874
875
|
return await providerObj.getUsageSnapshot();
|
|
@@ -893,12 +894,12 @@ async function measureUsageWithAuthRetry(provider, providerObj, routeInfo) {
|
|
|
893
894
|
// refresh cannot vouch for the reading it last produced: keeping a 100% meter
|
|
894
895
|
// earned by a dead token would hold the account out of the roster until its
|
|
895
896
|
// reset time, so the recorded quota goes with the credential.
|
|
896
|
-
function usageAfterFailure(err, provider, accountId, log) {
|
|
897
|
+
async function usageAfterFailure(err, provider, accountId, log) {
|
|
897
898
|
const snapshot = provider === 'anthropic-oauth' && accountId === 'default' ? latestClaudeStatuslineUsage() : null;
|
|
898
899
|
if (snapshot) return snapshot;
|
|
899
900
|
if (isUsageAuthRejection(err) && ACCOUNT_PROVIDERS.includes(provider)) {
|
|
900
901
|
try {
|
|
901
|
-
clearProviderAccountQuotaState(provider, accountId);
|
|
902
|
+
await clearProviderAccountQuotaState(provider, accountId);
|
|
902
903
|
} catch (error) {
|
|
903
904
|
log(`Account usage could not be cleared: ${error.message}`);
|
|
904
905
|
}
|
|
@@ -952,7 +953,7 @@ export async function fetchOAuthUsageSnapshot(routeInfo, providerObj, log = () =
|
|
|
952
953
|
try {
|
|
953
954
|
snapshot = await measureUsageWithAuthRetry(provider, providerObj, routeInfo);
|
|
954
955
|
} catch (err) {
|
|
955
|
-
snapshot = usageAfterFailure(err, provider, accountId, log);
|
|
956
|
+
snapshot = await usageAfterFailure(err, provider, accountId, log);
|
|
956
957
|
}
|
|
957
958
|
if (!isContentfulSnapshot(snapshot)) {
|
|
958
959
|
cacheNegative(key, 'empty');
|
|
@@ -961,7 +962,7 @@ export async function fetchOAuthUsageSnapshot(routeInfo, providerObj, log = () =
|
|
|
961
962
|
}
|
|
962
963
|
if (ACCOUNT_PROVIDERS.includes(provider)) {
|
|
963
964
|
try {
|
|
964
|
-
recordProviderAccountUsage(provider, accountId, snapshot);
|
|
965
|
+
await recordProviderAccountUsage(provider, accountId, snapshot);
|
|
965
966
|
} catch (error) {
|
|
966
967
|
log(`Account usage could not be saved: ${error.message}`);
|
|
967
968
|
}
|
|
@@ -38,7 +38,7 @@ export async function sendCompatChat(provider, messages, useModel, tools, opts)
|
|
|
38
38
|
// global catalog must not shape a user-defined endpoint's request.
|
|
39
39
|
let modelInfo = null;
|
|
40
40
|
if (isCustom) modelInfo = provider.getCachedModelInfo(useModel) || null;
|
|
41
|
-
else if (provider.name === 'opencode-go')
|
|
41
|
+
else if (provider.name === 'opencode-go' || provider.name === 'command-code-oauth')
|
|
42
42
|
modelInfo = provider.getCachedModelInfo(useModel) || getModelMetadataSync(useModel, provider.name);
|
|
43
43
|
else if (profile?.resolveModelInfo) modelInfo = profile.resolveModelInfo(provider, useModel) || null;
|
|
44
44
|
// Resolved before lowering: replay eligibility depends on the snapped level
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveProviderApiKey } from '../../../shared/provider-api-key.mjs';
|
|
1
2
|
import { enrichModels } from './model-catalog.mjs';
|
|
2
3
|
import { sanitizeModelList } from './model-list-sanitize.mjs';
|
|
3
4
|
import { PROVIDER_GENERATE_TOTAL_TIMEOUT_MS, createTimeoutSignal, resolveTimeoutMs } from '../stall-policy.mjs';
|
|
@@ -10,12 +11,15 @@ const MODEL_LIST_TIMEOUT_MS = resolveTimeoutMs('MIXDOG_COMPAT_MODEL_LIST_TIMEOUT
|
|
|
10
11
|
// `keyless` omits the Authorization header entirely (keyless endpoints);
|
|
11
12
|
// `signal` aborts the request together with the list timeout.
|
|
12
13
|
export async function fetchCompatModelItems(provider, { signal = null, keyless = false } = {}) {
|
|
13
|
-
const
|
|
14
|
+
const name = provider.name;
|
|
15
|
+
const timeout = createTimeoutSignal(signal, MODEL_LIST_TIMEOUT_MS, `${name} model list`);
|
|
14
16
|
try {
|
|
15
17
|
const res = await fetch(`${String(provider.baseURL || '').replace(/\/+$/, '')}/models`, {
|
|
16
18
|
method: 'GET',
|
|
17
19
|
headers: {
|
|
18
|
-
...(keyless
|
|
20
|
+
...(keyless
|
|
21
|
+
? {}
|
|
22
|
+
: { Authorization: `Bearer ${resolveProviderApiKey(name, provider.config || provider) || 'no-key'}` }),
|
|
19
23
|
...(provider.defaultHeaders || {}),
|
|
20
24
|
},
|
|
21
25
|
signal: timeout.signal,
|
|
@@ -74,6 +74,17 @@ export function applyCompatProviderChatOptions(params, providerName, opts = {},
|
|
|
74
74
|
}
|
|
75
75
|
return params;
|
|
76
76
|
}
|
|
77
|
+
if (providerName === 'command-code-oauth') {
|
|
78
|
+
// The API's field is undocumented; mirror the common `reasoning_effort`
|
|
79
|
+
// (levels the catalog row declares, else low/medium/high).
|
|
80
|
+
const rawEffort = opts.effort ?? config?.reasoningEffort;
|
|
81
|
+
const declared = modelInfo?.reasoningOptions?.some((option) => option?.type === 'effort');
|
|
82
|
+
const effort = declared
|
|
83
|
+
? normalizeOpencodeGoReasoningEffort(rawEffort, modelInfo)
|
|
84
|
+
: normalizeReasoningEffort(rawEffort, ['low', 'medium', 'high']);
|
|
85
|
+
if (effort) params.reasoning_effort = effort;
|
|
86
|
+
return params;
|
|
87
|
+
}
|
|
77
88
|
if (providerName === 'opencode-go') {
|
|
78
89
|
const reasoningEffort = normalizeOpencodeGoReasoningEffort(opts.effort ?? config?.reasoningEffort, modelInfo);
|
|
79
90
|
// OpenCode Go's OpenAI-compatible contract exposes reasoning_effort,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveProviderApiKey } from '../../../shared/provider-api-key.mjs';
|
|
1
2
|
import { markProviderRecoveryExhausted, withRetry } from './retry-classifier.mjs';
|
|
2
3
|
import { consumeCompatResponsesStream } from './openai-compat-stream.mjs';
|
|
3
4
|
import { PROVIDER_FIRST_BYTE_TIMEOUT_MS, createPassthroughSignal } from '../stall-policy.mjs';
|
|
@@ -158,7 +159,7 @@ export async function sendXaiResponsesWebSocket(provider, messages, useModel, to
|
|
|
158
159
|
const reason = signal.reason;
|
|
159
160
|
throw reason instanceof Error ? reason : new Error('xAI Responses WebSocket request aborted by session close');
|
|
160
161
|
}
|
|
161
|
-
const apiKey = provider.config
|
|
162
|
+
const apiKey = resolveProviderApiKey('xai', provider.config);
|
|
162
163
|
if (!apiKey) throw new Error('xAI API key not configured');
|
|
163
164
|
messages = await preparePdfTextForProvider(messages, provider);
|
|
164
165
|
const chatMessagesForTrace = toOpenAIMessages(messages, provider.name, { nativePdf: provider.nativePdf });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import { noteErrorUsage } from './lib/note-error-usage.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import { reloadProviderApiKey, resolveProviderApiKey } from '../../../shared/provider-api-key.mjs';
|
|
4
4
|
import { getLlmDispatcher, preconnect } from '../../../shared/llm/http-agent.mjs';
|
|
5
5
|
import { appendAgentTrace } from '../agent-trace.mjs';
|
|
6
6
|
import { OPENAI_COMPAT_PRESETS } from './openai-compat-presets.mjs';
|
|
@@ -69,7 +69,7 @@ export class OpenAICompatProvider {
|
|
|
69
69
|
constructor(name, config) {
|
|
70
70
|
const preset = PRESETS[name];
|
|
71
71
|
const baseURL = assertSafeBaseURL(config.baseURL || preset?.baseURL || 'http://localhost:8080/v1', name);
|
|
72
|
-
const apiKey = config
|
|
72
|
+
const apiKey = resolveProviderApiKey(name, config) || 'no-key';
|
|
73
73
|
this.name = name;
|
|
74
74
|
this.config = config;
|
|
75
75
|
this.baseURL = baseURL;
|
|
@@ -121,7 +121,7 @@ export class OpenAICompatProvider {
|
|
|
121
121
|
reloadApiKey() {
|
|
122
122
|
try {
|
|
123
123
|
const preset = PRESETS[this.name];
|
|
124
|
-
const newKey =
|
|
124
|
+
const newKey = reloadProviderApiKey(this.name, this.config);
|
|
125
125
|
const baseURL = assertSafeBaseURL(
|
|
126
126
|
this.config.baseURL || preset?.baseURL || 'http://localhost:8080/v1',
|
|
127
127
|
this.name
|
|
@@ -138,6 +138,12 @@ export class OpenAICompatProvider {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
async send(messages, model, tools, sendOpts) {
|
|
141
|
+
// A keyless-constructed instance picks up the key once it is available.
|
|
142
|
+
const resolvedKey = resolveProviderApiKey(this.name, this.config) || 'no-key';
|
|
143
|
+
if (resolvedKey !== this.apiKey) {
|
|
144
|
+
this.apiKey = resolvedKey;
|
|
145
|
+
this.client = this._createClient(resolvedKey);
|
|
146
|
+
}
|
|
141
147
|
try {
|
|
142
148
|
return await this._doSend(messages, model, tools, sendOpts);
|
|
143
149
|
} catch (err) {
|
|
@@ -23,7 +23,7 @@ import { resolveOpenAiTransportPolicy } from './openai-transport-policy.mjs';
|
|
|
23
23
|
import { applyOpenAIDirectCachePolicy } from './openai-direct-request.mjs';
|
|
24
24
|
import { streamCallbacks } from './lib/send-callbacks.mjs';
|
|
25
25
|
import { noteErrorUsage } from './lib/note-error-usage.mjs';
|
|
26
|
-
import {
|
|
26
|
+
import { reloadProviderApiKey, resolveProviderApiKey } from '../../../shared/provider-api-key.mjs';
|
|
27
27
|
import { getLlmDispatcher } from '../../../shared/llm/http-agent.mjs';
|
|
28
28
|
import { noteRequestServiceTier } from '../../../shared/llm/usage-context.mjs';
|
|
29
29
|
import { resolveProviderCacheKey, resolveProviderPromptCacheLane } from '../agent-runtime/cache-strategy.mjs';
|
|
@@ -86,7 +86,7 @@ export class OpenAIDirectProvider {
|
|
|
86
86
|
this.config = config || {};
|
|
87
87
|
}
|
|
88
88
|
_ensureKey() {
|
|
89
|
-
const k = this.config
|
|
89
|
+
const k = resolveProviderApiKey('openai', this.config);
|
|
90
90
|
if (!k) throw new Error('OPENAI_API_KEY not configured (providers.openai.apiKey)');
|
|
91
91
|
return k;
|
|
92
92
|
}
|
|
@@ -97,7 +97,7 @@ export class OpenAIDirectProvider {
|
|
|
97
97
|
// the WS/HTTP transports take the key per-call via the `auth` object.
|
|
98
98
|
reloadApiKey() {
|
|
99
99
|
try {
|
|
100
|
-
const newKey =
|
|
100
|
+
const newKey = reloadProviderApiKey('openai', this.config);
|
|
101
101
|
if (newKey) {
|
|
102
102
|
this.config = { ...(this.config || {}), apiKey: newKey };
|
|
103
103
|
return newKey;
|
|
@@ -295,6 +295,6 @@ export class OpenAIDirectProvider {
|
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
async isAvailable() {
|
|
298
|
-
return !!this.config
|
|
298
|
+
return !!resolveProviderApiKey('openai', this.config);
|
|
299
299
|
}
|
|
300
300
|
}
|
|
@@ -7,6 +7,7 @@ import { isInclusiveProvider } from '../../../shared/llm/cost.mjs';
|
|
|
7
7
|
import { noEnabledProvidersError } from './registry-errors.mjs';
|
|
8
8
|
import { providerTakesNativePdf } from '../../../attachments/store.mjs';
|
|
9
9
|
import { modelProxyClientMode } from '../../../shared/model-proxy-mode.mjs';
|
|
10
|
+
import { getModelProxyChannel } from './model-proxy-channel.mjs';
|
|
10
11
|
// OpenAI-compat provider names are self-declared by openai-compat-presets.mjs via
|
|
11
12
|
// OPENAI_COMPAT_PRESETS. No parallel list maintained here.
|
|
12
13
|
const providers = new Map();
|
|
@@ -28,6 +29,7 @@ const OAUTH_PROVIDER_NAMES = new Set([
|
|
|
28
29
|
'openai-oauth',
|
|
29
30
|
'grok-oauth',
|
|
30
31
|
'muse-code-oauth',
|
|
32
|
+
'command-code-oauth',
|
|
31
33
|
'mimo-oauth',
|
|
32
34
|
'cursor-oauth',
|
|
33
35
|
'antigravity-oauth',
|
|
@@ -96,6 +98,28 @@ function configSignature(cfg) {
|
|
|
96
98
|
}
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
// Remote mode: a proxy provider sends through the channel it was built on. A
|
|
102
|
+
// headless boundary installs a fresh channel per run (model-proxy-bridge.mjs)
|
|
103
|
+
// and closes it afterwards, so the channel's identity is part of every reuse
|
|
104
|
+
// signature — an identical PC config behind a new channel is a rebuild, never
|
|
105
|
+
// a provider bound to a closed channel. Local mode: '' (signatures unchanged).
|
|
106
|
+
const _proxyChannelIds = new WeakMap();
|
|
107
|
+
let _proxyChannelSeq = 0;
|
|
108
|
+
function proxyChannelTag(channel) {
|
|
109
|
+
if (!channel) return '';
|
|
110
|
+
let id = _proxyChannelIds.get(channel);
|
|
111
|
+
if (!id) {
|
|
112
|
+
_proxyChannelSeq += 1;
|
|
113
|
+
id = _proxyChannelSeq;
|
|
114
|
+
_proxyChannelIds.set(channel, id);
|
|
115
|
+
}
|
|
116
|
+
return `\u0000model-proxy-channel:${id}`;
|
|
117
|
+
}
|
|
118
|
+
function reuseSignature(cfg, proxyChannel) {
|
|
119
|
+
const sig = configSignature(cfg);
|
|
120
|
+
return sig === null ? null : sig + proxyChannelTag(proxyChannel);
|
|
121
|
+
}
|
|
122
|
+
|
|
99
123
|
function abortError(signal) {
|
|
100
124
|
return signal?.reason instanceof Error
|
|
101
125
|
? signal.reason
|
|
@@ -147,6 +171,7 @@ const PROVIDER_MODULES = {
|
|
|
147
171
|
'anthropic-oauth': ['./anthropic-oauth.mjs', 'AnthropicOAuthProvider'],
|
|
148
172
|
'grok-oauth': ['./grok-oauth.mjs', 'GrokOAuthProvider'],
|
|
149
173
|
'muse-code-oauth': ['./muse-code-oauth.mjs', 'MuseCodeOAuthProvider'],
|
|
174
|
+
'command-code-oauth': ['./command-code-oauth.mjs', 'CommandCodeOAuthProvider'],
|
|
150
175
|
'mimo-oauth': ['./mimo-oauth.mjs', 'MimoOAuthProvider'],
|
|
151
176
|
'cursor-oauth': ['./cursor.mjs', 'CursorOAuthProvider'],
|
|
152
177
|
'antigravity-oauth': ['./antigravity-oauth.mjs', 'AntigravityOAuthProvider'],
|
|
@@ -176,12 +201,12 @@ async function loadProviderCtor(name, signal = null, cfg = null) {
|
|
|
176
201
|
throw new Error(`unknown enabled provider: ${name}`);
|
|
177
202
|
}
|
|
178
203
|
|
|
179
|
-
async function instantiateProvider(name, Ctor, cfg) {
|
|
204
|
+
async function instantiateProvider(name, Ctor, cfg, proxyChannel = null) {
|
|
180
205
|
// Remote mode: the client PC executes the call with its own credentials.
|
|
181
206
|
// Admission and usage accounting still wrap the daemon-side stand-in.
|
|
182
207
|
if (modelProxyClientMode()) {
|
|
183
208
|
const { createModelProxyProvider } = await import('./model-proxy-provider.mjs');
|
|
184
|
-
return wrapProviderAdmission(createModelProxyProvider(name, cfg), name);
|
|
209
|
+
return wrapProviderAdmission(createModelProxyProvider(name, cfg, proxyChannel ?? getModelProxyChannel()), name);
|
|
185
210
|
}
|
|
186
211
|
if (isCustomProviderConfig(name, cfg)) return wrapProviderAdmission(await Ctor(name, cfg), name);
|
|
187
212
|
if (Object.hasOwn(OPENAI_COMPAT_PRESETS, name) && name !== 'opencode-go') {
|
|
@@ -194,11 +219,12 @@ export async function initProviders(config, { signal = null } = {}) {
|
|
|
194
219
|
throwIfAborted(signal);
|
|
195
220
|
// Remote mode: provider availability and config are the PC's, never this
|
|
196
221
|
// server's credential probes. No connected client fails here, clearly.
|
|
197
|
-
|
|
222
|
+
const proxyChannel = modelProxyClientMode() ? getModelProxyChannel() : null;
|
|
223
|
+
if (proxyChannel) {
|
|
198
224
|
const { loadModelProxyProviderConfig } = await import('./model-proxy-provider.mjs');
|
|
199
|
-
config = await awaitWithAbort(loadModelProxyProviderConfig(), signal);
|
|
225
|
+
config = await awaitWithAbort(loadModelProxyProviderConfig({ channel: proxyChannel }), signal);
|
|
200
226
|
}
|
|
201
|
-
const sig =
|
|
227
|
+
const sig = reuseSignature(config, proxyChannel);
|
|
202
228
|
// Coalesce: an identical config is already mid-init — attach to it.
|
|
203
229
|
if (sig !== null && _inFlightPromise && _inFlightSig === sig) {
|
|
204
230
|
return awaitWithAbort(_inFlightPromise, signal);
|
|
@@ -212,7 +238,7 @@ export async function initProviders(config, { signal = null } = {}) {
|
|
|
212
238
|
// signatures can never run their clear()+rebuild concurrently, regardless
|
|
213
239
|
// of caller-side gating (agent-tool gateOnPrior may release a queued init
|
|
214
240
|
// before the prior one settled). Errors do not poison the chain.
|
|
215
|
-
const run = () => _initProvidersUnsynchronized(config, signal);
|
|
241
|
+
const run = () => _initProvidersUnsynchronized(config, signal, proxyChannel);
|
|
216
242
|
const next = _initChain.then(run, run);
|
|
217
243
|
_initChain = next.then(
|
|
218
244
|
() => {},
|
|
@@ -241,7 +267,7 @@ export async function initProviders(config, { signal = null } = {}) {
|
|
|
241
267
|
return awaitWithAbort(tracked, signal);
|
|
242
268
|
}
|
|
243
269
|
|
|
244
|
-
async function _initProvidersUnsynchronized(config, signal = null) {
|
|
270
|
+
async function _initProvidersUnsynchronized(config, signal = null, proxyChannel = null) {
|
|
245
271
|
throwIfAborted(signal);
|
|
246
272
|
// Invariant: never wipe the live registry based on an empty / all-disabled
|
|
247
273
|
// config. Without this guard, a stale `loadAgentConfig()` (e.g. mid-reload
|
|
@@ -262,7 +288,7 @@ async function _initProvidersUnsynchronized(config, signal = null) {
|
|
|
262
288
|
// Idempotent reuse: an enabled provider whose config signature is
|
|
263
289
|
// unchanged from the live registry is carried forward as-is. Only
|
|
264
290
|
// added or changed providers are (re)constructed below.
|
|
265
|
-
const sig =
|
|
291
|
+
const sig = reuseSignature(cfg, proxyChannel);
|
|
266
292
|
if (sig !== null && providers.has(name) && signatures.get(name) === sig) {
|
|
267
293
|
return { name, inst: providers.get(name), sig };
|
|
268
294
|
}
|
|
@@ -270,7 +296,7 @@ async function _initProvidersUnsynchronized(config, signal = null) {
|
|
|
270
296
|
// A proxied provider never loads the real adapter module.
|
|
271
297
|
const Ctor = modelProxyClientMode() ? null : await loadProviderCtor(name, signal, cfg);
|
|
272
298
|
throwIfAborted(signal);
|
|
273
|
-
const inst = await instantiateProvider(name, Ctor, cfg);
|
|
299
|
+
const inst = await instantiateProvider(name, Ctor, cfg, proxyChannel);
|
|
274
300
|
return { name, inst, sig };
|
|
275
301
|
} catch (err) {
|
|
276
302
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -25,6 +25,9 @@ const EFFORT_OPTIONS_BY_PROVIDER = {
|
|
|
25
25
|
// xAI Grok 4.6+: low/medium/high/xhigh. Reasoning cannot be disabled.
|
|
26
26
|
xai: ['low', 'medium', 'high', 'xhigh'],
|
|
27
27
|
'mimo-oauth': ['minimal', 'low', 'medium', 'high'],
|
|
28
|
+
// Command Code relays several vendors over Chat Completions; the common
|
|
29
|
+
// `reasoning_effort` levels are the fallback when no catalog row declares any.
|
|
30
|
+
'command-code-oauth': ['low', 'medium', 'high'],
|
|
28
31
|
};
|
|
29
32
|
const EFFORT_BY_FAMILY = {
|
|
30
33
|
opus: ['low', 'medium', 'high', 'xhigh', 'max'],
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
// extension-scopes.mjs — plugin-aware layer over the shared scope model. A
|
|
2
2
|
// plugin's scope cascades to the skills it owns and to the MCP server(s) it
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import { pluginMcpServerName, pluginServerMatcher } from './plugin-mcp.mjs';
|
|
8
|
-
import { resolvePluginData } from '../../../shared/plugin-paths.mjs';
|
|
3
|
+
// declares, so limiting the plugin limits everything it brought along. MCP
|
|
4
|
+
// ownership comes from the tool's `mcpPluginId`, set when the plugin's server
|
|
5
|
+
// connected, never from the server name.
|
|
9
6
|
import { loadConfig } from '../config.mjs';
|
|
10
7
|
import {
|
|
11
8
|
extensionAllowedForCwd,
|
|
@@ -28,66 +25,29 @@ export {
|
|
|
28
25
|
withExtensionScope,
|
|
29
26
|
} from '../../../shared/extension-scopes.mjs';
|
|
30
27
|
|
|
31
|
-
/**
|
|
32
|
-
|
|
33
|
-
export function pluginIdForMcpServer(serverName, plugins) {
|
|
34
|
-
const name = clean(serverName);
|
|
35
|
-
if (!name) return '';
|
|
36
|
-
for (const plugin of Array.isArray(plugins) ? plugins : []) {
|
|
37
|
-
if (pluginServerMatcher(pluginMcpServerName(plugin))(name)) return clean(plugin?.id || plugin?.name);
|
|
38
|
-
}
|
|
39
|
-
return '';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function mcpServerAllowedForCwd(scopes, serverName, cwd, { plugins = [] } = {}) {
|
|
28
|
+
/** Server scope ∩ owning plugin's scope (`pluginId` from owner metadata). */
|
|
29
|
+
export function mcpServerAllowedForCwd(scopes, serverName, cwd, { pluginId = '' } = {}) {
|
|
43
30
|
if (!extensionAllowedForCwd(scopes, 'mcp', serverName, cwd)) return false;
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
31
|
+
const owner = clean(pluginId);
|
|
32
|
+
if (owner && !extensionAllowedForCwd(scopes, 'plugins', owner, cwd)) return false;
|
|
46
33
|
return true;
|
|
47
34
|
}
|
|
48
35
|
|
|
49
36
|
/** Drop tools of MCP servers whose scope excludes cwd; non-MCP tools pass. */
|
|
50
|
-
export function filterMcpToolsForCwd(tools, scopes, cwd
|
|
37
|
+
export function filterMcpToolsForCwd(tools, scopes, cwd) {
|
|
51
38
|
const list = Array.isArray(tools) ? tools : [];
|
|
52
39
|
const verdicts = new Map();
|
|
53
40
|
return list.filter((tool) => {
|
|
54
|
-
|
|
41
|
+
// A name shortened for provider limits carries its server explicitly.
|
|
42
|
+
const server = mcpServerNameOfTool(tool);
|
|
55
43
|
if (!server) return true;
|
|
56
|
-
|
|
57
|
-
|
|
44
|
+
const pluginId = clean(tool?.mcpPluginId);
|
|
45
|
+
const key = `${server}\0${pluginId}`;
|
|
46
|
+
if (!verdicts.has(key)) verdicts.set(key, mcpServerAllowedForCwd(scopes, server, cwd, { pluginId }));
|
|
47
|
+
return verdicts.get(key);
|
|
58
48
|
});
|
|
59
49
|
}
|
|
60
50
|
|
|
61
|
-
// `{ id, name }` of every registered plugin, from <data>/plugins/registry.json.
|
|
62
|
-
// mtime-cached: this runs on every session create and MCP catalog fold.
|
|
63
|
-
let _registryCache = { path: '', mtime: -1, plugins: [] };
|
|
64
|
-
export function registeredPluginIdentities(dataDir = null) {
|
|
65
|
-
let path = '';
|
|
66
|
-
try {
|
|
67
|
-
path = join(dataDir || resolvePluginData(), 'plugins', 'registry.json');
|
|
68
|
-
} catch {
|
|
69
|
-
return [];
|
|
70
|
-
}
|
|
71
|
-
let mtime = -1;
|
|
72
|
-
try {
|
|
73
|
-
mtime = statSync(path).mtimeMs;
|
|
74
|
-
} catch {
|
|
75
|
-
return [];
|
|
76
|
-
}
|
|
77
|
-
if (_registryCache.path === path && _registryCache.mtime === mtime) return _registryCache.plugins;
|
|
78
|
-
let plugins = [];
|
|
79
|
-
try {
|
|
80
|
-
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
81
|
-
plugins = (Array.isArray(parsed?.plugins) ? parsed.plugins : [])
|
|
82
|
-
.map((entry) => ({ id: clean(entry?.id || entry?.name), name: clean(entry?.name || entry?.id) }))
|
|
83
|
-
.filter((entry) => entry.id);
|
|
84
|
-
} catch {
|
|
85
|
-
plugins = [];
|
|
86
|
-
}
|
|
87
|
-
_registryCache = { path, mtime, plugins };
|
|
88
|
-
return plugins;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
51
|
/** The MCP tools a session at `cwd` may see. No scopes configured → input
|
|
92
52
|
* returned as-is, so the global-only fast path costs one config read. */
|
|
93
53
|
export function filterMcpToolsForSession(tools, cwd, config = null) {
|
|
@@ -95,5 +55,5 @@ export function filterMcpToolsForSession(tools, cwd, config = null) {
|
|
|
95
55
|
if (!list.length) return list;
|
|
96
56
|
const scopes = extensionScopesFromConfig(config || loadConfig({ secrets: false }));
|
|
97
57
|
if (!hasExtensionScopes(scopes)) return list;
|
|
98
|
-
return filterMcpToolsForCwd(list, scopes, cwd
|
|
58
|
+
return filterMcpToolsForCwd(list, scopes, cwd);
|
|
99
59
|
}
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
stopActiveClock,
|
|
20
20
|
validateObjective,
|
|
21
21
|
} from './goal-state.mjs';
|
|
22
|
-
import { assertExpectedGoal } from './goal-mutations.mjs';
|
|
22
|
+
import { assertExpectedGoal, goalFinished } from './goal-mutations.mjs';
|
|
23
23
|
import { clean } from '../../../shared/clean.mjs';
|
|
24
24
|
|
|
25
25
|
/** "12m remaining" for a budgeted Goal, "3m elapsed" for an open-ended one. */
|
|
@@ -109,6 +109,17 @@ const GOAL_CONTROL_ACTIONS = {
|
|
|
109
109
|
const goal = await ctx.updateGoal(id, { status: 'complete' }, { user: true });
|
|
110
110
|
return reply(action, goal, `Goal complete · ${goal.objective} · ${durationLabel(goal.timeUsedMs)} elapsed`);
|
|
111
111
|
},
|
|
112
|
+
async dismiss(ctx, { id, goal, at, action }) {
|
|
113
|
+
// The user's acknowledgement of finished work: the same archive a new
|
|
114
|
+
// prompt applies, so the chrome lets go while the record stays.
|
|
115
|
+
if (!goalFinished(goal)) throw new Error('only a finished Goal can be dismissed; stop or complete it first');
|
|
116
|
+
if (!goal.archivedAt) {
|
|
117
|
+
goal.archivedAt = at;
|
|
118
|
+
goal.updatedAt = at;
|
|
119
|
+
await ctx.commit(id, goal);
|
|
120
|
+
}
|
|
121
|
+
return reply(action, null, `Goal dismissed · ${goal.objective}`);
|
|
122
|
+
},
|
|
112
123
|
};
|
|
113
124
|
|
|
114
125
|
export async function runGoalControl(ctx, sessionId, rawArgs = {}) {
|
|
@@ -146,7 +157,9 @@ export async function runGoalControl(ctx, sessionId, rawArgs = {}) {
|
|
|
146
157
|
throw new Error('Goal changed while editing; reopen the editor before saving');
|
|
147
158
|
}
|
|
148
159
|
const at = ctx.now();
|
|
149
|
-
if (goal.status === 'stopped'
|
|
160
|
+
if (goal.status === 'stopped' && action !== 'dismiss') {
|
|
161
|
+
throw new Error('a stopped Goal cannot be changed; create a new Goal');
|
|
162
|
+
}
|
|
150
163
|
const handler = GOAL_CONTROL_ACTIONS[action];
|
|
151
164
|
if (!handler) throw new Error(`unknown Goal action: ${action}`);
|
|
152
165
|
return handler(ctx, { id, goal, args, at, expectedGoalId, action });
|