@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,12 @@
1
+ export type StopMessageStageTemplateName = 'status_probe' | 'active_continue' | 'loop_self_check';
2
+ export interface StopMessageStageTemplateLoadResult {
3
+ stage: StopMessageStageTemplateName;
4
+ ref: string;
5
+ text?: string;
6
+ error?: string;
7
+ }
8
+ export declare function loadStopMessageStageTemplate(stage: StopMessageStageTemplateName): StopMessageStageTemplateLoadResult;
9
+ export declare function validateStopMessageStageTemplatesCompleteness(): {
10
+ ok: boolean;
11
+ missing: StopMessageStageTemplateLoadResult[];
12
+ };
@@ -0,0 +1,67 @@
1
+ import { resolveStopMessageText } from './stop-message-file-resolver.js';
2
+ const DEFAULT_STAGE_TEMPLATE_REFS = {
3
+ status_probe: '<file://stopMessage/stage-status-check.md>',
4
+ active_continue: '<file://stopMessage/stage-active-continue.md>',
5
+ loop_self_check: '<file://stopMessage/stage-loop-self-check.md>'
6
+ };
7
+ const STAGE_ENV_KEYS = {
8
+ status_probe: 'ROUTECODEX_STOPMESSAGE_STAGE_STATUS_REF',
9
+ active_continue: 'ROUTECODEX_STOPMESSAGE_STAGE_ACTIVE_REF',
10
+ loop_self_check: 'ROUTECODEX_STOPMESSAGE_STAGE_LOOP_REF'
11
+ };
12
+ export function loadStopMessageStageTemplate(stage) {
13
+ const ref = resolveStopMessageStageTemplateRef(stage);
14
+ if (!ref) {
15
+ return {
16
+ stage,
17
+ ref: '',
18
+ error: `missing template reference for ${stage}`
19
+ };
20
+ }
21
+ try {
22
+ const text = resolveStopMessageText(ref);
23
+ const normalized = typeof text === 'string' ? text.trim() : '';
24
+ if (!normalized) {
25
+ return {
26
+ stage,
27
+ ref,
28
+ error: `template is empty for ${stage}`
29
+ };
30
+ }
31
+ return {
32
+ stage,
33
+ ref,
34
+ text: normalized
35
+ };
36
+ }
37
+ catch (error) {
38
+ const message = error instanceof Error ? error.message : String(error ?? 'unknown error');
39
+ return {
40
+ stage,
41
+ ref,
42
+ error: message
43
+ };
44
+ }
45
+ }
46
+ export function validateStopMessageStageTemplatesCompleteness() {
47
+ const requiredStages = ['status_probe', 'active_continue', 'loop_self_check'];
48
+ const missing = [];
49
+ for (const stage of requiredStages) {
50
+ const loaded = loadStopMessageStageTemplate(stage);
51
+ if (!loaded.text) {
52
+ missing.push(loaded);
53
+ }
54
+ }
55
+ return {
56
+ ok: missing.length === 0,
57
+ missing
58
+ };
59
+ }
60
+ function resolveStopMessageStageTemplateRef(stage) {
61
+ const envKey = STAGE_ENV_KEYS[stage];
62
+ const envRef = normalizeText(process.env[envKey]);
63
+ return envRef || DEFAULT_STAGE_TEMPLATE_REFS[stage];
64
+ }
65
+ function normalizeText(value) {
66
+ return typeof value === 'string' ? value.trim() : '';
67
+ }
@@ -1,5 +1,5 @@
1
1
  import type { RoutingInstructionState } from './routing-instructions.js';
