@jsonstudio/llms 0.6.1749 → 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 +325 -38
  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 +11 -1
  40. package/dist/router/virtual-router/routing-instructions.js +101 -183
  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 +1 -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 +15 -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 +352 -257
  85. package/dist/servertool/handlers/stop-message-stage-policy.d.ts +22 -1
  86. package/dist/servertool/handlers/stop-message-stage-policy.js +472 -60
  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,22 +1,43 @@
1
1
  export type StopMessageStageName = 'status_probe' | 'active_continue' | 'loop_self_check';
2
2
  export type StopMessageBdWorkState = 'active' | 'idle' | 'unknown';
3
+ export type StopMessageBdMode = 'auto' | 'runtime' | 'heuristic';
3
4
  export interface StopMessageStageStateSnapshot {
4
5
  stopMessageStage?: string;
6
+ stopMessageStageMode?: 'on' | 'off' | 'auto' | string;
5
7
  stopMessageObservationHash?: string;
6
8
  stopMessageObservationStableCount?: number;
7
9
  stopMessageBdWorkState?: string;
8
10
  }
11
+ export interface StopMessageBdCommandResult {
12
+ status: number | null;
13
+ stdout: string;
14
+ stderr: string;
15
+ error?: unknown;
16
+ }
17
+ export type StopMessageBdCommandRunner = (args: string[], options: {
18
+ cwd: string;
19
+ timeoutMs: number;
20
+ }) => StopMessageBdCommandResult;
9
21
  export interface StopMessageStageDecision {
10
22
  action: 'followup' | 'stop';
11
23
  stage?: StopMessageStageName;
12
24
  followupText?: string;
13
25
  observationHash: string;
14
26
  observationStableCount: number;
27
+ toolSignatureHash?: string;
15
28
  bdWorkState: StopMessageBdWorkState;
16
- stopReason?: 'loop_stable' | 'bd_idle';
29
+ stopReason?: 'loop_stable' | 'bd_idle' | 'mode_off';
17
30
  }
18
31
  export declare function resolveStopMessageStageDecision(args: {
19
32
  baseText: string;
20
33
  state: StopMessageStageStateSnapshot;
21
34
  capturedMessages: unknown[];
35
+ bdCommandRunner?: StopMessageBdCommandRunner;
36
+ nowMs?: number;
22
37
  }): StopMessageStageDecision;
38
+ export declare function resolveBdWorkStateFromRuntime(options?: {
39
+ bdCommandRunner?: StopMessageBdCommandRunner;
40
+ nowMs?: number;
41
+ cwd?: string;
42
+ }): StopMessageBdWorkState;
43
+ export declare function resetStopMessageBdRuntimeCacheForTests(): void;
@@ -1,18 +1,24 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { resolveStopMessageText } from '../../router/virtual-router/stop-message-file-resolver.js';
2
+ import * as childProcess from 'node:child_process';
3
+ import * as fs from 'node:fs';
4
+ import * as path from 'node:path';
5
+ import { loadStopMessageStageTemplate } from '../../router/virtual-router/stop-message-stage-template-files.js';
3
6
  const DEFAULT_STATUS_TEMPLATE = [
4
7
  '先做状态确认再继续执行:',
5
- '1) 运行 `bd --no-db ready`,确认是否存在 open/in_progress 任务;',
6
- '2) 若有当前任务,继续执行下一步并更新状态;',
7
- '3) 若没有可推进任务,直接说明并停止。',
8
+ '1) 运行 `bd --no-db ready` 与 `bd --no-db list --status in_progress --json --limit 5`,确认是否有可继续任务;',
9
+ '2) 若存在 blocked,先分析阻塞原因并制定解决方案;若仅有 in_progress/open,则直接推进可验证动作;',
10
+ '3) 若 ready 为空,再检查 `bd --no-db list --status blocked --json --limit 5` 与 `bd --no-db epic status`,判断是否仍有未完成工作;',
11
+ '4) 若仍有未完成工作,优先解 blocked;必要时用 bd update/dep add/create 同步计划后继续执行;',
12
+ '5) 仅当确认确实无任务可推进时,基于当前上下文判断是否需要 `bd --no-db create` 新任务;不需要才停止。',
8
13
  '',
9
14
  '{{BASE_STOP_MESSAGE}}'
10
15
  ].join('\n');
