@khalilgharbaoui/opencode-claude-code-plugin 0.42.0 → 0.42.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/dist/index.js +151 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +8 -3
package/dist/index.js
CHANGED
|
@@ -1723,6 +1723,21 @@ function cliSupportsDontAsk(v) {
|
|
|
1723
1723
|
if (!v) return false;
|
|
1724
1724
|
return gte(v, { major: 2, minor: 1, patch: 263 });
|
|
1725
1725
|
}
|
|
1726
|
+
var INTERACTIVE_MEASURED_CLI = "2.1.288";
|
|
1727
|
+
var INTERACTIVE_MEASURED = { major: 2, minor: 1, patch: 288 };
|
|
1728
|
+
function isUnmeasuredInteractiveCli(v) {
|
|
1729
|
+
if (!v) return false;
|
|
1730
|
+
return gte(v, INTERACTIVE_MEASURED) && !(v.major === INTERACTIVE_MEASURED.major && v.minor === INTERACTIVE_MEASURED.minor && v.patch === INTERACTIVE_MEASURED.patch);
|
|
1731
|
+
}
|
|
1732
|
+
var warnedUnmeasuredInteractive = /* @__PURE__ */ new Set();
|
|
1733
|
+
function reportUnmeasuredInteractiveCli(v) {
|
|
1734
|
+
if (!v || !isUnmeasuredInteractiveCli(v) || warnedUnmeasuredInteractive.has(v.raw)) return;
|
|
1735
|
+
warnedUnmeasuredInteractive.add(v.raw);
|
|
1736
|
+
log.warn(
|
|
1737
|
+
"the interactive transport has not been measured on this Claude Code version; it reads the TUI's screen and transcript, which a new release can change. If a turn hangs or a dialog is not handled, run /claude-code-doctor bundle and report it",
|
|
1738
|
+
{ cliVersion: v.raw, measuredCli: INTERACTIVE_MEASURED_CLI }
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1726
1741
|
function cliSupportsSideQuestion(v) {
|
|
1727
1742
|
if (!v) return false;
|
|
1728
1743
|
return gte(v, { major: 2, minor: 1, patch: 258 });
|
|
@@ -3783,12 +3798,16 @@ async function fetchSessionParentId(sessionID) {
|
|
|
3783
3798
|
return void 0;
|
|
3784
3799
|
}
|
|
3785
3800
|
}
|
|
3786
|
-
async function fetchSessionRunState(sessionID) {
|
|
3801
|
+
async function fetchSessionRunState(sessionID, directory) {
|
|
3787
3802
|
if (!sessionID || sessionID === "default") return "unknown";
|
|
3788
3803
|
const status = opencodeClient?.session?.status;
|
|
3789
3804
|
if (!status) return "unknown";
|
|
3790
3805
|
try {
|
|
3791
|
-
const
|
|
3806
|
+
const dir = directory ?? await fetchSessionDirectory(sessionID);
|
|
3807
|
+
const res = await status.call(
|
|
3808
|
+
opencodeClient.session,
|
|
3809
|
+
dir ? { query: { directory: dir } } : void 0
|
|
3810
|
+
);
|
|
3792
3811
|
const data = res.data;
|
|
3793
3812
|
if (!data || typeof data !== "object") return "unknown";
|
|
3794
3813
|
const entry = data[sessionID];
|
|
@@ -3808,8 +3827,10 @@ async function settleSessionRunState(sessionID, options = {}) {
|
|
|
3808
3827
|
const windowMs = options.windowMs ?? 1500;
|
|
3809
3828
|
const started = Date.now();
|
|
3810
3829
|
let last = "unknown";
|
|
3830
|
+
let directory = options.directory;
|
|
3811
3831
|
for (; ; ) {
|
|
3812
|
-
|
|
3832
|
+
directory ??= await fetchSessionDirectory(sessionID);
|
|
3833
|
+
const state = await fetchSessionRunState(sessionID, directory ?? "");
|
|
3813
3834
|
if (state === "busy") return "busy";
|
|
3814
3835
|
if (state !== "unknown") last = state;
|
|
3815
3836
|
if (Date.now() - started >= windowMs) return last;
|
|
@@ -5425,6 +5446,7 @@ function snapshotActiveProcesses(now = Date.now()) {
|
|
|
5425
5446
|
ageMs: ap.startedAt === void 0 ? void 0 : Math.max(0, now - ap.startedAt),
|
|
5426
5447
|
effort: ap.effort,
|
|
5427
5448
|
attached: ap.lineEmitter.listenerCount("line") > 0,
|
|
5449
|
+
transport: ap.interactiveControl ? "interactive" : "headless",
|
|
5428
5450
|
proxyUrl: ap.proxyServer?.url,
|
|
5429
5451
|
lastStderr: typeof lastStderr === "string" ? lastStderr : void 0
|
|
5430
5452
|
});
|
|
@@ -5839,6 +5861,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
5839
5861
|
"skipping remote MCP server with no url",
|
|
5840
5862
|
"stale scratch directory sweep failed",
|
|
5841
5863
|
"task_batch resolving with child results missing",
|
|
5864
|
+
"the interactive transport has not been measured on this Claude Code version; it reads the TUI's screen and transcript, which a new release can change. If a turn hangs or a dialog is not handled, run /claude-code-doctor bundle and report it",
|
|
5842
5865
|
"this opencode process is running an older plugin build than the one on disk",
|
|
5843
5866
|
"timed out pending proxy call",
|
|
5844
5867
|
"turn abort listener threw",
|
|
@@ -5988,6 +6011,7 @@ var BUNDLE_DATA_ALLOWLIST = {
|
|
|
5988
6011
|
toolNames: "name",
|
|
5989
6012
|
cliFloor: "version",
|
|
5990
6013
|
cliVersion: "version",
|
|
6014
|
+
measuredCli: "version",
|
|
5991
6015
|
// The two halves of the stale-build WARN: what this process loaded and what
|
|
5992
6016
|
// is on disk now. Both are this package's own version strings.
|
|
5993
6017
|
loadedVersion: "version",
|
|
@@ -6447,6 +6471,34 @@ import * as fs4 from "fs";
|
|
|
6447
6471
|
import * as path5 from "path";
|
|
6448
6472
|
import { promisify as promisify2 } from "util";
|
|
6449
6473
|
import { fileURLToPath } from "url";
|
|
6474
|
+
|
|
6475
|
+
// src/transport.ts
|
|
6476
|
+
function requestedTransport(config, env = process.env.CLAUDE_CODE_INTERACTIVE_TRANSPORT) {
|
|
6477
|
+
if (config.transport !== void 0) {
|
|
6478
|
+
if (!["auto", "headless", "interactive"].includes(config.transport)) {
|
|
6479
|
+
throw new Error(`Unknown Claude transport: ${config.transport}`);
|
|
6480
|
+
}
|
|
6481
|
+
return config.transport;
|
|
6482
|
+
}
|
|
6483
|
+
const enabled = config.interactive ?? (env !== void 0 && !["", "0", "false", "no", "off"].includes(env.trim().toLowerCase()));
|
|
6484
|
+
return enabled ? "interactive" : "headless";
|
|
6485
|
+
}
|
|
6486
|
+
function hasInteractiveTransport() {
|
|
6487
|
+
const runtime = globalThis;
|
|
6488
|
+
return typeof runtime.Bun?.Terminal === "function";
|
|
6489
|
+
}
|
|
6490
|
+
async function selectTransport(requested, cliPath, available = hasInteractiveTransport()) {
|
|
6491
|
+
if (requested === "headless") return "headless";
|
|
6492
|
+
if (requested === "interactive") {
|
|
6493
|
+
if (!available) throw new Error("Interactive Claude transport requires Bun.Terminal. Run opencode under a Bun runtime with PTY support.");
|
|
6494
|
+
return "interactive";
|
|
6495
|
+
}
|
|
6496
|
+
if (await detectHeadlessSupport(cliPath) !== "unsupported") return "headless";
|
|
6497
|
+
if (!available) throw new Error("Claude no longer supports the headless flags, and Bun.Terminal is unavailable. Run opencode under a Bun runtime with PTY support.");
|
|
6498
|
+
return "interactive";
|
|
6499
|
+
}
|
|
6500
|
+
|
|
6501
|
+
// src/startup-diagnostics.ts
|
|
6450
6502
|
var cachedPluginVersion;
|
|
6451
6503
|
function pluginVersion() {
|
|
6452
6504
|
if (cachedPluginVersion) return cachedPluginVersion;
|
|
@@ -6529,6 +6581,15 @@ function collectStartupDiagnostics(providers, opencodeVersion) {
|
|
|
6529
6581
|
error: err instanceof Error ? err.message : String(err)
|
|
6530
6582
|
});
|
|
6531
6583
|
}
|
|
6584
|
+
let transport;
|
|
6585
|
+
try {
|
|
6586
|
+
transport = requestedTransport({
|
|
6587
|
+
transport: firstOption(providers, "transport"),
|
|
6588
|
+
interactive: firstOption(providers, "interactive")
|
|
6589
|
+
});
|
|
6590
|
+
} catch {
|
|
6591
|
+
transport = "invalid";
|
|
6592
|
+
}
|
|
6532
6593
|
return {
|
|
6533
6594
|
plugin: pluginVersion(),
|
|
6534
6595
|
opencode: opencodeVersion ?? process.env.OPENCODE_VERSION ?? "unknown",
|
|
@@ -6541,7 +6602,8 @@ function collectStartupDiagnostics(providers, opencodeVersion) {
|
|
|
6541
6602
|
permissionPresets: Object.entries(providers).map(
|
|
6542
6603
|
([name, entry]) => summarizePermissionPreset(name, entry?.options)
|
|
6543
6604
|
),
|
|
6544
|
-
|
|
6605
|
+
transport,
|
|
6606
|
+
interactiveTransport: transport === "interactive",
|
|
6545
6607
|
planModeQuestion: firstOption(providers, "planModeQuestion") === true,
|
|
6546
6608
|
anthropicApiKeyInEnv: Boolean(
|
|
6547
6609
|
process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN
|
|
@@ -6810,7 +6872,14 @@ function formatDoctorReport(report) {
|
|
|
6810
6872
|
lines.push(`| proxyTools | ${list(report.proxyTools)} |`);
|
|
6811
6873
|
lines.push(`| MCP servers (on disk) | ${list(report.mcpServers)} |`);
|
|
6812
6874
|
lines.push(`| permissionPreset | ${describePermissionPresets(report.permissionPresets)} |`);
|
|
6813
|
-
lines.push(
|
|
6875
|
+
lines.push(
|
|
6876
|
+
`| transport | ${report.transport}${report.transport === "auto" ? " (headless until claude refuses `--print`)" : ""}; this conversation: ${report.transportInUse} |`
|
|
6877
|
+
);
|
|
6878
|
+
if (report.interactiveMeasured) {
|
|
6879
|
+
lines.push(
|
|
6880
|
+
`| interactive transport measured on | Claude Code ${report.interactiveMeasured.measured}${report.interactiveMeasured.unmeasured ? ` (this CLI, ${report.claudeCli.version}, is newer and unmeasured: if a turn hangs, send \`/claude-code-doctor bundle\`)` : ""} |`
|
|
6881
|
+
);
|
|
6882
|
+
}
|
|
6814
6883
|
lines.push(`| planModeQuestion | ${report.planModeQuestion} |`);
|
|
6815
6884
|
lines.push(`| turnStats | ${report.turnStats} |`);
|
|
6816
6885
|
lines.push(`| ANTHROPIC_API_KEY in env | ${report.anthropicApiKeyInEnv ? "yes" : "no"} |`);
|
|
@@ -6831,11 +6900,11 @@ function formatDoctorReport(report) {
|
|
|
6831
6900
|
if (report.processes.length === 0) {
|
|
6832
6901
|
lines.push("None. The next message in a Claude Code session spawns one.");
|
|
6833
6902
|
} else {
|
|
6834
|
-
lines.push("| session | model | pid | in flight | age | effort |");
|
|
6835
|
-
lines.push("
|
|
6903
|
+
lines.push("| session | model | transport | pid | in flight | age | effort |");
|
|
6904
|
+
lines.push("|---|---|---|---|---|---|---|");
|
|
6836
6905
|
for (const proc of report.processes) {
|
|
6837
6906
|
lines.push(
|
|
6838
|
-
`| ${proc.session}${proc.compaction ? " (compaction)" : ""} | ${proc.model} | ${proc.pid ?? "unknown"} | ${proc.inFlight ? "yes" : "no"} | ${formatAge(proc.ageMs)} | ${proc.effort ?? "inherited"} |`
|
|
6907
|
+
`| ${proc.session}${proc.compaction ? " (compaction)" : ""} | ${proc.model} | ${proc.transport} | ${proc.pid ?? "unknown"} | ${proc.inFlight ? "yes" : "no"} | ${formatAge(proc.ageMs)} | ${proc.effort ?? "inherited"} |`
|
|
6839
6908
|
);
|
|
6840
6909
|
}
|
|
6841
6910
|
}
|
|
@@ -7055,7 +7124,9 @@ async function gatherDoctorReport(options) {
|
|
|
7055
7124
|
proxyTools: base.proxyTools,
|
|
7056
7125
|
mcpServers: base.mcpServers,
|
|
7057
7126
|
permissionPresets: base.permissionPresets,
|
|
7058
|
-
transport:
|
|
7127
|
+
transport: base.transport,
|
|
7128
|
+
transportInUse: options.interactive ? "interactive" : "headless",
|
|
7129
|
+
interactiveMeasured: base.transport !== "headless" || options.interactive || processes.some((proc) => proc.transport === "interactive") ? { measured: INTERACTIVE_MEASURED_CLI, unmeasured: isUnmeasuredInteractiveCli(cli) } : void 0,
|
|
7059
7130
|
planModeQuestion: base.planModeQuestion,
|
|
7060
7131
|
turnStats: options.turnStats,
|
|
7061
7132
|
anthropicApiKeyInEnv: base.anthropicApiKeyInEnv,
|
|
@@ -8219,6 +8290,38 @@ Now continuing with the current message:
|
|
|
8219
8290
|
}
|
|
8220
8291
|
});
|
|
8221
8292
|
}
|
|
8293
|
+
function getTrailingUserMessages(prompt, opts = {}) {
|
|
8294
|
+
prompt = stripAccountFailoverParts(prompt);
|
|
8295
|
+
if (opts.stripContextReminders) prompt = stripContextReminders(prompt).prompt;
|
|
8296
|
+
let assistantIndex = -1;
|
|
8297
|
+
for (let i = prompt.length - 1; i >= 0; i--) {
|
|
8298
|
+
if (prompt[i].role === "assistant") {
|
|
8299
|
+
assistantIndex = i;
|
|
8300
|
+
break;
|
|
8301
|
+
}
|
|
8302
|
+
}
|
|
8303
|
+
const messages = [];
|
|
8304
|
+
for (let i = assistantIndex + 1; i < prompt.length; i++) {
|
|
8305
|
+
const msg = prompt[i];
|
|
8306
|
+
if (msg.role !== "user") continue;
|
|
8307
|
+
if (parseSideQuestionContent(msg.content) !== null) continue;
|
|
8308
|
+
const blocks = [];
|
|
8309
|
+
if (typeof msg.content === "string") {
|
|
8310
|
+
if (msg.content.trim()) blocks.push({ type: "text", text: msg.content });
|
|
8311
|
+
} else if (Array.isArray(msg.content)) {
|
|
8312
|
+
for (const part of msg.content) {
|
|
8313
|
+
if (part.type === "text") {
|
|
8314
|
+
if (part.text && part.text.trim()) blocks.push({ type: "text", text: part.text });
|
|
8315
|
+
} else if (part.type === "file" || part.type === "image") {
|
|
8316
|
+
const block = toImageBlock(part);
|
|
8317
|
+
if (block) blocks.push(block);
|
|
8318
|
+
}
|
|
8319
|
+
}
|
|
8320
|
+
}
|
|
8321
|
+
if (blocks.length > 0) messages.push(blocks);
|
|
8322
|
+
}
|
|
8323
|
+
return { assistantIndex, messages };
|
|
8324
|
+
}
|
|
8222
8325
|
|
|
8223
8326
|
// src/skill-bridge.ts
|
|
8224
8327
|
import * as crypto3 from "crypto";
|
|
@@ -10026,32 +10129,6 @@ function spawnInteractiveProcess(opts) {
|
|
|
10026
10129
|
return ap;
|
|
10027
10130
|
}
|
|
10028
10131
|
|
|
10029
|
-
// src/transport.ts
|
|
10030
|
-
function requestedTransport(config, env = process.env.CLAUDE_CODE_INTERACTIVE_TRANSPORT) {
|
|
10031
|
-
if (config.transport !== void 0) {
|
|
10032
|
-
if (!["auto", "headless", "interactive"].includes(config.transport)) {
|
|
10033
|
-
throw new Error(`Unknown Claude transport: ${config.transport}`);
|
|
10034
|
-
}
|
|
10035
|
-
return config.transport;
|
|
10036
|
-
}
|
|
10037
|
-
const enabled = config.interactive ?? (env !== void 0 && !["", "0", "false", "no", "off"].includes(env.trim().toLowerCase()));
|
|
10038
|
-
return enabled ? "interactive" : "headless";
|
|
10039
|
-
}
|
|
10040
|
-
function hasInteractiveTransport() {
|
|
10041
|
-
const runtime = globalThis;
|
|
10042
|
-
return typeof runtime.Bun?.Terminal === "function";
|
|
10043
|
-
}
|
|
10044
|
-
async function selectTransport(requested, cliPath, available = hasInteractiveTransport()) {
|
|
10045
|
-
if (requested === "headless") return "headless";
|
|
10046
|
-
if (requested === "interactive") {
|
|
10047
|
-
if (!available) throw new Error("Interactive Claude transport requires Bun.Terminal. Run opencode under a Bun runtime with PTY support.");
|
|
10048
|
-
return "interactive";
|
|
10049
|
-
}
|
|
10050
|
-
if (await detectHeadlessSupport(cliPath) !== "unsupported") return "headless";
|
|
10051
|
-
if (!available) throw new Error("Claude no longer supports the headless flags, and Bun.Terminal is unavailable. Run opencode under a Bun runtime with PTY support.");
|
|
10052
|
-
return "interactive";
|
|
10053
|
-
}
|
|
10054
|
-
|
|
10055
10132
|
// src/prompts.ts
|
|
10056
10133
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
10057
10134
|
import { homedir as homedir8 } from "os";
|
|
@@ -12502,6 +12579,7 @@ async function isProxyCallStillServed(callId) {
|
|
|
12502
12579
|
return await fetchSessionRunState(session) === "busy";
|
|
12503
12580
|
}
|
|
12504
12581
|
setProxyDeadlineGuard(({ callId }) => isProxyCallStillServed(callId));
|
|
12582
|
+
var FORWARD_SETTLE_MS = 250;
|
|
12505
12583
|
var CHILD_EXIT_STATUS_GRACE_MS = 250;
|
|
12506
12584
|
var ClaudeCodeLanguageModel = class {
|
|
12507
12585
|
specificationVersion = "v3";
|
|
@@ -13294,6 +13372,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13294
13372
|
return abortedBeforeWork("waiting for opencode's MCP status and the CLI version");
|
|
13295
13373
|
}
|
|
13296
13374
|
const [runtimeStatus, cliVersion] = prologue.value;
|
|
13375
|
+
if (useInteractive) reportUnmeasuredInteractiveCli(cliVersion);
|
|
13297
13376
|
const ptyReadOnly = useInteractive && !compactionMode && !doctor && isReadOnlyPermissionMode(this.config.permissionMode);
|
|
13298
13377
|
const ptyReadOnlySupported = cliSupportsRestricted(cliVersion) && cliSupportsDontAsk(cliVersion);
|
|
13299
13378
|
if (ptyReadOnly && !ptyReadOnlySupported) {
|
|
@@ -13422,7 +13501,7 @@ var ClaudeCodeLanguageModel = class {
|
|
|
13422
13501
|
});
|
|
13423
13502
|
return;
|
|
13424
13503
|
}
|
|
13425
|
-
log.info("abort between proxy tool boundaries; releasing pending calls", { sk, reason });
|
|
13504
|
+
log.info("abort between proxy tool boundaries; releasing pending calls", { sk, reason, session: stopped });
|
|
13426
13505
|
void interruptTurn(stoppedProcess).then((idle) => {
|
|
13427
13506
|
log.info("interrupt sent for aborted turn", { sk, idle });
|
|
13428
13507
|
});
|
|
@@ -14253,6 +14332,34 @@ var ClaudeCodeLanguageModel = class {
|
|
|
14253
14332
|
state.proc.on("error", procErrorHandler);
|
|
14254
14333
|
if (hasMatchedPendingResults) {
|
|
14255
14334
|
state.cliAskedForWork = true;
|
|
14335
|
+
const forwardTrailingUserMessages = () => {
|
|
14336
|
+
if (compactionMode || !state.activeProcess) return 0;
|
|
14337
|
+
if (state.activeProcess.interactiveControl) return 0;
|
|
14338
|
+
const trailing = getTrailingUserMessages(effectivePrompt, {
|
|
14339
|
+
stripContextReminders: self.stripContextRemindersEnabled()
|
|
14340
|
+
});
|
|
14341
|
+
const sent = state.activeProcess.forwardedUserMessages?.assistantIndex === trailing.assistantIndex ? state.activeProcess.forwardedUserMessages.count : 0;
|
|
14342
|
+
const fresh = trailing.messages.slice(sent);
|
|
14343
|
+
if (fresh.length === 0) return 0;
|
|
14344
|
+
state.activeProcess.forwardedUserMessages = {
|
|
14345
|
+
assistantIndex: trailing.assistantIndex,
|
|
14346
|
+
count: trailing.messages.length
|
|
14347
|
+
};
|
|
14348
|
+
for (const content of fresh) {
|
|
14349
|
+
state.proc.stdin?.write(
|
|
14350
|
+
JSON.stringify({ type: "user", message: { role: "user", content } }) + "\n"
|
|
14351
|
+
);
|
|
14352
|
+
}
|
|
14353
|
+
log.info("forwarded user messages that arrived beside tool results", {
|
|
14354
|
+
sessionKey: sk,
|
|
14355
|
+
messages: fresh.length
|
|
14356
|
+
});
|
|
14357
|
+
return fresh.length;
|
|
14358
|
+
};
|
|
14359
|
+
if (!state.unattendedTurnEnded && forwardTrailingUserMessages() > 0) {
|
|
14360
|
+
await new Promise((resolve6) => setTimeout(resolve6, FORWARD_SETTLE_MS));
|
|
14361
|
+
if (state.controllerClosed) return;
|
|
14362
|
+
}
|
|
14256
14363
|
for (const { call, result } of previousPendingProxyMatches) {
|
|
14257
14364
|
if (result) {
|
|
14258
14365
|
const channelClosed = isPendingProxyCallChannelClosed(call);
|
|
@@ -14282,7 +14389,10 @@ var ClaudeCodeLanguageModel = class {
|
|
|
14282
14389
|
);
|
|
14283
14390
|
}
|
|
14284
14391
|
}
|
|
14285
|
-
if (state.unattendedTurnEnded)
|
|
14392
|
+
if (state.unattendedTurnEnded) {
|
|
14393
|
+
deliverPendingCompletions(state);
|
|
14394
|
+
forwardTrailingUserMessages();
|
|
14395
|
+
}
|
|
14286
14396
|
const unemitted = getPendingProxyCalls(sk).filter(
|
|
14287
14397
|
(call) => !call.emitted
|
|
14288
14398
|
);
|
|
@@ -14311,7 +14421,10 @@ var ClaudeCodeLanguageModel = class {
|
|
|
14311
14421
|
}
|
|
14312
14422
|
}
|
|
14313
14423
|
state.cliAskedForWork = true;
|
|
14314
|
-
if (state.activeProcess)
|
|
14424
|
+
if (state.activeProcess) {
|
|
14425
|
+
noteTurnStarted(state.activeProcess);
|
|
14426
|
+
state.activeProcess.forwardedUserMessages = void 0;
|
|
14427
|
+
}
|
|
14315
14428
|
state.proc.stdin?.write(userMsg + "\n");
|
|
14316
14429
|
log.debug("sent user message", { textLength: userMsg.length });
|
|
14317
14430
|
armStartWatchdog(state);
|