@jsonstudio/llms 0.6.2125 → 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.
Files changed (37) hide show
  1. package/dist/conversion/compat/actions/deepseek-web-response.js +27 -3
  2. package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +1 -1
  3. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +9 -3
  4. package/dist/conversion/hub/process/chat-process.js +15 -18
  5. package/dist/conversion/responses/responses-openai-bridge.js +13 -12
  6. package/dist/conversion/shared/bridge-message-utils.js +92 -39
  7. package/dist/router/virtual-router/classifier.js +29 -5
  8. package/dist/router/virtual-router/engine/routing-pools/index.js +111 -5
  9. package/dist/router/virtual-router/engine-selection/multimodal-capability.d.ts +3 -0
  10. package/dist/router/virtual-router/engine-selection/multimodal-capability.js +26 -0
  11. package/dist/router/virtual-router/engine-selection/route-utils.js +6 -2
  12. package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +1 -0
  13. package/dist/router/virtual-router/engine-selection/tier-selection.js +2 -0
  14. package/dist/router/virtual-router/engine.d.ts +2 -0
  15. package/dist/router/virtual-router/engine.js +57 -14
  16. package/dist/router/virtual-router/features.js +12 -4
  17. package/dist/router/virtual-router/message-utils.d.ts +8 -0
  18. package/dist/router/virtual-router/message-utils.js +170 -45
  19. package/dist/router/virtual-router/token-counter.js +51 -10
  20. package/dist/router/virtual-router/types.d.ts +3 -0
  21. package/dist/servertool/clock/session-scope.d.ts +3 -0
  22. package/dist/servertool/clock/session-scope.js +52 -0
  23. package/dist/servertool/engine.js +68 -8
  24. package/dist/servertool/handlers/clock-auto.js +2 -8
  25. package/dist/servertool/handlers/clock.js +3 -9
  26. package/dist/servertool/handlers/stop-message-auto/blocked-report.d.ts +16 -0
  27. package/dist/servertool/handlers/stop-message-auto/blocked-report.js +349 -0
  28. package/dist/servertool/handlers/stop-message-auto/iflow-followup.d.ts +23 -0
  29. package/dist/servertool/handlers/stop-message-auto/iflow-followup.js +503 -0
  30. package/dist/servertool/handlers/stop-message-auto/routing-state.d.ts +38 -0
  31. package/dist/servertool/handlers/stop-message-auto/routing-state.js +149 -0
  32. package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +67 -0
  33. package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +387 -0
  34. package/dist/servertool/handlers/stop-message-auto.d.ts +1 -7
  35. package/dist/servertool/handlers/stop-message-auto.js +69 -971
  36. package/dist/servertool/handlers/web-search.js +117 -0
  37. package/package.json +1 -1
