@jsonstudio/llms 0.6.1739 → 0.6.1890

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/dist/conversion/compat/actions/deepseek-web-request.d.ts +3 -0
  2. package/dist/conversion/compat/actions/deepseek-web-request.js +350 -0
  3. package/dist/conversion/compat/actions/deepseek-web-response.d.ts +3 -0
  4. package/dist/conversion/compat/actions/deepseek-web-response.js +886 -0
  5. package/dist/conversion/compat/actions/gemini-cli-request.js +3 -1
  6. package/dist/conversion/compat/profiles/chat-deepseek-web.json +18 -0
  7. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +166 -2
  8. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +169 -0
  9. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +6 -0
  10. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +12 -0
  11. package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +1 -0
  12. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +4 -0
  13. package/dist/conversion/hub/pipeline/hub-pipeline.js +365 -144
  14. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +9 -0
  15. package/dist/conversion/hub/policy/policy-engine.d.ts +2 -0
  16. package/dist/conversion/hub/policy/policy-engine.js +8 -0
  17. package/dist/conversion/hub/process/chat-process.js +466 -16
  18. package/dist/conversion/hub/response/provider-response.js +0 -35
  19. package/dist/conversion/responses/responses-openai-bridge.d.ts +2 -0
  20. package/dist/conversion/responses/responses-openai-bridge.js +166 -8
  21. package/dist/conversion/shared/anthropic-message-utils.js +10 -1
  22. package/dist/conversion/shared/protocol-field-allowlists.d.ts +2 -2
  23. package/dist/conversion/shared/protocol-field-allowlists.js +4 -0
  24. package/dist/conversion/shared/tool-governor.js +102 -0
  25. package/dist/guidance/index.js +17 -0
  26. package/dist/router/virtual-router/bootstrap.js +46 -1
  27. package/dist/router/virtual-router/classifier.js +59 -4
  28. package/dist/router/virtual-router/engine/health/index.js +6 -6
  29. package/dist/router/virtual-router/engine/routing-state/store.js +16 -3
  30. package/dist/router/virtual-router/engine-logging.js +62 -24
  31. package/dist/router/virtual-router/engine-selection/route-utils.js +20 -20
  32. package/dist/router/virtual-router/engine-selection/tier-selection.js +2 -2
  33. package/dist/router/virtual-router/engine.d.ts +3 -1
  34. package/dist/router/virtual-router/engine.js +359 -39
  35. package/dist/router/virtual-router/features.js +2 -1
  36. package/dist/router/virtual-router/pre-command-file-resolver.d.ts +2 -0
  37. package/dist/router/virtual-router/pre-command-file-resolver.js +90 -0
  38. package/dist/router/virtual-router/provider-registry.js +3 -1
  39. package/dist/router/virtual-router/routing-instructions.d.ts +15 -1
  40. package/dist/router/virtual-router/routing-instructions.js +110 -151
  41. package/dist/router/virtual-router/routing-pre-command-actions.d.ts +3 -0
  42. package/dist/router/virtual-router/routing-pre-command-actions.js +26 -0
  43. package/dist/router/virtual-router/routing-pre-command-parser.d.ts +2 -0
  44. package/dist/router/virtual-router/routing-pre-command-parser.js +85 -0
  45. package/dist/router/virtual-router/routing-pre-command-state-codec.d.ts +3 -0
  46. package/dist/router/virtual-router/routing-pre-command-state-codec.js +24 -0
  47. package/dist/router/virtual-router/routing-stop-message-actions.d.ts +2 -0
  48. package/dist/router/virtual-router/routing-stop-message-actions.js +96 -0
  49. package/dist/router/virtual-router/routing-stop-message-parser.d.ts +3 -0
  50. package/dist/router/virtual-router/routing-stop-message-parser.js +142 -0
  51. package/dist/router/virtual-router/routing-stop-message-state-codec.d.ts +4 -0
  52. package/dist/router/virtual-router/routing-stop-message-state-codec.js +85 -0
  53. package/dist/router/virtual-router/sticky-session-store.js +206 -57
  54. package/dist/router/virtual-router/stop-message-stage-template-files.d.ts +12 -0
  55. package/dist/router/virtual-router/stop-message-stage-template-files.js +67 -0
  56. package/dist/router/virtual-router/stop-message-state-sync.d.ts +1 -1
  57. package/dist/router/virtual-router/stop-message-state-sync.js +5 -0
  58. package/dist/router/virtual-router/token-file-scanner.d.ts +9 -0
  59. package/dist/router/virtual-router/token-file-scanner.js +64 -3
  60. package/dist/router/virtual-router/tool-signals.d.ts +5 -0
  61. package/dist/router/virtual-router/tool-signals.js +42 -3
  62. package/dist/router/virtual-router/types.d.ts +19 -1
  63. package/dist/router/virtual-router/types.js +1 -0
  64. package/dist/servertool/clock/config.d.ts +1 -1
  65. package/dist/servertool/clock/config.js +27 -4
  66. package/dist/servertool/clock/state.js +41 -2
  67. package/dist/servertool/clock/task-store.d.ts +2 -2
  68. package/dist/servertool/clock/task-store.js +1 -1
  69. package/dist/servertool/clock/tasks.d.ts +3 -1
  70. package/dist/servertool/clock/tasks.js +209 -18
  71. package/dist/servertool/clock/types.d.ts +17 -0
  72. package/dist/servertool/continue-execution/log.d.ts +3 -0
  73. package/dist/servertool/continue-execution/log.js +13 -0
  74. package/dist/servertool/engine.js +414 -68
  75. package/dist/servertool/handlers/antigravity-thought-signature-bootstrap.js +6 -6
  76. package/dist/servertool/handlers/clock-auto.js +54 -71
  77. package/dist/servertool/handlers/clock.js +121 -6
  78. package/dist/servertool/handlers/continue-execution.d.ts +1 -0
  79. package/dist/servertool/handlers/continue-execution.js +91 -0
  80. package/dist/servertool/handlers/followup-request-builder.js +13 -0
  81. package/dist/servertool/handlers/gemini-empty-reply-continue.js +1 -1
  82. package/dist/servertool/handlers/iflow-model-error-retry.js +1 -1
  83. package/dist/servertool/handlers/recursive-detection-guard.js +1 -1
  84. package/dist/servertool/handlers/stop-message-auto.js +386 -257
  85. package/dist/servertool/handlers/stop-message-stage-policy.d.ts +43 -0
  86. package/dist/servertool/handlers/stop-message-stage-policy.js +684 -0
  87. package/dist/servertool/handlers/vision.js +1 -1
  88. package/dist/servertool/log/progress-file.d.ts +14 -0
  89. package/dist/servertool/log/progress-file.js +88 -0
  90. package/dist/servertool/pre-command-hooks.d.ts +17 -0
  91. package/dist/servertool/pre-command-hooks.js +491 -0
  92. package/dist/servertool/registry.d.ts +23 -6
  93. package/dist/servertool/registry.js +66 -1
  94. package/dist/servertool/server-side-tools.d.ts +1 -0
  95. package/dist/servertool/server-side-tools.js +216 -14
  96. package/dist/servertool/stop-gateway-context.d.ts +14 -0
  97. package/dist/servertool/stop-gateway-context.js +167 -0
  98. package/dist/servertool/stop-message-compare-context.d.ts +24 -0
  99. package/dist/servertool/stop-message-compare-context.js +133 -0
  100. package/dist/servertool/types.d.ts +12 -0
  101. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -0
  102. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.js +36 -1
  103. package/dist/sse/sse-to-json/builders/anthropic-response-builder.js +3 -0
  104. package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +3 -0
  105. package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +118 -1
  106. package/dist/tools/apply-patch/args-normalizer/default-actions.js +1 -1
  107. package/package.json +1 -1
