dsh-bailinghub 0.3.0 → 0.4.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 +46 -1
- package/PRIVACY.md +101 -7
- package/README.md +128 -199
- package/SECURITY.md +95 -3
- package/docs/AGENT_CLIENT_CONTRACT.md +268 -23
- package/docs/COMPATIBILITY.md +51 -4
- package/docs/GETTING_STARTED.md +95 -61
- package/docs/GETTING_STARTED.zh-CN.md +82 -52
- package/docs/MIGRATION_VNEXT.md +79 -17
- package/docs/PROJECT_BOUNDARIES.md +3 -2
- package/docs/README.zh-CN.md +98 -174
- package/lib/authorizations.js +61 -0
- package/lib/conversation-archive-store.js +265 -0
- package/lib/conversation-outbox.js +230 -0
- package/lib/index.js +3 -0
- package/lib/runtime.js +657 -47
- package/lib/session-scope-store.js +265 -0
- package/lib/session-scope.js +319 -0
- package/lib/transport.js +11 -1
- package/package.json +2 -2
package/SECURITY.md
CHANGED
|
@@ -19,9 +19,9 @@ configuration and are never model tool arguments.
|
|
|
19
19
|
|
|
20
20
|
Non-loopback HTTP is denied by default. Do not enable insecure HTTP on an untrusted network.
|
|
21
21
|
|
|
22
|
-
## Native 0.
|
|
22
|
+
## Native 0.4.0 boundary
|
|
23
23
|
|
|
24
|
-
The native 0.
|
|
24
|
+
The native 0.4.0 plugin accepts only `hubUrl`, `clientAppId`, `workspace`, and
|
|
25
25
|
`connectionName`. The generic SDK owns browser authorization, refresh, and secure credential
|
|
26
26
|
storage; business endpoints and final authorization remain Core/business-system concerns. The
|
|
27
27
|
Hub Client App owns one business authorization entry. That business page, not the plugin or model,
|
|
@@ -41,7 +41,7 @@ falsely report a complete logout.
|
|
|
41
41
|
Tools are Agent/run scoped. Message ids are replaced by Core-safe hash aliases, invocation ids are
|
|
42
42
|
stable 64-character digests, and an `accepted_unknown` outcome must resume that exact invocation
|
|
43
43
|
instead of creating a replacement. Completion retries are bounded and reuse one frozen,
|
|
44
|
-
visible-only payload. Version 0.
|
|
44
|
+
visible-only payload. Version 0.4.0 installs `bailinghub-mcp-server@0.4.0` as an exact ordinary
|
|
45
45
|
dependency and resolves its `./sdk` export. It does not depend on ambient modules, an optional
|
|
46
46
|
peer, a range, a dist-tag, or a local path. Public `0.1.1` does not provide that facade.
|
|
47
47
|
|
|
@@ -49,3 +49,95 @@ Agent Session credentials use macOS Keychain or Windows CurrentUser DPAPI-protec
|
|
|
49
49
|
LocalAppData. Windows PowerShell or DPAPI unavailability fails closed without a plaintext fallback.
|
|
50
50
|
Linux and other POSIX hosts must explicitly enable the SDK's isolated mode-0600 file store. The
|
|
51
51
|
plugin never receives the credential value and never writes one into Cordis configuration.
|
|
52
|
+
|
|
53
|
+
## Same-system authorization selection
|
|
54
|
+
|
|
55
|
+
Version 0.4.0 lets the model select a session-local `authorization_ref` from the current
|
|
56
|
+
conversation's directory. This is a constrained per-call selector, not a connection-management
|
|
57
|
+
tool or authority to supply a Hub, route, raw connection key, credential, or business identity.
|
|
58
|
+
The host must first explicitly select fixed connection keys for this conversation through
|
|
59
|
+
`setSessionScope` or the user-only `/bailinghub scope set <connection-key>...` command. Aliases are
|
|
60
|
+
not scope keys. Unset scope and `[]` (`/bailinghub scope none`) remain ordinary chat, without
|
|
61
|
+
BailingHub tools or runs. Authorization and registry defaults cannot grant conversation scope.
|
|
62
|
+
The selected bindings must share one Hub/client/workspace; unselected bindings are excluded.
|
|
63
|
+
The adapter never implements selection by changing the SDK's global current connection.
|
|
64
|
+
|
|
65
|
+
Hosts must await successful scope persistence and confirmation before sending the first user
|
|
66
|
+
message. The first `user/message` event freezes scope, with the inbox claim as a fallback, before
|
|
67
|
+
`startTurn`; an in-flight or
|
|
68
|
+
failed selection cannot admit business work. Subsequent changes require a new conversation.
|
|
69
|
+
The full selected group is checked before business input is sent. Any missing, revoked, replaced,
|
|
70
|
+
or unreadable selected authorization pauses the whole conversation's business access. The adapter
|
|
71
|
+
must not silently adopt a default or shrink the scope to the remaining valid authorizations.
|
|
72
|
+
|
|
73
|
+
Local connection names are untrusted display data. They do not prove tenant identity, widen an
|
|
74
|
+
authorization, or replace the business system's final permission checks. The directory is a
|
|
75
|
+
binding snapshot, not a credential snapshot: expired, removed, or revoked access must fail
|
|
76
|
+
without silently selecting another authorization. New authorizations and alias changes require
|
|
77
|
+
a new conversation.
|
|
78
|
+
The host checks the fixed connection key, workspace, and original Agent Session id before
|
|
79
|
+
transport operations. An Agent Session replacement also requires a new conversation, even if
|
|
80
|
+
the local alias or connection key remains unchanged.
|
|
81
|
+
|
|
82
|
+
Matching declarations share one typed tool. Conflicting same-name descriptions, schemas, or
|
|
83
|
+
governance are not merged for execution, and a shared declaration cannot confer another identity's permissions.
|
|
84
|
+
Each invocation binds its chosen authorization, Core run, and capability revision. Recovery
|
|
85
|
+
accepts only an invocation known to this conversation and resolves its original binding; the
|
|
86
|
+
model cannot provide a replacement authorization. Changing a default connection cannot retarget
|
|
87
|
+
an existing call.
|
|
88
|
+
This invocation map lasts only for the live conversation: later turns can recover its original
|
|
89
|
+
calls, while new conversations and process restarts must reject unknown invocation ids.
|
|
90
|
+
|
|
91
|
+
Only non-secret scope metadata belongs in the scope store. Its default file store uses SHA-256 session filenames,
|
|
92
|
+
mode-0600 files and mode-0700 directories on POSIX, bounded reads, rejection of symlinks/non-regular files,
|
|
93
|
+
revision compare-and-swap, a cross-process lock, and atomic replacement. Lock timeout reports a
|
|
94
|
+
conflict without deleting another process's lock. Corrupt data and I/O failure fail closed; they
|
|
95
|
+
are never interpreted as an absent selection or a reason to use memory storage. Before validating
|
|
96
|
+
a replacement scope, the coordinator attempts to persist `needs_selection`. Failure of that first
|
|
97
|
+
write can leave the previous draft on disk. Every unlocked snapshot loaded into a new runtime is
|
|
98
|
+
therefore blocked pending explicit selection, without checking its previous SDK authorizations;
|
|
99
|
+
restart safety does not assume that the failed write replaced the old record.
|
|
100
|
+
|
|
101
|
+
The host may inject a store with the same CAS semantics; the provided memory adapter is explicitly
|
|
102
|
+
non-persistent. `restoreSessionScope` restores only a valid locked selection after verifying its
|
|
103
|
+
keys, binding, and original Agent Session ids. Unlocked drafts require explicit selection again;
|
|
104
|
+
started conversations without valid locked scope stay blocked. History containing only metadata,
|
|
105
|
+
configuration, or seed markers does not prove that a conversation started. The lifecycle check
|
|
106
|
+
requires an actual user-sourced `user/message` or `turn/start` and uses seed/observation boundaries
|
|
107
|
+
to distinguish prior history from a new first message. It restores scope only, not invocations,
|
|
108
|
+
approvals, pending completions, or task execution. A new conversation is required to change an
|
|
109
|
+
already-started scope. No token, credential, prompt, or business payload belongs in the
|
|
110
|
+
scope snapshot.
|
|
111
|
+
The trusted host owns stable, unique conversation ids and the store namespace. Scope APIs and
|
|
112
|
+
records must not be exposed as model-controlled storage or allow an untrusted caller to select
|
|
113
|
+
another conversation's id. This plugin does not secure unrelated host filesystem tools; the host
|
|
114
|
+
must enforce that access boundary.
|
|
115
|
+
|
|
116
|
+
The adapter keeps authorization-specific instructions and context labeled, and each run receives
|
|
117
|
+
only its authorization's deterministic call summary. It does not broadcast a combined final
|
|
118
|
+
answer to every run. Visible user input and context do share the local conversation boundary;
|
|
119
|
+
see [Privacy](PRIVACY.md#same-system-authorization-selection).
|
|
120
|
+
|
|
121
|
+
The independent conversation-audit extension sends visible text for the complete frozen member
|
|
122
|
+
set through an optional SDK API. A durable random archive UUID supplies correlation, not authority:
|
|
123
|
+
the SDK/Core must validate every original member before confirming or appending, and Core owns the
|
|
124
|
+
aggregate read permission. Never expose mixed free text to a reader authorized for only one member
|
|
125
|
+
by assuming it can be safely redacted. The original run id and member Session bind run links;
|
|
126
|
+
archive synchronization cannot create or resume a business action. Late links remain attached to
|
|
127
|
+
their original turn.
|
|
128
|
+
|
|
129
|
+
The separate private outbox contains plaintext visible task text and must be protected from
|
|
130
|
+
untrusted host/model filesystem tools. It has bounded reads, no-follow regular-file checks,
|
|
131
|
+
CAS/lock/atomic-write semantics, and no credential or scope-store fallback. Payloads and ids remain
|
|
132
|
+
stable on ambiguous network retries. Local I/O failure can leave an unpersisted event: available
|
|
133
|
+
DSH history is compared after reopening, and missing events produce `recovery_gap`, not a claim
|
|
134
|
+
of a complete transcript. Hosts without history must show unverified coverage. This boundary is
|
|
135
|
+
not a distributed transaction or durable business-task recovery mechanism.
|
|
136
|
+
|
|
137
|
+
Transient transport failures do not prove that an original authorization was revoked. Scope
|
|
138
|
+
get/restore and archive retry may revalidate all original members on the same runtime, while
|
|
139
|
+
keeping business access and uploads closed until validation succeeds. Concurrent callers share
|
|
140
|
+
that validation. Confirmed revocation/replacement and storage/CAS conflicts remain terminally
|
|
141
|
+
blocked, with no default or subset fallback. The gate is rechecked after asynchronous archive
|
|
142
|
+
capability discovery and outbox opening; a late result cannot erase a confirmed revocation.
|
|
143
|
+
Known local storage errors and capture gaps remain visible even while network or scope checks block upload.
|
|
@@ -1,8 +1,238 @@
|
|
|
1
1
|
# Agent Client Host Adapter Contract
|
|
2
2
|
|
|
3
|
-
Status:
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Status: native Agent Client contract for `dsh-bailinghub@0.4.0`, paired with
|
|
4
|
+
`bailinghub-mcp-server@0.4.0` and recommended BailingHub Core `0.6.1` (minimum API version
|
|
5
|
+
`0.6.0`). This contract is separate from the legacy static `0.1.x` path. Version 0.3.0 supported user-managed connections but did not include
|
|
6
|
+
explicit conversation scope, multi-authorization tool selection, or the visible conversation archive.
|
|
7
|
+
|
|
8
|
+
## Same-System Authorization Selection
|
|
9
|
+
|
|
10
|
+
This increment supports multiple independently authorized identities for one public
|
|
11
|
+
`Hub + clientAppId + workspace` binding in one DSH conversation. It does not combine different
|
|
12
|
+
systems or routes, alter business capability declarations, or change Core authorization rules.
|
|
13
|
+
|
|
14
|
+
Version 0.4.0 changes the default: a new conversation with no selected scope, or an explicitly
|
|
15
|
+
empty `connectionKeys: []`, is ordinary chat. It starts no BailingHub run and exposes no BailingHub
|
|
16
|
+
business tools. Browser authorization, the registry's current connection, and the four bootstrap
|
|
17
|
+
fields do not select a conversation's scope. There is no automatic discovery-and-enable fallback.
|
|
18
|
+
|
|
19
|
+
The host explicitly selects fixed SDK connection keys before sending the first user message.
|
|
20
|
+
Exactly one selected authorization keeps the original typed arguments and result shape; two or
|
|
21
|
+
more selected authorizations use the shared envelope below. All selected keys must be authorized
|
|
22
|
+
under the same Hub/client/workspace binding. Unselected connections are never added to the scope.
|
|
23
|
+
The model receives a projected directory containing
|
|
24
|
+
session-local `authorization_ref` values, local display names, and availability, not raw connection
|
|
25
|
+
keys, credentials, Agent Session metadata,
|
|
26
|
+
or permission to supply arbitrary route or identity values. A local name is untrusted display
|
|
27
|
+
data, not proof of a tenant or store. The model must resolve ambiguous user intent before acting;
|
|
28
|
+
available authorization is not a request to act on every listed identity.
|
|
29
|
+
Labels use existing local `connectionName` metadata, not token-derived business names or a new
|
|
30
|
+
Core identity-display field. Generic aliases such as `default` and `default-2` do not establish
|
|
31
|
+
an A/B business mapping; the user must supply clear labels while the business authorization page
|
|
32
|
+
continues to determine the trusted identity.
|
|
33
|
+
|
|
34
|
+
The directory's references resolve to the explicitly selected SDK connection bindings. The first
|
|
35
|
+
`user/message` event freezes the selection, with `agent/inbox/claimed` as a fallback for drivers
|
|
36
|
+
that do not emit that event, before `startTurn`. Changing the selected keys after
|
|
37
|
+
that point requires a new conversation, including switching between ordinary chat and business
|
|
38
|
+
mode. Adding a registry authorization or changing its alias never expands an existing scope.
|
|
39
|
+
Capturing a binding does not freeze credentials or bypass refresh, expiry, revocation, or
|
|
40
|
+
downstream authorization checks.
|
|
41
|
+
Before transport operations, `status({ connectionKey })` must report the captured connection key,
|
|
42
|
+
workspace, and the original authorized Agent Session id captured during selection. If any selected
|
|
43
|
+
authorization is missing, invalid, replaced, or cannot be checked, business access for the entire
|
|
44
|
+
conversation pauses. It neither falls back to a default nor silently shrinks to the remaining
|
|
45
|
+
authorizations. A new conversation can explicitly select the still-valid subset. These inspection
|
|
46
|
+
fields stay host-side. The
|
|
47
|
+
adapter passes `connectionKey` and `workspace` as explicit SDK host metadata instead of
|
|
48
|
+
resolving a mutable alias or default.
|
|
49
|
+
|
|
50
|
+
The full selection is validated before any selected system receives the turn. For each direct
|
|
51
|
+
user turn, the adapter starts one Core run per selected authorization before
|
|
52
|
+
assembling the model request. Instructions, governance, knowledge, memory, and tool results carry
|
|
53
|
+
authorization labels. The user input is sent to each of those runs. Separate run state preserves
|
|
54
|
+
attribution; all injected context still shares the local Agent/model boundary described in
|
|
55
|
+
[Privacy](../PRIVACY.md#same-system-authorization-selection).
|
|
56
|
+
|
|
57
|
+
Business definitions with the same name, description, input schema, and governance are registered
|
|
58
|
+
once.
|
|
59
|
+
Conflicting declarations are not merged for execution. Availability remains specific to each
|
|
60
|
+
authorization, and the conversation's total active business-tool limit remains 12. In a
|
|
61
|
+
multi-authorization session, each shared definition wraps its unchanged business schema:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"authorization_ref": "<host-issued reference from this session>",
|
|
66
|
+
"arguments": { "<business parameter>": "<value>" }
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The selector is host metadata; it is not forwarded as a business argument or new Core HTTP
|
|
71
|
+
field. A single-authorization session keeps the original unwrapped business schema.
|
|
72
|
+
`search_business_capabilities` accepts an optional `authorization_ref` using the same reference
|
|
73
|
+
boundary; omission searches all captured authorizations. Its responses update the selected
|
|
74
|
+
authorizations' capability revisions and definitions before rebuilding
|
|
75
|
+
the shared tool view. It cannot import a different binding into the conversation.
|
|
76
|
+
|
|
77
|
+
Invocation state captures the selected authorization, Core run, capability revision, tool, and
|
|
78
|
+
arguments. A replay with a different selector or payload fails closed. Recovery accepts only an
|
|
79
|
+
invocation known to this conversation and uses the original binding; it accepts no replacement
|
|
80
|
+
authorization selector. Pending approval and unknown dispatch outcomes follow the same
|
|
81
|
+
exact-invocation recovery rules as the baseline. Removing or selecting another default must not
|
|
82
|
+
retarget an existing invocation.
|
|
83
|
+
The local invocation map survives later turns of the same live conversation. It is not persisted
|
|
84
|
+
across process restarts or copied into new conversations, and unknown invocation ids fail closed.
|
|
85
|
+
This increment does not provide durable task recovery across those boundaries.
|
|
86
|
+
|
|
87
|
+
On multi-authorization completion, the adapter freezes one deterministic summary of each run's
|
|
88
|
+
own governed calls and synchronizes that run separately. It does not send the combined visible
|
|
89
|
+
assistant answer, another authorization's results, or hidden reasoning to every run. The combined
|
|
90
|
+
answer is instead eligible for the separate conversation audit below. Single-authorization completion keeps the existing visible-answer
|
|
91
|
+
behavior. Connection add/use/remove remain user-only commands, not model tools.
|
|
92
|
+
|
|
93
|
+
### Host-owned session scope API
|
|
94
|
+
|
|
95
|
+
The runtime service exposes asynchronous `getSessionScope(sessionId)`,
|
|
96
|
+
`setSessionScope(sessionId, { connectionKeys, expectedRevision? })`, and
|
|
97
|
+
`restoreSessionScope(sessionId)`. They are host APIs, never model tools. Use keys returned by the
|
|
98
|
+
SDK registry, not aliases or model-provided identity values.
|
|
99
|
+
|
|
100
|
+
Native DSH users can inspect `/bailinghub scope`, choose ordinary chat with
|
|
101
|
+
`/bailinghub scope none`, or select fixed keys with `/bailinghub scope set <connection-key>...`
|
|
102
|
+
before the first user message. Obtain keys from `/bailinghub connections list`; the scope command
|
|
103
|
+
does not accept aliases, adopt a registry default, or start a business run. These are user-only
|
|
104
|
+
commands over the same scope API.
|
|
105
|
+
|
|
106
|
+
```js
|
|
107
|
+
const runtime = ctx.get('bailingHubAgentClient')
|
|
108
|
+
const previous = await runtime.getSessionScope(sessionId)
|
|
109
|
+
const selected = await runtime.setSessionScope(sessionId, {
|
|
110
|
+
connectionKeys: selectedConnectionKeys, // [] explicitly chooses ordinary chat
|
|
111
|
+
expectedRevision: previous.revision,
|
|
112
|
+
})
|
|
113
|
+
// Render selected.mode and selected.authorizations, then enable sending.
|
|
114
|
+
// Do not dispatch the first user message while this operation is pending or failed.
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The returned view includes `schema`, `sessionId`, integer-or-null `revision`, `state`
|
|
118
|
+
(`unselected`, `ready`, or `needs_selection`), `locked`, `mode` (`chat`, `business`, or `blocked`),
|
|
119
|
+
and public authorization entries. `get` reads the scope; it does not grant access. A host must await
|
|
120
|
+
a successful `set`, show its returned selection, and only then send the first message. Never
|
|
121
|
+
optimistically send using an earlier scope. Before the first message, a failed replacement
|
|
122
|
+
selection leaves business access blocked; it cannot restore the previous broader selection.
|
|
123
|
+
After the scope is frozen, a change request returns `SESSION_SCOPE_LOCKED` and leaves the original
|
|
124
|
+
frozen scope unchanged. The host must open a new conversation, not treat that rejection as a
|
|
125
|
+
successful selection of the requested keys. Revision values may advance more than once
|
|
126
|
+
during selection; always use the returned value for the next compare-and-swap request.
|
|
127
|
+
|
|
128
|
+
Each returned `authorizations` entry has exactly the public host-facing fields
|
|
129
|
+
`{ authorizationRef, connectionKey, label, workspace }`. The selection accepts at most 64 unique
|
|
130
|
+
connection keys. The trusted host owns `sessionId`: it must remain stable when reopening the same
|
|
131
|
+
conversation and be unique within that store's namespace. Do not let the model or an untrusted
|
|
132
|
+
client choose another conversation's id, edit scope records, or control the storage namespace.
|
|
133
|
+
The store and its access policy belong to the host. The model sees only the projected reference,
|
|
134
|
+
label, and availability directory; raw connection keys and scope storage are not model APIs.
|
|
135
|
+
|
|
136
|
+
Reopening an existing conversation must call `restoreSessionScope` before sending. A valid
|
|
137
|
+
`locked: true` snapshot restores its original scope after the saved binding and Agent Sessions
|
|
138
|
+
are checked; it remains locked. A stored `locked: false` snapshot is only a draft, even if its
|
|
139
|
+
stored state says `ready`. Loading it into a new runtime returns `needs_selection` / `blocked`
|
|
140
|
+
without probing its previously selected SDK connections. A never-started draft remains
|
|
141
|
+
`locked: false`: the host must explicitly call `setSessionScope` again and show success before
|
|
142
|
+
sending. It must not silently reactivate the saved draft selection.
|
|
143
|
+
|
|
144
|
+
If a locked conversation is reopened offline, its business gate stays closed. A timeout,
|
|
145
|
+
connection failure, or incomplete authorization response is not proof of revocation: retry
|
|
146
|
+
`restoreSessionScope`, `getSessionScope`, or `syncSessionArchive` on the **same runtime and Session**
|
|
147
|
+
after connectivity returns. All original members must pass validation before business access or
|
|
148
|
+
archive upload resumes. Concurrent callers wait for the same in-flight whole-scope validation.
|
|
149
|
+
Retries preserve the original keys, Agent Sessions, binding, member set, and persisted scope revision.
|
|
150
|
+
Confirmed revocation, identity/binding replacement, corrupt storage, or a CAS conflict remains
|
|
151
|
+
blocked; retries never select a default, remaining subset, or replacement Session.
|
|
152
|
+
|
|
153
|
+
An old conversation without a valid snapshot stays blocked; missing or corrupt state must not
|
|
154
|
+
adopt today's registry default or discovered authorizations. Trusted history containing only
|
|
155
|
+
configuration or metadata still permits draft selection. Actual user-message or turn history
|
|
156
|
+
requires the original locked scope; changing that scope requires a new conversation. Embedded
|
|
157
|
+
hosts without the native commands or a scope-selection UI must integrate these APIs;
|
|
158
|
+
`connections use` is not a substitute.
|
|
159
|
+
|
|
160
|
+
### Scope persistence seam
|
|
161
|
+
|
|
162
|
+
`createAgentClientPlugin({ scopeStore })` accepts an explicit store with `load(sessionId)` and
|
|
163
|
+
`save(sessionId, record, expectedRevision)`. `load` returns a validated record or `null` for absence;
|
|
164
|
+
errors and corrupt records are not absence. `save` compares the stored revision atomically:
|
|
165
|
+
`null` means the record must not exist, the first revision is `1`, and each update increments it.
|
|
166
|
+
Before validating a replacement selection, the coordinator attempts to save a `needs_selection`
|
|
167
|
+
record. If that first write fails, the previous draft can remain on disk; the write is not
|
|
168
|
+
reported as successful. Restart safety also depends on the draft rule above: every loaded
|
|
169
|
+
unlocked snapshot requires explicit confirmation, so a stale saved selection never becomes
|
|
170
|
+
`ready` automatically, even when its replacement marker could not be written.
|
|
171
|
+
|
|
172
|
+
The default `createFileSessionScopeStore()` saves non-secret JSON under
|
|
173
|
+
`$DSH_HOME/plugins/dsh-bailinghub/session-scopes`, using `~/.dsh` when `DSH_HOME` is unset. Session
|
|
174
|
+
ids are hashed into filenames. On POSIX, files use mode `0600` and directories `0700`; writes use a
|
|
175
|
+
cross-process lock, compare-and-swap, and atomic replacement. Lock conflicts and storage failures
|
|
176
|
+
fail closed; the adapter never switches to an in-memory fallback. Hosts may inject another
|
|
177
|
+
durable implementation. `createMemorySessionScopeStore()` is an explicit, non-persistent option
|
|
178
|
+
for tests or hosts that deliberately accept losing scope state on restart.
|
|
179
|
+
|
|
180
|
+
The snapshot contains only the scope schema, DSH session id, revision, state/lock, public binding,
|
|
181
|
+
and selected connection keys, sanitized labels, workspace, and original Agent Session ids. It
|
|
182
|
+
contains no credentials, tokens, prompts, business arguments/results, or invocation state.
|
|
183
|
+
Restoring a scope does **not** restore an invocation, approval, pending completion, or task.
|
|
184
|
+
|
|
185
|
+
### Independent visible conversation archive
|
|
186
|
+
|
|
187
|
+
The visible archive requires SDK `0.4.0` and Core APIs introduced in `0.6.0`; the recommended
|
|
188
|
+
Core release is `0.6.1`. Earlier `0.3.0` SDK/plugin packages do not provide this contract.
|
|
189
|
+
After a nonempty scope is frozen, the adapter captures claimed user text, every durable `assistant/message` text block, turn start/end, and verified original run links. It ignores
|
|
190
|
+
`assistant/chunk`, hidden reasoning, attachments, raw provider requests, and arbitrary tool payloads.
|
|
191
|
+
The archive is one record for the complete fixed authorization set; per-authorization run summaries
|
|
192
|
+
are unchanged. It is not written into each member's memory.
|
|
193
|
+
|
|
194
|
+
The optional SDK seam is `syncConversationArchive(envelope, { members })`. `envelope` contains a
|
|
195
|
+
durable random UUID `clientArchiveId`, the same `clientConversationId` used by `startTurn`, and
|
|
196
|
+
ordered `events`. Each event has a stable UUID `event_id`, contiguous `sequence` starting at one,
|
|
197
|
+
the original `client_turn_id`, and `kind`: `turn_start`, `user_message`, `assistant_message`,
|
|
198
|
+
`run_link`, or `turn_end`. Messages contain `content`; a run link contains the original `run_id`
|
|
199
|
+
and `member_session_id`; turn end contains `completed`, `failed`, or `cancelled` status. A late
|
|
200
|
+
original run response may attach its link to an already ended turn. It never reactivates its
|
|
201
|
+
business tools, dispatches that cancelled turn's remaining members, or replaces a newer active turn.
|
|
202
|
+
The SDK receives host-only member records `{ connectionKey, workspace, expectedSessionId, label }`,
|
|
203
|
+
checks every original authorization, and owns HTTP batching and credential use. The first frozen
|
|
204
|
+
member is the writer. Neither the old hash alias nor a random archive UUID confers read/write access.
|
|
205
|
+
|
|
206
|
+
`createAgentClientPlugin({ archiveStore })` accepts an independent CAS store with `load`/`save`,
|
|
207
|
+
using the same revision semantics as the scope store but a distinct schema and directory.
|
|
208
|
+
`createFileConversationArchiveStore({ directory? })` defaults to
|
|
209
|
+
`$DSH_HOME/plugins/dsh-bailinghub/conversation-outbox`, with private file permissions, atomic writes,
|
|
210
|
+
and a 32 MiB local record bound. `createMemoryConversationArchiveStore()` is explicitly volatile.
|
|
211
|
+
Outboxes retain the frozen membership, random identity, visible events, event hashes/ids, and
|
|
212
|
+
acknowledged cursor. They contain task text; they are not credential stores. The SDK rejects a
|
|
213
|
+
message above 64,000 characters; the adapter does not silently truncate it or discard pending text.
|
|
214
|
+
Core's conversation/event quotas and the local bound can leave synchronization pending.
|
|
215
|
+
|
|
216
|
+
Host APIs `getSessionArchiveStatus(sessionId)` and `syncSessionArchive(sessionId)` and the user-only
|
|
217
|
+
`/bailinghub archive status|sync` commands expose synchronization separately from business state.
|
|
218
|
+
Persisted pending events can be retried after reopening under the original valid frozen scope,
|
|
219
|
+
without replaying `startTurn`, `invoke`, `resume`, or `completeRun`. An older SDK reports
|
|
220
|
+
`unsupported` without accessing the outbox directory; an unavailable/older Core leaves saved events
|
|
221
|
+
pending or unsupported for retry. Empty scope does not load a transport or create an archive.
|
|
222
|
+
|
|
223
|
+
Capture begins with business turns enabled under this archive contract. Previously unarchived history is not silently
|
|
224
|
+
claimed as complete. Status compares saved visible events against the available DSH `session.events`:
|
|
225
|
+
missing user/assistant text or turn boundaries report `recovery_gap` / `coverage: incomplete`, even
|
|
226
|
+
if the saved prefix is synchronized. Hosts without durable history report `coverage: unverified`.
|
|
227
|
+
This check does not reconstruct missing business run ids. Network failure leaves durable events
|
|
228
|
+
retryable; a local write failure can leave only an in-memory pending event until storage recovers.
|
|
229
|
+
While scope validation or transport availability prevents upload, a known `storage_error` or
|
|
230
|
+
`recovery_gap` remains visible with the existing unsaved-event count and coverage. An optional
|
|
231
|
+
`availability` field describes the additional upload restriction; it does not clear the local error.
|
|
232
|
+
Capability-discovery network failures report `pending`, not a fabricated local storage failure.
|
|
233
|
+
If the process ends before that write succeeds, the host-history check reports the detectable gap;
|
|
234
|
+
there is no atomic transaction between DSH's event log, this outbox, and Core. It does not promise
|
|
235
|
+
recovery of absent host history, hidden content, or durable business task execution.
|
|
6
236
|
|
|
7
237
|
## Host Configuration
|
|
8
238
|
|
|
@@ -16,7 +246,8 @@ connectionName
|
|
|
16
246
|
```
|
|
17
247
|
|
|
18
248
|
`hubUrl`, `clientAppId`, and `workspace` identify a public Hub-side application/workspace binding.
|
|
19
|
-
`connectionName` selects
|
|
249
|
+
`connectionName` selects a local SDK connection for connection-management commands; it does not
|
|
250
|
+
select the conversation scope. It is not an account, tenant, or
|
|
20
251
|
identity claim. The Hub Client App resolves to one stable business authorization endpoint; no
|
|
21
252
|
business endpoint, authorization endpoint, token, secret, or business credential belongs in this
|
|
22
253
|
config. The business authorization page owns sign-in, account switching, tenant selection, and
|
|
@@ -34,7 +265,7 @@ connectionsUse(connectionNameOrKey)
|
|
|
34
265
|
connectionsRemove(connectionNameOrKey)
|
|
35
266
|
|
|
36
267
|
login({ hubUrl, clientAppId, workspace, route, connectionName })
|
|
37
|
-
status({
|
|
268
|
+
status({ connectionKey }) // conversation checks; management may use connectionName
|
|
38
269
|
logout({ connectionName })
|
|
39
270
|
workspaces({ connectionName })
|
|
40
271
|
use({ workspace, route, connectionName })
|
|
@@ -50,7 +281,7 @@ startTurn({
|
|
|
50
281
|
|
|
51
282
|
searchCapabilities({ query, limit?, runId? })
|
|
52
283
|
invoke({ invocationId, capabilityRevision, agentRunId, tool, arguments })
|
|
53
|
-
resume(invocationId, {}, { workspace,
|
|
284
|
+
resume(invocationId, {}, { workspace, connectionKey, signal? })
|
|
54
285
|
completeRun(runId, {
|
|
55
286
|
assistantMessageId,
|
|
56
287
|
content,
|
|
@@ -61,9 +292,11 @@ completeRun(runId, {
|
|
|
61
292
|
})
|
|
62
293
|
```
|
|
63
294
|
|
|
64
|
-
The adapter
|
|
65
|
-
|
|
66
|
-
|
|
295
|
+
The adapter passes host metadata (`workspace`, fixed `connectionKey`, and optional `signal`)
|
|
296
|
+
separately from the business DTO to turn/tool methods, and as the third argument to completion
|
|
297
|
+
and resume. The transport must honor that exact selection; it must never ignore it or substitute
|
|
298
|
+
a mutable alias/default. Connection-management commands may use `connectionName`. The optional
|
|
299
|
+
archive seam and its complete frozen member selection are specified above.
|
|
67
300
|
|
|
68
301
|
## Browser Identity and Local Reconciliation
|
|
69
302
|
|
|
@@ -142,7 +375,15 @@ agent/inbox/claimed
|
|
|
142
375
|
-> model request
|
|
143
376
|
```
|
|
144
377
|
|
|
145
|
-
The adapter captures only a claimed message whose `source.kind` is `user`.
|
|
378
|
+
The adapter captures only a claimed message whose `source.kind` is `user`. The
|
|
379
|
+
first `user/message` event freezes scope; the inbox claim is a fallback if that event is absent.
|
|
380
|
+
The assembly gate permits business work only for the saved selection.
|
|
381
|
+
When inspecting attached or restored history, only `user/message` with `source.kind: 'user'` or
|
|
382
|
+
`turn/start` proves that a conversation has started. Configuration events, metadata, and
|
|
383
|
+
`session/end-seed` alone do not freeze a draft. `firstLiveSeq` is a seed-history boundary, never
|
|
384
|
+
proof of a started conversation by itself. The adapter separates the previously observed or
|
|
385
|
+
seeded prefix from new events so the current first message is not mistaken for an older turn.
|
|
386
|
+
On the authoritative
|
|
146
387
|
`system-prompt/assemble` waterfall, it calls `startTurn`, registers the returned definitions
|
|
147
388
|
through `agent.ctx.tools.register()`, and also adds their schemas to the already-sampled current
|
|
148
389
|
assembly. Later steps receive the same agent-scoped definitions from the ordinary ToolRuntime
|
|
@@ -163,10 +404,11 @@ The adapter listens to durable `session/event` values:
|
|
|
163
404
|
|
|
164
405
|
## Tool Invocation and Recovery
|
|
165
406
|
|
|
166
|
-
Every active Core tool becomes an agent-scoped DSH definition. Its
|
|
167
|
-
64-character lowercase SHA-256 digest of the session, run, DSH call id,
|
|
168
|
-
|
|
169
|
-
|
|
407
|
+
Every active Core tool becomes an agent-scoped DSH definition. Its
|
|
408
|
+
invocation id is a stable 64-character lowercase SHA-256 digest of the session, run, DSH call id,
|
|
409
|
+
and Core tool name. It
|
|
410
|
+
calls the SDK `invoke` DTO without letting the model supply the run, capability revision, route,
|
|
411
|
+
or arbitrary identity. The constrained authorization selector is described above.
|
|
170
412
|
|
|
171
413
|
An SDK error with `disposition === 'accepted_unknown'` starts recovery with the exact invocation
|
|
172
414
|
id; it never repeats the business-tool `invoke`. Likewise, `awaiting_approval`, `in_progress`, and
|
|
@@ -197,41 +439,44 @@ Connection selector, workspace, conversation alias, Core run, active definitions
|
|
|
197
439
|
state are isolated per DSH Agent/session. Named connections for different trusted identities own
|
|
198
440
|
separate SDK credentials and Agent Sessions. Same-binding connections that resolve to the same
|
|
199
441
|
trusted identity are reconciled to one local survivor after authorization. A workspace switch
|
|
200
|
-
preserves the selected connection instance and
|
|
442
|
+
preserves the selected connection instance and changes connection-management defaults; it is rejected while any
|
|
201
443
|
Core run is active/completing or has an unsynchronized completion payload.
|
|
202
444
|
|
|
203
445
|
After a same-alias login resolves to a different trusted identity, the SDK-returned replacement
|
|
204
|
-
alias becomes the adapter default
|
|
446
|
+
alias becomes the adapter's registry default. The retained old alias and the new alias both
|
|
205
447
|
remain visible through `connections list` and user-selectable through `connections use`; existing
|
|
206
448
|
DSH sessions remain pinned as described below.
|
|
207
449
|
|
|
208
450
|
Multi-connection add/use/remove is exposed only through the `/bailinghub connections` user
|
|
209
|
-
command. It is never registered as a model tool. Selecting a connection changes defaults
|
|
210
|
-
|
|
211
|
-
|
|
451
|
+
command. It is never registered as a model tool. Selecting a connection changes registry defaults;
|
|
452
|
+
existing states keep their captured bindings. Defaults affect connection management only;
|
|
453
|
+
the host-owned scope API alone selects the conversation's authorization directory.
|
|
454
|
+
Removing a connection is rejected while any run is active or has an unsynchronized completion. The SDK then
|
|
212
455
|
revokes only that instance's remote Agent Session before removing its local credentials and
|
|
213
456
|
registry metadata; a revoke failure preserves both. Repeating add with the same name and public
|
|
214
457
|
binding selects the existing instance; reusing a name for different public metadata fails.
|
|
215
458
|
`connectionName` remains a local user selector and never becomes a trusted identity claim.
|
|
216
459
|
|
|
217
460
|
After a successful remove, the adapter reads the registry again. A valid remaining
|
|
218
|
-
`currentConnectionKey` replaces all four public defaults
|
|
461
|
+
`currentConnectionKey` replaces all four public connection-management defaults, using the key itself
|
|
219
462
|
when the profile has no alias; no remaining connection sets the adapter to unconfigured. A refresh
|
|
220
463
|
failure does not change the successful remove result. It makes a removed default unavailable, but
|
|
221
464
|
does not invalidate an unchanged non-current default. Existing session state is never rewritten.
|
|
222
465
|
|
|
223
|
-
The four static adapter fields bootstrap SDK construction only.
|
|
224
|
-
|
|
466
|
+
The four static adapter fields bootstrap SDK construction only. For connection management, the
|
|
467
|
+
adapter reads `connectionsList()` and adopts the public
|
|
225
468
|
metadata matching `currentConnectionKey`. Invalid, missing, or unavailable registry data leaves the
|
|
226
469
|
bootstrap defaults in place and must not remove or block unrelated host tools. The lookup is not a
|
|
227
470
|
model tool, and restoring or later selecting a default never mutates an already-created session.
|
|
471
|
+
These registry bootstrap rules do not grant business scope, even to a new
|
|
472
|
+
conversation. A failed scope load or selection never uses the bootstrap fields as a fallback.
|
|
228
473
|
|
|
229
474
|
The completion request is restricted to:
|
|
230
475
|
|
|
231
476
|
```json
|
|
232
477
|
{
|
|
233
478
|
"assistant_message_id": "stable alias",
|
|
234
|
-
"content": "visible final text",
|
|
479
|
+
"content": "visible final text for one authorization, or that run's deterministic call summary for a multi-authorization scope",
|
|
235
480
|
"status": "completed | failed | cancelled",
|
|
236
481
|
"model": "optional",
|
|
237
482
|
"runtime": "optional",
|
package/docs/COMPATIBILITY.md
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
# Compatibility
|
|
2
2
|
|
|
3
|
-
## Native Agent Client 0.
|
|
3
|
+
## Native Agent Client 0.4.0
|
|
4
|
+
|
|
5
|
+
| Component | Release pairing / requirement |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| DeepSeek Harness | `0.1.1-rc.2`; real Session, Cordis lifecycle, commands, prompt assembly, and ToolRuntime regression coverage |
|
|
8
|
+
| Node.js | `^22.19.0` or `>=24.0.0` |
|
|
9
|
+
| DSH tool presentation | Native Tool Mode; Code Mode deliberately degraded |
|
|
10
|
+
| Generic Agent Client SDK | Exact `bailinghub-mcp-server@0.4.0` via `./sdk` |
|
|
11
|
+
| BailingHub Core | Recommended `bailinghub@0.6.1`; Agent Auth v1, Agent Client Runtime v1, and conversation audit v1 |
|
|
12
|
+
| Visible archive acknowledgement | `bailing.agent-conversation-audit-ack.v1` |
|
|
13
|
+
| Selected authorization group | One Hub + Client App + workspace; no cross-system or cross-route scope |
|
|
14
|
+
|
|
15
|
+
Core `0.6.0` is the minimum API version for this contract. Use Core `0.6.1` for the
|
|
16
|
+
recommended release pairing; the patch does not change these business APIs.
|
|
17
|
+
|
|
18
|
+
Install only `dsh-bailinghub@0.4.0`; its ordinary dependency installs the exact SDK. A release
|
|
19
|
+
requires a registry-generated lockfile and a clean package/profile check. Local source and
|
|
20
|
+
synthetic HTTP verification are compatibility evidence, not evidence of an organization's
|
|
21
|
+
production use. The older 0.3 baseline is retained below for existing users, not as a claim that
|
|
22
|
+
0.3 includes 0.4 features.
|
|
23
|
+
|
|
24
|
+
New conversations need explicit scope selection before the first message. Custom hosts must await
|
|
25
|
+
and display selection, preserve the stable conversation id, and restore the original scope before
|
|
26
|
+
sending on reopen. Missing started-session snapshots stay blocked. Saved drafts require fresh
|
|
27
|
+
confirmation. Scope restoration and archive synchronization do not recover business invocations,
|
|
28
|
+
approvals, or task execution after a process restart.
|
|
29
|
+
|
|
30
|
+
Temporary network failure during reopening is retryable on the same runtime under the complete
|
|
31
|
+
original scope. Confirmed revocation, replaced identity, or storage/CAS conflict stays blocked.
|
|
32
|
+
Archived event ids and payloads remain stable on retry; known local write errors and detectable
|
|
33
|
+
history gaps cannot be hidden by a connectivity failure.
|
|
34
|
+
|
|
35
|
+
The archive transport seam is optional for injected older SDKs. They report `unsupported` while
|
|
36
|
+
existing business methods remain usable. An older Core may leave durable events pending or
|
|
37
|
+
unsupported; that degradation does not establish full compatibility with this release. Empty scope
|
|
38
|
+
starts no business run or archive operation. No model can select a new credential or bypass the
|
|
39
|
+
full selected group.
|
|
40
|
+
|
|
41
|
+
Local scope and outbox files use private POSIX permissions and atomic/CAS persistence. The outbox
|
|
42
|
+
contains plaintext visible task text, persists after acknowledgement, and has no automatic
|
|
43
|
+
retention cleanup. See [Privacy](../PRIVACY.md) and the [host contract](AGENT_CLIENT_CONTRACT.md).
|
|
44
|
+
|
|
45
|
+
The CI matrix checks Ubuntu and Windows with Node.js 22.19.0 and 24. Live business/browser
|
|
46
|
+
acceptance still belongs to each deployment; the matrix is not a universal deployment claim.
|
|
47
|
+
|
|
48
|
+
## Historical native Agent Client 0.3.0
|
|
4
49
|
|
|
5
50
|
| Component | Verified version |
|
|
6
51
|
| --- | --- |
|
|
@@ -80,7 +125,7 @@ Public `dsh-bailinghub@0.1.1` remains a configuration-only bundle. It starts the
|
|
|
80
125
|
operator-configured Hub URL, route-scoped Client Token, and route. It does not establish an Agent
|
|
81
126
|
Session, receive a dynamic capability catalog, or move orchestration into local DSH.
|
|
82
127
|
|
|
83
|
-
The
|
|
128
|
+
The native line must not mutate the published 0.1 package or reinterpret its configuration. A new
|
|
84
129
|
BailingHub Core release is compatible only after a separate clean legacy profile proves that the
|
|
85
130
|
0.1.1 `/run` and `/jobs/{job_id}` flow still works.
|
|
86
131
|
|
|
@@ -88,9 +133,11 @@ BailingHub Core release is compatible only after a separate clean legacy profile
|
|
|
88
133
|
|
|
89
134
|
Compatibility requires independent evidence for both paths:
|
|
90
135
|
|
|
91
|
-
1. Native 0.
|
|
136
|
+
1. Native 0.4: clean install of only the exact plugin package, browser authorization, workspace
|
|
92
137
|
discovery, same-identity replacement, different-identity isolation, read, permitted mutation,
|
|
93
|
-
approval/resume, visible completion, and Hub trajectory.
|
|
138
|
+
approval/resume, visible completion, and Hub trajectory. Additionally verify explicit single/multiple
|
|
139
|
+
scope selection, full-set archive authorization, offline reopen/retry, revocation, and no business
|
|
140
|
+
replay after a lost archive acknowledgement.
|
|
94
141
|
2. Legacy 0.1.1: clean static profile, fixed Client Token route, one submit, and same-job follow-up
|
|
95
142
|
through the unchanged public Client API.
|
|
96
143
|
|