@twinsearth/fang-dsh-plugin 0.0.0-stage → 3.3.4

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.
Files changed (144) hide show
  1. package/CLA.md +79 -0
  2. package/LICENSE-COMMERCIAL +78 -0
  3. package/LICENSE-CORE +661 -0
  4. package/LICENSE-SDK +21 -0
  5. package/LICENSES/cordis-MIT.txt +39 -0
  6. package/NOTICE +95 -0
  7. package/README.md +158 -2
  8. package/client.js +7287 -0
  9. package/cordis.patch.yml +163 -0
  10. package/defaults/umbrella-rules.yaml +113 -0
  11. package/docs/CONTRACT-INTEROP.md +77 -0
  12. package/docs/CRASH-SAFETY.md +69 -0
  13. package/docs/DECISION-RELAY.md +46 -0
  14. package/docs/DSH-ARCHITECTURE-MAP.md +73 -0
  15. package/docs/FORK-SEED.md +64 -0
  16. package/docs/MAIN-CITY-GATE.md +58 -0
  17. package/docs/OVERLAY-MASTER-SWITCH.md +35 -0
  18. package/docs/PACKAGE-RENAME-v3.3.3.md +40 -0
  19. package/docs/PM-DECISION-POLICY.md +61 -0
  20. package/docs/WORKFLOW-UI.md +86 -0
  21. package/icon.svg +8 -0
  22. package/locale/en.json +6 -0
  23. package/locale/zh.json +6 -0
  24. package/package.json +71 -4
  25. package/sdk/README.md +65 -0
  26. package/sdk/examples/status-badge/README.md +16 -0
  27. package/sdk/examples/status-badge/client.js +60 -0
  28. package/sdk/examples/status-badge/cordis.patch.yml +7 -0
  29. package/sdk/examples/status-badge/index.js +12 -0
  30. package/sdk/examples/status-badge/package.json +24 -0
  31. package/sdk/templates/plugin/README.md +36 -0
  32. package/sdk/templates/plugin/client.js +35 -0
  33. package/sdk/templates/plugin/cordis.patch.yml +8 -0
  34. package/sdk/templates/plugin/icon.svg +5 -0
  35. package/sdk/templates/plugin/index.js +23 -0
  36. package/sdk/templates/plugin/locale/en.json +6 -0
  37. package/sdk/templates/plugin/package.json +36 -0
  38. package/src/compose.mjs +67 -0
  39. package/src/contract/VENDOR.json +16 -0
  40. package/src/contract/client.js +93 -0
  41. package/src/contract/constants.js +94 -0
  42. package/src/contract/envelope.js +83 -0
  43. package/src/contract/index.js +20 -0
  44. package/src/contract/negotiate.js +106 -0
  45. package/src/contract/relay.js +223 -0
  46. package/src/contract/server.js +77 -0
  47. package/src/contract/transports.js +319 -0
  48. package/src/index.mjs +11 -0
  49. package/src/kernel/core.mjs +100 -0
  50. package/src/kernel/extensions.mjs +61 -0
  51. package/src/kernel/fsx.mjs +349 -0
  52. package/src/kernel/index.mjs +21 -0
  53. package/src/kernel/ledger.mjs +94 -0
  54. package/src/kernel/paths.mjs +182 -0
  55. package/src/kernel/role.mjs +68 -0
  56. package/src/kernel/rounds.mjs +172 -0
  57. package/src/kernel/schema.mjs +5 -0
  58. package/src/kernel/types.mjs +266 -0
  59. package/src/kernel/vendor/cosmokit.mjs +421 -0
  60. package/src/kernel/vendor/schemastery.mjs +638 -0
  61. package/src/lib/adapter-client.mjs +383 -0
  62. package/src/lib/adapter-protocol.mjs +243 -0
  63. package/src/lib/ai-secret-store.mjs +202 -0
  64. package/src/lib/boot-guard.mjs +186 -0
  65. package/src/lib/branch-store.mjs +84 -0
  66. package/src/lib/branch.mjs +74 -0
  67. package/src/lib/bridge-client.mjs +133 -0
  68. package/src/lib/broker-adapter.mjs +67 -0
  69. package/src/lib/broker.mjs +45 -0
  70. package/src/lib/business-producers.mjs +109 -0
  71. package/src/lib/command-cache.mjs +209 -0
  72. package/src/lib/compat.mjs +170 -0
  73. package/src/lib/composer-journal.mjs +128 -0
  74. package/src/lib/container/index.mjs +806 -0
  75. package/src/lib/decision-events.mjs +129 -0
  76. package/src/lib/decision-ingest-client.mjs +41 -0
  77. package/src/lib/decision-outbox.mjs +129 -0
  78. package/src/lib/decision-policy.mjs +52 -0
  79. package/src/lib/default-essence.mjs +61 -0
  80. package/src/lib/desktop-launch.mjs +138 -0
  81. package/src/lib/essence.mjs +1211 -0
  82. package/src/lib/fork-client.mjs +410 -0
  83. package/src/lib/fork-config-lock.mjs +166 -0
  84. package/src/lib/fork-seed.mjs +304 -0
  85. package/src/lib/fork-sessions.mjs +183 -0
  86. package/src/lib/fork-supervision-settings.mjs +58 -0
  87. package/src/lib/fork-supervisor.mjs +122 -0
  88. package/src/lib/fs-guard.mjs +28 -0
  89. package/src/lib/http-auth.mjs +162 -0
  90. package/src/lib/idle-audit.mjs +178 -0
  91. package/src/lib/interaction-poke.mjs +61 -0
  92. package/src/lib/main-gate.mjs +579 -0
  93. package/src/lib/mirror-etag.mjs +92 -0
  94. package/src/lib/mirror-official-source.mjs +93 -0
  95. package/src/lib/mirror.mjs +478 -0
  96. package/src/lib/mother-link.mjs +156 -0
  97. package/src/lib/mutex.mjs +31 -0
  98. package/src/lib/permissions.mjs +115 -0
  99. package/src/lib/pnpm-store.mjs +189 -0
  100. package/src/lib/preferences.mjs +212 -0
  101. package/src/lib/producer-contract.mjs +162 -0
  102. package/src/lib/reflow.mjs +41 -0
  103. package/src/lib/remote.mjs +408 -0
  104. package/src/lib/restart-contract.mjs +752 -0
  105. package/src/lib/roots.mjs +30 -0
  106. package/src/lib/rule-registry.mjs +1127 -0
  107. package/src/lib/runtime-capture.mjs +307 -0
  108. package/src/lib/semaphore.mjs +34 -0
  109. package/src/lib/session-bundle.mjs +138 -0
  110. package/src/lib/session-format.mjs +672 -0
  111. package/src/lib/sse-parser.mjs +102 -0
  112. package/src/lib/ssrf-guard.mjs +170 -0
  113. package/src/lib/status-store.mjs +257 -0
  114. package/src/lib/task-handle.mjs +622 -0
  115. package/src/lib/ui-actions.mjs +55 -0
  116. package/src/lib/umbrella.mjs +1661 -0
  117. package/src/lib/upgrade-coordinator.mjs +107 -0
  118. package/src/lib/upgrade-status.mjs +43 -0
  119. package/src/plugins/adapter.mjs +1468 -0
  120. package/src/plugins/approver.mjs +1210 -0
  121. package/src/plugins/auditor.mjs +330 -0
  122. package/src/plugins/buffer-keeper.mjs +522 -0
  123. package/src/plugins/decision-center.mjs +2581 -0
  124. package/src/plugins/forker.mjs +1610 -0
  125. package/src/plugins/healer.mjs +702 -0
  126. package/src/plugins/hot-reloader.mjs +274 -0
  127. package/src/plugins/imports.mjs +247 -0
  128. package/src/plugins/orchestrator.mjs +3938 -0
  129. package/src/plugins/restart-guard.mjs +1022 -0
  130. package/src/plugins/router.mjs +714 -0
  131. package/src/plugins/scorer.mjs +661 -0
  132. package/src/plugins/session-bridge.mjs +166 -0
  133. package/src/plugins/session-catalog.mjs +771 -0
  134. package/src/plugins/session-router.mjs +125 -0
  135. package/src/plugins/session-sync.mjs +171 -0
  136. package/src/plugins/umbrella.mjs +419 -0
  137. package/tools/boot-guard.mjs +95 -0
  138. package/tools/decision-ingest.mjs +53 -0
  139. package/tools/desktop-launch.mjs +71 -0
  140. package/tools/fangctl.mjs +408 -0
  141. package/tools/gate/main-city.mjs +204 -0
  142. package/tools/mcp/server.mjs +121 -0
  143. package/tools/mcp/tools.mjs +65 -0
  144. 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
+ }