dsh-agent-lines 0.3.7 → 0.3.9

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.3.9 (2026-11-09)
6
+
7
+ - Added read-only line_observe: a manager feed of cross-line messages without prompts or state changes.
8
+ - Added CLI observe --json.
9
+ - README now documents version-by-version business scenarios; fixed line_handoff version (0.3.7).
10
+ - No schema change; line_send semantics unchanged.
11
+
12
+ ## 0.3.8 (2026-11-09)
13
+
14
+ - line_status / line_list expose activity: turns/steps/decodeTokens/level/lastActivityAt.
15
+ - activityLevel is an activity proxy, not a context-window percentage; DSH does not expose context usage.
16
+ - Remains compatible with the 0.3.6/0.3.7 lifecycle/title work.
17
+ - Tests 35/35 pass.
18
+
5
19
  ## 0.3.7 (2026-11-09)
6
20
 
7
21
  - Added line_handoff: structured handoff snapshots (JSON/Markdown), no model call.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  [English](CHANGELOG.en.md) | 中文
4
4
 
5
+ ## 0.3.9 (2026-11-09)
6
+
7
+ - 新增只读 line_observe:管理线查看跨线消息 feed,不注入 prompt、不改消息状态。
8
+ - CLI 新增 observe --json。
9
+ - README 补“版本能力与业务场景”,修正 line_handoff 的版本标注(0.3.7)。
10
+ - 无 schema 变化,line_send 语义不变。
11
+
12
+ ## 0.3.8 (2026-11-09)
13
+
14
+ - line_status / line_list 增加 activity:turns/steps/decodeTokens/level/lastActivityAt。
15
+ - 明确 activityLevel 只是活跃度代理,不是 contextUsage 百分比;DSH 未暴露上下文窗口。
16
+ - 生命周期/标题/在飞版本保持 0.3.6/0.3.7 兼容。
17
+ - 测试 35/35 通过。
18
+
5
19
  ## 0.3.7 (2026-11-09)
6
20
 
7
21
  - 新增 line_handoff:结构化交接快照(JSON/Markdown),不调用模型。
package/README.en.md CHANGED
@@ -23,6 +23,32 @@ 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
+ ## What each version adds (business scenarios)
27
+
28
+ ### 0.3.6: replace a role when context is full
29
+
30
+ Scenario: a writer line has worked long enough that its context is saturated and quality drops.
31
+ Use: the manager calls line_create({ label: "writer", replace: true, force: true, reason: "context full" }).
32
+ Effect: the same label moves to a clean Session; the old Session is retired but kept; queued/claimed messages migrate; delivered/acked are never re-delivered; no manual routing.
33
+
34
+ ### 0.3.7: hand off before replacing
35
+
36
+ Scenario: the new writer does not know what the old line finished, what is next, or which traps to avoid.
37
+ Use: line_handoff({ line: "writer", format: "md" }).
38
+ Effect: a structured snapshot (progress, backlog, recent messages, lifecycle) without a model call; the new line reads it first.
39
+
40
+ ### 0.3.8: know when to replace
41
+
42
+ Scenario: the manager wants to see which line is close to saturation or has gone quiet.
43
+ Use: line_status / line_list expose activity.level, turns, steps, lastActivityAt.
44
+ Effect: a low/medium/high activity proxy, not a fake "78% context used"; exact context usage waits for DSH.
45
+
46
+ ### 0.3.9: the manager can see cross-line traffic
47
+
48
+ Scenario: lines message each other directly; the manager needs visibility without every message becoming a manager turn.
49
+ Use: line_observe({ line: "writer", limit: 20 }) or CLI observe --json.
50
+ Effect: a read-only feed with from/to/state/summary; no prompt injection and no state change; line_send semantics unchanged.
51
+
26
52
  ## Install
27
53
 
28
54
  One-command install (recommended; no need to find a profile):
@@ -36,7 +62,7 @@ The script picks the DSH Web profile, installs the plugin, and verifies agent-li
36
62
 
37
63
  Offline or when npm is unavailable, use the local tarball:
38
64
 
39
- bash install.sh ./dsh-agent-lines-0.3.7.tgz
65
+ bash install.sh ./dsh-agent-lines-0.3.9.tgz
40
66
 
41
67
  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.
42
68
 
@@ -134,11 +160,12 @@ Tools:
134
160
 
