@castlemilk/omega 0.1.8 → 0.2.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/dist/cli.js CHANGED
@@ -10,14 +10,15 @@ import { Command } from "commander";
10
10
  import { program } from "commander";
11
11
  function getApiUrl() {
12
12
  const opts = program.opts();
13
- return opts.api || "http://localhost:4000";
13
+ return opts.api ?? "http://localhost:4000";
14
14
  }
15
15
  async function apiFetch(path3, init) {
16
16
  const url = `${getApiUrl()}${path3}`;
17
17
  const res = await fetch(url, init);
18
18
  const data = await res.json().catch(() => ({}));
19
19
  if (!res.ok) {
20
- throw new Error(data.error || `HTTP ${res.status}`);
20
+ const message = typeof data === "object" && data !== null && "error" in data && typeof data.error === "string" ? data.error : `HTTP ${res.status.toString()}`;
21
+ throw new Error(message);
21
22
  }
22
23
  return data;
23
24
  }
@@ -85,9 +86,9 @@ import React2 from "react";
85
86
 
86
87
  // ../../apps/cli/src/commands/tui.tsx
87
88
  import { useEffect, useMemo, useRef, useState } from "react";
88
- import { Box, Text, useApp, useInput } from "ink";
89
+ import { Box, Text, useApp, useInput, useWindowSize } from "ink";
89
90
  import { jsx, jsxs } from "react/jsx-runtime";
90
- var API = process.env.HARNESS_API_URL || "http://localhost:4000";
91
+ var API = process.env.HARNESS_API_URL ?? "http://localhost:4000";
91
92
  function formatTime(d) {
92
93
  return d.toLocaleTimeString("en-US", { hour12: false });
93
94
  }
