@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.
Files changed (159) hide show
  1. package/dist/conversion/compat/actions/deepseek-web-request.js +16 -2
  2. package/dist/conversion/compat/actions/deepseek-web-response.d.ts +7 -1
  3. package/dist/conversion/compat/actions/deepseek-web-response.js +302 -40
  4. package/dist/conversion/compat/actions/harvest-tool-calls-from-text.d.ts +5 -0
  5. package/dist/conversion/compat/actions/harvest-tool-calls-from-text.js +7 -4
  6. package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +1 -0
  7. package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +12 -0
  8. package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +1 -1
  9. package/dist/conversion/compat/actions/tool-text-request-guidance.d.ts +9 -0
  10. package/dist/conversion/compat/actions/tool-text-request-guidance.js +177 -0
  11. package/dist/conversion/compat/antigravity-session-signature.d.ts +6 -0
  12. package/dist/conversion/compat/antigravity-session-signature.js +15 -0
  13. package/dist/conversion/compat/profiles/chat-deepseek-web.json +52 -1
  14. package/dist/conversion/compat/profiles/chat-glm.json +22 -0
  15. package/dist/conversion/compat/profiles/chat-iflow.json +4 -0
  16. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +13 -27
  17. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +10 -1
  18. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +13 -4
  19. package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +1 -53
  20. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +8 -0
  21. package/dist/conversion/hub/pipeline/hub-pipeline.js +8 -4
  22. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +191 -9
  23. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +118 -15
  24. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +65 -2
  25. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.d.ts +34 -0
  26. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.js +75 -0
  27. package/dist/conversion/hub/process/chat-process.js +85 -18
  28. package/dist/conversion/hub/response/provider-response.js +21 -50
  29. package/dist/conversion/hub/response/response-runtime.js +71 -10
  30. package/dist/conversion/responses/responses-openai-bridge/response-payload.d.ts +3 -0
  31. package/dist/conversion/responses/responses-openai-bridge/response-payload.js +576 -0
  32. package/dist/conversion/responses/responses-openai-bridge/types.d.ts +42 -0
  33. package/dist/conversion/responses/responses-openai-bridge/types.js +1 -0
  34. package/dist/conversion/responses/responses-openai-bridge.d.ts +3 -44
  35. package/dist/conversion/responses/responses-openai-bridge.js +193 -504
  36. package/dist/conversion/shared/anthropic-message-utils.js +82 -2
  37. package/dist/conversion/shared/bridge-message-utils.js +92 -39
  38. package/dist/conversion/shared/snapshot-hooks.js +8 -13
  39. package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.d.ts +2 -0
  40. package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.js +129 -0
  41. package/dist/conversion/shared/text-markup-normalizer/extractors-json.d.ts +4 -0
  42. package/dist/conversion/shared/text-markup-normalizer/extractors-json.js +637 -0
  43. package/dist/conversion/shared/text-markup-normalizer/extractors-shared.d.ts +21 -0
  44. package/dist/conversion/shared/text-markup-normalizer/extractors-shared.js +177 -0
  45. package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.d.ts +5 -0
  46. package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.js +385 -0
  47. package/dist/conversion/shared/text-markup-normalizer/extractors-xml.d.ts +10 -0
  48. package/dist/conversion/shared/text-markup-normalizer/extractors-xml.js +602 -0
  49. package/dist/conversion/shared/text-markup-normalizer/extractors.d.ts +5 -0
  50. package/dist/conversion/shared/text-markup-normalizer/extractors.js +4 -0
  51. package/dist/conversion/shared/text-markup-normalizer/normalize.d.ts +2 -0
  52. package/dist/conversion/shared/text-markup-normalizer/normalize.js +76 -0
  53. package/dist/conversion/shared/text-markup-normalizer.d.ts +3 -25
  54. package/dist/conversion/shared/text-markup-normalizer.js +2 -1386
  55. package/dist/conversion/shared/tool-governor.js +136 -10
  56. package/dist/filters/utils/snapshot-writer.js +3 -3
  57. package/dist/router/virtual-router/bootstrap/auth-utils.d.ts +6 -0
  58. package/dist/router/virtual-router/bootstrap/auth-utils.js +288 -0
  59. package/dist/router/virtual-router/bootstrap/claude-code-helpers.d.ts +11 -0
  60. package/dist/router/virtual-router/bootstrap/claude-code-helpers.js +18 -0
  61. package/dist/router/virtual-router/bootstrap/config-defaults.d.ts +5 -0
  62. package/dist/router/virtual-router/bootstrap/config-defaults.js +13 -0
  63. package/dist/router/virtual-router/bootstrap/config-normalizers.d.ts +4 -0
  64. package/dist/router/virtual-router/bootstrap/config-normalizers.js +106 -0
  65. package/dist/router/virtual-router/bootstrap/profile-builder.d.ts +7 -0
  66. package/dist/router/virtual-router/bootstrap/profile-builder.js +68 -0
  67. package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +40 -0
  68. package/dist/router/virtual-router/bootstrap/provider-normalization.js +212 -0
  69. package/dist/router/virtual-router/bootstrap/responses-helpers.d.ts +15 -0
  70. package/dist/router/virtual-router/bootstrap/responses-helpers.js +65 -0
  71. package/dist/router/virtual-router/bootstrap/routing-config.d.ts +23 -0
  72. package/dist/router/virtual-router/bootstrap/routing-config.js +293 -0
  73. package/dist/router/virtual-router/bootstrap/streaming-helpers.d.ts +12 -0
  74. package/dist/router/virtual-router/bootstrap/streaming-helpers.js +128 -0
  75. package/dist/router/virtual-router/bootstrap/utils.d.ts +5 -0
  76. package/dist/router/virtual-router/bootstrap/utils.js +41 -0
  77. package/dist/router/virtual-router/bootstrap/web-search-config.d.ts +4 -0
  78. package/dist/router/virtual-router/bootstrap/web-search-config.js +131 -0
  79. package/dist/router/virtual-router/bootstrap.d.ts +0 -4
  80. package/dist/router/virtual-router/bootstrap.js +31 -1275
  81. package/dist/router/virtual-router/classifier.js +32 -14
  82. package/dist/router/virtual-router/engine/antigravity/alias-lease.js +2 -2
  83. package/dist/router/virtual-router/engine/cooldown-manager.d.ts +34 -0
  84. package/dist/router/virtual-router/engine/cooldown-manager.js +118 -0
  85. package/dist/router/virtual-router/engine/route-analytics.d.ts +28 -0
  86. package/dist/router/virtual-router/engine/route-analytics.js +44 -0
  87. package/dist/router/virtual-router/engine/routing-pools/index.js +165 -4
  88. package/dist/router/virtual-router/engine/sticky-session-manager.d.ts +29 -0
  89. package/dist/router/virtual-router/engine/sticky-session-manager.js +55 -0
  90. package/dist/router/virtual-router/engine-logging.d.ts +42 -1
  91. package/dist/router/virtual-router/engine-logging.js +82 -15
  92. package/dist/router/virtual-router/engine-selection/multimodal-capability.d.ts +3 -0
  93. package/dist/router/virtual-router/engine-selection/multimodal-capability.js +26 -0
  94. package/dist/router/virtual-router/engine-selection/route-utils.js +6 -2
  95. package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +1 -0
  96. package/dist/router/virtual-router/engine-selection/tier-selection.js +31 -1
  97. package/dist/router/virtual-router/engine.d.ts +21 -7
  98. package/dist/router/virtual-router/engine.js +198 -194
  99. package/dist/router/virtual-router/features.js +12 -4
  100. package/dist/router/virtual-router/message-utils.d.ts +8 -0
  101. package/dist/router/virtual-router/message-utils.js +170 -45
  102. package/dist/router/virtual-router/pre-command-file-resolver.js +40 -2
  103. package/dist/router/virtual-router/routing-instructions.d.ts +8 -0
  104. package/dist/router/virtual-router/routing-instructions.js +18 -2
  105. package/dist/router/virtual-router/routing-stop-message-actions.js +34 -10
  106. package/dist/router/virtual-router/routing-stop-message-state-codec.d.ts +2 -0
  107. package/dist/router/virtual-router/routing-stop-message-state-codec.js +50 -1
  108. package/dist/router/virtual-router/stop-message-state-sync.d.ts +1 -1
  109. package/dist/router/virtual-router/stop-message-state-sync.js +3 -0
  110. package/dist/router/virtual-router/token-counter.js +51 -10
  111. package/dist/router/virtual-router/tool-signals.js +4 -0
  112. package/dist/router/virtual-router/types.d.ts +15 -0
  113. package/dist/servertool/clock/session-scope.d.ts +3 -0
  114. package/dist/servertool/clock/session-scope.js +52 -0
  115. package/dist/servertool/clock/state.js +9 -0
  116. package/dist/servertool/clock/tasks.js +12 -1
  117. package/dist/servertool/clock/types.d.ts +3 -0
  118. package/dist/servertool/engine.js +177 -31
  119. package/dist/servertool/handlers/clock-auto.js +2 -8
  120. package/dist/servertool/handlers/clock.js +6 -9
  121. package/dist/servertool/handlers/recursive-detection-guard.js +53 -14
  122. package/dist/servertool/handlers/stop-message-auto/blocked-report.d.ts +16 -0
  123. package/dist/servertool/handlers/stop-message-auto/blocked-report.js +349 -0
  124. package/dist/servertool/handlers/stop-message-auto/iflow-followup.d.ts +23 -0
  125. package/dist/servertool/handlers/stop-message-auto/iflow-followup.js +503 -0
  126. package/dist/servertool/handlers/stop-message-auto/routing-state.d.ts +38 -0
  127. package/dist/servertool/handlers/stop-message-auto/routing-state.js +149 -0
  128. package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +67 -0
  129. package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +387 -0
  130. package/dist/servertool/handlers/stop-message-auto.d.ts +1 -1
  131. package/dist/servertool/handlers/stop-message-auto.js +80 -556
  132. package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.d.ts +18 -0
  133. package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.js +398 -0
  134. package/dist/servertool/handlers/stop-message-stage-policy/decision.d.ts +9 -0
  135. package/dist/servertool/handlers/stop-message-stage-policy/decision.js +127 -0
  136. package/dist/servertool/handlers/stop-message-stage-policy/observation.d.ts +2 -0
  137. package/dist/servertool/handlers/stop-message-stage-policy/observation.js +179 -0
  138. package/dist/servertool/handlers/stop-message-stage-policy/templates.d.ts +4 -0
  139. package/dist/servertool/handlers/stop-message-stage-policy/templates.js +96 -0
  140. package/dist/servertool/handlers/stop-message-stage-policy/text-utils.d.ts +9 -0
  141. package/dist/servertool/handlers/stop-message-stage-policy/text-utils.js +89 -0
  142. package/dist/servertool/handlers/stop-message-stage-policy/types.d.ts +59 -0
  143. package/dist/servertool/handlers/stop-message-stage-policy/types.js +1 -0
  144. package/dist/servertool/handlers/stop-message-stage-policy.d.ts +3 -43
  145. package/dist/servertool/handlers/stop-message-stage-policy.js +2 -684
  146. package/dist/servertool/handlers/web-search.js +117 -0
  147. package/dist/servertool/server-side-tools.d.ts +0 -1
  148. package/dist/servertool/server-side-tools.js +4 -3
  149. package/dist/sse/sse-to-json/builders/response-builder.js +16 -0
  150. package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +1 -0
  151. package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +110 -37
  152. package/dist/telemetry/stats-center.d.ts +9 -0
  153. package/dist/telemetry/stats-center.js +29 -1
  154. package/dist/tools/apply-patch/structured/coercion.js +3 -11
  155. package/dist/tools/exec-command/validator.d.ts +1 -0
  156. package/dist/tools/exec-command/validator.js +132 -0
  157. package/dist/tools/tool-registry.d.ts +1 -0
  158. package/dist/tools/tool-registry.js +1 -1
  159. package/package.json +1 -1
