@gestaltrun/dsh-im-runtime 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.i18n.yaml +43 -0
- package/README.md +158 -2
- package/README.zh.md +158 -0
- package/UPSTREAM.json +66 -0
- package/lib/index.js +2 -0
- package/lib/runtime-C0-S-_CC.js +6758 -0
- package/lib/tools.js +2 -0
- package/lib/types/agent-coordinator.d.ts +84 -0
- package/lib/types/agent-prompt.d.ts +18 -0
- package/lib/types/delivery-schema.d.ts +52 -0
- package/lib/types/delivery-sqlite.d.ts +187 -0
- package/lib/types/delivery-types.d.ts +648 -0
- package/lib/types/delivery.d.ts +91 -0
- package/lib/types/errors.d.ts +8 -0
- package/lib/types/index.d.ts +19 -0
- package/lib/types/policies.d.ts +53 -0
- package/lib/types/route-resolve.d.ts +10 -0
- package/lib/types/runtime.d.ts +360 -0
- package/lib/types/schema.d.ts +50 -0
- package/lib/types/service-types.d.ts +209 -0
- package/lib/types/session-events.d.ts +28 -0
- package/lib/types/simulation-controller.d.ts +64 -0
- package/lib/types/simulation-tools.d.ts +14 -0
- package/lib/types/tools.d.ts +17 -0
- package/lib/types/transport.d.ts +162 -0
- package/lib/types/transports.d.ts +41 -0
- package/lib/types/types.d.ts +504 -0
- package/lib/types.js +1 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +99 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DeepSeek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write product/im-runtime/README.md
|
|
5
|
+
/:
|
|
6
|
+
en: 906a6c4920ae9a55
|
|
7
|
+
zh: 4e879f55b46a7c72
|
|
8
|
+
/gestaltrun-dsh-im-runtime:
|
|
9
|
+
en: 27f2065b62d82c56
|
|
10
|
+
zh: 85169d78b241e6ed
|
|
11
|
+
/gestaltrun-dsh-im-runtime/summary:
|
|
12
|
+
en: cdaba48ff5e905e3
|
|
13
|
+
zh: 5d863d7c03e57287
|
|
14
|
+
/gestaltrun-dsh-im-runtime/table-of-contents:
|
|
15
|
+
en: d152484eb41ac6b4
|
|
16
|
+
zh: f195efdb389893a5
|
|
17
|
+
/gestaltrun-dsh-im-runtime/use-this-package:
|
|
18
|
+
en: 4b8a4df6cb9539a6
|
|
19
|
+
zh: b8114f149b04f124
|
|
20
|
+
/gestaltrun-dsh-im-runtime/use-this-package/when-to-choose-it:
|
|
21
|
+
en: 1993d31bfe86eeb0
|
|
22
|
+
zh: 3469281d8f4faa1a
|
|
23
|
+
/gestaltrun-dsh-im-runtime/use-this-package/minimal-configuration:
|
|
24
|
+
en: 6a0b209c53330fde
|
|
25
|
+
zh: 15753119e86b859f
|
|
26
|
+
/gestaltrun-dsh-im-runtime/understand-the-implementation:
|
|
27
|
+
en: ad1d63cbb7590b13
|
|
28
|
+
zh: 62718b344eec8b68
|
|
29
|
+
/gestaltrun-dsh-im-runtime/further-exploration:
|
|
30
|
+
en: edc52c7f932707bb
|
|
31
|
+
zh: f37eab10331891a5
|
|
32
|
+
/gestaltrun-dsh-im-runtime/model-experience:
|
|
33
|
+
en: ce2e714647afb6cf
|
|
34
|
+
zh: f6b94dd4a2887d55
|
|
35
|
+
/gestaltrun-dsh-im-runtime/model-experience/kv-cache-effect:
|
|
36
|
+
en: c0457dc6d6e4b5ee
|
|
37
|
+
zh: 0f48dbcd0e2f60ed
|
|
38
|
+
/gestaltrun-dsh-im-runtime/known-limitations-and-deferred-work:
|
|
39
|
+
en: 7be0b9c37a9819e5
|
|
40
|
+
zh: 1981591ad2ce0581
|
|
41
|
+
/gestaltrun-dsh-im-runtime/known-limitations-and-deferred-work/dev-note:
|
|
42
|
+
en: c78baaa0547a5de3
|
|
43
|
+
zh: 70f24c35ac862142
|
package/README.md
CHANGED
|
@@ -1,3 +1,159 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
description: "Configure durable IM accounts and routes, then retain scoped message history, a rebuildable customer index, provider cursors, Session submission evidence, and outbound outcomes."
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
# @gestaltrun/dsh-im-runtime
|
|
7
|
+
|
|
8
|
+
English | [中文](README.zh.md)
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
Use this package to store safe DingTalk or Wangwang account facts, bind complete conversation routes to Workspaces, and retain message delivery state. Inbound pages, history imports, provider cursors, Session submission evidence, Agent task generations, outbound outcomes, customer lists, and handling pauses have durable query receipts. Credentials remain in the Credentials service. Platform providers own login, event conversion, sends, and receipt lookup; this runtime owns admission into ordinary Agents.
|
|
13
|
+
|
|
14
|
+
The installation supplies `@deepseek-ai/dsh-agent-preset-registry` as a runtime peer. Its matching development dependency supplies declarations and the test implementation.
|
|
15
|
+
|
|
16
|
+
## Table of Contents
|
|
17
|
+
|
|
18
|
+
- [Use this package](#use-this-package)
|
|
19
|
+
- [Understand the implementation](#understand-the-implementation)
|
|
20
|
+
- [Further Exploration](#further-exploration)
|
|
21
|
+
- [Model Experience](#model-experience)
|
|
22
|
+
- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
|
|
23
|
+
- [Dev Note](#dev-note)
|
|
24
|
+
|
|
25
|
+
-----
|
|
26
|
+
|
|
27
|
+
<a id="use-this-package"></a>
|
|
28
|
+
## Use this package
|
|
29
|
+
|
|
30
|
+
Mount Storage, a KV backend, StorageDomain, Credentials, Session persistence, Workspace, AgentLoop, AgentPresets, a default Agent model, this runtime, and at least one `ImTransport` provider. The runtime remains usable for configuration and history when the Agent services are absent; automatic admission activates only in their injected scope.
|
|
31
|
+
|
|
32
|
+
### When to choose it
|
|
33
|
+
|
|
34
|
+
Choose this package for the product IM configuration authority shared by Host providers and the IM BFF. Consumers that need wire DTOs or write-only account setup request types should import `@gestaltrun/dsh-im-runtime/types`; Host code imports the root entry for `ImRuntimeService`, `ImRuntimeError`, and `ImTransports`.
|
|
35
|
+
|
|
36
|
+
### Minimal configuration
|
|
37
|
+
|
|
38
|
+
`admissionBatchSize` limits one model-visible batch and defaults to 1000. Group `everyN` values cannot exceed this limit. `accountSetupTtlMs` limits unconfirmed provider setup material in Host memory and defaults to five minutes. StorageDomain selects the durable backend for account configuration, and the Credentials implementation selects the secret store. Conversation delivery uses a Host-owned sqlite file.
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
- name: '@deepseek-ai/dsh-storage'
|
|
42
|
+
- name: '@deepseek-ai/dsh-storage-json'
|
|
43
|
+
config:
|
|
44
|
+
root: /absolute/path/to/storage
|
|
45
|
+
- name: '@deepseek-ai/dsh-storage-domain'
|
|
46
|
+
config:
|
|
47
|
+
backend: json
|
|
48
|
+
- name: '@deepseek-ai/dsh-credentials-local'
|
|
49
|
+
config:
|
|
50
|
+
path: /absolute/path/to/.credentials.yaml
|
|
51
|
+
- name: '@gestaltrun/dsh-im-runtime'
|
|
52
|
+
config:
|
|
53
|
+
admissionBatchSize: 1000
|
|
54
|
+
accountSetupTtlMs: 300000
|
|
55
|
+
deliverySqlitePath: /absolute/path/to/storage/gestaltrun_im_delivery.sqlite
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`listAccountCandidates` returns installed DingTalk profiles and any legacy admitted Wangwang merchants from the registered transport. `startPlatformLogin`, `platformLoginStatus`, and `cancelPlatformLogin` delegate an optional interactive provider login without persisting attempt state or exposing process output; provider teardown owns active attempts. DingTalk setup must select one listed profile. New Wangwang setup may instead carry use-time merchant, main service-account, HTTPS endpoint, and write-only AccessKey fields; the runtime delegates those fields to the transport and verifies the returned merchant and any verified service-account identity. `discoverConversations` returns provider-known conversation titles with stable platform ids for one configured account. `inspectConversation` resolves one conversation title by its platform id when the page listing does not include it. `previewAccountSetup` asks the transport to verify the write-only input and returns a safe identity, authorization fact, expiration time, and Host-minted setup id without creating an account or writing Credentials. `confirmAccountSetup` fixes that identity, stores the credential record and account with one durable operation receipt, and replays the same setup and operation id without creating another account, including after a Host restart. A provider may return `unchecked` when a signed operation succeeds without enough independent identity evidence; the account is saved but remains unusable for route setup until authorization refresh supplies a verified fact. A second DingTalk profile or Wangwang merchant that is already configured is rejected as `IM_ACCOUNT_EXISTS`. `deleteAccount` compares the observed revision, stops the listener, and removes the account and its routes; conversation history remains. `cancelAccountSetup`, request cancellation, setup expiry, and runtime disposal release unconfirmed in-memory material; JavaScript does not guarantee physical memory erasure. Call `queryAccountOperation` after an uncertain pause, disconnect, reconnect, refresh, or confirmed setup response. A successful `deleteAccount` removes the account aggregate, so that operation has no later query receipt. `inspectAccount` and `refreshAccount` report provider-observed authorization facts without changing the account identity. Connection intent, authorization, and listener state remain separate facts.
|
|
59
|
+
|
|
60
|
+
Every route includes platform, account, conversation kind, an `all` or `specific` target, and a Workspace owner. A specific direct target may retain provider peer identifiers separately from its stable platform conversation id. A specific route wins over an `all` route even when the specific route is disabled. Direct routes reject group settings; group routes require at least one of `mention`, positive `everyN`, or positive `fixedIntervalSeconds`. `createRoute` may record optional `agentPreset` and `controller`. A recorded preset is used as-is; absence takes the runtime default at admit time. Empty values are rejected, and a controller requires a recorded preset. Callers fail when a recorded preset cannot mount; they do not fall back. Product Providers register at `ctx.imRoutePolicies` for one controller identity. Missing Providers refuse admission and automated sends; ordinary routes without a controller skip that registry.
|
|
61
|
+
|
|
62
|
+
`createRoute`, `saveRoute`, `rebindRoute`, and `deleteRoute` store their result with the owning account aggregate. `saveRoute` cannot change the Workspace owner; it may replace `agentPreset` and `controller` when those fields are sent. `rebindRoute` and `deleteRoute` compare both the observed route revision and Workspace. Call `queryRouteOperation` after a transport-level timeout before deciding whether to submit another operation id. Simulation-target save and removal use the same explicit query pattern per Workspace. `registerObservation` keeps history flowing for one connected account without an enabled admit route; those leases live and die with the registering Cordis fiber.
|
|
63
|
+
|
|
64
|
+
`ingestInboundPage` durably stores one complete real or configured simulation conversation page, deduplicated by full scope plus platform message identity. A later duplicate can only advance explicit mention evidence from absent or false to true; a submitted row stays submitted and its earlier Session event is unchanged. JSONL imports enter query history and never enter pending Agent delivery. `reconcileSession` scans `SessionPersistence` after a crash and confirms matching stable sources. Delivery records and Session logs remain separate durable writes and do not claim a cross-log transaction. `listCustomers` pages rebuildable conversation summaries for one account and never mixes real and simulated rows; literal substring search escapes `%` and `_`. `ackCustomerRead` advances a Host-owned reader watermark with max semantics. `setHandlingControl` persists a customer-level automatic-handling pause. Customer-index tables share the delivery sqlite file; physical layout version 2 migrates from version 1 explicitly, and a newer on-disk version is refused.
|
|
65
|
+
|
|
66
|
+
Provider polling cursors have explicit account-and-stream ownership. The runtime-owned listener sink accepts one provider page with stable per-conversation operation ids. It commits every conversation group, then commits the provider cursor with all page receipts. If the process stops or a later group fails between those writes, it re-reads the old provider cursor and safely replays the groups through durable deduplication. The listener receives an enabled-route plan that identifies whether mention evidence is required, plus optional observation leases that keep history flowing without admission. This permits a Wangwang merchant page to cover several conversations and lets DingTalk combine all-group and at-me observations without assigning a provider cursor to one conversation.
|
|
67
|
+
|
|
68
|
+
`registerOutbound` stores an intent before any platform call. `beginOutboundAttempt` grants one attempt; a restart or repeated begin while dispatch is unresolved records `result-unknown`, which callers query or confirm without a blind retry. Route and account generations are frozen for automated intents. `realScopeForSession` resolves a real Session only from its durable task generation and returns the frozen scope, Workspace, direct recipient, safe account identity, account and listener state, last conversation sync, and provider-known conversation label or group size. Unknown labels and sizes stay absent. The GUI uses `sendManualMessage`, `queryManualMessage`, and `confirmManualMessage` with one stable request id. Confirmation only calls the provider receipt lookup. `retryManualMessage` requires a new request id and a `result-unknown` predecessor from the same Session scope, and persists that relation. Callers may send optional `scopeId` and `accountId`; a scope send requires `accountId`. Manual DSH sends remain available while automatic handling is paused; disconnected, unauthorized, or unavailable-listener accounts reject a new manual intent. A settled human-manual send is also stored as live inbound with sender `human-dsh`, so the next Agent admission can include `[operator from DSH]` without waiting for a provider echo. A later provider echo of that same outbound request replaces the placeholder `dsh-manual:<requestId>` external id instead of opening a second inbound row. Automated intents remain blocked by pause and route-generation checks. A Wangwang direct recipient is the last external sender observed in that conversation, including its entire stored history. The conversation id is never that recipient. Only a conversation that has never received a buyer message lacks a recipient; automatic and manual sends then fail as `WANGWANG_DIRECT_RECIPIENT_UNAVAILABLE` and do not call the transport. A stored recipient that is not one of those senders is ignored until an observed sender replaces it.
|
|
69
|
+
|
|
70
|
+
Providers pass actor and echo facts to `classifyInboundSender`. A sent automated outbox match yields `ai`, a sent manual match yields `human-dsh`, and explicit provider-native evidence yields `human-native`. An inbound platform message id that already exists on a sent outbox is classified as that echo even when the provider labeled the actor as external. A later inbound from that echo actor, or from the configured account's durable actor id, is `human-native` and still enters Agent admission. Text equality never changes sender attribution. Agent admission ignores `ai` rows. Mention activation uses only the provider's explicit mention metadata stored with the message. Native-client operator rows reach the model as `[operator from native client]`; DSH manual rows use `[operator from DSH]`. External counterpart rows use `[counterpart <name>]` or `[counterpart]` when the display name is absent. The role marker is a bracketed prefix followed by a space, not `[label]: dest`, because GFM would treat the latter as a link definition and Trajectory's Markdown preview would be empty. These labels stay platform-neutral; they do not call the other party a buyer.
|
|
71
|
+
|
|
72
|
+
Durable live or simulation input automatically starts or resumes one ordinary Agent per route generation. Bundled Workspace leases protect Agent setup and each turn, release at `turn/end`, and reacquire at the next prompt admission even when queued turns share one running interval. Prompt sections are reassembled after a new lease applies content. Each IM Agent registers a scoped `im:takeover` system-prompt section on its Agent context. The section states that this Session takes over one conversation for the configured account, names the frozen platform, account, conversation kind, and destination, and explains counterpart versus operator labels. Labeled operator IM rows still wake the Agent and require no redundant reply. An unlabeled user message is the operator speaking directly to the Agent: the Agent does the requested work and always replies with assistant text in the Session. Assistant text is shown to the operator and is never delivered to IM. Current-turn private user work can continue across a customer pause or handling-epoch change; paused or stale IM inputs are filtered. This permission does not refresh the outbound handling epoch, and historical private user turns do not authorize later automatic work. That standing identity is not a per-turn runtime context. Direct messages activate immediately. Group mention, `everyN`, and fixed interval conditions are OR-combined into one batch; the interval timer fires without another inbound event. The Session source records the scope, exact message identities, sender evidence, trigger reasons, route revision, account revision, and Workspace. An enabled-only route save keeps the latest matching task generation, its Session, and its direct recipient. A change of workspace, account revision, agent preset, controller, or group trigger still opens a new generation. A rebind leaves an in-flight Agent on its recorded Workspace and sends later input to a new task generation. New input for an unchanged generation uses `steer`, so it enters at the nearest safe step without cancelling active model or tool work. A busy Agent records that steer as `agent/inbox/spliced`; admission treats the splice as committed Session evidence and marks the inbound submitted without waiting for a later `user/message`.
|
|
73
|
+
|
|
74
|
+
`createSimulationInstance` accepts one live simulated-user Session already owned by a Workspace with a configured target. It first persists `creating` with a Host-minted tested Session id, the exact target, both Workspace ids, route and account revisions, preset, trigger settings, and allowed participants. It then creates the tested Agent through the normal Agent and preset services, attaches its Session to the target Workspace, flushes both Session logs, and publishes `running`. A specific route fixes its conversation id; an `all` route requires the caller to name one. Target changes affect later instances only. Separate simulated-user Sessions can run isolated instances against the same target. Simulation admission uses the instance's frozen route and controller with the same policy Provider; edits or deletion of the live route do not rewrite existing instances. A pending pause or handling epoch change revokes an unsubmitted batch, including changes during policy authorization or Agent preparation.
|
|
75
|
+
|
|
76
|
+
Member and managed-human injection enter the same durable inbound store and Agent coordinator as provider messages. The Host derives the managed actor from the frozen account identity and checks member injections against the frozen allow-list. Only the simulated-user Session receives `im_sim_*` controls; the tested Agent receives the ordinary scoped history and send tools without simulation internals. Tested-Agent replies use the same scoped outbox, settle locally, and return only to the paired simulated-user Session; no simulation path calls a platform transport. `importSimulationHistory` derives the frozen scope, stores only the source basename and Host-counted total/imported/duplicate rows on the instance, and keeps every imported row query-only. `scopeForSession` returns the authoritative role, peer Session, Workspace pair, and delivery scope for Client navigation.
|
|
77
|
+
|
|
78
|
+
Inbound content distinguishes text, Markdown, an image placeholder, and unsupported provider types. A quote carries only its available sender, external id, and text context. Unsupported content preserves the original message type and provider-normalized display-safe JSON details; provider credentials, transport headers, and connection configuration are outside this value. These fields persist unchanged for history rendering and Session reconstruction.
|
|
79
|
+
|
|
80
|
+
`beginStopSimulation` first persists `stopping`, which rejects later input and reply delivery, and returns without waiting for a tool caller's current turn. `waitSimulationStopped` is the GUI terminal barrier. The controller cancels only the paired live activities and the selected scope's coordinator work, retains the simulated-user Session and both logs, and stores `stopped` only after quiescence. Calls are deduplicated across GUI and bound `im_sim_stop` orderings. A stopped instance never resumes; loading the runtime does not resume either running Session merely to display it.
|
|
81
|
+
|
|
82
|
+
The `@gestaltrun/dsh-im-runtime/tools` entry registers `im_query_history`, `im_send_message`, and the scoped `im:takeover` system-prompt section on a trusted tested-Agent context. `im_send_message` is the only outbound path for that conversation; the model must not also write the same reply as assistant text. Neither tool accepts account, conversation, simulation instance, route, or Workspace arguments from the model. Eligible simulated-user Agents receive `im_sim_create`; a running pair adds allow-listed member send, managed-human send, and `im_sim_stop`. These tools bind the actual calling Session and current instance rather than accepting those identities from model arguments. Automated real sends use the task's recorded route and the observed Wangwang counterpart; if that generation is stale, the outbox records `route-changed` before any provider call. A Wangwang send with no observed counterpart fails before the provider call. The model-visible tool result is `Error: IM reply failed (WANGWANG_DIRECT_RECIPIENT_UNAVAILABLE <request id>): no counterpart sender has been observed in this Wangwang conversation, so nothing was sent. Wait for a counterpart message. Do not use the conversation id as the recipient.`
|
|
83
|
+
|
|
84
|
+
-----
|
|
85
|
+
|
|
86
|
+
<a id="understand-the-implementation"></a>
|
|
87
|
+
## Understand the implementation
|
|
88
|
+
|
|
89
|
+
<details>
|
|
90
|
+
<summary>Implementation internals — click to expand</summary>
|
|
91
|
+
|
|
92
|
+
The `gestaltrun_im_runtime` StorageDomain has one record per account aggregate, Workspace simulation target, and two-Session simulation instance, and stays on the JSON backend. Conversation delivery, provider cursors, Agent task generations, and customer-index tables live in a Host-owned sqlite file at monotonic physical schema version 2. The runtime reads those records by key and keeps a small parsed-document cache; it does not load every conversation at open. A conversation write still stores one JSON document per scope, cursor, or execution row and updates the customer index in the same sqlite transaction. Provider cursor commits happen only after referenced page receipts exist. No operation claims atomicity across credentials, configuration, delivery, provider cursor, Agent task, simulation instance, or Session records.
|
|
93
|
+
|
|
94
|
+
`ImTransports` reserves one live provider per platform and releases it with the registering Cordis fiber. The runtime starts its listener only when a connected, unpaused account has authorization `ready` and either an enabled route or a live observation lease; `unchecked`, `required`, or `failed` authorization keeps listening and manual sending stopped. Refresh an `unchecked` account after the provider can return independent identity evidence. A provider `listen` resolves only after that listener is established and verified usable. Its returned `done` promise reports later clean termination or failure. An intentional stop aborts the signal, calls `dispose`, and waits for `done`; changing the enabled-route plan follows that sequence before starting its replacement. Terminal failures do not schedule an unbounded retry. `ctx.imRuntime.subscribe` and the typed `imRuntime/changed` event publish durable changes and later process listener transitions. Snapshot revisions order events within one process generation; durable operation ids and record revisions survive restart.
|
|
95
|
+
|
|
96
|
+
| Source | Purpose |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `src/types.ts` | Client-safe identifiers, views, requests, and receipts |
|
|
99
|
+
| `src/delivery-types.ts` | Client-safe history, cursor, Session-source, and outbox DTOs |
|
|
100
|
+
| `src/service-types.ts` | Host Context service and typed event declarations |
|
|
101
|
+
| `src/runtime.ts` | Configuration behavior and Host-facing Session integration |
|
|
102
|
+
| `src/delivery.ts` | Scoped receive, query, submission, cursor, and outbox behavior |
|
|
103
|
+
| `src/schema.ts` | Durable StorageDomain records and validation |
|
|
104
|
+
| `src/delivery-schema.ts` | Durable conversation and provider-cursor aggregates |
|
|
105
|
+
| `src/delivery-sqlite.ts` | Host-owned sqlite file, key lookups, lookup indexes, and customer-index tables |
|
|
106
|
+
| `src/policies.ts` | Fiber-scoped controller authorization registry |
|
|
107
|
+
| `src/route-resolve.ts` | Explicit Agent-preset resolution for a stored route |
|
|
108
|
+
| `src/transports.ts` | Reversible platform-provider registry |
|
|
109
|
+
| `src/agent-coordinator.ts` | Durable trigger evaluation and ordinary Agent create, resume, and steer lifecycle |
|
|
110
|
+
| `src/simulation-controller.ts` | Persistent two-Session creation, isolated reply delivery, recovery, and terminal stop |
|
|
111
|
+
| `src/simulation-tools.ts` | Trusted Session-bound simulation tool registration |
|
|
112
|
+
| `src/tools.ts` | Scope-bound history and outbound Agent tools |
|
|
113
|
+
|
|
114
|
+
</details>
|
|
115
|
+
|
|
116
|
+
-----
|
|
117
|
+
|
|
118
|
+
<a id="further-exploration"></a>
|
|
119
|
+
## Further Exploration
|
|
120
|
+
|
|
121
|
+
- [Architecture](../../docs/architecture.md) — persistence services used by the runtime
|
|
122
|
+
- [Credential service](../../packages/credentials/credentials/README.md) — opaque record ownership and write semantics
|
|
123
|
+
- [Workspace service](../../packages/workspace/workspace/README.md) — authoritative Workspace identifiers
|
|
124
|
+
- [IM migration decision](../../.agents/notes/proposed/architecture/2026-09-14-im-takeover-plugin-migration.md) — product package boundaries and later delivery slices
|
|
125
|
+
|
|
126
|
+
-----
|
|
127
|
+
|
|
128
|
+
<a id="model-experience"></a>
|
|
129
|
+
|
|
130
|
+
Simulation injections require a caller-minted `requestId`. Reusing it with the same instance, identity and text returns the persisted inbound receipt; changing identity or text fails. `querySimulationInjection` reads the original receipt, including after stop or restart, without delivering another message. Buyer input uses the frozen speaking-member allow-list. Seller input uses the managed account as `[operator from DSH]`. Both use instance-local delivery; stop closes input admission before draining work and awaiting both Sessions.
|
|
131
|
+
|
|
132
|
+
## Model Experience
|
|
133
|
+
|
|
134
|
+
Each admitted batch reaches the model as one identified user message assembled by the target Agent preset. Its source carries the complete durable evidence required for crash reconciliation. The Agent-scoped `im:takeover` section sits after the deployment persona prefix and tells the model it speaks as the configured account in this one conversation. Labeled user-role lines are IM records. An unlabeled user message is the operator speaking directly to the Agent and always receives assistant text in this Session. Assistant text is shown to the operator and is never delivered to IM. Counterpart and operator labels on inbound lines are the only per-message identity. The scope-bound IM tools expose only the admitted conversation's history and outbound path. Simulation tools are present only for a configured simulated-user Workspace or an existing bound instance, and their instance identity comes from the live Agent Session.
|
|
135
|
+
|
|
136
|
+
#### KV Cache effect
|
|
137
|
+
|
|
138
|
+
One unchanged route generation reuses its Agent Session and prompt cache. An enabled-only route save stays on that generation. Rebinding creates a new Session because the Workspace and task ownership changed.
|
|
139
|
+
|
|
140
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
141
|
+
## Known Limitations and Deferred Work
|
|
142
|
+
|
|
143
|
+
- Provider packages own live identity checks, conversation discovery, listeners, outbound sends, and receipt confirmation.
|
|
144
|
+
- A failed partial simulation creation is retained as a safe `failed` record for diagnosis and a fresh retry; the runtime does not delete either user-owned Session log or claim a cross-domain rollback.
|
|
145
|
+
- Operation receipts remain durable without automatic pruning because pruning would make an old operation indistinguishable from one never received.
|
|
146
|
+
- Credentials and configuration use separate durable services. A failed account write attempts credential rollback and reports both failures if rollback also fails; it does not claim a cross-service transaction.
|
|
147
|
+
- Opening a conversation still parses that conversation's stored JSON document. Customer-index tables are rebuilt from those documents during the version-1-to-2 migration and later writes.
|
|
148
|
+
|
|
149
|
+
Customer pauses invalidate automatic input and outbound authorization through the handling epoch. Resume admits new live input; previously queued AI replies cannot send. Pending admission batches retain their original epoch across reload. A forward journal owner projects unresolved pauses with `setAutomaticHandlingPausePending`; `isAutomaticHandlingBlocked` combines that hold with the durable handling control. The hold does not fabricate a persisted pause or epoch and remains until the journal owner reconciles persistence and acknowledgement, including across provider disposal. Pending holds block admission, pre-step, registration and final submission. Automatic provider preparation carries a scope-local epoch cancellation signal. Providers call `submission.authorizeSubmission()` after asynchronous preparation and immediately before external submission; that check also verifies the durable attempt and route generation. Customer pause cancels unsubmitted preparation, including across pause/resume. Already submitted messages retain the actual provider settlement. The runtime caller disposes the authorization after settlement. Human sends and provider history synchronization remain independent of customer AI pause.
|
|
150
|
+
|
|
151
|
+
<a id="dev-note"></a>
|
|
152
|
+
### Dev Note
|
|
153
|
+
|
|
154
|
+
<details>
|
|
155
|
+
<summary>Working context for maintainers — click to expand</summary>
|
|
156
|
+
|
|
157
|
+
The migration source and dirty-patch identity are recorded in `UPSTREAM.json`. Run `pnpm --dir product --filter @gestaltrun/dsh-im-runtime test`, `typecheck`, and `build`; after `build`, run `smoke:loader` to exercise the built entry through a real Loader YAML composition.
|
|
158
|
+
|
|
159
|
+
</details>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "配置持久化 IM 账号与路由,并保留作用域化消息历史、可重建客户索引、提供方游标、Session 提交证据和出站结果。"
|
|
3
|
+
kind: "package-reference"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# @gestaltrun/dsh-im-runtime
|
|
7
|
+
|
|
8
|
+
[English](README.md) | 中文
|
|
9
|
+
|
|
10
|
+
## 概要
|
|
11
|
+
|
|
12
|
+
使用此包保存安全的钉钉或旺旺账号事实、将完整会话路由绑定到工作区,并保留消息投递状态。入站页面、历史导入、提供方游标、Session 提交证据、Agent 任务代次、出站结果、客户列表与处理暂停都有持久化查询收据。凭据保留在 Credentials 服务中。平台提供方负责登录、事件转换、发送和回执查询;此 runtime 负责将消息准入普通 Agent。
|
|
13
|
+
|
|
14
|
+
安装环境以运行时对等依赖提供 `@deepseek-ai/dsh-agent-preset-registry`。匹配的开发依赖提供类型声明和测试实现。
|
|
15
|
+
|
|
16
|
+
## 目录
|
|
17
|
+
|
|
18
|
+
- [使用此包](#use-this-package)
|
|
19
|
+
- [理解实现](#understand-the-implementation)
|
|
20
|
+
- [进一步阅读](#further-exploration)
|
|
21
|
+
- [模型体验](#model-experience)
|
|
22
|
+
- [已知限制与后续工作](#known-limitations-and-deferred-work)
|
|
23
|
+
- [开发说明](#dev-note)
|
|
24
|
+
|
|
25
|
+
-----
|
|
26
|
+
|
|
27
|
+
<a id="use-this-package"></a>
|
|
28
|
+
## 使用此包
|
|
29
|
+
|
|
30
|
+
装载 Storage、一个 KV 后端、StorageDomain、Credentials、Session persistence、Workspace、AgentLoop、AgentPresets、默认 Agent 模型、此 runtime,以及至少一个 `ImTransport` 提供方。Agent 服务缺失时仍可使用配置和历史能力;自动准入只在这些服务所在的注入作用域中启用。
|
|
31
|
+
|
|
32
|
+
### 适用条件
|
|
33
|
+
|
|
34
|
+
产品 Host 提供方与 IM BFF 共享 IM 配置权威时使用此包。需要 wire DTO 或只写账号接入请求类型的使用方应导入 `@gestaltrun/dsh-im-runtime/types`;Host 代码从根入口导入 `ImRuntimeService`、`ImRuntimeError` 和 `ImTransports`。
|
|
35
|
+
|
|
36
|
+
### 最小配置
|
|
37
|
+
|
|
38
|
+
`admissionBatchSize` 限制一次模型可见批次,默认值为 1000。群聊 `everyN` 不能超过该限制。`accountSetupTtlMs` 限制未确认提供方接入材料在 Host 内存中的保留时间,默认五分钟。StorageDomain 为账号配置选择持久后端,Credentials 实现选择密钥存储。会话投递使用 Host 自管的 sqlite 文件。
|
|
39
|
+
|
|
40
|
+
```yaml
|
|
41
|
+
- name: '@deepseek-ai/dsh-storage'
|
|
42
|
+
- name: '@deepseek-ai/dsh-storage-json'
|
|
43
|
+
config:
|
|
44
|
+
root: /absolute/path/to/storage
|
|
45
|
+
- name: '@deepseek-ai/dsh-storage-domain'
|
|
46
|
+
config:
|
|
47
|
+
backend: json
|
|
48
|
+
- name: '@deepseek-ai/dsh-credentials-local'
|
|
49
|
+
config:
|
|
50
|
+
path: /absolute/path/to/.credentials.yaml
|
|
51
|
+
- name: '@gestaltrun/dsh-im-runtime'
|
|
52
|
+
config:
|
|
53
|
+
admissionBatchSize: 1000
|
|
54
|
+
accountSetupTtlMs: 300000
|
|
55
|
+
deliverySqlitePath: /absolute/path/to/storage/gestaltrun_im_delivery.sqlite
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`listAccountCandidates` 从已注册 transport 返回已安装的钉钉 profile 和旧版已准入旺旺商家。`startPlatformLogin`、`platformLoginStatus` 和 `cancelPlatformLogin` 委托可选的交互式 Provider 登录,不持久保存 attempt,也不暴露进程输出;Provider teardown 负责活跃 attempt。钉钉 setup 必须选择一个列表 Profile。新的旺旺 setup 可以直接携带使用时商家、主客服账号、HTTPS endpoint 与只写 AccessKey;runtime 把这些字段交给 Transport,并核对返回的商家身份及已验证客服身份。`discoverConversations` 为已配置账号返回提供方已知的会话标题及其稳定平台 id。`inspectConversation` 在列表页未包含该会话时按平台 id 解析标题。`previewAccountSetup` 要求 transport 校验只写输入,并返回安全身份、授权事实、过期时间与 Host 生成的 setup id,不创建账号,也不写入 Credentials。`confirmAccountSetup` 固定该身份,在同一持久操作收据中保存凭据记录与账号;相同 setup 与 operation id 会复用结果,即使 Host 重启也不会重复创建账号。签名操作成功但没有足够独立身份凭据时,Provider 可以返回 `unchecked`;账号会保存,但在刷新授权取得已验证事实之前不能配置路由。已配置的同一钉钉 profile 或旺旺商家会以 `IM_ACCOUNT_EXISTS` 拒绝。`deleteAccount` 比较已观察 revision,停止监听,并删除账号及其路由;会话历史保留。`cancelAccountSetup`、请求取消、setup 过期与 runtime dispose 都会释放未确认的内存材料;JavaScript 不保证物理擦除内存。暂停、断开、重连、刷新或确认接入响应不确定时,调用 `queryAccountOperation` 查询。成功的 `deleteAccount` 会删除账号聚合,该操作没有后续查询收据。`inspectAccount` 和 `refreshAccount` 报告提供方观察到的授权事实,不改变账号身份。连接意图、授权状态和监听状态仍是三个独立事实。
|
|
59
|
+
|
|
60
|
+
每条路由包含平台、账号、会话类型、`all` 或 `specific` 目标以及工作区归属。指定私聊目标可在稳定平台会话 ID 之外单独保留提供方对端标识。指定会话路由始终优先于全量路由,包括指定路由被停用时。私聊路由拒绝群触发设置;群聊路由必须至少配置 `mention`、正整数 `everyN` 或正整数 `fixedIntervalSeconds` 之一。`createRoute` 可记录可选的 `agentPreset` 与 `controller`。已记录的 preset 按原值使用;缺省时准入取 runtime 默认。空值会被拒绝,且声明 controller 必须同时记录 preset。已记录 preset 无法挂载时调用方必须失败,不得回退。产品 Provider 按 controller 身份注册到 `ctx.imRoutePolicies`。缺少 Provider 时拒绝准入和自动发送;未声明 controller 的普通路由跳过该注册表。
|
|
61
|
+
|
|
62
|
+
`createRoute`、`saveRoute`、`rebindRoute` 和 `deleteRoute` 将结果与所属账号聚合记录一同保存。`saveRoute` 不能改变工作区归属;提交 `agentPreset` 与 `controller` 时会替换这两项。`rebindRoute` 与 `deleteRoute` 同时比较读取时的路由 revision 和工作区。传输层超时后,先调用 `queryRouteOperation`,再决定是否使用新的 operation id。模拟目标保存和移除按工作区提供相同的显式查询方式。`registerObservation` 让已连接账号在没有启用准入路由时仍接收历史;这些租约随注册方 Cordis fiber 生存和释放。
|
|
63
|
+
|
|
64
|
+
`ingestInboundPage` 持久保存一个完整真实会话或已配置模拟会话页面,并按完整 scope 与平台消息标识去重。后到的重复消息只能把明确 mention 证据从缺失或 false 单调补为 true;已提交记录仍保持已提交,先前 Session 事件不回写。JSONL 导入只进入查询历史,不进入待处理 Agent 投递。崩溃后,`reconcileSession` 扫描 `SessionPersistence` 并确认匹配的稳定来源。投递记录和 Session 日志仍是独立持久写入,不声称存在跨日志事务。`listCustomers` 按账号分页可重建的会话摘要,真实与模拟行从不混排;字面子串搜索会转义 `%` 与 `_`。`ackCustomerRead` 以 max 语义推进 Host 拥有的读者水位。`setHandlingControl` 持久保存客户级自动处理暂停。客户索引表与投递 sqlite 共用同一文件;物理布局版本 2 从版本 1 显式迁移,磁盘上的更新版本会被拒绝。
|
|
65
|
+
|
|
66
|
+
提供方轮询游标由明确的平台账号与 stream 共同拥有。runtime 拥有的监听 sink 接收一个带稳定逐会话 operation id 的提供方页面。它先提交每个会话分组,再带全部页面收据提交提供方游标。如果进程在这些写入之间停止,或后续分组失败,重启后仍读取旧提供方游标,并通过持久去重安全重放各分组。监听器会收到启用路由计划,其中明确是否需要 mention 证据,以及可选的观察租约,使历史在没有准入时仍能流动。因此一个旺旺商家页面可以覆盖多个会话,钉钉也可组合全量群消息与 at-me 观察,而不会把提供方游标错误归给某个会话。
|
|
67
|
+
|
|
68
|
+
`registerOutbound` 在任何平台调用前保存意图。`beginOutboundAttempt` 只授予一次尝试;调度结果未决时重启或重复 begin 会记录 `result-unknown`,调用者随后查询或确认,不盲目重试。自动意图冻结路由与账号 generation。`realScopeForSession` 只根据持久任务代次解析真实 Session,并返回冻结 scope、工作区、直接收件人、安全账号身份、账号及监听状态、最近会话同步时间,以及提供方已知的会话名称或群人数;未知名称和人数保持缺失。GUI 使用同一个稳定 request id 调用 `sendManualMessage`、`queryManualMessage` 与 `confirmManualMessage`;核验只调用提供方回执查询。`retryManualMessage` 要求新 request id 和同一 Session scope 中处于 `result-unknown` 的前序请求,并持久保存该关联。调用方可传可选的 `scopeId` 与 `accountId`;带 scope 的发送必须带 `accountId`。自动处理暂停时 DSH 人工发送仍可用;账号断连、未授权或监听不可用时拒绝新人工发送意图。已结算的人工发送会同时作为 `human-dsh` 入站保存,后续 Agent 准入可写入 `[operator from DSH]`,不必等待提供方回声。之后同一出站请求的提供方回声会替换占位的 `dsh-manual:<requestId>` 外部 id,不会再开第二条入站。自动意图继续受暂停和路由代次检查阻断。旺旺私聊收件人是本会话最后一个 external 发送者,查找范围包括已存储的全部历史消息。会话 id 不能充当收件人。只有本会话从未收到买家消息时才缺少收件人;此时自动发送和人工发送都以 `WANGWANG_DIRECT_RECIPIENT_UNAVAILABLE` 失败,且不调用 transport。已存收件人若不属于这些发送者,则忽略,直到观察到的发送者将其替换。
|
|
69
|
+
|
|
70
|
+
提供方把参与者和回显事实传给 `classifyInboundSender`。匹配已发送自动 outbox 时返回 `ai`,匹配已发送人工 outbox 时返回 `human-dsh`,明确的平台原生操作证据返回 `human-native`。若入站平台消息 id 已出现在已发送 outbox 上,即使提供方把该参与者标成外部,仍按该回声分类。该回声参与者或已配置账号的持久 actor id 之后再发的入站是 `human-native`,仍进入 Agent 准入。文本相等不会改变发送者归因。Agent 准入忽略 `ai` 行。mention 触发只使用随消息持久保存的提供方明确 mention 元数据。原生客户端人工行到达模型时为 `[operator from native client]`;DSH 人工行使用 `[operator from DSH]`。对端外部行使用 `[counterpart <name>]`,没有显示名时为 `[counterpart]`。角色标记是方括号前缀加空格,不是 `[label]: dest`,否则 GFM 会把它当成链接定义,轨迹页的 Markdown 预览就会是空的。这些标签保持平台中性,不把对端称为买家。
|
|
71
|
+
|
|
72
|
+
持久化真实或模拟输入会按路由代次自动启动或恢复一个普通 Agent。内置工作区租约保护 Agent 设置和每个 turn,在 `turn/end` 释放,并在下一次提示词准入时重新获取;排队的 turn 共享同一 running 区间也适用。新租约应用内容后会重新组装提示词各节。每个 IM Agent 在其 Agent 作用域注册 `im:takeover` 系统提示词 section。该 section 说明本 Session 接管已配置账号的一个会话,写出冻结的平台、账号、会话种类和目的地,并解释 counterpart 与 operator 标签。带标签的操作者 IM 行仍会唤醒 Agent,但无需重复回复。没有标签的用户消息是操作者直接对 Agent 说的话:Agent 完成要求的工作,并总是在 Session 内用 assistant 文本回复。assistant 文本展示给操作者,从不发到 IM。当前轮次的私有用户工作可以跨客户暂停或 handling epoch 变化继续执行;已暂停或过期的 IM 输入会被过滤。这项权限不会刷新出站 handling epoch,历史轮次的私有用户请求也不会授权后续自动工作。这份站立身份不是每轮 runtime context。私聊立即触发。群聊 mention、`everyN` 与固定周期按 OR 合并成一个批次;固定周期无需等待下一条入站事件。Session source 记录 scope、精确消息标识、发送者证据、触发原因、路由 revision、账号 revision 与工作区。只改变路由 enabled 的保存沿用最新匹配代次、其 Session 和直接收件人。工作区、账号 revision、agent preset、controller 或群触发变化仍会新开一代。改绑后,在途 Agent 仍留在其已记录工作区,后续输入进入新的任务代次。未变代次的新输入使用 `steer`,在最近安全步骤进入,不取消正在执行的模型或工具工作。忙碌 Agent 会把这次 steer 记为 `agent/inbox/spliced`;准入把该 splice 视为已提交的 Session 证据,不必再等后续 `user/message`。
|
|
73
|
+
|
|
74
|
+
`createSimulationInstance` 接受一个已经属于工作区且当前存活的模拟用户 Session,该工作区必须已配置目标。它先持久保存 `creating`,其中包含 Host 生成的被测 Session id、精确目标、两个工作区 id、路由和账号 revision、preset、触发设置与准入参与者;随后通过普通 Agent 与 preset 服务创建被测 Agent,把其 Session 关联到目标工作区,flush 两份 Session 日志,最后发布 `running`。指定路由固定其会话 id;`all` 路由要求调用方提供一个会话 id。目标变更只影响后续实例。不同模拟用户 Session 可以针对同一目标运行互相隔离的实例。 模拟准入使用实例冻结的 route 与 controller,通过同一策略 Provider 授权;实时 route 的修改或删除不改写现有实例。pending pause 或 handling epoch 变化会撤销尚未提交的批次,包括策略授权或 Agent 准备期间发生的变化。
|
|
75
|
+
|
|
76
|
+
组员和受管人工注入与提供方消息进入同一个持久入站存储与 Agent coordinator。Host 根据冻结账号身份推导受管操作人,并依据冻结准入名单检查组员注入。只有模拟用户 Session 获得 `im_sim_*` 控制工具;被测 Agent 只获得普通的 scope 绑定历史与发送工具,看不到模拟内部控制。被测 Agent 回复使用同一个 scope 绑定 outbox,在本地结算后只返回配对的模拟用户 Session;模拟路径不会调用平台 transport。`importSimulationHistory` 推导冻结 scope,仅在实例上保存来源 basename 与 Host 统计的总数、导入数和重复数,并让所有导入行保持仅可查询。`scopeForSession` 为 Client 导航返回权威角色、对端 Session、工作区对和投递 scope。
|
|
77
|
+
|
|
78
|
+
入站内容区分文本、Markdown、图片占位与提供方不支持类型。引用只携带可取得的发送者、外部 id 与文本上下文。不支持内容保留原始消息类型及提供方已归一化、可安全展示的 JSON 明细;提供方凭据、transport header 与连接配置不属于此值。这些字段按原样持久保存,供历史展示与 Session 重建。
|
|
79
|
+
|
|
80
|
+
`beginStopSimulation` 先持久保存 `stopping`,使后续输入和回复投递被拒绝,并且不等待工具调用方的当前 turn。`waitSimulationStopped` 是 GUI 的终态屏障。控制器只取消配对的实时活动与所选 scope 的 coordinator 工作,保留模拟用户 Session 和两份日志,并在静止后保存 `stopped`。GUI 与绑定的 `im_sim_stop` 无论调用顺序如何都会去重。已停止实例永不恢复;runtime 装载也不会只为显示运行中实例而恢复任一 Session。
|
|
81
|
+
|
|
82
|
+
`@gestaltrun/dsh-im-runtime/tools` 入口在可信被测 Agent 上下文注册 `im_query_history`、`im_send_message` 和 scoped `im:takeover` 系统提示词 section。`im_send_message` 是该会话唯一出站路径;模型不得再把同一段回复写成 assistant 文本。模型参数不包含账号、会话、模拟实例、路由或工作区。符合条件的模拟用户 Agent 获得 `im_sim_create`;运行中配对另有准入组员发送、受管人工发送和 `im_sim_stop`。这些工具绑定实际调用 Session 与当前实例,不从模型参数接受这些身份。真实自动发送使用任务已记录的路由和观察到的旺旺对端;若该代次已过期,outbox 会在任何提供方调用前记录 `route-changed`。没有观察到对端时,旺旺发送在提供方调用前失败。模型可见的工具结果是 `Error: IM reply failed (WANGWANG_DIRECT_RECIPIENT_UNAVAILABLE <request id>): no counterpart sender has been observed in this Wangwang conversation, so nothing was sent. Wait for a counterpart message. Do not use the conversation id as the recipient.`
|
|
83
|
+
|
|
84
|
+
-----
|
|
85
|
+
|
|
86
|
+
<a id="understand-the-implementation"></a>
|
|
87
|
+
## 理解实现
|
|
88
|
+
|
|
89
|
+
<details>
|
|
90
|
+
<summary>实现细节——点击展开</summary>
|
|
91
|
+
|
|
92
|
+
`gestaltrun_im_runtime` StorageDomain 为每个账号聚合、工作区模拟目标和双 Session 模拟实例保存一条记录,并继续使用 JSON 后端。会话投递、提供方游标、Agent 任务代次和客户索引表保存在 Host 自管的 sqlite 文件中,物理 schema 版本单调为 2。runtime 按 key 读取这些记录,并保留一小份已解析文档缓存;打开时不会载入全部会话。一次会话写入仍按 scope、游标或 execution 各存一个 JSON 文档,并在同一 sqlite 事务中更新客户索引。提供方游标只在引用的页面收据都存在后提交。任何操作都不声称在凭据、配置、投递、提供方游标、Agent 任务、模拟实例或 Session 记录之间提供原子事务。
|
|
93
|
+
|
|
94
|
+
`ImTransports` 为每个平台保留一个存活的提供方,并随注册方 Cordis fiber 释放。只有账号连接意图为连接、未暂停、授权状态为 `ready`,并且存在启用路由或存活的观察租约时,runtime 才启动监听器;`unchecked`、`required` 或 `failed` 授权状态会使监听和手动发送保持停止。提供方能返回独立身份依据后,刷新 `unchecked` 账号即可启用接管。提供方 `listen` 只在监听器已建立并验证可用后返回,其返回的 `done` promise 报告后续正常结束或失败。主动停止会中止 signal、调用 `dispose` 并等待 `done`;启用路由计划变化时也按此顺序停止旧监听器,再启动替代实例。监听器终止失败不会触发无界重试。`ctx.imRuntime.subscribe` 和类型化 `imRuntime/changed` 事件发布持久变更及后续进程监听状态变化。snapshot revision 只在当前进程 generation 内排序;持久 operation id 与记录 revision 跨重启保留。
|
|
95
|
+
|
|
96
|
+
| 源文件 | 用途 |
|
|
97
|
+
|---|---|
|
|
98
|
+
| `src/types.ts` | 客户端安全的标识、视图、请求与收据 |
|
|
99
|
+
| `src/delivery-types.ts` | 客户端安全的历史、游标、Session 来源与 outbox DTO |
|
|
100
|
+
| `src/service-types.ts` | Host Context 服务与类型化事件声明 |
|
|
101
|
+
| `src/runtime.ts` | 配置行为与 Host 侧 Session 集成 |
|
|
102
|
+
| `src/delivery.ts` | 作用域化接收、查询、提交、游标与 outbox 行为 |
|
|
103
|
+
| `src/schema.ts` | 持久 StorageDomain 记录与校验 |
|
|
104
|
+
| `src/delivery-schema.ts` | 持久会话与提供方游标聚合 |
|
|
105
|
+
| `src/delivery-sqlite.ts` | Host 自管 sqlite 文件、按 key 查询、查找索引与客户索引表 |
|
|
106
|
+
| `src/policies.ts` | 随 fiber 生存的 controller 授权注册表 |
|
|
107
|
+
| `src/route-resolve.ts` | 已存储路由的显式 Agent preset 解析 |
|
|
108
|
+
| `src/transports.ts` | 可逆的平台提供方注册表 |
|
|
109
|
+
| `src/agent-coordinator.ts` | 持久触发判断和普通 Agent 创建、恢复与 steer 生命周期 |
|
|
110
|
+
| `src/simulation-controller.ts` | 持久双 Session 创建、隔离回复投递、恢复与终态停止 |
|
|
111
|
+
| `src/simulation-tools.ts` | 绑定可信 Session 的模拟工具注册 |
|
|
112
|
+
| `src/tools.ts` | scope 绑定的历史与出站 Agent 工具 |
|
|
113
|
+
|
|
114
|
+
</details>
|
|
115
|
+
|
|
116
|
+
-----
|
|
117
|
+
|
|
118
|
+
<a id="further-exploration"></a>
|
|
119
|
+
## 进一步阅读
|
|
120
|
+
|
|
121
|
+
- [架构](../../docs/architecture.zh.md) — runtime 使用的持久化服务
|
|
122
|
+
- [凭据服务](../../packages/credentials/credentials/README.zh.md) — 不透明记录的归属与写入语义
|
|
123
|
+
- [工作区服务](../../packages/workspace/workspace/README.zh.md) — 权威工作区标识
|
|
124
|
+
- [IM 迁移决策](../../.agents/notes/proposed/architecture/2026-09-14-im-takeover-plugin-migration.zh.md) — 产品包边界与后续交付切片
|
|
125
|
+
|
|
126
|
+
-----
|
|
127
|
+
|
|
128
|
+
<a id="model-experience"></a>
|
|
129
|
+
## 模型体验
|
|
130
|
+
|
|
131
|
+
每个准入批次通过目标 Agent preset 组装为一条带稳定标识的 user message。其 source 包含崩溃核对所需的完整持久证据。Agent 作用域的 `im:takeover` section 紧挨部署人格前缀之后,告诉模型它在这一个会话中以已配置账号发言。带标签的 user-role 行是 IM 记录。没有标签的用户消息是操作者直接对 Agent 说的话,总是在本 Session 内得到 assistant 文本回复。assistant 文本展示给操作者,从不发到 IM。入站行上的 counterpart 与 operator 标签是仅有的逐条身份。scope 绑定的 IM 工具只暴露已准入会话的历史与出站路径。模拟工具只对已配置的模拟用户工作区或已有绑定实例可见,其实例身份来自实时 Agent Session。
|
|
132
|
+
|
|
133
|
+
#### KV Cache 影响
|
|
134
|
+
|
|
135
|
+
未变化的路由代次复用其 Agent Session 与 prompt cache。只改 enabled 的路由保存仍停留在该代次。改绑会创建新 Session,因为工作区与任务归属已变化。
|
|
136
|
+
|
|
137
|
+
<a id="known-limitations-and-deferred-work"></a>
|
|
138
|
+
## 已知限制与后续工作
|
|
139
|
+
|
|
140
|
+
- 提供方包负责在线身份检查、会话发现、监听器、出站发送和回执确认。
|
|
141
|
+
- 部分模拟创建失败会保留安全的 `failed` 记录,供诊断并重新创建;runtime 不删除任何用户拥有的 Session 日志,也不声称跨领域回滚。
|
|
142
|
+
- operation 收据不会自动清理,因为清理后无法区分旧操作与从未收到的操作。
|
|
143
|
+
- 凭据和配置使用不同的持久化服务。账号写入失败时会尝试回滚凭据;若回滚也失败,则同时报告两个错误,不声称存在跨服务事务。
|
|
144
|
+
- 打开一个会话时仍会解析该会话已存储的 JSON 文档。客户索引表在 1→2 迁移及后续写入时从这些文档重建。
|
|
145
|
+
|
|
146
|
+
客户暂停通过 handling epoch 使自动输入和自动发送授权失效。恢复只准入新实时输入,先前排队的 AI 回复不能发送。待准入批次重载后保留原始 epoch。Forward journal 的 owner 用 `setAutomaticHandlingPausePending` 投影未落实的暂停;`isAutomaticHandlingBlocked` 将该阻断与持久 handling control 合并。阻断不伪造持久暂停或 epoch,并跨 Provider 卸载保留,直到 journal owner 协调好持久化与 acknowledgement。Pending 阻断准入、pre-step、出站注册与最终提交。自动发送的 Provider 准备阶段携带该 scope 的 epoch 撤销信号。Provider 在异步准备完成后、向外提交之前调用 `submission.authorizeSubmission()`;该检查还核验持久尝试与路由代次。客户暂停撤销尚未提交的准备,即使随后恢复也不复活旧授权。已经提交的消息保留真实的 Provider 结算,结算后由 Runtime 调用方释放授权。人工发送和 Provider 历史同步不受客户 AI 暂停影响。
|
|
147
|
+
|
|
148
|
+
<a id="dev-note"></a>
|
|
149
|
+
### 开发说明
|
|
150
|
+
|
|
151
|
+
<details>
|
|
152
|
+
<summary>维护上下文——点击展开</summary>
|
|
153
|
+
|
|
154
|
+
迁移来源与 dirty patch 标识记录在 `UPSTREAM.json`。运行 `pnpm --dir product --filter @gestaltrun/dsh-im-runtime test`、`typecheck` 和 `build`;完成 `build` 后运行 `smoke:loader`,通过真实 Loader YAML 装配验证构建入口。
|
|
155
|
+
|
|
156
|
+
</details>
|
|
157
|
+
|
|
158
|
+
模拟注入必须携带调用方生成的 `requestId`。同一实例、身份和文本复用该标识时返回持久化的入站回执;改变身份或文本会失败。`querySimulationInjection` 按原标识读取回执,停止或重启后仍可查询,不会再投递消息。买家输入使用冻结的发言成员白名单;卖家输入以托管账号的 `[operator from DSH]` 身份记录。两者都使用实例内投递;停止先关闭输入,再排空工作并等待两侧 Session 静止。
|
package/UPSTREAM.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"source": {
|
|
4
|
+
"repository": "https://github.com/gestaltrun/deepseek-harness-gestalt.git",
|
|
5
|
+
"commit": "17ce1b29d96f180a31e7cfa3be8770be20d589e7",
|
|
6
|
+
"dirtyPatchSha256": "8175cef04d3c542d7e0a93d2e85455b636e7e8530c392afa68789325cacfbea3",
|
|
7
|
+
"paths": [
|
|
8
|
+
"packages/im/im-core/src/config-errors.ts",
|
|
9
|
+
"packages/im/im-core/src/service.ts",
|
|
10
|
+
"packages/im/im-core/src/spec.ts",
|
|
11
|
+
"packages/im/im-core/src/types.ts",
|
|
12
|
+
"packages/im/im-core/src/delivery/index.ts",
|
|
13
|
+
"packages/im/im-core/src/delivery/scope.ts",
|
|
14
|
+
"packages/im/im-core/src/delivery/service.ts",
|
|
15
|
+
"packages/im/im-core/src/delivery/spec.ts",
|
|
16
|
+
"packages/im/im-core/src/delivery/types.ts",
|
|
17
|
+
"packages/im/im-core/tests/delivery/crash-window.spec.ts",
|
|
18
|
+
"packages/im/im-core/tests/delivery/cursor.spec.ts",
|
|
19
|
+
"packages/im/im-core/tests/delivery/inbound.spec.ts",
|
|
20
|
+
"packages/im/im-core/tests/delivery/outbound.spec.ts",
|
|
21
|
+
"packages/im/im-core/tests/config-service.spec.ts",
|
|
22
|
+
"packages/im/im-core/src/simulation/types.ts",
|
|
23
|
+
"packages/im/im-core/src/simulation/service.ts",
|
|
24
|
+
"packages/im/im-core/src/simulation/tools.ts",
|
|
25
|
+
"packages/im/im-core/tests/simulation/session-pair-persistence.spec.ts",
|
|
26
|
+
"packages/im/im-core/tests/simulation/transport.spec.ts"
|
|
27
|
+
],
|
|
28
|
+
"license": "MIT"
|
|
29
|
+
},
|
|
30
|
+
"adoptedBehavior": [
|
|
31
|
+
"complete account and conversation route tuples",
|
|
32
|
+
"specific-route precedence including disabled binding retention",
|
|
33
|
+
"account pause without deleting route configuration",
|
|
34
|
+
"group trigger validation for mention, everyN, and fixedIntervalSeconds",
|
|
35
|
+
"explicit route rebind guarded by an observed mutation token",
|
|
36
|
+
"scope-qualified provider message identity and receive ordering",
|
|
37
|
+
"queryable inbound page receipts and outbound result-unknown state",
|
|
38
|
+
"cursor advancement after durable receive",
|
|
39
|
+
"durable two-Session simulation pairing and frozen target identity",
|
|
40
|
+
"local simulation transport isolation and terminal stop state"
|
|
41
|
+
],
|
|
42
|
+
"productChanges": [
|
|
43
|
+
"Host-minted account and route lifecycle identifiers",
|
|
44
|
+
"single-account StorageDomain aggregates with durable operation receipts",
|
|
45
|
+
"workspace and revision compare-and-swap on rebind and delete",
|
|
46
|
+
"Credentials records instead of secrets or generic GUI metadata",
|
|
47
|
+
"simulation targets that reference configured route identifiers",
|
|
48
|
+
"client-safe DTO subpath separated from Host service declarations",
|
|
49
|
+
"single-scope delivery aggregates with deduplication, history, submission evidence, and outbox state",
|
|
50
|
+
"provider-owned cursors advanced only from complete conversation-page receipt sets",
|
|
51
|
+
"stable Session user-message sources and recovery without a cross-log transaction",
|
|
52
|
+
"route-generation Agent admission through public Agent, preset, Workspace, tool, and Session services",
|
|
53
|
+
"scope-bound Agent tools with frozen outbound route and direct-recipient facts",
|
|
54
|
+
"monotonic provider mention-evidence merging for duplicate platform messages",
|
|
55
|
+
"typed provider listener readiness, completion, and exact-generation shutdown",
|
|
56
|
+
"creating-first simulation records with Host-minted tested Session identity and recoverable failure state",
|
|
57
|
+
"normal Agent, preset, Workspace, Session, history, and outbox paths for isolated simulation delivery",
|
|
58
|
+
"Host-authoritative peer Session navigation and current-instance tool binding",
|
|
59
|
+
"separate begin-stop and terminal-wait semantics with instance-scoped cancellation",
|
|
60
|
+
"durable real-Session binding for identity-confirmed manual platform sends",
|
|
61
|
+
"provider-known conversation labels, group size, last-sync, and account availability in real-Session binding",
|
|
62
|
+
"receipt-only confirmation and explicit request-linked retry for uncertain manual sends",
|
|
63
|
+
"query-only simulation history origin with Host-counted rows and basename-only display",
|
|
64
|
+
"durable quote, image-placeholder, and unsupported-message presentation facts"
|
|
65
|
+
]
|
|
66
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as ImRoutePolicies, c as IM_DELIVERY_SCHEMA_VERSION, d as encodeImScopeId, f as imRuntimeDomainSpec, i as resolveRouteAgentPreset, l as ImDeliverySqlite, o as ImTransportError, p as ImRuntimeError, s as ImTransports, t as ImRuntime, u as encodeImProviderCursorId } from "./runtime-C0-S-_CC.js";
|
|
2
|
+
export { IM_DELIVERY_SCHEMA_VERSION, ImDeliverySqlite, ImRoutePolicies, ImRuntime, ImRuntime as default, ImRuntimeError, ImTransportError, ImTransports, encodeImProviderCursorId, encodeImScopeId, imRuntimeDomainSpec, resolveRouteAgentPreset };
|