@@ -123,7 +124,7 @@ function useTasks(pollMs = 1e3) {
123
124
  const addLog = (message, level = "info") => {
124
125
  setLogs((prev) => {
125
126
  const next = [...prev, { time: formatTime(/* @__PURE__ */ new Date()), message, level }];
126
- return next.slice(-50);
127
+ return next.slice(-200);
127
128
  });
128
129
  };
129
130
  useEffect(() => {
@@ -131,7 +132,7 @@ function useTasks(pollMs = 1e3) {
131
132
  const tick = async () => {
132
133
  try {
133
134
  const res = await fetch(`${API}/tasks`);
134
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
135
+ if (!res.ok) throw new Error(`HTTP ${res.status.toString()}`);
135
136
  const data = await res.json();
136
137
  setConnected(true);
137
138
  const current = {};
@@ -142,8 +143,8 @@ function useTasks(pollMs = 1e3) {
142
143
  addLog(`New task queued: "${task.title}"`, "info");
143
144
  } else if (prev.status !== task.status) {
144
145
  if (task.status === "in_progress") {
145
- const provider = task.provider || "unknown";
146
- const model = task.model || "unknown";
146
+ const provider = task.provider ?? "unknown";
147
+ const model = task.model ?? "unknown";
147
148
  addLog(`LLM picked up "${task.title}" \u2192 ${provider}/${model}`, "info");
148
149
  } else if (task.status === "done") {
149
150
  addLog(`Completed: "${task.title}"`, "success");
@@ -153,8 +154,9 @@ function useTasks(pollMs = 1e3) {
153
154
  }
154
155
  }
155
156
  for (const id of Object.keys(previousTasks.current)) {
156
- if (!current[id]) {
157
- addLog(`Task removed: "${previousTasks.current[id].title}"`, "warning");
157
+ const removed = previousTasks.current[id];
158
+ if (!current[id] && !id.startsWith("__") && removed) {
159
+ addLog(`Task removed: "${removed.title}"`, "warning");
158
160
  }
159
161
  }
160
162
  previousTasks.current = current;
@@ -162,24 +164,30 @@ function useTasks(pollMs = 1e3) {
162
164
  } catch (err) {
163
165
  setConnected(false);
164
166
  const message = err instanceof Error ? err.message : String(err);
165
- if (message !== previousTasks.current["__last_error"]?.title) {
167
+ if (message !== previousTasks.current.__last_error?.title) {
166
168
  addLog(`Connection lost: ${message}`, "error");
167
- previousTasks.current["__last_error"] = { title: message };
169
+ previousTasks.current.__last_error = { title: message };
168
170
  }
169
171
  }
170
172
  if (!cancelled) {
171
- setTimeout(tick, pollMs);
173
+ setTimeout(() => {
174
+ void tick();
175
+ }, pollMs);
172
176
  }
173
177
  };
174
- tick();
178
+ void tick();
175
179
  return () => {
176
180
  cancelled = true;
177
181
  };
178
182
  }, [pollMs]);
179
183
  return { tasks, logs, connected };
180
184
  }
185
+ function truncate(str, max) {
186
+ if (str.length <= max) return str;
187
+ return str.slice(0, max - 1) + "\u2026";
188
+ }
181
189
  function Header({ connected }) {
182
- return /* @__PURE__ */ jsxs(Box, { borderStyle: "single", paddingX: 1, children: [
190
+ return /* @__PURE__ */ jsxs(Box, { borderStyle: "single", paddingX: 1, height: 3, flexShrink: 0, children: [
183
191
  /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: "Omega Harness Console" }),
184
192
  /* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
185
193
  /* @__PURE__ */ jsx(Text, { color: connected ? "green" : "red", children: connected ? "\u25CF connected" : "\u25CF disconnected" }),
@@ -189,39 +197,6 @@ function Header({ connected }) {
189
197
  ] })
190
198
  ] });
191
199
  }
192
- function TaskList({ tasks }) {
193
- const sorted = useMemo(
194
- () => [...tasks].sort(
195
- (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
196
- ),
197
- [tasks]
198
- );
199
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", paddingX: 1, minHeight: 10, children: [
200
- /* @__PURE__ */ jsxs(Text, { bold: true, underline: true, children: [
201
- "Tasks (",
202
- tasks.length,
203
- ")"
204
- ] }),
205
- sorted.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No tasks yet. Create one in the web UI." }),
206
- sorted.map((task) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
207
- /* @__PURE__ */ jsx(Text, { color: statusColor(task.status), children: statusSymbol(task.status) }),
208
- /* @__PURE__ */ jsx(Box, { width: 20, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: formatTime(new Date(task.updatedAt)) }) }),
209
- /* @__PURE__ */ jsx(Box, { width: 12, children: /* @__PURE__ */ jsx(Text, { color: statusColor(task.status), bold: true, children: task.status.toUpperCase() }) }),
210
- /* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: task.provider ? `${task.provider}/${task.model}` : "-" }) }),
211
- /* @__PURE__ */ jsx(Box, { flexGrow: 1, children: /* @__PURE__ */ jsx(Text, { children: task.title }) })
212
- ] }, task.id))
213
- ] });
214
- }
215
- function ConsoleLog({ logs }) {
216
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", borderStyle: "single", paddingX: 1, flexGrow: 1, children: [
217
- /* @__PURE__ */ jsx(Text, { bold: true, underline: true, children: "Console" }),
218
- logs.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Waiting for activity..." }),
219
- logs.map((log, i) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
220
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: log.time }),
221
- /* @__PURE__ */ jsx(Text, { color: log.level, children: log.message })
222
- ] }, i))
223
- ] });
224
- }
225
200
  function Stats({ tasks }) {
226
201
  const counts = useMemo(() => {
227
202
  const total = tasks.length;
@@ -231,7 +206,7 @@ function Stats({ tasks }) {
231
206
  const failed = tasks.filter((t) => t.status === "failed").length;
232
207
  return { total, todo, inProgress, done, failed };
233
208
  }, [tasks]);
234
- return /* @__PURE__ */ jsxs(Box, { borderStyle: "single", paddingX: 1, gap: 2, children: [
209
+ return /* @__PURE__ */ jsxs(Box, { borderStyle: "single", paddingX: 1, height: 3, flexShrink: 0, gap: 2, children: [
235
210
  /* @__PURE__ */ jsxs(Text, { children: [
236
211
  "total: ",
237
212
  counts.total
@@ -254,27 +229,118 @@ function Stats({ tasks }) {
254
229
  ] })
255
230
  ] });
256
231
  }
232
+ function TaskList({
233
+ tasks,
234
+ height,
235
+ collapsed,
236
+ toggle: _toggle,
237
+ columns
238
+ }) {
239
+ const sorted = useMemo(
240
+ () => [...tasks].sort(
241
+ (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
242
+ ),
243
+ [tasks]
244
+ );
245
+ const header = /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
246
+ /* @__PURE__ */ jsxs(Text, { bold: true, underline: true, children: [
247
+ "Tasks (",
248
+ tasks.length,
249
+ ")"
250
+ ] }),
251
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
252
+ "[t] ",
253
+ collapsed ? "expand" : "collapse"
254
+ ] })
255
+ ] });
256
+ if (collapsed) {
257
+ return /* @__PURE__ */ jsx(Box, { borderStyle: "single", paddingX: 1, height: 3, flexShrink: 0, children: header });
258
+ }
259
+ const availableLines = Math.max(0, height - 3);
260
+ const visible = sorted.slice(0, availableLines);
261
+ return /* @__PURE__ */ jsxs(Box, { borderStyle: "single", paddingX: 1, flexDirection: "column", height, flexShrink: 0, children: [
262
+ header,
263
+ visible.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No tasks yet. Create one in the web UI." }),
264
+ visible.map((task) => {
265
+ const providerText = task.provider ? `${task.provider}/${task.model ?? ""}` : "-";
266
+ const titleMax = Math.max(10, columns - 50);
267
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
268
+ /* @__PURE__ */ jsx(Text, { color: statusColor(task.status), children: statusSymbol(task.status) }),
269
+ /* @__PURE__ */ jsx(Box, { width: 20, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: formatTime(new Date(task.updatedAt)) }) }),
270
+ /* @__PURE__ */ jsx(Box, { width: 12, children: /* @__PURE__ */ jsx(Text, { color: statusColor(task.status), bold: true, children: task.status.toUpperCase() }) }),
271
+ /* @__PURE__ */ jsx(Box, { width: 24, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: truncate(providerText, 23) }) }),
272
+ /* @__PURE__ */ jsx(Box, { flexGrow: 1, children: /* @__PURE__ */ jsx(Text, { children: truncate(task.title, titleMax) }) })
273
+ ] }, task.id);
274
+ })
275
+ ] });
276
+ }
277
+ function ConsoleLog({
278
+ logs,
279
+ height,
280
+ columns
281
+ }) {
282
+ const availableLines = Math.max(0, height - 3);
283
+ const visible = logs.slice(-availableLines);
284
+ return /* @__PURE__ */ jsxs(Box, { borderStyle: "single", paddingX: 1, flexDirection: "column", height, flexGrow: 1, children: [
285
+ /* @__PURE__ */ jsx(Text, { bold: true, underline: true, children: "Console" }),
286
+ visible.length === 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Waiting for activity..." }),
287
+ visible.map((log, i) => {
288
+ const timeWidth = 10;
289
+ const msgMax = Math.max(10, columns - timeWidth - 4);
290
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
291
+ /* @__PURE__ */ jsx(Box, { width: timeWidth, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: log.time }) }),
292
+ /* @__PURE__ */ jsx(Box, { flexGrow: 1, children: /* @__PURE__ */ jsx(Text, { color: log.level, children: truncate(log.message, msgMax) }) })
293
+ ] }, i);
294
+ })
295
+ ] });
296
+ }
257
297
  function TuiApp() {
258
298
  const { exit } = useApp();
259
299
  const { tasks, logs, connected } = useTasks();
300
+ const { columns, rows } = useWindowSize();
301
+ const [tasksCollapsed, setTasksCollapsed] = useState(false);
302
+ const [sidebar, setSidebar] = useState(false);
260
303
  useInput((input, key) => {
261
304
  if (input === "q" || key.escape) {
262
305
  exit();
263
306
  }
307
+ if (input === "t") {
308
+ setTasksCollapsed((c) => !c);
309
+ }
310
+ if (input === "s") {
311
+ setSidebar((s) => !s);
312
+ }
264
313
  });
265
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: "100%", gap: 1, padding: 1, children: [
314
+ const mainHeight = Math.max(6, rows - 9);
315
+ const taskListWidth = sidebar ? Math.max(30, Math.floor(columns * 0.4)) : void 0;
316
+ const taskListHeight = sidebar ? mainHeight : tasksCollapsed ? 3 : Math.max(3, Math.floor(mainHeight * 0.45));
317
+ const consoleHeight = sidebar ? mainHeight : mainHeight - taskListHeight;
318
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: rows, width: columns, gap: 1, padding: 1, children: [
266
319
  /* @__PURE__ */ jsx(Header, { connected }),
267
320
  /* @__PURE__ */ jsx(Stats, { tasks }),
268
- /* @__PURE__ */ jsx(TaskList, { tasks }),
269
- /* @__PURE__ */ jsx(ConsoleLog, { logs }),
270
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press q or Esc to quit" })
321
+ /* @__PURE__ */ jsxs(Box, { flexDirection: sidebar ? "row" : "column", gap: 1, flexGrow: 1, children: [
322
+ /* @__PURE__ */ jsx(
323
+ TaskList,
324
+ {
325
+ tasks,
326
+ height: taskListHeight,
327
+ collapsed: tasksCollapsed,
328
+ toggle: () => {
329
+ setTasksCollapsed((c) => !c);
330
+ },
331
+ columns: taskListWidth ?? columns
332
+ }
333
+ ),
334
+ /* @__PURE__ */ jsx(ConsoleLog, { logs, height: consoleHeight, columns })
335
+ ] }),
336
+ /* @__PURE__ */ jsx(Box, { height: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "q/esc quit \u2022 t toggle tasks \u2022 s toggle sidebar" }) })
271
337
  ] });
