@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
@@ -0,0 +1,24 @@
1
+ export function serializePreCommandState(state) {
2
+ return {
3
+ ...(typeof state.preCommandSource === 'string' && state.preCommandSource.trim()
4
+ ? { preCommandSource: state.preCommandSource.trim() }
5
+ : {}),
6
+ ...(typeof state.preCommandScriptPath === 'string' && state.preCommandScriptPath.trim()
7
+ ? { preCommandScriptPath: state.preCommandScriptPath.trim() }
8
+ : {}),
9
+ ...(typeof state.preCommandUpdatedAt === 'number' && Number.isFinite(state.preCommandUpdatedAt)
10
+ ? { preCommandUpdatedAt: state.preCommandUpdatedAt }
11
+ : {})
12
+ };
13
+ }
14
+ export function deserializePreCommandState(data, state) {
15
+ if (typeof data.preCommandSource === 'string' && data.preCommandSource.trim()) {
16
+ state.preCommandSource = data.preCommandSource.trim();
17
+ }
18
+ if (typeof data.preCommandScriptPath === 'string' && data.preCommandScriptPath.trim()) {
19
+ state.preCommandScriptPath = data.preCommandScriptPath.trim();
20
+ }
21
+ if (typeof data.preCommandUpdatedAt === 'number' && Number.isFinite(data.preCommandUpdatedAt)) {
22
+ state.preCommandUpdatedAt = data.preCommandUpdatedAt;
23
+ }
24
+ }
@@ -0,0 +1,2 @@
1
+ import type { RoutingInstruction, RoutingInstructionState } from './routing-instructions.js';
2
+ export declare function applyStopMessageInstructionToState(instruction: RoutingInstruction, state: RoutingInstructionState): boolean;
@@ -0,0 +1,96 @@
1
+ import { normalizeStopMessageStageMode } from './routing-stop-message-state-codec.js';
2
+ export function applyStopMessageInstructionToState(instruction, state) {
3
+ switch (instruction.type) {
4
+ case 'stopMessageSet': {
5
+ const text = typeof instruction.stopMessageText === 'string' && instruction.stopMessageText.trim()
6
+ ? instruction.stopMessageText.trim()
7
+ : '';
8
+ const maxRepeats = typeof instruction.stopMessageMaxRepeats === 'number' && Number.isFinite(instruction.stopMessageMaxRepeats)
9
+ ? Math.floor(instruction.stopMessageMaxRepeats)
10
+ : 0;
11
+ if (!text || maxRepeats <= 0) {
12
+ return true;
13
+ }
14
+ const incomingMode = normalizeStopMessageStageMode(instruction.stopMessageStageMode);
15
+ const currentMode = normalizeStopMessageStageMode(state.stopMessageStageMode);
16
+ const targetMode = incomingMode ?? (currentMode === 'off' ? 'on' : currentMode);
17
+ const sameText = typeof state.stopMessageText === 'string' && state.stopMessageText.trim() === text;
18
+ const sameMax = typeof state.stopMessageMaxRepeats === 'number' &&
19
+ Math.floor(state.stopMessageMaxRepeats) === maxRepeats;
20
+ const sameMode = currentMode === targetMode;
21
+ const isSameInstruction = sameText && sameMax && sameMode;
22
+ const used = typeof state.stopMessageUsed === 'number' && Number.isFinite(state.stopMessageUsed)
23
+ ? Math.max(0, Math.floor(state.stopMessageUsed))
24
+ : 0;
25
+ const hasLastUsedAt = typeof state.stopMessageLastUsedAt === 'number' && Number.isFinite(state.stopMessageLastUsedAt);
26
+ const shouldRearm = !isSameInstruction || used > 0 || hasLastUsedAt;
27
+ state.stopMessageText = text;
28
+ state.stopMessageMaxRepeats = maxRepeats;
29
+ state.stopMessageSource = 'explicit';
30
+ if (targetMode) {
31
+ state.stopMessageStageMode = targetMode;
32
+ }
33
+ if (shouldRearm) {
34
+ resetStopMessageRuntimeState(state, Date.now());
35
+ }
36
+ return true;
37
+ }
38
+ case 'stopMessageMode': {
39
+ const mode = normalizeStopMessageStageMode(instruction.stopMessageStageMode);
40
+ if (!mode) {
41
+ return true;
42
+ }
43
+ const maxRepeats = typeof instruction.stopMessageMaxRepeats === 'number' && Number.isFinite(instruction.stopMessageMaxRepeats)
44
+ ? Math.floor(instruction.stopMessageMaxRepeats)
45
+ : 0;
46
+ if (mode === 'off') {
47
+ clearStopMessageState(state, { keepMode: 'off' });
48
+ return true;
49
+ }
50
+ const previousMode = normalizeStopMessageStageMode(state.stopMessageStageMode);
51
+ const previousMax = typeof state.stopMessageMaxRepeats === 'number' && Number.isFinite(state.stopMessageMaxRepeats)
52
+ ? Math.floor(state.stopMessageMaxRepeats)
53
+ : 0;
54
+ state.stopMessageStageMode = mode;
55
+ state.stopMessageSource = 'explicit';
56
+ if (maxRepeats > 0) {
57
+ state.stopMessageMaxRepeats = maxRepeats;
58
+ }
59
+ const shouldRearm = previousMode !== mode ||
60
+ (maxRepeats > 0 && previousMax !== maxRepeats) ||
61
+ typeof state.stopMessageUsed !== 'number' ||
62
+ !Number.isFinite(state.stopMessageUsed);
63
+ if (shouldRearm) {
64
+ resetStopMessageRuntimeState(state, Date.now());
65
+ }
66
+ return true;
67
+ }
68
+ case 'stopMessageClear':
69
+ clearStopMessageState(state);
70
+ return true;
71
+ default:
72
+ return false;
73
+ }
74
+ }
75
+ function resetStopMessageRuntimeState(state, atMs) {
76
+ state.stopMessageUsed = 0;
77
+ state.stopMessageUpdatedAt = atMs;
78
+ state.stopMessageLastUsedAt = undefined;
79
+ state.stopMessageStage = undefined;
80
+ state.stopMessageObservationHash = undefined;
81
+ state.stopMessageObservationStableCount = 0;
82
+ state.stopMessageBdWorkState = undefined;
83
+ }
84
+ function clearStopMessageState(state, options) {
85
+ state.stopMessageText = undefined;
86
+ state.stopMessageMaxRepeats = undefined;
87
+ state.stopMessageUsed = undefined;
88
+ state.stopMessageSource = undefined;
89
+ state.stopMessageUpdatedAt = undefined;
90
+ state.stopMessageLastUsedAt = undefined;
91
+ state.stopMessageStage = undefined;
92
+ state.stopMessageStageMode = options?.keepMode;
93
+ state.stopMessageObservationHash = undefined;
94
+ state.stopMessageObservationStableCount = undefined;
95
+ state.stopMessageBdWorkState = undefined;
96
+ }
@@ -0,0 +1,3 @@
1
+ import type { RoutingInstruction } from './routing-instructions.js';
2
+ export declare const DEFAULT_STOP_MESSAGE_MAX_REPEATS = 10;
3
+ export declare function parseStopMessageInstruction(instruction: string): RoutingInstruction | null;
@@ -0,0 +1,142 @@
1
+ import { resolveStopMessageText } from './stop-message-file-resolver.js';
2
+ export const DEFAULT_STOP_MESSAGE_MAX_REPEATS = 10;
3
+ export function parseStopMessageInstruction(instruction) {
4
+ if (!/^stopMessage\s*:/i.test(instruction)) {
5
+ return null;
6
+ }
7
+ const body = instruction.slice('stopMessage'.length + 1).trim();
8
+ if (!body) {
9
+ return null;
10
+ }
11
+ if (/^clear$/i.test(body)) {
12
+ return { type: 'stopMessageClear' };
13
+ }
14
+ const modeOnly = parseStopMessageModeWithOptionalRepeats(body);
15
+ if (modeOnly) {
16
+ return {
17
+ type: 'stopMessageMode',
18
+ stopMessageStageMode: modeOnly.mode,
19
+ ...(typeof modeOnly.maxRepeats === 'number' ? { stopMessageMaxRepeats: modeOnly.maxRepeats } : {})
20
+ };
21
+ }
22
+ let text = '';
23
+ let maxRepeats = DEFAULT_STOP_MESSAGE_MAX_REPEATS;
24
+ let cursor = body;
25
+ let stageMode;
26
+ if (cursor[0] === '"') {
27
+ let escaped = false;
28
+ let endIndex = -1;
29
+ for (let i = 1; i < cursor.length; i += 1) {
30
+ const ch = cursor[i];
31
+ if (escaped) {
32
+ escaped = false;
33
+ continue;
34
+ }
35
+ if (ch === '\\') {
36
+ escaped = true;
37
+ continue;
38
+ }
39
+ if (ch === '"') {
40
+ endIndex = i;
41
+ break;
42
+ }
43
+ }
44
+ if (endIndex <= 0) {
45
+ return null;
46
+ }
47
+ const rawText = cursor.slice(1, endIndex);
48
+ text = rawText.replace(/\\"/g, '"');
49
+ cursor = cursor.slice(endIndex + 1).trim();
50
+ if (cursor.startsWith(',')) {
51
+ const tail = cursor.slice(1).trim();
52
+ const parsedTail = parseStopMessageTail(tail);
53
+ maxRepeats = parsedTail.maxRepeats;
54
+ stageMode = parsedTail.stageMode;
55
+ }
56
+ }
57
+ else {
58
+ const parts = cursor
59
+ .split(',')
60
+ .map((part) => part.trim())
61
+ .filter(Boolean);
62
+ if (!parts.length) {
63
+ return null;
64
+ }
65
+ text = parts[0];
66
+ const parsedTail = parseStopMessageTail(parts.slice(1));
67
+ maxRepeats = parsedTail.maxRepeats;
68
+ stageMode = parsedTail.stageMode;
69
+ }
70
+ if (!text) {
71
+ return null;
72
+ }
73
+ return {
74
+ type: 'stopMessageSet',
75
+ stopMessageText: resolveStopMessageText(text),
76
+ stopMessageMaxRepeats: maxRepeats,
77
+ ...(stageMode ? { stopMessageStageMode: stageMode } : {})
78
+ };
79
+ }
80
+ function parseStopMessageTail(input) {
81
+ const tokens = Array.isArray(input)
82
+ ? input.map((part) => part.trim()).filter(Boolean)
83
+ : input
84
+ .split(',')
85
+ .map((part) => part.trim())
86
+ .filter(Boolean);
87
+ let maxRepeats = DEFAULT_STOP_MESSAGE_MAX_REPEATS;
88
+ let stageMode;
89
+ for (const token of tokens) {
90
+ const mode = parseStopMessageStageModeToken(token);
91
+ if (mode) {
92
+ stageMode = mode;
93
+ continue;
94
+ }
95
+ const parsed = Number.parseInt(token, 10);
96
+ if (!Number.isNaN(parsed) && parsed > 0) {
97
+ maxRepeats = parsed;
98
+ }
99
+ }
100
+ return {
101
+ maxRepeats,
102
+ ...(stageMode ? { stageMode } : {})
103
+ };
104
+ }
105
+ function parseStopMessageModeWithOptionalRepeats(value) {
106
+ const normalized = value.trim().toLowerCase();
107
+ const match = normalized.match(/^(?:(?:stage|mode)\s*[:=]\s*)?(on|off|auto)\s*(?:,\s*(\d+)\s*)?$/i);
108
+ if (!match) {
109
+ return null;
110
+ }
111
+ const token = match[1].trim().toLowerCase();
112
+ const mode = token === 'on' || token === 'off' || token === 'auto'
113
+ ? token
114
+ : undefined;
115
+ if (!mode) {
116
+ return null;
117
+ }
118
+ if (mode === 'off') {
119
+ return { mode };
120
+ }
121
+ const repeatToken = match[2];
122
+ if (!repeatToken) {
123
+ return { mode, maxRepeats: DEFAULT_STOP_MESSAGE_MAX_REPEATS };
124
+ }
125
+ const parsed = Number.parseInt(repeatToken, 10);
126
+ if (Number.isNaN(parsed) || parsed <= 0) {
127
+ return { mode, maxRepeats: DEFAULT_STOP_MESSAGE_MAX_REPEATS };
128
+ }
129
+ return { mode, maxRepeats: parsed };
130
+ }
131
+ function parseStopMessageStageModeToken(value) {
132
+ const normalized = value.trim().toLowerCase();
133
+ const match = normalized.match(/^(?:(?:stage|mode)\s*[:=]\s*)?(on|off|auto)$/i);
134
+ if (!match) {
135
+ return undefined;
136
+ }
137
+ const token = match[1].trim().toLowerCase();
138
+ if (token === 'on' || token === 'off' || token === 'auto') {
139
+ return token;
140
+ }
141
+ return undefined;
142
+ }
@@ -0,0 +1,4 @@
1
+ import type { RoutingInstructionState } from './routing-instructions.js';
2
+ export declare function serializeStopMessageState(state: RoutingInstructionState): Record<string, unknown>;
3
+ export declare function deserializeStopMessageState(data: Record<string, unknown>, state: RoutingInstructionState): void;
4
+ export declare function normalizeStopMessageStageMode(value: unknown): 'on' | 'off' | 'auto' | undefined;
@@ -0,0 +1,85 @@
1
+ export function serializeStopMessageState(state) {
2
+ return {
3
+ ...(typeof state.stopMessageSource === 'string' && state.stopMessageSource.trim()
4
+ ? { stopMessageSource: state.stopMessageSource }
5
+ : {}),
6
+ ...(typeof state.stopMessageText === 'string' && state.stopMessageText.trim()
7
+ ? { stopMessageText: state.stopMessageText }
8
+ : {}),
9
+ ...(typeof state.stopMessageMaxRepeats === 'number' && Number.isFinite(state.stopMessageMaxRepeats)
10
+ ? { stopMessageMaxRepeats: state.stopMessageMaxRepeats }
11
+ : {}),
12
+ ...(typeof state.stopMessageUsed === 'number' && Number.isFinite(state.stopMessageUsed)
13
+ ? { stopMessageUsed: state.stopMessageUsed }
14
+ : {}),
15
+ ...(typeof state.stopMessageUpdatedAt === 'number' && Number.isFinite(state.stopMessageUpdatedAt)
16
+ ? { stopMessageUpdatedAt: state.stopMessageUpdatedAt }
17
+ : {}),
18
+ ...(typeof state.stopMessageLastUsedAt === 'number' && Number.isFinite(state.stopMessageLastUsedAt)
19
+ ? { stopMessageLastUsedAt: state.stopMessageLastUsedAt }
20
+ : {}),
21
+ ...(typeof state.stopMessageStage === 'string' && state.stopMessageStage.trim()
22
+ ? { stopMessageStage: state.stopMessageStage.trim() }
23
+ : {}),
24
+ ...(typeof state.stopMessageStageMode === 'string' && state.stopMessageStageMode.trim()
25
+ ? { stopMessageStageMode: state.stopMessageStageMode.trim() }
26
+ : {}),
27
+ ...(typeof state.stopMessageObservationHash === 'string' && state.stopMessageObservationHash.trim()
28
+ ? { stopMessageObservationHash: state.stopMessageObservationHash.trim() }
29
+ : {}),
30
+ ...(typeof state.stopMessageObservationStableCount === 'number' && Number.isFinite(state.stopMessageObservationStableCount)
31
+ ? { stopMessageObservationStableCount: Math.max(0, Math.floor(state.stopMessageObservationStableCount)) }
32
+ : {}),
33
+ ...(typeof state.stopMessageBdWorkState === 'string' && state.stopMessageBdWorkState.trim()
34
+ ? { stopMessageBdWorkState: state.stopMessageBdWorkState.trim() }
35
+ : {})
36
+ };
37
+ }
38
+ export function deserializeStopMessageState(data, state) {
39
+ if (typeof data.stopMessageSource === 'string' && data.stopMessageSource.trim()) {
40
+ state.stopMessageSource = data.stopMessageSource.trim();
41
+ }
42
+ if (typeof data.stopMessageText === 'string' && data.stopMessageText.trim()) {
43
+ state.stopMessageText = data.stopMessageText;
44
+ }
45
+ if (typeof data.stopMessageMaxRepeats === 'number' && Number.isFinite(data.stopMessageMaxRepeats)) {
46
+ state.stopMessageMaxRepeats = Math.floor(data.stopMessageMaxRepeats);
47
+ }
48
+ if (typeof data.stopMessageUsed === 'number' && Number.isFinite(data.stopMessageUsed)) {
49
+ state.stopMessageUsed = Math.max(0, Math.floor(data.stopMessageUsed));
50
+ }
51
+ if (typeof data.stopMessageUpdatedAt === 'number' && Number.isFinite(data.stopMessageUpdatedAt)) {
52
+ state.stopMessageUpdatedAt = data.stopMessageUpdatedAt;
53
+ }
54
+ if (typeof data.stopMessageLastUsedAt === 'number' && Number.isFinite(data.stopMessageLastUsedAt)) {
55
+ state.stopMessageLastUsedAt = data.stopMessageLastUsedAt;
56
+ }
57
+ if (typeof data.stopMessageStage === 'string' && data.stopMessageStage.trim()) {
58
+ state.stopMessageStage = data.stopMessageStage.trim();
59
+ }
60
+ if (typeof data.stopMessageStageMode === 'string' && data.stopMessageStageMode.trim()) {
61
+ const normalized = normalizeStopMessageStageMode(data.stopMessageStageMode);
62
+ if (normalized) {
63
+ state.stopMessageStageMode = normalized;
64
+ }
65
+ }
66
+ if (typeof data.stopMessageObservationHash === 'string' && data.stopMessageObservationHash.trim()) {
67
+ state.stopMessageObservationHash = data.stopMessageObservationHash.trim();
68
+ }
69
+ if (typeof data.stopMessageObservationStableCount === 'number' && Number.isFinite(data.stopMessageObservationStableCount)) {
70
+ state.stopMessageObservationStableCount = Math.max(0, Math.floor(data.stopMessageObservationStableCount));
71
+ }
72
+ if (typeof data.stopMessageBdWorkState === 'string' && data.stopMessageBdWorkState.trim()) {
73
+ state.stopMessageBdWorkState = data.stopMessageBdWorkState.trim();
74
+ }
75
+ }
76
+ export function normalizeStopMessageStageMode(value) {
77
+ if (typeof value !== 'string') {
78
+ return undefined;
79
+ }
80
+ const normalized = value.trim().toLowerCase();
81
+ if (normalized === 'on' || normalized === 'off' || normalized === 'auto') {
82
+ return normalized;
83
+ }
84
+ return undefined;
85
+ }
@@ -8,17 +8,62 @@ function isPersistentKey(key) {
8
8
  return false;
9
9
  return key.startsWith('session:') || key.startsWith('conversation:');
10
10
  }
11
+ function resolveRoutecodexUserDir() {
12
+ try {
13
+ const override = String(process.env.ROUTECODEX_USER_DIR || '').trim();
14
+ if (override) {
15
+ return path.resolve(override);
16
+ }
17
+ const home = os.homedir();
18
+ if (!home) {
19
+ return null;
20
+ }
21
+ return path.join(home, '.routecodex');
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ function resolveDefaultSessionDir() {
28
+ try {
29
+ const userDir = resolveRoutecodexUserDir();
30
+ if (!userDir) {
31
+ return null;
32
+ }
33
+ return path.join(userDir, 'sessions');
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
11
39
  function resolveSessionDir() {
12
40
  try {
13
41
  const override = process.env.ROUTECODEX_SESSION_DIR;
14
42
  if (override && override.trim()) {
15
- return override.trim();
43
+ return path.resolve(override.trim());
16
44
  }
17
- const home = os.homedir();
18
- if (!home) {
45
+ return resolveDefaultSessionDir();
46
+ }
47
+ catch {
48
+ return null;
49
+ }
50
+ }
51
+ function resolveSessionFallbackDir(primaryDir) {
52
+ try {
53
+ const defaultDir = resolveDefaultSessionDir();
54
+ if (!defaultDir) {
55
+ return null;
56
+ }
57
+ const normalizedPrimary = path.resolve(primaryDir);
58
+ const normalizedDefault = path.resolve(defaultDir);
59
+ if (normalizedPrimary === normalizedDefault) {
19
60
  return null;
20
61
  }
21
- return path.join(home, '.routecodex', 'sessions');
62
+ if (normalizedPrimary.startsWith(`${normalizedDefault}${path.sep}`) ||
63
+ normalizedPrimary === normalizedDefault) {
64
+ return normalizedDefault;
65
+ }
66
+ return null;
22
67
  }
23
68
  catch {
24
69
  return null;
@@ -37,16 +82,102 @@ function keyToFilename(key) {
37
82
  const safeId = rawId.replace(/[^a-zA-Z0-9_.-]/g, '_');
38
83
  return `${scope}-${safeId}.json`;
39
84
  }
40
- export function loadRoutingInstructionStateSync(key) {
85
+ function resolveSessionFilepaths(key) {
41
86
  if (!isPersistentKey(key)) {
42
- return null;
87
+ return [];
88
+ }
89
+ const dir = resolveSessionDir();
90
+ const filename = keyToFilename(key);
91
+ if (!dir || !filename) {
92
+ return [];
93
+ }
94
+ const filepaths = [path.join(dir, filename)];
95
+ const fallbackDir = resolveSessionFallbackDir(dir);
96
+ if (fallbackDir) {
97
+ const fallbackPath = path.join(fallbackDir, filename);
98
+ if (!filepaths.includes(fallbackPath)) {
99
+ filepaths.push(fallbackPath);
100
+ }
101
+ }
102
+ return filepaths;
103
+ }
104
+ function resolveSessionLoadFilepaths(key) {
105
+ if (!isPersistentKey(key)) {
106
+ return [];
107
+ }
108
+ const filepaths = resolveSessionFilepaths(key);
109
+ if (filepaths.length === 0) {
110
+ return [];
43
111
  }
44
112
  const dir = resolveSessionDir();
45
113
  const filename = keyToFilename(key);
46
114
  if (!dir || !filename) {
115
+ return filepaths;
116
+ }
117
+ const legacyFallbacks = resolveLegacyServerScopedFilepaths(dir, filename);
118
+ for (const candidate of legacyFallbacks) {
119
+ if (!filepaths.includes(candidate)) {
120
+ filepaths.push(candidate);
121
+ }
122
+ }
123
+ return filepaths;
124
+ }
125
+ function resolveLegacyServerScopedFilepaths(primaryDir, filename) {
126
+ try {
127
+ const normalizedPrimary = path.resolve(primaryDir);
128
+ const searchRoot = resolveSessionSearchRoot(normalizedPrimary);
129
+ if (!searchRoot) {
130
+ return [];
131
+ }
132
+ const currentDirName = path.dirname(normalizedPrimary) === searchRoot ? path.basename(normalizedPrimary) : '';
133
+ const entries = fs.readdirSync(searchRoot, { withFileTypes: true });
134
+ const candidates = [];
135
+ for (const entry of entries) {
136
+ if (!entry.isDirectory()) {
137
+ continue;
138
+ }
139
+ if (currentDirName && entry.name === currentDirName) {
140
+ continue;
141
+ }
142
+ const candidatePath = path.join(searchRoot, entry.name, filename);
143
+ try {
144
+ const stat = fs.statSync(candidatePath);
145
+ if (!stat.isFile()) {
146
+ continue;
147
+ }
148
+ candidates.push({
149
+ filepath: candidatePath,
150
+ mtimeMs: Number.isFinite(stat.mtimeMs) ? stat.mtimeMs : 0
151
+ });
152
+ }
153
+ catch {
154
+ // ignore missing legacy files
155
+ }
156
+ }
157
+ candidates.sort((left, right) => right.mtimeMs - left.mtimeMs);
158
+ return candidates.map((entry) => entry.filepath);
159
+ }
160
+ catch {
161
+ return [];
162
+ }
163
+ }
164
+ function resolveSessionSearchRoot(sessionDir) {
165
+ try {
166
+ const normalized = path.resolve(sessionDir);
167
+ if (path.basename(normalized) === 'sessions') {
168
+ return normalized;
169
+ }
170
+ const parent = path.dirname(normalized);
171
+ if (path.basename(parent) === 'sessions') {
172
+ return parent;
173
+ }
174
+ return null;
175
+ }
176
+ catch {
47
177
  return null;
48
178
  }
49
- const filepath = path.join(dir, filename);
179
+ }
180
+ function readPersistedStateFromFile(filepath) {
50
181
  try {
51
182
  if (!fs.existsSync(filepath)) {
52
183
  return null;
@@ -87,81 +218,99 @@ export function loadRoutingInstructionStateSync(key) {
87
218
  return null;
88
219
  }
89
220
  }
90
- export function saveRoutingInstructionStateAsync(key, state) {
91
- if (!isPersistentKey(key)) {
92
- return;
221
+ export function loadRoutingInstructionStateSync(key) {
222
+ const filepaths = resolveSessionLoadFilepaths(key);
223
+ if (filepaths.length === 0) {
224
+ return null;
93
225
  }
94
- const dir = resolveSessionDir();
95
- const filename = keyToFilename(key);
96
- if (!dir || !filename) {
226
+ const writeTargets = resolveSessionFilepaths(key);
227
+ for (const filepath of filepaths) {
228
+ const loaded = readPersistedStateFromFile(filepath);
229
+ if (!loaded) {
230
+ continue;
231
+ }
232
+ if (writeTargets.length > 0 && !writeTargets.includes(filepath)) {
233
+ saveRoutingInstructionStateSync(key, loaded);
234
+ }
235
+ return loaded;
236
+ }
237
+ return null;
238
+ }
239
+ export function saveRoutingInstructionStateAsync(key, state) {
240
+ const filepaths = resolveSessionFilepaths(key);
241
+ if (filepaths.length === 0) {
97
242
  return;
98
243
  }
99
- const filepath = path.join(dir, filename);
100
244
  // 空状态意味着清除持久化文件
101
245
  if (!state) {
246
+ for (const filepath of filepaths) {
247
+ scheduleWrite(filepath, async () => {
248
+ try {
249
+ await fs.promises.unlink(filepath);
250
+ }
251
+ catch {
252
+ // ignore unlink errors (e.g. ENOENT)
253
+ }
254
+ });
255
+ }
256
+ return;
257
+ }
258
+ const payload = {
259
+ version: 1,
260
+ state: serializeRoutingInstructionState(state)
261
+ };
262
+ for (const filepath of filepaths) {
263
+ const dir = path.dirname(filepath);
102
264
  scheduleWrite(filepath, async () => {
103
265
  try {
104
- await fs.promises.unlink(filepath);
266
+ await fs.promises.mkdir(dir, { recursive: true });
267
+ }
268
+ catch {
269
+ // ignore mkdir errors; write below will fail silently
270
+ }
271
+ try {
272
+ await atomicWriteFile(filepath, JSON.stringify(payload));
105
273
  }
106
274
  catch {
107
- // ignore unlink errors (e.g. ENOENT)
275
+ // ignore async write failures
108
276
  }
109
277
  });
278
+ }
279
+ }
280
+ export function saveRoutingInstructionStateSync(key, state) {
281
+ const filepaths = resolveSessionFilepaths(key);
282
+ if (filepaths.length === 0) {
283
+ return;
284
+ }
285
+ if (!state) {
286
+ for (const filepath of filepaths) {
287
+ try {
288
+ fs.unlinkSync(filepath);
289
+ }
290
+ catch {
291
+ // ignore unlink failures
292
+ }
293
+ }
110
294
  return;
111
295
  }
112
296
  const payload = {
113
297
  version: 1,
114
298
  state: serializeRoutingInstructionState(state)
115
299
  };
116
- scheduleWrite(filepath, async () => {
117
- try {
118
- await fs.promises.mkdir(dir, { recursive: true });
119
- }
120
- catch {
121
- // ignore mkdir errors; write below will fail silently
122
- }
300
+ for (const filepath of filepaths) {
301
+ const dir = path.dirname(filepath);
123
302
  try {
124
- await atomicWriteFile(filepath, JSON.stringify(payload));
303
+ fs.mkdirSync(dir, { recursive: true });
125
304
  }
126
305
  catch {
127
- // ignore async write failures
306
+ // ignore mkdir errors
128
307
  }
129
- });
130
- }
131
- export function saveRoutingInstructionStateSync(key, state) {
132
- if (!isPersistentKey(key)) {
133
- return;
134
- }
135
- const dir = resolveSessionDir();
136
- const filename = keyToFilename(key);
137
- if (!dir || !filename) {
138
- return;
139
- }
140
- const filepath = path.join(dir, filename);
141
- if (!state) {
142
308
  try {
143
- fs.unlinkSync(filepath);
309
+ atomicWriteFileSync(filepath, JSON.stringify(payload));
144
310
  }
145
311
  catch {
146
- // ignore unlink failures
312
+ // ignore sync write failures
147
313
  }
148
- return;
149
- }
150
- const payload = {
151
- version: 1,
152
- state: serializeRoutingInstructionState(state)
153
- };
154
- try {
155
- fs.mkdirSync(dir, { recursive: true });
156
- }
157
- catch {
158
- // ignore mkdir errors
159
- }
160
- try {
161
- atomicWriteFileSync(filepath, JSON.stringify(payload));
162
- }
163
- catch {
164
- // ignore sync write failures
165
314
  }
166
315
  }
167
316
  function scheduleWrite(filepath, task) {