@twinsearth/fang-dsh-plugin 0.0.0-stage → 3.3.5
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/CLA.md +79 -0
- package/LICENSE-COMMERCIAL +78 -0
- package/LICENSE-CORE +661 -0
- package/LICENSE-SDK +21 -0
- package/LICENSES/cordis-MIT.txt +39 -0
- package/NOTICE +95 -0
- package/README.md +158 -2
- package/client.js +7287 -0
- package/cordis.patch.yml +163 -0
- package/defaults/umbrella-rules.yaml +113 -0
- package/docs/APPROVAL-DRILL.md +42 -0
- package/docs/CONTRACT-INTEROP.md +77 -0
- package/docs/CRASH-SAFETY.md +69 -0
- package/docs/DECISION-RELAY.md +46 -0
- package/docs/DSH-ARCHITECTURE-MAP.md +73 -0
- package/docs/FORK-SEED.md +64 -0
- package/docs/MAIN-CITY-GATE.md +58 -0
- package/docs/OVERLAY-MASTER-SWITCH.md +35 -0
- package/docs/PACKAGE-RENAME-v3.3.3.md +40 -0
- package/docs/PM-DECISION-POLICY.md +61 -0
- package/docs/WORKFLOW-UI.md +86 -0
- package/icon.svg +8 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +71 -4
- package/sdk/README.md +65 -0
- package/sdk/examples/status-badge/README.md +16 -0
- package/sdk/examples/status-badge/client.js +60 -0
- package/sdk/examples/status-badge/cordis.patch.yml +7 -0
- package/sdk/examples/status-badge/index.js +12 -0
- package/sdk/examples/status-badge/package.json +24 -0
- package/sdk/templates/plugin/README.md +36 -0
- package/sdk/templates/plugin/client.js +35 -0
- package/sdk/templates/plugin/cordis.patch.yml +8 -0
- package/sdk/templates/plugin/icon.svg +5 -0
- package/sdk/templates/plugin/index.js +23 -0
- package/sdk/templates/plugin/locale/en.json +6 -0
- package/sdk/templates/plugin/package.json +36 -0
- package/src/compose.mjs +67 -0
- package/src/contract/VENDOR.json +16 -0
- package/src/contract/client.js +93 -0
- package/src/contract/constants.js +94 -0
- package/src/contract/envelope.js +83 -0
- package/src/contract/index.js +20 -0
- package/src/contract/negotiate.js +106 -0
- package/src/contract/relay.js +223 -0
- package/src/contract/server.js +77 -0
- package/src/contract/transports.js +319 -0
- package/src/index.mjs +11 -0
- package/src/kernel/core.mjs +100 -0
- package/src/kernel/extensions.mjs +61 -0
- package/src/kernel/fsx.mjs +349 -0
- package/src/kernel/index.mjs +21 -0
- package/src/kernel/ledger.mjs +94 -0
- package/src/kernel/paths.mjs +182 -0
- package/src/kernel/role.mjs +68 -0
- package/src/kernel/rounds.mjs +172 -0
- package/src/kernel/schema.mjs +5 -0
- package/src/kernel/types.mjs +266 -0
- package/src/kernel/vendor/cosmokit.mjs +421 -0
- package/src/kernel/vendor/schemastery.mjs +638 -0
- package/src/lib/adapter-client.mjs +383 -0
- package/src/lib/adapter-protocol.mjs +243 -0
- package/src/lib/ai-secret-store.mjs +202 -0
- package/src/lib/approval-drill.mjs +111 -0
- package/src/lib/boot-guard.mjs +186 -0
- package/src/lib/branch-store.mjs +84 -0
- package/src/lib/branch.mjs +74 -0
- package/src/lib/bridge-client.mjs +133 -0
- package/src/lib/broker-adapter.mjs +67 -0
- package/src/lib/broker.mjs +45 -0
- package/src/lib/business-producers.mjs +109 -0
- package/src/lib/command-cache.mjs +209 -0
- package/src/lib/compat.mjs +170 -0
- package/src/lib/composer-journal.mjs +128 -0
- package/src/lib/container/index.mjs +806 -0
- package/src/lib/decision-events.mjs +129 -0
- package/src/lib/decision-ingest-client.mjs +41 -0
- package/src/lib/decision-outbox.mjs +129 -0
- package/src/lib/decision-policy.mjs +52 -0
- package/src/lib/default-essence.mjs +61 -0
- package/src/lib/desktop-launch.mjs +138 -0
- package/src/lib/essence.mjs +1211 -0
- package/src/lib/fork-client.mjs +410 -0
- package/src/lib/fork-config-lock.mjs +166 -0
- package/src/lib/fork-seed.mjs +304 -0
- package/src/lib/fork-sessions.mjs +183 -0
- package/src/lib/fork-supervision-settings.mjs +58 -0
- package/src/lib/fork-supervisor.mjs +122 -0
- package/src/lib/fs-guard.mjs +28 -0
- package/src/lib/http-auth.mjs +162 -0
- package/src/lib/idle-audit.mjs +178 -0
- package/src/lib/interaction-poke.mjs +61 -0
- package/src/lib/main-gate.mjs +582 -0
- package/src/lib/mirror-etag.mjs +92 -0
- package/src/lib/mirror-official-source.mjs +93 -0
- package/src/lib/mirror.mjs +478 -0
- package/src/lib/mother-link.mjs +156 -0
- package/src/lib/mutex.mjs +31 -0
- package/src/lib/permissions.mjs +115 -0
- package/src/lib/pnpm-store.mjs +189 -0
- package/src/lib/preferences.mjs +212 -0
- package/src/lib/producer-contract.mjs +162 -0
- package/src/lib/reflow.mjs +41 -0
- package/src/lib/remote.mjs +408 -0
- package/src/lib/restart-contract.mjs +752 -0
- package/src/lib/roots.mjs +30 -0
- package/src/lib/rule-registry.mjs +1127 -0
- package/src/lib/runtime-capture.mjs +307 -0
- package/src/lib/semaphore.mjs +34 -0
- package/src/lib/session-bundle.mjs +138 -0
- package/src/lib/session-format.mjs +672 -0
- package/src/lib/sse-parser.mjs +102 -0
- package/src/lib/ssrf-guard.mjs +170 -0
- package/src/lib/status-store.mjs +257 -0
- package/src/lib/task-handle.mjs +622 -0
- package/src/lib/ui-actions.mjs +55 -0
- package/src/lib/umbrella.mjs +1661 -0
- package/src/lib/upgrade-coordinator.mjs +107 -0
- package/src/lib/upgrade-status.mjs +43 -0
- package/src/plugins/adapter.mjs +1468 -0
- package/src/plugins/approver.mjs +1210 -0
- package/src/plugins/auditor.mjs +330 -0
- package/src/plugins/buffer-keeper.mjs +522 -0
- package/src/plugins/decision-center.mjs +2581 -0
- package/src/plugins/forker.mjs +1610 -0
- package/src/plugins/healer.mjs +702 -0
- package/src/plugins/hot-reloader.mjs +274 -0
- package/src/plugins/imports.mjs +247 -0
- package/src/plugins/orchestrator.mjs +3979 -0
- package/src/plugins/restart-guard.mjs +1022 -0
- package/src/plugins/router.mjs +714 -0
- package/src/plugins/scorer.mjs +661 -0
- package/src/plugins/session-bridge.mjs +166 -0
- package/src/plugins/session-catalog.mjs +771 -0
- package/src/plugins/session-router.mjs +125 -0
- package/src/plugins/session-sync.mjs +171 -0
- package/src/plugins/umbrella.mjs +419 -0
- package/tools/approval-drill.mjs +21 -0
- package/tools/boot-guard.mjs +95 -0
- package/tools/decision-ingest.mjs +53 -0
- package/tools/desktop-launch.mjs +71 -0
- package/tools/fangctl.mjs +425 -0
- package/tools/gate/main-city.mjs +204 -0
- package/tools/mcp/server.mjs +121 -0
- package/tools/mcp/tools.mjs +65 -0
- package/tools/recover-queue.mjs +133 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// Copyright (c) 2026 dsh-Fang contributors — see LICENSE-CORE.
|
|
3
|
+
// fang-session-bridge — mother-side façade over a running PC host's loopback bridge.
|
|
4
|
+
// Transport only: it never copies session files and never writes to the PC host.
|
|
5
|
+
import { z, roleGate, resolveFangLayout, SERVICE, FANG_VERSION } from '../kernel/index.mjs'
|
|
6
|
+
import { createHttpGuard } from '../lib/http-auth.mjs'
|
|
7
|
+
import { createBridgeClient, readBridgeDescriptor } from '../lib/bridge-client.mjs'
|
|
8
|
+
|
|
9
|
+
export const name = 'fang-session-bridge'
|
|
10
|
+
export const inject = []
|
|
11
|
+
|
|
12
|
+
export const Config = z.object({
|
|
13
|
+
dshHome: z.string().default('')
|
|
14
|
+
.description('PC 宿主的 DSH_HOME(读取 desktop-bridge.json);留空 = 本机 $DSH_HOME'),
|
|
15
|
+
prefix: z.string().default('/fang')
|
|
16
|
+
.description('HTTP 路由前缀'),
|
|
17
|
+
timeoutMs: z.number().min(1000).max(120000).default(15000)
|
|
18
|
+
.description('每次桥接请求的超时(毫秒)'),
|
|
19
|
+
routes: z.object({ enabled: z.boolean().default(true) }).default({}),
|
|
20
|
+
authToken: z.string().default('')
|
|
21
|
+
.description('安装级鉴权 token;留空 = env DSH_FANG_AUTH_TOKEN 或 <dataDir>/fang-auth.json(0600,自动生成)'),
|
|
22
|
+
authEnabled: z.boolean().default(true)
|
|
23
|
+
.description('是否要求 /fang/api/bridge/* 携带安装级鉴权 token(默认开启;仅测试/本地调试可关)'),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export function apply(ctx, config) {
|
|
27
|
+
const gate = roleGate('sessionBridge', { logger: ctx.logger })
|
|
28
|
+
if (!gate.active) return
|
|
29
|
+
|
|
30
|
+
const cfg = { ...Config({}), ...(config ?? {}) }
|
|
31
|
+
const client = createBridgeClient({ dshHome: cfg.dshHome, timeoutMs: cfg.timeoutMs })
|
|
32
|
+
const prefix = String(cfg.prefix ?? '/fang').replace(/\/+$/, '') || '/fang'
|
|
33
|
+
const authGuard = createHttpGuard({
|
|
34
|
+
dataDir: resolveFangLayout().dataDir,
|
|
35
|
+
token: cfg.authToken,
|
|
36
|
+
enabled: cfg.authEnabled !== false,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const audit = (kind, data) => {
|
|
40
|
+
try {
|
|
41
|
+
const auditor = ctx.get(SERVICE.auditor)
|
|
42
|
+
if (auditor && typeof auditor.append === 'function') auditor.append(kind, data)
|
|
43
|
+
} catch { /* 审计失败不阻塞桥接 */ }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const service = {
|
|
47
|
+
role: gate.role,
|
|
48
|
+
version: FANG_VERSION,
|
|
49
|
+
descriptor: () => {
|
|
50
|
+
const desc = readBridgeDescriptor({ dshHome: cfg.dshHome })
|
|
51
|
+
return desc.ok
|
|
52
|
+
? { ok: true, file: desc.file, pid: desc.info.pid, startedAt: desc.info.startedAt }
|
|
53
|
+
: { ok: false, code: desc.code, error: desc.error, file: desc.file }
|
|
54
|
+
},
|
|
55
|
+
async status() { return client.status() },
|
|
56
|
+
async listSessions() {
|
|
57
|
+
const out = await client.listSessions()
|
|
58
|
+
audit('bridge-list', { ok: out.ok, count: out.ok ? out.items.length : 0, code: out.code ?? null })
|
|
59
|
+
return out
|
|
60
|
+
},
|
|
61
|
+
async events(sessionId, options = {}) {
|
|
62
|
+
const out = await client.events(sessionId, options)
|
|
63
|
+
audit('bridge-events', { ok: out.ok, sessionId, count: out.ok ? out.events.length : 0, code: out.code ?? null })
|
|
64
|
+
return out
|
|
65
|
+
},
|
|
66
|
+
async send(sessionId, text) {
|
|
67
|
+
const out = await client.send(sessionId, text)
|
|
68
|
+
audit('bridge-send', { ok: out.ok, sessionId, accepted: out.ok ? out.accepted : false, requestId: out.ok ? out.requestId : null, code: out.code ?? null })
|
|
69
|
+
return out
|
|
70
|
+
},
|
|
71
|
+
async create(input = {}) {
|
|
72
|
+
const out = await client.create(input)
|
|
73
|
+
audit('bridge-create', { ok: out.ok, code: out.code ?? null, sessionId: out.ok ? out.session?.id ?? out.session?.sessionId ?? null : null })
|
|
74
|
+
return out
|
|
75
|
+
},
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
ctx.effect(() => ctx.provide(SERVICE.sessionBridge, service))
|
|
79
|
+
|
|
80
|
+
if (cfg.routes?.enabled === false) return
|
|
81
|
+
ctx.inject(['webServer'], (child) => {
|
|
82
|
+
let webServer = null
|
|
83
|
+
try { webServer = child?.get?.('webServer') ?? child?.webServer ?? null } catch { webServer = null }
|
|
84
|
+
if (!webServer || typeof webServer.register !== 'function') return
|
|
85
|
+
const json = (res, status, value) => {
|
|
86
|
+
res.statusCode = status
|
|
87
|
+
res.setHeader('content-type', 'application/json; charset=utf-8')
|
|
88
|
+
res.end(JSON.stringify(value))
|
|
89
|
+
}
|
|
90
|
+
const queryOf = (req) => new URL(String(req?.url ?? '/'), 'http://fang.local').searchParams
|
|
91
|
+
const readBody = async (req) => {
|
|
92
|
+
const chunks = []
|
|
93
|
+
for await (const chunk of req) chunks.push(Buffer.from(chunk))
|
|
94
|
+
const text = Buffer.concat(chunks).toString('utf8')
|
|
95
|
+
if (!text) return {}
|
|
96
|
+
try { return JSON.parse(text) } catch { return { __parseError: true } }
|
|
97
|
+
}
|
|
98
|
+
const route = (method, routePath, handler) => {
|
|
99
|
+
webServer.register({
|
|
100
|
+
kind: 'exact',
|
|
101
|
+
path: routePath,
|
|
102
|
+
method,
|
|
103
|
+
handler: async (req, res) => {
|
|
104
|
+
try {
|
|
105
|
+
const decision = authGuard.check(req)
|
|
106
|
+
if (!decision.ok) return json(res, decision.status, { ok: false, error: decision.code, code: decision.code })
|
|
107
|
+
if (String(req?.method ?? 'GET').toUpperCase() !== method) {
|
|
108
|
+
try { res.setHeader?.('allow', method) } catch { /* fake res */ }
|
|
109
|
+
return json(res, 405, { ok: false, error: 'method-not-allowed', code: 'method-not-allowed' })
|
|
110
|
+
}
|
|
111
|
+
return await handler(req, res)
|
|
112
|
+
} catch (e) {
|
|
113
|
+
return json(res, 500, { ok: false, error: String(e?.message ?? e), code: 'handler-threw' })
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// DSH exact routes are keyed by path only: GET+POST share one registration.
|
|
120
|
+
const routeMethods = (routePath, handlers) => {
|
|
121
|
+
webServer.register({
|
|
122
|
+
kind: 'exact',
|
|
123
|
+
path: routePath,
|
|
124
|
+
method: Object.keys(handlers).join(','),
|
|
125
|
+
handler: async (req, res) => {
|
|
126
|
+
try {
|
|
127
|
+
const decision = authGuard.check(req)
|
|
128
|
+
if (!decision.ok) return json(res, decision.status, { ok: false, error: decision.code, code: decision.code })
|
|
129
|
+
const allow = Object.keys(handlers)
|
|
130
|
+
const fn = handlers[String(req?.method ?? 'GET').toUpperCase()]
|
|
131
|
+
if (typeof fn !== 'function') {
|
|
132
|
+
try { res.setHeader?.('allow', allow.join(', ')) } catch { /* fake res */ }
|
|
133
|
+
return json(res, 405, { ok: false, error: 'method-not-allowed', code: 'method-not-allowed' })
|
|
134
|
+
}
|
|
135
|
+
return await fn(req, res)
|
|
136
|
+
} catch (e) {
|
|
137
|
+
return json(res, 500, { ok: false, error: String(e?.message ?? e), code: 'handler-threw' })
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
route('GET', `${prefix}/api/bridge/status`, (req, res) => json(res, 200, service.descriptor()))
|
|
144
|
+
routeMethods(`${prefix}/api/bridge/sessions`, {
|
|
145
|
+
GET: async (req, res) => json(res, 200, await service.listSessions()),
|
|
146
|
+
POST: async (req, res) => {
|
|
147
|
+
const body = await readBody(req)
|
|
148
|
+
if (body.__parseError) return json(res, 400, { ok: false, error: 'bad-json' })
|
|
149
|
+
const out = await service.create({ cwd: body.cwd, title: body.title })
|
|
150
|
+
json(res, out.ok ? 201 : 400, out)
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
route('GET', `${prefix}/api/bridge/sessions/events`, async (req, res) => {
|
|
154
|
+
const q = queryOf(req)
|
|
155
|
+
const out = await service.events(q.get('sessionId') ?? '', { limit: q.get('limit') })
|
|
156
|
+
json(res, out.ok ? 200 : 404, out)
|
|
157
|
+
})
|
|
158
|
+
route('POST', `${prefix}/api/bridge/sessions/prompt`, async (req, res) => {
|
|
159
|
+
const body = await readBody(req)
|
|
160
|
+
if (body.__parseError) return json(res, 400, { ok: false, error: 'bad-json' })
|
|
161
|
+
const out = await service.send(body.sessionId, body.text)
|
|
162
|
+
json(res, out.ok ? 200 : 400, out)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
})
|
|
166
|
+
}
|