@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 +174 -26
- package/dist/proto/tasks.proto +37 -0
- package/dist/server.js +303 -2
- package/package.json +9 -6
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(
|
|
16
|
-
const url = `${getApiUrl()}${
|
|
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
|
-
|
|
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
|
|
156
|
+
import { Command as Command4 } from "commander";
|
|
79
157
|
import { spawn } from "child_process";
|
|
80
|
-
import { existsSync } from "fs";
|
|
81
|
-
import
|
|
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
|
|
342
|
-
var
|
|
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 =
|
|
346
|
-
if (
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
443
|
-
import
|
|
444
|
-
import { fileURLToPath as
|
|
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
|
|
623
|
+
return path3.join(path3.resolve(process.env.OMEGA_HARNESS_ROOT), "packages/skills/src");
|
|
546
624
|
}
|
|
547
625
|
try {
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
return
|
|
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
|
|
630
|
+
return path3.join(process.cwd(), "harness-skills");
|
|
553
631
|
}
|
|
554
632
|
}
|
|
555
|
-
var skillCmd = new
|
|
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(
|
|
636
|
+
const manifest = await registerSkill(path3.resolve(source), outputDir);
|
|
559
637
|
console.log(`Generated adapter for skill: ${manifest.name}`);
|
|
560
|
-
console.log(`Output directory: ${
|
|
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.
|
|
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.
|
|
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/
|
|
32
|
-
"@omega/
|
|
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
|
-
"
|
|
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
|
}
|