@castlemilk/omega 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +70 -0
  2. package/dist/server.js +183 -2
  3. package/package.json +14 -13
package/dist/cli.js CHANGED
@@ -638,6 +638,75 @@ skillCmd.command("generate").description("Generate a harness adapter from a SKIL
638
638
  console.log(`Output directory: ${path3.join(outputDir, "generated")}`);
639
639
  });
640
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));
708
+ });
709
+
641
710
  // ../../apps/cli/src/index.ts
642
711
  program2.name("harness").description("Omega harness CLI").version("0.1.0").option("--api <url>", "API base URL", "http://localhost:4000");
643
712
  program2.addCommand(projectCmd);
@@ -645,4 +714,5 @@ program2.addCommand(taskCmd);
645
714
  program2.addCommand(uiCmd);
646
715
  program2.addCommand(consoleCmd);
647
716
  program2.addCommand(skillCmd);
717
+ program2.addCommand(agentCmd);
648
718
  program2.parse();
package/dist/server.js CHANGED
@@ -3,7 +3,6 @@
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";
9
8
  import * as grpc from "@grpc/grpc-js";
@@ -49,6 +48,48 @@ var OpenAIProvider = class {
49
48
  const data = await res.json();
50
49
  return data.choices?.[0]?.message?.content ?? "";
51
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
+ }
52
93
  authHeaders() {
53
94
  return this.config.apiKey ? { Authorization: `Bearer ${this.config.apiKey}` } : {};
54
95
  }
@@ -189,7 +230,7 @@ var KimiProvider = class extends OpenAIProvider {
189
230
  constructor(config) {
190
231
  super({
191
232
  ...config,
192
- baseUrl: config.baseUrl ?? "https://api.moonshot.ai/v1"
233
+ baseUrl: config.baseUrl ?? "https://api.kimi.com/coding/v1"
193
234
  });
194
235
  this.config = config;
195
236
  }
@@ -278,6 +319,11 @@ function selectProvider(configs, rules2, task) {
278
319
  }
279
320
 
280
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);
281
327
  var __filename = fileURLToPath(import.meta.url);
282
328
  var __dirname = path.dirname(__filename);
283
329
  function asyncHandler(fn) {
@@ -498,10 +544,145 @@ function parseGrpcRequest(req) {
498
544
  function isValidComplexity(value) {
499
545
  return ["simple", "medium", "complex"].includes(value);
500
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
+ }
501
679
  async function executeTask(task) {
502
680
  task.status = "in_progress";
503
681
  task.error = void 0;
504
682
  task.result = void 0;
683
+ if (task.tags.includes("agent") || task.tags.includes("self-improve")) {
684
+ return executeAgentTask(task);
685
+ }
505
686
  const selection = selectProvider(providerConfigs, rules, task);
506
687
  if (!selection) {
507
688
  task.status = "failed";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@castlemilk/omega",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Omega Harness CLI - installable via npx",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",
@@ -11,6 +11,14 @@
11
11
  "files": [
12
12
  "dist"
13
13
  ],
14
+ "scripts": {
15
+ "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",
16
+ "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",
17
+ "copy:web": "cp -R ../../apps/web/dist dist/web",
18
+ "copy:proto": "mkdir -p dist/proto && cp ../../proto/tasks.proto dist/proto/tasks.proto",
19
+ "build": "pnpm build:cli && pnpm build:server && pnpm copy:web && pnpm copy:proto",
20
+ "prepublishOnly": "pnpm build"
21
+ },
14
22
  "dependencies": {
15
23
  "@grpc/grpc-js": "^1.14.4",
16
24
  "@grpc/proto-loader": "^0.8.1",
@@ -25,13 +33,13 @@
25
33
  "zod": "^3.23.8"
26
34
  },
27
35
  "devDependencies": {
36
+ "@omega/core": "workspace:*",
37
+ "@omega/providers": "workspace:*",
38
+ "@omega/router": "workspace:*",
28
39
  "@types/cors": "^2.8.17",
29
40
  "@types/express": "^4.17.21",
30
41
  "@types/node": "^20.14.0",
31
- "esbuild": "^0.23.0",
32
- "@omega/core": "0.1.0",
33
- "@omega/providers": "0.1.0",
34
- "@omega/router": "0.1.0"
42
+ "esbuild": "^0.23.0"
35
43
  },
36
44
  "publishConfig": {
37
45
  "access": "public"
@@ -40,12 +48,5 @@
40
48
  "repository": {
41
49
  "type": "git",
42
50
  "url": "git+https://github.com/castlemilk/harness.git"
43
- },
44
- "scripts": {
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",
47
- "copy:web": "cp -R ../../apps/web/dist dist/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"
50
51
  }
51
- }
52
+ }