@whaletalk/dsh-codex-collab 0.1.2 → 0.1.3
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/harness/dsh-bridge.mjs
CHANGED
|
@@ -46,9 +46,11 @@ export async function apply(ctx) {
|
|
|
46
46
|
const sessionPersistence = ctx.get('sessionPersistence')
|
|
47
47
|
const workspaceRegistry = ctx.get('workspaceRegistry')
|
|
48
48
|
const llm = ctx.get('llm')
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
|
|
49
|
+
// 已有会话的读写入口。**必须惰性取**:@deepseek-ai/dsh-api-session-controller 的
|
|
50
|
+
// sessionController 由组合异步注册(DSH 启动日志里它先处于 waiting for services),
|
|
51
|
+
// 在 apply() 时捕获会永久拿到 undefined。每次使用时现取;缺失时返回稳定的
|
|
52
|
+
// session-controller-unavailable,而不是让整个插件挂掉。
|
|
53
|
+
const sessionControllerNow = () => ctx.get('sessionController')
|
|
52
54
|
|
|
53
55
|
// 清理历史版本可能泄漏的路由条目(重启前的动态插件、旧版本次插件)
|
|
54
56
|
try {
|
|
@@ -397,14 +399,19 @@ export async function apply(ctx) {
|
|
|
397
399
|
const sessionTargets = new Map()
|
|
398
400
|
|
|
399
401
|
const sessionSearch = async (query) => {
|
|
400
|
-
const
|
|
402
|
+
const controller = sessionControllerNow()
|
|
403
|
+
if (controller === undefined) {
|
|
404
|
+
throw Object.assign(new Error('sessionController service is not mounted in this profile'), { code: 'bridge/no-controller' })
|
|
405
|
+
}
|
|
406
|
+
const value = await controller.search({ query }, signalOf())
|
|
401
407
|
const items = value !== undefined && value !== null && Array.isArray(value.items) ? value.items : []
|
|
402
408
|
return items.filter((it) => it !== null && typeof it === 'object' && typeof it.sessionId === 'string' && it.sessionId !== '')
|
|
403
409
|
}
|
|
404
410
|
|
|
405
411
|
/** 只读解析:query → search 选一;sessionId → 直接用。任何不确定性都返回 reason。 */
|
|
406
412
|
const resolveSessionTarget = async (target) => {
|
|
407
|
-
|
|
413
|
+
const controller = sessionControllerNow()
|
|
414
|
+
if (controller === undefined) {
|
|
408
415
|
return { ok: false, reason: REASONS.CONTROLLER_UNAVAILABLE, message: 'sessionController service is not mounted in this profile', candidates: [] }
|
|
409
416
|
}
|
|
410
417
|
let hits = []
|
|
@@ -419,7 +426,7 @@ export async function apply(ctx) {
|
|
|
419
426
|
if (!picked.ok) return picked
|
|
420
427
|
let result
|
|
421
428
|
try {
|
|
422
|
-
result = await
|
|
429
|
+
result = await controller.resolveAgent(picked.sessionId)
|
|
423
430
|
} catch (e) {
|
|
424
431
|
return { ok: false, reason: mapControllerError(e), message: errText(e), candidates: picked.candidates }
|
|
425
432
|
}
|
|
@@ -478,7 +485,9 @@ export async function apply(ctx) {
|
|
|
478
485
|
record.watcher = 'follow'
|
|
479
486
|
void (async () => {
|
|
480
487
|
try {
|
|
481
|
-
const
|
|
488
|
+
const controllerRef = sessionControllerNow()
|
|
489
|
+
if (controllerRef === undefined) throw new Error('sessionController is not mounted')
|
|
490
|
+
const stream = controllerRef.follow({ address: { kind: 'session', sessionId: record.sessionId } }, controller.signal)
|
|
482
491
|
for await (const frame of stream) {
|
|
483
492
|
if (record.settled) break
|
|
484
493
|
if (frame === null || typeof frame !== 'object') continue
|
|
@@ -552,7 +561,17 @@ export async function apply(ctx) {
|
|
|
552
561
|
sessionTargets.set(resolved.sessionId, record)
|
|
553
562
|
|
|
554
563
|
try {
|
|
555
|
-
|
|
564
|
+
const controllerRef = sessionControllerNow()
|
|
565
|
+
if (controllerRef === undefined) {
|
|
566
|
+
sessionTargets.delete(resolved.sessionId)
|
|
567
|
+
record.settled = true
|
|
568
|
+
task.status = 'error'
|
|
569
|
+
task.error = 'sessionController service is not mounted in this profile'
|
|
570
|
+
task.reason = REASONS.CONTROLLER_UNAVAILABLE
|
|
571
|
+
pushTask(task.taskId, 'error', { error: task.error, reason: task.reason, target: { kind: 'session', sessionId: resolved.sessionId } })
|
|
572
|
+
return { task, failed: { reason: task.reason, message: task.error, candidates: [] } }
|
|
573
|
+
}
|
|
574
|
+
await controllerRef.prompt({
|
|
556
575
|
requestId: task.taskId,
|
|
557
576
|
sessionId: resolved.sessionId,
|
|
558
577
|
mode: deliver,
|
|
@@ -720,7 +739,8 @@ export async function apply(ctx) {
|
|
|
720
739
|
}
|
|
721
740
|
|
|
722
741
|
// 完成信号:会话从 running 回到 idle 即这一轮结束(follow 只负责流式进度)。
|
|
723
|
-
|
|
742
|
+
// 订阅的是 ctx 上的事件,与服务何时注册无关,所以不设门禁。
|
|
743
|
+
{
|
|
724
744
|
ctx.effect(() => {
|
|
725
745
|
const off = ctx.on('api-session/status', (sessionId, running) => {
|
|
726
746
|
const record = sessionTargets.get(sessionId)
|
|
@@ -819,8 +839,9 @@ export async function apply(ctx) {
|
|
|
819
839
|
} else {
|
|
820
840
|
parsed = qsOf(req)
|
|
821
841
|
}
|
|
822
|
-
|
|
823
|
-
|
|
842
|
+
const controller = sessionControllerNow()
|
|
843
|
+
if (controller === undefined) {
|
|
844
|
+
sendJson(res, 503, { error: 'sessionController service is not mounted yet (it registers asynchronously after startup; retry shortly)', reason: REASONS.CONTROLLER_UNAVAILABLE })
|
|
824
845
|
return
|
|
825
846
|
}
|
|
826
847
|
const query = typeof parsed.query === 'string' && parsed.query.trim() !== '' ? parsed.query.trim() : undefined
|
|
@@ -849,7 +870,7 @@ export async function apply(ctx) {
|
|
|
849
870
|
// 只对前 5 条探能力:逐条 stat 会很贵,这里保持有界。
|
|
850
871
|
for (const item of items.slice(0, 5)) {
|
|
851
872
|
try {
|
|
852
|
-
const r = await
|
|
873
|
+
const r = await controller.resolveAgent(item.sessionId)
|
|
853
874
|
const failed = r !== undefined && r !== null && r.error !== undefined
|
|
854
875
|
item.agentAvailable = !failed
|
|
855
876
|
if (failed) item.problem = mapControllerError(r.error)
|
|
@@ -914,7 +935,9 @@ export async function apply(ctx) {
|
|
|
914
935
|
}
|
|
915
936
|
let cancelled = false
|
|
916
937
|
try {
|
|
917
|
-
|
|
938
|
+
const controller = sessionControllerNow()
|
|
939
|
+
if (controller === undefined) throw new Error('sessionController service is not mounted in this profile')
|
|
940
|
+
await controller.cancel({ sessionId: task.target.sessionId }, signalOf())
|
|
918
941
|
cancelled = true
|
|
919
942
|
} catch (e) {
|
|
920
943
|
console.error('[dsh-bridge] session cancel failed:', errText(e).slice(0, 200))
|
|
@@ -971,7 +994,7 @@ export async function apply(ctx) {
|
|
|
971
994
|
lanes: Array.from(buckets.keys()),
|
|
972
995
|
reviewers: Array.from(reviewerBuckets.keys()),
|
|
973
996
|
sessions: Array.from(sessionTargets.keys()),
|
|
974
|
-
sessionController:
|
|
997
|
+
sessionController: sessionControllerNow() !== undefined,
|
|
975
998
|
tasks: tasks.slice(-20).map((t) => ({ taskId: t.taskId, kind: t.kind, status: t.status, cwd: t.cwd, lane: t.lane, target: t.target, deliver: t.deliver })),
|
|
976
999
|
})
|
|
977
1000
|
},
|
|
@@ -982,7 +1005,15 @@ export async function apply(ctx) {
|
|
|
982
1005
|
path: '/api/dsh-bridge/debug',
|
|
983
1006
|
handler: async (req, res) => {
|
|
984
1007
|
if (!isLoopback(req)) { res.writeHead(403); res.end('forbidden'); return }
|
|
985
|
-
|
|
1008
|
+
// services 是现场探测(不是 apply 时的快照):sessionController 可能晚于本插件注册,
|
|
1009
|
+
// 这份清单能在"接不上会话"时直接告诉我们到底缺哪个服务。
|
|
1010
|
+
const probe = (name) => { try { return ctx.get(name) !== undefined } catch (e) { return false } }
|
|
1011
|
+
const out = {
|
|
1012
|
+
pending: 0, historyCount: history.length, presets: [], providers: [], buckets: [], reviewers: [],
|
|
1013
|
+
sessionController: probe('sessionController'),
|
|
1014
|
+
services: Object.fromEntries(['sessionController', 'connection', 'sessionQuery', 'sessionPersistence', 'workspaceRegistry', 'agents', 'subagents', 'agentPresets', 'timer', 'llm'].map((n) => [n, probe(n)])),
|
|
1015
|
+
sessionTargets: [], models: knownModels, modelProvider, modelListError, workspaces: [],
|
|
1016
|
+
}
|
|
986
1017
|
out.pending = tasks.filter((t) => t.status === 'accepted' || t.status === 'running').length
|
|
987
1018
|
try { out.providers = subagents.list() } catch (e) { out.providers = ['list failed: ' + errText(e)] }
|
|
988
1019
|
if (agentPresets !== undefined) {
|
|
@@ -1124,5 +1155,5 @@ export async function apply(ctx) {
|
|
|
1124
1155
|
sessionTargets.clear()
|
|
1125
1156
|
})
|
|
1126
1157
|
|
|
1127
|
-
console.log('[dsh-bridge] persistent gateway ready (host composition): task(new|session) · sessions · review · tasks · cancel · status · debug · WS | sessionController=' + (
|
|
1158
|
+
console.log('[dsh-bridge] persistent gateway ready (host composition): task(new|session) · sessions · review · tasks · cancel · status · debug · WS | sessionController=' + (sessionControllerNow() !== undefined ? 'yes' : 'late-bound (registers asynchronously)'))
|
|
1128
1159
|
}
|
package/mcp/dsh-mcp.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// 客户端(Secure MCP Tunnel / cloudflared)桥接给 ChatGPT 等远程产品。
|
|
7
7
|
const GATEWAY = process.env.DSH_BRIDGE_URL || 'http://127.0.0.1:3080'
|
|
8
8
|
const SERVER_NAME = 'dsh-bridge'
|
|
9
|
-
const SERVER_VERSION = '0.1.
|
|
9
|
+
const SERVER_VERSION = '0.1.3'
|
|
10
10
|
const PROTOCOL_VERSION = '2024-11-05'
|
|
11
11
|
|
|
12
12
|
const log = (...args) => console.error('[dsh-mcp]', ...args)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whaletalk/dsh-codex-collab",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Codex × DeepSeek Harness 双向编码协作:Codex 主导派活与验收,DeepSeek Harness 编码子代理在共享工作目录执行并回传,独立评审子代理实际跑通构建/测试后出具报告。提供 DSH 宿主网关插件、零依赖 MCP 服务器与 Codex 技能三件套。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|