dsh-agent-lines 0.3.9 → 0.4.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/CHANGELOG.en.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  English | [中文](CHANGELOG.md)
4
4
 
5
+ ## 0.4.1 (2026-11-09)
6
+
7
+ - Fix F1: after line_create replace / line_rebind, the new line did not re-acquire a lease, leaving it offline and messages queued until restart.
8
+ - Regression test: the replaced line must have a live lease.
9
+ - No schema change; 0.4.0 kinds/approval/docs/landing unchanged.
10
+
11
+ ## 0.4.0 (2026-11-09)
12
+
13
+ - Message governance: line_send adds kind (info/request/task/alert) and ccManager; a non-manager task becomes pending_approval and is delivered only after line_manager_ack.
14
+ - Schema v8: messages.kind/cc_manager/approval_state/approved_by/approved_at; additive migration.
15
+ - Added line_manager_ack (approve/reject).
16
+ - line_observe shows kind/ccManager/approvalState; pending tasks do not enter the target inbox.
17
+ - README is team-agnostic; GitHub Pages is rewritten for beginners (DSH-assisted install, novel/video/dev scenarios, restart handoff card).
18
+
5
19
  ## 0.3.9 (2026-11-09)
6
20
 
7
21
  - Added read-only line_observe: a manager feed of cross-line messages without prompts or state changes.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  [English](CHANGELOG.en.md) | 中文
4
4
 
5
+ ## 0.4.1 (2026-11-09)
6
+
7
+ - 修复 F1:line_create replace / line_rebind 在 rebind 后没有重新 acquire lease,导致线 offline、消息 queued 直到进程重启。
8
+ - 回归测试:replace 后新 Session 必须有 live lease。
9
+ - 无 schema 变化;0.4.0 的 kind/审批/文档/官网不变。
10
+
11
+ ## 0.4.0 (2026-11-09)
12
+
13
+ - 消息治理:line_send 新增 kind(info/request/task/alert)、ccManager;非管理线发 task 进入 pending_approval,需 line_manager_ack 审批后才投递。
14
+ - schema v8:messages.kind/cc_manager/approval_state/approved_by/approved_at;additive 迁移。
15
+ - 新增 line_manager_ack(approve/reject)。
16
+ - line_observe 展示 kind/ccManager/approvalState;待审批任务不进目标线 inbox。
17
+ - README 团队类型通用化;GitHub Pages 面向小白改版(安装交给 DSH、小说/视频/开发场景、重启前交接卡)。
18
+
5
19
  ## 0.3.9 (2026-11-09)
6
20
 
7
21
  - 新增只读 line_observe:管理线查看跨线消息 feed,不注入 prompt、不改消息状态。
package/README.en.md CHANGED
@@ -23,6 +23,18 @@ Use it when:
23
23
  - You want one lead agent to decompose work, create the team, track progress, collect results, and report back.
24
24
 
25
25
 
26
+ ## Any team type, not just development
27
+
28
+ The plugin does not limit team types. It is "one manager + several long-lived members".
29
+ The examples below use a development team; novel, video, research, or operations teams only change roles and tasks.
30
+
31
+ - Novel: editor-in-chief, outline, writer, editor, proofreader, reviewer;
32
+ - Video: manager, planning, script, editing, cover, publishing;
33
+ - Development: manager, developer, tester, acceptance;
34
+ - Research: manager, research, analysis, writing, review.
35
+
36
+ You do not need to know the roles up front. Tell the manager "build a novel team and set up its workflow and mechanisms"; it proposes the team shape first.
37
+
26
38
  ## What each version adds (business scenarios)
27
39
 
28
40
  ### 0.3.6: replace a role when context is full
@@ -43,7 +55,7 @@ Scenario: the manager wants to see which line is close to saturation or has gone
43
55
  Use: line_status / line_list expose activity.level, turns, steps, lastActivityAt.
44
56
  Effect: a low/medium/high activity proxy, not a fake "78% context used"; exact context usage waits for DSH.
45
57
 
46
- ### 0.3.9: the manager can see cross-line traffic
58
+ ### 0.4.1: the manager can see cross-line traffic
47
59
 
48
60
  Scenario: lines message each other directly; the manager needs visibility without every message becoming a manager turn.
49
61
  Use: line_observe({ line: "writer", limit: 20 }) or CLI observe --json.
@@ -62,7 +74,7 @@ The script picks the DSH Web profile, installs the plugin, and verifies agent-li
62
74
 
63
75
  Offline or when npm is unavailable, use the local tarball:
64
76
 
65
- bash install.sh ./dsh-agent-lines-0.3.9.tgz
77
+ bash install.sh ./dsh-agent-lines-0.4.1.tgz
66
78
 
67
79
  After install and restart, it works without a config. When you want a fixed manager/worker setup, paste the "Manager config" block below into the file printed by the installer.
68
80
 
@@ -165,12 +177,13 @@ Tools:
165
177
  | `line_list` | List lines, status, unread counts, current line and role |
166
178
  | `line_create` | Manager only: create a line; same label is idempotent; replace:true swaps the label to a fresh Session |
167
179
  | `line_handoff` | Manager only: structured handoff snapshot (JSON/Markdown), no model call |
168
- | `line_observe` | Manager only: read-only cross-line feed; no prompt injection, no state change |
180
+ | `line_observe` | Manager only: read-only cross-line feed with kind/approval state; no prompt injection, no state change |
181
+ | `line_manager_ack` | Manager only: approve/reject a pending_approval task |
169
182
  | `line_retire` | Manager only: soft-retire; reject new sends, keep Session and history |
170
183
  | `line_rebind` | Manager only: atomically switch a line to a new live Session, optionally migrating backlog |
171
184
  | `line_rename` | Manager only: rename the sidebar title and persist it in lines.db |
172
185
  | `line_move_workspace` | Manager only: attach an existing line Session to a workspace whose path exactly matches the Session cwd |
173
- | `line_send` | Send to `line` or `agent#line`; optional `idempotencyKey`, `replyTo`, `noReply`, `forceDistinct`, `ttlMs` |
186
+ | `line_send` | Send to `line` or `agent#line`; optional `kind`, `ccManager`, `idempotencyKey`, `replyTo`, `noReply`, `forceDistinct`, `ttlMs` |
174
187
  | `line_inbox` | Read delivered messages for the current line |
175
188
  | `line_ack` | Acknowledge a delivered message |
176
189
 
@@ -263,6 +276,19 @@ dsh-agent-lines claim --agent agent-demo --line manager --session "$DSH_SESSION_
263
276
 
264
277
  Claim is never automatic for every new Session: it needs an explicit user request or an explicit CLI call.
265
278
 
279
+ ## Message kinds and task approval
280
+
281
+ `line_send` supports four kinds:
282
+
283
+ | kind | Meaning | Non-manager send |
284
+ |---|---|---|
285
+ | info | Informational sync | delivered directly |
286
+ | request | Request help | delivered directly |
287
+ | task | Assign work | becomes pending_approval; delivered only after manager approve |
288
+ | alert | Alert | delivered directly |
289
+
290
+ Manager tasks skip approval. Use `line_observe` to see pending tasks and `line_manager_ack({ messageId, decision: "approve" | "reject" })` to decide.
291
+
266
292
  ## Echo and duplicate protection
267
293
 
268
294
  - Same `idempotencyKey` returns the existing message.
package/README.md CHANGED
@@ -23,6 +23,18 @@ DSH 的 super agent 平替插件。
23
23
  - 你希望一个负责人 agent 自己拆任务、建团队、追进度、收结果,最后向你汇报。
24
24
 
25
25
 
