ocuclaw 1.3.0 → 1.3.1

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/README.md CHANGED
@@ -50,7 +50,9 @@ Advanced optional settings:
50
50
 
51
51
  ```bash
52
52
  openclaw config set plugins.entries.ocuclaw.config.wsBind "127.0.0.1"
53
- openclaw config set plugins.entries.ocuclaw.config.wsPort 9000 --strict-json
53
+ # wsPort default is 9000; on Windows that port is often reserved by WinNAT, so the
54
+ # setup assistant uses 47800. Pick any free port in 30000-49151 if you override it.
55
+ openclaw config set plugins.entries.ocuclaw.config.wsPort 47800 --strict-json
54
56
  openclaw config set plugins.entries.ocuclaw.config.sessionLimit 10 --strict-json
55
57
  openclaw config set plugins.entries.ocuclaw.config.externalDebugToolsEnabled true --strict-json
56
58
  ```
@@ -97,29 +97,36 @@ function resolveDebugNoisyPolicies(pluginValue, envValue) {
97
97
  return parseJsonOrUndefined(envValue, "debugNoisyPolicies");
98
98
  }
99
99
 
100
- // Supported live-refresh LLM backends. codex-cli was removed because the
101
- // Codex CLI's read-only sandbox still permits filesystem reads — agents
102
- // could exfil ~/.aws/credentials, ~/.ssh/*, etc. via stdout into the
103
- // glasses surface. Claude CLI is kept because --tools "" structurally
104
- // disables file access; operators who want Codex use openai-compat
105
- // pointed at the Codex API endpoint (tool-less).
100
+ // Supported live-refresh LLM backends both are HTTP API backends. The two
101
+ // CLI-spawn backends were removed: codex-cli because Codex's read-only sandbox
102
+ // still permits filesystem reads (an agent prompt could exfil ~/.aws/credentials,
103
+ // ~/.ssh/*, etc. via stdout into the glasses surface), and claude-cli to remove
104
+ // the plugin's last child_process spawn so the OpenClaw installer's static
105
+ // dangerous-code scanner passes without --dangerously-force-unsafe-install. (The
106
+ // scanner can't see that --tools "" made the CLI tool-less; it just sees a spawn.
107
+ // This is not an exploit claim about claude-cli — it removes spawn surface and
108
+ // clears the static block.) The proper agentic tier — delegating ticks to the
109
+ // native OpenClaw runtime instead of spawning a CLI — is tracked separately (the
110
+ // glasses-ui L1/L2 delegation redesign) and blocked on request-scoped
111
+ // api.runtime.subagent.run. Operators who want Claude/Codex point an *-api
112
+ // backend at the provider endpoint (key resolved via the host modelAuth).
106
113
  const GLASSES_UI_LIVE_BACKENDS = new Set([
107
- "claude-cli",
108
114
  "anthropic-api",
109
115
  "openai-compat",
110
116
  ]);
111
117
 
112
118
  const GLASSES_UI_LIVE_DEFAULT_MODEL = {
113
- "claude-cli": "claude-haiku-4-5-20251001",
114
119
  "anthropic-api": "anthropic/claude-haiku-4-5-20251001",
115
120
  "openai-compat": "gpt-4o-mini",
116
121
  };
117
122
 
