@krischoichoi/channel-harness 0.6.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.
Files changed (202) hide show
  1. package/README.md +110 -0
  2. package/lib/access/controller.d.ts +37 -0
  3. package/lib/access/controller.d.ts.map +1 -0
  4. package/lib/access/controller.js +51 -0
  5. package/lib/access/controller.js.map +1 -0
  6. package/lib/access/decision.d.ts +18 -0
  7. package/lib/access/decision.d.ts.map +1 -0
  8. package/lib/access/decision.js +2 -0
  9. package/lib/access/decision.js.map +1 -0
  10. package/lib/access/resolver.d.ts +41 -0
  11. package/lib/access/resolver.d.ts.map +1 -0
  12. package/lib/access/resolver.js +31 -0
  13. package/lib/access/resolver.js.map +1 -0
  14. package/lib/agent-manager.d.ts +314 -0
  15. package/lib/agent-manager.d.ts.map +1 -0
  16. package/lib/agent-manager.js +603 -0
  17. package/lib/agent-manager.js.map +1 -0
  18. package/lib/agent-router.d.ts +37 -0
  19. package/lib/agent-router.d.ts.map +1 -0
  20. package/lib/agent-router.js +33 -0
  21. package/lib/agent-router.js.map +1 -0
  22. package/lib/bind-support.d.ts +23 -0
  23. package/lib/bind-support.d.ts.map +1 -0
  24. package/lib/bind-support.js +63 -0
  25. package/lib/bind-support.js.map +1 -0
  26. package/lib/binding-store.d.ts +104 -0
  27. package/lib/binding-store.d.ts.map +1 -0
  28. package/lib/binding-store.js +354 -0
  29. package/lib/binding-store.js.map +1 -0
  30. package/lib/bridge.d.ts +248 -0
  31. package/lib/bridge.d.ts.map +1 -0
  32. package/lib/bridge.js +961 -0
  33. package/lib/bridge.js.map +1 -0
  34. package/lib/channel-label.d.ts +46 -0
  35. package/lib/channel-label.d.ts.map +1 -0
  36. package/lib/channel-label.js +89 -0
  37. package/lib/channel-label.js.map +1 -0
  38. package/lib/channel-session-factory.d.ts +76 -0
  39. package/lib/channel-session-factory.d.ts.map +1 -0
  40. package/lib/channel-session-factory.js +187 -0
  41. package/lib/channel-session-factory.js.map +1 -0
  42. package/lib/commands/bind.d.ts +21 -0
  43. package/lib/commands/bind.d.ts.map +1 -0
  44. package/lib/commands/bind.js +91 -0
  45. package/lib/commands/bind.js.map +1 -0
  46. package/lib/commands/help.d.ts +15 -0
  47. package/lib/commands/help.d.ts.map +1 -0
  48. package/lib/commands/help.js +89 -0
  49. package/lib/commands/help.js.map +1 -0
  50. package/lib/commands/index.d.ts +122 -0
  51. package/lib/commands/index.d.ts.map +1 -0
  52. package/lib/commands/index.js +63 -0
  53. package/lib/commands/index.js.map +1 -0
  54. package/lib/commands/locale.d.ts +9 -0
  55. package/lib/commands/locale.d.ts.map +1 -0
  56. package/lib/commands/locale.js +27 -0
  57. package/lib/commands/locale.js.map +1 -0
  58. package/lib/commands/mirror.d.ts +14 -0
  59. package/lib/commands/mirror.d.ts.map +1 -0
  60. package/lib/commands/mirror.js +44 -0
  61. package/lib/commands/mirror.js.map +1 -0
  62. package/lib/commands/model.d.ts +20 -0
  63. package/lib/commands/model.d.ts.map +1 -0
  64. package/lib/commands/model.js +106 -0
  65. package/lib/commands/model.js.map +1 -0
  66. package/lib/commands/models.d.ts +14 -0
  67. package/lib/commands/models.d.ts.map +1 -0
  68. package/lib/commands/models.js +67 -0
  69. package/lib/commands/models.js.map +1 -0
  70. package/lib/commands/new.d.ts +14 -0
  71. package/lib/commands/new.d.ts.map +1 -0
  72. package/lib/commands/new.js +37 -0
  73. package/lib/commands/new.js.map +1 -0
  74. package/lib/commands/status.d.ts +12 -0
  75. package/lib/commands/status.d.ts.map +1 -0
  76. package/lib/commands/status.js +30 -0
  77. package/lib/commands/status.js.map +1 -0
  78. package/lib/commands/stop.d.ts +16 -0
  79. package/lib/commands/stop.d.ts.map +1 -0
  80. package/lib/commands/stop.js +27 -0
  81. package/lib/commands/stop.js.map +1 -0
  82. package/lib/commands/version.d.ts +25 -0
  83. package/lib/commands/version.d.ts.map +1 -0
  84. package/lib/commands/version.js +57 -0
  85. package/lib/commands/version.js.map +1 -0
  86. package/lib/config.d.ts +119 -0
  87. package/lib/config.d.ts.map +1 -0
  88. package/lib/config.js +60 -0
  89. package/lib/config.js.map +1 -0
  90. package/lib/debug-logger.d.ts +8 -0
  91. package/lib/debug-logger.d.ts.map +1 -0
  92. package/lib/debug-logger.js +32 -0
  93. package/lib/debug-logger.js.map +1 -0
  94. package/lib/dsh-home.d.ts +12 -0
  95. package/lib/dsh-home.d.ts.map +1 -0
  96. package/lib/dsh-home.js +31 -0
  97. package/lib/dsh-home.js.map +1 -0
  98. package/lib/failure-display.d.ts +10 -0
  99. package/lib/failure-display.d.ts.map +1 -0
  100. package/lib/failure-display.js +42 -0
  101. package/lib/failure-display.js.map +1 -0
  102. package/lib/file-provider.d.ts +96 -0
  103. package/lib/file-provider.d.ts.map +1 -0
  104. package/lib/file-provider.js +44 -0
  105. package/lib/file-provider.js.map +1 -0
  106. package/lib/index.d.ts +31 -0
  107. package/lib/index.d.ts.map +1 -0
  108. package/lib/index.js +31 -0
  109. package/lib/index.js.map +1 -0
  110. package/lib/interactions/index.d.ts +13 -0
  111. package/lib/interactions/index.d.ts.map +1 -0
  112. package/lib/interactions/index.js +13 -0
  113. package/lib/interactions/index.js.map +1 -0
  114. package/lib/interactions/question-backend.d.ts +133 -0
  115. package/lib/interactions/question-backend.d.ts.map +1 -0
  116. package/lib/interactions/question-backend.js +37 -0
  117. package/lib/interactions/question-backend.js.map +1 -0
  118. package/lib/interactions/question-presenter.d.ts +68 -0
  119. package/lib/interactions/question-presenter.d.ts.map +1 -0
  120. package/lib/interactions/question-presenter.js +425 -0
  121. package/lib/interactions/question-presenter.js.map +1 -0
  122. package/lib/interactions/question-renderer.d.ts +37 -0
  123. package/lib/interactions/question-renderer.d.ts.map +1 -0
  124. package/lib/interactions/question-renderer.js +79 -0
  125. package/lib/interactions/question-renderer.js.map +1 -0
  126. package/lib/interactions/question-state.d.ts +106 -0
  127. package/lib/interactions/question-state.d.ts.map +1 -0
  128. package/lib/interactions/question-state.js +99 -0
  129. package/lib/interactions/question-state.js.map +1 -0
  130. package/lib/interactions/question-text-answer.d.ts +37 -0
  131. package/lib/interactions/question-text-answer.d.ts.map +1 -0
  132. package/lib/interactions/question-text-answer.js +70 -0
  133. package/lib/interactions/question-text-answer.js.map +1 -0
  134. package/lib/interactions/question-waterfall-backend.d.ts +42 -0
  135. package/lib/interactions/question-waterfall-backend.d.ts.map +1 -0
  136. package/lib/interactions/question-waterfall-backend.js +162 -0
  137. package/lib/interactions/question-waterfall-backend.js.map +1 -0
  138. package/lib/lifecycle.d.ts +36 -0
  139. package/lib/lifecycle.d.ts.map +1 -0
  140. package/lib/lifecycle.js +303 -0
  141. package/lib/lifecycle.js.map +1 -0
  142. package/lib/loggable-error.d.ts +10 -0
  143. package/lib/loggable-error.d.ts.map +1 -0
  144. package/lib/loggable-error.js +32 -0
  145. package/lib/loggable-error.js.map +1 -0
  146. package/lib/message-converter.d.ts +64 -0
  147. package/lib/message-converter.d.ts.map +1 -0
  148. package/lib/message-converter.js +191 -0
  149. package/lib/message-converter.js.map +1 -0
  150. package/lib/model-selection.d.ts +54 -0
  151. package/lib/model-selection.d.ts.map +1 -0
  152. package/lib/model-selection.js +116 -0
  153. package/lib/model-selection.js.map +1 -0
  154. package/lib/outbox/binding-resolver.d.ts +23 -0
  155. package/lib/outbox/binding-resolver.d.ts.map +1 -0
  156. package/lib/outbox/binding-resolver.js +38 -0
  157. package/lib/outbox/binding-resolver.js.map +1 -0
  158. package/lib/outbox/capabilities.d.ts +48 -0
  159. package/lib/outbox/capabilities.d.ts.map +1 -0
  160. package/lib/outbox/capabilities.js +35 -0
  161. package/lib/outbox/capabilities.js.map +1 -0
  162. package/lib/outbox/index.d.ts +14 -0
  163. package/lib/outbox/index.d.ts.map +1 -0
  164. package/lib/outbox/index.js +14 -0
  165. package/lib/outbox/index.js.map +1 -0
  166. package/lib/outbox/service.d.ts +48 -0
  167. package/lib/outbox/service.d.ts.map +1 -0
  168. package/lib/outbox/service.js +74 -0
  169. package/lib/outbox/service.js.map +1 -0
  170. package/lib/outbox/target.d.ts +17 -0
  171. package/lib/outbox/target.d.ts.map +1 -0
  172. package/lib/outbox/target.js +15 -0
  173. package/lib/outbox/target.js.map +1 -0
  174. package/lib/outbox/tool-send.d.ts +19 -0
  175. package/lib/outbox/tool-send.d.ts.map +1 -0
  176. package/lib/outbox/tool-send.js +77 -0
  177. package/lib/outbox/tool-send.js.map +1 -0
  178. package/lib/outbox/types.d.ts +42 -0
  179. package/lib/outbox/types.d.ts.map +1 -0
  180. package/lib/outbox/types.js +30 -0
  181. package/lib/outbox/types.js.map +1 -0
  182. package/lib/plugin.d.ts +27 -0
  183. package/lib/plugin.d.ts.map +1 -0
  184. package/lib/plugin.js +47 -0
  185. package/lib/plugin.js.map +1 -0
  186. package/lib/reply-context-store.d.ts +69 -0
  187. package/lib/reply-context-store.d.ts.map +1 -0
  188. package/lib/reply-context-store.js +75 -0
  189. package/lib/reply-context-store.js.map +1 -0
  190. package/lib/reply-router.d.ts +138 -0
  191. package/lib/reply-router.d.ts.map +1 -0
  192. package/lib/reply-router.js +674 -0
  193. package/lib/reply-router.js.map +1 -0
  194. package/lib/session-router.d.ts +49 -0
  195. package/lib/session-router.d.ts.map +1 -0
  196. package/lib/session-router.js +45 -0
  197. package/lib/session-router.js.map +1 -0
  198. package/lib/workspace-resolver.d.ts +67 -0
  199. package/lib/workspace-resolver.d.ts.map +1 -0
  200. package/lib/workspace-resolver.js +102 -0
  201. package/lib/workspace-resolver.js.map +1 -0
  202. package/package.json +103 -0
