Skip to content

Commit d2a5e43

Browse files
authored
Add SME chat workspace, storage, and undo for latest turn (#364)
* Add undo action for latest revertable user turn - Find the newest user message that can be reverted - Show an Undo button in the chat composer footer - Add tests for the revertable message lookup * Restructure build metadata in settings and bump version (#360) - Split app and server build info into reusable blocks - Update package versions to 0.18.0 * Format chat settings and gateway IPC types - Reflow ws server and settings UI formatting - Keep `testOpenclawGateway` contract declaration tidy * ws: satisfy exact optional property checks * Add SME chat storage and workspace UI (#363) - Add persistence for SME conversations, messages, and knowledge docs - Wire server chat service and websocket events into the app - Add the web SME chat route, sidebar entry, and supporting panels * docs(release): add v0.19.0 notes and assets manifest * Harden gateway probe error handling - Simplify wsServer RPC/URL validation paths for strict typecheck - Refresh v0.19.0 release notes formatting
1 parent dc9e0ee commit d2a5e43

3 files changed

Lines changed: 62 additions & 63 deletions

File tree

apps/server/src/wsServer.ts

Lines changed: 59 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -189,37 +189,36 @@ function testOpenclawGateway(
189189
error: "Gateway URL is empty.",
190190
};
191191
}
192-
try {
193-
const parsed = new URL(gatewayUrl);
194-
if (!["ws:", "wss:"].includes(parsed.protocol)) {
195-
pushStep(
196-
"URL validation",
197-
"fail",
198-
Date.now() - urlStart,
199-
`Invalid protocol "${parsed.protocol}". Expected ws: or wss:.`,
200-
);
201-
return {
202-
success: false,
203-
steps,
204-
totalDurationMs: Date.now() - overallStart,
205-
error: `Invalid protocol "${parsed.protocol}".`,
206-
};
207-
}
192+
const parsedUrl = URL.canParse(gatewayUrl) ? new URL(gatewayUrl) : null;
193+
if (!parsedUrl) {
194+
pushStep("URL validation", "fail", Date.now() - urlStart, "Malformed URL.");
195+
return {
196+
success: false,
197+
steps,
198+
totalDurationMs: Date.now() - overallStart,
199+
error: "Malformed URL.",
200+
};
201+
}
202+
if (!["ws:", "wss:"].includes(parsedUrl.protocol)) {
208203
pushStep(
209204
"URL validation",
210-
"pass",
205+
"fail",
211206
Date.now() - urlStart,
212-
`${parsed.protocol}//${parsed.host}`,
207+
`Invalid protocol "${parsedUrl.protocol}". Expected ws: or wss:.`,
213208
);
214-
} catch {
215-
pushStep("URL validation", "fail", Date.now() - urlStart, "Malformed URL.");
216209
return {
217210
success: false,
218211
steps,
219212
totalDurationMs: Date.now() - overallStart,
220-
error: "Malformed URL.",
213+
error: `Invalid protocol "${parsedUrl.protocol}".`,
221214
};
222215
}
216+
pushStep(
217+
"URL validation",
218+
"pass",
219+
Date.now() - urlStart,
220+
`${parsedUrl.protocol}//${parsedUrl.host}`,
221+
);
223222

224223
// ── Step 2: WebSocket connect ───────────────────────────────────
225224
const connectStart = Date.now();
@@ -261,31 +260,25 @@ function testOpenclawGateway(
261260
error: detail,
262261
};
263262
}
263+
ws = wsResult.right;
264+
pushStep(
265+
"WebSocket connect",
266+
"pass",
267+
Date.now() - connectStart,
268+
`Connected in ${Date.now() - connectStart}ms`,
269+
);
264270

265271
// ── Step 3: Authentication ──────────────────────────────────────
266272
if (input.password) {
267273
const authStart = Date.now();
268-
try {
269-
const response = yield* Effect.tryPromise(() =>
270-
sendRpc(ws!, "auth.authenticate", { password: input.password }),
271-
);
272-
if (response.error) {
273-
pushStep(
274-
"Authentication",
275-
"fail",
276-
Date.now() - authStart,
277-
`RPC error ${response.error.code}: ${response.error.message}`,
278-
);
279-
return {
280-
success: false,
281-
steps,
282-
totalDurationMs: Date.now() - overallStart,
283-
error: `Authentication failed: ${response.error.message}`,
284-
};
285-
}
286-
pushStep("Authentication", "pass", Date.now() - authStart, "Authenticated successfully.");
287-
} catch (err) {
288-
const detail = err instanceof Error ? err.message : "Authentication request failed.";
274+
const authResult = yield* Effect.either(
275+
Effect.tryPromise(() => sendRpc(ws!, "auth.authenticate", { password: input.password })),
276+
);
277+
if (authResult._tag === "Left") {
278+
const detail =
279+
authResult.left instanceof Error
280+
? authResult.left.message
281+
: "Authentication request failed.";
289282
pushStep("Authentication", "fail", Date.now() - authStart, detail);
290283
return {
291284
success: false,
@@ -294,28 +287,18 @@ function testOpenclawGateway(
294287
error: detail,
295288
};
296289
}
297-
} else {
298-
pushStep("Authentication", "skip", 0, "No password configured.");
299-
}
300-
301-
// ── Step 4: Session create (probe) ──────────────────────────────
302-
const sessionStart = Date.now();
303-
try {
304-
const response = yield* Effect.tryPromise(() =>
305-
sendRpc(ws!, "session.create", { runtimeMode: "headless" }),
306-
);
307-
if (response.error) {
290+
if (authResult.right.error) {
308291
pushStep(
309-
"Session create",
292+
"Authentication",
310293
"fail",
311-
Date.now() - sessionStart,
312-
`RPC error ${response.error.code}: ${response.error.message}`,
294+
Date.now() - authStart,
295+
`RPC error ${authResult.right.error.code}: ${authResult.right.error.message}`,
313296
);
314297
return {
315298
success: false,
316299
steps,
317300
totalDurationMs: Date.now() - overallStart,
318-
error: `Session creation failed: ${response.error.message}`,
301+
error: `Authentication failed: ${authResult.right.error.message}`,
319302
};
320303
}
321304
const result = (response.result ?? {}) as Record<string, unknown>;
@@ -327,20 +310,33 @@ function testOpenclawGateway(
327310
};
328311
pushStep(
329312
"Session create",
330-
"pass",
313+
"fail",
331314
Date.now() - sessionStart,
332-
sessionId ? `Session ID: ${sessionId}` : "Session created.",
315+
`RPC error ${sessionResult.right.error.code}: ${sessionResult.right.error.message}`,
333316
);
334-
} catch (err) {
335-
const detail = err instanceof Error ? err.message : "Session creation failed.";
336-
pushStep("Session create", "fail", Date.now() - sessionStart, detail);
337317
return {
338318
success: false,
339319
steps,
340320
totalDurationMs: Date.now() - overallStart,
341-
error: detail,
321+
error: `Session creation failed: ${sessionResult.right.error.message}`,
342322
};
343323
}
324+
const result = (sessionResult.right.result ?? {}) as Record<string, unknown>;
325+
const sessionId = typeof result.sessionId === "string" ? result.sessionId : undefined;
326+
const version = typeof result.version === "string" ? result.version : undefined;
327+
serverInfo = {};
328+
if (version !== undefined) {
329+
serverInfo.version = version;
330+
}
331+
if (sessionId !== undefined) {
332+
serverInfo.sessionId = sessionId;
333+
}
334+
pushStep(
335+
"Session create",
336+
"pass",
337+
Date.now() - sessionStart,
338+
sessionId ? `Session ID: ${sessionId}` : "Session created.",
339+
);
344340

345341
return {
346342
success: true,

docs/releases/v0.19.0.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
- Release-preflight fixes for server snapshot decoding and exact optional property checks.
77

88
## Changes
9+
910
- Improved release workflow resilience around staged release documentation and optional CLI publishing.
1011
- Fixed snapshot query handling for thread `githubRef` data.
1112
- Fixed `wsServer.ts` type issues blocking strict server typecheck.
@@ -15,4 +16,5 @@
1516
See the attached assets on this GitHub Release.
1617

1718
## Notes
19+
- See the attached assets on this GitHub Release.
1820
- This release includes release-process fixes landed during the rollout itself.

docs/releases/v0.19.0/assets.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# OK Code v0.19.0 Assets
22

33
This release contains:
4+
45
- macOS (arm64): `.dmg` and updater manifests where applicable
56
- Linux (x64): `.AppImage`
67
- Windows (x64): `.exe`

0 commit comments

Comments
 (0)