26
+ ## 团队类型不限于开发
27
+
28
+ 插件不限定团队类型。本质是“一个负责人 + 若干长期成员”的协作方式。
29
+ 下面用开发团队做示例;小说、视频、研究、运营团队只换角色和任务,不需要改插件或代码。
30
+
31
+ - 小说:总编、大纲、写手、编辑、校对、审读;
32
+ - 视频:负责人、策划、脚本、剪辑、封面、发布;
33
+ - 开发:负责人、开发、测试、验收;
34
+ - 研究:负责人、资料、分析、写作、审阅。
35
+
36
+ 第一次不需要自己想清楚角色:直接对负责人说“帮我建个小说创作团队,按小说创作的流程建立工作流程与机制”,它会先给团队结构和流程建议,你确认或补充即可。
37
+
26
38
  ## 版本能力与业务场景
27
39
 
28
40
  ### 0.3.6:上下文满了,角色可以换人
@@ -43,7 +55,7 @@ DSH 的 super agent 平替插件。
43
55
  用法:line_status / line_list 看 activity.level、turns、steps、lastActivityAt。
44
56
  效果:给出低/中/高活跃度,不伪造“上下文使用率 78%”;真实窗口等 DSH 上游提供。
45
57
 
46
- ### 0.3.9:管理线看得到跨线消息
58
+ ### 0.4.1:管理线看得到跨线消息
47
59
 
48
60
  场景:成员线之间直接同步,管理线需要知道发生了什么,但不希望每条消息都打断自己。
49
61
  用法:line_observe({ line: "writer", limit: 20 }) 或 CLI observe --json。
@@ -51,6 +63,22 @@ DSH 的 super agent 平替插件。
51
63
 
52
64
  ## 安装
53
65
 
66
+ ### 让 DSH 帮你装(推荐给新手)
67
+
68
+ 在 DSH 新会话里说:
69
+
70
+ ~~~text
71
+ 帮我检查并安装 GitHub 上 Lencen/dsh-agent-lines 插件。
72
+ 只从官方仓库或官方 npm 安装,不要从其他 fork 安装。
73
+ 安装前先告诉我准备执行什么命令,等我确认后再执行。
74
+ 安装完成后按插件要求加载;如果需要重启 DSH Web,先告诉我重启后打开哪个地址、
75
+ 怎么新开会话、第一句该说什么,并提醒当前会话会断开;等我确认后再重启。
76
+ ~~~
77
+
78
+ 重启后当前会话会断开,agent 无法再自动通知你;重启后的步骤会在重启前给你。
79
+
80
+ ### 手动安装
81
+
54
82
  一条命令安装(推荐,不需要自己找 profile):
55
83
 
56
84
  curl -fsSL https://raw.githubusercontent.com/Lencen/dsh-agent-lines/main/install.sh -o install.sh
@@ -62,7 +90,7 @@ DSH 的 super agent 平替插件。
62
90
 
63
91
  离线或 npm 不可用时,用本地 tarball:
64
92
 
65
- bash install.sh ./dsh-agent-lines-0.3.9.tgz
93
+ bash install.sh ./dsh-agent-lines-0.4.1.tgz
66
94
 
67
95
  安装并重启后,不写配置也可以直接使用;需要固定管理线/worker 时,再把下面“管理线配置”粘贴到脚本打印的配置文件。
68
96
 
@@ -170,12 +198,13 @@ level 是活跃度代理(low/medium/high),不是上下文使用率百分
170
198
  | `line_list` | 列出线、状态、未读数、当前线和 role |
171
199
  | `line_create` | 仅管理线:创建新线;同 label 幂等;传 replace:true 可给同 label 换新 Session |
172
200
  | `line_handoff` | 仅管理线:生成结构化交接单(JSON/Markdown),不调模型 |
173
- | `line_observe` | 仅管理线:只读跨线消息 feed;不注入 prompt、不改状态 |
201
+ | `line_observe` | 仅管理线:只读跨线消息 feed;显示 kind/审批状态,不注入 prompt、不改状态 |
202
+ | `line_manager_ack` | 仅管理线:对 pending_approval 的 task 做 approve/reject |
174
203
  | `line_retire` | 仅管理线:软下线;拒绝新发送,保留 Session/历史 |
175
204
  | `line_rebind` | 仅管理线:把线原子切到新的 live Session,可迁移积压 |
176
205
  | `line_rename` | 仅管理线:改侧栏标题,持久化到 lines.db |
177
206
  | `line_move_workspace` | 仅管理线:把已有线的 Session 挂到路径完全匹配的工作区 |
178
- | `line_send` | 发到 `line` 或 `agent#line`;可选 `idempotencyKey`、`replyTo`、`noReply`、`forceDistinct`、`ttlMs` |
207
+ | `line_send` | 发到 `line` 或 `agent#line`;可选 `kind`、`ccManager`、`idempotencyKey`、`replyTo`、`noReply`、`forceDistinct`、`ttlMs` |
179
208
  | `line_inbox` | 读当前线已投递消息 |
180
209
  | `line_ack` | 确认已处理 |
181
210
 
@@ -261,6 +290,19 @@ dsh-agent-lines claim --agent agent-demo --line manager --session "$DSH_SESSION_
261
290
 
262
291
  claim 不会对每个新会话自动执行:需要用户明确要求,或运维显式调用。
263
292
 
293
+ ## 消息类型与任务审批
294
+
295
+ `line_send` 支持四种 kind:
296
+
297
+ | kind | 含义 | 非管理线发送 |
298
+ |---|---|---|
299
+ | info | 信息同步 | 直接投递 |
300
+ | request | 请求协助 | 直接投递 |
301
+ | task | 派任务 | 进入 pending_approval,管理线 approve 后才投递 |
302
+ | alert | 告警 | 直接投递 |
303
+
304
+ 管理线发 task 不进入审批。管理线用 `line_observe` 看 pending,再用 `line_manager_ack({ messageId, decision: "approve" | "reject" })` 决定。
305
+
264
306
  ## 回声与重复保护
265
307
 
266
308
  - 同 `idempotencyKey` 返回已有消息。
@@ -3,96 +3,150 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>dsh-agent-lines — let one AI lead run a team</title>
7
- <meta name="description" content="State the goal. One AI lead splits the work, creates team lines, tracks progress, and reports back. Each line keeps its own context; work survives a restart.">
6
+ <title>dsh-agent-lines — let an AI lead run your AI team</title>
7
+ <meta name="description" content="Tell the lead your goal. It splits work, builds a team, tracks progress and summarizes results. Members and messages survive a DSH restart.">
8
8
  <meta property="og:image" content="https://lencen.github.io/dsh-agent-lines/assets/social-preview.png">
9
9
  <style>