@@ -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);
@@ -1,5 +1,6 @@
1
1
  const WEB_TOOL_KEYWORDS = ['websearch', 'web_search', 'web-search', 'webfetch', 'web_fetch', 'web_request', 'search_web', 'internet_search'];
2
2
  const READ_TOOL_EXACT = new Set([
3
+ 'read',
3
4
  'read_file',
4
5
  'read_text',
5
6
  'view_file',
@@ -11,6 +12,9 @@ const READ_TOOL_EXACT = new Set([
11
12
  'describe_current_request'
12
13
  ]);
13
14
  const WRITE_TOOL_EXACT = new Set([
15
+ 'edit',
16
+ 'write',
17
+ 'multiedit',
14
18
  'apply_patch',
15
19
  'write_file',
16
20
  'create_file',
@@ -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;
@@ -473,6 +476,9 @@ export interface StopMessageStateSnapshot {
473
476
  stopMessageObservationHash?: string;
474
477
  stopMessageObservationStableCount?: number;
475
478
  stopMessageBdWorkState?: string;
479
+ stopMessageAssignedIssueId?: string;
480
+ stopMessageAssignedIssueSource?: 'in_progress' | 'ready' | 'open';
481
+ stopMessageNoTaskSummaryUsed?: boolean;
476
482
  }
477
483
  export interface PreCommandStateSnapshot {
478
484
  preCommandScriptPath: string;
@@ -484,6 +490,15 @@ export interface RoutingStatusSnapshot {
484
490
  providers: string[];
485
491
  hits: number;
486
492
  lastUsedProvider?: string;
493
+ lastHit?: {
494
+ timestampMs: number;
495
+ reason?: string;
496
+ requestTokens?: number;
497
+ selectionPenalty?: number;
498
+ stopMessageActive: boolean;
499
+ stopMessageMode?: 'on' | 'off' | 'auto';
500
+ stopMessageRemaining?: number;
501
+ };
487
502
  }>;
488
503
  health: ProviderHealthState[];
489
504
  }
@@ -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
+ }
@@ -24,6 +24,13 @@ function normalizeRecurrence(raw) {
24
24
  }
25
25
  return { kind, maxRuns };
26
26
  }
27
+ function normalizeTaskSetter(raw) {
28
+ const value = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
29
+ if (value === 'agent') {
30
+ return 'agent';
31
+ }
32
+ return 'user';
33
+ }
27
34
  export function setClockOffsetMs(value) {
28
35
  if (typeof value !== 'number' || !Number.isFinite(value))
29
36
  return;
@@ -68,12 +75,14 @@ export function coerceState(raw, sessionId) {
68
75
  : undefined;
69
76
  const deliveryCount = typeof e.deliveryCount === 'number' && Number.isFinite(e.deliveryCount) ? Math.max(0, Math.floor(e.deliveryCount)) : 0;
70
77
  const recurrence = normalizeRecurrence(e.recurrence);
78
+ const setBy = normalizeTaskSetter(e.setBy);
71
79
  tasks.push({
72
80
  taskId,
73
81
  sessionId,
74
82
  dueAtMs,
75
83
  createdAtMs,
76
84
  updatedAtMs,
85
+ setBy,
77
86
  task,
78
87
  ...(tool ? { tool } : {}),
79
88
  ...(args ? { arguments: args } : {}),
@@ -50,6 +50,13 @@ function normalizeRecurrence(raw) {
50
50
  }
51
51
  return { kind, maxRuns };
52
52
  }
53
+ function normalizeTaskSetter(raw) {
54
+ const value = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
55
+ if (value === 'agent') {
56
+ return 'agent';
57
+ }
58
+ return 'user';
59
+ }
53
60
  function resolveRecurringStepMs(recurrence) {
54
61
  if (recurrence.kind === 'daily') {
55
62
  return 24 * 60 * 60_000;
@@ -139,6 +146,7 @@ export async function scheduleClockTasks(sessionId, items, config) {
139
146
  if (item.recurrence && !recurrence) {
140
147
  continue;
141
148
  }
149
+ const setBy = normalizeTaskSetter(item.setBy);
142
150
  const taskId = buildTaskId();
143
151
  scheduled.push({
144
152
  taskId,
@@ -146,6 +154,7 @@ export async function scheduleClockTasks(sessionId, items, config) {
146
154
  dueAtMs: Math.floor(dueAtMs),
147
155
  createdAtMs: at,
148
156
  updatedAtMs: at,
157
+ setBy,
149
158
  task: text,
150
159
  ...(item.tool ? { tool: item.tool } : {}),
151
160
  ...(item.arguments ? { arguments: item.arguments } : {}),
@@ -355,13 +364,15 @@ export async function reserveDueTasksForRequest(args) {
355
364
  const injectText = due
356
365
  .map((t) => {
357
366
  const dueAtIso = new Date(t.dueAtMs).toISOString();
367
+ const setAtIso = new Date(t.createdAtMs).toISOString();
358
368
  const toolLabel = t.tool ? ` tool=${t.tool}` : '';
359
369
  const argsLabel = t.arguments ? ` args=${safeJson(t.arguments)}` : '';
370
+ const setByLabel = t.setBy === 'agent' ? 'agent' : 'user';
360
371
  const recurrence = normalizeRecurrence(t.recurrence);
361
372
  const recurrenceLabel = recurrence
362
373
  ? ` recurrence=${recurrence.kind}${recurrence.kind === 'interval' ? `(${recurrence.everyMinutes}m)` : ''} maxRuns=${recurrence.maxRuns} run=${Math.max(0, t.deliveryCount)}/${recurrence.maxRuns}`
363
374
  : '';
364
- return `[scheduled task:${safeQuoted(t.task)}${toolLabel}${argsLabel}${recurrenceLabel} dueAt=${dueAtIso}]`;
375
+ return `[scheduled task:${safeQuoted(t.task)}${toolLabel}${argsLabel}${recurrenceLabel} dueAt=${dueAtIso} setBy=${setByLabel} setAt=${setAtIso}]`;
365
376
  })
366
377
  .join('\n');
367
378
  return { reservation, injectText };
@@ -4,12 +4,14 @@ export type ClockTaskRecurrence = {
4
4
  maxRuns: number;
5
5
  everyMinutes?: number;
6
6
  };
7
+ export type ClockTaskSetter = 'user' | 'agent';
7
8
  export type ClockTask = {
8
9
  taskId: string;
9
10
  sessionId: string;
10
11
  dueAtMs: number;
11
12
  createdAtMs: number;
12
13
  updatedAtMs: number;
14
+ setBy: ClockTaskSetter;
13
15
  task: string;
14
16
  tool?: string;
15
17
  arguments?: Record<string, unknown>;
@@ -49,6 +51,7 @@ export type ClockConfigSnapshot = {
49
51
  export type ClockScheduleItem = {
50
52
  dueAtMs: number;
51
53
  task: string;
54
+ setBy?: ClockTaskSetter;
52
55
  tool?: string;
53
56
  arguments?: Record<string, unknown>;
54
57
  notBeforeRequestId?: string;
@@ -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';
@@ -46,6 +47,57 @@ function withTimeout(promise, timeoutMs, buildError) {
46
47
  });
47
48
  });
48
49
  }
50
+ class ServerToolClientDisconnectedError extends Error {
51
+ code = 'SERVERTOOL_CLIENT_DISCONNECTED';
52
+ }
53
+ function createServerToolClientDisconnectedError(options) {
54
+ const error = new ServerToolClientDisconnectedError(`[servertool] client disconnected during followup` + (options.flowId ? ` flow=${options.flowId}` : ''));
55
+ error.details = {
56
+ requestId: options.requestId,
57
+ flowId: options.flowId
58
+ };
59
+ return error;
60
+ }
61
+ function isServerToolClientDisconnectedError(error) {
62
+ return Boolean(error &&
63
+ typeof error === 'object' &&
64
+ typeof error.code === 'string' &&
65
+ error.code === 'SERVERTOOL_CLIENT_DISCONNECTED');
66
+ }
67
+ function createClientDisconnectWatcher(options) {
68
+ const interval = typeof options.pollIntervalMs === 'number' && Number.isFinite(options.pollIntervalMs) && options.pollIntervalMs > 0
69
+ ? Math.max(20, Math.floor(options.pollIntervalMs))
70
+ : 80;
71
+ let timer;
72
+ let active = true;
73
+ const cancel = () => {
74
+ active = false;
75
+ if (timer) {
76
+ clearTimeout(timer);
77
+ timer = undefined;
78
+ }
79
+ };
80
+ const promise = new Promise((_resolve, reject) => {
81
+ const check = () => {
82
+ if (!active) {
83
+ return;
84
+ }
85
+ if (isAdapterClientDisconnected(options.adapterContext)) {
86
+ cancel();
87
+ reject(createServerToolClientDisconnectedError({
88
+ requestId: options.requestId,
89
+ flowId: options.flowId
90
+ }));
91
+ return;
92
+ }
93
+ timer = setTimeout(check, interval);
94
+ timer.unref?.();
95
+ };
96
+ timer = setTimeout(check, interval);
97
+ timer.unref?.();
98
+ });
99
+ return { promise, cancel };
100
+ }
49
101
  function isServerToolTimeoutError(error) {
50
102
  return Boolean(error &&
51
103
  typeof error === 'object' &&
@@ -194,6 +246,21 @@ function isEmptyClientResponsePayload(payload) {
194
246
  }
195
247
  return true;
196
248
  }
249
+ function createEmptyFollowupError(args) {
250
+ const wrapped = new ProviderProtocolError(`[servertool] Followup returned empty response for flow ${args.flowId ?? 'unknown'}`, {
251
+ code: 'SERVERTOOL_EMPTY_FOLLOWUP',
252
+ category: 'EXTERNAL_ERROR',
253
+ details: {
254
+ flowId: args.flowId,
255
+ requestId: args.requestId,
256
+ error: args.lastError instanceof Error ? args.lastError.message : undefined,
257
+ ...(args.originalResponseWasEmpty ? { originalResponseWasEmpty: true } : {})
258
+ }
259
+ });
260
+ wrapped.status = 502;
261
+ wrapped.cause = args.lastError;
262
+ return wrapped;
263
+ }
197
264
  function isStopFinishReasonWithoutToolCalls(base) {
198
265
  return inspectStopGatewaySignal(base).eligible;
199
266
  }
@@ -204,7 +271,7 @@ async function shouldDisableServerToolTimeoutForClockHold(args) {
204
271
  }
205
272
  const record = args.adapterContext;
206
273
  const rt = readRuntimeMetadata(record);
207
- const sessionId = typeof record.sessionId === 'string' ? record.sessionId.trim() : '';
274
+ const sessionId = resolveClockSessionScope(record, rt);
208
275
  if (!sessionId) {
209
276
  return false;
210
277
  }
@@ -526,6 +593,7 @@ export async function runServerToolOrchestration(options) {
526
593
  }
527
594
  const isStopMessageFlow = engineResult.execution.flowId === 'stop_message_flow';
528
595
  const isClockHoldFlow = engineResult.execution.flowId === 'clock_hold_flow';
596
+ const isContinueExecutionFlow = engineResult.execution.flowId === 'continue_execution_flow';
529
597
  const isEmptyReplyContinue = engineResult.execution.flowId === 'empty_reply_continue';
530
598
  const isApplyPatchGuard = engineResult.execution.flowId === 'apply_patch_guard';
531
599
  const isExecCommandGuard = engineResult.execution.flowId === 'exec_command_guard';
@@ -570,6 +638,7 @@ export async function runServerToolOrchestration(options) {
570
638
  };
571
639
  }
572
640
  const loopState = buildServerToolLoopState(options.adapterContext, engineResult.execution.flowId, followupPayloadRaw, engineResult.finalChatResponse);
641
+ const stopMessageReservation = null;
573
642
  if (applyAutoLimit && loopState && typeof loopState.repeatCount === 'number' && loopState.repeatCount >= 3) {
574
643
  logProgress(5, totalSteps, 'completed (auto limit hit)', { flowId });
575
644
  return {
@@ -640,9 +709,10 @@ export async function runServerToolOrchestration(options) {
640
709
  (typeof options.entryEndpoint === 'string' && options.entryEndpoint.trim().length
641
710
  ? options.entryEndpoint
642
711
  : followupEntryEndpoint);
643
- // For stateful auto-followups (e.g. stop_message_flow / clock_hold_flow), keep the same providerKey/alias.
712
+ // For stateful auto-followups (e.g. stop_message_flow / clock_hold_flow / continue_execution_flow),
713
+ // keep the same providerKey/alias.
644
714
  // Otherwise the followup requestId suffix would cause round-robin alias switching and compatibility drift.
645
- if (isStopMessageFlow || isClockHoldFlow) {
715
+ if (isStopMessageFlow || isClockHoldFlow || isContinueExecutionFlow) {
646
716
  const providerKeyRaw = options.adapterContext.providerKey;
647
717
  const providerKey = typeof providerKeyRaw === 'string' && providerKeyRaw.trim().length ? providerKeyRaw.trim() : '';
648
718
  if (providerKey) {
@@ -669,7 +739,6 @@ export async function runServerToolOrchestration(options) {
669
739
  // 否则会出现下一轮仍然自动触发 → 再次失败 → 客户端永远 502 的死循环。
670
740
  //
671
741
  // stop_message_flow 的计数器递增由 handler 在决定触发时处理,engine 不再提前递增。
672
- const stopMessageReservation = null;
673
742
  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
674
743
  const elapsedBeforeAttempt = isStopMessageFlow && loopState && typeof loopState.startedAtMs === 'number' && Number.isFinite(loopState.startedAtMs)
675
744
  ? Math.max(0, Date.now() - loopState.startedAtMs)
@@ -687,13 +756,19 @@ export async function runServerToolOrchestration(options) {
687
756
  const attemptTimeoutMs = isStopMessageFlow && STOP_MESSAGE_STAGE_TIMEOUT_MS > elapsedBeforeAttempt
688
757
  ? Math.max(1, Math.min(followupTimeoutMs, STOP_MESSAGE_STAGE_TIMEOUT_MS - elapsedBeforeAttempt))
689
758
  : followupTimeoutMs;
759
+ const disconnectWatcher = createClientDisconnectWatcher({
760
+ adapterContext: options.adapterContext,
761
+ requestId: options.requestId,
762
+ flowId: engineResult.execution.flowId
763
+ });
690
764
  try {
691
- followup = await withTimeout(options.reenterPipeline({
765
+ const followupPromise = options.reenterPipeline({
692
766
  entryEndpoint: followupEntryEndpoint,
693
767
  requestId: followupRequestId,
694
768
  body: followupPayload,
695
769
  metadata
696
- }), attemptTimeoutMs, () => isStopMessageFlow
770
+ });
771
+ followup = await withTimeout(Promise.race([followupPromise, disconnectWatcher.promise]), attemptTimeoutMs, () => isStopMessageFlow
697
772
  ? createStopMessageFetchFailedError({
698
773
  requestId: options.requestId,
699
774
  reason: 'stage_timeout',
@@ -710,6 +785,7 @@ export async function runServerToolOrchestration(options) {
710
785
  attempt,
711
786
  maxAttempts
712
787
  }));
788
+ disconnectWatcher.cancel();
713
789
  // Treat empty followup as failure for auto followup flows:
714
790
  // - retry once (maxAttempts=2)
715
791
  // - if still empty, surface as HTTP error so client can retry.
@@ -729,6 +805,15 @@ export async function runServerToolOrchestration(options) {
729
805
  break;
730
806
  }
731
807
  catch (error) {
808
+ disconnectWatcher.cancel();
809
+ if (isServerToolClientDisconnectedError(error) || isAdapterClientDisconnected(options.adapterContext)) {
810
+ logProgress(5, totalSteps, 'completed (client disconnected)', { flowId, attempt });
811
+ return {
812
+ chat: engineResult.finalChatResponse,
813
+ executed: true,
814
+ flowId: engineResult.execution.flowId
815
+ };
816
+ }
732
817
  if (isServerToolTimeoutError(error)) {
733
818
  throw error;
734
819
  }
@@ -755,28 +840,30 @@ export async function runServerToolOrchestration(options) {
755
840
  : undefined;
756
841
  if (retryEmptyFollowupOnce && (!followupBody || isEmptyClientResponsePayload(followupBody))) {
757
842
  if (isStopMessageFlow) {
758
- // 对 stopMessage:避免把 empty followup 升级为 502,直接清理 stopMessage 状态并返回原始响应。
759
- // 这样客户端至少能拿到本轮输出,且 stopMessage 不会在后续请求里继续触发导致“永远 502”。
843
+ // 对 stopMessage:先清理状态,避免后续请求继续触发失败循环;
844
+ // 若原始响应可用则回退原始响应,否则显式抛错,避免“静默空响应”。
760
845
  disableStopMessageAfterFailedFollowup(options.adapterContext, stopMessageReservation);
761
- logProgress(5, totalSteps, 'completed (stopMessage followup empty; cleaned state)', { flowId });
762
- return {
763
- chat: engineResult.finalChatResponse,
764
- executed: true,
765
- flowId: engineResult.execution.flowId
766
- };
767
- }
768
- const wrapped = new ProviderProtocolError(`[servertool] Followup returned empty response for flow ${engineResult.execution.flowId ?? 'unknown'}`, {
769
- code: 'SERVERTOOL_EMPTY_FOLLOWUP',
770
- category: 'EXTERNAL_ERROR',
771
- details: {
846
+ const fallbackOriginal = engineResult.finalChatResponse;
847
+ if (fallbackOriginal && !isEmptyClientResponsePayload(fallbackOriginal)) {
848
+ logProgress(5, totalSteps, 'completed (stopMessage followup empty; fallback to original)', { flowId });
849
+ return {
850
+ chat: fallbackOriginal,
851
+ executed: true,
852
+ flowId: engineResult.execution.flowId
853
+ };
854
+ }
855
+ throw createEmptyFollowupError({
772
856
  flowId: engineResult.execution.flowId,
773
857
  requestId: options.requestId,
774
- error: lastError instanceof Error ? lastError.message : undefined
775
- }
858
+ lastError,
859
+ originalResponseWasEmpty: true
860
+ });
861
+ }
862
+ throw createEmptyFollowupError({
863
+ flowId: engineResult.execution.flowId,
864
+ requestId: options.requestId,
865
+ lastError
776
866
  });
777
- wrapped.status = 502;
778
- wrapped.cause = lastError;
779
- throw wrapped;
780
867
  }
781
868
  // Special case: Antigravity thoughtSignature bootstrap flow.
782
869
  // - First followup performs a minimal preflight (forces clock.get) to obtain a fresh signature.
@@ -899,19 +986,78 @@ function resolveStickyKeyFromAdapterContext(adapterContext) {
899
986
  if (!adapterContext || typeof adapterContext !== 'object') {
900
987
  return undefined;
901
988
  }
902
- const sessionId = typeof adapterContext.sessionId === 'string'
903
- ? adapterContext.sessionId.trim()
904
- : '';
905
- const conversationId = typeof adapterContext.conversationId === 'string'
906
- ? adapterContext.conversationId.trim()
907
- : '';
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);
908
1013
  if (sessionId) {
909
1014
  return `session:${sessionId}`;
910
1015
  }
911
1016
  if (conversationId) {
912
1017
  return `conversation:${conversationId}`;
913
1018
  }
914
- 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() : '';
915
1061
  }
916
1062
  function cloneRoutingInstructionState(state) {
917
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
  }