Skip to content

Commit 1ab6043

Browse files
committed
fix: unblock web build and enforce web-ui CI checks
1 parent 590b119 commit 1ab6043

File tree

11 files changed

+127
-33
lines changed

11 files changed

+127
-33
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,6 @@ jobs:
3131

3232
- name: Agent fast tests
3333
run: bun test packages/agents/test/cli-command.test.ts packages/agents/test/agent-registry.test.ts
34+
35+
- name: Build web UI
36+
run: bun run build:web

.github/workflows/release.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ jobs:
3535
- name: Install dependencies
3636
run: bun install
3737

38+
- name: Install web-ui deps
39+
run: bun install --cwd packages/web-ui
40+
3841
- name: Run core tests
3942
run: bun test packages/core/test
4043

packages/config/local/ode.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
2-
import { homedir } from "os";
3-
import { join } from "path";
1+
import * as fs from "fs";
2+
import * as os from "os";
3+
import * as path from "path";
44
import { z } from "zod";
55
import { normalizeCwd } from "../paths";
66

7+
const existsSync = fs.existsSync;
8+
const mkdirSync = fs.mkdirSync;
9+
const readFileSync = fs.readFileSync;
10+
const writeFileSync = fs.writeFileSync;
11+
const join = typeof path.join === "function" ? path.join : (...parts: string[]) => parts.join("/");
12+
const homedir = typeof os.homedir === "function" ? os.homedir : () => "";
13+
714
const XDG_CONFIG_HOME = join(homedir(), ".config");
815
const ODE_CONFIG_DIR = join(XDG_CONFIG_HOME, "ode");
916
export const ODE_CONFIG_FILE = join(ODE_CONFIG_DIR, "ode.json");

packages/config/local/sessions.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1-
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from "fs";
2-
import { join } from "path";
3-
import { homedir } from "os";
1+
import * as fs from "fs";
2+
import * as path from "path";
3+
import * as os from "os";
44
import { log } from "@/utils";
55

6+
const readFileSync = fs.readFileSync;
7+
const writeFileSync = fs.writeFileSync;
8+
const existsSync = fs.existsSync;
9+
const mkdirSync = fs.mkdirSync;
10+
const readdirSync = fs.readdirSync;
11+
const unlinkSync = fs.unlinkSync;
12+
const join = typeof path.join === "function" ? path.join : (...parts: string[]) => parts.join("/");
13+
const homedir = typeof os.homedir === "function" ? os.homedir : () => "";
14+
615
const ODE_CONFIG_DIR = join(homedir(), ".config", "ode");
716
const SESSIONS_DIR = join(ODE_CONFIG_DIR, "sessions");
817

packages/config/local/settings.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
2-
import { join } from "path";
3-
import { homedir } from "os";
1+
import * as fs from "fs";
2+
import * as path from "path";
3+
import * as os from "os";
44
import {
55
setChannelCwd as setChannelCwdInConfig,
66
} from "./ode";
77
import { loadSession } from "./sessions";
88

9+
const readFileSync = fs.readFileSync;
10+
const writeFileSync = fs.writeFileSync;
11+
const existsSync = fs.existsSync;
12+
const mkdirSync = fs.mkdirSync;
13+
const join = typeof path.join === "function" ? path.join : (...parts: string[]) => parts.join("/");
14+
const homedir = typeof os.homedir === "function" ? os.homedir : () => "";
15+
916
const ODE_CONFIG_DIR = join(homedir(), ".config", "ode");
1017
const SETTINGS_FILE = join(ODE_CONFIG_DIR, "settings.json");
1118
const AGENTS_DIR = join(ODE_CONFIG_DIR, "agents");

packages/config/paths.ts

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { join, resolve } from "path";
2-
31
const isNodeRuntime =
42
typeof process !== "undefined" &&
53
typeof process.versions === "object" &&
@@ -11,11 +9,53 @@ const homeDir = isNodeRuntime
119

1210
const cwd = isNodeRuntime && typeof process.cwd === "function" ? process.cwd() : "/";
1311

12+
const WINDOWS_DRIVE_PATH = /^[A-Za-z]:[\\/]/;
13+
14+
function isAbsolutePath(input: string): boolean {
15+
return input.startsWith("/") || input.startsWith("\\") || WINDOWS_DRIVE_PATH.test(input);
16+
}
17+
18+
function normalizePath(input: string): string {
19+
if (!input) return "/";
20+
21+
const normalized = input.replace(/\\/g, "/");
22+
const hasDrivePrefix = WINDOWS_DRIVE_PATH.test(normalized);
23+
const drivePrefix = hasDrivePrefix ? normalized.slice(0, 2) : "";
24+
const withoutDrive = hasDrivePrefix ? normalized.slice(2) : normalized;
25+
const isAbsolute = withoutDrive.startsWith("/");
26+
const segments = withoutDrive.split("/");
27+
const stack: string[] = [];
28+
29+
for (const segment of segments) {
30+
if (!segment || segment === ".") continue;
31+
if (segment === "..") {
32+
if (stack.length > 0) {
33+
stack.pop();
34+
}
35+
continue;
36+
}
37+
stack.push(segment);
38+
}
39+
40+
const body = stack.join("/");
41+
const prefix = `${drivePrefix}${isAbsolute ? "/" : ""}`;
42+
const output = `${prefix}${body}`;
43+
44+
if (output) return output;
45+
if (drivePrefix) return `${drivePrefix}/`;
46+
return isAbsolute ? "/" : ".";
47+
}
48+
49+
function resolvePath(basePath: string, input: string): string {
50+
if (isAbsolutePath(input)) return normalizePath(input);
51+
return normalizePath(`${basePath.replace(/\\/g, "/")}/${input}`);
52+
}
53+
1454
export function normalizeCwd(input: string): string {
1555
if (!input) return cwd;
1656
if (input === "~") return homeDir || cwd;
1757
if (input.startsWith("~/")) {
18-
return resolve(join(homeDir || cwd, input.slice(2)));
58+
return resolvePath(homeDir || cwd, input.slice(2));
1959
}
20-
return resolve(input);
60+
return resolvePath(cwd, input);
2161
}

