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 +3 -1
- package/dist/config/runtime-config.js +17 -14
- package/dist/domain/debug-store.js +18 -0
- package/dist/runtime/plugin-version-service.js +23 -0
- package/dist/runtime/relay-core.js +98 -35
- package/dist/runtime/relay-worker-protocol.js +0 -4
- package/dist/runtime/relay-worker-supervisor.js +0 -77
- package/dist/runtime/session-service.js +23 -3
- package/dist/tools/glasses-ui-recipes.js +13 -178
- package/dist/tools/glasses-ui-tool.js +10 -46
- package/dist/version.js +2 -2
- package/package.json +4 -3
- package/skills/glasses-ui/SKILL.md +156 -0
- package/dist/runtime/downstream-server.js +0 -2057
- package/dist/runtime/plugin-update-service.js +0 -216
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
|
-
|
|
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
|
|
101
|
-
// Codex
|
|
102
|
-
// could exfil ~/.aws/credentials,
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
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
|
-
: "
|
|
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
|
-
//
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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: () =>
|
|
2510
|
-
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
|
|
1579
|
-
|
|
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
|
}
|