mixdog 1.3.2 → 1.3.3
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 +7 -7
- package/scripts/verify-runtime-dependency-versions.mjs +158 -0
- package/src/agents/reviewer/AGENT.md +5 -7
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +4 -0
- package/src/defaults/skills/browser-use/SKILL.md +6 -3
- package/src/defaults/skills/local-provider/SKILL.md +33 -6
- package/src/rules/lead/LEAD.md +5 -8
- package/src/rules/shared/45-destructive.md +5 -1
- package/src/rules/shared/60-verification.md +6 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +17 -4
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/registry.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +40 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +32 -11
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +13 -0
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +68 -12
- package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +31 -8
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +117 -19
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +147 -137
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +37 -74
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +0 -2
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +13 -7
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +12 -20
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +24 -15
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +17 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +7 -5
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +10 -1
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +76 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +18 -7
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +8 -2
- package/src/runtime/attachments/legacy-office.mjs +110 -0
- package/src/runtime/attachments/prompt-restore.mjs +70 -0
- package/src/runtime/attachments/store.mjs +60 -3
- package/src/runtime/effort-judge/judge-client.mjs +61 -14
- package/src/runtime/effort-judge/model-install.mjs +34 -2
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
- package/src/runtime/office/portable/portable-soffice.mjs +132 -30
- package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
- package/src/runtime/shared/agent-avatar.mjs +3 -2
- package/src/runtime/shared/agent-room-host.d.mts +31 -0
- package/src/runtime/shared/agent-room-host.mjs +58 -0
- package/src/runtime/shared/bot-hops.mjs +16 -11
- package/src/runtime/shared/redact-directories.mjs +18 -0
- package/src/runtime/shared/remove-directory.mjs +28 -0
- package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
- package/src/runtime/shared/session-sidebar-host.mjs +16 -3
- package/src/runtime/shared/tool-execution-contract.mjs +4 -0
- package/src/runtime/shared/tool-work-units.mjs +12 -9
- package/src/runtime/shared/turn-snapshot.mjs +233 -122
- package/src/runtime/shared/user-data-guard.mjs +51 -19
- package/src/runtime/shared/worktrees/activity-gate.mjs +209 -227
- package/src/runtime/shared/worktrees/contract.mjs +11 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
- package/src/runtime/shared/worktrees/manager.mjs +419 -52
- package/src/runtime/shared/worktrees/registry.mjs +3 -1
- package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
- package/src/runtime/tidy/process.mjs +41 -8
- package/src/runtime/tidy/resolve.mjs +10 -7
- package/src/session-runtime/agent-home-delivery.mjs +73 -91
- package/src/session-runtime/agent-profile-changes.mjs +16 -0
- package/src/session-runtime/agent-room-delivery.mjs +2 -63
- package/src/session-runtime/agent-task-sessions.mjs +194 -64
- package/src/session-runtime/boot/apis.mjs +3 -0
- package/src/session-runtime/boot/facade.mjs +5 -6
- package/src/session-runtime/boot/tools.mjs +21 -1
- package/src/session-runtime/held-requests.mjs +20 -25
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +5 -22
- package/src/session-runtime/model-recency.mjs +1 -0
- package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
- package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
- package/src/session-runtime/runtime-review-api.mjs +4 -1
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -0
- package/src/session-runtime/services/provider-admin.mjs +12 -2
- package/src/session-runtime/services/worktree-access.mjs +27 -0
- package/src/session-runtime/session-hooks.mjs +9 -17
- package/src/session-runtime/settings-builtin-tools-api.mjs +2 -0
- package/src/session-runtime/setup-tool/executor.mjs +4 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +3 -0
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +98 -14
- package/src/session-runtime/tool-policy/policy-stage.mjs +23 -5
- package/src/session-runtime/tool-policy-refresh.mjs +1 -0
- package/src/session-runtime/tool-surface.mjs +9 -5
- package/src/session-runtime/turn/session-ops.mjs +8 -0
- package/src/session-runtime/turn/turn-run.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +10 -2
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +39 -17
- package/src/session-runtime/workspace/transition-steps.mjs +1 -18
- package/src/session-runtime/workspace/transitions.mjs +279 -67
- package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
- package/src/standalone/daemon-lag-profiler.mjs +3 -1
- package/src/standalone/daemon-session-control.mjs +140 -23
- package/src/standalone/daemon-telemetry.mjs +9 -0
- package/src/standalone/daemon.mjs +5 -0
- package/src/standalone/local-session-runtime.mjs +11 -2
- package/src/standalone/session-protocol.mjs +1 -0
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
- package/src/standalone/session-service/agent-route-follow.mjs +69 -0
- package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
- package/src/standalone/session-service.mjs +10 -0
- package/src/standalone/session-tool-output.mjs +1 -0
- package/src/standalone/staged-import.mjs +47 -0
- package/src/tui/App.jsx +2 -0
- package/src/tui/app/message-selector.mjs +15 -2
- package/src/tui/app/model-options.mjs +16 -2
- package/src/tui/app/model-picker.mjs +2 -1
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/dist/index.mjs +92 -40
- package/src/tui/paste-attachments.mjs +0 -1
- package/src/tui/paste-text-policy.mjs +4 -5
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
- package/src/tui/session/notice-surface.mjs +3 -0
- package/src/tui/session/session-api/intake/abort.mjs +33 -17
- package/src/tui/session/session-api/intake/rewind.mjs +39 -4
- package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
- package/src/tui/session/tool-approval.mjs +11 -48
- package/src/session-runtime/room-member-gate.mjs +0 -61
|
@@ -58,6 +58,7 @@ export function createLoopState({ provider, messages, model, tools, cwd, sendOpt
|
|
|
58
58
|
lastUsage: undefined,
|
|
59
59
|
firstTurnUsage: undefined,
|
|
60
60
|
response: undefined,
|
|
61
|
+
lastStreamedChars: 0,
|
|
61
62
|
lastSendTools: tools,
|
|
62
63
|
contextOverflowRetryUsed: false,
|
|
63
64
|
// Set when a provider context-overflow refusal triggers the in-turn
|
|
@@ -133,6 +134,10 @@ export function createLoopState({ provider, messages, model, tools, cwd, sendOpt
|
|
|
133
134
|
sessionId,
|
|
134
135
|
sessionAgent: state.sessionAgent,
|
|
135
136
|
drainSteering: state.drainSteering,
|
|
137
|
+
// Only a provider that can offer a higher max_tokens implements this.
|
|
138
|
+
escalateMaxTokens: () => state.provider?.escalatedMaxTokens?.(state.model, state.opts, state.lastSendTools) ?? null,
|
|
139
|
+
// Characters the latest send put on screen through the live text stream.
|
|
140
|
+
streamedChars: () => state.lastStreamedChars,
|
|
136
141
|
});
|
|
137
142
|
return state;
|
|
138
143
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// continuations with no intervening tool batch, the current text is accepted
|
|
7
7
|
// as the final answer.
|
|
8
8
|
import { writeLoopDiagnostic } from './diagnostic.mjs';
|
|
9
|
+
import { resetOutputLimitRecovery } from './output-limit.mjs';
|
|
9
10
|
|
|
10
11
|
const PROVIDER_CONTINUATION_NO_TOOL_LIMIT = Math.max(
|
|
11
12
|
1,
|
|
@@ -38,6 +39,7 @@ export function resumeContinuation(
|
|
|
38
39
|
state.providerContinuationCount += 1;
|
|
39
40
|
state.continuationsSinceToolBatch += 1;
|
|
40
41
|
state.emptyNudgeStreak = 0;
|
|
42
|
+
resetOutputLimitRecovery(state);
|
|
41
43
|
trace(iteration, 'provider_continuation', {
|
|
42
44
|
signal,
|
|
43
45
|
stop_reason: stopReason,
|
|
@@ -25,7 +25,7 @@ export function nudgeEmptyTurn(response, { stopReason, isIncompleteStop }, { sta
|
|
|
25
25
|
messages.push({
|
|
26
26
|
role: 'user',
|
|
27
27
|
content: isIncompleteStop
|
|
28
|
-
? `[mixdog-runtime] Empty
|
|
28
|
+
? `[mixdog-runtime] Empty incomplete turn (stopReason=${stopReason}). Return the remaining final handoff; use tools only for required evidence still missing.`
|
|
29
29
|
: `[mixdog-runtime] Empty response (${state.emptyNudgeStreak}/${EMPTY_NUDGE_MAX}). Return final text, or use tools only for required evidence still missing.`,
|
|
30
30
|
});
|
|
31
31
|
return { action: 'continue', response };
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
// Max-output
|
|
1
|
+
// Max-output recovery — the single rule for every turn the provider cut at its
|
|
2
|
+
// output ceiling, with or without visible text.
|
|
2
3
|
//
|
|
3
|
-
// A
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
4
|
+
// A max-output stop is not a completed assistant turn. A turn that spent its
|
|
5
|
+
// whole budget (typically on thinking) would repeat identically if re-sent
|
|
6
|
+
// unchanged, so recovery changes the request each time:
|
|
7
|
+
// 1. Escalation, once per progress window: re-send the same transcript with
|
|
8
|
+
// a higher max_tokens when the provider has one to offer (no message
|
|
9
|
+
// added; the cut partial is discarded and regenerated).
|
|
10
|
+
// 2. Up to MAX_OUTPUT_RECOVERY_LIMIT resume prompts at the normal
|
|
11
|
+
// max_tokens, preserving any partial text in the transcript.
|
|
12
|
+
// 3. Then the turn ends with a visible notice that the output limit was hit.
|
|
13
|
+
// Real progress (a tool-call turn, a continued text turn) resets both steps.
|
|
14
|
+
import { isOutputLimitStopReason } from '../termination.mjs';
|
|
15
|
+
|
|
7
16
|
const MAX_OUTPUT_RECOVERY_LIMIT = 3;
|
|
8
|
-
const MAX_OUTPUT_EXHAUSTED_NOTICE = `[mixdog-runtime]
|
|
17
|
+
const MAX_OUTPUT_EXHAUSTED_NOTICE = `[mixdog-runtime] The model hit its output limit and the response stayed truncated after ${MAX_OUTPUT_RECOVERY_LIMIT} recovery attempts.`;
|
|
9
18
|
const MAX_OUTPUT_RESUME_PROMPT =
|
|
10
|
-
'Output token limit hit. Resume directly — no apology, no recap. Pick up exactly where the
|
|
19
|
+
'[mixdog-runtime] Output token limit hit. Resume directly — no apology, no recap. Pick up exactly where you left off, and break the remaining work into smaller pieces.';
|
|
11
20
|
// A response whose stream was cut mid-text (and could not be withdrawn from
|
|
12
21
|
// the screen) resumes through the same ladder with its own reason.
|
|
13
22
|
const STREAM_CUT_RESUME_PROMPT =
|
|
@@ -19,25 +28,65 @@ function resumePrompt(response, attempt) {
|
|
|
19
28
|
: { role: 'user', content: MAX_OUTPUT_RESUME_PROMPT, meta: { source: 'max-output-recovery', attempt } };
|
|
20
29
|
}
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
// Real progress ends the recovery window: the next cut starts a fresh ladder.
|
|
32
|
+
export function resetOutputLimitRecovery(state) {
|
|
33
|
+
state.maxOutputRecoveryCount = 0;
|
|
34
|
+
state.outputEscalationUsed = false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// The escalation discards the cut response, so whatever it already put on the
|
|
38
|
+
// live stream must be withdrawn through the owner's retraction channel — the
|
|
39
|
+
// same one transport replays use. Text the owner cannot withdraw would be
|
|
40
|
+
// shown twice, so that case does not escalate.
|
|
41
|
+
async function withdrawStreamedText(opts, chars) {
|
|
42
|
+
if (!(chars > 0)) return true;
|
|
43
|
+
if (typeof opts?.onTextReset !== 'function') return false;
|
|
44
|
+
try {
|
|
45
|
+
return (await opts.onTextReset({ chars, reasoning: false, reason: 'output-limit-escalation' })) === true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function resolveOutputLimit(
|
|
52
|
+
response,
|
|
53
|
+
{ state, segments, messages, opts, escalateMaxTokens, streamedChars }
|
|
54
|
+
) {
|
|
55
|
+
const hasContent = typeof response.content === 'string' && response.content.trim().length > 0;
|
|
56
|
+
if (!state.outputEscalationUsed && isOutputLimitStopReason(response.stopReason ?? response.stop_reason)) {
|
|
57
|
+
state.outputEscalationUsed = true;
|
|
58
|
+
// A provider may resolve the higher limit asynchronously (route lookup);
|
|
59
|
+
// failing to find one only skips the escalation.
|
|
60
|
+
const limit = await Promise.resolve()
|
|
61
|
+
.then(escalateMaxTokens)
|
|
62
|
+
.catch(() => null);
|
|
63
|
+
if (limit > 0 && (await withdrawStreamedText(opts, streamedChars()))) {
|
|
64
|
+
// Taken off the shared options by the very next send (sendProviderRequest).
|
|
65
|
+
opts.maxOutputTokensOverride = limit;
|
|
66
|
+
return { action: 'continue', response };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (hasContent) segments.record(response.content);
|
|
24
70
|
if (state.maxOutputRecoveryCount < MAX_OUTPUT_RECOVERY_LIMIT) {
|
|
25
71
|
// The partial assistant turn must be visible to the model so it can
|
|
26
72
|
// resume at the exact cutoff instead of reconstructing or repeating
|
|
27
73
|
// it. askSession persists this natural recovery chain;
|
|
28
74
|
// historyContent below prevents the aggregate returned to callers
|
|
29
|
-
// from being persisted a second time.
|
|
30
|
-
segments.commitIntermediate(response);
|
|
75
|
+
// from being persisted a second time. An empty cut has no output to keep.
|
|
76
|
+
if (hasContent) segments.commitIntermediate(response);
|
|
31
77
|
state.maxOutputRecoveryCount += 1;
|
|
32
78
|
messages.push(resumePrompt(response, state.maxOutputRecoveryCount));
|
|
33
79
|
return { action: 'continue', response };
|
|
34
80
|
}
|
|
35
|
-
const terminalSegment =
|
|
81
|
+
const terminalSegment = hasContent
|
|
82
|
+
? `${response.content}\n\n${MAX_OUTPUT_EXHAUSTED_NOTICE}`
|
|
83
|
+
: MAX_OUTPUT_EXHAUSTED_NOTICE;
|
|
84
|
+
const earlier = (hasContent ? segments.parts.slice(0, -1) : segments.parts).join('');
|
|
36
85
|
return {
|
|
37
86
|
action: 'break',
|
|
38
87
|
response: {
|
|
39
88
|
...response,
|
|
40
|
-
content: `${
|
|
89
|
+
content: `${earlier}${earlier && !hasContent ? '\n\n' : ''}${terminalSegment}`,
|
|
41
90
|
historyContent: terminalSegment,
|
|
42
91
|
maxOutputRecoveryAttempts: state.maxOutputRecoveryCount,
|
|
43
92
|
},
|
|
@@ -14,16 +14,27 @@ import { HIDDEN_AGENT_NAMES } from './hidden-agents.mjs';
|
|
|
14
14
|
import { INCOMPLETE_STOP_REASONS, isOutputLimitStopReason, providerContinuationSignal } from './termination.mjs';
|
|
15
15
|
import { appendAgentTrace } from '../../agent-trace.mjs';
|
|
16
16
|
import { createTurnSegments } from './no-tool-turn/segments.mjs';
|
|
17
|
-
import { resolveOutputLimit } from './no-tool-turn/output-limit.mjs';
|
|
17
|
+
import { resetOutputLimitRecovery, resolveOutputLimit } from './no-tool-turn/output-limit.mjs';
|
|
18
18
|
import { resolveRefusal } from './no-tool-turn/refusal.mjs';
|
|
19
19
|
import { resumeContinuation } from './no-tool-turn/continuation.mjs';
|
|
20
20
|
import { nudgeEmptyTurn } from './no-tool-turn/empty-nudge.mjs';
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
// `escalateMaxTokens()` asks the live provider for a higher one-shot max_tokens
|
|
23
|
+
// (number) or reports none (null/undefined) — the loop stays provider-agnostic.
|
|
24
|
+
export function createNoToolTurnResolver({
|
|
25
|
+
messages,
|
|
26
|
+
opts,
|
|
27
|
+
sessionId,
|
|
28
|
+
sessionAgent,
|
|
29
|
+
drainSteering,
|
|
30
|
+
escalateMaxTokens = () => null,
|
|
31
|
+
streamedChars = () => 0,
|
|
32
|
+
}) {
|
|
23
33
|
const segments = createTurnSegments({ messages, opts });
|
|
24
34
|
// Per-turn recovery counters shared by the ladders.
|
|
25
35
|
const state = {
|
|
26
36
|
maxOutputRecoveryCount: 0,
|
|
37
|
+
outputEscalationUsed: false,
|
|
27
38
|
refusalRetryUsed: false,
|
|
28
39
|
emptyNudgeStreak: 0,
|
|
29
40
|
// Structured provider continuation signals honored this turn. Diagnostic
|
|
@@ -42,7 +53,7 @@ export function createNoToolTurnResolver({ messages, opts, sessionId, sessionAge
|
|
|
42
53
|
/* best-effort telemetry */
|
|
43
54
|
}
|
|
44
55
|
};
|
|
45
|
-
const ladder = { state, segments, messages, sessionId, trace };
|
|
56
|
+
const ladder = { state, segments, messages, opts, sessionId, trace, escalateMaxTokens, streamedChars };
|
|
46
57
|
|
|
47
58
|
// A no-tool message ends the turn. Unresolved tool failures remain visible
|
|
48
59
|
// in history and can be reported directly without a synthetic continuation
|
|
@@ -66,11 +77,13 @@ export function createNoToolTurnResolver({ messages, opts, sessionId, sessionAge
|
|
|
66
77
|
// { action: 'continue' | 'break', response }. 'continue' means the
|
|
67
78
|
// recovery appended to the transcript and the loop must re-send;
|
|
68
79
|
// 'break' returns the (possibly aggregated) terminal response.
|
|
69
|
-
resolve(response, iteration) {
|
|
80
|
+
async resolve(response, iteration) {
|
|
70
81
|
const hasContent = typeof response.content === 'string' && response.content.trim().length > 0;
|
|
71
82
|
const stopReason = response.stopReason ?? response.stop_reason ?? null;
|
|
72
83
|
const isOutputLimitStop = isOutputLimitStopReason(stopReason);
|
|
73
|
-
if (
|
|
84
|
+
if (isOutputLimitStop || (hasContent && response.streamCut === true)) {
|
|
85
|
+
return resolveOutputLimit(response, ladder);
|
|
86
|
+
}
|
|
74
87
|
if (stopReason === 'refusal') return resolveRefusal(response, hasContent, ladder);
|
|
75
88
|
// Output-limit stops keep the bounded max-output ladder above and
|
|
76
89
|
// refusals (already returned) keep the bounded refusal retry; both
|
|
@@ -108,13 +121,16 @@ export function createNoToolTurnResolver({ messages, opts, sessionId, sessionAge
|
|
|
108
121
|
})
|
|
109
122
|
) {
|
|
110
123
|
state.emptyNudgeStreak = 0;
|
|
124
|
+
resetOutputLimitRecovery(state);
|
|
111
125
|
return { action: 'continue', response };
|
|
112
126
|
}
|
|
113
127
|
return finishTurn(response);
|
|
114
128
|
},
|
|
115
|
-
// A turn that produced tool calls is never an empty contract violation
|
|
129
|
+
// A turn that produced tool calls is never an empty contract violation,
|
|
130
|
+
// and it is real progress: a later output-limit cut starts a fresh ladder.
|
|
116
131
|
noteToolCallTurn() {
|
|
117
132
|
state.emptyNudgeStreak = 0;
|
|
133
|
+
resetOutputLimitRecovery(state);
|
|
118
134
|
},
|
|
119
135
|
noteToolBatchCompleted() {
|
|
120
136
|
state.continuationsSinceToolBatch = 0;
|
|
@@ -186,10 +186,16 @@ export async function sendProviderRequest(state, round) {
|
|
|
186
186
|
// included: the span the usage ledger recorded as `duration_ms` (stamped on
|
|
187
187
|
// the response); wall time only for a response no ledger row timed.
|
|
188
188
|
const requestStartedAt = Date.now();
|
|
189
|
+
// The output-limit escalation is one-shot: take it off the shared options
|
|
190
|
+
// before sending, so this attempt alone carries it whatever the outcome
|
|
191
|
+
// (retry, route fallback, thrown error, abort).
|
|
192
|
+
const maxOutputTokensOverride = opts.maxOutputTokensOverride;
|
|
193
|
+
delete opts.maxOutputTokensOverride;
|
|
189
194
|
let result;
|
|
190
195
|
try {
|
|
191
196
|
result = await runWithProviderRequestToolsScope(round.requestToolScope, () =>
|
|
192
197
|
sendWithRecovery({
|
|
198
|
+
maxOutputTokensOverride,
|
|
193
199
|
provider,
|
|
194
200
|
messages: providerMessages,
|
|
195
201
|
recoveryMessages: messageSource,
|
|
@@ -384,6 +390,7 @@ export function settleSendResult(state, round, sent) {
|
|
|
384
390
|
if (state.imageStrip) settleImageStrip(state, round, sent);
|
|
385
391
|
opts.onToolCall = undefined;
|
|
386
392
|
delete opts.cacheBreakIntent;
|
|
393
|
+
state.lastStreamedChars = sent.result.streamedChars || 0;
|
|
387
394
|
state.contextOverflowRetryUsed = false;
|
|
388
395
|
// A completed send ends the outage this budget was covering; the next
|
|
389
396
|
// iteration is a fresh request and must get the full replay budget again.
|
|
@@ -7,12 +7,7 @@ import { runAbortable, throwIfAborted } from '../../../../../shared/abort-race.m
|
|
|
7
7
|
import { evaluateExecPolicy } from '../../../tools/bash-policy-scan.mjs';
|
|
8
8
|
import { beginSessionApprovalWait } from '../../manager/runtime-liveness.mjs';
|
|
9
9
|
import { agentMcpToolPermission, writeAgentMcpToolPermission } from '../../../mcp/agent-connectors.mjs';
|
|
10
|
-
import {
|
|
11
|
-
actingContactBotId,
|
|
12
|
-
humanGateDecision,
|
|
13
|
-
recordHumanDecision,
|
|
14
|
-
rememberSurfaceOf,
|
|
15
|
-
} from '../../human-gate/gate.mjs';
|
|
10
|
+
import { actingBotId, actingContactBotId, humanGateDecision, recordHumanDecision } from '../../human-gate/gate.mjs';
|
|
16
11
|
import { applyEditedArgs } from '../../human-gate/requests.mjs';
|
|
17
12
|
|
|
18
13
|
const isArgsObject = (value) => value && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -60,18 +55,22 @@ export const ALLOW_NOT_SAVED_NOTICE = 'Allowed this once. The setting could not
|
|
|
60
55
|
export const CONNECTOR_APPROVAL_REASON = 'This connector tool is set to ask before it runs for this agent.';
|
|
61
56
|
|
|
62
57
|
/**
|
|
63
|
-
* A connector tool
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
58
|
+
* A connector tool a bot's own session (its home chat or room seat; a
|
|
59
|
+
* schedule/webhook run, which the approval hook then answers by run policy)
|
|
60
|
+
* runs, and the bot set to `needs_approval`, asks through the ordinary
|
|
61
|
+
* approval hook, whatever the PreToolUse hooks decided. Any other session (a
|
|
62
|
+
* coding Lead, a task session, a workflow agent, a sub-agent) runs it unasked.
|
|
63
|
+
* Run after the hooks, on the final call: a renamed call is judged under its
|
|
64
|
+
* final name. A call the outbound gate owns (its card approved it, or a
|
|
65
|
+
* remembered outbound grant answered it) is not asked a second time.
|
|
68
66
|
* @returns {Promise<{ denial: string } | null>} the denial, or null to run
|
|
69
67
|
*/
|
|
70
68
|
export function applyConnectorApproval(call, { agentId, callerSessionId, executeOpts, toolApprovalHook }) {
|
|
71
69
|
if (call.outboundApproved) return null;
|
|
70
|
+
if (!actingBotId(callerSessionId)) return null;
|
|
72
71
|
if (agentMcpToolPermission(agentId, call.name) !== 'needs_approval') return null;
|
|
73
72
|
// "Always allow" is offered only to the contact bot that is acting, for its
|
|
74
|
-
// own policy entry for this one tool: never another bot
|
|
73
|
+
// own policy entry for this one tool: never another bot or Mixy.
|
|
75
74
|
const botId = actingContactBotId(callerSessionId);
|
|
76
75
|
const rememberable = Boolean(botId) && botId === agentId;
|
|
77
76
|
return askApproval(call, {
|
|
@@ -79,14 +78,7 @@ export function applyConnectorApproval(call, { agentId, callerSessionId, execute
|
|
|
79
78
|
executeOpts,
|
|
80
79
|
toolApprovalHook,
|
|
81
80
|
decision: { reason: CONNECTOR_APPROVAL_REASON },
|
|
82
|
-
request: rememberable
|
|
83
|
-
? {
|
|
84
|
-
kind: 'approval',
|
|
85
|
-
agentId: botId,
|
|
86
|
-
remember: ['once', 'agent'],
|
|
87
|
-
rememberAt: rememberSurfaceOf(callerSessionId),
|
|
88
|
-
}
|
|
89
|
-
: null,
|
|
81
|
+
request: rememberable ? { kind: 'approval', agentId: botId, remember: ['once', 'agent'] } : null,
|
|
90
82
|
onApproved: async (approval) => {
|
|
91
83
|
if (!rememberable || approval?.remember !== 'agent') return;
|
|
92
84
|
try {
|
|
@@ -7,6 +7,7 @@ import { enqueuePendingMessage, markCompletionEntry } from '../../manager/pendin
|
|
|
7
7
|
import { markSessionToolOutputTail } from '../../manager/runtime-liveness.mjs';
|
|
8
8
|
import { createScopedCacheOutcome } from '../../cache/scoped-cache-outcome.mjs';
|
|
9
9
|
import { modelVisibleToolCompletionMessage } from '../../../../../shared/tool-execution-contract.mjs';
|
|
10
|
+
import { turnHopOf } from '../../../../../shared/bot-hops.mjs';
|
|
10
11
|
import { _isScopedCacheableTool } from '../tool-classify.mjs';
|
|
11
12
|
import { refreshDeferredMcpToolCatalog } from '../../../runtime-core/tool-catalog.mjs';
|
|
12
13
|
import { coerceToolArgsForSession } from '../arg-schema-coerce.mjs';
|
|
@@ -84,8 +85,12 @@ export function prepareToolCall(name, args, cwd, callerSessionId, sessionRef, ex
|
|
|
84
85
|
const notificationSessionId = resolveToolCompletionSessionId({ callerSessionId });
|
|
85
86
|
const notifyFn =
|
|
86
87
|
typeof executeOpts.notifyFn === 'function' ? executeOpts.notifyFn : completionNotifier(notificationSessionId);
|
|
88
|
+
// Background work launched in a bot-descended turn reports back at that hop:
|
|
89
|
+
// it rides the job's notify context, so every completion's meta carries it.
|
|
90
|
+
const hop = turnHopOf(sessionRef);
|
|
87
91
|
const completionToolOpts = {
|
|
88
92
|
...toolOpts,
|
|
93
|
+
...(hop > 0 ? { hop } : {}),
|
|
89
94
|
sessionId: callerSessionId,
|
|
90
95
|
agent: sessionRef?.agent || null,
|
|
91
96
|
callerSessionId: notificationSessionId || callerSessionId,
|
|
@@ -17,7 +17,7 @@ import { isOnDeferredToolSurface } from '../deferred-call-through.mjs';
|
|
|
17
17
|
import { _executeCodeGraphToolLazy } from '../../manager/runtime-loaders.mjs';
|
|
18
18
|
import { requiresReadBody } from '../tool-classify.mjs';
|
|
19
19
|
import { executeCursorNativeBuiltin } from '../../../tools/builtin/external-tool-adapters.mjs';
|
|
20
|
-
import { providerNativePdf, providerReadsImages } from '../../../providers/registry.mjs';
|
|
20
|
+
import { providerModelVisionOverride, providerNativePdf, providerReadsImages } from '../../../providers/registry.mjs';
|
|
21
21
|
import { getModelMetadataSync } from '../../../../../shared/llm/model-catalog.mjs';
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -25,16 +25,22 @@ import { getModelMetadataSync } from '../../../../../shared/llm/model-catalog.mj
|
|
|
25
25
|
* only whether a short PDF may arrive whole as a document block. A scanned
|
|
26
26
|
* page arrives as a page image whenever the model sees images — the provider
|
|
27
27
|
* sends image input (only the text-only local runtime does not) and the
|
|
28
|
-
* catalog does not say the model lacks it — and as OCR text otherwise.
|
|
28
|
+
* catalog does not say the model lacks it — and as OCR text otherwise. A
|
|
29
|
+
* custom model's own image-input setting (on or off) beats the catalog.
|
|
29
30
|
*/
|
|
30
31
|
export function pdfReadModes(
|
|
31
32
|
sessionRef,
|
|
32
|
-
{
|
|
33
|
+
{
|
|
34
|
+
nativePdf = providerNativePdf,
|
|
35
|
+
readsImages = providerReadsImages,
|
|
36
|
+
catalogRow = getModelMetadataSync,
|
|
37
|
+
visionOverride = providerModelVisionOverride,
|
|
38
|
+
} = {}
|
|
33
39
|
) {
|
|
34
40
|
const provider = sessionRef?.provider;
|
|
35
41
|
const model = sessionRef?.model;
|
|
36
|
-
const
|
|
37
|
-
|
|
42
|
+
const vision = model ? (visionOverride(provider, model) ?? catalogRow(model, provider)?.supportsVision) : undefined;
|
|
43
|
+
const seesImages = readsImages(provider) && vision !== false;
|
|
38
44
|
return { pdfPageMode: seesImages ? 'image' : 'ocr', pdfNativeDocument: nativePdf(provider) };
|
|
39
45
|
}
|
|
40
46
|
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// manager/agent-listing-notice.mjs
|
|
2
|
+
// The agents a coding Lead may delegate to, kept current without touching the
|
|
3
|
+
// request prefix (the workflow switch's pattern, workflow-switch-notice.mjs).
|
|
4
|
+
// What the conversation was told is read from the conversation itself, every
|
|
5
|
+
// turn: the "# Available Agents" list the system prompt carries (the one the
|
|
6
|
+
// session was built with: a rebuilt prompt keeps it, prompt-utils.mjs
|
|
7
|
+
// presentedAgentCatalogSlice), then each agent list reminder still in
|
|
8
|
+
// `session.messages`, in order (the user turn that carried one records its
|
|
9
|
+
// change as `meta.agentListing`). A turn whose message stays (answered or
|
|
10
|
+
// failed) counts; one rolled back or compacted away does not, so its change is
|
|
11
|
+
// told again. When the agents enabled at this turn's start (resolved by the
|
|
12
|
+
// session runtime, passed in with the turn) differ from that, the turn carries
|
|
13
|
+
// one reminder naming the agents added and removed.
|
|
14
|
+
import { agentListEntries, presentedAgentCatalogSlice, suffixUserTurnReminders } from './prompt-utils.mjs';
|
|
15
|
+
import { stripRuntimeUserContext, withRuntimeUserContext } from '../runtime-user-context.mjs';
|
|
16
|
+
|
|
17
|
+
/** The agent list the system prompt carries: one complete generated slice of
|
|
18
|
+
* the BP3 block, [] when it carries none (or none valid). */
|
|
19
|
+
function promptAgentList(session) {
|
|
20
|
+
return (session && agentListEntries(presentedAgentCatalogSlice(session) || '')) || [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const ref = (entry) => ({ id: String(entry.id), name: String(entry.name) });
|
|
24
|
+
|
|
25
|
+
/** The agents the conversation was told about: the prompt's list, then every
|
|
26
|
+
* agent list reminder the conversation still holds, in order. */
|
|
27
|
+
export function announcedAgents(session) {
|
|
28
|
+
const told = new Map(promptAgentList(session).map((entry) => [entry.id, ref(entry)]));
|
|
29
|
+
for (const message of Array.isArray(session?.messages) ? session.messages : []) {
|
|
30
|
+
const change = message?.role === 'user' ? message.meta?.agentListing : null;
|
|
31
|
+
if (!change) continue;
|
|
32
|
+
for (const entry of Array.isArray(change.removed) ? change.removed : []) told.delete(entry?.id);
|
|
33
|
+
for (const entry of Array.isArray(change.added) ? change.added : []) {
|
|
34
|
+
if (entry?.id) told.set(entry.id, ref(entry));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return [...told.values()];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function agentListingContent(added, removed, current) {
|
|
41
|
+
return [
|
|
42
|
+
'<system-reminder>',
|
|
43
|
+
'# Available Agents Changed',
|
|
44
|
+
'This updates the Available Agents list in the system prompt and every earlier Available Agents Changed reminder.',
|
|
45
|
+
...(added.length
|
|
46
|
+
? ['New agents are now available to delegate to with `agent`:', ...added.map((entry) => entry.line)]
|
|
47
|
+
: []),
|
|
48
|
+
...(removed.length
|
|
49
|
+
? [
|
|
50
|
+
'These agents are no longer available; an `agent` call naming one is refused:',
|
|
51
|
+
...removed.map((entry) => `- ${entry.name} (${entry.id})`),
|
|
52
|
+
]
|
|
53
|
+
: []),
|
|
54
|
+
...(current.length ? [] : ['No agents are enabled now: do the work yourself until one is enabled.']),
|
|
55
|
+
'</system-reminder>',
|
|
56
|
+
].join('\n');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The agent list reminder this user turn carries, or null when the
|
|
61
|
+
* conversation already knows `current`, the agents enabled at this turn's
|
|
62
|
+
* start (`agentListEntries` entries, from the session runtime's ask options;
|
|
63
|
+
* absent for agent-mode threads and delegated workers: no reminder).
|
|
64
|
+
* @returns {{ content: string, meta: { added: object[], removed: object[] } } | null}
|
|
65
|
+
*/
|
|
66
|
+
export function buildAgentListingNotice(session, current) {
|
|
67
|
+
if (!Array.isArray(current)) return null;
|
|
68
|
+
const announced = announcedAgents(session);
|
|
69
|
+
const now = new Set(current.map((entry) => entry.id));
|
|
70
|
+
const told = new Set(announced.map((entry) => entry.id));
|
|
71
|
+
const added = current.filter((entry) => !told.has(entry.id));
|
|
72
|
+
const removed = announced.filter((entry) => !now.has(entry.id));
|
|
73
|
+
if (!added.length && !removed.length) return null;
|
|
74
|
+
return {
|
|
75
|
+
content: agentListingContent(added, removed, current),
|
|
76
|
+
meta: { added: added.map(ref), removed: removed.map(ref) },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A retained failed turn resubmitted as it was (its own message, its input
|
|
82
|
+
* untouched) learns what changed since: the reminder joins its runtime
|
|
83
|
+
* context, and `meta.agentListing` becomes the net change against what the
|
|
84
|
+
* conversation was told before that message, so later turns read it back as a
|
|
85
|
+
* normal turn's. `index` is the message's place in `session.messages`.
|
|
86
|
+
*/
|
|
87
|
+
export function withAgentListingNotice(session, index, message, current) {
|
|
88
|
+
const notice = buildAgentListingNotice(session, current);
|
|
89
|
+
if (!notice) return message;
|
|
90
|
+
const before = { ...session, messages: session.messages.filter((_, i) => i !== index) };
|
|
91
|
+
const net = buildAgentListingNotice(before, current);
|
|
92
|
+
const { agentListing: _told, ...meta } = message.meta || {};
|
|
93
|
+
const stripped = stripRuntimeUserContext({ ...message, meta });
|
|
94
|
+
const owned = stripped.meta?.runtimeUserContext ? null : message.meta?.runtimeUserContext;
|
|
95
|
+
return withRuntimeUserContext(
|
|
96
|
+
{ ...stripped, meta: { ...stripped.meta, ...(net ? { agentListing: net.meta } : {}) } },
|
|
97
|
+
{
|
|
98
|
+
prefix: owned?.prefix || '',
|
|
99
|
+
suffix: `${owned?.suffix || ''}${suffixUserTurnReminders('', notice.content)}`,
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -34,6 +34,7 @@ const promptSourceFields = (source, execution) => {
|
|
|
34
34
|
const tailTurnFor = (group) => ({
|
|
35
35
|
content: group.content,
|
|
36
36
|
entries: group.entries,
|
|
37
|
+
...(group.hop ? { hop: group.hop } : {}),
|
|
37
38
|
...(group.mode === PENDING_MODE_TASK_NOTIFICATION
|
|
38
39
|
? promptSourceFields(PENDING_MODE_TASK_NOTIFICATION, group.execution)
|
|
39
40
|
: {}),
|
|
@@ -46,6 +47,7 @@ const queuedTurnInput = (tailTurn) => ({
|
|
|
46
47
|
context: null,
|
|
47
48
|
explicitPrefetch: null,
|
|
48
49
|
pendingEntries: tailTurn.entries,
|
|
50
|
+
...(tailTurn.hop ? { hop: tailTurn.hop } : {}),
|
|
49
51
|
promptSource: promptSourceFields(tailTurn.source, tailTurn.execution),
|
|
50
52
|
});
|
|
51
53
|
|
|
@@ -84,7 +84,19 @@ async function runAskTurn({ sessionId, opened, input, askOpts, onToolCall, cwdOv
|
|
|
84
84
|
cwdOverride || opened.turn.session.cwd || process.cwd(),
|
|
85
85
|
opened.runtime.controller?.signal
|
|
86
86
|
);
|
|
87
|
-
const prepared = await prepareAskTurn({
|
|
87
|
+
const prepared = await prepareAskTurn({
|
|
88
|
+
sessionId,
|
|
89
|
+
opened,
|
|
90
|
+
input,
|
|
91
|
+
cwdOverride,
|
|
92
|
+
// A task notification resumes the bot-chain hop of the turn that launched its work.
|
|
93
|
+
transcriptMeta: input.hop ? { ...transcript.user, hop: input.hop } : transcript.user,
|
|
94
|
+
// The agents enabled as this user turn is prepared (the session
|
|
95
|
+
// runtime's resolver; coding Leads): the agent list reminder compares
|
|
96
|
+
// them with what the model was told. Asked again for every user turn
|
|
97
|
+
// the ask runs, queued and background follow-ups included.
|
|
98
|
+
currentAgents: askOpts?.agentsNow?.(),
|
|
99
|
+
});
|
|
88
100
|
const result = await runAskAgentLoop({
|
|
89
101
|
sessionId,
|
|
90
102
|
opened,
|
|
@@ -15,6 +15,7 @@ import { filterModelVisibleSessionMessages } from './message-sanitize.mjs';
|
|
|
15
15
|
import { _getAgentLoop } from './runtime-loaders.mjs';
|
|
16
16
|
import { runAbortable, throwIfAborted } from '../../../../shared/abort-race.mjs';
|
|
17
17
|
import { applyTurnContextMeta, buildUserTurn, startProviderPrewarm, traceTurnContext } from './ask-turn-prepare.mjs';
|
|
18
|
+
import { withAgentListingNotice } from './agent-listing-notice.mjs';
|
|
18
19
|
import { retainedFailedTurnInput, rewindUnansweredPrompt } from './failed-turn-rewind.mjs';
|
|
19
20
|
import { buildAgentLoopOptions, trackedToolCallHook } from './ask-turn-loop-options.mjs';
|
|
20
21
|
import { _api_call_with_interrupt } from './ask-call-interrupt.mjs';
|
|
@@ -23,11 +24,11 @@ import { stampTurnHop } from '../../../../shared/bot-hops.mjs';
|
|
|
23
24
|
|
|
24
25
|
/** Resolves the provider, builds the user turn and pre-commits it; returns
|
|
25
26
|
* what the loop and the commit need from this preparation. */
|
|
26
|
-
export async function prepareAskTurn({ sessionId, opened, input, cwdOverride, transcriptMeta }) {
|
|
27
|
+
export async function prepareAskTurn({ sessionId, opened, input, cwdOverride, transcriptMeta, currentAgents }) {
|
|
27
28
|
const { turn, runtime, turnSignal, askGeneration, checkpoint, turnToken, startedAt, codexTurnId } = opened;
|
|
28
29
|
const session = turn.session;
|
|
29
30
|
// Where the turn sits in a bot-to-bot chain (session-runtime/bot-hops.mjs).
|
|
30
|
-
stampTurnHop(session, transcriptMeta
|
|
31
|
+
stampTurnHop(session, transcriptMeta);
|
|
31
32
|
const retained = input.retryTurnId
|
|
32
33
|
? retainedFailedTurnInput(session.messages, input.retryTurnId, input.retryAttemptId)
|
|
33
34
|
: null;
|
|
@@ -71,21 +72,28 @@ export async function prepareAskTurn({ sessionId, opened, input, cwdOverride, tr
|
|
|
71
72
|
? (() => {
|
|
72
73
|
const historyMessages = filterModelVisibleSessionMessages(session.messages.slice(0, retained.index));
|
|
73
74
|
const { completion, ...previousTranscript } = retained.message.meta.transcript;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
75
|
+
// The retained message keeps its input; agents enabled or switched
|
|
76
|
+
// off since it was built ride its runtime context.
|
|
77
|
+
const userTurnMessage = withAgentListingNotice(
|
|
78
|
+
session,
|
|
79
|
+
retained.index,
|
|
80
|
+
{
|
|
81
|
+
...retained.message,
|
|
82
|
+
meta: {
|
|
83
|
+
...retained.message.meta,
|
|
84
|
+
transcript: {
|
|
85
|
+
...previousTranscript,
|
|
86
|
+
...transcriptMeta,
|
|
87
|
+
messageId: previousTranscript.messageId,
|
|
88
|
+
previousCompletions: [
|
|
89
|
+
...(previousTranscript.previousCompletions || []),
|
|
90
|
+
{ ...completion, at: completion.at ?? previousTranscript.at },
|
|
91
|
+
],
|
|
92
|
+
},
|
|
86
93
|
},
|
|
87
94
|
},
|
|
88
|
-
|
|
95
|
+
currentAgents
|
|
96
|
+
);
|
|
89
97
|
return { historyMessages, userTurnMessage, outgoing: [...historyMessages, userTurnMessage] };
|
|
90
98
|
})()
|
|
91
99
|
: buildUserTurn({
|
|
@@ -97,6 +105,7 @@ export async function prepareAskTurn({ sessionId, opened, input, cwdOverride, tr
|
|
|
97
105
|
transcriptMeta,
|
|
98
106
|
turnPromptSource: input.promptSource,
|
|
99
107
|
effortConfiguration: null,
|
|
108
|
+
currentAgents,
|
|
100
109
|
}),
|
|
101
110
|
wireRoutePending,
|
|
102
111
|
]);
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from './prompt-utils.mjs';
|
|
22
22
|
import { snapshotPendingWorkflowNotice } from './workflow-switch-notice.mjs';
|
|
23
23
|
import { snapshotPendingOrchestrationModeNotice } from './orchestration-mode-switch-notice.mjs';
|
|
24
|
+
import { buildAgentListingNotice } from './agent-listing-notice.mjs';
|
|
24
25
|
import { codexWireSendOpts } from './session-id.mjs';
|
|
25
26
|
import { _tryBridgeExplicitPrefetch } from './prefetch-bridge.mjs';
|
|
26
27
|
import { filterModelVisibleSessionMessages } from './message-sanitize.mjs';
|
|
@@ -150,6 +151,7 @@ export async function buildUserTurn({
|
|
|
150
151
|
transcriptMeta,
|
|
151
152
|
turnPromptSource,
|
|
152
153
|
effortConfiguration,
|
|
154
|
+
currentAgents,
|
|
153
155
|
}) {
|
|
154
156
|
const explicitPrefetchResult = await _tryBridgeExplicitPrefetch(session, explicitPrefetch, turnSignal);
|
|
155
157
|
let contextBlock = '';
|
|
@@ -188,9 +190,12 @@ export async function buildUserTurn({
|
|
|
188
190
|
// A mid-conversation orchestration mode switch, after the workflow notice
|
|
189
191
|
// so the mode it names wins over the one a workflow reminder carries.
|
|
190
192
|
const orchestrationModeNotice = snapshotPendingOrchestrationModeNotice(session);
|
|
193
|
+
// Agents enabled or switched off since the conversation was last told.
|
|
194
|
+
const agentListingNotice = buildAgentListingNotice(session, currentAgents);
|
|
191
195
|
const reminderBlock = [
|
|
192
196
|
workflowNotice?.content || '',
|
|
193
197
|
orchestrationModeNotice?.content || '',
|
|
198
|
+
agentListingNotice?.content || '',
|
|
194
199
|
workspaceNotice?.content || '',
|
|
195
200
|
setupNotice?.content || '',
|
|
196
201
|
currentTimeBlock ? `<system-reminder>\n# Current Time\n${currentTimeBlock}\n</system-reminder>` : '',
|
|
@@ -205,6 +210,8 @@ export async function buildUserTurn({
|
|
|
205
210
|
...(transcriptMeta ? { transcript: transcriptMeta } : {}),
|
|
206
211
|
...(turnPromptSource || {}),
|
|
207
212
|
...(effortConfiguration ? { effortConfiguration } : {}),
|
|
213
|
+
// What its reminder told, read back by later turns (agent-listing-notice.mjs).
|
|
214
|
+
...(agentListingNotice ? { agentListing: agentListingNotice.meta } : {}),
|
|
208
215
|
};
|
|
209
216
|
const userTurnMessage = withRuntimeUserContext(
|
|
210
217
|
{
|
|
@@ -221,6 +228,7 @@ export async function buildUserTurn({
|
|
|
221
228
|
goalReminder,
|
|
222
229
|
workflowNotice,
|
|
223
230
|
orchestrationModeNotice,
|
|
231
|
+
agentListingNotice,
|
|
224
232
|
workspaceNotice,
|
|
225
233
|
setupNotice,
|
|
226
234
|
userTurnMessage,
|
|
@@ -59,6 +59,7 @@ export function createAskSteeringDrain({ sessionId, turn, askOpts }) {
|
|
|
59
59
|
ids: group.ids,
|
|
60
60
|
mode: group.mode,
|
|
61
61
|
...(group.execution ? { execution: group.execution } : {}),
|
|
62
|
+
...(group.hop ? { transcriptMeta: { hop: group.hop } } : {}),
|
|
62
63
|
});
|
|
63
64
|
}
|
|
64
65
|
} else {
|