dsh-browser-playwright-codex 0.8.0 → 0.8.1

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.
package/lib/session.js CHANGED
@@ -240,7 +240,12 @@ export class PlaywrightSession {
240
240
  }
241
241
  /** Element-level stability before a pointer/keyboard action, bounded. Returns true when stable. */
242
242
  async waitForElementStable(locator) {
243
- const ok = await locator.evaluate(elementStabilityProbe, { frames: 2, timeoutMs: 1500 }).catch(() => false);
243
+ // locator.evaluate has no timeout of its own either: the probe runs page
244
+ // script, so a wedged main thread makes it hang before the action it is
245
+ // supposed to protect. Degrade to "not stable" and let the action's own
246
+ // Playwright timeout report the real problem (same contract as before, just
247
+ // no longer unbounded).
248
+ const ok = await withDeadline(locator.evaluate(elementStabilityProbe, { frames: 2, timeoutMs: 1500 }).catch(() => false), Math.max(3000, this.timeoutMs()), 'waiting for the element to hold still', this.currentSignal, 'resolve-undefined').catch(() => false);
244
249
  return ok === true;
245
250
  }
246
251
  /** Post-action settle: wait out a real navigation, then render-stabilize before snapshotting. */
@@ -579,7 +584,11 @@ export class PlaywrightSession {
579
584
  if (page === undefined)
580
585
  continue;
581
586
  this.guardBlockedDialog(page);
582
- out.push({ index, url: page.url(), title: await page.title(), active: index === activeIndex });
587
+ // page.title() is a page round trip with no timeout: one tab running a
588
+ // stuck script used to hang the whole tab list, and every tab after it.
589
+ // An unreadable title degrades to "" — the URL still identifies the tab.
590
+ const title = await withDeadline(page.title().catch(() => ''), Math.max(2000, Math.min(this.timeoutMs(), 5000)), 'reading the title of tab ' + String(index), this.currentSignal, 'resolve-undefined').catch(() => undefined);
591
+ out.push({ index, url: page.url(), title: title ?? '', active: index === activeIndex });
583
592
  }
584
593
  return out;
585
594
  });
@@ -623,7 +632,29 @@ export class PlaywrightSession {
623
632
  });
624
633
  }
625
634
  async pageData() {
626
- return pageData(this);
635
+ // page.evaluate again: the extract walker is page script, so a stuck tab used
636
+ // to hang browser_extract exactly like a snapshot read. Same deadline, and on
637
+ // timeout the same cleanup — drop the handle and the page so the next call
638
+ // does not inherit a page that will never answer.
639
+ try {
640
+ return await withDeadline(pageData(this), this.timeoutMs(), 'reading the page content', this.currentSignal, 'reject', 'That page has been closed; anything typed into it but not submitted is lost.');
641
+ }
642
+ catch (error) {
643
+ if (isReadTimeout(error)) {
644
+ this.closed = true;
645
+ this.provider.invalidateOwner(this.owner);
646
+ let page;
647
+ try {
648
+ page = this.currentPageSync();
649
+ }
650
+ catch {
651
+ page = undefined;
652
+ }
653
+ if (page !== undefined)
654
+ await this.discardStuckPage(page);
655
+ }
656
+ throw error;
657
+ }
627
658
  }
628
659
  async evaluate(expression, signal) {
629
660
  // The switch is enforced HERE, not only in the tool layer: browser_evaluate
@@ -639,14 +670,26 @@ export class PlaywrightSession {
639
670
  this.provider.touch(this.owner);
640
671
  const page = await this.ensurePage();
641
672
  const fn = new Function('return (' + expression + ')');
673
+ // page.evaluate takes no timeout: an expression with an infinite loop (a
674
+ // shader compile, a spin on a mutex) never returns and the tool call hangs
675
+ // forever. Bound it with the same budget the snapshot read uses, and on
676
+ // timeout drop this handle + the stuck page so the next call starts clean
677
+ // instead of inheriting a page that will never answer.
678
+ const once = async () => (await withDeadline(withAbort(page.evaluate(fn), signal), this.timeoutMs(), 'evaluating page JavaScript', undefined, 'reject', 'That page has been closed; anything typed into it but not submitted is lost, and the expression may have left the page unresponsive.'));
642
679
  try {
643
- return await withAbort(page.evaluate(fn), signal);
680
+ return await once();
644
681
  }
645
682
  catch (error) {
683
+ if (isReadTimeout(error)) {
684
+ this.closed = true;
685
+ this.provider.invalidateOwner(this.owner);
686
+ await this.discardStuckPage(page);
687
+ throw error;
688
+ }
646
689
  if (!isContextDestroyed(error) || signal?.aborted === true)
647
690
  throw error;
648
691
  await this.settleNavigation(page);
649
- return withAbort(page.evaluate(fn), signal);
692
+ return once();
650
693
  }
651
694
  });