11
16
  const DEFAULT_ACTIVE_TEMPLATE = [
12
- '检测到当前存在进行中任务(in_progress),必须继续执行,不允许只汇报状态:',
13
- '1) 继续当前 in_progress 任务并执行至少一个可验证动作(改代码/跑测试/更新证据);',
14
- '2) 未完成上述动作前,不要输出“已完成/已汇报”类结论;',
15
- '3) 完成动作后只汇报结果与下一步。',
17
+ '检测到当前仍有任务,按优先级继续执行,不允许只汇报状态:',
18
+ '1) 先检查是否存在 blocked 任务;若存在,先分析阻塞原因并执行解阻塞动作(补依赖/修配置/拆子任务/补证据);',
19
+ '2) 需要调整执行顺序或新增动作时,必须用 bd 更新计划与状态(update/dep add/create),保持可追踪;',
20
+ '3) 若无 blocked,再继续当前 in_progress/open 任务并执行至少一个可验证动作(改代码/跑测试/更新证据);',
21
+ '4) 未完成上述动作前,不要输出“已完成/已汇报”类结论;完成后只汇报结果与下一步。',
16
22
  '',
17
23
  '{{BASE_STOP_MESSAGE}}'
18
24
  ].join('\n');
@@ -24,35 +30,57 @@ const DEFAULT_LOOP_TEMPLATE = [
24
30
  '',
25
31
  '{{BASE_STOP_MESSAGE}}'
26
32
  ].join('\n');
