@jsonstudio/llms 0.6.1739 → 0.6.1890
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/dist/conversion/compat/actions/deepseek-web-request.d.ts +3 -0
- package/dist/conversion/compat/actions/deepseek-web-request.js +350 -0
- package/dist/conversion/compat/actions/deepseek-web-response.d.ts +3 -0
- package/dist/conversion/compat/actions/deepseek-web-response.js +886 -0
- package/dist/conversion/compat/actions/gemini-cli-request.js +3 -1
- package/dist/conversion/compat/profiles/chat-deepseek-web.json +18 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +166 -2
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +169 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +6 -0
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +12 -0
- package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +1 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +4 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +365 -144
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +9 -0
- package/dist/conversion/hub/policy/policy-engine.d.ts +2 -0
- package/dist/conversion/hub/policy/policy-engine.js +8 -0
- package/dist/conversion/hub/process/chat-process.js +466 -16
- package/dist/conversion/hub/response/provider-response.js +0 -35
- package/dist/conversion/responses/responses-openai-bridge.d.ts +2 -0
- package/dist/conversion/responses/responses-openai-bridge.js +166 -8
- package/dist/conversion/shared/anthropic-message-utils.js +10 -1
- package/dist/conversion/shared/protocol-field-allowlists.d.ts +2 -2
- package/dist/conversion/shared/protocol-field-allowlists.js +4 -0
- package/dist/conversion/shared/tool-governor.js +102 -0
- package/dist/guidance/index.js +17 -0
- package/dist/router/virtual-router/bootstrap.js +46 -1
- package/dist/router/virtual-router/classifier.js +59 -4
- package/dist/router/virtual-router/engine/health/index.js +6 -6
- package/dist/router/virtual-router/engine/routing-state/store.js +16 -3
- package/dist/router/virtual-router/engine-logging.js +62 -24
- package/dist/router/virtual-router/engine-selection/route-utils.js +20 -20
- package/dist/router/virtual-router/engine-selection/tier-selection.js +2 -2
- package/dist/router/virtual-router/engine.d.ts +3 -1
- package/dist/router/virtual-router/engine.js +359 -39
- package/dist/router/virtual-router/features.js +2 -1
- package/dist/router/virtual-router/pre-command-file-resolver.d.ts +2 -0
- package/dist/router/virtual-router/pre-command-file-resolver.js +90 -0
- package/dist/router/virtual-router/provider-registry.js +3 -1
- package/dist/router/virtual-router/routing-instructions.d.ts +15 -1
- package/dist/router/virtual-router/routing-instructions.js +110 -151
- package/dist/router/virtual-router/routing-pre-command-actions.d.ts +3 -0
- package/dist/router/virtual-router/routing-pre-command-actions.js +26 -0
- package/dist/router/virtual-router/routing-pre-command-parser.d.ts +2 -0
- package/dist/router/virtual-router/routing-pre-command-parser.js +85 -0
- package/dist/router/virtual-router/routing-pre-command-state-codec.d.ts +3 -0
- package/dist/router/virtual-router/routing-pre-command-state-codec.js +24 -0
- package/dist/router/virtual-router/routing-stop-message-actions.d.ts +2 -0
- package/dist/router/virtual-router/routing-stop-message-actions.js +96 -0
- package/dist/router/virtual-router/routing-stop-message-parser.d.ts +3 -0
- package/dist/router/virtual-router/routing-stop-message-parser.js +142 -0
- package/dist/router/virtual-router/routing-stop-message-state-codec.d.ts +4 -0
- package/dist/router/virtual-router/routing-stop-message-state-codec.js +85 -0
- package/dist/router/virtual-router/sticky-session-store.js +206 -57
- package/dist/router/virtual-router/stop-message-stage-template-files.d.ts +12 -0
- package/dist/router/virtual-router/stop-message-stage-template-files.js +67 -0
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +1 -1
- package/dist/router/virtual-router/stop-message-state-sync.js +5 -0
- package/dist/router/virtual-router/token-file-scanner.d.ts +9 -0
- package/dist/router/virtual-router/token-file-scanner.js +64 -3
- package/dist/router/virtual-router/tool-signals.d.ts +5 -0
- package/dist/router/virtual-router/tool-signals.js +42 -3
- package/dist/router/virtual-router/types.d.ts +19 -1
- package/dist/router/virtual-router/types.js +1 -0
- package/dist/servertool/clock/config.d.ts +1 -1
- package/dist/servertool/clock/config.js +27 -4
- package/dist/servertool/clock/state.js +41 -2
- package/dist/servertool/clock/task-store.d.ts +2 -2
- package/dist/servertool/clock/task-store.js +1 -1
- package/dist/servertool/clock/tasks.d.ts +3 -1
- package/dist/servertool/clock/tasks.js +209 -18
- package/dist/servertool/clock/types.d.ts +17 -0
- package/dist/servertool/continue-execution/log.d.ts +3 -0
- package/dist/servertool/continue-execution/log.js +13 -0
- package/dist/servertool/engine.js +414 -68
- package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.js +6 -6
- package/dist/servertool/handlers/clock-auto.js +54 -71
- package/dist/servertool/handlers/clock.js +121 -6
- package/dist/servertool/handlers/continue-execution.d.ts +1 -0
- package/dist/servertool/handlers/continue-execution.js +91 -0
- package/dist/servertool/handlers/followup-request-builder.js +13 -0
- package/dist/servertool/handlers/gemini-empty-reply-continue.js +1 -1
- package/dist/servertool/handlers/iflow-model-error-retry.js +1 -1
- package/dist/servertool/handlers/recursive-detection-guard.js +1 -1
- package/dist/servertool/handlers/stop-message-auto.js +386 -257
- package/dist/servertool/handlers/stop-message-stage-policy.d.ts +43 -0
- package/dist/servertool/handlers/stop-message-stage-policy.js +684 -0
- package/dist/servertool/handlers/vision.js +1 -1
- package/dist/servertool/log/progress-file.d.ts +14 -0
- package/dist/servertool/log/progress-file.js +88 -0
- package/dist/servertool/pre-command-hooks.d.ts +17 -0
- package/dist/servertool/pre-command-hooks.js +491 -0
- package/dist/servertool/registry.d.ts +23 -6
- package/dist/servertool/registry.js +66 -1
- package/dist/servertool/server-side-tools.d.ts +1 -0
- package/dist/servertool/server-side-tools.js +216 -14
- package/dist/servertool/stop-gateway-context.d.ts +14 -0
- package/dist/servertool/stop-gateway-context.js +167 -0
- package/dist/servertool/stop-message-compare-context.d.ts +24 -0
- package/dist/servertool/stop-message-compare-context.js +133 -0
- package/dist/servertool/types.d.ts +12 -0
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
- package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +36 -1
- package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +3 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +3 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +118 -1
- package/dist/tools/apply-patch/args-normalizer/default-actions.js +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { registerServerToolHandler } from '../registry.js';
|
|
2
|
-
import { loadRoutingInstructionStateSync, saveRoutingInstructionStateAsync } from '../../router/virtual-router/sticky-session-store.js';
|
|
2
|
+
import { loadRoutingInstructionStateSync, saveRoutingInstructionStateAsync, saveRoutingInstructionStateSync } from '../../router/virtual-router/sticky-session-store.js';
|
|
3
3
|
import { isCompactionRequest } from './compaction-detect.js';
|
|
4
4
|
import { extractCapturedChatSeed } from './followup-request-builder.js';
|
|
5
|
+
import { resolveStopMessageStageDecision } from './stop-message-stage-policy.js';
|
|
5
6
|
import { readRuntimeMetadata } from '../../conversion/shared/runtime-metadata.js';
|
|
7
|
+
import { isStopEligibleForServerTool } from '../stop-gateway-context.js';
|
|
8
|
+
import { attachStopMessageCompareContext } from '../stop-message-compare-context.js';
|
|
6
9
|
const STOPMESSAGE_DEBUG = (process.env.ROUTECODEX_STOPMESSAGE_DEBUG || '').trim() === '1';
|
|
7
10
|
const STOPMESSAGE_IMPLICIT_GEMINI = (process.env.ROUTECODEX_STOPMESSAGE_IMPLICIT_GEMINI || '').trim() === '1';
|
|
8
11
|
function debugLog(message, extra) {
|
|
@@ -20,190 +23,277 @@ function debugLog(message, extra) {
|
|
|
20
23
|
}
|
|
21
24
|
}
|
|
22
25
|
const FLOW_ID = 'stop_message_flow';
|
|
26
|
+
const STOP_MESSAGE_EXECUTION_DIRECTIVE = [
|
|
27
|
+
'执行策略(必须遵守):',
|
|
28
|
+
'- 禁止输出口播式状态汇报(如“继续执行中”“我会继续”)。',
|
|
29
|
+
'- 先执行一个真实动作(优先工具调用),再在必要时简短汇报结果。',
|
|
30
|
+
'- 如果即将返回 finish_reason=stop 或协议等价 end_turn,先调用 continue_execution 工具,不要直接结束会话。'
|
|
31
|
+
].join('\n');
|
|
23
32
|
const handler = async (ctx) => {
|
|
24
33
|
const record = ctx.adapterContext;
|
|
25
34
|
const rt = readRuntimeMetadata(ctx.adapterContext);
|
|
35
|
+
const compare = {
|
|
36
|
+
armed: false,
|
|
37
|
+
mode: 'off',
|
|
38
|
+
allowModeOnly: false,
|
|
39
|
+
textLength: 0,
|
|
40
|
+
maxRepeats: 0,
|
|
41
|
+
used: 0,
|
|
42
|
+
remaining: 0,
|
|
43
|
+
active: false,
|
|
44
|
+
stopEligible: false,
|
|
45
|
+
hasCapturedRequest: false,
|
|
46
|
+
compactionRequest: false,
|
|
47
|
+
hasSeed: false,
|
|
48
|
+
decision: 'skip',
|
|
49
|
+
reason: 'handler_start'
|
|
50
|
+
};
|
|
51
|
+
const syncCompareRound = () => {
|
|
52
|
+
const max = Number.isFinite(compare.maxRepeats) ? Math.max(0, Math.floor(compare.maxRepeats)) : 0;
|
|
53
|
+
const used = Number.isFinite(compare.used) ? Math.max(0, Math.floor(compare.used)) : 0;
|
|
54
|
+
compare.maxRepeats = max;
|
|
55
|
+
compare.used = used;
|
|
56
|
+
compare.remaining = max > 0 ? Math.max(0, max - used) : 0;
|
|
57
|
+
compare.active =
|
|
58
|
+
compare.armed &&
|
|
59
|
+
compare.mode !== 'off' &&
|
|
60
|
+
max > 0 &&
|
|
61
|
+
(compare.textLength > 0 || compare.allowModeOnly);
|
|
62
|
+
};
|
|
63
|
+
const updateCompare = (patch) => {
|
|
64
|
+
Object.assign(compare, patch);
|
|
65
|
+
syncCompareRound();
|
|
66
|
+
};
|
|
67
|
+
const markSkip = (reason, patch) => {
|
|
68
|
+
updateCompare({
|
|
69
|
+
decision: 'skip',
|
|
70
|
+
reason,
|
|
71
|
+
...(patch || {})
|
|
72
|
+
});
|
|
73
|
+
return null;
|
|
74
|
+
};
|
|
26
75
|
debugLog('handler_start', {
|
|
27
76
|
requestId: record.requestId,
|
|
28
77
|
providerProtocol: record.providerProtocol
|
|
29
78
|
});
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
(
|
|
33
|
-
|
|
34
|
-
// Other servertool followups must not re-trigger stopMessage (prevents cross-flow loops).
|
|
35
|
-
const loopState = rt?.serverToolLoopState;
|
|
36
|
-
const flowId = loopState && typeof loopState === 'object' && !Array.isArray(loopState)
|
|
37
|
-
? String(loopState.flowId || '').trim()
|
|
38
|
-
: '';
|
|
39
|
-
if (flowId !== FLOW_ID) {
|
|
79
|
+
try {
|
|
80
|
+
const followupFlagRaw = rt?.serverToolFollowup;
|
|
81
|
+
if (followupFlagRaw === true ||
|
|
82
|
+
(typeof followupFlagRaw === 'string' && followupFlagRaw.trim().toLowerCase() === 'true')) {
|
|
40
83
|
debugLog('skip_followup_loop');
|
|
41
|
-
return
|
|
84
|
+
return markSkip('skip_followup_loop');
|
|
42
85
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
const stickyKey = resolveStickyKey(record);
|
|
60
|
-
if (!stickyKey) {
|
|
61
|
-
debugLog('skip_no_sticky_key');
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
let state = loadRoutingInstructionStateSync(stickyKey);
|
|
65
|
-
// If stopMessage was created implicitly (auto) but implicit mode is disabled, do not run it.
|
|
66
|
-
// This avoids surprising followups like "继续执行" when the user never enabled stopMessage.
|
|
67
|
-
if (state &&
|
|
68
|
-
typeof state.stopMessageSource === 'string' &&
|
|
69
|
-
state.stopMessageSource.trim().toLowerCase() === 'auto' &&
|
|
70
|
-
!STOPMESSAGE_IMPLICIT_GEMINI) {
|
|
71
|
-
state.stopMessageText = undefined;
|
|
72
|
-
state.stopMessageMaxRepeats = undefined;
|
|
73
|
-
state.stopMessageUsed = undefined;
|
|
74
|
-
state.stopMessageUpdatedAt = undefined;
|
|
75
|
-
state.stopMessageLastUsedAt = undefined;
|
|
76
|
-
saveRoutingInstructionStateAsync(stickyKey, state);
|
|
77
|
-
debugLog('skip_auto_state_disabled', { stickyKey });
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
if (!state || !state.stopMessageText || !state.stopMessageMaxRepeats) {
|
|
81
|
-
const fallback = resolveStopMessageSnapshot(rt?.stopMessageState);
|
|
82
|
-
if (fallback) {
|
|
83
|
-
state = createStopMessageState(fallback);
|
|
86
|
+
if (hasCompactionFlag(rt)) {
|
|
87
|
+
debugLog('skip_compaction_flag');
|
|
88
|
+
return markSkip('skip_compaction_flag');
|
|
89
|
+
}
|
|
90
|
+
const connectionState = resolveClientConnectionState(record.clientConnectionState);
|
|
91
|
+
if (connectionState?.disconnected === true) {
|
|
92
|
+
debugLog('skip_client_disconnected');
|
|
93
|
+
return markSkip('skip_client_disconnected');
|
|
94
|
+
}
|
|
95
|
+
const clientDisconnectedRaw = record.clientDisconnected;
|
|
96
|
+
if (clientDisconnectedRaw === true ||
|
|
97
|
+
(typeof clientDisconnectedRaw === 'string' && clientDisconnectedRaw.trim().toLowerCase() === 'true')) {
|
|
98
|
+
debugLog('skip_client_disconnected_flag');
|
|
99
|
+
return markSkip('skip_client_disconnected_flag');
|
|
84
100
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
const stickyKey = resolveStickyKey(record, rt);
|
|
102
|
+
let state = stickyKey ? loadRoutingInstructionStateSync(stickyKey) : undefined;
|
|
103
|
+
if (state &&
|
|
104
|
+
typeof state.stopMessageSource === 'string' &&
|
|
105
|
+
state.stopMessageSource.trim().toLowerCase() === 'auto' &&
|
|
106
|
+
!STOPMESSAGE_IMPLICIT_GEMINI) {
|
|
107
|
+
clearStopMessageState(state, Date.now());
|
|
108
|
+
if (stickyKey) {
|
|
109
|
+
persistStopMessageState(stickyKey, state);
|
|
90
110
|
}
|
|
91
|
-
|
|
111
|
+
debugLog('skip_auto_state_disabled', { stickyKey });
|
|
112
|
+
return markSkip('skip_auto_state_disabled', { armed: false, mode: 'off' });
|
|
92
113
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
114
|
+
if (!state || !hasArmedStopMessageState(state)) {
|
|
115
|
+
const fallback = resolveStopMessageSnapshot(rt?.stopMessageState);
|
|
116
|
+
if (fallback) {
|
|
117
|
+
state = createStopMessageState(fallback);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
const implicit = STOPMESSAGE_IMPLICIT_GEMINI ? resolveImplicitGeminiStopMessageSnapshot(ctx, record) : null;
|
|
121
|
+
if (!implicit) {
|
|
122
|
+
debugLog('skip_no_state', { stickyKey });
|
|
123
|
+
return markSkip('skip_no_state', { armed: false, mode: 'off' });
|
|
124
|
+
}
|
|
125
|
+
state = createStopMessageState(implicit);
|
|
126
|
+
}
|
|
127
|
+
if (stickyKey) {
|
|
128
|
+
persistStopMessageState(stickyKey, state);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
updateCompare({ armed: hasArmedStopMessageState(state) });
|
|
132
|
+
const text = typeof state.stopMessageText === 'string' ? state.stopMessageText.trim() : '';
|
|
133
|
+
const maxRepeats = typeof state.stopMessageMaxRepeats === 'number' && Number.isFinite(state.stopMessageMaxRepeats)
|
|
134
|
+
? Math.max(1, Math.floor(state.stopMessageMaxRepeats))
|
|
135
|
+
: 0;
|
|
136
|
+
const stageMode = normalizeStopMessageModeValue(state.stopMessageStageMode);
|
|
137
|
+
const mode = stageMode === 'on' || stageMode === 'auto' || stageMode === 'off' ? stageMode : 'off';
|
|
138
|
+
const allowModeOnlyState = !text && maxRepeats > 0 && (stageMode === 'on' || stageMode === 'auto');
|
|
139
|
+
updateCompare({
|
|
140
|
+
mode,
|
|
141
|
+
allowModeOnly: allowModeOnlyState,
|
|
102
142
|
textLength: text.length,
|
|
103
143
|
maxRepeats
|
|
104
144
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
145
|
+
if ((!text && !allowModeOnlyState) || maxRepeats <= 0) {
|
|
146
|
+
debugLog('skip_invalid_text_or_maxRepeats', {
|
|
147
|
+
stickyKey,
|
|
148
|
+
textLength: text.length,
|
|
149
|
+
maxRepeats,
|
|
150
|
+
stageMode
|
|
151
|
+
});
|
|
152
|
+
return markSkip('skip_invalid_text_or_maxRepeats');
|
|
153
|
+
}
|
|
154
|
+
const used = typeof state.stopMessageUsed === 'number' && Number.isFinite(state.stopMessageUsed)
|
|
155
|
+
? Math.max(0, Math.floor(state.stopMessageUsed))
|
|
156
|
+
: 0;
|
|
157
|
+
updateCompare({ used });
|
|
158
|
+
if (used >= maxRepeats) {
|
|
159
|
+
debugLog('skip_reached_max_repeats', {
|
|
160
|
+
stickyKey,
|
|
161
|
+
used,
|
|
162
|
+
maxRepeats
|
|
163
|
+
});
|
|
164
|
+
clearStopMessageState(state, Date.now());
|
|
165
|
+
if (stickyKey) {
|
|
166
|
+
persistStopMessageState(stickyKey, state);
|
|
167
|
+
}
|
|
168
|
+
return markSkip('skip_reached_max_repeats');
|
|
169
|
+
}
|
|
170
|
+
const stopEligible = isStopEligibleForServerTool(ctx.base, ctx.adapterContext);
|
|
171
|
+
updateCompare({ stopEligible });
|
|
172
|
+
if (!stopEligible) {
|
|
173
|
+
debugLog('skip_not_stop_finish_reason', {
|
|
174
|
+
stickyKey
|
|
175
|
+
});
|
|
176
|
+
return markSkip('skip_not_stop_finish_reason');
|
|
177
|
+
}
|
|
178
|
+
const captured = getCapturedRequest(ctx.adapterContext);
|
|
179
|
+
updateCompare({ hasCapturedRequest: Boolean(captured) });
|
|
180
|
+
if (!captured) {
|
|
181
|
+
debugLog('skip_no_captured_request', {
|
|
182
|
+
stickyKey
|
|
183
|
+
});
|
|
184
|
+
return markSkip('skip_no_captured_request');
|
|
185
|
+
}
|
|
186
|
+
const compactionRequest = isCompactionRequest(captured);
|
|
187
|
+
updateCompare({ compactionRequest });
|
|
188
|
+
if (compactionRequest) {
|
|
189
|
+
debugLog('skip_compaction_request', { stickyKey });
|
|
190
|
+
return markSkip('skip_compaction_request');
|
|
191
|
+
}
|
|
192
|
+
const entryEndpoint = resolveEntryEndpoint(record);
|
|
193
|
+
const seed = extractCapturedChatSeed(captured);
|
|
194
|
+
updateCompare({ hasSeed: Boolean(seed) });
|
|
195
|
+
if (!seed) {
|
|
196
|
+
debugLog('skip_failed_build_followup', { stickyKey });
|
|
197
|
+
return markSkip('skip_failed_build_followup');
|
|
198
|
+
}
|
|
199
|
+
const stageDecision = resolveStopMessageStageDecision({
|
|
200
|
+
baseText: text,
|
|
201
|
+
state,
|
|
202
|
+
capturedMessages: Array.isArray(seed.messages)
|
|
203
|
+
? (seed.messages || [])
|
|
204
|
+
: []
|
|
115
205
|
});
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
state.
|
|
119
|
-
state.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return null;
|
|
127
|
-
}
|
|
128
|
-
if (!isStopFinishReason(ctx.base)) {
|
|
129
|
-
debugLog('skip_not_stop_finish_reason', {
|
|
130
|
-
stickyKey
|
|
206
|
+
state.stopMessageObservationHash = stageDecision.observationHash;
|
|
207
|
+
state.stopMessageObservationStableCount = stageDecision.observationStableCount;
|
|
208
|
+
state.stopMessageBdWorkState = stageDecision.bdWorkState;
|
|
209
|
+
state.stopMessageStage = stageDecision.stage;
|
|
210
|
+
updateCompare({
|
|
211
|
+
stage: stageDecision.stage,
|
|
212
|
+
bdWorkState: stageDecision.bdWorkState,
|
|
213
|
+
observationHash: stageDecision.observationHash,
|
|
214
|
+
observationStableCount: stageDecision.observationStableCount,
|
|
215
|
+
toolSignatureHash: stageDecision.toolSignatureHash
|
|
131
216
|
});
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
217
|
+
if (stageDecision.action === 'stop') {
|
|
218
|
+
clearStopMessageState(state, Date.now());
|
|
219
|
+
if (stickyKey) {
|
|
220
|
+
persistStopMessageState(stickyKey, state);
|
|
221
|
+
}
|
|
222
|
+
debugLog('stop_by_stage_policy', {
|
|
223
|
+
stickyKey,
|
|
224
|
+
reason: stageDecision.stopReason,
|
|
225
|
+
observationStableCount: stageDecision.observationStableCount,
|
|
226
|
+
bdWorkState: stageDecision.bdWorkState
|
|
227
|
+
});
|
|
228
|
+
return markSkip('stage_policy_stop');
|
|
229
|
+
}
|
|
230
|
+
const followupText = typeof stageDecision.followupText === 'string' && stageDecision.followupText.trim()
|
|
231
|
+
? stageDecision.followupText.trim()
|
|
232
|
+
: text;
|
|
233
|
+
if (!followupText) {
|
|
234
|
+
debugLog('skip_empty_followup_text_after_stage', {
|
|
235
|
+
stickyKey,
|
|
236
|
+
stage: stageDecision.stage,
|
|
237
|
+
bdWorkState: stageDecision.bdWorkState
|
|
238
|
+
});
|
|
239
|
+
return markSkip('skip_empty_followup_text_after_stage');
|
|
240
|
+
}
|
|
241
|
+
const nextUsed = used + 1;
|
|
242
|
+
state.stopMessageUsed = nextUsed;
|
|
243
|
+
state.stopMessageLastUsedAt = Date.now();
|
|
244
|
+
updateCompare({ used: nextUsed });
|
|
245
|
+
if (nextUsed >= maxRepeats) {
|
|
246
|
+
clearStopMessageState(state, Date.now());
|
|
247
|
+
}
|
|
248
|
+
if (stickyKey) {
|
|
249
|
+
persistStopMessageState(stickyKey, state);
|
|
250
|
+
}
|
|
251
|
+
const followupProviderKey = resolveStopMessageFollowupProviderKey({ record, runtimeMetadata: rt });
|
|
252
|
+
const followupToolContentMaxChars = resolveStopMessageFollowupToolContentMaxChars({
|
|
253
|
+
providerKey: followupProviderKey,
|
|
254
|
+
model: seed.model
|
|
138
255
|
});
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
256
|
+
const followupOps = [];
|
|
257
|
+
if (typeof followupToolContentMaxChars === 'number' &&
|
|
258
|
+
Number.isFinite(followupToolContentMaxChars) &&
|
|
259
|
+
followupToolContentMaxChars > 0) {
|
|
260
|
+
followupOps.push({ op: 'compact_tool_content', maxChars: Math.floor(followupToolContentMaxChars) });
|
|
261
|
+
}
|
|
262
|
+
followupOps.push({ op: 'ensure_standard_tools' });
|
|
263
|
+
followupOps.push({ op: 'inject_system_text', text: STOP_MESSAGE_EXECUTION_DIRECTIVE });
|
|
264
|
+
followupOps.push({ op: 'append_user_text', text: followupText });
|
|
265
|
+
updateCompare({
|
|
266
|
+
decision: 'trigger',
|
|
267
|
+
reason: 'triggered'
|
|
268
|
+
});
|
|
269
|
+
return {
|
|
270
|
+
flowId: FLOW_ID,
|
|
271
|
+
finalize: async () => ({
|
|
272
|
+
chatResponse: ctx.base,
|
|
273
|
+
execution: {
|
|
274
|
+
flowId: FLOW_ID,
|
|
275
|
+
followup: {
|
|
276
|
+
requestIdSuffix: ':stop_followup',
|
|
277
|
+
entryEndpoint,
|
|
278
|
+
injection: {
|
|
279
|
+
ops: followupOps
|
|
280
|
+
},
|
|
281
|
+
metadata: (connectionState ? { clientConnectionState: connectionState } : {})
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
})
|
|
285
|
+
};
|
|
150
286
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// Increment stopMessage usage counter when we decide to trigger followup.
|
|
154
|
-
const nextUsed = used + 1;
|
|
155
|
-
state.stopMessageUsed = nextUsed;
|
|
156
|
-
state.stopMessageLastUsedAt = Date.now();
|
|
157
|
-
// If this will be the last allowed trigger, mark it for cleanup.
|
|
158
|
-
// We still return the followup plan for this trigger, but clear the config
|
|
159
|
-
// so the next response won't trigger again.
|
|
160
|
-
if (nextUsed >= maxRepeats) {
|
|
161
|
-
const now = Date.now();
|
|
162
|
-
state.stopMessageText = undefined;
|
|
163
|
-
state.stopMessageMaxRepeats = undefined;
|
|
164
|
-
state.stopMessageUsed = undefined;
|
|
165
|
-
state.stopMessageSource = undefined;
|
|
166
|
-
state.stopMessageUpdatedAt = now;
|
|
287
|
+
finally {
|
|
288
|
+
attachStopMessageCompareContext(ctx.adapterContext, compare);
|
|
167
289
|
}
|
|
168
|
-
saveRoutingInstructionStateAsync(stickyKey, state);
|
|
169
|
-
const followupProviderKey = resolveStopMessageFollowupProviderKey({ record, runtimeMetadata: rt });
|
|
170
|
-
const followupToolContentMaxChars = resolveStopMessageFollowupToolContentMaxChars({
|
|
171
|
-
providerKey: followupProviderKey,
|
|
172
|
-
model: seed.model
|
|
173
|
-
});
|
|
174
|
-
const followupOps = [];
|
|
175
|
-
if (typeof followupToolContentMaxChars === 'number' &&
|
|
176
|
-
Number.isFinite(followupToolContentMaxChars) &&
|
|
177
|
-
followupToolContentMaxChars > 0) {
|
|
178
|
-
followupOps.push({ op: 'compact_tool_content', maxChars: Math.floor(followupToolContentMaxChars) });
|
|
179
|
-
}
|
|
180
|
-
followupOps.push({ op: 'append_assistant_message', required: false });
|
|
181
|
-
followupOps.push({ op: 'ensure_standard_tools' });
|
|
182
|
-
followupOps.push({ op: 'append_user_text', text });
|
|
183
|
-
return {
|
|
184
|
-
flowId: FLOW_ID,
|
|
185
|
-
finalize: async () => ({
|
|
186
|
-
chatResponse: ctx.base,
|
|
187
|
-
execution: {
|
|
188
|
-
flowId: FLOW_ID,
|
|
189
|
-
followup: {
|
|
190
|
-
requestIdSuffix: ':stop_followup',
|
|
191
|
-
entryEndpoint,
|
|
192
|
-
injection: {
|
|
193
|
-
ops: followupOps
|
|
194
|
-
},
|
|
195
|
-
metadata: (connectionState ? { clientConnectionState: connectionState } : {})
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
})
|
|
199
|
-
};
|
|
200
290
|
};
|
|
201
|
-
registerServerToolHandler('stop_message_auto', handler, { trigger: 'auto' });
|
|
202
|
-
function resolveStickyKey(record) {
|
|
203
|
-
const sessionId =
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
291
|
+
registerServerToolHandler('stop_message_auto', handler, { trigger: 'auto', hook: { phase: 'default', priority: 40 } });
|
|
292
|
+
function resolveStickyKey(record, runtimeMetadata) {
|
|
293
|
+
const sessionId = readSessionScopeValue(record, runtimeMetadata, 'sessionId') ||
|
|
294
|
+
readSessionScopeValue(record, runtimeMetadata, 'session_id');
|
|
295
|
+
const conversationId = readSessionScopeValue(record, runtimeMetadata, 'conversationId') ||
|
|
296
|
+
readSessionScopeValue(record, runtimeMetadata, 'conversation_id');
|
|
207
297
|
if (sessionId) {
|
|
208
298
|
return `session:${sessionId}`;
|
|
209
299
|
}
|
|
@@ -212,6 +302,47 @@ function resolveStickyKey(record) {
|
|
|
212
302
|
}
|
|
213
303
|
return undefined;
|
|
214
304
|
}
|
|
305
|
+
function persistStopMessageState(stickyKey, state) {
|
|
306
|
+
if (!stickyKey) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
saveRoutingInstructionStateSync(stickyKey, state);
|
|
310
|
+
saveRoutingInstructionStateAsync(stickyKey, state);
|
|
311
|
+
}
|
|
312
|
+
function readSessionScopeValue(record, runtimeMetadata, key) {
|
|
313
|
+
const direct = toNonEmptyText(record[key]);
|
|
314
|
+
if (direct) {
|
|
315
|
+
return direct;
|
|
316
|
+
}
|
|
317
|
+
const metadata = asRecord(record.metadata);
|
|
318
|
+
const fromMetadata = metadata ? toNonEmptyText(metadata[key]) : '';
|
|
319
|
+
if (fromMetadata) {
|
|
320
|
+
return fromMetadata;
|
|
321
|
+
}
|
|
322
|
+
const fromMetadataContext = metadata ? toNonEmptyText(asRecord(metadata.context)?.[key]) : '';
|
|
323
|
+
if (fromMetadataContext) {
|
|
324
|
+
return fromMetadataContext;
|
|
325
|
+
}
|
|
326
|
+
const originalRequest = asRecord(record.originalRequest);
|
|
327
|
+
const fromOriginalMetadata = originalRequest
|
|
328
|
+
? toNonEmptyText(asRecord(originalRequest.metadata)?.[key])
|
|
329
|
+
: '';
|
|
330
|
+
if (fromOriginalMetadata) {
|
|
331
|
+
return fromOriginalMetadata;
|
|
332
|
+
}
|
|
333
|
+
const runtime = asRecord(runtimeMetadata);
|
|
334
|
+
const fromRuntime = runtime ? toNonEmptyText(runtime[key]) : '';
|
|
335
|
+
if (fromRuntime) {
|
|
336
|
+
return fromRuntime;
|
|
337
|
+
}
|
|
338
|
+
return '';
|
|
339
|
+
}
|
|
340
|
+
function asRecord(value) {
|
|
341
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
return value;
|
|
345
|
+
}
|
|
215
346
|
function toNonEmptyText(value) {
|
|
216
347
|
return typeof value === 'string' && value.trim().length ? value.trim() : '';
|
|
217
348
|
}
|
|
@@ -233,6 +364,30 @@ function readProviderKeyFromMetadata(value) {
|
|
|
233
364
|
}
|
|
234
365
|
return '';
|
|
235
366
|
}
|
|
367
|
+
function hasArmedStopMessageState(state) {
|
|
368
|
+
const maxRepeats = typeof state.stopMessageMaxRepeats === 'number' && Number.isFinite(state.stopMessageMaxRepeats)
|
|
369
|
+
? Math.max(1, Math.floor(state.stopMessageMaxRepeats))
|
|
370
|
+
: 0;
|
|
371
|
+
if (maxRepeats <= 0) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
const text = typeof state.stopMessageText === 'string' ? state.stopMessageText.trim() : '';
|
|
375
|
+
if (text) {
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
const mode = normalizeStopMessageModeValue(state.stopMessageStageMode);
|
|
379
|
+
return mode === 'on' || mode === 'auto';
|
|
380
|
+
}
|
|
381
|
+
function normalizeStopMessageModeValue(value) {
|
|
382
|
+
if (typeof value !== 'string') {
|
|
383
|
+
return undefined;
|
|
384
|
+
}
|
|
385
|
+
const normalized = value.trim().toLowerCase();
|
|
386
|
+
if (normalized === 'on' || normalized === 'off' || normalized === 'auto') {
|
|
387
|
+
return normalized;
|
|
388
|
+
}
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
236
391
|
function resolveStopMessageFollowupProviderKey(args) {
|
|
237
392
|
const direct = toNonEmptyText(args.record.providerKey) ||
|
|
238
393
|
toNonEmptyText(args.record.providerId) ||
|
|
@@ -259,100 +414,27 @@ function resolveStopMessageFollowupToolContentMaxChars(params) {
|
|
|
259
414
|
}
|
|
260
415
|
return undefined;
|
|
261
416
|
}
|
|
262
|
-
function isStopFinishReason(base) {
|
|
263
|
-
if (!base || typeof base !== 'object' || Array.isArray(base)) {
|
|
264
|
-
return false;
|
|
265
|
-
}
|
|
266
|
-
const payload = base;
|
|
267
|
-
const choicesRaw = payload.choices;
|
|
268
|
-
if (Array.isArray(choicesRaw) && choicesRaw.length) {
|
|
269
|
-
const first = choicesRaw[0];
|
|
270
|
-
if (!first || typeof first !== 'object' || Array.isArray(first)) {
|
|
271
|
-
return false;
|
|
272
|
-
}
|
|
273
|
-
const finishReasonRaw = first.finish_reason;
|
|
274
|
-
const finishReason = typeof finishReasonRaw === 'string' && finishReasonRaw.trim()
|
|
275
|
-
? finishReasonRaw.trim().toLowerCase()
|
|
276
|
-
: '';
|
|
277
|
-
// 将模型视为“自然结束”的场景:
|
|
278
|
-
// - OpenAI 兼容:finish_reason === 'stop'
|
|
279
|
-
// - 截断场景:finish_reason === 'length'(例如 Gemini/Claude 流式输出被 max token 截断)
|
|
280
|
-
// 统一视作可触发 stopMessage 的终止点;仅排除显式的 tool_calls。
|
|
281
|
-
if (!finishReason || finishReason === 'tool_calls') {
|
|
282
|
-
return false;
|
|
283
|
-
}
|
|
284
|
-
if (finishReason !== 'stop' && finishReason !== 'length') {
|
|
285
|
-
return false;
|
|
286
|
-
}
|
|
287
|
-
const message = first.message &&
|
|
288
|
-
typeof first.message === 'object' &&
|
|
289
|
-
!Array.isArray(first.message)
|
|
290
|
-
? first.message
|
|
291
|
-
: null;
|
|
292
|
-
if (!message) {
|
|
293
|
-
return false;
|
|
294
|
-
}
|
|
295
|
-
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
|
|
296
|
-
if (toolCalls.length > 0) {
|
|
297
|
-
// 如果当前响应仍在发起工具调用,则由工具执行驱动后续轮次,不触发 stopMessage。
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
return true;
|
|
301
|
-
}
|
|
302
|
-
// OpenAI Responses shape: status completed + no required_action + no tool-like output entries
|
|
303
|
-
const statusRaw = typeof payload.status === 'string' ? payload.status.trim().toLowerCase() : '';
|
|
304
|
-
if (statusRaw && statusRaw !== 'completed') {
|
|
305
|
-
return false;
|
|
306
|
-
}
|
|
307
|
-
if (payload.required_action && typeof payload.required_action === 'object') {
|
|
308
|
-
return false;
|
|
309
|
-
}
|
|
310
|
-
const outputRaw = Array.isArray(payload.output) ? payload.output : [];
|
|
311
|
-
if (!outputRaw.length) {
|
|
312
|
-
return false;
|
|
313
|
-
}
|
|
314
|
-
if (outputRaw.some((item) => hasToolLikeOutput(item))) {
|
|
315
|
-
return false;
|
|
316
|
-
}
|
|
317
|
-
return true;
|
|
318
|
-
}
|
|
319
|
-
function extractAssistantMessageForFollowup(chatResponse) {
|
|
320
|
-
if (!chatResponse || typeof chatResponse !== 'object' || Array.isArray(chatResponse)) {
|
|
321
|
-
return null;
|
|
322
|
-
}
|
|
323
|
-
const choices = Array.isArray(chatResponse.choices)
|
|
324
|
-
? chatResponse.choices
|
|
325
|
-
: [];
|
|
326
|
-
if (!choices.length) {
|
|
327
|
-
return null;
|
|
328
|
-
}
|
|
329
|
-
const first = choices[0];
|
|
330
|
-
if (!first || typeof first !== 'object' || Array.isArray(first)) {
|
|
331
|
-
return null;
|
|
332
|
-
}
|
|
333
|
-
const message = first.message;
|
|
334
|
-
if (!message || typeof message !== 'object' || Array.isArray(message)) {
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
const role = typeof message.role === 'string' ? String(message.role) : '';
|
|
338
|
-
if (role && role.toLowerCase() !== 'assistant') {
|
|
339
|
-
return null;
|
|
340
|
-
}
|
|
341
|
-
const content = message.content;
|
|
342
|
-
if (typeof content !== 'string' || !content.trim()) {
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
345
|
-
return { role: 'assistant', content: content.trim() };
|
|
346
|
-
}
|
|
347
417
|
function getCapturedRequest(adapterContext) {
|
|
348
418
|
if (!adapterContext || typeof adapterContext !== 'object') {
|
|
349
419
|
return null;
|
|
350
420
|
}
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
421
|
+
const contextRecord = adapterContext;
|
|
422
|
+
const direct = contextRecord.capturedChatRequest;
|
|
423
|
+
if (direct && typeof direct === 'object' && !Array.isArray(direct)) {
|
|
424
|
+
return direct;
|
|
425
|
+
}
|
|
426
|
+
const runtime = readRuntimeMetadata(contextRecord);
|
|
427
|
+
const runtimeCaptured = runtime && typeof runtime === 'object' && !Array.isArray(runtime)
|
|
428
|
+
? runtime.capturedChatRequest
|
|
429
|
+
: undefined;
|
|
430
|
+
if (runtimeCaptured && typeof runtimeCaptured === 'object' && !Array.isArray(runtimeCaptured)) {
|
|
431
|
+
return runtimeCaptured;
|
|
354
432
|
}
|
|
355
|
-
|
|
433
|
+
const originalRequest = contextRecord.originalRequest;
|
|
434
|
+
if (originalRequest && typeof originalRequest === 'object' && !Array.isArray(originalRequest)) {
|
|
435
|
+
return originalRequest;
|
|
436
|
+
}
|
|
437
|
+
return null;
|
|
356
438
|
}
|
|
357
439
|
function extractResponsesOutputText(base) {
|
|
358
440
|
const raw = base.output_text;
|
|
@@ -422,7 +504,9 @@ function resolveStopMessageSnapshot(raw) {
|
|
|
422
504
|
const maxRepeats = typeof record.stopMessageMaxRepeats === 'number' && Number.isFinite(record.stopMessageMaxRepeats)
|
|
423
505
|
? Math.max(1, Math.floor(record.stopMessageMaxRepeats))
|
|
424
506
|
: 0;
|
|
425
|
-
|
|
507
|
+
const stageMode = normalizeStopMessageStageMode(record.stopMessageStageMode);
|
|
508
|
+
const allowModeOnlyState = !text && maxRepeats > 0 && (stageMode === 'on' || stageMode === 'auto');
|
|
509
|
+
if ((!text && !allowModeOnlyState) || maxRepeats <= 0) {
|
|
426
510
|
return null;
|
|
427
511
|
}
|
|
428
512
|
const used = typeof record.stopMessageUsed === 'number' && Number.isFinite(record.stopMessageUsed)
|
|
@@ -437,13 +521,30 @@ function resolveStopMessageSnapshot(raw) {
|
|
|
437
521
|
const source = typeof record.stopMessageSource === 'string' && record.stopMessageSource.trim()
|
|
438
522
|
? record.stopMessageSource.trim()
|
|
439
523
|
: undefined;
|
|
524
|
+
const stage = typeof record.stopMessageStage === 'string' && record.stopMessageStage.trim()
|
|
525
|
+
? record.stopMessageStage.trim()
|
|
526
|
+
: undefined;
|
|
527
|
+
const observationHash = typeof record.stopMessageObservationHash === 'string' && record.stopMessageObservationHash.trim()
|
|
528
|
+
? record.stopMessageObservationHash.trim()
|
|
529
|
+
: undefined;
|
|
530
|
+
const observationStableCount = typeof record.stopMessageObservationStableCount === 'number' && Number.isFinite(record.stopMessageObservationStableCount)
|
|
531
|
+
? Math.max(0, Math.floor(record.stopMessageObservationStableCount))
|
|
532
|
+
: undefined;
|
|
533
|
+
const bdWorkState = typeof record.stopMessageBdWorkState === 'string' && record.stopMessageBdWorkState.trim()
|
|
534
|
+
? record.stopMessageBdWorkState.trim()
|
|
535
|
+
: undefined;
|
|
440
536
|
return {
|
|
441
537
|
text,
|
|
442
538
|
maxRepeats,
|
|
443
539
|
used,
|
|
444
540
|
...(source ? { source } : {}),
|
|
445
541
|
...(updatedAt ? { updatedAt } : {}),
|
|
446
|
-
...(lastUsedAt ? { lastUsedAt } : {})
|
|
542
|
+
...(lastUsedAt ? { lastUsedAt } : {}),
|
|
543
|
+
...(stage ? { stage } : {}),
|
|
544
|
+
...(stageMode ? { stageMode } : {}),
|
|
545
|
+
...(observationHash ? { observationHash } : {}),
|
|
546
|
+
...(typeof observationStableCount === 'number' ? { observationStableCount } : {}),
|
|
547
|
+
...(bdWorkState ? { bdWorkState } : {})
|
|
447
548
|
};
|
|
448
549
|
}
|
|
449
550
|
function hasCompactionFlag(rt) {
|
|
@@ -478,7 +579,7 @@ function resolveImplicitGeminiStopMessageSnapshot(ctx, record) {
|
|
|
478
579
|
return null;
|
|
479
580
|
}
|
|
480
581
|
// 仅在本轮响应被视为“自然结束”(stop/length,且无 tool_calls)时触发,避免干扰正常对话。
|
|
481
|
-
if (!
|
|
582
|
+
if (!isStopEligibleForServerTool(ctx.base, ctx.adapterContext)) {
|
|
482
583
|
return null;
|
|
483
584
|
}
|
|
484
585
|
// 仅在“空回复”时触发隐式 stopMessage:
|
|
@@ -564,9 +665,37 @@ function createStopMessageState(snapshot) {
|
|
|
564
665
|
stopMessageMaxRepeats: snapshot.maxRepeats,
|
|
565
666
|
stopMessageUsed: snapshot.used,
|
|
566
667
|
stopMessageUpdatedAt: snapshot.updatedAt,
|
|
567
|
-
stopMessageLastUsedAt: snapshot.lastUsedAt
|
|
668
|
+
stopMessageLastUsedAt: snapshot.lastUsedAt,
|
|
669
|
+
stopMessageStage: snapshot.stage,
|
|
670
|
+
stopMessageStageMode: snapshot.stageMode,
|
|
671
|
+
stopMessageObservationHash: snapshot.observationHash,
|
|
672
|
+
stopMessageObservationStableCount: snapshot.observationStableCount,
|
|
673
|
+
stopMessageBdWorkState: snapshot.bdWorkState
|
|
568
674
|
};
|
|
569
675
|
}
|
|
676
|
+
function normalizeStopMessageStageMode(value) {
|
|
677
|
+
if (typeof value !== 'string') {
|
|
678
|
+
return undefined;
|
|
679
|
+
}
|
|
680
|
+
const normalized = value.trim().toLowerCase();
|
|
681
|
+
if (normalized === 'on' || normalized === 'off' || normalized === 'auto') {
|
|
682
|
+
return normalized;
|
|
683
|
+
}
|
|
684
|
+
return undefined;
|
|
685
|
+
}
|
|
686
|
+
function clearStopMessageState(state, now) {
|
|
687
|
+
state.stopMessageText = undefined;
|
|
688
|
+
state.stopMessageMaxRepeats = undefined;
|
|
689
|
+
state.stopMessageUsed = undefined;
|
|
690
|
+
state.stopMessageSource = undefined;
|
|
691
|
+
state.stopMessageStage = undefined;
|
|
692
|
+
state.stopMessageStageMode = undefined;
|
|
693
|
+
state.stopMessageObservationHash = undefined;
|
|
694
|
+
state.stopMessageObservationStableCount = undefined;
|
|
695
|
+
state.stopMessageBdWorkState = undefined;
|
|
696
|
+
state.stopMessageUpdatedAt = now;
|
|
697
|
+
state.stopMessageLastUsedAt = now;
|
|
698
|
+
}
|
|
570
699
|
function resolveEntryEndpoint(record) {
|
|
571
700
|
const raw = typeof record.entryEndpoint === 'string' && record.entryEndpoint.trim()
|
|
572
701
|
? record.entryEndpoint.trim()
|