135
161
  | Tool | Purpose |
136
162
  |---|---|
137
- | `line_status` | Read-only: current Session binding, manager Session live/persisted, lines, repair command; visible even when unbound |
163
+ | `line_status` | Read-only: current Session binding, manager Session live/persisted, lines, repair command; visible even when unbound; includes activity (not contextUsage) |
138
164
  | `line_claim_manager` | Bind this live root Session as manager; requires `allowClaim: true` and `confirm: "bind-manager"`; global, no shell needed |
139
165
  | `line_list` | List lines, status, unread counts, current line and role |
140
166
  | `line_create` | Manager only: create a line; same label is idempotent; replace:true swaps the label to a fresh Session |
141
167
  | `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 |
142
169
  | `line_retire` | Manager only: soft-retire; reject new sends, keep Session and history |
143
170
  | `line_rebind` | Manager only: atomically switch a line to a new live Session, optionally migrating backlog |
144
171
  | `line_rename` | Manager only: rename the sidebar title and persist it in lines.db |
@@ -171,6 +198,7 @@ dsh-agent-lines list
171
198
  dsh-agent-lines send --from frontend-line --to backend-line --text "hi"
172
199
  dsh-agent-lines inbox --line backend-line
173
200
  dsh-agent-lines ack --line backend-line --id msg_...
201
+ dsh-agent-lines --json observe --line writer --limit 20
174
202
  dsh-agent-lines claim --agent agent-demo --line manager --session "$DSH_SESSION_ID"
175
203
  dsh-agent-lines doctor
176
204
  ```
package/README.md CHANGED
@@ -23,6 +23,32 @@ DSH 的 super agent 平替插件。
23
23
  - 你希望一个负责人 agent 自己拆任务、建团队、追进度、收结果,最后向你汇报。
24
24
 
25
25
 
26
+ ## 版本能力与业务场景
27
+
28
+ ### 0.3.6:上下文满了,角色可以换人
29
+
30
+ 场景:写手线连续工作很久,上下文配额耗尽;继续写容易出现前后矛盾。
31
+ 用法:管理线调用 line_create({ label: "writer", replace: true, force: true, reason: "context full" })。
32
+ 效果:同一个 label 切到干净 Session,旧 Session 只 retired、不删除;queued/claimed 消息迁移,delivered/acked 不重投;管理线不需要手工改路由。
33
+
34
+ ### 0.3.7:换人之前先交接
35
+
36
+ 场景:新写手不知道旧线做到哪、下一步是什么、哪些坑不能踩。
37
+ 用法:line_handoff({ line: "writer", format: "md" })。
38
+ 效果:生成结构化交接单(进度、积压、最近消息、生命周期),不调用模型;新线先读交接单再继续。
39
+
40
+ ### 0.3.8:什么时候该换线
41
+
42
+ 场景:管理线想知道哪条线快满了、哪条线最近没动静。
43
+ 用法:line_status / line_list 看 activity.level、turns、steps、lastActivityAt。
44
+ 效果:给出低/中/高活跃度,不伪造“上下文使用率 78%”;真实窗口等 DSH 上游提供。
45
+
46
+ ### 0.3.9:管理线看得到跨线消息
47
+
48
+ 场景:成员线之间直接同步,管理线需要知道发生了什么,但不希望每条消息都打断自己。
49
+ 用法:line_observe({ line: "writer", limit: 20 }) 或 CLI observe --json。
50
+ 效果:只读 feed,显示 from/to/state/摘要;不注入 prompt、不改消息状态;line_send 语义不变。
51
+
26
52
  ## 安装
27
53
 
28
54
  一条命令安装(推荐,不需要自己找 profile):
@@ -36,7 +62,7 @@ DSH 的 super agent 平替插件。
36
62
 
37
63
  离线或 npm 不可用时,用本地 tarball:
38
64
 
39
- bash install.sh ./dsh-agent-lines-0.3.7.tgz
65
+ bash install.sh ./dsh-agent-lines-0.3.9.tgz
40
66
 
41
67
  安装并重启后,不写配置也可以直接使用;需要固定管理线/worker 时,再把下面“管理线配置”粘贴到脚本打印的配置文件。
42
68
 
@@ -124,6 +150,11 @@ line_create({ label: "ops", role: "运维" })
124
150
 