packages/core/runtime/event-stream.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
type TrackedTodo,
77
type TrackedTool,
88
} from "@/config/local/sessions";
9+
import { resolveMessageFrequency } from "@/config/message-frequency";
910
import { storeSessionEvent } from "@/config/local/redis";
1011
import { CoreStateMachine } from "@/core/state-machine";
1112
import type { AgentAdapter, IMAdapter } from "@/core/types";
@@ -163,7 +164,12 @@ export async function startEventStreamWatcher(
163164
await deps.im.updateMessage(
164165
request.channelId,
165166
request.statusMessageTs,
166-
buildLiveStatusMessage(request, workingPath, liveParsedState.get(messageKey)),
167+
buildLiveStatusMessage(
168+
request,
169+
workingPath,
170+
liveParsedState.get(messageKey),
171+
resolveMessageFrequency()
172+
),
167173
false
168174
);
169175
setPendingQuestion(request.channelId, request.threadId, {

packages/core/runtime/open-request.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { storeSessionMeta } from "@/config/local/redis";
2+
import { resolveMessageFrequency } from "@/config/message-frequency";
23
import {
34
completeActiveRequest,
45
createActiveRequest,
@@ -114,7 +115,8 @@ export async function runOpenRequest(params: {
114115
const statusText = buildLiveStatusMessage(
115116
request,
116117
cwd,
117-
liveParsedState.get(getStatusMessageKey(request))
118+
liveParsedState.get(getStatusMessageKey(request)),
119+
resolveMessageFrequency()
118120
);
119121
if (!request.statusFrozen) {
120122
await deps.im.updateMessage(context.channelId, statusTs, statusText, false);

packages/core/runtime/selection-reply.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
markMessageProcessed,
88
saveSession,
99
} from "@/config/local/sessions";
10+
import { resolveMessageFrequency } from "@/config/message-frequency";
1011
import { runTrackedRequest } from "@/core/runtime/request-runner";
1112
import { CoreStateMachine } from "@/core/state-machine";
1213
import type { AgentAdapter, IMAdapter } from "@/core/types";
@@ -115,7 +116,8 @@ export async function handleSelectionReply(params: HandleSelectionReplyParams):
115116
const statusText = buildLiveStatusMessage(
116117
request,
117118
cwd,
118-
state.liveParsedState.get(getStatusMessageKey(request))
119+
state.liveParsedState.get(getStatusMessageKey(request)),
120+
resolveMessageFrequency()
119121
);
120122
await deps.im.updateMessage(channelId, statusTs, statusText, false);
121123
},

packages/utils/status.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
import {
2-
resolveMessageFrequency,
32
TOOL_DISPLAY_CONFIG,
43
type MessageFrequency,
5-
} from "@/config";
6-
import type { ActiveRequest, TrackedTodo } from "@/config/local/sessions";
4+
} from "@/config/web";
75
import type { SessionMessageState } from "./session-inspector";
86

7+
export type StatusRequest = {
8+
channelId: string;
9+
threadId: string;
10+
statusMessageTs: string;
11+
startedAt: number;
12+
currentText: string;
13+
statusFrozen?: boolean;
14+
};
15+
16+
type StatusTodo = {
17+
content: string;
18+
status: string;
19+
};
20+
921
const PLAN_TODO_LIMIT = 15;
1022

1123
export function formatElapsedTime(startedAt: number): string {
@@ -40,7 +52,7 @@ export function getTodoIcon(status: string): string {
4052
}
4153
}
4254

43-
export function getStatusMessageKey(request: ActiveRequest): string {
55+
export function getStatusMessageKey(request: StatusRequest): string {
4456
return `${request.channelId}:${request.threadId}:${request.statusMessageTs}`;
4557
}
4658

@@ -74,7 +86,7 @@ export function trimToolPath(label: string, workingPath: string): string {
7486
return trimmed;
7587
}
7688

77-
function formatTodoLines(todos: TrackedTodo[], limit = PLAN_TODO_LIMIT): string[] {
89+
function formatTodoLines(todos: StatusTodo[], limit = PLAN_TODO_LIMIT): string[] {
7890
const lines: string[] = [];
7991
for (const todo of todos.slice(0, limit)) {
8092
const statusLabel = todo.status === "in_progress"
@@ -165,9 +177,10 @@ export function buildToolLines(
165177
}
166178

167179
export function buildLiveStatusMessage(
168-
request: ActiveRequest,
180+
request: StatusRequest,
169181
workingPath: string,
170-
state?: SessionMessageState
182+
state?: SessionMessageState,
183+
frequency: MessageFrequency = "medium"
171184
): string {
172185
if (!state) {
173186
if (request.statusFrozen && request.currentText) {
@@ -194,12 +207,12 @@ export function buildLiveStatusMessage(
194207
if (state.todos.length > 0) {
195208
const todos = state.todos.map((todo) => ({
196209
content: todo.content,
197-
status: todo.status as TrackedTodo["status"],
210+
status: todo.status,
198211
}));
199212
lines.push("", "*Tasks*", ...formatTodoLines(todos));
200213
}
201214

202-
const toolLines = buildToolLines(state, workingPath, resolveMessageFrequency());
215+
const toolLines = buildToolLines(state, workingPath, frequency);
203216
if (toolLines.length > 0) {
204217
lines.push("");
205218
lines.push(...toolLines);

0 commit comments

Comments
 (0)