dsh-bailinghub 0.2.0 → 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.
- package/CHANGELOG.md +25 -0
- package/PRIVACY.md +9 -4
- package/README.md +83 -31
- package/SECURITY.md +22 -4
- package/docs/AGENT_CLIENT_CONTRACT.md +74 -12
- package/docs/COMPATIBILITY.md +28 -12
- package/docs/GETTING_STARTED.md +92 -0
- package/docs/GETTING_STARTED.zh-CN.md +83 -0
- package/docs/MIGRATION_VNEXT.md +39 -17
- package/docs/PROJECT_BOUNDARIES.md +3 -2
- package/docs/README.zh-CN.md +69 -27
- package/lib/runtime.js +423 -27
- package/lib/transport.js +5 -1
- package/package.json +14 -4
package/lib/runtime.js
CHANGED
|
@@ -31,6 +31,7 @@ const INVOCATION_STATES = new Set([
|
|
|
31
31
|
const DEFAULT_RESUME_POLL_INTERVAL_MILLISECONDS = 2_000
|
|
32
32
|
const DEFAULT_RESUME_MAX_WAIT_MILLISECONDS = 120_000
|
|
33
33
|
const DEFAULT_RESUME_MAX_ATTEMPTS = 60
|
|
34
|
+
export const TESTED_DSH_HOST_RELEASES = Object.freeze(['0.1.0-rc.7', '0.1.1-rc.2'])
|
|
34
35
|
const SENSITIVE_TEXT = [
|
|
35
36
|
/\bBearer\s+[A-Za-z0-9._~+\/-]+=*/gi,
|
|
36
37
|
/([?&](?:access_token|code|credential|secret|token)=)[^&#\s]+/gi,
|
|
@@ -63,6 +64,116 @@ function commandText(value) {
|
|
|
63
64
|
return text || '(no additional details)'
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
function loginCommandText(value) {
|
|
68
|
+
if (
|
|
69
|
+
value === null ||
|
|
70
|
+
typeof value !== 'object' ||
|
|
71
|
+
Array.isArray(value) ||
|
|
72
|
+
value.state !== 'authorized' ||
|
|
73
|
+
value.cleanupRequired !== true
|
|
74
|
+
) {
|
|
75
|
+
return commandText(value)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const cleanupNames = Array.isArray(value.cleanupConnections)
|
|
79
|
+
? value.cleanupConnections
|
|
80
|
+
.map((connection) => redactText(
|
|
81
|
+
connection?.connectionName ?? connection?.connectionKey ?? '',
|
|
82
|
+
).replace(/\s+/gu, ' ').trim())
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
: []
|
|
85
|
+
const pending = cleanupNames.length > 0
|
|
86
|
+
? ` Pending cleanup: ${cleanupNames.join(', ')}.`
|
|
87
|
+
: ''
|
|
88
|
+
return [
|
|
89
|
+
'Authorization succeeded. The selected connection remains authorized.',
|
|
90
|
+
`WARNING: an existing connection still requires cleanup.${pending}`,
|
|
91
|
+
'Do not authorize again. Use /bailinghub connections list, then /bailinghub connections remove <name-or-key> to retry cleanup.',
|
|
92
|
+
commandText(value),
|
|
93
|
+
].join('\n')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function parseCommandArguments(value) {
|
|
97
|
+
const tokens = []
|
|
98
|
+
let current = ''
|
|
99
|
+
let quote = ''
|
|
100
|
+
let escaped = false
|
|
101
|
+
let started = false
|
|
102
|
+
for (const character of String(value ?? '')) {
|
|
103
|
+
if (escaped) {
|
|
104
|
+
current += character
|
|
105
|
+
escaped = false
|
|
106
|
+
started = true
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
if (character === '\\') {
|
|
110
|
+
escaped = true
|
|
111
|
+
started = true
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
if (quote) {
|
|
115
|
+
if (character === quote) quote = ''
|
|
116
|
+
else current += character
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
if (character === '"' || character === "'") {
|
|
120
|
+
quote = character
|
|
121
|
+
started = true
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
if (/\s/u.test(character)) {
|
|
125
|
+
if (started) {
|
|
126
|
+
tokens.push(current)
|
|
127
|
+
current = ''
|
|
128
|
+
started = false
|
|
129
|
+
}
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
current += character
|
|
133
|
+
started = true
|
|
134
|
+
}
|
|
135
|
+
if (quote || escaped) throw new TypeError('BailingHub command contains an unfinished quote or escape')
|
|
136
|
+
if (started) tokens.push(current)
|
|
137
|
+
return tokens
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function inspectHostContract(ctx) {
|
|
141
|
+
const capabilities = {
|
|
142
|
+
events: typeof ctx?.on === 'function',
|
|
143
|
+
commands: typeof ctx?.commands?.register === 'function',
|
|
144
|
+
effects: typeof ctx?.effect === 'function',
|
|
145
|
+
services: typeof ctx?.provide === 'function',
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
ok: Object.values(capabilities).every(Boolean),
|
|
149
|
+
capabilities,
|
|
150
|
+
host_contract_tested_releases: [...TESTED_DSH_HOST_RELEASES],
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function doctorText(report) {
|
|
155
|
+
const label = (check) => (check?.ok ? 'PASS' : check?.skipped ? 'SKIP' : 'FAIL')
|
|
156
|
+
const configuration = report.checks.configuration
|
|
157
|
+
const authorization = report.checks.agent_session
|
|
158
|
+
const workspace = report.checks.workspace
|
|
159
|
+
const lines = [
|
|
160
|
+
'BailingHub Agent Client doctor',
|
|
161
|
+
`Overall: ${report.ok ? 'PASS' : 'FAIL'}`,
|
|
162
|
+
`- Configuration: ${label(configuration)}`,
|
|
163
|
+
`- DSH host contract: ${label(report.checks.dsh_host)} (host-contract tests: ${TESTED_DSH_HOST_RELEASES.join(', ')})`,
|
|
164
|
+
`- Agent Client SDK: ${label(report.checks.sdk)}`,
|
|
165
|
+
'- Connection selector: connectionName is a local user-selected label, not a business identity claim',
|
|
166
|
+
'- Business identity: the single business authorization page handles sign-in, account switching, and tenant selection',
|
|
167
|
+
`- Authorization: ${label(authorization)} (${authorization.state})`,
|
|
168
|
+
`- Workspace: ${label(workspace)}${Number.isSafeInteger(workspace.authorized_count) ? ` (${workspace.authorized_count} authorized)` : ''}`,
|
|
169
|
+
]
|
|
170
|
+
if (configuration.invalid?.length) {
|
|
171
|
+
lines.push(`- Invalid configuration fields: ${configuration.invalid.join(', ')}`)
|
|
172
|
+
}
|
|
173
|
+
if (report.next_action) lines.push(`Next action: ${report.next_action}`)
|
|
174
|
+
return lines.join('\n')
|
|
175
|
+
}
|
|
176
|
+
|
|
66
177
|
function redactText(value) {
|
|
67
178
|
let text = String(value ?? '')
|
|
68
179
|
for (const pattern of SENSITIVE_TEXT) {
|
|
@@ -249,6 +360,46 @@ export function normalizeConfig(config = {}) {
|
|
|
249
360
|
return { ...normalized, valid: invalid.length === 0, invalid }
|
|
250
361
|
}
|
|
251
362
|
|
|
363
|
+
function currentConnectionDefaults(value) {
|
|
364
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined
|
|
365
|
+
|
|
366
|
+
const currentConnectionKey = typeof value.currentConnectionKey === 'string'
|
|
367
|
+
? value.currentConnectionKey.trim()
|
|
368
|
+
: ''
|
|
369
|
+
if (!currentConnectionKey || currentConnectionKey.length > 128 || !Array.isArray(value.connections)) {
|
|
370
|
+
return undefined
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const selected = value.connections.find((entry) =>
|
|
374
|
+
entry !== null &&
|
|
375
|
+
typeof entry === 'object' &&
|
|
376
|
+
!Array.isArray(entry) &&
|
|
377
|
+
typeof entry.connectionKey === 'string' &&
|
|
378
|
+
entry.connectionKey.trim() === currentConnectionKey,
|
|
379
|
+
)
|
|
380
|
+
if (!selected) return undefined
|
|
381
|
+
|
|
382
|
+
const connectionName = typeof selected.connectionName === 'string'
|
|
383
|
+
? selected.connectionName.trim()
|
|
384
|
+
: ''
|
|
385
|
+
const normalized = normalizeConfig({
|
|
386
|
+
hubUrl: selected.hubUrl,
|
|
387
|
+
clientAppId: selected.clientAppId,
|
|
388
|
+
workspace: selected.workspace,
|
|
389
|
+
connectionName: connectionName || currentConnectionKey,
|
|
390
|
+
})
|
|
391
|
+
return normalized.valid ? normalized : undefined
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function connectionRegistryIsEmpty(value) {
|
|
395
|
+
return value !== null &&
|
|
396
|
+
typeof value === 'object' &&
|
|
397
|
+
!Array.isArray(value) &&
|
|
398
|
+
Array.isArray(value.connections) &&
|
|
399
|
+
value.connections.length === 0 &&
|
|
400
|
+
(value.currentConnectionKey === null || value.currentConnectionKey === undefined)
|
|
401
|
+
}
|
|
402
|
+
|
|
252
403
|
function visibleText(message) {
|
|
253
404
|
if (!Array.isArray(message?.content)) return ''
|
|
254
405
|
return message.content
|
|
@@ -591,7 +742,14 @@ export class BailingHubAgentClientRuntime {
|
|
|
591
742
|
this.config = normalizeConfig(config)
|
|
592
743
|
this.getTransport = getTransport
|
|
593
744
|
this.recovery = normalizeRecoveryOptions(recoveryOptions)
|
|
745
|
+
this.hostContract = inspectHostContract(ctx)
|
|
594
746
|
this.defaultWorkspace = this.config.workspace
|
|
747
|
+
this.defaultConnectionName = this.config.connectionName
|
|
748
|
+
this.defaultHubUrl = this.config.hubUrl
|
|
749
|
+
this.defaultClientAppId = this.config.clientAppId
|
|
750
|
+
this.connectionReady = this.config.valid
|
|
751
|
+
this.connectionBootstrap = undefined
|
|
752
|
+
this.pendingClaimsByAgent = new WeakMap()
|
|
595
753
|
this.states = new Set()
|
|
596
754
|
this.statesByAgent = new WeakMap()
|
|
597
755
|
this.statesBySessionId = new Map()
|
|
@@ -607,8 +765,8 @@ export class BailingHubAgentClientRuntime {
|
|
|
607
765
|
this.ctx.on('session/disposed', (session) => void this.disposeSession(session.id))
|
|
608
766
|
const registerCommand = () => this.ctx.commands.register({
|
|
609
767
|
name: 'bailinghub',
|
|
610
|
-
description: 'authorize, inspect, or resync
|
|
611
|
-
input: { hint: 'login|status|logout|workspaces|use <workspace>|sync' },
|
|
768
|
+
description: 'authorize, diagnose, inspect, switch, or resync BailingHub Agent Client connections',
|
|
769
|
+
input: { hint: 'doctor|connections list|add|use|remove|login|status|logout|workspaces|use <workspace>|sync' },
|
|
612
770
|
handler: (invocation) => this.handleCommand(invocation),
|
|
613
771
|
})
|
|
614
772
|
if (typeof this.ctx.effect === 'function') this.ctx.effect(registerCommand)
|
|
@@ -618,12 +776,47 @@ export class BailingHubAgentClientRuntime {
|
|
|
618
776
|
}
|
|
619
777
|
|
|
620
778
|
async handleCommand(invocation) {
|
|
779
|
+
await this.restoreCurrentConnection()
|
|
621
780
|
const input = invocation.rawInput.trim()
|
|
622
|
-
const [action, ...rest] = input ? input.split(/\s+/u) : ['status']
|
|
623
781
|
try {
|
|
782
|
+
const [action = 'status', ...rest] = parseCommandArguments(input)
|
|
624
783
|
switch (action) {
|
|
784
|
+
case 'connections': {
|
|
785
|
+
const [operation = 'list', ...parameters] = rest
|
|
786
|
+
if (operation === 'list' && parameters.length === 0) {
|
|
787
|
+
return { kind: 'success', text: commandText(await this.connectionsList()) }
|
|
788
|
+
}
|
|
789
|
+
if (operation === 'add' && parameters.length === 4) {
|
|
790
|
+
const [connectionName, hubUrl, clientAppId, workspace] = parameters
|
|
791
|
+
const result = await this.connectionsAdd({ connectionName, hubUrl, clientAppId, workspace })
|
|
792
|
+
const action = result?.state === 'selected' ? 'selected' : 'registered and selected'
|
|
793
|
+
return {
|
|
794
|
+
kind: 'success',
|
|
795
|
+
text: `Connection instance ${action} for new sessions: ${connectionName}\n${commandText(result)}`,
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (operation === 'use' && parameters.length === 1) {
|
|
799
|
+
const result = await this.connectionsUse(parameters[0])
|
|
800
|
+
return {
|
|
801
|
+
kind: 'success',
|
|
802
|
+
text: `Connection selected for new sessions: ${parameters[0]}\n${commandText(result)}`,
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
if (operation === 'remove' && parameters.length === 1) {
|
|
806
|
+
const result = await this.connectionsRemove(parameters[0])
|
|
807
|
+
return { kind: 'success', text: commandText(result) }
|
|
808
|
+
}
|
|
809
|
+
return {
|
|
810
|
+
kind: 'error',
|
|
811
|
+
text: 'Usage: /bailinghub connections list|add <name> <hub-url> <client-app-id> <workspace>|use <name-or-key>|remove <name-or-key>',
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
case 'doctor': {
|
|
815
|
+
const result = await this.doctor()
|
|
816
|
+
return { kind: result.ok ? 'success' : 'error', text: doctorText(result) }
|
|
817
|
+
}
|
|
625
818
|
case 'login':
|
|
626
|
-
return { kind: 'success', text:
|
|
819
|
+
return { kind: 'success', text: loginCommandText(await this.login()) }
|
|
627
820
|
case 'status':
|
|
628
821
|
return { kind: 'success', text: commandText(await this.status()) }
|
|
629
822
|
case 'logout':
|
|
@@ -648,7 +841,7 @@ export class BailingHubAgentClientRuntime {
|
|
|
648
841
|
default:
|
|
649
842
|
return {
|
|
650
843
|
kind: 'error',
|
|
651
|
-
text: 'Usage: /bailinghub login|status|logout|workspaces|use <workspace>|sync',
|
|
844
|
+
text: 'Usage: /bailinghub doctor|connections list|add|use|remove|login|status|logout|workspaces|use <workspace>|sync',
|
|
652
845
|
}
|
|
653
846
|
}
|
|
654
847
|
} catch (error) {
|
|
@@ -663,8 +856,13 @@ export class BailingHubAgentClientRuntime {
|
|
|
663
856
|
let state = this.statesByAgent.get(agent)
|
|
664
857
|
if (state) return state
|
|
665
858
|
|
|
666
|
-
state = createState(agent, this.defaultWorkspace, this.
|
|
667
|
-
|
|
859
|
+
state = createState(agent, this.defaultWorkspace, this.defaultConnectionName)
|
|
860
|
+
const pendingClaims = this.pendingClaimsByAgent.get(agent)
|
|
861
|
+
if (pendingClaims) {
|
|
862
|
+
state.pendingClaims = pendingClaims
|
|
863
|
+
this.pendingClaimsByAgent.delete(agent)
|
|
864
|
+
}
|
|
865
|
+
if (!this.connectionReady) {
|
|
668
866
|
state.status = {
|
|
669
867
|
state: 'unconfigured',
|
|
670
868
|
missing: [...this.config.invalid],
|
|
@@ -678,17 +876,23 @@ export class BailingHubAgentClientRuntime {
|
|
|
678
876
|
|
|
679
877
|
onInboxClaimed({ agent, message, turn }) {
|
|
680
878
|
if (message?.source?.kind !== 'user') return
|
|
681
|
-
const state = this.
|
|
879
|
+
const state = this.statesByAgent.get(agent)
|
|
880
|
+
let pendingClaims = state?.pendingClaims ?? this.pendingClaimsByAgent.get(agent)
|
|
881
|
+
if (!pendingClaims) {
|
|
882
|
+
pendingClaims = new Map()
|
|
883
|
+
this.pendingClaimsByAgent.set(agent, pendingClaims)
|
|
884
|
+
}
|
|
885
|
+
const sessionId = String(agent.session.id)
|
|
682
886
|
const rawMessageId = String(
|
|
683
|
-
message.id ?? messageAlias('source',
|
|
887
|
+
message.id ?? messageAlias('source', sessionId, String(turn), visibleText(message)),
|
|
684
888
|
)
|
|
685
889
|
const claim = {
|
|
686
|
-
id: messageAlias('user',
|
|
890
|
+
id: messageAlias('user', sessionId, String(turn), rawMessageId),
|
|
687
891
|
text: visibleText(message),
|
|
688
892
|
}
|
|
689
|
-
const claims =
|
|
893
|
+
const claims = pendingClaims.get(turn) ?? []
|
|
690
894
|
claims.push(claim)
|
|
691
|
-
|
|
895
|
+
pendingClaims.set(turn, claims)
|
|
692
896
|
}
|
|
693
897
|
|
|
694
898
|
async onAssemble(assembly, context, next) {
|
|
@@ -696,6 +900,7 @@ export class BailingHubAgentClientRuntime {
|
|
|
696
900
|
const agent = context?.agent
|
|
697
901
|
if (!agent) return base
|
|
698
902
|
|
|
903
|
+
await this.restoreCurrentConnection()
|
|
699
904
|
const state = this.stateFor(agent)
|
|
700
905
|
const turn = Math.max(...state.pendingClaims.keys(), -1)
|
|
701
906
|
let removedNames = new Set()
|
|
@@ -712,7 +917,7 @@ export class BailingHubAgentClientRuntime {
|
|
|
712
917
|
await this.clearActiveTools(state)
|
|
713
918
|
if (state.currentRun?.status === 'active') {
|
|
714
919
|
state.currentRun.status = 'degraded'
|
|
715
|
-
state.currentRun.degradedReason = 'DSH Code Mode is not supported by this
|
|
920
|
+
state.currentRun.degradedReason = 'DSH Code Mode is not supported by this plugin version'
|
|
716
921
|
}
|
|
717
922
|
state.status = { state: 'degraded', reason: 'unsupported DSH tool presentation mode' }
|
|
718
923
|
}
|
|
@@ -738,7 +943,7 @@ export class BailingHubAgentClientRuntime {
|
|
|
738
943
|
const removedNames = new Set(state.knownToolNames)
|
|
739
944
|
await this.clearActiveTools(state)
|
|
740
945
|
|
|
741
|
-
if (!this.
|
|
946
|
+
if (!this.connectionReady) {
|
|
742
947
|
const run = { turn, status: 'unconfigured', degradedReason: 'configuration required' }
|
|
743
948
|
state.runs.set(turn, run)
|
|
744
949
|
state.currentRun = run
|
|
@@ -1101,7 +1306,7 @@ export class BailingHubAgentClientRuntime {
|
|
|
1101
1306
|
|
|
1102
1307
|
if (!run || run.status !== 'active') {
|
|
1103
1308
|
tools = tools.filter((tool) => !state.knownToolNames.has(tool.name))
|
|
1104
|
-
const reason = run?.degradedReason ?? (this.
|
|
1309
|
+
const reason = run?.degradedReason ?? (this.connectionReady ? 'no active user turn' : 'configuration required')
|
|
1105
1310
|
sections = replaceNamed(sections, degradedSection(reason))
|
|
1106
1311
|
return { ...base, sections, contexts, tools }
|
|
1107
1312
|
}
|
|
@@ -1234,34 +1439,162 @@ export class BailingHubAgentClientRuntime {
|
|
|
1234
1439
|
return { synced, pending }
|
|
1235
1440
|
}
|
|
1236
1441
|
|
|
1442
|
+
async restoreCurrentConnection() {
|
|
1443
|
+
if (!this.connectionBootstrap) {
|
|
1444
|
+
this.connectionBootstrap = this.restoreCurrentConnectionOnce()
|
|
1445
|
+
}
|
|
1446
|
+
return this.connectionBootstrap
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
async restoreCurrentConnectionOnce() {
|
|
1450
|
+
if (!this.config.valid) return false
|
|
1451
|
+
|
|
1452
|
+
try {
|
|
1453
|
+
const selected = currentConnectionDefaults(await this.connectionsList())
|
|
1454
|
+
if (!selected) return false
|
|
1455
|
+
this.applyConnectionDefaults(selected)
|
|
1456
|
+
return true
|
|
1457
|
+
} catch {
|
|
1458
|
+
return false
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
applyConnectionDefaults(selected) {
|
|
1463
|
+
this.defaultConnectionName = selected.connectionName
|
|
1464
|
+
this.defaultWorkspace = selected.workspace
|
|
1465
|
+
this.defaultHubUrl = selected.hubUrl
|
|
1466
|
+
this.defaultClientAppId = selected.clientAppId
|
|
1467
|
+
this.connectionReady = true
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1237
1470
|
async login(options = {}) {
|
|
1238
|
-
|
|
1239
|
-
hubUrl: this.
|
|
1240
|
-
clientAppId: this.
|
|
1471
|
+
const result = await this.callConnection('login', {
|
|
1472
|
+
hubUrl: this.defaultHubUrl,
|
|
1473
|
+
clientAppId: this.defaultClientAppId,
|
|
1241
1474
|
workspace: options.workspace ?? this.defaultWorkspace,
|
|
1242
1475
|
route: options.workspace ?? this.defaultWorkspace,
|
|
1243
|
-
connectionName: options.connectionName ?? this.
|
|
1476
|
+
connectionName: options.connectionName ?? this.defaultConnectionName,
|
|
1244
1477
|
})
|
|
1478
|
+
const selectedConnectionName = typeof result?.connectionName === 'string'
|
|
1479
|
+
? result.connectionName.trim()
|
|
1480
|
+
: ''
|
|
1481
|
+
if (
|
|
1482
|
+
result?.state === 'authorized' &&
|
|
1483
|
+
selectedConnectionName &&
|
|
1484
|
+
selectedConnectionName.length <= 128
|
|
1485
|
+
) {
|
|
1486
|
+
this.defaultConnectionName = selectedConnectionName
|
|
1487
|
+
if (typeof result.workspace === 'string' && WORKSPACE.test(result.workspace)) {
|
|
1488
|
+
this.defaultWorkspace = result.workspace
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
return result
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
async doctor(options = {}) {
|
|
1495
|
+
const report = {
|
|
1496
|
+
schema: 'bailing.agent-client-doctor.v1',
|
|
1497
|
+
ok: false,
|
|
1498
|
+
checks: {
|
|
1499
|
+
configuration: {
|
|
1500
|
+
ok: this.connectionReady,
|
|
1501
|
+
invalid: this.connectionReady ? [] : [...this.config.invalid, 'selectedConnection'],
|
|
1502
|
+
},
|
|
1503
|
+
dsh_host: cloneJson(this.hostContract),
|
|
1504
|
+
sdk: { ok: false },
|
|
1505
|
+
agent_session: { ok: false, state: 'not_checked', skipped: true },
|
|
1506
|
+
workspace: { ok: false, state: 'not_checked', skipped: true },
|
|
1507
|
+
},
|
|
1508
|
+
next_action: '',
|
|
1509
|
+
}
|
|
1510
|
+
const finish = (nextAction) => sanitizePublicValue({
|
|
1511
|
+
...report,
|
|
1512
|
+
ok: Object.values(report.checks).every((check) => check.ok === true),
|
|
1513
|
+
next_action: nextAction,
|
|
1514
|
+
})
|
|
1515
|
+
|
|
1516
|
+
if (!this.hostContract.ok) {
|
|
1517
|
+
return finish('Use a DSH release whose host lifecycle matches the documented compatibility matrix.')
|
|
1518
|
+
}
|
|
1519
|
+
if (!this.connectionReady) {
|
|
1520
|
+
return finish('Set the four public connection fields, reload the DSH profile, and run doctor again.')
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
let transport
|
|
1524
|
+
try {
|
|
1525
|
+
transport = await this.getTransport()
|
|
1526
|
+
report.checks.sdk = { ok: true }
|
|
1527
|
+
} catch {
|
|
1528
|
+
return finish('Reinstall this plugin so its exact Agent Client SDK dependency can resolve.')
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
let status
|
|
1532
|
+
try {
|
|
1533
|
+
status = sanitizePublicValue(await transport.status({
|
|
1534
|
+
connectionName: options.connectionName ?? this.defaultConnectionName,
|
|
1535
|
+
}))
|
|
1536
|
+
} catch {
|
|
1537
|
+
report.checks.agent_session = {
|
|
1538
|
+
ok: false,
|
|
1539
|
+
state: 'unreachable',
|
|
1540
|
+
}
|
|
1541
|
+
return finish('Check Hub reachability, then run doctor again; no business action was attempted.')
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
const authorizationState = typeof status?.state === 'string' ? status.state : 'unknown'
|
|
1545
|
+
const authorized = authorizationState === 'authorized'
|
|
1546
|
+
report.checks.agent_session = {
|
|
1547
|
+
ok: authorized,
|
|
1548
|
+
state: authorizationState,
|
|
1549
|
+
}
|
|
1550
|
+
if (!authorized) {
|
|
1551
|
+
return finish('Run /bailinghub login to authorize this Hub/client/workspace connection.')
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
try {
|
|
1555
|
+
const result = sanitizePublicValue(await transport.workspaces({
|
|
1556
|
+
connectionName: options.connectionName ?? this.defaultConnectionName,
|
|
1557
|
+
}))
|
|
1558
|
+
const workspaces = Array.isArray(result?.workspaces) ? result.workspaces : []
|
|
1559
|
+
const configuredAuthorized = workspaces.some((entry) => entry?.route === this.defaultWorkspace)
|
|
1560
|
+
const selectedMatches = status?.workspace === this.defaultWorkspace
|
|
1561
|
+
report.checks.workspace = {
|
|
1562
|
+
ok: configuredAuthorized && selectedMatches,
|
|
1563
|
+
state: configuredAuthorized && selectedMatches ? 'authorized' : 'mismatch',
|
|
1564
|
+
authorized_count: workspaces.length,
|
|
1565
|
+
}
|
|
1566
|
+
} catch {
|
|
1567
|
+
report.checks.workspace = {
|
|
1568
|
+
ok: false,
|
|
1569
|
+
state: 'unreachable',
|
|
1570
|
+
}
|
|
1571
|
+
return finish('Check Agent API reachability and the current authorization, then run doctor again.')
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
if (!report.checks.workspace.ok) {
|
|
1575
|
+
return finish('Authorize the configured workspace; use a dedicated client app or workspace when credential isolation is required.')
|
|
1576
|
+
}
|
|
1577
|
+
return finish('Open a new conversation in Native Tool Mode and run a read-only acceptance request.')
|
|
1245
1578
|
}
|
|
1246
1579
|
|
|
1247
1580
|
async status(options = {}) {
|
|
1248
|
-
if (!this.
|
|
1249
|
-
return { state: 'unconfigured', missing: [...this.config.invalid] }
|
|
1581
|
+
if (!this.connectionReady) {
|
|
1582
|
+
return { state: 'unconfigured', missing: [...this.config.invalid, 'selectedConnection'] }
|
|
1250
1583
|
}
|
|
1251
1584
|
return this.callConnection('status', {
|
|
1252
|
-
connectionName: options.connectionName ?? this.
|
|
1585
|
+
connectionName: options.connectionName ?? this.defaultConnectionName,
|
|
1253
1586
|
})
|
|
1254
1587
|
}
|
|
1255
1588
|
|
|
1256
1589
|
async logout(options = {}) {
|
|
1257
1590
|
return this.callConnection('logout', {
|
|
1258
|
-
connectionName: options.connectionName ?? this.
|
|
1591
|
+
connectionName: options.connectionName ?? this.defaultConnectionName,
|
|
1259
1592
|
})
|
|
1260
1593
|
}
|
|
1261
1594
|
|
|
1262
1595
|
async workspaces(options = {}) {
|
|
1263
1596
|
return this.callConnection('workspaces', {
|
|
1264
|
-
connectionName: options.connectionName ?? this.
|
|
1597
|
+
connectionName: options.connectionName ?? this.defaultConnectionName,
|
|
1265
1598
|
})
|
|
1266
1599
|
}
|
|
1267
1600
|
|
|
@@ -1281,7 +1614,7 @@ export class BailingHubAgentClientRuntime {
|
|
|
1281
1614
|
'Finish active BailingHub runs and sync pending completions before switching workspace',
|
|
1282
1615
|
)
|
|
1283
1616
|
}
|
|
1284
|
-
const connectionName = options.connectionName ?? this.
|
|
1617
|
+
const connectionName = options.connectionName ?? this.defaultConnectionName
|
|
1285
1618
|
const result = await this.callConnection('use', {
|
|
1286
1619
|
workspace: selected,
|
|
1287
1620
|
route: selected,
|
|
@@ -1291,6 +1624,69 @@ export class BailingHubAgentClientRuntime {
|
|
|
1291
1624
|
return result
|
|
1292
1625
|
}
|
|
1293
1626
|
|
|
1627
|
+
async connectionsList() {
|
|
1628
|
+
return this.callConnection('connectionsList', {})
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
async connectionsAdd(input) {
|
|
1632
|
+
const result = await this.callConnection('connectionsAdd', input)
|
|
1633
|
+
const selected = result?.connection
|
|
1634
|
+
if (selected?.connectionName && selected?.workspace) {
|
|
1635
|
+
this.defaultConnectionName = selected.connectionName
|
|
1636
|
+
this.defaultWorkspace = selected.workspace
|
|
1637
|
+
this.defaultHubUrl = selected.hubUrl
|
|
1638
|
+
this.defaultClientAppId = selected.clientAppId
|
|
1639
|
+
this.connectionReady = true
|
|
1640
|
+
}
|
|
1641
|
+
return result
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
async connectionsUse(selector) {
|
|
1645
|
+
const connectionName = String(selector ?? '').trim()
|
|
1646
|
+
if (!connectionName) throw new TypeError('connection name is required')
|
|
1647
|
+
const result = await this.callTransport('connectionsUse', [connectionName])
|
|
1648
|
+
const selected = result?.connection
|
|
1649
|
+
if (!selected?.connectionName || !selected?.hubUrl || !selected?.clientAppId || !selected?.workspace) {
|
|
1650
|
+
throw new TypeError('The selected BailingHub connection metadata is incomplete')
|
|
1651
|
+
}
|
|
1652
|
+
this.defaultConnectionName = selected.connectionName
|
|
1653
|
+
this.defaultWorkspace = selected.workspace
|
|
1654
|
+
this.defaultHubUrl = selected.hubUrl
|
|
1655
|
+
this.defaultClientAppId = selected.clientAppId
|
|
1656
|
+
this.connectionReady = true
|
|
1657
|
+
return result
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
async connectionsRemove(selector) {
|
|
1661
|
+
const connectionName = String(selector ?? '').trim()
|
|
1662
|
+
if (!connectionName) throw new TypeError('connection name is required')
|
|
1663
|
+
const hasBlockingRun = [...this.states].some((state) =>
|
|
1664
|
+
[...state.runs.values()].some((run) =>
|
|
1665
|
+
run.status === 'active' ||
|
|
1666
|
+
run.status === 'completing' ||
|
|
1667
|
+
run.completion?.state === 'pending' ||
|
|
1668
|
+
run.completion?.state === 'syncing',
|
|
1669
|
+
),
|
|
1670
|
+
)
|
|
1671
|
+
if (hasBlockingRun) {
|
|
1672
|
+
throw new Error(
|
|
1673
|
+
'Finish active BailingHub runs and sync pending completions before removing a connection',
|
|
1674
|
+
)
|
|
1675
|
+
}
|
|
1676
|
+
const result = await this.callTransport('connectionsRemove', [connectionName])
|
|
1677
|
+
const removedDefault = [result?.connectionName, result?.connectionKey]
|
|
1678
|
+
.some((value) => typeof value === 'string' && value.trim() === this.defaultConnectionName)
|
|
1679
|
+
try {
|
|
1680
|
+
const registry = await this.connectionsList()
|
|
1681
|
+
const selected = currentConnectionDefaults(registry)
|
|
1682
|
+
if (selected) this.applyConnectionDefaults(selected)
|
|
1683
|
+
else if (connectionRegistryIsEmpty(registry) || removedDefault) this.connectionReady = false
|
|
1684
|
+
} catch {
|
|
1685
|
+
if (removedDefault || result?.currentConnectionKey === null) this.connectionReady = false
|
|
1686
|
+
}
|
|
1687
|
+
return result
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1294
1690
|
async searchCapabilities(query, options = {}) {
|
|
1295
1691
|
return this.callTransport('searchCapabilities', [
|
|
1296
1692
|
{
|
|
@@ -1300,7 +1696,7 @@ export class BailingHubAgentClientRuntime {
|
|
|
1300
1696
|
},
|
|
1301
1697
|
{
|
|
1302
1698
|
workspace: options.workspace ?? this.defaultWorkspace,
|
|
1303
|
-
connectionName: options.connectionName ?? this.
|
|
1699
|
+
connectionName: options.connectionName ?? this.defaultConnectionName,
|
|
1304
1700
|
signal: options.signal,
|
|
1305
1701
|
},
|
|
1306
1702
|
])
|
|
@@ -1319,7 +1715,7 @@ export class BailingHubAgentClientRuntime {
|
|
|
1319
1715
|
{},
|
|
1320
1716
|
{
|
|
1321
1717
|
workspace: options.workspace ?? this.defaultWorkspace,
|
|
1322
|
-
connectionName: options.connectionName ?? this.
|
|
1718
|
+
connectionName: options.connectionName ?? this.defaultConnectionName,
|
|
1323
1719
|
signal: options.signal,
|
|
1324
1720
|
},
|
|
1325
1721
|
),
|
package/lib/transport.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
const DEFAULT_SDK_MODULE = 'bailinghub-mcp-server/sdk'
|
|
2
2
|
|
|
3
3
|
const REQUIRED_METHODS = [
|
|
4
|
+
'connectionsList',
|
|
5
|
+
'connectionsAdd',
|
|
6
|
+
'connectionsUse',
|
|
7
|
+
'connectionsRemove',
|
|
4
8
|
'login',
|
|
5
9
|
'status',
|
|
6
10
|
'logout',
|
|
@@ -45,7 +49,7 @@ function resolveSdkFactory(module) {
|
|
|
45
49
|
* Lazily resolves the generic BailingHub Agent Client SDK. The SDK, rather than
|
|
46
50
|
* this DSH adapter, owns browser authorization, token storage, refresh, and the
|
|
47
51
|
* HTTP endpoint mapping. A missing SDK therefore degrades only the first use;
|
|
48
|
-
* it never prevents Cordis from loading the plugin
|
|
52
|
+
* it never prevents Cordis from loading the plugin.
|
|
49
53
|
*/
|
|
50
54
|
export function createLazySdkTransport(config, options = {}) {
|
|
51
55
|
const importModule = options.importModule ?? ((specifier) => import(specifier))
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-bailinghub",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Let a local DeepSeek Harness Agent use BailingHub to query data, update records, and run permitted actions in your online store, SaaS, or other admin system.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
@@ -39,6 +39,8 @@
|
|
|
39
39
|
"cordis.agent-client.patch.yml",
|
|
40
40
|
"cordis.patch.yml",
|
|
41
41
|
"docs/AGENT_CLIENT_CONTRACT.md",
|
|
42
|
+
"docs/GETTING_STARTED.md",
|
|
43
|
+
"docs/GETTING_STARTED.zh-CN.md",
|
|
42
44
|
"docs/MIGRATION_VNEXT.md",
|
|
43
45
|
"docs/README.zh-CN.md",
|
|
44
46
|
"SECURITY.md",
|
|
@@ -52,14 +54,22 @@
|
|
|
52
54
|
},
|
|
53
55
|
"scripts": {
|
|
54
56
|
"check:project": "node scripts/check-project.mjs",
|
|
57
|
+
"check:marketplace": "node scripts/check-marketplace.mjs",
|
|
55
58
|
"check:release-tag": "node scripts/check-release-tag.mjs",
|
|
56
59
|
"test": "node --test tests/*.test.mjs",
|
|
57
|
-
"verify": "npm run check:project && npm test",
|
|
60
|
+
"verify": "npm run check:project && npm run check:marketplace && npm test",
|
|
58
61
|
"prepack": "npm run verify"
|
|
59
62
|
},
|
|
60
63
|
"dependencies": {
|
|
61
64
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
62
|
-
"bailinghub-mcp-server": "0.
|
|
65
|
+
"bailinghub-mcp-server": "0.3.0"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@deepseek-ai/cordis": "4.0.1",
|
|
69
|
+
"@deepseek-ai/dsh-commands": "0.1.1-rc.2",
|
|
70
|
+
"@deepseek-ai/dsh-scope": "0.1.1-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.1-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-tools": "0.1.1-rc.2"
|
|
63
73
|
},
|
|
64
74
|
"dsh": {
|
|
65
75
|
"bundle": {
|