mixdog 0.9.0 → 0.9.2
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/package.json +10 -3
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/session-ingest-smoke.mjs +2 -2
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +306 -66
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +4 -2
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +4 -2
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/mixdog-debug.cjs +0 -22
- package/src/lib/plugin-paths.cjs +1 -7
- package/src/lib/rules-builder.cjs +34 -56
- package/src/mixdog-session-runtime.mjs +710 -319
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +12 -4
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +7 -8
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +7 -0
- package/src/rules/shared/01-tool.md +17 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +359 -106
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +63 -51
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +86 -30
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +254 -280
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +191 -50
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +265 -1
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
- package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -32
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +1 -44
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
- package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +441 -1254
- package/src/runtime/channels/lib/cli-worker-host.mjs +1 -8
- package/src/runtime/channels/lib/config.mjs +54 -3
- package/src/runtime/channels/lib/drop-trace.mjs +1 -1
- package/src/runtime/channels/lib/executor.mjs +0 -3
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/memory-client.mjs +0 -38
- package/src/runtime/channels/lib/output-forwarder.mjs +77 -71
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -6
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/session-discovery.mjs +0 -4
- package/src/runtime/channels/lib/telegram-format.mjs +283 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -2
- package/src/runtime/channels/lib/transcript-discovery.mjs +20 -11
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/lib/keychain-cjs.cjs +0 -1
- package/src/runtime/memory/data/runtime-manifest.json +6 -7
- package/src/runtime/memory/index.mjs +187 -43
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
- package/src/runtime/memory/lib/llm-worker-host.mjs +0 -4
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-ops-policy.mjs +0 -1
- package/src/runtime/memory/lib/memory.mjs +101 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/runtime-fetcher.mjs +43 -18
- package/src/runtime/memory/lib/session-ingest.mjs +116 -7
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +6 -3
- package/src/runtime/search/index.mjs +2 -7
- package/src/runtime/search/lib/config.mjs +0 -4
- package/src/runtime/search/lib/state.mjs +1 -15
- package/src/runtime/search/lib/web-tools.mjs +0 -1
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/child-spawn-gate.mjs +0 -6
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +129 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/standalone/agent-tool.mjs +255 -109
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +8 -291
- package/src/standalone/explore-tool.mjs +2 -2
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/provider-admin.mjs +11 -0
- package/src/standalone/seeds.mjs +1 -11
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2137 -750
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +146 -9
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +177 -100
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +312 -40
- package/src/tui/components/tool-output-format.test.mjs +180 -1
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +7324 -2393
- package/src/tui/engine.mjs +287 -126
- package/src/tui/index.jsx +117 -7
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +453 -0
- package/src/tui/markdown/format-token.mjs +354 -142
- package/src/tui/markdown/format-token.test.mjs +155 -17
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +0 -11
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -647
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +81 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +26 -27
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -623
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +39 -12
- package/src/workflows/sequential/WORKFLOW.md +46 -0
- package/src/workflows/solo/WORKFLOW.md +7 -0
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +154 -20
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/hooks/lib/permission-rules.cjs +0 -170
- package/src/hooks/lib/settings-loader.cjs +0 -112
- package/src/lib/hook-pipe-path.cjs +0 -10
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/runtime/channels/lib/hook-pipe-server.mjs +0 -671
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFileSync, mkdirSync } from "fs";
|
|
2
2
|
import { join } from "path";
|
|
3
3
|
import { DiscordBackend } from "../backends/discord.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import { TelegramBackend } from "../backends/telegram.mjs";
|
|
5
|
+
import { readSection, updateSection, CONFIG_PATH as MIXDOG_CONFIG_PATH, getDiscordToken, getTelegramToken, diagnoseDiscordTokenValue } from "../../shared/config.mjs";
|
|
5
6
|
import { listSchedules } from "../../shared/schedules-store.mjs";
|
|
6
7
|
import { resolvePluginData } from "../../shared/plugin-paths.mjs";
|
|
7
8
|
import { isHolidaySync } from "./holidays.mjs";
|
|
@@ -15,6 +16,7 @@ const DEFAULT_ACCESS = {
|
|
|
15
16
|
const DEFAULT_CONFIG = {
|
|
16
17
|
backend: "discord",
|
|
17
18
|
discord: { token: "" },
|
|
19
|
+
telegram: { token: "" },
|
|
18
20
|
access: DEFAULT_ACCESS,
|
|
19
21
|
mainChannel: "main",
|
|
20
22
|
channelsConfig: {
|
|
@@ -47,6 +49,22 @@ function applyDefaults(config) {
|
|
|
47
49
|
out.webhook = { ...CONFIG_DEFAULTS.webhook, ...(out.webhook || {}) };
|
|
48
50
|
return out;
|
|
49
51
|
}
|
|
52
|
+
|
|
53
|
+
function channelIdForBackend(entry = {}, backend = "discord") {
|
|
54
|
+
if (backend === "telegram") {
|
|
55
|
+
return String(entry?.telegramChatId || (entry?.discordChannelId ? "" : entry?.channelId) || "");
|
|
56
|
+
}
|
|
57
|
+
return String(entry?.discordChannelId || (entry?.telegramChatId ? "" : entry?.channelId) || "");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function channelsConfigForBackend(rawChannelsConfig = {}, backend = "discord") {
|
|
61
|
+
return Object.fromEntries(Object.entries(rawChannelsConfig || {}).map(([name, entry]) => {
|
|
62
|
+
const value = entry && typeof entry === "object" ? entry : {};
|
|
63
|
+
const channelId = channelIdForBackend(value, backend);
|
|
64
|
+
return [name, { ...value, channelId }];
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
|
|
50
68
|
/**
|
|
51
69
|
* Shared DND / quiet-window helper used by scheduler + webhook.
|
|
52
70
|
*
|
|
@@ -185,11 +203,20 @@ function loadConfig() {
|
|
|
185
203
|
if (discordTokenProblem) {
|
|
186
204
|
process.stderr.write(`mixdog: discord token ignored: ${discordTokenProblem}\n`);
|
|
187
205
|
}
|
|
206
|
+
// Single-backend select: config.backend picks ONE of discord|telegram.
|
|
207
|
+
// Anything else falls back to the discord default.
|
|
208
|
+
const backend = raw.backend === "telegram" ? "telegram" : "discord";
|
|
209
|
+
const telegramToken = getTelegramToken();
|
|
210
|
+
const rawChannelsConfig = { ...DEFAULT_CONFIG.channelsConfig, ...(raw.channelsConfig || {}) };
|
|
188
211
|
return applyDefaults({
|
|
189
212
|
...DEFAULT_CONFIG,
|
|
190
213
|
...raw,
|
|
191
|
-
backend
|
|
214
|
+
backend,
|
|
215
|
+
channelsConfig: channelsConfigForBackend(rawChannelsConfig, backend),
|
|
192
216
|
discord: { ...DEFAULT_CONFIG.discord, ...(({ token: _, ...rest }) => rest)(raw.discord || {}), ...(discordToken && !discordTokenProblem ? { token: discordToken } : {}) },
|
|
217
|
+
// Merge the keychain-resolved telegram token (harmless when backend is
|
|
218
|
+
// discord; the secret never lands in the on-disk config either way).
|
|
219
|
+
telegram: { ...DEFAULT_CONFIG.telegram, ...(({ token: _t, ...rest }) => rest)(raw.telegram || {}), ...(telegramToken ? { token: telegramToken } : {}) },
|
|
193
220
|
access: {
|
|
194
221
|
...DEFAULT_ACCESS,
|
|
195
222
|
// Drop the retired pairing-era keys at the config layer too (the
|
|
@@ -218,6 +245,10 @@ function loadConfig() {
|
|
|
218
245
|
}
|
|
219
246
|
const HEADLESS_BACKEND = {
|
|
220
247
|
name: "headless",
|
|
248
|
+
MAX_MESSAGE_LENGTH: 2000,
|
|
249
|
+
formatOutgoing(t) {
|
|
250
|
+
return t;
|
|
251
|
+
},
|
|
221
252
|
async connect() {
|
|
222
253
|
},
|
|
223
254
|
async disconnect() {
|
|
@@ -244,6 +275,26 @@ const HEADLESS_BACKEND = {
|
|
|
244
275
|
}
|
|
245
276
|
};
|
|
246
277
|
function createBackend(config) {
|
|
278
|
+
// Single-backend select: exactly one backend is constructed based on
|
|
279
|
+
// config.backend (discord|telegram). The two are mutually exclusive.
|
|
280
|
+
if (config.backend === "telegram") {
|
|
281
|
+
const telegramToken = getTelegramToken();
|
|
282
|
+
if (!telegramToken) {
|
|
283
|
+
process.stderr.write("mixdog: telegram bot not configured; channel runtime running in headless mode\n");
|
|
284
|
+
return HEADLESS_BACKEND;
|
|
285
|
+
}
|
|
286
|
+
const tgStateDir = config.telegram?.stateDir ?? join(DATA_DIR, "telegram");
|
|
287
|
+
mkdirSync(tgStateDir, { recursive: true });
|
|
288
|
+
return new TelegramBackend({
|
|
289
|
+
...config.telegram,
|
|
290
|
+
configPath: CONFIG_FILE,
|
|
291
|
+
access: config.access,
|
|
292
|
+
// Single-source channel setup: the main chat is auto-allowed inside
|
|
293
|
+
// TelegramBackend.loadAccess() so a configured channelsConfig.main.channelId
|
|
294
|
+
// is enough for both inbound gating and outbound.
|
|
295
|
+
mainChannelId: config.channelsConfig?.main?.channelId
|
|
296
|
+
}, tgStateDir);
|
|
297
|
+
}
|
|
247
298
|
const discordToken = getDiscordToken();
|
|
248
299
|
const discordTokenProblem = diagnoseDiscordTokenValue(discordToken, config);
|
|
249
300
|
if (discordTokenProblem) {
|
|
@@ -276,9 +327,9 @@ function loadProfileConfig() {
|
|
|
276
327
|
export {
|
|
277
328
|
DATA_DIR,
|
|
278
329
|
DEFAULT_HOLIDAY_COUNTRY,
|
|
279
|
-
applyDefaults,
|
|
280
330
|
createBackend,
|
|
281
331
|
getDiscordToken,
|
|
332
|
+
getTelegramToken,
|
|
282
333
|
isInQuietWindow,
|
|
283
334
|
loadConfig,
|
|
284
335
|
loadProfileConfig
|
|
@@ -140,7 +140,8 @@ function safeCodeBlock(content, lang = "") {
|
|
|
140
140
|
const escaped = content.replace(/```/g, "```");
|
|
141
141
|
return "```" + lang + "\n" + escaped + "\n```";
|
|
142
142
|
}
|
|
143
|
-
|
|
143
|
+
const MAX_DISCORD_MESSAGE = 2000;
|
|
144
|
+
function chunk(text, limit = MAX_DISCORD_MESSAGE) {
|
|
144
145
|
if (text.length <= limit) return [text];
|
|
145
146
|
const out = [];
|
|
146
147
|
let rest = text;
|
|
@@ -184,5 +185,6 @@ function chunk(text, limit = 2e3) {
|
|
|
184
185
|
export {
|
|
185
186
|
chunk,
|
|
186
187
|
formatForDiscord,
|
|
187
|
-
safeCodeBlock
|
|
188
|
+
safeCodeBlock,
|
|
189
|
+
MAX_DISCORD_MESSAGE
|
|
188
190
|
};
|
|
@@ -109,41 +109,3 @@ export async function ingestTranscript(filePath, { cwd } = {}) {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
export async function listCoreMemories(args = {}) {
|
|
113
|
-
const rawProjectId = args && Object.prototype.hasOwnProperty.call(args, 'project_id')
|
|
114
|
-
? args.project_id
|
|
115
|
-
: args?.projectScope
|
|
116
|
-
const projectId = rawProjectId == null || rawProjectId === 'all' ? '*' : rawProjectId
|
|
117
|
-
const result = await memoryFetch('POST', '/api/tool', {
|
|
118
|
-
name: 'memory',
|
|
119
|
-
arguments: {
|
|
120
|
-
action: 'core',
|
|
121
|
-
op: 'list',
|
|
122
|
-
project_id: projectId,
|
|
123
|
-
},
|
|
124
|
-
}, 30_000)
|
|
125
|
-
if (!result || result.error) {
|
|
126
|
-
throw new Error(result?.error || 'core memory list: empty response')
|
|
127
|
-
}
|
|
128
|
-
return result
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export async function searchMemories(args = {}) {
|
|
132
|
-
const result = await memoryFetch('POST', '/api/tool', {
|
|
133
|
-
name: 'search_memories',
|
|
134
|
-
arguments: args && typeof args === 'object' ? args : {},
|
|
135
|
-
}, 30_000)
|
|
136
|
-
if (!result || result.error) {
|
|
137
|
-
throw new Error(result?.error || 'memory_search: empty response')
|
|
138
|
-
}
|
|
139
|
-
return result
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export async function isHealthy() {
|
|
143
|
-
try {
|
|
144
|
-
const result = await memoryFetch('GET', '/health')
|
|
145
|
-
return result.status === 'ok'
|
|
146
|
-
} catch {
|
|
147
|
-
return false
|
|
148
|
-
}
|
|
149
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, statSync, watch, openSync, readSync, closeSync } from "fs";
|
|
2
2
|
import { createHash } from "crypto";
|
|
3
|
-
import {
|
|
3
|
+
import { safeCodeBlock } from "./format.mjs";
|
|
4
4
|
import { dropTrace, _dtPreview } from "./drop-trace.mjs";
|
|
5
5
|
import {
|
|
6
6
|
formatToolSurface,
|
|
@@ -282,7 +282,12 @@ class OutputForwarder {
|
|
|
282
282
|
this.commitReadProgress(item.nextFileSize);
|
|
283
283
|
return;
|
|
284
284
|
}
|
|
285
|
-
|
|
285
|
+
// Formatting is routed through the active backend via the send-callback so
|
|
286
|
+
// the forwarder stays backend-agnostic (Discord/Telegram/etc). Preformatted
|
|
287
|
+
// items (tool logs) are sent as-is. Fallback to raw text when no hook.
|
|
288
|
+
const formatted = item.preformatted
|
|
289
|
+
? item.text
|
|
290
|
+
: (this.cb.formatOutgoing ? this.cb.formatOutgoing(item.text) : item.text);
|
|
286
291
|
const hash = item.skipHashDedup
|
|
287
292
|
? ""
|
|
288
293
|
: item.dedupKey
|
|
@@ -292,48 +297,25 @@ class OutputForwarder {
|
|
|
292
297
|
this.commitReadProgress(item.nextFileSize);
|
|
293
298
|
return;
|
|
294
299
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
//
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const status = err?.status ?? err?.code ?? err?.httpStatus;
|
|
315
|
-
const retryAfter = err?.retryAfter ?? err?.retry_after
|
|
316
|
-
?? err?.headers?.["retry-after"] ?? err?.response?.headers?.["retry-after"];
|
|
317
|
-
dropTrace("discord.send.err", { channelId: this.channelId, chunkIndex: _ci, status, retryAfter: retryAfter ?? "(none)", err: String(err) });
|
|
318
|
-
item._sendRetries = (item._sendRetries || 0) + 1;
|
|
319
|
-
if (item._sendRetries >= 3) {
|
|
320
|
-
// Cap retries to avoid infinite duplicate loop — give up on this item
|
|
321
|
-
process.stderr.write(`[output-forwarder] chunk send exceeded 3 retries at chunk ${_ci}, dropping item\n`);
|
|
322
|
-
item._nextChunkIdx = item._chunks.length; // mark exhausted
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
if (status === 429) {
|
|
326
|
-
if (retryAfter != null) {
|
|
327
|
-
const ms = Number(retryAfter) > 1000 ? Number(retryAfter) : Number(retryAfter) * 1000;
|
|
328
|
-
if (Number.isFinite(ms) && ms > 0) {
|
|
329
|
-
await new Promise((r) => setTimeout(r, Math.min(ms, 60_000)));
|
|
330
|
-
}
|
|
331
|
-
} else {
|
|
332
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
throw err;
|
|
336
|
-
}
|
|
300
|
+
// Backend owns chunking + send retry: pass the whole (unchunked) text once.
|
|
301
|
+
// On failure the backend has already exhausted its per-chunk retries and
|
|
302
|
+
// throws; we re-throw so drainQueue/scheduleRetry requeues the whole item.
|
|
303
|
+
try {
|
|
304
|
+
// Hand back any opaque resume token stored from a prior partial-send
|
|
305
|
+
// failure so the backend resumes at the failed chunk instead of
|
|
306
|
+
// re-sending chunks that already landed. The token is opaque here —
|
|
307
|
+
// the forwarder only stores/passes/clears it, never interprets it.
|
|
308
|
+
await this.cb.send(targetChannelId, formatted, item._resumeToken ? { resumeToken: item._resumeToken } : undefined);
|
|
309
|
+
// Full success — drop any stale token so a later reuse of this item
|
|
310
|
+
// object can't carry a dead token.
|
|
311
|
+
item._resumeToken = undefined;
|
|
312
|
+
dropTrace("discord.send.ok", null);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
// Persist the opaque resume token (if any) on the item so the requeued
|
|
315
|
+
// retry resumes from the failed chunk.
|
|
316
|
+
if (err?.resumeToken) item._resumeToken = err.resumeToken;
|
|
317
|
+
dropTrace("discord.send.err", { channelId: this.channelId, err: String(err) });
|
|
318
|
+
throw err;
|
|
337
319
|
}
|
|
338
320
|
if (!item.skipHashDedup) {
|
|
339
321
|
this.lastHash = hash;
|
|
@@ -344,7 +326,9 @@ class OutputForwarder {
|
|
|
344
326
|
|
|
345
327
|
${_bt.trim()}` : _bt.trim();
|
|
346
328
|
}
|
|
347
|
-
|
|
329
|
+
// sentCount now tracks delivered items (the forwarder no longer knows the
|
|
330
|
+
// backend's chunk count). Increment by 1 per delivered item.
|
|
331
|
+
this.sentCount += 1;
|
|
348
332
|
this.commitReadProgress(item.nextFileSize);
|
|
349
333
|
}
|
|
350
334
|
scheduleRetry() {
|
|
@@ -375,7 +359,13 @@ ${_bt.trim()}` : _bt.trim();
|
|
|
375
359
|
// finalLane[0] is being drained when this.sending; coalesce into tail only
|
|
376
360
|
// when tail is not index 0 (i.e. length >= 2) or drain is not in flight.
|
|
377
361
|
const ftLen = this.finalLane.length;
|
|
378
|
-
|
|
362
|
+
// A partially-delivered item carries an opaque _resumeToken pinned (by
|
|
363
|
+
// hash) to its exact text. Mutating its text here would break that hash on
|
|
364
|
+
// the next retry, forcing the backend to full-resend from chunk 0 and
|
|
365
|
+
// duplicate the chunks already delivered. Treat any tokenized item as
|
|
366
|
+
// sealed: never coalesce/cap-merge into it; new text goes behind it as a
|
|
367
|
+
// separate item.
|
|
368
|
+
const ftTail = (ftLen > 0 && !(this.sending && ftLen === 1) && !this.finalLane[ftLen - 1]._resumeToken)
|
|
379
369
|
? this.finalLane[ftLen - 1] : null;
|
|
380
370
|
if (ftTail) {
|
|
381
371
|
ftTail.text = `${ftTail.text}\n\n${newText}`;
|
|
@@ -391,6 +381,8 @@ ${_bt.trim()}` : _bt.trim();
|
|
|
391
381
|
const capEnd = this.sending ? 1 : 0;
|
|
392
382
|
for (let i = this.finalLane.length - 1; i >= capEnd; i--) {
|
|
393
383
|
const cap = this.finalLane[i];
|
|
384
|
+
// Never fold into a sealed (partially-delivered) item — see above.
|
|
385
|
+
if (cap._resumeToken) continue;
|
|
394
386
|
cap.text = `${cap.text}\n\n${newText}`;
|
|
395
387
|
cap.bufferText = `${cap.bufferText}\n\n${newText}`;
|
|
396
388
|
cap.nextFileSize = nextFileSize;
|
|
@@ -446,8 +438,20 @@ ${_bt.trim()}` : _bt.trim();
|
|
|
446
438
|
}
|
|
447
439
|
lane.shift();
|
|
448
440
|
} catch (err) {
|
|
449
|
-
|
|
450
|
-
|
|
441
|
+
// Permanent (non-retryable) send failure — e.g. Telegram 400 "chat
|
|
442
|
+
// not found", Discord 403/404. Retrying would loop forever and wedge
|
|
443
|
+
// the queue, so DROP the item: advance the read cursor past its bytes
|
|
444
|
+
// (so it never reprocesses), clear any stale resume token, and keep
|
|
445
|
+
// draining the next item.
|
|
446
|
+
if (err?.permanent) {
|
|
447
|
+
process.stderr.write(`[output-forwarder] permanent send failure, dropping item: ${err?.message || err}\n`);
|
|
448
|
+
dropTrace("drain.send.permanent", { lane: fromFinal ? "final" : "stream", itemType: item?.type, err: String(err) });
|
|
449
|
+
item._resumeToken = undefined;
|
|
450
|
+
this.commitReadProgress(item.nextFileSize);
|
|
451
|
+
lane.shift();
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
process.stderr.write(`[output-forwarder] send failed: ${err}\n`);
|
|
451
455
|
dropTrace("drain.send.err", { finalLen: this.finalLane.length, streamLen: this.streamLane.length, lane: fromFinal ? "final" : "stream", itemType: item?.type, err: String(err) });
|
|
452
456
|
this.scheduleRetry();
|
|
453
457
|
break;
|
|
@@ -467,8 +471,7 @@ ${_bt.trim()}` : _bt.trim();
|
|
|
467
471
|
}
|
|
468
472
|
await this.cb.react(this.channelId, this.userMessageId, newEmoji);
|
|
469
473
|
this.emoji = newEmoji;
|
|
470
|
-
} catch {
|
|
471
|
-
}
|
|
474
|
+
} catch {} // best-effort: reaction is non-critical
|
|
472
475
|
}
|
|
473
476
|
await this.deliverQueueItem(item);
|
|
474
477
|
}
|
|
@@ -487,7 +490,7 @@ ${_bt.trim()}` : _bt.trim();
|
|
|
487
490
|
try {
|
|
488
491
|
this.pendingFinalFlush = true;
|
|
489
492
|
this.updateState((state) => { state.pendingFinalFlush = true; });
|
|
490
|
-
} catch {}
|
|
493
|
+
} catch {} // best-effort: durable flush marker is non-critical
|
|
491
494
|
if (retries < 5) {
|
|
492
495
|
setTimeout(() => void this.forwardFinalText(retries + 1, channelId), 300);
|
|
493
496
|
} else {
|
|
@@ -511,8 +514,7 @@ ${_bt.trim()}` : _bt.trim();
|
|
|
511
514
|
if (this.userMessageId && this.emoji) {
|
|
512
515
|
try {
|
|
513
516
|
await this.cb.removeReaction(channelId, this.userMessageId, this.emoji);
|
|
514
|
-
} catch {
|
|
515
|
-
}
|
|
517
|
+
} catch {} // best-effort: remove reaction is non-critical
|
|
516
518
|
}
|
|
517
519
|
const { text: newText, nextFileSize } = this.extractNewText();
|
|
518
520
|
if (newText) {
|
|
@@ -520,14 +522,23 @@ ${_bt.trim()}` : _bt.trim();
|
|
|
520
522
|
try {
|
|
521
523
|
await this.deliverQueueItem(finalItem);
|
|
522
524
|
} catch (err) {
|
|
523
|
-
//
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
525
|
+
// Permanent (non-retryable) failure — drop instead of requeue so we
|
|
526
|
+
// don't loop forever on a dead channel. extractNewText already
|
|
527
|
+
// advanced the read cursor, so commit it and let the frame go.
|
|
528
|
+
if (err?.permanent) {
|
|
529
|
+
process.stderr.write(`[output-forwarder] permanent final-send failure, dropping: ${err?.message || err}\n`);
|
|
530
|
+
this.commitReadProgress(nextFileSize);
|
|
531
|
+
} else {
|
|
532
|
+
// Transient send failure: extractNewText already advanced the read
|
|
533
|
+
// cursor past these bytes, so dropping the item here would lose the
|
|
534
|
+
// final text. Requeue the WHOLE item and let drainQueue retry
|
|
535
|
+
// instead of silently discarding. The backend owns chunking+retry,
|
|
536
|
+
// so a requeued send restarts from chunk 0. pendingFinalFlush stays
|
|
537
|
+
// set so a process restart can also resume the flush.
|
|
538
|
+
this.finalLane.push(finalItem);
|
|
539
|
+
this.scheduleRetry();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
531
542
|
}
|
|
532
543
|
} else {
|
|
533
544
|
this.commitReadProgress(nextFileSize);
|
|
@@ -546,7 +557,7 @@ ${_bt.trim()}` : _bt.trim();
|
|
|
546
557
|
try {
|
|
547
558
|
this.pendingFinalFlush = false;
|
|
548
559
|
this.updateState((state) => { state.pendingFinalFlush = false; });
|
|
549
|
-
} catch {}
|
|
560
|
+
} catch {} // best-effort: clear flush marker is non-critical
|
|
550
561
|
this.updateState((state) => {
|
|
551
562
|
state.sessionIdle = true;
|
|
552
563
|
});
|
|
@@ -569,7 +580,7 @@ ${_bt.trim()}` : _bt.trim();
|
|
|
569
580
|
// isHidden, because that helper would re-consult the module-local
|
|
570
581
|
// HIDDEN_TOOLS Set and ignore the OutputForwarder static.
|
|
571
582
|
if (OutputForwarder.HIDDEN_TOOLS.has(name)) return true;
|
|
572
|
-
if (
|
|
583
|
+
if (name === "reply" || name === "react" || name === "edit_message" || name === "fetch" || name === "download_attachment") return true;
|
|
573
584
|
return false;
|
|
574
585
|
};
|
|
575
586
|
/** Concatenate text blocks from a transcript entry (user or assistant). */
|
|
@@ -684,13 +695,12 @@ ${_bt.trim()}` : _bt.trim();
|
|
|
684
695
|
this.watchDebounce = null;
|
|
685
696
|
let _wfStat = null;
|
|
686
697
|
if (this.transcriptPath) {
|
|
687
|
-
try { _wfStat = statSync(this.transcriptPath); } catch {}
|
|
698
|
+
try { _wfStat = statSync(this.transcriptPath); } catch {} // best-effort: stat during flush
|
|
688
699
|
}
|
|
689
700
|
if (this.transcriptPath && !_wfStat) {
|
|
690
701
|
const relocated = detectCurrentSessionTranscript()?.transcriptPath ?? findLatestTranscriptByMtime();
|
|
691
702
|
if (relocated && relocated !== this.transcriptPath) {
|
|
692
|
-
process.stderr.write(`
|
|
693
|
-
`);
|
|
703
|
+
process.stderr.write(`[output-forwarder] watched transcript gone during flush, relocated to ${relocated}\n`);
|
|
694
704
|
dropTrace("watch.flush.relocate", { from: this.transcriptPath, to: relocated });
|
|
695
705
|
this.closeWatcher();
|
|
696
706
|
this.transcriptPath = relocated;
|
|
@@ -755,11 +765,7 @@ ${_bt.trim()}` : _bt.trim();
|
|
|
755
765
|
}
|
|
756
766
|
export {
|
|
757
767
|
OutputForwarder,
|
|
758
|
-
cwdToProjectSlug,
|
|
759
|
-
detectCurrentSessionTranscript,
|
|
760
|
-
discoverCurrentClaudeSession,
|
|
761
768
|
discoverSessionBoundTranscript,
|
|
762
769
|
findLatestTranscriptByMtime,
|
|
763
|
-
|
|
764
|
-
listInteractiveClaudeSessions
|
|
770
|
+
sameResolvedPath
|
|
765
771
|
};
|
|
@@ -68,6 +68,7 @@ function isPidAlive(pid) {
|
|
|
68
68
|
return e?.code === "EPERM";
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
|
+
const UI_HEARTBEAT_STALE_MS = 5 * 60 * 1e3;
|
|
71
72
|
function activeInstanceStaleReason(state) {
|
|
72
73
|
const ownerPid = getActiveOwnerPid(state);
|
|
73
74
|
if (!isPidAlive(ownerPid)) return `owner PID ${ownerPid ?? "unknown"} is dead`;
|
|
@@ -77,8 +78,35 @@ function activeInstanceStaleReason(state) {
|
|
|
77
78
|
if (workerPid && !isPidAlive(workerPid)) return `worker PID ${workerPid} is dead`;
|
|
78
79
|
const serverPid = parsePositivePid(state?.server_pid);
|
|
79
80
|
if (serverPid && !isPidAlive(serverPid)) return `server PID ${serverPid} is dead`;
|
|
81
|
+
// Zombie-Lead repro (2026-07-02): a Lead's owner/channels/worker/server
|
|
82
|
+
// PIDs can all still be alive (process not killed) while the TUI's render
|
|
83
|
+
// loop is dead in the water — no signal ever fires, so pid-only staleness
|
|
84
|
+
// never trips. If the TUI is heartbeating (field present), treat a stale
|
|
85
|
+
// heartbeat as stale ownership too. Backward-compat: state written by an
|
|
86
|
+
// older/non-TUI process (or before the first heartbeat tick) simply omits
|
|
87
|
+
// ui_heartbeat_at, so this branch is a no-op and pid-only judgment stands.
|
|
88
|
+
const uiHeartbeatAt = Number(state?.ui_heartbeat_at);
|
|
89
|
+
if (Number.isFinite(uiHeartbeatAt) && uiHeartbeatAt > 0) {
|
|
90
|
+
const age = Date.now() - uiHeartbeatAt;
|
|
91
|
+
if (age > UI_HEARTBEAT_STALE_MS) {
|
|
92
|
+
return `ui heartbeat stale (${Math.round(age / 1000)}s since last tick)`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
80
95
|
return null;
|
|
81
96
|
}
|
|
97
|
+
// Called from src/tui on a 30s timer while the render loop is alive. Only
|
|
98
|
+
// touches ui_heartbeat_at (and updatedAt) so it never races/clobbers the
|
|
99
|
+
// channels worker's own refreshActiveInstance() writes.
|
|
100
|
+
function touchUiHeartbeat(instanceId) {
|
|
101
|
+
ensureRuntimeDirs();
|
|
102
|
+
try {
|
|
103
|
+
updateJsonAtomicSync(ACTIVE_INSTANCE_FILE, (curRaw) => {
|
|
104
|
+
if (!curRaw) return undefined;
|
|
105
|
+
if (instanceId && curRaw.instanceId !== instanceId) return undefined;
|
|
106
|
+
return { ...curRaw, ui_heartbeat_at: Date.now(), updatedAt: Date.now() };
|
|
107
|
+
}, { compact: true, fsync: false, fsyncDir: false });
|
|
108
|
+
} catch { /* best-effort; a missed tick just relies on the next one */ }
|
|
109
|
+
}
|
|
82
110
|
function buildRuntimeIdentity() {
|
|
83
111
|
const terminalLeadPid = getTerminalLeadPid();
|
|
84
112
|
const serverPid = getServerPid();
|
|
@@ -468,11 +496,7 @@ function clearActiveInstance(instanceId) {
|
|
|
468
496
|
});
|
|
469
497
|
}
|
|
470
498
|
export {
|
|
471
|
-
ACTIVE_INSTANCE_FILE,
|
|
472
|
-
OWNER_DIR,
|
|
473
499
|
RUNTIME_ROOT,
|
|
474
|
-
RUNTIME_STALE_TTL,
|
|
475
|
-
buildActiveInstanceState,
|
|
476
500
|
cleanupInstanceRuntimeFiles,
|
|
477
501
|
cleanupStaleRuntimeFiles,
|
|
478
502
|
clearActiveInstance,
|
|
@@ -485,13 +509,12 @@ export {
|
|
|
485
509
|
getPermissionResultPath,
|
|
486
510
|
getTerminalLeadPid,
|
|
487
511
|
getStatusPath,
|
|
488
|
-
getStopFlagPath,
|
|
489
512
|
getTurnEndPath,
|
|
490
513
|
notePreviousServerIfAny,
|
|
491
514
|
makeInstanceId,
|
|
492
515
|
readActiveInstance,
|
|
493
516
|
refreshActiveInstance,
|
|
494
517
|
releaseOwnedChannelLocks,
|
|
495
|
-
|
|
518
|
+
touchUiHeartbeat,
|
|
496
519
|
writeServerPid
|
|
497
520
|
};
|
|
@@ -8,7 +8,7 @@ import { runScript as execScript, ensureNopluginDir } from "./executor.mjs";
|
|
|
8
8
|
import { withFileLockSync } from "../../shared/atomic-file.mjs";
|
|
9
9
|
import { makeAgentDispatch } from '../../agent/orchestrator/agent-runtime/agent-dispatch.mjs';
|
|
10
10
|
|
|
11
|
-
const schedulerLlm = makeAgentDispatch({ taskType: 'scheduler-task',
|
|
11
|
+
const schedulerLlm = makeAgentDispatch({ taskType: 'scheduler-task', agent: 'scheduler-task', sourceType: 'scheduler' });
|
|
12
12
|
const SCHEDULE_LOG = join(DATA_DIR, "schedule.log");
|
|
13
13
|
// Buffered async logger — coalesces per-line appends into batched writes.
|
|
14
14
|
let _schedLogBuf = [];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, statSync } from "fs";
|
|
2
2
|
import { execFileSync } from "child_process";
|
|
3
3
|
import { basename, join, resolve } from "path";
|
|
4
|
-
import { homedir } from "os";
|
|
5
4
|
import { mixdogHome } from "../../shared/plugin-paths.mjs";
|
|
6
5
|
|
|
7
6
|
function cwdToProjectSlug(cwd) {
|
|
@@ -94,9 +93,6 @@ function getLatestInteractiveClaudeSession() {
|
|
|
94
93
|
|
|
95
94
|
export {
|
|
96
95
|
cwdToProjectSlug,
|
|
97
|
-
getParentPid,
|
|
98
|
-
readSessionRecord,
|
|
99
|
-
isInteractiveSession,
|
|
100
96
|
discoverCurrentClaudeSession,
|
|
101
97
|
listInteractiveClaudeSessions,
|
|
102
98
|
getLatestInteractiveClaudeSession
|