125
151
  这只是“用提示词组织团队”的方式,不是插件内置的组织架构;怎么拆任务由负责人模型决定。技术细节(消息、状态、工具)在下面的“使用”和“投递语义”。
126
152
 
153
+ ## 上下文活跃度
154
+
155
+ line_status / line_list 返回 activity:turns/steps/decodeTokens/level/lastActivityAt。
156
+ level 是活跃度代理(low/medium/high),不是上下文使用率百分比;DSH 当前不暴露上下文窗口。
157
+
127
158
  ## 使用
128
159
 
129
160
  管理线可以完全由对话驱动:
@@ -134,11 +165,12 @@ line_create({ label: "ops", role: "运维" })
134
165
 
135
166
  | 工具 | 作用 |
136
167
  |---|---|
137
- | `line_status` | 只读:当前会话绑定、manager Session 是否存在/live、线列表、修复命令;未绑定也可见 |
168
+ | `line_status` | 只读:当前会话绑定、manager Session 是否存在/live、线列表、修复命令;未绑定也可见;含 activity 活跃度(不是 contextUsage) |
138
169
  | `line_claim_manager` | 把当前 live 根 Session 绑成 manager;需要 `allowClaim: true` 和 `confirm: "bind-manager"`;全局工具,不需要 shell |
139
170
  | `line_list` | 列出线、状态、未读数、当前线和 role |
140
171
  | `line_create` | 仅管理线:创建新线;同 label 幂等;传 replace:true 可给同 label 换新 Session |
141
172
  | `line_handoff` | 仅管理线:生成结构化交接单(JSON/Markdown),不调模型 |
173
+ | `line_observe` | 仅管理线:只读跨线消息 feed;不注入 prompt、不改状态 |
142
174
  | `line_retire` | 仅管理线:软下线;拒绝新发送,保留 Session/历史 |
143
175
  | `line_rebind` | 仅管理线:把线原子切到新的 live Session,可迁移积压 |
144
176
  | `line_rename` | 仅管理线:改侧栏标题,持久化到 lines.db |
@@ -171,6 +203,7 @@ dsh-agent-lines list
171
203
  dsh-agent-lines send --from frontend-line --to backend-line --text "hi"
172
204
  dsh-agent-lines inbox --line backend-line
173
205
  dsh-agent-lines ack --line backend-line --id msg_...
206
+ dsh-agent-lines --json observe --line writer --limit 20
174
207
  dsh-agent-lines claim --agent agent-demo --line manager --session "$DSH_SESSION_ID"
175
208
  dsh-agent-lines doctor
176
209
  ```
@@ -9,6 +9,7 @@ import { LineError, lineId } from '../lib/protocol.js'
9
9
 
10
10
  function usage() {
11
11
  console.error(`Usage:
12
+ dsh-agent-lines [--db <path>] [--json] observe [--line <line>] [--since <epochMs>] [--limit N] [--agent <id>]
12
13
  dsh-agent-lines [--db <path>] [--json] list
13
14
  dsh-agent-lines [--db <path>] [--json] send --from <line> --to <line|agent#line> --text <text> [--agent <id>] [--key <key>] [--reply-to <id>]
14
15
  dsh-agent-lines [--db <path>] [--json] inbox --line <line> [--agent <id>] [--limit N] [--all]
@@ -62,6 +63,16 @@ function main() {
62
63
  }
63
64
  const store = new LineStore(opts.db)