2
- type StopMessageSubset = Pick<RoutingInstructionState, 'stopMessageSource' | 'stopMessageText' | 'stopMessageMaxRepeats' | 'stopMessageUsed' | 'stopMessageUpdatedAt' | 'stopMessageLastUsedAt'>;
2
+ type StopMessageSubset = Pick<RoutingInstructionState, 'stopMessageSource' | 'stopMessageText' | 'stopMessageMaxRepeats' | 'stopMessageUsed' | 'stopMessageStage' | 'stopMessageStageMode' | 'stopMessageObservationHash' | 'stopMessageObservationStableCount' | 'stopMessageBdWorkState' | 'stopMessageUpdatedAt' | 'stopMessageLastUsedAt'>;
3
3
  /**
4
4
  * Decide whether we should overwrite in-memory stopMessage fields with persisted ones.
5
5
  *
@@ -41,6 +41,11 @@ export function mergeStopMessageFromPersisted(existing, persisted) {
41
41
  stopMessageText: persisted.stopMessageText,
42
42
  stopMessageMaxRepeats: persisted.stopMessageMaxRepeats,
43
43
  stopMessageUsed: persisted.stopMessageUsed,
44
+ stopMessageStage: persisted.stopMessageStage,
45
+ stopMessageStageMode: persisted.stopMessageStageMode,
46
+ stopMessageObservationHash: persisted.stopMessageObservationHash,
47
+ stopMessageObservationStableCount: persisted.stopMessageObservationStableCount,
48
+ stopMessageBdWorkState: persisted.stopMessageBdWorkState,
44
49
  stopMessageUpdatedAt: persisted.stopMessageUpdatedAt,
45
50
  stopMessageLastUsedAt: persisted.stopMessageLastUsedAt
46
51
  };
@@ -13,3 +13,12 @@ export interface OAuthTokenFileMatch {
13
13
  * 仅在 Node 环境下使用;如果环境不满足,返回空列表。
14
14
  */
15
15
  export declare function scanOAuthTokenFiles(oauthProviderId: string, authDir?: string): OAuthTokenFileMatch[];
16
+ /**
17
+ * 扫描 DeepSeek account token 文件。
18
+ *
19
+ * 约定:
20
+ * - 目录: ~/.routecodex/auth
21
+ * - 文件名: deepseek-account-<alias>.json
22
+ * 例如: deepseek-account-1.json, deepseek-account-2-work.json
23
+ */
24
+ export declare function scanDeepSeekAccountTokenFiles(authDir?: string): OAuthTokenFileMatch[];
@@ -2,6 +2,17 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
4
  const TOKEN_FILE_PATTERN = /^([a-z0-9_-]+)-oauth-(\d+)(?:-(.+))?\.json$/i;
