dsh-agent-lines 0.3.6 → 0.3.7
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 +8 -0
- package/CHANGELOG.md +8 -0
- package/CODE_OF_CONDUCT.en.md +0 -0
- package/CODE_OF_CONDUCT.md +0 -0
- package/CONTRIBUTING.en.md +0 -0
- package/CONTRIBUTING.md +0 -0
- package/LICENSE +0 -0
- package/NOTICE +0 -0
- package/README.en.md +2 -1
- package/README.md +2 -1
- package/SECURITY.en.md +0 -0
- package/SECURITY.md +0 -0
- package/bin/dsh-agent-lines.js +0 -0
- package/cordis.patch.yml +0 -0
- package/docs/ARCHITECTURE.en.md +0 -0
- package/docs/ARCHITECTURE.md +0 -0
- package/docs/DEPLOY.en.md +0 -0
- package/docs/DEPLOY.md +0 -0
- package/docs/PROTOCOL.en.md +1 -0
- package/docs/PROTOCOL.md +1 -0
- package/docs/SECURITY.en.md +0 -0
- package/docs/SECURITY.md +0 -0
- package/docs/assets/first-use.gif +0 -0
- package/docs/assets/social-preview.png +0 -0
- package/docs/en/index.html +4 -4
- package/docs/index.html +4 -4
- package/docs/market-index.json +3 -3
- package/examples/dashboard/README.md +16 -0
- package/examples/market-index.json +3 -3
- package/lib/delivery.js +0 -0
- package/lib/index.js +11 -2
- package/lib/prompt.js +0 -0
- package/lib/protocol.js +0 -0
- package/lib/store.js +27 -1
- package/lib/tools.js +53 -0
- package/lib/transport.js +0 -0
- package/package.json +1 -1
package/CHANGELOG.en.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
English | [中文](CHANGELOG.md)
|
|
4
4
|
|
|
5
|
+
## 0.3.7 (2026-11-09)
|
|
6
|
+
|
|
7
|
+
- Added line_handoff: structured handoff snapshots (JSON/Markdown), no model call.
|
|
8
|
+
- line_status / line_list now expose lifecycle/title/generation/pending/lastActivityAt/oldSessionId.
|
|
9
|
+
- rebind sends a noReply handoff notice to the new line; replace reuses the onboarding message.
|
|
10
|
+
- list --json can feed an external dashboard; added examples/dashboard notes.
|
|
11
|
+
- Tests 33/33 pass.
|
|
12
|
+
|
|
5
13
|
## 0.3.6 (2026-11-09)
|
|
6
14
|
|
|
7
15
|
- Added line lifecycle: line_retire, line_rebind, line_create replace:true, line_rename.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
[English](CHANGELOG.en.md) | 中文
|
|
4
4
|
|
|
5
|
+
## 0.3.7 (2026-11-09)
|
|
6
|
+
|
|
7
|
+
- 新增 line_handoff:结构化交接快照(JSON/Markdown),不调用模型。
|
|
8
|
+
- line_status / line_list 增加 lifecycle/title/generation/pending/lastActivityAt/oldSessionId。
|
|
9
|
+
- rebind 后给新线发 noReply 交接通知;replace 复用 onboarding 消息。
|
|
10
|
+
- list --json 可直接喂给外部看板;新增 examples/dashboard 说明。
|
|
11
|
+
- 测试 33/33 通过。
|
|
12
|
+
|
|
5
13
|
## 0.3.6 (2026-11-09)
|
|
6
14
|
|
|
7
15
|
- 新增线生命周期:line_retire、line_rebind、line_create replace:true、line_rename。
|
package/CODE_OF_CONDUCT.en.md
CHANGED
|
File without changes
|
package/CODE_OF_CONDUCT.md
CHANGED
|
File without changes
|
package/CONTRIBUTING.en.md
CHANGED
|
File without changes
|
package/CONTRIBUTING.md
CHANGED
|
File without changes
|
package/LICENSE
CHANGED
|
File without changes
|
package/NOTICE
CHANGED
|
File without changes
|
package/README.en.md
CHANGED
|
@@ -36,7 +36,7 @@ The script picks the DSH Web profile, installs the plugin, and verifies agent-li
|
|
|
36
36
|
|
|
37
37
|
Offline or when npm is unavailable, use the local tarball:
|
|
38
38
|
|
|
39
|
-
bash install.sh ./dsh-agent-lines-0.3.
|
|
39
|
+
bash install.sh ./dsh-agent-lines-0.3.7.tgz
|
|
40
40
|
|
|
41
41
|
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
42
|
|
|
@@ -138,6 +138,7 @@ Tools:
|
|
|
138
138
|
| `line_claim_manager` | Bind this live root Session as manager; requires `allowClaim: true` and `confirm: "bind-manager"`; global, no shell needed |
|
|
139
139
|
| `line_list` | List lines, status, unread counts, current line and role |
|
|
140
140
|
| `line_create` | Manager only: create a line; same label is idempotent; replace:true swaps the label to a fresh Session |
|
|
141
|
+
| `line_handoff` | Manager only: structured handoff snapshot (JSON/Markdown), no model call |
|
|
141
142
|
| `line_retire` | Manager only: soft-retire; reject new sends, keep Session and history |
|
|
142
143
|
| `line_rebind` | Manager only: atomically switch a line to a new live Session, optionally migrating backlog |
|
|
143
144
|
| `line_rename` | Manager only: rename the sidebar title and persist it in lines.db |
|
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ DSH 的 super agent 平替插件。
|
|
|
36
36
|
|
|
37
37
|
离线或 npm 不可用时,用本地 tarball:
|
|
38
38
|
|
|
39
|
-
bash install.sh ./dsh-agent-lines-0.3.
|
|
39
|
+
bash install.sh ./dsh-agent-lines-0.3.7.tgz
|
|
40
40
|
|
|
41
41
|
安装并重启后,不写配置也可以直接使用;需要固定管理线/worker 时,再把下面“管理线配置”粘贴到脚本打印的配置文件。
|
|
42
42
|
|
|
@@ -138,6 +138,7 @@ line_create({ label: "ops", role: "运维" })
|
|
|
138
138
|
| `line_claim_manager` | 把当前 live 根 Session 绑成 manager;需要 `allowClaim: true` 和 `confirm: "bind-manager"`;全局工具,不需要 shell |
|
|
139
139
|
| `line_list` | 列出线、状态、未读数、当前线和 role |
|
|
140
140
|
| `line_create` | 仅管理线:创建新线;同 label 幂等;传 replace:true 可给同 label 换新 Session |
|
|
141
|
+
| `line_handoff` | 仅管理线:生成结构化交接单(JSON/Markdown),不调模型 |
|
|
141
142
|
| `line_retire` | 仅管理线:软下线;拒绝新发送,保留 Session/历史 |
|
|
142
143
|
| `line_rebind` | 仅管理线:把线原子切到新的 live Session,可迁移积压 |
|
|
143
144
|
| `line_rename` | 仅管理线:改侧栏标题,持久化到 lines.db |
|
package/SECURITY.en.md
CHANGED
|
File without changes
|
package/SECURITY.md
CHANGED
|
File without changes
|
package/bin/dsh-agent-lines.js
CHANGED
|
File without changes
|
package/cordis.patch.yml
CHANGED
|
File without changes
|
package/docs/ARCHITECTURE.en.md
CHANGED
|
File without changes
|
package/docs/ARCHITECTURE.md
CHANGED
|
File without changes
|
package/docs/DEPLOY.en.md
CHANGED
|
File without changes
|
package/docs/DEPLOY.md
CHANGED
|
File without changes
|
package/docs/PROTOCOL.en.md
CHANGED
|
@@ -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_handoff({ line, format?: "md" | "json", limit?: number })
|
|
106
107
|
line_rename({ line, title })
|
|
107
108
|
|
|
108
109
|
- retired: line_send returns LINE_RETIRED; the Session and history are kept.
|
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_handoff({ line, format?: "md" | "json", limit?: number })
|
|
98
99
|
line_rename({ line, title })
|
|
99
100
|
|
|
100
101
|
- retired:line_send 返回 LINE_RETIRED;Session 和消息历史保留。
|
package/docs/SECURITY.en.md
CHANGED
|
File without changes
|
package/docs/SECURITY.md
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/docs/en/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="../">中文</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.
|
|
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>
|
|
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 && 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.
|
|
57
|
+
<a class="btn" href="https://github.com/Lencen/dsh-agent-lines/releases/tag/v0.3.7">Download 0.3.7</a>
|
|
58
58
|
</div>
|
|
59
59
|
</div>
|
|
60
60
|
<div class="shot">
|
|
@@ -92,8 +92,8 @@ 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.
|
|
95
|
+
<footer>dsh-agent-lines 0.3.7 · Apache-2.0 · Community plugin for DeepSeek Harness. Not an official DeepSeek product.</footer>
|
|
96
96
|
</main>
|
|
97
97
|
</div>
|
|
98
98
|
</body>
|
|
99
|
-
</html>
|
|
99
|
+
</html>
|
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.
|
|
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>
|
|
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 && 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.
|
|
57
|
+
<a class="btn" href="https://github.com/Lencen/dsh-agent-lines/releases/tag/v0.3.7">下载 0.3.7</a>
|
|
58
58
|
</div>
|
|
59
59
|
</div>
|
|
60
60
|
<div class="shot">
|
|
@@ -92,8 +92,8 @@ 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.
|
|
95
|
+
<footer>dsh-agent-lines 0.3.7 · Apache-2.0 · Community plugin for DeepSeek Harness. Not an official DeepSeek product.</footer>
|
|
96
96
|
</main>
|
|
97
97
|
</div>
|
|
98
98
|
</body>
|
|
99
|
-
</html>
|
|
99
|
+
</html>
|
package/docs/market-index.json
CHANGED
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
]
|
|
51
51
|
},
|
|
52
52
|
"dist-tags": {
|
|
53
|
-
"latest": "0.3.
|
|
53
|
+
"latest": "0.3.7"
|
|
54
54
|
},
|
|
55
55
|
"versions": [
|
|
56
56
|
{
|
|
57
|
-
"version": "0.3.
|
|
57
|
+
"version": "0.3.7",
|
|
58
58
|
"publishTimestamp": 1793771000,
|
|
59
59
|
"source": {
|
|
60
60
|
"type": "npm"
|
|
@@ -66,4 +66,4 @@
|
|
|
66
66
|
]
|
|
67
67
|
}
|
|
68
68
|
]
|
|
69
|
-
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Dashboard example
|
|
2
|
+
|
|
3
|
+
The plugin deliberately does not run an HTTP server. Read the same SQLite queue with:
|
|
4
|
+
|
|
5
|
+
~~~bash
|
|
6
|
+
dsh-agent-lines --json list
|
|
7
|
+
~~~
|
|
8
|
+
|
|
9
|
+
Fields: label/title/role/lifecycle/generation/pending/unread/lastActivityAt/lease.
|
|
10
|
+
The Surface feedback dashboard (refresh-dashboard.js) can consume this JSON and keep its own directory scan.
|
|
11
|
+
|
|
12
|
+
For a handoff snapshot before replacing a line:
|
|
13
|
+
|
|
14
|
+
~~~text
|
|
15
|
+
line_handoff({ line: "writer", format: "json" })
|
|
16
|
+
~~~
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
]
|
|
51
51
|
},
|
|
52
52
|
"dist-tags": {
|
|
53
|
-
"latest": "0.3.
|
|
53
|
+
"latest": "0.3.7"
|
|
54
54
|
},
|
|
55
55
|
"versions": [
|
|
56
56
|
{
|
|
57
|
-
"version": "0.3.
|
|
57
|
+
"version": "0.3.7",
|
|
58
58
|
"publishTimestamp": 1793771000,
|
|
59
59
|
"source": {
|
|
60
60
|
"type": "npm"
|
|
@@ -66,4 +66,4 @@
|
|
|
66
66
|
]
|
|
67
67
|
}
|
|
68
68
|
]
|
|
69
|
-
}
|
|
69
|
+
}
|
package/lib/delivery.js
CHANGED
|
File without changes
|
package/lib/index.js
CHANGED
|
@@ -297,7 +297,7 @@ export async function apply(ctx, rawConfig) {
|
|
|
297
297
|
|
|
298
298
|
const lineTitle = (def, row) => String((row && row.title) || (def && def.title) || (def && def.role) || (def && def.label) || 'agent line').slice(0, 120)
|
|
299
299
|
|
|
300
|
-
const rebindLine = async ({ label, sessionId, actor, reason, migrate = 'queued', force = false }) => {
|
|
300
|
+
const rebindLine = async ({ label, sessionId, actor, reason, migrate = 'queued', force = false, notify = true }) => {
|
|
301
301
|
if (label === config.createFrom) throw new LineError('LINE_MANAGER_REBIND_UNSUPPORTED', 'The manager line is rebound with line_claim_manager, not line_rebind', { line: label })
|
|
302
302
|
const row = store.getLineRow(config.agentId, label)
|
|
303
303
|
if (row === undefined) throw new LineError('LINE_NOT_FOUND', `No such line: ${config.agentId}#${label}`)
|
|
@@ -332,6 +332,15 @@ export async function apply(ctx, rawConfig) {
|
|
|
332
332
|
startLoop(def)
|
|
333
333
|
const agent = ctx.agents.get(sessionId)
|
|
334
334
|
if (agent !== undefined) bindAgent(agent)
|
|
335
|
+
if (notify !== false) {
|
|
336
|
+
try {
|
|
337
|
+
store.send({
|
|
338
|
+
agentId: config.agentId, fromLine: config.createFrom, to: label,
|
|
339
|
+
text: `[Agent Line] ${label} switched to a fresh Session. old=${row.session_id} new=${sessionId} migrated=${JSON.stringify(updated.counts)}. Read the handoff file before continuing.`,
|
|
340
|
+
noReply: true, maxReplyDepth: config.maxReplyDepth, maxMessageBytes: config.maxMessageBytes,
|
|
341
|
+
})
|
|
342
|
+
} catch (error) { ctx.logger?.warn?.('agent-lines: rebind notification failed: %s', String(error)) }
|
|
343
|
+
}
|
|
335
344
|
return { line: label, oldSessionId: row.session_id, newSessionId: sessionId, generation: updated.generation, counts: updated.counts }
|
|
336
345
|
} catch (error) {
|
|
337
346
|
try { await stopLoopFor(label) } catch { /* best effort */ }
|
|
@@ -397,7 +406,7 @@ export async function apply(ctx, rawConfig) {
|
|
|
397
406
|
const actualSessionId = created.sessionId
|
|
398
407
|
const result = await rebindLine({
|
|
399
408
|
label: existing.label, sessionId: actualSessionId, actor: (exec && exec.agent && exec.agent.session && exec.agent.session.id) || null,
|
|
400
|
-
reason: String(args.reason || 'line_create replace'), migrate: args.migrate === 'none' ? 'none' : 'queued', force: args.force === true,
|
|
409
|
+
reason: String(args.reason || 'line_create replace'), migrate: args.migrate === 'none' ? 'none' : 'queued', force: args.force === true, notify: false,
|
|
401
410
|
})
|
|
402
411
|
const newDef = lineDefs.get(actualSessionId)
|
|
403
412
|
if (newDef !== undefined) {
|
package/lib/prompt.js
CHANGED
|
File without changes
|
package/lib/protocol.js
CHANGED
|
File without changes
|
package/lib/store.js
CHANGED
|
@@ -410,13 +410,19 @@ export class LineStore {
|
|
|
410
410
|
/** All lines with unread counts and live lease state. */
|
|
411
411
|
listLines(now = Date.now()) {
|
|
412
412
|
const rows = this.#stmt(`SELECT l.*, (SELECT COUNT(*) FROM messages m
|
|
413
|
-
WHERE m.to_agent = l.agent_id AND m.to_line = l.label AND m.state IN ('queued', 'claimed', 'delivered')) AS unread
|
|
413
|
+
WHERE m.to_agent = l.agent_id AND m.to_line = l.label AND m.state IN ('queued', 'claimed', 'delivered')) AS unread,
|
|
414
|
+
(SELECT COUNT(*) FROM messages m
|
|
415
|
+
WHERE m.to_agent = l.agent_id AND m.to_line = l.label AND m.state IN ('queued', 'claimed')) AS pending,
|
|
416
|
+
(SELECT MAX(m.created_at) FROM messages m
|
|
417
|
+
WHERE m.to_agent = l.agent_id AND m.to_line = l.label) AS last_activity_at
|
|
414
418
|
FROM lines l ORDER BY l.agent_id, l.label`).all()
|
|
415
419
|
return rows.map(row => {
|
|
416
420
|
const lease = this.#stmt('SELECT * FROM leases WHERE line_id = ?').get(row.line_id)
|
|
417
421
|
return {
|
|
418
422
|
...row,
|
|
419
423
|
unread: Number(row.unread),
|
|
424
|
+
pending: Number(row.pending ?? 0),
|
|
425
|
+
lastActivityAt: row.last_activity_at === null ? null : Number(row.last_activity_at),
|
|
420
426
|
lease: lease === undefined ? null : {
|
|
421
427
|
instanceId: lease.instance_id,
|
|
422
428
|
expiresAt: lease.expires_at,
|
|
@@ -433,9 +439,13 @@ export class LineStore {
|
|
|
433
439
|
if (row === undefined) return undefined
|
|
434
440
|
const lease = this.#stmt('SELECT * FROM leases WHERE line_id = ?').get(id)
|
|
435
441
|
const unread = this.#stmt(`SELECT COUNT(*) AS n FROM messages WHERE to_agent = ? AND to_line = ? AND state IN ('queued', 'claimed', 'delivered')`).get(agentId, line)
|
|
442
|
+
const pending = this.#stmt(`SELECT COUNT(*) AS n FROM messages WHERE to_agent = ? AND to_line = ? AND state IN ('queued', 'claimed')`).get(agentId, line)
|
|
443
|
+
const activity = this.#stmt('SELECT MAX(created_at) AS n FROM messages WHERE to_agent = ? AND to_line = ?').get(agentId, line)
|
|
436
444
|
return {
|
|
437
445
|
...row,
|
|
438
446
|
unread: Number(unread.n),
|
|
447
|
+
pending: Number(pending.n),
|
|
448
|
+
lastActivityAt: activity.n === null ? null : Number(activity.n),
|
|
439
449
|
lease: lease === undefined ? null : { instanceId: lease.instance_id, expiresAt: lease.expires_at, live: lease.expires_at > now },
|
|
440
450
|
}
|
|
441
451
|
}
|
|
@@ -532,6 +542,22 @@ export class LineStore {
|
|
|
532
542
|
return this.#stmt('SELECT * FROM line_events WHERE line_id = ? AND action = ? ORDER BY created_at DESC LIMIT 1').get(id, action)
|
|
533
543
|
}
|
|
534
544
|
|
|
545
|
+
/** Structured handoff snapshot: counts + recent messages + lifecycle events. */
|
|
546
|
+
handoff({ agentId, line, limit = 20, now = Date.now() }) {
|
|
547
|
+
const row = this.getLine(agentId, line, now)
|
|
548
|
+
if (row === undefined) throw new LineError('LINE_NOT_FOUND', `No such line: ${lineId(agentId, line)}`)
|
|
549
|
+
const recent = this.#stmt(`SELECT * FROM messages WHERE to_agent = ? AND to_line = ? ORDER BY created_at DESC LIMIT ?`).all(agentId, line, limit).reverse()
|
|
550
|
+
const events = this.lineEvents({ agentId, line, limit: 10 })
|
|
551
|
+
const lastEvent = events.length ? events[0] : null
|
|
552
|
+
return {
|
|
553
|
+
line: line, label: line, role: row.role, title: row.title ?? row.role ?? row.label,
|
|
554
|
+
sessionId: row.session_id, lifecycle: row.lifecycle ?? 'active', generation: row.generation ?? 1,
|
|
555
|
+
createdBy: row.created_by, createdAt: row.created_at, counts: this.messageCounts({ agentId, line }),
|
|
556
|
+
lastActivityAt: row.lastActivityAt, oldSessionId: lastEvent && lastEvent.action === 'rebind' ? lastEvent.old_session_id : null,
|
|
557
|
+
lastEvent, events, messages: recent,
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
535
561
|
/** Recent lifecycle events for diagnostics. */
|
|
536
562
|
lineEvents({ agentId, line, limit = 20 }) {
|
|
537
563
|
const id = lineId(agentId, line)
|
package/lib/tools.js
CHANGED
|
@@ -98,6 +98,9 @@ export function registerStatusTool(ctx, { config, store, lineDefs }) {
|
|
|
98
98
|
title: line.title ?? line.role ?? line.label,
|
|
99
99
|
lifecycle: line.lifecycle ?? 'active',
|
|
100
100
|
generation: line.generation ?? 1,
|
|
101
|
+
oldSessionId: store.lastLineEvent({ agentId: line.agent_id, line: line.label, action: 'rebind' })?.old_session_id ?? null,
|
|
102
|
+
pending: line.pending ?? 0,
|
|
103
|
+
lastActivityAt: line.lastActivityAt ?? null,
|
|
101
104
|
sessionId: line.session_id,
|
|
102
105
|
createdBy: line.created_by,
|
|
103
106
|
lease: line.lease,
|
|
@@ -383,6 +386,56 @@ export function registerToolsForAgent(agent, def, { config, store, lineDefs, cre
|
|
|
383
386
|
},
|
|
384
387
|
}))
|
|
385
388
|
|
|
389
|
+
disposers.push(agent.ctx.tools.register({
|
|
390
|
+
name: 'line_handoff',
|
|
391
|
+
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.',
|
|
392
|
+
parameters: {
|
|
393
|
+
type: 'object', additionalProperties: false,
|
|
394
|
+
properties: {
|
|
395
|
+
line: { type: 'string', description: 'Existing line label' },
|
|
396
|
+
format: { type: 'string', enum: ['md', 'json'], description: 'md (default) returns markdown; json returns the snapshot object' },
|
|
397
|
+
limit: { type: 'number', description: 'Recent messages to include, 1-100 (default 20)' },
|
|
398
|
+
},
|
|
399
|
+
required: ['line'],
|
|
400
|
+
},
|
|
401
|
+
output: OUTPUT,
|
|
402
|
+
execute: async (args, exec) => {
|
|
403
|
+
const caller = currentLine(exec, lineDefs)
|
|
404
|
+
if (caller.label !== config.createFrom) throw new LineError('LINE_CREATE_FORBIDDEN', `Only ${config.createFrom} may create handoffs`, { caller: caller.label })
|
|
405
|
+
const limit = limitValue(args.limit, 20, 100)
|
|
406
|
+
const data = store.handoff({ agentId: config.agentId, line: String(args.line), limit })
|
|
407
|
+
if (args.format === 'json') return data
|
|
408
|
+
const lines = [
|
|
409
|
+
'# 交接单 · ' + data.label,
|
|
410
|
+
'',
|
|
411
|
+
'## 1 我是谁',
|
|
412
|
+
'- 角色: ' + (data.role ?? data.label),
|
|
413
|
+
'- 当前 label: ' + data.label,
|
|
414
|
+
'- Session: ' + data.sessionId,
|
|
415
|
+
'- lifecycle: ' + data.lifecycle + ' (generation ' + data.generation + ')',
|
|
416
|
+
'',
|
|
417
|
+
'## 2 积压',
|
|
418
|
+
'- queued: ' + data.counts.queued,
|
|
419
|
+
'- claimed: ' + data.counts.claimed,
|
|
420
|
+
'- delivered: ' + data.counts.delivered,
|
|
421
|
+
'- acked: ' + data.counts.acked,
|
|
422
|
+
'- failed/expired: ' + (data.counts.failed + data.counts.expired),
|
|
423
|
+
'',
|
|
424
|
+
'## 3 最近消息',
|
|
425
|
+
...(data.messages.length === 0 ? ['- 无'] : data.messages.map(m => `- ${new Date(m.created_at).toISOString()} [${m.state}] ${m.from_line} -> ${m.to_line}: ${String(m.body).replace(/\s+/g, ' ').slice(0, 180)}`)),
|
|
426
|
+
'',
|
|
427
|
+
'## 4 生命周期',
|
|
428
|
+
...(data.events.length === 0 ? ['- 无'] : data.events.map(e => `- ${new Date(e.created_at).toISOString()} ${e.action} ${e.old_session_id ? e.old_session_id + ' -> ' : ''}${e.new_session_id ?? ''}`)),
|
|
429
|
+
'',
|
|
430
|
+
'## 5 交接提示',
|
|
431
|
+
'- 先读本文件,再继续工作。',
|
|
432
|
+
'- 不要复用旧 Session;它只保留历史。',
|
|
433
|
+
'- 如有 queued/claimed,新线会继续收到。',
|
|
434
|
+
]
|
|
435
|
+
return { line: data.label, format: 'md', markdown: lines.join('\n'), counts: data.counts, sessionId: data.sessionId, generation: data.generation }
|
|
436
|
+
},
|
|
437
|
+
}))
|
|
438
|
+
|
|
386
439
|
disposers.push(agent.ctx.tools.register({
|
|
387
440
|
name: 'line_move_workspace',
|
|
388
441
|
description: 'Move an existing line Session into a target workspace record so it appears under that workspace in the sidebar. DSH only allows attachment when the Session cwd exactly matches the workspace path; it cannot move a Session to a different directory (use a new line for that).',
|
package/lib/transport.js
CHANGED
|
File without changes
|