@v587d/capital-generation 2.8.3 → 2.9.0

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 (164) hide show
  1. package/CHANGELOG.md +54 -1
  2. package/README.md +40 -19
  3. package/chart-ui/README.md +1 -1
  4. package/chart-ui/client.src.cjs +7 -16
  5. package/chart-ui/index.js +12 -33
  6. package/chart-ui/package.json +2 -2
  7. package/config/client.js +18 -39
  8. package/config/client.src.cjs +79 -217
  9. package/config/index.js +22 -72
  10. package/config/package.json +2 -2
  11. package/cordis.patch.yml +46 -51
  12. package/lib/agents/bash-guard.d.ts +9 -55
  13. package/lib/agents/bash-guard.js +27 -67
  14. package/lib/agents/preset-roster.d.ts +22 -0
  15. package/lib/agents/preset-roster.js +87 -0
  16. package/lib/agents/role-tool-policy.d.ts +46 -0
  17. package/lib/agents/role-tool-policy.js +205 -0
  18. package/lib/agents/root-tool-policy.d.ts +28 -124
  19. package/lib/agents/root-tool-policy.js +164 -237
  20. package/lib/agents/selected-skills-provider.d.ts +2 -6
  21. package/lib/agents/selected-skills-provider.js +27 -84
  22. package/lib/capital-settings.d.ts +27 -0
  23. package/lib/capital-settings.js +23 -0
  24. package/lib/chart/artifact-ref.d.ts +3 -8
  25. package/lib/chart/artifact-ref.js +3 -8
  26. package/lib/chart/downsample.d.ts +2 -5
  27. package/lib/chart/downsample.js +3 -8
  28. package/lib/chart/errors.d.ts +3 -7
  29. package/lib/chart/errors.js +1 -2
  30. package/lib/chart/event-context.d.ts +8 -0
  31. package/lib/chart/event-context.js +39 -0
  32. package/lib/chart/events.d.ts +21 -78
  33. package/lib/chart/events.js +40 -189
  34. package/lib/chart/html.d.ts +2 -5
  35. package/lib/chart/html.js +8 -15
  36. package/lib/chart/runtime.d.ts +5 -18
  37. package/lib/chart/runtime.js +14 -34
  38. package/lib/chart/series.d.ts +10 -17
  39. package/lib/chart/series.js +22 -47
  40. package/lib/chart/source-token.d.ts +5 -13
  41. package/lib/chart/source-token.js +18 -17
  42. package/lib/chart/spec.d.ts +6 -12
  43. package/lib/chart/spec.js +7 -31
  44. package/lib/chart/tool.d.ts +6 -29
  45. package/lib/chart/tool.js +9 -23
  46. package/lib/chart-render-entry.d.ts +9 -0
  47. package/lib/chart-render-entry.js +17 -0
  48. package/lib/chart-source-entry.d.ts +13 -0
  49. package/lib/chart-source-entry.js +20 -0
  50. package/lib/collect-entry.d.ts +9 -0
  51. package/lib/collect-entry.js +10 -0
  52. package/lib/collector-runtime.d.ts +22 -0
  53. package/lib/collector-runtime.js +97 -0
  54. package/lib/data-collector/args.d.ts +6 -24
  55. package/lib/data-collector/args.js +6 -24
  56. package/lib/data-collector/dataset-tools.d.ts +7 -1
  57. package/lib/data-collector/dataset-tools.js +27 -56
  58. package/lib/data-collector/describe.d.ts +8 -33
  59. package/lib/data-collector/describe.js +17 -70
  60. package/lib/data-collector/hub.d.ts +27 -85
  61. package/lib/data-collector/hub.js +19 -85
  62. package/lib/data-collector/query-time.d.ts +7 -26
  63. package/lib/data-collector/query-time.js +8 -27
  64. package/lib/data-collector/store.d.ts +40 -117
  65. package/lib/data-collector/store.js +32 -111
  66. package/lib/data-collector/time-axis.d.ts +16 -76
  67. package/lib/data-collector/time-axis.js +25 -111
  68. package/lib/data-collector/tools.d.ts +6 -6
  69. package/lib/data-collector/tools.js +34 -43
  70. package/lib/dataset-read-entry.d.ts +9 -0
  71. package/lib/dataset-read-entry.js +7 -0
  72. package/lib/index.d.ts +7 -117
  73. package/lib/index.js +43 -422
  74. package/lib/net/eastmoney-client.d.ts +13 -65
  75. package/lib/net/eastmoney-client.js +12 -53
  76. package/lib/net/throttle.d.ts +2 -13
  77. package/lib/net/throttle.js +3 -14
  78. package/lib/ocr/client.d.ts +12 -21
  79. package/lib/ocr/client.js +23 -40
  80. package/lib/ocr/markdown.d.ts +20 -26
  81. package/lib/ocr/markdown.js +23 -36
  82. package/lib/ocr/store.d.ts +14 -47
  83. package/lib/ocr/store.js +15 -45
  84. package/lib/ocr-entry.d.ts +9 -0
  85. package/lib/ocr-entry.js +10 -0
  86. package/lib/policy-entry.d.ts +17 -0
  87. package/lib/policy-entry.js +22 -0
  88. package/lib/preset-claim.d.ts +22 -0
  89. package/lib/preset-claim.js +24 -0
  90. package/lib/preset-ids.d.ts +38 -0
  91. package/lib/preset-ids.js +46 -0
  92. package/lib/retriever-config.d.ts +37 -0
  93. package/lib/retriever-config.js +36 -0
  94. package/lib/retriever-runtime.d.ts +31 -0
  95. package/lib/retriever-runtime.js +82 -0
  96. package/lib/root-extras-entry.d.ts +17 -0
  97. package/lib/root-extras-entry.js +25 -0
  98. package/lib/runtime-entry.d.ts +15 -0
  99. package/lib/runtime-entry.js +5 -0
  100. package/lib/runtime.d.ts +21 -0
  101. package/lib/runtime.js +33 -0
  102. package/lib/search-entry.d.ts +9 -0
  103. package/lib/search-entry.js +9 -0
  104. package/lib/sources/eastmoney-http.js +20 -68
  105. package/lib/sources/ftshare-rest.d.ts +6 -34
  106. package/lib/sources/ftshare-rest.js +84 -292
  107. package/lib/sources/fuyao-core.d.ts +8 -18
  108. package/lib/sources/fuyao-core.js +10 -21
  109. package/lib/sources/fuyao-rest.d.ts +2 -6
  110. package/lib/sources/fuyao-rest.js +23 -102
  111. package/lib/sources/security-code.d.ts +6 -12
  112. package/lib/sources/security-code.js +6 -12
  113. package/lib/sources/security-identity.d.ts +1 -4
  114. package/lib/sources/security-identity.js +3 -8
  115. package/lib/sources/tencent-http.js +14 -28
  116. package/lib/sources/tencent-public-core.d.ts +8 -60
  117. package/lib/sources/tencent-public-core.js +12 -77
  118. package/lib/sources/wind-mcp.d.ts +0 -5
  119. package/lib/sources/wind-mcp.js +23 -78
  120. package/lib/sources-entry.d.ts +9 -0
  121. package/lib/sources-entry.js +9 -0
  122. package/lib/time/tools.d.ts +8 -31
  123. package/lib/time/tools.js +30 -66
  124. package/lib/time-entry.d.ts +9 -0
  125. package/lib/time-entry.js +7 -0
  126. package/lib/tool-exec.d.ts +45 -11
  127. package/lib/tool-exec.js +59 -6
  128. package/lib/user-customization.d.ts +14 -0
  129. package/lib/user-customization.js +47 -0
  130. package/lib/watchlist/tools.d.ts +3 -13
  131. package/lib/watchlist/tools.js +10 -30
  132. package/lib/watchlist-entry.d.ts +9 -0
  133. package/lib/watchlist-entry.js +6 -0
  134. package/lib/web-retriever/engines.d.ts +6 -11
  135. package/lib/web-retriever/engines.js +9 -14
  136. package/lib/web-retriever/html-markdown.d.ts +14 -34
  137. package/lib/web-retriever/html-markdown.js +39 -75
  138. package/lib/web-retriever/local-fetch.d.ts +22 -58
  139. package/lib/web-retriever/local-fetch.js +33 -77
  140. package/lib/web-retriever/retriever.d.ts +6 -18
  141. package/lib/web-retriever/retriever.js +7 -7
  142. package/lib/web-retriever/sources.d.ts +37 -124
  143. package/lib/web-retriever/sources.js +64 -196
  144. package/lib/web-retriever/tools.d.ts +43 -27
  145. package/lib/web-retriever/tools.js +99 -137
  146. package/lib/web-retriever/wind-client.d.ts +14 -31
  147. package/lib/web-retriever/wind-client.js +16 -32
  148. package/locale/zh.json +1 -1
  149. package/package.json +67 -7
  150. package/preset/capital-generation/README.md +87 -53
  151. package/preset/capital-generation/agent.patch.yml +526 -337
  152. package/preset/capital-generation/skills/capital-data-profile/SKILL.md +2 -2
  153. package/preset/capital-generation/skills/capital-data-protocol/SKILL.md +1 -1
  154. package/preset/capital-generation/skills/capital-equity-research/SKILL.md +1 -1
  155. package/preset/capital-generation/skills/capital-event-impact/SKILL.md +1 -1
  156. package/preset/capital-generation/skills/capital-orchestration/SKILL.md +15 -19
  157. package/preset/capital-generation/skills/capital-portfolio-review/SKILL.md +1 -1
  158. package/preset/capital-generation/skills/capital-visualization-protocol/SKILL.md +9 -12
  159. package/preset/capital-generation/skills/capital-web-evidence/SKILL.md +1 -1
  160. package/watchlist/client.js +15 -148
  161. package/watchlist/client.src.cjs +148 -662
  162. package/watchlist/holding-rules.js +11 -38
  163. package/watchlist/index.js +127 -397
  164. package/watchlist/package.json +2 -2