5
+ const DEEPSEEK_ACCOUNT_TOKEN_PATTERN = /^deepseek-account-(.+)\.json$/i;
6
+ function resolveAuthDir(authDir) {
7
+ if (authDir && authDir.trim()) {
8
+ return authDir.trim();
9
+ }
10
+ const envDir = (process.env.ROUTECODEX_AUTH_DIR || process.env.RCC_AUTH_DIR || '').trim();
11
+ if (envDir) {
12
+ return envDir;
13
+ }
14
+ return path.join(os.homedir(), '.routecodex', 'auth');
15
+ }
5
16
  /**
6
17
  * 扫描本地 RouteCodex auth 目录中的 OAuth token 文件。
7
18
  *
@@ -19,9 +30,7 @@ export function scanOAuthTokenFiles(oauthProviderId, authDir) {
19
30
  if (!provider) {
20
31
  return [];
21
32
  }
22
- const baseDir = authDir && authDir.trim()
23
- ? authDir.trim()
24
- : path.join(os.homedir(), '.routecodex', 'auth');
33
+ const baseDir = resolveAuthDir(authDir);
25
34
  let entries;
26
35
  try {
27
36
  entries = fs.readdirSync(baseDir);
@@ -51,6 +60,58 @@ export function scanOAuthTokenFiles(oauthProviderId, authDir) {
51
60
  matches.sort((a, b) => a.sequence - b.sequence);
52
61
  return matches;
53
62
  }
63
+ /**
64
+ * 扫描 DeepSeek account token 文件。
65
+ *
66
+ * 约定:
67
+ * - 目录: ~/.routecodex/auth
68
+ * - 文件名: deepseek-account-<alias>.json
69
+ * 例如: deepseek-account-1.json, deepseek-account-2-work.json
70
+ */
71
+ export function scanDeepSeekAccountTokenFiles(authDir) {
72
+ if (!isNodeEnvironment()) {
73
+ return [];
74
+ }
75
+ const baseDir = resolveAuthDir(authDir);
76
+ let entries;
77
+ try {
78
+ entries = fs.readdirSync(baseDir);
79
+ }
80
+ catch {
81
+ return [];
82
+ }
83
+ const matches = [];
84
+ for (const entry of entries) {
85
+ if (!entry.endsWith('.json'))
86
+ continue;
87
+ const match = entry.match(DEEPSEEK_ACCOUNT_TOKEN_PATTERN);
88
+ if (!match)
89
+ continue;
90
+ const aliasRaw = (match[1] || '').trim();
91
+ if (!aliasRaw.length)
92
+ continue;
93
+ const numericMatch = aliasRaw.match(/^(\d+)(?:-|$)/);
94
+ const numericPrefix = numericMatch ? Number.parseInt(numericMatch[1], 10) : undefined;
95
+ matches.push({
96
+ filePath: path.join(baseDir, entry),
97
+ alias: aliasRaw,
98
+ ...(Number.isFinite(numericPrefix) ? { numericPrefix } : {})
99
+ });
100
+ }
101
+ matches.sort((a, b) => {
102
+ const aNum = typeof a.numericPrefix === 'number' ? a.numericPrefix : Number.POSITIVE_INFINITY;
103
+ const bNum = typeof b.numericPrefix === 'number' ? b.numericPrefix : Number.POSITIVE_INFINITY;
104
+ if (aNum !== bNum) {
105
+ return aNum - bNum;
106
+ }
107
+ return a.alias.localeCompare(b.alias);
108
+ });
109
+ return matches.map((entry, index) => ({
110
+ filePath: entry.filePath,
111
+ sequence: index + 1,
112
+ alias: entry.alias
113
+ }));
114
+ }
54
115
  function isNodeEnvironment() {
55
116
  return typeof process !== 'undefined' && !!process.release && process.release.name === 'node';
56
117
  }
@@ -8,6 +8,11 @@ export type ToolClassification = {
8
8
  export declare function detectVisionTool(request: StandardizedRequest): boolean;
9
9
  export declare function detectCodingTool(request: StandardizedRequest): boolean;
10
10
  export declare function detectWebTool(request: StandardizedRequest): boolean;
11
+ /**
12
+ * Detect if a web_search tool is explicitly declared in the request tools list.
13
+ * This is used for routing decisions in the classifier.
14
+ */
15
+ export declare function detectWebSearchToolDeclared(request: StandardizedRequest): boolean;
11
16
  export declare function extractMeaningfulDeclaredToolNames(tools: StandardizedRequest['tools'] | undefined): string[];
12
17
  export declare function detectLastAssistantToolCategory(messages: StandardizedMessage[]): ToolClassification | undefined;
13
18
  export declare function classifyToolCallForReport(call: StandardizedMessage['tool_calls'][number]): ToolClassification | undefined;
@@ -61,6 +61,8 @@ const COMMAND_ALIASES = new Map([
61
61
  ['perl5', 'perl']
62
62
  ]);
63
63
  const GIT_WRITE_SUBCOMMANDS = new Set(['add', 'commit', 'apply', 'am', 'rebase', 'checkout', 'merge']);
64
+ const GIT_SEARCH_SUBCOMMANDS = new Set(['grep', 'log', 'shortlog', 'reflog', 'blame']);
65
+ const BD_SEARCH_SUBCOMMANDS = new Set(['search']);
64
66
  const PACKAGE_MANAGER_COMMANDS = new Map([
65
67
  ['npm', new Set(['install'])],
66
68
  ['pnpm', new Set(['install'])],
@@ -113,6 +115,21 @@ export function detectWebTool(request) {
113
115
  WEB_TOOL_KEYWORDS.some((keyword) => normalizedDesc.includes(keyword)));
114
116
  });
115
117
  }
