@v587d/capital-generation 2.12.0 → 2.13.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.
@@ -63,6 +63,15 @@ const DOC_URLS = {
63
63
 
64
64
  /** Render order = array order; one list drives status, refs, drafts and event refresh. */
65
65
  const CREDENTIAL_FIELDS = ['fuyao', 'ftshare', 'anysearch', 'wind', 'paddleocr']
66
+ const AUTOMATION_FIELDS = ['tickMs', 'runTimeoutMinutes', 'idleTimeoutMinutes', 'maxSteps', 'maxToolCalls', 'inAppNotifications']
67
+ const AUTOMATION_DEFAULTS = {
68
+ tickMs: 60_000,
69
+ runTimeoutMinutes: 30,
70
+ idleTimeoutMinutes: 5,
71
+ maxSteps: 300,
72
+ maxToolCalls: 1_200,
73
+ inAppNotifications: true,
74
+ }
66
75
  const SELECTED_SKILL_FIELDS = SELECTED_SKILL_CATALOG.map(({ key }) => key)
67
76
  // Stable orders: facet counts change with the other dimension, but chips must not jump under the cursor.
68
77
  const SELECTED_SKILL_TRAITS = selectedSkillTraitOrder()
@@ -92,6 +101,14 @@ const zh = {
92
101
  anysearchHint: 'AnySearch 网页搜索 / 提取接口。保存后新自动模式会话生效。',
93
102
  localFetchLabel: '允许启动本地提取网页内容',
94
103
  localFetchHint: '开启后本地抓取兜底(回执来源 local-http)。保存后新自动模式会话生效。',
104
+ automationTitle: '投研自动化',
105
+ automationHint: '调度器与无人值守运行的默认上限;任务详情里的设置优先。改动在下一个 tick 生效。',
106
+ automationTick: '调度间隔(毫秒)',
107
+ automationRunTimeout: 'wall-clock 上限(分钟)',
108
+ automationIdleTimeout: '空闲超时(分钟)',
109
+ automationMaxSteps: '最大步骤',
110
+ automationMaxToolCalls: '最大工具调用',
111
+ automationInApp: '写入应用内通知',
95
112
  windLabel: 'Wind Alice API Key',
96
113
  windHint: 'Wind 金融信披类文档检索接口。保存后新自动模式会话生效。',
97
114
  paddleocrLabel: 'PaddleOCR 文档解析 Token',
@@ -139,6 +156,14 @@ const en = {
139
156
  anysearchHint: 'AnySearch web search / extract interface. Takes effect in new Auto mode sessions.',
140
157
  localFetchLabel: 'Allow local web page extraction',
141
158
  localFetchHint: 'When on, a failed fetch falls back to local direct fetching (receipt via: local-http). Takes effect in new Auto mode sessions.',
159
+ automationTitle: 'Research automation',
160
+ automationHint: 'Scheduler and unattended-run defaults; per-task limits win. Changes apply on the next tick.',
161
+ automationTick: 'Tick interval (ms)',
162
+ automationRunTimeout: 'Wall-clock limit (minutes)',
163
+ automationIdleTimeout: 'Idle timeout (minutes)',
164
+ automationMaxSteps: 'Max steps',
165
+ automationMaxToolCalls: 'Max tool calls',
166
+ automationInApp: 'Write in-app notifications',
142
167
  windLabel: 'Wind Alice API Key',
143
168
  windHint: 'Wind financial disclosure document retrieval interface. Takes effect in new Auto mode sessions.',
144
169
  paddleocrLabel: 'PaddleOCR document token',
@@ -205,6 +230,8 @@ function installStyles() {
205
230
  .capital-config-secret-row label { flex: 0 1 auto; }
206
231
  .capital-config-secret-row label ~ span { order: 1; margin-left: auto; }
207
232
  .capital-config-field { display: flex; flex-direction: column; gap: 6px; padding: 12px 0; border-top: 0.5px solid var(--dsw-alias-border-l2); }
233
+ .capital-config-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 8px; }
234
+ .capital-config-field input[type="number"] { width: 100%; box-sizing: border-box; padding: 6px 8px; border: .5px solid var(--dsw-alias-border-l1); border-radius: 8px; background: var(--dsw-alias-bg-module-platform); color: var(--dsw-alias-label-primary); font: inherit; font-size: 12px; }
208
235
  .capital-config-selected-mode { padding: 12px 14px; border: 0.5px solid var(--dsw-alias-border-l1); border-radius: 10px; background: var(--dsw-alias-bg-layer-2); }
209
236
  .capital-config-switch-row { align-items: center; gap: 12px; display: flex; }
210
237
  .capital-config-switch-text { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 4px; }
@@ -274,6 +301,8 @@ class CapitalCardController {
274
301
  this.selectedSkillFailed = new Set()
275
302
  this.selectedSkillsModeWriting = false
276
303
  this.selectedSkillsModeFailed = false
304
+ this.automationWriting = new Set()
305
+ this.automationFailed = new Set()
277
306
  // Row count is external: null = unknown, never 0; fetch only after the card is shown.
278
307
  this.bundleRows = null
279
308
  this.rowsRequested = false
@@ -323,6 +352,12 @@ class CapitalCardController {
323
352
  writing: this.selectedSkillsModeWriting,
324
353
  failed: this.selectedSkillsModeFailed,
325
354
  },
355
+ automation: Object.fromEntries(AUTOMATION_FIELDS.map((field) => [field, {
356
+ value: snapshot.value?.automation?.[field] ?? AUTOMATION_DEFAULTS[field],
357
+ on: snapshot.value?.automation?.[field] !== false,
358
+ writing: this.automationWriting.has(field),
359
+ failed: this.automationFailed.has(field),
360
+ }])),
326
361
  // `count === null` means unknown, not zero: the card decides whether to draw it.
327
362
  components: { count: this.bundleRows },
328
363
  }
@@ -436,6 +471,27 @@ class CapitalCardController {
436
471
  this.store.set(this.projection())
437
472
  }
438
473
 
474
+ async writeAutomation(field, value) {
475
+ if (!AUTOMATION_FIELDS.includes(field) || this.automationWriting.has(field)) return
476
+ const snapshot = this.scope.getSnapshot()
477
+ if (snapshot.status !== 'ready' || !snapshot.writable) return
478
+ const next = field === 'inAppNotifications' ? value === true : Number(value)
479
+ if (field !== 'inAppNotifications' && !Number.isFinite(next)) return
480
+ this.automationWriting.add(field)
481
+ this.automationFailed.delete(field)
482
+ this.store.set(this.projection())
483
+ try {
484
+ const current = this.scope.getSnapshot().value?.automation ?? { ...AUTOMATION_DEFAULTS }
485
+ await this.scope.mutate([{ op: 'set', path: ['automation'], value: { ...AUTOMATION_DEFAULTS, ...current, [field]: next } }])
486
+ } catch {
487
+ // The landed check below reports the failure.
488
+ }
489
+ this.automationWriting.delete(field)
490
+ const landed = this.scope.getSnapshot().value?.automation?.[field] ?? AUTOMATION_DEFAULTS[field]
491
+ if (landed !== next) this.automationFailed.add(field)
492
+ this.store.set(this.projection())
493
+ }
494
+
439
495
  /**
440
496
  * Count comes only from `pluginManager.listBundles()`. Merge in-flight reads;
441
497
  * failures are not cached (the next tab switch retries); push only on change.
@@ -476,6 +532,7 @@ class CapitalCardController {
476
532
  },
477
533
  ...this.model.actions(),
478
534
  toggleLocalFetch: (value) => this.toggleLocalFetch(value),
535
+ writeAutomation: (field, value) => this.writeAutomation(field, value),
479
536
  toggleSelectedSkill: (field, value) => this.toggleSelectedSkill(field, value),
480
537
  toggleSelectedSkillsMode: (auto) => this.toggleSelectedSkillsMode(auto),
481
538
  ensureBundleRows: () => this.ensureBundleRows(),
@@ -732,6 +789,38 @@ function CapitalCard(props) {
732
789
  ),
733
790
  state.localFetch?.failed ? h('p', { role: 'status', className: 'capital-config-write-failed' }, t('saveFailed')) : null,
734
791
  )
792
+ const automationNumber = (field, label, min, max) => h('label', { className: 'capital-config-field' },
793
+ h('span', { className: 'capital-config-switch-label' }, t(label)),
794
+ h('input', {
795
+ type: 'number',
796
+ min,
797
+ max,
798
+ defaultValue: state.automation?.[field]?.value,
799
+ disabled: disabled || state.saving,
800
+ onBlur: (event) => props.writeAutomation(field, event.target.value),
801
+ }),
802
+ state.automation?.[field]?.failed ? h('p', { role: 'status', className: 'capital-config-write-failed' }, t('saveFailed')) : null,
803
+ )
804
+ const automationSection = h('div', { className: 'capital-config-field' },
805
+ h('span', { className: 'capital-config-switch-label' }, t('automationTitle')),
806
+ h('p', { className: 'capital-config-hint' }, t('automationHint')),
807
+ h('div', { className: 'capital-config-grid' },
808
+ automationNumber('tickMs', 'automationTick', 30000, 3600000),
809
+ automationNumber('runTimeoutMinutes', 'automationRunTimeout', 5, 120),
810
+ automationNumber('idleTimeoutMinutes', 'automationIdleTimeout', 1, 30),
811
+ automationNumber('maxSteps', 'automationMaxSteps', 10, 2000),
812
+ automationNumber('maxToolCalls', 'automationMaxToolCalls', 10, 6000),
813
+ ),
814
+ h('label', { className: 'capital-config-switch-row' },
815
+ h('span', { className: 'capital-config-switch-label' }, t('automationInApp')),
816
+ h('input', {
817
+ type: 'checkbox',
818
+ checked: state.automation?.inAppNotifications?.on !== false,
819
+ disabled: disabled || state.saving,
820
+ onChange: (event) => props.writeAutomation('inAppNotifications', event.target.checked),
821
+ }),
822
+ ),
823
+ )
735
824
  const modeSection = h('div', { className: 'capital-config-selected-mode' },
736
825
  h('div', { className: 'capital-config-switch-row' },
737
826
  h('div', { className: 'capital-config-switch-text' },
@@ -826,7 +915,7 @@ function CapitalCard(props) {
826
915
  if (next === 'skills') setPinnedSkills(enabledKeys())
827
916
  },
828
917
  }),
829
- panel('sources', [form, localFetchSection]),
918
+ panel('sources', [form, localFetchSection, automationSection]),
830
919
  panel('skills', autoMode
831
920
  ? [modeSection]
832
921
  : [modeSection, h('div', { className: 'capital-config-skills-browser' },
package/config/index.js CHANGED
@@ -76,6 +76,21 @@ export const Config = z.object({
76
76
  selectedSkills: SelectedSkillsSchema.default(SELECTED_SKILL_DEFAULTS).description('Explicitly selected community methods; default off, each visible only to the Capital role(s) that own it'),
77
77
  fuyaoCredentialRef: z.string().default('FUYAO_API_KEY').volatile().description('Fuyao credentials 引用名'),
78
78
  ftshareCredentialRef: z.string().default('FTSHARE_API_KEY').volatile().description('FTShare(非凸科技)credentials 引用名'),
79
+ automation: z.object({
80
+ tickMs: z.number().default(60_000).volatile().description('投研自动化调度间隔毫秒(30000–3600000)'),
81
+ runTimeoutMinutes: z.number().default(30).volatile().description('单次运行 wall-clock 上限分钟(5–120)'),
82
+ idleTimeoutMinutes: z.number().default(5).volatile().description('单次运行空闲超时分钟(1–30)'),
83
+ maxSteps: z.number().default(300).volatile().description('单次运行最大步骤数(10–2000)'),
84
+ maxToolCalls: z.number().default(1_200).volatile().description('单次运行最大工具调用数(10–6000)'),
85
+ inAppNotifications: z.boolean().default(true).volatile().description('是否写入应用内通知'),
86
+ }).default({
87
+ tickMs: 60_000,
88
+ runTimeoutMinutes: 30,
89
+ idleTimeoutMinutes: 5,
90
+ maxSteps: 300,
91
+ maxToolCalls: 1_200,
92
+ inAppNotifications: true,
93
+ }).description('投研自动化默认值;任务级覆盖优先'),
79
94
  retriever: z.object({
80
95
  baseURL: z.string().default('').volatile().description('AnySearch API 地址'),
81
96
  credentialRef: z.string().default('ANYSEARCH_API_KEY').volatile().description('AnySearch credentials 引用名'),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/config",
3
- "version": "2.12.0",
3
+ "version": "2.13.0",
4
4
  "description": "自动模式的取数凭据与偏好配置行:卡片坐在 @v587d/capital-generation 插件主页的「配置」段,点这一行本身没有表单。",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/cordis.patch.yml CHANGED
@@ -54,6 +54,14 @@
54
54
  - id: capital-watchlist
55
55
  name: './watchlist/index.js'
56
56
 
57
+ # ── CapGen 投研自动化 (host plane) ─────────────────────────────────────────────
58
+ # Independent from the official `ui-schedule` row: no `schedules` main/sidebar id, no
59
+ # turnTail, no right-sidebar kind. The page is host-plane for the same injection reason
60
+ # as chart-ui; `name` stays a literal path-like relative path (see the three pitfalls above).
61
+ - insert:
62
+ - id: capital-automation
63
+ name: './automation/index.js'
64
+
57
65
  # ── Cross-preset runtime / policy (0.2.1) ─────────────────────────────────────
58
66
  # Root and role presets mount separately, so apply() runs several times per process. Shared state
59
67
  # (chart source tokens, chart artifacts) and the agent/created listeners (root convergence, shell
package/lib/index.d.ts CHANGED
@@ -13,6 +13,14 @@ export interface Config {
13
13
  selectedSkillsMode?: SelectedSkillsMode;
14
14
  fuyaoCredentialRef?: string;
15
15
  ftshareCredentialRef?: string;
16
+ automation?: {
17
+ tickMs?: number;
18
+ runTimeoutMinutes?: number;
19
+ idleTimeoutMinutes?: number;
20
+ maxSteps?: number;
21
+ maxToolCalls?: number;
22
+ inAppNotifications?: boolean;
23
+ };
16
24
  retriever?: RetrieverConfig;
17
25
  }
18
26
  export declare const Config: z<Schemastery.ObjectS<NoInfer<{
@@ -25,6 +33,21 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
25
33
  selectedSkillsMode: z<"manual" | "auto", "manual" | "auto", "defined">;
26
34
  fuyaoCredentialRef: z<string, string, "defined">;
27
35
  ftshareCredentialRef: z<string, string, "defined">;
36
+ automation: z<Schemastery.ObjectS<NoInfer<{
37
+ tickMs: z<number, number, "defined">;
38
+ runTimeoutMinutes: z<number, number, "defined">;
39
+ idleTimeoutMinutes: z<number, number, "defined">;
40
+ maxSteps: z<number, number, "defined">;
41
+ maxToolCalls: z<number, number, "defined">;
42
+ inAppNotifications: z<boolean, boolean, "defined">;
43
+ }>>, Schemastery.ObjectT<NoInfer<{
44
+ tickMs: z<number, number, "defined">;
45
+ runTimeoutMinutes: z<number, number, "defined">;
46
+ idleTimeoutMinutes: z<number, number, "defined">;
47
+ maxSteps: z<number, number, "defined">;
48
+ maxToolCalls: z<number, number, "defined">;
49
+ inAppNotifications: z<boolean, boolean, "defined">;
50
+ }>>, "defined">;
28
51
  retriever: z<Schemastery.ObjectS<NoInfer<{
29
52
  baseURL: z<string, string, "defined">;
30
53
  credentialRef: z<string, string, "defined">;
@@ -112,6 +135,21 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
112
135
  selectedSkillsMode: z<"manual" | "auto", "manual" | "auto", "defined">;
113
136
  fuyaoCredentialRef: z<string, string, "defined">;
114
137
  ftshareCredentialRef: z<string, string, "defined">;
138
+ automation: z<Schemastery.ObjectS<NoInfer<{
139
+ tickMs: z<number, number, "defined">;
140
+ runTimeoutMinutes: z<number, number, "defined">;
141
+ idleTimeoutMinutes: z<number, number, "defined">;
142
+ maxSteps: z<number, number, "defined">;
143
+ maxToolCalls: z<number, number, "defined">;
144
+ inAppNotifications: z<boolean, boolean, "defined">;
145
+ }>>, Schemastery.ObjectT<NoInfer<{
146
+ tickMs: z<number, number, "defined">;
147
+ runTimeoutMinutes: z<number, number, "defined">;
148
+ idleTimeoutMinutes: z<number, number, "defined">;
149
+ maxSteps: z<number, number, "defined">;
150
+ maxToolCalls: z<number, number, "defined">;
151
+ inAppNotifications: z<boolean, boolean, "defined">;
152
+ }>>, "defined">;
115
153
  retriever: z<Schemastery.ObjectS<NoInfer<{
116
154
  baseURL: z<string, string, "defined">;
117
155
  credentialRef: z<string, string, "defined">;
package/lib/index.js CHANGED
@@ -48,6 +48,14 @@ const RetrieverSchema = z.object({
48
48
  paddleOcr: PaddleOcrSchema.default({ ...PADDLE_OCR_DEFAULTS })
49
49
  .description('PaddleOCR 文档解析(ocr 工具)配置;Token 走 credentials 引用 PADDLE_OCR_TOKEN'),
50
50
  });
51
+ const AutomationSchema = z.object({
52
+ tickMs: z.number().default(60_000).description('投研自动化调度间隔毫秒(30000–3600000)'),
53
+ runTimeoutMinutes: z.number().default(30).description('单次运行 wall-clock 上限分钟(5–120)'),
54
+ idleTimeoutMinutes: z.number().default(5).description('单次运行空闲超时分钟(1–30)'),
55
+ maxSteps: z.number().default(300).description('单次运行最大步骤数(10–2000)'),
56
+ maxToolCalls: z.number().default(1_200).description('单次运行最大工具调用数(10–6000)'),
57
+ inAppNotifications: z.boolean().default(true).description('是否写入应用内通知'),
58
+ });
51
59
  // Derive toggles from the catalog: a hand-copied list silently drops defaults at the entry→plugin boundary.
52
60
  const SelectedSkillsSchema = z.object(Object.fromEntries(SELECTED_SKILL_CATALOG.map(({ key, name }) => [
53
61
  key,
@@ -67,6 +75,14 @@ export const Config = z.object({
67
75
  ftshareCredentialRef: z.string()
68
76
  .default('FTSHARE_API_KEY')
69
77
  .description('FTShare(非凸科技)credentials 引用名,空 = FTSHARE_API_KEY'),
78
+ automation: AutomationSchema.default({
79
+ tickMs: 60_000,
80
+ runTimeoutMinutes: 30,
81
+ idleTimeoutMinutes: 5,
82
+ maxSteps: 300,
83
+ maxToolCalls: 1_200,
84
+ inAppNotifications: true,
85
+ }).description('投研自动化默认值;任务级覆盖优先'),
70
86
  retriever: RetrieverSchema.default({ baseURL: '', credentialRef: 'ANYSEARCH_API_KEY', windDocs: { endpoint: '', credentialRef: 'WIND_API_KEY', timeoutMs: 60000 }, localFetch: { ...LOCAL_FETCH_DEFAULTS }, paddleOcr: { ...PADDLE_OCR_DEFAULTS } })
71
87
  .description('web_retriever 配置(可选;缺省使用 AnySearch 默认地址与凭据名)'),
72
88
  });
@@ -3,7 +3,7 @@
3
3
  * Known limit: DNS resolves per request but the IP is not pinned to the connection,
4
4
  * so a rebinding window remains (same trade-off as dsh-search-first).
5
5
  */
6
- export declare const LOCAL_FETCH_CLIENT_VERSION = "2.12.0";
6
+ export declare const LOCAL_FETCH_CLIENT_VERSION = "2.13.0";
7
7
  export declare const LOCAL_FETCH_MAX_URL_LENGTH = 2048;
8
8
  export type LocalFetchErrorCode = 'ABORTED' | 'TIMEOUT' | 'INVALID_URL' | 'BLOCKED_URL' | 'DNS' | 'REDIRECT' | 'HTTP' | 'TOO_LARGE' | 'UNSUPPORTED_CONTENT_TYPE';
9
9
  export declare class LocalFetchError extends Error {
@@ -4,7 +4,7 @@
4
4
  * so a rebinding window remains (same trade-off as dsh-search-first).
5
5
  */
6
6
  import { htmlToMarkdown, stripInvisibleText } from './html-markdown.js';
7
- export const LOCAL_FETCH_CLIENT_VERSION = '2.12.0';
7
+ export const LOCAL_FETCH_CLIENT_VERSION = '2.13.0';
8
8
  export const LOCAL_FETCH_MAX_URL_LENGTH = 2048;
9
9
  export class LocalFetchError extends Error {
10
10
  code;
@@ -35,7 +35,7 @@ export interface WindCallResult {
35
35
  code?: WindErrorCode;
36
36
  }
37
37
  /** Must match `package.json` version; a test pins the two together. */
38
- export declare const WIND_CLIENT_VERSION = "2.12.0";
38
+ export declare const WIND_CLIENT_VERSION = "2.13.0";
39
39
  export interface WindClient {
40
40
  callTool(toolName: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<WindCallResult>;
41
41
  }
@@ -19,7 +19,7 @@ export const WIND_SERVER_ENDPOINTS = {
19
19
  analytics_data: 'https://mcp.wind.com.cn/vserver_analytics_data/mcp/',
20
20
  };
21
21
  /** Must match `package.json` version; a test pins the two together. */
22
- export const WIND_CLIENT_VERSION = '2.12.0';
22
+ export const WIND_CLIENT_VERSION = '2.13.0';
23
23
  function errorText(error) {
24
24
  return error instanceof Error ? error.message : String(error);
25
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/capital-generation",
3
- "version": "2.12.0",
3
+ "version": "2.13.0",
4
4
  "description": "Capital Generation:面向中国散户的 AI 投资助手插件(自动模式 Agent Preset)",
5
5
  "icon": "assets/cg-icon.png",
6
6
  "author": "v587d",
@@ -106,6 +106,7 @@
106
106
  "chart-ui",
107
107
  "config",
108
108
  "watchlist",
109
+ "automation",
109
110
  "preset",
110
111
  "selected-skills/catalog.js",
111
112
  "selected-skills/filter.js",
@@ -136,10 +137,11 @@
136
137
  },
137
138
  "scripts": {
138
139
  "clean": "node -e \"import('node:fs').then(({ rmSync }) => rmSync('lib', { recursive: true, force: true }))\"",
139
- "build": "npm run clean && tsc && node scripts/copy-assets.mjs && npm run build:client && npm run build:config-client && npm run build:watchlist-client",
140
+ "build": "npm run clean && tsc && node scripts/copy-assets.mjs && npm run build:client && npm run build:config-client && npm run build:watchlist-client && npm run build:automation-client",
140
141
  "build:client": "node scripts/build-client.mjs",
141
142
  "build:config-client": "node scripts/build-config-client.mjs",
142
143
  "build:watchlist-client": "node scripts/build-watchlist-client.mjs",
144
+ "build:automation-client": "node scripts/build-automation-client.mjs",
143
145
  "dev": "tsc -w",
144
146
  "vendor:charts": "node scripts/vendor-lightweight-charts.mjs",
145
147
  "check:dsh": "node scripts/check-dsh-compat.mjs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@v587d/watchlist",
3
- "version": "2.12.0",
3
+ "version": "2.13.0",
4
4
  "description": "自动模式的用户自选股(宿主级清单 + Fuyao 报价快照 + /capital-watchlist 路由 + 输入框菜单弹窗):由 @v587d/capital-generation 随包携带并作为 host 平面行挂载。",
5
5
  "license": "MIT",
6
6
  "type": "module",