652
695
  }
@@ -688,13 +731,16 @@ export class PlaywrightSession {
688
731
  // inside the driver, and the failure gets reported as a protocol error.
689
732
  const session = this.cdpSession;
690
733
  const send = session.send.bind(session);
691
- return await withAbort(send(method, params ?? {}), signal);
734
+ // A CDP command has no timeout of its own either, and some of them
735
+ // (screenshots, layout dumps) run page script internally. Same budget as
736
+ // a page read; the CDP session itself stays usable, only this call dies.
737
+ return await withDeadline(withAbort(send(method, params ?? {}), signal), this.timeoutMs(), 'running CDP command ' + method, undefined, 'reject', 'The command was abandoned; the page may still be working on it. Take a fresh snapshot to see where it landed.');
692
738
  }
693
739
  catch (error) {
694
- if (isAbortError(error))
740
+ // A deadline and a cancel both mean what they say: never relabel them as
741
+ // a protocol failure (the wrapping below only owns real CDP errors).
742
+ if (isAbortError(error) || isReadTimeout(error))
695
743
  throw error;
696
- // CDP protocol errors are already structured ("Protocol error
697
- // (DOM.foo): ..."); wrap non-abort failures in a stable code.
698
744
  const detail = error instanceof Error ? error.message : String(error);
699
745
  if (/Protocol error/.test(detail)) {
700
746
  const m = /\(([^)]+)\): ([\s\S]+)/.exec(detail);
@@ -42,6 +42,21 @@ const TRACKING_PARAMS = new Set([
42
42
  ]);
43
43
  /** Default cap for a rendered href, in characters. */
44
44
  export const MAX_HREF_LENGTH = 160;
45
+ /**
46
+ * Path shapes that mark a redirect-wrapping URL — a link that does not point at
47
+ * its destination but at the site's own click-tracking endpoint, which forwards
48
+ * there. Search engines wrap every organic result this way (Bing `/ck/a?...`,
49
+ * Google `/url?q=...`, Facebook `l.php?u=...`). The real destination is encoded
50
+ * in a long query parameter, so the rendered href is 160 characters of base64
51
+ * that says nothing the link's own name does not — and it repeats for every
52
+ * result on the page.
53
+ *
54
+ * This is a generic category (a tracking endpoint on the same origin as the
55
+ * page), not a per-site special case. When one matches we render a short marker
56
+ * instead of the wrapped URL: the ref is what clicks, and the link's name plus
57
+ * any destination node already carry the meaning.
58
+ */
59
+ const REDIRECT_WRAPPERS = [/\/ck\/a\?/i, /\/url\?q=/i, /l\.php\?u=/i];
45
60
  /** Clip a string to a display budget, marking the cut. */
46
61
  function clip(value, maxLength) {
47
62
  return value.length <= maxLength ? value : value.slice(0, maxLength - 1) + '…';
@@ -56,6 +71,13 @@ function clip(value, maxLength) {
56
71
  * @returns the shortened href
57
72
  */
58
73
  export function trimUrlForDisplay(href, maxLength = MAX_HREF_LENGTH) {
74
+ // A redirect-wrapping href (search-engine click tracker) is 160 characters of
75
+ // encoded destination that says nothing the link's name does not. Render a
76
+ // short marker instead; the ref is what clicks, so this never changes where a
77
+ // click lands — only what the model reads.
78
+ if (REDIRECT_WRAPPERS.some((re) => re.test(href))) {
79
+ return '→ (redirect wrapper)';
80
+ }
59
81
  const hashAt = href.indexOf('#');
60
82
  const hash = hashAt >= 0 ? href.slice(hashAt) : '';
61
83
  const head = hashAt >= 0 ? href.slice(0, hashAt) : href;
@@ -94,13 +116,14 @@ function isTransparentWrapper(node) {
94
116
  node.level === undefined &&
95
117
  node.checked === undefined &&
96
118
  node.selected === undefined &&
97
- node.disabled === undefined);
119
+ node.disabled === undefined &&
120
+ node.value === undefined);
98
121
  }
99
122
  /** Render one node line into the accumulating line list. */
100
- function renderNode(node, depth, lines) {
123
+ function renderNode(node, depth, lines, seenHrefs) {
101
124
  if (isTransparentWrapper(node)) {
102
125
  for (const child of node.children)
103
- renderNode(child, depth, lines);
126
+ renderNode(child, depth, lines, seenHrefs);
104
127
  return;
105
128
  }
106
129
  const indent = ' '.repeat(depth);
@@ -116,15 +139,33 @@ function renderNode(node, depth, lines) {
116
139
  flags.push('selected');
117
140
  if (node.disabled === true)
118
141
  flags.push('disabled');
142
+ // The current value of a text entry field: without it the model cannot see
143
+ // what a fill actually left behind, which read as "no visible change".
144
+ if (node.value !== undefined && node.value !== '')
145
+ flags.push('value=' + node.value);
119
146
  if (node.ref !== undefined)
120
147
  flags.push('ref=' + node.ref);
121
148
  if (flags.length > 0)
122
149
  line += ' [' + flags.join(', ') + ']';
123
- if (node.href !== undefined)
124
- line += ' -> ' + trimUrlForDisplay(node.href);
150
+ if (node.href !== undefined) {
151
+ // Same href, second and later occurrence within ONE snapshot: the URL is
152
+ // already printed at its first occurrence, so repeat only a pointer to it.
153
+ // Every ref and element stays — this trims bytes, never click targets. Only
154
+ // absolute URLs are deduped (a bare "/docs" can name different pages).
155
+ const abs = /^https?:\/\//i.test(node.href);
156
+ const firstRef = abs ? seenHrefs.get(node.href) : undefined;
157
+ if (firstRef !== undefined && firstRef !== node.ref) {
158
+ line += ' -> (same as ' + firstRef + ')';
159
+ }
160
+ else {
161
+ line += ' -> ' + trimUrlForDisplay(node.href);
162
+ if (abs && node.ref !== undefined)
163
+ seenHrefs.set(node.href, node.ref);
164
+ }
165
+ }
125
166
  lines.push(line);
126
167
  for (const child of node.children)
127
- renderNode(child, depth + 1, lines);
168
+ renderNode(child, depth + 1, lines, seenHrefs);
128
169
  }
129
170
  /**
130
171
  * Render a node tree as indented lines, one element per line.
@@ -133,8 +174,12 @@ function renderNode(node, depth, lines) {
133
174
  */
134
175
  export function renderSnapshotTree(nodes) {
135
176
  const lines = [];
177
+ // href -> ref of its first occurrence in THIS snapshot. Same href second and
178
+ // later renders as "(same as <ref>)" instead of repeating the URL. Scoped to
179
+ // one snapshot on purpose: a fresh snapshot after navigation re-prints URLs.
180
+ const seenHrefs = new Map();
136
181
  for (const node of nodes)
137
- renderNode(node, 0, lines);
182
+ renderNode(node, 0, lines, seenHrefs);
138
183
  return lines.join('\n');
139
184
  }
140
185
  /**
@@ -27,9 +27,11 @@
27
27
  * `assets/swarm-rebuild.upstream.js` 里逐条核对下面的每个名字是否真的存在 —— 上游换人、
28
28
  * 改名、删节点,这里第一个报错,而不是等某天浏览器里莫名其妙不播了。
29
29
  *
30
- * 法律面(别绕过它):上游仓库根目录**没有 LICENSE**(默认保留所有权利),
31
- * issue #12 至今零回复。所以这段资产**不能 fork 成"我们自己的版本"** —— fork 不改变授权,
32
- * 只多出一份照样没有许可的副本。它只能留在本机自用、默认关闭、不随包分发。
30
+ * 法律面(0.8.1 起):上游仓库根目录**仍然没有 LICENSE**(默认保留所有权利),
31
+ * 但 Dhvanil Patel 于 2026-11-11 邮件书面许可本项目**打包与修改**,Joel Califa 于
32
+ * 2026-11-08 书面许可并仅要求署名。所以资产可以随包分发,**但授权不是 MIT**:
33
+ * 它是邮件许可,只覆盖本项目,不给第三方通用许可。别把这份资产 fork 成
34
+ * "我们自己的版本" —— fork 不改变任何人拿到的权利,只会多出一份没有许可的副本。
33
35
  */
34
36
  /** 上游出处与入仓指纹。这三项之外的任何东西,都不该成为插件的依赖。 */
35
37
  export declare const SWARM_UPSTREAM: {
@@ -45,8 +47,12 @@ export declare const SWARM_UPSTREAM: {
45
47
  readonly sha256: "6a30f19daaa182d52673035feaeae775c398fa426655cc71a87a451fe00c7bfe";
46
48
  /** 补丁后的产出(**随包分发的那份**)。 */
47
49
  readonly patchedFile: "assets/swarm-rebuild.js";
48
- /** 授权状态:`none` = 无许可证,默认保留所有权利。不是"宽松许可"。 */
49
- readonly license: "none";
50
+ /**
51
+ * 授权状态:`none` = 仓库没有许可证;`permission` = 无许可证文本,但有作者的书面
52
+ * 邮件许可(本项目:Dhvanil 2026-11-11 打包 + 修改;Joel 2026-11-08 效果 + 署名)。
53
+ * 它不是"宽松许可",第三方不能据此复用。
54
+ */
55
+ readonly license: "permission";
50
56
  };
51
57
  /**
52
58
  * Tier A —— 硬接口。上游官方句柄树,补丁完全没碰,插件主路径全走这里。
@@ -27,9 +27,11 @@
27
27
  * `assets/swarm-rebuild.upstream.js` 里逐条核对下面的每个名字是否真的存在 —— 上游换人、
28
28
  * 改名、删节点,这里第一个报错,而不是等某天浏览器里莫名其妙不播了。
29
29
  *
30
- * 法律面(别绕过它):上游仓库根目录**没有 LICENSE**(默认保留所有权利),
31
- * issue #12 至今零回复。所以这段资产**不能 fork 成"我们自己的版本"** —— fork 不改变授权,
32
- * 只多出一份照样没有许可的副本。它只能留在本机自用、默认关闭、不随包分发。
30
+ * 法律面(0.8.1 起):上游仓库根目录**仍然没有 LICENSE**(默认保留所有权利),
31
+ * 但 Dhvanil Patel 于 2026-11-11 邮件书面许可本项目**打包与修改**,Joel Califa 于
32
+ * 2026-11-08 书面许可并仅要求署名。所以资产可以随包分发,**但授权不是 MIT**:
33
+ * 它是邮件许可,只覆盖本项目,不给第三方通用许可。别把这份资产 fork 成
34
+ * "我们自己的版本" —— fork 不改变任何人拿到的权利,只会多出一份没有许可的副本。
33
35
  */
34
36
  /** 上游出处与入仓指纹。这三项之外的任何东西,都不该成为插件的依赖。 */
35
37
  export const SWARM_UPSTREAM = {
@@ -45,8 +47,12 @@ export const SWARM_UPSTREAM = {
45
47
  sha256: '6a30f19daaa182d52673035feaeae775c398fa426655cc71a87a451fe00c7bfe',
46
48
  /** 补丁后的产出(**随包分发的那份**)。 */
47
49
  patchedFile: 'assets/swarm-rebuild.js',
48
- /** 授权状态:`none` = 无许可证,默认保留所有权利。不是"宽松许可"。 */
49
- license: 'none',
50
+ /**
51
+ * 授权状态:`none` = 仓库没有许可证;`permission` = 无许可证文本,但有作者的书面
52
+ * 邮件许可(本项目:Dhvanil 2026-11-11 打包 + 修改;Joel 2026-11-08 效果 + 署名)。
53
+ * 它不是"宽松许可",第三方不能据此复用。
54
+ */
55
+ license: 'permission',
50
56
  };
51
57
  /**
52
58
  * Tier A —— 硬接口。上游官方句柄树,补丁完全没碰,插件主路径全走这里。
@@ -36,11 +36,11 @@ export function swarmBootScript() {
36
36
  asset = fs.readFileSync(fileURLToPath(new URL('../assets/swarm-rebuild.js', import.meta.url)), 'utf8');
37
37
  }
38
38
  catch {
39
- // 资产不在(0.8.0 起 assets/ 不再随 npm 包分发)。这里**绝不能往外抛**:
39
+ // 资产读不到(0.8.1 起随包分发,理论上不会发生;手工删包时会)。这里**绝不能往外抛**:
40
40
  // 两个调用点都是 `await ctx.addInitScript({ content: swarmBootScript() })`,
41
41
  // 抛出去就是上下文创建失败、浏览器直接打不开 —— 把"动效没了"放大成"插件挂了"。
42
42
  // 返回一段空注释脚本,等于这次启动不注入任何东西,行为与"没开这个功能"一致。
43
- return '/* dsh: swarm asset not shipped (0.8.0+) — nothing injected */\n';
43
+ return '/* dsh: swarm asset missing — nothing injected */\n';
44
44
  }
45
45
  return ('if (window.top === window && typeof window.name === "string" &&\n' +
46
46
  ' window.name.indexOf("' +
@@ -4,11 +4,13 @@
4
4
  * 这不是扩展。效果本体是 assets/swarm-rebuild.js,由 Playwright 直接注入当前
5
5
  * 页面执行,所以它跑在插件自己那个窗口里 —— 也就是用户已经登录的那个浏览器。
6
6
  *
7
- * **授权状态(发布前必须处理)**:那份脚本来自 Dhvanil Patel 的
8
- * github.com/DhvanilPatel/swarm-cursors,仓库当前**没有许可证**,README 明确
9
- * 写着「尚未授权,重用代码前请先问 Joel Califa」。底层的动效设计与编排又来自
10
- * Joel Califa 的 califa/swarm-site(同样没有许可证)。两人都点头之前,这个
11
- * 功能只能自己用,不能随公开版本分发。
7
+ * **授权状态(0.8.1 起已解决)**:那份脚本来自 Dhvanil Patel 的
8
+ * github.com/DhvanilPatel/swarm-cursors。2026-11-11 他邮件书面许可本项目
9
+ * **打包与修改**("you are welcome to bundle and modify it as needed"),
10
+ * 所以资产重新随 npm 包分发;许可没有附 MIT 许可文本,也只覆盖本项目,
11
+ * 不给第三方通用许可。底层的动效设计与编排来自 Joel Califa 的
12
+ * califa/swarm-site,他 2026-11-08 已书面许可、唯一条件是署名。
13
+ * 功能仍然**默认关闭**(config.swarm.enabled),打开才播。
12
14
  *
13
15
  * 三个约束决定了这里的实现方式:
14
16
  *
@@ -46,6 +46,16 @@ export interface ChallengeNode {
46
46
  readonly name?: string;
47
47
  /** Accessible role. Both built-in engines provide one; it gates the wording tier. */
48
48
  readonly role?: string;
49
+ /** Whether a checkbox/radio is checked; present on toggles. */
50
+ readonly checked?: boolean;
51
+ /** Whether an option is selected; present on options. */
52
+ readonly selected?: boolean;
53
+ /**
54
+ * Current value of a textbox/textarea/select. The no-progress fingerprint
55
+ * includes it so that filling a field registers as change — otherwise a
56
+ * normal fill looks like "no visible change" and trips the anti-bot guess.
57
+ */
58
+ readonly value?: string;
49
59
  readonly children?: readonly ChallengeNode[];
50
60
  }
51
61
  /** The slice of a snapshot the challenge detector reads. */
package/lib/tool.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import z from '@deepseek-ai/schemastery';
2
2
  import { defineTool } from '@deepseek-ai/dsh-tools';
3
3
  import { BlockAssembler, createUserMessage } from '@deepseek-ai/dsh-llm';
4
- import { BrowserError, cdpDisabled, evaluateDisabled } from "./errors.js";
4
+ import { BrowserError, cdpDisabled, evaluateDisabled, normalizeEvaluateResult } from "./errors.js";
5
5
  import { registeredSuffixes } from "./contract.js";
6
6
  import { getEnabled, publishGates, publishLoginSites } from "./runtime-state.js";
7
7
  import { summarizeLoginSites } from "./login-sites.js";
@@ -563,6 +563,9 @@ export function apply(ctx, config) {
563
563
  registerMaybe('evaluate', defineTool({
564
564
  name: p('evaluate'),
565
565
  description: 'Evaluate one JavaScript expression in the page and return its JSON-serializable result. ' +
566
+ 'Return value rules: an object, string, number, boolean or null comes back as-is; ' +
567
+ 'a top-level ARRAY, undefined or a function is returned as a JSON string with a note saying it was converted ' +
568
+ '(wrap it yourself, e.g. {items: [...]} , if you need the array shape). ' +
566
569
  'Disabled by default; the deployment must enable allowEvaluate. ' +
567
570
  'IRON RULE: prefer read-only expressions to INSPECT state (value, outerHTML, checked, textContent) before ' +
568
571
  'concluding an interaction failed. If you write to the DOM directly (setting value/textContent), ' +
@@ -590,7 +593,8 @@ export function apply(ctx, config) {
590
593
  throw evaluateDisabled();
591
594
  }
592
595
  const session = await acquire(exec);
593
- return (await session.evaluate(args.expression, exec.signal));
596
+ const raw = await session.evaluate(args.expression, exec.signal);
597
+ return normalizeEvaluateResult(raw);
594
598
  },
595
599
  }));
596
600
  registerMaybe('cdp', defineTool({
package/lib/types.d.ts CHANGED
@@ -21,6 +21,12 @@ export interface BrowserNode {
21
21
  readonly selected?: boolean;
22
22
  /** Whether the element is disabled. */
23
23
  readonly disabled?: boolean;
24
+ /**
25
+ * Current value of a textbox, textarea or select, clipped to the snapshot's
26
+ * text budget. Present so the model can see what a fill actually left behind
27
+ * and so the no-progress fingerprint registers a value change as change.
28
+ */
29
+ readonly value?: string;
24
30
  /** Href for links; present when the page is same-origin. */
25
31
  readonly href?: string;
26
32
  readonly children: readonly BrowserNode[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-browser-playwright-codex",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "description": "Browser automation for DeepSeek Harness, one plugin: fill in forms, scrape lists and work across tabs in real pages, using the logins you already have. Runs in a visible window that stays quiet when minimised; native dialogs are parked instead of confirmed, CAPTCHA pages are flagged with a note that says stop and hand the page back, password / one-time-code / card fields are refused by browser_fill, and the Settings card (enable/disable, page-reading mode, capability gates) now lives inside this package.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -44,6 +44,8 @@
44
44
  "CHANGELOG.md",
45
45
  "SECURITY.md",
46
46
  "NOTICE.md",
47
+ "assets",
48
+ "!assets/swarm-rebuild.upstream.js",
47
49
  "locale",
48
50
  "scripts/doctor.mjs",
49
51
  "scripts/dsh-browser-verify.mjs"
@@ -70,19 +72,20 @@
70
72
  "playwright-core": "~1.62.1"
71
73
  },
72
74
  "peerDependencies": {
73
- "@deepseek-ai/cordis": ">=4.0.4",
74
- "@deepseek-ai/dsh-llm": ">=0.2.0-rc.1",
75
- "@deepseek-ai/dsh-tools": ">=0.2.0-rc.1"
75
+ "@deepseek-ai/cordis": ">=4.1.0",
76
+ "@deepseek-ai/dsh-llm": ">=0.2.2-rc.1",
77
+ "@deepseek-ai/dsh-tools": ">=0.2.2-rc.1"
76
78
  },
77
79
  "devDependencies": {
78
- "@deepseek-ai/cordis": "4.0.4",
79
- "@deepseek-ai/dsh-agent": "0.2.0-rc.1",
80
- "@deepseek-ai/dsh-attachment": "0.2.0-rc.1",
81
- "@deepseek-ai/dsh-llm": "0.2.0-rc.1",
82
- "@deepseek-ai/dsh-session": "0.2.0-rc.1",
83
- "@deepseek-ai/dsh-system-prompt": "0.2.0-rc.1",
84
- "@deepseek-ai/dsh-tools": "0.2.0-rc.1",
85
- "@deepseek-ai/schemastery": "3.18.4",
80
+ "@deepseek-ai/cordis": "4.1.0",
81
+ "@deepseek-ai/dsh-agent": "0.2.2-rc.1",
82
+ "@deepseek-ai/dsh-attachment": "0.2.2-rc.1",
83
+ "@deepseek-ai/dsh-llm": "0.2.2-rc.1",
84
+ "@deepseek-ai/dsh-session": "0.2.2-rc.1",
85
+ "@deepseek-ai/dsh-system-prompt": "0.2.2-rc.1",
86
+ "@deepseek-ai/dsh-tools": "0.2.2-rc.1",
87
+ "@deepseek-ai/dsh-tool-executor-native": "0.2.2-rc.1",
88
+ "@deepseek-ai/schemastery": "3.19.0",
86
89
  "@types/node": "^24.13.3",
87
90
  "prettier": "^3.9.9",
88
91
  "tsx": "^4.20.0",