272
338
  }
273
339
 
274
340
  // ../../apps/cli/src/commands/ui.ts
275
341
  var __filename = fileURLToPath(import.meta.url);
276
342
  var __dirname = path.dirname(__filename);
277
- var API2 = process.env.HARNESS_API_URL || "http://localhost:4000";
343
+ var API2 = process.env.HARNESS_API_URL ?? "http://localhost:4000";
278
344
  function startBundledServer() {
279
345
  const serverPath = path.resolve(__dirname, "server.js");
280
346
  if (existsSync(serverPath)) {
@@ -326,7 +392,7 @@ async function ensureProject() {
326
392
  var uiCmd = new Command3("ui").description("Open the harness web UI").option("--no-tui", "Do not open the terminal console alongside the web UI").action(async (options) => {
327
393
  let server = void 0;
328
394
  const alreadyRunning = await isApiReady();
329
- const useTui = options.tui !== false;
395
+ const useTui = options.tui;
330
396
  if (!alreadyRunning) {
331
397
  server = startBundledServer() ?? spawn("pnpm", ["--filter", "@omega/server", "dev"], {
332
398
  stdio: "inherit",
@@ -364,7 +430,7 @@ var uiCmd = new Command3("ui").description("Open the harness web UI").option("--
364
430
  import { Command as Command4 } from "commander";
365
431
  import { render as render2 } from "ink";
366
432
  import React3 from "react";
367
- var consoleCmd = new Command4("console").description("Open the harness TUI console").action(async () => {
433
+ var consoleCmd = new Command4("console").description("Open the harness TUI console").action(() => {
368
434
  if (!process.stdin.isTTY) {
369
435
  console.error("The harness console requires an interactive terminal (TTY).");
370
436
  process.exit(1);
@@ -396,14 +462,14 @@ function parseSkill(md) {
396
462
  }
397
463
  args = frontmatter.args.map((entry, index) => {
398
464
  if (typeof entry !== "object" || entry === null) {
399
- throw new Error(`SKILL.md arg at index ${index} must be an object`);
465
+ throw new Error(`SKILL.md arg at index ${index.toString()} must be an object`);
400
466
  }
401
467
  const arg = entry;
402
468
  if (typeof arg.name !== "string" || arg.name.trim() === "") {
403
- throw new Error(`SKILL.md arg at index ${index} must have a non-empty "name"`);
469
+ throw new Error(`SKILL.md arg at index ${index.toString()} must have a non-empty "name"`);
404
470
  }
405
471
  if (typeof arg.type !== "string" || arg.type.trim() === "") {
406
- throw new Error(`SKILL.md arg at index ${index} must have a non-empty "type"`);
472
+ throw new Error(`SKILL.md arg at index ${index.toString()} must have a non-empty "type"`);
407
473
  }
408
474
  return {
409
475
  name: arg.name.trim(),
package/dist/server.js CHANGED
@@ -5,6 +5,7 @@ import express from "express";
5
5
  import cors from "cors";
6
6
  import path from "path";
7
7
  import { fileURLToPath } from "url";
8
+ import { z } from "zod";
8
9
 
9
10
  // ../providers/src/openai.ts
10
11
  var DEFAULT_BASE_URL = "https://api.openai.com/v1";
@@ -41,7 +42,7 @@ var OpenAIProvider = class {
41
42
  })
42
43
  });
43
44
  if (!res.ok) {
44
- throw new Error(`OpenAI request failed: ${res.status} ${res.statusText}`);
45
+ throw new Error(`OpenAI request failed: ${res.status.toString()} ${res.statusText}`);
45
46
  }
46
47
  const data = await res.json();
47
48
  return data.choices?.[0]?.message?.content ?? "";
@@ -86,7 +87,7 @@ var AnthropicProvider = class {
86
87
  body: JSON.stringify(body)
87
88
  });
88
89
  if (!res.ok) {
89
- throw new Error(`Anthropic request failed: ${res.status} ${res.statusText}`);
90
+ throw new Error(`Anthropic request failed: ${res.status.toString()} ${res.statusText}`);
90
91
  }
91
92
  const data = await res.json();
92
93
  return data.content?.find((c) => c.type === "text")?.text ?? "";
@@ -130,7 +131,7 @@ var OllamaProvider = class {
130
131
  })
131
132
  });
132
133
  if (!res.ok) {
133
- throw new Error(`Ollama request failed: ${res.status} ${res.statusText}`);
134
+ throw new Error(`Ollama request failed: ${res.status.toString()} ${res.statusText}`);
134
135
  }
135
136
  const data = await res.json();
136
137
  return data.message?.content ?? "";
@@ -174,7 +175,7 @@ var GeminiProvider = class {
174
175
  body: JSON.stringify(body)
175
176
  });
176
177
  if (!res.ok) {
177
- throw new Error(`Gemini request failed: ${res.status} ${res.statusText}`);
178
+ throw new Error(`Gemini request failed: ${res.status.toString()} ${res.statusText}`);
178
179
  }
179
180
  const data = await res.json();
180
181
  return data.candidates?.[0]?.content?.parts?.map((p) => p.text).join("") ?? "";
@@ -186,7 +187,7 @@ var KimiProvider = class extends OpenAIProvider {
186
187
  constructor(config) {
187
188
  super({
188
189
  ...config,
189
- baseUrl: config.baseUrl || "https://api.moonshot.ai/v1"
190
+ baseUrl: config.baseUrl ?? "https://api.moonshot.ai/v1"
190
191
  });
191
192
  this.config = config;
192
193
  }
@@ -212,7 +213,7 @@ function createProvider(config) {
212
213
  case "generic":
213
214
  return new GenericProvider(config);
214
215
  default:
215
- throw new Error(`Unknown provider kind: ${config.kind}`);
216
+ throw new Error(`Unknown provider kind: ${String(config.kind)}`);
216
217
  }
217
218
  }
218
219
 
@@ -255,9 +256,11 @@ function selectProvider(configs, rules2, task) {
255
256
  return true;
256
257
  }).sort((a, b) => b.priority - a.priority);
257
258
  for (const rule of matchingRules) {
258
- const provider = rule.then.provider ? enabled.find((cfg) => cfg.id === rule.then.provider || cfg.name === rule.then.provider) : enabled[0];
259
+ const provider = rule.then.provider ? enabled.find(
260
+ (cfg) => cfg.id === rule.then.provider || cfg.name === rule.then.provider
261
+ ) : enabled[0];
259
262
  if (provider) {
260
- return { provider, model: rule.then.model || provider.defaultModel };
263
+ return { provider, model: rule.then.model ?? provider.defaultModel };
261
264
  }
262
265
  }
263
266
  let best;
@@ -275,6 +278,11 @@ function selectProvider(configs, rules2, task) {
275
278
  // src/server.ts
276
279
  var __filename = fileURLToPath(import.meta.url);
277
280
  var __dirname = path.dirname(__filename);
281
+ function asyncHandler(fn) {
282
+ return (req, res, next) => {
283
+ Promise.resolve(fn(req, res, next)).catch(next);
284
+ };
285
+ }
278
286
  var app = express();
279
287
  app.use(cors());
280
288
  app.use(express.json());
@@ -305,11 +313,44 @@ if (process.env.KIMI_API_KEY) {
305
313
  }
306
314
  var rules = [];
307
315
  function newId() {
308
- return `${Date.now()}-${Math.random().toString(36).slice(2)}`;
316
+ return `${Date.now().toString()}-${Math.random().toString(36).slice(2)}`;
309
317
  }
310
318
  function now() {
311
319
  return /* @__PURE__ */ new Date();
312
320
  }
321
+ function parseCapabilities(value) {
322
+ if (Array.isArray(value)) return value;
323
+ if (typeof value === "string") return JSON.parse(value);
324
+ return [];
325
+ }
326
+ var projectCreateSchema = z.object({
327
+ name: z.string().min(1),
328
+ path: z.string().min(1),
329
+ repoUrl: z.string().optional(),
330
+ description: z.string().optional(),
331
+ env: z.record(z.string()).optional()
332
+ });
333
+ var taskCreateSchema = z.object({
334
+ projectId: z.string().min(1),
335
+ title: z.string().min(1),
336
+ description: z.string().optional(),
337
+ complexity: z.enum(["simple", "medium", "complex"]).default("simple"),
338
+ tags: z.array(z.string()).default([])
339
+ });
340
+ var providerCreateSchema = z.object({
341
+ name: z.string().min(1),
342
+ kind: z.enum(["openai", "anthropic", "ollama", "gemini", "kimi", "generic"]),
343
+ baseUrl: z.string().optional(),
344
+ apiKey: z.string().optional(),
345
+ defaultModel: z.string().min(1),
346
+ capabilities: z.union([z.string(), z.array(z.any())]).optional(),
347
+ enabled: z.boolean().optional()
348
+ });
349
+ var routerSelectSchema = z.object({
350
+ title: z.string().min(1),
351
+ complexity: z.enum(["simple", "medium", "complex"]).default("simple"),
352
+ tags: z.array(z.string()).default([])
353
+ });
313
354
  app.get("/projects", (_req, res) => {
314
355
  res.json(
315
356
  projects.map((p) => ({
@@ -319,14 +360,10 @@ app.get("/projects", (_req, res) => {
319
360
  );
320
361
  });
321
362
  app.post("/projects", (req, res) => {
322
- const body = req.body;
363
+ const body = projectCreateSchema.parse(req.body);
323
364
  const project = {
324
365
  id: newId(),
325
- name: body.name,
326
- path: body.path,
327
- repoUrl: body.repoUrl,
328
- description: body.description,
329
- env: body.env,
366
+ ...body,
330
367
  createdAt: now()
331
368
  };
332
369
  projects.push(project);
@@ -338,19 +375,15 @@ app.delete("/projects/:id", (req, res) => {
338
375
  res.status(204).send();
339
376
  });
340
377
  app.get("/tasks", (req, res) => {
341
- const projectId = req.query.projectId;
378
+ const projectId = typeof req.query.projectId === "string" ? req.query.projectId : void 0;
342
379
  res.json(tasks.filter((t) => projectId ? t.projectId === projectId : true));
343
380
  });
344
381
  app.post("/tasks", (req, res) => {
345
- const body = req.body;
382
+ const body = taskCreateSchema.parse(req.body);
346
383
  const task = {
347
384
  id: newId(),
348
- projectId: body.projectId,
349
- title: body.title,
350
- description: body.description,
385
+ ...body,
351
386
  status: "todo",
352
- complexity: body.complexity || "simple",
353
- tags: body.tags || [],
354
387
  createdAt: now(),
355
388
  updatedAt: now()
356
389
  };
@@ -363,9 +396,12 @@ app.patch("/tasks/:id", (req, res) => {
363
396
  Object.assign(task, req.body, { updatedAt: now() });
364
397
  res.json(task);
365
398
  });
366
- app.post("/tasks/:id/run", async (req, res) => {
399
+ app.post("/tasks/:id/run", asyncHandler(async (req, res) => {
367
400
  const task = tasks.find((t) => t.id === req.params.id);
368
- if (!task) return res.status(404).json({ error: "Task not found" });
401
+ if (!task) {
402
+ res.status(404).json({ error: "Task not found" });
403
+ return;
404
+ }
369
405
  task.status = "in_progress";
370
406
  task.error = void 0;
371
407
  task.result = void 0;
@@ -373,7 +409,8 @@ app.post("/tasks/:id/run", async (req, res) => {
373
409
  if (!selection) {
374
410
  task.status = "failed";
375
411
  task.error = "No provider available for this task";
376
- return res.json(task);
412
+ res.json(task);
413
+ return;
377
414
  }
378
415
  try {
379
416
  const provider = createProvider(selection.provider);
@@ -387,20 +424,16 @@ app.post("/tasks/:id/run", async (req, res) => {
387
424
  task.error = err instanceof Error ? err.message : String(err);
388
425
  }
389
426
  res.json(task);
390
- });
427
+ }));
391
428
  app.get("/providers", (_req, res) => {
392
429
  res.json(providerConfigs);
393
430
  });
394
431
  app.post("/providers", (req, res) => {
395
- const body = req.body;
432
+ const body = providerCreateSchema.parse(req.body);
396
433
  const cfg = {
397
434
  id: newId(),
398
- name: body.name,
399
- kind: body.kind,
400
- baseUrl: body.baseUrl,
401
- apiKey: body.apiKey,
402
- defaultModel: body.defaultModel,
403
- capabilities: Array.isArray(body.capabilities) ? body.capabilities : JSON.parse(body.capabilities || "[]"),
435
+ ...body,
436
+ capabilities: parseCapabilities(body.capabilities),
404
437
  enabled: body.enabled ?? true
405
438
  };
406
439
  providerConfigs.push(cfg);
@@ -417,14 +450,12 @@ app.delete("/providers/:id", (req, res) => {
417
450
  res.status(204).send();
418
451
  });
419
452
  app.post("/router/select", (req, res) => {
420
- const body = req.body;
453
+ const body = routerSelectSchema.parse(req.body);
421
454
  const task = {
422
455
  id: "preview",
423
456
  projectId: "preview",
424
- title: body.title,
457
+ ...body,
425
458
  status: "todo",
426
- complexity: body.complexity || "simple",
427
- tags: body.tags || [],
428
459
  createdAt: now(),
429
460
  updatedAt: now()
430
461
  };
@@ -437,7 +468,7 @@ app.use(express.static(webDir));
437
468
  app.get("*", (_req, res) => {
438
469
  res.sendFile(path.join(webDir, "index.html"));
439
470
  });
440
- var PORT = process.env.PORT || 4e3;
471
+ var PORT = Number(process.env.PORT ?? 4e3);
441
472
  app.listen(PORT, () => {
442
- console.log(`Omega harness server on http://localhost:${PORT}`);
473
+ console.log(`Omega harness server on http://localhost:${PORT.toString()}`);
443
474
  });
@@ -1 +1 @@
1
- {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":"AAOA,iBAAS,GAAG,gCAoDX;AAED,eAAe,GAAG,CAAC"}
1
+ {"version":3,"file":"App.d.ts","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":"AAOA,iBAAS,GAAG,gCAsDX;AAED,eAAe,GAAG,CAAC"}
@@ -30,10 +30,12 @@ function App() {
30
30
  }
31
31
  }
32
32
  useEffect(() => {
33
- loadProjects();
34
- loadProviders();
33
+ void (async () => {
34
+ await loadProjects();
35
+ await loadProviders();
36
+ })();
35
37
  }, []);
36
- return (_jsxs("div", { className: "flex h-screen w-screen overflow-hidden", children: [_jsx(ProjectSidebar, { projects: projects, selectedId: selectedProjectId, onSelect: setSelectedProjectId, onChange: loadProjects }), _jsx(TaskBoard, { projectId: selectedProjectId }), _jsxs("aside", { className: "w-80 bg-white border-l border-gray-200 h-screen overflow-y-auto", children: [error && (_jsx("div", { className: "p-3 bg-red-50 text-red-700 text-xs border-b border-red-100", children: error })), _jsx(ProviderSettings, { providers: providers, onChange: loadProviders }), _jsx(RouterPanel, {})] })] }));
38
+ return (_jsxs("div", { className: "flex h-screen w-screen overflow-hidden", children: [_jsx(ProjectSidebar, { projects: projects, selectedId: selectedProjectId, onSelect: setSelectedProjectId, onChange: () => { void loadProjects(); } }), _jsx(TaskBoard, { projectId: selectedProjectId }), _jsxs("aside", { className: "w-80 bg-white border-l border-gray-200 h-screen overflow-y-auto", children: [error && (_jsx("div", { className: "p-3 bg-red-50 text-red-700 text-xs border-b border-red-100", children: error })), _jsx(ProviderSettings, { providers: providers, onChange: () => { void loadProviders(); } }), _jsx(RouterPanel, {})] })] }));
37
39
  }
38
40
  export default App;
39
41
  //# sourceMappingURL=App.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"App.js","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,cAAc,EAAgB,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAiB,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE1D,SAAS,GAAG;IACV,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAY,EAAE,CAAC,CAAC;IACxD,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAa,EAAE,CAAC,CAAC;IAC3D,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,QAAQ,EAAU,CAAC;IACrE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEvC,KAAK,UAAU,YAAY;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;YACrC,WAAW,CAAC,IAAI,CAAC,CAAC;YAClB,QAAQ,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,KAAK,UAAU,aAAa;QAC1B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,YAAY,EAAE,CAAC;YACtC,YAAY,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,SAAS,CAAC,GAAG,EAAE;QACb,YAAY,EAAE,CAAC;QACf,aAAa,EAAE,CAAC;IAClB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CACL,eAAK,SAAS,EAAC,wCAAwC,aACrD,KAAC,cAAc,IACb,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,iBAAiB,EAC7B,QAAQ,EAAE,oBAAoB,EAC9B,QAAQ,EAAE,YAAY,GACtB,EAEF,KAAC,SAAS,IAAC,SAAS,EAAE,iBAAiB,GAAI,EAE3C,iBAAO,SAAS,EAAC,iEAAiE,aAC/E,KAAK,IAAI,CACR,cAAK,SAAS,EAAC,4DAA4D,YACxE,KAAK,GACF,CACP,EACD,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,GAAI,EACnE,KAAC,WAAW,KAAG,IACT,IACJ,CACP,CAAC;AACJ,CAAC;AAED,eAAe,GAAG,CAAC"}
1
+ {"version":3,"file":"App.js","sourceRoot":"","sources":["../src/App.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,cAAc,EAAgB,MAAM,gCAAgC,CAAC;AAC9E,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAiB,MAAM,kCAAkC,CAAC;AACnF,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE1D,SAAS,GAAG;IACV,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAY,EAAE,CAAC,CAAC;IACxD,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAa,EAAE,CAAC,CAAC;IAC3D,MAAM,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,GAAG,QAAQ,EAAU,CAAC;IACrE,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEvC,KAAK,UAAU,YAAY;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;YACrC,WAAW,CAAC,IAAI,CAAC,CAAC;YAClB,QAAQ,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,KAAK,UAAU,aAAa;QAC1B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,YAAY,EAAE,CAAC;YACtC,YAAY,CAAC,IAAI,CAAC,CAAC;QACrB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,SAAS,CAAC,GAAG,EAAE;QACb,KAAK,CAAC,KAAK,IAAI,EAAE;YACf,MAAM,YAAY,EAAE,CAAC;YACrB,MAAM,aAAa,EAAE,CAAC;QACxB,CAAC,CAAC,EAAE,CAAC;IACP,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CACL,eAAK,SAAS,EAAC,wCAAwC,aACrD,KAAC,cAAc,IACb,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,iBAAiB,EAC7B,QAAQ,EAAE,oBAAoB,EAC9B,QAAQ,EAAE,GAAG,EAAE,GAAG,KAAK,YAAY,EAAE,CAAC,CAAC,CAAC,GACxC,EAEF,KAAC,SAAS,IAAC,SAAS,EAAE,iBAAiB,GAAI,EAE3C,iBAAO,SAAS,EAAC,iEAAiE,aAC/E,KAAK,IAAI,CACR,cAAK,SAAS,EAAC,4DAA4D,YACxE,KAAK,GACF,CACP,EACD,KAAC,gBAAgB,IAAC,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,KAAK,aAAa,EAAE,CAAC,CAAC,CAAC,GAAI,EACrF,KAAC,WAAW,KAAG,IACT,IACJ,CACP,CAAC;AACJ,CAAC;AAED,eAAe,GAAG,CAAC"}