27
- const STAGE_TEMPLATE_REFS = {
28
- status_probe: '<file://stopMessage/stage-status-check.md>',
29
- active_continue: '<file://stopMessage/stage-active-continue.md>',
30
- loop_self_check: '<file://stopMessage/stage-loop-self-check.md>'
31
- };
33
+ let bdRuntimeCache = null;
34
+ const DEFAULT_BD_TIMEOUT_MS = 1200;
35
+ const DEFAULT_BD_CACHE_TTL_MS = 1500;
32
36
  export function resolveStopMessageStageDecision(args) {
33
- const templates = resolveStageTemplates();
37
+ const mode = resolveStageMode(args.state.stopMessageStageMode);
34
38
  const observationHash = buildObservationHash(args.capturedMessages);
39
+ const toolSignatureHash = buildToolSignatureHash(args.capturedMessages);
40
+ if (mode === 'off') {
41
+ return {
42
+ action: 'stop',
43
+ observationHash,
44
+ observationStableCount: 0,
45
+ bdWorkState: 'unknown',
46
+ stopReason: 'mode_off',
47
+ ...(toolSignatureHash ? { toolSignatureHash } : {})
48
+ };
49
+ }
50
+ const templates = resolveStageTemplates(mode);
35
51
  if (!templates.enabled) {
36
52
  return {
37
53
  action: 'followup',
38
54
  followupText: args.baseText,
39
55
  observationHash,
40
56
  observationStableCount: 0,
41
- bdWorkState: 'unknown'
57
+ bdWorkState: 'unknown',
58
+ ...(toolSignatureHash ? { toolSignatureHash } : {})
42
59
  };
43
60
  }
44
61
  const previousHash = normalizeText(args.state.stopMessageObservationHash);
45
62
  const previousStableCount = toSafeInt(args.state.stopMessageObservationStableCount, 0);
46
63
  const stableCount = previousHash && previousHash === observationHash ? previousStableCount + 1 : 0;
47
- const bdWorkState = detectBdWorkState(args.capturedMessages);
64
+ const bdWorkState = detectBdWorkState(args.capturedMessages, {
65
+ bdCommandRunner: args.bdCommandRunner,
66
+ nowMs: args.nowMs
67
+ });
68
+ const previousStage = normalizeStageName(args.state.stopMessageStage);
69
+ const previousBdWorkState = normalizeBdWorkState(args.state.stopMessageBdWorkState);
70
+ const idleConfirmed = bdWorkState === 'idle' &&
71
+ previousBdWorkState === 'idle' &&
72
+ previousStage === 'status_probe';
48
73
  if (bdWorkState === 'idle') {
49
- return {
50
- action: 'stop',
51
- observationHash,
52
- observationStableCount: stableCount,
53
- bdWorkState,
54
- stopReason: 'bd_idle'
55
- };
74
+ if (mode !== 'on' || idleConfirmed) {
75
+ return {
76
+ action: 'stop',
77
+ observationHash,
78
+ observationStableCount: stableCount,
79
+ bdWorkState,
80
+ stopReason: 'bd_idle',
81
+ ...(toolSignatureHash ? { toolSignatureHash } : {})
82
+ };
83
+ }
56
84
  }
57
85
  if (stableCount >= 2) {
58
86
  return {
@@ -60,7 +88,8 @@ export function resolveStopMessageStageDecision(args) {
60
88
  observationHash,
61
89
  observationStableCount: stableCount,
62
90
  bdWorkState,
63
- stopReason: 'loop_stable'
91
+ stopReason: 'loop_stable',
92
+ ...(toolSignatureHash ? { toolSignatureHash } : {})
64
93
  };
65
94
  }
66
95
  const stage = bdWorkState === 'active'
@@ -80,29 +109,87 @@ export function resolveStopMessageStageDecision(args) {
80
109
  followupText,
81
110
  observationHash,
82
111
  observationStableCount: stableCount,
83
- bdWorkState
112
+ bdWorkState,
113
+ ...(toolSignatureHash ? { toolSignatureHash } : {})
84
114
  };
85
115
  }
116
+ export function resolveBdWorkStateFromRuntime(options) {
117
+ const mode = resolveBdMode();
118
+ const nowMs = typeof options?.nowMs === 'number' && Number.isFinite(options.nowMs) ? options.nowMs : Date.now();
119
+ const cwd = resolveBdWorkingDirectory(options?.cwd);
120
+ const ttlMs = resolveBdCacheTtlMs();
121
+ if (ttlMs > 0 &&
122
+ bdRuntimeCache &&
123
+ bdRuntimeCache.mode === mode &&
124
+ bdRuntimeCache.cwd === cwd &&
125
+ bdRuntimeCache.expiresAt > nowMs) {
126
+ return bdRuntimeCache.state;
127
+ }
128
+ const commandRunner = options?.bdCommandRunner ?? runBdCommand;
129
+ const timeoutMs = resolveBdTimeoutMs();
130
+ const inProgress = runBdQuery(commandRunner, ['--no-db', 'list', '--status', 'in_progress', '--json', '--limit', '1'], {
131
+ cwd,
132
+ timeoutMs
133
+ });
134
+ if (inProgress === 'active') {
135
+ return writeBdRuntimeCacheAndReturn(mode, cwd, nowMs, ttlMs, 'active');
136
+ }
137
+ if (inProgress === 'unknown') {
138
+ return writeBdRuntimeCacheAndReturn(mode, cwd, nowMs, ttlMs, 'unknown');
139
+ }
140
+ const ready = runBdQuery(commandRunner, ['--no-db', 'ready', '--json', '--limit', '1'], {
141
+ cwd,
142
+ timeoutMs
143
+ });
144
+ if (ready === 'active') {
145
+ return writeBdRuntimeCacheAndReturn(mode, cwd, nowMs, ttlMs, 'active');
146
+ }
147
+ if (ready === 'unknown') {
148
+ return writeBdRuntimeCacheAndReturn(mode, cwd, nowMs, ttlMs, 'unknown');
149
+ }
150
+ const open = runBdQuery(commandRunner, ['--no-db', 'list', '--status', 'open', '--json', '--limit', '1'], {
151
+ cwd,
152
+ timeoutMs
153
+ });
154
+ if (open === 'active') {
155
+ return writeBdRuntimeCacheAndReturn(mode, cwd, nowMs, ttlMs, 'active');
156
+ }
157
+ if (open === 'unknown') {
158
+ return writeBdRuntimeCacheAndReturn(mode, cwd, nowMs, ttlMs, 'unknown');
159
+ }
160
+ const blocked = runBdQuery(commandRunner, ['--no-db', 'list', '--status', 'blocked', '--json', '--limit', '1'], {
161
+ cwd,
162
+ timeoutMs
163
+ });
164
+ if (blocked === 'active') {
165
+ return writeBdRuntimeCacheAndReturn(mode, cwd, nowMs, ttlMs, 'active');
166
+ }
167
+ if (blocked === 'unknown') {
168
+ return writeBdRuntimeCacheAndReturn(mode, cwd, nowMs, ttlMs, 'unknown');
169
+ }
170
+ return writeBdRuntimeCacheAndReturn(mode, cwd, nowMs, ttlMs, 'idle');
171
+ }
172
+ export function resetStopMessageBdRuntimeCacheForTests() {
173
+ bdRuntimeCache = null;
174
+ }
86
175
  function buildStageMessage(template, baseText) {
87
176
  const safeBaseText = baseText.trim();
88
- if (!safeBaseText) {
89
- return template.trim();
177
+ const safeTemplate = typeof template === 'string' ? template.trim() : '';
178
+ if (safeTemplate.includes('{{BASE_STOP_MESSAGE}}')) {
179
+ const replaced = safeTemplate.replaceAll('{{BASE_STOP_MESSAGE}}', safeBaseText).replace(/\n{3,}/g, '\n\n').trim();
180
+ if (replaced) {
181
+ return replaced;
182
+ }
90
183
  }
91
- if (template.includes('{{BASE_STOP_MESSAGE}}')) {
92
- return template.replaceAll('{{BASE_STOP_MESSAGE}}', safeBaseText).trim();
184
+ if (!safeTemplate) {
185
+ return safeBaseText;
93
186
  }
94
- return `${template.trim()}\n\n原始约束:\n${safeBaseText}`.trim();
95
- }
96
- function resolveStageTemplates() {
97
- const mode = resolveStageMode();
98
- if (mode === 'off') {
99
- return {
100
- enabled: false,
101
- statusProbeTemplate: '',
102
- activeContinueTemplate: '',
103
- loopSelfCheckTemplate: ''
104
- };
187
+ if (!safeBaseText) {
188
+ return safeTemplate;
105
189
  }
190
+ return `${safeTemplate}\n\n原始约束:\n${safeBaseText}`.trim();
191
+ }
192
+ function resolveStageTemplates(mode) {
106
193
  const statusProbeTemplate = loadStageTemplate('status_probe');
107
194
  const activeContinueTemplate = loadStageTemplate('active_continue');
108
195
  const loopSelfCheckTemplate = loadStageTemplate('loop_self_check');
@@ -123,7 +210,11 @@ function resolveStageTemplates() {
123
210
  loopSelfCheckTemplate: loopSelfCheckTemplate || DEFAULT_LOOP_TEMPLATE
124
211
  };
125
212
  }
126
- function resolveStageMode() {
213
+ function resolveStageMode(modeFromState) {
214
+ const stateMode = normalizeStageMode(modeFromState);
215
+ if (stateMode) {
216
+ return stateMode;
217
+ }
127
218
  const raw = normalizeText(process.env.ROUTECODEX_STOPMESSAGE_STAGE_MODE).toLowerCase();
128
219
  if (raw === '1' || raw === 'true' || raw === 'on') {
129
220
  return 'on';
@@ -134,27 +225,29 @@ function resolveStageMode() {
134
225
  if (raw === 'auto') {
135
226
  return 'auto';
136
227
  }
137
- return 'on';
228
+ return 'auto';
138
229
  }
139
230
  function loadStageTemplate(stage) {
140
- const envRef = stage === 'status_probe'
141
- ? normalizeText(process.env.ROUTECODEX_STOPMESSAGE_STAGE_STATUS_REF)
142
- : stage === 'active_continue'
143
- ? normalizeText(process.env.ROUTECODEX_STOPMESSAGE_STAGE_ACTIVE_REF)
144
- : normalizeText(process.env.ROUTECODEX_STOPMESSAGE_STAGE_LOOP_REF);
145
- const ref = envRef || STAGE_TEMPLATE_REFS[stage];
146
- if (!ref) {
147
- return null;
148
- }
149
- try {
150
- const resolved = resolveStopMessageText(ref);
151
- return normalizeText(resolved) || null;
152
- }
153
- catch {
154
- return null;
231
+ const loaded = loadStopMessageStageTemplate(stage);
232
+ return loaded.text || null;
233
+ }
234
+ function detectBdWorkState(messages, options) {
235
+ const mode = resolveBdMode();
236
+ if (mode !== 'heuristic') {
237
+ const runtimeState = resolveBdWorkStateFromRuntime({
238
+ bdCommandRunner: options?.bdCommandRunner,
239
+ nowMs: options?.nowMs
240
+ });
241
+ if (runtimeState === 'active' || runtimeState === 'idle') {
242
+ return runtimeState;
243
+ }
244
+ if (mode === 'runtime') {
245
+ return 'unknown';
246
+ }
155
247
  }
248
+ return detectBdWorkStateFromHeuristics(messages);
156
249
  }
157
- function detectBdWorkState(messages) {
250
+ function detectBdWorkStateFromHeuristics(messages) {
158
251
  const normalizedTail = messages
159
252
  .slice(-30)
160
253
  .map((message) => normalizeMessageForHeuristics(message))
@@ -188,6 +281,174 @@ function detectBdWorkState(messages) {
188
281
  }
189
282
  return 'unknown';
190
283
  }
284
+ function writeBdRuntimeCacheAndReturn(mode, cwd, nowMs, ttlMs, state) {
285
+ if (ttlMs > 0) {
286
+ bdRuntimeCache = {
287
+ mode,
288
+ cwd,
289
+ expiresAt: nowMs + ttlMs,
290
+ state
291
+ };
292
+ }
293
+ return state;
294
+ }
295
+ function resolveBdMode() {
296
+ const modeRaw = normalizeText(process.env.ROUTECODEX_STOPMESSAGE_BD_MODE).toLowerCase();
297
+ if (modeRaw === 'runtime' || modeRaw === 'real') {
298
+ return 'runtime';
299
+ }
300
+ if (modeRaw === 'heuristic' || modeRaw === 'text') {
301
+ return 'heuristic';
302
+ }
303
+ const legacyRuntimeRaw = normalizeText(process.env.ROUTECODEX_STOPMESSAGE_BD_RUNTIME).toLowerCase();
304
+ if (legacyRuntimeRaw === '1' || legacyRuntimeRaw === 'true' || legacyRuntimeRaw === 'on') {
305
+ return 'runtime';
306
+ }
307
+ if (legacyRuntimeRaw === '0' || legacyRuntimeRaw === 'false' || legacyRuntimeRaw === 'off') {
308
+ return 'heuristic';
309
+ }
310
+ return 'auto';
311
+ }
312
+ function resolveBdCacheTtlMs() {
313
+ const parsed = parseNonNegativeInteger(process.env.ROUTECODEX_STOPMESSAGE_BD_CACHE_TTL_MS);
314
+ if (typeof parsed === 'number') {
315
+ return parsed;
316
+ }
317
+ return DEFAULT_BD_CACHE_TTL_MS;
318
+ }
319
+ function resolveBdTimeoutMs() {
320
+ const parsed = parsePositiveInteger(process.env.ROUTECODEX_STOPMESSAGE_BD_TIMEOUT_MS);
321
+ if (typeof parsed === 'number') {
322
+ return parsed;
323
+ }
324
+ return DEFAULT_BD_TIMEOUT_MS;
325
+ }
326
+ function parseNonNegativeInteger(value) {
327
+ if (typeof value !== 'string') {
328
+ return undefined;
329
+ }
330
+ const parsed = Number.parseInt(value.trim(), 10);
331
+ if (!Number.isFinite(parsed) || parsed < 0) {
332
+ return undefined;
333
+ }
334
+ return parsed;
335
+ }
336
+ function parsePositiveInteger(value) {
337
+ const parsed = parseNonNegativeInteger(value);
338
+ if (typeof parsed !== 'number' || parsed <= 0) {
339
+ return undefined;
340
+ }
341
+ return parsed;
342
+ }
343
+ function resolveBdWorkingDirectory(cwdOverride) {
344
+ const fromOverride = normalizeText(cwdOverride);
345
+ if (fromOverride) {
346
+ return path.resolve(fromOverride);
347
+ }
348
+ const fromEnv = normalizeText(process.env.ROUTECODEX_STOPMESSAGE_BD_WORKDIR);
349
+ if (fromEnv) {
350
+ return path.resolve(fromEnv);
351
+ }
352
+ const current = process.cwd();
353
+ return findBdProjectRoot(current) || current;
354
+ }
355
+ function findBdProjectRoot(startDirectory) {
356
+ let current = path.resolve(startDirectory);
357
+ while (true) {
358
+ const beadsDir = path.join(current, '.beads');
359
+ const issuesJsonlPath = path.join(beadsDir, 'issues.jsonl');
360
+ if (fs.existsSync(issuesJsonlPath)) {
361
+ return current;
362
+ }
363
+ if (fs.existsSync(beadsDir)) {
364
+ return current;
365
+ }
366
+ const parent = path.dirname(current);
367
+ if (parent === current) {
368
+ return null;
369
+ }
370
+ current = parent;
371
+ }
372
+ }
373
+ function runBdCommand(args, options) {
374
+ try {
375
+ const result = childProcess.spawnSync('bd', args, {
376
+ cwd: options.cwd,
377
+ encoding: 'utf8',
378
+ timeout: options.timeoutMs,
379
+ maxBuffer: 1024 * 1024
380
+ });
381
+ return {
382
+ status: result.status,
383
+ stdout: typeof result.stdout === 'string' ? result.stdout : String(result.stdout ?? ''),
384
+ stderr: typeof result.stderr === 'string' ? result.stderr : String(result.stderr ?? ''),
385
+ error: result.error
386
+ };
387
+ }
388
+ catch (error) {
389
+ return {
390
+ status: null,
391
+ stdout: '',
392
+ stderr: '',
393
+ error
394
+ };
395
+ }
396
+ }
397
+ function runBdQuery(commandRunner, args, options) {
398
+ const result = commandRunner(args, options);
399
+ if (result.error || result.status !== 0) {
400
+ return 'unknown';
401
+ }
402
+ const list = parseJsonArrayOutput(result.stdout);
403
+ if (!list) {
404
+ return 'unknown';
405
+ }
406
+ return list.length > 0 ? 'active' : 'idle';
407
+ }
408
+ function parseJsonArrayOutput(stdout) {
409
+ const trimmed = typeof stdout === 'string' ? stdout.trim() : '';
410
+ if (!trimmed) {
411
+ return [];
412
+ }
413
+ const parsedDirect = parseJsonArray(trimmed);
414
+ if (parsedDirect) {
415
+ return parsedDirect;
416
+ }
417
+ const firstBracket = trimmed.indexOf('[');
418
+ const lastBracket = trimmed.lastIndexOf(']');
419
+ if (firstBracket >= 0 && lastBracket > firstBracket) {
420
+ const arraySlice = trimmed.slice(firstBracket, lastBracket + 1).trim();
421
+ const parsedSlice = parseJsonArray(arraySlice);
422
+ if (parsedSlice) {
423
+ return parsedSlice;
424
+ }
425
+ }
426
+ return null;
427
+ }
428
+ function parseJsonArray(raw) {
429
+ try {
430
+ const value = JSON.parse(raw);
431
+ if (Array.isArray(value)) {
432
+ return value;
433
+ }
434
+ if (value && typeof value === 'object') {
435
+ const record = value;
436
+ if (Array.isArray(record.items)) {
437
+ return record.items;
438
+ }
439
+ if (Array.isArray(record.issues)) {
440
+ return record.issues;
441
+ }
442
+ if (Array.isArray(record.data)) {
443
+ return record.data;
444
+ }
445
+ }
446
+ return null;
447
+ }
448
+ catch {
449
+ return null;
450
+ }
451
+ }
191
452
  function buildObservationHash(messages) {
192
453
  const tail = messages
193
454
  .slice(-20)
@@ -199,6 +460,125 @@ function buildObservationHash(messages) {
199
460
  }
200
461
  return createHash('sha1').update(tail).digest('hex').slice(0, 16);
201
462
  }
463
+ function stableStringifyForObservation(value) {
464
+ if (value === null)
465
+ return 'null';
466
+ const type = typeof value;
467
+ if (type === 'string')
468
+ return JSON.stringify(value);
469
+ if (type === 'number')
470
+ return Number.isFinite(value) ? String(value) : 'null';
471
+ if (type === 'boolean')
472
+ return value ? 'true' : 'false';
473
+ if (type === 'undefined' || type === 'function' || type === 'symbol')
474
+ return 'null';
475
+ if (Array.isArray(value)) {
476
+ return `[${value.map((item) => stableStringifyForObservation(item)).join(',')}]`;
477
+ }
478
+ if (value && typeof value === 'object') {
479
+ const record = value;
480
+ const keys = Object.keys(record).sort();
481
+ const chunks = [];
482
+ for (const key of keys) {
483
+ const child = record[key];
484
+ if (typeof child === 'undefined') {
485
+ continue;
486
+ }
487
+ chunks.push(`${JSON.stringify(key)}:${stableStringifyForObservation(child)}`);
488
+ }
489
+ return `{${chunks.join(',')}}`;
490
+ }
491
+ try {
492
+ return JSON.stringify(value);
493
+ }
494
+ catch {
495
+ return 'null';
496
+ }
497
+ }
498
+ function normalizeToolArgumentsForObservation(value) {
499
+ if (typeof value === 'string') {
500
+ const trimmed = value.trim();
501
+ if (!trimmed) {
502
+ return '{}';
503
+ }
504
+ if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
505
+ try {
506
+ return stableStringifyForObservation(JSON.parse(trimmed));
507
+ }
508
+ catch {
509
+ return normalizeWhitespace(trimmed);
510
+ }
511
+ }
512
+ return normalizeWhitespace(trimmed);
513
+ }
514
+ if (typeof value === 'undefined') {
515
+ return '{}';
516
+ }
517
+ return stableStringifyForObservation(value);
518
+ }
519
+ function extractToolCallSignatureText(record) {
520
+ const signatures = [];
521
+ const appendSignature = (nameValue, argsValue) => {
522
+ const name = normalizeText(nameValue).toLowerCase();
523
+ if (!name) {
524
+ return;
525
+ }
526
+ const args = normalizeToolArgumentsForObservation(argsValue);
527
+ signatures.push(`${name}(${args})`);
528
+ };
529
+ const scan = (node) => {
530
+ if (!node) {
531
+ return;
532
+ }
533
+ if (Array.isArray(node)) {
534
+ for (const item of node) {
535
+ scan(item);
536
+ }
537
+ return;
538
+ }
539
+ if (typeof node !== 'object') {
540
+ return;
541
+ }
542
+ const current = node;
543
+ if (current.function && typeof current.function === 'object' && !Array.isArray(current.function)) {
544
+ const fn = current.function;
545
+ appendSignature(fn.name, fn.arguments);
546
+ }
547
+ if (normalizeText(current.type).toLowerCase() === 'function_call') {
548
+ appendSignature(current.name, current.arguments);
549
+ }
550
+ if (typeof current.name === 'string' && Object.prototype.hasOwnProperty.call(current, 'arguments')) {
551
+ appendSignature(current.name, current.arguments);
552
+ }
553
+ if (current.function_call && typeof current.function_call === 'object' && !Array.isArray(current.function_call)) {
554
+ const fnCall = current.function_call;
555
+ appendSignature(fnCall.name, fnCall.arguments);
556
+ }
557
+ };
558
+ scan(record.tool_calls);
559
+ scan(record.tool_call);
560
+ scan(record.function_call);
561
+ scan(record.output);
562
+ return signatures.join('; ');
563
+ }
564
+ function buildToolSignatureHash(messages) {
565
+ const signatures = messages
566
+ .slice(-20)
567
+ .map((message) => extractToolSignatureForHash(message))
568
+ .filter(Boolean)
569
+ .join('\n');
570
+ if (!signatures) {
571
+ return undefined;
572
+ }
573
+ return createHash('sha1').update(signatures).digest('hex').slice(0, 16);
574
+ }
575
+ function extractToolSignatureForHash(message) {
576
+ if (!message || typeof message !== 'object' || Array.isArray(message)) {
577
+ return '';
578
+ }
579
+ const record = message;
580
+ return extractToolCallSignatureText(record);
581
+ }
202
582
  function normalizeMessageForHash(message) {
203
583
  if (!message || typeof message !== 'object' || Array.isArray(message)) {
204
584
  return '';
@@ -206,10 +586,12 @@ function normalizeMessageForHash(message) {
206
586
  const record = message;
207
587
  const role = normalizeText(record.role).toLowerCase() || 'unknown';
208
588
  const text = extractMessageText(record.content || record.output || record.input || '');
209
- if (!text) {
589
+ const toolSignature = extractToolCallSignatureText(record);
590
+ const combined = [text, toolSignature].filter((entry) => typeof entry === 'string' && entry.trim()).join(' | ');
591
+ if (!combined) {
210
592
  return role;
211
593
  }
212
- return `${role}:${text.slice(0, 400)}`;
594
+ return `${role}:${combined.slice(0, 800)}`;
213
595
  }
214
596
  function normalizeMessageForHeuristics(message) {
215
597
  if (!message || typeof message !== 'object' || Array.isArray(message)) {
@@ -264,6 +646,36 @@ function normalizeWhitespace(text) {
264
646
  function normalizeText(value) {
265
647
  return typeof value === 'string' ? value.trim() : '';
266
648
  }
649
+ function normalizeStageName(value) {
650
+ if (typeof value !== 'string') {
651
+ return undefined;
652
+ }
653
+ const normalized = value.trim().toLowerCase();
654
+ if (normalized === 'status_probe' || normalized === 'active_continue' || normalized === 'loop_self_check') {
655
+ return normalized;
656
+ }
657
+ return undefined;
658
+ }
659
+ function normalizeBdWorkState(value) {
660
+ if (typeof value !== 'string') {
661
+ return undefined;
662
+ }
663
+ const normalized = value.trim().toLowerCase();
664
+ if (normalized === 'active' || normalized === 'idle' || normalized === 'unknown') {
665
+ return normalized;
666
+ }
667
+ return undefined;
668
+ }
669
+ function normalizeStageMode(value) {
670
+ if (typeof value !== 'string') {
671
+ return undefined;
672
+ }
673
+ const normalized = value.trim().toLowerCase();
674
+ if (normalized === 'on' || normalized === 'off' || normalized === 'auto') {
675
+ return normalized;
676
+ }
677
+ return undefined;
678
+ }
267
679
  function toSafeInt(value, fallback) {
268
680
  if (typeof value !== 'number' || !Number.isFinite(value)) {
269
681
  return fallback;
@@ -66,7 +66,7 @@ const handler = async (ctx) => {
66
66
  }
67
67
  };
68
68
  };
69
- registerServerToolHandler('vision_auto', handler, { trigger: 'auto' });
69
+ registerServerToolHandler('vision_auto', handler, { trigger: 'auto', hook: { phase: 'post', priority: 60 } });
70
70
  export async function executeVisionBackendPlan(args) {
71
71
  const plan = args.plan;
72
72
  const options = args.options;