@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,77 @@
1
+ /**
2
+ * send_channel_message — the M6 Harness tool.
3
+ *
4
+ * Lets the model send a proactive message back to the channel conversation the
5
+ * CURRENT session is durably bound to. Parameters are ONLY `{ text?,
6
+ * attachment_id? }` — there is NO `recipient` / `channel` / `account` /
7
+ * `conversation` / `user_id` / `openid` / `file_path` (those
8
+ * fields must NOT exist in the parameter schema nor in the request type). The
9
+ * durable binding resolves the target.
10
+ *
11
+ * Agent-scoped registration: installSendChannelMessageTool registers
12
+ * through the official @deepseek-ai/dsh-tools ctx.tools.register on the agent's
13
+ * own scope, mirroring installReadChannelAttachmentTool.
14
+ */
15
+ import { defineTool, ToolArgsError } from '@deepseek-ai/dsh-tools';
16
+ /**
17
+ * The tool definition, ready to register on an Agent scope. Parameters are
18
+ * type-level guaranteed to be only `{ text?, attachment_id? }`.
19
+ */
20
+ export function registerSendChannelMessageTool(options) {
21
+ const outbox = options.outbox;
22
+ return defineTool({
23
+ name: 'send_channel_message',
24
+ description: 'Send a proactive message to the channel conversation bound to the current session. ' +
25
+ 'Supply text and/or an attachment_id; the destination is derived from the session binding.',
26
+ parameters: {
27
+ text: { type: 'string', description: 'Plain text to send.' },
28
+ attachment_id: {
29
+ type: 'string',
30
+ description: 'A stored channel attachment id to send using its stored media kind.',
31
+ },
32
+ },
33
+ output: {
34
+ schema: {
35
+ type: 'object',
36
+ additionalProperties: false,
37
+ properties: {
38
+ delivered: { type: 'boolean' },
39
+ messageId: { type: 'string' },
40
+ },
41
+ },
42
+ render: (args, value) => {
43
+ const delivered = Boolean(value.delivered);
44
+ const messageId = typeof value.messageId === 'string' ? value.messageId : undefined;
45
+ const text = delivered
46
+ ? 'Message sent to the channel' + (messageId ? ' (message id ' + messageId + ')' : '') + '.'
47
+ : 'Message could not be delivered.';
48
+ return [{ type: 'text', text }];
49
+ },
50
+ },
51
+ async execute(args, exec) {
52
+ const sessionId = exec.agent ? String(exec.agent.id) : undefined;
53
+ if (sessionId === undefined) {
54
+ throw new ToolArgsError(['send_channel_message requires a session (no agent context)']);
55
+ }
56
+ if (!args.text && !args.attachment_id) {
57
+ throw new ToolArgsError([
58
+ 'send_channel_message requires at least one of: text, attachment_id',
59
+ ]);
60
+ }
61
+ const result = await outbox.send(sessionId, { text: args.text, attachmentId: args.attachment_id }, { signal: exec.signal });
62
+ const canonical = {
63
+ delivered: result.delivered,
64
+ ...(result.messageId ? { messageId: result.messageId } : {}),
65
+ };
66
+ return canonical;
67
+ },
68
+ });
69
+ }
70
+ /** Install the tool on an Agent scoped context (Agent-scoped registration). */
71
+ export async function installSendChannelMessageTool(agentCtx, options) {
72
+ const fiber = agentCtx.inject(['tools'], function* sendToolScope(ctx) {
73
+ yield ctx.tools.register(registerSendChannelMessageTool(options));
74
+ });
75
+ await fiber.await().then(() => undefined);
76
+ }
77
+ //# sourceMappingURL=tool-send.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-send.js","sourceRoot":"","sources":["../../src/outbox/tool-send.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Durable outbox types.
3
+ *
4
+ * A channel send request is deliberately MINIMAL: the model supplies only
5
+ * content (`text` and/or `attachment_id`). The durable binding resolves the
6
+ * recipient/target; the model NEVER names a `recipient` / `channel` /
7
+ * `account` / `conversation` / `user_id` / `openid` / `file_path`
8
+ * (those fields must not exist on the request type).
9
+ *
10
+ * An outbound attachment is referenced by its private-store `attachment_id`,
11
+ * never by a model-visible `file_path` (V1 model tools do not
12
+ * provide `file_path`; there is no path handling anywhere in this module).
13
+ */
14
+ import { HarnessError } from '@deepseek-ai/dsh-llm';
15
+ /**
16
+ * One outbound channel send request. At least one of `text` / `attachmentId`
17
+ * must be set; `text` and `attachmentId` may be combined in a single send.
18
+ */
19
+ export interface ChannelOutboundRequest {
20
+ /** Plain text payload. */
21
+ text?: string;
22
+ /** Private-store attachment id whose bytes are sent using its stored media kind. */
23
+ attachmentId?: string;
24
+ }
25
+ /**
26
+ * Stable, machine-routable outbox failure codes. Tool results route on `code`, never on `message`.
27
+ */
28
+ export type OutboxErrorCode = 'OUTBOX_NO_BINDING' | 'OUTBOX_AMBIGUOUS_BINDING' | 'OUTBOX_CAPABILITY_UNAVAILABLE' | 'ATTACHMENT_NOT_FOUND' | 'ATTACHMENT_ACCESS_DENIED' | 'ATTACHMENT_TOO_LARGE';
29
+ /**
30
+ * Typed outbox failure carried as a `HarnessError` (mirrors the M4
31
+ * `AttachmentReadError` pattern in tool-read.ts) so the tool pipeline records
32
+ * a stable machine-routable `code`. Carries the session id it was raised for
33
+ * as an optional diagnostic.
34
+ */
35
+ export declare class OutboxError extends HarnessError {
36
+ readonly sessionId?: string;
37
+ constructor(code: OutboxErrorCode, message: string, options?: {
38
+ sessionId?: string;
39
+ cause?: unknown;
40
+ });
41
+ }
42
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/outbox/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC,0BAA0B;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oFAAoF;IACpF,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GACvB,mBAAmB,GACnB,0BAA0B,GAC1B,+BAA+B,GAC/B,sBAAsB,GACtB,0BAA0B,GAC1B,sBAAsB,CAAC;AAE3B;;;;;GAKG;AACH,qBAAa,WAAY,SAAQ,YAAY;IAC3C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;gBAChB,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAKtG"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Durable outbox types.
3
+ *
4
+ * A channel send request is deliberately MINIMAL: the model supplies only
5
+ * content (`text` and/or `attachment_id`). The durable binding resolves the
6
+ * recipient/target; the model NEVER names a `recipient` / `channel` /
7
+ * `account` / `conversation` / `user_id` / `openid` / `file_path`
8
+ * (those fields must not exist on the request type).
9
+ *
10
+ * An outbound attachment is referenced by its private-store `attachment_id`,
11
+ * never by a model-visible `file_path` (V1 model tools do not
12
+ * provide `file_path`; there is no path handling anywhere in this module).
13
+ */
14
+ import { HarnessError } from '@deepseek-ai/dsh-llm';
15
+ /**
16
+ * Typed outbox failure carried as a `HarnessError` (mirrors the M4
17
+ * `AttachmentReadError` pattern in tool-read.ts) so the tool pipeline records
18
+ * a stable machine-routable `code`. Carries the session id it was raised for
19
+ * as an optional diagnostic.
20
+ */
21
+ export class OutboxError extends HarnessError {
22
+ sessionId;
23
+ constructor(code, message, options) {
24
+ super(message, code, options?.cause !== undefined ? { cause: options.cause } : undefined);
25
+ this.name = 'OutboxError';
26
+ if (options?.sessionId !== undefined)
27
+ this.sessionId = options.sessionId;
28
+ }
29
+ }
30
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/outbox/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAwBpD;;;;;GAKG;AACH,MAAM,OAAO,WAAY,SAAQ,YAAY;IAClC,SAAS,CAAU;IAC5B,YAAY,IAAqB,EAAE,OAAe,EAAE,OAAiD;QACnG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAC1F,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,OAAO,EAAE,SAAS,KAAK,SAAS;YAAE,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IAC3E,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Cordis plugin entry for the channel-harness bridge (doc §30 / H0.6).
3
+ *
4
+ * The plugin injects `channels` (ChannelService), `agents` (AgentRegistry),
5
+ * `agentDefaultModel`, `agentPresets`, `llm`, and `commands` (CommandRuntime).
6
+ * `sessionPersistence` and the question transports are
7
+ * NOT required: they are optional capabilities resolved LIVE at the use site
8
+ * (a resolver passed into the gateway), so probes reflect whether the service
9
+ * is present at probe time — including mounts/unmounts after startup.
10
+ * `commands` is a required capability (no optional fallback) so the bridge can
11
+ * install Agent-scoped channel commands.
12
+ *
13
+ * `userQuestions` is deliberately absent from the PLUGIN's own `inject`: the
14
+ * channel composes its answerer on the official `user-questions/request`
15
+ * waterfall and only probes the service for diagnostics (see
16
+ * `interactions/question-backend.ts`). Listener ORDER is a contract there, so
17
+ * the answerer is prepended rather than relying on fiber order.
18
+ *
19
+ * The whole bridge lifecycle is registered as one `ctx.effect` whose disposer
20
+ * is the teardown chain from `startBridge`.
21
+ */
22
+ import { type Context } from '@deepseek-ai/cordis';
23
+ import type { Config } from './config.js';
24
+ export declare const name = "channel-harness";
25
+ export declare const inject: string[];
26
+ export declare function apply(ctx: Context, config: Config): void;
27
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAG1C,eAAO,MAAM,IAAI,oBAAoB,CAAC;AAMtC,eAAO,MAAM,MAAM,EAAE,MAAM,EAO1B,CAAC;AAEF,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CASxD"}
package/lib/plugin.js ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Cordis plugin entry for the channel-harness bridge (doc §30 / H0.6).
3
+ *
4
+ * The plugin injects `channels` (ChannelService), `agents` (AgentRegistry),
5
+ * `agentDefaultModel`, `agentPresets`, `llm`, and `commands` (CommandRuntime).
6
+ * `sessionPersistence` and the question transports are
7
+ * NOT required: they are optional capabilities resolved LIVE at the use site
8
+ * (a resolver passed into the gateway), so probes reflect whether the service
9
+ * is present at probe time — including mounts/unmounts after startup.
10
+ * `commands` is a required capability (no optional fallback) so the bridge can
11
+ * install Agent-scoped channel commands.
12
+ *
13
+ * `userQuestions` is deliberately absent from the PLUGIN's own `inject`: the
14
+ * channel composes its answerer on the official `user-questions/request`
15
+ * waterfall and only probes the service for diagnostics (see
16
+ * `interactions/question-backend.ts`). Listener ORDER is a contract there, so
17
+ * the answerer is prepended rather than relying on fiber order.
18
+ *
19
+ * The whole bridge lifecycle is registered as one `ctx.effect` whose disposer
20
+ * is the teardown chain from `startBridge`.
21
+ */
22
+ import {} from '@deepseek-ai/cordis';
23
+ import { startBridge } from './lifecycle.js';
24
+ export const name = 'channel-harness';
25
+ // `agentDefaultModel` is the Harness default-model service: an agent created
26
+ // without an explicit provider/model (the channel `agent.default` route) must
27
+ // inherit the user's Harness-wide default model so the `{{model}}` persona
28
+ // variable always resolves. See `resolveRoute` in agent-manager.ts.
29
+ export const inject = [
30
+ 'channels',
31
+ 'agents',
32
+ 'agentDefaultModel',
33
+ 'agentPresets',
34
+ 'llm',
35
+ 'commands',
36
+ ];
37
+ export function apply(ctx, config) {
38
+ ctx.effect(() => {
39
+ // Optional service resolved LIVE at the use site (never required): the
40
+ // resolver is re-queried on every probe, so a sessionPersistence mounted,
41
+ // unmounted, or replaced AFTER the bridge started is observed on the next
42
+ // `canResume()` / existence probe (no startup snapshot).
43
+ const lifecycle = startBridge(ctx, config, () => ctx.get('sessionPersistence'));
44
+ return () => lifecycle.dispose();
45
+ });
46
+ }
47
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EAAgB,MAAM,qBAAqB,CAAC;AAEnD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,CAAC,MAAM,IAAI,GAAG,iBAAiB,CAAC;AAEtC,6EAA6E;AAC7E,8EAA8E;AAC9E,2EAA2E;AAC3E,oEAAoE;AACpE,MAAM,CAAC,MAAM,MAAM,GAAa;IAC9B,UAAU;IACV,QAAQ;IACR,mBAAmB;IACnB,cAAc;IACd,KAAK;IACL,UAAU;CACX,CAAC;AAEF,MAAM,UAAU,KAAK,CAAC,GAAY,EAAE,MAAc;IAChD,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE;QACd,uEAAuE;QACvE,0EAA0E;QAC1E,0EAA0E;QAC1E,yDAAyD;QACzD,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,CAAC;QAChF,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * `ReplyContextStore` — carries the transient per-turn reply context from an
3
+ * inbound message to the outbound reply target.
4
+ *
5
+ * The triggering message id (and the conversation kind it came from) belongs
6
+ * to one user-message ↔ one agent-turn pair, so it is *never* persisted on a
7
+ * `SessionBinding` (which models the durable conversation ↔ session relation).
8
+ * Instead the correlation is established with the Harness `MessageId`:
9
+ *
10
+ * 1. The bridge converts the inbound message to a Harness `UserMessage`
11
+ * (`createUserMessage` assigns a fresh stable `MessageId`) and *registers*
12
+ * a pending context keyed by that message id, strictly BEFORE `followup`.
13
+ * 2. Harness drives the message through the agent inbox; when the agent loop
14
+ * actually picks it up, `agent/inbox/claimed { agent, message, turn }`
15
+ * fires, and the store *claims* the pending context by `message.id`,
16
+ * moving it to the active slot for `sessionId`+`turn`.
17
+ * 3. `agent/assistant-stream` / `assistant/message` (which only flow after the claim)
18
+ * resolve the active context lazily via `getTurn`, so the reply target is
19
+ * bound exactly once per message-id — never via a session FIFO guessed at
20
+ * `turn/start`.
21
+ * 4. `turn/end` releases the active context; `agent/inbox/discarded` drops a
22
+ * pending context that never became a turn (prevents leaks).
23
+ */
24
+ export interface ChannelReplyContext {
25
+ conversationType: 'dm' | 'group';
26
+ /** Authorized sender that initiated this turn. */
27
+ senderId?: string;
28
+ replyToMessageId?: string;
29
+ raw?: unknown;
30
+ /**
31
+ * Turn-scoped correlation id. Mined once per inbound turn and shared by every
32
+ * outbound send scoped to that turn, so the platform (e.g. Weixin run_id)
33
+ * sees ONE stable correlation instead of a fresh UUID per sender call.
34
+ */
35
+ runId?: string;
36
+ }
37
+ export declare class ReplyContextStore {
38
+ /** MessageId → pending context waiting for its `agent/inbox/claimed`. */
39
+ private readonly pendingByMessageId;
40
+ /** `${sessionId}:${turn}` → active context for that turn. */
41
+ private readonly activeByTurn;
42
+ /** Register a context for one Harness UserMessage id (call BEFORE followup). */
43
+ register(messageId: string, pending: {
44
+ sessionId: string;
45
+ context: ChannelReplyContext;
46
+ }): void;
47
+ /** Move pendingByMessageId[messageId] → activeByTurn[sessionId:turn]; delete the pending entry. */
48
+ claim(input: {
49
+ sessionId: string;
50
+ messageId: string;
51
+ turn: number;
52
+ }): ChannelReplyContext | undefined;
53
+ /** Drop a pending context by message id (agent/inbox/discarded) — prevents leaks. */
54
+ discard(messageId: string): void;
55
+ /** Session id of a still-pending context, if any (used to cancel typing on discard). */
56
+ pendingSessionId(messageId: string): string | undefined;
57
+ /** Pending context before discard, used to stop target-bound indicators. */
58
+ pendingContext(messageId: string): ChannelReplyContext | undefined;
59
+ /** Active context for sessionId+turn, if any. */
60
+ getTurn(sessionId: string, turn: number): ChannelReplyContext | undefined;
61
+ /** Active context for a single-flight Agent session, including its turn. */
62
+ getActiveForSession(sessionId: string): {
63
+ turn: number;
64
+ context: ChannelReplyContext;
65
+ } | undefined;
66
+ /** Drop the active context for sessionId+turn (turn/end cleanup). */
67
+ releaseTurn(sessionId: string, turn: number): void;
68
+ }
69
+ //# sourceMappingURL=reply-context-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reply-context-store.d.ts","sourceRoot":"","sources":["../src/reply-context-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,mBAAmB;IAClC,gBAAgB,EAAE,IAAI,GAAG,OAAO,CAAC;IACjC,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,GAAG,CAAC,EAAE,OAAO,CAAC;IACd;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,iBAAiB;IAC5B,yEAAyE;IACzE,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAG/B;IACJ,6DAA6D;IAC7D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA0C;IAEvE,gFAAgF;IAChF,QAAQ,CACN,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,mBAAmB,CAAA;KAAE,GAC3D,IAAI;IAIP,mGAAmG;IACnG,KAAK,CAAC,KAAK,EAAE;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;KACd,GAAG,mBAAmB,GAAG,SAAS;IAQnC,qFAAqF;IACrF,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAIhC,wFAAwF;IACxF,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIvD,4EAA4E;IAC5E,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAIlE,iDAAiD;IACjD,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAIzE,4EAA4E;IAC5E,mBAAmB,CACjB,SAAS,EAAE,MAAM,GAChB;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,mBAAmB,CAAA;KAAE,GAAG,SAAS;IAU7D,qEAAqE;IACrE,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;CAGnD"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * `ReplyContextStore` — carries the transient per-turn reply context from an
3
+ * inbound message to the outbound reply target.
4
+ *
5
+ * The triggering message id (and the conversation kind it came from) belongs
6
+ * to one user-message ↔ one agent-turn pair, so it is *never* persisted on a
7
+ * `SessionBinding` (which models the durable conversation ↔ session relation).
8
+ * Instead the correlation is established with the Harness `MessageId`:
9
+ *
10
+ * 1. The bridge converts the inbound message to a Harness `UserMessage`
11
+ * (`createUserMessage` assigns a fresh stable `MessageId`) and *registers*
12
+ * a pending context keyed by that message id, strictly BEFORE `followup`.
13
+ * 2. Harness drives the message through the agent inbox; when the agent loop
14
+ * actually picks it up, `agent/inbox/claimed { agent, message, turn }`
15
+ * fires, and the store *claims* the pending context by `message.id`,
16
+ * moving it to the active slot for `sessionId`+`turn`.
17
+ * 3. `agent/assistant-stream` / `assistant/message` (which only flow after the claim)
18
+ * resolve the active context lazily via `getTurn`, so the reply target is
19
+ * bound exactly once per message-id — never via a session FIFO guessed at
20
+ * `turn/start`.
21
+ * 4. `turn/end` releases the active context; `agent/inbox/discarded` drops a
22
+ * pending context that never became a turn (prevents leaks).
23
+ */
24
+ export class ReplyContextStore {
25
+ /** MessageId → pending context waiting for its `agent/inbox/claimed`. */
26
+ pendingByMessageId = new Map();
27
+ /** `${sessionId}:${turn}` → active context for that turn. */
28
+ activeByTurn = new Map();
29
+ /** Register a context for one Harness UserMessage id (call BEFORE followup). */
30
+ register(messageId, pending) {
31
+ this.pendingByMessageId.set(messageId, pending);
32
+ }
33
+ /** Move pendingByMessageId[messageId] → activeByTurn[sessionId:turn]; delete the pending entry. */
34
+ claim(input) {
35
+ const pending = this.pendingByMessageId.get(input.messageId);
36
+ if (!pending)
37
+ return undefined;
38
+ this.pendingByMessageId.delete(input.messageId);
39
+ this.activeByTurn.set(`${input.sessionId}:${input.turn}`, pending.context);
40
+ return pending.context;
41
+ }
42
+ /** Drop a pending context by message id (agent/inbox/discarded) — prevents leaks. */
43
+ discard(messageId) {
44
+ this.pendingByMessageId.delete(messageId);
45
+ }
46
+ /** Session id of a still-pending context, if any (used to cancel typing on discard). */
47
+ pendingSessionId(messageId) {
48
+ return this.pendingByMessageId.get(messageId)?.sessionId;
49
+ }
50
+ /** Pending context before discard, used to stop target-bound indicators. */
51
+ pendingContext(messageId) {
52
+ return this.pendingByMessageId.get(messageId)?.context;
53
+ }
54
+ /** Active context for sessionId+turn, if any. */
55
+ getTurn(sessionId, turn) {
56
+ return this.activeByTurn.get(`${sessionId}:${turn}`);
57
+ }
58
+ /** Active context for a single-flight Agent session, including its turn. */
59
+ getActiveForSession(sessionId) {
60
+ const prefix = `${sessionId}:`;
61
+ for (const [key, context] of this.activeByTurn) {
62
+ if (!key.startsWith(prefix))
63
+ continue;
64
+ const turn = Number(key.slice(prefix.length));
65
+ if (Number.isSafeInteger(turn))
66
+ return { turn, context };
67
+ }
68
+ return undefined;
69
+ }
70
+ /** Drop the active context for sessionId+turn (turn/end cleanup). */
71
+ releaseTurn(sessionId, turn) {
72
+ this.activeByTurn.delete(`${sessionId}:${turn}`);
73
+ }
74
+ }
75
+ //# sourceMappingURL=reply-context-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reply-context-store.js","sourceRoot":"","sources":["../src/reply-context-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAgBH,MAAM,OAAO,iBAAiB;IAC5B,yEAAyE;IACxD,kBAAkB,GAAG,IAAI,GAAG,EAG1C,CAAC;IACJ,6DAA6D;IAC5C,YAAY,GAAG,IAAI,GAAG,EAA+B,CAAC;IAEvE,gFAAgF;IAChF,QAAQ,CACN,SAAiB,EACjB,OAA4D;QAE5D,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAClD,CAAC;IAED,mGAAmG;IACnG,KAAK,CAAC,KAIL;QACC,MAAM,OAAO,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC7D,IAAI,CAAC,OAAO;YAAE,OAAO,SAAS,CAAC;QAC/B,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAChD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAC3E,OAAO,OAAO,CAAC,OAAO,CAAC;IACzB,CAAC;IAED,qFAAqF;IACrF,OAAO,CAAC,SAAiB;QACvB,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC5C,CAAC;IAED,wFAAwF;IACxF,gBAAgB,CAAC,SAAiB;QAChC,OAAO,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,SAAS,CAAC;IAC3D,CAAC;IAED,4EAA4E;IAC5E,cAAc,CAAC,SAAiB;QAC9B,OAAO,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IACzD,CAAC;IAED,iDAAiD;IACjD,OAAO,CAAC,SAAiB,EAAE,IAAY;QACrC,OAAO,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,SAAS,IAAI,IAAI,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,4EAA4E;IAC5E,mBAAmB,CACjB,SAAiB;QAEjB,MAAM,MAAM,GAAG,GAAG,SAAS,GAAG,CAAC;QAC/B,KAAK,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YAC/C,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC;gBAAE,SAAS;YACtC,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;YAC9C,IAAI,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC;gBAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;QAC3D,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,qEAAqE;IACrE,WAAW,CAAC,SAAiB,EAAE,IAAY;QACzC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,SAAS,IAAI,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;CACF"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * ReplyRouter — the outbound reply pipeline (architecture §19).
3
+ *
4
+ * Consumes only official `session/event` records:
5
+ * - `assistant/message.stream` is the V3 durable stream source and feeds the
6
+ * reply buffer;
7
+ * - `assistant/message` content provides the final fallback text;
8
+ * - `turn/end` flushes and finishes the reply, then cleans up.
9
+ *
10
+ * Strategy comes from the adapter's resolved streaming mode, which may be
11
+ * target-aware (via `adapter.resolveStreamingMode(target)` falling back to the
12
+ * static `adapter.capabilities.streaming`):
13
+ * - `native` — `adapter.createReply` + `handle.append` (throttled previews);
14
+ * - `edit` — `adapter.createReply` + `handle.replace` (throttled previews);
15
+ * - `buffered` — accumulate and send once via `adapter.send` at `turn/end`
16
+ * (throttling is irrelevant for a send-once strategy).
17
+ * An adapter without `createReply` is always `buffered`.
18
+ *
19
+ * The reply target is resolved by reversing `sessionId -> SessionBinding`,
20
+ * never by inspecting the agent itself. Per-turn reply context (conversation
21
+ * type + triggering message id) comes from the shared `ReplyContextStore`.
22
+ *
23
+ * The two concepts are deliberately separated (core hardening):
24
+ * - `SessionBinding` answers "WHERE would a reply go" (the channel
25
+ * conversation ↔ session relation). It does NOT authorize outbound.
26
+ * - `ReplyContext` answers "SHOULD this turn go back to the channel" — a turn
27
+ * is only routed when the store has an active context for `sessionId:turn`,
28
+ * which exists only because the turn was triggered by a channel inbound
29
+ * message (`register` → `agent/inbox/claimed` → `getTurn`).
30
+ * Both are required: a turn driven by Web UI / CLI / steer / another plugin
31
+ * on a channel-bound session has no `ReplyContext`, so it is never delivered
32
+ * back to the channel.
33
+ *
34
+ * - `turn/start` only establishes turn existence (no active reply);
35
+ * - `assistant/message` requires an active `ReplyContext`
36
+ * (authoritative by then because `agent/inbox/claimed` fired first) and
37
+ * otherwise drop the output;
38
+ * - `turn/end` releases the active context via `releaseTurn`.
39
+ * The target passed to `createReply`/`send` carries the context so
40
+ * target-aware adapters (e.g. QQ C2C native streaming) behave correctly.
41
+ */
42
+ import type { Context } from '@deepseek-ai/cordis';
43
+ import type { Agent, AssistantStreamFrame } from '@deepseek-ai/dsh-agent';
44
+ import type { Session, SessionEvent } from '@deepseek-ai/dsh-session';
45
+ import type { ChannelAdapter, ChannelLogger } from '@krischoichoi/channel-core';
46
+ import type { ReplyConfig } from './config.js';
47
+ import type { SessionBinding } from './session-router.js';
48
+ import type { ReplyContextStore } from './reply-context-store.js';
49
+ export interface ReplyRouterOptions {
50
+ config: ReplyConfig;
51
+ getAdapter(channelId: string): ChannelAdapter | undefined;
52
+ getBinding(sessionId: string): SessionBinding | undefined;
53
+ replyContexts: ReplyContextStore;
54
+ logger: ChannelLogger;
55
+ }
56
+ export declare class ReplyRouter {
57
+ private readonly options;
58
+ private readonly active;
59
+ private readonly streamTurns;
60
+ constructor(options: ReplyRouterOptions);
61
+ /** Register the `session/event` listener; returns a disposer. */
62
+ attach(ctx: Context): () => boolean;
63
+ /** Consume transient V3 stream frames for native/edit previews. */
64
+ onAssistantStream(agent: Agent, frame: AssistantStreamFrame): void;
65
+ /** Testable entry point; the registered listener delegates here. */
66
+ onSessionEvent(session: Session, event: SessionEvent): void;
67
+ /** Session ids with an in-flight reply (used by the lifecycle drain). */
68
+ activeSessions(): string[];
69
+ /**
70
+ * Finalize every reply still marked active, exactly like `turn/end` would.
71
+ * Turns still streaming at unload never deliver `turn/end`, so the lifecycle
72
+ * drain calls this after `whenIdle` to guarantee buffered/final text is
73
+ * delivered (or the reply handle fails) instead of being dropped.
74
+ */
75
+ flushAll(): Promise<void>;
76
+ /**
77
+ * Durable-log reconcile (doc §4.3 / §4.4): deliver the final assistant text
78
+ * for the last unfinished turn straight from the Session durable log, even
79
+ * when the `session/event` listener is no longer attached.
80
+ *
81
+ * Finds the last `turn/start` with no matching `turn/end`; if the
82
+ * ReplyRouter already holds an active reply for it, finalizes normally.
83
+ * Otherwise it rebuilds the assistant text from each V3
84
+ * `assistant/message.stream` (falling back to the message text) and delivers it via `adapter.send` (buffered
85
+ * strategy), then finishes/cleans up. The Session log is the ONLY source of
86
+ * transcript truth — reconcile never maintains a second copy.
87
+ */
88
+ reconcileSession(session: {
89
+ id: string;
90
+ events: readonly SessionEvent[];
91
+ }): Promise<void>;
92
+ /** Clear all timers and drop active replies. */
93
+ dispose(): void;
94
+ private ensureActive;
95
+ /**
96
+ * Mirror tracking for a non-channel turn (issue #5): only when the bound
97
+ * conversation opted in (`binding.mirror === true`) does the router attach
98
+ * a buffered-only reply that delivers the final assistant text to the bound
99
+ * conversation at `turn/end`. Everything else stays unrouted. The synthetic
100
+ * context carries NO platform state (no replyToMessageId / raw / runId) —
101
+ * the binding's stable identity is the sole delivery address.
102
+ */
103
+ private ensureMirrorActive;
104
+ private appendText;
105
+ /**
106
+ * Enqueue a preview flush. Deltas are drained through a single in-flight
107
+ * chain so concurrent deltas can never double-`createReply` on the same
108
+ * reply; the chain reads the current buffer at execution time, so a flush
109
+ * that started before the newest delta still covers it.
110
+ */
111
+ private flush;
112
+ /** Drain all pending deltas, flushing until the buffer is fully sent. */
113
+ private drain;
114
+ private doFlush;
115
+ private resolveTurnTarget;
116
+ private finishTurn;
117
+ /**
118
+ * Finalize one active reply: clear its timer, drain any in-flight preview
119
+ * chain, deliver the accumulated text (or fail the handle), and release the
120
+ * active reply context for its turn (v1.1 §20). Shared by `turn/end`
121
+ * handling and the lifecycle `flushAll()` so an unloaded turn is finalized
122
+ * exactly like a completed one.
123
+ */
124
+ private finalize;
125
+ private stopTypingIfSupported;
126
+ private deliver;
127
+ }
128
+ /**
129
+ * Split a long message into `maxLength`-bounded pieces, keeping fenced code
130
+ * blocks whole and preferring paragraph boundaries. `finalFlush: false`
131
+ * drops a trailing partial piece instead of delivering it.
132
+ */
133
+ export declare function splitMessage(text: string, maxLength: number, config: {
134
+ splitParagraphs: boolean;
135
+ splitCodeBlocks: boolean;
136
+ finalFlush: boolean;
137
+ }): string[];
138
+ //# sourceMappingURL=reply-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reply-router.d.ts","sourceRoot":"","sources":["../src/reply-router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAiB,MAAM,0BAA0B,CAAC;AAErF,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EAGd,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,KAAK,EAAuB,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAkCvF,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,WAAW,CAAC;IACpB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC;IAC1D,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAC;IAC1D,aAAa,EAAE,iBAAiB,CAAC;IACjC,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,qBAAa,WAAW;IAIV,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;gBAE5B,OAAO,EAAE,kBAAkB;IAExD,iEAAiE;IACjE,MAAM,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,OAAO;IAUnC,mEAAmE;IACnE,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,oBAAoB,GAAG,IAAI;IAiBlE,oEAAoE;IACpE,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI;IA8B3D,yEAAyE;IACzE,cAAc,IAAI,MAAM,EAAE;IAI1B;;;;;OAKG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAO/B;;;;;;;;;;;OAWG;IACG,gBAAgB,CAAC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,SAAS,YAAY,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAiE/F,gDAAgD;IAChD,OAAO,IAAI,IAAI;IASf,OAAO,CAAC,YAAY;IA6DpB;;;;;;;OAOG;IACH,OAAO,CAAC,kBAAkB;IAoC1B,OAAO,CAAC,UAAU;IAqBlB;;;;;OAKG;IACH,OAAO,CAAC,KAAK;IASb,yEAAyE;YAC3D,KAAK;YASL,OAAO;IA+BrB,OAAO,CAAC,iBAAiB;YA4BX,UAAU;IAiExB;;;;;;OAMG;YACW,QAAQ;IAsDtB,OAAO,CAAC,qBAAqB;YASf,OAAO;CAgBtB;AAgFD;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;IAAE,eAAe,EAAE,OAAO,CAAC;IAAC,eAAe,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAA;CAAE,GAClF,MAAM,EAAE,CAqBV"}