118
123
  function resolveGlassesUiLive(value) {
119
124
  const raw = isObject(value) ? value : {};
125
+ // Unknown/removed backends (incl. a stale tickBackend: "claude-cli" or
126
+ // "codex-cli" from an operator's pre-removal config) coerce to this default.
120
127
  const tickBackend = GLASSES_UI_LIVE_BACKENDS.has(raw.tickBackend)
121
128
  ? raw.tickBackend
122
- : "claude-cli";
129
+ : "anthropic-api";
123
130
  const tickModel = pickString(raw.tickModel) || GLASSES_UI_LIVE_DEFAULT_MODEL[tickBackend];
124
131
  const tickApiBaseUrl = pickString(raw.tickApiBaseUrl) || "https://api.openai.com";
125
132
  return {
@@ -129,16 +136,12 @@ function resolveGlassesUiLive(value) {
129
136
  tickApiBaseUrl,
130
137
  allowAgentModelOverride: parseBool(raw.allowAgentModelOverride, false),
131
138
  tickMaxOutputTokens: parseIntOrDefault(raw.tickMaxOutputTokens, 200),
132
- // Shell recipes run agent-supplied commands on a schedule as the plugin
133
- // user. Default to disabled — operators must explicitly opt in via
134
- // plugins.entries.ocuclaw.config.glassesUiLive.shellEnabled = true.
135
- shellEnabled: parseBool(raw.shellEnabled, false),
136
139
  // http recipes issue agent-influenced outbound network requests on a
137
140
  // schedule. The recipe executor blocks loopback/RFC1918/link-local
138
141
  // destinations and resolves hostnames through an SSRF-safe dispatcher,
139
142
  // but the capability — "the plugin's gateway host can fetch arbitrary
140
143
  // public URLs the agent chooses" — is itself worth an operator opt-in.
141
- // Mirrors the shellEnabled default above; set
144
+ // Default to disabled; set
142
145
  // plugins.entries.ocuclaw.config.glassesUiLive.httpEnabled = true to
143
146
  // enable. The dispatcher protection still applies once enabled.
144
147
  httpEnabled: parseBool(raw.httpEnabled, false),
@@ -157,6 +157,24 @@ function createDebugStore(opts) {
157
157
  /** @type {Map<string, number>} */
158
158
  const enabledUntil = new Map();
159
159
 
160
+ // Rehydrate the arm from a persisted snapshot. relay-core reads debug-arm.json
161
+ // at construction and passes it here as options.initialEnabled, whose entries are
162
+ // the exact shape getSnapshot().enabled emits. That snapshot is already pruned of
163
+ // expired categories (getEnabledCategories -> pruneExpired), so the `> seedNow`
164
+ // re-check below is defensive belt-and-suspenders. Expired/unknown categories are
165
+ // SILENTLY skipped (do not log or warn on skip). Restoring the ORIGINAL absolute
166
+ // expiresAtMs makes a relay restart transparent without refreshing the TTL window.
167
+ if (Array.isArray(options.initialEnabled)) {
168
+ const seedNow = nowFn();
169
+ for (const entry of options.initialEnabled) {
170
+ if (!entry || typeof entry.cat !== "string") continue;
171
+ if (!categories.has(entry.cat)) continue;
172
+ const expiresAtMs = Number(entry.expiresAtMs);
173
+ if (!Number.isFinite(expiresAtMs) || expiresAtMs <= seedNow) continue;
174
+ enabledUntil.set(entry.cat, Math.floor(expiresAtMs));
175
+ }
176
+ }
177
+
160
178
  /** @type {Map<string, number>} */
161
179
  const noisyCounters = new Map();
162
180
 
@@ -0,0 +1,23 @@
1
+ import { PLUGIN_VERSION, REQUIRES_CLIENT_VERSION } from "../version.js";
2
+
3
+ /**
4
+ * Plugin version provider. Exposes the build-time version constants used by the
5
+ * relay handshake. No process execution.
6
+ */
7
+ function createPluginVersionService() {
8
+ function getPluginVersion() {
9
+ return typeof PLUGIN_VERSION === "string" && PLUGIN_VERSION.length > 0
10
+ ? PLUGIN_VERSION
11
+ : null;
12
+ }
13
+
14
+ function getRequiresClientVersion() {
15
+ return typeof REQUIRES_CLIENT_VERSION === "string" && REQUIRES_CLIENT_VERSION.length > 0
16
+ ? REQUIRES_CLIENT_VERSION
17
+ : null;
18
+ }
19
+
20
+ return { getPluginVersion, getRequiresClientVersion };
21
+ }
22
+
23
+ export { createPluginVersionService };
@@ -1,8 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import * as childProcess from "node:child_process";
4
3
  import { EventEmitter } from "node:events";
5
- import { createPluginUpdateService } from "./plugin-update-service.js";
4
+ import { createPluginVersionService } from "./plugin-version-service.js";
6
5
  import * as conversationStateModule from "../domain/conversation-state.js";
7
6
  import { createDebugStore } from "../domain/debug-store.js";
8
7
  import { summarizeGlassesUiContent } from "../domain/glasses-ui-content-summary.js";
@@ -22,7 +21,10 @@ import { createOcuClawSettingsStore } from "./ocuclaw-settings-store.js";
22
21
  import { createRelayHealthMonitor } from "./relay-health-monitor.js";
23
22
  import { createRelayOperationRegistry } from "./relay-operation-registry.js";
24
23
  import { createRelayWorkerSupervisor } from "./relay-worker-supervisor.js";
25
- import { createSessionService } from "./session-service.js";
24
+ import {
25
+ createSessionService,
26
+ NEW_SESSION_GREETING_PROMPT,
27
+ } from "./session-service.js";
26
28
  import { createUpstreamRuntime } from "./upstream-runtime.js";
27
29
 
28
30
  const SONIOX_TEMP_KEY_URL = "https://api.soniox.com/v1/auth/temporary-api-key";
@@ -373,6 +375,31 @@ function createRelay(opts) {
373
375
  // log lines share an identical ts (downstream reconcilers dedupe on it).
374
376
  const debugNow =
375
377
  typeof opts.debugNow === "function" ? opts.debugNow : () => Date.now();
378
+
379
+ // --- Durable debug-store arm (survives relay/gateway restarts) ---
380
+ // The capture arm (enabled categories + TTLs) lives only in the in-memory
381
+ // debug-store, which a restart rebuilds empty. We persist it to debug-arm.json
382
+ // (via persistDebugArm) and rehydrate it here at construction — read-once,
383
+ // mirroring liveUiTraceFlagPath below — so a restart no longer silently drops
384
+ // capture. This path covers process RESTART only: a pure WebUI *reload* does NOT
385
+ // restart the relay and already preserves + re-advertises the arm via
386
+ // relay-worker-transport.ts:327-328 (cache.debugConfig re-broadcast to app
387
+ // clients) — do not add reconnect machinery here.
388
+ const debugArmStatePath =
389
+ typeof opts.stateDir === "string" && opts.stateDir
390
+ ? path.join(opts.stateDir, "debug-arm.json")
391
+ : null;
392
+ let initialDebugArm = [];
393
+ if (debugArmStatePath) {
394
+ try {
395
+ const parsed = JSON.parse(fs.readFileSync(debugArmStatePath, "utf8"));
396
+ if (parsed && Array.isArray(parsed.enabled)) {
397
+ initialDebugArm = parsed.enabled;
398
+ }
399
+ } catch {
400
+ initialDebugArm = [];
401
+ }
402
+ }
376
403
  const debugStore = createDebugStore({
377
404
  categories: debugCategories,
378
405
  capacity: opts.debugCapacity,
@@ -383,6 +410,7 @@ function createRelay(opts) {
383
410
  dumpMaxLimit: opts.debugDumpMaxLimit,
384
411
  now: debugNow,
385
412
  noisyPolicies: opts.debugNoisyPolicies,
413
+ initialEnabled: initialDebugArm,
386
414
  });
387
415
 
388
416
  // --- Live-interface trace-log flag (durable across restarts) ---
@@ -1621,6 +1649,49 @@ function createRelay(opts) {
1621
1649
  return { ok: true, enabled, persisted, persistedPath: liveUiTraceFlagPath };
1622
1650
  }
1623
1651
 
1652
+ // Persist the current debug-store arm to debug-arm.json. Mirrors the
1653
+ // applyTraceLogSet writeFileSync above (plain, non-atomic): a partial/corrupt
1654
+ // write degrades to an empty arm on next boot — acceptable, the nothing-armed
1655
+ // warning catches it. getSnapshot().enabled is already pruned of expired
1656
+ // categories, so the persisted JSON never holds an expired entry. Never throws
1657
+ // into the caller.
1658
+ function persistDebugArm() {
1659
+ if (!debugArmStatePath) return false;
1660
+ try {
1661
+ const enabled = debugStore.getSnapshot().enabled;
1662
+ fs.writeFileSync(debugArmStatePath, JSON.stringify({ enabled }) + "\n");
1663
+ return true;
1664
+ } catch (err) {
1665
+ logger.warn(`[relay] debug arm persist failed: ${err && err.message ? err.message : err}`);
1666
+ return false;
1667
+ }
1668
+ }
1669
+
1670
+ function applyDebugSet(clientId, request) {
1671
+ const result = debugStore.setCategories(request);
1672
+ if (!result.ok) {
1673
+ throw new Error(result.error || "debug-set failed");
1674
+ }
1675
+ // Persist after every successful set — enable AND disable-to-empty — so the
1676
+ // on-disk arm always tracks live state and a deliberately-cleared arm is not
1677
+ // resurrected on the next restart.
1678
+ persistDebugArm();
1679
+ emitDebug(
1680
+ "relay.protocol",
1681
+ "debug_set",
1682
+ "info",
1683
+ { sessionKey: sessionService.ensureSessionKey() },
1684
+ () => ({
1685
+ clientId,
1686
+ enable: result.applied.enable,
1687
+ disable: result.applied.disable,
1688
+ ttlMs: result.ttlMs,
1689
+ enabledCount: result.enabled.length,
1690
+ }),
1691
+ );
1692
+ return result;
1693
+ }
1694
+
1624
1695
  const handler = createDownstreamHandler({
1625
1696
  logger,
1626
1697
  externalDebugToolsEnabled,
@@ -1898,6 +1969,12 @@ function createRelay(opts) {
1898
1969
  const pages = conversationState.getPages();
1899
1970
  cachePages(pages);
1900
1971
  if (upstreamRuntime && upstreamRuntime.isConnected()) {
1972
+ // NOTE: onNewChat targets the hard-coded "main" key (legacy) without
1973
+ // changing currentSessionKey, so it must NOT elicit a welcome turn here:
1974
+ // the turn's events would carry "main" and be dropped by isCurrentSession()
1975
+ // whenever the active session is an ocuclaw:* key. The welcome restore for
1976
+ // the real glasses paths lives in newSession() (New) and onSlashCommand
1977
+ // "/reset" (Reset). Unifying onNewChat onto newSession() is Phase-2 work.
1901
1978
  gatewayBridge.sendMessage("/new", "main").catch((err) => {
1902
1979
  logger.error(`[relay] Failed to send /new: ${err.message}`);
1903
1980
  });
@@ -2043,7 +2120,17 @@ function createRelay(opts) {
2043
2120
  broadcastPages();
2044
2121
  }
2045
2122
  if (upstreamRuntime && upstreamRuntime.isConnected()) {
2046
- return gatewayBridge.sendMessage(command, sessionService.ensureSessionKey());
2123
+ // Bare /reset no longer elicits an agent turn on OpenClaw 2026.6.x
2124
+ // (fast-reset). Append the greeting prompt so Reset gets the same
2125
+ // welcome as New. Other slash commands forward verbatim.
2126
+ const outboundCommand =
2127
+ command === "/reset"
2128
+ ? `/reset ${NEW_SESSION_GREETING_PROMPT}`
2129
+ : command;
2130
+ return gatewayBridge.sendMessage(
2131
+ outboundCommand,
2132
+ sessionService.ensureSessionKey(),
2133
+ );
2047
2134
  }
2048
2135
  return Promise.resolve();
2049
2136
  },
@@ -2102,26 +2189,7 @@ function createRelay(opts) {
2102
2189
  },
2103
2190
 
2104
2191
  onDebugSet(clientId, request) {
2105
- const result = debugStore.setCategories(request);
2106
- if (!result.ok) {
2107
- throw new Error(result.error || "debug-set failed");
2108
- }
2109
-
2110
- emitDebug(
2111
- "relay.protocol",
2112
- "debug_set",
2113
- "info",
2114
- { sessionKey: sessionService.ensureSessionKey() },
2115
- () => ({
2116
- clientId,
2117
- enable: result.applied.enable,
2118
- disable: result.applied.disable,
2119
- ttlMs: result.ttlMs,
2120
- enabledCount: result.enabled.length,
2121
- }),
2122
- );
2123
-
2124
- return result;
2192
+ return applyDebugSet(clientId, request);
2125
2193
  },
2126
2194
 
2127
2195
  onTraceLogSet(clientId, request) {
@@ -2496,18 +2564,12 @@ function createRelay(opts) {
2496
2564
 
2497
2565
  // --- Worker supervisor ---
2498
2566
 
2499
- const pluginUpdateService = createPluginUpdateService({
2500
- spawn: childProcess.spawn,
2501
- logger,
2502
- nowMs: () => Date.now(),
2503
- setTimeout: (fn, ms) => setTimeout(fn, ms),
2504
- clearTimeout: (handle) => clearTimeout(handle),
2505
- });
2567
+ const pluginVersionService = createPluginVersionService();
2506
2568
 
2507
2569
  server = createRelayWorkerSupervisor({
2508
2570
  pluginId: "ocuclaw",
2509
- getPluginVersion: () => pluginUpdateService.getPluginVersion(),
2510
- getRequiresClientVersion: () => pluginUpdateService.getRequiresClientVersion(),
2571
+ getPluginVersion: () => pluginVersionService.getPluginVersion(),
2572
+ getRequiresClientVersion: () => pluginVersionService.getRequiresClientVersion(),
2511
2573
  logger,
2512
2574
  handler,
2513
2575
  operationRegistry: relayOperationRegistry,
@@ -2515,8 +2577,6 @@ function createRelay(opts) {
2515
2577
  port: opts.port,
2516
2578
  token: opts.token,
2517
2579
  externalDebugToolsEnabled,
2518
- runPluginUpdate: () => pluginUpdateService.runPluginUpdate(),
2519
- runGatewayRestart: () => pluginUpdateService.runGatewayRestart(),
2520
2580
  evenAiRequestTimeoutMs: opts.evenAiRequestTimeoutMs,
2521
2581
  evenAiMaxBodyBytes: opts.evenAiMaxBodyBytes,
2522
2582
  evenAiMaxResponseBytes: opts.evenAiMaxResponseBytes,
@@ -3019,6 +3079,9 @@ function createRelay(opts) {
3019
3079
  __onTraceLogSetForTest(clientId, request) {
3020
3080
  return applyTraceLogSet(clientId, request);
3021
3081
  },
3082
+ __onDebugSetForTest(clientId, request) {
3083
+ return applyDebugSet(clientId, request);
3084
+ },
3022
3085
 
3023
3086
  get operationRegistryForTest() {
3024
3087
  return relayOperationRegistry;
@@ -16,10 +16,6 @@ export const APP_PROTOCOL = Object.freeze({
16
16
  readinessProbeAck: "ocuclaw.readiness.probe.ack",
17
17
  automationStateGet: "ocuclaw.automation.state.get",
18
18
  automationStateSnapshot: "ocuclaw.automation.state.snapshot",
19
- restartGateway: "ocuclaw.restartGateway",
20
- restartGatewayAck: "ocuclaw.restartGatewayAck",
21
- updatePlugin: "ocuclaw.updatePlugin",
22
- updatePluginResult: "ocuclaw.updatePluginResult",
23
19
  approvalRequest: "ocuclaw.approval.request",
24
20
  approvalResolved: "ocuclaw.approval.resolved",
25
21
  sessionContextSnapshot: "ocuclaw.session.context.snapshot",
@@ -92,33 +92,6 @@ function parseRequestIdFromRaw(raw) {
92
92
  return normalizeRequestId((parseFrame(raw) || {}).requestId);
93
93
  }
94
94
 
95
- function formatUpdatePluginResult(requestId, result) {
96
- const payload = { type: APP_PROTOCOL.updatePluginResult };
97
- if (requestId) payload.requestId = requestId;
98
- if (result && result.ok === true) {
99
- payload.ok = true;
100
- } else {
101
- payload.ok = false;
102
- if (result && typeof result.reason === "string") payload.reason = result.reason;
103
- if (result && typeof result.exitCode === "number") payload.exitCode = result.exitCode;
104
- if (result && typeof result.stderrTail === "string" && result.stderrTail.length > 0) {
105
- payload.stderrTail = result.stderrTail;
106
- }
107
- }
108
- return JSON.stringify(payload);
109
- }
110
-
111
- function formatRestartGatewayAck(requestId, result) {
112
- const payload = {
113
- type: APP_PROTOCOL.restartGatewayAck,
114
- ok: !!(result && result.ok),
115
- started: !!(result && result.started),
116
- };
117
- if (requestId) payload.requestId = requestId;
118
- if (result && typeof result.reason === "string") payload.reason = result.reason;
119
- return JSON.stringify(payload);
120
- }
121
-
122
95
  export function createRelayWorkerSupervisor(options = {}) {
123
96
  const logger = normalizeLogger(options.logger);
124
97
  const handler = options.handler || options.downstreamHandler || null;
@@ -547,56 +520,6 @@ export function createRelayWorkerSupervisor(options = {}) {
547
520
  return;
548
521
  }
549
522
  if (message.kind === "app.message") {
550
- if (message.operation === APP_PROTOCOL.updatePlugin) {
551
- if (typeof options.runPluginUpdate !== "function") {
552
- logger.warn("[relay-worker] updatePlugin requested but no runPluginUpdate is configured");
553
- return;
554
- }
555
- const requestId = parseRequestIdFromRaw(message.raw);
556
- (async () => {
557
- try {
558
- const result = await Promise.resolve(options.runPluginUpdate());
559
- postMainFrame("unicast", formatUpdatePluginResult(requestId, result), message.clientId);
560
- } catch (err) {
561
- logger.error(
562
- `[relay-worker] updatePlugin threw: ${err && err.message ? err.message : err}`,
563
- );
564
- postMainFrame(
565
- "unicast",
566
- formatUpdatePluginResult(requestId, { ok: false, reason: "spawn_failed" }),
567
- message.clientId,
568
- );
569
- }
570
- })();
571
- return;
572
- }
573
- if (message.operation === APP_PROTOCOL.restartGateway) {
574
- if (typeof options.runGatewayRestart !== "function") {
575
- logger.warn("[relay-worker] restartGateway requested but no runGatewayRestart is configured");
576
- return;
577
- }
578
- const requestId = parseRequestIdFromRaw(message.raw);
579
- (async () => {
580
- try {
581
- const result = await Promise.resolve(options.runGatewayRestart());
582
- postMainFrame("unicast", formatRestartGatewayAck(requestId, result), message.clientId);
583
- } catch (err) {
584
- logger.error(
585
- `[relay-worker] restartGateway threw: ${err && err.message ? err.message : err}`,
586
- );
587
- postMainFrame(
588
- "unicast",
589
- formatRestartGatewayAck(requestId, {
590
- ok: false,
591
- started: false,
592
- reason: "spawn_failed",
593
- }),
594
- message.clientId,
595
- );
596
- }
597
- })();
598
- return;
599
- }
600
523
  if (!handler || typeof handler.handleMessage !== "function") return;
601
524
  const processOptions = {};
602
525
  if (message.operation === "message.send" && message.requestId) {
@@ -7,6 +7,24 @@ const SESSION_TITLE_CACHE_FILE = "session-title-cache.json";
7
7
  const SESSION_PIN_CACHE_FILE = "ocuclaw-session-pins.json";
8
8
  const PIN_CAP_PER_KIND = 20;
9
9
 
10
+ /**
11
+ * Greeting-eliciting content appended to /new and /reset so OpenClaw runs an
12
+ * agent turn (the new-session "welcome"). OpenClaw 2026.6.x ("make bare reset
13
+ * commands fast", gateway commit 2c6a3f6b04) made a BARE /new or /reset return
14
+ * a synchronous ack and run NO agent turn — so a bare reset no longer produces
15
+ * a welcome and leaves the glasses stuck on the "starting new session"
16
+ * placeholder. Sending "/new <prompt>" / "/reset <prompt>" routes through the
17
+ * gateway's normal agent path (content = the prompt) and elicits the welcome.
18
+ * The wording matches the prompt OpenClaw used to inject itself, so the
19
+ * existing synthetic-session-starter filters (conversation-state display hide +
20
+ * isSyntheticSessionStarter title/preview filter) keep it hidden from the
21
+ * transcript and session titles. NOTE: newSession() does NOT call
22
+ * conversationState.addMessage("user", ...), so this content never renders as a
23
+ * user bubble on the glasses.
24
+ */
25
+ export const NEW_SESSION_GREETING_PROMPT =
26
+ "A new session was started via /new or /reset. Execute your Session Startup sequence now - read the required files before responding to the user. If BOOTSTRAP.md exists in the provided Project Context, read it and follow its instructions first. Then greet the user in your configured persona, if one is provided. Be yourself - use your defined voice, mannerisms, and mood. Keep it to 1-3 sentences and ask what they want to do. If the runtime model differs from default_model in the system prompt, mention the default model. Do not mention internal steps, files, tools, or reasoning.";
27
+
10
28
 
11
29
  function normalizeLogger(logger) {
12
30
  if (!logger || typeof logger !== "object") {
@@ -1575,9 +1593,11 @@ export function createSessionService(opts = {}) {
1575
1593
  onStatusChanged();
1576
1594
  }
1577
1595
  if (sendResetCommand && isUpstreamConnected()) {
1578
- gatewayBridge.sendMessage("/new", sessionKey).catch((err) => {
1579
- logger.error(`[relay] Failed to send /new for new session: ${err.message}`);
1580
- });
1596
+ gatewayBridge
1597
+ .sendMessage(`/new ${NEW_SESSION_GREETING_PROMPT}`, sessionKey)
1598
+ .catch((err) => {
1599
+ logger.error(`[relay] Failed to send /new for new session: ${err.message}`);
1600
+ });
1581
1601
  }
1582
1602
  return { sessionKey, pages };
1583
1603
  }