@jsonstudio/llms 0.6.1892 → 0.6.2172
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversion/compat/actions/deepseek-web-request.js +16 -2
- package/dist/conversion/compat/actions/deepseek-web-response.d.ts +7 -1
- package/dist/conversion/compat/actions/deepseek-web-response.js +302 -40
- package/dist/conversion/compat/actions/harvest-tool-calls-from-text.d.ts +5 -0
- package/dist/conversion/compat/actions/harvest-tool-calls-from-text.js +7 -4
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.d.ts +1 -0
- package/dist/conversion/compat/actions/iflow-tool-text-fallback.js +12 -0
- package/dist/conversion/compat/actions/strip-orphan-function-calls-tag.js +1 -1
- package/dist/conversion/compat/actions/tool-text-request-guidance.d.ts +9 -0
- package/dist/conversion/compat/actions/tool-text-request-guidance.js +177 -0
- package/dist/conversion/compat/antigravity-session-signature.d.ts +6 -0
- package/dist/conversion/compat/antigravity-session-signature.js +15 -0
- package/dist/conversion/compat/profiles/chat-deepseek-web.json +52 -1
- package/dist/conversion/compat/profiles/chat-glm.json +22 -0
- package/dist/conversion/compat/profiles/chat-iflow.json +4 -0
- package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +13 -27
- package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +10 -1
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +13 -4
- package/dist/conversion/hub/pipeline/compat/compat-profile-resolver.js +1 -53
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +8 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +8 -4
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +191 -9
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +118 -15
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +65 -2
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.d.ts +34 -0
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage3_servertool_orchestration/index.js +75 -0
- package/dist/conversion/hub/process/chat-process.js +85 -18
- package/dist/conversion/hub/response/provider-response.js +21 -50
- package/dist/conversion/hub/response/response-runtime.js +71 -10
- package/dist/conversion/responses/responses-openai-bridge/response-payload.d.ts +3 -0
- package/dist/conversion/responses/responses-openai-bridge/response-payload.js +576 -0
- package/dist/conversion/responses/responses-openai-bridge/types.d.ts +42 -0
- package/dist/conversion/responses/responses-openai-bridge/types.js +1 -0
- package/dist/conversion/responses/responses-openai-bridge.d.ts +3 -44
- package/dist/conversion/responses/responses-openai-bridge.js +193 -504
- package/dist/conversion/shared/anthropic-message-utils.js +82 -2
- package/dist/conversion/shared/bridge-message-utils.js +92 -39
- package/dist/conversion/shared/snapshot-hooks.js +8 -13
- package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-apply-patch.js +129 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-json.d.ts +4 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-json.js +637 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-shared.d.ts +21 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-shared.js +177 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.d.ts +5 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-transcript.js +385 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-xml.d.ts +10 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors-xml.js +602 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors.d.ts +5 -0
- package/dist/conversion/shared/text-markup-normalizer/extractors.js +4 -0
- package/dist/conversion/shared/text-markup-normalizer/normalize.d.ts +2 -0
- package/dist/conversion/shared/text-markup-normalizer/normalize.js +76 -0
- package/dist/conversion/shared/text-markup-normalizer.d.ts +3 -25
- package/dist/conversion/shared/text-markup-normalizer.js +2 -1386
- package/dist/conversion/shared/tool-governor.js +136 -10
- package/dist/filters/utils/snapshot-writer.js +3 -3
- package/dist/router/virtual-router/bootstrap/auth-utils.d.ts +6 -0
- package/dist/router/virtual-router/bootstrap/auth-utils.js +288 -0
- package/dist/router/virtual-router/bootstrap/claude-code-helpers.d.ts +11 -0
- package/dist/router/virtual-router/bootstrap/claude-code-helpers.js +18 -0
- package/dist/router/virtual-router/bootstrap/config-defaults.d.ts +5 -0
- package/dist/router/virtual-router/bootstrap/config-defaults.js +13 -0
- package/dist/router/virtual-router/bootstrap/config-normalizers.d.ts +4 -0
- package/dist/router/virtual-router/bootstrap/config-normalizers.js +106 -0
- package/dist/router/virtual-router/bootstrap/profile-builder.d.ts +7 -0
- package/dist/router/virtual-router/bootstrap/profile-builder.js +68 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.d.ts +40 -0
- package/dist/router/virtual-router/bootstrap/provider-normalization.js +212 -0
- package/dist/router/virtual-router/bootstrap/responses-helpers.d.ts +15 -0
- package/dist/router/virtual-router/bootstrap/responses-helpers.js +65 -0
- package/dist/router/virtual-router/bootstrap/routing-config.d.ts +23 -0
- package/dist/router/virtual-router/bootstrap/routing-config.js +293 -0
- package/dist/router/virtual-router/bootstrap/streaming-helpers.d.ts +12 -0
- package/dist/router/virtual-router/bootstrap/streaming-helpers.js +128 -0
- package/dist/router/virtual-router/bootstrap/utils.d.ts +5 -0
- package/dist/router/virtual-router/bootstrap/utils.js +41 -0
- package/dist/router/virtual-router/bootstrap/web-search-config.d.ts +4 -0
- package/dist/router/virtual-router/bootstrap/web-search-config.js +131 -0
- package/dist/router/virtual-router/bootstrap.d.ts +0 -4
- package/dist/router/virtual-router/bootstrap.js +31 -1275
- package/dist/router/virtual-router/classifier.js +32 -14
- package/dist/router/virtual-router/engine/antigravity/alias-lease.js +2 -2
- package/dist/router/virtual-router/engine/cooldown-manager.d.ts +34 -0
- package/dist/router/virtual-router/engine/cooldown-manager.js +118 -0
- package/dist/router/virtual-router/engine/route-analytics.d.ts +28 -0
- package/dist/router/virtual-router/engine/route-analytics.js +44 -0
- package/dist/router/virtual-router/engine/routing-pools/index.js +165 -4
- package/dist/router/virtual-router/engine/sticky-session-manager.d.ts +29 -0
- package/dist/router/virtual-router/engine/sticky-session-manager.js +55 -0
- package/dist/router/virtual-router/engine-logging.d.ts +42 -1
- package/dist/router/virtual-router/engine-logging.js +82 -15
- package/dist/router/virtual-router/engine-selection/multimodal-capability.d.ts +3 -0
- package/dist/router/virtual-router/engine-selection/multimodal-capability.js +26 -0
- package/dist/router/virtual-router/engine-selection/route-utils.js +6 -2
- package/dist/router/virtual-router/engine-selection/selection-deps.d.ts +1 -0
- package/dist/router/virtual-router/engine-selection/tier-selection.js +31 -1
- package/dist/router/virtual-router/engine.d.ts +21 -7
- package/dist/router/virtual-router/engine.js +198 -194
- package/dist/router/virtual-router/features.js +12 -4
- package/dist/router/virtual-router/message-utils.d.ts +8 -0
- package/dist/router/virtual-router/message-utils.js +170 -45
- package/dist/router/virtual-router/pre-command-file-resolver.js +40 -2
- package/dist/router/virtual-router/routing-instructions.d.ts +8 -0
- package/dist/router/virtual-router/routing-instructions.js +18 -2
- package/dist/router/virtual-router/routing-stop-message-actions.js +34 -10
- package/dist/router/virtual-router/routing-stop-message-state-codec.d.ts +2 -0
- package/dist/router/virtual-router/routing-stop-message-state-codec.js +50 -1
- package/dist/router/virtual-router/stop-message-state-sync.d.ts +1 -1
- package/dist/router/virtual-router/stop-message-state-sync.js +3 -0
- package/dist/router/virtual-router/token-counter.js +51 -10
- package/dist/router/virtual-router/tool-signals.js +4 -0
- package/dist/router/virtual-router/types.d.ts +15 -0
- package/dist/servertool/clock/session-scope.d.ts +3 -0
- package/dist/servertool/clock/session-scope.js +52 -0
- package/dist/servertool/clock/state.js +9 -0
- package/dist/servertool/clock/tasks.js +12 -1
- package/dist/servertool/clock/types.d.ts +3 -0
- package/dist/servertool/engine.js +177 -31
- package/dist/servertool/handlers/clock-auto.js +2 -8
- package/dist/servertool/handlers/clock.js +6 -9
- package/dist/servertool/handlers/recursive-detection-guard.js +53 -14
- package/dist/servertool/handlers/stop-message-auto/blocked-report.d.ts +16 -0
- package/dist/servertool/handlers/stop-message-auto/blocked-report.js +349 -0
- package/dist/servertool/handlers/stop-message-auto/iflow-followup.d.ts +23 -0
- package/dist/servertool/handlers/stop-message-auto/iflow-followup.js +503 -0
- package/dist/servertool/handlers/stop-message-auto/routing-state.d.ts +38 -0
- package/dist/servertool/handlers/stop-message-auto/routing-state.js +149 -0
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.d.ts +67 -0
- package/dist/servertool/handlers/stop-message-auto/runtime-utils.js +387 -0
- package/dist/servertool/handlers/stop-message-auto.d.ts +1 -1
- package/dist/servertool/handlers/stop-message-auto.js +80 -556
- package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.d.ts +18 -0
- package/dist/servertool/handlers/stop-message-stage-policy/bd-runtime.js +398 -0
- package/dist/servertool/handlers/stop-message-stage-policy/decision.d.ts +9 -0
- package/dist/servertool/handlers/stop-message-stage-policy/decision.js +127 -0
- package/dist/servertool/handlers/stop-message-stage-policy/observation.d.ts +2 -0
- package/dist/servertool/handlers/stop-message-stage-policy/observation.js +179 -0
- package/dist/servertool/handlers/stop-message-stage-policy/templates.d.ts +4 -0
- package/dist/servertool/handlers/stop-message-stage-policy/templates.js +96 -0
- package/dist/servertool/handlers/stop-message-stage-policy/text-utils.d.ts +9 -0
- package/dist/servertool/handlers/stop-message-stage-policy/text-utils.js +89 -0
- package/dist/servertool/handlers/stop-message-stage-policy/types.d.ts +59 -0
- package/dist/servertool/handlers/stop-message-stage-policy/types.js +1 -0
- package/dist/servertool/handlers/stop-message-stage-policy.d.ts +3 -43
- package/dist/servertool/handlers/stop-message-stage-policy.js +2 -684
- package/dist/servertool/handlers/web-search.js +117 -0
- package/dist/servertool/server-side-tools.d.ts +0 -1
- package/dist/servertool/server-side-tools.js +4 -3
- package/dist/sse/sse-to-json/builders/response-builder.js +16 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +1 -0
- package/dist/sse/sse-to-json/chat-sse-to-json-converter.js +110 -37
- package/dist/telemetry/stats-center.d.ts +9 -0
- package/dist/telemetry/stats-center.js +29 -1
- package/dist/tools/apply-patch/structured/coercion.js +3 -11
- package/dist/tools/exec-command/validator.d.ts +1 -0
- package/dist/tools/exec-command/validator.js +132 -0
- package/dist/tools/tool-registry.d.ts +1 -0
- package/dist/tools/tool-registry.js +1 -1
- package/package.json +1 -1
|
@@ -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)
|
|
@@ -236,6 +230,7 @@ function normalizeScheduleItems(parsed) {
|
|
|
236
230
|
items.push({
|
|
237
231
|
dueAtMs,
|
|
238
232
|
task,
|
|
233
|
+
setBy: 'agent',
|
|
239
234
|
...(tool ? { tool } : {}),
|
|
240
235
|
...(argsObj ? { arguments: argsObj } : {}),
|
|
241
236
|
...(recurrenceParsed.recurrence ? { recurrence: recurrenceParsed.recurrence } : {})
|
|
@@ -259,6 +254,8 @@ function mapTaskForTool(t) {
|
|
|
259
254
|
return {
|
|
260
255
|
taskId: t.taskId,
|
|
261
256
|
dueAt: toIso(t.dueAtMs),
|
|
257
|
+
setBy: t.setBy === 'agent' ? 'agent' : 'user',
|
|
258
|
+
setAt: toIso(t.createdAtMs),
|
|
262
259
|
task: t.task,
|
|
263
260
|
...(t.tool ? { tool: t.tool } : {}),
|
|
264
261
|
...(t.arguments ? { arguments: t.arguments } : {}),
|
|
@@ -274,7 +271,7 @@ const handler = async (ctx) => {
|
|
|
274
271
|
return null;
|
|
275
272
|
}
|
|
276
273
|
const rt = readRuntimeMetadata(ctx.adapterContext);
|
|
277
|
-
const sessionId =
|
|
274
|
+
const sessionId = resolveClockSessionScope(ctx.adapterContext, rt);
|
|
278
275
|
const rawConfig = rt?.clock ?? ctx.adapterContext.clock;
|
|
279
276
|
// Default-enable clock when config is absent, but keep "explicitly disabled" honored.
|
|
280
277
|
const clockConfig = resolveClockConfig(rawConfig);
|
|
@@ -388,7 +385,7 @@ const handler = async (ctx) => {
|
|
|
388
385
|
return respond({
|
|
389
386
|
ok: false,
|
|
390
387
|
action,
|
|
391
|
-
message: 'clock requires
|
|
388
|
+
message: 'clock requires session scope (sessionId/conversationId or clockDaemonId).'
|
|
392
389
|
});
|
|
393
390
|
}
|
|
394
391
|
if (action === 'list') {
|
|
@@ -252,13 +252,34 @@ const handler = async (ctx) => {
|
|
|
252
252
|
// Treat followup hops as an interruption: do not count them, and clear any ongoing streak
|
|
253
253
|
// so that post-followup calls always restart from 0.
|
|
254
254
|
if (shouldSkipFollowup(ctx.adapterContext)) {
|
|
255
|
-
sessionStates.
|
|
255
|
+
const prev = sessionStates.get(sessionKey);
|
|
256
|
+
if (prev) {
|
|
257
|
+
sessionStates.set(sessionKey, {
|
|
258
|
+
...prev,
|
|
259
|
+
updatedAt: now,
|
|
260
|
+
signature: undefined,
|
|
261
|
+
consecutiveCount: 0
|
|
262
|
+
});
|
|
263
|
+
}
|
|
256
264
|
return null;
|
|
257
265
|
}
|
|
258
|
-
const existing = sessionStates.get(sessionKey) ?? { updatedAt: now, consecutiveCount: 0 };
|
|
266
|
+
const existing = sessionStates.get(sessionKey) ?? { updatedAt: now, consecutiveCount: 0, triggerCount: 0 };
|
|
259
267
|
// Any interruption (no tool calls) resets the counter.
|
|
260
268
|
if (!ctx.toolCalls || !ctx.toolCalls.length) {
|
|
261
|
-
if (existing.signature || existing.consecutiveCount) {
|
|
269
|
+
if (existing.signature || existing.consecutiveCount || existing.triggerCount) {
|
|
270
|
+
if (existing.triggerCount > 0) {
|
|
271
|
+
sessionStates.set(sessionKey, {
|
|
272
|
+
...existing,
|
|
273
|
+
updatedAt: now,
|
|
274
|
+
signature: undefined,
|
|
275
|
+
consecutiveCount: 0
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
sessionStates.delete(sessionKey);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
262
283
|
sessionStates.delete(sessionKey);
|
|
263
284
|
}
|
|
264
285
|
return null;
|
|
@@ -282,8 +303,21 @@ const handler = async (ctx) => {
|
|
|
282
303
|
if (state.consecutiveCount < CONSECUTIVE_TRIGGER_COUNT) {
|
|
283
304
|
continue;
|
|
284
305
|
}
|
|
285
|
-
|
|
286
|
-
|
|
306
|
+
const triggerCount = (state.triggerCount || 0) + 1;
|
|
307
|
+
const escalatedStop = triggerCount >= 2;
|
|
308
|
+
// Triggered: clear streak immediately; escalate on second trigger.
|
|
309
|
+
// After escalated stop, clear state so next execution starts fresh.
|
|
310
|
+
if (escalatedStop) {
|
|
311
|
+
sessionStates.delete(sessionKey);
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
sessionStates.set(sessionKey, {
|
|
315
|
+
updatedAt: now,
|
|
316
|
+
signature: undefined,
|
|
317
|
+
consecutiveCount: 0,
|
|
318
|
+
triggerCount
|
|
319
|
+
});
|
|
320
|
+
}
|
|
287
321
|
// Must send a followup request to provider (not a direct client warning).
|
|
288
322
|
if (!ctx.capabilities.reenterPipeline) {
|
|
289
323
|
return null;
|
|
@@ -304,9 +338,19 @@ const handler = async (ctx) => {
|
|
|
304
338
|
if (!toolMessages.length) {
|
|
305
339
|
return null;
|
|
306
340
|
}
|
|
307
|
-
const reminder =
|
|
308
|
-
|
|
309
|
-
|
|
341
|
+
const reminder = escalatedStop
|
|
342
|
+
? `再次检测到同一循环(同一工具同一参数连续 ${CONSECUTIVE_TRIGGER_COUNT} 次:${toolCall.name})。` +
|
|
343
|
+
`现在停止该工具链路并直接报告阻塞点,等待用户处理后再继续。`
|
|
344
|
+
: `检测到循环调用(同一工具同一参数连续 ${CONSECUTIVE_TRIGGER_COUNT} 次:${toolCall.name})。` +
|
|
345
|
+
`请先简短提醒风险并清理当前循环计数,然后继续自动执行;若再次出现同样循环,立即停止并报告阻塞。`;
|
|
346
|
+
const ops = [
|
|
347
|
+
{ op: 'inject_system_text', text: reminder },
|
|
348
|
+
{ op: 'append_assistant_message' },
|
|
349
|
+
{ op: 'append_tool_messages_from_tool_outputs' }
|
|
350
|
+
];
|
|
351
|
+
if (escalatedStop) {
|
|
352
|
+
ops.push({ op: 'drop_tool_by_name', name: toolCall.name });
|
|
353
|
+
}
|
|
310
354
|
return {
|
|
311
355
|
flowId: FLOW_ID,
|
|
312
356
|
finalize: async () => ({
|
|
@@ -317,12 +361,7 @@ const handler = async (ctx) => {
|
|
|
317
361
|
requestIdSuffix: ':recursive_detection_guard_followup',
|
|
318
362
|
entryEndpoint: ctx.entryEndpoint,
|
|
319
363
|
injection: {
|
|
320
|
-
ops
|
|
321
|
-
{ op: 'inject_system_text', text: reminder },
|
|
322
|
-
{ op: 'append_assistant_message' },
|
|
323
|
-
{ op: 'append_tool_messages_from_tool_outputs' },
|
|
324
|
-
{ op: 'drop_tool_by_name', name: toolCall.name }
|
|
325
|
-
]
|
|
364
|
+
ops
|
|
326
365
|
}
|
|
327
366
|
}
|
|
328
367
|
}
|
|
@@ -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;
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import * as childProcess from 'node:child_process';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
const STOP_MESSAGE_BD_CREATE_TIMEOUT_MS = 2_000;
|
|
5
|
+
const STOP_MESSAGE_BLOCKED_TEXT_SCAN_LIMIT = 12;
|
|
6
|
+
const STOP_MESSAGE_BLOCKED_CANDIDATE_MAX_LENGTH = 12_000;
|
|
7
|
+
export function extractBlockedReportFromMessages(messages) {
|
|
8
|
+
if (!Array.isArray(messages) || messages.length === 0) {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
const start = Math.max(0, messages.length - STOP_MESSAGE_BLOCKED_TEXT_SCAN_LIMIT);
|
|
12
|
+
for (let idx = messages.length - 1; idx >= start; idx -= 1) {
|
|
13
|
+
const text = extractCapturedMessageText(messages[idx]);
|
|
14
|
+
if (!text) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const blockedReport = extractBlockedReportFromText(text);
|
|
18
|
+
if (blockedReport) {
|
|
19
|
+
return blockedReport;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
export function extractBlockedReportFromMessagesForTests(messages) {
|
|
25
|
+
return extractBlockedReportFromMessages(messages);
|
|
26
|
+
}
|
|
27
|
+
export function createBdIssueFromBlockedReport(blockedReport, context, cwdOverride) {
|
|
28
|
+
const cwd = resolveBdWorkingDirectoryForStopMessage(cwdOverride);
|
|
29
|
+
const title = buildBlockedIssueTitle(blockedReport);
|
|
30
|
+
const description = buildBlockedIssueDescription(blockedReport, context);
|
|
31
|
+
const acceptance = buildBlockedIssueAcceptance(blockedReport);
|
|
32
|
+
try {
|
|
33
|
+
const result = childProcess.spawnSync('bd', [
|
|
34
|
+
'--no-db',
|
|
35
|
+
'create',
|
|
36
|
+
'--json',
|
|
37
|
+
'-t',
|
|
38
|
+
'bug',
|
|
39
|
+
'-p',
|
|
40
|
+
'0',
|
|
41
|
+
'--title',
|
|
42
|
+
title,
|
|
43
|
+
'--description',
|
|
44
|
+
description,
|
|
45
|
+
'--acceptance',
|
|
46
|
+
acceptance
|
|
47
|
+
], {
|
|
48
|
+
cwd,
|
|
49
|
+
encoding: 'utf8',
|
|
50
|
+
timeout: STOP_MESSAGE_BD_CREATE_TIMEOUT_MS,
|
|
51
|
+
maxBuffer: 1024 * 1024
|
|
52
|
+
});
|
|
53
|
+
if (result.error || result.status !== 0) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return parseCreatedIssueId(result.stdout);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function extractCapturedMessageText(message) {
|
|
63
|
+
if (!message) {
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
if (typeof message === 'string') {
|
|
67
|
+
return message.trim();
|
|
68
|
+
}
|
|
69
|
+
if (typeof message !== 'object' || Array.isArray(message)) {
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
const record = message;
|
|
73
|
+
const contentText = extractTextFromMessageContent(record.content);
|
|
74
|
+
if (contentText) {
|
|
75
|
+
return contentText;
|
|
76
|
+
}
|
|
77
|
+
const inputText = extractTextFromMessageContent(record.input);
|
|
78
|
+
if (inputText) {
|
|
79
|
+
return inputText;
|
|
80
|
+
}
|
|
81
|
+
const outputText = extractTextFromMessageContent(record.output);
|
|
82
|
+
if (outputText) {
|
|
83
|
+
return outputText;
|
|
84
|
+
}
|
|
85
|
+
const argumentsText = toNonEmptyText(record.arguments);
|
|
86
|
+
return argumentsText;
|
|
87
|
+
}
|
|
88
|
+
export function extractTextFromMessageContent(content) {
|
|
89
|
+
if (typeof content === 'string') {
|
|
90
|
+
return content.trim();
|
|
91
|
+
}
|
|
92
|
+
if (!Array.isArray(content)) {
|
|
93
|
+
return '';
|
|
94
|
+
}
|
|
95
|
+
const chunks = [];
|
|
96
|
+
for (const item of content) {
|
|
97
|
+
if (typeof item === 'string') {
|
|
98
|
+
if (item.trim())
|
|
99
|
+
chunks.push(item.trim());
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!item || typeof item !== 'object' || Array.isArray(item)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const record = item;
|
|
106
|
+
const type = toNonEmptyText(record.type).toLowerCase();
|
|
107
|
+
if (type === 'text' || type === 'output_text' || type === 'input_text' || !type) {
|
|
108
|
+
const text = toNonEmptyText(record.text);
|
|
109
|
+
if (text) {
|
|
110
|
+
chunks.push(text);
|
|
111
|
+
}
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const fallbackText = toNonEmptyText(record.content) || toNonEmptyText(record.value);
|
|
115
|
+
if (fallbackText) {
|
|
116
|
+
chunks.push(fallbackText);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return chunks.join('\n').trim();
|
|
120
|
+
}
|
|
121
|
+
function extractBlockedReportFromText(text) {
|
|
122
|
+
const trimmed = typeof text === 'string' ? text.trim() : '';
|
|
123
|
+
if (!trimmed) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
const candidates = [];
|
|
127
|
+
const pushCandidate = (candidate) => {
|
|
128
|
+
const normalized = candidate.trim();
|
|
129
|
+
if (!normalized || normalized.length > STOP_MESSAGE_BLOCKED_CANDIDATE_MAX_LENGTH) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (candidates.includes(normalized)) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
candidates.push(normalized);
|
|
136
|
+
};
|
|
137
|
+
pushCandidate(trimmed);
|
|
138
|
+
for (const codeBlock of extractJsonCodeBlocks(trimmed)) {
|
|
139
|
+
pushCandidate(codeBlock);
|
|
140
|
+
}
|
|
141
|
+
for (const objectText of extractBalancedJsonObjectStrings(trimmed)) {
|
|
142
|
+
if (objectText.includes('"type"') && objectText.toLowerCase().includes('"blocked"')) {
|
|
143
|
+
pushCandidate(objectText);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const candidate of candidates) {
|
|
147
|
+
let parsed;
|
|
148
|
+
try {
|
|
149
|
+
parsed = JSON.parse(candidate);
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const report = normalizeBlockedReport(parsed);
|
|
155
|
+
if (report) {
|
|
156
|
+
return report;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
function normalizeBlockedReport(value) {
|
|
162
|
+
if (Array.isArray(value)) {
|
|
163
|
+
for (const entry of value) {
|
|
164
|
+
const report = normalizeBlockedReport(entry);
|
|
165
|
+
if (report) {
|
|
166
|
+
return report;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
if (!value || typeof value !== 'object') {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const record = value;
|
|
175
|
+
const type = toNonEmptyText(record.type).toLowerCase();
|
|
176
|
+
if (type !== 'blocked') {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
const summary = toNonEmptyText(record.summary) ||
|
|
180
|
+
toNonEmptyText(record.title) ||
|
|
181
|
+
toNonEmptyText(record.problem);
|
|
182
|
+
const blocker = toNonEmptyText(record.blocker) ||
|
|
183
|
+
toNonEmptyText(record.reason) ||
|
|
184
|
+
toNonEmptyText(record.blocked_by);
|
|
185
|
+
if (!summary || !blocker) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
const impact = toNonEmptyText(record.impact) || toNonEmptyText(record.effect);
|
|
189
|
+
const nextAction = toNonEmptyText(record.next_action) ||
|
|
190
|
+
toNonEmptyText(record.nextAction) ||
|
|
191
|
+
toNonEmptyText(record.next_step);
|
|
192
|
+
const evidence = normalizeBlockedEvidence(record.evidence);
|
|
193
|
+
return {
|
|
194
|
+
summary: summary.slice(0, 1_000),
|
|
195
|
+
blocker: blocker.slice(0, 1_000),
|
|
196
|
+
...(impact ? { impact: impact.slice(0, 1_000) } : {}),
|
|
197
|
+
...(nextAction ? { nextAction: nextAction.slice(0, 1_000) } : {}),
|
|
198
|
+
evidence
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function normalizeBlockedEvidence(raw) {
|
|
202
|
+
if (Array.isArray(raw)) {
|
|
203
|
+
const normalized = raw
|
|
204
|
+
.map((entry) => toNonEmptyText(entry))
|
|
205
|
+
.filter((entry) => entry.length > 0)
|
|
206
|
+
.map((entry) => entry.slice(0, 800));
|
|
207
|
+
return normalized.slice(0, 8);
|
|
208
|
+
}
|
|
209
|
+
const single = toNonEmptyText(raw);
|
|
210
|
+
return single ? [single.slice(0, 800)] : [];
|
|
211
|
+
}
|
|
212
|
+
function extractJsonCodeBlocks(text) {
|
|
213
|
+
const candidates = [];
|
|
214
|
+
const regex = /```(?:json)?\s*([\s\S]*?)```/gi;
|
|
215
|
+
let match;
|
|
216
|
+
while ((match = regex.exec(text)) !== null) {
|
|
217
|
+
const body = (match[1] || '').trim();
|
|
218
|
+
if (!body) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
candidates.push(body);
|
|
222
|
+
}
|
|
223
|
+
return candidates;
|
|
224
|
+
}
|
|
225
|
+
function extractBalancedJsonObjectStrings(text) {
|
|
226
|
+
const results = [];
|
|
227
|
+
let start = -1;
|
|
228
|
+
let depth = 0;
|
|
229
|
+
let inString = false;
|
|
230
|
+
let escaped = false;
|
|
231
|
+
for (let idx = 0; idx < text.length; idx += 1) {
|
|
232
|
+
const ch = text[idx];
|
|
233
|
+
if (inString) {
|
|
234
|
+
if (escaped) {
|
|
235
|
+
escaped = false;
|
|
236
|
+
}
|
|
237
|
+
else if (ch === '\\') {
|
|
238
|
+
escaped = true;
|
|
239
|
+
}
|
|
240
|
+
else if (ch === '"') {
|
|
241
|
+
inString = false;
|
|
242
|
+
}
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (ch === '"') {
|
|
246
|
+
inString = true;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
if (ch === '{') {
|
|
250
|
+
if (depth === 0) {
|
|
251
|
+
start = idx;
|
|
252
|
+
}
|
|
253
|
+
depth += 1;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (ch === '}') {
|
|
257
|
+
if (depth <= 0) {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
depth -= 1;
|
|
261
|
+
if (depth === 0 && start >= 0) {
|
|
262
|
+
results.push(text.slice(start, idx + 1));
|
|
263
|
+
start = -1;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return results;
|
|
268
|
+
}
|
|
269
|
+
function resolveBdWorkingDirectoryForStopMessage(cwdOverride) {
|
|
270
|
+
const fromOverride = toNonEmptyText(cwdOverride);
|
|
271
|
+
if (fromOverride) {
|
|
272
|
+
return path.resolve(fromOverride);
|
|
273
|
+
}
|
|
274
|
+
const fromEnv = toNonEmptyText(process.env.ROUTECODEX_STOPMESSAGE_BD_WORKDIR);
|
|
275
|
+
if (fromEnv) {
|
|
276
|
+
return path.resolve(fromEnv);
|
|
277
|
+
}
|
|
278
|
+
const cwd = process.cwd();
|
|
279
|
+
return findBdProjectRootForStopMessage(cwd) || cwd;
|
|
280
|
+
}
|
|
281
|
+
function findBdProjectRootForStopMessage(startDirectory) {
|
|
282
|
+
let current = path.resolve(startDirectory);
|
|
283
|
+
while (true) {
|
|
284
|
+
const beadFile = path.join(current, '.beads', 'issues.jsonl');
|
|
285
|
+
if (fs.existsSync(beadFile)) {
|
|
286
|
+
return current;
|
|
287
|
+
}
|
|
288
|
+
const parent = path.dirname(current);
|
|
289
|
+
if (parent === current) {
|
|
290
|
+
return null;
|
|
291
|
+
}
|
|
292
|
+
current = parent;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function buildBlockedIssueTitle(report) {
|
|
296
|
+
const base = report.summary.trim() || report.blocker.trim() || 'stopMessage blocked';
|
|
297
|
+
const cleaned = base.replace(/\s+/g, ' ').slice(0, 120);
|
|
298
|
+
return `[stopMessage] ${cleaned}`.trim();
|
|
299
|
+
}
|
|
300
|
+
function buildBlockedIssueDescription(report, context) {
|
|
301
|
+
const lines = [
|
|
302
|
+
'自动建单来源:stop_message_auto 结构化 blocked 报告',
|
|
303
|
+
'',
|
|
304
|
+
`Summary: ${report.summary}`,
|
|
305
|
+
`Blocker: ${report.blocker}`,
|
|
306
|
+
`Impact: ${report.impact || 'n/a'}`,
|
|
307
|
+
`Next Action: ${report.nextAction || 'n/a'}`,
|
|
308
|
+
'',
|
|
309
|
+
`RequestId: ${context?.requestId || 'n/a'}`,
|
|
310
|
+
`SessionId: ${context?.sessionId || 'n/a'}`,
|
|
311
|
+
'',
|
|
312
|
+
'Evidence:',
|
|
313
|
+
...(report.evidence.length > 0 ? report.evidence.map((entry) => `- ${entry}`) : ['- n/a']),
|
|
314
|
+
'',
|
|
315
|
+
'Notes:',
|
|
316
|
+
'- 本 issue 由系统在 stopMessage 检测到结构化阻塞后自动创建。',
|
|
317
|
+
'- 请按 blocker/next action 先解除阻塞,再恢复执行。'
|
|
318
|
+
];
|
|
319
|
+
return lines.join('\n');
|
|
320
|
+
}
|
|
321
|
+
function buildBlockedIssueAcceptance(report) {
|
|
322
|
+
const next = report.nextAction || '执行可验证的解阻动作并记录结果';
|
|
323
|
+
return [
|
|
324
|
+
`1. 明确并确认 blocker:${report.blocker}`,
|
|
325
|
+
`2. 完成解阻动作:${next}`,
|
|
326
|
+
'3. 验证 stopMessage followup 可继续推进'
|
|
327
|
+
].join('\n');
|
|
328
|
+
}
|
|
329
|
+
function parseCreatedIssueId(stdout) {
|
|
330
|
+
if (typeof stdout !== 'string') {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
const trimmed = stdout.trim();
|
|
334
|
+
if (!trimmed) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
try {
|
|
338
|
+
const parsed = JSON.parse(trimmed);
|
|
339
|
+
const id = toNonEmptyText(parsed.id);
|
|
340
|
+
return id || null;
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
const match = trimmed.match(/\b[a-z]+-\d+(?:\.\d+)?\b/i);
|
|
344
|
+
return match ? match[0] : null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function toNonEmptyText(value) {
|
|
348
|
+
return typeof value === 'string' && value.trim() ? value.trim() : '';
|
|
349
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface StopMessageAutoResponseSnapshot {
|
|
2
|
+
providerProtocol?: string;
|
|
3
|
+
finishReason?: string;
|
|
4
|
+
assistantText?: string;
|
|
5
|
+
reasoningText?: string;
|
|
6
|
+
responseExcerpt?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function renderStopMessageAutoFollowupViaIflow(args: {
|
|
9
|
+
baseStopMessageText: string;
|
|
10
|
+
candidateFollowupText: string;
|
|
11
|
+
responseSnapshot: StopMessageAutoResponseSnapshot;
|
|
12
|
+
requestId?: string;
|
|
13
|
+
sessionId?: string;
|
|
14
|
+
providerKey?: string;
|
|
15
|
+
model?: string;
|
|
16
|
+
usedRepeats: number;
|
|
17
|
+
maxRepeats: number;
|
|
18
|
+
}): string | null;
|
|
19
|
+
export declare function extractStopMessageAutoResponseSnapshot(base: unknown, adapterContext: unknown): StopMessageAutoResponseSnapshot;
|
|
20
|
+
export declare function extractResponsesOutputText(base: {
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}): string;
|
|
23
|
+
export declare function hasToolLikeOutput(value: unknown): boolean;
|