@jsonstudio/llms 0.6.1892 → 0.6.2172
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.js +16 -2
- package/dist/conversion/compat/actions/deepseek-web-response.d.ts +7 -1
- package/dist/conversion/compat/actions/deepseek-web-response.js +302 -40
- package/dist/conversion/compat/actions/harvest-tool-calls-from-text.d.ts +5 -0
- package/dist/conversion/compat/actions/harvest-tool-calls-from-text.js +7 -4
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +1 -0
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +12 -0
- package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +1 -1
- package/dist/conversion/compat/actions/tool-text-request-guidance.d.ts +9 -0
- package/dist/conversion/compat/actions/tool-text-request-guidance.js +177 -0
- package/dist/conversion/compat/antigravity-session-signature.d.ts +6 -0
- package/dist/conversion/compat/antigravity-session-signature.js +15 -0
- package/dist/conversion/compat/profiles/chat-deepseek-web.json +52 -1
- package/dist/conversion/compat/profiles/chat-glm.json +22 -0
- package/dist/conversion/compat/profiles/chat-iflow.json +4 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +13 -27
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +10 -1
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +13 -4
- package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +1 -53
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +8 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +8 -4
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +191 -9
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +118 -15
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +65 -2
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.d.ts +34 -0
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.js +75 -0
- package/dist/conversion/hub/process/chat-process.js +85 -18
- package/dist/conversion/hub/response/provider-response.js +21 -50
- package/dist/conversion/hub/response/response-runtime.js +71 -10
- package/dist/conversion/responses/responses-openai-bridge/response-payload.d.ts +3 -0
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +576 -0
- package/dist/conversion/responses/responses-openai-bridge/types.d.ts +42 -0
- package/dist/conversion/responses/responses-openai-bridge/types.js +1 -0
- package/dist/conversion/responses/responses-openai-bridge.d.ts +3 -44
- package/dist/conversion/responses/responses-openai-bridge.js +193 -504
- package/dist/conversion/shared/anthropic-message-utils.js +82 -2
- package/dist/conversion/shared/bridge-message-utils.js +92 -39
- package/dist/conversion/shared/snapshot-hooks.js +8 -13
- package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.js +129 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-json.d.ts +4 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-json.js +637 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-shared.d.ts +21 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-shared.js +177 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.d.ts +5 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.js +385 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-xml.d.ts +10 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-xml.js +602 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors.d.ts +5 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors.js +4 -0
- package/dist/conversion/shared/text-markup-normalizer/normalize.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer/normalize.js +76 -0
- package/dist/conversion/shared/text-markup-normalizer.d.ts +3 -25
- package/dist/conversion/shared/text-markup-normalizer.js +2 -1386
- package/dist/conversion/shared/tool-governor.js +136 -10
- package/dist/filters/utils/snapshot-writer.js +3 -3
- package/dist/router/virtual-router/bootstrap/auth-utils.d.ts +6 -0
- package/dist/router/virtual-router/bootstrap/auth-utils.js +288 -0
- package/dist/router/virtual-router/bootstrap/claude-code-helpers.d.ts +11 -0
- package/dist/router/virtual-router/bootstrap/claude-code-helpers.js +18 -0
- package/dist/router/virtual-router/bootstrap/config-defaults.d.ts +5 -0
- package/dist/router/virtual-router/bootstrap/config-defaults.js +13 -0
- package/dist/router/virtual-router/bootstrap/config-normalizers.d.ts +4 -0
- package/dist/router/virtual-router/bootstrap/config-normalizers.js +106 -0
- package/dist/router/virtual-router/bootstrap/profile-builder.d.ts +7 -0
- package/dist/router/virtual-router/bootstrap/profile-builder.js +68 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +40 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.js +212 -0
- package/dist/router/virtual-router/bootstrap/responses-helpers.d.ts +15 -0
- package/dist/router/virtual-router/bootstrap/responses-helpers.js +65 -0
- package/dist/router/virtual-router/bootstrap/routing-config.d.ts +23 -0
- package/dist/router/virtual-router/bootstrap/routing-config.js +293 -0
- package/dist/router/virtual-router/bootstrap/streaming-helpers.d.ts +12 -0
- package/dist/router/virtual-router/bootstrap/streaming-helpers.js +128 -0
- package/dist/router/virtual-router/bootstrap/utils.d.ts +5 -0
- package/dist/router/virtual-router/bootstrap/utils.js +41 -0
- package/dist/router/virtual-router/bootstrap/web-search-config.d.ts +4 -0
- package/dist/router/virtual-router/bootstrap/web-search-config.js +131 -0
- package/dist/router/virtual-router/bootstrap.d.ts +0 -4
- package/dist/router/virtual-router/bootstrap.js +31 -1275
- package/dist/router/virtual-router/classifier.js +32 -14
- package/dist/router/virtual-router/engine/antigravity/alias-lease.js +2 -2
- package/dist/router/virtual-router/engine/cooldown-manager.d.ts +34 -0
- package/dist/router/virtual-router/engine/cooldown-manager.js +118 -0
- package/dist/router/virtual-router/engine/route-analytics.d.ts +28 -0
- package/dist/router/virtual-router/engine/route-analytics.js +44 -0
- package/dist/router/virtual-router/engine/routing-pools/index.js +165 -4
- package/dist/router/virtual-router/engine/sticky-session-manager.d.ts +29 -0
- package/dist/router/virtual-router/engine/sticky-session-manager.js +55 -0
- package/dist/router/virtual-router/engine-logging.d.ts +42 -1
- package/dist/router/virtual-router/engine-logging.js +82 -15
- package/dist/router/virtual-router/engine-selection/multimodal-capability.d.ts +3 -0
- package/dist/router/virtual-router/engine-selection/multimodal-capability.js +26 -0
- package/dist/router/virtual-router/engine-selection/route-utils.js +6 -2
- package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/tier-selection.js +31 -1
- package/dist/router/virtual-router/engine.d.ts +21 -7
- package/dist/router/virtual-router/engine.js +198 -194
- package/dist/router/virtual-router/features.js +12 -4
- package/dist/router/virtual-router/message-utils.d.ts +8 -0
- package/dist/router/virtual-router/message-utils.js +170 -45
- package/dist/router/virtual-router/pre-command-file-resolver.js +40 -2
- package/dist/router/virtual-router/routing-instructions.d.ts +8 -0
- package/dist/router/virtual-router/routing-instructions.js +18 -2
- package/dist/router/virtual-router/routing-stop-message-actions.js +34 -10
- package/dist/router/virtual-router/routing-stop-message-state-codec.d.ts +2 -0
- package/dist/router/virtual-router/routing-stop-message-state-codec.js +50 -1
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +1 -1
- package/dist/router/virtual-router/stop-message-state-sync.js +3 -0
- package/dist/router/virtual-router/token-counter.js +51 -10
- package/dist/router/virtual-router/tool-signals.js +4 -0
- package/dist/router/virtual-router/types.d.ts +15 -0
- package/dist/servertool/clock/session-scope.d.ts +3 -0
- package/dist/servertool/clock/session-scope.js +52 -0
- package/dist/servertool/clock/state.js +9 -0
- package/dist/servertool/clock/tasks.js +12 -1
- package/dist/servertool/clock/types.d.ts +3 -0
- package/dist/servertool/engine.js +177 -31
- package/dist/servertool/handlers/clock-auto.js +2 -8
- package/dist/servertool/handlers/clock.js +6 -9
- package/dist/servertool/handlers/recursive-detection-guard.js +53 -14
- package/dist/servertool/handlers/stop-message-auto/blocked-report.d.ts +16 -0
- package/dist/servertool/handlers/stop-message-auto/blocked-report.js +349 -0
- package/dist/servertool/handlers/stop-message-auto/iflow-followup.d.ts +23 -0
- package/dist/servertool/handlers/stop-message-auto/iflow-followup.js +503 -0
- package/dist/servertool/handlers/stop-message-auto/routing-state.d.ts +38 -0
- package/dist/servertool/handlers/stop-message-auto/routing-state.js +149 -0
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +67 -0
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +387 -0
- package/dist/servertool/handlers/stop-message-auto.d.ts +1 -1
- package/dist/servertool/handlers/stop-message-auto.js +80 -556
- package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.d.ts +18 -0
- package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.js +398 -0
- package/dist/servertool/handlers/stop-message-stage-policy/decision.d.ts +9 -0
- package/dist/servertool/handlers/stop-message-stage-policy/decision.js +127 -0
- package/dist/servertool/handlers/stop-message-stage-policy/observation.d.ts +2 -0
- package/dist/servertool/handlers/stop-message-stage-policy/observation.js +179 -0
- package/dist/servertool/handlers/stop-message-stage-policy/templates.d.ts +4 -0
- package/dist/servertool/handlers/stop-message-stage-policy/templates.js +96 -0
- package/dist/servertool/handlers/stop-message-stage-policy/text-utils.d.ts +9 -0
- package/dist/servertool/handlers/stop-message-stage-policy/text-utils.js +89 -0
- package/dist/servertool/handlers/stop-message-stage-policy/types.d.ts +59 -0
- package/dist/servertool/handlers/stop-message-stage-policy/types.js +1 -0
- package/dist/servertool/handlers/stop-message-stage-policy.d.ts +3 -43
- package/dist/servertool/handlers/stop-message-stage-policy.js +2 -684
- package/dist/servertool/handlers/web-search.js +117 -0
- package/dist/servertool/server-side-tools.d.ts +0 -1
- package/dist/servertool/server-side-tools.js +4 -3
- package/dist/sse/sse-to-json/builders/response-builder.js +16 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +1 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +110 -37
- package/dist/telemetry/stats-center.d.ts +9 -0
- package/dist/telemetry/stats-center.js +29 -1
- package/dist/tools/apply-patch/structured/coercion.js +3 -11
- package/dist/tools/exec-command/validator.d.ts +1 -0
- package/dist/tools/exec-command/validator.js +132 -0
- package/dist/tools/tool-registry.d.ts +1 -0
- package/dist/tools/tool-registry.js +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { analyzeMediaAttachments, detectExtendedThinkingKeyword, detectKeyword, extractMessageText, getLatestMessageRole, } from './message-utils.js';
|
|
2
2
|
import { extractAntigravityGeminiSessionId } from '../../conversion/compat/antigravity-session-signature.js';
|
|
3
3
|
import { detectCodingTool, detectLastAssistantToolCategory, detectVisionTool, detectWebSearchToolDeclared, detectWebTool, extractMeaningfulDeclaredToolNames } from './tool-signals.js';
|
|
4
4
|
import { computeRequestTokens } from './token-estimator.js';
|
|
@@ -18,10 +18,14 @@ export function buildRoutingFeatures(request, metadata) {
|
|
|
18
18
|
return undefined;
|
|
19
19
|
}
|
|
20
20
|
})();
|
|
21
|
-
const latestUserMessage = getLatestUserMessage(request.messages);
|
|
22
21
|
const latestMessageRole = getLatestMessageRole(request.messages);
|
|
22
|
+
const latestMessage = Array.isArray(request.messages) && request.messages.length
|
|
23
|
+
? request.messages[request.messages.length - 1]
|
|
24
|
+
: undefined;
|
|
23
25
|
const assistantMessages = request.messages.filter((msg) => msg.role === 'assistant');
|
|
24
|
-
const latestUserText =
|
|
26
|
+
const latestUserText = latestMessageRole === 'user' && latestMessage
|
|
27
|
+
? extractMessageText(latestMessage)
|
|
28
|
+
: '';
|
|
25
29
|
const normalizedUserText = latestUserText.toLowerCase();
|
|
26
30
|
const meaningfulDeclaredTools = extractMeaningfulDeclaredToolNames(request.tools);
|
|
27
31
|
const hasTools = meaningfulDeclaredTools.length > 0;
|
|
@@ -31,7 +35,8 @@ export function buildRoutingFeatures(request, metadata) {
|
|
|
31
35
|
const hasVisionTool = detectVisionTool(request);
|
|
32
36
|
// Vision routing must only trigger for the current user turn (latest message),
|
|
33
37
|
// not for historical user messages carrying images during tool/assistant followups.
|
|
34
|
-
const
|
|
38
|
+
const mediaSignals = latestMessageRole === 'user' ? analyzeMediaAttachments(latestMessage) : analyzeMediaAttachments(undefined);
|
|
39
|
+
const hasImageAttachment = mediaSignals.hasAnyMedia;
|
|
35
40
|
const hasCodingTool = detectCodingTool(request);
|
|
36
41
|
const hasWebTool = detectWebTool(request);
|
|
37
42
|
const hasThinkingKeyword = hasThinking || detectExtendedThinkingKeyword(normalizedUserText);
|
|
@@ -58,6 +63,9 @@ export function buildRoutingFeatures(request, metadata) {
|
|
|
58
63
|
hasToolCallResponses,
|
|
59
64
|
hasVisionTool,
|
|
60
65
|
hasImageAttachment,
|
|
66
|
+
hasVideoAttachment: mediaSignals.hasVideo,
|
|
67
|
+
hasRemoteVideoAttachment: mediaSignals.hasRemoteVideo,
|
|
68
|
+
hasLocalVideoAttachment: mediaSignals.hasLocalVideo,
|
|
61
69
|
hasWebTool,
|
|
62
70
|
hasWebSearchToolDeclared: detectWebSearchToolDeclared(request),
|
|
63
71
|
hasCodingTool,
|
|
@@ -4,4 +4,12 @@ export declare function getLatestMessageRole(messages: StandardizedMessage[]): s
|
|
|
4
4
|
export declare function extractMessageText(message: StandardizedMessage): string;
|
|
5
5
|
export declare function detectKeyword(text: string, keywords: string[]): boolean;
|
|
6
6
|
export declare function detectExtendedThinkingKeyword(text: string): boolean;
|
|
7
|
+
export interface MediaAttachmentSignals {
|
|
8
|
+
hasAnyMedia: boolean;
|
|
9
|
+
hasImage: boolean;
|
|
10
|
+
hasVideo: boolean;
|
|
11
|
+
hasRemoteVideo: boolean;
|
|
12
|
+
hasLocalVideo: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare function analyzeMediaAttachments(message: StandardizedMessage | undefined): MediaAttachmentSignals;
|
|
7
15
|
export declare function detectImageAttachment(message: StandardizedMessage | undefined): boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isIP } from 'node:net';
|
|
1
2
|
export function getLatestUserMessage(messages) {
|
|
2
3
|
for (let idx = messages.length - 1; idx >= 0; idx -= 1) {
|
|
3
4
|
if (messages[idx]?.role === 'user') {
|
|
@@ -56,55 +57,179 @@ export function detectExtendedThinkingKeyword(text) {
|
|
|
56
57
|
const keywords = ['仔细分析', '思考', '超级思考', '深度思考', 'careful analysis', 'deep thinking', 'deliberate'];
|
|
57
58
|
return keywords.some((keyword) => text.includes(keyword));
|
|
58
59
|
}
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
const LOCAL_URL_SCHEMES = ['data:', 'file:', 'blob:'];
|
|
61
|
+
function extractMediaUrlCandidate(record) {
|
|
62
|
+
if (typeof record.image_url === 'string') {
|
|
63
|
+
return record.image_url ?? '';
|
|
64
|
+
}
|
|
65
|
+
if (typeof record.video_url === 'string') {
|
|
66
|
+
return record.video_url ?? '';
|
|
67
|
+
}
|
|
68
|
+
if (record.image_url &&
|
|
69
|
+
typeof record.image_url?.url === 'string') {
|
|
70
|
+
return record.image_url?.url ?? '';
|
|
71
|
+
}
|
|
72
|
+
if (record.video_url &&
|
|
73
|
+
typeof record.video_url?.url === 'string') {
|
|
74
|
+
return record.video_url?.url ?? '';
|
|
75
|
+
}
|
|
76
|
+
if (typeof record.url === 'string') {
|
|
77
|
+
return record.url ?? '';
|
|
78
|
+
}
|
|
79
|
+
if (typeof record.uri === 'string') {
|
|
80
|
+
return record.uri ?? '';
|
|
81
|
+
}
|
|
82
|
+
if (typeof record.data === 'string') {
|
|
83
|
+
return record.data ?? '';
|
|
84
|
+
}
|
|
85
|
+
return '';
|
|
86
|
+
}
|
|
87
|
+
function resolveMediaKind(typeValue, record) {
|
|
88
|
+
if (typeValue.includes('video')) {
|
|
89
|
+
return 'video';
|
|
90
|
+
}
|
|
91
|
+
if (typeValue.includes('image')) {
|
|
92
|
+
return 'image';
|
|
93
|
+
}
|
|
94
|
+
if (Object.prototype.hasOwnProperty.call(record, 'video_url')) {
|
|
95
|
+
return 'video';
|
|
96
|
+
}
|
|
97
|
+
if (Object.prototype.hasOwnProperty.call(record, 'image_url')) {
|
|
98
|
+
return 'image';
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
function isPrivateHost(host) {
|
|
103
|
+
const normalized = host.trim().toLowerCase();
|
|
104
|
+
if (!normalized) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
if (normalized === 'localhost' || normalized.endsWith('.local')) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
const ipType = isIP(normalized);
|
|
111
|
+
if (ipType === 4) {
|
|
112
|
+
const octets = normalized.split('.').map((part) => Number.parseInt(part, 10));
|
|
113
|
+
if (octets.length !== 4 || octets.some((value) => !Number.isFinite(value))) {
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
if (octets[0] === 10)
|
|
117
|
+
return true;
|
|
118
|
+
if (octets[0] === 127)
|
|
119
|
+
return true;
|
|
120
|
+
if (octets[0] === 0)
|
|
121
|
+
return true;
|
|
122
|
+
if (octets[0] === 169 && octets[1] === 254)
|
|
123
|
+
return true;
|
|
124
|
+
if (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31)
|
|
125
|
+
return true;
|
|
126
|
+
if (octets[0] === 192 && octets[1] === 168)
|
|
127
|
+
return true;
|
|
61
128
|
return false;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
129
|
+
}
|
|
130
|
+
if (ipType === 6) {
|
|
131
|
+
if (normalized === '::1')
|
|
132
|
+
return true;
|
|
133
|
+
if (normalized.startsWith('fc') || normalized.startsWith('fd'))
|
|
134
|
+
return true;
|
|
135
|
+
if (normalized.startsWith('fe80:'))
|
|
136
|
+
return true;
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
function isRemotePublicHttpUrl(raw) {
|
|
142
|
+
const value = raw.trim();
|
|
143
|
+
if (!value) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
const lowered = value.toLowerCase();
|
|
147
|
+
if (LOCAL_URL_SCHEMES.some((prefix) => lowered.startsWith(prefix))) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
let parsed;
|
|
151
|
+
try {
|
|
152
|
+
parsed = new URL(value);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
return !isPrivateHost(parsed.hostname);
|
|
161
|
+
}
|
|
162
|
+
export function analyzeMediaAttachments(message) {
|
|
163
|
+
const result = {
|
|
164
|
+
hasAnyMedia: false,
|
|
165
|
+
hasImage: false,
|
|
166
|
+
hasVideo: false,
|
|
167
|
+
hasRemoteVideo: false,
|
|
168
|
+
hasLocalVideo: false
|
|
169
|
+
};
|
|
170
|
+
if (!message) {
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
if (typeof message.content === 'string' && message.content.trim()) {
|
|
174
|
+
const raw = message.content;
|
|
175
|
+
const hasImageBlock = /"type"\s*:\s*"(?:input_)?image(?:_url)?"/iu.test(raw);
|
|
176
|
+
const hasVideoBlock = /"type"\s*:\s*"(?:input_)?video(?:_url)?"/iu.test(raw);
|
|
177
|
+
const hasDataVideo = /data:video\//iu.test(raw);
|
|
178
|
+
const hasRemoteVideo = /https?:\/\/[^\s"'\\]+/iu.test(raw);
|
|
179
|
+
if (hasImageBlock || hasVideoBlock) {
|
|
180
|
+
result.hasAnyMedia = true;
|
|
181
|
+
}
|
|
182
|
+
if (hasImageBlock) {
|
|
183
|
+
result.hasImage = true;
|
|
184
|
+
}
|
|
185
|
+
if (hasVideoBlock) {
|
|
186
|
+
result.hasVideo = true;
|
|
187
|
+
if (hasDataVideo) {
|
|
188
|
+
result.hasLocalVideo = true;
|
|
100
189
|
}
|
|
101
|
-
|
|
102
|
-
|
|
190
|
+
if (hasRemoteVideo) {
|
|
191
|
+
result.hasRemoteVideo = true;
|
|
103
192
|
}
|
|
104
|
-
if (
|
|
105
|
-
|
|
193
|
+
if (!hasDataVideo && !hasRemoteVideo) {
|
|
194
|
+
result.hasLocalVideo = true;
|
|
106
195
|
}
|
|
107
196
|
}
|
|
197
|
+
if (result.hasAnyMedia) {
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
108
200
|
}
|
|
109
|
-
|
|
201
|
+
if (!Array.isArray(message.content)) {
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
for (const part of message.content) {
|
|
205
|
+
if (!part || typeof part !== 'object') {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const record = part;
|
|
209
|
+
const typeValue = typeof record.type === 'string' ? record.type.toLowerCase() : '';
|
|
210
|
+
const mediaKind = resolveMediaKind(typeValue, record);
|
|
211
|
+
if (!mediaKind) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const mediaUrl = extractMediaUrlCandidate(record).trim();
|
|
215
|
+
if (!mediaUrl) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
result.hasAnyMedia = true;
|
|
219
|
+
if (mediaKind === 'image') {
|
|
220
|
+
result.hasImage = true;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
result.hasVideo = true;
|
|
224
|
+
if (isRemotePublicHttpUrl(mediaUrl)) {
|
|
225
|
+
result.hasRemoteVideo = true;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
result.hasLocalVideo = true;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
export function detectImageAttachment(message) {
|
|
234
|
+
return analyzeMediaAttachments(message).hasAnyMedia;
|
|
110
235
|
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import * as fs from 'node:fs';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
+
const DEFAULT_PRECOMMAND_SCRIPT = 'default.sh';
|
|
5
|
+
const DEFAULT_PRECOMMAND_SCRIPT_CONTENT = [
|
|
6
|
+
'#!/usr/bin/env bash',
|
|
7
|
+
'# RouteCodex default precommand hook (no-op).',
|
|
8
|
+
'# You can edit this file to customize precommand behavior.',
|
|
9
|
+
'exit 0',
|
|
10
|
+
''
|
|
11
|
+
].join('\n');
|
|
4
12
|
function resolveRoutecodexUserDir() {
|
|
5
13
|
const override = process.env.ROUTECODEX_USER_DIR;
|
|
6
14
|
if (override && override.trim()) {
|
|
@@ -62,14 +70,44 @@ export function resolvePreCommandScriptPath(raw) {
|
|
|
62
70
|
stat = fs.statSync(abs);
|
|
63
71
|
}
|
|
64
72
|
catch (err) {
|
|
65
|
-
|
|
66
|
-
|
|
73
|
+
if (shouldAutoCreateDefaultScript(relToPreCommand, err)) {
|
|
74
|
+
tryCreateDefaultPreCommandScript(base, abs);
|
|
75
|
+
try {
|
|
76
|
+
stat = fs.statSync(abs);
|
|
77
|
+
}
|
|
78
|
+
catch (retryErr) {
|
|
79
|
+
const retryMessage = retryErr && typeof retryErr.message === 'string' ? retryErr.message : String(retryErr || 'unknown error');
|
|
80
|
+
throw new Error(`precommand: cannot stat ${abs}: ${retryMessage}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const message = err && typeof err.message === 'string' ? err.message : String(err || 'unknown error');
|
|
85
|
+
throw new Error(`precommand: cannot stat ${abs}: ${message}`);
|
|
86
|
+
}
|
|
67
87
|
}
|
|
68
88
|
if (!stat.isFile()) {
|
|
69
89
|
throw new Error(`precommand: not a file: ${abs}`);
|
|
70
90
|
}
|
|
71
91
|
return abs;
|
|
72
92
|
}
|
|
93
|
+
function shouldAutoCreateDefaultScript(relToPreCommand, err) {
|
|
94
|
+
const code = err?.code;
|
|
95
|
+
if (code !== 'ENOENT') {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return relToPreCommand === DEFAULT_PRECOMMAND_SCRIPT;
|
|
99
|
+
}
|
|
100
|
+
function tryCreateDefaultPreCommandScript(baseDir, scriptPath) {
|
|
101
|
+
try {
|
|
102
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
103
|
+
if (!fs.existsSync(scriptPath)) {
|
|
104
|
+
fs.writeFileSync(scriptPath, DEFAULT_PRECOMMAND_SCRIPT_CONTENT, { encoding: 'utf8', mode: 0o755 });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Keep original resolver contract: caller gets a stat error on failure.
|
|
109
|
+
}
|
|
110
|
+
}
|
|
73
111
|
export function isPreCommandScriptPathAllowed(rawPath) {
|
|
74
112
|
const scriptPath = typeof rawPath === 'string' ? rawPath.trim() : '';
|
|
75
113
|
if (!scriptPath) {
|
|
@@ -10,6 +10,11 @@ export interface RoutingInstruction {
|
|
|
10
10
|
stopMessageText?: string;
|
|
11
11
|
stopMessageMaxRepeats?: number;
|
|
12
12
|
stopMessageStageMode?: 'on' | 'off' | 'auto';
|
|
13
|
+
/**
|
|
14
|
+
* True when the instruction is parsed from an older user message
|
|
15
|
+
* (not the latest user turn in current request).
|
|
16
|
+
*/
|
|
17
|
+
fromHistoricalUserMessage?: boolean;
|
|
13
18
|
preCommandScriptPath?: string;
|
|
14
19
|
}
|
|
15
20
|
export interface RoutingInstructionState {
|
|
@@ -57,6 +62,9 @@ export interface RoutingInstructionState {
|
|
|
57
62
|
stopMessageObservationHash?: string;
|
|
58
63
|
stopMessageObservationStableCount?: number;
|
|
59
64
|
stopMessageBdWorkState?: string;
|
|
65
|
+
stopMessageAssignedIssueId?: string;
|
|
66
|
+
stopMessageAssignedIssueSource?: 'in_progress' | 'ready' | 'open';
|
|
67
|
+
stopMessageNoTaskSummaryUsed?: boolean;
|
|
60
68
|
preCommandSource?: string;
|
|
61
69
|
preCommandScriptPath?: string;
|
|
62
70
|
preCommandUpdatedAt?: number;
|
|
@@ -50,14 +50,23 @@ export function parseRoutingInstructions(messages) {
|
|
|
50
50
|
for (const segment of segments) {
|
|
51
51
|
const parsed = parseSingleInstruction(segment);
|
|
52
52
|
if (parsed) {
|
|
53
|
+
const fromHistoricalUserMessage = lastUserIndex >= 0 &&
|
|
54
|
+
sanitizedIndex >= 0 &&
|
|
55
|
+
sanitizedIndex !== lastUserIndex;
|
|
56
|
+
const normalizedParsed = fromHistoricalUserMessage &&
|
|
57
|
+
(parsed.type === 'stopMessageSet' ||
|
|
58
|
+
parsed.type === 'stopMessageMode' ||
|
|
59
|
+
parsed.type === 'stopMessageClear')
|
|
60
|
+
? { ...parsed, fromHistoricalUserMessage: true }
|
|
61
|
+
: parsed;
|
|
53
62
|
// preCommand 是强副作用命令(执行本地脚本),仅允许从 latest user message 生效。
|
|
54
|
-
if ((
|
|
63
|
+
if ((normalizedParsed.type === 'preCommandSet' || normalizedParsed.type === 'preCommandClear') &&
|
|
55
64
|
lastUserIndex >= 0 &&
|
|
56
65
|
sanitizedIndex >= 0 &&
|
|
57
66
|
sanitizedIndex !== lastUserIndex) {
|
|
58
67
|
continue;
|
|
59
68
|
}
|
|
60
|
-
instructions.push(
|
|
69
|
+
instructions.push(normalizedParsed);
|
|
61
70
|
}
|
|
62
71
|
}
|
|
63
72
|
}
|
|
@@ -371,6 +380,9 @@ export function applyRoutingInstructions(instructions, currentState) {
|
|
|
371
380
|
stopMessageObservationHash: currentState.stopMessageObservationHash,
|
|
372
381
|
stopMessageObservationStableCount: currentState.stopMessageObservationStableCount,
|
|
373
382
|
stopMessageBdWorkState: currentState.stopMessageBdWorkState,
|
|
383
|
+
stopMessageAssignedIssueId: currentState.stopMessageAssignedIssueId,
|
|
384
|
+
stopMessageAssignedIssueSource: currentState.stopMessageAssignedIssueSource,
|
|
385
|
+
stopMessageNoTaskSummaryUsed: currentState.stopMessageNoTaskSummaryUsed,
|
|
374
386
|
preCommandSource: currentState.preCommandSource,
|
|
375
387
|
preCommandScriptPath: currentState.preCommandScriptPath,
|
|
376
388
|
preCommandUpdatedAt: currentState.preCommandUpdatedAt
|
|
@@ -500,6 +512,7 @@ export function applyRoutingInstructions(instructions, currentState) {
|
|
|
500
512
|
newState.disabledProviders.clear();
|
|
501
513
|
newState.disabledKeys.clear();
|
|
502
514
|
newState.disabledModels.clear();
|
|
515
|
+
applyStopMessageInstructionToState({ type: 'stopMessageClear' }, newState);
|
|
503
516
|
clearPreCommandState(newState);
|
|
504
517
|
break;
|
|
505
518
|
case 'stopMessageSet':
|
|
@@ -573,6 +586,9 @@ export function deserializeRoutingInstructionState(data) {
|
|
|
573
586
|
stopMessageStageMode: undefined,
|
|
574
587
|
stopMessageObservationHash: undefined,
|
|
575
588
|
stopMessageBdWorkState: undefined,
|
|
589
|
+
stopMessageAssignedIssueId: undefined,
|
|
590
|
+
stopMessageAssignedIssueSource: undefined,
|
|
591
|
+
stopMessageNoTaskSummaryUsed: undefined,
|
|
576
592
|
preCommandSource: undefined,
|
|
577
593
|
preCommandScriptPath: undefined,
|
|
578
594
|
preCommandUpdatedAt: undefined
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { normalizeStopMessageStageMode } from './routing-stop-message-state-codec.js';
|
|
1
|
+
import { DEFAULT_STOP_MESSAGE_MAX_REPEATS, ensureStopMessageModeMaxRepeats, normalizeStopMessageStageMode } from './routing-stop-message-state-codec.js';
|
|
2
2
|
export function applyStopMessageInstructionToState(instruction, state) {
|
|
3
3
|
switch (instruction.type) {
|
|
4
4
|
case 'stopMessageSet': {
|
|
@@ -24,13 +24,16 @@ export function applyStopMessageInstructionToState(instruction, state) {
|
|
|
24
24
|
: 0;
|
|
25
25
|
const hasLastUsedAt = typeof state.stopMessageLastUsedAt === 'number' && Number.isFinite(state.stopMessageLastUsedAt);
|
|
26
26
|
const shouldRearm = !isSameInstruction || used > 0 || hasLastUsedAt;
|
|
27
|
+
const shouldRearmForSource = instruction.fromHistoricalUserMessage
|
|
28
|
+
? !isSameInstruction
|
|
29
|
+
: shouldRearm;
|
|
27
30
|
state.stopMessageText = text;
|
|
28
31
|
state.stopMessageMaxRepeats = maxRepeats;
|
|
29
32
|
state.stopMessageSource = 'explicit';
|
|
30
33
|
if (targetMode) {
|
|
31
34
|
state.stopMessageStageMode = targetMode;
|
|
32
35
|
}
|
|
33
|
-
if (
|
|
36
|
+
if (shouldRearmForSource) {
|
|
34
37
|
resetStopMessageRuntimeState(state, Date.now());
|
|
35
38
|
}
|
|
36
39
|
return true;
|
|
@@ -40,9 +43,9 @@ export function applyStopMessageInstructionToState(instruction, state) {
|
|
|
40
43
|
if (!mode) {
|
|
41
44
|
return true;
|
|
42
45
|
}
|
|
43
|
-
const
|
|
46
|
+
const explicitMaxRepeats = typeof instruction.stopMessageMaxRepeats === 'number' && Number.isFinite(instruction.stopMessageMaxRepeats)
|
|
44
47
|
? Math.floor(instruction.stopMessageMaxRepeats)
|
|
45
|
-
:
|
|
48
|
+
: undefined;
|
|
46
49
|
if (mode === 'off') {
|
|
47
50
|
clearStopMessageState(state, { keepMode: 'off' });
|
|
48
51
|
return true;
|
|
@@ -50,17 +53,32 @@ export function applyStopMessageInstructionToState(instruction, state) {
|
|
|
50
53
|
const previousMode = normalizeStopMessageStageMode(state.stopMessageStageMode);
|
|
51
54
|
const previousMax = typeof state.stopMessageMaxRepeats === 'number' && Number.isFinite(state.stopMessageMaxRepeats)
|
|
52
55
|
? Math.floor(state.stopMessageMaxRepeats)
|
|
53
|
-
:
|
|
56
|
+
: undefined;
|
|
57
|
+
const preservedMax = typeof state.stopMessageMaxRepeats === 'number' && Number.isFinite(state.stopMessageMaxRepeats)
|
|
58
|
+
? Math.floor(state.stopMessageMaxRepeats)
|
|
59
|
+
: undefined;
|
|
60
|
+
const resolvedMax = typeof explicitMaxRepeats === 'number' && explicitMaxRepeats > 0
|
|
61
|
+
? explicitMaxRepeats
|
|
62
|
+
: typeof preservedMax === 'number' && preservedMax > 0
|
|
63
|
+
? preservedMax
|
|
64
|
+
: mode === 'on' || mode === 'auto'
|
|
65
|
+
? DEFAULT_STOP_MESSAGE_MAX_REPEATS
|
|
66
|
+
: undefined;
|
|
54
67
|
state.stopMessageStageMode = mode;
|
|
55
68
|
state.stopMessageSource = 'explicit';
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
state.stopMessageMaxRepeats = resolvedMax;
|
|
70
|
+
ensureStopMessageModeMaxRepeats(state);
|
|
71
|
+
const nextMax = typeof state.stopMessageMaxRepeats === 'number' && Number.isFinite(state.stopMessageMaxRepeats)
|
|
72
|
+
? Math.floor(state.stopMessageMaxRepeats)
|
|
73
|
+
: undefined;
|
|
59
74
|
const shouldRearm = previousMode !== mode ||
|
|
60
|
-
|
|
75
|
+
previousMax !== nextMax ||
|
|
61
76
|
typeof state.stopMessageUsed !== 'number' ||
|
|
62
77
|
!Number.isFinite(state.stopMessageUsed);
|
|
63
|
-
|
|
78
|
+
const shouldRearmForSource = instruction.fromHistoricalUserMessage
|
|
79
|
+
? (previousMode !== mode || previousMax !== nextMax || typeof state.stopMessageUsed !== 'number' || !Number.isFinite(state.stopMessageUsed))
|
|
80
|
+
: shouldRearm;
|
|
81
|
+
if (shouldRearmForSource) {
|
|
64
82
|
resetStopMessageRuntimeState(state, Date.now());
|
|
65
83
|
}
|
|
66
84
|
return true;
|
|
@@ -80,6 +98,9 @@ function resetStopMessageRuntimeState(state, atMs) {
|
|
|
80
98
|
state.stopMessageObservationHash = undefined;
|
|
81
99
|
state.stopMessageObservationStableCount = 0;
|
|
82
100
|
state.stopMessageBdWorkState = undefined;
|
|
101
|
+
state.stopMessageAssignedIssueId = undefined;
|
|
102
|
+
state.stopMessageAssignedIssueSource = undefined;
|
|
103
|
+
state.stopMessageNoTaskSummaryUsed = undefined;
|
|
83
104
|
}
|
|
84
105
|
function clearStopMessageState(state, options) {
|
|
85
106
|
state.stopMessageText = undefined;
|
|
@@ -93,4 +114,7 @@ function clearStopMessageState(state, options) {
|
|
|
93
114
|
state.stopMessageObservationHash = undefined;
|
|
94
115
|
state.stopMessageObservationStableCount = undefined;
|
|
95
116
|
state.stopMessageBdWorkState = undefined;
|
|
117
|
+
state.stopMessageAssignedIssueId = undefined;
|
|
118
|
+
state.stopMessageAssignedIssueSource = undefined;
|
|
119
|
+
state.stopMessageNoTaskSummaryUsed = undefined;
|
|
96
120
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { RoutingInstructionState } from './routing-instructions.js';
|
|
2
|
+
export declare const DEFAULT_STOP_MESSAGE_MAX_REPEATS = 10;
|
|
2
3
|
export declare function serializeStopMessageState(state: RoutingInstructionState): Record<string, unknown>;
|
|
3
4
|
export declare function deserializeStopMessageState(data: Record<string, unknown>, state: RoutingInstructionState): void;
|
|
4
5
|
export declare function normalizeStopMessageStageMode(value: unknown): 'on' | 'off' | 'auto' | undefined;
|
|
6
|
+
export declare function ensureStopMessageModeMaxRepeats(state: RoutingInstructionState): boolean;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export const DEFAULT_STOP_MESSAGE_MAX_REPEATS = 10;
|
|
1
2
|
export function serializeStopMessageState(state) {
|
|
2
3
|
return {
|
|
3
4
|
...(typeof state.stopMessageSource === 'string' && state.stopMessageSource.trim()
|
|
@@ -32,6 +33,18 @@ export function serializeStopMessageState(state) {
|
|
|
32
33
|
: {}),
|
|
33
34
|
...(typeof state.stopMessageBdWorkState === 'string' && state.stopMessageBdWorkState.trim()
|
|
34
35
|
? { stopMessageBdWorkState: state.stopMessageBdWorkState.trim() }
|
|
36
|
+
: {}),
|
|
37
|
+
...(typeof state.stopMessageAssignedIssueId === 'string' && state.stopMessageAssignedIssueId.trim()
|
|
38
|
+
? { stopMessageAssignedIssueId: state.stopMessageAssignedIssueId.trim() }
|
|
39
|
+
: {}),
|
|
40
|
+
...(typeof state.stopMessageAssignedIssueSource === 'string' &&
|
|
41
|
+
(state.stopMessageAssignedIssueSource === 'in_progress' ||
|
|
42
|
+
state.stopMessageAssignedIssueSource === 'ready' ||
|
|
43
|
+
state.stopMessageAssignedIssueSource === 'open')
|
|
44
|
+
? { stopMessageAssignedIssueSource: state.stopMessageAssignedIssueSource }
|
|
45
|
+
: {}),
|
|
46
|
+
...(typeof state.stopMessageNoTaskSummaryUsed === 'boolean'
|
|
47
|
+
? { stopMessageNoTaskSummaryUsed: state.stopMessageNoTaskSummaryUsed }
|
|
35
48
|
: {})
|
|
36
49
|
};
|
|
37
50
|
}
|
|
@@ -42,7 +55,8 @@ export function deserializeStopMessageState(data, state) {
|
|
|
42
55
|
if (typeof data.stopMessageText === 'string' && data.stopMessageText.trim()) {
|
|
43
56
|
state.stopMessageText = data.stopMessageText;
|
|
44
57
|
}
|
|
45
|
-
|
|
58
|
+
const hasPersistedMaxRepeats = typeof data.stopMessageMaxRepeats === 'number' && Number.isFinite(data.stopMessageMaxRepeats);
|
|
59
|
+
if (hasPersistedMaxRepeats) {
|
|
46
60
|
state.stopMessageMaxRepeats = Math.floor(data.stopMessageMaxRepeats);
|
|
47
61
|
}
|
|
48
62
|
if (typeof data.stopMessageUsed === 'number' && Number.isFinite(data.stopMessageUsed)) {
|
|
@@ -63,6 +77,10 @@ export function deserializeStopMessageState(data, state) {
|
|
|
63
77
|
state.stopMessageStageMode = normalized;
|
|
64
78
|
}
|
|
65
79
|
}
|
|
80
|
+
// Keep stopMessage mode state armed consistently across old/new snapshots.
|
|
81
|
+
if (!hasPersistedMaxRepeats) {
|
|
82
|
+
ensureStopMessageModeMaxRepeats(state);
|
|
83
|
+
}
|
|
66
84
|
if (typeof data.stopMessageObservationHash === 'string' && data.stopMessageObservationHash.trim()) {
|
|
67
85
|
state.stopMessageObservationHash = data.stopMessageObservationHash.trim();
|
|
68
86
|
}
|
|
@@ -72,6 +90,18 @@ export function deserializeStopMessageState(data, state) {
|
|
|
72
90
|
if (typeof data.stopMessageBdWorkState === 'string' && data.stopMessageBdWorkState.trim()) {
|
|
73
91
|
state.stopMessageBdWorkState = data.stopMessageBdWorkState.trim();
|
|
74
92
|
}
|
|
93
|
+
if (typeof data.stopMessageAssignedIssueId === 'string' && data.stopMessageAssignedIssueId.trim()) {
|
|
94
|
+
state.stopMessageAssignedIssueId = data.stopMessageAssignedIssueId.trim();
|
|
95
|
+
}
|
|
96
|
+
if (typeof data.stopMessageAssignedIssueSource === 'string') {
|
|
97
|
+
const source = data.stopMessageAssignedIssueSource.trim().toLowerCase();
|
|
98
|
+
if (source === 'in_progress' || source === 'ready' || source === 'open') {
|
|
99
|
+
state.stopMessageAssignedIssueSource = source;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (typeof data.stopMessageNoTaskSummaryUsed === 'boolean') {
|
|
103
|
+
state.stopMessageNoTaskSummaryUsed = data.stopMessageNoTaskSummaryUsed;
|
|
104
|
+
}
|
|
75
105
|
}
|
|
76
106
|
export function normalizeStopMessageStageMode(value) {
|
|
77
107
|
if (typeof value !== 'string') {
|
|
@@ -83,3 +113,22 @@ export function normalizeStopMessageStageMode(value) {
|
|
|
83
113
|
}
|
|
84
114
|
return undefined;
|
|
85
115
|
}
|
|
116
|
+
export function ensureStopMessageModeMaxRepeats(state) {
|
|
117
|
+
const mode = normalizeStopMessageStageMode(state.stopMessageStageMode);
|
|
118
|
+
if (mode !== 'on' && mode !== 'auto') {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
const hasValidRepeats = typeof state.stopMessageMaxRepeats === 'number' &&
|
|
122
|
+
Number.isFinite(state.stopMessageMaxRepeats) &&
|
|
123
|
+
Math.floor(state.stopMessageMaxRepeats) > 0;
|
|
124
|
+
if (hasValidRepeats) {
|
|
125
|
+
const normalized = Math.floor(state.stopMessageMaxRepeats);
|
|
126
|
+
if (state.stopMessageMaxRepeats !== normalized) {
|
|
127
|
+
state.stopMessageMaxRepeats = normalized;
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
state.stopMessageMaxRepeats = DEFAULT_STOP_MESSAGE_MAX_REPEATS;
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RoutingInstructionState } from './routing-instructions.js';
|
|
2
|
-
type StopMessageSubset = Pick<RoutingInstructionState, 'stopMessageSource' | 'stopMessageText' | 'stopMessageMaxRepeats' | 'stopMessageUsed' | 'stopMessageStage' | 'stopMessageStageMode' | 'stopMessageObservationHash' | 'stopMessageObservationStableCount' | 'stopMessageBdWorkState' | 'stopMessageUpdatedAt' | 'stopMessageLastUsedAt'>;
|
|
2
|
+
type StopMessageSubset = Pick<RoutingInstructionState, 'stopMessageSource' | 'stopMessageText' | 'stopMessageMaxRepeats' | 'stopMessageUsed' | 'stopMessageStage' | 'stopMessageStageMode' | 'stopMessageObservationHash' | 'stopMessageObservationStableCount' | 'stopMessageBdWorkState' | 'stopMessageAssignedIssueId' | 'stopMessageAssignedIssueSource' | 'stopMessageNoTaskSummaryUsed' | 'stopMessageUpdatedAt' | 'stopMessageLastUsedAt'>;
|
|
3
3
|
/**
|
|
4
4
|
* Decide whether we should overwrite in-memory stopMessage fields with persisted ones.
|
|
5
5
|
*
|
|
@@ -46,6 +46,9 @@ export function mergeStopMessageFromPersisted(existing, persisted) {
|
|
|
46
46
|
stopMessageObservationHash: persisted.stopMessageObservationHash,
|
|
47
47
|
stopMessageObservationStableCount: persisted.stopMessageObservationStableCount,
|
|
48
48
|
stopMessageBdWorkState: persisted.stopMessageBdWorkState,
|
|
49
|
+
stopMessageAssignedIssueId: persisted.stopMessageAssignedIssueId,
|
|
50
|
+
stopMessageAssignedIssueSource: persisted.stopMessageAssignedIssueSource,
|
|
51
|
+
stopMessageNoTaskSummaryUsed: persisted.stopMessageNoTaskSummaryUsed,
|
|
49
52
|
stopMessageUpdatedAt: persisted.stopMessageUpdatedAt,
|
|
50
53
|
stopMessageLastUsedAt: persisted.stopMessageLastUsedAt
|
|
51
54
|
};
|