10
- :root{--bg:#080b10;--panel:#0d1117;--line:#263241;--text:#e6edf3;--muted:#8b949e;--cyan:#58a6ff;--green:#7ee787}
10
+ :root{--bg:#080b10;--panel:#0d1117;--line:#263241;--text:#e6edf3;--muted:#8b949e;--cyan:#58a6ff;--green:#7ee787;--warn:#f0b72f}
11
11
  *{box-sizing:border-box}
12
- body{margin:0;background:radial-gradient(circle at 80% 0%,#12314a 0,#080b10 42%);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;line-height:1.7}
12
+ body{margin:0;background:radial-gradient(circle at 80% 0%,#12314a 0,#080b10 42%);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans CJK SC",sans-serif;line-height:1.75}
13
13
  a{color:var(--cyan);text-decoration:none}
14
- .wrap{max-width:1080px;margin:0 auto;padding:0 24px}
14
+ .wrap{max-width:1060px;margin:0 auto;padding:0 24px}
15
15
  header{display:flex;align-items:center;justify-content:space-between;padding:22px 0;border-bottom:1px solid #1f2937}
16
16
  .brand{display:flex;align-items:center;gap:10px;color:var(--text);font-weight:700}
17
17
  .mark{width:24px;height:24px;border-radius:7px;background:linear-gradient(135deg,#58a6ff,#7ee787)}
18
18
  nav a{color:var(--muted);margin-left:18px;font-size:14px}
19
- .hero{padding:72px 0 48px;display:grid;grid-template-columns:1.05fr .95fr;gap:42px;align-items:center}
20
- .kicker{color:var(--green);font-size:12px;letter-spacing:.16em;text-transform:uppercase}
21
- h1{font-size:48px;line-height:1.15;margin:14px 0 18px;letter-spacing:-.03em}
19
+ .hero{padding:64px 0 32px;display:grid;grid-template-columns:1.1fr .9fr;gap:40px;align-items:center}
20
+ .kicker{color:var(--green);font-size:12px;letter-spacing:.14em;text-transform:uppercase}
21
+ h1{font-size:46px;line-height:1.15;margin:12px 0 16px;letter-spacing:-.03em}
22
22
  h1 span{color:var(--cyan)}
23
- .lead{color:#b7c3cf;font-size:17px;max-width:560px}
24
- .install{margin:26px 0;background:#0a0f16;border:1px solid var(--line);border-radius:10px;padding:14px 16px;font-family:"DejaVu Sans Mono",monospace;font-size:13px;color:#c9d1d9;overflow:auto}
25
- .actions{display:flex;gap:12px;flex-wrap:wrap}
26
- .btn{display:inline-block;padding:10px 16px;border-radius:9px;border:1px solid var(--line);color:var(--text);font-size:14px}
27
- .btn.primary{background:var(--cyan);color:#06111f;border-color:var(--cyan);font-weight:700}
23
+ .lead{color:#b7c3cf;font-size:17px;max-width:580px}
24
+ .actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:22px}
25
+ .btn{display:inline-block;padding:11px 17px;border-radius:9px;border:1px solid var(--line);color:var(--text);font-size:14px}
26
+ .btn.primary{background:var(--green);color:#06111f;border-color:var(--green);font-weight:700}
28
27
  .shot{border:1px solid var(--line);border-radius:14px;overflow:hidden;background:var(--panel);box-shadow:0 22px 60px rgba(0,0,0,.35)}
29
28
  .shot img{display:block;width:100%;height:auto}
30
29
  .caption{font-size:12px;color:var(--muted);padding:10px 14px;border-top:1px solid #1f2937}
31
- section{padding:36px 0;border-top:1px solid #161d27}
32
- h2{font-size:22px;margin:0 0 18px}
30
+ section{padding:34px 0;border-top:1px solid #161d27}
31
+ h2{font-size:23px;margin:0 0 16px}
33
32
  .cards{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
34
- .card{background:rgba(13,17,23,.8);border:1px solid var(--line);border-radius:12px;padding:18px}
33
+ .card{background:rgba(13,17,23,.82);border:1px solid var(--line);border-radius:12px;padding:18px}
35
34
  .card b{display:block;margin-bottom:6px}
36
35
  .card p{color:var(--muted);font-size:14px;margin:0}
37
- pre{background:#0a0f16;border:1px solid var(--line);border-radius:10px;padding:16px;overflow:auto;color:#c9d1d9;font-size:13px}
38
- footer{padding:34px 0 48px;color:var(--muted);font-size:12px}
39
- @media(max-width:820px){.hero{grid-template-columns:1fr;padding-top:44px}h1{font-size:36px}.cards{grid-template-columns:1fr}}
36
+ pre{background:#0a0f16;border:1px solid var(--line);border-radius:10px;padding:16px;overflow:auto;color:#c9d1d9;font-size:13px;white-space:pre-wrap}
37
+ .note{color:var(--muted);font-size:13px}
38
+ .warn{border-left:3px solid var(--warn);padding:10px 14px;background:#141007;color:#e8d7a8;border-radius:6px;font-size:14px}
39
+ details{background:rgba(13,17,23,.7);border:1px solid var(--line);border-radius:10px;padding:14px 16px;margin-top:14px}
40
+ summary{cursor:pointer;color:var(--muted);font-size:14px}
41
+ footer{padding:30px 0 46px;color:var(--muted);font-size:12px;border-top:1px solid #161d27}
42
+ @media(max-width:820px){.hero{grid-template-columns:1fr;padding-top:40px}h1{font-size:36px}.cards{grid-template-columns:1fr}}
40
43
  </style>
41
44
  </head>
42
45
  <body>
43
46
  <div class="wrap">
44
47
  <header>
45
48
  <a class="brand" href="../"><span class="mark"></span>dsh-agent-lines</a>
46
- <nav><a href="../">中文</a><a href="https://github.com/Lencen/dsh-agent-lines">GitHub</a><a href="https://github.com/Lencen/dsh-agent-lines/releases/tag/v0.3.9">Release</a></nav>
49
+ <nav><a href="../">中文</a><a href="https://github.com/Lencen/dsh-agent-lines">GitHub</a><a href="https://github.com/Lencen/dsh-agent-lines/releases/tag/v0.4.1">Release</a></nav>
47
50
  </header>
48
51
  <main>
49
- <section class="hero" style="border:0;padding-bottom:22px">
52
+ <section class="hero" style="border:0;padding-bottom:20px">
50
53
  <div>
51
54
  <div class="kicker">Community plugin · Apache-2.0</div>
52
- <h1>Let one AI lead<br><span>run a team for you</span></h1>
53
- <p class="lead">You state the goal. The lead splits it into research, architecture, UI, development, test, acceptance, release, and ops lines; assigns work, tracks progress, and reports back. Close the window or restart DSH; the work and messages remain.</p>
54
- <div class="install">curl -fsSL https://raw.githubusercontent.com/Lencen/dsh-agent-lines/main/install.sh -o install.sh &amp;&amp; bash install.sh</div>
55
+ <h1>Let an AI lead<br><span>run your AI team</span></h1>
56
+ <p class="lead">Tell it the goal. The lead splits work, picks members, assigns tasks, tracks progress and summarizes the result. Close the window or restart DSH: members and messages stay.</p>
55
57
  <div class="actions">
56
- <a class="btn primary" href="https://github.com/Lencen/dsh-agent-lines">GitHub repository</a>
57
- <a class="btn" href="https://github.com/Lencen/dsh-agent-lines/releases/tag/v0.3.9">Download 0.3.9</a>
58
+ <a class="btn primary" href="#start">How to start</a>
59
+ <a class="btn" href="#install">Install</a>
58
60
  </div>
59
61
  </div>
60
62
  <div class="shot">
61
- <img src="../assets/first-use.gif" alt="First use: create a manager line and 8 member lines, then receive acks">
62
- <div class="caption">demo profile · generic labels · no internal data</div>
63
+ <img src="../assets/first-use.gif" alt="A lead creates member lines and gets acknowledgements">
64
+ <div class="caption">Demo profile · generic roles · no internal data</div>
63
65
  </div>
64
66
  </section>
67
+
65
68
  <section>
66
- <h2>What it takes off your plate</h2>
69
+ <h2>What it does for you</h2>
67
70
  <div class="cards">
68
- <div class="card"><b>No repeated context</b><p>Each member line is a long-lived DSH Session with its own context. Send new work to the right role instead of re-explaining the project.</p></div>
69
- <div class="card"><b>No manual dispatch</b><p>The lead decomposes work, assigns it, coordinates, resolves disagreements, and collects results. You read the summary.</p></div>
70
- <div class="card"><b>No lost work</b><p>Tasks and messages are stored locally. Restart DSH and continue; member state and unread messages are still there.</p></div>
71
+ <div class="card"><b>No repeated background</b><p>Each member keeps its own context; new tasks go straight to the right role.</p></div>
72
+ <div class="card"><b>No manual scheduling</b><p>The lead splits work, coordinates, tracks progress; you read the summary.</p></div>
73
+ <div class="card"><b>No fear of closing</b><p>Members and messages are stored locally; restart DSH and continue.</p></div>
71
74
  </div>
72
- <p style="color:var(--muted);font-size:14px;margin-top:14px">Built for long-running projects and multi-role work.</p>
73
75
  </section>
76
+
74
77
  <section>
75
- <h2>A real usage pattern</h2>
78
+ <h2>Any team type</h2>
79
+ <p class="note">Not only development. Novel, video, research, operations — change roles and tasks, not the plugin.</p>
76
80
  <div class="cards">
77
- <div class="card"><b>1. You state the goal</b><p>“Add CSV export to the reports page, with tests and a short runbook.”</p></div>
78
- <div class="card"><b>2. The lead forms the team</b><p>Split into research, architecture, UI/UX, development, test, acceptance, release, and ops; create each line.</p></div>
79
- <div class="card"><b>3. The team works in parallel</b><p>Members do their parts; the lead tracks progress, resolves interface disagreements, and reassigns when needed.</p></div>
80
- <div class="card"><b>4. You get the result</b><p>The lead reports conclusions, file paths, risks, and open questions. You do not chase every member yourself.</p></div>
81
+ <div class="card"><b>Novel team</b><p>Editor-in-chief, outline, writer, editor, proofreader, reviewer.</p></div>
82
+ <div class="card"><b>Video team</b><p>Lead, planning, script, editing, cover, publishing.</p></div>
83
+ <div class="card"><b>Development team</b><p>Lead, developer, tester, acceptance.</p></div>
81
84
  </div>
85
+ <p class="note" style="margin-top:14px">You do not need to know the roles first. Say “build a novel team and set up its workflow and mechanisms”; the lead proposes the team shape first.</p>
82
86
  </section>
83
- <section>
84
- <h2>First use: let the lead form the team</h2>
85
- <pre>Use dsh-agent-lines. Make this Session the development team lead; bind it first if needed.
86
87
 
87
- You own task decomposition, assignment, coordination, progress tracking, result collection, and reporting. You manage and coordinate only; do not execute development tasks.
88
+ <section id="install">
89
+ <h2>Install: let DSH do it</h2>
90
+ <p>In a new DSH session, say:</p>
91
+ <pre>Check and install the GitHub plugin Lencen/dsh-agent-lines.
92
+ Use only the official repo or official npm package; do not install from forks.
93
+ Before running anything, show me the command and wait for my confirmation.
94
+ After install, load it as required; if DSH Web must restart, first tell me which URL to open,
95
+ how to start a new session, and what to say first, and warn me that this session will disconnect.
96
+ Wait for my confirmation before restarting.</pre>
97
+ <div class="warn">Restarting DSH Web disconnects this session; the agent cannot notify you after the restart. Put the next steps in the last message before restarting.</div>
98
+ <details>
99
+ <summary>Prefer manual install? Command</summary>
100
+ <pre>curl -fsSL https://raw.githubusercontent.com/Lencen/dsh-agent-lines/main/install.sh -o install.sh &amp;&amp; bash install.sh</pre>
101
+ <p class="note">Restart DSH Web after install so the plugin loads.</p>
102
+ </details>
103
+ </section>
104
+
105
+ <section id="start">
106
+ <h2>How to start</h2>
107
+ <div class="cards">
108
+ <div class="card"><b>1. Install and restart</b><p>Use the flow above; restart DSH Web when asked.</p></div>
109
+ <div class="card"><b>2. Open a new session</b><p>Tell the lead what kind of team you want.</p></div>
110
+ <div class="card"><b>3. Confirm the plan</b><p>The lead proposes team shape and workflow; confirm or add roles.</p></div>
111
+ </div>
112
+ <p class="note" style="margin-top:14px">Say:</p>
113
+ <pre>Build a novel team and set up its workflow and mechanisms.
114
+ or
115
+ Build a development team and set up its development workflow and mechanisms.</pre>
116
+ <p class="note">If you have specific roles, add:</p>
117
+ <pre>Also add a proofreader and a reviewer.</pre>
118
+ </section>
88
119
 
89
- Form the team. Use short English names and Chinese roles:
90
- research (调研), architecture (架构), uiux (UIUX), dev (开发), test (测试), acceptance (验收), release (发布), ops (运维).
120
+ <section>
121
+ <h2>First prompts</h2>
122
+ <p class="note">Novel:</p>
123
+ <pre>Build a novel team and set up its workflow and mechanisms.
124
+ Do not write the novel yet; wait for my first chapter task.</pre>
125
+ <p class="note">Video:</p>
126
+ <pre>Build a video team and set up its workflow and mechanisms.
127
+ Do not start yet; wait for my first topic.</pre>
128
+ <p class="note">Development:</p>
129
+ <pre>Build a development team and set up its development workflow and mechanisms.
130
+ Do not start yet; wait for my first requirement.</pre>
131
+ </section>
91
132
 
92
- After the team is ready, wait for my first task. Do not start by yourself.</pre>
93
- <p style="color:var(--muted);font-size:14px">Restart DSH Web after install or update. Technical details, protocol, and limits: <a href="https://github.com/Lencen/dsh-agent-lines">README / architecture</a>. Community plugin, not an official DeepSeek product.</p>
133
+ <section>
134
+ <h2>FAQ</h2>
135
+ <div class="cards">
136
+ <div class="card"><b>Is it expensive?</b><p>You pay for actual usage; the lead proposes a minimal team first and expands later.</p></div>
137
+ <div class="card"><b>Do I lose it on restart?</b><p>No. Members and messages are stored locally; continue after restart.</p></div>
138
+ <div class="card"><b>Two teams at once?</b><p>One plugin instance supports one team. Use separate environments for novel and video.</p></div>
139
+ </div>
94
140
  </section>
95
- <footer>dsh-agent-lines 0.3.9 · Apache-2.0 · Community plugin for DeepSeek Harness. Not an official DeepSeek product.</footer>
141
+
142
+ <footer>
143
+ <details>
144
+ <summary>Developers: protocol, limits, install command</summary>
145
+ <p><a href="https://github.com/Lencen/dsh-agent-lines">GitHub</a> · <a href="https://github.com/Lencen/dsh-agent-lines/releases/tag/v0.4.1">Release</a> · <a href="https://github.com/Lencen/dsh-agent-lines/blob/main/docs/PROTOCOL.en.md">Protocol</a></p>
146
+ <p class="note">Community plugin, not an official DeepSeek product.</p>
147
+ </details>
148
+ <p style="margin-top:18px">dsh-agent-lines 0.4.1 · Apache-2.0 · Community plugin for DeepSeek Harness. Not an official DeepSeek product.</p>
149
+ </footer>
96
150
  </main>
97
151
  </div>
98
152
  </body>
package/docs/index.html CHANGED
@@ -3,96 +3,149 @@
3
3
  <head>
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>dsh-agent-lines — 让 AI 负责人带一支 AI 团队</title>
7
- <meta name="description" content="你说目标,AI 负责人拆任务、建团队、盯进度、收结果;每条成员线有独立会话和持久消息,重启不丢。">
6
+ <title>dsh-agent-lines — 让 AI 负责人带一支团队</title>
7
+ <meta name="description" content="你说目标,AI 负责人拆任务、组团队、追进度、收结果;关掉窗口、重启 DSH,成员和消息还在。">
8
8
  <meta property="og:image" content="https://lencen.github.io/dsh-agent-lines/assets/social-preview.png">
9
9
  <style>
10
- :root{--bg:#080b10;--panel:#0d1117;--line:#263241;--text:#e6edf3;--muted:#8b949e;--cyan:#58a6ff;--green:#7ee787}
10
+ :root{--bg:#080b10;--panel:#0d1117;--line:#263241;--text:#e6edf3;--muted:#8b949e;--cyan:#58a6ff;--green:#7ee787;--warn:#f0b72f}
11
11
  *{box-sizing:border-box}
12
- body{margin:0;background:radial-gradient(circle at 80% 0%,#12314a 0,#080b10 42%);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans CJK SC",sans-serif;line-height:1.7}
12
+ body{margin:0;background:radial-gradient(circle at 80% 0%,#12314a 0,#080b10 42%);color:var(--text);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Noto Sans CJK SC",sans-serif;line-height:1.75}
13
13
  a{color:var(--cyan);text-decoration:none}
14
- .wrap{max-width:1080px;margin:0 auto;padding:0 24px}
14
+ .wrap{max-width:1060px;margin:0 auto;padding:0 24px}
15
15
  header{display:flex;align-items:center;justify-content:space-between;padding:22px 0;border-bottom:1px solid #1f2937}
16
16
  .brand{display:flex;align-items:center;gap:10px;color:var(--text);font-weight:700}
17
17
  .mark{width:24px;height:24px;border-radius:7px;background:linear-gradient(135deg,#58a6ff,#7ee787)}
18
18
  nav a{color:var(--muted);margin-left:18px;font-size:14px}
19
- .hero{padding:72px 0 48px;display:grid;grid-template-columns:1.05fr .95fr;gap:42px;align-items:center}
20
- .kicker{color:var(--green);font-size:12px;letter-spacing:.16em;text-transform:uppercase}
21
- h1{font-size:48px;line-height:1.15;margin:14px 0 18px;letter-spacing:-.03em}
19
+ .hero{padding:64px 0 32px;display:grid;grid-template-columns:1.1fr .9fr;gap:40px;align-items:center}
20
+ .kicker{color:var(--green);font-size:12px;letter-spacing:.14em;text-transform:uppercase}
21
+ h1{font-size:46px;line-height:1.15;margin:12px 0 16px;letter-spacing:-.03em}
22
22
  h1 span{color:var(--cyan)}
23
- .lead{color:#b7c3cf;font-size:17px;max-width:560px}
24
- .install{margin:26px 0;background:#0a0f16;border:1px solid var(--line);border-radius:10px;padding:14px 16px;font-family:"DejaVu Sans Mono",monospace;font-size:13px;color:#c9d1d9;overflow:auto}
25
- .actions{display:flex;gap:12px;flex-wrap:wrap}
26
- .btn{display:inline-block;padding:10px 16px;border-radius:9px;border:1px solid var(--line);color:var(--text);font-size:14px}
27
- .btn.primary{background:var(--cyan);color:#06111f;border-color:var(--cyan);font-weight:700}
23
+ .lead{color:#b7c3cf;font-size:17px;max-width:580px}
24
+ .actions{display:flex;gap:12px;flex-wrap:wrap;margin-top:22px}
25
+ .btn{display:inline-block;padding:11px 17px;border-radius:9px;border:1px solid var(--line);color:var(--text);font-size:14px}
26
+ .btn.primary{background:var(--green);color:#06111f;border-color:var(--green);font-weight:700}
28
27
  .shot{border:1px solid var(--line);border-radius:14px;overflow:hidden;background:var(--panel);box-shadow:0 22px 60px rgba(0,0,0,.35)}
29
28
  .shot img{display:block;width:100%;height:auto}
30
29
  .caption{font-size:12px;color:var(--muted);padding:10px 14px;border-top:1px solid #1f2937}
31
- section{padding:36px 0;border-top:1px solid #161d27}
32
- h2{font-size:22px;margin:0 0 18px}
30
+ section{padding:34px 0;border-top:1px solid #161d27}
31
+ h2{font-size:23px;margin:0 0 16px}
33
32
  .cards{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
34
- .card{background:rgba(13,17,23,.8);border:1px solid var(--line);border-radius:12px;padding:18px}
33
+ .card{background:rgba(13,17,23,.82);border:1px solid var(--line);border-radius:12px;padding:18px}
35
34
  .card b{display:block;margin-bottom:6px}
36
35
  .card p{color:var(--muted);font-size:14px;margin:0}
37
- pre{background:#0a0f16;border:1px solid var(--line);border-radius:10px;padding:16px;overflow:auto;color:#c9d1d9;font-size:13px}
38
- footer{padding:34px 0 48px;color:var(--muted);font-size:12px}
39
- @media(max-width:820px){.hero{grid-template-columns:1fr;padding-top:44px}h1{font-size:36px}.cards{grid-template-columns:1fr}}
36
+ pre{background:#0a0f16;border:1px solid var(--line);border-radius:10px;padding:16px;overflow:auto;color:#c9d1d9;font-size:13px;white-space:pre-wrap}
37
+ .note{color:var(--muted);font-size:13px}
38
+ .warn{border-left:3px solid var(--warn);padding:10px 14px;background:#141007;color:#e8d7a8;border-radius:6px;font-size:14px}
39
+ details{background:rgba(13,17,23,.7);border:1px solid var(--line);border-radius:10px;padding:14px 16px;margin-top:14px}
40
+ summary{cursor:pointer;color:var(--muted);font-size:14px}
41
+ footer{padding:30px 0 46px;color:var(--muted);font-size:12px;border-top:1px solid #161d27}
42
+ @media(max-width:820px){.hero{grid-template-columns:1fr;padding-top:40px}h1{font-size:36px}.cards{grid-template-columns:1fr}}
40
43
  </style>
41
44
  </head>
42
45
  <body>
43
46
  <div class="wrap">
44
47
  <header>
45
48
  <a class="brand" href="./"><span class="mark"></span>dsh-agent-lines</a>
46
- <nav><a href="en/index.html">English</a><a href="https://github.com/Lencen/dsh-agent-lines">GitHub</a><a href="https://github.com/Lencen/dsh-agent-lines/releases/tag/v0.3.9">Release</a></nav>
49
+ <nav><a href="en/index.html">English</a><a href="https://github.com/Lencen/dsh-agent-lines">GitHub</a><a href="https://github.com/Lencen/dsh-agent-lines/releases/tag/v0.4.1">Release</a></nav>
47
50
  </header>
48
51
  <main>
49
- <section class="hero" style="border:0;padding-bottom:22px">
52
+ <section class="hero" style="border:0;padding-bottom:20px">
50
53
  <div>
51
54
  <div class="kicker">Community plugin · Apache-2.0</div>
52
- <h1>让一个 AI 负责人<br><span>替你带一支 AI 团队</span></h1>
53
- <p class="lead">你只管说目标。负责人会拆任务、建调研 / 架构 / 开发 / 测试等成员线、分配工作、追进度、收结果,最后给你一份汇总。关掉窗口或重启 DSH,任务和消息还在。</p>
54
- <div class="install">curl -fsSL https://raw.githubusercontent.com/Lencen/dsh-agent-lines/main/install.sh -o install.sh &amp;&amp; bash install.sh</div>
55
+ <h1>让 AI 负责人<br><span>替你带一支 AI 团队</span></h1>
56
+ <p class="lead">你只说目标。负责人会拆任务、找合适的成员、安排工作、追进度,最后把结果汇总给你。关掉窗口、重启 DSH,成员和消息都还在。</p>
55
57
  <div class="actions">
56
- <a class="btn primary" href="https://github.com/Lencen/dsh-agent-lines">GitHub 仓库</a>
57
- <a class="btn" href="https://github.com/Lencen/dsh-agent-lines/releases/tag/v0.3.9">下载 0.3.9</a>
58
+ <a class="btn primary" href="#start">怎么开始</a>
59
+ <a class="btn" href="#install">安装插件</a>
58
60
  </div>
59
61
  </div>
60
62
  <div class="shot">
61
- <img src="assets/first-use.gif" alt="首次使用:创建管理线、8 条成员线并收到 ack">
62
- <div class="caption">demo profile · 通用 label · 不含内部数据</div>
63
+ <img src="assets/first-use.gif" alt="负责人创建成员线并收到确认">
64
+ <div class="caption">演示环境 · 通用角色 · 不含内部数据</div>
63
65
  </div>
64
66
  </section>
67
+
65
68
  <section>
66
- <h2>它替你省掉什么</h2>
69
+ <h2>它能帮你做什么</h2>
67
70
  <div class="cards">
68
- <div class="card"><b>不用反复讲背景</b><p>每条成员线是一个独立的 DSH 会话,长期保留自己的上下文;新任务直接派给对应角色。</p></div>
69
- <div class="card"><b>不用自己当调度</b><p>负责人拆任务、分配、协调、处理分歧、收结果;你只需要看最后的汇总。</p></div>
70
- <div class="card"><b>不用怕关窗口</b><p>任务和消息保存在本地;重启 DSH 后还能接着做,成员状态和未处理消息都在。</p></div>
71
+ <div class="card"><b>不用反复讲背景</b><p>每个成员长期保留自己的上下文;新任务直接派给对应角色。</p></div>
72
+ <div class="card"><b>不用自己当调度</b><p>负责人拆任务、分工、协调、追进度,你只看最后的汇总。</p></div>
73
+ <div class="card"><b>不用怕关窗口</b><p>成员和消息保存在本地;重启 DSH 后还能接着做。</p></div>
71
74
  </div>
72
- <p style="color:var(--muted);font-size:14px;margin-top:14px">适合长期项目、多角色协作。</p>
73
75
  </section>
76
+
74
77
  <section>
75
- <h2>一个真实用法</h2>
78
+ <h2>适合什么团队</h2>
79
+ <p class="note">不限于开发。小说、视频、研究、运营都可以;只换角色和任务,不需要改插件。</p>
76
80
  <div class="cards">
77
- <div class="card"><b>1. 你说目标</b><p>“给报表页加 CSV 导出,带测试和一页 runbook。”</p></div>
78
- <div class="card"><b>2. 负责人组队</b><p>拆成调研、架构、UIUX、开发、测试、验收、发布、运维,按角色建线。</p></div>
79
- <div class="card"><b>3. 团队并行</b><p>成员各自做事;负责人追进度、处理接口分歧,必要时重新分配。</p></div>
80
- <div class="card"><b>4. 你收结果</b><p>负责人汇总结论、文件路径、风险和遗留问题;不用自己挨个问。</p></div>
81
+ <div class="card"><b>小说团队</b><p>总编、大纲、写手、编辑、校对、审读。</p></div>
82
+ <div class="card"><b>视频团队</b><p>负责人、策划、脚本、剪辑、封面、发布。</p></div>
83
+ <div class="card"><b>开发团队</b><p>负责人、开发、测试、验收。</p></div>
81
84
  </div>
85
+ <p class="note" style="margin-top:14px">第一次不需要自己想清楚角色。你只说“帮我建个小说创作团队,按小说创作的流程建立工作流程与机制”,负责人会先给团队结构和流程建议。</p>
82
86
  </section>
83
- <section>
84
- <h2>首次使用:让负责人自己组队</h2>
85
- <pre>使用 dsh-agent-lines。先把这个会话设为开发团队的管理线;如果还没绑定,就先完成绑定。
86
87
 
87
- 你是开发团队负责人:负责任务拆解与分配、协调沟通、跟踪进度、汇总结果、总结汇报。你只做管理和协调,不执行具体开发任务。
88
+ <section id="install">
89
+ <h2>安装:可以让 DSH 帮你装</h2>
90
+ <p>在 DSH 新会话里说:</p>
91
+ <pre>帮我检查并安装 GitHub 上 Lencen/dsh-agent-lines 插件。
92
+ 只从官方仓库或官方 npm 安装,不要从其他 fork 安装。
93
+ 安装前先告诉我准备执行什么命令,等我确认后再执行。
94
+ 安装完成后按插件要求加载;如果需要重启 DSH Web,先告诉我重启后打开哪个地址、
95
+ 怎么新开会话、第一句该说什么,并提醒当前会话会断开;等我确认后再重启。</pre>
96
+ <div class="warn">重启 DSH Web 会让当前会话断开,重启后 agent 无法再自动通知你;重启后的步骤必须写在重启前。</div>
97
+ <details>
98
+ <summary>不想让 DSH 装?手动安装命令</summary>
99
+ <pre>curl -fsSL https://raw.githubusercontent.com/Lencen/dsh-agent-lines/main/install.sh -o install.sh &amp;&amp; bash install.sh</pre>
100
+ <p class="note">安装后必须重启 DSH Web,插件才会生效。</p>
101
+ </details>
102
+ </section>
103
+
104
+ <section id="start">
105
+ <h2>怎么开始(白话)</h2>
106
+ <div class="cards">
107
+ <div class="card"><b>1. 装好并重启</b><p>按上面的方式安装;需要时重启 DSH Web。</p></div>
108
+ <div class="card"><b>2. 新开一个会话</b><p>重启后新开会话,对负责人说要建什么团队。</p></div>
109
+ <div class="card"><b>3. 等它给方案</b><p>负责人先提团队结构和流程;你确认或补充角色。</p></div>
110
+ </div>
111
+ <p class="note" style="margin-top:14px">直接说:</p>
112
+ <pre>帮我建个小说创作团队,按小说创作的流程建立工作流程与机制。
113
+ 或
114
+ 帮我建个开发团队,按开发流程建立工作流程与机制。</pre>
115
+ <p class="note">如果你有明确要求,再补一句:</p>
116
+ <pre>另外加一个校对和一个审读角色。</pre>
117
+ </section>
88
118
 
89
- 请组建开发团队。成员名字用简短英文,岗位用中文:
90
- research(调研)、architecture(架构)、uiux(UIUX)、dev(开发)、test(测试)、acceptance(验收)、release(发布)、ops(运维)。
119
+ <section>
120
+ <h2>第一次使用提示词</h2>
121
+ <p class="note">小说:</p>
122
+ <pre>帮我建个小说创作团队,按小说创作的流程建立工作流程与机制。
123
+ 先不要写正文,建好后等我发第一章任务。</pre>
124
+ <p class="note">视频:</p>
125
+ <pre>帮我建个视频创作团队,按视频创作的流程建立工作流程与机制。
126
+ 先不要开工,建好后等我发第一个选题。</pre>
127
+ <p class="note">开发:</p>
128
+ <pre>帮我建个开发团队,按开发流程建立工作流程与机制。
129
+ 先不要开工,建好后等我发第一个需求。</pre>
130
+ </section>
91
131
 
92
- 团队建好后先待命,不要自动开工,等我发第一个任务。</pre>
93
- <p style="color:var(--muted);font-size:14px">安装或更新后需要重启 DSH Web。技术细节、协议和限制见 <a href="https://github.com/Lencen/dsh-agent-lines">README / 架构文档</a>。社区插件,不是 DeepSeek 官方产品。</p>
132
+ <section>
133
+ <h2>常见问题</h2>
134
+ <div class="cards">
135
+ <div class="card"><b>会不会很贵?</b><p>按实际使用计费;负责人会先提最小可用团队,后续再扩。</p></div>
136
+ <div class="card"><b>关掉会丢吗?</b><p>不会,成员和消息保存在本地,重启后还能接着做。</p></div>
137
+ <div class="card"><b>能同时做两个团队吗?</b><p>当前一个插件实例只支持一个团队;小说和视频建议分开环境。</p></div>
138
+ </div>
94
139
  </section>
95
- <footer>dsh-agent-lines 0.3.9 · Apache-2.0 · Community plugin for DeepSeek Harness. Not an official DeepSeek product.</footer>
140
+
141
+ <footer>
142
+ <details>
143
+ <summary>给开发者:协议、限制、安装命令</summary>
144
+ <p><a href="https://github.com/Lencen/dsh-agent-lines">GitHub 仓库</a> · <a href="https://github.com/Lencen/dsh-agent-lines/releases/tag/v0.4.1">Release</a> · <a href="https://github.com/Lencen/dsh-agent-lines/blob/main/docs/PROTOCOL.md">协议</a></p>
145
+ <p class="note">社区插件,不是 DeepSeek 官方产品。技术细节以 README / 协议为准。</p>
146
+ </details>
147
+ <p style="margin-top:18px">dsh-agent-lines 0.4.1 · Apache-2.0 · Community plugin for DeepSeek Harness. Not an official DeepSeek product.</p>
148
+ </footer>
96
149
  </main>
97
150
  </div>
98
151
  </body>
@@ -50,11 +50,11 @@
50
50
  ]
51
51
  },
52
52
  "dist-tags": {
53
- "latest": "0.3.9"
53
+ "latest": "0.4.1"
54
54
  },
55
55
  "versions": [
56
56
  {
57
- "version": "0.3.9",
57
+ "version": "0.4.1",
58
58
  "publishTimestamp": 1793771000,
59
59
  "source": {
60
60
  "type": "npm"
@@ -50,11 +50,11 @@
50
50
  ]
51
51
  },
52
52
  "dist-tags": {
53
- "latest": "0.3.9"
53
+ "latest": "0.4.1"
54
54
  },
55
55
  "versions": [
56
56
  {
57
- "version": "0.3.9",
57
+ "version": "0.4.1",
58
58
  "publishTimestamp": 1793771000,
59
59
  "source": {
60
60
  "type": "npm"
package/lib/index.js CHANGED
@@ -322,6 +322,14 @@ export async function apply(ctx, rawConfig) {
322
322
  const updated = store.rebind({
323
323
  agentId: config.agentId, line: label, sessionId, actor, reason, migrate, locked: true,
324
324
  })
325
+ // F1 fix: rebind deliberately clears the old lease; the new line must acquire one
326
+ // before startLoop, otherwise heartbeat throws LINE_LEASE_LOST and messages stay queued.
327
+ store.acquire({
328
+ agentId: config.agentId, line: label, sessionId,
329
+ instanceId: config.instanceId, ttlMs: config.leaseTtlMs,
330
+ role: (oldDef && oldDef.role) || row.role || null,
331
+ createdBy: row.created_by || (oldDef && oldDef.createdBy) || null,
332
+ })
325
333
  const def = {
326
334
  agentId: config.agentId, label, sessionId, lineId: lineId(config.agentId, label),
327
335
  role: (oldDef && oldDef.role) || row.role || null,
package/lib/store.js CHANGED
@@ -62,6 +62,11 @@ CREATE TABLE IF NOT EXISTS messages (
62
62
  root_message_id TEXT,
63
63
  hop INTEGER NOT NULL DEFAULT 0,
64
64
  no_reply INTEGER NOT NULL DEFAULT 0,
65
+ kind TEXT NOT NULL DEFAULT 'info',
66
+ cc_manager INTEGER NOT NULL DEFAULT 0,
67
+ approval_state TEXT,
68
+ approved_by TEXT,
69
+ approved_at INTEGER,
65
70
  state TEXT NOT NULL,
66
71
  claimed_by TEXT,
67
72
  claimed_at INTEGER,
@@ -115,7 +120,7 @@ CREATE TABLE IF NOT EXISTS line_events (
115
120
  );
116
121
  CREATE INDEX IF NOT EXISTS line_events_line ON line_events(line_id, created_at);
117
122
 
118
- PRAGMA user_version = 7;
123
+ PRAGMA user_version = 8;
119
124
  `
120
125
 
121
126
  export function defaultDbPath() {
@@ -168,9 +173,14 @@ export class LineStore {
168
173
  if (!messageColumns.includes('root_message_id')) this.db.exec('ALTER TABLE messages ADD COLUMN root_message_id TEXT')
169
174
  if (!messageColumns.includes('hop')) this.db.exec('ALTER TABLE messages ADD COLUMN hop INTEGER NOT NULL DEFAULT 0')
170
175
  if (!messageColumns.includes('no_reply')) this.db.exec('ALTER TABLE messages ADD COLUMN no_reply INTEGER NOT NULL DEFAULT 0')
176
+ if (!messageColumns.includes('kind')) this.db.exec("ALTER TABLE messages ADD COLUMN kind TEXT NOT NULL DEFAULT 'info'")
177
+ if (!messageColumns.includes('cc_manager')) this.db.exec('ALTER TABLE messages ADD COLUMN cc_manager INTEGER NOT NULL DEFAULT 0')
178
+ if (!messageColumns.includes('approval_state')) this.db.exec('ALTER TABLE messages ADD COLUMN approval_state TEXT')
179
+ if (!messageColumns.includes('approved_by')) this.db.exec('ALTER TABLE messages ADD COLUMN approved_by TEXT')
180
+ if (!messageColumns.includes('approved_at')) this.db.exec('ALTER TABLE messages ADD COLUMN approved_at INTEGER')
171
181
  this.db.exec('CREATE TABLE IF NOT EXISTS line_events (id TEXT PRIMARY KEY, line_id TEXT NOT NULL, action TEXT NOT NULL, old_session_id TEXT, new_session_id TEXT, actor TEXT, reason TEXT, detail TEXT, created_at INTEGER NOT NULL)')
172
182
  this.db.exec('CREATE INDEX IF NOT EXISTS line_events_line ON line_events(line_id, created_at)')
173
- this.db.exec('PRAGMA user_version = 7')
183
+ this.db.exec('PRAGMA user_version = 8')
174
184
  this.prepared = new Map()
175
185
  this.closed = false
176
186
  this.fresh = fresh
@@ -265,7 +275,10 @@ export class LineStore {
265
275
  }
266
276
 
267
277
  /** Enqueue a message from one registered line to another. */
268
- send({ agentId, fromLine, to, text, idempotencyKey, replyTo, noReply = false, maxReplyDepth = 8, maxMessageBytes = 65536, ttlMs, now = Date.now() }) {
278
+ send({ agentId, fromLine, to, text, idempotencyKey, replyTo, noReply = false, kind = 'info', ccManager = false, requireApproval = false, maxReplyDepth = 8, maxMessageBytes = 65536, ttlMs, now = Date.now() }) {
279
+ if (!['info', 'request', 'task', 'alert'].includes(String(kind))) {
280
+ throw new LineError('LINE_INVALID_KIND', `kind must be info/request/task/alert: ${kind}`)
281
+ }
269
282
  const from = this.#stmt('SELECT * FROM lines WHERE agent_id = ? AND label = ?').get(agentId, fromLine)
270
283
  if (from === undefined) throw new LineError('LINE_NOT_FOUND', `Sender line is not registered: ${agentId}#${fromLine}`)
271
284
  const target = this.resolveTarget(agentId, to)
@@ -290,9 +303,9 @@ export class LineStore {
290
303
  const expiresAt = ttlMs === undefined || ttlMs === null || ttlMs <= 0 ? null : now + ttlMs
291
304
  this.#stmt(`INSERT INTO messages(
292
305
  id, idempotency_key, from_agent, from_line, from_session, to_agent, to_line, body,
293
- created_at, expires_at, reply_to, root_message_id, hop, no_reply, state, attempts, next_attempt_at
294
- ) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'queued', 0, ?)`)
295
- .run(id, key, from.agent_id, from.label, from.session_id, target.agentId, target.line, body, now, expiresAt, replyTo ?? null, rootMessageId, hop, noReply === true ? 1 : 0, now)
306
+ created_at, expires_at, reply_to, root_message_id, hop, no_reply, kind, cc_manager, approval_state, state, attempts, next_attempt_at
307
+ ) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`)
308
+ .run(id, key, from.agent_id, from.label, from.session_id, target.agentId, target.line, body, now, expiresAt, replyTo ?? null, rootMessageId, hop, noReply === true ? 1 : 0, String(kind), ccManager === true ? 1 : 0, requireApproval ? 'pending' : null, requireApproval ? 'pending_approval' : 'queued', now)
296
309
  return { ...this.#stmt('SELECT * FROM messages WHERE id = ?').get(id), deduplicated: false }
297
310
  }
298
311
 
@@ -542,12 +555,28 @@ export class LineStore {
542
555
  return this.#stmt('SELECT * FROM line_events WHERE line_id = ? AND action = ? ORDER BY created_at DESC LIMIT 1').get(id, action)
543
556
  }
544
557
 
558
+ /** Manager decision on a pending task message: approve queues it, reject closes it. */
559
+ approveMessage({ messageId, decision = 'approve', actor = null, now = Date.now() }) {
560
+ if (!['approve', 'reject'].includes(String(decision))) throw new LineError('LINE_INVALID_ARGUMENT', 'decision must be approve or reject')
561
+ return this.#tx(() => {
562
+ const row = this.#stmt('SELECT * FROM messages WHERE id = ?').get(String(messageId))
563
+ if (row === undefined) throw new LineError('LINE_MESSAGE_NOT_FOUND', `No message: ${messageId}`)
564
+ if (row.state !== 'pending_approval') throw new LineError('LINE_NOT_PENDING_APPROVAL', `Message ${messageId} is ${row.state}`)
565
+ const approved = String(decision) === 'approve'
566
+ this.#stmt(`UPDATE messages SET state = ?, approval_state = ?, approved_by = ?, approved_at = ?
567
+ WHERE id = ? AND state = 'pending_approval'`).run(
568
+ approved ? 'queued' : 'rejected', approved ? 'approved' : 'rejected', actor, now, row.id,
569
+ )
570
+ return { ...this.#stmt('SELECT * FROM messages WHERE id = ?').get(row.id), approved }
571
+ })
572
+ }
573
+
545
574
  /** Read-only cross-line feed for the manager; no state changes. */
546
575
  observe({ agentId, line = null, since = 0, limit = 100 } = {}) {
547
576
  const n = Math.min(200, Math.max(1, Number(limit) || 100))
548
577
  const from = Math.max(0, Number(since) || 0)
549
578
  const params = [agentId, agentId]
550
- let sql = `SELECT id, from_agent, from_line, to_agent, to_line, body, state, reply_to, root_message_id,
579
+ let sql = `SELECT id, from_agent, from_line, to_agent, to_line, body, state, kind, cc_manager, approval_state, reply_to, root_message_id,
551
580
  hop, no_reply, created_at, delivered_at, acked_at
552
581
  FROM messages WHERE (from_agent = ? OR to_agent = ?) AND from_line != to_line AND created_at > ?`
553
582
  params.push(from)
@@ -565,6 +594,9 @@ export class LineStore {
565
594
  from: row.from_agent + '#' + row.from_line,
566
595
  to: row.to_agent + '#' + row.to_line,
567
596
  state: row.state,
597
+ kind: row.kind,
598
+ ccManager: Number(row.cc_manager) === 1,
599
+ approvalState: row.approval_state,
568
600
  noReply: Number(row.no_reply) === 1,
569
601
  replyTo: row.reply_to,
570
602
  hop: row.hop,
package/lib/tools.js CHANGED
@@ -20,11 +20,12 @@ function currentLine(exec, lineDefs) {
20
20
  return def
21
21
  }
22
22
 
23
- function windowKey({ from, to, text, replyTo, windowMs, now = Date.now() }) {
23
+ function windowKey({ from, to, text, replyTo, kind = 'info', ccManager = false, windowMs, now = Date.now() }) {
24
24
  if (windowMs <= 0) return undefined
25
25
  const bucket = Math.floor(now / windowMs)
26
26
  const digest = createHash('sha256')
27
27
  .update(from).update('\0').update(to).update('\0').update(text).update('\0').update(String(replyTo ?? ''))
28
+ .update('\0').update(String(kind)).update('\0').update(ccManager ? 'cc' : '')
28
29
  .digest('hex')
29
30
  return `window:${bucket}:${digest}`
30
31
  }
@@ -208,6 +209,8 @@ export function registerToolsForAgent(agent, def, { config, store, lineDefs, cre
208
209
  idempotencyKey: { type: 'string', description: 'Optional stable key for retry-safe sends' },
209
210
  replyTo: { type: 'string', description: 'Optional message id this message replies to; blocked if that message is noReply and checked against maxReplyDepth' },
210
211
  noReply: { type: 'boolean', description: 'Mark this message as a notification; replies are rejected' },
212
+ kind: { type: 'string', enum: ['info', 'request', 'task', 'alert'], description: 'Message intent; non-manager task messages wait for line_manager_ack approval' },
213
+ ccManager: { type: 'boolean', description: 'Mark this message as manager-visible; defaults true for task/alert' },
211
214
  forceDistinct: { type: 'boolean', description: 'Allow two identical messages inside dedupeWindowMs' },
212
215
  ttlMs: { type: 'number', description: 'Optional time-to-live in milliseconds' },
213
216
  },
@@ -222,9 +225,14 @@ export function registerToolsForAgent(agent, def, { config, store, lineDefs, cre
222
225
  if (target.lineId === def.lineId && config.allowSelfSend !== true) {
223
226
  throw new LineError('LINE_SELF_SEND_FORBIDDEN', `Self-send is disabled; set allowSelfSend to enable: ${def.lineId}`)
224
227
  }
228
+ const kind = args.kind === undefined ? 'info' : String(args.kind)
229
+ if (!['info', 'request', 'task', 'alert'].includes(kind)) throw new LineError('LINE_INVALID_KIND', `kind must be info/request/task/alert: ${kind}`)
230
+ const isManager = def.label === config.createFrom
231
+ const ccManager = args.ccManager === undefined ? (kind === 'task' || kind === 'alert') : args.ccManager === true
232
+ const requireApproval = kind === 'task' && !isManager
225
233
  const idempotencyKey = args.idempotencyKey ?? (args.forceDistinct === true
226
234
  ? undefined
227
- : windowKey({ from: def.lineId, to: target.lineId, text, replyTo: args.replyTo, windowMs: config.dedupeWindowMs }))
235
+ : windowKey({ from: def.lineId, to: target.lineId, text, replyTo: args.replyTo, kind, ccManager, windowMs: config.dedupeWindowMs }))
228
236
  const row = store.send({
229
237
  agentId: def.agentId,
230
238
  fromLine: def.label,
@@ -233,6 +241,7 @@ export function registerToolsForAgent(agent, def, { config, store, lineDefs, cre
233
241
  idempotencyKey,
234
242
  replyTo: args.replyTo,
235
243
  noReply: args.noReply === true,
244
+ kind, ccManager, requireApproval,
236
245
  maxReplyDepth: config.maxReplyDepth,
237
246
  ttlMs: args.ttlMs,
238
247
  maxMessageBytes: config.maxMessageBytes,
@@ -244,6 +253,10 @@ export function registerToolsForAgent(agent, def, { config, store, lineDefs, cre
244
253
  hop: row.hop,
245
254
  rootMessageId: row.root_message_id,
246
255
  noReply: Number(row.no_reply) === 1,
256
+ kind: row.kind,
257
+ ccManager: Number(row.cc_manager) === 1,
258
+ approvalState: row.approval_state ?? null,
259
+ requiresApproval: row.state === 'pending_approval',
247
260
  deduplicated: row.deduplicated === true,
248
261
  }
249
262
  },
@@ -398,6 +411,26 @@ export function registerToolsForAgent(agent, def, { config, store, lineDefs, cre
398
411
  },
399
412
  }))
400
413
 
414
+ disposers.push(agent.ctx.tools.register({
415
+ name: 'line_manager_ack',
416
+ description: 'Manager-only decision for a pending task message: approve queues it for delivery, reject closes it. Use line_observe to find pending approvals.',
417
+ parameters: {
418
+ type: 'object', additionalProperties: false,
419
+ properties: {
420
+ messageId: { type: 'string', description: 'Pending task message id' },
421
+ decision: { type: 'string', enum: ['approve', 'reject'], description: 'Approve queues the message; reject closes it' },
422
+ },
423
+ required: ['messageId', 'decision'],
424
+ },
425
+ output: OUTPUT,
426
+ execute: async (args, exec) => {
427
+ const caller = currentLine(exec, lineDefs)
428
+ if (caller.label !== config.createFrom) throw new LineError('LINE_CREATE_FORBIDDEN', `Only ${config.createFrom} may approve tasks`, { caller: caller.label })
429
+ const row = store.approveMessage({ messageId: String(args.messageId), decision: String(args.decision), actor: caller.label })
430
+ return { messageId: row.id, state: row.state, approvalState: row.approval_state, approved: row.approved }
431
+ },
432
+ }))
433
+
401
434
  disposers.push(agent.ctx.tools.register({
402
435
  name: 'line_observe',
403
436
  description: 'Manager-only read-only feed of recent cross-line messages (from/to, state, noReply, summary). Does not inject prompts or change any message state. Use it to watch team traffic without turning every message into a manager turn.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-agent-lines",
3
- "version": "0.3.9",
3
+ "version": "0.4.1",
4
4
  "description": "DSH Agent 的持久、可寻址消息线(社区插件)。",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",