@@ -0,0 +1,33 @@
1
+ /** Compare two routes structurally (used to detect binding route drift). */
2
+ export function routesEqual(a, b) {
3
+ if (a === b)
4
+ return true;
5
+ if (!a || !b)
6
+ return false;
7
+ return (a.preset === b.preset &&
8
+ a.provider === b.provider &&
9
+ a.model === b.model &&
10
+ a.maxTokens === b.maxTokens);
11
+ }
12
+ export class AgentRouter {
13
+ config;
14
+ constructor(config) {
15
+ this.config = config;
16
+ }
17
+ resolve(input) {
18
+ const overrides = this.config.routing.overrides;
19
+ if (overrides) {
20
+ const conversationAgent = overrides.conversation?.[input.conversationId];
21
+ if (conversationAgent)
22
+ return conversationAgent;
23
+ const accountAgent = overrides.account?.[input.accountId];
24
+ if (accountAgent)
25
+ return accountAgent;
26
+ const channelAgent = overrides.channel?.[input.channelId];
27
+ if (channelAgent)
28
+ return channelAgent;
29
+ }
30
+ return this.config.agent.default;
31
+ }
32
+ }
33
+ //# sourceMappingURL=agent-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-router.js","sourceRoot":"","sources":["../src/agent-router.ts"],"names":[],"mappings":"AAgCA,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,CAA6B,EAAE,CAA6B;IACtF,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3B,OAAO,CACL,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QACrB,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ;QACzB,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,KAAK;QACnB,CAAC,CAAC,SAAS,KAAK,CAAC,CAAC,SAAS,CAC5B,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,WAAW;IACO;IAA7B,YAA6B,MAAc;QAAd,WAAM,GAAN,MAAM,CAAQ;IAAG,CAAC;IAE/C,OAAO,CAAC,KAAsB;QAC5B,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC;QAChD,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,iBAAiB,GAAG,SAAS,CAAC,YAAY,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;YACzE,IAAI,iBAAiB;gBAAE,OAAO,iBAAiB,CAAC;YAChD,MAAM,YAAY,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAC1D,IAAI,YAAY;gBAAE,OAAO,YAAY,CAAC;YACtC,MAAM,YAAY,GAAG,SAAS,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YAC1D,IAAI,YAAY;gBAAE,OAAO,YAAY,CAAC;QACxC,CAAC;QACD,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC;IACnC,CAAC;CACF"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Fail-closed support behind the `/bind` command (issue #6).
3
+ *
4
+ * Resolution NEVER mutates: it resolves the query against the persisted
5
+ * session universe (exact id first, then a unique prefix of at least
6
+ * {@link MIN_BIND_QUERY_LENGTH} characters) and reports the two refusal
7
+ * conditions — a target already bound to a different conversation, and a
8
+ * persisted Agent preset that conflicts with the conversation's route (which
9
+ * would make the next resume throw, per the official resume semantics).
10
+ * `confirmBindTarget` re-runs the full resolution at confirm time (guarding
11
+ * against races) and only then re-points the durable binding.
12
+ */
13
+ import type { Agent } from '@deepseek-ai/dsh-agent';
14
+ import type { HarnessAgentGateway } from './agent-manager.js';
15
+ import type { ChannelBindResolution } from './commands/index.js';
16
+ import type { SessionBindingStore } from './binding-store.js';
17
+ /** Minimum prefix length before a non-exact query is even considered. */
18
+ export declare const MIN_BIND_QUERY_LENGTH = 4;
19
+ /** Resolve one `/bind` query against persisted sessions without mutating. */
20
+ export declare function resolveBindTarget(gateway: HarnessAgentGateway, bindingStore: SessionBindingStore, agent: Agent, query: string): Promise<ChannelBindResolution>;
21
+ /** Re-resolve and re-point the current conversation's binding at `sessionId`. */
22
+ export declare function confirmBindTarget(gateway: HarnessAgentGateway, bindingStore: SessionBindingStore, agent: Agent, sessionId: string): Promise<void>;
23
+ //# sourceMappingURL=bind-support.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bind-support.d.ts","sourceRoot":"","sources":["../src/bind-support.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAE9D,yEAAyE;AACzE,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAEvC,6EAA6E;AAC7E,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,mBAAmB,EAC5B,YAAY,EAAE,mBAAmB,EACjC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,qBAAqB,CAAC,CAgBhC;AAED,iFAAiF;AACjF,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,mBAAmB,EAC5B,YAAY,EAAE,mBAAmB,EACjC,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CA2Bf"}
@@ -0,0 +1,63 @@
1
+ import { bindingKey } from './session-router.js';
2
+ /** Minimum prefix length before a non-exact query is even considered. */
3
+ export const MIN_BIND_QUERY_LENGTH = 4;
4
+ /** Resolve one `/bind` query against persisted sessions without mutating. */
5
+ export async function resolveBindTarget(gateway, bindingStore, agent, query) {
6
+ const currentBinding = await requireCurrentBinding(bindingStore, agent);
7
+ const ids = await gateway.listPersistedSessionIds();
8
+ let matches = ids.filter((id) => id === query);
9
+ if (matches.length === 0 && query.length >= MIN_BIND_QUERY_LENGTH) {
10
+ matches = ids.filter((id) => id.startsWith(query));
11
+ }
12
+ if (matches.length === 0)
13
+ return { kind: 'missing' };
14
+ if (matches.length > 1)
15
+ return { kind: 'ambiguous', candidates: matches.slice(0, 6) };
16
+ const target = matches[0];
17
+ const boundElsewhere = await isBoundElsewhere(bindingStore, currentBinding, target);
18
+ const preset = await gateway.persistedPresetOf(target);
19
+ const routePreset = currentBinding.route.preset;
20
+ const conflict = preset !== undefined && routePreset !== undefined && routePreset !== preset;
21
+ return { kind: 'resolved', sessionId: target, preset, boundElsewhere, conflict };
22
+ }
23
+ /** Re-resolve and re-point the current conversation's binding at `sessionId`. */
24
+ export async function confirmBindTarget(gateway, bindingStore, agent, sessionId) {
25
+ const currentBinding = await requireCurrentBinding(bindingStore, agent);
26
+ if (sessionId === String(agent.id)) {
27
+ throw new Error('this conversation is already bound to that session');
28
+ }
29
+ // Race guard: the full resolution re-runs at confirm time; any changed
30
+ // condition fails the rebind instead of producing a broken binding.
31
+ const resolution = await resolveBindTarget(gateway, bindingStore, agent, sessionId);
32
+ if (resolution.kind !== 'resolved' || resolution.sessionId !== sessionId) {
33
+ throw new Error('bind target is no longer uniquely resolvable; refused');
34
+ }
35
+ if (resolution.boundElsewhere) {
36
+ throw new Error('target session is bound to another conversation; refused');
37
+ }
38
+ if (resolution.conflict) {
39
+ throw new Error('target session preset conflicts with this conversation route; refused');
40
+ }
41
+ await bindingStore.put({
42
+ ...currentBinding,
43
+ sessionId,
44
+ // Persisted composition wins, exactly like the official resume path: the
45
+ // next message resumes under the target's recorded preset.
46
+ route: resolution.preset
47
+ ? { ...currentBinding.route, preset: resolution.preset }
48
+ : currentBinding.route,
49
+ updatedAt: Date.now(),
50
+ });
51
+ }
52
+ async function requireCurrentBinding(bindingStore, agent) {
53
+ const binding = await bindingStore.findBySessionId(String(agent.id));
54
+ if (!binding) {
55
+ throw new Error(`no session binding for '${String(agent.id)}'`);
56
+ }
57
+ return binding;
58
+ }
59
+ async function isBoundElsewhere(bindingStore, currentBinding, targetSessionId) {
60
+ const existing = await bindingStore.findBySessionId(targetSessionId);
61
+ return !!existing && bindingKey(existing) !== bindingKey(currentBinding);
62
+ }
63
+ //# sourceMappingURL=bind-support.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bind-support.js","sourceRoot":"","sources":["../src/bind-support.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ import { type SessionBinding } from './session-router.js';
2
+ import type { AgentRouteSpec } from './agent-router.js';
3
+ export interface SessionBindingStore {
4
+ get(key: string): Promise<SessionBinding | undefined>;
5
+ put(binding: SessionBinding): Promise<void>;
6
+ delete(key: string): Promise<void>;
7
+ /** Resolve the single current binding for a session id. */
8
+ findBySessionId(sessionId: string): Promise<SessionBinding | undefined>;
9
+ }
10
+ /** The v1 binding shape that must be migrated (never used as-is). */
11
+ export interface SessionBindingV1 {
12
+ channelId: string;
13
+ accountId: string;
14
+ conversationId: string;
15
+ threadId?: string;
16
+ agentId?: string;
17
+ sessionId: string;
18
+ createdAt: number;
19
+ updatedAt: number;
20
+ }
21
+ /** The v2 binding shape that must be promoted to v3 (never used as-is). */
22
+ export interface SessionBindingV2 {
23
+ channelId: string;
24
+ accountId: string;
25
+ conversationId: string;
26
+ threadId?: string;
27
+ sessionId: string;
28
+ route: AgentRouteSpec;
29
+ createdAt: number;
30
+ updatedAt: number;
31
+ }
32
+ /**
33
+ * Raised by `findBySessionId` when one session id maps to more than one
34
+ * CURRENT binding. The durable binding authority fails closed
35
+ * rather than guessing which conversation "owns" the session.
36
+ */
37
+ export declare class AmbiguousBindingError extends Error {
38
+ static readonly code: "OUTBOX_AMBIGUOUS_BINDING";
39
+ readonly code: "OUTBOX_AMBIGUOUS_BINDING";
40
+ readonly sessionId: string;
41
+ readonly bindingCount: number;
42
+ constructor(sessionId: string, bindingCount: number);
43
+ }
44
+ /** Whether a raw stored value is a v1 binding needing migration. */
45
+ export declare function isV1Binding(value: unknown): value is SessionBindingV1;
46
+ /**
47
+ * Whether a raw stored value is a v2 binding needing promotion to v3: a
48
+ * current binding carrying `route` + `sessionId` but NO `conversationType`
49
+ * (v2 never modeled group vs dm). A v3 entry always has `conversationType`,
50
+ * so it is never misread as v2.
51
+ */
52
+ export declare function isV2Binding(value: unknown): value is SessionBindingV2;
53
+ /**
54
+ * Normalize any stored binding into the current v3 shape.
55
+ *
56
+ * - v1 -> migrate to v2 (`route: { model: agentId }`, drop `agentId`), then
57
+ * chain into v3;
58
+ * - v2 -> promote to v3 with the documented legacy default
59
+ * `conversationType: 'dm'` and no `senderId`.
60
+ *
61
+ * Already-v3 values are returned unchanged (same reference). Migration NEVER
62
+ * reinterprets `agentId` as the new identity, and never fabricates a
63
+ * conversation type for an entry that already carries one.
64
+ */
65
+ export declare function migrateBinding(value: unknown): SessionBinding;
66
+ export declare class MemoryBindingStore implements SessionBindingStore {
67
+ private readonly store;
68
+ /** sessionId -> bindable key index. */
69
+ private readonly bySession;
70
+ get(key: string): Promise<SessionBinding | undefined>;
71
+ put(binding: SessionBinding): Promise<void>;
72
+ delete(key: string): Promise<void>;
73
+ findBySessionId(sessionId: string): Promise<SessionBinding | undefined>;
74
+ private index;
75
+ }
76
+ /**
77
+ * Single-file JSON binding store. Loads once into memory; writes are
78
+ * serialized through a promise chain and land atomically (temp + rename).
79
+ * On load, any v1/v2 entries are migrated to v3 (in memory) and the migrated
80
+ * file is re-persisted immediately. A `sessionId -> key` index is built
81
+ * alongside the cache for `findBySessionId`.
82
+ */
83
+ export declare class FileBindingStore implements SessionBindingStore {
84
+ private readonly filePath;
85
+ private readonly cache;
86
+ /** sessionId -> bindable key index. */
87
+ private readonly bySession;
88
+ private loaded;
89
+ private writeChain;
90
+ constructor(filePath: string);
91
+ private index;
92
+ private load;
93
+ get(key: string): Promise<SessionBinding | undefined>;
94
+ put(binding: SessionBinding): Promise<void>;
95
+ delete(key: string): Promise<void>;
96
+ findBySessionId(sessionId: string): Promise<SessionBinding | undefined>;
97
+ private persist;
98
+ }
99
+ /** Build the store selected by configuration (file without a path falls back to the default). */
100
+ export declare function createBindingStore(config: {
101
+ type: 'memory' | 'file';
102
+ path?: string;
103
+ }): SessionBindingStore;
104
+ //# sourceMappingURL=binding-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding-store.d.ts","sourceRoot":"","sources":["../src/binding-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,354 @@
1
+ /**
2
+ * Session binding persistence (doc H0.3 / §32).
3
+ *
4
+ * Adapters never touch this store; only the bridge does. The store carries a
5
+ * schema version (`SessionBinding.schemaVersion: 3`) and performs a one-time
6
+ * migration on load:
7
+ *
8
+ * - a v1 entry (no `schemaVersion`, has `agentId`) is converted to v2 by
9
+ * mapping `agentId` -> `route: { model: agentId }`, then chained to v3;
10
+ * - a v2 entry (has `route` + `sessionId`, no `conversationType`) is converted
11
+ * to v3 by adding the documented legacy default `conversationType: 'dm'`
12
+ * (v2 did not model group vs dm) and leaving `senderId` unset, then stamped
13
+ * `schemaVersion: 3` and logged. Bindings without the later optional
14
+ * `durability` policy are treated as durable by the bridge (fail closed).
15
+ *
16
+ * Old fields are NEVER silently reinterpreted as the new semantics: `agentId`
17
+ * is explicitly carried into `route.model` (never onto the identity), and a v2
18
+ * binding is only ever promoted from an entry that truly lacks a
19
+ * `conversationType`.
20
+ *
21
+ * `FileBindingStore` is the default (persists a plain JSON file, written
22
+ * atomically via a temp file + rename and read once into a cache map) and
23
+ * persists the migrated file after a load-time migration. `MemoryBindingStore`
24
+ * is opt-in for non-persistent deployments and applies the same migration on
25
+ * get/put.
26
+ *
27
+ * §59: both stores expose `findBySessionId` so a durable channel can resolve
28
+ * the "WHERE does a reply to session S go" question from the binding
29
+ * authority. When more than one CURRENT binding maps to the same session, they
30
+ * fail CLOSED with `AmbiguousBindingError` (code `OUTBOX_AMBIGUOUS_BINDING`)
31
+ * instead of guessing.
32
+ */
33
+ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
34
+ import { dirname } from 'node:path';
35
+ import { z } from 'zod';
36
+ import { resolveDefaultBindingStorePath } from './dsh-home.js';
37
+ import { bindingKey, SESSION_BINDING_SCHEMA_VERSION } from './session-router.js';
38
+ /**
39
+ * Raised by `findBySessionId` when one session id maps to more than one
40
+ * CURRENT binding. The durable binding authority fails closed
41
+ * rather than guessing which conversation "owns" the session.
42
+ */
43
+ export class AmbiguousBindingError extends Error {
44
+ static code = 'OUTBOX_AMBIGUOUS_BINDING';
45
+ code = AmbiguousBindingError.code;
46
+ sessionId;
47
+ bindingCount;
48
+ constructor(sessionId, bindingCount) {
49
+ super("session '" + sessionId + "' maps to " + bindingCount
50
+ + " current channel bindings; expected exactly one (OUTBOX_AMBIGUOUS_BINDING)");
51
+ this.name = 'AmbiguousBindingError';
52
+ this.sessionId = sessionId;
53
+ this.bindingCount = bindingCount;
54
+ }
55
+ }
56
+ /**
57
+ * Structural v1 shape used to detect a legacy stored binding: it carries
58
+ * `agentId` + `sessionId`, and `schemaVersion` is absent (or explicitly
59
+ * undefined). A v2/v3 entry fails this schema, so it is never misread as v1.
60
+ */
61
+ const V1_BINDING_SCHEMA = z.object({
62
+ channelId: z.string(),
63
+ accountId: z.string(),
64
+ conversationId: z.string(),
65
+ threadId: z.string().optional(),
66
+ agentId: z.string(),
67
+ sessionId: z.string(),
68
+ createdAt: z.number(),
69
+ updatedAt: z.number(),
70
+ schemaVersion: z.undefined().optional(),
71
+ }).loose();
72
+ /** Whether a raw stored value is a v1 binding needing migration. */
73
+ export function isV1Binding(value) {
74
+ return V1_BINDING_SCHEMA.safeParse(value).success;
75
+ }
76
+ // Reused record guard (zod/TS-safe).
77
+ function isRecord(value) {
78
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
79
+ }
80
+ /**
81
+ * Whether a raw stored value is a v2 binding needing promotion to v3: a
82
+ * current binding carrying `route` + `sessionId` but NO `conversationType`
83
+ * (v2 never modeled group vs dm). A v3 entry always has `conversationType`,
84
+ * so it is never misread as v2.
85
+ */
86
+ export function isV2Binding(value) {
87
+ if (!isRecord(value) || isV1Binding(value))
88
+ return false;
89
+ if (value.conversationType !== undefined)
90
+ return false;
91
+ if (typeof value.sessionId !== 'string')
92
+ return false;
93
+ if (!isRecord(value.route))
94
+ return false;
95
+ return typeof value.channelId === 'string' && typeof value.accountId === 'string'
96
+ && typeof value.conversationId === 'string';
97
+ }
98
+ /**
99
+ * Normalize any stored binding into the current v3 shape.
100
+ *
101
+ * - v1 -> migrate to v2 (`route: { model: agentId }`, drop `agentId`), then
102
+ * chain into v3;
103
+ * - v2 -> promote to v3 with the documented legacy default
104
+ * `conversationType: 'dm'` and no `senderId`.
105
+ *
106
+ * Already-v3 values are returned unchanged (same reference). Migration NEVER
107
+ * reinterprets `agentId` as the new identity, and never fabricates a
108
+ * conversation type for an entry that already carries one.
109
+ */
110
+ export function migrateBinding(value) {
111
+ if (isV1Binding(value)) {
112
+ const v1 = value;
113
+ // v1 only knew an agent id, used as the model — carry that meaning forward.
114
+ const v2 = {
115
+ channelId: v1.channelId,
116
+ accountId: v1.accountId,
117
+ conversationId: v1.conversationId,
118
+ ...(typeof v1.threadId === 'string' ? { threadId: v1.threadId } : {}),
119
+ sessionId: v1.sessionId,
120
+ route: { model: v1.agentId },
121
+ createdAt: v1.createdAt,
122
+ updatedAt: v1.updatedAt,
123
+ };
124
+ return promoteV2ToV3(v2);
125
+ }
126
+ if (isV2Binding(value)) {
127
+ return promoteV2ToV3(value);
128
+ }
129
+ return value;
130
+ }
131
+ /** v2 -> v3: add the legacy-default dm conversation type. */
132
+ function promoteV2ToV3(v2) {
133
+ const migrated = {
134
+ channelId: v2.channelId,
135
+ accountId: v2.accountId,
136
+ conversationId: v2.conversationId,
137
+ // Legacy default: v2 did not model dm vs group, so a promoted entry is a
138
+ // DM unless the caller later upgrades it. Documented in the plan.
139
+ conversationType: 'dm',
140
+ ...(typeof v2.threadId === 'string' ? { threadId: v2.threadId } : {}),
141
+ sessionId: v2.sessionId,
142
+ route: v2.route,
143
+ schemaVersion: SESSION_BINDING_SCHEMA_VERSION,
144
+ createdAt: v2.createdAt,
145
+ updatedAt: v2.updatedAt,
146
+ };
147
+ return migrated;
148
+ }
149
+ function migratedNotice(sessionId, from) {
150
+ const origin = from === 'v1' ? 'v1 (agentId) -> v2 (route.model) -> v3' : 'v2 -> v3 (dm default)';
151
+ // Overriding console is intentional: no logger is injected and the migration
152
+ // must be visible (doc H0.3: "log a migration notice").
153
+ // eslint-disable-next-line no-console
154
+ console.error("[channel-harness] binding for session '" + sessionId + "' migrated from " + origin);
155
+ }
156
+ export class MemoryBindingStore {
157
+ store = new Map();
158
+ /** sessionId -> bindable key index. */
159
+ bySession = new Map();
160
+ get(key) {
161
+ const raw = this.store.get(key);
162
+ if (!raw)
163
+ return Promise.resolve(undefined);
164
+ const migrated = migrateBinding(raw);
165
+ if (migrated !== raw) {
166
+ migratedNotice(migrated.sessionId, isV1Binding(raw) ? 'v1' : 'v2');
167
+ this.index(migrated);
168
+ this.store.set(key, migrated);
169
+ }
170
+ return Promise.resolve(migrated);
171
+ }
172
+ async put(binding) {
173
+ const key = bindingKey(binding);
174
+ const value = migrateBinding(binding);
175
+ // If this key previously held a DIFFERENT session's binding, drop that
176
+ // session's stale reverse mapping before re-pointing it.
177
+ const prev = this.store.get(key);
178
+ if (prev && prev.sessionId !== value.sessionId && this.bySession.get(prev.sessionId) === key) {
179
+ this.bySession.delete(prev.sessionId);
180
+ }
181
+ this.bySession.set(value.sessionId, key);
182
+ this.store.set(key, value);
183
+ }
184
+ async delete(key) {
185
+ const binding = this.store.get(key);
186
+ if (binding && this.bySession.get(binding.sessionId) === key) {
187
+ this.bySession.delete(binding.sessionId);
188
+ }
189
+ this.store.delete(key);
190
+ }
191
+ async findBySessionId(sessionId) {
192
+ const keys = new Set();
193
+ if (this.bySession.has(sessionId)) {
194
+ const k = this.bySession.get(sessionId);
195
+ if (this.store.has(k))
196
+ keys.add(k);
197
+ }
198
+ for (const [key, binding] of this.store) {
199
+ if (binding.sessionId === sessionId)
200
+ keys.add(key);
201
+ }
202
+ if (keys.size === 0)
203
+ return undefined;
204
+ if (keys.size > 1)
205
+ throw new AmbiguousBindingError(sessionId, keys.size);
206
+ const key = Array.from(keys)[0];
207
+ const raw = this.store.get(key);
208
+ const migrated = migrateBinding(raw);
209
+ if (migrated !== raw) {
210
+ migratedNotice(migrated.sessionId, isV1Binding(raw) ? 'v1' : 'v2');
211
+ this.bySession.set(migrated.sessionId, bindingKey(migrated));
212
+ this.store.set(key, migrated);
213
+ }
214
+ return migrated;
215
+ }
216
+ index(binding) {
217
+ this.bySession.set(binding.sessionId, bindingKey(binding));
218
+ }
219
+ }
220
+ /**
221
+ * Single-file JSON binding store. Loads once into memory; writes are
222
+ * serialized through a promise chain and land atomically (temp + rename).
223
+ * On load, any v1/v2 entries are migrated to v3 (in memory) and the migrated
224
+ * file is re-persisted immediately. A `sessionId -> key` index is built
225
+ * alongside the cache for `findBySessionId`.
226
+ */
227
+ export class FileBindingStore {
228
+ filePath;
229
+ cache = new Map();
230
+ /** sessionId -> bindable key index. */
231
+ bySession = new Map();
232
+ loaded = false;
233
+ writeChain = Promise.resolve();
234
+ constructor(filePath) {
235
+ this.filePath = filePath;
236
+ }
237
+ index(key, binding) {
238
+ this.cache.set(key, binding);
239
+ this.bySession.set(binding.sessionId, key);
240
+ }
241
+ async load() {
242
+ if (this.loaded)
243
+ return;
244
+ this.loaded = true;
245
+ let text;
246
+ try {
247
+ text = await readFile(this.filePath, 'utf8');
248
+ }
249
+ catch {
250
+ // No file yet — empty store.
251
+ return;
252
+ }
253
+ let migratedAny = false;
254
+ try {
255
+ const parsed = JSON.parse(text);
256
+ if (parsed && typeof parsed === 'object') {
257
+ for (const [key, value] of Object.entries(parsed)) {
258
+ if (value && typeof value === 'object') {
259
+ const migrated = migrateBinding(value);
260
+ this.index(key, migrated);
261
+ if (migrated !== value) {
262
+ migratedAny = true;
263
+ migratedNotice(migrated.sessionId, isV1Binding(value) ? 'v1' : 'v2');
264
+ }
265
+ }
266
+ }
267
+ }
268
+ }
269
+ catch {
270
+ // Corrupt file — start fresh; the single-file store has no better recovery.
271
+ }
272
+ // Persist the migrated file so a later load does not re-run migration.
273
+ if (migratedAny) {
274
+ await this.persist();
275
+ }
276
+ }
277
+ async get(key) {
278
+ await this.load();
279
+ const raw = this.cache.get(key);
280
+ if (!raw)
281
+ return undefined;
282
+ const migrated = migrateBinding(raw);
283
+ if (migrated !== raw) {
284
+ migratedNotice(migrated.sessionId, isV1Binding(raw) ? 'v1' : 'v2');
285
+ this.index(key, migrated);
286
+ await this.persist();
287
+ }
288
+ return migrated;
289
+ }
290
+ async put(binding) {
291
+ await this.load();
292
+ const key = bindingKey(binding);
293
+ const value = migrateBinding(binding);
294
+ const prev = this.cache.get(key);
295
+ if (prev && prev.sessionId !== value.sessionId && this.bySession.get(prev.sessionId) === key) {
296
+ this.bySession.delete(prev.sessionId);
297
+ }
298
+ this.index(key, value);
299
+ await this.persist();
300
+ }
301
+ async delete(key) {
302
+ await this.load();
303
+ const binding = this.cache.get(key);
304
+ if (binding && this.bySession.get(binding.sessionId) === key) {
305
+ this.bySession.delete(binding.sessionId);
306
+ }
307
+ if (this.cache.delete(key)) {
308
+ await this.persist();
309
+ }
310
+ }
311
+ async findBySessionId(sessionId) {
312
+ await this.load();
313
+ const keys = new Set();
314
+ if (this.bySession.has(sessionId)) {
315
+ const k = this.bySession.get(sessionId);
316
+ if (this.cache.has(k))
317
+ keys.add(k);
318
+ }
319
+ for (const [key, binding] of this.cache) {
320
+ if (binding.sessionId === sessionId)
321
+ keys.add(key);
322
+ }
323
+ if (keys.size === 0)
324
+ return undefined;
325
+ if (keys.size > 1)
326
+ throw new AmbiguousBindingError(sessionId, keys.size);
327
+ const key = Array.from(keys)[0];
328
+ const raw = this.cache.get(key);
329
+ const migrated = migrateBinding(raw);
330
+ if (migrated !== raw) {
331
+ migratedNotice(migrated.sessionId, isV1Binding(raw) ? 'v1' : 'v2');
332
+ this.index(key, migrated);
333
+ await this.persist();
334
+ }
335
+ return migrated;
336
+ }
337
+ persist() {
338
+ this.writeChain = this.writeChain.then(async () => {
339
+ await mkdir(dirname(this.filePath), { recursive: true });
340
+ const tmp = this.filePath + '.' + process.pid + '.' + Date.now() + '.tmp';
341
+ await writeFile(tmp, JSON.stringify(Object.fromEntries(this.cache), null, 2), 'utf8');
342
+ await rename(tmp, this.filePath);
343
+ });
344
+ return this.writeChain;
345
+ }
346
+ }
347
+ /** Build the store selected by configuration (file without a path falls back to the default). */
348
+ export function createBindingStore(config) {
349
+ if (config.type === 'file') {
350
+ return new FileBindingStore(config.path ?? resolveDefaultBindingStorePath());
351
+ }
352
+ return new MemoryBindingStore();
353
+ }
354
+ //# sourceMappingURL=binding-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding-store.js","sourceRoot":"","sources":["../src/binding-store.ts"],"names":[],"mappings":"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"}