dsh-agent-lines 0.0.0-stage → 0.3.0

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.
@@ -0,0 +1,85 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>dsh-agent-lines — DSH Agent 的持久消息线</title>
7
+ <meta name="description" content="DSH Agent 的持久、可寻址消息线:一个管理线,多条执行线,本地 SQLite,社区插件。">
8
+ <meta property="og:image" content="https://lencen.github.io/dsh-agent-lines/assets/social-preview.png">
9
+ <style>
10
+ :root{--bg:#080b10;--panel:#0d1117;--line:#263241;--text:#e6edf3;--muted:#8b949e;--cyan:#58a6ff;--green:#7ee787}
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}
13
+ a{color:var(--cyan);text-decoration:none}
14
+ .wrap{max-width:1080px;margin:0 auto;padding:0 24px}
15
+ header{display:flex;align-items:center;justify-content:space-between;padding:22px 0;border-bottom:1px solid #1f2937}
16
+ .brand{display:flex;align-items:center;gap:10px;color:var(--text);font-weight:700}
17
+ .mark{width:24px;height:24px;border-radius:7px;background:linear-gradient(135deg,#58a6ff,#7ee787)}
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}
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}
28
+ .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
+ .shot img{display:block;width:100%;height:auto}
30
+ .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}
33
+ .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}
35
+ .card b{display:block;margin-bottom:6px}
36
+ .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}}
40
+ </style>
41
+ </head>
42
+ <body>
43
+ <div class="wrap">
44
+ <header>
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.0">Release</a></nav>
47
+ </header>
48
+ <main>
49
+ <section class="hero" style="border:0;padding-bottom:22px">
50
+ <div>
51
+ <div class="kicker">Community plugin · Apache-2.0</div>
52
+ <h1>给 DSH Agent 的<br><span>持久消息线</span></h1>
53
+ <p class="lead">一个管理线,多条执行线。每条线是普通 DSH Session,有独立上下文和持久收件箱;消息落本地 SQLite,跨进程共享,不依赖网络服务。</p>
54
+ <div class="install">dsh plugin --profile &lt;profile&gt; add dsh-agent-lines@0.3.0</div>
55
+ <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.0">下载 0.3.0</a>
58
+ </div>
59
+ </div>
60
+ <div class="shot">
61
+ <img src="assets/first-use.gif" alt="首次使用:创建管理线、8 条成员线并收到 ack">
62
+ <div class="caption">demo profile · 通用 label · 不含内部数据</div>
63
+ </div>
64
+ </section>
65
+ <section>
66
+ <h2>为什么不是再做一个聊天框</h2>
67
+ <div class="cards">
68
+ <div class="card"><b>线是可寻址的</b><p>agent#line 是稳定身份;管理线可以按角色建线、发任务、收结果。</p></div>
69
+ <div class="card"><b>消息是持久的</b><p>接收方重启后消息仍在;至少一次投递,Session 日志用 requestId 做幂等。</p></div>
70
+ <div class="card"><b>边界是清楚的</b><p>0.3 只做本地 SQLite;不做 handover、不做进程隔离、不内置团队 UI。</p></div>
71
+ </div>
72
+ </section>
73
+ <section>
74
+ <h2>首次使用:一个管理线 + 8 条成员线</h2>
75
+ <pre>你是开发团队负责人:负责任务拆解、分配、协调、跟踪、汇总。
76
+ 请用 line_create 创建:
77
+ research / architecture / uiux / dev / test / acceptance / release / ops
78
+ 创建后用 line_list 确认,然后待命。</pre>
79
+ <p style="color:var(--muted);font-size:14px">完整说明、协议和限制见 <a href="https://github.com/Lencen/dsh-agent-lines">README</a>。社区插件,不是 DeepSeek 官方产品。</p>
80
+ </section>
81
+ <footer>dsh-agent-lines 0.3.0 · Apache-2.0 · Community plugin for DeepSeek Harness. Not an official DeepSeek product.</footer>
82
+ </main>
83
+ </div>
84
+ </body>
85
+ </html>
@@ -0,0 +1,69 @@
1
+ {
2
+ "format": 1,
3
+ "title": {
4
+ "en": "Agent Lines",
5
+ "zh": "Agent 线"
6
+ },
7
+ "description": {
8
+ "en": "Durable peer messaging lines for DSH agents.",
9
+ "zh": "DSH agent 的持久化跨进程同级消息线。"
10
+ },
11
+ "metadata": {
12
+ "categories": {
13
+ "agent-collaboration": {
14
+ "title": {
15
+ "en": "Agent collaboration",
16
+ "zh": "Agent 协作"
17
+ }
18
+ }
19
+ },
20
+ "labels": {
21
+ "community": {
22
+ "title": {
23
+ "en": "Community",
24
+ "zh": "社区"
25
+ }
26
+ }
27
+ }
28
+ },
29
+ "plugins": [
30
+ {
31
+ "name": "dsh-agent-lines",
32
+ "title": {
33
+ "en": "Agent Lines",
34
+ "zh": "Agent 线"
35
+ },
36
+ "description": {
37
+ "en": "Durable, addressable peer messaging between DSH processes.",
38
+ "zh": "DSH 进程间可寻址、持久化的同级消息层。"
39
+ },
40
+ "author": "Lencen",
41
+ "metadata": {
42
+ "category": "agent-collaboration",
43
+ "labels": [
44
+ "community"
45
+ ],
46
+ "keywords": [
47
+ "dsh-plugin",
48
+ "agent",
49
+ "messaging"
50
+ ]
51
+ },
52
+ "dist-tags": {
53
+ "latest": "0.3.0"
54
+ },
55
+ "versions": [
56
+ {
57
+ "version": "0.3.0",
58
+ "publishTimestamp": 1793771000,
59
+ "source": {
60
+ "type": "npm"
61
+ },
62
+ "engines": {
63
+ "dsh": ">=0.2.1-rc.5 <0.3.0"
64
+ }
65
+ }
66
+ ]
67
+ }
68
+ ]
69
+ }
@@ -0,0 +1,69 @@
1
+ {
2
+ "format": 1,
3
+ "title": {
4
+ "en": "Agent Lines",
5
+ "zh": "Agent 线"
6
+ },
7
+ "description": {
8
+ "en": "Durable peer messaging lines for DSH agents.",
9
+ "zh": "DSH agent 的持久化跨进程同级消息线。"
10
+ },
11
+ "metadata": {
12
+ "categories": {
13
+ "agent-collaboration": {
14
+ "title": {
15
+ "en": "Agent collaboration",
16
+ "zh": "Agent 协作"
17
+ }
18
+ }
19
+ },
20
+ "labels": {
21
+ "community": {
22
+ "title": {
23
+ "en": "Community",
24
+ "zh": "社区"
25
+ }
26
+ }
27
+ }
28
+ },
29
+ "plugins": [
30
+ {
31
+ "name": "dsh-agent-lines",
32
+ "title": {
33
+ "en": "Agent Lines",
34
+ "zh": "Agent 线"
35
+ },
36
+ "description": {
37
+ "en": "Durable, addressable peer messaging between DSH processes.",
38
+ "zh": "DSH 进程间可寻址、持久化的同级消息层。"
39
+ },
40
+ "author": "Lencen",
41
+ "metadata": {
42
+ "category": "agent-collaboration",
43
+ "labels": [
44
+ "community"
45
+ ],
46
+ "keywords": [
47
+ "dsh-plugin",
48
+ "agent",
49
+ "messaging"
50
+ ]
51
+ },
52
+ "dist-tags": {
53
+ "latest": "0.3.0"
54
+ },
55
+ "versions": [
56
+ {
57
+ "version": "0.3.0",
58
+ "publishTimestamp": 1793771000,
59
+ "source": {
60
+ "type": "npm"
61
+ },
62
+ "engines": {
63
+ "dsh": ">=0.2.1-rc.5 <0.3.0"
64
+ }
65
+ }
66
+ ]
67
+ }
68
+ ]
69
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Owner-process delivery loop.
3
+ *
4
+ * Only the DSH process that owns `config.sessionId` consumes its queue. The
5
+ * loop never resolves another process's Session, which keeps DSH's per-Session
6
+ * writer lock intact.
7
+ */
8
+ import { formatEnvelope } from './protocol.js'
9
+
10
+ const RETRYABLE = new Set([
11
+ 'session/agent-busy',
12
+ 'session/conflict',
13
+ 'LINE_LEASE_LOST',
14
+ 'SQLITE_BUSY',
15
+ ])
16
+
17
+ function errorCode(error) {
18
+ return String(error?.code ?? error?.remoteCode ?? error?.name ?? '')
19
+ }
20
+
21
+ function backoffMs(attempts) {
22
+ return Math.min(60_000, 1_000 * 2 ** Math.max(0, attempts - 1))
23
+ }
24
+
25
+ export class DeliveryLoop {
26
+ constructor({ ctx, config, store, logger = ctx.logger }) {
27
+ this.ctx = ctx
28
+ this.config = config
29
+ this.store = store
30
+ this.logger = logger
31
+ this.instanceId = config.instanceId
32
+ this.timer = undefined
33
+ this.current = Promise.resolve()
34
+ this.stopping = false
35
+ this.started = false
36
+ }
37
+
38
+ start() {
39
+ if (this.started) return
40
+ this.started = true
41
+ this.stopping = false
42
+ this.timer = setInterval(() => {
43
+ this.current = this.current.then(() => this.tick()).catch(error => {
44
+ this.logger?.warn?.('agent-lines: delivery tick failed: %s', String(error))
45
+ })
46
+ }, this.config.pollMs)
47
+ this.timer.unref?.()
48
+ this.current = this.tick().catch(error => {
49
+ this.logger?.warn?.('agent-lines: initial delivery tick failed: %s', String(error))
50
+ })
51
+ }
52
+
53
+ async stop() {
54
+ if (!this.started) return
55
+ this.stopping = true
56
+ if (this.timer !== undefined) clearInterval(this.timer)
57
+ this.timer = undefined
58
+ await this.current.catch(() => {})
59
+ try {
60
+ this.store.release({ agentId: this.config.agentId, line: this.config.line, instanceId: this.instanceId })
61
+ } catch (error) {
62
+ this.logger?.warn?.('agent-lines: release lease failed: %s', String(error))
63
+ }
64
+ this.started = false
65
+ }
66
+
67
+ async tick() {
68
+ if (this.stopping) return
69
+ const { agentId, line, instanceId, leaseTtlMs, maxAttempts } = this.config
70
+ this.store.heartbeat({ agentId, line, instanceId, ttlMs: leaseTtlMs })
71
+ const rows = this.store.claim({
72
+ agentId,
73
+ line,
74
+ instanceId,
75
+ limit: this.config.claimBatch,
76
+ claimTimeoutMs: this.config.claimTimeoutMs,
77
+ })
78
+ for (const row of rows) {
79
+ if (this.stopping) break
80
+ await this.deliver(row, { agentId, line, instanceId, maxAttempts })
81
+ }
82
+ }
83
+
84
+ async deliver(row, { agentId, line, instanceId, maxAttempts }) {
85
+ const text = formatEnvelope(row)
86
+ try {
87
+ const signal = AbortSignal.timeout(this.config.deliveryTimeoutMs)
88
+ await this.ctx.sessionController.prompt({
89
+ requestId: row.id,
90
+ sessionId: this.config.sessionId,
91
+ mode: 'steer',
92
+ content: [{ type: 'text', text }],
93
+ }, signal)
94
+ this.store.markDelivered(row.id, instanceId)
95
+ this.logger?.info?.('agent-lines: delivered %s from %s#%s', row.id, row.from_agent, row.from_line)
96
+ } catch (error) {
97
+ const code = errorCode(error)
98
+ const permanent = code === 'session/not-found' || code === 'LINE_NOT_FOUND'
99
+ const terminal = permanent || row.attempts >= maxAttempts
100
+ const nextAttemptAt = terminal ? undefined : Date.now() + backoffMs(row.attempts)
101
+ this.store.markError(row.id, instanceId, `${code || 'error'}: ${error?.message ?? String(error)}`, {
102
+ nextAttemptAt,
103
+ maxAttempts,
104
+ })
105
+ this.logger?.warn?.('agent-lines: delivery %s failed (%s, attempt %d/%d)%s',
106
+ row.id, code || 'error', row.attempts, maxAttempts, terminal ? ' terminal' : '')
107
+ }
108
+ }
109
+ }
110
+
111
+ export { backoffMs, errorCode, RETRYABLE }