118
+ /**
119
+ * Detect if a web_search tool is explicitly declared in the request tools list.
120
+ * This is used for routing decisions in the classifier.
121
+ */
122
+ export function detectWebSearchToolDeclared(request) {
123
+ if (!Array.isArray(request.tools)) {
124
+ return false;
125
+ }
126
+ return request.tools.some((tool) => {
127
+ const functionName = extractToolName(tool);
128
+ const normalizedName = functionName.toLowerCase().replace(/[-_]/g, '');
129
+ // Match exact web_search tool name (with or without underscore/dash)
130
+ return normalizedName === 'websearch';
131
+ });
132
+ }
116
133
  export function extractMeaningfulDeclaredToolNames(tools) {
117
134
  if (!Array.isArray(tools) || tools.length === 0) {
118
135
  return [];
@@ -547,9 +564,31 @@ function isSearchBinary(binary, args) {
547
564
  if (SHELL_SEARCH_COMMANDS.has(normalized)) {
548
565
  return true;
549
566
  }
550
- if (normalized === 'git' && args.length > 0) {
551
- const sub = args[0].toLowerCase();
552
- if (sub === 'grep') {
567
+ if (normalized === 'git') {
568
+ if (containsSubcommand(args, GIT_SEARCH_SUBCOMMANDS)) {
569
+ return true;
570
+ }
571
+ }
572
+ if (normalized === 'bd') {
573
+ if (containsSubcommand(args, BD_SEARCH_SUBCOMMANDS)) {
574
+ return true;
575
+ }
576
+ }
577
+ return false;
578
+ }
579
+ function containsSubcommand(args, candidates) {
580
+ if (!Array.isArray(args) || args.length === 0 || !candidates.size) {
581
+ return false;
582
+ }
583
+ for (const raw of args) {
584
+ if (typeof raw !== 'string') {
585
+ continue;
586
+ }
587
+ const token = raw.trim().toLowerCase();
588
+ if (!token || token.startsWith('-')) {
589
+ continue;
590
+ }
591
+ if (candidates.has(token)) {
553
592
  return true;
554
593
  }
555
594
  }
@@ -44,6 +44,10 @@ export interface ProviderAuthConfig {
44
44
  oauthProviderId?: string;
45
45
  rawType?: string;
46
46
  }
47
+ export interface DeepSeekCompatRuntimeOptions {
48
+ strictToolRequired?: boolean;
49
+ textToolFallback?: boolean;
50
+ }
47
51
  export interface ProviderProfile {
48
52
  providerKey: string;
49
53
  providerType: string;
@@ -57,6 +61,7 @@ export interface ProviderProfile {
57
61
  responsesConfig?: ResponsesProviderConfig;
58
62
  streaming?: StreamingPreference;
59
63
  maxContextTokens?: number;
64
+ deepseek?: DeepSeekCompatRuntimeOptions;
60
65
  /**
61
66
  * When true, this provider must be skipped for any request that
62
67
  * requires server-side tool orchestration (e.g. web_search).
@@ -83,6 +88,7 @@ export interface ProviderRuntimeProfile {
83
88
  modelContextTokens?: Record<string, number>;
84
89
  defaultContextTokens?: number;
85
90
  maxContextTokens?: number;
91
+ deepseek?: DeepSeekCompatRuntimeOptions;
86
92
  /**
87
93
  * Provider-level flag propagated from virtualrouter.providers[*].
88
94
  * When true, VirtualRouterEngine will skip this runtime for any
@@ -381,6 +387,7 @@ export interface RoutingFeatures {
381
387
  hasVisionTool: boolean;
382
388
  hasImageAttachment: boolean;
383
389
  hasWebTool: boolean;
390
+ hasWebSearchToolDeclared?: boolean;
384
391
  hasCodingTool: boolean;
385
392
  hasThinkingKeyword: boolean;
386
393
  estimatedTokens: number;
@@ -417,6 +424,7 @@ export interface TargetMetadata {
417
424
  responsesConfig?: ResponsesProviderConfig;
418
425
  streaming?: StreamingPreference;
419
426
  maxContextTokens?: number;
427
+ deepseek?: DeepSeekCompatRuntimeOptions;
420
428
  /**
421
429
  * Route-level flags propagated from the virtual router.
422
430
  * These are derived from routing pools and webSearch config and
@@ -449,7 +457,7 @@ export interface RoutingDiagnostics {
449
457
  confidence: number;
450
458
  }
451
459
  export interface StopMessageStateSnapshot {
452
- stopMessageText: string;
460
+ stopMessageText?: string;
453
461
  stopMessageMaxRepeats: number;
454
462
  /**
455
463
  * stopMessage 来源:
@@ -460,6 +468,16 @@ export interface StopMessageStateSnapshot {
460
468
  stopMessageUsed?: number;
461
469
  stopMessageUpdatedAt?: number;
462
470
  stopMessageLastUsedAt?: number;
471
+ stopMessageStage?: string;
472
+ stopMessageStageMode?: 'on' | 'off' | 'auto';
473
+ stopMessageObservationHash?: string;
474
+ stopMessageObservationStableCount?: number;
475
+ stopMessageBdWorkState?: string;
476
+ }
477
+ export interface PreCommandStateSnapshot {
478
+ preCommandScriptPath: string;
479
+ preCommandSource?: string;
480
+ preCommandUpdatedAt?: number;
463
481
  }
464
482
  export interface RoutingStatusSnapshot {
465
483
  routes: Record<string, {
@@ -4,6 +4,7 @@
4
4
  export const DEFAULT_MODEL_CONTEXT_TOKENS = 200_000;
5
5
  export const DEFAULT_ROUTE = 'default';
6
6
  export const ROUTE_PRIORITY = [
7
+ 'multimodal',
7
8
  'vision',
8
9
  'longcontext',
9
10
  'web_search',
@@ -1,7 +1,7 @@
1
1
  import type { ClockConfigSnapshot } from './types.js';
2
2
  export declare const CLOCK_CONFIG_DEFAULTS: {
3
3
  readonly retentionMs: number;
4
- readonly dueWindowMs: 60000;
4
+ readonly dueWindowMs: 0;
5
5
  readonly tickMs: 60000;
6
6
  readonly holdNonStreaming: true;
7
7
  readonly holdMaxMs: 60000;
@@ -1,10 +1,35 @@
1
1
  export const CLOCK_CONFIG_DEFAULTS = {
2
2
  retentionMs: 20 * 60_000,
3
- dueWindowMs: 60_000,
3
+ dueWindowMs: 0,
4
4
  tickMs: 60_000,
5
5
  holdNonStreaming: true,
6
6
  holdMaxMs: 60_000
7
7
  };
8
+ function parseBooleanWithDefault(value, fallback) {
9
+ if (value === undefined) {
10
+ return fallback;
11
+ }
12
+ if (typeof value === 'boolean') {
13
+ return value;
14
+ }
15
+ if (typeof value === 'number') {
16
+ if (value === 1)
17
+ return true;
18
+ if (value === 0)
19
+ return false;
20
+ return fallback;
21
+ }
22
+ if (typeof value === 'string') {
23
+ const normalized = value.trim().toLowerCase();
24
+ if (normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on') {
25
+ return true;
26
+ }
27
+ if (normalized === 'false' || normalized === '0' || normalized === 'no' || normalized === 'off') {
28
+ return false;
29
+ }
30
+ }
31
+ return fallback;
32
+ }
8
33
  export function normalizeClockConfig(raw) {
9
34
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
10
35
  return null;
@@ -25,9 +50,7 @@ export function normalizeClockConfig(raw) {
25
50
  const tickMs = typeof record.tickMs === 'number' && Number.isFinite(record.tickMs) && record.tickMs >= 0
26
51
  ? Math.floor(record.tickMs)
27
52
  : CLOCK_CONFIG_DEFAULTS.tickMs;
28
- const holdNonStreaming = record.holdNonStreaming === true ||
29
- (typeof record.holdNonStreaming === 'string' && record.holdNonStreaming.trim().toLowerCase() === 'true') ||
30
- (typeof record.holdNonStreaming === 'number' && record.holdNonStreaming === 1);
53
+ const holdNonStreaming = parseBooleanWithDefault(record.holdNonStreaming, CLOCK_CONFIG_DEFAULTS.holdNonStreaming);
31
54
  const holdMaxMs = typeof record.holdMaxMs === 'number' && Number.isFinite(record.holdMaxMs) && record.holdMaxMs >= 0
32
55
  ? Math.floor(record.holdMaxMs)
33
56
  : CLOCK_CONFIG_DEFAULTS.holdMaxMs;
@@ -1,4 +1,29 @@
1
1
  let clockOffsetMs = 0;
2
+ const CLOCK_OVERDUE_AUTO_REMOVE_MS = 60_000;
3
+ function normalizeRecurrence(raw) {
4
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
5
+ return undefined;
6
+ }
7
+ const record = raw;
8
+ const rawKind = typeof record.kind === 'string' ? record.kind.trim().toLowerCase() : '';
9
+ const kind = rawKind === 'daily' || rawKind === 'weekly' || rawKind === 'interval'
10
+ ? rawKind
11
+ : undefined;
12
+ const maxRunsRaw = Number(record.maxRuns);
13
+ const maxRuns = Number.isFinite(maxRunsRaw) ? Math.floor(maxRunsRaw) : NaN;
14
+ if (!kind || !Number.isFinite(maxRuns) || maxRuns <= 0) {
15
+ return undefined;
16
+ }
17
+ if (kind === 'interval') {
18
+ const minutesRaw = Number(record.everyMinutes);
19
+ const everyMinutes = Number.isFinite(minutesRaw) ? Math.floor(minutesRaw) : NaN;
20
+ if (!Number.isFinite(everyMinutes) || everyMinutes <= 0) {
21
+ return undefined;
22
+ }
23
+ return { kind, maxRuns, everyMinutes };
24
+ }
25
+ return { kind, maxRuns };
26
+ }
2
27
  export function setClockOffsetMs(value) {
3
28
  if (typeof value !== 'number' || !Number.isFinite(value))
4
29
  return;
@@ -42,6 +67,7 @@ export function coerceState(raw, sessionId) {
42
67
  ? e.notBeforeRequestId.trim()
43
68
  : undefined;
44
69
  const deliveryCount = typeof e.deliveryCount === 'number' && Number.isFinite(e.deliveryCount) ? Math.max(0, Math.floor(e.deliveryCount)) : 0;
70
+ const recurrence = normalizeRecurrence(e.recurrence);
45
71
  tasks.push({
46
72
  taskId,
47
73
  sessionId,
@@ -53,7 +79,8 @@ export function coerceState(raw, sessionId) {
53
79
  ...(args ? { arguments: args } : {}),
54
80
  ...(deliveredAtMs !== undefined ? { deliveredAtMs } : {}),
55
81
  deliveryCount,
56
- ...(notBeforeRequestId ? { notBeforeRequestId } : {})
82
+ ...(notBeforeRequestId ? { notBeforeRequestId } : {}),
83
+ ...(recurrence ? { recurrence } : {})
57
84
  });
58
85
  }
59
86
  const updatedAtMs = typeof record.updatedAtMs === 'number' && Number.isFinite(record.updatedAtMs) ? Math.floor(record.updatedAtMs) : nowMs();
@@ -66,7 +93,19 @@ export function cleanExpiredTasks(tasks, config, atMs) {
66
93
  continue;
67
94
  if (!Number.isFinite(task.dueAtMs))
68
95
  continue;
69
- if (atMs > task.dueAtMs + config.retentionMs) {
96
+ const recurrence = normalizeRecurrence(task.recurrence);
97
+ const deliveryCount = typeof task.deliveryCount === 'number' && Number.isFinite(task.deliveryCount)
98
+ ? Math.max(0, Math.floor(task.deliveryCount))
99
+ : 0;
100
+ if (recurrence) {
101
+ if (deliveryCount >= recurrence.maxRuns) {
102
+ continue;
103
+ }
104
+ out.push({ ...task, recurrence });
105
+ continue;
106
+ }
107
+ const oneShotRetentionMs = Math.max(0, Math.min(config.retentionMs, CLOCK_OVERDUE_AUTO_REMOVE_MS));
108
+ if (atMs > task.dueAtMs + oneShotRetentionMs) {
70
109
  continue;
71
110
  }
72
111
  out.push(task);
@@ -1,5 +1,5 @@
1
- export type { ClockConfigSnapshot, ClockReservation, ClockScheduleItem, ClockSessionState, ClockTask } from './types.js';
1
+ export type { ClockConfigSnapshot, ClockReservation, ClockScheduleItem, ClockSessionState, ClockTask, ClockTaskRecurrence, ClockTaskUpdatePatch } from './types.js';
2
2
  export { normalizeClockConfig, resolveClockConfig } from './config.js';
3
3
  export { startClockDaemonIfNeeded, stopClockDaemonForTests } from './daemon.js';
4
4
  export { loadClockSessionState, clearClockSession } from './session-store.js';
5
- export { cancelClockTask, clearClockTasks, commitClockReservation, findNextUndeliveredDueAtMs, listClockTasks, parseDueAtMs, reserveDueTasksForRequest, scheduleClockTasks, selectDueUndeliveredTasks } from './tasks.js';
5
+ export { cancelClockTask, clearClockTasks, commitClockReservation, findNextUndeliveredDueAtMs, listClockSessionIds, listClockTasks, parseDueAtMs, reserveDueTasksForRequest, scheduleClockTasks, selectDueUndeliveredTasks, updateClockTask } from './tasks.js';
@@ -1,4 +1,4 @@
1
1
  export { normalizeClockConfig, resolveClockConfig } from './config.js';
2
2
  export { startClockDaemonIfNeeded, stopClockDaemonForTests } from './daemon.js';
3
3
  export { loadClockSessionState, clearClockSession } from './session-store.js';
4
- export { cancelClockTask, clearClockTasks, commitClockReservation, findNextUndeliveredDueAtMs, listClockTasks, parseDueAtMs, reserveDueTasksForRequest, scheduleClockTasks, selectDueUndeliveredTasks } from './tasks.js';
4
+ export { cancelClockTask, clearClockTasks, commitClockReservation, findNextUndeliveredDueAtMs, listClockSessionIds, listClockTasks, parseDueAtMs, reserveDueTasksForRequest, scheduleClockTasks, selectDueUndeliveredTasks, updateClockTask } from './tasks.js';
@@ -1,7 +1,9 @@
1
- import type { ClockConfigSnapshot, ClockReservation, ClockScheduleItem, ClockTask } from './types.js';
1
+ import type { ClockConfigSnapshot, ClockReservation, ClockScheduleItem, ClockTask, ClockTaskUpdatePatch } from './types.js';
2
2
  export declare function parseDueAtMs(value: unknown): number | null;
3
+ export declare function listClockSessionIds(): Promise<string[]>;
3
4
  export declare function listClockTasks(sessionId: string, config: ClockConfigSnapshot): Promise<ClockTask[]>;
4
5
  export declare function scheduleClockTasks(sessionId: string, items: ClockScheduleItem[], config: ClockConfigSnapshot): Promise<ClockTask[]>;
6
+ export declare function updateClockTask(sessionId: string, taskId: string, patch: ClockTaskUpdatePatch, config: ClockConfigSnapshot): Promise<ClockTask | null>;
5
7
  export declare function cancelClockTask(sessionId: string, taskId: string, config: ClockConfigSnapshot): Promise<boolean>;
6
8
  export declare function clearClockTasks(sessionId: string, config: ClockConfigSnapshot): Promise<number>;
7
9
  export declare function selectDueUndeliveredTasks(tasks: ClockTask[], config: ClockConfigSnapshot, atMs: number): ClockTask[];