mixdog 1.2.1 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/gate-local.mjs +19 -3
- package/scripts/model-catalog-audit.mjs +1 -15
- package/src/agents/reviewer/AGENT.md +3 -3
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +1 -0
- package/src/repl/slash-commands.mjs +16 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +29 -4
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +10 -0
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +36 -7
- package/src/runtime/agent/orchestrator/mcp/client-handshake.mjs +49 -10
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +40 -3
- package/src/runtime/agent/orchestrator/mcp/client.mjs +230 -96
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +103 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +150 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +111 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-probe.mjs +87 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +95 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-request.mjs +122 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +9 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +229 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +188 -29
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +94 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +96 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +39 -18
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +55 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +25 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +18 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +13 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-midstream.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-recovery.mjs +23 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +46 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request.mjs +14 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +73 -35
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +69 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +34 -7
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +30 -9
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +30 -14
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +82 -9
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +38 -5
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-guards.mjs +97 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +102 -23
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +103 -33
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +29 -6
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +18 -1
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +77 -17
- package/src/runtime/agent/orchestrator/providers/gemini-cache-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +29 -8
- package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +191 -0
- package/src/runtime/agent/orchestrator/providers/gemini-family.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/gemini-history-signatures.mjs +9 -4
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +180 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +21 -82
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -12
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +21 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +56 -23
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +68 -19
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +4 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-result.mjs +22 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-error-contract.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +16 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-state.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +94 -13
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +66 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +23 -14
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +42 -25
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +274 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +72 -32
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-outcome.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-stamps.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-state.mjs +2 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +1 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-dispatch.mjs +34 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +22 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +5 -9
- package/src/runtime/agent/orchestrator/providers/openai-retry-policy.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-handshake.mjs +16 -8
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/policy.mjs +25 -14
- package/src/runtime/agent/orchestrator/providers/openai-ws-terminal.mjs +18 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +31 -20
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +581 -0
- package/src/runtime/agent/orchestrator/providers/retry-backoff.mjs +46 -7
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +118 -219
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +148 -45
- package/src/runtime/agent/orchestrator/providers/retry-midstream.mjs +81 -16
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-reminder.mjs +0 -18
- package/src/runtime/agent/orchestrator/runtime-core/model-capabilities.mjs +57 -28
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +19 -16
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -97
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +102 -18
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +30 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +41 -21
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +5 -10
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +18 -7
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +155 -29
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +22 -10
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +20 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +12 -15
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
- package/src/runtime/memory/lib/pg/process.mjs +34 -24
- package/src/runtime/memory/lib/pg/supervisor.mjs +16 -13
- package/src/runtime/office/README.md +1 -1
- package/src/runtime/office/shared/values.mjs +0 -5
- package/src/runtime/shared/background-tasks.mjs +92 -14
- package/src/runtime/shared/err-text.mjs +25 -0
- package/src/runtime/shared/error-presentation.mjs +159 -19
- package/src/runtime/shared/llm/anthropic-betas.mjs +4 -3
- package/src/runtime/shared/llm/anthropic-effort.mjs +123 -134
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +50 -14
- package/src/runtime/shared/llm/model-catalog.mjs +53 -40
- package/src/runtime/shared/llm/openai-daybreak.mjs +4 -16
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +11 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +13 -4
- package/src/runtime/shared/llm/usage-rollup.mjs +0 -1
- package/src/runtime/shared/model-display.mjs +13 -0
- package/src/runtime/shared/quota-resume-store.mjs +11 -0
- package/src/runtime/shared/session-baseline-store.mjs +960 -0
- package/src/runtime/shared/statusline/statusline-format.mjs +4 -4
- package/src/runtime/shared/task-notification-envelope.mjs +2 -2
- package/src/runtime/shared/turn-changes.mjs +447 -219
- package/src/runtime/shared/turn-snapshot-store.mjs +182 -1
- package/src/runtime/shared/turn-snapshot.mjs +170 -48
- package/src/runtime/shared/turn-worktree-snapshot.mjs +135 -46
- package/src/runtime/shared/worktrees/contract.mjs +0 -24
- package/src/runtime/shared/worktrees/test-support.mjs +19 -4
- package/src/session-runtime/boot/apis.mjs +6 -0
- package/src/session-runtime/boot/infrastructure.mjs +6 -0
- package/src/session-runtime/lifecycle/session-catalog.mjs +2 -0
- package/src/session-runtime/lifecycle/teardown.mjs +1 -1
- package/src/session-runtime/mcp-glue/connect.mjs +29 -9
- package/src/session-runtime/mcp-glue/server-catalog.mjs +20 -3
- package/src/session-runtime/mcp-glue.mjs +3 -0
- package/src/session-runtime/mcp-oauth-login.mjs +174 -0
- package/src/session-runtime/model-route/model-switch-target.mjs +34 -0
- package/src/session-runtime/model-route/set-route.mjs +83 -15
- package/src/session-runtime/model-route/web-search-route.mjs +1 -1
- package/src/session-runtime/native-web-search/routes.mjs +22 -3
- package/src/session-runtime/provider-models/model-meta.mjs +12 -3
- package/src/session-runtime/quick-model-rows/web-search-rows.mjs +13 -8
- package/src/session-runtime/quick-web-search-models.mjs +13 -38
- package/src/session-runtime/resource-extension-sync.mjs +4 -0
- package/src/session-runtime/resource-mcp-api.mjs +98 -2
- package/src/session-runtime/runtime-review-api.mjs +13 -0
- package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +13 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +7 -2
- package/src/session-runtime/services/agent-tool/send-flow.mjs +22 -4
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +2 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +22 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +8 -1
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -2
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +17 -0
- package/src/session-runtime/settings-compaction-api.mjs +1 -0
- package/src/standalone/daemon-drain-policy.mjs +8 -0
- package/src/standalone/daemon-telemetry.mjs +55 -0
- package/src/standalone/daemon.mjs +3 -7
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +33 -2
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +26 -10
- package/src/standalone/session-service/agent-tree.mjs +12 -0
- package/src/standalone/session-service/quota-resume.mjs +8 -0
- package/src/standalone/session-service/retention/busy.mjs +15 -1
- package/src/standalone/session-service-api.mjs +5 -0
- package/src/standalone/session-service.mjs +1 -0
- package/src/tui/app/app-view.jsx +1 -0
- package/src/tui/app/create-app-pickers.mjs +4 -0
- package/src/tui/app/extension-pickers/mcp-login.mjs +107 -0
- package/src/tui/app/extension-pickers/mcp-servers-picker.mjs +2 -1
- package/src/tui/app/extension-pickers.mjs +2 -1
- package/src/tui/app/model-options.mjs +11 -16
- package/src/tui/app/slash-commands.mjs +7 -2
- package/src/tui/app/slash-dispatch/panel-commands.mjs +16 -1
- package/src/tui/components/StatusLine.jsx +2 -0
- package/src/tui/components/status-line/local-line.mjs +5 -2
- package/src/tui/components/status-line/use-statusline-text.mjs +7 -1
- package/src/tui/dist/index.mjs +816 -43
- package/src/tui/session/quota-resume.mjs +46 -14
- package/src/tui/session/session-api/commands.mjs +33 -24
- package/src/tui/session/session-api/extensions.mjs +22 -0
- package/src/tui/session/session-api/integrations.mjs +7 -0
- package/src/tui/session/session-api/lifecycle.mjs +5 -1
- package/src/tui/session/session-api/routes.mjs +71 -7
- package/src/tui/session/session-api/settings.mjs +5 -14
- package/src/tui/session/session-flow/drain.mjs +7 -1
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +10 -4
- package/src/tui/session/turn.mjs +38 -15
- package/src/tui/statusline-ansi-bridge.mjs +1 -1
- package/src/ui/statusline-agents.mjs +15 -4
- package/src/ui/statusline.mjs +7 -3
- package/src/ui/streaming-markdown-heal.mjs +34 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +14 -4
- package/src/runtime/memory/lib/memory-session-merge.mjs +0 -32
- package/src/runtime/shared/llm/usage-session-history.mjs +0 -266
|
@@ -2,16 +2,14 @@ import { normalizeOrchestrationMode } from '../../../shared/orchestration.mjs';
|
|
|
2
2
|
|
|
3
3
|
const BATCHING = {
|
|
4
4
|
focused: `Lead executes the main scope directly. Delegate only substantial,
|
|
5
|
-
self-contained supporting work
|
|
6
|
-
|
|
7
|
-
balanced: `Delegate independent work by coherent feature or module
|
|
8
|
-
tasks within the same change scope
|
|
9
|
-
splitting every file or step
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
output dependency. Prefer parallel scopes over sequential slices in one agent.
|
|
14
|
-
If the plan has only one scope, Lead executes it directly.`,
|
|
5
|
+
self-contained supporting work, one agent per role; batch related tasks
|
|
6
|
+
rather than splitting them for parallelism.`,
|
|
7
|
+
balanced: `Delegate independent work by coherent feature or module, one agent per
|
|
8
|
+
scope. Batch related tasks within the same change scope rather than
|
|
9
|
+
splitting every file or step.`,
|
|
10
|
+
swarm: `Delegate every independent scope to its own agent. Treat disjoint file or
|
|
11
|
+
module sets as independent and merge scopes only on a true output
|
|
12
|
+
dependency. Prefer parallel scopes over sequential slices in one agent.`,
|
|
15
13
|
};
|
|
16
14
|
|
|
17
15
|
const SOLO = `# Orchestration Mode: Solo
|
|
@@ -27,6 +25,12 @@ export function orchestrationInstructions(value) {
|
|
|
27
25
|
|
|
28
26
|
${BATCHING[mode]}
|
|
29
27
|
|
|
28
|
+
The mode sets how work splits; an agent's description sets its role and when
|
|
29
|
+
it runs. Splitting applies to every role alike — implementation, review,
|
|
30
|
+
investigation, writing — by file, module, commit range or risk area.
|
|
31
|
+
If the plan has only one implementation scope, Lead executes it directly; a
|
|
32
|
+
role tied to a point in the work, such as the final check, still runs then.
|
|
33
|
+
|
|
30
34
|
Delegation is for work whose size justifies a brief. When the brief would have
|
|
31
35
|
to spell out the edits themselves — a few localized changes whose sites Lead
|
|
32
36
|
already holds — Lead makes them directly in every mode; the number of files or
|
|
@@ -34,10 +38,9 @@ scopes does not change this.
|
|
|
34
38
|
|
|
35
39
|
Dispatch all ready independent scopes in one turn. Only a scope that requires
|
|
36
40
|
another's output waits. Brief each agent as the Lead rules describe.
|
|
37
|
-
Use only available agents;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
Do not skip review or describe Lead's own review as independent review.`;
|
|
41
|
+
Use only available agents; when none fits a role, Lead does that work itself
|
|
42
|
+
and never changes settings to enable one. If the reviewer is disabled or
|
|
43
|
+
unavailable, Lead alone reviews the result against the approved scope and
|
|
44
|
+
performs the required verification, without a substitute agent; never skip
|
|
45
|
+
review or describe Lead's own review as independent.`;
|
|
43
46
|
}
|
|
@@ -1,17 +1,5 @@
|
|
|
1
1
|
// Plugin/project MCP server discovery + normalization, and skill-file counting.
|
|
2
|
-
import {
|
|
3
|
-
chmodSync,
|
|
4
|
-
closeSync,
|
|
5
|
-
existsSync,
|
|
6
|
-
fsyncSync,
|
|
7
|
-
openSync,
|
|
8
|
-
readFileSync,
|
|
9
|
-
readdirSync,
|
|
10
|
-
renameSync,
|
|
11
|
-
statSync,
|
|
12
|
-
unlinkSync,
|
|
13
|
-
writeFileSync,
|
|
14
|
-
} from 'node:fs';
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
15
3
|
import { join, relative, sep } from 'node:path';
|
|
16
4
|
import { clean } from './session-text.mjs';
|
|
17
5
|
import {
|
|
@@ -54,90 +42,6 @@ export function mergeMcpServerConfig(existing, next) {
|
|
|
54
42
|
return { ...merged, ...next };
|
|
55
43
|
}
|
|
56
44
|
|
|
57
|
-
function readProjectMcpDocument(cwd, allowMissing = false) {
|
|
58
|
-
const path = join(cwd || '.', '.mcp.json');
|
|
59
|
-
if (!existsSync(path)) {
|
|
60
|
-
if (allowMissing) return { path, raw: { mcpServers: {} }, usesWrapper: true };
|
|
61
|
-
throw new Error(`MCP config file not found: ${path}`);
|
|
62
|
-
}
|
|
63
|
-
let raw;
|
|
64
|
-
try {
|
|
65
|
-
raw = JSON.parse(readFileSync(path, 'utf8'));
|
|
66
|
-
} catch (error) {
|
|
67
|
-
throw new Error(`cannot read ${path}: ${error?.message || String(error)}`);
|
|
68
|
-
}
|
|
69
|
-
if (!isPlainObject(raw)) throw new Error(`unexpected .mcp.json shape at ${path}`);
|
|
70
|
-
if (Object.hasOwn(raw, 'mcpServers') && !isPlainObject(raw.mcpServers)) {
|
|
71
|
-
throw new Error(`unexpected .mcp.json shape at ${path}`);
|
|
72
|
-
}
|
|
73
|
-
const usesWrapper = isPlainObject(raw.mcpServers);
|
|
74
|
-
return { path, raw, usesWrapper };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function writeProjectMcpDocument(path, raw) {
|
|
78
|
-
let mode = 0o644;
|
|
79
|
-
try {
|
|
80
|
-
mode = statSync(path).mode;
|
|
81
|
-
} catch {
|
|
82
|
-
/* new file */
|
|
83
|
-
}
|
|
84
|
-
const tempPath = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
85
|
-
let fd = null;
|
|
86
|
-
try {
|
|
87
|
-
fd = openSync(tempPath, 'w', mode);
|
|
88
|
-
writeFileSync(fd, `${JSON.stringify(raw, null, 2)}\n`, 'utf8');
|
|
89
|
-
fsyncSync(fd);
|
|
90
|
-
closeSync(fd);
|
|
91
|
-
fd = null;
|
|
92
|
-
chmodSync(tempPath, mode);
|
|
93
|
-
renameSync(tempPath, path);
|
|
94
|
-
} catch (error) {
|
|
95
|
-
if (fd !== null) {
|
|
96
|
-
try {
|
|
97
|
-
closeSync(fd);
|
|
98
|
-
} catch {
|
|
99
|
-
/* best effort */
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
try {
|
|
103
|
-
unlinkSync(tempPath);
|
|
104
|
-
} catch {
|
|
105
|
-
/* best effort */
|
|
106
|
-
}
|
|
107
|
-
throw error;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// The stored key for `key`: an exact own key first, else one that trims to it.
|
|
112
|
-
function mcpEntryKey(map, key) {
|
|
113
|
-
return Object.hasOwn(map, key) ? key : Object.keys(map).find((candidate) => String(candidate || '').trim() === key);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export function readProjectMcpServerConfig(cwd, name) {
|
|
117
|
-
const { raw, usesWrapper } = readProjectMcpDocument(cwd);
|
|
118
|
-
const map = usesWrapper ? raw.mcpServers : raw;
|
|
119
|
-
const entryKey = mcpEntryKey(map, String(name || '').trim());
|
|
120
|
-
return entryKey && isPlainObject(map[entryKey]) ? { name: entryKey, config: { ...map[entryKey] } } : null;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
export function saveProjectMcpServer(cwd, { originalName = '', name, config }) {
|
|
124
|
-
const { path, raw, usesWrapper } = readProjectMcpDocument(cwd, !originalName);
|
|
125
|
-
const map = usesWrapper ? raw.mcpServers : raw;
|
|
126
|
-
const target = String(name || '').trim();
|
|
127
|
-
const original = String(originalName || '').trim();
|
|
128
|
-
if (!target) throw new Error('MCP server name is required');
|
|
129
|
-
const entryKey = original ? mcpEntryKey(map, original) : null;
|
|
130
|
-
if (original && !entryKey) throw new Error(`MCP server not defined in ${path}: ${original}`);
|
|
131
|
-
if (target !== entryKey && Object.hasOwn(map, target)) {
|
|
132
|
-
throw new Error(`MCP server already exists in ${path}: ${target}`);
|
|
133
|
-
}
|
|
134
|
-
const existing = entryKey ? map[entryKey] : {};
|
|
135
|
-
if (entryKey && entryKey !== target) delete map[entryKey];
|
|
136
|
-
map[target] = mergeMcpServerConfig(existing, config);
|
|
137
|
-
writeProjectMcpDocument(path, raw);
|
|
138
|
-
return { name: target, source: 'project', path, config: { ...map[target] } };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
45
|
// Manifest + contained-path resolution live in the shared module so skill
|
|
142
46
|
// discovery and this status surface read the same roots.
|
|
143
47
|
export { pluginManifest, resolveContainedPluginPath };
|
|
@@ -11,7 +11,7 @@ import { createHash } from 'node:crypto';
|
|
|
11
11
|
|
|
12
12
|
/** Bumped whenever the table layout or a codec changes: the DB is then
|
|
13
13
|
* dropped and backfilled again from the JSON catalog. */
|
|
14
|
-
export const SCHEMA_VERSION =
|
|
14
|
+
export const SCHEMA_VERSION = 4;
|
|
15
15
|
/** PRAGMA application_id ('MXSC'): a file without it is not this catalog. */
|
|
16
16
|
export const APPLICATION_ID = 0x4d585343;
|
|
17
17
|
|
|
@@ -57,6 +57,8 @@ const FIELDS = [
|
|
|
57
57
|
['detachedReason', 'detached_reason', 'loose'],
|
|
58
58
|
// importOrigin.key of an imported conversation (schema 3).
|
|
59
59
|
['importKey', 'import_key', 'loose'],
|
|
60
|
+
// Source session of an inherited (forked) session (schema 4).
|
|
61
|
+
['inheritedFromSessionId', 'inherited_from_session_id', 'loose'],
|
|
60
62
|
];
|
|
61
63
|
|
|
62
64
|
const COLUMN_TYPES = { text: 'TEXT', real: 'REAL', int: 'INTEGER', bool: 'INTEGER', json: 'TEXT', optjson: 'TEXT' };
|
|
@@ -8,6 +8,7 @@ import { resolveLiveToolCwd } from './tool-exec.mjs';
|
|
|
8
8
|
import { notifySkillsApplied, prepareExplicitSkills, recordAppliedSkills } from '../explicit-skills.mjs';
|
|
9
9
|
import { addUsage, usageDeltaEvent } from './usage.mjs';
|
|
10
10
|
import { prepareProviderRequest } from './request-boundary.mjs';
|
|
11
|
+
import { applyStepEffort } from './step-auto-effort.mjs';
|
|
11
12
|
import { projectProviderRequest } from './request-projection.mjs';
|
|
12
13
|
import { traceProviderSend, traceOutputTruncation } from './diagnostics.mjs';
|
|
13
14
|
import { createEagerDispatcher } from '../eager-dispatch.mjs';
|
|
@@ -132,6 +133,9 @@ export async function sendProviderRequest(state, round) {
|
|
|
132
133
|
const sendStartedAt = Date.now();
|
|
133
134
|
const preSendMs = sendStartedAt - round.iterT0;
|
|
134
135
|
const toolResumeMs = state.lastToolBatchEndedAt ? sendStartedAt - state.lastToolBatchEndedAt : null;
|
|
136
|
+
// The step judgment ran beside opening the iteration; wait for it only up to
|
|
137
|
+
// its grace, then it rides on this request or on the next.
|
|
138
|
+
await applyStepEffort(state);
|
|
135
139
|
// Every attempt is built from the live transcript, so a compaction between
|
|
136
140
|
// attempts reaches the provider; a pending image strip only filters it.
|
|
137
141
|
const messageSource = state.imageStrip
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
* later request rebuilds the same history and the prompt cache keeps hitting.
|
|
9
9
|
* A judge that is missing, slow or failing leaves the step at the effort in
|
|
10
10
|
* force (the turn's or the previous step's).
|
|
11
|
-
* A custom route
|
|
12
|
-
*
|
|
13
|
-
* turn's following requests
|
|
11
|
+
* A custom route snaps the judged effort onto the model's levels. Without a
|
|
12
|
+
* mid-conversation update (snapshot mode 'custom') it becomes the effort of
|
|
13
|
+
* the turn's following requests; a custom GPT-6+ Responses route (mode
|
|
14
|
+
* 'responses') also marks it as the update.
|
|
14
15
|
*/
|
|
15
16
|
import { resolveAutoEffort } from '../../providers/auto-effort.mjs';
|
|
16
|
-
import { resolveCustomReasoning } from '../../providers/custom-reasoning.mjs';
|
|
17
|
+
import { isCustomProviderName, resolveCustomReasoning } from '../../providers/custom-reasoning.mjs';
|
|
17
18
|
import { autoEffortModel, stepEffortConfiguration } from '../../providers/effort-configuration.mjs';
|
|
18
19
|
import { effortOptionsFor } from '../../runtime-core/effort.mjs';
|
|
19
20
|
import { judgeTurn, recordEffortDecision } from '../../../../effort-judge/judge-client.mjs';
|
|
@@ -35,12 +36,58 @@ const argText = (args) =>
|
|
|
35
36
|
.slice(0, 600)
|
|
36
37
|
.join('');
|
|
37
38
|
|
|
39
|
+
// A judgment runs beside the next iteration instead of in front of it. Its
|
|
40
|
+
// record lives on the loop state (`state.stepEffort`), so it can only ever
|
|
41
|
+
// reach the turn that started it; its promise settles the record and touches
|
|
42
|
+
// nothing else, so an aborted loop is never mutated afterwards. The result
|
|
43
|
+
// lands on the batch's last tool result in one of two places:
|
|
44
|
+
// - resolved before the next request is projected: on that tool result, so
|
|
45
|
+
// the request carries it (applyStepEffort);
|
|
46
|
+
// - resolved later: the request already went out with the earlier effort and
|
|
47
|
+
// its history is never rewritten (that would break the prompt cache), so
|
|
48
|
+
// the result marks the last tool result of the next batch, and the
|
|
49
|
+
// following request carries it (markStepEffort).
|
|
50
|
+
function applyResult(state, target, result) {
|
|
51
|
+
if (result.level) state.opts.effort = result.level;
|
|
52
|
+
if (result.marked) target.meta = { ...target.meta, effortConfiguration: result.marked };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// How long a request waits for its step judgment (measured judge p90 ≈ 284 ms).
|
|
56
|
+
export const STEP_EFFORT_GRACE_MS = 300;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Before a provider request is projected: wait up to the grace for the pending
|
|
60
|
+
* judgment and apply it if it resolved. A slower one leaves this request at
|
|
61
|
+
* the effort in force and lands on the following request.
|
|
62
|
+
*/
|
|
63
|
+
export async function applyStepEffort(state) {
|
|
64
|
+
const pending = state.stepEffort;
|
|
65
|
+
if (!pending || pending.sent) return;
|
|
66
|
+
if (!pending.settled && !state.signal?.aborted) {
|
|
67
|
+
let timer;
|
|
68
|
+
await Promise.race([
|
|
69
|
+
pending.promise,
|
|
70
|
+
new Promise((resolve) => {
|
|
71
|
+
timer = setTimeout(resolve, STEP_EFFORT_GRACE_MS);
|
|
72
|
+
}),
|
|
73
|
+
]);
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
// Another caller may have handled it while this one waited.
|
|
76
|
+
if (pending.sent) return;
|
|
77
|
+
}
|
|
78
|
+
pending.sent = true;
|
|
79
|
+
if (!pending.result || state.signal?.aborted || !state.messages.includes(pending.target)) return;
|
|
80
|
+
applyResult(state, pending.target, pending.result);
|
|
81
|
+
pending.applied = true;
|
|
82
|
+
}
|
|
83
|
+
|
|
38
84
|
/**
|
|
39
|
-
*
|
|
85
|
+
* Start judging the step after this batch (not awaited). The result applies to
|
|
86
|
+
* the next request if it is ready by then, else to the one after.
|
|
40
87
|
* `state.opts.stepAutoEffort` ({ request, base }) is set only when auto effort
|
|
41
88
|
* is on for this turn and the installed judge was trained on steps.
|
|
42
89
|
*/
|
|
43
|
-
export
|
|
90
|
+
export function markStepEffort(state, assistantTurnMsg) {
|
|
44
91
|
const auto = state.opts?.stepAutoEffort;
|
|
45
92
|
const snapshot = state.opts?.effortConfiguration;
|
|
46
93
|
if (!auto || !snapshot || state.signal?.aborted) return;
|
|
@@ -55,11 +102,27 @@ export async function markStepEffort(state, assistantTurnMsg) {
|
|
|
55
102
|
}
|
|
56
103
|
}
|
|
57
104
|
if (!last) return;
|
|
105
|
+
const previous = state.stepEffort;
|
|
106
|
+
if (previous) {
|
|
107
|
+
// It resolved after its request went out: this batch carries it. One still
|
|
108
|
+
// running is superseded: the newest step's judgment queues behind it and
|
|
109
|
+
// any judgment still waiting in between is skipped (latest wins), so the
|
|
110
|
+
// judge never has more than one running and one queued.
|
|
111
|
+
if (previous.settled) {
|
|
112
|
+
if (previous.result && !previous.applied) applyResult(state, last, previous.result);
|
|
113
|
+
} else {
|
|
114
|
+
previous.superseded = true;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
58
117
|
state.autoEffortSteps = (state.autoEffortSteps || 0) + 1;
|
|
59
118
|
const step = state.autoEffortSteps;
|
|
60
119
|
const provider = state.sessionRef?.provider;
|
|
61
120
|
const model = state.model;
|
|
62
|
-
const
|
|
121
|
+
const pending = { target: last, sent: false, settled: false, applied: false, superseded: false, result: null };
|
|
122
|
+
const behind = previous && !previous.settled ? previous.promise : Promise.resolve();
|
|
123
|
+
state.stepEffort = pending;
|
|
124
|
+
const context = { auto, snapshot, step, provider, model, providerClient: state.provider, sessionId: state.sessionId };
|
|
125
|
+
const input = {
|
|
63
126
|
step: {
|
|
64
127
|
request: auto.request,
|
|
65
128
|
step,
|
|
@@ -70,11 +133,26 @@ export async function markStepEffort(state, assistantTurnMsg) {
|
|
|
70
133
|
result: stepResultWindow(textOf(results.get(call.id)?.content)),
|
|
71
134
|
})),
|
|
72
135
|
},
|
|
73
|
-
}
|
|
136
|
+
};
|
|
137
|
+
pending.promise = behind
|
|
138
|
+
.then(() => (pending.superseded ? null : judgeTurn(input)))
|
|
139
|
+
.then((judged) => (judged ? stepResult(context, judged) : null))
|
|
140
|
+
// A failed judgment leaves the step at the effort in force.
|
|
141
|
+
.catch(() => null)
|
|
142
|
+
.then((result) => {
|
|
143
|
+
// Only this record changes here; the loop reads it at its next boundary.
|
|
144
|
+
pending.result = result;
|
|
145
|
+
pending.settled = true;
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// What a resolved judgment sets: `{ marked, level }` (the tool result's effort
|
|
150
|
+
// configuration, and the per-request effort of a custom route), or null.
|
|
151
|
+
function stepResult({ auto, snapshot, step, provider, model, providerClient, sessionId }, judged) {
|
|
74
152
|
// `atStep` numbers the tool-result step; `step` in a resolved decision is the ladder move.
|
|
75
153
|
const record = {
|
|
76
154
|
at: new Date().toISOString(),
|
|
77
|
-
sessionId
|
|
155
|
+
sessionId,
|
|
78
156
|
provider,
|
|
79
157
|
model,
|
|
80
158
|
mode: 'on',
|
|
@@ -82,9 +160,9 @@ export async function markStepEffort(state, assistantTurnMsg) {
|
|
|
82
160
|
};
|
|
83
161
|
if (!judged.probs) {
|
|
84
162
|
recordEffortDecision({ ...record, skipped: judged.skipped });
|
|
85
|
-
return;
|
|
163
|
+
return null;
|
|
86
164
|
}
|
|
87
|
-
const modelMeta = autoEffortModel(provider,
|
|
165
|
+
const modelMeta = autoEffortModel(provider, providerClient, model);
|
|
88
166
|
const resolved = resolveAutoEffort({
|
|
89
167
|
base: auto.base,
|
|
90
168
|
options: effortOptionsFor(provider, modelMeta),
|
|
@@ -96,19 +174,25 @@ export async function markStepEffort(state, assistantTurnMsg) {
|
|
|
96
174
|
ms: judged.ms,
|
|
97
175
|
probs: judged.probs.map((value) => Number(value.toFixed(3))),
|
|
98
176
|
});
|
|
99
|
-
if (
|
|
177
|
+
if (isCustomProviderName(provider)) {
|
|
100
178
|
const level =
|
|
101
179
|
resolved &&
|
|
102
180
|
resolveCustomReasoning({
|
|
103
|
-
protocol:
|
|
181
|
+
protocol: providerClient?.customConfig?.protocol,
|
|
104
182
|
model: modelMeta,
|
|
105
183
|
effort: resolved.effort,
|
|
106
184
|
}).level;
|
|
107
|
-
if (!level) return;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
return
|
|
185
|
+
if (!level) return null;
|
|
186
|
+
// The per-request effort also carries the step on an update route,
|
|
187
|
+
// where a rejected update replays the request with it.
|
|
188
|
+
return {
|
|
189
|
+
level,
|
|
190
|
+
marked:
|
|
191
|
+
snapshot.mode === 'custom'
|
|
192
|
+
? { ...snapshot, effort: level }
|
|
193
|
+
: stepEffortConfiguration(snapshot, provider, model, level),
|
|
194
|
+
};
|
|
111
195
|
}
|
|
112
196
|
const marked = resolved && stepEffortConfiguration(snapshot, provider, model, resolved.effort);
|
|
113
|
-
|
|
197
|
+
return marked ? { level: null, marked } : null;
|
|
114
198
|
}
|
|
@@ -70,7 +70,9 @@ export async function runToolPhase(state, round, sent, onToolCall) {
|
|
|
70
70
|
// carries a runtime notice for the model, delivered after its results.
|
|
71
71
|
if (response.recoveryNotice) messages.push(response.recoveryNotice);
|
|
72
72
|
const toolsEndedAt = Date.now();
|
|
73
|
-
|
|
73
|
+
// Starts the step judgment; the next request uses it if it has resolved by
|
|
74
|
+
// then (send-phase.mjs applyStepEffort), else the one after.
|
|
75
|
+
markStepEffort(state, assistantTurnMsg);
|
|
74
76
|
try {
|
|
75
77
|
opts.onToolPhaseCompleted?.({
|
|
76
78
|
iteration: round.nextIteration,
|
|
@@ -54,6 +54,12 @@ export function createAskPromptQueue({ sessionId, promptSource }) {
|
|
|
54
54
|
// after each turn — keeps queued `agent type=send` messages in order.
|
|
55
55
|
const tail = [];
|
|
56
56
|
let initialPromptSource = initialPromptSourceFor(promptSource);
|
|
57
|
+
// Queue entries of the input nextTurn handed out that no turn owns yet.
|
|
58
|
+
// Once a turn starts, its failure path acks or releases them instead.
|
|
59
|
+
let unstartedEntries = [];
|
|
60
|
+
const release = (entries) => {
|
|
61
|
+
if (entries?.length > 0) releasePendingMessages(sessionId, entries);
|
|
62
|
+
};
|
|
57
63
|
|
|
58
64
|
/** The next turn's input — prompt, caller context, consumed queue entries
|
|
59
65
|
* and provenance — or null when nothing model-visible is left to run. */
|
|
@@ -83,7 +89,26 @@ export function createAskPromptQueue({ sessionId, promptSource }) {
|
|
|
83
89
|
tail.unshift(...groups.slice(1).map(tailTurnFor));
|
|
84
90
|
input = queuedTurnInput(tailTurnFor(first));
|
|
85
91
|
}
|
|
86
|
-
|
|
92
|
+
if (!hasModelVisiblePromptContent(input.prompt)) {
|
|
93
|
+
release(input.pendingEntries);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
unstartedEntries = input.pendingEntries;
|
|
97
|
+
return input;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/** The turn for the last nextTurn input started: it now owns its entries. */
|
|
101
|
+
const markStarted = () => {
|
|
102
|
+
unstartedEntries = [];
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/** Hands every drained entry no turn started back to the pending queue —
|
|
106
|
+
* the waiting follow-up turns and an input whose turn never opened.
|
|
107
|
+
* Called once when the ask unwinds; each entry is released once. */
|
|
108
|
+
const releaseUnstarted = () => {
|
|
109
|
+
const entries = [...unstartedEntries, ...tail.splice(0).flatMap((turn) => turn.entries || [])];
|
|
110
|
+
unstartedEntries = [];
|
|
111
|
+
release(entries);
|
|
87
112
|
};
|
|
88
113
|
|
|
89
114
|
/** Queues drained pending-message entries as follow-up turns; true when
|
|
@@ -101,5 +126,8 @@ export function createAskPromptQueue({ sessionId, promptSource }) {
|
|
|
101
126
|
return true;
|
|
102
127
|
};
|
|
103
128
|
|
|
104
|
-
|
|
129
|
+
/** True while drained follow-up turns are still waiting to run. */
|
|
130
|
+
const hasQueuedTurns = () => tail.length > 0;
|
|
131
|
+
|
|
132
|
+
return { nextTurn, pushDrained, hasQueuedTurns, markStarted, releaseUnstarted };
|
|
105
133
|
}
|
|
@@ -178,6 +178,9 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
|
|
|
178
178
|
...claimed,
|
|
179
179
|
...armTurnDurability({ sessionId, ...claimed, pendingEntries: input.pendingEntries }),
|
|
180
180
|
};
|
|
181
|
+
// From here the turn's own unwind (ask-turn-failure) acks or releases
|
|
182
|
+
// its entries; the queue keeps owning only the not-yet-started ones.
|
|
183
|
+
queue.markStarted();
|
|
181
184
|
const turn = await runAskTurn({
|
|
182
185
|
sessionId,
|
|
183
186
|
opened,
|
|
@@ -193,9 +196,12 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
|
|
|
193
196
|
// that arrived while this turn was in flight runs next, in order, as a
|
|
194
197
|
// follow-up user turn. The mutex is still held, so a send racing this
|
|
195
198
|
// drain either landed before (picked up here) or enqueues for the next
|
|
196
|
-
// loop.
|
|
199
|
+
// loop. Follow-ups an idle ask already drained wait in the queue's
|
|
200
|
+
// tail and run before anything new. When both are empty we return the
|
|
201
|
+
// latest turn's result.
|
|
197
202
|
const drained = turn.drained.length > 0 ? turn.drained : drainPendingMessages(sessionId);
|
|
198
|
-
|
|
203
|
+
queue.pushDrained(drained);
|
|
204
|
+
if (queue.hasQueuedTurns()) {
|
|
199
205
|
// Carry the just-committed in-memory session into the follow-up
|
|
200
206
|
// turn so the queued tail sees the preceding assistant/tool
|
|
201
207
|
// context. loadSession() would return this same live snapshot
|
|
@@ -216,6 +222,9 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
|
|
|
216
222
|
return result;
|
|
217
223
|
}
|
|
218
224
|
} finally {
|
|
225
|
+
// A failed/cancelled turn ends the ask: the follow-up turns drained
|
|
226
|
+
// behind it never ran, so their entries go back to the pending queue.
|
|
227
|
+
queue.releaseUnstarted();
|
|
219
228
|
// A thrown setup/provider path must never leave a delayed checkpoint
|
|
220
229
|
// writer alive after the turn lock is released.
|
|
221
230
|
// Clear the controller only if it's still ours (closeSession may have
|
|
@@ -27,16 +27,12 @@ export async function prepareAskTurn({ sessionId, opened, input, cwdOverride, tr
|
|
|
27
27
|
// Register the live session object for synchronous close snapshots.
|
|
28
28
|
runtime.session = session;
|
|
29
29
|
if (!provider) throw new Error(`Provider "${session.provider}" not available`);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
);
|
|
37
|
-
// Auto effort also judges every tool-result step of the turn when the
|
|
38
|
-
// installed judge was trained on steps (session/loop/step-auto-effort.mjs).
|
|
39
|
-
const stepAutoEffort = turnStepAutoEffort(autoEffort, effortConfiguration);
|
|
30
|
+
// The turn-level judgment runs while the rest of the turn is prepared; it
|
|
31
|
+
// reads the session as the turn found it (synchronously, on this call).
|
|
32
|
+
// The first request still carries its decision: it is awaited below,
|
|
33
|
+
// together with the user turn, before the effort configuration is stamped.
|
|
34
|
+
const autoEffortPending = resolveTurnAutoEffort({ sessionId, session, provider, input });
|
|
35
|
+
autoEffortPending.catch(() => {});
|
|
40
36
|
applyTurnContextMeta(session, provider);
|
|
41
37
|
const effectiveCwd = cwdOverride || session.cwd;
|
|
42
38
|
// A failed-turn retry resubmits the prompt the failure left unanswered;
|
|
@@ -46,25 +42,49 @@ export async function prepareAskTurn({ sessionId, opened, input, cwdOverride, tr
|
|
|
46
42
|
if (session.sessionStartMetaInjected !== true && !hasUserConversationMessage(session.messages)) {
|
|
47
43
|
refreshSessionBp3Environment(session, effectiveCwd);
|
|
48
44
|
}
|
|
45
|
+
// The prewarm only opens the transport (effort never shapes the socket), so
|
|
46
|
+
// it starts now instead of after the judgment.
|
|
49
47
|
const startupProviderPrewarm = startProviderPrewarm({
|
|
50
48
|
sessionId,
|
|
51
49
|
session,
|
|
52
50
|
provider,
|
|
53
|
-
turnEffort,
|
|
54
|
-
effortConfiguration,
|
|
51
|
+
turnEffort: session.effort || null,
|
|
52
|
+
effortConfiguration: null,
|
|
55
53
|
codexTurnId,
|
|
56
54
|
startedAtMs: startedAt,
|
|
57
55
|
});
|
|
58
|
-
const userTurn = await
|
|
56
|
+
const [autoEffort, userTurn] = await Promise.all([
|
|
57
|
+
autoEffortPending,
|
|
58
|
+
buildUserTurn({
|
|
59
|
+
session,
|
|
60
|
+
prompt: input.prompt,
|
|
61
|
+
context: input.context,
|
|
62
|
+
explicitPrefetch: input.explicitPrefetch,
|
|
63
|
+
turnSignal,
|
|
64
|
+
transcriptMeta,
|
|
65
|
+
turnPromptSource: input.promptSource,
|
|
66
|
+
effortConfiguration: null,
|
|
67
|
+
}),
|
|
68
|
+
]);
|
|
69
|
+
const turnEffort = autoEffort?.applied ? autoEffort.effort : session.effort || null;
|
|
70
|
+
const effortConfiguration = prepareTurnEffortConfiguration(
|
|
59
71
|
session,
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
72
|
+
provider,
|
|
73
|
+
autoEffort?.applied ? autoEffort.effort : undefined
|
|
74
|
+
);
|
|
75
|
+
// Auto effort also judges every tool-result step of the turn when the
|
|
76
|
+
// installed judge was trained on steps (session/loop/step-auto-effort.mjs).
|
|
77
|
+
const stepAutoEffort = turnStepAutoEffort(autoEffort, effortConfiguration);
|
|
78
|
+
if (effortConfiguration) {
|
|
79
|
+
// Stamp the turn's effort on the user message (same meta order as when it
|
|
80
|
+
// was built with it: before the runtime-context provenance).
|
|
81
|
+
const { runtimeUserContext, ...meta } = userTurn.userTurnMessage.meta || {};
|
|
82
|
+
userTurn.userTurnMessage.meta = {
|
|
83
|
+
...meta,
|
|
84
|
+
effortConfiguration,
|
|
85
|
+
...(runtimeUserContext ? { runtimeUserContext } : {}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
68
88
|
const { historyMessages, outgoing } = userTurn;
|
|
69
89
|
turn.userTurnContent = userTurn.userTurnMessage.content;
|
|
70
90
|
turn.outgoing = outgoing;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import { mergeNormalizedContentEntries } from '../loop/steering.mjs';
|
|
3
3
|
import { isDeliveredCompletion, logDuplicateSkip } from './delivered-completions.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import { promptContentText } from './prompt-utils.mjs';
|
|
5
5
|
import { parseTaskNotification, taskNotificationId } from '../../../../shared/task-notification-envelope.mjs';
|
|
6
6
|
|
|
7
7
|
const STALE_USER_INJECTION_TTL_MS = 30 * 60 * 1000;
|
|
@@ -295,15 +295,10 @@ function normalizePersistedEntryBase(entry) {
|
|
|
295
295
|
}
|
|
296
296
|
|
|
297
297
|
export function modelVisiblePendingMessages(messages) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
(message) =>
|
|
303
|
-
!isInternalRuntimeNotificationText(
|
|
304
|
-
message && typeof message === 'object' && Object.hasOwn(message, 'content') ? message.content : message
|
|
305
|
-
)
|
|
306
|
-
);
|
|
298
|
+
// No content filter: isInternalRuntimeNotificationText is a display-only
|
|
299
|
+
// predicate. Every canonical completion envelope matches it, and a drained
|
|
300
|
+
// entry the model never sees is never claimed/acked/released.
|
|
301
|
+
return (Array.isArray(messages) ? messages : []).map(pendingMessageQueueEntry).filter(Boolean);
|
|
307
302
|
}
|
|
308
303
|
|
|
309
304
|
export function _mergePendingMessageEntries(entries) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { sanitizeToolPairs } from '../context-utils.mjs';
|
|
2
2
|
import { collectAssistantToolCallIds as assistantToolCallIds } from '../context-tool-pairs.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import { promptContentText } from './prompt-utils.mjs';
|
|
4
4
|
import { filterModelVisibleSessionMessages } from './message-sanitize.mjs';
|
|
5
5
|
import { journalDelta } from './turn-interruption-journal.mjs';
|
|
6
6
|
|
|
@@ -72,8 +72,11 @@ function finalizeInterruptedTurn({
|
|
|
72
72
|
const index = provisionalUserTurnIndex(messages, currentUserContent);
|
|
73
73
|
return index >= 0 && index < messages.length - 1;
|
|
74
74
|
})();
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
// A turn woken by a task notification is preserved by the same rule as a
|
|
76
|
+
// user prompt: once it produced output its notification is consumed (the
|
|
77
|
+
// host commits it and never requeues it), so rewinding it would erase the
|
|
78
|
+
// turn and its side effects from history.
|
|
79
|
+
const preserveResponse = responseStarted && (producedOutput || !userCancelled);
|
|
77
80
|
if (!preserveResponse) {
|
|
78
81
|
if (!userCancelled) {
|
|
79
82
|
return {
|
|
@@ -198,8 +201,11 @@ export function createTurnInterruptionTracker() {
|
|
|
198
201
|
// copies of the same first sentence). Reference agents never
|
|
199
202
|
// commit a replayed attempt's discarded stream at all; the
|
|
200
203
|
// tombstone exists only for the window between a retraction and
|
|
201
|
-
// its replacement.
|
|
202
|
-
|
|
204
|
+
// its replacement. Whitespace is not a replacement: it is not output
|
|
205
|
+
// (finalizeInterruptedTurn trims, and the host commits the prompt only
|
|
206
|
+
// on non-blank text), so until real text arrives the retracted bytes
|
|
207
|
+
// remain the only output the user saw.
|
|
208
|
+
if (state.tombstonedAssistantContent && value.trim()) {
|
|
203
209
|
state.tombstonedAssistantContent = '';
|
|
204
210
|
state.tombEpoch += 1;
|
|
205
211
|
}
|
|
@@ -212,8 +218,13 @@ export function createTurnInterruptionTracker() {
|
|
|
212
218
|
// REPLACE, never accumulate: consecutive retractions belong to
|
|
213
219
|
// successive attempts at the SAME answer, so keeping the earlier
|
|
214
220
|
// tombstone alongside the newer one is what multiplied the visible
|
|
215
|
-
// text across retries.
|
|
216
|
-
|
|
221
|
+
// text across retries. A whitespace-only retraction is not a newer
|
|
222
|
+
// attempt at the answer (recordTextDelta never let it supersede the
|
|
223
|
+
// draft), so it never replaces a meaningful tombstone.
|
|
224
|
+
const retracted = state.partialAssistantContent.slice(cutAt);
|
|
225
|
+
if (retracted.trim() || !state.tombstonedAssistantContent.trim()) {
|
|
226
|
+
state.tombstonedAssistantContent = retracted;
|
|
227
|
+
}
|
|
217
228
|
state.partialAssistantContent = state.partialAssistantContent.slice(0, cutAt);
|
|
218
229
|
state.textEpoch += 1;
|
|
219
230
|
state.tombEpoch += 1;
|