@@ -1,9 +1,10 @@
1
1
  import { registerServerToolHandler } from '../registry.js';
2
2
  import { extractCapturedChatSeed } from './followup-request-builder.js';
3
3
  import { readRuntimeMetadata } from '../../conversion/shared/runtime-metadata.js';
4
- import { findNextUndeliveredDueAtMs, listClockTasks, resolveClockConfig, startClockDaemonIfNeeded } from '../clock/task-store.js';
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 { isStopEligibleForServerTool } from '../stop-gateway-context.js';
7
8
  const FLOW_ID = 'clock_hold_flow';
8
9
  function resolveClientConnectionState(value) {
9
10
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
@@ -20,7 +21,14 @@ function clientWantsStreaming(adapterContext) {
20
21
  return false;
21
22
  }
22
23
  const record = adapterContext;
23
- if (record.stream === true) {
24
+ if (record.stream === true ||
25
+ record.inboundStream === true ||
26
+ record.outboundStream === true ||
27
+ record.clientStream === true) {
28
+ return true;
29
+ }
30
+ const streamingHint = typeof record.streamingHint === 'string' ? record.streamingHint.trim().toLowerCase() : '';
31
+ if (streamingHint === 'force') {
24
32
  return true;
25
33
  }
26
34
  const clientHeaders = record.clientHeaders;
@@ -32,50 +40,6 @@ function clientWantsStreaming(adapterContext) {
32
40
  }
33
41
  return false;
34
42
  }
35
- function isStopFinishReason(base) {
36
- if (!base || typeof base !== 'object' || Array.isArray(base)) {
37
- return false;
38
- }
39
- const payload = base;
40
- const choicesRaw = payload.choices;
41
- if (Array.isArray(choicesRaw) && choicesRaw.length) {
42
- const first = choicesRaw[0];
43
- if (!first || typeof first !== 'object' || Array.isArray(first)) {
44
- return false;
45
- }
46
- const finishReasonRaw = first.finish_reason;
47
- const finishReason = typeof finishReasonRaw === 'string' && finishReasonRaw.trim()
48
- ? finishReasonRaw.trim().toLowerCase()
49
- : '';
50
- if (!finishReason || finishReason === 'tool_calls') {
51
- return false;
52
- }
53
- if (finishReason !== 'stop' && finishReason !== 'length') {
54
- return false;
55
- }
56
- const message = first.message &&
57
- typeof first.message === 'object' &&
58
- !Array.isArray(first.message)
59
- ? first.message
60
- : null;
61
- if (!message) {
62
- return false;
63
- }
64
- const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
65
- if (toolCalls.length > 0) {
66
- return false;
67
- }
68
- return true;
69
- }
70
- const statusRaw = typeof payload.status === 'string' ? payload.status.trim().toLowerCase() : '';
71
- if (statusRaw && statusRaw !== 'completed') {
72
- return false;
73
- }
74
- if (payload.required_action && typeof payload.required_action === 'object') {
75
- return false;
76
- }
77
- return true;
78
- }
79
43
  function resolveSessionId(adapterContext) {
80
44
  if (!adapterContext || typeof adapterContext !== 'object' || Array.isArray(adapterContext)) {
81
45
  return null;
@@ -103,7 +67,7 @@ async function sleep(ms) {
103
67
  const handler = async (ctx) => {
104
68
  const record = ctx.adapterContext;
105
69
  // Only trigger on stop/length completion (no tool calls).
106
- if (!isStopFinishReason(ctx.base)) {
70
+ if (!isStopEligibleForServerTool(ctx.base, ctx.adapterContext)) {
107
71
  return null;
108
72
  }
109
73
  // When client already disconnected, skip holding.
@@ -147,33 +111,47 @@ const handler = async (ctx) => {
147
111
  }
148
112
  // Wait until the "due window" is reached (now >= dueAt - dueWindowMs).
149
113
  const thresholdMs = nextDueAtMs - clockConfig.dueWindowMs;
150
- // Important: if we're already inside the due window, do NOT auto-followup in the current request
151
- // (prevents same-request loops; the reminder will be injected on the next request).
152
- if (at >= thresholdMs) {
153
- return null;
154
- }
155
- const remainingMs = thresholdMs - at;
156
- if (clockConfig.holdMaxMs >= 0 && remainingMs > clockConfig.holdMaxMs) {
157
- return null;
158
- }
159
- logClock('hold_start', { sessionId, nextDueAtMs, thresholdMs });
160
- while (nowMs() < thresholdMs) {
161
- const state = resolveClientConnectionState(ctx.adapterContext.clientConnectionState);
162
- if (state?.disconnected === true) {
163
- return null;
164
- }
165
- const remaining = thresholdMs - nowMs();
166
- await sleep(computeHoldSleepMs(remaining));
167
- // Best-effort: if tasks were cleared/cancelled while holding, stop holding.
114
+ const inDueWindow = at >= thresholdMs;
115
+ if (inDueWindow) {
168
116
  try {
169
- const refreshed = await listClockTasks(sessionId, clockConfig);
170
- const refreshedNext = findNextUndeliveredDueAtMs(refreshed, nowMs());
171
- if (!refreshedNext) {
117
+ const probe = await reserveDueTasksForRequest({
118
+ reservationId: `${ctx.requestId}:clock_auto_probe`,
119
+ sessionId,
120
+ config: clockConfig,
121
+ requestId: ctx.requestId
122
+ });
123
+ if (!probe.reservation || !Array.isArray(probe.reservation.taskIds) || probe.reservation.taskIds.length === 0) {
172
124
  return null;
173
125
  }
174
126
  }
175
127
  catch {
176
- // ignore refresh errors; keep holding
128
+ return null;
129
+ }
130
+ }
131
+ else {
132
+ const remainingMs = thresholdMs - at;
133
+ if (clockConfig.holdMaxMs >= 0 && remainingMs > clockConfig.holdMaxMs) {
134
+ return null;
135
+ }
136
+ logClock('hold_start', { sessionId, nextDueAtMs, thresholdMs });
137
+ while (nowMs() < thresholdMs) {
138
+ const state = resolveClientConnectionState(ctx.adapterContext.clientConnectionState);
139
+ if (state?.disconnected === true) {
140
+ return null;
141
+ }
142
+ const remaining = thresholdMs - nowMs();
143
+ await sleep(computeHoldSleepMs(remaining));
144
+ // Best-effort: if tasks were cleared/cancelled while holding, stop holding.
145
+ try {
146
+ const refreshed = await listClockTasks(sessionId, clockConfig);
147
+ const refreshedNext = findNextUndeliveredDueAtMs(refreshed, nowMs());
148
+ if (!refreshedNext) {
149
+ return null;
150
+ }
151
+ }
152
+ catch {
153
+ // ignore refresh errors; keep holding
154
+ }
177
155
  }
178
156
  }
179
157
  return {
@@ -191,10 +169,15 @@ const handler = async (ctx) => {
191
169
  { op: 'append_user_text', text: 'continue' }
192
170
  ]
193
171
  },
194
- metadata: (connectionState ? { clientConnectionState: connectionState } : {})
172
+ metadata: {
173
+ ...(connectionState ? { clientConnectionState: connectionState } : {}),
174
+ __rt: {
175
+ clockFollowupInjectReminders: true
176
+ }
177
+ }
195
178
  }
196
179
  }
197
180
  })
198
181
  };
199
182
  };
200
- registerServerToolHandler('clock_auto', handler, { trigger: 'auto' });
183
+ registerServerToolHandler('clock_auto', handler, { trigger: 'auto', hook: { phase: 'post', priority: 50 } });
@@ -2,11 +2,27 @@ import { registerServerToolHandler } from '../registry.js';
2
2
  import { cloneJson } from '../server-side-tools.js';
3
3
  import { extractCapturedChatSeed } from './followup-request-builder.js';
4
4
  import { ensureRuntimeMetadata, readRuntimeMetadata } from '../../conversion/shared/runtime-metadata.js';
5
- import { cancelClockTask, clearClockTasks, listClockTasks, resolveClockConfig, parseDueAtMs, scheduleClockTasks, startClockDaemonIfNeeded } from '../clock/task-store.js';
5
+ import { cancelClockTask, clearClockTasks, listClockTasks, resolveClockConfig, parseDueAtMs, scheduleClockTasks, startClockDaemonIfNeeded, updateClockTask } from '../clock/task-store.js';
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
9
  const FLOW_ID = 'clock_flow';
10
+ let fallbackClockToolCallSeq = 0;
11
+ function ensureClockToolCall(toolCall, requestId) {
12
+ if (!toolCall || toolCall.name !== 'clock') {
13
+ return null;
14
+ }
15
+ const existingId = typeof toolCall.id === 'string' ? toolCall.id.trim() : '';
16
+ if (existingId) {
17
+ return toolCall;
18
+ }
19
+ fallbackClockToolCallSeq += 1;
20
+ const reqToken = String(requestId || 'req').replace(/[^a-zA-Z0-9_-]+/g, '_') || 'req';
21
+ return {
22
+ ...toolCall,
23
+ id: `call_clock_fallback_${reqToken}_${fallbackClockToolCallSeq}`
24
+ };
25
+ }
10
26
  function extractAssistantMessageFromChatLike(chatResponse) {
11
27
  if (!chatResponse || typeof chatResponse !== 'object') {
12
28
  return null;
@@ -112,7 +128,7 @@ function asPlainObject(value) {
112
128
  }
113
129
  function normalizeAction(value) {
114
130
  const raw = typeof value === 'string' ? value.trim().toLowerCase() : '';
115
- if (raw === 'get' || raw === 'list' || raw === 'cancel' || raw === 'clear' || raw === 'schedule') {
131
+ if (raw === 'get' || raw === 'list' || raw === 'cancel' || raw === 'clear' || raw === 'schedule' || raw === 'update') {
116
132
  return raw;
117
133
  }
118
134
  return 'schedule';
@@ -125,6 +141,58 @@ function toIso(ms) {
125
141
  return new Date(0).toISOString();
126
142
  }
127
143
  }
144
+ function parseRecurrenceKind(raw) {
145
+ const value = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
146
+ if (!value) {
147
+ return undefined;
148
+ }
149
+ if (value === 'daily' || value === 'day') {
150
+ return 'daily';
151
+ }
152
+ if (value === 'weekly' || value === 'week') {
153
+ return 'weekly';
154
+ }
155
+ if (value === 'interval' || value === 'every_minutes' || value === 'every-minutes' || value === 'everyminutes') {
156
+ return 'interval';
157
+ }
158
+ return undefined;
159
+ }
160
+ function parseRecurrenceFromRecord(record) {
161
+ const recurrenceRaw = record.recurrence ?? record.repeat ?? record.cycle;
162
+ const maxRunsRaw = Number(record.maxRuns ?? (recurrenceRaw && typeof recurrenceRaw === 'object' && !Array.isArray(recurrenceRaw)
163
+ ? recurrenceRaw.maxRuns
164
+ : undefined));
165
+ if (recurrenceRaw === undefined || recurrenceRaw === null || recurrenceRaw === false) {
166
+ return {};
167
+ }
168
+ let kind;
169
+ let everyMinutesRaw;
170
+ if (typeof recurrenceRaw === 'string') {
171
+ kind = parseRecurrenceKind(recurrenceRaw);
172
+ everyMinutesRaw = record.everyMinutes;
173
+ }
174
+ else if (recurrenceRaw && typeof recurrenceRaw === 'object' && !Array.isArray(recurrenceRaw)) {
175
+ const recurrenceRecord = recurrenceRaw;
176
+ kind = parseRecurrenceKind(recurrenceRecord.kind ?? recurrenceRecord.type ?? recurrenceRecord.mode ?? recurrenceRecord.every);
177
+ everyMinutesRaw = recurrenceRecord.everyMinutes ?? recurrenceRecord.minutes ?? record.everyMinutes;
178
+ }
179
+ if (!kind) {
180
+ return { message: 'clock.schedule recurrence kind must be daily|weekly|interval' };
181
+ }
182
+ const maxRuns = Number.isFinite(maxRunsRaw) ? Math.floor(maxRunsRaw) : NaN;
183
+ if (!Number.isFinite(maxRuns) || maxRuns <= 0) {
184
+ return { message: 'clock.schedule recurring task requires maxRuns >= 1' };
185
+ }
186
+ if (kind === 'interval') {
187
+ const everyMinutesNum = Number(everyMinutesRaw);
188
+ const everyMinutes = Number.isFinite(everyMinutesNum) ? Math.floor(everyMinutesNum) : NaN;
189
+ if (!Number.isFinite(everyMinutes) || everyMinutes <= 0) {
190
+ return { message: 'clock.schedule interval recurrence requires everyMinutes >= 1' };
191
+ }
192
+ return { recurrence: { kind: 'interval', maxRuns, everyMinutes } };
193
+ }
194
+ return { recurrence: { kind, maxRuns } };
195
+ }
128
196
  function normalizeScheduleItems(parsed) {
129
197
  const itemsRaw = parsed.items;
130
198
  if (!Array.isArray(itemsRaw) || itemsRaw.length === 0) {
@@ -161,16 +229,33 @@ function normalizeScheduleItems(parsed) {
161
229
  }
162
230
  return undefined;
163
231
  })();
232
+ const recurrenceParsed = parseRecurrenceFromRecord(rec);
233
+ if (recurrenceParsed.message) {
234
+ return { ok: false, items: [], message: recurrenceParsed.message };
235
+ }
164
236
  items.push({
165
237
  dueAtMs,
166
238
  task,
167
239
  ...(tool ? { tool } : {}),
168
- ...(argsObj ? { arguments: argsObj } : {})
240
+ ...(argsObj ? { arguments: argsObj } : {}),
241
+ ...(recurrenceParsed.recurrence ? { recurrence: recurrenceParsed.recurrence } : {})
169
242
  });
170
243
  }
171
244
  return { ok: true, items };
172
245
  }
173
246
  function mapTaskForTool(t) {
247
+ const recurrence = t.recurrence && typeof t.recurrence === 'object'
248
+ ? {
249
+ kind: t.recurrence.kind,
250
+ maxRuns: t.recurrence.maxRuns,
251
+ ...(t.recurrence.kind === 'interval' && typeof t.recurrence.everyMinutes === 'number'
252
+ ? { everyMinutes: t.recurrence.everyMinutes }
253
+ : {})
254
+ }
255
+ : undefined;
256
+ const remainingRuns = recurrence
257
+ ? Math.max(0, Math.floor(Number(recurrence.maxRuns) || 0) - Math.max(0, Math.floor(Number(t.deliveryCount) || 0)))
258
+ : undefined;
174
259
  return {
175
260
  taskId: t.taskId,
176
261
  dueAt: toIso(t.dueAtMs),
@@ -178,12 +263,14 @@ function mapTaskForTool(t) {
178
263
  ...(t.tool ? { tool: t.tool } : {}),
179
264
  ...(t.arguments ? { arguments: t.arguments } : {}),
180
265
  ...(typeof t.deliveredAtMs === 'number' ? { deliveredAt: toIso(t.deliveredAtMs) } : {}),
181
- deliveryCount: t.deliveryCount
266
+ deliveryCount: t.deliveryCount,
267
+ ...(recurrence ? { recurrence } : {}),
268
+ ...(typeof remainingRuns === 'number' ? { remainingRuns } : {})
182
269
  };
183
270
  }
184
271
  const handler = async (ctx) => {
185
- const toolCall = ctx.toolCall;
186
- if (!toolCall || toolCall.name !== 'clock') {
272
+ const toolCall = ensureClockToolCall(ctx.toolCall, ctx.requestId);
273
+ if (!toolCall) {
187
274
  return null;
188
275
  }
189
276
  const rt = readRuntimeMetadata(ctx.adapterContext);
@@ -324,6 +411,34 @@ const handler = async (ctx) => {
324
411
  logClock('cancel', { sessionId, taskId, removed });
325
412
  return respond({ ok: true, action, removed: removed ? taskId : null });
326
413
  }
414
+ if (action === 'update') {
415
+ const taskId = typeof parsedArgs.taskId === 'string' ? parsedArgs.taskId.trim() : '';
416
+ if (!taskId) {
417
+ logClock('update_invalid', { sessionId, action, reason: 'missing_task_id' });
418
+ return respond({ ok: false, action, message: 'clock.update requires taskId' });
419
+ }
420
+ const normalized = normalizeScheduleItems(parsedArgs);
421
+ if (!normalized.ok || normalized.items.length < 1) {
422
+ logClock('update_invalid', { sessionId, action, message: normalized.message ?? 'invalid update payload' });
423
+ return respond({ ok: false, action, message: normalized.message ?? 'clock.update requires items[0] with dueAt/task' });
424
+ }
425
+ const item = normalized.items[0];
426
+ const patch = {
427
+ dueAtMs: item.dueAtMs,
428
+ task: item.task,
429
+ resetDelivery: true,
430
+ ...(item.tool ? { tool: item.tool } : {}),
431
+ ...(item.arguments ? { arguments: item.arguments } : {}),
432
+ ...(item.recurrence ? { recurrence: item.recurrence } : {})
433
+ };
434
+ const updated = await updateClockTask(sessionId, taskId, patch, clockConfig);
435
+ if (!updated) {
436
+ logClock('update_miss', { sessionId, taskId });
437
+ return respond({ ok: false, action, message: 'clock.update failed: task not found or patch invalid' });
438
+ }
439
+ logClock('update', { sessionId, taskId });
440
+ return respond({ ok: true, action, updated: mapTaskForTool(updated) });
441
+ }
327
442
  const normalized = normalizeScheduleItems(parsedArgs);
328
443
  if (!normalized.ok) {
329
444
  logClock('schedule_invalid', { sessionId, message: normalized.message ?? 'invalid schedule items' });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,91 @@
1
+ import { registerServerToolHandler } from '../registry.js';
2
+ import { cloneJson } from '../server-side-tools.js';
3
+ const FLOW_ID = 'continue_execution_flow';
4
+ const TOOL_NAME = 'continue_execution';
5
+ function parseToolArguments(toolCall) {
6
+ if (!toolCall.arguments || typeof toolCall.arguments !== 'string') {
7
+ return {};
8
+ }
9
+ try {
10
+ const parsed = JSON.parse(toolCall.arguments);
11
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
12
+ }
13
+ catch {
14
+ return {};
15
+ }
16
+ }
17
+ function injectToolOutput(base, toolCall, content) {
18
+ const cloned = cloneJson(base);
19
+ const existingOutputs = Array.isArray(cloned.tool_outputs)
20
+ ? cloned.tool_outputs
21
+ : [];
22
+ let payloadText;
23
+ if (typeof content === 'string') {
24
+ payloadText = content;
25
+ }
26
+ else {
27
+ try {
28
+ payloadText = JSON.stringify(content ?? {});
29
+ }
30
+ catch {
31
+ payloadText = String(content ?? '');
32
+ }
33
+ }
34
+ cloned.tool_outputs = [
35
+ ...existingOutputs,
36
+ {
37
+ tool_call_id: toolCall.id,
38
+ name: TOOL_NAME,
39
+ content: payloadText
40
+ }
41
+ ];
42
+ return cloned;
43
+ }
44
+ function normalizeReason(parsed) {
45
+ const candidate = (typeof parsed.reason === 'string' ? parsed.reason : '') ||
46
+ (typeof parsed.note === 'string' ? parsed.note : '') ||
47
+ (typeof parsed.message === 'string' ? parsed.message : '');
48
+ return candidate.trim();
49
+ }
50
+ const handler = async (ctx) => {
51
+ const toolCall = ctx.toolCall;
52
+ if (!toolCall || toolCall.name !== TOOL_NAME) {
53
+ return null;
54
+ }
55
+ const parsed = parseToolArguments(toolCall);
56
+ const reason = normalizeReason(parsed);
57
+ const followupText = '继续执行当前任务,不要中断到总结/汇报。仅在任务真正完成后再给最终总结。该工具用于避免打断会话并提高执行效率。';
58
+ return {
59
+ flowId: FLOW_ID,
60
+ finalize: async () => {
61
+ const patched = injectToolOutput(ctx.base, toolCall, {
62
+ ok: true,
63
+ executed: true,
64
+ noop: true,
65
+ action: 'continue_execution',
66
+ ...(reason ? { reason } : {}),
67
+ message: 'No-op acknowledged. Continue execution without pausing for status summaries or ending turn early. If you were about to stop/end_turn, emit continue_execution first; server auto-followup keeps execution moving.'
68
+ });
69
+ return {
70
+ chatResponse: patched,
71
+ execution: {
72
+ flowId: FLOW_ID,
73
+ followup: {
74
+ requestIdSuffix: ':continue_execution_followup',
75
+ entryEndpoint: ctx.entryEndpoint,
76
+ injection: {
77
+ ops: [
78
+ { op: 'preserve_tools' },
79
+ { op: 'ensure_standard_tools' },
80
+ { op: 'append_assistant_message', required: true },
81
+ { op: 'append_tool_messages_from_tool_outputs', required: true },
82
+ { op: 'append_user_text', text: followupText }
83
+ ]
84
+ }
85
+ }
86
+ }
87
+ };
88
+ }
89
+ };
90
+ };
91
+ registerServerToolHandler(TOOL_NAME, handler);
@@ -327,6 +327,19 @@ function buildStandardFollowupTools() {
327
327
  }
328
328
  }
329
329
  },
330
+ {
331
+ type: 'function',
332
+ function: {
333
+ name: 'continue_execution',
334
+ description: 'No-op control tool. Use this before summaries or before ending the turn. If you are about to return stop/end_turn, emit continue_execution first instead of ending directly. Keep it lightweight; server auto-followup continues execution.',
335
+ parameters: {
336
+ type: 'object',
337
+ properties: {},
338
+ required: [],
339
+ additionalProperties: false
340
+ }
341
+ }
342
+ },
330
343
  {
331
344
  type: 'function',
332
345
  function: {
@@ -99,7 +99,7 @@ const handler = async (ctx) => {
99
99
  })
100
100
  };
101
101
  };
102
- registerServerToolHandler('empty_reply_continue', handler, { trigger: 'auto' });
102
+ registerServerToolHandler('empty_reply_continue', handler, { trigger: 'auto', hook: { phase: 'default', priority: 20 } });
103
103
  function decideEmptyReply(base) {
104
104
  // 1) OpenAI Chat shape
105
105
  const choices = Array.isArray(base.choices) ? base.choices : [];
@@ -69,7 +69,7 @@ const handler = async (ctx) => {
69
69
  })
70
70
  };
71
71
  };
72
- registerServerToolHandler('iflow_model_error_retry', handler, { trigger: 'auto' });
72
+ registerServerToolHandler('iflow_model_error_retry', handler, { trigger: 'auto', hook: { phase: 'pre', priority: 10 } });
73
73
  function getCapturedRequest(adapterContext) {
74
74
  if (!adapterContext || typeof adapterContext !== 'object') {
75
75
  return null;
@@ -332,4 +332,4 @@ const handler = async (ctx) => {
332
332
  sessionStates.set(sessionKey, state);
333
333
  return null;
334
334
  };
335
- registerServerToolHandler('recursive_detection_guard', handler, { trigger: 'auto' });
335
+ registerServerToolHandler('recursive_detection_guard', handler, { trigger: 'auto', hook: { phase: 'pre', priority: 5 } });