64
65
  try {
66
+ if (command === 'observe') {
67
+ const result = store.observe({
68
+ agentId: String(opts.agent ?? 'agent-deepseek'),
69
+ line: opts.line === undefined ? null : String(opts.line),
70
+ since: Number(opts.since ?? 0),
71
+ limit: Number(opts.limit ?? 100),
72
+ })
73
+ print({ db: store.dbPath, ...result }, opts.json)
74
+ return 0
75
+ }
65
76
  if (command === 'list') {
66
77
  print({ db: store.dbPath, lines: store.listLines(), counts: store.counts() }, opts.json)
67
78
  return 0
@@ -152,4 +163,4 @@ function main() {
152
163
  }
153
164
  }
154
165
 
155
- process.exitCode = main()
166
+ process.exitCode = main()
@@ -103,6 +103,7 @@ The new line is an ordinary DSH Session in the same DSH process; no OS process,
103
103
  line_retire({ line, reason? })
104
104
  line_rebind({ line, sessionId, migrate?: "queued" | "none", reason? })
105
105
  line_create({ label, replace: true, force?, migrate?, title?, reason? })
106
+ line_observe({ line?, since?, limit? })
106
107
  line_handoff({ line, format?: "md" | "json", limit?: number })
107
108
  line_rename({ line, title })
108
109
 
@@ -112,6 +113,7 @@ The new line is an ordinary DSH Session in the same DSH process; no OS process,
112
113
  - migrate=none: returns LINE_BACKLOG_NOT_EMPTY if the backlog is non-empty.
113
114
  - replace: creates a fresh Session for the same label and rebinds; an active line requires force + reason.
114
115
  - line_events records title/retire/rebind audit; titles survive restarts.
116
+ - line_observe is a manager-only read-only cross-line feed; it does not inject prompts or change state.
115
117
 
116
118
  ## Echo and reply depth
117
119
 
package/docs/PROTOCOL.md CHANGED
@@ -95,6 +95,7 @@ line_move_workspace({ line, workspace }) 把已有线 Session 挂到已有工作
95
95
  line_retire({ line, reason? })
96
96
  line_rebind({ line, sessionId, migrate?: "queued" | "none", reason? })
97
97
  line_create({ label, replace: true, force?, migrate?, title?, reason? })
98
+ line_observe({ line?, since?, limit? })
98
99
  line_handoff({ line, format?: "md" | "json", limit?: number })
99
100
  line_rename({ line, title })
100
101
 
@@ -104,6 +105,7 @@ line_move_workspace({ line, workspace }) 把已有线 Session 挂到已有工作
104
105
  - migrate=none:backlog 非空时返回 LINE_BACKLOG_NOT_EMPTY。
105
106
  - replace:同 label 创建新 Session 并 rebind;active 线需 force + reason。
106
107
  - line_events 记录 title/retire/rebind 审计;title 持久化,重启保留。
108
+ - line_observe 仅管理线、只读跨线 feed;不注入 prompt、不改消息状态。
107
109
 
108
110
  ## 回声与回复深度
109
111
 
@@ -43,7 +43,7 @@ footer{padding:34px 0 48px;color:var(--muted);font-size:12px}
43
43
  <div class="wrap">
44
44
  <header>
45
45
  <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.7">Release</a></nav>
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>
47
47
  </header>
48
48
  <main>
49
49
  <section class="hero" style="border:0;padding-bottom:22px">
@@ -54,7 +54,7 @@ footer{padding:34px 0 48px;color:var(--muted);font-size:12px}
54
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
55
  <div class="actions">
56
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.7">Download 0.3.7</a>
57
+ <a class="btn" href="https://github.com/Lencen/dsh-agent-lines/releases/tag/v0.3.9">Download 0.3.9</a>
58
58
  </div>
59
59
  </div>
60
60
  <div class="shot">
@@ -92,7 +92,7 @@ research (调研), architecture (架构), uiux (UIUX), dev (开发), test (测
92
92
  After the team is ready, wait for my first task. Do not start by yourself.</pre>
93
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>
94
94
  </section>
95
- <footer>dsh-agent-lines 0.3.7 · Apache-2.0 · Community plugin for DeepSeek Harness. Not an official DeepSeek product.</footer>
95
+ <footer>dsh-agent-lines 0.3.9 · Apache-2.0 · Community plugin for DeepSeek Harness. Not an official DeepSeek product.</footer>
96
96
  </main>
97
97
  </div>
98
98
  </body>
package/docs/index.html CHANGED
@@ -43,7 +43,7 @@ footer{padding:34px 0 48px;color:var(--muted);font-size:12px}
43
43
  <div class="wrap">
44
44
  <header>
45
45
  <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.7">Release</a></nav>
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>
47
47
  </header>
48
48
  <main>
49
49
  <section class="hero" style="border:0;padding-bottom:22px">
@@ -54,7 +54,7 @@ footer{padding:34px 0 48px;color:var(--muted);font-size:12px}
54
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
55
  <div class="actions">
56
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.7">下载 0.3.7</a>
57
+ <a class="btn" href="https://github.com/Lencen/dsh-agent-lines/releases/tag/v0.3.9">下载 0.3.9</a>
58
58
  </div>
59
59
  </div>
60
60
  <div class="shot">
@@ -92,7 +92,7 @@ research(调研)、architecture(架构)、uiux(UIUX)、dev(开发
92
92
  团队建好后先待命,不要自动开工,等我发第一个任务。</pre>
93
93
  <p style="color:var(--muted);font-size:14px">安装或更新后需要重启 DSH Web。技术细节、协议和限制见 <a href="https://github.com/Lencen/dsh-agent-lines">README / 架构文档</a>。社区插件,不是 DeepSeek 官方产品。</p>
94
94
  </section>
95
- <footer>dsh-agent-lines 0.3.7 · Apache-2.0 · Community plugin for DeepSeek Harness. Not an official DeepSeek product.</footer>
95
+ <footer>dsh-agent-lines 0.3.9 · Apache-2.0 · Community plugin for DeepSeek Harness. Not an official DeepSeek product.</footer>
96
96
  </main>
97
97
  </div>
98
98
  </body>
@@ -50,11 +50,11 @@
50
50
  ]
51
51
  },
52
52
  "dist-tags": {
53
- "latest": "0.3.7"
53
+ "latest": "0.3.9"
54
54
  },
55
55
  "versions": [
56
56
  {
57
- "version": "0.3.7",
57
+ "version": "0.3.9",
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.7"
53
+ "latest": "0.3.9"
54
54
  },
55
55
  "versions": [
56
56
  {
57
- "version": "0.3.7",
57
+ "version": "0.3.9",
58
58
  "publishTimestamp": 1793771000,
59
59
  "source": {
60
60
  "type": "npm"
@@ -0,0 +1,19 @@
1
+ /** Pure activity summarizer. Never claims a context-window percentage. */
2
+ export function activityLevel({ steps = 0, turns = 0 } = {}) {
3
+ const n = Number(steps) || 0
4
+ if (n >= 40) return 'high'
5
+ if (n >= 15) return 'medium'
6
+ return 'low'
7
+ }
8
+
9
+ export function summarizeActivity(stats, lastActivityAt = null) {
10
+ if (stats === undefined || stats === null) return null
11
+ const turns = Number(stats.turns) || 0
12
+ const steps = Number(stats.steps) || 0
13
+ return {
14
+ turns, steps,
15
+ decodeTokens: Number(stats.decodeTokens) || 0,
16
+ level: activityLevel({ steps, turns }),
17
+ lastActivityAt: lastActivityAt === null ? null : Number(lastActivityAt),
18
+ }
19
+ }
package/lib/store.js CHANGED
@@ -542,6 +542,38 @@ export class LineStore {
542
542
  return this.#stmt('SELECT * FROM line_events WHERE line_id = ? AND action = ? ORDER BY created_at DESC LIMIT 1').get(id, action)
543
543
  }
544
544
 
545
+ /** Read-only cross-line feed for the manager; no state changes. */
546
+ observe({ agentId, line = null, since = 0, limit = 100 } = {}) {
547
+ const n = Math.min(200, Math.max(1, Number(limit) || 100))
548
+ const from = Math.max(0, Number(since) || 0)
549
+ const params = [agentId, agentId]
550
+ let sql = `SELECT id, from_agent, from_line, to_agent, to_line, body, state, reply_to, root_message_id,
551
+ hop, no_reply, created_at, delivered_at, acked_at
552
+ FROM messages WHERE (from_agent = ? OR to_agent = ?) AND from_line != to_line AND created_at > ?`
553
+ params.push(from)
554
+ if (line !== null && String(line).trim() !== '') {
555
+ sql += ' AND (from_line = ? OR to_line = ?)'
556
+ params.push(String(line), String(line))
557
+ }
558
+ sql += ' ORDER BY created_at DESC LIMIT ?'
559
+ params.push(n)
560
+ const rows = this.#stmt(sql).all(...params)
561
+ return {
562
+ agentId, line: line === null ? null : String(line), since: from, count: rows.length,
563
+ messages: rows.map(row => ({
564
+ id: row.id,
565
+ from: row.from_agent + '#' + row.from_line,
566
+ to: row.to_agent + '#' + row.to_line,
567
+ state: row.state,
568
+ noReply: Number(row.no_reply) === 1,
569
+ replyTo: row.reply_to,
570
+ hop: row.hop,
571
+ createdAt: row.created_at,
572
+ summary: String(row.body || '').replace(/\s+/g, ' ').slice(0, 240),
573
+ })),
574
+ }
575
+ }
576
+
545
577
  /** Structured handoff snapshot: counts + recent messages + lifecycle events. */
546
578
  handoff({ agentId, line, limit = 20, now = Date.now() }) {
547
579
  const row = this.getLine(agentId, line, now)
package/lib/tools.js CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { createHash } from 'node:crypto'
7
7
  import { LineError, assertMessageSize, lineId, parseTarget } from './protocol.js'
8
+ import { summarizeActivity } from './activity.js'
8
9
 
9
10
  const OUTPUT = {
10
11
  schema: { type: 'object', additionalProperties: true },
@@ -48,6 +49,16 @@ function materializationOf(ctx, sessionId) {
48
49
  } catch { return null }
49
50
  }
50
51
 
52
+ function activityOf(ctx, sessionId) {
53
+ const projections = ctx.get?.('sessionProjections')
54
+ const agent = ctx.agents.get(sessionId)
55
+ if (projections?.snapshot === undefined || agent === undefined) return null
56
+ try {
57
+ const snapshot = projections.snapshot(agent.session, ['sessionStats'])
58
+ return summarizeActivity(snapshot?.values?.sessionStats, null)
59
+ } catch { return null }
60
+ }
61
+
51
62
  function currentView(def) {
52
63
  return def === undefined ? null : {
53
64
  agentId: def.agentId, line: def.label, sessionId: def.sessionId, lineId: def.lineId, role: def.role,
@@ -106,6 +117,7 @@ export function registerStatusTool(ctx, { config, store, lineDefs }) {
106
117
  lease: line.lease,
107
118
  live: ctx.agents.get(line.session_id) !== undefined,
108
119
  materialized: materializationOf(ctx, line.session_id),
120
+ activity: activityOf(ctx, line.session_id),
109
121
  }))
110
122
  const current = currentId === undefined
111
123
  ? null
@@ -178,7 +190,7 @@ export function registerToolsForAgent(agent, def, { config, store, lineDefs, cre
178
190
  const current = lineDefs.get(exec?.agent?.session?.id)
179
191
  return {
180
192
  current: currentView(current),
181
- lines: store.listLines().map(line => ({ ...line, live: agent.ctx.agents.get(line.session_id) !== undefined, materialized: materializationOf(agent.ctx, line.session_id) })),
193
+ lines: store.listLines().map(line => ({ ...line, live: agent.ctx.agents.get(line.session_id) !== undefined, materialized: materializationOf(agent.ctx, line.session_id), activity: activityOf(agent.ctx, line.session_id) })),
182
194
  bootstrapOnCreate: config.bootstrapOnCreate,
183
195
  }
184
196
  },
@@ -386,6 +398,30 @@ export function registerToolsForAgent(agent, def, { config, store, lineDefs, cre
386
398
  },
387
399
  }))
388
400
 
401
+ disposers.push(agent.ctx.tools.register({
402
+ name: 'line_observe',
403
+ 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.',
404
+ parameters: {
405
+ type: 'object', additionalProperties: false,
406
+ properties: {
407
+ line: { type: 'string', description: 'Optional line filter (matches from or to)' },
408
+ since: { type: 'number', description: 'Only messages created after this epoch ms' },
409
+ limit: { type: 'number', description: 'Maximum messages, 1-200 (default 100)' },
410
+ },
411
+ },
412
+ output: OUTPUT,
413
+ execute: async (args, exec) => {
414
+ const caller = currentLine(exec, lineDefs)
415
+ if (caller.label !== config.createFrom) throw new LineError('LINE_CREATE_FORBIDDEN', `Only ${config.createFrom} may observe lines`, { caller: caller.label })
416
+ return store.observe({
417
+ agentId: config.agentId,
418
+ line: args.line === undefined ? null : String(args.line),
419
+ since: Number(args.since) || 0,
420
+ limit: limitValue(args.limit, 100, 200),
421
+ })
422
+ },
423
+ }))
424
+
389
425
  disposers.push(agent.ctx.tools.register({
390
426
  name: 'line_handoff',
391
427
  description: 'Generate a structured handoff snapshot for a line: current metadata, backlog counts, recent messages, lifecycle events. Does not call a model; write the returned markdown to a file before retire/replace/rebind.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-agent-lines",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "DSH Agent 的持久、可寻址消息线(社区插件)。",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",