mixdog 1.0.12 → 1.1.0
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 +75 -181
- package/package.json +4 -2
- package/scripts/build-shipped-history.mjs +131 -0
- package/scripts/lib/run-node-tests.mjs +235 -13
- package/scripts/lib/test-failure-records.mjs +5 -0
- package/scripts/lib/test-failure-summary.mjs +9 -3
- package/scripts/lib/test-spawn-ledger.mjs +19 -0
- package/scripts/prune-embedding-runtime.mjs +9 -5
- package/scripts/test-direct.mjs +16 -1
- package/src/agents/advisor/AGENT.md +38 -0
- package/src/agents/advisor/agent.json +6 -0
- package/src/agents/reviewer/AGENT.md +5 -2
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +381 -0
- package/src/defaults/skills/goal-management/SKILL.md +11 -5
- package/src/defaults/skills/setup/SKILL.md +5 -8
- package/src/defaults/skills/setup/references/actions.md +61 -31
- package/src/defaults/skills/setup/references/surfaces.md +12 -10
- package/src/lib/rules-builder.cjs +1 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +3 -1
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.d.mts +1 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +44 -0
- package/src/runtime/agent/orchestrator/config-providers-merge.mjs +9 -1
- package/src/runtime/agent/orchestrator/config.mjs +102 -21
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +77 -26
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +44 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +11 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +388 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +92 -109
- package/src/runtime/agent/orchestrator/providers/custom-reasoning.mjs +107 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +51 -9
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +14 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +51 -11
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +7 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-options.mjs +12 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +27 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +12 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +70 -0
- package/src/runtime/agent/orchestrator/providers/registry.mjs +38 -3
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +19 -35
- package/src/runtime/agent/orchestrator/runtime-core/effort.mjs +5 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +0 -17
- package/src/runtime/agent/orchestrator/runtime-core/goal-state.mjs +0 -3
- package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +0 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-text.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -2
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +18 -4
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +7 -1
- package/src/runtime/agent/orchestrator/runtime-core/session-route-policy.mjs +16 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +2 -2
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +7 -7
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +28 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +24 -11
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +2 -6
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +63 -3
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +1 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +0 -1
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +18 -8
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +19 -20
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +6 -11
- package/src/runtime/agent/orchestrator/tools/builtin/file-version-test-helpers.mjs +11 -0
- package/src/runtime/agent/orchestrator/tools/builtin/file-version.mjs +13 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-single-file-rescue.mjs +8 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +25 -12
- package/src/runtime/agent/orchestrator/tools/builtin/raw-content-publication.mjs +17 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +136 -76
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +15 -17
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +11 -10
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +30 -16
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +4 -11
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +7 -3
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-validation.mjs +16 -19
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +14 -4
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +58 -8
- package/src/runtime/agent/orchestrator/tools/patch/content-guard.mjs +47 -0
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +155 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-proofs.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/dispatch/native-snapshots.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +19 -16
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +13 -44
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/post-patch-excerpts.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +47 -7
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +72 -8
- package/src/runtime/agent/orchestrator/tools/patch/section-coalesce.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/patch/sequence/run.mjs +9 -1
- package/src/runtime/agent/orchestrator/tools/patch/sequence/units.mjs +9 -1
- package/src/runtime/agent/orchestrator/tools/patch/sequence.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/patch/ui-diff.mjs +16 -4
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +35 -14
- package/src/runtime/agent/orchestrator/tools/patch/wave.mjs +42 -4
- package/src/runtime/agent/orchestrator/tools/shell-codepage.mjs +296 -0
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +75 -24
- package/src/runtime/channels/lib/config.mjs +3 -8
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +7 -3
- package/src/{tui → runtime/channels}/lib/voice-setup.mjs +38 -22
- package/src/runtime/channels/lib/webhook/request-handler.mjs +42 -8
- package/src/runtime/computer-bridge/actions.mjs +2 -0
- package/src/runtime/effort-judge/judge-client.mjs +12 -3
- package/src/runtime/effort-judge/model-manifest.json +7 -7
- package/src/runtime/effort-judge/previous-human-request.mjs +162 -0
- package/src/runtime/effort-judge/verified-context-input.mjs +10 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +12 -0
- package/src/runtime/memory/lib/http-router/session-routes.mjs +2 -1
- package/src/runtime/memory/lib/memory-config-flags.mjs +3 -2
- package/src/runtime/memory/lib/memory-embed.mjs +5 -0
- package/src/runtime/shared/agent-route-config.mjs +50 -2
- package/src/runtime/shared/bounded-file-read.mjs +166 -0
- package/src/runtime/shared/builtin-grandfathering.mjs +37 -0
- package/src/runtime/shared/config.mjs +24 -4
- package/src/runtime/shared/defaults-separation.mjs +263 -0
- package/src/runtime/shared/llm/anthropic-effort.mjs +4 -0
- package/src/runtime/shared/llm/model-catalog.mjs +23 -0
- package/src/runtime/shared/llm/usage-ledger-rollup.mjs +105 -1
- package/src/runtime/shared/llm/usage-ledger-worker.mjs +2 -1
- package/src/runtime/shared/llm/usage-ledger.mjs +72 -5
- package/src/runtime/shared/onnx-runtime-support.mjs +6 -0
- package/src/runtime/shared/orchestration.mjs +14 -6
- package/src/runtime/shared/projects.mjs +65 -48
- package/src/runtime/shared/provider-accounts.mjs +47 -6
- package/src/runtime/shared/quota-resume-store.mjs +255 -0
- package/src/runtime/shared/shipped-definitions.mjs +275 -0
- package/src/runtime/shared/tool-card-model.golden.json +3 -3
- package/src/runtime/shared/tool-result-summary.mjs +86 -15
- package/src/runtime/shared/transcript-metadata.mjs +2 -0
- package/src/runtime/shared/turn-review-reply.mjs +2 -0
- package/src/runtime/shared/turn-snapshot.mjs +65 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +202 -22
- package/src/session-runtime/boot/apis.mjs +19 -0
- package/src/session-runtime/boot/config.mjs +4 -0
- package/src/session-runtime/boot/facade.mjs +4 -1
- package/src/session-runtime/boot/infrastructure.mjs +21 -2
- package/src/session-runtime/boot/session.mjs +3 -0
- package/src/session-runtime/channel-config-api.mjs +5 -0
- package/src/session-runtime/config-lifecycle/config-writers.mjs +3 -0
- package/src/session-runtime/config-lifecycle.mjs +31 -6
- package/src/session-runtime/goal-facade-api.mjs +4 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +28 -6
- package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +3 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +7 -1
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +6 -0
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +14 -2
- package/src/session-runtime/media-api.mjs +32 -22
- package/src/session-runtime/model-recency.mjs +14 -0
- package/src/session-runtime/model-route/route-tuning.mjs +3 -5
- package/src/session-runtime/model-route/set-route.mjs +49 -21
- package/src/session-runtime/model-route-api.mjs +2 -1
- package/src/session-runtime/notification-bus.mjs +31 -6
- package/src/session-runtime/provider-auth/login.mjs +6 -0
- package/src/session-runtime/provider-models/catalog-load.mjs +3 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +15 -14
- package/src/session-runtime/quick-model-rows/model-meta.mjs +26 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +40 -14
- package/src/session-runtime/resource-api.mjs +8 -1
- package/src/session-runtime/runtime-core.mjs +4 -0
- package/src/session-runtime/runtime-facade.mjs +20 -1
- package/src/session-runtime/runtime-review-api.mjs +12 -1
- package/src/session-runtime/services/agent-tool/helpers.mjs +4 -0
- package/src/session-runtime/services/agent-tool/index-write-queue.mjs +10 -15
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +11 -3
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +2 -0
- package/src/session-runtime/services/channel-admin.mjs +87 -13
- package/src/session-runtime/services/provider-admin.mjs +256 -63
- package/src/session-runtime/services/seeds.mjs +73 -142
- package/src/session-runtime/services/usage-dashboard-model.mjs +29 -0
- package/src/session-runtime/services/usage-dashboard.mjs +7 -0
- package/src/session-runtime/session-address.mjs +125 -0
- package/src/session-runtime/session-create.mjs +57 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +8 -0
- package/src/session-runtime/settings-compaction-api.mjs +13 -0
- package/src/session-runtime/setup-tool/executor.mjs +92 -40
- package/src/session-runtime/setup-tool/extended-actions.mjs +64 -5
- package/src/session-runtime/setup-tool/settings-contract.mjs +44 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +12 -7
- package/src/session-runtime/tool-surface.mjs +16 -9
- package/src/session-runtime/turn/session-ops.mjs +3 -0
- package/src/session-runtime/turn/share-ops.mjs +4 -1
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +6 -0
- package/src/session-runtime/usage-stats-api.mjs +15 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +31 -5
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +4 -0
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +27 -7
- package/src/standalone/daemon-boot-coordinator.mjs +10 -0
- package/src/standalone/daemon-drain-policy.mjs +9 -0
- package/src/standalone/daemon-stored-session-views.mjs +7 -8
- package/src/standalone/daemon.mjs +21 -5
- package/src/standalone/local-session-runtime.mjs +11 -0
- package/src/standalone/session-client.mjs +52 -7
- package/src/standalone/session-protocol.mjs +8 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +2 -0
- package/src/standalone/session-service/entries/lifecycle.mjs +2 -0
- package/src/standalone/session-service/entries/session-loads.mjs +9 -0
- package/src/standalone/session-service/projection/revisions.mjs +28 -4
- package/src/standalone/session-service/projection.mjs +1 -0
- package/src/standalone/session-service/quota-resume.mjs +140 -0
- package/src/standalone/session-service/session-calls/session-action.mjs +2 -2
- package/src/standalone/session-service/session-calls.mjs +10 -2
- package/src/standalone/session-service/stored-reader.mjs +26 -1
- package/src/standalone/session-service.mjs +21 -0
- package/src/standalone/session-transport/routes.mjs +2 -0
- package/src/tui/app/doctor.mjs +7 -11
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +3 -5
- package/src/tui/app/settings-picker/settings-rows.mjs +4 -6
- package/src/tui/app/settings-picker/settings-toggles/store-toggles.mjs +1 -1
- package/src/tui/app/slash-dispatch/route-commands.mjs +3 -2
- package/src/tui/components/ContextPanel.jsx +5 -1
- package/src/tui/components/Spinner.jsx +34 -319
- package/src/tui/components/SpinnerByline.jsx +256 -0
- package/src/tui/components/use-spinner-progress.mjs +113 -0
- package/src/tui/dist/index.mjs +15540 -14452
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +13 -1
- package/src/tui/session/agent-job-feed/notification-router.mjs +3 -0
- package/src/tui/session/agent-job-feed/pending-resume.mjs +14 -1
- package/src/tui/session/agent-job-feed.mjs +4 -0
- package/src/tui/session/context-state.mjs +13 -1
- package/src/tui/session/initial-state.mjs +6 -0
- package/src/tui/session/quota-resume.mjs +263 -0
- package/src/tui/session/session-api/intake/abort.mjs +39 -35
- package/src/tui/session/session-api/intake/submission.mjs +13 -21
- package/src/tui/session/session-api/intake.mjs +3 -3
- package/src/tui/session/session-api/integrations.mjs +5 -1
- package/src/tui/session/session-api/lifecycle/dispose.mjs +1 -0
- package/src/tui/session/session-api/lifecycle/inheritance.mjs +2 -2
- package/src/tui/session/session-api/lifecycle/session-reset.mjs +4 -0
- package/src/tui/session/session-api/lifecycle.mjs +71 -2
- package/src/tui/session/session-api/media.mjs +2 -2
- package/src/tui/session/session-api/quota-resume.mjs +21 -0
- package/src/tui/session/session-api/routes.mjs +2 -0
- package/src/tui/session/session-api/settings.mjs +1 -0
- package/src/tui/session/session-api.mjs +2 -0
- package/src/tui/session/session-flow/auto-clear.mjs +14 -6
- package/src/tui/session/session-flow/drain.mjs +52 -5
- package/src/tui/session/session-flow/queue/take-entries.mjs +2 -3
- package/src/tui/session/session-flow/queue.mjs +3 -0
- package/src/tui/session/session-flow/steering.mjs +11 -0
- package/src/tui/session/session-flow.mjs +1 -0
- package/src/tui/session/tool-approval.mjs +7 -2
- package/src/tui/session/transcript-intake.mjs +1 -0
- package/src/tui/session/turn.mjs +40 -3
- package/src/vendor/statusline/bin/statusline-route.mjs +3 -2
|
@@ -41,6 +41,7 @@ export async function consumeCompatChatCompletionStream(
|
|
|
41
41
|
parseToolCalls,
|
|
42
42
|
knownToolNames,
|
|
43
43
|
semanticIdleTimeoutMs,
|
|
44
|
+
firstByteTimeoutMs,
|
|
44
45
|
} = {}
|
|
45
46
|
) {
|
|
46
47
|
const idleOverrideEnabled = Number.isFinite(Number(semanticIdleTimeoutMs)) && Number(semanticIdleTimeoutMs) > 0;
|
|
@@ -57,7 +58,11 @@ export async function consumeCompatChatCompletionStream(
|
|
|
57
58
|
reportTransport(state);
|
|
58
59
|
const iterator = stream[Symbol.asyncIterator]();
|
|
59
60
|
let iteratorDone = false;
|
|
60
|
-
const firstByteTimeout = createTimeoutSignal(
|
|
61
|
+
const firstByteTimeout = createTimeoutSignal(
|
|
62
|
+
signal,
|
|
63
|
+
Number(firstByteTimeoutMs) > 0 ? Number(firstByteTimeoutMs) : PROVIDER_FIRST_BYTE_TIMEOUT_MS,
|
|
64
|
+
`${label} first byte`
|
|
65
|
+
);
|
|
61
66
|
try {
|
|
62
67
|
while (true) {
|
|
63
68
|
const { value: chunk, done } = await nextAsyncWithWatchdog(iterator, {
|
|
@@ -7,13 +7,15 @@ const MODEL_LIST_TIMEOUT_MS = resolveTimeoutMs('MIXDOG_COMPAT_MODEL_LIST_TIMEOUT
|
|
|
7
7
|
maxMs: PROVIDER_GENERATE_TOTAL_TIMEOUT_MS,
|
|
8
8
|
});
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
// `keyless` omits the Authorization header entirely (keyless endpoints);
|
|
11
|
+
// `signal` aborts the request together with the list timeout.
|
|
12
|
+
export async function fetchCompatModelItems(provider, { signal = null, keyless = false } = {}) {
|
|
13
|
+
const timeout = createTimeoutSignal(signal, MODEL_LIST_TIMEOUT_MS, `${provider.name} model list`);
|
|
12
14
|
try {
|
|
13
15
|
const res = await fetch(`${String(provider.baseURL || '').replace(/\/+$/, '')}/models`, {
|
|
14
16
|
method: 'GET',
|
|
15
17
|
headers: {
|
|
16
|
-
Authorization: `Bearer ${provider.apiKey || 'no-key'}
|
|
18
|
+
...(keyless ? {} : { Authorization: `Bearer ${provider.apiKey || 'no-key'}` }),
|
|
17
19
|
...(provider.defaultHeaders || {}),
|
|
18
20
|
},
|
|
19
21
|
signal: timeout.signal,
|
|
@@ -28,9 +30,9 @@ export async function fetchCompatModelItems(provider) {
|
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
export async function listCompatModels(provider, { throwOnError = false } = {}) {
|
|
33
|
+
export async function listCompatModels(provider, { throwOnError = false, signal = null } = {}) {
|
|
32
34
|
try {
|
|
33
|
-
const list = await provider._fetchModelItems();
|
|
35
|
+
const list = await provider._fetchModelItems({ signal });
|
|
34
36
|
const models = [];
|
|
35
37
|
for (const m of list) {
|
|
36
38
|
const contextWindow = Number(
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
xaiModelSupportsReasoningEffort,
|
|
4
4
|
normalizeOpencodeGoReasoningEffort,
|
|
5
5
|
} from './openai-compat-xai.mjs';
|
|
6
|
+
import { isCustomProviderName, resolveCustomReasoning } from './custom-reasoning.mjs';
|
|
6
7
|
|
|
7
8
|
function normalizeReasoningEffort(value, allowed) {
|
|
8
9
|
const effort = String(value ?? '')
|
|
@@ -15,6 +16,17 @@ function normalizeReasoningEffort(value, allowed) {
|
|
|
15
16
|
// backend are frequently rejected by another even when the core Chat schema
|
|
16
17
|
// is shared.
|
|
17
18
|
export function applyCompatProviderChatOptions(params, providerName, opts = {}, config = {}, modelInfo = null) {
|
|
19
|
+
if (isCustomProviderName(providerName)) {
|
|
20
|
+
// User-defined provider: the model row's reasoning settings decide the
|
|
21
|
+
// level and its wire shape (custom-reasoning.mjs).
|
|
22
|
+
const { fields } = resolveCustomReasoning({
|
|
23
|
+
protocol: 'openai-chat',
|
|
24
|
+
model: modelInfo,
|
|
25
|
+
effort: opts.effort ?? config?.reasoningEffort,
|
|
26
|
+
});
|
|
27
|
+
Object.assign(params, fields);
|
|
28
|
+
return params;
|
|
29
|
+
}
|
|
18
30
|
if (providerName === 'openrouter') {
|
|
19
31
|
const effort = normalizeReasoningEffort(opts.openRouterReasoningEffort ?? opts.effort ?? config?.reasoningEffort, [
|
|
20
32
|
'none',
|
|
@@ -19,9 +19,11 @@ import {
|
|
|
19
19
|
responseOutputText,
|
|
20
20
|
collectCompatResponseSearchSources,
|
|
21
21
|
toXaiResponsesInput,
|
|
22
|
+
xaiSystemInstructions,
|
|
22
23
|
} from './openai-compat-wire.mjs';
|
|
23
24
|
import { preparePdfTextForProvider } from './media-normalization.mjs';
|
|
24
25
|
import { normalizeOpencodeGoReasoningEffort } from './openai-compat-xai.mjs';
|
|
26
|
+
import { isCustomProviderName, resolveCustomReasoning } from './custom-reasoning.mjs';
|
|
25
27
|
import { createProviderReplay } from './lib/provider-replay.mjs';
|
|
26
28
|
import {
|
|
27
29
|
applyCompatToolChoice,
|
|
@@ -38,11 +40,17 @@ import {
|
|
|
38
40
|
// provider switch never replays foreign encrypted items into this gateway.
|
|
39
41
|
const COMPAT_RESPONSES_STATE_KEY = 'compatResponses';
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
// The request's `reasoning` field, or null. Custom providers snap onto their
|
|
44
|
+
// own model row (never promoting); gateway brands keep the catalog promotion.
|
|
45
|
+
function resolveReasoning(provider, useModel, opts) {
|
|
46
|
+
const effort = opts.effort ?? provider.config?.reasoningEffort;
|
|
47
|
+
const cached = (typeof provider.getCachedModelInfo === 'function' && provider.getCachedModelInfo(useModel)) || null;
|
|
48
|
+
if (isCustomProviderName(provider.name)) {
|
|
49
|
+
return resolveCustomReasoning({ protocol: 'openai-responses', model: cached, effort }).fields.reasoning || null;
|
|
50
|
+
}
|
|
51
|
+
const modelInfo = cached || getModelMetadataSync(useModel, provider.name);
|
|
52
|
+
const reasoningEffort = normalizeOpencodeGoReasoningEffort(effort, modelInfo);
|
|
53
|
+
return reasoningEffort ? { effort: reasoningEffort } : null;
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
/** The request body: lowered transcript plus the per-turn knobs (output cap,
|
|
@@ -63,8 +71,14 @@ function buildCompatResponsesParams({ provider, useModel, input, previousRespons
|
|
|
63
71
|
params.parallel_tool_calls = true;
|
|
64
72
|
}
|
|
65
73
|
applyCompatToolChoice(params, opts);
|
|
66
|
-
const
|
|
67
|
-
if (
|
|
74
|
+
const reasoning = resolveReasoning(provider, useModel, opts);
|
|
75
|
+
if (reasoning) params.reasoning = reasoning;
|
|
76
|
+
// Opt-in per-provider final shaping (a provider passes the hook in its config).
|
|
77
|
+
const hook = provider.config?.responsesParamsHook;
|
|
78
|
+
if (typeof hook === 'function') {
|
|
79
|
+
const modelInfo = (typeof provider.getCachedModelInfo === 'function' && provider.getCachedModelInfo(useModel)) || null;
|
|
80
|
+
hook(params, { useModel, opts, modelInfo });
|
|
81
|
+
}
|
|
68
82
|
return params;
|
|
69
83
|
}
|
|
70
84
|
|
|
@@ -146,8 +160,14 @@ export async function sendCompatResponses(provider, messages, useModel, tools, o
|
|
|
146
160
|
replayProvider,
|
|
147
161
|
providerName: provider.name,
|
|
148
162
|
nativePdf: provider.nativePdf,
|
|
163
|
+
// Opt-in: system prompts travel as top-level `instructions`, not input items.
|
|
164
|
+
includeSystem: provider.config?.responsesSystemAsInstructions !== true,
|
|
149
165
|
});
|
|
150
166
|
const params = buildCompatResponsesParams({ provider, useModel, input, previousResponseId, tools, opts });
|
|
167
|
+
if (provider.config?.responsesSystemAsInstructions === true) {
|
|
168
|
+
const instructions = xaiSystemInstructions(messages);
|
|
169
|
+
if (instructions) params.instructions = instructions;
|
|
170
|
+
}
|
|
151
171
|
|
|
152
172
|
try {
|
|
153
173
|
opts.onStageChange?.('requesting');
|
|
@@ -46,7 +46,13 @@ export function toOpenAIMessages(messages, providerName, options = {}) {
|
|
|
46
46
|
// their official multi-turn shape and cache prefix stability when prior
|
|
47
47
|
// assistant reasoning_content is replayed; reasoning_effort itself remains
|
|
48
48
|
// caller/user-selected.
|
|
49
|
-
|
|
49
|
+
//
|
|
50
|
+
// Custom providers pass options.customReplay (custom-reasoning.mjs replay
|
|
51
|
+
// eligibility). When it is a boolean it alone decides; a replaying custom
|
|
52
|
+
// endpoint also needs a non-empty reasoning_content on every assistant
|
|
53
|
+
// tool-call turn, so missing/empty reasoning there is padded with " ".
|
|
54
|
+
const customReplay = typeof options.customReplay === 'boolean' ? options.customReplay : null;
|
|
55
|
+
const replaysReasoningContent = customReplay ?? (options.replaysReasoningContent === true || providerName === 'xai');
|
|
50
56
|
const out = [];
|
|
51
57
|
const pendingToolMedia = [];
|
|
52
58
|
const openRouterReasoningDetails = (message) => {
|
|
@@ -56,7 +62,11 @@ export function toOpenAIMessages(messages, providerName, options = {}) {
|
|
|
56
62
|
return Array.isArray(details) && details.length ? details : null;
|
|
57
63
|
};
|
|
58
64
|
const attachAssistantReasoning = (msg, message) => {
|
|
59
|
-
if (
|
|
65
|
+
if (customReplay) {
|
|
66
|
+
const stored = typeof message.reasoningContent === 'string' ? message.reasoningContent : '';
|
|
67
|
+
if (stored) msg.reasoning_content = stored;
|
|
68
|
+
else if (Array.isArray(msg.tool_calls) && msg.tool_calls.length) msg.reasoning_content = ' ';
|
|
69
|
+
} else if (replaysReasoningContent && typeof message.reasoningContent === 'string') {
|
|
60
70
|
msg.reasoning_content = message.reasoningContent;
|
|
61
71
|
}
|
|
62
72
|
const reasoningDetails = openRouterReasoningDetails(message);
|
|
@@ -99,11 +99,16 @@ export class OpenAICompatProvider {
|
|
|
99
99
|
return new (loadOpenAI())({
|
|
100
100
|
baseURL: this.baseURL,
|
|
101
101
|
apiKey,
|
|
102
|
-
|
|
102
|
+
// A keyless endpoint (config.keyless) gets no Authorization header: the
|
|
103
|
+
// SDK drops a header whose default is null.
|
|
104
|
+
defaultHeaders: this.config?.keyless ? { ...this.defaultHeaders, Authorization: null } : this.defaultHeaders,
|
|
103
105
|
// The SDK's own retry loop (default 2) would nest underneath our
|
|
104
106
|
// withRetry wrapper and multiply tail latency on a transient
|
|
105
107
|
// backend. We own retry/backoff via withRetry, so disable the SDK's.
|
|
106
108
|
maxRetries: 0,
|
|
109
|
+
// Opt-in SDK constructor overrides (e.g. an explicit null organization so
|
|
110
|
+
// the SDK does not inherit OPENAI_ORG_ID from the environment).
|
|
111
|
+
...(this.config?.clientOptions || {}),
|
|
107
112
|
// Force the shared long-keepalive undici dispatcher to be installed
|
|
108
113
|
// globally (setGlobalDispatcher) so the SDK's global fetch rides a
|
|
109
114
|
// warm socket pool instead of Node's short-keepalive default. The
|
|
@@ -81,7 +81,7 @@ export function createOpenAIOAuthLogin(deps) {
|
|
|
81
81
|
return tokens;
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
async function beginOAuthLogin() {
|
|
84
|
+
async function beginOAuthLogin({ openBrowser = true } = {}) {
|
|
85
85
|
const pkce = createOAuthPkce(64);
|
|
86
86
|
const state = randomBytes(16).toString('hex');
|
|
87
87
|
const url = new URL(AUTHORIZE_URL);
|
|
@@ -141,6 +141,7 @@ export function createOpenAIOAuthLogin(deps) {
|
|
|
141
141
|
process.stderr.write(
|
|
142
142
|
`\n[openai-oauth] Open this URL to log in to ChatGPT (OpenAI OAuth):\n${url.toString()}\n\n`
|
|
143
143
|
);
|
|
144
|
+
if (!openBrowser) return;
|
|
144
145
|
try {
|
|
145
146
|
const { openInBrowser } = await import('../../../shared/open-url.mjs');
|
|
146
147
|
openInBrowser(url.toString());
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quota-reading.mjs — the authoritative "can this provider serve this model
|
|
3
|
+
* now?" answer a quota resume asks before it fires. Every account is
|
|
4
|
+
* re-measured with a forced usage reading (the account pool's own probe), and
|
|
5
|
+
* the answer is the pool's own choice over the fresh readings, so a resume
|
|
6
|
+
* fires exactly when a send would find a usable account.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
ACCOUNT_PROVIDERS,
|
|
10
|
+
chooseProviderAccount,
|
|
11
|
+
quotaWindowAppliesToModel,
|
|
12
|
+
readProviderAccountPool,
|
|
13
|
+
} from '../../../shared/provider-accounts.mjs';
|
|
14
|
+
import { fetchOAuthUsageSnapshot } from './oauth-usage.mjs';
|
|
15
|
+
import { getProvider } from './registry.mjs';
|
|
16
|
+
|
|
17
|
+
const forcedReading = (routeInfo, providerObj) =>
|
|
18
|
+
fetchOAuthUsageSnapshot(routeInfo, providerObj, () => {}, { force: true }).catch(() => null);
|
|
19
|
+
|
|
20
|
+
const positive = (value) => typeof value === 'number' && Number.isFinite(value) && value > 0;
|
|
21
|
+
|
|
22
|
+
/** Measured evidence in this one reading that `model` can be served: an
|
|
23
|
+
* applicable window with a numeric meter and none of them spent, or, when
|
|
24
|
+
* the account pays with credits, a positive measured balance. A failed,
|
|
25
|
+
* unknown or balance-only reading is no evidence. */
|
|
26
|
+
function readingServesModel(reading, model, useCredits) {
|
|
27
|
+
if (!reading) return false;
|
|
28
|
+
const balance = reading.balance;
|
|
29
|
+
if (
|
|
30
|
+
useCredits &&
|
|
31
|
+
balance &&
|
|
32
|
+
(balance.unlimited === true || positive(balance.remainingCredits) || positive(balance.remainingUsd))
|
|
33
|
+
) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
const measured = (reading.quotaWindows || []).filter(
|
|
37
|
+
(window) =>
|
|
38
|
+
quotaWindowAppliesToModel(window, model) && typeof window?.usedPct === 'number' && Number.isFinite(window.usedPct)
|
|
39
|
+
);
|
|
40
|
+
return measured.length > 0 && measured.every((window) => window.usedPct < 100);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function providerQuotaUsable(providerName, model = '', { provider = getProvider(providerName) } = {}) {
|
|
44
|
+
// Only an account-pooled provider reports exhausted quota at all.
|
|
45
|
+
if (!ACCOUNT_PROVIDERS.includes(providerName)) return true;
|
|
46
|
+
const pool = readProviderAccountPool(providerName);
|
|
47
|
+
const useCredits = pool.useCredits === true;
|
|
48
|
+
// Only this reading counts as evidence: an account whose forced reading
|
|
49
|
+
// failed or measured nothing for this model stays exhausted however its
|
|
50
|
+
// older record reads; the pool's own choice (selection, blocks) decides
|
|
51
|
+
// among the rest.
|
|
52
|
+
if (pool.accounts.length > 0) {
|
|
53
|
+
const readings = await Promise.all(
|
|
54
|
+
pool.accounts.map((row) =>
|
|
55
|
+
forcedReading(
|
|
56
|
+
{ provider: providerName, accountId: row.id },
|
|
57
|
+
typeof provider?.forAccount === 'function' ? provider.forAccount(row.id) : provider
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
);
|
|
61
|
+
const unproven = new Set(
|
|
62
|
+
pool.accounts.filter((_, index) => !readingServesModel(readings[index], model, useCredits)).map((row) => row.id)
|
|
63
|
+
);
|
|
64
|
+
if (unproven.size === pool.accounts.length) return false;
|
|
65
|
+
return chooseProviderAccount(readProviderAccountPool(providerName), unproven, Date.now(), model) !== null;
|
|
66
|
+
}
|
|
67
|
+
// No roster: the single credential's fresh meter decides.
|
|
68
|
+
const reading = await forcedReading({ provider: providerName, model: '' }, provider);
|
|
69
|
+
return readingServesModel(reading, model, useCredits);
|
|
70
|
+
}
|
|
@@ -256,21 +256,50 @@ async function _initProvidersUnsynchronized(config, signal = null) {
|
|
|
256
256
|
return { name, inst, sig };
|
|
257
257
|
} catch (err) {
|
|
258
258
|
const msg = err instanceof Error ? err.message : String(err);
|
|
259
|
-
|
|
259
|
+
const error = new Error(`[provider] Failed to init "${name}": ${msg}`);
|
|
260
|
+
// One invalid custom provider is isolated: it is skipped (never
|
|
261
|
+
// partially loaded) and reported, the rest of the registry builds.
|
|
262
|
+
if (isCustomProviderConfig(name, cfg) && !signal?.aborted) return { name, error, isolated: true };
|
|
263
|
+
return { name, error };
|
|
260
264
|
}
|
|
261
265
|
})
|
|
262
266
|
);
|
|
267
|
+
const nextInitErrors = new Map();
|
|
263
268
|
for (const result of enabledResults) {
|
|
264
269
|
throwIfAborted(signal);
|
|
265
270
|
if (!result) continue;
|
|
271
|
+
if (result.isolated) {
|
|
272
|
+
nextInitErrors.set(result.name, result.error.message);
|
|
273
|
+
process.stderr.write(`${result.error.message} (provider skipped; fix its configuration)\n`);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
266
276
|
if (result.error) throw result.error;
|
|
267
277
|
next.set(result.name, result.inst);
|
|
268
278
|
if (result.sig !== null) nextSignatures.set(result.name, result.sig);
|
|
269
279
|
}
|
|
280
|
+
_providerInitErrors = nextInitErrors;
|
|
270
281
|
// Second guard: every entry was disabled. Same reasoning — keep the
|
|
271
|
-
// existing registry rather than going dark.
|
|
282
|
+
// existing registry rather than going dark. Isolation still holds: a live
|
|
283
|
+
// instance of a provider that is now an invalid custom config is removed
|
|
284
|
+
// (the rest of the registry stays), and the recorded reasons are reported.
|
|
272
285
|
if (next.size === 0) {
|
|
273
|
-
|
|
286
|
+
let removed = false;
|
|
287
|
+
for (const name of nextInitErrors.keys()) {
|
|
288
|
+
removed = providers.delete(name) || removed;
|
|
289
|
+
signatures.delete(name);
|
|
290
|
+
}
|
|
291
|
+
if (removed) {
|
|
292
|
+
_providerCatalogRevision += 1;
|
|
293
|
+
_startupCatalogRefreshPromise = null;
|
|
294
|
+
// The live registry no longer reflects the last applied config.
|
|
295
|
+
_lastAppliedSig = null;
|
|
296
|
+
}
|
|
297
|
+
const reasons = [...nextInitErrors.values()];
|
|
298
|
+
throw noEnabledProvidersError(
|
|
299
|
+
`[provider] all providers disabled in config — refusing to clear registry${
|
|
300
|
+
reasons.length ? `; invalid custom providers skipped: ${reasons.join('; ')}` : ''
|
|
301
|
+
}`
|
|
302
|
+
);
|
|
274
303
|
}
|
|
275
304
|
// OAuth preservation guard. anthropic-oauth / openai-oauth are NOT stored
|
|
276
305
|
// in mixdog-config.json — buildDefaultConfig injects them at runtime by
|
|
@@ -320,6 +349,12 @@ async function _initProvidersUnsynchronized(config, signal = null) {
|
|
|
320
349
|
signatures.clear();
|
|
321
350
|
for (const [k, v] of nextSignatures) signatures.set(k, v);
|
|
322
351
|
}
|
|
352
|
+
|
|
353
|
+
// Custom providers skipped by the last applied init, by name → error message.
|
|
354
|
+
let _providerInitErrors = new Map();
|
|
355
|
+
export function getProviderInitErrors() {
|
|
356
|
+
return new Map(_providerInitErrors);
|
|
357
|
+
}
|
|
323
358
|
// Register a lazily self-healed OAuth instance. Bumps the catalog revision and
|
|
324
359
|
// drops the settled startup-refresh epoch so the newcomer's catalog is fetched
|
|
325
360
|
// instead of inheriting a refresh that finished before it existed.
|
|
@@ -21,6 +21,8 @@ import { readBridgeDiscovery } from '../../../bridge-discovery.mjs';
|
|
|
21
21
|
import { HEADLESS_MODEL_TOOL_NAMES, HEADLESS_TOOL_PROFILE, normalizeToolProfile } from './tool-profile.mjs';
|
|
22
22
|
import { DEFERRED_DEFAULT_LEAD_TOOLS } from './tool-catalog-data.mjs';
|
|
23
23
|
import { gitExecutablePresent } from '../../../shared/path-executable.mjs';
|
|
24
|
+
import { onnxRuntimeSupported } from '../../../shared/onnx-runtime-support.mjs';
|
|
25
|
+
import { setBuiltinInstalledInConfig, withGrandfatheredBuiltins } from '../../../shared/builtin-grandfathering.mjs';
|
|
24
26
|
|
|
25
27
|
// Browser Use / Computer Use have no install marker: the desktop app publishes
|
|
26
28
|
// a loopback bridge discovery file while the feature is on. The same file
|
|
@@ -37,8 +39,14 @@ function bridgePresent(file) {
|
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
40
|
-
export const INSTALLABLE_BUILTIN_IDS = Object.freeze([
|
|
41
|
-
|
|
42
|
+
export const INSTALLABLE_BUILTIN_IDS = Object.freeze([
|
|
43
|
+
'git',
|
|
44
|
+
'memory',
|
|
45
|
+
'office',
|
|
46
|
+
'tidy',
|
|
47
|
+
'localProvider',
|
|
48
|
+
'autoEffort',
|
|
49
|
+
]);
|
|
42
50
|
|
|
43
51
|
/** Model-facing activation for one gated feature: an explicit MIXDOG_FEATURE_*
|
|
44
52
|
* env override (headless/bench) wins, otherwise the install marker and the
|
|
@@ -85,6 +93,7 @@ export function builtinFeatureActive(configLike, id) {
|
|
|
85
93
|
// the judge model downloads at boot, and until it is on disk every call keeps
|
|
86
94
|
// the user's effort.
|
|
87
95
|
if (id === 'autoEffort') {
|
|
96
|
+
if (!onnxRuntimeSupported()) return false;
|
|
88
97
|
return featureEnvOverride('MIXDOG_FEATURE_AUTO_EFFORT') ?? moduleEnabled(configLike, 'autoEffort', true);
|
|
89
98
|
}
|
|
90
99
|
// Media Studio is a hidden built-in like setup: no Settings card, no install
|
|
@@ -117,7 +126,13 @@ export function builtinFeatureActive(configLike, id) {
|
|
|
117
126
|
* overrides), which the caller passes in. */
|
|
118
127
|
export function featureDisallowedToolsFor(
|
|
119
128
|
configLike,
|
|
120
|
-
{
|
|
129
|
+
{
|
|
130
|
+
browserAvailable = false,
|
|
131
|
+
computerAvailable = false,
|
|
132
|
+
terminalAvailable = false,
|
|
133
|
+
toolProfile = 'interactive',
|
|
134
|
+
gitAvailable,
|
|
135
|
+
} = {}
|
|
121
136
|
) {
|
|
122
137
|
const browser = featureEnvOverride('MIXDOG_FEATURE_BROWSER') ?? browserAvailable === true;
|
|
123
138
|
const computer = featureEnvOverride('MIXDOG_FEATURE_COMPUTER') ?? computerAvailable === true;
|
|
@@ -162,35 +177,4 @@ export function localGitToolsActive(configLike, { gitAvailable } = {}) {
|
|
|
162
177
|
);
|
|
163
178
|
}
|
|
164
179
|
|
|
165
|
-
export
|
|
166
|
-
const next = { ...(configLike || {}) };
|
|
167
|
-
next.builtins = { ...(next.builtins || {}) };
|
|
168
|
-
if (installed === true) {
|
|
169
|
-
next.builtins[id] = { ...(next.builtins[id] || {}), installed: true };
|
|
170
|
-
} else {
|
|
171
|
-
const entry = { ...(next.builtins[id] || {}) };
|
|
172
|
-
delete entry.installed;
|
|
173
|
-
next.builtins[id] = entry;
|
|
174
|
-
}
|
|
175
|
-
return next;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/** Stamp or grandfather the `builtins` section on config adoption. Returns the
|
|
179
|
-
* same object when the section already exists, so callers can use identity to
|
|
180
|
-
* decide whether a persist is needed. */
|
|
181
|
-
export function withGrandfatheredBuiltins(configLike) {
|
|
182
|
-
const config = configLike && typeof configLike === 'object' ? configLike : {};
|
|
183
|
-
if (config.builtins && typeof config.builtins === 'object') return config;
|
|
184
|
-
let next = { ...config, builtins: {} };
|
|
185
|
-
// User-mark keys only: a default in-memory config may carry harmless
|
|
186
|
-
// structural keys before onboarding ever writes, and must stay "fresh".
|
|
187
|
-
const existingProfile = ['presets', 'providers', 'modules', 'default', 'memoryTools', 'recap'].some(
|
|
188
|
-
(key) => config[key] !== undefined
|
|
189
|
-
);
|
|
190
|
-
if (existingProfile) {
|
|
191
|
-
for (const id of GRANDFATHERED_BUILTIN_IDS) {
|
|
192
|
-
next = setBuiltinInstalledInConfig(next, id, true);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
return next;
|
|
196
|
-
}
|
|
180
|
+
export { setBuiltinInstalledInConfig, withGrandfatheredBuiltins };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Reasoning-effort catalogs and coercion. Pure helpers.
|
|
2
2
|
import { clean } from './session-text.mjs';
|
|
3
|
+
import { customEffortLevels, snapCustomEffort } from '../providers/custom-reasoning.mjs';
|
|
3
4
|
|
|
4
5
|
const TOOL_MODES = new Set(['full', 'readonly', 'lead']);
|
|
5
6
|
const ALL_EFFORT_LEVELS = new Set(['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra']);
|
|
@@ -71,6 +72,9 @@ export function normalizeEffortInput(value) {
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
export function effortOptionsFor(provider, model) {
|
|
75
|
+
// Custom-provider rows declare their levels authoritatively: an explicit []
|
|
76
|
+
// (or no declaration) means none, with no family/provider fallback.
|
|
77
|
+
if (model?.customReasoning) return customEffortLevels(model) || [];
|
|
74
78
|
const providerAllowed = EFFORT_OPTIONS_BY_PROVIDER[provider] || null;
|
|
75
79
|
const normalizeCatalogValues = (values) => {
|
|
76
80
|
const seen = new Set();
|
|
@@ -106,6 +110,7 @@ export function effortOptionsFor(provider, model) {
|
|
|
106
110
|
|
|
107
111
|
export function coerceEffortFor(provider, model, effort) {
|
|
108
112
|
if (!effort) return null;
|
|
113
|
+
if (model?.customReasoning) return snapCustomEffort(customEffortLevels(model), effort);
|
|
109
114
|
const allowed = effortOptionsFor(provider, model);
|
|
110
115
|
if (!allowed || allowed.length === 0) return null;
|
|
111
116
|
if (allowed.includes(effort)) return effort;
|
|
@@ -117,19 +117,6 @@ function assertCompletionEvidence(goal, at) {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
// Three consecutive turns naming the same blocker confirm it; true once the
|
|
121
|
-
// audit reaches that count.
|
|
122
|
-
function recordBlockAudit(goal, blocker) {
|
|
123
|
-
const turn = goal.turnCount;
|
|
124
|
-
const previous = goal.blockAudit;
|
|
125
|
-
const sameBlocker = previous?.reason === blocker;
|
|
126
|
-
let count = 1;
|
|
127
|
-
if (sameBlocker && previous.turn === turn) count = previous.count;
|
|
128
|
-
else if (sameBlocker && previous.turn === turn - 1) count = previous.count + 1;
|
|
129
|
-
goal.blockAudit = { reason: blocker, turn, count };
|
|
130
|
-
return count >= 3;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
120
|
export function createGoalMutations(ctx) {
|
|
134
121
|
const { root, now, defaultTimeLimitMs, turnGoalIds, turnStartedAt } = ctx;
|
|
135
122
|
|
|
@@ -201,10 +188,6 @@ export function createGoalMutations(ctx) {
|
|
|
201
188
|
clearTurnFailures(goal);
|
|
202
189
|
} else {
|
|
203
190
|
const blocker = validateGoalBlocker(args.blocker);
|
|
204
|
-
if (!recordBlockAudit(goal, blocker)) {
|
|
205
|
-
goal.updatedAt = at;
|
|
206
|
-
return ctx.commit(id, goal);
|
|
207
|
-
}
|
|
208
191
|
settleActiveClock(id, goal, at);
|
|
209
192
|
goal.status = 'blocked';
|
|
210
193
|
goal.blocker = blocker;
|
|
@@ -149,7 +149,6 @@ export function normalizeStoredGoal(value, sessionId, resumedAt = Date.now()) {
|
|
|
149
149
|
tasks: normalizeGoalTasks(value.tasks ?? value.criteria ?? []),
|
|
150
150
|
blocker: clean(value.blocker),
|
|
151
151
|
pauseReason: ['waiting', 'cancelled'].includes(value.pauseReason) ? value.pauseReason : 'user',
|
|
152
|
-
blockAudit: value.blockAudit && typeof value.blockAudit === 'object' ? value.blockAudit : null,
|
|
153
152
|
failureReason: clean(value.failureReason),
|
|
154
153
|
failureCount: Math.max(0, Math.floor(Number(value.failureCount) || 0)),
|
|
155
154
|
// Observations report progress; they never decide when work is complete.
|
|
@@ -207,7 +206,6 @@ export function publicGoal(goal, now = Date.now()) {
|
|
|
207
206
|
...goalTaskProgress(tasks),
|
|
208
207
|
blocker: goal.blocker || '',
|
|
209
208
|
pauseReason: goal.pauseReason || 'user',
|
|
210
|
-
blockAudit: goal.blockAudit || null,
|
|
211
209
|
timeLimitMs: goal.timeLimitMs,
|
|
212
210
|
timeMode: goal.timeMode,
|
|
213
211
|
timeUsedMs,
|
|
@@ -288,7 +286,6 @@ export function activateGoal(goal, now = Date.now()) {
|
|
|
288
286
|
goal.completedAt = null;
|
|
289
287
|
goal.archivedAt = null;
|
|
290
288
|
goal.blocker = '';
|
|
291
|
-
goal.blockAudit = null;
|
|
292
289
|
// A running Goal has no pause to explain. Leaving the previous reason behind
|
|
293
290
|
// left active records reading as a wait that had already ended.
|
|
294
291
|
goal.pauseReason = 'user';
|
|
@@ -137,7 +137,7 @@ export function continuationPrompt(goal, { idleReview = false, includeRules = tr
|
|
|
137
137
|
'- Before completing, audit each user condition on its own: name the evidence that would prove it, inspect current state for it, and match the check to the claim. The audit must prove completion, not merely fail to find remaining work.',
|
|
138
138
|
'- Missing or insufficient evidence means incomplete; keep working. Complete only when every user condition is proven met and no required work remains. Existing checks need not be repeated and verification need not be a separate task row.',
|
|
139
139
|
'- Only the user retires a condition: drop a task because the user changed the objective, never to reach completion — a task dropped this turn blocks completion.',
|
|
140
|
-
'-
|
|
140
|
+
'- block stops the Goal immediately. Do not block the first time a blocker appears: block only after the same blocking condition has repeated for at least 3 consecutive Goal turns, counting the user-triggered turn and automatic continuations; after a resume, count only turns since that resume. Until then, re-check the blocker against current state with a tool each turn rather than restating it. Exception: when current state verifies an impasse that only the user can clear (their action, approval, credentials, or a resource they control), block on that turn. Once the threshold is met, block instead of reporting the same blocker again. Treat equivalent blockers as the same condition even when their wording or stated next step changes. Never block for difficulty, uncertainty, merely incomplete work, a choice you can make, confirmation for in-scope work, or clarification that would only help.',
|
|
141
141
|
'- Never complete or block merely because time is low or the turn is ending.',
|
|
142
142
|
'</system-reminder>',
|
|
143
143
|
].join('\n');
|
|
@@ -99,7 +99,7 @@ export const GOAL_TOOL_DEFS = Object.freeze([
|
|
|
99
99
|
minLength: 1,
|
|
100
100
|
maxLength: 1_000,
|
|
101
101
|
description:
|
|
102
|
-
'block: stable description of
|
|
102
|
+
'block: stable description of an impasse only external state or the user can clear; stops the Goal immediately.',
|
|
103
103
|
},
|
|
104
104
|
},
|
|
105
105
|
required: ['action'],
|
|
@@ -27,7 +27,6 @@ function toolReply(ctx, id, goal, { full = false, previousIds = null } = {}) {
|
|
|
27
27
|
timeUsedMs: goal.timeUsedMs,
|
|
28
28
|
};
|
|
29
29
|
if (goal.blocker) goalView.blocker = goal.blocker;
|
|
30
|
-
if (goal.blockAudit) goalView.blockAudit = goal.blockAudit;
|
|
31
30
|
if (goal.needsTaskReview) goalView.needsTaskReview = true;
|
|
32
31
|
}
|
|
33
32
|
const result = { goal: goalView, remaining_ms: goal?.remainingMs ?? null };
|
|
@@ -76,7 +75,7 @@ async function pauseOrResume(ctx, id, args, action) {
|
|
|
76
75
|
throw new Error(`a ${current.status === 'complete' ? 'completed' : 'stopped'} Goal cannot be paused or resumed`);
|
|
77
76
|
}
|
|
78
77
|
// The model pauses only at the user's explicit request, so its pause is the
|
|
79
|
-
// user's pause; waits on the user go through
|
|
78
|
+
// user's pause; waits on the user go through block.
|
|
80
79
|
const change =
|
|
81
80
|
action === 'resume'
|
|
82
81
|
? { updates: args.updates, tasks: args.tasks, duration: timeLimitMs, timeMode: args.time_mode }
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
checkpointActiveClock,
|
|
11
11
|
clearTurnFailures,
|
|
12
12
|
publicGoal,
|
|
13
|
+
resumeGoalState,
|
|
13
14
|
runningAgentWork,
|
|
14
15
|
startActiveClock,
|
|
15
16
|
stopActiveClock,
|
|
@@ -28,8 +29,7 @@ function settledDurationWait(goal) {
|
|
|
28
29
|
goal.remainingMs > 0 &&
|
|
29
30
|
!goal.needsTaskReview &&
|
|
30
31
|
goal.tasks.length > 0 &&
|
|
31
|
-
goal.tasks.every((task) => GOAL_TASK_SETTLED.includes(task.status))
|
|
32
|
-
goal.blockAudit?.turn !== goal.turnCount
|
|
32
|
+
goal.tasks.every((task) => GOAL_TASK_SETTLED.includes(task.status))
|
|
33
33
|
);
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -89,8 +89,7 @@ function applyTurnOutcome(goal, detail, at) {
|
|
|
89
89
|
}
|
|
90
90
|
if (status === 'failed' && goal.status === 'active') {
|
|
91
91
|
// A failed turn has exhausted its recovery. Starting another Goal turn
|
|
92
|
-
// retries the same terminal error with a larger transcript.
|
|
93
|
-
// separate external-blocker audit still spans 3 turns.
|
|
92
|
+
// retries the same terminal error with a larger transcript.
|
|
94
93
|
stopActiveClock(goal, at);
|
|
95
94
|
goal.status = 'blocked';
|
|
96
95
|
goal.failureReason = clean(detail?.error) || 'Goal turn failed';
|
|
@@ -169,6 +168,21 @@ export function createGoalTurnLifecycle(ctx) {
|
|
|
169
168
|
return published;
|
|
170
169
|
});
|
|
171
170
|
},
|
|
171
|
+
// A quota resume revives exactly the Goal its failure suspended, and only
|
|
172
|
+
// while that Goal is still waiting on the limit: a Goal the user paused,
|
|
173
|
+
// stopped, replaced or completed meanwhile stays as it is.
|
|
174
|
+
reactivateUsageLimited(sessionId, goalId) {
|
|
175
|
+
return withMutation(sessionId, async (id) => {
|
|
176
|
+
const goal = readRecord(id).goal;
|
|
177
|
+
if (!goal || goal.archivedAt || goal.id !== clean(goalId) || goal.status !== 'usage_limited') return null;
|
|
178
|
+
try {
|
|
179
|
+
resumeGoalState(goal, now());
|
|
180
|
+
} catch {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
return commit(id, goal);
|
|
184
|
+
});
|
|
185
|
+
},
|
|
172
186
|
continuation(sessionId, { agentStatus = null } = {}) {
|
|
173
187
|
const goal = visibleSnapshot(sessionId);
|
|
174
188
|
if (goal?.status !== 'active') return { run: false, reason: goal?.status || 'missing', goal };
|
|
@@ -14,9 +14,15 @@ output dependency. Prefer parallel scopes over sequential slices in one agent.
|
|
|
14
14
|
If the plan has only one scope, Lead executes it directly.`,
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
const SOLO = `# Orchestration Mode: Solo
|
|
18
|
+
|
|
19
|
+
Lead executes all work directly and never delegates on its own judgment.
|
|
20
|
+
Only when the user explicitly asks for delegation, load the deferred \`agent\`
|
|
21
|
+
tool and brief each agent as the Lead rules describe.`;
|
|
22
|
+
|
|
17
23
|
export function orchestrationInstructions(value) {
|
|
18
24
|
const mode = normalizeOrchestrationMode(value);
|
|
19
|
-
if (mode === 'none') return
|
|
25
|
+
if (mode === 'none') return SOLO;
|
|
20
26
|
return `# Orchestration Mode: ${mode[0].toUpperCase()}${mode.slice(1)}
|
|
21
27
|
|
|
22
28
|
${BATCHING[mode]}
|