@castlemilk/omega 0.2.0 → 0.4.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
@@ -12,8 +12,8 @@ function getApiUrl() {
12
12
  const opts = program.opts();
13
13
  return opts.api ?? "http://localhost:4000";
14
14
  }
15
- async function apiFetch(path3, init) {
16
- const url = `${getApiUrl()}${path3}`;
15
+ async function apiFetch(path4, init) {
16
+ const url = `${getApiUrl()}${path4}`;
17
17
  const res = await fetch(url, init);
18
18
  const data = await res.json().catch(() => ({}));
19
19
  if (!res.ok) {
@@ -48,8 +48,85 @@ projectCmd.command("remove").description("Remove a project").argument("<id>", "p
48
48
  });
49
49
 
50
50
  // ../../apps/cli/src/commands/task.ts
51
+ import { Command as Command3 } from "commander";
52
+
53
+ // ../../apps/cli/src/commands/task-feed.ts
51
54
  import { Command as Command2 } from "commander";
52
- var taskCmd = new Command2("task").description("Manage tasks");
55
+ import * as grpc from "@grpc/grpc-js";
56
+ import * as protoLoader from "@grpc/proto-loader";
57
+
58
+ // ../../apps/cli/src/lib/proto-path.ts
59
+ import { existsSync } from "fs";
60
+ import path from "path";
61
+ import { fileURLToPath } from "url";
62
+ var __filename = fileURLToPath(import.meta.url);
63
+ var __dirname = path.dirname(__filename);
64
+ function findProtoPath() {
65
+ const candidates = [
66
+ // Bundled / installed package layout: dist/proto/tasks.proto
67
+ path.join(__dirname, "proto/tasks.proto"),
68
+ // Development layout from apps/cli/src/lib: ../../../../proto/tasks.proto
69
+ path.resolve(__dirname, "../../../../proto/tasks.proto")
70
+ ];
71
+ for (const candidate of candidates) {
72
+ if (existsSync(candidate)) return candidate;
73
+ }
74
+ return candidates[0] ?? "proto/tasks.proto";
75
+ }
76
+
77
+ // ../../apps/cli/src/commands/task-feed.ts
78
+ var GRPC_TARGET = process.env.HARNESS_GRPC_TARGET ?? "localhost:50051";
79
+ var packageDefinition = protoLoader.loadSync(findProtoPath(), {
80
+ keepCase: true,
81
+ longs: String,
82
+ enums: String,
83
+ defaults: true,
84
+ oneofs: true
85
+ });
86
+ var proto = grpc.loadPackageDefinition(packageDefinition);
87
+ var taskFeedCmd = new Command2("feed").description("Feed a task into the harness via gRPC").requiredOption("--project <id>", "project id").requiredOption("--title <title>", "task title").option("--description <text>", "task description").option("--complexity <level>", "simple | medium | complex", "simple").option("--tags <tags>", "comma-separated tags").option("--auto-run", "run the task immediately after submission", false).option("--stream", "stream task updates after submission", false).action(
88
+ (opts) => {
89
+ const client = new proto.omega.TaskIngestion(
90
+ GRPC_TARGET,
91
+ grpc.credentials.createInsecure()
92
+ );
93
+ const request = {
94
+ project_id: opts.project,
95
+ title: opts.title,
96
+ description: opts.description ?? "",
97
+ complexity: opts.complexity,
98
+ tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : [],
99
+ auto_run: opts.autoRun
100
+ };
101
+ if (opts.stream) {
102
+ const call = client.streamTasks();
103
+ call.on("data", (event) => {
104
+ console.log(JSON.stringify(event, null, 2));
105
+ });
106
+ call.on("error", (err) => {
107
+ console.error("Stream error:", err.message);
108
+ process.exit(1);
109
+ });
110
+ call.on("end", () => {
111
+ process.exit(0);
112
+ });
113
+ call.write({ project_id: opts.project });
114
+ }
115
+ client.submitTask(request, (err, response) => {
116
+ if (err) {
117
+ console.error("gRPC error:", err.message);
118
+ process.exit(1);
119
+ }
120
+ console.log(JSON.stringify(response, null, 2));
121
+ if (!opts.stream) {
122
+ client.close();
123
+ }
124
+ });
125
+ }
126
+ );
127
+
128
+ // ../../apps/cli/src/commands/task.ts
129
+ var taskCmd = new Command3("task").description("Manage tasks");
53
130
  taskCmd.command("create").description("Create a task").requiredOption("--project <id>", "project id").requiredOption("--title <title>", "task title").option("--description <text>", "task description").option("--complexity <level>", "simple | medium | complex", "simple").option("--tags <tags>", "comma-separated tags").action(async (opts) => {
54
131
  const task = await apiFetch("/tasks", {
55
132
  method: "POST",
@@ -73,13 +150,14 @@ taskCmd.command("run").description("Run a task through the router").argument("<i
73
150
  const result = await apiFetch(`/tasks/${id}/run`, { method: "POST" });
74
151
  console.log(JSON.stringify(result, null, 2));
75
152
  });
153
+ taskCmd.addCommand(taskFeedCmd);
76
154
 
77
155
  // ../../apps/cli/src/commands/ui.ts
78
- import { Command as Command3 } from "commander";
156
+ import { Command as Command4 } from "commander";
79
157
  import { spawn } from "child_process";
80
- import { existsSync } from "fs";
81
- import path from "path";
82
- import { fileURLToPath } from "url";
158
+ import { existsSync as existsSync2 } from "fs";
159
+ import path2 from "path";
160
+ import { fileURLToPath as fileURLToPath2 } from "url";
83
161
  import open from "open";
84
162
  import { render } from "ink";
85
163
  import React2 from "react";
@@ -338,12 +416,12 @@ function TuiApp() {
338
416
  }
339
417
 
340
418
  // ../../apps/cli/src/commands/ui.ts
341
- var __filename = fileURLToPath(import.meta.url);
342
- var __dirname = path.dirname(__filename);
419
+ var __filename2 = fileURLToPath2(import.meta.url);
420
+ var __dirname2 = path2.dirname(__filename2);
343
421
  var API2 = process.env.HARNESS_API_URL ?? "http://localhost:4000";
344
422
  function startBundledServer() {
345
- const serverPath = path.resolve(__dirname, "server.js");
346
- if (existsSync(serverPath)) {
423
+ const serverPath = path2.resolve(__dirname2, "server.js");
424
+ if (existsSync2(serverPath)) {
347
425
  return spawn(process.execPath, [serverPath], { stdio: "inherit" });
348
426
  }
349
427
  return void 0;
@@ -366,7 +444,7 @@ async function waitForApi(maxMs = 15e3) {
366
444
  }
367
445
  async function ensureProject() {
368
446
  const cwd = process.cwd();
369
- const name = path.basename(cwd);
447
+ const name = path2.basename(cwd);
370
448
  try {
371
449
  const listRes = await fetch(`${API2}/projects`);
372
450
  const projects = await listRes.json();
@@ -389,7 +467,7 @@ async function ensureProject() {
389
467
  console.warn("Could not auto-detect project:", err);
390
468
  }
391
469
  }
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) => {
470
+ var uiCmd = new Command4("ui").description("Open the harness web UI").option("--no-tui", "Do not open the terminal console alongside the web UI").action(async (options) => {
393
471
  let server = void 0;
394
472
  const alreadyRunning = await isApiReady();
395
473
  const useTui = options.tui;
@@ -427,10 +505,10 @@ var uiCmd = new Command3("ui").description("Open the harness web UI").option("--
427
505
  });
428
506
 
429
507
  // ../../apps/cli/src/commands/console.ts
430
- import { Command as Command4 } from "commander";
508
+ import { Command as Command5 } from "commander";
431
509
  import { render as render2 } from "ink";
432
510
  import React3 from "react";
433
- var consoleCmd = new Command4("console").description("Open the harness TUI console").action(() => {
511
+ var consoleCmd = new Command5("console").description("Open the harness TUI console").action(() => {
434
512
  if (!process.stdin.isTTY) {
435
513
  console.error("The harness console requires an interactive terminal (TTY).");
436
514
  process.exit(1);
@@ -439,9 +517,9 @@ var consoleCmd = new Command4("console").description("Open the harness TUI conso
439
517
  });
440
518
 
441
519
  // ../../apps/cli/src/commands/skill.ts
442
- import { Command as Command5 } from "commander";
443
- import path2 from "path";
444
- import { fileURLToPath as fileURLToPath2 } from "url";
520
+ import { Command as Command6 } from "commander";
521
+ import path3 from "path";
522
+ import { fileURLToPath as fileURLToPath3 } from "url";
445
523
 
446
524
  // ../skills/src/parse.ts
447
525
  import matter from "gray-matter";
@@ -542,22 +620,91 @@ async function registerSkill(sourcePath, outputDir) {
542
620
  // ../../apps/cli/src/commands/skill.ts
543
621
  function getOutputDir() {
544
622
  if (process.env.OMEGA_HARNESS_ROOT) {
545
- return path2.join(path2.resolve(process.env.OMEGA_HARNESS_ROOT), "packages/skills/src");
623
+ return path3.join(path3.resolve(process.env.OMEGA_HARNESS_ROOT), "packages/skills/src");
546
624
  }
547
625
  try {
548
- const __filename2 = fileURLToPath2(import.meta.url);
549
- const __dirname2 = path2.dirname(__filename2);
550
- return path2.join(path2.resolve(__dirname2, "../../../.."), "packages/skills/src");
626
+ const __filename3 = fileURLToPath3(import.meta.url);
627
+ const __dirname3 = path3.dirname(__filename3);
628
+ return path3.join(path3.resolve(__dirname3, "../../../.."), "packages/skills/src");
551
629
  } catch {
552
- return path2.join(process.cwd(), "harness-skills");
630
+ return path3.join(process.cwd(), "harness-skills");
553
631
  }
554
632
  }
555
- var skillCmd = new Command5("skill").description("Manage skill artifacts");
633
+ var skillCmd = new Command6("skill").description("Manage skill artifacts");
556
634
  skillCmd.command("generate").description("Generate a harness adapter from a SKILL.md file").argument("<source>", "path to SKILL.md").action(async (source) => {
557
635
  const outputDir = getOutputDir();
558
- const manifest = await registerSkill(path2.resolve(source), outputDir);
636
+ const manifest = await registerSkill(path3.resolve(source), outputDir);
559
637
  console.log(`Generated adapter for skill: ${manifest.name}`);
560
- console.log(`Output directory: ${path2.join(outputDir, "generated")}`);
638
+ console.log(`Output directory: ${path3.join(outputDir, "generated")}`);
639
+ });
640
+
641
+ // ../../apps/cli/src/commands/agent.ts
642
+ import { Command as Command7 } from "commander";
643
+ var agentCmd = new Command7("agent").description("Autonomous agent loop");
644
+ agentCmd.command("run").description("Run a single agent task").requiredOption("--project <id>", "project id").requiredOption("--title <title>", "task title").option("--description <text>", "task description").option("--complexity <level>", "simple | medium | complex", "complex").option("--auto-publish", "publish if validation passes", false).action(
645
+ async (opts) => {
646
+ const tags = ["agent"];
647
+ if (opts.autoPublish) tags.push("publish");
648
+ const task = await apiFetch("/tasks", {
649
+ method: "POST",
650
+ headers: { "Content-Type": "application/json" },
651
+ body: JSON.stringify({
652
+ projectId: opts.project,
653
+ title: opts.title,
654
+ description: opts.description,
655
+ complexity: opts.complexity,
656
+ tags
657
+ })
658
+ });
659
+ console.log(JSON.stringify(task, null, 2));
660
+ const result = await apiFetch(`/tasks/${task.id}/run`, { method: "POST" });
661
+ console.log(JSON.stringify(result, null, 2));
662
+ }
663
+ );
664
+ agentCmd.command("loop").description("Continuously run agent tasks").requiredOption("--project <id>", "project id").option("--interval <min>", "minutes between loops", "30").option("--auto-publish", "publish if validation passes", false).option("--prompt <text>", "overriding self-improvement prompt").action(
665
+ async (opts) => {
666
+ const intervalMin = parseInt(opts.interval, 10);
667
+ const intervalMs = intervalMin * 60 * 1e3;
668
+ const runOnce = async () => {
669
+ const title = opts.prompt ?? "Improve the Omega harness: fix a TODO, add a test, refactor a function, or improve documentation";
670
+ const tags = ["agent", "self-improve"];
671
+ if (opts.autoPublish) tags.push("publish");
672
+ const task = await apiFetch("/tasks", {
673
+ method: "POST",
674
+ headers: { "Content-Type": "application/json" },
675
+ body: JSON.stringify({
676
+ projectId: opts.project,
677
+ title,
678
+ description: "Use the available tools to make a small, verifiable improvement to the codebase. Run lint and tests. Finish with a summary.",
679
+ complexity: "complex",
680
+ tags
681
+ })
682
+ });
683
+ console.log(`Created task ${task.id}`);
684
+ const result = await apiFetch(`/tasks/${task.id}/run`, { method: "POST" });
685
+ console.log(JSON.stringify(result, null, 2));
686
+ return result;
687
+ };
688
+ await runOnce();
689
+ if (intervalMs > 0) {
690
+ console.log(`Looping every ${opts.interval} minute(s). Press Ctrl+C to stop.`);
691
+ setInterval(() => {
692
+ void runOnce();
693
+ }, intervalMs);
694
+ }
695
+ }
696
+ );
697
+ agentCmd.command("steps").description("Show steps for a task").argument("<id>", "task id").action(async (id) => {
698
+ const steps = await apiFetch(`/tasks/${id}/steps`);
699
+ console.log(JSON.stringify(steps, null, 2));
700
+ });
701
+ agentCmd.command("traces").description("Show traces for a task").argument("<id>", "task id").action(async (id) => {
702
+ const traces = await apiFetch(`/tasks/${id}/traces`);
703
+ console.log(JSON.stringify(traces, null, 2));
704
+ });
705
+ agentCmd.command("diffs").description("Show diffs for a task").argument("<id>", "task id").action(async (id) => {
706
+ const diffs = await apiFetch(`/tasks/${id}/diffs`);
707
+ console.log(JSON.stringify(diffs, null, 2));
561
708
  });
562
709
 
563
710
  // ../../apps/cli/src/index.ts
@@ -567,4 +714,5 @@ program2.addCommand(taskCmd);
567
714
  program2.addCommand(uiCmd);
568
715
  program2.addCommand(consoleCmd);
569
716
  program2.addCommand(skillCmd);
717
+ program2.addCommand(agentCmd);
570
718
  program2.parse();
@@ -0,0 +1,37 @@
1
+ syntax = "proto3";
2
+
3
+ package omega;
4
+
5
+ service TaskIngestion {
6
+ rpc SubmitTask (TaskRequest) returns (TaskResponse);
7
+ rpc StreamTasks (TaskStreamRequest) returns (stream TaskEvent);
8
+ }
9
+
10
+ message TaskRequest {
11
+ string project_id = 1;
12
+ string title = 2;
13
+ string description = 3;
14
+ string complexity = 4;
15
+ repeated string tags = 5;
16
+ bool auto_run = 6;
17
+ }
18
+
19
+ message TaskResponse {
20
+ string id = 1;
21
+ string status = 2;
22
+ string error = 3;
23
+ }
24
+
25
+ message TaskStreamRequest {
26
+ string project_id = 1;
27
+ }
28
+
29
+ message TaskEvent {
30
+ string id = 1;
31
+ string title = 2;
32
+ string status = 3;
33
+ string provider = 4;
34
+ string model = 5;
35
+ string result = 6;
36
+ string error = 7;
37
+ }
package/dist/server.js CHANGED
@@ -3,9 +3,10 @@
3
3
  // src/server.ts
4
4
  import express from "express";
5
5
  import cors from "cors";
6
- import path from "path";
7
6
  import { fileURLToPath } from "url";
8
7
  import { z } from "zod";
8
+ import * as grpc from "@grpc/grpc-js";
9
+ import * as protoLoader from "@grpc/proto-loader";
9
10
 
10
11
  // ../providers/src/openai.ts
11
12
  var DEFAULT_BASE_URL = "https://api.openai.com/v1";
@@ -47,6 +48,48 @@ var OpenAIProvider = class {
47
48
  const data = await res.json();
48
49
  return data.choices?.[0]?.message?.content ?? "";
49
50
  }
51
+ async sendWithTools(prompt, tools, opts) {
52
+ const res = await fetch(`${this.baseUrl}/chat/completions`, {
53
+ method: "POST",
54
+ headers: {
55
+ "Content-Type": "application/json",
56
+ ...this.authHeaders()
57
+ },
58
+ body: JSON.stringify({
59
+ model: opts?.model ?? this.config.defaultModel,
60
+ messages: [
61
+ ...opts?.system ? [{ role: "system", content: opts.system }] : [],
62
+ { role: "user", content: prompt }
63
+ ],
64
+ tools: tools.map((t) => ({
65
+ type: "function",
66
+ function: { name: t.name, description: t.description, parameters: t.parameters }
67
+ })),
68
+ temperature: opts?.temperature
69
+ })
70
+ });
71
+ if (!res.ok) {
72
+ throw new Error(`OpenAI tools request failed: ${res.status.toString()} ${res.statusText}`);
73
+ }
74
+ const data = await res.json();
75
+ const message = data.choices?.[0]?.message;
76
+ const toolCalls = message?.tool_calls;
77
+ if (toolCalls && toolCalls.length > 0) {
78
+ const normalized = toolCalls.map((tc) => ({
79
+ id: tc.id ?? "",
80
+ name: tc.function?.name ?? "",
81
+ arguments: (() => {
82
+ try {
83
+ return JSON.parse(tc.function?.arguments ?? "{}");
84
+ } catch {
85
+ return {};
86
+ }
87
+ })()
88
+ })).filter((tc) => tc.id && tc.name);
89
+ return JSON.stringify({ tool_calls: normalized });
90
+ }
91
+ return message?.content ?? "";
92
+ }
50
93
  authHeaders() {
51
94
  return this.config.apiKey ? { Authorization: `Bearer ${this.config.apiKey}` } : {};
52
95
  }
@@ -187,7 +230,7 @@ var KimiProvider = class extends OpenAIProvider {
187
230
  constructor(config) {
188
231
  super({
189
232
  ...config,
190
- baseUrl: config.baseUrl ?? "https://api.moonshot.ai/v1"
233
+ baseUrl: config.baseUrl ?? "https://api.moonshot.cn/v1"
191
234
  });
192
235
  this.config = config;
193
236
  }
@@ -276,6 +319,11 @@ function selectProvider(configs, rules2, task) {
276
319
  }
277
320
 
278
321
  // src/server.ts
322
+ import fs from "node:fs/promises";
323
+ import path from "node:path";
324
+ import { execFile } from "node:child_process";
325
+ import { promisify } from "node:util";
326
+ var execFileAsync = promisify(execFile);
279
327
  var __filename = fileURLToPath(import.meta.url);
280
328
  var __dirname = path.dirname(__filename);
281
329
  function asyncHandler(fn) {
@@ -472,3 +520,256 @@ var PORT = Number(process.env.PORT ?? 4e3);
472
520
  app.listen(PORT, () => {
473
521
  console.log(`Omega harness server on http://localhost:${PORT.toString()}`);
474
522
  });
523
+ var GRPC_PORT = Number(process.env.GRPC_PORT ?? 50051);
524
+ var GRPC_PROTO_PATH = path.join(__dirname, "proto/tasks.proto");
525
+ var grpcPackageDef = protoLoader.loadSync(GRPC_PROTO_PATH, {
526
+ keepCase: true,
527
+ longs: String,
528
+ enums: String,
529
+ defaults: true,
530
+ oneofs: true
531
+ });
532
+ var grpcProto = grpc.loadPackageDefinition(grpcPackageDef);
533
+ function parseGrpcRequest(req) {
534
+ const r = req;
535
+ return {
536
+ project_id: typeof r.project_id === "string" ? r.project_id : "",
537
+ title: typeof r.title === "string" ? r.title : "",
538
+ description: typeof r.description === "string" ? r.description : void 0,
539
+ complexity: typeof r.complexity === "string" ? r.complexity : void 0,
540
+ tags: Array.isArray(r.tags) ? r.tags.filter((t) => typeof t === "string") : void 0,
541
+ auto_run: typeof r.auto_run === "boolean" ? r.auto_run : void 0
542
+ };
543
+ }
544
+ function isValidComplexity(value) {
545
+ return ["simple", "medium", "complex"].includes(value);
546
+ }
547
+ var AGENT_TOOLS = [
548
+ {
549
+ name: "read_file",
550
+ description: "Read a file relative to project root.",
551
+ parameters: { type: "object", properties: { path: { type: "string" } }, required: ["path"] }
552
+ },
553
+ {
554
+ name: "write_file",
555
+ description: "Write content to a file relative to project root.",
556
+ parameters: {
557
+ type: "object",
558
+ properties: { path: { type: "string" }, content: { type: "string" } },
559
+ required: ["path", "content"]
560
+ }
561
+ },
562
+ {
563
+ name: "run_command",
564
+ description: "Run a shell command in the project root.",
565
+ parameters: { type: "object", properties: { command: { type: "string" } }, required: ["command"] }
566
+ },
567
+ {
568
+ name: "finish",
569
+ description: "Mark the task complete.",
570
+ parameters: {
571
+ type: "object",
572
+ properties: { summary: { type: "string" }, success: { type: "boolean" } },
573
+ required: ["summary", "success"]
574
+ }
575
+ }
576
+ ];
577
+ var AGENT_SYSTEM_PROMPT = `You are Omega, an autonomous software engineering agent. Respond with JSON: {"tool_calls":[{"id":"1","name":"tool_name","arguments":{}}]}. Available tools: read_file, write_file, run_command, finish. Work in small steps, run tests, and finish when done.`;
578
+ function argString(value) {
579
+ if (value === void 0 || value === null) return "";
580
+ if (typeof value === "string") return value;
581
+ if (typeof value === "number" || typeof value === "boolean") return value.toString();
582
+ return JSON.stringify(value);
583
+ }
584
+ async function executeBundleTool(projectPath, name, args) {
585
+ const target = (file) => {
586
+ const resolved = path.resolve(projectPath, file);
587
+ if (!resolved.startsWith(path.resolve(projectPath))) throw new Error("Path traversal blocked");
588
+ return resolved;
589
+ };
590
+ try {
591
+ switch (name) {
592
+ case "read_file": {
593
+ const content = await fs.readFile(target(argString(args.path)), "utf-8");
594
+ return { success: true, output: content };
595
+ }
596
+ case "write_file": {
597
+ const p = target(argString(args.path));
598
+ await fs.mkdir(path.dirname(p), { recursive: true });
599
+ await fs.writeFile(p, argString(args.content), "utf-8");
600
+ return { success: true, output: `Wrote ${argString(args.path)}` };
601
+ }
602
+ case "run_command": {
603
+ const [cmd, ...cmdArgs] = argString(args.command).split(" ");
604
+ const { stdout, stderr } = await execFileAsync(cmd, cmdArgs, {
605
+ cwd: projectPath,
606
+ timeout: 12e4,
607
+ shell: false
608
+ });
609
+ return { success: true, output: stdout + stderr };
610
+ }
611
+ default:
612
+ return { success: false, output: `Unknown tool: ${name}` };
613
+ }
614
+ } catch (err) {
615
+ return { success: false, output: err instanceof Error ? err.message : String(err) };
616
+ }
617
+ }
618
+ function parseBundleToolCalls(raw) {
619
+ try {
620
+ const cleaned = raw.trim().replace(/^```[a-z]*\n?/, "").replace(/\n?```$/, "");
621
+ const parsed = JSON.parse(cleaned);
622
+ if (!Array.isArray(parsed.tool_calls)) return [];
623
+ const calls = parsed.tool_calls;
624
+ return calls.filter((t) => Boolean(t.id && t.name)).map((t) => ({ id: t.id, name: t.name, arguments: t.arguments }));
625
+ } catch {
626
+ return [];
627
+ }
628
+ }
629
+ async function executeAgentTask(task) {
630
+ task.status = "in_progress";
631
+ task.error = void 0;
632
+ task.result = void 0;
633
+ const project = projects.find((p) => p.id === task.projectId);
634
+ if (!project) {
635
+ task.status = "failed";
636
+ task.error = "Project not found for agent task";
637
+ return;
638
+ }
639
+ const selection = selectProvider(providerConfigs, rules, task);
640
+ if (!selection) {
641
+ task.status = "failed";
642
+ task.error = "No provider available for this task";
643
+ return;
644
+ }
645
+ try {
646
+ const provider = createProvider(selection.provider);
647
+ const prompt = [task.title, task.description].filter(Boolean).join("\n\n");
648
+ let response;
649
+ if ("sendWithTools" in provider && typeof provider.sendWithTools === "function") {
650
+ response = await provider.sendWithTools(prompt, AGENT_TOOLS, {
651
+ system: AGENT_SYSTEM_PROMPT,
652
+ model: selection.model
653
+ });
654
+ } else {
655
+ response = await provider.send(prompt, { system: AGENT_SYSTEM_PROMPT, model: selection.model });
656
+ }
657
+ const calls = parseBundleToolCalls(response);
658
+ const results = [];
659
+ for (const call of calls) {
660
+ if (call.name === "finish") {
661
+ task.status = call.arguments.success ? "done" : "failed";
662
+ const summaryArg = call.arguments.summary;
663
+ task.result = typeof summaryArg === "string" ? summaryArg : "";
664
+ break;
665
+ }
666
+ const result = await executeBundleTool(project.path, call.name, call.arguments);
667
+ results.push(`${call.name}: ${result.output}`);
668
+ }
669
+ if (calls.length > 0 && calls.every((c) => c.name !== "finish")) {
670
+ task.status = "done";
671
+ task.result = results.join("\n");
672
+ }
673
+ task.assignedModel = { provider: selection.provider.name, model: selection.model };
674
+ } catch (err) {
675
+ task.status = "failed";
676
+ task.error = err instanceof Error ? err.message : String(err);
677
+ }
678
+ }
679
+ async function executeTask(task) {
680
+ task.status = "in_progress";
681
+ task.error = void 0;
682
+ task.result = void 0;
683
+ if (task.tags.includes("agent") || task.tags.includes("self-improve")) {
684
+ return executeAgentTask(task);
685
+ }
686
+ const selection = selectProvider(providerConfigs, rules, task);
687
+ if (!selection) {
688
+ task.status = "failed";
689
+ task.error = "No provider available for this task";
690
+ return;
691
+ }
692
+ try {
693
+ const provider = createProvider(selection.provider);
694
+ const prompt = [task.title, task.description].filter(Boolean).join("\n\n");
695
+ const result = await provider.send(prompt, { model: selection.model });
696
+ task.status = "done";
697
+ task.result = result;
698
+ task.assignedModel = { provider: selection.provider.name, model: selection.model };
699
+ } catch (err) {
700
+ task.status = "failed";
701
+ task.error = err instanceof Error ? err.message : String(err);
702
+ }
703
+ }
704
+ var grpcServer = new grpc.Server();
705
+ grpcServer.addService(grpcProto.omega.TaskIngestion.service, {
706
+ submitTask: (call, callback) => {
707
+ void (async () => {
708
+ try {
709
+ const req = parseGrpcRequest(call.request);
710
+ if (!req.project_id || !req.title) {
711
+ callback(
712
+ { code: grpc.status.INVALID_ARGUMENT, message: "project_id and title are required" },
713
+ null
714
+ );
715
+ return;
716
+ }
717
+ const complexity = isValidComplexity(req.complexity ?? "") ? req.complexity : "simple";
718
+ const task = {
719
+ id: newId(),
720
+ projectId: req.project_id,
721
+ title: req.title,
722
+ description: req.description ?? void 0,
723
+ status: "todo",
724
+ complexity,
725
+ tags: req.tags ?? [],
726
+ createdAt: now(),
727
+ updatedAt: now()
728
+ };
729
+ tasks.push(task);
730
+ if (req.auto_run) {
731
+ await executeTask(task);
732
+ }
733
+ callback(null, { id: task.id, status: task.status, error: task.error ?? "" });
734
+ } catch (err) {
735
+ const message = err instanceof Error ? err.message : String(err);
736
+ callback({ code: grpc.status.INTERNAL, message }, null);
737
+ }
738
+ })();
739
+ },
740
+ streamTasks: (call) => {
741
+ const req = call.request;
742
+ const projectId = req.project_id;
743
+ let cancelled = false;
744
+ const interval = setInterval(() => {
745
+ if (cancelled) return;
746
+ const filtered = projectId ? tasks.filter((t) => t.projectId === projectId) : tasks;
747
+ for (const task of filtered.slice(-20)) {
748
+ call.write({
749
+ id: task.id,
750
+ title: task.title,
751
+ status: task.status,
752
+ provider: task.assignedModel?.provider ?? "",
753
+ model: task.assignedModel?.model ?? "",
754
+ result: task.result ?? "",
755
+ error: task.error ?? ""
756
+ });
757
+ }
758
+ }, 1e3);
759
+ call.on("cancelled", () => {
760
+ cancelled = true;
761
+ clearInterval(interval);
762
+ });
763
+ call.on("error", () => {
764
+ cancelled = true;
765
+ clearInterval(interval);
766
+ });
767
+ }
768
+ });
769
+ grpcServer.bindAsync(`0.0.0.0:${GRPC_PORT.toString()}`, grpc.ServerCredentials.createInsecure(), (err) => {
770
+ if (err) {
771
+ console.error("gRPC server failed to start:", err);
772
+ return;
773
+ }
774
+ console.log(`gRPC task ingestion on 0.0.0.0:${GRPC_PORT.toString()}`);
775
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@castlemilk/omega",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Omega Harness CLI - installable via npx",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",
@@ -12,6 +12,8 @@
12
12
  "dist"
13
13
  ],
14
14
  "dependencies": {
15
+ "@grpc/grpc-js": "^1.14.4",
16
+ "@grpc/proto-loader": "^0.8.1",
15
17
  "commander": "^12.1.0",
16
18
  "cors": "^2.8.5",
17
19
  "express": "^4.19.2",
@@ -28,8 +30,8 @@
28
30
  "@types/node": "^20.14.0",
29
31
  "esbuild": "^0.23.0",
30
32
  "@omega/core": "0.1.0",
31
- "@omega/providers": "0.1.0",
32
- "@omega/router": "0.1.0"
33
+ "@omega/router": "0.1.0",
34
+ "@omega/providers": "0.1.0"
33
35
  },
34
36
  "publishConfig": {
35
37
  "access": "public"
@@ -40,9 +42,10 @@
40
42
  "url": "git+https://github.com/castlemilk/harness.git"
41
43
  },
42
44
  "scripts": {
43
- "build:cli": "esbuild ../../apps/cli/src/index.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --external:commander --external:open --external:gray-matter --external:yaml --external:ink --external:react --alias:@omega/core=../../packages/core/src/index.ts --alias:@omega/skills=../../packages/skills/src/index.ts",
44
- "build:server": "esbuild src/server.ts --bundle --platform=node --format=esm --outfile=dist/server.js --external:express --external:cors --external:zod --alias:@omega/core=../../packages/core/src/index.ts --alias:@omega/providers=../../packages/providers/src/index.ts --alias:@omega/router=../../packages/router/src/index.ts",
45
+ "build:cli": "esbuild ../../apps/cli/src/index.ts --bundle --platform=node --format=esm --outfile=dist/cli.js --external:commander --external:open --external:gray-matter --external:yaml --external:ink --external:react --external:@grpc/grpc-js --external:@grpc/proto-loader --alias:@omega/core=../../packages/core/src/index.ts --alias:@omega/skills=../../packages/skills/src/index.ts",
46
+ "build:server": "esbuild src/server.ts --bundle --platform=node --format=esm --outfile=dist/server.js --external:express --external:cors --external:zod --external:@grpc/grpc-js --external:@grpc/proto-loader --alias:@omega/core=../../packages/core/src/index.ts --alias:@omega/providers=../../packages/providers/src/index.ts --alias:@omega/router=../../packages/router/src/index.ts",
45
47
  "copy:web": "cp -R ../../apps/web/dist dist/web",
46
- "build": "pnpm build:cli && pnpm build:server && pnpm copy:web"
48
+ "copy:proto": "mkdir -p dist/proto && cp ../../proto/tasks.proto dist/proto/tasks.proto",
49
+ "build": "pnpm build:cli && pnpm build:server && pnpm copy:web && pnpm copy:proto"
47
50
  }
48
51
  }