@xirang-ai/dsh-teamflow 0.1.0 → 0.1.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/README.en.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Teamflow (`@xirang-ai/dsh-teamflow`) is a [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH) plugin that turns **requirements → high-level design → detailed design → stories → development runs → bug fixes** into a persistent, resumable pipeline. It also adds a team workbench called **Xirang Workbench** to the DSH Web UI.
6
6
 
7
- Version: **`0.1.0`** (first release; see [CHANGELOG.md](CHANGELOG.md)).
7
+ Version: **`0.1.1`** (adds drag and drop to the create page attachments; see [CHANGELOG.md](CHANGELOG.md)).
8
8
 
9
9
  - Data lives in SQLite; documents live in the workspace, so closing a session does not lose progress.
10
10
  - Five agent presets cover requirements, stories, bugs, development, and detailed design. Each preset controls its own tool boundary.
@@ -69,8 +69,8 @@ dsh plugin --profile web remove @xirang-ai/dsh-teamflow
69
69
  - **Requirement detail**: high-level design, detailed-design file tree, story list, execution flow chart, linked sessions, and a properties panel.
70
70
  - **Story / bug detail**: body, status, run history, and manual actions such as run, cancel, and complete.
71
71
  - **Three permission tiers**: the **Run** button on a story row, the story page, and the bug page has an arrow menu for **Normal run / Read-only run / Full permission run**. The main button always starts a normal run; the choice is not remembered, so picking full permission once does not carry over. The session starts with the matching sandbox mode and approval policy, and its header shows a short Normal / Read-only / Full badge (sessions started before this feature have no record and show no badge).
72
- - **Create page**: pick a type and workspace to start a session without choosing an agent manually. Files can be attached too: images are checked against the same rules the session composer uses (type, per-file size, count and total taken from `ctx.attachments.imageLimits`, never a second list defined here), other file types carry no extra limits, each picked file can be removed, and the submission sends them as attachments on the new conversation's first message next to the workspace and description. The submit button is held back until uploading finishes so no attachment is lost.
73
- - **Composer type picker**: in an empty session, switch between requirement / story / bug / high-level design / detailed design / story-splitting presets.
72
+ - **Create page**: pick a type and workspace to start a session without choosing an agent manually. Files can be attached too: the attachment card is both a click target and a drop zone, and a drop goes through the same rules as a picker selection (images are checked against the same rules the session composer uses — type, per-file size, count and total taken from `ctx.attachments.imageLimits`, never a second list defined here; other file types carry no extra limits), with a reason for every refused file. Each picked file can be removed, and the submission sends them as attachments on the new conversation's first message next to the workspace and description. The submit button is held back until uploading finishes so no attachment is lost.
73
+ - **Composer type picker**: an empty session switches between requirement / story / bug; a discussion session started from a requirement detail also offers requirement & outline / detailed design / story splitting.
74
74
  - **Session tab**: read-only view of the requirement, story, or bug linked to the current session.
75
75
  - **Delete and clean up**: only records with no active agent run can be deleted. The confirmation shows the impact; deletion is physical and cannot be undone.
76
76
 
@@ -124,7 +124,7 @@ dsh plugin --profile dev add .
124
124
  dsh --profile dev --port 3091 --no-open
125
125
  ```
126
126
 
127
- Development rules, official documentation links, and the full verification checklist are in [AGENTS.md](https://github.com/xirang-ai/teamflow/blob/main/AGENTS.md) and [RELEASE.md](RELEASE.md).
127
+ Development rules, official documentation links, and the full verification checklist are in [AGENTS.md](https://github.com/xirang-ai/dsh-teamflow/blob/main/AGENTS.md) and [RELEASE.md](RELEASE.md).
128
128
 
129
129
  ## License
130
130
 
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Teamflow(`@xirang-ai/dsh-teamflow`)是 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(下称 DSH)的插件:把**需求 → 概要设计 → 详细设计 → 故事拆分 → 研发执行 → 缺陷修复**做成一条可持久化、可断点续跑的流水线,并在 Web 端提供团队工作台「息壤工作台」。
6
6
 
7
- 当前版本:**`0.1.0`**(首版,变更记录见 [CHANGELOG.md](CHANGELOG.md))。
7
+ 当前版本:**`0.1.1`**(新增创建页附件拖放,变更记录见 [CHANGELOG.md](CHANGELOG.md))。
8
8
 
9
9
  - 数据落在 SQLite,文档落在工作区,关掉会话不丢进度。
10
10
  - 五个 Agent 预设分别负责需求、故事、缺陷、研发和详细设计,工具边界由预设控制。
@@ -69,8 +69,8 @@ dsh plugin --profile web remove @xirang-ai/dsh-teamflow
69
69
  - **需求详情**:概设正文、详设文件树、故事列表、执行流程图、关联会话和属性面板。
70
70
  - **故事 / 缺陷详情**:正文、状态、运行记录,以及执行、取消、完成等人工动作。
71
71
  - **三档权限执行**:需求详情的故事行、故事页、缺陷页三处的「执行」按钮右侧带箭头菜单,可选「普通执行 / 仅查看执行 / 完全权限执行」。主按钮直接按「普通执行」启动;默认档不记忆,上一次选「完全权限执行」也不会留到下一次。会话以对应沙箱模式与审批策略启动,会话头部显示「普通 / 仅查看 / 完全权限」短标签(旧会话没有记录,不显示)。
72
- - **创建页**:选类型和工作区后直接开新会话,不需要手动挑 Agent。还可以附带文件:图片按宿主会话上传的同一套规则校验(类型、单张大小、条数与总大小取自 `ctx.attachments.imageLimits`,不在本页另立白名单),其它类型的文件不设额外限制;已选文件可逐个移除,提交时与工作区、描述一起作为第一条消息的附件发进新会话。附件上传完成前提交按钮会拦住,避免丢附件。
73
- - **输入框「创建类型」**:空白会话下可切换需求 / 故事 / 缺陷 / 概要设计 / 详细设计 / 故事拆分预设。
72
+ - **创建页**:选类型和工作区后直接开新会话,不需要手动挑 Agent。还可以附带文件:附件区整块可点击选择,也可以把文件直接拖进附件卡片,拖放与点选走同一套规则(图片按宿主会话上传的同一套规则校验,类型、单张大小、条数与总大小取自 `ctx.attachments.imageLimits`,不在本页另立白名单;其它类型的文件不设额外限制),不合规的文件逐条给出原因;已选文件可逐个移除,提交时与工作区、描述一起作为第一条消息的附件发进新会话。附件上传完成前提交按钮会拦住,避免丢附件。
73
+ - **输入框「创建类型」**:空白会话下可切换需求 / 故事 / 缺陷;从需求详情发起的讨论会话还会显示「需求与概要设计」/「详细设计」/「故事拆分」,用于继续对应的讨论。
74
74
  - **会话页签**:在会话顶部只读查看当前会话关联的需求、故事或缺陷。
75
75
  - **删除与清理**:只允许删除没有 Agent 正在执行的记录;删除前展示影响范围,物理删除不可恢复。
76
76
 
@@ -124,7 +124,7 @@ dsh plugin --profile dev add .
124
124
  dsh --profile dev --port 3091 --no-open
125
125
  ```
