@botcord/daemon 0.2.49 → 0.2.51

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.
@@ -768,6 +768,8 @@ function normalizeBlockForHub(block, seq) {
768
768
  if (kind === "assistant_text") {
769
769
  // Claude Code: {type:"assistant", message:{content:[{type:"text",text}]}}
770
770
  // Codex: {type:"item.completed", item:{type:"agent_message", text}}
771
+ // DeepSeek: {event:"message.delta", payload:{content}} or
772
+ // {event:"item.delta", payload:{payload:{kind:"agent_message", delta}}}
771
773
  let text = "";
772
774
  const contents = Array.isArray(raw?.message?.content) ? raw.message.content : [];
773
775
  for (const c of contents) {
@@ -776,6 +778,15 @@ function normalizeBlockForHub(block, seq) {
776
778
  }
777
779
  if (!text && typeof raw?.item?.text === "string")
778
780
  text = raw.item.text;
781
+ if (!text && raw?.event === "message.delta" && typeof raw?.payload?.content === "string") {
782
+ text = raw.payload.content;
783
+ }
784
+ if (!text &&
785
+ raw?.event === "item.delta" &&
786
+ raw?.payload?.payload?.kind === "agent_message" &&
787
+ typeof raw?.payload?.payload?.delta === "string") {
788
+ text = raw.payload.payload.delta;
789
+ }
779
790
  return { kind: "assistant", seq, payload: { text } };
780
791
  }
781
792
  if (kind === "tool_use") {
@@ -24,6 +24,13 @@ const MAX_BATCH_BUFFER_CHARS = 16000;
24
24
  * cancel-previous bursts on a fast user can otherwise trip 429 silently.
25
25
  */
26
26
  const TYPING_DEBOUNCE_MS = 2000;
27
+ /**
28
+ * Most provider typing APIs are short-lived one-shots. Telegram's
29
+ * `sendChatAction(typing)`, for example, must be refreshed while the runtime
30
+ * is still working or the visible typing indicator disappears before the
31
+ * reply lands.
32
+ */
33
+ const TYPING_REFRESH_MS = 4000;
27
34
  /** LRU cap on the typing-recency map so long-running daemons don't grow unbounded. */
28
35
  const TYPING_RECENCY_CAP = 1024;
29
36
  /**
@@ -591,7 +598,9 @@ export class Dispatcher {
591
598
  const trustLevel = route.trustLevel ?? "trusted";
592
599
  const streamable = msg.trace?.streamable === true;
593
600
  const traceId = msg.trace?.id;
594
- const canType = streamable && typeof traceId === "string" && typeof channel.typing === "function";
601
+ const canType = typeof traceId === "string" &&
602
+ typeof channel.typing === "function" &&
603
+ (streamable || !isBotCordChannel(channel));
595
604
  const canStream = streamable && typeof traceId === "string" && typeof channel.streamBlock === "function";
596
605
  const recordBlock = (block) => {
597
606
  const summary = { type: block.kind };
@@ -611,7 +620,8 @@ export class Dispatcher {
611
620
  // arrival), and `thinking` is auto-synthesized on the first non-assistant
612
621
  // block so adapters that emit nothing-but-blocks still drive the
613
622
  // "Thinking..." UI.
614
- let typingFired = false;
623
+ let typingLoopStarted = false;
624
+ let typingRefreshTimer = null;
615
625
  let thinkingActive = false;
616
626
  /**
617
627
  * Sticky: once we've forwarded any assistant_text to the wire, we stop
@@ -670,10 +680,9 @@ export class Dispatcher {
670
680
  // actually emitted, not daemon-synthesized lifecycle frames.
671
681
  forwardBlockToChannel(synth);
672
682
  };
673
- const fireTypingIfNeeded = () => {
674
- if (!canType || typingFired)
683
+ const sendTypingPing = () => {
684
+ if (!canType)
675
685
  return;
676
- typingFired = true;
677
686
  const key = `${msg.accountId}:${msg.conversation.id}`;
678
687
  const now = Date.now();
679
688
  const last = this.recentTypingPings.get(key);
@@ -719,7 +728,22 @@ export class Dispatcher {
719
728
  });
720
729
  }
721
730
  };
722
- const onStatus = canStream
731
+ const fireTypingIfNeeded = () => {
732
+ if (!canType || typingLoopStarted)
733
+ return;
734
+ typingLoopStarted = true;
735
+ sendTypingPing();
736
+ typingRefreshTimer = setInterval(sendTypingPing, TYPING_REFRESH_MS);
737
+ if (typeof typingRefreshTimer.unref === "function")
738
+ typingRefreshTimer.unref();
739
+ };
740
+ const stopTypingRefresh = () => {
741
+ if (!typingRefreshTimer)
742
+ return;
743
+ clearInterval(typingRefreshTimer);
744
+ typingRefreshTimer = null;
745
+ };
746
+ const onStatus = canType || canStream
723
747
  ? (event) => {
724
748
  // Drop runtime callbacks after this turn's controller aborts —
725
749
  // NDJSON/ACP adapters keep parsing stdout until the child exits
@@ -1123,6 +1147,7 @@ export class Dispatcher {
1123
1147
  });
1124
1148
  }
1125
1149
  finally {
1150
+ stopTypingRefresh();
1126
1151
  // Emit a final thinking.stopped on terminal paths so the frontend
1127
1152
  // never sticks at "Thinking..." when no assistant_text ever landed
1128
1153
  // (timeout, error, gated reply). Skipped on cancel-previous: the
@@ -12,6 +12,8 @@ export { createTranscriptWriter, resolveTranscriptEnabled, defaultTranscriptRoot
12
12
  export { safePathSegment, transcriptFilePath, transcriptRoomDir, transcriptAgentRoot, } from "./transcript-paths.js";
13
13
  export { ClaudeCodeAdapter, probeClaude, resolveClaudeCommand, } from "./runtimes/claude-code.js";
14
14
  export { CodexAdapter, probeCodex, resolveCodexCommand } from "./runtimes/codex.js";
15
+ export { DeepseekTuiAdapter, probeDeepseekTui, resolveDeepseekCommand, } from "./runtimes/deepseek-tui.js";
16
+ export { KimiAdapter, probeKimi, resolveKimiCommand } from "./runtimes/kimi.js";
15
17
  export { GeminiAdapter, probeGemini, resolveGeminiCommand } from "./runtimes/gemini.js";
16
18
  export { NdjsonStreamAdapter, type NdjsonEventCtx, type NdjsonRunState, } from "./runtimes/ndjson-stream.js";
17
19
  export { firstExistingPath, readCommandVersion, resolveCommandOnPath, resolveHomePath, type ProbeDeps, } from "./runtimes/probe.js";
@@ -12,6 +12,8 @@ export { createTranscriptWriter, resolveTranscriptEnabled, defaultTranscriptRoot
12
12
  export { safePathSegment, transcriptFilePath, transcriptRoomDir, transcriptAgentRoot, } from "./transcript-paths.js";
13
13
  export { ClaudeCodeAdapter, probeClaude, resolveClaudeCommand, } from "./runtimes/claude-code.js";
14
14
  export { CodexAdapter, probeCodex, resolveCodexCommand } from "./runtimes/codex.js";
15
+ export { DeepseekTuiAdapter, probeDeepseekTui, resolveDeepseekCommand, } from "./runtimes/deepseek-tui.js";
16
+ export { KimiAdapter, probeKimi, resolveKimiCommand } from "./runtimes/kimi.js";
15
17
  export { GeminiAdapter, probeGemini, resolveGeminiCommand } from "./runtimes/gemini.js";
16
18
  export { NdjsonStreamAdapter, } from "./runtimes/ndjson-stream.js";
17
19
  export { firstExistingPath, readCommandVersion, resolveCommandOnPath, resolveHomePath, } from "./runtimes/probe.js";
@@ -0,0 +1,44 @@
1
+ import { spawn } from "node:child_process";
2
+ import { type ProbeDeps } from "./probe.js";
3
+ import type { RuntimeAdapter, RuntimeProbeResult, RuntimeRunOptions, RuntimeRunResult } from "../types.js";
4
+ interface DeepseekAdapterDeps {
5
+ binary?: string;
6
+ /** Test seam: use an already-running compatible server instead of spawning `deepseek`. */
7
+ serverUrl?: string;
8
+ authToken?: string;
9
+ fetchFn?: typeof fetch;
10
+ spawnFn?: typeof spawn;
11
+ }
12
+ /** Resolve the `deepseek` dispatcher CLI on PATH. */
13
+ export declare function resolveDeepseekCommand(deps?: ProbeDeps): string | null;
14
+ /** Probe whether DeepSeek TUI is installed and report its version. */
15
+ export declare function probeDeepseekTui(deps?: ProbeDeps): RuntimeProbeResult;
16
+ /**
17
+ * DeepSeek TUI adapter.
18
+ *
19
+ * Drives the headless runtime API exposed by `deepseek serve --http`, not the
20
+ * interactive TUI and not ACP. The HTTP/SSE API is the documented complete
21
+ * runtime surface; ACP is currently a conservative editor baseline.
22
+ */
23
+ export declare class DeepseekTuiAdapter implements RuntimeAdapter {
24
+ readonly id: "deepseek-tui";
25
+ private readonly explicitBinary;
26
+ private readonly explicitServerUrl;
27
+ private readonly explicitAuthToken;
28
+ private readonly fetchFn;
29
+ private readonly spawnFn;
30
+ private resolvedBinary;
31
+ constructor(deps?: DeepseekAdapterDeps);
32
+ probe(): RuntimeProbeResult;
33
+ run(opts: RuntimeRunOptions): Promise<RuntimeRunResult>;
34
+ private resolveBinary;
35
+ private acquireHandle;
36
+ private spawnEnv;
37
+ private createThread;
38
+ private patchThreadSystemContext;
39
+ private startTurnAndReadEvents;
40
+ private readEvents;
41
+ private requestJson;
42
+ }
43
+ export declare function __resetDeepseekTuiPoolForTests(): void;
44
+ export {};