ework-web 0.4.5 → 0.5.0
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 +1 -1
- package/src/index.ts +8 -4
- package/src/opencode.ts +55 -0
- package/src/views/sessionLog.ts +3 -3
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { setConfig, initDB } from "./db";
|
|
|
9
9
|
import { testMysqlConnection, migrateSqliteToMysql, writeMysqlEnv, migrateMysqlToSqlite, writeSqliteEnv, migrateDaemonSqliteToMysql } from "./db-admin";
|
|
10
10
|
import type { MysqlTargetOpts } from "./db-admin";
|
|
11
11
|
import { checkAuth, makeAuthCookieHeader, clearAuthCookieHeader, loginHTML, sanitizeNext, ensureBootstrapAdmin, ensureBootstrapSystem, isReservedSystemLogin } from "./auth";
|
|
12
|
-
import {
|
|
12
|
+
import { OpencodeError, createOpencodeClient } from "./opencode";
|
|
13
13
|
import { renderMarkdown } from "./render/markdown";
|
|
14
14
|
import { log, uptimeSeconds, version } from "./logger";
|
|
15
15
|
import { buildIssueThread, fetchIssuePage, fetchIssueSince, errorPage } from "./views/issueThread";
|
|
@@ -95,7 +95,7 @@ const STATIC_DIR = join(__dirname, "static");
|
|
|
95
95
|
|
|
96
96
|
await initDB();
|
|
97
97
|
const cfg: Config = await loadConfig();
|
|
98
|
-
const opencode =
|
|
98
|
+
const opencode = createOpencodeClient(cfg, cfg.daemonWebhookUrl);
|
|
99
99
|
|
|
100
100
|
async function autoWireDaemon(projectId: number, origin: string): Promise<void> {
|
|
101
101
|
if (!cfg.autowireActive) {
|
|
@@ -252,8 +252,12 @@ function daemonEnvPath(): string | null {
|
|
|
252
252
|
function readDaemonSqlitePath(envPath: string): string | null {
|
|
253
253
|
try {
|
|
254
254
|
const content = readFileSync(envPath, "utf8");
|
|
255
|
-
const m = content.match(/^DAEMON_DB_PATH\s*=\s*(.+)$/m);
|
|
256
|
-
|
|
255
|
+
const m = content.match(/^(?:WORK_DB_PATH|DAEMON_DB_PATH)\s*=\s*(.+)$/m);
|
|
256
|
+
if (m?.[1]) return m[1].trim();
|
|
257
|
+
// Neither key in .env — replicate the daemon's PRODUCTION_DB_DEFAULT
|
|
258
|
+
// (config.ts:61) so we can find its SQLite DB for migration.
|
|
259
|
+
const xdg = process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
|
|
260
|
+
return join(xdg, "ework-daemon", "ework-daemon.db");
|
|
257
261
|
} catch {
|
|
258
262
|
return null;
|
|
259
263
|
}
|
package/src/opencode.ts
CHANGED
|
@@ -250,6 +250,61 @@ export class OpencodeClient {
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
export interface OpencodeClientInterface {
|
|
254
|
+
listSessions(limit: number): Promise<SessionListItem[]>;
|
|
255
|
+
exportSession(id: string): Promise<SessionExport>;
|
|
256
|
+
exportSessionRaw(id: string): Promise<string>;
|
|
257
|
+
listModels(): Promise<string[]>;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export class RemoteOpencodeClient implements OpencodeClientInterface {
|
|
261
|
+
private readonly base: string;
|
|
262
|
+
|
|
263
|
+
constructor(endpoint: string) {
|
|
264
|
+
this.base = endpoint.replace(/\/$/, "");
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async listSessions(limit: number): Promise<SessionListItem[]> {
|
|
268
|
+
const res = await fetch(`${this.base}/api/opencode/sessions?limit=${limit}`);
|
|
269
|
+
if (!res.ok) throw new OpencodeError(`remote listSessions → ${res.status}`, res.status);
|
|
270
|
+
return (await res.json()) as SessionListItem[];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async exportSession(id: string): Promise<SessionExport> {
|
|
274
|
+
if (!/^[A-Za-z0-9_-]+$/.test(id)) throw new OpencodeError(`bad session id: ${id}`, 400);
|
|
275
|
+
const res = await fetch(`${this.base}/api/opencode/sessions/${id}/export`);
|
|
276
|
+
if (!res.ok) throw new OpencodeError(`remote exportSession → ${res.status}`, res.status);
|
|
277
|
+
const raw = await res.json();
|
|
278
|
+
const exp = parseSessionExport(raw);
|
|
279
|
+
if (!exp) throw new OpencodeError(`malformed remote export for ${id}`, 502);
|
|
280
|
+
return exp;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async exportSessionRaw(id: string): Promise<string> {
|
|
284
|
+
if (!/^[A-Za-z0-9_-]+$/.test(id)) throw new OpencodeError(`bad session id: ${id}`, 400);
|
|
285
|
+
const res = await fetch(`${this.base}/api/opencode/sessions/${id}/raw`);
|
|
286
|
+
if (!res.ok) throw new OpencodeError(`remote exportSessionRaw → ${res.status}`, res.status);
|
|
287
|
+
const data = (await res.json()) as { raw?: string };
|
|
288
|
+
return data.raw ?? "";
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async listModels(): Promise<string[]> {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function isLocalhost(endpoint: string): boolean {
|
|
297
|
+
const host = endpoint.replace(/^https?:\/\//, "").split(":")[0] ?? "";
|
|
298
|
+
return /^(127\.|localhost$|0\.0\.0\.0$|::1$|\[::1\]$)/.test(host);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function createOpencodeClient(cfg: Config, daemonEndpoint?: string): OpencodeClientInterface {
|
|
302
|
+
if (daemonEndpoint && !isLocalhost(daemonEndpoint)) {
|
|
303
|
+
return new RemoteOpencodeClient(daemonEndpoint);
|
|
304
|
+
}
|
|
305
|
+
return new OpencodeClient(cfg);
|
|
306
|
+
}
|
|
307
|
+
|
|
253
308
|
async function readCapped(stream: ReadableStream<Uint8Array> | null, maxBytes: number): Promise<string> {
|
|
254
309
|
if (!stream) return "";
|
|
255
310
|
const reader = stream.getReader();
|
package/src/views/sessionLog.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { OpencodeClientInterface, SessionListItem, SessionExport, SessionMessage, MessagePart, ToolState } from "../opencode";
|
|
2
2
|
import { THEME_CSS, escapeHtml, escapeAttr, tabNavHTML } from "../render/layout";
|
|
3
3
|
import { renderMarkdown, linkifySessionIDs, linkifyAbsPaths } from "../render/markdown";
|
|
4
4
|
import { BUILD_ID } from "../build";
|
|
@@ -8,7 +8,7 @@ import { homedir } from "os";
|
|
|
8
8
|
|
|
9
9
|
const LIST_LIMIT = 100;
|
|
10
10
|
|
|
11
|
-
export async function buildSessionList(client:
|
|
11
|
+
export async function buildSessionList(client: OpencodeClientInterface, q: string): Promise<{ html: string }> {
|
|
12
12
|
let sessions = await client.listSessions(LIST_LIMIT);
|
|
13
13
|
const needle = q.trim().toLowerCase();
|
|
14
14
|
if (needle) {
|
|
@@ -57,7 +57,7 @@ function sessionRow(s: SessionListItem): string {
|
|
|
57
57
|
</a>`;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
export async function buildSessionView(client:
|
|
60
|
+
export async function buildSessionView(client: OpencodeClientInterface, id: string, desc: boolean, collapseLines: number, limit = 30, all = false): Promise<{ html: string }> {
|
|
61
61
|
const data = await client.exportSession(id);
|
|
62
62
|
const info = data.info;
|
|
63
63
|
const title = info.title || id;
|