126
126
 
127
- 开发规则、官方文档入口与完整验证清单见 [AGENTS.md](https://github.com/xirang-ai/teamflow/blob/main/AGENTS.md) 和 [RELEASE.md](RELEASE.md)。
127
+ 开发规则、官方文档入口与完整验证清单见 [AGENTS.md](https://github.com/xirang-ai/dsh-teamflow/blob/main/AGENTS.md) 和 [RELEASE.md](RELEASE.md)。
128
128
 
129
129
  ## 许可证
130
130
 
package/RELEASE.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  | 项 | 值 |
11
11
  | --- | --- |
12
- | 目标版本号 | **0.1.0**(用户已确认保持当前 `package.json` 版本) |
12
+ | 目标版本号 | **0.1.1**(用户已确认) |
13
13
  | 发布分支 / commit | 发布时填写 |
14
14
  | 发布日期 | 发布时填写 |
15
15
  | 发布人 | 发布时填写 |
@@ -30,7 +30,7 @@ git ls-remote --tags --refs https://github.com/deepseek-ai/deepseek-harness.git
30
30
  | sort -V
31
31
  ```
32
32
 
33
- 2026-11-10 核对结果:
33
+ 2026-11-11 核对结果:
34
34
 
35
35
  | 项 | 结果 |
36
36
  | --- | --- |
@@ -123,7 +123,7 @@ sqlite3 "$DSH_HOME/xirang-ai/dsh-teamflow/teamflow.sqlite" \
123
123
  - [ ] 安装命令使用 `dsh plugin --profile <profile> add ...`,并说明安装后要重启 Host,不能只刷新浏览器。
124
124
  - [ ] Node 版本写 `>=24.9.0`,与 `engines.node` 一致。
125
125
  - [ ] 开发端口统一为 `3091`(与本文件、AGENTS.md §5.1 一致)。
126
- - [ ] README 中的链接均可访问;仓库根的 `docs/` 仅作为提示词一致性报告与快照的测试证据保留,不进入 npm 包。
126
+ - [ ] README 中的链接均可访问;仓库根的 `docs/` 仅作为提示词一致性报告、快照、Demo 走查结论与历次发版检查留痕保留,不进入 npm 包。
127
127
  - [ ] 配置说明指向 **profile 配置层** 的 `id: teamflow` 覆盖,而不是让用户改 `node_modules` 里的包内文件。
128
128
  - [ ] 版本号、schema 版本、工具数量(当前 19 个)、预设数量(当前 5 个)与实际一致。
129
129
  - [ ] 本文件已按本次发版更新;AGENTS.md 的阅读路径与文档索引没有死链。
@@ -134,7 +134,7 @@ sqlite3 "$DSH_HOME/xirang-ai/dsh-teamflow/teamflow.sqlite" \
134
134
  - [ ] 没有未完成功能、`TODO` / `FIXME` / `HACK` / `XXX`、注释掉的半成品代码、调试日志。
135
135
  - [ ] 没有 smoke / mock / demo / fixture / seed 数据进入 `src/` 或 npm 包(测试临时目录必须在 `tmpdir()` 下创建并清理)。
136
136
  - [ ] 本地 scratch 已删除:`.tmp/`、`.devhome/`、`.xirang-ai/`、`*.sqlite*`、`*.log`、临时截图 / 原型。
137
- - [ ] `design/` 已按维护决定删除;`docs/` 只保留提示词一致性报告与快照,测试依赖它,不得删除,且不进入 npm 包。
137
+ - [ ] `design/` 已按维护决定删除;`docs/` 只保留提示词一致性报告与快照、Demo 走查结论、发版检查留痕,测试依赖其中的快照,不得删除,且不进入 npm 包。
138
138
  - [ ] `lib/` 未提交(由构建生成),但发布包中必须包含最新构建。
139
139
  - [ ] `git status` 干净;`git ls-files` 包含 `package.json`、`cordis.patch.yml`、`src/`、`test/`、`locale/`、`scripts/`,不再只有 README / LICENSE。
140
140
 
@@ -157,7 +157,7 @@ find . -maxdepth 2 \( -name '*.sqlite*' -o -name '.tmp' -o -name '.devhome' -o -
157
157
  - [ ] Agent 人设 / prompt 中的语言与目标用户一致;新增语言时同时补字典、`normalizeLocale` 与测试。
158
158
  - [ ] `pnpm test` 中的 locale 测试通过。
159
159
 
160
- 当前核对(2026-11-10):en / zh 各 332 个键,集合一致、无空值;en 无中文残留;`src/remote` 的错误均走 `translate()`;客户端硬编码中文只出现在注释与验收标准正则中。
160
+ 当前核对(2026-11-11):en / zh 各 334 个键,集合一致、无空值;en 无中文残留;`src/remote` 的错误均走 `translate()`;客户端硬编码中文只出现在注释与验收标准正则中。
161
161
 
162
162
  ## 7. Git、npm 与发布
163
163
 
package/lib/client.js CHANGED
@@ -1,4 +1,4 @@
1
- /* @xirang-ai/dsh-teamflow 0.1.0 — generated by scripts/build.mjs, do not edit. */
1
+ /* @xirang-ai/dsh-teamflow 0.1.1 — generated by scripts/build.mjs, do not edit. */
2
2
  window.__ModuleLoader__.load({
3
3
  id: "@xirang-ai/dsh-teamflow",
4
4
  factory: (require) => {
@@ -234,10 +234,12 @@ window.__ModuleLoader__.load({
234
234
  "composer.locked": "The conversation has started; create a new one to switch agents.",
235
235
  "composer.noPreset": "The preset agent is not loaded, so the switch was not applied.",
236
236
  "composer.other": "Other (default)",
237
- "create.attachment.add": "Add files",
237
+ "create.attachment.add": "Add attachments",
238
238
  "create.attachment.blocked": "Attachments are still uploading; wait for them to finish before sending.",
239
239
  "create.attachment.busy": "The conversation is busy, so the attachments were not sent. Try again.",
240
240
  "create.attachment.cancelled": "Upload cancelled; nothing was sent.",
241
+ "create.attachment.dropActive": "Release to attach these files.",
242
+ "create.attachment.dropHint": "Click to choose files, or drop them here.",
241
243
  "create.attachment.failed": "Upload failed",
242
244
  "create.attachment.failed.network": "The upload failed. Check the network and try again.",
243
245
  "create.attachment.failed.rejected": "The host rejected the file (damaged content or wrong type).",
@@ -570,10 +572,12 @@ window.__ModuleLoader__.load({
570
572
  "composer.locked": "会话已开始,无法再切换 Agent;请新建会话。",
571
573
  "composer.noPreset": "预设 Agent 未加载,无法切换。",
572
574
  "composer.other": "其他(默认)",
573
- "create.attachment.add": "选择文件",
575
+ "create.attachment.add": "添加附件",
574
576
  "create.attachment.blocked": "附件正在上传,上传完成后再提交。",
575
577
  "create.attachment.busy": "会话正忙,附件未发送,请稍后重试。",
576
578
  "create.attachment.cancelled": "已取消上传,附件未发送。",
579
+ "create.attachment.dropActive": "松手即可添加附件。",
580
+ "create.attachment.dropHint": "点击选择文件,或把文件拖到这里。",
577
581
  "create.attachment.failed": "上传失败",
578
582
  "create.attachment.failed.network": "上传暂时失败,请检查网络后重试。",
579
583
  "create.attachment.failed.rejected": "宿主拒绝了该文件(内容损坏或类型不符)。",
@@ -1093,6 +1097,7 @@ window.__ModuleLoader__.load({
1093
1097
  });
1094
1098
 
1095
1099
  // src/client/composer.tsx
1100
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
1096
1101
  var import_react = require("react");
1097
1102
 
1098
1103
  // src/create.ts
@@ -2281,8 +2286,82 @@ window.__ModuleLoader__.load({
2281
2286
  * Attachment block. The picker is the Host's own file input, so it keeps the
2282
2287
  * Host rules for what can be selected; it is only hidden because the visible
2283
2288
  * control is the card's button and native inputs cannot be styled consistently.
2289
+ * The whole card body is one picker: a large plus, the action, and what a drop
2290
+ * will do. It stays a real <button>, so the drop target always keeps a
2291
+ * keyboard and single-pointer equivalent for the drag gesture.
2284
2292
  */
2285
- .tf-attachment-bar { display: flex; align-items: center; gap: 10px; margin-top: 8px; }
2293
+ .tf-attachment-pick {
2294
+ display: flex;
2295
+ flex-direction: column;
2296
+ align-items: center;
2297
+ justify-content: center;
2298
+ gap: 6px;
2299
+ width: 100%;
2300
+ min-height: 150px;
2301
+ margin-top: 10px;
2302
+ padding: 20px 16px;
2303
+ border: 1px dashed var(--dsw-alias-border-l3, currentColor);
2304
+ border-radius: 12px;
2305
+ background: var(--dsw-alias-bg-l2, transparent);
2306
+ color: var(--dsw-alias-label-secondary, inherit);
2307
+ font: inherit;
2308
+ text-align: center;
2309
+ cursor: pointer;
2310
+ transition: border-color .12s ease, background-color .12s ease;
2311
+ }
2312
+ .tf-attachment-pick:hover:not(:disabled) {
2313
+ border-color: var(--dsw-alias-state-business-primary, #4176e6);
2314
+ background: var(--dsw-alias-interactive-bg-hover, transparent);
2315
+ }
2316
+ .tf-attachment-pick:focus-visible {
2317
+ outline: 2px solid var(--dsw-alias-link, #4176e6);
2318
+ outline-offset: 2px;
2319
+ }
2320
+ .tf-attachment-pick:disabled { opacity: .5; cursor: default; }
2321
+ .tf-attachment-plus {
2322
+ display: grid;
2323
+ place-items: center;
2324
+ width: 40px;
2325
+ height: 40px;
2326
+ border-radius: 50%;
2327
+ background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, .04));
2328
+ color: var(--dsw-alias-state-business-primary, #4176e6);
2329
+ font-size: 24px;
2330
+ font-weight: 300;
2331
+ line-height: 1;
2332
+ }
2333
+ .tf-attachment-pick-title {
2334
+ font-size: 13px;
2335
+ font-weight: 600;
2336
+ color: var(--dsw-alias-label-primary, inherit);
2337
+ }
2338
+ .tf-attachment-pick-sub {
2339
+ font-size: 11.5px;
2340
+ line-height: 1.5;
2341
+ color: var(--dsw-alias-label-tertiary, inherit);
2342
+ }
2343
+ .tf-attachment-limits {
2344
+ margin: 8px 0 0;
2345
+ font-size: 11.5px;
2346
+ line-height: 1.5;
2347
+ text-align: center;
2348
+ color: var(--dsw-alias-label-caption, inherit);
2349
+ }
2350
+ /*
2351
+ * The attachment card doubles as the drop target, so a file dropped anywhere
2352
+ * the user reads as "the attachment area" lands on the picker's own intake path.
2353
+ * Only the picker itself lights up while a file drag is inside the card, and its
2354
+ * subtitle says what releasing will do.
2355
+ */
2356
+ .tf-attachment-dropzone[data-drag='active'] .tf-attachment-pick {
2357
+ border-style: solid;
2358
+ border-color: var(--dsw-alias-state-business-primary, #4176e6);
2359
+ background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #4176e6) 7%, transparent);
2360
+ }
2361
+ .tf-attachment-dropzone[data-drag='active'] .tf-attachment-pick-sub {
2362
+ color: var(--dsw-alias-state-business-primary, #4176e6);
2363
+ font-weight: 600;
2364
+ }
2286
2365
  .tf-attachment-input {
2287
2366
  position: absolute;
2288
2367
  width: 1px;
@@ -2472,23 +2551,37 @@ window.__ModuleLoader__.load({
2472
2551
  `;
2473
2552
  var COMPOSER_STYLES = `
2474
2553
  .tf-composer { display: inline-flex; align-items: center; gap: 6px; min-width: 0; max-width: 100%; }
2475
- .tf-composer-field { display: inline-flex; align-items: center; gap: 5px; min-width: 0; }
2476
- .tf-composer-label { font-size: 11.5px; color: var(--dsw-alias-label-tertiary, inherit); white-space: nowrap; }
2477
- .tf-composer-select {
2478
- height: 24px;
2479
- max-width: 132px;
2480
- padding: 0 6px;
2481
- border: 1px solid var(--dsw-alias-border-l2, transparent);
2482
- border-radius: 7px;
2483
- background: transparent;
2554
+ /* Trigger matches the Host's composer selects (permission, model): same height,
2555
+ radius, spacing, type scale and interaction colors, all from theme tokens. */
2556
+ .tf-composer-trigger {
2557
+ display: inline-flex;
2558
+ align-items: center;
2559
+ gap: 4px;
2560
+ min-width: 0;
2561
+ max-width: 220px;
2562
+ height: 28px;
2563
+ padding: 0 4px 0 8px;
2564
+ border: none;
2565
+ border-radius: var(--dsw-radius-sm, 7px);
2566
+ background: none;
2484
2567
  color: var(--dsw-alias-label-secondary, inherit);
2485
2568
  font: inherit;
2486
- font-size: 11.5px;
2569
+ font-size: 13px;
2570
+ font-weight: 500;
2571
+ line-height: 20px;
2487
2572
  cursor: pointer;
2488
2573
  outline: none;
2489
2574
  }
2490
- .tf-composer-select:focus { border-color: var(--dsw-alias-label-caption, transparent); }
2491
- .tf-composer-select:disabled { opacity: .55; cursor: default; }
2575
+ @media (hover: hover) {
2576
+ .tf-composer-trigger:hover:not(:disabled) { background: var(--dsw-alias-interactive-bg-hover, transparent); }
2577
+ }
2578
+ .tf-composer-trigger:focus-visible {
2579
+ box-shadow: 0 0 0 2px var(--dsw-focus-ring-color, var(--dsw-alias-state-business-primary));
2580
+ }
2581
+ .tf-composer-trigger:disabled { color: var(--dsw-alias-label-dimmed, inherit); cursor: default; }
2582
+ .tf-composer-value { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2583
+ .tf-composer-chevron { display: inline-flex; flex: none; color: var(--dsw-alias-label-caption, inherit); transition: transform .12s; }
2584
+ .tf-composer-chevron-open { transform: rotate(180deg); }
2492
2585
  .tf-composer-note {
2493
2586
  font-size: 11px;
2494
2587
  color: var(--dsw-alias-label-caption, inherit);
@@ -2516,6 +2609,7 @@ window.__ModuleLoader__.load({
2516
2609
  const kinds = discussionSession ? COMPOSER_KINDS : CREATE_KINDS;
2517
2610
  const [pending, setPending] = (0, import_react.useState)(false);
2518
2611
  const [error, setError] = (0, import_react.useState)(null);
2612
+ const [open, setOpen] = (0, import_react.useState)(false);
2519
2613
  const change = (0, import_react.useCallback)(
2520
2614
  (next) => {
2521
2615
  setError(null);
@@ -2569,26 +2663,55 @@ window.__ModuleLoader__.load({
2569
2663
  },
2570
2664
  [agentPresets, blank, mode, sessionId, t]
2571
2665
  );
2666
+ const locked = !blank;
2667
+ const currentLabel = mode === "" ? t("composer.other") : t(COMPOSER_KIND_LABEL[mode]);
2668
+ const items = [
2669
+ { id: "", label: t("composer.other") },
2670
+ ...kinds.map((kind) => ({ id: kind, label: t(COMPOSER_KIND_LABEL[kind]) }))
2671
+ ];
2572
2672
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2573
2673
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: COMPOSER_STYLES }),
2574
2674
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "tf-composer", children: [
2575
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "tf-composer-field", children: [
2576
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tf-composer-label", children: t("composer.label") }),
2577
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2578
- "select",
2579
- {
2580
- className: "tf-select tf-composer-select",
2581
- value: mode,
2582
- disabled: pending,
2583
- title: blank ? t("composer.hint") : t("composer.locked"),
2584
- onChange: (event) => change(event.target.value),
2585
- children: [
2586
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "", children: t("composer.other") }),
2587
- kinds.map((kind) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: kind, children: t(COMPOSER_KIND_LABEL[kind]) }, kind))
2588
- ]
2589
- }
2590
- )
2591
- ] }),
2675
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2676
+ import_dsh_client_ui_primitives.Menu,
2677
+ {
2678
+ open,
2679
+ items,
2680
+ selectedId: mode,
2681
+ onSelect: (id2) => {
2682
+ setOpen(false);
2683
+ change(id2);
2684
+ },
2685
+ onClose: () => setOpen(false),
2686
+ side: "top",
2687
+ portal: true,
2688
+ anchor: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2689
+ "button",
2690
+ {
2691
+ type: "button",
2692
+ className: "tf-composer-trigger",
2693
+ "aria-label": `${t("composer.label")}: ${currentLabel}`,
2694
+ "aria-haspopup": "menu",
2695
+ "aria-expanded": open,
2696
+ "aria-busy": pending,
2697
+ title: blank ? t("composer.hint") : t("composer.locked"),
2698
+ disabled: pending || locked,
2699
+ onClick: () => setOpen((value2) => !value2),
2700
+ children: [
2701
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tf-composer-value", children: currentLabel }),
2702
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2703
+ "span",
2704
+ {
2705
+ className: open ? "tf-composer-chevron tf-composer-chevron-open" : "tf-composer-chevron",
2706
+ "aria-hidden": "true",
2707
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconChevronDownOutlineRegular, {})
2708
+ }
2709
+ )
2710
+ ]
2711
+ }
2712
+ )
2713
+ }
2714
+ ),
2592
2715
  error !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "tf-composer-note", role: "status", children: error }) : null
2593
2716
  ] })
2594
2717
  ] });
@@ -2700,6 +2823,12 @@ window.__ModuleLoader__.load({
2700
2823
  }
2701
2824
  return { accepted: kept, rejections };
2702
2825
  }
2826
+ function carriesFiles(types) {
2827
+ return types !== void 0 && types.includes("Files");
2828
+ }
2829
+ function droppedFiles(files) {
2830
+ return files === void 0 ? [] : Array.from(files);
2831
+ }
2703
2832
 
2704
2833
  // src/client/icon.tsx
2705
2834
  var import_react2 = require("react");
@@ -2747,7 +2876,7 @@ window.__ModuleLoader__.load({
2747
2876
  }
2748
2877
 
2749
2878
  // src/client/page.tsx
2750
- var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
2879
+ var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
2751
2880
  var import_react10 = require("react");
2752
2881
 
2753
2882
  // src/client/board.ts
@@ -3449,6 +3578,8 @@ window.__ModuleLoader__.load({
3449
3578
  const [files, setFiles] = (0, import_react4.useState)([]);
3450
3579
  const [rejections, setRejections] = (0, import_react4.useState)([]);
3451
3580
  const picker = (0, import_react4.useRef)(null);
3581
+ const [dragActive, setDragActive] = (0, import_react4.useState)(false);
3582
+ const dragDepth = (0, import_react4.useRef)(0);
3452
3583
  const previews = (0, import_react4.useRef)(/* @__PURE__ */ new Map());
3453
3584
  const [, bumpPreviews] = (0, import_react4.useState)(0);
3454
3585
  const [broken, setBroken] = (0, import_react4.useState)([]);
@@ -3501,18 +3632,75 @@ window.__ModuleLoader__.load({
3501
3632
  },
3502
3633
  []
3503
3634
  );
3504
- const pick = (event) => {
3505
- const picked = Array.from(event.target.files ?? []);
3506
- event.target.value = "";
3635
+ const addFiles = (picked) => {
3507
3636
  if (picked.length === 0) return;
3508
3637
  const intake = intakeAttachments(picked, files, attachmentPolicy ?? null);
3509
3638
  if (intake.accepted.length > 0) setFiles((current) => [...current, ...intake.accepted]);
3510
3639
  setRejections(intake.rejections);
3511
3640
  };
3641
+ const pick = (event) => {
3642
+ const picked = Array.from(event.target.files ?? []);
3643
+ event.target.value = "";
3644
+ addFiles(picked);
3645
+ };
3646
+ const dropRefused = busy || attachmentAvailable === false;
3647
+ const dragEnter = (event) => {
3648
+ if (!carriesFiles(event.dataTransfer?.types) || dropRefused) return;
3649
+ event.preventDefault();
3650
+ dragDepth.current += 1;
3651
+ setDragActive(true);
3652
+ };
3653
+ const dragOver = (event) => {
3654
+ if (!carriesFiles(event.dataTransfer?.types)) return;
3655
+ event.preventDefault();
3656
+ event.dataTransfer.dropEffect = dropRefused ? "none" : "copy";
3657
+ };
3658
+ const dragLeave = (event) => {
3659
+ if (!carriesFiles(event.dataTransfer?.types)) return;
3660
+ dragDepth.current = Math.max(0, dragDepth.current - 1);
3661
+ if (dragDepth.current === 0) setDragActive(false);
3662
+ };
3663
+ const drop = (event) => {
3664
+ if (!carriesFiles(event.dataTransfer?.types)) return;
3665
+ event.preventDefault();
3666
+ dragDepth.current = 0;
3667
+ setDragActive(false);
3668
+ if (dropRefused) return;
3669
+ addFiles(droppedFiles(event.dataTransfer?.files));
3670
+ };
3671
+ (0, import_react4.useEffect)(() => {
3672
+ if (!dragActive) return;
3673
+ const reset = () => {
3674
+ dragDepth.current = 0;
3675
+ setDragActive(false);
3676
+ };
3677
+ const onKeyDown = (event) => {
3678
+ if (event.key === "Escape") reset();
3679
+ };
3680
+ window.addEventListener("keydown", onKeyDown);
3681
+ window.addEventListener("dragend", reset);
3682
+ window.addEventListener("drop", reset);
3683
+ window.addEventListener("blur", reset);
3684
+ return () => {
3685
+ window.removeEventListener("keydown", onKeyDown);
3686
+ window.removeEventListener("dragend", reset);
3687
+ window.removeEventListener("drop", reset);
3688
+ window.removeEventListener("blur", reset);
3689
+ };
3690
+ }, [dragActive]);
3512
3691
  const remove2 = (index2) => {
3513
3692
  setFiles((current) => current.filter((_, position) => position !== index2));
3514
3693
  setRejections([]);
3515
3694
  };
3695
+ const pageDragOver = (event) => {
3696
+ if (carriesFiles(event.dataTransfer?.types)) event.preventDefault();
3697
+ };
3698
+ const pageDrop = (event) => {
3699
+ if (!carriesFiles(event.dataTransfer?.types)) return;
3700
+ event.preventDefault();
3701
+ dragDepth.current = 0;
3702
+ setDragActive(false);
3703
+ };
3516
3704
  const submit = (event) => {
3517
3705
  event.preventDefault();
3518
3706
  if (busy) return;
@@ -3543,7 +3731,7 @@ window.__ModuleLoader__.load({
3543
3731
  size: formatBytes(attachmentPolicy.maxImageBytes),
3544
3732
  count: attachmentPolicy.maxImagesPerMessage
3545
3733
  });
3546
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "tf-create", children: [
3734
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "tf-create", onDragOver: pageDragOver, onDrop: pageDrop, children: [
3547
3735
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("header", { className: "tf-detail-head", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "tf-detail-heading", children: [
3548
3736
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h1", { className: "tf-title", children: t("create.title") }),
3549
3737
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "tf-sub", children: t("create.subtitle") })
@@ -3606,88 +3794,100 @@ window.__ModuleLoader__.load({
3606
3794
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-field-hint", children: t("create.descriptionHint") })
3607
3795
  ] })
3608
3796
  ] }),
3609
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "tf-card", children: [
3610
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "tf-card-head", children: [
3611
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { className: "tf-card-title", children: t("create.attachment.label") }),
3612
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-card-hint", children: t("create.attachment.optional") })
3613
- ] }),
3614
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "tf-attachment-bar", children: [
3615
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3616
- "button",
3617
- {
3618
- className: "tf-btn",
3619
- "data-tone": "soft",
3620
- type: "button",
3621
- disabled: busy || attachmentAvailable === false,
3622
- onClick: () => picker.current?.click(),
3623
- children: t("create.attachment.add")
3624
- }
3625
- ),
3626
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-field-hint tf-grow", children: attachmentHint })
3627
- ] }),
3628
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3629
- "input",
3630
- {
3631
- ref: picker,
3632
- className: "tf-attachment-input",
3633
- type: "file",
3634
- multiple: true,
3635
- tabIndex: -1,
3636
- "aria-hidden": true,
3637
- onChange: pick
3638
- }
3639
- ),
3640
- files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "tf-attachment-list", children: files.map((file, index2) => {
3641
- const state = uploads[fileKey(file)];
3642
- const label = state === void 0 ? null : state.status === "uploaded" ? t("create.attachment.uploaded") : state.status === "failed" ? t("create.attachment.failed") : state.status === "uploading" ? state.percent === void 0 ? t("create.attachment.uploading") : t("create.attachment.uploadingPercent", { percent: state.percent }) : t("create.attachment.queued");
3643
- const key = fileKey(file);
3644
- const preview = broken.includes(key) ? void 0 : previews.current.get(key);
3645
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { className: "tf-attachment-item", "data-state": state?.status, children: [
3646
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-thumb", "data-kind": isImageFile(file) ? "image" : "file", children: preview === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-ext", children: fileExtension(file.name) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3647
- "img",
3648
- {
3649
- className: "tf-attachment-thumb-img",
3650
- src: preview,
3651
- alt: file.name,
3652
- onError: () => setBroken((current) => current.includes(key) ? current : [...current, key])
3653
- }
3654
- ) }),
3655
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "tf-attachment-body", children: [
3656
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-name", title: file.name, children: file.name }),
3657
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "tf-attachment-meta", children: [
3658
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-size", children: formatBytes(file.size) }),
3659
- label === null ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
3660
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-sep", "aria-hidden": true, children: t("create.attachment.metaSep") }),
3661
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-status", children: label })
3662
- ] })
3663
- ] }),
3664
- state?.status === "failed" && state.reason !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-error", children: t(FAILURE_KEY[state.reason]) }) : null,
3665
- state?.status === "uploading" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-track", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3666
- "span",
3667
- {
3668
- className: "tf-attachment-fill",
3669
- style: { width: `${Math.max(4, Math.min(100, state.percent ?? 0))}%` }
3670
- }
3671
- ) }) : null
3797
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3798
+ "section",
3799
+ {
3800
+ className: "tf-card tf-attachment-dropzone",
3801
+ "data-drag": dragActive ? "active" : void 0,
3802
+ onDragEnter: dragEnter,
3803
+ onDragOver: dragOver,
3804
+ onDragLeave: dragLeave,
3805
+ onDrop: drop,
3806
+ children: [
3807
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "tf-card-head", children: [
3808
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { className: "tf-card-title", children: t("create.attachment.label") }),
3809
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-card-hint", children: t("create.attachment.optional") })
3672
3810
  ] }),
3673
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3811
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
3674
3812
  "button",
3675
3813
  {
3676
- className: "tf-attachment-remove",
3814
+ className: "tf-attachment-pick",
3677
3815
  type: "button",
3678
- "aria-label": `${t("create.attachment.remove")} ${file.name}`,
3679
- disabled: busy,
3680
- onClick: () => remove2(index2),
3681
- children: "×"
3816
+ disabled: busy || attachmentAvailable === false,
3817
+ onClick: () => picker.current?.click(),
3818
+ children: [
3819
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-plus", "aria-hidden": "true", children: "+" }),
3820
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-pick-title", children: t("create.attachment.add") }),
3821
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-pick-sub", children: dragActive ? t("create.attachment.dropActive") : t("create.attachment.dropHint") })
3822
+ ]
3682
3823
  }
3683
- )
3684
- ] }, `${fileKey(file)}-${index2}`);
3685
- }) }) : null,
3686
- rejections.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "tf-attachment-rejected", role: "alert", children: rejections.map((rejection, index2) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: t(REJECTION_KEY[rejection.reason], {
3687
- name: rejection.file.name,
3688
- ...rejection.params
3689
- }) }, `${rejection.file.name}-${index2}`)) }) : null
3690
- ] }),
3824
+ ),
3825
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "tf-attachment-limits", children: attachmentHint }),
3826
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3827
+ "input",
3828
+ {
3829
+ ref: picker,
3830
+ className: "tf-attachment-input",
3831
+ type: "file",
3832
+ multiple: true,
3833
+ tabIndex: -1,
3834
+ "aria-hidden": true,
3835
+ onChange: pick
3836
+ }
3837
+ ),
3838
+ files.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "tf-attachment-list", children: files.map((file, index2) => {
3839
+ const state = uploads[fileKey(file)];
3840
+ const label = state === void 0 ? null : state.status === "uploaded" ? t("create.attachment.uploaded") : state.status === "failed" ? t("create.attachment.failed") : state.status === "uploading" ? state.percent === void 0 ? t("create.attachment.uploading") : t("create.attachment.uploadingPercent", { percent: state.percent }) : t("create.attachment.queued");
3841
+ const key = fileKey(file);
3842
+ const preview = broken.includes(key) ? void 0 : previews.current.get(key);
3843
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("li", { className: "tf-attachment-item", "data-state": state?.status, children: [
3844
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-thumb", "data-kind": isImageFile(file) ? "image" : "file", children: preview === void 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-ext", children: fileExtension(file.name) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3845
+ "img",
3846
+ {
3847
+ className: "tf-attachment-thumb-img",
3848
+ src: preview,
3849
+ alt: file.name,
3850
+ onError: () => setBroken((current) => current.includes(key) ? current : [...current, key])
3851
+ }
3852
+ ) }),
3853
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "tf-attachment-body", children: [
3854
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-name", title: file.name, children: file.name }),
3855
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "tf-attachment-meta", children: [
3856
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-size", children: formatBytes(file.size) }),
3857
+ label === null ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
3858
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-sep", "aria-hidden": true, children: t("create.attachment.metaSep") }),
3859
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-status", children: label })
3860
+ ] })
3861
+ ] }),
3862
+ state?.status === "failed" && state.reason !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-error", children: t(FAILURE_KEY[state.reason]) }) : null,
3863
+ state?.status === "uploading" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "tf-attachment-track", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3864
+ "span",
3865
+ {
3866
+ className: "tf-attachment-fill",
3867
+ style: { width: `${Math.max(4, Math.min(100, state.percent ?? 0))}%` }
3868
+ }
3869
+ ) }) : null
3870
+ ] }),
3871
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3872
+ "button",
3873
+ {
3874
+ className: "tf-attachment-remove",
3875
+ type: "button",
3876
+ "aria-label": `${t("create.attachment.remove")} ${file.name}`,
3877
+ disabled: busy,
3878
+ onClick: () => remove2(index2),
3879
+ children: "×"
3880
+ }
3881
+ )
3882
+ ] }, `${fileKey(file)}-${index2}`);
3883
+ }) }) : null,
3884
+ rejections.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("ul", { className: "tf-attachment-rejected", role: "alert", children: rejections.map((rejection, index2) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("li", { children: t(REJECTION_KEY[rejection.reason], {
3885
+ name: rejection.file.name,
3886
+ ...rejection.params
3887
+ }) }, `${rejection.file.name}-${index2}`)) }) : null
3888
+ ]
3889
+ }
3890
+ ),
3691
3891
  invalid !== null || error !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "tf-error", role: "alert", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(AlertText, { text: invalid?.message ?? error ?? "" }) }) : null,
3692
3892
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "tf-create-actions", children: [
3693
3893
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { className: "tf-btn", "data-tone": "primary", type: "submit", disabled: !canSubmit, children: staging ? t("create.attachment.submitUploading") : busy ? t("create.submitting") : t("create.submit") }),
@@ -13852,7 +14052,7 @@ window.__ModuleLoader__.load({
13852
14052
  },
13853
14053
  [basePath, onMissing, onOpenPath]
13854
14054
  );
13855
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_dsh_client_ui_primitives.MarkdownDelegateProvider, { openFile: basePath === void 0 ? void 0 : openFile, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_dsh_client_ui_primitives.MarkdownText, { text, labels }) });
14055
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_dsh_client_ui_primitives2.MarkdownDelegateProvider, { openFile: basePath === void 0 ? void 0 : openFile, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_dsh_client_ui_primitives2.MarkdownText, { text, labels }) });
13856
14056
  }
13857
14057
  function SectionHead({
13858
14058
  title,
package/locale/en.json CHANGED
@@ -26,10 +26,12 @@
26
26
  "composer.locked": "The conversation has started; create a new one to switch agents.",
27
27
  "composer.noPreset": "The preset agent is not loaded, so the switch was not applied.",
28
28
  "composer.other": "Other (default)",
29
- "create.attachment.add": "Add files",
29
+ "create.attachment.add": "Add attachments",
30
30
  "create.attachment.blocked": "Attachments are still uploading; wait for them to finish before sending.",
31
31
  "create.attachment.busy": "The conversation is busy, so the attachments were not sent. Try again.",
32
32
  "create.attachment.cancelled": "Upload cancelled; nothing was sent.",
33
+ "create.attachment.dropActive": "Release to attach these files.",
34
+ "create.attachment.dropHint": "Click to choose files, or drop them here.",
33
35
  "create.attachment.failed": "Upload failed",
34
36
  "create.attachment.failed.network": "The upload failed. Check the network and try again.",
35
37
  "create.attachment.failed.rejected": "The host rejected the file (damaged content or wrong type).",
package/locale/zh.json CHANGED
@@ -26,10 +26,12 @@
26
26
  "composer.locked": "会话已开始,无法再切换 Agent;请新建会话。",
27
27
  "composer.noPreset": "预设 Agent 未加载,无法切换。",
28
28
  "composer.other": "其他(默认)",
29
- "create.attachment.add": "选择文件",
29
+ "create.attachment.add": "添加附件",
30
30
  "create.attachment.blocked": "附件正在上传,上传完成后再提交。",
31
31
  "create.attachment.busy": "会话正忙,附件未发送,请稍后重试。",
32
32
  "create.attachment.cancelled": "已取消上传,附件未发送。",
33
+ "create.attachment.dropActive": "松手即可添加附件。",
34
+ "create.attachment.dropHint": "点击选择文件,或把文件拖到这里。",
33
35
  "create.attachment.failed": "上传失败",
34
36
  "create.attachment.failed.network": "上传暂时失败,请检查网络后重试。",
35
37
  "create.attachment.failed.rejected": "宿主拒绝了该文件(内容损坏或类型不符)。",
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@xirang-ai/dsh-teamflow",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "DeepSeek Harness plugin: Xirang workbench for the requirement → development → defect pipeline.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/xirang-ai/teamflow.git"
8
+ "url": "git+https://github.com/xirang-ai/dsh-teamflow.git"
9
9
  },
10
10
  "keywords": [
11
11
  "deepseek-harness",
@@ -115,8 +115,8 @@
115
115
  "@deepseek-ai/schemastery": "^3.19.0",
116
116
  "nanoid": "^6.0.2"
117
117
  },
118
- "homepage": "https://github.com/xirang-ai/teamflow#readme",
118
+ "homepage": "https://github.com/xirang-ai/dsh-teamflow#readme",
119
119
  "bugs": {
120
- "url": "https://github.com/xirang-ai/teamflow/issues"
120
+ "url": "https://github.com/xirang-ai/dsh-teamflow/issues"
121
121
  }
122
122
  }