@@ -0,0 +1,36 @@
1
+ /** web_retriever configuration surface: types, defaults, and consumer-side default filling.
2
+ * Kept free of Cordis imports so role entries can reuse it without cycling through `index.js`. */
3
+ import { LOCAL_FETCH_CLIENT_VERSION } from './web-retriever/local-fetch.js';
4
+ /** Sole default source for both the schema and `resolveLocalFetchConfig()`.
5
+ * timeoutMs 30000 matches dsh-web-fetch-http; 15s misreads slow proxy failover as TIMEOUT.
6
+ * maxContentChars applies to raw HTML before markdown conversion, so size it for HTML.
7
+ * userAgent is a product token: a bare version looks like a crawler to WAFs. */
8
+ export const LOCAL_FETCH_DEFAULTS = {
9
+ enabled: true,
10
+ timeoutMs: 30_000,
11
+ maxBytes: 524_288,
12
+ maxContentChars: 100_000,
13
+ maxRedirects: 5,
14
+ userAgent: '@v587d/capital-generation',
15
+ };
16
+ /** Empty endpoint/model defer to `src/ocr/client.ts`; pollBudgetMs 0 defers to its DEFAULT_WAIT_BUDGET_MS. */
17
+ export const PADDLE_OCR_DEFAULTS = {
18
+ endpoint: '',
19
+ credentialRef: 'PADDLE_OCR_TOKEN',
20
+ model: '',
21
+ pollBudgetMs: 0,
22
+ };
23
+ /** Consumers fill defaults themselves: `apply()` can receive a raw object that never passed the schema. */
24
+ export function resolveLocalFetchConfig(config) {
25
+ const source = config ?? {};
26
+ // UA tri-state: unset → product default; empty string → plugin version; explicit value → as-is.
27
+ const userAgent = source.userAgent ?? LOCAL_FETCH_DEFAULTS.userAgent;
28
+ return {
29
+ enabled: source.enabled !== false,
30
+ timeoutMs: source.timeoutMs ?? LOCAL_FETCH_DEFAULTS.timeoutMs,
31
+ maxBytes: source.maxBytes ?? LOCAL_FETCH_DEFAULTS.maxBytes,
32
+ maxContentChars: source.maxContentChars ?? LOCAL_FETCH_DEFAULTS.maxContentChars,
33
+ maxRedirects: source.maxRedirects ?? LOCAL_FETCH_DEFAULTS.maxRedirects,
34
+ userAgent: userAgent || LOCAL_FETCH_CLIENT_VERSION,
35
+ };
36
+ }
@@ -0,0 +1,31 @@
1
+ import { type WindClient } from './web-retriever/wind-client.js';
2
+ import { type PaddleOcrClient } from './ocr/client.js';
3
+ import { WebRetriever, type LocalFetcherLike } from './web-retriever/retriever.js';
4
+ import { type LocalFetchConfig, type RetrieverConfig } from './retriever-config.js';
5
+ interface CredentialContext {
6
+ get(name: string): unknown;
7
+ }
8
+ /** credentials first, env var fallback; resolve per call, never cache (boot-time races). */
9
+ export declare function resolveRetrieverCredential(ctx: CredentialContext, ref: string): Promise<string | undefined>;
10
+ /** Wind ref + per-call resolver; `wind_docs` and `wind_*` market data share the key. */
11
+ export declare function createWindCredentialResolver(ctx: CredentialContext, credentialRef?: string): {
12
+ credentialRef: string;
13
+ resolve: () => Promise<string | undefined>;
14
+ };
15
+ export interface RetrieverRuntime {
16
+ retriever: WebRetriever;
17
+ windClient: WindClient;
18
+ ocrClient: PaddleOcrClient;
19
+ localFetchConfig: Required<LocalFetchConfig>;
20
+ resolveWindApiKey: () => Promise<string | undefined>;
21
+ /** Reference name only (for logs/diagnostics), never the secret. */
22
+ windCredentialRef: string;
23
+ /** Present only when localFetch.enabled is true. */
24
+ localFetch?: LocalFetcherLike;
25
+ }
26
+ /**
27
+ * `config` is the raw, unvalidated object from preset/settings, so every default must be
28
+ * applied here rather than relying on Schemastery.
29
+ */
30
+ export declare function createRetrieverRuntime(ctx: CredentialContext, config?: RetrieverConfig): RetrieverRuntime;
31
+ export {};
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Sole construction point for AnySearch / Wind / local fetch / PaddleOCR / WebRetriever.
3
+ * Search, source and OCR rows reuse it; copies would drift defaults, credential
4
+ * resolution and OCR budgets. Registers no tools or services.
5
+ */
6
+ import { createAnySearchClient, envKey } from './web-retriever/engines.js';
7
+ import { createHttpRequester, createLocalFetcher } from './web-retriever/local-fetch.js';
8
+ import { createWindClient } from './web-retriever/wind-client.js';
9
+ import { createPaddleOcrClient } from './ocr/client.js';
10
+ import { WebRetriever } from './web-retriever/retriever.js';
11
+ import { PADDLE_OCR_DEFAULTS, resolveLocalFetchConfig, } from './retriever-config.js';
12
+ /** credentials first, env var fallback; resolve per call, never cache (boot-time races). */
13
+ export async function resolveRetrieverCredential(ctx, ref) {
14
+ try {
15
+ const credentials = ctx.get('credentials');
16
+ const resolved = await credentials?.resolve?.(ref);
17
+ if (resolved?.value)
18
+ return resolved.value;
19
+ }
20
+ catch {
21
+ // Fall back to the env var when the credentials service is unavailable.
22
+ }
23
+ return envKey(ref);
24
+ }
25
+ /** Wind ref + per-call resolver; `wind_docs` and `wind_*` market data share the key. */
26
+ export function createWindCredentialResolver(ctx, credentialRef = 'WIND_API_KEY') {
27
+ const ref = credentialRef || 'WIND_API_KEY';
28
+ return { credentialRef: ref, resolve: () => resolveRetrieverCredential(ctx, ref) };
29
+ }
30
+ /**
31
+ * `config` is the raw, unvalidated object from preset/settings, so every default must be
32
+ * applied here rather than relying on Schemastery.
33
+ */
34
+ export function createRetrieverRuntime(ctx, config = {}) {
35
+ const credentialRef = config.credentialRef || 'ANYSEARCH_API_KEY';
36
+ const client = createAnySearchClient(config.baseURL || undefined, () => resolveRetrieverCredential(ctx, credentialRef));
37
+ const windDocs = config.windDocs ?? {};
38
+ const windCredential = createWindCredentialResolver(ctx, windDocs.credentialRef);
39
+ const windCredentialRef = windCredential.credentialRef;
40
+ const resolveWindApiKey = windCredential.resolve;
41
+ const windClient = createWindClient({
42
+ endpoint: windDocs.endpoint || undefined,
43
+ timeoutMs: windDocs.timeoutMs || undefined,
44
+ resolveApiKey: resolveWindApiKey,
45
+ });
46
+ const localFetchConfig = resolveLocalFetchConfig(config.localFetch);
47
+ const localFetch = localFetchConfig.enabled
48
+ ? createLocalFetcher({
49
+ timeoutMs: localFetchConfig.timeoutMs,
50
+ maxBytes: localFetchConfig.maxBytes,
51
+ maxContentChars: localFetchConfig.maxContentChars,
52
+ maxRedirects: localFetchConfig.maxRedirects,
53
+ userAgent: localFetchConfig.userAgent,
54
+ })
55
+ : undefined;
56
+ const paddleOcrConfig = config.paddleOcr ?? {};
57
+ const paddleOcrCredentialRef = paddleOcrConfig.credentialRef || PADDLE_OCR_DEFAULTS.credentialRef;
58
+ const ocrClient = createPaddleOcrClient({
59
+ endpoint: paddleOcrConfig.endpoint || undefined,
60
+ model: paddleOcrConfig.model || undefined,
61
+ ...(paddleOcrConfig.pollBudgetMs ? { waitBudgetMs: paddleOcrConfig.pollBudgetMs } : {}),
62
+ resolveToken: () => resolveRetrieverCredential(ctx, paddleOcrCredentialRef),
63
+ // Reuse the local-fetch requester (URL/IP gates, size and timeout caps). 120s because
64
+ // PDF multipart upload and whole-document JSONL exceed the 30s web-page budget.
65
+ request: createHttpRequester({
66
+ timeoutMs: 120_000,
67
+ maxBytes: 1_048_576,
68
+ maxContentChars: 1_048_576,
69
+ maxRedirects: localFetchConfig.maxRedirects,
70
+ userAgent: localFetchConfig.userAgent,
71
+ }).request,
72
+ });
73
+ return {
74
+ retriever: new WebRetriever(client, localFetch),
75
+ windClient,
76
+ ocrClient,
77
+ localFetchConfig,
78
+ resolveWindApiKey,
79
+ windCredentialRef,
80
+ ...(localFetch === undefined ? {} : { localFetch }),
81
+ };
82
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * `tools/root-extras` row: root-only selected-skills provider and the `customPersona`
3
+ * system-prompt section. This row registers no tools; both pieces re-read settings
4
+ * every turn so saved changes apply on the next step.
5
+ */
6
+ import type { Context } from '@deepseek-ai/cordis';
7
+ import { type SelectedSkillSelection } from './agents/selected-skills-provider.js';
8
+ export declare const name = "capital-root-extras";
9
+ export declare const inject: string[];
10
+ interface RootExtrasConfig {
11
+ customPersona?: string;
12
+ selectedSkills?: SelectedSkillSelection;
13
+ }
14
+ /** Re-read per turn so a settings save applies to the same session on the next step. */
15
+ export declare function readRootExtrasConfig(ctx: Context): RootExtrasConfig;
16
+ export declare function apply(ctx: Context): void;
17
+ export {};
@@ -0,0 +1,25 @@
1
+ import { registerRootSelectedSkills } from './agents/selected-skills-provider.js';
2
+ import { userCustomizationSection } from './user-customization.js';
3
+ import { readCapitalConfig } from './capital-settings.js';
4
+ export const name = 'capital-root-extras';
5
+ export const inject = ['systemPrompt'];
6
+ /** Re-read per turn so a settings save applies to the same session on the next step. */
7
+ export function readRootExtrasConfig(ctx) {
8
+ const config = readCapitalConfig(ctx);
9
+ return { customPersona: config.customPersona, selectedSkills: config.selectedSkills };
10
+ }
11
+ export function apply(ctx) {
12
+ registerRootSelectedSkills(ctx, () => readRootExtrasConfig(ctx).selectedSkills ?? {});
13
+ let lastPersonaError;
14
+ const section = userCustomizationSection(() => readRootExtrasConfig(ctx).customPersona, (error) => {
15
+ const message = error instanceof Error ? error.message : String(error);
16
+ if (message === lastPersonaError)
17
+ return;
18
+ lastPersonaError = message;
19
+ ctx.logger?.warn?.(`capital-generation: customPersona 未加载(${message})`);
20
+ });
21
+ ctx.effect(() => {
22
+ const dispose = ctx.systemPrompt.section(section);
23
+ return typeof dispose === 'function' ? dispose : () => { };
24
+ }, 'capital-generation.user-customization()');
25
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Host-plane Capital runtime row. Chart source tokens are issued by data_junior and
3
+ * redeemed by the specialist, so both presets must see one registry; provide it here
4
+ * instead of per preset. The module-level cache still keys by root for older call sites.
5
+ */
6
+ import type { Context } from '@deepseek-ai/cordis';
7
+ import { type CapitalShared } from './runtime.js';
8
+ declare module '@deepseek-ai/cordis' {
9
+ interface Context {
10
+ /** Shared by the Capital root, junior and specialist presets. */
11
+ capitalChartRuntime: CapitalShared;
12
+ }
13
+ }
14
+ export declare const name = "capital-runtime";
15
+ export declare function apply(ctx: Context): void;
@@ -0,0 +1,5 @@
1
+ import { capitalSharedRuntime } from './runtime.js';
2
+ export const name = 'capital-runtime';
3
+ export function apply(ctx) {
4
+ ctx.provide('capitalChartRuntime', capitalSharedRuntime(ctx));
5
+ }
@@ -0,0 +1,21 @@
1
+ import { WorkspaceDatasetStore } from './data-collector/store.js';
2
+ import { ChartArtifactRegistry } from './chart/artifact-ref.js';
3
+ import { ChartSourceTokenStore } from './chart/source-token.js';
4
+ /** Structural type so tests can pass a fake ctx. */
5
+ export interface RuntimeContext {
6
+ get(name: string): unknown;
7
+ root?: unknown;
8
+ }
9
+ export interface CapitalShared {
10
+ sourceTokens: ChartSourceTokenStore;
11
+ artifacts: ChartArtifactRegistry;
12
+ }
13
+ /**
14
+ * One Dataset store per mount, deliberately not a singleton: the hub registers source
15
+ * capabilities once per isolate realm, so a shared hub throws `data source capability
16
+ * already registered`. Stores only touch workspace via host fs, no state to synchronize.
17
+ */
18
+ export declare function capitalDatasetStore(ctx: RuntimeContext): WorkspaceDatasetStore;
19
+ export declare function capitalSharedRuntime(ctx: RuntimeContext): CapitalShared;
20
+ /** Test-only: drop the per-root cache. */
21
+ export declare function resetCapitalRuntime(): void;
package/lib/runtime.js ADDED
@@ -0,0 +1,33 @@
1
+ import { WorkspaceDatasetStore } from './data-collector/store.js';
2
+ import { ChartArtifactRegistry } from './chart/artifact-ref.js';
3
+ import { ChartSourceTokenStore } from './chart/source-token.js';
4
+ /**
5
+ * One Dataset store per mount, deliberately not a singleton: the hub registers source
6
+ * capabilities once per isolate realm, so a shared hub throws `data source capability
7
+ * already registered`. Stores only touch workspace via host fs, no state to synchronize.
8
+ */
9
+ export function capitalDatasetStore(ctx) {
10
+ return new WorkspaceDatasetStore({
11
+ fs: ctx.get('fs'),
12
+ sandboxPolicy: ctx.get('sandboxPolicy'),
13
+ });
14
+ }
15
+ /**
16
+ * Process-wide state shared by every mounted preset; `apply()` runs once per role preset.
17
+ * Per-role token stores would make `render_chart` silently report `chart_source_expired`.
18
+ * Key by `ctx.root` (stable) because mount points differ per application.
19
+ */
20
+ const cached = new Map();
21
+ export function capitalSharedRuntime(ctx) {
22
+ const ref = ctx.root ?? ctx;
23
+ const existing = cached.get(ref);
24
+ if (existing !== undefined)
25
+ return existing;
26
+ const shared = { sourceTokens: new ChartSourceTokenStore(), artifacts: new ChartArtifactRegistry() };
27
+ cached.set(ref, shared);
28
+ return shared;
29
+ }
30
+ /** Test-only: drop the per-root cache. */
31
+ export function resetCapitalRuntime() {
32
+ cached.clear();
33
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * `tools/retrieve-search` row: anysearch_search / web_retriever_fetch / wind_docs_*.
3
+ * Split from source and OCR rows; all share `retriever-runtime.ts` and resolve
4
+ * credentials per call.
5
+ */
6
+ import type { Context } from '@deepseek-ai/cordis';
7
+ export declare const name = "capital-retrieve-search-tools";
8
+ export declare const inject: string[];
9
+ export declare function apply(ctx: Context): void;
@@ -0,0 +1,9 @@
1
+ import { createRetrieverRuntime } from './retriever-runtime.js';
2
+ import { registerSearchTools } from './web-retriever/tools.js';
3
+ import { readRetrieverConfig } from './capital-settings.js';
4
+ export const name = 'capital-retrieve-search-tools';
5
+ export const inject = ['tools'];
6
+ export function apply(ctx) {
7
+ const runtime = createRetrieverRuntime(ctx, readRetrieverConfig(ctx));
8
+ registerSearchTools(ctx, runtime.retriever, runtime.windClient);
9
+ }
@@ -99,16 +99,8 @@ function dateMs(value) { return Date.parse(`${value}T00:00:00+08:00`); }
99
99
  function textOrNull(value) { return value === undefined || value === null || value === '' ? null : String(value); }
100
100
  function sourceError(message, code = 'eastmoney_source_error') { const error = new Error(message); error.code = code; return error; }
101
101
  /**
102
- * 东财请求统一走**进程级共享**的东财客户端(`src/net/eastmoney-client.ts`)。
103
- *
104
- * 为什么必须共享:东财按**出口 IP** 风控(社区实测 >5 次/秒、1 分钟 ≥200 次、5 分钟 ≥300 次
105
- * 即临时封禁),而本仓曾经有**两份互不知情**的东财出口——数据面这里的裸 `fetch`,与
106
- * web_retriever 具名来源工具自己的一份。两份独立限流等于没有限流。
107
- * 现在两条数据面共用同一个节流器:数据面按"可复用性"分层(数值/分页走 Hub、流式文本走
108
- * web_retriever),但**网络面只有一份**。
109
- *
110
- * 本函数原有的错误码语义(`eastmoney_rate_limit` / `eastmoney_http_error` /
111
- * `eastmoney_invalid_response`)刻意保持不变——收敛的是网络面,不是调用方的契约。
102
+ * All Eastmoney calls use the process-wide client. Limits are per egress IP (>5/s, >=200/min,
103
+ * >=300/5min bans); two throttles mean no throttle. Error codes stay unchanged.
112
104
  */
113
105
  async function getJson(url, signal, context) {
114
106
  let payload;
@@ -116,7 +108,6 @@ async function getJson(url, signal, context) {
116
108
  payload = await sharedEastmoneyClient().fetchJson(url, { signal });
117
109
  }
118
110
  catch (error) {
119
- // 调用方取消原样抛出;上游 HTTP 错误状态按既有口径归类。
120
111
  if (error instanceof Error && error.name === 'AbortError')
121
112
  throw error;
122
113
  const status = error instanceof EastmoneyTransportError ? error.status : undefined;
@@ -129,9 +120,7 @@ async function getJson(url, signal, context) {
129
120
  return payload;
130
121
  }
131
122
  function requireDatacenterResult(payload, context) {
132
- // 上游用 code 9201 表示"返回数据为空"(success:false),这是**真实数据缺口**而非上游故障:
133
- // 节假日、或该窗口确实没有任何记录。归类成 eastmoney_upstream_error 会诱导模型反复重试
134
- // (§10.4 明令"数据缺口不要重试"),也与单票路径把 9201 判成"无记录"自相矛盾。
123
+ // code 9201 = empty result (holiday/no records), a real data gap; treating it as a failure invites retries.
135
124
  if (payload.success !== true && Number(payload.code) === 9201)
136
125
  return { data: [], pages: 0, count: 0 };
137
126
  if (payload.success !== true || Number(payload.code) !== 0 || !isRecord(payload.result))
@@ -201,9 +190,8 @@ function topFilter(params, ticker) {
201
190
  return code ? `${base}(SECURITY_CODE="${code}")` : base;
202
191
  }
203
192
  function parseTopRow(raw) {
204
- // ⚠️ 上游 `MARKET` 是自由文本(实测存在 `SZ` 之外的写法),而 SECUCODE 自带市场后缀时
205
- // 它是冗余信息:只有 SECUCODE 不带市场时才拿它做必填校验,否则原文喂进硬白名单会把
206
- // 整页龙虎榜判成错误(护栏误杀真实数据,§10.2)。行里的 `market` 字段仍原样保留。
193
+ // MARKET is free text and redundant when SECUCODE has a suffix; only require it for bare codes,
194
+ // otherwise a whitelist rejects valid billboard pages. Keep the raw `market` field.
207
195
  const secucode = typeof raw.SECUCODE === 'string' ? raw.SECUCODE : '';
208
196
  const identity = secucode.includes('.')
209
197
  ? normalizeEastmoneySecurityIdentity({ secucode })
@@ -472,12 +460,7 @@ const MACRO_TABLES = [
472
460
  texts: [{ from: 'REMARK', to: 'announcement' }],
473
461
  },
474
462
  ];
475
- /**
476
- * 沪深港通六个渠道(`docs/dev/tool-schema.md` §10.6:上游给的是 `001`…`006` 这种数字码,
477
- * 模型侧只认语义名)。渠道身份由上游自己标注的 `MUTUAL_TYPE_NAME` 核对,并用同日的
478
- * `DEAL_AMT` 加法复核过:`001+003=005`(北向合计)、`002+004=006`(南向合计),实测 2026-09-30
479
- * 两条等式都精确成立。
480
- */
463
+ /** Stock Connect channels: upstream codes 001–006; 001+003=005 (north) and 002+004=006 (south) verified. */
481
464
  const MUTUAL_CHANNELS = {
482
465
  sh_stock_connect: '001',
483
466
  hk_connect_sh: '002',
@@ -567,7 +550,7 @@ const mutualQuotaRow = {
567
550
  },
568
551
  additionalProperties: true,
569
552
  };
570
- /** 上游 `MUTUAL_TYPE_NAME` → 我们的渠道名(额度表只给单渠道,没有合计档)。 */
553
+ /** Quota table has single channels only, no totals. */
571
554
  const MUTUAL_QUOTA_CHANNELS = { 沪股通: 'sh_stock_connect', 深股通: 'sz_stock_connect', '港股通(沪)': 'hk_connect_sh', '港股通(深)': 'hk_connect_sz' };
572
555
  const MUTUAL_QUOTA_CODE_CHANNELS = { '001': 'sh_stock_connect', '002': 'hk_connect_sh', '003': 'sz_stock_connect', '004': 'hk_connect_sz' };
573
556
  function parseMutualQuotaRow(raw) {
@@ -607,11 +590,7 @@ async function executeMutualQuota(params, signal) {
607
590
  const parsed = requireRows(result, 'mutual quota');
608
591
  return { data: { item: parsed.rows.map(parseMutualQuotaRow), pagination: pagination(1, 10, parsed.pages, parsed.total) }, schema: datacenterOutput(mutualQuotaRow) };
609
592
  }
610
- /**
611
- * 个股主力资金快照。上游 `RPT_DMSK_TS_STOCKNEW` 实测**只保留最近一个交易日**的全市场一行一股
612
- * (count=5199、TRADE_DATE 单一;带旧日期过滤器返回 9201 空),所以这不是时序能力:
613
- * 契约里必须写死"按日期循环取不到历史",否则模型会拿它当序列反复请求。
614
- */
593
+ /** Main-capital snapshot: latest trading day only, one row per stock (5199, single TRADE_DATE); older dates return 9201. */
615
594
  const MAIN_CAPITAL_SORTS = {
616
595
  main_net_inflow: 'PRIME_INFLOW',
617
596
  change_pct: 'CHANGE_RATE',
@@ -667,8 +646,7 @@ function parseMainCapitalRow(raw) {
667
646
  const tradeDate = String(raw.TRADE_DATE ?? '').slice(0, 10);
668
647
  if (!/^\d{4}-\d{2}-\d{2}$/.test(tradeDate))
669
648
  throw sourceError('Eastmoney main capital row has invalid TRADE_DATE', 'eastmoney_invalid_response');
670
- // SECUCODE 自带市场后缀(实测 200/200 行都有),不做"按代码首位猜市场"的兜底:猜错就是把一只
671
- // 股票的数据挂到另一只上。缺了就说明上游形状变了,响亮失败。
649
+ // SECUCODE always has a market suffix; never guess from the first digit. A missing suffix is a shape change.
672
650
  if (typeof raw.SECUCODE !== 'string' || !raw.SECUCODE.includes('.')) {
673
651
  throw sourceError('Eastmoney main capital row lost its market suffix in SECUCODE', 'eastmoney_invalid_response');
674
652
  }
@@ -719,12 +697,7 @@ async function executeMainCapital(params, signal) {
719
697
  const parsed = requireRows(result, 'main capital snapshot');
720
698
  return { data: { item: parsed.rows.map(parseMainCapitalRow), pagination: pagination(Number(params.page), Number(params.size), parsed.pages, parsed.total) }, schema: datacenterOutput(mainCapitalRow) };
721
699
  }
722
- /**
723
- * 分红送配方案:一行一个方案,按除权除息日过滤。`PRETAX_BONUS_RMB` / `BONUS_RATIO` / `IT_RATIO`
724
- * 的"每 10 股"标度由上游自己写在 `IMPL_PLAN_PROFILE` 原文里(实测 `10派280.2423元(含税)` 配
725
- * `PRETAX_BONUS_RMB=280.2423`、`10送4.00派3.00元` 配 `BONUS_RATIO=4`),所以照原值给出并保留原文。
726
- * `IS_KCB` 与 `PUBLISH_DATE` 实测 200/200 全为 null,不收录(描述里点名,免得当成取数失败)。
727
- */
700
+ /** Dividend plans: per-10-share scale comes from `IMPL_PLAN_PROFILE` text; values kept as-is. `IS_KCB`/`PUBLISH_DATE` always null. */
728
701
  const dividendRow = {
729
702
  type: 'object',
730
703
  properties: {
@@ -751,7 +724,6 @@ const dividendRow = {
751
724
  },
752
725
  additionalProperties: true,
753
726
  };
754
- /** 日期键归一:`2026-06-26 00:00:00` → `2026-06-26`;空值保留 null(12/200 行没有股权登记日)。 */
755
727
  function dayValue(value) {
756
728
  const text = String(value ?? '').slice(0, 10);
757
729
  return /^\d{4}-\d{2}-\d{2}$/.test(text) ? text : null;
@@ -763,8 +735,7 @@ function parseDividendRow(raw) {
763
735
  const exDate = dayValue(raw.EX_DIVIDEND_DATE);
764
736
  if (exDate === null)
765
737
  throw sourceError('Eastmoney dividend row has invalid EX_DIVIDEND_DATE', 'eastmoney_invalid_response');
766
- // 这三列实测 200/200 行都有值(缺了就是上游形状变了),所以按必填处理、缺一就响亮失败;
767
- // equity_record_date 实测 12/200 为 null(方案还没定登记日),那一列才允许 null。
738
+ // These three are always populated; equity_record_date may be null until the plan sets one.
768
739
  const requiredDates = ['REPORT_DATE', 'PLAN_NOTICE_DATE', 'NOTICE_DATE'].map((key) => {
769
740
  const day = dayValue(raw[key]);
770
741
  if (day === null)
@@ -821,9 +792,8 @@ async function executeDividend(params, signal) {
821
792
  return { data: { item: parsed.rows.map(parseDividendRow), pagination: pagination(Number(params.page), Number(params.size), parsed.pages, parsed.total) }, schema: datacenterOutput(dividendRow) };
822
793
  }
823
794
  /**
824
- * 股东户数**最新一期截面**(`RPT_HOLDERNUMLATEST`):一股一行,`END_DATE` 是**报告期**、
825
- * `HOLD_NOTICE_DATE` 才是披露日(实测茅台 END_DATE=2026-06-30 / 披露 2026-08-15),两者必须分开。
826
- * 新上市标的 `PRE_HOLDER_NUM=0` 且 `HOLDER_NUM_RATIO=null`(无上期可比),不是数据错误。
795
+ * Holder-count snapshot: `END_DATE` is the report period; `HOLD_NOTICE_DATE` is the disclosure date.
796
+ * New listings have `PRE_HOLDER_NUM=0` and null ratio (no prior period).
827
797
  */
828
798
  const holderRow = {
829
799
  type: 'object',
@@ -907,13 +877,8 @@ async function executeHolder(params, signal) {
907
877
  return { data: { item: parsed.rows.map(parseHolderRow), pagination: pagination(Number(params.page), Number(params.size), parsed.pages, parsed.total) }, schema: datacenterOutput(holderRow) };
908
878
  }
909
879
  /**
910
- * 两融个股明细(`RPTA_WEB_RZRQ_GGMX`)。45 列里绝大多数是同一族的"余额 / 买入 / 偿还 / 净买入 ×
911
- * 当日 / 3 / 5 / 10 日",所以按映射表成对声明,不逐列写 JSON Schema。
912
- *
913
- * 口径靠算术核对,不靠记忆(实测 600519,2026-09-30):`RZRQYE = RZYE + RQYE`、
914
- * `RZRQYECZ = RZYE - RQYE`、`RZJME = RZMRE - RZCHE`、`RQJMG = RQMCL - RQCHL` 四式精确成立,
915
- * `RZYEZB = RZYE / SZ`(0.8617% 对得上),所以这几列的含义可以写进契约。
916
- * 披露新鲜度**跨市场不一致**:实测沪市已到 2026-09-30,深市(000001)最新只有 2026-09-29。
880
+ * Margin detail: verified identities RZRQYE=RZYE+RQYE, RZRQYECZ=RZYE-RQYE, RZJME=RZMRE-RZCHE,
881
+ * RQJMG=RQMCL-RQCHL. Disclosure freshness differs by market (SH one day ahead of SZ).
917
882
  */
918
883
  const MARGIN_COLUMNS = [
919
884
  ['RZYE', 'margin_balance'], ['RQYE', 'short_balance'], ['RZRQYE', 'total_balance'], ['RZRQYECZ', 'balance_gap'],
@@ -943,8 +908,7 @@ function parseMarginRow(raw, requestedThscode) {
943
908
  const day = dayValue(raw.DATE);
944
909
  if (day === null)
945
910
  throw sourceError('Eastmoney margin row has invalid DATE', 'eastmoney_invalid_response');
946
- // SCODE 不带市场后缀,市场由请求的 ticker 决定:行身份取调用方传入的 canonical 代码,
947
- // 再与上游 SECUCODE 对照,对不上就是上游把明细串行了。
911
+ // SCODE has no market suffix, so identity comes from the requested ticker; a mismatched SECUCODE is a row mix-up.
948
912
  const secucode = typeof raw.SECUCODE === 'string' ? raw.SECUCODE : '';
949
913
  if (secucode && secucode !== requestedThscode) {
950
914
  throw sourceError(`Eastmoney margin row SECUCODE ${secucode} does not match requested ${requestedThscode}`, 'eastmoney_invalid_response');
@@ -982,15 +946,7 @@ async function executeMargin(params, signal) {
982
946
  const thscode = String(params.ticker);
983
947
  return { data: { item: parsed.rows.map((raw) => parseMarginRow(raw, thscode)), pagination: pagination(Number(params.page), Number(params.size), parsed.pages, parsed.total) }, schema: datacenterOutput(marginRow) };
984
948
  }
985
- /**
986
- * 可转债发行清单(`RPT_BOND_CB_LIST`,实测全库 1059 只、起息日 2007-07-02 起)。
987
- *
988
- * 72 列里只有**条款与日期**有值:`CONVERT_STOCK_PRICE` / `CURRENT_BOND_PRICE`(_NEW) /
989
- * `TRANSFER_PRICE` / `TRANSFER_PREMIUM_RATIO` / `RESALE_TRIG_PRICE` / `REDEEM_TRIG_PRICE` /
990
- * `PBV_RATIO` / `MARKET` / `CONVERT_STOCK_PRICEHQ` 实测 **1059/1059 恒为 null**(东财页面上那些
991
- * 行情数是另一条接口现算的),`TRANSFER_VALUE` 只是 `INITIAL_TRANSFER_PRICE` 的副本(1051/1059 相同
992
- * 却多 8 个 null)。所以这张表**不承诺行情**,也不把那些 null 当成取数失败。
993
- */
949
+ /** Bond list: only terms/dates are populated; price and premium columns are always null (computed elsewhere). */
994
950
  const BOND_MARKET_SUFFIX = { CNSESH: 'SH', CNSESZ: 'SZ', STAS00: null };
995
951
  function bondStockIdentity(raw) {
996
952
  const digits = String(raw.CONVERT_STOCK_CODE ?? '');
@@ -999,9 +955,7 @@ function bondStockIdentity(raw) {
999
955
  return {
1000
956
  stock_ticker: digits,
1001
957
  stock_name: String(raw.SECURITY_SHORT_NAME ?? ''),
1002
- // 正股市场只取上游 TRADE_MARKET,**不按数字首位推**:实测 15 只 2007-2009 沪市老债代码是 126 开头
1003
- // (首位 1 也不等于沪),老三板退市债(STAS00)的正股 400266 更不是北交所标的——猜错就是把一只证券
1004
- // 挂到另一个市场名下。未知市场一律不给后缀(`stock_thscode` 为 null),不猜也不报错。
958
+ // Market only from TRADE_MARKET: 126-prefixed 2007–09 SH bonds and STAS00 stock 400266 would be misattributed. Unknown → null.
1005
959
  stock_thscode: BOND_MARKET_SUFFIX[String(raw.TRADE_MARKET ?? '')] ? `${digits}.${BOND_MARKET_SUFFIX[String(raw.TRADE_MARKET ?? '')]}` : null,
1006
960
  };
1007
961
  }
@@ -1035,7 +989,6 @@ const bondRow = {
1035
989
  additionalProperties: true,
1036
990
  };
1037
991
  function parseBondRow(raw) {
1038
- // 债券身份只认上游给的 SECUCODE(带 .SH/.SZ/.NQ 后缀):本表 1059/1059 都有值,丢了后缀就是形状变了。
1039
992
  if (typeof raw.SECUCODE !== 'string' || !raw.SECUCODE.includes('.')) {
1040
993
  throw sourceError('Eastmoney convertible bond row lost its market suffix in SECUCODE', 'eastmoney_invalid_response');
1041
994
  }
@@ -1075,7 +1028,7 @@ function parseBondRow(raw) {
1075
1028
  in_conversion_period: textOrNull(raw.IS_CONVERT_STOCK),
1076
1029
  };
1077
1030
  }
1078
- /** 债市代码是 11/12/13/40 等六位数;后缀只用于收窄(实测出现 .SH/.SZ/.NQ),一律剥掉再过滤,不给市场推断留口子。 */
1031
+ /** Strip .SH/.SZ/.NQ suffixes; never infer a market. */
1079
1032
  function bondCode(value, name) {
1080
1033
  const raw = String(value).toUpperCase();
1081
1034
  const digits = raw.replace(/\.[A-Z]{2,3}$/u, '');
@@ -1100,8 +1053,7 @@ async function executeBondList(params, signal) {
1100
1053
  url.searchParams.set('columns', 'ALL');
1101
1054
  url.searchParams.set('source', 'WEB');
1102
1055
  url.searchParams.set('client', 'WEB');
1103
- // sortColumns 与过滤器都用 PUBLIC_START_DATE(实测该列可过滤、可排序,且与 VALUE_DATE 1059/1059 相同);
1104
- // 只按其一是因为日期区间过滤实测只有这一列**从不为空**——LISTING_DATE 有 8 只未上市的债会被区间静默吞掉。
1056
+ // Use PUBLIC_START_DATE: the only date filter never null (equals VALUE_DATE); LISTING_DATE would drop 8 unlisted bonds.
1105
1057
  url.searchParams.set('sortColumns', 'PUBLIC_START_DATE');
1106
1058
  url.searchParams.set('sortTypes', '-1');
1107
1059
  url.searchParams.set('pageNumber', String(params.page));
@@ -1,37 +1,13 @@
1
1
  /**
2
- * FTShare(非凸科技 `market.ft.tech/gateway`)REST source —— **资金流 + 行情/日 K 截面 + 快照与日历
3
- * + 大宗与披露 + 申万行业 + 期货与贵金属**。
4
- *
5
- * 为什么接这一族(批次 A–E 逐条真报文实测于 2026-10-15,批次 F 于 2026-10-16):
6
- * - 同花顺 `capital-flow/*` 对我们是 `code=2004` 永久关闭,东财 `eastmoney_main_capital_snapshot`
7
- * 实测**只有查询日一股一行**——个股资金流的历史序列在本仓一直是空的。这一族给到最近约 45 个交易日
8
- * (T-45 仍有 5199 行、T-50 起 total=0),主力五档更给到约 187 个交易日。
9
- * - 板块(行业)资金滚动此前只有 `eastmoney_cashflow_rotation` 一条,上游一抖就没有第二家。
10
- * - 北交所行情此前是**零**:腾讯那一路在参数层明确拒 BJ,而这一族实测含 349 只北证票。
11
- * - 期货与贵金属此前在本仓是**整块空白**(`ticker_search` 只允许消歧到期货交易所,没有一条数据端点),
12
- * 所以批次 F 那十颗不是第二家,而是第一家:合约清单与要素、日/周/月/季 K 线、涨跌停价、每日结算参数、
13
- * 仓单日报、品种周报、南华商品指数、东财会员持仓排名、贵金属价格。
14
- * 上游分别是同花顺与同花顺 daec,与现有能力**不是同一条抓取路径**,才够称 plan B。
15
- *
16
- * ⛔ 本模块的 description 与注释**只允许写有原始报文可回查的数字**。2026-10-15 曾有一批"实测计数"
17
- * (行数在 3766/3864/5450/5574 之间跳、`ys` 返回 309 行、业务码 100605、页码回显谎报)事后逐条回查
18
- * **在任何一次真报文里都不存在**,是我把不同接口的读数串成了因果;据此撤掉的两颗能力已于当晚复验注册回来。
19
- * 教训写进 `docs/design/ftshare-source-survey.md` §7.5–§7.7:**没打进日志的数字不算实测**。
20
- *
21
- * ⛔ 免费档限流 **10 QPS / burst 20**(官方口径)。`DataCollectorHub` 的 FIFO 只保证同一时刻
22
- * 一个请求,**不含最小间隔**——串行 ≠ 节流(`src/net/eastmoney-client.ts` 同一条教训)。
23
- * 本模块用进程级节流链把上限压到 ~8 QPS,留突发余量。
24
- *
25
- * ⛔ 凭据一律**调用期解析**,注册只看结构(AGENTS.md §9.7,2026-09-30 桌面端 61 能力静默缺席
26
- * 那一族事故)。套餐外路由返回 403 `route_not_allowed`,归类成 `plan_closed` 且不重试——
27
- * 它和 Fuyao 的 `2004` 是同一件事:能力级不可用,重试只白烧回合。
2
+ * FTShare (非凸科技 `market.ft.tech/gateway`): capital flows, quote/K-line, calendars, block trades,
3
+ * SW industry, futures/metals; the only source for A-share flow history, BJ quotes and futures.
4
+ * ⛔ Only numbers verifiable in a logged raw response; free tier 10 QPS/burst 20 (we cap ~8 QPS).
5
+ * ⛔ Credentials resolve per call; 403 route_not_allowed = plan_closed, never retry (cf. Fuyao 2004).
28
6
  */
29
7
  import type { Context } from '@deepseek-ai/cordis';
30
8
  import type { DataSource } from '../data-collector/hub.js';
31
9
  type JsonRecord = Record<string, unknown>;
32
- /** 10 QPS 的 80% 上限:120ms 最小间隔 ⇒ 稳态 ~8.3 次/秒,突发也不会越过 20。 */
33
10
  export declare const FTSHARE_MIN_INTERVAL_MS = 120;
34
- /** 失败归类:与 `FuyaoFailureKind` 同一套语汇,`plan_closed` 对应 Fuyao 的 `capability_closed`。 */
35
11
  type FtshareFailureKind = 'credential_missing' | 'rate_limited' | 'plan_closed' | 'invalid_parameter' | 'http_error' | 'upstream_error' | 'invalid_response';
36
12
  export declare class FtshareError extends Error {
37
13
  readonly kind: FtshareFailureKind;
@@ -45,13 +21,9 @@ export declare class FtshareError extends Error {
45
21
  export interface FtshareRequester {
46
22
  getJson(path: string, search: URLSearchParams, signal: AbortSignal): Promise<JsonRecord>;
47
23
  }
48
- /** key 每次调用现取(`resolveApiKey` 由装配层注入),构造客户端不依赖 key。 */
24
+ /** Key resolved per call; constructing the client touches no credentials. */
49
25
  export declare function createFtshareClient(resolveApiKey: () => Promise<string | undefined>, baseUrl?: string): FtshareRequester;
50
- /**
51
- * key 每次调用现取(与 `resolveFuyaoApiKey` 同一口径):credentials 优先、环境变量兜底。
52
- * ⛔ 注册面**不得**以"装配期读到 key"为条件(AGENTS.md §9.7),也不要把这里的 rejection 缓存住——
53
- * 一次装配期抢跑会把这个能力钉到进程结束。
54
- */
26
+ /** Credentials first, env fallback; never gate registration on key availability or cache a rejection. */
55
27
  export declare function resolveFtshareApiKey(ctx: Context, credentialRef?: string): Promise<string | undefined>;
56
28
  export declare function createFtshareSources(resolveApiKey: () => Promise<string | undefined>): DataSource[];
57
29
  export {};