@@ -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
- export function detectImageAttachment(message) {
60
- if (!message)
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
- // 仅基于标准 Chat 语义判断是否携带视觉媒体(图片/视频):
63
- // - content 为数组时查找 { type: 'image' | 'image_url' | 'input_image' | 'video' | 'video_url' | 'input_video', ... } 块;
64
- // - 不再依赖 metadata.attachments,也不再用纯文本关键字或剪贴板标记作为信号。
65
- if (Array.isArray(message.content)) {
66
- for (const part of message.content) {
67
- if (!part || typeof part !== 'object') {
68
- continue;
69
- }
70
- const record = part;
71
- const typeValue = typeof record.type === 'string' ? record.type.toLowerCase() : '';
72
- // For chat/standardized content, media may appear as:
73
- // - { type: "image_url", image_url: { url } }
74
- // - { type: "image", uri: "...", data: "...", url: "..." }
75
- // - { type: "input_image", image_url: "data:..." }
76
- // - { type: "video_url", video_url: { url } }
77
- // - { type: "video", uri: "...", data: "...", url: "..." }
78
- // - { type: "input_video", video_url: "data:..." }
79
- // Treat any non-empty URL/URI/data on a media-* block as a signal.
80
- let imageCandidate = '';
81
- if (typeof record.image_url === 'string') {
82
- imageCandidate = record.image_url ?? '';
83
- }
84
- else if (typeof record.video_url === 'string') {
85
- imageCandidate = record.video_url ?? '';
86
- }
87
- else if (record.image_url &&
88
- typeof record.image_url?.url === 'string') {
89
- imageCandidate = record.image_url?.url ?? '';
90
- }
91
- else if (record.video_url &&
92
- typeof record.video_url?.url === 'string') {
93
- imageCandidate = record.video_url?.url ?? '';
94
- }
95
- else if (typeof record.url === 'string') {
96
- imageCandidate = record.url ?? '';
97
- }
98
- else if (typeof record.uri === 'string') {
99
- imageCandidate = record.uri ?? '';
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
- else if (typeof record.data === 'string') {
102
- imageCandidate = record.data ?? '';
190
+ if (hasRemoteVideo) {
191
+ result.hasRemoteVideo = true;
103
192
  }
104
- if ((typeValue.includes('image') || typeValue.includes('video')) && imageCandidate.trim().length > 0) {
105
- return true;
193
+ if (!hasDataVideo && !hasRemoteVideo) {
194
+ result.hasLocalVideo = true;
106
195
  }
107
196
  }
197
+ if (result.hasAnyMedia) {
198
+ return result;
199
+ }
108
200
  }
109
- return false;
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
  }
@@ -64,11 +64,54 @@ function countMessageTokens(message, encoder) {
64
64
  }
65
65
  return total;
66
66
  }
67
+ function detectMediaKind(record) {
68
+ const typeValue = typeof record.type === 'string' ? record.type.trim().toLowerCase() : '';
69
+ if (typeValue.includes('video')) {
70
+ return 'video';
71
+ }
72
+ if (typeValue.includes('image')) {
73
+ return 'image';
74
+ }
75
+ if (Object.prototype.hasOwnProperty.call(record, 'video_url')) {
76
+ return 'video';
77
+ }
78
+ if (Object.prototype.hasOwnProperty.call(record, 'image_url')) {
79
+ return 'image';
80
+ }
81
+ const dataField = typeof record.data === 'string' ? record.data.trim().toLowerCase() : '';
82
+ if (dataField.startsWith('data:video/')) {
83
+ return 'video';
84
+ }
85
+ if (dataField.startsWith('data:image/')) {
86
+ return 'image';
87
+ }
88
+ return null;
89
+ }
90
+ function parseStructuredContentString(raw) {
91
+ const trimmed = raw.trim();
92
+ if (!trimmed) {
93
+ return undefined;
94
+ }
95
+ const likelyJson = (trimmed.startsWith('{') && trimmed.endsWith('}')) || (trimmed.startsWith('[') && trimmed.endsWith(']'));
96
+ if (!likelyJson) {
97
+ return undefined;
98
+ }
99
+ try {
100
+ return JSON.parse(trimmed);
101
+ }
102
+ catch {
103
+ return undefined;
104
+ }
105
+ }
67
106
  function encodeContent(content, encoder) {
68
107
  if (content === null || content === undefined) {
69
108
  return 0;
70
109
  }
71
110
  if (typeof content === 'string') {
111
+ const structured = parseStructuredContentString(content);
112
+ if (structured !== undefined) {
113
+ return encodeContent(structured, encoder);
114
+ }
72
115
  return encodeText(content, encoder);
73
116
  }
74
117
  if (Array.isArray(content)) {
@@ -79,17 +122,11 @@ function encodeContent(content, encoder) {
79
122
  }
80
123
  else if (part && typeof part === 'object') {
81
124
  const record = part;
82
- const typeValue = typeof record.type === 'string' ? record.type.toLowerCase() : '';
83
- // Large binary/image payloads (data URIs, base64, etc.) should not
84
- // dominate context estimation. For image-like blocks, only count a
85
- // small textual placeholder instead of the full JSON/body.
86
- if (typeValue.startsWith('image')) {
87
- const caption = typeof record.caption === 'string' && record.caption.trim().length
88
- ? record.caption
89
- : '[image]';
90
- total += encodeText(caption, encoder);
125
+ // Ignore image/video payload blocks in token estimation.
126
+ if (detectMediaKind(record)) {
127
+ continue;
91
128
  }
92
- else if (typeof record.text === 'string') {
129
+ if (typeof record.text === 'string') {
93
130
  total += encodeText(record.text, encoder);
94
131
  }
95
132
  else {
@@ -100,6 +137,10 @@ function encodeContent(content, encoder) {
100
137
  return total;
101
138
  }
102
139
  if (typeof content === 'object') {
140
+ const record = content;
141
+ if (detectMediaKind(record)) {
142
+ return 0;
143
+ }
103
144
  return encodeText(JSON.stringify(content), encoder);
104
145
  }
105
146
  return encodeText(String(content), encoder);
@@ -386,6 +386,9 @@ export interface RoutingFeatures {
386
386
  hasToolCallResponses: boolean;
387
387
  hasVisionTool: boolean;
388
388
  hasImageAttachment: boolean;
389
+ hasVideoAttachment?: boolean;
390
+ hasRemoteVideoAttachment?: boolean;
391
+ hasLocalVideoAttachment?: boolean;
389
392
  hasWebTool: boolean;
390
393
  hasWebSearchToolDeclared?: boolean;
391
394
  hasCodingTool: boolean;
@@ -0,0 +1,3 @@
1
+ export declare function buildClockSessionScopeFromDaemonId(daemonId: string): string;
2
+ export declare function extractClockDaemonIdFromSessionScope(sessionScope: string): string | null;
3
+ export declare function resolveClockSessionScope(primary?: Record<string, unknown> | null, fallback?: Record<string, unknown> | null): string | null;
@@ -0,0 +1,52 @@
1
+ const CLOCK_DAEMON_SESSION_PREFIX = 'clockd.';
2
+ function readToken(value) {
3
+ return typeof value === 'string' && value.trim().length ? value.trim() : '';
4
+ }
5
+ function readRecordToken(record, keys) {
6
+ if (!record) {
7
+ return '';
8
+ }
9
+ for (const key of keys) {
10
+ const value = readToken(record[key]);
11
+ if (value) {
12
+ return value;
13
+ }
14
+ }
15
+ return '';
16
+ }
17
+ export function buildClockSessionScopeFromDaemonId(daemonId) {
18
+ const normalized = readToken(daemonId);
19
+ if (!normalized) {
20
+ return '';
21
+ }
22
+ return `${CLOCK_DAEMON_SESSION_PREFIX}${normalized}`;
23
+ }
24
+ export function extractClockDaemonIdFromSessionScope(sessionScope) {
25
+ const normalized = readToken(sessionScope);
26
+ if (!normalized.startsWith(CLOCK_DAEMON_SESSION_PREFIX)) {
27
+ return null;
28
+ }
29
+ const daemonId = normalized.slice(CLOCK_DAEMON_SESSION_PREFIX.length).trim();
30
+ return daemonId || null;
31
+ }
32
+ export function resolveClockSessionScope(primary, fallback) {
33
+ const daemonId = readRecordToken(primary, ['clockDaemonId', 'clockClientDaemonId', 'clock_daemon_id', 'clock_client_daemon_id']) ||
34
+ readRecordToken(fallback, ['clockDaemonId', 'clockClientDaemonId', 'clock_daemon_id', 'clock_client_daemon_id']);
35
+ if (daemonId) {
36
+ const daemonScope = buildClockSessionScopeFromDaemonId(daemonId);
37
+ if (daemonScope) {
38
+ return daemonScope;
39
+ }
40
+ }
41
+ const sessionId = readRecordToken(primary, ['sessionId', 'session_id']) ||
42
+ readRecordToken(fallback, ['sessionId', 'session_id']);
43
+ if (sessionId) {
44
+ return sessionId;
45
+ }
46
+ const conversationId = readRecordToken(primary, ['conversationId', 'conversation_id']) ||
47
+ readRecordToken(fallback, ['conversationId', 'conversation_id']);
48
+ if (conversationId) {
49
+ return conversationId;
50
+ }
51
+ return null;
52
+ }
@@ -7,6 +7,7 @@ import { deserializeRoutingInstructionState, serializeRoutingInstructionState }
7
7
  import { applyHubFollowupPolicyShadow } from './followup-shadow.js';
8
8
  import { buildServerToolFollowupChatPayloadFromInjection, extractCapturedChatSeed } from './handlers/followup-request-builder.js';
9
9
  import { findNextUndeliveredDueAtMs, listClockTasks, resolveClockConfig } from './clock/task-store.js';
10
+ import { resolveClockSessionScope } from './clock/session-scope.js';
10
11
  import { savePendingServerToolInjection } from './pending-session.js';
11
12
  import { appendServerToolProgressFileEvent } from './log/progress-file.js';
12
13
  import { attachStopGatewayContext, inspectStopGatewaySignal } from './stop-gateway-context.js';
@@ -270,7 +271,7 @@ async function shouldDisableServerToolTimeoutForClockHold(args) {
270
271
  }
271
272
  const record = args.adapterContext;
272
273
  const rt = readRuntimeMetadata(record);
273
- const sessionId = typeof record.sessionId === 'string' ? record.sessionId.trim() : '';
274
+ const sessionId = resolveClockSessionScope(record, rt);
274
275
  if (!sessionId) {
275
276
  return false;
276
277
  }
@@ -985,19 +986,78 @@ function resolveStickyKeyFromAdapterContext(adapterContext) {
985
986
  if (!adapterContext || typeof adapterContext !== 'object') {
986
987
  return undefined;
987
988
  }
988
- const sessionId = typeof adapterContext.sessionId === 'string'
989
- ? adapterContext.sessionId.trim()
990
- : '';
991
- const conversationId = typeof adapterContext.conversationId === 'string'
992
- ? adapterContext.conversationId.trim()
993
- : '';
989
+ const record = adapterContext;
990
+ const runtime = readRuntimeMetadata(record);
991
+ const providerProtocol = readTextFromAny(record.providerProtocol) ||
992
+ readTextFromAny(runtime?.providerProtocol) ||
993
+ readTextFromAny(record.provider_protocol) ||
994
+ readTextFromAny(runtime?.provider_protocol);
995
+ if (providerProtocol.trim().toLowerCase() === 'openai-responses') {
996
+ const previousRequestId = resolveResponsesResumePreviousRequestId(record, runtime);
997
+ if (previousRequestId) {
998
+ return previousRequestId;
999
+ }
1000
+ const requestId = resolveStickyRequestId(record, runtime);
1001
+ if (requestId) {
1002
+ return requestId;
1003
+ }
1004
+ }
1005
+ const sessionId = readTextFromAny(record.sessionId) ||
1006
+ readTextFromAny(record.session_id) ||
1007
+ readTextFromAny(runtime?.sessionId) ||
1008
+ readTextFromAny(runtime?.session_id);
1009
+ const conversationId = readTextFromAny(record.conversationId) ||
1010
+ readTextFromAny(record.conversation_id) ||
1011
+ readTextFromAny(runtime?.conversationId) ||
1012
+ readTextFromAny(runtime?.conversation_id);
994
1013
  if (sessionId) {
995
1014
  return `session:${sessionId}`;
996
1015
  }
997
1016
  if (conversationId) {
998
1017
  return `conversation:${conversationId}`;
999
1018
  }
1000
- return undefined;
1019
+ const requestId = resolveStickyRequestId(record, runtime);
1020
+ return requestId || undefined;
1021
+ }
1022
+ function resolveStickyRequestId(context, runtime) {
1023
+ return (readTextFromAny(context.requestId) ||
1024
+ readTextFromAny(context.request_id) ||
1025
+ readTextFromAny(runtime?.requestId) ||
1026
+ readTextFromAny(runtime?.request_id));
1027
+ }
1028
+ function resolveResponsesResumePreviousRequestId(context, runtime) {
1029
+ const contextMetadata = asRecord(context.metadata);
1030
+ const contextMetadataContext = asRecord(contextMetadata?.context);
1031
+ const originalRequest = asRecord(context.originalRequest);
1032
+ const originalMetadata = asRecord(originalRequest?.metadata);
1033
+ const candidates = [
1034
+ context.responsesResume,
1035
+ contextMetadata?.responsesResume,
1036
+ contextMetadataContext?.responsesResume,
1037
+ originalRequest?.responsesResume,
1038
+ originalMetadata?.responsesResume,
1039
+ runtime?.responsesResume
1040
+ ];
1041
+ for (const candidate of candidates) {
1042
+ const resume = asRecord(candidate);
1043
+ if (!resume) {
1044
+ continue;
1045
+ }
1046
+ const previousRequestId = readTextFromAny(resume.previousRequestId) || readTextFromAny(resume.previous_request_id);
1047
+ if (previousRequestId) {
1048
+ return previousRequestId;
1049
+ }
1050
+ }
1051
+ return '';
1052
+ }
1053
+ function asRecord(value) {
1054
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
1055
+ return null;
1056
+ }
1057
+ return value;
1058
+ }
1059
+ function readTextFromAny(value) {
1060
+ return typeof value === 'string' && value.trim().length ? value.trim() : '';
1001
1061
  }
1002
1062
  function cloneRoutingInstructionState(state) {
1003
1063
  if (!state) {
@@ -4,6 +4,7 @@ import { readRuntimeMetadata } from '../../conversion/shared/runtime-metadata.js
4
4
  import { findNextUndeliveredDueAtMs, listClockTasks, reserveDueTasksForRequest, resolveClockConfig, startClockDaemonIfNeeded } from '../clock/task-store.js';
5
5
  import { nowMs } from '../clock/state.js';
6
6
  import { logClock } from '../clock/log.js';
7
+ import { resolveClockSessionScope } from '../clock/session-scope.js';
7
8
  import { isStopEligibleForServerTool } from '../stop-gateway-context.js';
8
9
  const FLOW_ID = 'clock_hold_flow';
9
10
  function resolveClientConnectionState(value) {
@@ -40,13 +41,6 @@ function clientWantsStreaming(adapterContext) {
40
41
  }
41
42
  return false;
42
43
  }
43
- function resolveSessionId(adapterContext) {
44
- if (!adapterContext || typeof adapterContext !== 'object' || Array.isArray(adapterContext)) {
45
- return null;
46
- }
47
- const sessionId = typeof adapterContext.sessionId === 'string' ? String(adapterContext.sessionId).trim() : '';
48
- return sessionId || null;
49
- }
50
44
  function computeHoldSleepMs(remainingMs) {
51
45
  if (remainingMs <= 0)
52
46
  return 0;
@@ -81,7 +75,7 @@ const handler = async (ctx) => {
81
75
  return null;
82
76
  }
83
77
  const rt = readRuntimeMetadata(ctx.adapterContext);
84
- const sessionId = resolveSessionId(ctx.adapterContext);
78
+ const sessionId = resolveClockSessionScope(ctx.adapterContext, rt);
85
79
  if (!sessionId) {
86
80
  return null;
87
81
  }
@@ -6,6 +6,7 @@ import { cancelClockTask, clearClockTasks, listClockTasks, resolveClockConfig, p
6
6
  import { getClockTimeSnapshot } from '../clock/ntp.js';
7
7
  import { nowMs } from '../clock/state.js';
8
8
  import { logClock } from '../clock/log.js';
9
+ import { resolveClockSessionScope } from '../clock/session-scope.js';
9
10
  const FLOW_ID = 'clock_flow';
10
11
  let fallbackClockToolCallSeq = 0;
11
12
  function ensureClockToolCall(toolCall, requestId) {
@@ -86,13 +87,6 @@ function parseToolArguments(toolCall) {
86
87
  return {};
87
88
  }
88
89
  }
89
- function resolveSessionId(adapterContext) {
90
- if (!adapterContext || typeof adapterContext !== 'object' || Array.isArray(adapterContext)) {
91
- return null;
92
- }
93
- const sessionId = typeof adapterContext.sessionId === 'string' ? String(adapterContext.sessionId).trim() : '';
94
- return sessionId || null;
95
- }
96
90
  function injectClockToolOutput(base, toolCall, content) {
97
91
  const cloned = cloneJson(base);
98
92
  const existingOutputs = Array.isArray(cloned.tool_outputs)
@@ -277,7 +271,7 @@ const handler = async (ctx) => {
277
271
  return null;
278
272
  }
279
273
  const rt = readRuntimeMetadata(ctx.adapterContext);
280
- const sessionId = resolveSessionId(ctx.adapterContext);
274
+ const sessionId = resolveClockSessionScope(ctx.adapterContext, rt);
281
275
  const rawConfig = rt?.clock ?? ctx.adapterContext.clock;
282
276
  // Default-enable clock when config is absent, but keep "explicitly disabled" honored.
283
277
  const clockConfig = resolveClockConfig(rawConfig);
@@ -391,7 +385,7 @@ const handler = async (ctx) => {
391
385
  return respond({
392
386
  ok: false,
393
387
  action,
394
- message: 'clock requires sessionId (x-session-id header or metadata.sessionId).'
388
+ message: 'clock requires session scope (sessionId/conversationId or clockDaemonId).'
395
389
  });
396
390
  }
397
391
  if (action === 'list') {
@@ -0,0 +1,16 @@
1
+ export interface StopMessageBlockedReport {
2
+ summary: string;
3
+ blocker: string;
4
+ impact?: string;
5
+ nextAction?: string;
6
+ evidence: string[];
7
+ }
8
+ export interface StopMessageBlockedIssueContext {
9
+ requestId?: string;
10
+ sessionId?: string;
11
+ }
12
+ export declare function extractBlockedReportFromMessages(messages: unknown[]): StopMessageBlockedReport | null;
13
+ export declare function extractBlockedReportFromMessagesForTests(messages: unknown[]): StopMessageBlockedReport | null;
14
+ export declare function createBdIssueFromBlockedReport(blockedReport: StopMessageBlockedReport, context?: StopMessageBlockedIssueContext, cwdOverride?: string): string | null;
15
+ export declare function extractCapturedMessageText(message: unknown): string;
16
+ export declare function extractTextFromMessageContent(content: unknown): string;